/**
* 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 {
--- /dev/null
+<?php
+
+/*
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC. All rights reserved. |
+ | |
+ | This work is published under the GNU AGPLv3 license with some |
+ | permitted exceptions and without any warranty. For full license |
+ | and copyright information, see https://civicrm.org/licensing |
+ +--------------------------------------------------------------------+
+ */
+
+namespace Civi\Api4\Action\Contact;
+
+use Civi\Api4\Generic\BasicGetFieldsAction;
+use Civi\Api4\Generic\Result;
+
+/**
+ * Merge 2 contacts
+ *
+ * @method $this setMode(string $mode)
+ * @method string getMode()
+ * @method $this setContactId(int $contactId)
+ * @method int getContactId()
+ * @method $this setDuplicateId(int $duplicateId)
+ * @method int getDuplicateId()
+ */
+class MergeDuplicates extends \Civi\Api4\Generic\AbstractAction {
+
+ /**
+ * Main contact to keep with merged values
+ *
+ * @var int
+ * @required
+ */
+ protected $contactId;
+
+ /**
+ * Duplicate contact to delete
+ *
+ * @var int
+ * @required
+ */
+ protected $duplicateId;
+
+ /**
+ * Whether to run in "Safe Mode".
+ *
+ * Safe Mode skips the merge if there are conflicts. Does a force merge otherwise.
+ *
+ * @var string
+ * @required
+ * @options safe,aggressive
+ */
+ protected $mode = 'safe';
+
+ /**
+ * @param \Civi\Api4\Generic\Result $result
+ */
+ public function _run(Result $result) {
+ $merge = \CRM_Dedupe_Merger::merge(
+ [['srcID' => $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 [];
+ }
+
+}
->setCheckPermissions($checkPermissions);
}
+ /**
+ * @param bool $checkPermissions
+ * @return Action\Contact\MergeDuplicates
+ */
+ public static function mergeDuplicates($checkPermissions = TRUE) {
+ return (new Action\Contact\MergeDuplicates(__CLASS__, __FUNCTION__))
+ ->setCheckPermissions($checkPermissions);
+ }
+
}
}
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';
*
* @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
/**
* @group headless
*/
-class ContactGetDuplicatesTest extends CustomTestBase {
+class ContactDuplicatesTest extends CustomTestBase {
public function testGetDuplicatesUnsupervised() {
$email = uniqid('test@');
$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']);
+ }
+
}