diff --git a/CRM/Contact/Import/Form/MapField.php b/CRM/Contact/Import/Form/MapField.php index 39e4ca1dce8d..e79fd196b20d 100644 --- a/CRM/Contact/Import/Form/MapField.php +++ b/CRM/Contact/Import/Form/MapField.php @@ -101,17 +101,8 @@ public function preProcess() { // retrieve and highlight required custom fields $formattedFieldNames = $this->formatCustomFieldName($this->_mapperFields); - $this->assign('highlightedFields', $this->getHighlightedFields()); $this->_formattedFieldNames[$contactType] = $this->_mapperFields = array_merge($this->_mapperFields, $formattedFieldNames); - - $columnNames = $this->getColumnHeaders(); - - $this->_columnCount = $this->getNumberOfColumns(); - $this->_columnNames = $columnNames; - $this->assign('columnNames', $this->getColumnHeaders()); - $this->assign('columnCount', $this->_columnCount); - $this->_dataValues = array_values($this->getDataRows([], 2)); - $this->assign('dataValues', $this->_dataValues); + $this->assignMapFieldVariables(); } /** @@ -512,7 +503,7 @@ private function isSkipDuplicates(): bool { * * @throws \CRM_Core_Exception */ - private function getHighlightedFields(): array { + protected function getHighlightedFields(): array { $entityFields = [ 'Individual' => ['first_name', 'last_name'], 'Organization' => ['organization_name'], diff --git a/CRM/Contact/Import/Form/Preview.php b/CRM/Contact/Import/Form/Preview.php index 3bf15b4ea251..37ad7552ec20 100644 --- a/CRM/Contact/Import/Form/Preview.php +++ b/CRM/Contact/Import/Form/Preview.php @@ -15,6 +15,9 @@ * @copyright CiviCRM LLC https://civicrm.org/licensing */ +use Civi\Api4\Group; +use Civi\Api4\Tag; + /** * This class previews the uploaded file and returns summary statistics. */ @@ -34,38 +37,8 @@ class CRM_Contact_Import_Form_Preview extends CRM_Import_Form_Preview { * @throws \CRM_Core_Exception */ public function preProcess() { - $columnNames = $this->getColumnHeaders(); + parent::preProcess(); $this->_disableUSPS = $this->getSubmittedValue('disableUSPS'); - - //assign column names - $this->assign('columnNames', $columnNames); - - //get the mapping name displayed if the mappingId is set - $mappingId = $this->get('loadMappingId'); - if ($mappingId) { - $mapDAO = new CRM_Core_DAO_Mapping(); - $mapDAO->id = $mappingId; - $mapDAO->find(TRUE); - } - $this->assign('savedMappingName', $mappingId ? $mapDAO->name : NULL); - - $this->assign('rowDisplayCount', 2); - - $groups = CRM_Core_PseudoConstant::nestedGroup(); - $this->set('groups', $groups); - - $tag = CRM_Core_PseudoConstant::get('CRM_Core_DAO_EntityTag', 'tag_id', array('onlyActive' => FALSE)); - if ($tag) { - $this->set('tag', $tag); - } - - $this->assign('downloadErrorRecordsUrl', $this->getDownloadURL(CRM_Import_Parser::ERROR)); - $this->assign('invalidRowCount', $this->getRowCount(CRM_Import_Parser::ERROR)); - $this->assign('validRowCount', $this->getRowCount(CRM_Import_Parser::VALID)); - $this->assign('totalRowCount', $this->getRowCount([])); - $this->assign('mapper', $this->getMappedFieldLabels()); - $this->assign('dataValues', $this->getDataRows([], 2)); - $this->setStatusUrl(); } @@ -84,24 +57,25 @@ public function buildQuickForm() { ); } - $groups = $this->get('groups'); + $groups = CRM_Core_PseudoConstant::nestedGroup();; if (!empty($groups)) { - $this->addElement('select', 'groups', ts('Add imported records to existing group(s)'), $groups, array( + $this->addElement('select', 'groups', ts('Add imported records to existing group(s)'), $groups, [ 'multiple' => "multiple", 'class' => 'crm-select2', - )); + ]); } //display new tag $this->addElement('text', 'newTagName', ts('Tag'), CRM_Core_DAO::getAttribute('CRM_Core_DAO_Tag', 'name')); $this->addElement('text', 'newTagDesc', ts('Description'), CRM_Core_DAO::getAttribute('CRM_Core_DAO_Tag', 'description')); - $tag = $this->get('tag'); + $tag = CRM_Core_PseudoConstant::get('CRM_Core_DAO_EntityTag', 'tag_id', ['onlyActive' => FALSE]); if (!empty($tag)) { - foreach ($tag as $tagID => $tagName) { - $this->addElement('checkbox', "tag[$tagID]", NULL, $tagName); - } + $this->addElement('select', 'tag', ts(' Tag imported records'), $tag, [ + 'multiple' => 'multiple', + 'class' => 'crm-select2', + ]); } $this->addFormRule(array('CRM_Contact_Import_Form_Preview', 'formRule'), $this); @@ -163,37 +137,71 @@ public static function formRule($fields, $files, $self) { /** * Process the mapped fields and map it into the uploaded file. * - * @throws \API_Exception + * @throws \API_Exception|\CRM_Core_Exception */ - public function postProcess() { - - $importJobParams = array( - 'doGeocodeAddress' => $this->getSubmittedValue('doGeocodeAddress'), - 'invalidRowCount' => $this->getRowCount(CRM_Import_Parser::ERROR), - 'onDuplicate' => $this->getSubmittedValue('onDuplicate'), - 'dedupe' => $this->getSubmittedValue('dedupe_rule_id'), - 'newGroupName' => $this->controller->exportValue($this->_name, 'newGroupName'), - 'newGroupDesc' => $this->controller->exportValue($this->_name, 'newGroupDesc'), - 'newGroupType' => $this->controller->exportValue($this->_name, 'newGroupType'), - 'groups' => $this->controller->exportValue($this->_name, 'groups'), - 'allGroups' => $this->get('groups'), - 'newTagName' => $this->controller->exportValue($this->_name, 'newTagName'), - 'newTagDesc' => $this->controller->exportValue($this->_name, 'newTagDesc'), - 'tag' => $this->controller->exportValue($this->_name, 'tag'), - 'allTags' => $this->get('tag'), - 'mapper' => $this->controller->exportValue('MapField', 'mapper'), - 'mapFields' => $this->getAvailableFields(), - 'contactType' => $this->getContactType(), - 'contactSubType' => $this->getSubmittedValue('contactSubType'), - 'primaryKeyName' => '_id', - 'statusFieldName' => '_status', - 'statusID' => $this->get('statusID'), - 'totalRowCount' => $this->getRowCount([]), - 'userJobID' => $this->getUserJobID(), - ); + public function postProcess(): void { + $groupsToAddTo = (array) $this->getSubmittedValue('groups'); + $summaryInfo = ['groups' => [], 'tags' => []]; + foreach ($groupsToAddTo as $groupID) { + // This is a convenience for now - really url & name should be determined at + // presentation stage - ie the summary screen. The only info we are really + // preserving is which groups were created vs already existed. + $summaryInfo['groups'][$groupID] = [ + 'url' => CRM_Utils_System::url('civicrm/group/search', 'reset=1&force=1&context=smog&gid=' . $groupID), + 'name' => Group::get(FALSE)->addWhere('id', '=', $groupID)->addSelect('name')->execute()->first()['name'], + 'new' => FALSE, + 'added' => 0, + 'notAdded' => 0, + ]; + } - $importJob = new CRM_Contact_Import_ImportJob(); - $importJob->setJobParams($importJobParams); + if ($this->getSubmittedValue('newGroupName')) { + /* Create a new group */ + $groupsToAddTo[] = $groupID = Group::create(FALSE)->setValues([ + 'title' => $this->getSubmittedValue('newGroupName'), + 'description' => $this->getSubmittedValue('newGroupDesc'), + 'group_type' => $this->getSubmittedValue('newGroupType') ?? [], + 'is_active' => TRUE, + ])->execute()->first()['id']; + $summaryInfo['groups'][$groupID] = [ + 'url' => CRM_Utils_System::url('civicrm/group/search', 'reset=1&force=1&context=smog&gid=' . $groupID), + 'name' => $this->getSubmittedValue('newGroupName'), + 'new' => TRUE, + 'added' => 0, + 'notAdded' => 0, + ]; + } + $tagsToAdd = (array) $this->getSubmittedValue('tag'); + foreach ($tagsToAdd as $tagID) { + // This is a convenience for now - really url & name should be determined at + // presentation stage - ie the summary screen. The only info we are really + // preserving is which tags were created vs already existed. + $summaryInfo['tags'][$tagID] = [ + 'url' => CRM_Utils_System::url('civicrm/contact/search', 'reset=1&force=1&context=smog&id=' . $tagID), + 'name' => Tag::get(FALSE)->addWhere('id', '=', $tagID)->addSelect('name')->execute()->first()['name'], + 'new' => TRUE, + 'added' => 0, + 'notAdded' => 0, + ]; + } + if ($this->getSubmittedValue('newTagName')) { + $tagsToAdd[] = $tagID = Tag::create(FALSE)->setValues([ + 'name' => $this->getSubmittedValue('newTagName'), + 'description' => $this->getSubmittedValue('newTagDesc'), + 'is_selectable' => TRUE, + 'used_for' => 'civicrm_contact', + ])->execute()->first()['id']; + $summaryInfo['tags'][$tagID] = [ + 'url' => CRM_Utils_System::url('civicrm/contact/search', 'reset=1&force=1&context=smog&id=' . $tagID), + 'name' => $this->getSubmittedValue('newTagName'), + 'new' => FALSE, + 'added' => 0, + 'notAdded' => 0, + ]; + } + // Store the actions to take on each row & the data to present at the end to the userJob. + $this->updateUserJobMetadata('post_actions', ['group' => $groupsToAddTo, 'tag' => $tagsToAdd]); + $this->updateUserJobMetadata('summary_info', $summaryInfo); // If ACL applies to the current user, update cache before running the import. if (!CRM_Core_Permission::check('view all contacts')) { @@ -205,16 +213,20 @@ public function postProcess() { CRM_Utils_Address_USPS::disable($this->_disableUSPS); // run the import - $importJob->runImport($this); + + $this->_parser = $this->getParser(); + $this->_parser->run( + [], + CRM_Import_Parser::MODE_IMPORT, + $this->get('statusID') + ); // Clear all caches, forcing any searches to recheck the ACLs or group membership as the import // may have changed it. CRM_Contact_BAO_Contact_Utils::clearContactCaches(TRUE); - // add all the necessary variables to the form - $importJob->setFormVariables($this); - // check if there is any error occurred + // @todo - it's really unclear that this error code should still exist... $errorStack = CRM_Core_Error::singleton(); $errors = $errorStack->getErrors(); $errorMessage = []; @@ -235,10 +247,6 @@ public function postProcess() { $this->set('errorFile', $errorFile); } - - //hack to clean db - //if job complete drop table. - $importJob->isComplete(); } /** diff --git a/CRM/Contact/Import/Form/Summary.php b/CRM/Contact/Import/Form/Summary.php index d4a4655af7bd..33d02af4c63d 100644 --- a/CRM/Contact/Import/Form/Summary.php +++ b/CRM/Contact/Import/Form/Summary.php @@ -27,7 +27,8 @@ class CRM_Contact_Import_Form_Summary extends CRM_Import_Form_Summary { * @throws \CRM_Core_Exception */ public function preProcess() { - // set the error message path to display + // @todo - totally unclear that this errorFile could ever be set / render. + // Probably it can go. $this->assign('errorFile', $this->get('errorFile')); $onDuplicate = $this->getSubmittedValue('onDuplicate'); $this->assign('dupeError', FALSE); @@ -44,8 +45,8 @@ public function preProcess() { $this->assign('dupeError', TRUE); } - $this->assign('groupAdditions', $this->get('groupAdditions')); - $this->assign('tagAdditions', $this->get('tagAdditions')); + $this->assign('groupAdditions', $this->getUserJob()['metadata']['summary_info']['groups']); + $this->assign('tagAdditions', $this->getUserJob()['metadata']['summary_info']['tags']); $this->assign('totalRowCount', $this->getRowCount()); $this->assign('validRowCount', $this->getRowCount(CRM_Import_Parser::VALID) + $this->getRowCount(CRM_Import_Parser::UNPARSED_ADDRESS_WARNING)); $this->assign('invalidRowCount', $this->getRowCount(CRM_Import_Parser::ERROR)); diff --git a/CRM/Contact/Import/ImportJob.php b/CRM/Contact/Import/ImportJob.php index 035f6abe4d45..a61ec1899022 100644 --- a/CRM/Contact/Import/ImportJob.php +++ b/CRM/Contact/Import/ImportJob.php @@ -23,14 +23,10 @@ class CRM_Contact_Import_ImportJob { protected $_onDuplicate; protected $_dedupe; protected $_newGroupName; - protected $_newGroupDesc; - protected $_newGroupType; protected $_groups; protected $_allGroups; protected $_newTagName; - protected $_newTagDesc; protected $_tag; - protected $_allTags; protected $_mapper; protected $_mapperKeys = []; @@ -72,44 +68,6 @@ public function runImport(&$form, $timeout = 55) { $this->_mapperKeys[$key] = $mapper[$key][0] ?? NULL; } - $this->_parser = new CRM_Contact_Import_Parser_Contact( - $this->_mapperKeys - ); - $this->_parser->setUserJobID($this->_userJobID); - $this->_parser->run( - [], - CRM_Import_Parser::MODE_IMPORT, - $this->_statusID - ); - - $contactIds = $this->_parser->getImportedContacts(); - - //get the related contactIds. CRM-2926 - $relatedContactIds = $this->_parser->getRelatedImportedContacts(); - if ($relatedContactIds) { - $contactIds = array_merge($contactIds, $relatedContactIds); - } - - if ($this->_newGroupName || count($this->_groups)) { - $groupAdditions = $this->_addImportedContactsToNewGroup($contactIds, - $this->_newGroupName, - $this->_newGroupDesc, - $this->_newGroupType - ); - if ($form) { - $form->set('groupAdditions', $groupAdditions); - } - } - - if ($this->_newTagName || !empty($this->_tag)) { - $tagAdditions = $this->_tagImportedContactsWithNewTag($contactIds, - $this->_newTagName, - $this->_newTagDesc - ); - if ($form) { - $form->set('tagAdditions', $tagAdditions); - } - } } /** @@ -119,118 +77,4 @@ public function setFormVariables($form) { $this->_parser->set($form, CRM_Import_Parser::MODE_IMPORT); } - /** - * Add imported contacts. - * - * @param array $contactIds - * @param string $newGroupName - * @param string $newGroupDesc - * @param string $newGroupType - * - * @return array|bool - */ - private function _addImportedContactsToNewGroup( - $contactIds, - $newGroupName, $newGroupDesc, $newGroupType - ) { - - $newGroupId = NULL; - - if ($newGroupName) { - /* Create a new group */ - $newGroupType = $newGroupType ?? []; - $gParams = array( - 'title' => $newGroupName, - 'description' => $newGroupDesc, - 'group_type' => $newGroupType, - 'is_active' => TRUE, - ); - $group = CRM_Contact_BAO_Group::create($gParams); - $this->_groups[] = $newGroupId = $group->id; - } - - if (is_array($this->_groups)) { - $groupAdditions = []; - foreach ($this->_groups as $groupId) { - $addCount = CRM_Contact_BAO_GroupContact::addContactsToGroup($contactIds, $groupId); - $totalCount = $addCount[1]; - if ($groupId == $newGroupId) { - $name = $newGroupName; - $new = TRUE; - } - else { - $name = $this->_allGroups[$groupId]; - $new = FALSE; - } - $groupAdditions[] = array( - 'url' => CRM_Utils_System::url('civicrm/group/search', - 'reset=1&force=1&context=smog&gid=' . $groupId - ), - 'name' => $name, - 'added' => $totalCount, - 'notAdded' => $addCount[2], - 'new' => $new, - ); - } - return $groupAdditions; - } - return FALSE; - } - - /** - * @param $contactIds - * @param string $newTagName - * @param $newTagDesc - * - * @return array|bool - * @throws \CRM_Core_Exception - */ - private function _tagImportedContactsWithNewTag( - $contactIds, - $newTagName, $newTagDesc - ) { - - $newTagId = NULL; - if ($newTagName) { - /* Create a new Tag */ - - $tagParams = array( - 'name' => $newTagName, - 'description' => $newTagDesc, - 'is_selectable' => TRUE, - 'used_for' => 'civicrm_contact', - ); - $addedTag = CRM_Core_BAO_Tag::add($tagParams); - $this->_tag[$addedTag->id] = 1; - } - //add Tag to Import - - if (is_array($this->_tag)) { - $tagAdditions = []; - foreach ($this->_tag as $tagId => $val) { - $addTagCount = CRM_Core_BAO_EntityTag::addEntitiesToTag($contactIds, $tagId, 'civicrm_contact', FALSE); - $totalTagCount = $addTagCount[1]; - if (isset($addedTag) && $tagId == $addedTag->id) { - $tagName = $newTagName; - $new = TRUE; - } - else { - $tagName = $this->_allTags[$tagId]; - $new = FALSE; - } - $tagAdditions[] = array( - 'url' => CRM_Utils_System::url('civicrm/contact/search', - 'reset=1&force=1&context=smog&id=' . $tagId - ), - 'name' => $tagName, - 'added' => $totalTagCount, - 'notAdded' => $addTagCount[2], - 'new' => $new, - ); - } - return $tagAdditions; - } - return FALSE; - } - } diff --git a/CRM/Contact/Import/MetadataTrait.php b/CRM/Contact/Import/MetadataTrait.php index fc8d57048427..368df0e11a93 100644 --- a/CRM/Contact/Import/MetadataTrait.php +++ b/CRM/Contact/Import/MetadataTrait.php @@ -81,7 +81,7 @@ protected function getRelationships(): array { * * @return array */ - public function getHeaderPatterns() { + public function getHeaderPatterns(): array { return CRM_Utils_Array::collect('headerPattern', $this->getContactImportMetadata()); } @@ -90,7 +90,7 @@ public function getHeaderPatterns() { * * @return array */ - public function getDataPatterns() { + public function getDataPatterns(): array { return CRM_Utils_Array::collect('dataPattern', $this->getContactImportMetadata()); } diff --git a/CRM/Contact/Import/Parser/Contact.php b/CRM/Contact/Import/Parser/Contact.php index 5092ac1da60a..198dba14ae1b 100644 --- a/CRM/Contact/Import/Parser/Contact.php +++ b/CRM/Contact/Import/Parser/Contact.php @@ -209,6 +209,7 @@ public function getAllFields() { */ public function import($onDuplicate, &$values) { $rowNumber = (int) $values[array_key_last($values)]; + $this->_unparsedStreetAddressContacts = []; if (!$this->getSubmittedValue('doGeocodeAddress')) { // CRM-5854, reset the geocode method to null to prevent geocoding @@ -251,11 +252,7 @@ public function import($onDuplicate, &$values) { //fixed CRM-4148 //now we create new contact in update/fill mode also. $newContact = $this->createContact($formatted, $contactFields, $onDuplicate, $params['id'] ?? NULL, TRUE, $this->_dedupeRuleGroupID); - - if (!is_array($newContact)) { - $contactID = $newContact->id; - $this->_newContacts[] = $contactID; - } + $this->createdContacts[$newContact->id] = $contactID = $newContact->id; if ($contactID) { // call import hook @@ -296,18 +293,17 @@ public function import($onDuplicate, &$values) { if (empty($formatting['id']) || $this->isUpdateExistingContacts()) { try { $relatedNewContact = $this->createContact($formatting, $contactFields, $onDuplicate, $formatting['id']); + $relContactId = $relatedNewContact->id; + $this->createdContacts[$relContactId] = $relContactId; } catch (CiviCRM_API3_Exception $e) { $this->setImportStatus($rowNumber, 'ERROR', $e->getMessage()); return FALSE; } - $relContactId = $relatedNewContact->id; - $this->_newRelatedContacts[$relContactId] = $relContactId; } $this->createRelationship($key, $relContactId, $primaryContactId); } } - $this->setImportStatus($rowNumber, $this->getStatus(CRM_Import_Parser::VALID), $this->getSuccessMessage(), $contactID); return CRM_Import_Parser::VALID; } @@ -569,16 +565,7 @@ private function formatCommonData($params, &$formatted) { * @return array */ public function getImportedContacts() { - return $this->_newContacts; - } - - /** - * Get the array of successfully imported related contact id's - * - * @return array - */ - public function &getRelatedImportedContacts() { - return $this->_newRelatedContacts; + return $this->createdContacts; } /** @@ -1256,6 +1243,8 @@ public function run( // here to make sure it is instantiated. $this->getContactType(); $this->getContactSubType(); + // Reset user job in case to null in case it was loaded prior to the job being complete. + $this->userJob = NULL; $this->init(); @@ -1290,6 +1279,7 @@ public function run( $prevTimestamp = $this->progressImport($statusID, FALSE, $startTimestamp, $prevTimestamp, $totalRowCount); } } + $this->doPostImportActions(); } /** @@ -1367,24 +1357,6 @@ public static function exportCSV($fileName, $header, $data) { fclose($fd); } - /** - * Update the status of the import row to reflect the processing outcome. - * - * @param int $id - * @param string $status - * @param string $message - * @param int|null $entityID - * Optional created entity ID - * @param array $relatedEntityIDs - * Optional array e.g ['related_contact' => 4] - * - * @throws \API_Exception - * @throws \CRM_Core_Exception - */ - public function setImportStatus(int $id, string $status, string $message, ?int $entityID = NULL, array $relatedEntityIDs = []): void { - $this->getDataSourceObject()->updateStatus($id, $status, $message, $entityID, $relatedEntityIDs); - } - /** * Format contact parameters. * diff --git a/CRM/Import/Form/MapField.php b/CRM/Import/Form/MapField.php index 9c10336654f4..1f9187c35ec4 100644 --- a/CRM/Import/Form/MapField.php +++ b/CRM/Import/Form/MapField.php @@ -66,6 +66,14 @@ public function getTitle() { return ts('Match Fields'); } + /** + * Shared preProcess code. + */ + public function preProcess() { + $this->assignMapFieldVariables(); + parent::preProcess(); + } + /** * Attempt to match header labels with our mapper fields. * diff --git a/CRM/Import/Form/Preview.php b/CRM/Import/Form/Preview.php index 3ba704f33987..cce75402e928 100644 --- a/CRM/Import/Form/Preview.php +++ b/CRM/Import/Form/Preview.php @@ -36,8 +36,7 @@ public function getTitle() { * Assign common values to the template. */ public function preProcess() { - $this->assign('skipColumnHeader', $this->getSubmittedValue('skipColumnHeader')); - $this->assign('rowDisplayCount', $this->getSubmittedValue('skipColumnHeader') ? 3 : 2); + $this->assignPreviewVariables(); } /** @@ -91,4 +90,31 @@ public function setStatusUrl() { $this->assign('statusUrl', $statusUrl); } + /** + * Assign smarty variables for the preview screen. + * + * @throws \API_Exception + * @throws \CRM_Core_Exception + */ + protected function assignPreviewVariables(): void { + $this->assign('downloadErrorRecordsUrl', $this->getDownloadURL(CRM_Import_Parser::ERROR)); + $this->assign('invalidRowCount', $this->getRowCount(CRM_Import_Parser::ERROR)); + $this->assign('validRowCount', $this->getRowCount(CRM_Import_Parser::VALID)); + $this->assign('totalRowCount', $this->getRowCount([])); + $this->assign('mapper', $this->getMappedFieldLabels()); + $this->assign('dataValues', $this->getDataRows([], 2)); + $this->assign('columnNames', $this->getColumnHeaders()); + //get the mapping name displayed if the mappingId is set + $mappingId = $this->get('loadMappingId'); + if ($mappingId) { + $mapDAO = new CRM_Core_DAO_Mapping(); + $mapDAO->id = $mappingId; + $mapDAO->find(TRUE); + } + $this->assign('savedMappingName', $mappingId ? $mapDAO->name : NULL); + $this->assign('skipColumnHeader', $this->getSubmittedValue('skipColumnHeader')); + // rowDisplayCount is deprecated - it used to be used with {section} but we have nearly gotten rid of it. + $this->assign('rowDisplayCount', $this->getSubmittedValue('skipColumnHeader') ? 3 : 2); + } + } diff --git a/CRM/Import/Forms.php b/CRM/Import/Forms.php index 6c61172567b3..c3f364600213 100644 --- a/CRM/Import/Forms.php +++ b/CRM/Import/Forms.php @@ -586,4 +586,53 @@ protected function getMappedFieldLabels(): array { return $mapper; } + /** + * Assign variables required for the MapField form. + * + * @throws \API_Exception + * @throws \CRM_Core_Exception + */ + protected function assignMapFieldVariables(): void { + $this->addExpectedSmartyVariable('highlightedRelFields'); + $this->_columnCount = $this->getNumberOfColumns(); + $this->_columnNames = $this->getColumnHeaders(); + $this->_dataValues = array_values($this->getDataRows([], 2)); + $this->assign('columnNames', $this->getColumnHeaders()); + $this->assign('highlightedFields', $this->getHighlightedFields()); + $this->assign('columnCount', $this->_columnCount); + $this->assign('dataValues', $this->_dataValues); + } + + /** + * Get the fields to be highlighted in the UI. + * + * The highlighted fields are those used to match + * to an existing entity. + * + * @return array + * + * @throws \CRM_Core_Exception + */ + protected function getHighlightedFields(): array { + return []; + } + + /** + * Get the data patterns to pattern match the incoming data. + * + * @return array + */ + public function getDataPatterns(): array { + return $this->getParser()->getDataPatterns(); + } + + /** + * Get the data patterns to pattern match the incoming data. + * + * @return array + */ + public function getHeaderPatterns(): array { + return $this->getParser()->getHeaderPatterns(); + } + } diff --git a/CRM/Import/Parser.php b/CRM/Import/Parser.php index d25e5e1a55b1..8a12340f48fb 100644 --- a/CRM/Import/Parser.php +++ b/CRM/Import/Parser.php @@ -53,6 +53,13 @@ abstract class CRM_Import_Parser { */ protected $userJobID; + /** + * The user job in use. + * + * @var array + */ + protected $userJob; + /** * Potentially ambiguous options. * @@ -76,6 +83,13 @@ public function getUserJobID(): ?int { return $this->userJobID; } + /** + * Ids of contacts created this iteration. + * + * @var array + */ + protected $createdContacts = []; + /** * Set user job ID. * @@ -105,10 +119,13 @@ public function setUserJobID(int $userJobID): self { * @throws \API_Exception */ protected function getUserJob(): array { - return UserJob::get() - ->addWhere('id', '=', $this->getUserJobID()) - ->execute() - ->first(); + if (empty($this->userJob)) { + $this->userJob = UserJob::get() + ->addWhere('id', '=', $this->getUserJobID()) + ->execute() + ->first(); + } + return $this->userJob; } /** @@ -510,7 +527,7 @@ public function getSelectTypes() { /** * @return array */ - public function getHeaderPatterns() { + public function getHeaderPatterns(): array { $values = []; foreach ($this->_fields as $name => $field) { if (isset($field->_headerPattern)) { @@ -523,7 +540,7 @@ public function getHeaderPatterns() { /** * @return array */ - public function getDataPatterns() { + public function getDataPatterns():array { $values = []; foreach ($this->_fields as $name => $field) { $values[$name] = $field->_dataPattern; @@ -606,6 +623,76 @@ protected function validateRequiredContactFields(string $contactType, array $par $this->validateRequiredFields($requiredFields, $params, $prefixString); } + protected function doPostImportActions() { + $userJob = $this->getUserJob(); + $summaryInfo = $userJob['metadata']['summary_info']; + $actions = $userJob['metadata']['post_actions']; + if (!empty($actions['group'])) { + $groupAdditions = $this->addImportedContactsToNewGroup($this->createdContacts, $actions['group']); + foreach ($actions['group'] as $groupID) { + $summaryInfo['groups'][$groupID]['added'] += $groupAdditions[$groupID]['added']; + $summaryInfo['groups'][$groupID]['notAdded'] += $groupAdditions[$groupID]['notAdded']; + } + } + if (!empty($actions['tag'])) { + $tagAdditions = $this->tagImportedContactsWithNewTag($this->createdContacts, $actions['tag']); + foreach ($actions['tag'] as $tagID) { + $summaryInfo['tags'][$tagID]['added'] += $tagAdditions[$tagID]['added']; + $summaryInfo['tags'][$tagID]['notAdded'] += $tagAdditions[$tagID]['notAdded']; + } + } + + $this->userJob['metadata']['summary_info'] = $summaryInfo; + UserJob::update(FALSE)->addWhere('id', '=', $userJob['id'])->setValues(['metadata' => $this->userJob['metadata']])->execute(); + } + + /** + * Add imported contacts to groups. + * + * @param array $contactIDs + * @param array $groups + * + * @return array + */ + private function addImportedContactsToNewGroup(array $contactIDs, array $groups): array { + $groupAdditions = []; + foreach ($groups as $groupID) { + // @todo - this function has been in use historically but it does not seem + // to add much efficiency of get + create api calls + // and it doesn't give enough control over cache flushing for smaller batches. + // Note that the import updates a lot of enities & checking & updating the group + // shouldn't add much performance wise. However, cache flushing will + $addCount = CRM_Contact_BAO_GroupContact::addContactsToGroup($contactIDs, $groupID); + $groupAdditions[$groupID] = [ + 'added' => (int) $addCount[1], + 'notAdded' => (int) $addCount[2], + ]; + } + return $groupAdditions; + } + + /** + * Tag imported contacts. + * + * @param array $contactIDs + * @param array $tags + * + * @return array + */ + private function tagImportedContactsWithNewTag(array $contactIDs, array $tags) { + $tagAdditions = []; + foreach ($tags as $tagID) { + // @todo - this function has been in use historically but it does not seem + // to add much efficiency of get + create api calls + // and it doesn't give enough control over cache flushing for smaller batches. + // Note that the import updates a lot of enities & checking & updating the group + // shouldn't add much performance wise. However, cache flushing will + $outcome = CRM_Core_BAO_EntityTag::addEntitiesToTag($contactIDs, $tagID, 'civicrm_contact', FALSE); + $tagAdditions[$tagID] = ['added' => $outcome[1], 'notAdded' => $outcome[2]]; + } + return $tagAdditions; + } + /** * Determines the file extension based on error code. * @@ -1521,6 +1608,29 @@ protected function isAmbiguous(string $fieldName, $importedValue): bool { return !empty($this->ambiguousOptions[$fieldName][mb_strtolower($importedValue)]); } + /** + * Get the civicrm_mapping_field appropriate layout for the mapper input. + * + * For simple parsers (not contribution or contact) the input looks like + * ['first_name', 'custom_32'] + * and it is converted to + * + * ['name' => 'first_name', 'mapping_id' => 1, 'column_number' => 5], + * + * @param array $fieldMapping + * @param int $mappingID + * @param int $columnNumber + * + * @return array + */ + public function getMappingFieldFromMapperInput(array $fieldMapping, int $mappingID, int $columnNumber): array { + return [ + 'name' => $fieldMapping[0], + 'mapping_id' => $mappingID, + 'column_number' => $columnNumber, + ]; + } + /** * Get the field mappings for the import. * @@ -1616,4 +1726,20 @@ protected function getSubtypes($contactType) { return $subTypes; } + /** + * Update the status of the import row to reflect the processing outcome. + * + * @param int $id + * @param string $status + * @param string $message + * @param int|null $entityID + * Optional created entity ID + * + * @throws \API_Exception + * @throws \CRM_Core_Exception + */ + protected function setImportStatus(int $id, string $status, string $message, ?int $entityID = NULL): void { + $this->getDataSourceObject()->updateStatus($id, $status, $message, $entityID); + } + } diff --git a/templates/CRM/Contact/Import/Form/MapTable.tpl b/templates/CRM/Contact/Import/Form/MapTable.tpl index e0fbce6125fb..b13969c0fddd 100644 --- a/templates/CRM/Contact/Import/Form/MapTable.tpl +++ b/templates/CRM/Contact/Import/Form/MapTable.tpl @@ -7,111 +7,26 @@ | and copyright information, see https://civicrm.org/licensing | +--------------------------------------------------------------------+ *} -
+{include file="CRM/Import/Form/MapTableCommon.tpl"} -{* Import Wizard - Data Mapping table used by MapFields.tpl and Preview.tpl *} -
- {strip} - - {if $savedMappingName} - - {/if} - - {* Header row - has column for column names if they have been supplied *} - - {if $columnNames} - - {/if} - {foreach from=$dataValues item=row key=index} - {math equation="x + y" x=$index y=1 assign="rowNumber"} - - {/foreach} - - - - {*Loop on columns parsed from the import data rows*} - {foreach from=$mapper key=i item=mapperField} - - {if array_key_exists($i, $columnNames)} - - {/if} - {foreach from=$dataValues item=row key=index} - - {/foreach} - - {* Display mapper - - {/foreach} -
{ts 1=$savedMappingName}Saved Field Mapping: %1{/ts}
{ts}Column Names{/ts}{ts 1=$rowNumber}Import Data (row %1){/ts}{ts}Matching CiviCRM Field{/ts}
{$columnNames[$i]}{$row[$i]|escape} - {if $wizard.currentStepName == 'Preview'} - {$mapperField} - {else} - {$mapperField.html|smarty:nodefaults} - {/if} -
- {/strip} - - {if $wizard.currentStepName != 'Preview'} -
- {if $savedMappingName} - {$form.updateMapping.html}    {$form.updateMapping.label} - {/if} - {$form.saveMapping.html}    {$form.saveMapping.label} -
- - - - - - - - - -
{$form.saveMappingName.label}{$form.saveMappingName.html}
{$form.saveMappingDesc.label}{$form.saveMappingDesc.html}
-
- {literal} - -
- {/if} -
-
+ }); + } + }); + + {/literal} +{/if} diff --git a/templates/CRM/Contact/Import/Form/Preview.tpl b/templates/CRM/Contact/Import/Form/Preview.tpl index 503d561a5f7a..77b28e9b6f95 100644 --- a/templates/CRM/Contact/Import/Form/Preview.tpl +++ b/templates/CRM/Contact/Import/Form/Preview.tpl @@ -128,9 +128,7 @@
- {foreach from=$form.tag item="tag_val"} -
{$tag_val.html}
- {/foreach} + {$form.tag.html}
diff --git a/templates/CRM/Import/Form/MapTableCommon.tpl b/templates/CRM/Import/Form/MapTableCommon.tpl new file mode 100644 index 000000000000..83fa1fcc8e48 --- /dev/null +++ b/templates/CRM/Import/Form/MapTableCommon.tpl @@ -0,0 +1,92 @@ +
+ + {* Import Wizard - Data Mapping table used by MapFields.tpl and Preview.tpl *} +
+ {strip} + + {if $savedMappingName} + + {/if} + + {* Header row - has column for column names if they have been supplied *} + + {if $columnNames} + + {/if} + {foreach from=$dataValues item=row key=index} + {math equation="x + y" x=$index y=1 assign="rowNumber"} + + {/foreach} + + + + {*Loop on columns parsed from the import data rows*} + {foreach from=$mapper key=i item=mapperField} + + {if array_key_exists($i, $columnNames)} + + {/if} + {foreach from=$dataValues item=row key=index} + + {/foreach} + + {* Display mapper + + {/foreach} +
{ts 1=$savedMappingName}Saved Field Mapping: %1{/ts}
{ts}Column Names{/ts}{ts 1=$rowNumber}Import Data (row %1){/ts}{ts}Matching CiviCRM Field{/ts}
{$columnNames[$i]}{$row[$i]|escape} + {if $wizard.currentStepName == 'Preview'} + {$mapperField} + {else} + {$mapperField.html|smarty:nodefaults} + {/if} +
+ {/strip} + + {if $wizard.currentStepName != 'Preview'} +
+ {if $savedMappingName} + {$form.updateMapping.html}    {$form.updateMapping.label} + {/if} + {$form.saveMapping.html}    {$form.saveMapping.label} +
+ + + + + + + + + +
{$form.saveMappingName.label}{$form.saveMappingName.html}
{$form.saveMappingDesc.label}{$form.saveMappingDesc.html}
+
+ {literal} + +
+ {/if} + + +
+
diff --git a/templates/CRM/common/highLightImport.tpl b/templates/CRM/common/highLightImport.tpl index bd9156c941c2..6279ff532b90 100644 --- a/templates/CRM/common/highLightImport.tpl +++ b/templates/CRM/common/highLightImport.tpl @@ -14,7 +14,7 @@ CRM.$(function($) { $.each(highlightedFields, function() { $('select[id^="mapper"][id$="_0"] option[value='+ this + ']').append(' *').css({"color":"#FF0000"}); }); - {/literal}{if $relationship}{literal} + {/literal}{if $highlightedRelFields}{literal} var highlightedRelFields = {/literal}{$highlightedRelFields|@json_encode}{literal}; function highlight() { var select, fields = highlightedRelFields[$(this).val()];