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