From 92400d703b41cec7925e62eb3981ef77c0471b6d Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Fri, 16 Sep 2022 09:34:29 -0400 Subject: [PATCH] APIv4 - Add Contact::mergeDuplicates action --- Civi/Api4/Action/Contact/GetDuplicates.php | 2 +- Civi/Api4/Action/Contact/MergeDuplicates.php | 83 +++++++++++++++++++ Civi/Api4/Contact.php | 9 ++ Civi/Test/Api3TestTrait.php | 6 ++ api/v3/Contact.php | 4 +- ...atesTest.php => ContactDuplicatesTest.php} | 41 ++++++++- 6 files changed, 141 insertions(+), 4 deletions(-) create mode 100644 Civi/Api4/Action/Contact/MergeDuplicates.php rename tests/phpunit/api/v4/Action/{ContactGetDuplicatesTest.php => ContactDuplicatesTest.php} (77%) diff --git a/Civi/Api4/Action/Contact/GetDuplicates.php b/Civi/Api4/Action/Contact/GetDuplicates.php index 2986869de2..f39298c003 100644 --- a/Civi/Api4/Action/Contact/GetDuplicates.php +++ b/Civi/Api4/Action/Contact/GetDuplicates.php @@ -19,7 +19,7 @@ use Civi\Api4\Utils\FormattingUtil; /** * Get matching contacts based on a dedupe rule - * @method setDedupeRule(string $dedupeRule) + * @method $this setDedupeRule(string $dedupeRule) * @method string getDedupeRule() */ class GetDuplicates extends \Civi\Api4\Generic\DAOCreateAction { diff --git a/Civi/Api4/Action/Contact/MergeDuplicates.php b/Civi/Api4/Action/Contact/MergeDuplicates.php new file mode 100644 index 0000000000..00b32ed326 --- /dev/null +++ b/Civi/Api4/Action/Contact/MergeDuplicates.php @@ -0,0 +1,83 @@ + $this->duplicateId, 'dstID' => $this->contactId]], + [], + $this->mode, + FALSE, + $this->getCheckPermissions() + ); + if ($merge) { + $result[] = $merge; + } + else { + throw new \CRM_Core_Exception('Merge failed'); + } + } + + /** + * @return array + */ + public static function fields(BasicGetFieldsAction $action) { + return []; + } + +} diff --git a/Civi/Api4/Contact.php b/Civi/Api4/Contact.php index 4b7db45231..b8ddca6aa0 100644 --- a/Civi/Api4/Contact.php +++ b/Civi/Api4/Contact.php @@ -90,4 +90,13 @@ class Contact extends Generic\DAOEntity { ->setCheckPermissions($checkPermissions); } + /** + * @param bool $checkPermissions + * @return Action\Contact\MergeDuplicates + */ + public static function mergeDuplicates($checkPermissions = TRUE) { + return (new Action\Contact\MergeDuplicates(__CLASS__, __FUNCTION__)) + ->setCheckPermissions($checkPermissions); + } + } diff --git a/Civi/Test/Api3TestTrait.php b/Civi/Test/Api3TestTrait.php index 57007fa4bc..22e3058cfd 100644 --- a/Civi/Test/Api3TestTrait.php +++ b/Civi/Test/Api3TestTrait.php @@ -466,6 +466,12 @@ trait Api3TestTrait { } break; + case 'merge': + $v4Action = 'mergeDuplicates'; + $v3Params['contact_id'] = $v3Params['to_keep_id']; + $v3Params['duplicate_id'] = $v3Params['to_remove_id']; + break; + case 'getoptions': $indexBy = 0; $v4Action = 'getFields'; diff --git a/api/v3/Contact.php b/api/v3/Contact.php index 36d40f277e..732f14be83 100644 --- a/api/v3/Contact.php +++ b/api/v3/Contact.php @@ -1150,8 +1150,8 @@ function _civicrm_api3_contact_deprecation() { * * @param array $params * Allowed array keys are: - * -int main_id: main contact id with whom merge has to happen - * -int other_id: duplicate contact which would be deleted after merge operation + * -int to_keep_id: main contact id with whom merge has to happen + * -int to_remove_id: duplicate contact which would be deleted after merge operation * -string mode: "safe" skips the merge if there are no conflicts. Does a force merge otherwise. * * @return array diff --git a/tests/phpunit/api/v4/Action/ContactGetDuplicatesTest.php b/tests/phpunit/api/v4/Action/ContactDuplicatesTest.php similarity index 77% rename from tests/phpunit/api/v4/Action/ContactGetDuplicatesTest.php rename to tests/phpunit/api/v4/Action/ContactDuplicatesTest.php index 977966a88b..5c2442ad7b 100644 --- a/tests/phpunit/api/v4/Action/ContactGetDuplicatesTest.php +++ b/tests/phpunit/api/v4/Action/ContactDuplicatesTest.php @@ -25,7 +25,7 @@ use Civi\Api4\Contact; /** * @group headless */ -class ContactGetDuplicatesTest extends CustomTestBase { +class ContactDuplicatesTest extends CustomTestBase { public function testGetDuplicatesUnsupervised() { $email = uniqid('test@'); @@ -140,4 +140,43 @@ class ContactGetDuplicatesTest extends CustomTestBase { $this->assertContains($testContacts[4], $found); } + public function testMergeDuplicates():void { + $email = uniqid('test@'); + + $testContacts = $this->saveTestRecords('Contact', [ + 'records' => [['first_name' => 'Jo'], ['first_name' => 'Not']], + 'defaults' => ['email_primary.email' => $email], + ])->column('id'); + + // Run merge in "safe mode" which will stop because of the name conflicts + $result = Contact::mergeDuplicates(FALSE) + ->setContactId($testContacts[0]) + ->setDuplicateId($testContacts[1]) + ->execute(); + + $this->assertCount(0, $result[0]['merged']); + $this->assertCount(1, $result[0]['skipped']); + $check = Contact::get(FALSE) + ->addWhere('is_deleted', '=', FALSE) + ->addWhere('id', 'IN', $testContacts) + ->execute(); + $this->assertCount(2, $check); + + // Run merge in "aggressive mode" which will overwrite the name conflicts + $result = Contact::mergeDuplicates(FALSE) + ->setContactId($testContacts[0]) + ->setDuplicateId($testContacts[1]) + ->setMode('aggressive') + ->execute(); + + $this->assertCount(1, $result[0]['merged']); + $this->assertCount(0, $result[0]['skipped']); + $check = Contact::get(FALSE) + ->addWhere('is_deleted', '=', FALSE) + ->addWhere('id', 'IN', $testContacts) + ->execute(); + $this->assertCount(1, $check); + $this->assertEquals('Jo', $check[0]['first_name']); + } + } -- 2.25.1