Merge pull request #17446 from Edzelopez/fix-hideshow-receivedate
[civicrm-core.git] / tests / phpunit / CRM / Dedupe / MergerTest.php
1 <?php
2
3 /**
4 * Class CRM_Dedupe_DedupeMergerTest
5 *
6 * @group headless
7 */
8 class CRM_Dedupe_MergerTest extends CiviUnitTestCase {
9
10 protected $_groupId;
11
12 protected $_contactIds = [];
13
14 /**
15 * Contacts created for the test.
16 *
17 * Overlaps contactIds....
18 *
19 * @var array
20 */
21 protected $contacts = [];
22
23 /**
24 * Tear down.
25 *
26 * @throws \Exception
27 */
28 public function tearDown() {
29 $this->quickCleanup([
30 'civicrm_contact',
31 'civicrm_group_contact',
32 'civicrm_group',
33 'civicrm_prevnext_cache',
34 ]);
35 parent::tearDown();
36 }
37
38 /**
39 * @throws \CRM_Core_Exception
40 */
41 public function createDupeContacts() {
42 // create a group to hold contacts, so that dupe checks don't consider any other contacts in the DB
43 $params = [
44 'name' => 'Test Dupe Merger Group',
45 'title' => 'Test Dupe Merger Group',
46 'domain_id' => 1,
47 'is_active' => 1,
48 'visibility' => 'Public Pages',
49 ];
50
51 $result = $this->callAPISuccess('group', 'create', $params);
52 $this->_groupId = $result['id'];
53
54 // contact data set
55
56 // make dupe checks based on based on following contact sets:
57 // FIRST - LAST - EMAIL
58 // ---------------------------------
59 // robin - hood - robin@example.com
60 // robin - hood - robin@example.com
61 // robin - hood - hood@example.com
62 // robin - dale - robin@example.com
63 // little - dale - dale@example.com
64 // little - dale - dale@example.com
65 // will - dale - dale@example.com
66 // will - dale - will@example.com
67 // will - dale - will@example.com
68 $params = [
69 [
70 'first_name' => 'robin',
71 'last_name' => 'hood',
72 'email' => 'robin@example.com',
73 'contact_type' => 'Individual',
74 ],
75 [
76 'first_name' => 'robin',
77 'last_name' => 'hood',
78 'email' => 'robin@example.com',
79 'contact_type' => 'Individual',
80 ],
81 [
82 'first_name' => 'robin',
83 'last_name' => 'hood',
84 'email' => 'hood@example.com',
85 'contact_type' => 'Individual',
86 ],
87 [
88 'first_name' => 'robin',
89 'last_name' => 'dale',
90 'email' => 'robin@example.com',
91 'contact_type' => 'Individual',
92 ],
93 [
94 'first_name' => 'little',
95 'last_name' => 'dale',
96 'email' => 'dale@example.com',
97 'contact_type' => 'Individual',
98 ],
99 [
100 'first_name' => 'little',
101 'last_name' => 'dale',
102 'email' => 'dale@example.com',
103 'contact_type' => 'Individual',
104 ],
105 [
106 'first_name' => 'will',
107 'last_name' => 'dale',
108 'email' => 'dale@example.com',
109 'contact_type' => 'Individual',
110 ],
111 [
112 'first_name' => 'will',
113 'last_name' => 'dale',
114 'email' => 'will@example.com',
115 'contact_type' => 'Individual',
116 ],
117 [
118 'first_name' => 'will',
119 'last_name' => 'dale',
120 'email' => 'will@example.com',
121 'contact_type' => 'Individual',
122 ],
123 ];
124
125 $count = 1;
126 foreach ($params as $param) {
127 $param['version'] = 3;
128 $contact = civicrm_api('contact', 'create', $param);
129 $this->_contactIds[$count++] = $contact['id'];
130
131 $grpParams = [
132 'contact_id' => $contact['id'],
133 'group_id' => $this->_groupId,
134 'version' => 3,
135 ];
136 $this->callAPISuccess('group_contact', 'create', $grpParams);
137 }
138 }
139
140 /**
141 * Delete all created contacts.
142 */
143 public function deleteDupeContacts() {
144 foreach ($this->_contactIds as $contactId) {
145 $this->contactDelete($contactId);
146 }
147 $this->groupDelete($this->_groupId);
148 }
149
150 /**
151 * Test the batch merge.
152 *
153 * @throws \CRM_Core_Exception
154 */
155 public function testBatchMergeSelectedDuplicates() {
156 $this->createDupeContacts();
157
158 // verify that all contacts have been created separately
159 $this->assertEquals(count($this->_contactIds), 9, 'Check for number of contacts.');
160
161 $dao = new CRM_Dedupe_DAO_RuleGroup();
162 $dao->contact_type = 'Individual';
163 $dao->name = 'IndividualSupervised';
164 $dao->is_default = 1;
165 $dao->find(TRUE);
166
167 $foundDupes = CRM_Dedupe_Finder::dupesInGroup($dao->id, $this->_groupId);
168
169 // -------------------------------------------------------------------------
170 // Name and Email (reserved) Matches ( 3 pairs )
171 // --------------------------------------------------------------------------
172 // robin - hood - robin@example.com
173 // robin - hood - robin@example.com
174 // little - dale - dale@example.com
175 // little - dale - dale@example.com
176 // will - dale - will@example.com
177 // will - dale - will@example.com
178 // so 3 pairs for - first + last + mail
179 $this->assertEquals(count($foundDupes), 3, 'Check Individual-Supervised dupe rule for dupesInGroup().');
180
181 // Run dedupe finder as the browser would
182 //avoid invalid key error
183 $_SERVER['REQUEST_METHOD'] = 'GET';
184 $object = new CRM_Contact_Page_DedupeFind();
185 $object->set('gid', $this->_groupId);
186 $object->set('rgid', $dao->id);
187 $object->set('action', CRM_Core_Action::UPDATE);
188 $object->setEmbedded(TRUE);
189 @$object->run();
190
191 // Retrieve pairs from prev next cache table
192 $select = ['pn.is_selected' => 'is_selected'];
193 $cacheKeyString = CRM_Dedupe_Merger::getMergeCacheKeyString($dao->id, $this->_groupId, [], TRUE, 0);
194 $pnDupePairs = CRM_Core_BAO_PrevNextCache::retrieve($cacheKeyString, NULL, NULL, 0, 0, $select);
195 $this->assertEquals(count($foundDupes), count($pnDupePairs), 'Check number of dupe pairs in prev next cache.');
196
197 // mark first two pairs as selected
198 CRM_Core_DAO::singleValueQuery("UPDATE civicrm_prevnext_cache SET is_selected = 1 WHERE id IN ({$pnDupePairs[0]['prevnext_id']}, {$pnDupePairs[1]['prevnext_id']})");
199
200 $pnDupePairs = CRM_Core_BAO_PrevNextCache::retrieve($cacheKeyString, NULL, NULL, 0, 0, $select);
201 $this->assertEquals($pnDupePairs[0]['is_selected'], 1, 'Check if first record in dupe pairs is marked as selected.');
202 $this->assertEquals($pnDupePairs[0]['is_selected'], 1, 'Check if second record in dupe pairs is marked as selected.');
203
204 // batch merge selected dupes
205 $result = CRM_Dedupe_Merger::batchMerge($dao->id, $this->_groupId, 'safe', 5, 1);
206 $this->assertEquals(count($result['merged']), 2, 'Check number of merged pairs.');
207
208 $stats = $this->callAPISuccess('Dedupe', 'getstatistics', [
209 'group_id' => $this->_groupId,
210 'rule_group_id' => $dao->id,
211 'check_permissions' => TRUE,
212 ])['values'];
213 $this->assertEquals(['merged' => 2, 'skipped' => 0], $stats);
214
215 // retrieve pairs from prev next cache table
216 $pnDupePairs = CRM_Core_BAO_PrevNextCache::retrieve($cacheKeyString, NULL, NULL, 0, 0, $select);
217 $this->assertEquals(count($pnDupePairs), 1, 'Check number of remaining dupe pairs in prev next cache.');
218
219 $this->deleteDupeContacts();
220 }
221
222 /**
223 * Test the batch merge.
224 */
225 public function testBatchMergeAllDuplicates() {
226 $this->createDupeContacts();
227
228 // verify that all contacts have been created separately
229 $this->assertEquals(count($this->_contactIds), 9, 'Check for number of contacts.');
230
231 $dao = new CRM_Dedupe_DAO_RuleGroup();
232 $dao->contact_type = 'Individual';
233 $dao->name = 'IndividualSupervised';
234 $dao->is_default = 1;
235 $dao->find(TRUE);
236
237 $foundDupes = CRM_Dedupe_Finder::dupesInGroup($dao->id, $this->_groupId);
238
239 // -------------------------------------------------------------------------
240 // Name and Email (reserved) Matches ( 3 pairs )
241 // --------------------------------------------------------------------------
242 // robin - hood - robin@example.com
243 // robin - hood - robin@example.com
244 // little - dale - dale@example.com
245 // little - dale - dale@example.com
246 // will - dale - will@example.com
247 // will - dale - will@example.com
248 // so 3 pairs for - first + last + mail
249 $this->assertEquals(count($foundDupes), 3, 'Check Individual-Supervised dupe rule for dupesInGroup().');
250
251 // Run dedupe finder as the browser would
252 //avoid invalid key error
253 $_SERVER['REQUEST_METHOD'] = 'GET';
254 $object = new CRM_Contact_Page_DedupeFind();
255 $object->set('gid', $this->_groupId);
256 $object->set('rgid', $dao->id);
257 $object->set('action', CRM_Core_Action::UPDATE);
258 $object->setEmbedded(TRUE);
259 @$object->run();
260
261 // Retrieve pairs from prev next cache table
262 $select = ['pn.is_selected' => 'is_selected'];
263 $cacheKeyString = CRM_Dedupe_Merger::getMergeCacheKeyString($dao->id, $this->_groupId, [], TRUE, 0);
264 $pnDupePairs = CRM_Core_BAO_PrevNextCache::retrieve($cacheKeyString, NULL, NULL, 0, 0, $select);
265
266 $this->assertEquals(count($foundDupes), count($pnDupePairs), 'Check number of dupe pairs in prev next cache.');
267
268 // batch merge all dupes
269 $result = CRM_Dedupe_Merger::batchMerge($dao->id, $this->_groupId, 'safe', 5, 2);
270 $this->assertEquals(count($result['merged']), 3, 'Check number of merged pairs.');
271
272 $stats = $this->callAPISuccess('Dedupe', 'getstatistics', [
273 'rule_group_id' => $dao->id,
274 'group_id' => $this->_groupId,
275 ]);
276 // retrieve pairs from prev next cache table
277 $pnDupePairs = CRM_Core_BAO_PrevNextCache::retrieve($cacheKeyString, NULL, NULL, 0, 0, $select);
278 $this->assertEquals(count($pnDupePairs), 0, 'Check number of remaining dupe pairs in prev next cache.');
279
280 $this->deleteDupeContacts();
281 }
282
283 /**
284 * The goal of this function is to test that all required tables are returned.
285 */
286 public function testGetCidRefs() {
287 $this->entityCustomGroupWithSingleFieldCreate(__FUNCTION__, 'Contacts');
288 $this->assertEquals($this->getStaticCIDRefs(), CRM_Dedupe_Merger::cidRefs());
289 $this->assertEquals($this->getCalculatedCIDRefs(), CRM_Dedupe_Merger::cidRefs());
290 }
291
292 /**
293 * Test function that gets duplicate pairs.
294 *
295 * It turns out there are 2 code paths retrieving this data so my initial
296 * focus is on ensuring they match.
297 *
298 * @throws \CRM_Core_Exception
299 */
300 public function testGetMatches() {
301 $this->setupMatchData();
302
303 $pairs = $this->callAPISuccess('Dedupe', 'getduplicates', [
304 'rule_group_id' => 1,
305 ])['values'];
306 $this->assertEquals([
307 0 => [
308 'srcID' => $this->contacts[1]['id'],
309 'srcName' => 'Mr. Mickey Mouse II',
310 'dstID' => $this->contacts[0]['id'],
311 'dstName' => 'Mr. Mickey Mouse II',
312 'weight' => 20,
313 'canMerge' => TRUE,
314 ],
315 1 => [
316 'srcID' => $this->contacts[3]['id'],
317 'srcName' => 'Mr. Minnie Mouse II',
318 'dstID' => $this->contacts[2]['id'],
319 'dstName' => 'Mr. Minnie Mouse II',
320 'weight' => 20,
321 'canMerge' => TRUE,
322 ],
323 ], $pairs);
324 }
325
326 /**
327 * Test results are returned when criteria are passed in.
328 *
329 * @throws \CRM_Core_Exception
330 */
331 public function testGetMatchesCriteriaMatched() {
332 $this->setupMatchData();
333 $pairs = $this->callAPISuccess('Dedupe', 'getduplicates', [
334 'rule_group_id' => 1,
335 'criteria' => ['contact' => ['id' => ['>' => 1]]],
336 ])['values'];
337 $this->assertCount(2, $pairs);
338 }
339
340 /**
341 * Test results are returned when criteria are passed in & limit is respected.
342 *
343 * @throws \CRM_Core_Exception
344 */
345 public function testGetMatchesCriteriaMatchedWithLimit() {
346 $this->setupMatchData();
347 $pairs = $this->callAPISuccess('Dedupe', 'getduplicates', [
348 'rule_group_id' => 1,
349 'criteria' => ['contact' => ['id' => ['>' => 1]]],
350 'options' => ['limit' => 1],
351 ])['values'];
352 $this->assertCount(1, $pairs);
353 }
354
355 /**
356 * Test results are returned when criteria are passed in & limit is respected.
357 *
358 * @throws \CRM_Core_Exception
359 */
360 public function testGetMatchesCriteriaMatchedWithSearchLimit() {
361 $this->setupMatchData();
362 $pairs = $this->callAPISuccess('Dedupe', 'getduplicates', [
363 'rule_group_id' => 1,
364 'criteria' => ['contact' => ['id' => ['>' => 1]]],
365 'search_limit' => 1,
366 ])['values'];
367 $this->assertCount(1, $pairs);
368 }
369
370 /**
371 * Test getting matches where there are no criteria.
372 *
373 * @throws \CRM_Core_Exception
374 */
375 public function testGetMatchesNoCriteria() {
376 $this->setupMatchData();
377 $pairs = $this->callAPISuccess('Dedupe', 'getduplicates', [
378 'rule_group_id' => 1,
379 ])['values'];
380 $this->assertCount(2, $pairs);
381 }
382
383 /**
384 * Test getting matches with a limit in play.
385 *
386 * @throws \CRM_Core_Exception
387 */
388 public function testGetMatchesNoCriteriaButLimit() {
389 $this->setupMatchData();
390 $pairs = $this->callAPISuccess('Dedupe', 'getduplicates', [
391 'rule_group_id' => 1,
392 'options' => ['limit' => 1],
393 ])['values'];
394 $this->assertCount(1, $pairs);
395 }
396
397 /**
398 * Test that if criteria are passed and there are no matching contacts no matches are returned.
399 */
400 public function testGetMatchesCriteriaNotMatched() {
401 $this->setupMatchData();
402 $pairs = $this->callAPISuccess('Dedupe', 'getduplicates', [
403 'rule_group_id' => 1,
404 'criteria' => ['contact' => ['id' => ['>' => 100000]]],
405 ])['values'];
406 $this->assertCount(0, $pairs);
407 }
408
409 /**
410 * Test function that gets organization pairs.
411 *
412 * Note the rule will match on organization_name OR email - hence lots of
413 * matches.
414 *
415 * @throws \Exception
416 */
417 public function testGetOrganizationMatches() {
418 $this->setupMatchData();
419 $ruleGroups = $this->callAPISuccessGetSingle('RuleGroup', [
420 'contact_type' => 'Organization',
421 'used' => 'Supervised',
422 ]);
423
424 $pairs = CRM_Dedupe_Merger::getDuplicatePairs(
425 $ruleGroups['id'],
426 NULL,
427 TRUE,
428 25,
429 FALSE
430 );
431
432 $expectedPairs = [
433 0 => [
434 'srcID' => $this->contacts[5]['id'],
435 'srcName' => 'Walt Disney Ltd',
436 'dstID' => $this->contacts[4]['id'],
437 'dstName' => 'Walt Disney Ltd',
438 'weight' => 20,
439 'canMerge' => TRUE,
440 ],
441 1 => [
442 'srcID' => $this->contacts[7]['id'],
443 'srcName' => 'Walt Disney',
444 'dstID' => $this->contacts[6]['id'],
445 'dstName' => 'Walt Disney',
446 'weight' => 10,
447 'canMerge' => TRUE,
448 ],
449 2 => [
450 'srcID' => $this->contacts[6]['id'],
451 'srcName' => 'Walt Disney',
452 'dstID' => $this->contacts[4]['id'],
453 'dstName' => 'Walt Disney Ltd',
454 'weight' => 10,
455 'canMerge' => TRUE,
456 ],
457 3 => [
458 'srcID' => $this->contacts[6]['id'],
459 'srcName' => 'Walt Disney',
460 'dstID' => $this->contacts[5]['id'],
461 'dstName' => 'Walt Disney Ltd',
462 'weight' => 10,
463 'canMerge' => TRUE,
464 ],
465 ];
466 usort($pairs, [__CLASS__, 'compareDupes']);
467 usort($expectedPairs, [__CLASS__, 'compareDupes']);
468 $this->assertEquals($expectedPairs, $pairs);
469 }
470
471 /**
472 * Function to sort $duplicate records in a stable way.
473 *
474 * @param array $a
475 * @param array $b
476 *
477 * @return int
478 */
479 public static function compareDupes($a, $b) {
480 foreach (['srcName', 'dstName', 'srcID', 'dstID'] as $field) {
481 if ($a[$field] != $b[$field]) {
482 return ($a[$field] < $b[$field]) ? 1 : -1;
483 }
484 }
485 return 0;
486 }
487
488 /**
489 * Test function that gets organization duplicate pairs.
490 *
491 * @throws \Exception
492 */
493 public function testGetOrganizationMatchesInGroup() {
494 $this->setupMatchData();
495 $ruleGroups = $this->callAPISuccessGetSingle('RuleGroup', [
496 'contact_type' => 'Organization',
497 'used' => 'Supervised',
498 ]);
499
500 $groupID = $this->groupCreate(['title' => 'she-mice']);
501
502 $this->callAPISuccess('GroupContact', 'create', [
503 'group_id' => $groupID,
504 'contact_id' => $this->contacts[4]['id'],
505 ]);
506
507 $pairs = CRM_Dedupe_Merger::getDuplicatePairs(
508 $ruleGroups['id'],
509 $groupID,
510 TRUE,
511 25,
512 FALSE
513 );
514
515 $this->assertEquals([
516 0 => [
517 'srcID' => $this->contacts[5]['id'],
518 'srcName' => 'Walt Disney Ltd',
519 'dstID' => $this->contacts[4]['id'],
520 'dstName' => 'Walt Disney Ltd',
521 'weight' => 20,
522 'canMerge' => TRUE,
523 ],
524 1 => [
525 'srcID' => $this->contacts[6]['id'],
526 'srcName' => 'Walt Disney',
527 'dstID' => $this->contacts[4]['id'],
528 'dstName' => 'Walt Disney Ltd',
529 'weight' => 10,
530 'canMerge' => TRUE,
531 ],
532 ], $pairs);
533
534 $this->callAPISuccess('GroupContact', 'create', [
535 'group_id' => $groupID,
536 'contact_id' => $this->contacts[5]['id'],
537 ]);
538 CRM_Core_DAO::executeQuery("DELETE FROM civicrm_prevnext_cache");
539 $pairs = CRM_Dedupe_Merger::getDuplicatePairs(
540 $ruleGroups['id'],
541 $groupID,
542 TRUE,
543 25,
544 FALSE
545 );
546
547 $this->assertEquals([
548 0 => [
549 'srcID' => $this->contacts[5]['id'],
550 'srcName' => 'Walt Disney Ltd',
551 'dstID' => $this->contacts[4]['id'],
552 'dstName' => 'Walt Disney Ltd',
553 'weight' => 20,
554 'canMerge' => TRUE,
555 ],
556 1 => [
557 'srcID' => $this->contacts[6]['id'],
558 'srcName' => 'Walt Disney',
559 'dstID' => $this->contacts[4]['id'],
560 'dstName' => 'Walt Disney Ltd',
561 'weight' => 10,
562 'canMerge' => TRUE,
563 ],
564 2 => [
565 'srcID' => $this->contacts[6]['id'],
566 'srcName' => 'Walt Disney',
567 'dstID' => $this->contacts[5]['id'],
568 'dstName' => 'Walt Disney Ltd',
569 'weight' => 10,
570 'canMerge' => TRUE,
571 ],
572 ], $pairs);
573 }
574
575 /**
576 * Test function that gets duplicate pairs.
577 *
578 * It turns out there are 2 code paths retrieving this data so my initial
579 * focus is on ensuring they match.
580 */
581 public function testGetMatchesInGroup() {
582 $this->setupMatchData();
583
584 $groupID = $this->groupCreate(['title' => 'she-mice']);
585
586 $this->callAPISuccess('GroupContact', 'create', [
587 'group_id' => $groupID,
588 'contact_id' => $this->contacts[3]['id'],
589 ]);
590
591 $pairs = CRM_Dedupe_Merger::getDuplicatePairs(
592 1,
593 $groupID,
594 TRUE,
595 25,
596 FALSE
597 );
598
599 $this->assertEquals([
600 0 => [
601 'srcID' => $this->contacts[3]['id'],
602 'srcName' => 'Mr. Minnie Mouse II',
603 'dstID' => $this->contacts[2]['id'],
604 'dstName' => 'Mr. Minnie Mouse II',
605 'weight' => 20,
606 'canMerge' => TRUE,
607 ],
608 ], $pairs);
609 }
610
611 /**
612 * Test the special info handling is unchanged after cleanup.
613 *
614 * Note the handling is silly - we are testing to lock in over short term
615 * changes not to imply any contract on the function.
616 *
617 * @throws \CRM_Core_Exception
618 * @throws \CiviCRM_API3_Exception
619 */
620 public function testGetRowsElementsAndInfoSpecialInfo() {
621 $contact1 = $this->individualCreate([
622 'preferred_communication_method' => [],
623 'communication_style_id' => 'Familiar',
624 'prefix_id' => 'Mrs.',
625 'suffix_id' => 'III',
626 ]);
627 $contact2 = $this->individualCreate([
628 'preferred_communication_method' => [
629 'SMS',
630 'Fax',
631 ],
632 'communication_style_id' => 'Formal',
633 'gender_id' => 'Female',
634 ]);
635 $rowsElementsAndInfo = CRM_Dedupe_Merger::getRowsElementsAndInfo($contact1, $contact2);
636 $rows = $rowsElementsAndInfo['rows'];
637 $this->assertEquals([
638 'main' => 'Mrs.',
639 'other' => 'Mr.',
640 'title' => 'Individual Prefix',
641 ], $rows['move_prefix_id']);
642 $this->assertEquals([
643 'main' => 'III',
644 'other' => 'II',
645 'title' => 'Individual Suffix',
646 ], $rows['move_suffix_id']);
647 $this->assertEquals([
648 'main' => '',
649 'other' => 'Female',
650 'title' => 'Gender',
651 ], $rows['move_gender_id']);
652 $this->assertEquals([
653 'main' => 'Familiar',
654 'other' => 'Formal',
655 'title' => 'Communication Style',
656 ], $rows['move_communication_style_id']);
657 $this->assertEquals(1, $rowsElementsAndInfo['migration_info']['move_communication_style_id']);
658 $this->assertEquals([
659 'main' => '',
660 'other' => 'SMS, Fax',
661 'title' => 'Preferred Communication Method',
662 ], $rows['move_preferred_communication_method']);
663 $this->assertEquals('\ 14\ 15\ 1', $rowsElementsAndInfo['migration_info']['move_preferred_communication_method']);
664 }
665
666 /**
667 * Test migration of Membership.
668 */
669 public function testMergeMembership() {
670 // Contacts setup
671 $this->setupMatchData();
672 $originalContactID = $this->contacts[0]['id'];
673 $duplicateContactID = $this->contacts[1]['id'];
674
675 //Add Membership for the duplicate contact.
676 $memTypeId = $this->membershipTypeCreate();
677 $this->callAPISuccess('Membership', 'create', [
678 'membership_type_id' => $memTypeId,
679 'contact_id' => $duplicateContactID,
680 ]);
681 //Assert if 'add new' checkbox is enabled on the merge form.
682 $rowsElementsAndInfo = CRM_Dedupe_Merger::getRowsElementsAndInfo($originalContactID, $duplicateContactID);
683 foreach ($rowsElementsAndInfo['elements'] as $element) {
684 if (!empty($element[3]) && $element[3] == 'add new') {
685 $checkedAttr = ['checked' => 'checked'];
686 $this->checkArrayEquals($element[4], $checkedAttr);
687 }
688 }
689
690 //Merge and move the mem to the main contact.
691 $this->mergeContacts($originalContactID, $duplicateContactID, [
692 'move_rel_table_memberships' => 1,
693 'operation' => ['move_rel_table_memberships' => ['add' => 1]],
694 ]);
695
696 //Check if membership is correctly transferred to original contact.
697 $originalContactMembership = $this->callAPISuccess('Membership', 'get', [
698 'membership_type_id' => $memTypeId,
699 'contact_id' => $originalContactID,
700 ]);
701 $this->assertEquals(1, $originalContactMembership['count']);
702 }
703
704 /**
705 * CRM-19653 : Test that custom field data should/shouldn't be overriden on
706 * selecting/not selecting option to migrate data respectively
707 *
708 * @throws \CRM_Core_Exception
709 */
710 public function testCustomDataOverwrite() {
711 // Create Custom Field
712 $createGroup = $this->setupCustomGroupForIndividual();
713 $createField = $this->setupCustomField('Graduation', $createGroup);
714 $customFieldName = "custom_" . $createField['id'];
715
716 // Contacts setup
717 $this->setupMatchData();
718
719 $originalContactID = $this->contacts[0]['id'];
720 // used as duplicate contact in 1st use-case
721 $duplicateContactID1 = $this->contacts[1]['id'];
722 // used as duplicate contact in 2nd use-case
723 $duplicateContactID2 = $this->contacts[2]['id'];
724
725 // update the text custom field for original contact with value 'abc'
726 $this->callAPISuccess('Contact', 'create', [
727 'id' => $originalContactID,
728 "{$customFieldName}" => 'abc',
729 ]);
730 $this->assertCustomFieldValue($originalContactID, 'abc', $customFieldName);
731
732 // update the text custom field for duplicate contact 1 with value 'def'
733 $this->callAPISuccess('Contact', 'create', [
734 'id' => $duplicateContactID1,
735 "{$customFieldName}" => 'def',
736 ]);
737 $this->assertCustomFieldValue($duplicateContactID1, 'def', $customFieldName);
738
739 // update the text custom field for duplicate contact 2 with value 'ghi'
740 $this->callAPISuccess('Contact', 'create', [
741 'id' => $duplicateContactID2,
742 "{$customFieldName}" => 'ghi',
743 ]);
744 $this->assertCustomFieldValue($duplicateContactID2, 'ghi', $customFieldName);
745
746 /*** USE-CASE 1: DO NOT OVERWRITE CUSTOM FIELD VALUE **/
747 $this->mergeContacts($originalContactID, $duplicateContactID1, [
748 "move_{$customFieldName}" => NULL,
749 ]);
750 $this->assertCustomFieldValue($originalContactID, 'abc', $customFieldName);
751
752 /*** USE-CASE 2: OVERWRITE CUSTOM FIELD VALUE **/
753 $this->mergeContacts($originalContactID, $duplicateContactID2, [
754 "move_{$customFieldName}" => 'ghi',
755 ]);
756 $this->assertCustomFieldValue($originalContactID, 'ghi', $customFieldName);
757
758 // cleanup created custom set
759 $this->callAPISuccess('CustomField', 'delete', ['id' => $createField['id']]);
760 $this->callAPISuccess('CustomGroup', 'delete', ['id' => $createGroup['id']]);
761 }
762
763 /**
764 * Creatd Date merge cases
765 * @return array
766 */
767 public function createdDateMergeCases() {
768 $cases = [];
769 // Normal pattern merge into the lower id
770 $cases[] = [0, 1];
771 // Check if we flipped the contacts that it still does right thing
772 $cases[] = [1, 0];
773 return $cases;
774 }
775
776 /**
777 * dev/core#996 Ensure that the oldest created date is retained even if duplicates have been flipped
778 *
779 * @dataProvider createdDateMergeCases
780 *
781 * @param $keepContactKey
782 * @param $duplicateContactKey
783 *
784 * @throws \API_Exception
785 * @throws \CRM_Core_Exception
786 * @throws \CiviCRM_API3_Exception
787 * @throws \Civi\API\Exception\UnauthorizedException
788 */
789 public function testCreatedDatePostMerge($keepContactKey, $duplicateContactKey) {
790 $this->setupMatchData();
791 $lowerContactCreatedDate = $this->callAPISuccess('Contact', 'getsingle', [
792 'id' => $this->contacts[0]['id'],
793 'return' => ['created_date'],
794 ])['created_date'];
795 // Assume contats have been flipped in the UL so merging into the higher id
796 $this->mergeContacts($this->contacts[$keepContactKey]['id'], $this->contacts[$duplicateContactKey]['id'], []);
797 $this->assertEquals($lowerContactCreatedDate, $this->callAPISuccess('Contact', 'getsingle', ['id' => $this->contacts[$keepContactKey]['id'], 'return' => ['created_date']])['created_date']);
798 }
799
800 /**
801 * Verifies that when a contact with a custom field value is merged into a
802 * contact without a record int its corresponding custom group table, and none
803 * of the custom fields of that custom table are selected, the value is not
804 * merged in.
805 */
806 public function testMigrationOfUnselectedCustomDataOnEmptyCustomRecord() {
807 // Create Custom Fields
808 $createGroup = $this->setupCustomGroupForIndividual();
809 $customField1 = $this->setupCustomField('TestField', $createGroup);
810
811 // Create multi-value custom field
812 $multiGroup = $this->CustomGroupMultipleCreateByParams();
813 $multiField = $this->customFieldCreate([
814 'custom_group_id' => $multiGroup['id'],
815 'label' => 'field_1' . $multiGroup['id'],
816 'in_selector' => 1,
817 ]);
818
819 // Contacts setup
820 $this->setupMatchData();
821 $originalContactID = $this->contacts[0]['id'];
822 $duplicateContactID = $this->contacts[1]['id'];
823
824 // Update the text custom fields for duplicate contact
825 $this->callAPISuccess('Contact', 'create', [
826 'id' => $duplicateContactID,
827 "custom_{$customField1['id']}" => 'abc',
828 "custom_{$multiField['id']}" => 'def',
829 ]);
830 $this->assertCustomFieldValue($duplicateContactID, 'abc', "custom_{$customField1['id']}");
831 $this->assertCustomFieldValue($duplicateContactID, 'def', "custom_{$multiField['id']}");
832
833 // Merge, and ensure that no value was migrated
834 $this->mergeContacts($originalContactID, $duplicateContactID, [
835 "move_custom_{$customField1['id']}" => NULL,
836 "move_rel_table_custom_{$multiGroup['id']}" => NULL,
837 ]);
838 $this->assertCustomFieldValue($originalContactID, '', "custom_{$customField1['id']}");
839 $this->assertCustomFieldValue($originalContactID, '', "custom_{$multiField['id']}");
840
841 // cleanup created custom set
842 $this->callAPISuccess('CustomField', 'delete', ['id' => $customField1['id']]);
843 $this->callAPISuccess('CustomGroup', 'delete', ['id' => $createGroup['id']]);
844 $this->callAPISuccess('CustomField', 'delete', ['id' => $multiField['id']]);
845 $this->callAPISuccess('CustomGroup', 'delete', ['id' => $multiGroup['id']]);
846 }
847
848 /**
849 * Tests that if only part of the custom fields of a custom group are selected
850 * for a merge, only those values are merged, while all other fields of the
851 * custom group retain their original value, specifically for a contact with
852 * no records on the custom group table.
853 *
854 * @throws \CRM_Core_Exception
855 */
856 public function testMigrationOfSomeCustomDataOnEmptyCustomRecord() {
857 // Create Custom Fields
858 $createGroup = $this->setupCustomGroupForIndividual();
859 $customField1 = $this->setupCustomField('Test1', $createGroup);
860 $customField2 = $this->setupCustomField('Test2', $createGroup);
861
862 // Create multi-value custom field
863 $multiGroup = $this->CustomGroupMultipleCreateByParams();
864 $multiField = $this->customFieldCreate([
865 'custom_group_id' => $multiGroup['id'],
866 'label' => 'field_1' . $multiGroup['id'],
867 'in_selector' => 1,
868 ]);
869
870 // Contacts setup
871 $this->setupMatchData();
872 $originalContactID = $this->contacts[0]['id'];
873 $duplicateContactID = $this->contacts[1]['id'];
874
875 // Update the text custom fields for duplicate contact
876 $this->callAPISuccess('Contact', 'create', [
877 'id' => $duplicateContactID,
878 "custom_{$customField1['id']}" => 'abc',
879 "custom_{$customField2['id']}" => 'def',
880 "custom_{$multiField['id']}" => 'ghi',
881 ]);
882 $this->assertCustomFieldValue($duplicateContactID, 'abc', "custom_{$customField1['id']}");
883 $this->assertCustomFieldValue($duplicateContactID, 'def', "custom_{$customField2['id']}");
884 $this->assertCustomFieldValue($duplicateContactID, 'ghi', "custom_{$multiField['id']}");
885
886 // Perform merge
887 $this->mergeContacts($originalContactID, $duplicateContactID, [
888 "move_custom_{$customField1['id']}" => NULL,
889 "move_custom_{$customField2['id']}" => 'def',
890 "move_rel_table_custom_{$multiGroup['id']}" => '1',
891 ]);
892 $this->assertCustomFieldValue($originalContactID, '', "custom_{$customField1['id']}");
893 $this->assertCustomFieldValue($originalContactID, 'def', "custom_{$customField2['id']}");
894 $this->assertCustomFieldValue($originalContactID, 'ghi', "custom_{$multiField['id']}");
895
896 // cleanup created custom set
897 $this->callAPISuccess('CustomField', 'delete', ['id' => $customField1['id']]);
898 $this->callAPISuccess('CustomField', 'delete', ['id' => $customField2['id']]);
899 $this->callAPISuccess('CustomGroup', 'delete', ['id' => $createGroup['id']]);
900 $this->callAPISuccess('CustomField', 'delete', ['id' => $multiField['id']]);
901 $this->callAPISuccess('CustomGroup', 'delete', ['id' => $multiGroup['id']]);
902 }
903
904 /**
905 * Test that ContactReference fields are updated to point to the main contact
906 * after a merge is performed and the duplicate contact is deleted.
907 *
908 * @throws \API_Exception
909 * @throws \CRM_Core_Exception
910 * @throws \CiviCRM_API3_Exception
911 * @throws \Civi\API\Exception\UnauthorizedException
912 */
913 public function testMigrationOfContactReferenceCustomField() {
914 // Create Custom Fields
915 $contactGroup = $this->setupCustomGroupForIndividual();
916 $activityGroup = $this->customGroupCreate([
917 'name' => 'test_group_activity',
918 'extends' => 'Activity',
919 ]);
920 $refFieldContact = $this->customFieldCreate([
921 'custom_group_id' => $contactGroup['id'],
922 'label' => 'field_1' . $contactGroup['id'],
923 'data_type' => 'ContactReference',
924 'default_value' => NULL,
925 ]);
926 $refFieldActivity = $this->customFieldCreate([
927 'custom_group_id' => $activityGroup['id'],
928 'label' => 'field_1' . $activityGroup['id'],
929 'data_type' => 'ContactReference',
930 'default_value' => NULL,
931 ]);
932
933 // Contacts setup
934 $this->setupMatchData();
935 $originalContactID = $this->contacts[0]['id'];
936 $duplicateContactID = $this->contacts[1]['id'];
937
938 // create a contact that won't be merged but has a ContactReference field
939 // pointing to the duplicate (to be deleted) contact
940 $unrelatedContact = $this->individualCreate([
941 'first_name' => 'Unrelated',
942 'last_name' => 'Contact',
943 'email' => 'unrelated@example.com',
944 "custom_{$refFieldContact['id']}" => $duplicateContactID,
945 ]);
946 // also create an activity with a ContactReference custom field
947 $activity = $this->activityCreate([
948 'target_contact_id' => $unrelatedContact,
949 "custom_{$refFieldActivity['id']}" => $duplicateContactID,
950 ]);
951
952 // verify that the fields were set
953 $this->assertCustomFieldValue($unrelatedContact, $duplicateContactID, "custom_{$refFieldContact['id']}");
954 $this->assertEntityCustomFieldValue('Activity', $activity['id'], $duplicateContactID, "custom_{$refFieldActivity['id']}_id");
955
956 // Perform merge
957 $this->mergeContacts($originalContactID, $duplicateContactID, []);
958
959 // verify that the ContactReference fields were updated to point to the surviving contact post-merge
960 $this->assertCustomFieldValue($unrelatedContact, $originalContactID, "custom_{$refFieldContact['id']}");
961 $this->assertEntityCustomFieldValue('Activity', $activity['id'], $originalContactID, "custom_{$refFieldActivity['id']}_id");
962
963 // cleanup created custom set
964 $this->callAPISuccess('CustomField', 'delete', ['id' => $refFieldContact['id']]);
965 $this->callAPISuccess('CustomGroup', 'delete', ['id' => $contactGroup['id']]);
966 $this->callAPISuccess('CustomField', 'delete', ['id' => $refFieldActivity['id']]);
967 $this->callAPISuccess('CustomGroup', 'delete', ['id' => $activityGroup['id']]);
968 }
969
970 /**
971 * Calls merge method on given contacts, with values given in $params array.
972 *
973 * @param $originalContactID
974 * ID of target contact
975 * @param $duplicateContactID
976 * ID of contact to be merged
977 * @param $params
978 * Array of fields to be merged from source into target contact, of the form
979 * ['move_<fieldName>' => <fieldValue>]
980 *
981 * @throws \API_Exception
982 * @throws \CRM_Core_Exception
983 * @throws \CiviCRM_API3_Exception
984 * @throws \Civi\API\Exception\UnauthorizedException
985 */
986 private function mergeContacts($originalContactID, $duplicateContactID, $params) {
987 $rowsElementsAndInfo = CRM_Dedupe_Merger::getRowsElementsAndInfo($originalContactID, $duplicateContactID);
988
989 $migrationData = [
990 'main_details' => $rowsElementsAndInfo['main_details'],
991 'other_details' => $rowsElementsAndInfo['other_details'],
992 ];
993
994 // Migrate data of duplicate contact
995 CRM_Dedupe_Merger::moveAllBelongings($originalContactID, $duplicateContactID, array_merge($migrationData, $params));
996 }
997
998 /**
999 * Checks if the expected value for the given field corresponds to what is
1000 * stored in the database for the given contact ID.
1001 *
1002 * @param $contactID
1003 * @param $expectedValue
1004 * @param $customFieldName
1005 *
1006 * @throws \CRM_Core_Exception
1007 */
1008 private function assertCustomFieldValue($contactID, $expectedValue, $customFieldName) {
1009 $this->assertEntityCustomFieldValue('Contact', $contactID, $expectedValue, $customFieldName);
1010 }
1011
1012 /**
1013 * Check if the custom field of the given field and entity id matches the
1014 * expected value
1015 *
1016 * @param $entity
1017 * @param $id
1018 * @param $expectedValue
1019 * @param $customFieldName
1020 *
1021 * @throws \CRM_Core_Exception
1022 */
1023 private function assertEntityCustomFieldValue($entity, $id, $expectedValue, $customFieldName) {
1024 $data = $this->callAPISuccess($entity, 'getsingle', [
1025 'id' => $id,
1026 'return' => [$customFieldName],
1027 ]);
1028
1029 $this->assertEquals($expectedValue, $data[$customFieldName], "Custom field value was supposed to be '{$expectedValue}', '{$data[$customFieldName]}' found.");
1030 }
1031
1032 /**
1033 * Creates a custom group to run tests on contacts that are individuals.
1034 *
1035 * @return array
1036 * Data for the created custom group record
1037 * @throws \CRM_Core_Exception
1038 */
1039 private function setupCustomGroupForIndividual() {
1040 $customGroup = $this->callAPISuccess('custom_group', 'get', [
1041 'name' => 'test_group',
1042 ]);
1043
1044 if ($customGroup['count'] > 0) {
1045 $this->callAPISuccess('CustomGroup', 'delete', ['id' => $customGroup['id']]);
1046 }
1047
1048 $customGroup = $this->callAPISuccess('custom_group', 'create', [
1049 'title' => 'Test_Group',
1050 'name' => 'test_group',
1051 'extends' => ['Individual'],
1052 'style' => 'Inline',
1053 'is_multiple' => FALSE,
1054 'is_active' => 1,
1055 ]);
1056
1057 return $customGroup;
1058 }
1059
1060 /**
1061 * Creates a custom field on the provided custom group with the given field
1062 * label.
1063 *
1064 * @param string $fieldLabel
1065 * @param array $createGroup
1066 *
1067 * @return array
1068 * Data for the created custom field record
1069 * @throws \CRM_Core_Exception
1070 */
1071 private function setupCustomField($fieldLabel, $createGroup) {
1072 return $this->callAPISuccess('custom_field', 'create', [
1073 'label' => $fieldLabel,
1074 'data_type' => 'Alphanumeric',
1075 'html_type' => 'Text',
1076 'custom_group_id' => $createGroup['id'],
1077 ]);
1078 }
1079
1080 /**
1081 * Set up some contacts for our matching.
1082 *
1083 * @throws \CRM_Core_Exception
1084 */
1085 public function setupMatchData() {
1086 $fixtures = [
1087 [
1088 'first_name' => 'Mickey',
1089 'last_name' => 'Mouse',
1090 'email' => 'mickey@mouse.com',
1091 ],
1092 [
1093 'first_name' => 'Mickey',
1094 'last_name' => 'Mouse',
1095 'email' => 'mickey@mouse.com',
1096 ],
1097 [
1098 'first_name' => 'Minnie',
1099 'last_name' => 'Mouse',
1100 'email' => 'mickey@mouse.com',
1101 ],
1102 [
1103 'first_name' => 'Minnie',
1104 'last_name' => 'Mouse',
1105 'email' => 'mickey@mouse.com',
1106 ],
1107 ];
1108 foreach ($fixtures as $fixture) {
1109 $contactID = $this->individualCreate($fixture);
1110 $this->contacts[] = array_merge($fixture, ['id' => $contactID]);
1111 sleep(2);
1112 }
1113 $organizationFixtures = [
1114 [
1115 'organization_name' => 'Walt Disney Ltd',
1116 'email' => 'walt@disney.com',
1117 ],
1118 [
1119 'organization_name' => 'Walt Disney Ltd',
1120 'email' => 'walt@disney.com',
1121 ],
1122 [
1123 'organization_name' => 'Walt Disney',
1124 'email' => 'walt@disney.com',
1125 ],
1126 [
1127 'organization_name' => 'Walt Disney',
1128 'email' => 'walter@disney.com',
1129 ],
1130 ];
1131 foreach ($organizationFixtures as $fixture) {
1132 $contactID = $this->organizationCreate($fixture);
1133 $this->contacts[] = array_merge($fixture, ['id' => $contactID]);
1134 }
1135 }
1136
1137 /**
1138 * Get the list of tables that refer to the CID.
1139 *
1140 * This is a statically maintained (in this test list).
1141 *
1142 * There is also a check against an automated list but having both seems to
1143 * add extra stability to me. They do not change often.
1144 */
1145 public function getStaticCIDRefs() {
1146 return [
1147 'civicrm_acl_cache' => [
1148 0 => 'contact_id',
1149 ],
1150 'civicrm_acl_contact_cache' => [
1151 0 => 'contact_id',
1152 ],
1153 'civicrm_action_log' => [
1154 0 => 'contact_id',
1155 ],
1156 'civicrm_activity_contact' => [
1157 0 => 'contact_id',
1158 ],
1159 'civicrm_address' => [
1160 0 => 'contact_id',
1161 ],
1162 'civicrm_batch' => [
1163 0 => 'created_id',
1164 1 => 'modified_id',
1165 ],
1166 'civicrm_campaign' => [
1167 0 => 'created_id',
1168 1 => 'last_modified_id',
1169 ],
1170 'civicrm_case_contact' => [
1171 0 => 'contact_id',
1172 ],
1173 'civicrm_contact' => [
1174 0 => 'primary_contact_id',
1175 1 => 'employer_id',
1176 ],
1177 'civicrm_contribution' => [
1178 0 => 'contact_id',
1179 ],
1180 'civicrm_contribution_page' => [
1181 0 => 'created_id',
1182 ],
1183 'civicrm_contribution_recur' => [
1184 0 => 'contact_id',
1185 ],
1186 'civicrm_contribution_soft' => [
1187 0 => 'contact_id',
1188 ],
1189 'civicrm_custom_group' => [
1190 0 => 'created_id',
1191 ],
1192 'civicrm_dashboard_contact' => [
1193 0 => 'contact_id',
1194 ],
1195 'civicrm_dedupe_exception' => [
1196 0 => 'contact_id1',
1197 1 => 'contact_id2',
1198 ],
1199 'civicrm_domain' => [
1200 0 => 'contact_id',
1201 ],
1202 'civicrm_email' => [
1203 0 => 'contact_id',
1204 ],
1205 'civicrm_event' => [
1206 0 => 'created_id',
1207 ],
1208 'civicrm_event_carts' => [
1209 0 => 'user_id',
1210 ],
1211 'civicrm_financial_account' => [
1212 0 => 'contact_id',
1213 ],
1214 'civicrm_financial_item' => [
1215 0 => 'contact_id',
1216 ],
1217 'civicrm_grant' => [
1218 0 => 'contact_id',
1219 ],
1220 'civicrm_group' => [
1221 0 => 'created_id',
1222 1 => 'modified_id',
1223 ],
1224 'civicrm_group_contact' => [
1225 0 => 'contact_id',
1226 ],
1227 'civicrm_group_contact_cache' => [
1228 0 => 'contact_id',
1229 ],
1230 'civicrm_group_organization' => [
1231 0 => 'organization_id',
1232 ],
1233 'civicrm_im' => [
1234 0 => 'contact_id',
1235 ],
1236 'civicrm_log' => [
1237 0 => 'modified_id',
1238 ],
1239 'civicrm_mailing' => [
1240 0 => 'created_id',
1241 1 => 'scheduled_id',
1242 2 => 'approver_id',
1243 ],
1244 'civicrm_file' => [
1245 'created_id',
1246 ],
1247 'civicrm_mailing_abtest' => [
1248 0 => 'created_id',
1249 ],
1250 'civicrm_mailing_event_queue' => [
1251 0 => 'contact_id',
1252 ],
1253 'civicrm_mailing_event_subscribe' => [
1254 0 => 'contact_id',
1255 ],
1256 'civicrm_mailing_recipients' => [
1257 0 => 'contact_id',
1258 ],
1259 'civicrm_membership' => [
1260 0 => 'contact_id',
1261 ],
1262 'civicrm_membership_log' => [
1263 0 => 'modified_id',
1264 ],
1265 'civicrm_membership_type' => [
1266 0 => 'member_of_contact_id',
1267 ],
1268 'civicrm_note' => [
1269 0 => 'contact_id',
1270 ],
1271 'civicrm_openid' => [
1272 0 => 'contact_id',
1273 ],
1274 'civicrm_participant' => [
1275 0 => 'contact_id',
1276 //CRM-16761
1277 1 => 'transferred_to_contact_id',
1278 ],
1279 'civicrm_payment_token' => [
1280 0 => 'contact_id',
1281 1 => 'created_id',
1282 ],
1283 'civicrm_pcp' => [
1284 0 => 'contact_id',
1285 ],
1286 'civicrm_phone' => [
1287 0 => 'contact_id',
1288 ],
1289 'civicrm_pledge' => [
1290 0 => 'contact_id',
1291 ],
1292 'civicrm_print_label' => [
1293 0 => 'created_id',
1294 ],
1295 'civicrm_relationship' => [
1296 0 => 'contact_id_a',
1297 1 => 'contact_id_b',
1298 ],
1299 'civicrm_report_instance' => [
1300 0 => 'created_id',
1301 1 => 'owner_id',
1302 ],
1303 'civicrm_setting' => [
1304 0 => 'contact_id',
1305 1 => 'created_id',
1306 ],
1307 'civicrm_subscription_history' => [
1308 0 => 'contact_id',
1309 ],
1310 'civicrm_survey' => [
1311 0 => 'created_id',
1312 1 => 'last_modified_id',
1313 ],
1314 'civicrm_tag' => [
1315 0 => 'created_id',
1316 ],
1317 'civicrm_uf_group' => [
1318 0 => 'created_id',
1319 ],
1320 'civicrm_uf_match' => [
1321 0 => 'contact_id',
1322 ],
1323 'civicrm_value_testgetcidref_1' => [
1324 0 => 'entity_id',
1325 ],
1326 'civicrm_website' => [
1327 0 => 'contact_id',
1328 ],
1329 ];
1330 }
1331
1332 /**
1333 * Get a list of CIDs that is calculated off the schema.
1334 *
1335 * Note this is an expensive and table locking query. Should be safe in tests
1336 * though.
1337 */
1338 public function getCalculatedCIDRefs() {
1339 $cidRefs = [];
1340 $sql = "
1341 SELECT
1342 table_name,
1343 column_name
1344 FROM information_schema.key_column_usage
1345 WHERE
1346 referenced_table_schema = database() AND
1347 referenced_table_name = 'civicrm_contact' AND
1348 referenced_column_name = 'id';
1349 ";
1350 $dao = CRM_Core_DAO::executeQuery($sql);
1351 while ($dao->fetch()) {
1352 $cidRefs[$dao->table_name][] = $dao->column_name;
1353 }
1354 // Do specific re-ordering changes to make this the same as the ref validated one.
1355 // The above query orders by FK alphabetically.
1356 // There might be cleverer ways to do this but it shouldn't change much.
1357 $cidRefs['civicrm_contact'][0] = 'primary_contact_id';
1358 $cidRefs['civicrm_contact'][1] = 'employer_id';
1359 $cidRefs['civicrm_acl_contact_cache'][0] = 'contact_id';
1360 $cidRefs['civicrm_mailing'][0] = 'created_id';
1361 $cidRefs['civicrm_mailing'][1] = 'scheduled_id';
1362 $cidRefs['civicrm_mailing'][2] = 'approver_id';
1363 return $cidRefs;
1364 }
1365
1366 }