From f159d35c51fee66bdb2d6821eb8940e41f05081a Mon Sep 17 00:00:00 2001 From: Eileen McNaughton Date: Thu, 25 Aug 2022 11:00:54 +1200 Subject: [PATCH] Fix participant import not to import to deleted contacts --- CRM/Contact/Import/Parser/Contact.php | 48 +------------------ CRM/Event/Import/Parser/Participant.php | 11 ++--- CRM/Import/Parser.php | 47 ++++++++++++++++++ .../Event/Import/Parser/ParticipantTest.php | 37 +++++++++++++- .../Parser/data/participant_with_ext_id.csv | 2 + 5 files changed, 89 insertions(+), 56 deletions(-) create mode 100644 tests/phpunit/CRM/Event/Import/Parser/data/participant_with_ext_id.csv diff --git a/CRM/Contact/Import/Parser/Contact.php b/CRM/Contact/Import/Parser/Contact.php index fc4737e2b6..c1c338c499 100644 --- a/CRM/Contact/Import/Parser/Contact.php +++ b/CRM/Contact/Import/Parser/Contact.php @@ -1644,48 +1644,6 @@ class CRM_Contact_Import_Parser_Contact extends CRM_Import_Parser { return $relatedContacts; } - /** - * Look up for an existing contact with the given external_identifier. - * - * If the identifier is found on a deleted contact then it is not a match - * but it must be removed from that contact to allow the new contact to - * have that external_identifier. - * - * @param string|null $externalIdentifier - * @param string $contactType - * - * @return int|null - * - * @throws \CRM_Core_Exception - * @throws \CiviCRM_API3_Exception - */ - protected function lookupExternalIdentifier(?string $externalIdentifier, string $contactType): ?int { - if (!$externalIdentifier) { - return NULL; - } - // Check for any match on external id, deleted or otherwise. - $foundContact = civicrm_api3('Contact', 'get', [ - 'external_identifier' => $externalIdentifier, - 'showAll' => 'all', - 'sequential' => TRUE, - 'return' => ['id', 'contact_is_deleted', 'contact_type'], - ]); - if (empty($foundContact['id'])) { - return NULL; - } - if (!empty($foundContact['values'][0]['contact_is_deleted'])) { - // If the contact is deleted, update external identifier to be blank - // to avoid key error from MySQL. - $params = ['id' => $foundContact['id'], 'external_identifier' => '']; - civicrm_api3('Contact', 'create', $params); - return NULL; - } - if ($foundContact['values'][0]['contact_type'] !== $contactType) { - throw new CRM_Core_Exception('Mismatched contact Types', CRM_Import_Parser::NO_MATCH); - } - return (int) $foundContact['id']; - } - /** * Lookup the contact's contact ID. * @@ -1700,12 +1658,8 @@ class CRM_Contact_Import_Parser_Contact extends CRM_Import_Parser { * @throws \Civi\API\Exception\UnauthorizedException */ protected function lookupContactID(array $params, bool $isMainContact): ?int { - $extIDMatch = $this->lookupExternalIdentifier($params['external_identifier'] ?? NULL, $params['contact_type']); $contactID = !empty($params['id']) ? (int) $params['id'] : NULL; - //check if external identifier exists in database - if ($extIDMatch && $contactID && $extIDMatch !== $contactID) { - throw new CRM_Core_Exception(ts('Existing external ID does not match the imported contact ID.'), CRM_Import_Parser::ERROR); - } + $extIDMatch = $this->lookupExternalIdentifier($params['external_identifier'] ?? NULL, $params['contact_type'], $contactID); if ($extIDMatch && $isMainContact && ($this->isSkipDuplicates() || $this->isIgnoreDuplicates())) { throw new CRM_Core_Exception(ts('External ID already exists in Database.'), CRM_Import_Parser::DUPLICATE); } diff --git a/CRM/Event/Import/Parser/Participant.php b/CRM/Event/Import/Parser/Participant.php index 720a464f32..1d6d75e748 100644 --- a/CRM/Event/Import/Parser/Participant.php +++ b/CRM/Event/Import/Parser/Participant.php @@ -150,6 +150,9 @@ class CRM_Event_Import_Parser_Participant extends CRM_Import_Parser { $rowNumber = (int) ($values[array_key_last($values)]); try { $params = $this->getMappedRow($values); + if ($params['external_identifier']) { + $params['contact_id'] = $this->lookupExternalIdentifier($params['external_identifier'], $this->getContactType(), $params['contact_id'] ?? NULL); + } $session = CRM_Core_Session::singleton(); $formatted = $params; // don't add to recent items, CRM-4399 @@ -271,14 +274,6 @@ class CRM_Event_Import_Parser_Participant extends CRM_Import_Parser { } } else { - if (!empty($formatValues['external_identifier'])) { - $checkCid = new CRM_Contact_DAO_Contact(); - $checkCid->external_identifier = $formatValues['external_identifier']; - $checkCid->find(TRUE); - if ($checkCid->id != $formatted['contact_id']) { - throw new CRM_Core_Exception('Mismatch of External ID:' . $formatValues['external_identifier'] . ' and Contact Id:' . $formatted['contact_id']); - } - } $newParticipant = $this->deprecated_create_participant_formatted($formatted); } diff --git a/CRM/Import/Parser.php b/CRM/Import/Parser.php index b11548791f..4d9e78e96c 100644 --- a/CRM/Import/Parser.php +++ b/CRM/Import/Parser.php @@ -1963,4 +1963,51 @@ abstract class CRM_Import_Parser implements UserJobInterface { return is_numeric($importedValue) ? $importedValue : mb_strtolower(str_replace('’', "'", $importedValue)); } + /** + * Look up for an existing contact with the given external_identifier. + * + * If the identifier is found on a deleted contact then it is not a match + * but it must be removed from that contact to allow the new contact to + * have that external_identifier. + * + * @param string|null $externalIdentifier + * @param string $contactType + * @param int|null $contactID + * + * @return int|null + * + * @throws \CRM_Core_Exception + * @throws \CiviCRM_API3_Exception + */ + protected function lookupExternalIdentifier(?string $externalIdentifier, string $contactType, ?int $contactID): ?int { + if (!$externalIdentifier) { + return NULL; + } + // Check for any match on external id, deleted or otherwise. + $foundContact = civicrm_api3('Contact', 'get', [ + 'external_identifier' => $externalIdentifier, + 'showAll' => 'all', + 'sequential' => TRUE, + 'return' => ['id', 'contact_is_deleted', 'contact_type'], + ]); + if (empty($foundContact['id'])) { + return NULL; + } + if (!empty($foundContact['values'][0]['contact_is_deleted'])) { + // If the contact is deleted, update external identifier to be blank + // to avoid key error from MySQL. + $params = ['id' => $foundContact['id'], 'external_identifier' => '']; + civicrm_api3('Contact', 'create', $params); + return NULL; + } + if ($contactType && $foundContact['values'][0]['contact_type'] !== $contactType) { + throw new CRM_Core_Exception('Mismatched contact Types', CRM_Import_Parser::NO_MATCH); + } + //check if external identifier exists in database + if ($contactID && $foundContact['id'] !== $contactID) { + throw new CRM_Core_Exception(ts('Existing external ID does not match the imported contact ID.'), CRM_Import_Parser::ERROR); + } + return (int) $foundContact['id']; + } + } diff --git a/tests/phpunit/CRM/Event/Import/Parser/ParticipantTest.php b/tests/phpunit/CRM/Event/Import/Parser/ParticipantTest.php index 88425db7af..dd7bcee745 100644 --- a/tests/phpunit/CRM/Event/Import/Parser/ParticipantTest.php +++ b/tests/phpunit/CRM/Event/Import/Parser/ParticipantTest.php @@ -102,7 +102,17 @@ class CRM_Participant_Import_Parser_ParticipantTest extends CiviUnitTestCase { $form->setUserJobID($this->userJobID); $form->buildForm(); $this->assertTrue($form->validate()); - $form->postProcess(); + try { + $form->postProcess(); + } + catch (CRM_Core_Exception_PrematureExitException $e) { + $queue = Civi::queue('user_job_' . $this->userJobID); + $runner = new CRM_Queue_Runner([ + 'queue' => $queue, + 'errorMode' => CRM_Queue_Runner::ERROR_ABORT, + ]); + $runner->runAll(); + } } /** @@ -162,6 +172,31 @@ class CRM_Participant_Import_Parser_ParticipantTest extends CiviUnitTestCase { ]; } + /** + * Test that an external id will not match to a deleted contact.. + */ + public function testImportWithExternalID() :void { + $this->eventCreate(['title' => 'Rain-forest Cup Youth Soccer Tournament']); + $this->individualCreate(['external_identifier' => 'ref-77', 'is_deleted' => TRUE]); + $this->importCSV('participant_with_ext_id.csv', [ + ['name' => 'event_id'], + ['name' => 'do_not_import'], + ['name' => 'external_identifier'], + ['name' => 'fee_amount'], + ['name' => 'fee_currency'], + ['name' => 'fee_level'], + ['name' => 'is_pay_later'], + ['name' => 'role_id'], + ['name' => 'source'], + ['name' => 'status_id'], + ['name' => 'register_date'], + ['name' => 'do_not_import'], + ]); + $dataSource = new CRM_Import_DataSource_CSV($this->userJobID); + $row = $dataSource->getRow(); + $this->assertEquals('ERROR', $row['_status']); + } + /** * @param array $submittedValues * diff --git a/tests/phpunit/CRM/Event/Import/Parser/data/participant_with_ext_id.csv b/tests/phpunit/CRM/Event/Import/Parser/data/participant_with_ext_id.csv new file mode 100644 index 0000000000..8cf0050d54 --- /dev/null +++ b/tests/phpunit/CRM/Event/Import/Parser/data/participant_with_ext_id.csv @@ -0,0 +1,2 @@ +Event Title,Campaign ID,External Identifier,Fee Amount,Fee Currency,Fee level,Is Pay Later,Participant Role,Participant Source,Participant Status,Register date,Do not import +Rain-forest Cup Youth Soccer Tournament,Soccer Cup,ref-77,5000.55,USD,High,No,"Attendee, Volunteer",Phoned up,Registered,2022-12-07, -- 2.25.1