4 * Class CRM_Dedupe_DedupeMergerTest
8 class CRM_Dedupe_MergerTest
extends CiviUnitTestCase
{
12 protected $_contactIds = [];
19 public function tearDown() {
22 'civicrm_group_contact',
28 public function createDupeContacts() {
29 // create a group to hold contacts, so that dupe checks don't consider any other contacts in the DB
31 'name' => 'Test Dupe Merger Group',
32 'title' => 'Test Dupe Merger Group',
35 'visibility' => 'Public Pages',
38 $result = $this->callAPISuccess('group', 'create', $params);
39 $this->_groupId
= $result['id'];
43 // make dupe checks based on based on following contact sets:
44 // FIRST - LAST - EMAIL
45 // ---------------------------------
46 // robin - hood - robin@example.com
47 // robin - hood - robin@example.com
48 // robin - hood - hood@example.com
49 // robin - dale - robin@example.com
50 // little - dale - dale@example.com
51 // little - dale - dale@example.com
52 // will - dale - dale@example.com
53 // will - dale - will@example.com
54 // will - dale - will@example.com
57 'first_name' => 'robin',
58 'last_name' => 'hood',
59 'email' => 'robin@example.com',
60 'contact_type' => 'Individual',
63 'first_name' => 'robin',
64 'last_name' => 'hood',
65 'email' => 'robin@example.com',
66 'contact_type' => 'Individual',
69 'first_name' => 'robin',
70 'last_name' => 'hood',
71 'email' => 'hood@example.com',
72 'contact_type' => 'Individual',
75 'first_name' => 'robin',
76 'last_name' => 'dale',
77 'email' => 'robin@example.com',
78 'contact_type' => 'Individual',
81 'first_name' => 'little',
82 'last_name' => 'dale',
83 'email' => 'dale@example.com',
84 'contact_type' => 'Individual',
87 'first_name' => 'little',
88 'last_name' => 'dale',
89 'email' => 'dale@example.com',
90 'contact_type' => 'Individual',
93 'first_name' => 'will',
94 'last_name' => 'dale',
95 'email' => 'dale@example.com',
96 'contact_type' => 'Individual',
99 'first_name' => 'will',
100 'last_name' => 'dale',
101 'email' => 'will@example.com',
102 'contact_type' => 'Individual',
105 'first_name' => 'will',
106 'last_name' => 'dale',
107 'email' => 'will@example.com',
108 'contact_type' => 'Individual',
113 foreach ($params as $param) {
114 $param['version'] = 3;
115 $contact = civicrm_api('contact', 'create', $param);
116 $this->_contactIds
[$count++
] = $contact['id'];
119 'contact_id' => $contact['id'],
120 'group_id' => $this->_groupId
,
123 $this->callAPISuccess('group_contact', 'create', $grpParams);
128 * Delete all created contacts.
130 public function deleteDupeContacts() {
131 foreach ($this->_contactIds
as $contactId) {
132 $this->contactDelete($contactId);
134 $this->groupDelete($this->_groupId
);
138 * Test the batch merge.
140 public function testBatchMergeSelectedDuplicates() {
141 $this->createDupeContacts();
143 // verify that all contacts have been created separately
144 $this->assertEquals(count($this->_contactIds
), 9, 'Check for number of contacts.');
146 $dao = new CRM_Dedupe_DAO_RuleGroup();
147 $dao->contact_type
= 'Individual';
148 $dao->name
= 'IndividualSupervised';
149 $dao->is_default
= 1;
152 $foundDupes = CRM_Dedupe_Finder
::dupesInGroup($dao->id
, $this->_groupId
);
154 // -------------------------------------------------------------------------
155 // Name and Email (reserved) Matches ( 3 pairs )
156 // --------------------------------------------------------------------------
157 // robin - hood - robin@example.com
158 // robin - hood - robin@example.com
159 // little - dale - dale@example.com
160 // little - dale - dale@example.com
161 // will - dale - will@example.com
162 // will - dale - will@example.com
163 // so 3 pairs for - first + last + mail
164 $this->assertEquals(count($foundDupes), 3, 'Check Individual-Supervised dupe rule for dupesInGroup().');
166 // Run dedupe finder as the browser would
167 //avoid invalid key error
168 $_SERVER['REQUEST_METHOD'] = 'GET';
169 $object = new CRM_Contact_Page_DedupeFind();
170 $object->set('gid', $this->_groupId
);
171 $object->set('rgid', $dao->id
);
172 $object->set('action', CRM_Core_Action
::UPDATE
);
173 $object->setEmbedded(TRUE);
176 // Retrieve pairs from prev next cache table
177 $select = ['pn.is_selected' => 'is_selected'];
178 $cacheKeyString = CRM_Dedupe_Merger
::getMergeCacheKeyString($dao->id
, $this->_groupId
);
179 $pnDupePairs = CRM_Core_BAO_PrevNextCache
::retrieve($cacheKeyString, NULL, NULL, 0, 0, $select);
181 $this->assertEquals(count($foundDupes), count($pnDupePairs), 'Check number of dupe pairs in prev next cache.');
183 // mark first two pairs as selected
184 CRM_Core_DAO
::singleValueQuery("UPDATE civicrm_prevnext_cache SET is_selected = 1 WHERE id IN ({$pnDupePairs[0]['prevnext_id']}, {$pnDupePairs[1]['prevnext_id']})");
186 $pnDupePairs = CRM_Core_BAO_PrevNextCache
::retrieve($cacheKeyString, NULL, NULL, 0, 0, $select);
187 $this->assertEquals($pnDupePairs[0]['is_selected'], 1, 'Check if first record in dupe pairs is marked as selected.');
188 $this->assertEquals($pnDupePairs[0]['is_selected'], 1, 'Check if second record in dupe pairs is marked as selected.');
190 // batch merge selected dupes
191 $result = CRM_Dedupe_Merger
::batchMerge($dao->id
, $this->_groupId
, 'safe', 5, 1);
192 $this->assertEquals(count($result['merged']), 2, 'Check number of merged pairs.');
194 // retrieve pairs from prev next cache table
195 $pnDupePairs = CRM_Core_BAO_PrevNextCache
::retrieve($cacheKeyString, NULL, NULL, 0, 0, $select);
196 $this->assertEquals(count($pnDupePairs), 1, 'Check number of remaining dupe pairs in prev next cache.');
198 $this->deleteDupeContacts();
202 * Test the batch merge.
204 public function testBatchMergeAllDuplicates() {
205 $this->createDupeContacts();
207 // verify that all contacts have been created separately
208 $this->assertEquals(count($this->_contactIds
), 9, 'Check for number of contacts.');
210 $dao = new CRM_Dedupe_DAO_RuleGroup();
211 $dao->contact_type
= 'Individual';
212 $dao->name
= 'IndividualSupervised';
213 $dao->is_default
= 1;
216 $foundDupes = CRM_Dedupe_Finder
::dupesInGroup($dao->id
, $this->_groupId
);
218 // -------------------------------------------------------------------------
219 // Name and Email (reserved) Matches ( 3 pairs )
220 // --------------------------------------------------------------------------
221 // robin - hood - robin@example.com
222 // robin - hood - robin@example.com
223 // little - dale - dale@example.com
224 // little - dale - dale@example.com
225 // will - dale - will@example.com
226 // will - dale - will@example.com
227 // so 3 pairs for - first + last + mail
228 $this->assertEquals(count($foundDupes), 3, 'Check Individual-Supervised dupe rule for dupesInGroup().');
230 // Run dedupe finder as the browser would
231 //avoid invalid key error
232 $_SERVER['REQUEST_METHOD'] = 'GET';
233 $object = new CRM_Contact_Page_DedupeFind();
234 $object->set('gid', $this->_groupId
);
235 $object->set('rgid', $dao->id
);
236 $object->set('action', CRM_Core_Action
::UPDATE
);
237 $object->setEmbedded(TRUE);
240 // Retrieve pairs from prev next cache table
241 $select = ['pn.is_selected' => 'is_selected'];
242 $cacheKeyString = CRM_Dedupe_Merger
::getMergeCacheKeyString($dao->id
, $this->_groupId
);
243 $pnDupePairs = CRM_Core_BAO_PrevNextCache
::retrieve($cacheKeyString, NULL, NULL, 0, 0, $select);
245 $this->assertEquals(count($foundDupes), count($pnDupePairs), 'Check number of dupe pairs in prev next cache.');
247 // batch merge all dupes
248 $result = CRM_Dedupe_Merger
::batchMerge($dao->id
, $this->_groupId
, 'safe', 5, 2);
249 $this->assertEquals(count($result['merged']), 3, 'Check number of merged pairs.');
251 // retrieve pairs from prev next cache table
252 $pnDupePairs = CRM_Core_BAO_PrevNextCache
::retrieve($cacheKeyString, NULL, NULL, 0, 0, $select);
253 $this->assertEquals(count($pnDupePairs), 0, 'Check number of remaining dupe pairs in prev next cache.');
255 $this->deleteDupeContacts();
259 * The goal of this function is to test that all required tables are returned.
261 public function testGetCidRefs() {
262 $this->entityCustomGroupWithSingleFieldCreate(__FUNCTION__
, 'Contacts');
263 $this->assertEquals(array_merge($this->getStaticCIDRefs(), $this->getHackedInCIDRef()), CRM_Dedupe_Merger
::cidRefs());
264 $this->assertEquals(array_merge($this->getCalculatedCIDRefs(), $this->getHackedInCIDRef()), CRM_Dedupe_Merger
::cidRefs());
268 * Get the list of not-really-cid-refs that are currently hacked in.
270 * This is hacked into getCIDs function.
274 public function getHackedInCIDRef() {
276 'civicrm_entity_tag' => [
283 * Test function that gets duplicate pairs.
285 * It turns out there are 2 code paths retrieving this data so my initial
286 * focus is on ensuring they match.
288 public function testGetMatches() {
289 $this->setupMatchData();
290 $pairs = CRM_Dedupe_Merger
::getDuplicatePairs(
298 $this->assertEquals([
300 'srcID' => $this->contacts
[1]['id'],
301 'srcName' => 'Mr. Mickey Mouse II',
302 'dstID' => $this->contacts
[0]['id'],
303 'dstName' => 'Mr. Mickey Mouse II',
308 'srcID' => $this->contacts
[3]['id'],
309 'srcName' => 'Mr. Minnie Mouse II',
310 'dstID' => $this->contacts
[2]['id'],
311 'dstName' => 'Mr. Minnie Mouse II',
319 * Test function that gets organization pairs.
321 * Note the rule will match on organization_name OR email - hence lots of
326 public function testGetOrganizationMatches() {
327 $this->setupMatchData();
328 $ruleGroups = $this->callAPISuccessGetSingle('RuleGroup', [
329 'contact_type' => 'Organization',
330 'used' => 'Supervised',
333 $pairs = CRM_Dedupe_Merger
::getDuplicatePairs(
343 'srcID' => $this->contacts
[5]['id'],
344 'srcName' => 'Walt Disney Ltd',
345 'dstID' => $this->contacts
[4]['id'],
346 'dstName' => 'Walt Disney Ltd',
351 'srcID' => $this->contacts
[7]['id'],
352 'srcName' => 'Walt Disney',
353 'dstID' => $this->contacts
[6]['id'],
354 'dstName' => 'Walt Disney',
359 'srcID' => $this->contacts
[6]['id'],
360 'srcName' => 'Walt Disney',
361 'dstID' => $this->contacts
[4]['id'],
362 'dstName' => 'Walt Disney Ltd',
367 'srcID' => $this->contacts
[6]['id'],
368 'srcName' => 'Walt Disney',
369 'dstID' => $this->contacts
[5]['id'],
370 'dstName' => 'Walt Disney Ltd',
375 usort($pairs, [__CLASS__
, 'compareDupes']);
376 usort($expectedPairs, [__CLASS__
, 'compareDupes']);
377 $this->assertEquals($expectedPairs, $pairs);
381 * Function to sort $duplicate records in a stable way.
388 public static function compareDupes($a, $b) {
389 foreach (['srcName', 'dstName', 'srcID', 'dstID'] as $field) {
390 if ($a[$field] != $b[$field]) {
391 return ($a[$field] < $b[$field]) ?
1 : -1;
398 * Test function that gets organization duplicate pairs.
402 public function testGetOrganizationMatchesInGroup() {
403 $this->setupMatchData();
404 $ruleGroups = $this->callAPISuccessGetSingle('RuleGroup', [
405 'contact_type' => 'Organization',
406 'used' => 'Supervised',
409 $groupID = $this->groupCreate(['title' => 'she-mice']);
411 $this->callAPISuccess('GroupContact', 'create', [
412 'group_id' => $groupID,
413 'contact_id' => $this->contacts
[4]['id'],
416 $pairs = CRM_Dedupe_Merger
::getDuplicatePairs(
424 $this->assertEquals([
426 'srcID' => $this->contacts
[5]['id'],
427 'srcName' => 'Walt Disney Ltd',
428 'dstID' => $this->contacts
[4]['id'],
429 'dstName' => 'Walt Disney Ltd',
434 'srcID' => $this->contacts
[6]['id'],
435 'srcName' => 'Walt Disney',
436 'dstID' => $this->contacts
[4]['id'],
437 'dstName' => 'Walt Disney Ltd',
443 $this->callAPISuccess('GroupContact', 'create', [
444 'group_id' => $groupID,
445 'contact_id' => $this->contacts
[5]['id'],
447 CRM_Core_DAO
::executeQuery("DELETE FROM civicrm_prevnext_cache");
448 $pairs = CRM_Dedupe_Merger
::getDuplicatePairs(
456 $this->assertEquals([
458 'srcID' => $this->contacts
[5]['id'],
459 'srcName' => 'Walt Disney Ltd',
460 'dstID' => $this->contacts
[4]['id'],
461 'dstName' => 'Walt Disney Ltd',
466 'srcID' => $this->contacts
[6]['id'],
467 'srcName' => 'Walt Disney',
468 'dstID' => $this->contacts
[4]['id'],
469 'dstName' => 'Walt Disney Ltd',
474 'srcID' => $this->contacts
[6]['id'],
475 'srcName' => 'Walt Disney',
476 'dstID' => $this->contacts
[5]['id'],
477 'dstName' => 'Walt Disney Ltd',
485 * Test function that gets duplicate pairs.
487 * It turns out there are 2 code paths retrieving this data so my initial
488 * focus is on ensuring they match.
490 public function testGetMatchesInGroup() {
491 $this->setupMatchData();
493 $groupID = $this->groupCreate(['title' => 'she-mice']);
495 $this->callAPISuccess('GroupContact', 'create', [
496 'group_id' => $groupID,
497 'contact_id' => $this->contacts
[3]['id'],
500 $pairs = CRM_Dedupe_Merger
::getDuplicatePairs(
508 $this->assertEquals([
510 'srcID' => $this->contacts
[3]['id'],
511 'srcName' => 'Mr. Minnie Mouse II',
512 'dstID' => $this->contacts
[2]['id'],
513 'dstName' => 'Mr. Minnie Mouse II',
521 * Test the special info handling is unchanged after cleanup.
523 * Note the handling is silly - we are testing to lock in over short term
524 * changes not to imply any contract on the function.
526 public function testGetRowsElementsAndInfoSpecialInfo() {
527 $contact1 = $this->individualCreate([
528 'preferred_communication_method' => [],
529 'communication_style_id' => 'Familiar',
530 'prefix_id' => 'Mrs.',
531 'suffix_id' => 'III',
533 $contact2 = $this->individualCreate([
534 'preferred_communication_method' => [
538 'communication_style_id' => 'Formal',
539 'gender_id' => 'Female',
541 $rowsElementsAndInfo = CRM_Dedupe_Merger
::getRowsElementsAndInfo($contact1, $contact2);
542 $rows = $rowsElementsAndInfo['rows'];
543 $this->assertEquals([
546 'title' => 'Individual Prefix',
547 ], $rows['move_prefix_id']);
548 $this->assertEquals([
551 'title' => 'Individual Suffix',
552 ], $rows['move_suffix_id']);
553 $this->assertEquals([
557 ], $rows['move_gender_id']);
558 $this->assertEquals([
559 'main' => 'Familiar',
561 'title' => 'Communication Style',
562 ], $rows['move_communication_style_id']);
563 $this->assertEquals(1, $rowsElementsAndInfo['migration_info']['move_communication_style_id']);
564 $this->assertEquals([
566 'other' => 'SMS, Fax',
567 'title' => 'Preferred Communication Method',
568 ], $rows['move_preferred_communication_method']);
569 $this->assertEquals('\ 14\ 15\ 1', $rowsElementsAndInfo['migration_info']['move_preferred_communication_method']);
573 * Test migration of Membership.
575 public function testMergeMembership() {
577 $this->setupMatchData();
578 $originalContactID = $this->contacts
[0]['id'];
579 $duplicateContactID = $this->contacts
[1]['id'];
581 //Add Membership for the duplicate contact.
582 $memTypeId = $this->membershipTypeCreate();
583 $this->callAPISuccess('Membership', 'create', [
584 'membership_type_id' => $memTypeId,
585 'contact_id' => $duplicateContactID,
587 //Assert if 'add new' checkbox is enabled on the merge form.
588 $rowsElementsAndInfo = CRM_Dedupe_Merger
::getRowsElementsAndInfo($originalContactID, $duplicateContactID);
589 foreach ($rowsElementsAndInfo['elements'] as $element) {
590 if (!empty($element[3]) && $element[3] == 'add new') {
591 $checkedAttr = ['checked' => 'checked'];
592 $this->checkArrayEquals($element[4], $checkedAttr);
596 //Merge and move the mem to the main contact.
597 $this->mergeContacts($originalContactID, $duplicateContactID, [
598 'move_rel_table_memberships' => 1,
599 'operation' => ['move_rel_table_memberships' => ['add' => 1]],
602 //Check if membership is correctly transferred to original contact.
603 $originalContactMembership = $this->callAPISuccess('Membership', 'get', [
604 'membership_type_id' => $memTypeId,
605 'contact_id' => $originalContactID,
607 $this->assertEquals(1, $originalContactMembership['count']);
611 * CRM-19653 : Test that custom field data should/shouldn't be overriden on
612 * selecting/not selecting option to migrate data respectively
614 public function testCustomDataOverwrite() {
615 // Create Custom Field
616 $createGroup = $this->setupCustomGroupForIndividual();
617 $createField = $this->setupCustomField('Graduation', $createGroup);
618 $customFieldName = "custom_" . $createField['id'];
621 $this->setupMatchData();
623 $originalContactID = $this->contacts
[0]['id'];
624 // used as duplicate contact in 1st use-case
625 $duplicateContactID1 = $this->contacts
[1]['id'];
626 // used as duplicate contact in 2nd use-case
627 $duplicateContactID2 = $this->contacts
[2]['id'];
629 // update the text custom field for original contact with value 'abc'
630 $this->callAPISuccess('Contact', 'create', [
631 'id' => $originalContactID,
632 "{$customFieldName}" => 'abc',
634 $this->assertCustomFieldValue($originalContactID, 'abc', $customFieldName);
636 // update the text custom field for duplicate contact 1 with value 'def'
637 $this->callAPISuccess('Contact', 'create', [
638 'id' => $duplicateContactID1,
639 "{$customFieldName}" => 'def',
641 $this->assertCustomFieldValue($duplicateContactID1, 'def', $customFieldName);
643 // update the text custom field for duplicate contact 2 with value 'ghi'
644 $this->callAPISuccess('Contact', 'create', [
645 'id' => $duplicateContactID2,
646 "{$customFieldName}" => 'ghi',
648 $this->assertCustomFieldValue($duplicateContactID2, 'ghi', $customFieldName);
650 /*** USE-CASE 1: DO NOT OVERWRITE CUSTOM FIELD VALUE **/
651 $this->mergeContacts($originalContactID, $duplicateContactID1, [
652 "move_{$customFieldName}" => NULL,
654 $this->assertCustomFieldValue($originalContactID, 'abc', $customFieldName);
656 /*** USE-CASE 2: OVERWRITE CUSTOM FIELD VALUE **/
657 $this->mergeContacts($originalContactID, $duplicateContactID2, [
658 "move_{$customFieldName}" => 'ghi',
660 $this->assertCustomFieldValue($originalContactID, 'ghi', $customFieldName);
662 // cleanup created custom set
663 $this->callAPISuccess('CustomField', 'delete', ['id' => $createField['id']]);
664 $this->callAPISuccess('CustomGroup', 'delete', ['id' => $createGroup['id']]);
668 * Verifies that when a contact with a custom field value is merged into a
669 * contact without a record int its corresponding custom group table, and none
670 * of the custom fields of that custom table are selected, the value is not
673 public function testMigrationOfUnselectedCustomDataOnEmptyCustomRecord() {
674 // Create Custom Fields
675 $createGroup = $this->setupCustomGroupForIndividual();
676 $customField1 = $this->setupCustomField('TestField', $createGroup);
678 // Create multi-value custom field
679 $multiGroup = $this->CustomGroupMultipleCreateByParams();
680 $multiField = $this->customFieldCreate([
681 'custom_group_id' => $multiGroup['id'],
682 'label' => 'field_1' . $multiGroup['id'],
687 $this->setupMatchData();
688 $originalContactID = $this->contacts
[0]['id'];
689 $duplicateContactID = $this->contacts
[1]['id'];
691 // Update the text custom fields for duplicate contact
692 $this->callAPISuccess('Contact', 'create', [
693 'id' => $duplicateContactID,
694 "custom_{$customField1['id']}" => 'abc',
695 "custom_{$multiField['id']}" => 'def',
697 $this->assertCustomFieldValue($duplicateContactID, 'abc', "custom_{$customField1['id']}");
698 $this->assertCustomFieldValue($duplicateContactID, 'def', "custom_{$multiField['id']}");
700 // Merge, and ensure that no value was migrated
701 $this->mergeContacts($originalContactID, $duplicateContactID, [
702 "move_custom_{$customField1['id']}" => NULL,
703 "move_rel_table_custom_{$multiGroup['id']}" => NULL,
705 $this->assertCustomFieldValue($originalContactID, '', "custom_{$customField1['id']}");
706 $this->assertCustomFieldValue($originalContactID, '', "custom_{$multiField['id']}");
708 // cleanup created custom set
709 $this->callAPISuccess('CustomField', 'delete', ['id' => $customField1['id']]);
710 $this->callAPISuccess('CustomGroup', 'delete', ['id' => $createGroup['id']]);
711 $this->callAPISuccess('CustomField', 'delete', ['id' => $multiField['id']]);
712 $this->callAPISuccess('CustomGroup', 'delete', ['id' => $multiGroup['id']]);
716 * Tests that if only part of the custom fields of a custom group are selected
717 * for a merge, only those values are merged, while all other fields of the
718 * custom group retain their original value, specifically for a contact with
719 * no records on the custom group table.
721 public function testMigrationOfSomeCustomDataOnEmptyCustomRecord() {
722 // Create Custom Fields
723 $createGroup = $this->setupCustomGroupForIndividual();
724 $customField1 = $this->setupCustomField('Test1', $createGroup);
725 $customField2 = $this->setupCustomField('Test2', $createGroup);
727 // Create multi-value custom field
728 $multiGroup = $this->CustomGroupMultipleCreateByParams();
729 $multiField = $this->customFieldCreate([
730 'custom_group_id' => $multiGroup['id'],
731 'label' => 'field_1' . $multiGroup['id'],
736 $this->setupMatchData();
737 $originalContactID = $this->contacts
[0]['id'];
738 $duplicateContactID = $this->contacts
[1]['id'];
740 // Update the text custom fields for duplicate contact
741 $this->callAPISuccess('Contact', 'create', [
742 'id' => $duplicateContactID,
743 "custom_{$customField1['id']}" => 'abc',
744 "custom_{$customField2['id']}" => 'def',
745 "custom_{$multiField['id']}" => 'ghi',
747 $this->assertCustomFieldValue($duplicateContactID, 'abc', "custom_{$customField1['id']}");
748 $this->assertCustomFieldValue($duplicateContactID, 'def', "custom_{$customField2['id']}");
749 $this->assertCustomFieldValue($duplicateContactID, 'ghi', "custom_{$multiField['id']}");
752 $this->mergeContacts($originalContactID, $duplicateContactID, [
753 "move_custom_{$customField1['id']}" => NULL,
754 "move_custom_{$customField2['id']}" => 'def',
755 "move_rel_table_custom_{$multiGroup['id']}" => '1',
757 $this->assertCustomFieldValue($originalContactID, '', "custom_{$customField1['id']}");
758 $this->assertCustomFieldValue($originalContactID, 'def', "custom_{$customField2['id']}");
759 $this->assertCustomFieldValue($originalContactID, 'ghi', "custom_{$multiField['id']}");
761 // cleanup created custom set
762 $this->callAPISuccess('CustomField', 'delete', ['id' => $customField1['id']]);
763 $this->callAPISuccess('CustomField', 'delete', ['id' => $customField2['id']]);
764 $this->callAPISuccess('CustomGroup', 'delete', ['id' => $createGroup['id']]);
765 $this->callAPISuccess('CustomField', 'delete', ['id' => $multiField['id']]);
766 $this->callAPISuccess('CustomGroup', 'delete', ['id' => $multiGroup['id']]);
770 * Calls merge method on given contacts, with values given in $params array.
772 * @param $originalContactID
773 * ID of target contact
774 * @param $duplicateContactID
775 * ID of contact to be merged
777 * Array of fields to be merged from source into target contact, of the form
778 * ['move_<fieldName>' => <fieldValue>]
780 * @throws \CRM_Core_Exception
781 * @throws \CiviCRM_API3_Exception
783 private function mergeContacts($originalContactID, $duplicateContactID, $params) {
784 $rowsElementsAndInfo = CRM_Dedupe_Merger
::getRowsElementsAndInfo($originalContactID, $duplicateContactID);
787 'main_details' => $rowsElementsAndInfo['main_details'],
788 'other_details' => $rowsElementsAndInfo['other_details'],
791 // Migrate data of duplicate contact
792 CRM_Dedupe_Merger
::moveAllBelongings($originalContactID, $duplicateContactID, array_merge($migrationData, $params));
796 * Checks if the expected value for the given field corresponds to what is
797 * stored in the database for the given contact ID.
800 * @param $expectedValue
801 * @param $customFieldName
803 private function assertCustomFieldValue($contactID, $expectedValue, $customFieldName) {
804 $data = $this->callAPISuccess('Contact', 'getsingle', [
806 'return' => [$customFieldName],
809 $this->assertEquals($expectedValue, $data[$customFieldName], "Custom field value was supposed to be '{$expectedValue}', '{$data[$customFieldName]}' found.");
813 * Creates a custom group to run tests on contacts that are individuals.
816 * Data for the created custom group record
818 private function setupCustomGroupForIndividual() {
819 $customGroup = $this->callAPISuccess('custom_group', 'get', [
820 'name' => 'test_group',
823 if ($customGroup['count'] > 0) {
824 $this->callAPISuccess('CustomGroup', 'delete', ['id' => $customGroup['id']]);
827 $customGroup = $this->callAPISuccess('custom_group', 'create', [
828 'title' => 'Test_Group',
829 'name' => 'test_group',
830 'extends' => ['Individual'],
832 'is_multiple' => FALSE,
840 * Creates a custom field on the provided custom group with the given field
844 * @param $createGroup
847 * Data for the created custom field record
849 private function setupCustomField($fieldLabel, $createGroup) {
850 return $this->callAPISuccess('custom_field', 'create', [
851 'label' => $fieldLabel,
852 'data_type' => 'Alphanumeric',
853 'html_type' => 'Text',
854 'custom_group_id' => $createGroup['id'],
859 * Set up some contacts for our matching.
861 public function setupMatchData() {
864 'first_name' => 'Mickey',
865 'last_name' => 'Mouse',
866 'email' => 'mickey@mouse.com',
869 'first_name' => 'Mickey',
870 'last_name' => 'Mouse',
871 'email' => 'mickey@mouse.com',
874 'first_name' => 'Minnie',
875 'last_name' => 'Mouse',
876 'email' => 'mickey@mouse.com',
879 'first_name' => 'Minnie',
880 'last_name' => 'Mouse',
881 'email' => 'mickey@mouse.com',
884 foreach ($fixtures as $fixture) {
885 $contactID = $this->individualCreate($fixture);
886 $this->contacts
[] = array_merge($fixture, ['id' => $contactID]);
888 $organizationFixtures = [
890 'organization_name' => 'Walt Disney Ltd',
891 'email' => 'walt@disney.com',
894 'organization_name' => 'Walt Disney Ltd',
895 'email' => 'walt@disney.com',
898 'organization_name' => 'Walt Disney',
899 'email' => 'walt@disney.com',
902 'organization_name' => 'Walt Disney',
903 'email' => 'walter@disney.com',
906 foreach ($organizationFixtures as $fixture) {
907 $contactID = $this->organizationCreate($fixture);
908 $this->contacts
[] = array_merge($fixture, ['id' => $contactID]);
913 * Get the list of tables that refer to the CID.
915 * This is a statically maintained (in this test list).
917 * There is also a check against an automated list but having both seems to
918 * add extra stability to me. They do not change often.
920 public function getStaticCIDRefs() {
922 'civicrm_acl_cache' => [
925 'civicrm_acl_contact_cache' => [
928 'civicrm_action_log' => [
931 'civicrm_activity_contact' => [
934 'civicrm_address' => [
941 'civicrm_campaign' => [
943 1 => 'last_modified_id',
945 'civicrm_case_contact' => [
948 'civicrm_contact' => [
949 0 => 'primary_contact_id',
952 'civicrm_contribution' => [
955 'civicrm_contribution_page' => [
958 'civicrm_contribution_recur' => [
961 'civicrm_contribution_soft' => [
964 'civicrm_custom_group' => [
967 'civicrm_dashboard_contact' => [
970 'civicrm_dedupe_exception' => [
974 'civicrm_domain' => [
983 'civicrm_event_carts' => [
986 'civicrm_financial_account' => [
989 'civicrm_financial_item' => [
999 'civicrm_group_contact' => [
1002 'civicrm_group_contact_cache' => [
1005 'civicrm_group_organization' => [
1006 0 => 'organization_id',
1014 'civicrm_mailing' => [
1016 1 => 'scheduled_id',
1022 'civicrm_mailing_abtest' => [
1025 'civicrm_mailing_event_queue' => [
1028 'civicrm_mailing_event_subscribe' => [
1031 'civicrm_mailing_recipients' => [
1034 'civicrm_membership' => [
1037 'civicrm_membership_log' => [
1040 'civicrm_membership_type' => [
1041 0 => 'member_of_contact_id',
1046 'civicrm_openid' => [
1049 'civicrm_participant' => [
1052 1 => 'transferred_to_contact_id',
1054 'civicrm_payment_token' => [
1061 'civicrm_phone' => [
1064 'civicrm_pledge' => [
1067 'civicrm_print_label' => [
1070 'civicrm_relationship' => [
1071 0 => 'contact_id_a',
1072 1 => 'contact_id_b',
1074 'civicrm_report_instance' => [
1078 'civicrm_setting' => [
1082 'civicrm_subscription_history' => [
1085 'civicrm_survey' => [
1087 1 => 'last_modified_id',
1092 'civicrm_uf_group' => [
1095 'civicrm_uf_match' => [
1098 'civicrm_value_testgetcidref_1' => [
1101 'civicrm_website' => [
1108 * Get a list of CIDs that is calculated off the schema.
1110 * Note this is an expensive and table locking query. Should be safe in tests
1113 public function getCalculatedCIDRefs() {
1119 FROM information_schema.key_column_usage
1121 referenced_table_schema = database() AND
1122 referenced_table_name = 'civicrm_contact' AND
1123 referenced_column_name = 'id';
1125 $dao = CRM_Core_DAO
::executeQuery($sql);
1126 while ($dao->fetch()) {
1127 $cidRefs[$dao->table_name
][] = $dao->column_name
;
1129 // Do specific re-ordering changes to make this the same as the ref validated one.
1130 // The above query orders by FK alphabetically.
1131 // There might be cleverer ways to do this but it shouldn't change much.
1132 $cidRefs['civicrm_contact'][0] = 'primary_contact_id';
1133 $cidRefs['civicrm_contact'][1] = 'employer_id';
1134 $cidRefs['civicrm_acl_contact_cache'][0] = 'contact_id';
1135 $cidRefs['civicrm_mailing'][0] = 'created_id';
1136 $cidRefs['civicrm_mailing'][1] = 'scheduled_id';
1137 $cidRefs['civicrm_mailing'][2] = 'approver_id';