Skip to content

Commit

Permalink
APIv4 - Add Contact::mergeDuplicates action
Browse files Browse the repository at this point in the history
  • Loading branch information
colemanw committed Sep 16, 2022
1 parent 93d5fa6 commit 92400d7
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 4 deletions.
2 changes: 1 addition & 1 deletion Civi/Api4/Action/Contact/GetDuplicates.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

/**
* 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 {
Expand Down
83 changes: 83 additions & 0 deletions Civi/Api4/Action/Contact/MergeDuplicates.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?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 [];
}

}
9 changes: 9 additions & 0 deletions Civi/Api4/Contact.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,13 @@ public static function getDuplicates($checkPermissions = TRUE) {
->setCheckPermissions($checkPermissions);
}

/**
* @param bool $checkPermissions
* @return Action\Contact\MergeDuplicates
*/
public static function mergeDuplicates($checkPermissions = TRUE) {
return (new Action\Contact\MergeDuplicates(__CLASS__, __FUNCTION__))
->setCheckPermissions($checkPermissions);
}

}
6 changes: 6 additions & 0 deletions Civi/Test/Api3TestTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,12 @@ public function runApi4Legacy($v3Entity, $v3Action, $v3Params = []) {
}
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';
Expand Down
4 changes: 2 additions & 2 deletions api/v3/Contact.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
/**
* @group headless
*/
class ContactGetDuplicatesTest extends CustomTestBase {
class ContactDuplicatesTest extends CustomTestBase {

public function testGetDuplicatesUnsupervised() {
$email = uniqid('test@');
Expand Down Expand Up @@ -140,4 +140,43 @@ public function testDedupeWithCustomFields() {
$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']);
}

}

0 comments on commit 92400d7

Please sign in to comment.