Merge pull request #14371 from seamuslee001/nfc_update_civicrm_generated_data
[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();
300 $pairs = CRM_Dedupe_Merger::getDuplicatePairs(
301 1,
302 NULL,
303 TRUE,
304 25,
305 FALSE
306 );
307
763a0fec 308 $this->assertEquals([
309 0 => [
08cde01f 310 'srcID' => $this->contacts[1]['id'],
2988f5c7 311 'srcName' => 'Mr. Mickey Mouse II',
08cde01f 312 'dstID' => $this->contacts[0]['id'],
2988f5c7 313 'dstName' => 'Mr. Mickey Mouse II',
314 'weight' => 20,
315 'canMerge' => TRUE,
763a0fec 316 ],
317 1 => [
08cde01f 318 'srcID' => $this->contacts[3]['id'],
3308aac0 319 'srcName' => 'Mr. Minnie Mouse II',
08cde01f 320 'dstID' => $this->contacts[2]['id'],
3308aac0 321 'dstName' => 'Mr. Minnie Mouse II',
322 'weight' => 20,
323 'canMerge' => TRUE,
763a0fec 324 ],
325 ], $pairs);
2988f5c7 326 }
327
bc0f3965 328 /**
329 * Test function that gets organization pairs.
330 *
763a0fec 331 * Note the rule will match on organization_name OR email - hence lots of
332 * matches.
2f10fa02 333 *
334 * @throws \Exception
bc0f3965 335 */
336 public function testGetOrganizationMatches() {
337 $this->setupMatchData();
763a0fec 338 $ruleGroups = $this->callAPISuccessGetSingle('RuleGroup', [
339 'contact_type' => 'Organization',
340 'used' => 'Supervised',
341 ]);
bc0f3965 342
343 $pairs = CRM_Dedupe_Merger::getDuplicatePairs(
344 $ruleGroups['id'],
345 NULL,
346 TRUE,
347 25,
348 FALSE
349 );
350
763a0fec 351 $expectedPairs = [
352 0 => [
bc0f3965 353 'srcID' => $this->contacts[5]['id'],
354 'srcName' => 'Walt Disney Ltd',
355 'dstID' => $this->contacts[4]['id'],
356 'dstName' => 'Walt Disney Ltd',
357 'weight' => 20,
358 'canMerge' => TRUE,
763a0fec 359 ],
360 1 => [
bc0f3965 361 'srcID' => $this->contacts[7]['id'],
362 'srcName' => 'Walt Disney',
363 'dstID' => $this->contacts[6]['id'],
364 'dstName' => 'Walt Disney',
365 'weight' => 10,
366 'canMerge' => TRUE,
763a0fec 367 ],
368 2 => [
bc0f3965 369 'srcID' => $this->contacts[6]['id'],
370 'srcName' => 'Walt Disney',
371 'dstID' => $this->contacts[4]['id'],
372 'dstName' => 'Walt Disney Ltd',
373 'weight' => 10,
374 'canMerge' => TRUE,
763a0fec 375 ],
376 3 => [
bc0f3965 377 'srcID' => $this->contacts[6]['id'],
378 'srcName' => 'Walt Disney',
379 'dstID' => $this->contacts[5]['id'],
380 'dstName' => 'Walt Disney Ltd',
381 'weight' => 10,
382 'canMerge' => TRUE,
763a0fec 383 ],
384 ];
385 usort($pairs, [__CLASS__, 'compareDupes']);
386 usort($expectedPairs, [__CLASS__, 'compareDupes']);
72475b30
TO
387 $this->assertEquals($expectedPairs, $pairs);
388 }
389
390 /**
391 * Function to sort $duplicate records in a stable way.
392 *
393 * @param array $a
394 * @param array $b
763a0fec 395 *
72475b30
TO
396 * @return int
397 */
398 public static function compareDupes($a, $b) {
763a0fec 399 foreach (['srcName', 'dstName', 'srcID', 'dstID'] as $field) {
72475b30
TO
400 if ($a[$field] != $b[$field]) {
401 return ($a[$field] < $b[$field]) ? 1 : -1;
402 }
403 }
404 return 0;
bc0f3965 405 }
406
407 /**
408 * Test function that gets organization duplicate pairs.
2f10fa02 409 *
410 * @throws \Exception
bc0f3965 411 */
412 public function testGetOrganizationMatchesInGroup() {
413 $this->setupMatchData();
763a0fec 414 $ruleGroups = $this->callAPISuccessGetSingle('RuleGroup', [
415 'contact_type' => 'Organization',
416 'used' => 'Supervised',
417 ]);
bc0f3965 418
763a0fec 419 $groupID = $this->groupCreate(['title' => 'she-mice']);
bc0f3965 420
763a0fec 421 $this->callAPISuccess('GroupContact', 'create', [
422 'group_id' => $groupID,
423 'contact_id' => $this->contacts[4]['id'],
424 ]);
bc0f3965 425
426 $pairs = CRM_Dedupe_Merger::getDuplicatePairs(
427 $ruleGroups['id'],
428 $groupID,
429 TRUE,
430 25,
431 FALSE
432 );
433
763a0fec 434 $this->assertEquals([
435 0 => [
bc0f3965 436 'srcID' => $this->contacts[5]['id'],
437 'srcName' => 'Walt Disney Ltd',
438 'dstID' => $this->contacts[4]['id'],
439 'dstName' => 'Walt Disney Ltd',
440 'weight' => 20,
441 'canMerge' => TRUE,
763a0fec 442 ],
443 1 => [
bc0f3965 444 'srcID' => $this->contacts[6]['id'],
445 'srcName' => 'Walt Disney',
446 'dstID' => $this->contacts[4]['id'],
447 'dstName' => 'Walt Disney Ltd',
448 'weight' => 10,
449 'canMerge' => TRUE,
763a0fec 450 ],
451 ], $pairs);
be61083d 452
763a0fec 453 $this->callAPISuccess('GroupContact', 'create', [
454 'group_id' => $groupID,
455 'contact_id' => $this->contacts[5]['id'],
456 ]);
be61083d 457 CRM_Core_DAO::executeQuery("DELETE FROM civicrm_prevnext_cache");
458 $pairs = CRM_Dedupe_Merger::getDuplicatePairs(
459 $ruleGroups['id'],
460 $groupID,
461 TRUE,
462 25,
463 FALSE
464 );
465
763a0fec 466 $this->assertEquals([
467 0 => [
be61083d 468 'srcID' => $this->contacts[5]['id'],
469 'srcName' => 'Walt Disney Ltd',
470 'dstID' => $this->contacts[4]['id'],
471 'dstName' => 'Walt Disney Ltd',
472 'weight' => 20,
473 'canMerge' => TRUE,
763a0fec 474 ],
475 1 => [
be61083d 476 'srcID' => $this->contacts[6]['id'],
477 'srcName' => 'Walt Disney',
478 'dstID' => $this->contacts[4]['id'],
479 'dstName' => 'Walt Disney Ltd',
480 'weight' => 10,
481 'canMerge' => TRUE,
763a0fec 482 ],
483 2 => [
be61083d 484 'srcID' => $this->contacts[6]['id'],
485 'srcName' => 'Walt Disney',
486 'dstID' => $this->contacts[5]['id'],
487 'dstName' => 'Walt Disney Ltd',
488 'weight' => 10,
489 'canMerge' => TRUE,
763a0fec 490 ],
491 ], $pairs);
bc0f3965 492 }
493
3308aac0 494 /**
495 * Test function that gets duplicate pairs.
496 *
763a0fec 497 * It turns out there are 2 code paths retrieving this data so my initial
498 * focus is on ensuring they match.
3308aac0 499 */
500 public function testGetMatchesInGroup() {
501 $this->setupMatchData();
502
763a0fec 503 $groupID = $this->groupCreate(['title' => 'she-mice']);
3308aac0 504
763a0fec 505 $this->callAPISuccess('GroupContact', 'create', [
506 'group_id' => $groupID,
507 'contact_id' => $this->contacts[3]['id'],
508 ]);
3308aac0 509
510 $pairs = CRM_Dedupe_Merger::getDuplicatePairs(
511 1,
512 $groupID,
513 TRUE,
514 25,
515 FALSE
516 );
517
763a0fec 518 $this->assertEquals([
519 0 => [
3308aac0 520 'srcID' => $this->contacts[3]['id'],
521 'srcName' => 'Mr. Minnie Mouse II',
522 'dstID' => $this->contacts[2]['id'],
523 'dstName' => 'Mr. Minnie Mouse II',
524 'weight' => 20,
525 'canMerge' => TRUE,
763a0fec 526 ],
527 ], $pairs);
3308aac0 528 }
529
0608e1e0 530 /**
531 * Test the special info handling is unchanged after cleanup.
532 *
763a0fec 533 * Note the handling is silly - we are testing to lock in over short term
534 * changes not to imply any contract on the function.
0608e1e0 535 */
2f10fa02 536 public function testGetRowsElementsAndInfoSpecialInfo() {
763a0fec 537 $contact1 = $this->individualCreate([
538 'preferred_communication_method' => [],
539 'communication_style_id' => 'Familiar',
540 'prefix_id' => 'Mrs.',
541 'suffix_id' => 'III',
542 ]);
543 $contact2 = $this->individualCreate([
544 'preferred_communication_method' => [
545 'SMS',
546 'Fax',
547 ],
548 'communication_style_id' => 'Formal',
549 'gender_id' => 'Female',
550 ]);
0608e1e0 551 $rowsElementsAndInfo = CRM_Dedupe_Merger::getRowsElementsAndInfo($contact1, $contact2);
552 $rows = $rowsElementsAndInfo['rows'];
763a0fec 553 $this->assertEquals([
554 'main' => 'Mrs.',
555 'other' => 'Mr.',
556 'title' => 'Individual Prefix',
557 ], $rows['move_prefix_id']);
558 $this->assertEquals([
559 'main' => 'III',
560 'other' => 'II',
561 'title' => 'Individual Suffix',
562 ], $rows['move_suffix_id']);
563 $this->assertEquals([
564 'main' => '',
565 'other' => 'Female',
566 'title' => 'Gender',
567 ], $rows['move_gender_id']);
568 $this->assertEquals([
569 'main' => 'Familiar',
570 'other' => 'Formal',
571 'title' => 'Communication Style',
572 ], $rows['move_communication_style_id']);
0608e1e0 573 $this->assertEquals(1, $rowsElementsAndInfo['migration_info']['move_communication_style_id']);
763a0fec 574 $this->assertEquals([
575 'main' => '',
576 'other' => 'SMS, Fax',
577 'title' => 'Preferred Communication Method',
578 ], $rows['move_preferred_communication_method']);
0608e1e0 579 $this->assertEquals('\ 14\ 15\ 1', $rowsElementsAndInfo['migration_info']['move_preferred_communication_method']);
580 }
581
c231c0dd
JP
582 /**
583 * Test migration of Membership.
584 */
585 public function testMergeMembership() {
586 // Contacts setup
587 $this->setupMatchData();
588 $originalContactID = $this->contacts[0]['id'];
589 $duplicateContactID = $this->contacts[1]['id'];
590
591 //Add Membership for the duplicate contact.
592 $memTypeId = $this->membershipTypeCreate();
2f10fa02 593 $this->callAPISuccess('Membership', 'create', [
c231c0dd
JP
594 'membership_type_id' => $memTypeId,
595 'contact_id' => $duplicateContactID,
596 ]);
597 //Assert if 'add new' checkbox is enabled on the merge form.
598 $rowsElementsAndInfo = CRM_Dedupe_Merger::getRowsElementsAndInfo($originalContactID, $duplicateContactID);
599 foreach ($rowsElementsAndInfo['elements'] as $element) {
600 if (!empty($element[3]) && $element[3] == 'add new') {
601 $checkedAttr = ['checked' => 'checked'];
602 $this->checkArrayEquals($element[4], $checkedAttr);
603 }
604 }
605
606 //Merge and move the mem to the main contact.
607 $this->mergeContacts($originalContactID, $duplicateContactID, [
608 'move_rel_table_memberships' => 1,
39b959db 609 'operation' => ['move_rel_table_memberships' => ['add' => 1]],
c231c0dd
JP
610 ]);
611
612 //Check if membership is correctly transferred to original contact.
613 $originalContactMembership = $this->callAPISuccess('Membership', 'get', [
614 'membership_type_id' => $memTypeId,
615 'contact_id' => $originalContactID,
616 ]);
617 $this->assertEquals(1, $originalContactMembership['count']);
618 }
619
0946ab3f 620 /**
621 * CRM-19653 : Test that custom field data should/shouldn't be overriden on
622 * selecting/not selecting option to migrate data respectively
623 */
624 public function testCustomDataOverwrite() {
dba77ae8 625 // Create Custom Field
763a0fec 626 $createGroup = $this->setupCustomGroupForIndividual();
dba77ae8
CR
627 $createField = $this->setupCustomField('Graduation', $createGroup);
628 $customFieldName = "custom_" . $createField['id'];
629
630 // Contacts setup
0946ab3f 631 $this->setupMatchData();
632
633 $originalContactID = $this->contacts[0]['id'];
39b959db
SL
634 // used as duplicate contact in 1st use-case
635 $duplicateContactID1 = $this->contacts[1]['id'];
636 // used as duplicate contact in 2nd use-case
637 $duplicateContactID2 = $this->contacts[2]['id'];
0946ab3f 638
0946ab3f 639 // update the text custom field for original contact with value 'abc'
763a0fec 640 $this->callAPISuccess('Contact', 'create', [
0946ab3f 641 'id' => $originalContactID,
dba77ae8 642 "{$customFieldName}" => 'abc',
763a0fec 643 ]);
dba77ae8
CR
644 $this->assertCustomFieldValue($originalContactID, 'abc', $customFieldName);
645
0946ab3f 646 // update the text custom field for duplicate contact 1 with value 'def'
763a0fec 647 $this->callAPISuccess('Contact', 'create', [
0946ab3f 648 'id' => $duplicateContactID1,
dba77ae8 649 "{$customFieldName}" => 'def',
763a0fec 650 ]);
dba77ae8
CR
651 $this->assertCustomFieldValue($duplicateContactID1, 'def', $customFieldName);
652
0946ab3f 653 // update the text custom field for duplicate contact 2 with value 'ghi'
763a0fec 654 $this->callAPISuccess('Contact', 'create', [
0946ab3f 655 'id' => $duplicateContactID2,
dba77ae8 656 "{$customFieldName}" => 'ghi',
763a0fec 657 ]);
dba77ae8 658 $this->assertCustomFieldValue($duplicateContactID2, 'ghi', $customFieldName);
0946ab3f 659
660 /*** USE-CASE 1: DO NOT OVERWRITE CUSTOM FIELD VALUE **/
763a0fec 661 $this->mergeContacts($originalContactID, $duplicateContactID1, [
39b959db 662 "move_{$customFieldName}" => NULL,
763a0fec 663 ]);
dba77ae8 664 $this->assertCustomFieldValue($originalContactID, 'abc', $customFieldName);
0946ab3f 665
666 /*** USE-CASE 2: OVERWRITE CUSTOM FIELD VALUE **/
763a0fec 667 $this->mergeContacts($originalContactID, $duplicateContactID2, [
dba77ae8 668 "move_{$customFieldName}" => 'ghi',
763a0fec 669 ]);
dba77ae8
CR
670 $this->assertCustomFieldValue($originalContactID, 'ghi', $customFieldName);
671
672 // cleanup created custom set
763a0fec 673 $this->callAPISuccess('CustomField', 'delete', ['id' => $createField['id']]);
674 $this->callAPISuccess('CustomGroup', 'delete', ['id' => $createGroup['id']]);
dba77ae8
CR
675 }
676
677 /**
678 * Verifies that when a contact with a custom field value is merged into a
679 * contact without a record int its corresponding custom group table, and none
680 * of the custom fields of that custom table are selected, the value is not
681 * merged in.
682 */
683 public function testMigrationOfUnselectedCustomDataOnEmptyCustomRecord() {
684 // Create Custom Fields
763a0fec 685 $createGroup = $this->setupCustomGroupForIndividual();
dba77ae8
CR
686 $customField1 = $this->setupCustomField('TestField', $createGroup);
687
c1955865
J
688 // Create multi-value custom field
689 $multiGroup = $this->CustomGroupMultipleCreateByParams();
763a0fec 690 $multiField = $this->customFieldCreate([
c1955865
J
691 'custom_group_id' => $multiGroup['id'],
692 'label' => 'field_1' . $multiGroup['id'],
693 'in_selector' => 1,
763a0fec 694 ]);
c1955865 695
dba77ae8
CR
696 // Contacts setup
697 $this->setupMatchData();
698 $originalContactID = $this->contacts[0]['id'];
699 $duplicateContactID = $this->contacts[1]['id'];
700
701 // Update the text custom fields for duplicate contact
763a0fec 702 $this->callAPISuccess('Contact', 'create', [
dba77ae8
CR
703 'id' => $duplicateContactID,
704 "custom_{$customField1['id']}" => 'abc',
c1955865 705 "custom_{$multiField['id']}" => 'def',
763a0fec 706 ]);
dba77ae8 707 $this->assertCustomFieldValue($duplicateContactID, 'abc', "custom_{$customField1['id']}");
c1955865 708 $this->assertCustomFieldValue($duplicateContactID, 'def', "custom_{$multiField['id']}");
dba77ae8 709
c1955865 710 // Merge, and ensure that no value was migrated
763a0fec 711 $this->mergeContacts($originalContactID, $duplicateContactID, [
ee3b1d86 712 "move_custom_{$customField1['id']}" => NULL,
c1955865 713 "move_rel_table_custom_{$multiGroup['id']}" => NULL,
763a0fec 714 ]);
dba77ae8 715 $this->assertCustomFieldValue($originalContactID, '', "custom_{$customField1['id']}");
c1955865 716 $this->assertCustomFieldValue($originalContactID, '', "custom_{$multiField['id']}");
dba77ae8
CR
717
718 // cleanup created custom set
763a0fec 719 $this->callAPISuccess('CustomField', 'delete', ['id' => $customField1['id']]);
720 $this->callAPISuccess('CustomGroup', 'delete', ['id' => $createGroup['id']]);
721 $this->callAPISuccess('CustomField', 'delete', ['id' => $multiField['id']]);
722 $this->callAPISuccess('CustomGroup', 'delete', ['id' => $multiGroup['id']]);
dba77ae8
CR
723 }
724
725 /**
726 * Tests that if only part of the custom fields of a custom group are selected
727 * for a merge, only those values are merged, while all other fields of the
728 * custom group retain their original value, specifically for a contact with
729 * no records on the custom group table.
730 */
731 public function testMigrationOfSomeCustomDataOnEmptyCustomRecord() {
732 // Create Custom Fields
763a0fec 733 $createGroup = $this->setupCustomGroupForIndividual();
dba77ae8
CR
734 $customField1 = $this->setupCustomField('Test1', $createGroup);
735 $customField2 = $this->setupCustomField('Test2', $createGroup);
736
c1955865
J
737 // Create multi-value custom field
738 $multiGroup = $this->CustomGroupMultipleCreateByParams();
763a0fec 739 $multiField = $this->customFieldCreate([
c1955865
J
740 'custom_group_id' => $multiGroup['id'],
741 'label' => 'field_1' . $multiGroup['id'],
742 'in_selector' => 1,
763a0fec 743 ]);
c1955865 744
dba77ae8
CR
745 // Contacts setup
746 $this->setupMatchData();
747 $originalContactID = $this->contacts[0]['id'];
748 $duplicateContactID = $this->contacts[1]['id'];
749
750 // Update the text custom fields for duplicate contact
763a0fec 751 $this->callAPISuccess('Contact', 'create', [
dba77ae8
CR
752 'id' => $duplicateContactID,
753 "custom_{$customField1['id']}" => 'abc',
754 "custom_{$customField2['id']}" => 'def',
c1955865 755 "custom_{$multiField['id']}" => 'ghi',
763a0fec 756 ]);
dba77ae8
CR
757 $this->assertCustomFieldValue($duplicateContactID, 'abc', "custom_{$customField1['id']}");
758 $this->assertCustomFieldValue($duplicateContactID, 'def', "custom_{$customField2['id']}");
c1955865 759 $this->assertCustomFieldValue($duplicateContactID, 'ghi', "custom_{$multiField['id']}");
dba77ae8
CR
760
761 // Perform merge
763a0fec 762 $this->mergeContacts($originalContactID, $duplicateContactID, [
ee3b1d86 763 "move_custom_{$customField1['id']}" => NULL,
dba77ae8 764 "move_custom_{$customField2['id']}" => 'def',
c1955865 765 "move_rel_table_custom_{$multiGroup['id']}" => '1',
763a0fec 766 ]);
dba77ae8
CR
767 $this->assertCustomFieldValue($originalContactID, '', "custom_{$customField1['id']}");
768 $this->assertCustomFieldValue($originalContactID, 'def', "custom_{$customField2['id']}");
c1955865 769 $this->assertCustomFieldValue($originalContactID, 'ghi', "custom_{$multiField['id']}");
dba77ae8
CR
770
771 // cleanup created custom set
763a0fec 772 $this->callAPISuccess('CustomField', 'delete', ['id' => $customField1['id']]);
773 $this->callAPISuccess('CustomField', 'delete', ['id' => $customField2['id']]);
774 $this->callAPISuccess('CustomGroup', 'delete', ['id' => $createGroup['id']]);
775 $this->callAPISuccess('CustomField', 'delete', ['id' => $multiField['id']]);
776 $this->callAPISuccess('CustomGroup', 'delete', ['id' => $multiGroup['id']]);
dba77ae8
CR
777 }
778
779 /**
780 * Calls merge method on given contacts, with values given in $params array.
781 *
782 * @param $originalContactID
783 * ID of target contact
784 * @param $duplicateContactID
785 * ID of contact to be merged
786 * @param $params
787 * Array of fields to be merged from source into target contact, of the form
788 * ['move_<fieldName>' => <fieldValue>]
2f10fa02 789 *
790 * @throws \CRM_Core_Exception
791 * @throws \CiviCRM_API3_Exception
dba77ae8
CR
792 */
793 private function mergeContacts($originalContactID, $duplicateContactID, $params) {
794 $rowsElementsAndInfo = CRM_Dedupe_Merger::getRowsElementsAndInfo($originalContactID, $duplicateContactID);
795
763a0fec 796 $migrationData = [
0946ab3f 797 'main_details' => $rowsElementsAndInfo['main_details'],
798 'other_details' => $rowsElementsAndInfo['other_details'],
763a0fec 799 ];
dba77ae8
CR
800
801 // Migrate data of duplicate contact
802 CRM_Dedupe_Merger::moveAllBelongings($originalContactID, $duplicateContactID, array_merge($migrationData, $params));
803 }
804
805 /**
806 * Checks if the expected value for the given field corresponds to what is
807 * stored in the database for the given contact ID.
808 *
809 * @param $contactID
810 * @param $expectedValue
811 * @param $customFieldName
812 */
813 private function assertCustomFieldValue($contactID, $expectedValue, $customFieldName) {
763a0fec 814 $data = $this->callAPISuccess('Contact', 'getsingle', [
dba77ae8 815 'id' => $contactID,
763a0fec 816 'return' => [$customFieldName],
817 ]);
0946ab3f 818
dba77ae8
CR
819 $this->assertEquals($expectedValue, $data[$customFieldName], "Custom field value was supposed to be '{$expectedValue}', '{$data[$customFieldName]}' found.");
820 }
821
822 /**
823 * Creates a custom group to run tests on contacts that are individuals.
824 *
825 * @return array
826 * Data for the created custom group record
827 */
828 private function setupCustomGroupForIndividual() {
763a0fec 829 $customGroup = $this->callAPISuccess('custom_group', 'get', [
dba77ae8 830 'name' => 'test_group',
763a0fec 831 ]);
dba77ae8
CR
832
833 if ($customGroup['count'] > 0) {
763a0fec 834 $this->callAPISuccess('CustomGroup', 'delete', ['id' => $customGroup['id']]);
dba77ae8
CR
835 }
836
763a0fec 837 $customGroup = $this->callAPISuccess('custom_group', 'create', [
dba77ae8
CR
838 'title' => 'Test_Group',
839 'name' => 'test_group',
763a0fec 840 'extends' => ['Individual'],
dba77ae8
CR
841 'style' => 'Inline',
842 'is_multiple' => FALSE,
843 'is_active' => 1,
763a0fec 844 ]);
dba77ae8
CR
845
846 return $customGroup;
847 }
848
849 /**
850 * Creates a custom field on the provided custom group with the given field
851 * label.
852 *
853 * @param $fieldLabel
854 * @param $createGroup
855 *
856 * @return array
857 * Data for the created custom field record
858 */
859 private function setupCustomField($fieldLabel, $createGroup) {
763a0fec 860 return $this->callAPISuccess('custom_field', 'create', [
dba77ae8
CR
861 'label' => $fieldLabel,
862 'data_type' => 'Alphanumeric',
863 'html_type' => 'Text',
864 'custom_group_id' => $createGroup['id'],
763a0fec 865 ]);
0946ab3f 866 }
867
3308aac0 868 /**
869 * Set up some contacts for our matching.
870 */
2988f5c7 871 public function setupMatchData() {
763a0fec 872 $fixtures = [
873 [
2988f5c7 874 'first_name' => 'Mickey',
875 'last_name' => 'Mouse',
876 'email' => 'mickey@mouse.com',
763a0fec 877 ],
878 [
2988f5c7 879 'first_name' => 'Mickey',
880 'last_name' => 'Mouse',
881 'email' => 'mickey@mouse.com',
763a0fec 882 ],
883 [
2988f5c7 884 'first_name' => 'Minnie',
885 'last_name' => 'Mouse',
886 'email' => 'mickey@mouse.com',
763a0fec 887 ],
888 [
3308aac0 889 'first_name' => 'Minnie',
890 'last_name' => 'Mouse',
891 'email' => 'mickey@mouse.com',
763a0fec 892 ],
893 ];
2988f5c7 894 foreach ($fixtures as $fixture) {
895 $contactID = $this->individualCreate($fixture);
763a0fec 896 $this->contacts[] = array_merge($fixture, ['id' => $contactID]);
bc0f3965 897 }
763a0fec 898 $organizationFixtures = [
899 [
bc0f3965 900 'organization_name' => 'Walt Disney Ltd',
901 'email' => 'walt@disney.com',
763a0fec 902 ],
903 [
bc0f3965 904 'organization_name' => 'Walt Disney Ltd',
905 'email' => 'walt@disney.com',
763a0fec 906 ],
907 [
bc0f3965 908 'organization_name' => 'Walt Disney',
909 'email' => 'walt@disney.com',
763a0fec 910 ],
911 [
bc0f3965 912 'organization_name' => 'Walt Disney',
913 'email' => 'walter@disney.com',
763a0fec 914 ],
915 ];
bc0f3965 916 foreach ($organizationFixtures as $fixture) {
917 $contactID = $this->organizationCreate($fixture);
763a0fec 918 $this->contacts[] = array_merge($fixture, ['id' => $contactID]);
2988f5c7 919 }
920 }
921
bf17fa88 922 /**
923 * Get the list of tables that refer to the CID.
924 *
925 * This is a statically maintained (in this test list).
926 *
763a0fec 927 * There is also a check against an automated list but having both seems to
928 * add extra stability to me. They do not change often.
bf17fa88 929 */
930 public function getStaticCIDRefs() {
763a0fec 931 return [
932 'civicrm_acl_cache' => [
bf17fa88 933 0 => 'contact_id',
763a0fec 934 ],
935 'civicrm_acl_contact_cache' => [
dbb4e4f9 936 0 => 'contact_id',
763a0fec 937 ],
938 'civicrm_action_log' => [
bf17fa88 939 0 => 'contact_id',
763a0fec 940 ],
941 'civicrm_activity_contact' => [
bf17fa88 942 0 => 'contact_id',
763a0fec 943 ],
944 'civicrm_address' => [
bf17fa88 945 0 => 'contact_id',
763a0fec 946 ],
947 'civicrm_batch' => [
bf17fa88 948 0 => 'created_id',
949 1 => 'modified_id',
763a0fec 950 ],
951 'civicrm_campaign' => [
bf17fa88 952 0 => 'created_id',
953 1 => 'last_modified_id',
763a0fec 954 ],
955 'civicrm_case_contact' => [
bf17fa88 956 0 => 'contact_id',
763a0fec 957 ],
958 'civicrm_contact' => [
bf17fa88 959 0 => 'primary_contact_id',
960 1 => 'employer_id',
763a0fec 961 ],
962 'civicrm_contribution' => [
bf17fa88 963 0 => 'contact_id',
763a0fec 964 ],
965 'civicrm_contribution_page' => [
bf17fa88 966 0 => 'created_id',
763a0fec 967 ],
968 'civicrm_contribution_recur' => [
bf17fa88 969 0 => 'contact_id',
763a0fec 970 ],
971 'civicrm_contribution_soft' => [
bf17fa88 972 0 => 'contact_id',
763a0fec 973 ],
974 'civicrm_custom_group' => [
bf17fa88 975 0 => 'created_id',
763a0fec 976 ],
977 'civicrm_dashboard_contact' => [
bf17fa88 978 0 => 'contact_id',
763a0fec 979 ],
980 'civicrm_dedupe_exception' => [
bf17fa88 981 0 => 'contact_id1',
982 1 => 'contact_id2',
763a0fec 983 ],
984 'civicrm_domain' => [
bf17fa88 985 0 => 'contact_id',
763a0fec 986 ],
987 'civicrm_email' => [
bf17fa88 988 0 => 'contact_id',
763a0fec 989 ],
990 'civicrm_event' => [
bf17fa88 991 0 => 'created_id',
763a0fec 992 ],
993 'civicrm_event_carts' => [
bf17fa88 994 0 => 'user_id',
763a0fec 995 ],
996 'civicrm_financial_account' => [
bf17fa88 997 0 => 'contact_id',
763a0fec 998 ],
999 'civicrm_financial_item' => [
bf17fa88 1000 0 => 'contact_id',
763a0fec 1001 ],
1002 'civicrm_grant' => [
bf17fa88 1003 0 => 'contact_id',
763a0fec 1004 ],
1005 'civicrm_group' => [
bf17fa88 1006 0 => 'created_id',
1007 1 => 'modified_id',
763a0fec 1008 ],
1009 'civicrm_group_contact' => [
bf17fa88 1010 0 => 'contact_id',
763a0fec 1011 ],
1012 'civicrm_group_contact_cache' => [
bf17fa88 1013 0 => 'contact_id',
763a0fec 1014 ],
1015 'civicrm_group_organization' => [
bf17fa88 1016 0 => 'organization_id',
763a0fec 1017 ],
1018 'civicrm_im' => [
bf17fa88 1019 0 => 'contact_id',
763a0fec 1020 ],
1021 'civicrm_log' => [
bf17fa88 1022 0 => 'modified_id',
763a0fec 1023 ],
1024 'civicrm_mailing' => [
bf17fa88 1025 0 => 'created_id',
1026 1 => 'scheduled_id',
1027 2 => 'approver_id',
763a0fec 1028 ],
1029 'civicrm_file' => [
ae2c7e00 1030 'created_id',
763a0fec 1031 ],
1032 'civicrm_mailing_abtest' => [
bf17fa88 1033 0 => 'created_id',
763a0fec 1034 ],
1035 'civicrm_mailing_event_queue' => [
bf17fa88 1036 0 => 'contact_id',
763a0fec 1037 ],
1038 'civicrm_mailing_event_subscribe' => [
bf17fa88 1039 0 => 'contact_id',
763a0fec 1040 ],
1041 'civicrm_mailing_recipients' => [
bf17fa88 1042 0 => 'contact_id',
763a0fec 1043 ],
1044 'civicrm_membership' => [
bf17fa88 1045 0 => 'contact_id',
763a0fec 1046 ],
1047 'civicrm_membership_log' => [
bf17fa88 1048 0 => 'modified_id',
763a0fec 1049 ],
1050 'civicrm_membership_type' => [
bf17fa88 1051 0 => 'member_of_contact_id',
763a0fec 1052 ],
1053 'civicrm_note' => [
bf17fa88 1054 0 => 'contact_id',
763a0fec 1055 ],
1056 'civicrm_openid' => [
bf17fa88 1057 0 => 'contact_id',
763a0fec 1058 ],
1059 'civicrm_participant' => [
bf17fa88 1060 0 => 'contact_id',
39b959db
SL
1061 //CRM-16761
1062 1 => 'transferred_to_contact_id',
763a0fec 1063 ],
1064 'civicrm_payment_token' => [
bf17fa88 1065 0 => 'contact_id',
1066 1 => 'created_id',
763a0fec 1067 ],
1068 'civicrm_pcp' => [
bf17fa88 1069 0 => 'contact_id',
763a0fec 1070 ],
1071 'civicrm_phone' => [
bf17fa88 1072 0 => 'contact_id',
763a0fec 1073 ],
1074 'civicrm_pledge' => [
bf17fa88 1075 0 => 'contact_id',
763a0fec 1076 ],
1077 'civicrm_print_label' => [
bf17fa88 1078 0 => 'created_id',
763a0fec 1079 ],
1080 'civicrm_relationship' => [
bf17fa88 1081 0 => 'contact_id_a',
1082 1 => 'contact_id_b',
763a0fec 1083 ],
1084 'civicrm_report_instance' => [
bf17fa88 1085 0 => 'created_id',
1086 1 => 'owner_id',
763a0fec 1087 ],
1088 'civicrm_setting' => [
bf17fa88 1089 0 => 'contact_id',
1090 1 => 'created_id',
763a0fec 1091 ],
1092 'civicrm_subscription_history' => [
bf17fa88 1093 0 => 'contact_id',
763a0fec 1094 ],
1095 'civicrm_survey' => [
bf17fa88 1096 0 => 'created_id',
1097 1 => 'last_modified_id',
763a0fec 1098 ],
1099 'civicrm_tag' => [
bf17fa88 1100 0 => 'created_id',
763a0fec 1101 ],
1102 'civicrm_uf_group' => [
bf17fa88 1103 0 => 'created_id',
763a0fec 1104 ],
1105 'civicrm_uf_match' => [
bf17fa88 1106 0 => 'contact_id',
763a0fec 1107 ],
1108 'civicrm_value_testgetcidref_1' => [
bf17fa88 1109 0 => 'entity_id',
763a0fec 1110 ],
1111 'civicrm_website' => [
bf17fa88 1112 0 => 'contact_id',
763a0fec 1113 ],
1114 ];
bf17fa88 1115 }
1116
1117 /**
1118 * Get a list of CIDs that is calculated off the schema.
1119 *
763a0fec 1120 * Note this is an expensive and table locking query. Should be safe in tests
1121 * though.
bf17fa88 1122 */
1123 public function getCalculatedCIDRefs() {
763a0fec 1124 $cidRefs = [];
bf17fa88 1125 $sql = "
1126SELECT
1127 table_name,
1128 column_name
1129FROM information_schema.key_column_usage
1130WHERE
1131 referenced_table_schema = database() AND
1132 referenced_table_name = 'civicrm_contact' AND
1133 referenced_column_name = 'id';
1134 ";
1135 $dao = CRM_Core_DAO::executeQuery($sql);
1136 while ($dao->fetch()) {
1137 $cidRefs[$dao->table_name][] = $dao->column_name;
1138 }
1139 // Do specific re-ordering changes to make this the same as the ref validated one.
1140 // The above query orders by FK alphabetically.
1141 // There might be cleverer ways to do this but it shouldn't change much.
1142 $cidRefs['civicrm_contact'][0] = 'primary_contact_id';
1143 $cidRefs['civicrm_contact'][1] = 'employer_id';
dbb4e4f9 1144 $cidRefs['civicrm_acl_contact_cache'][0] = 'contact_id';
bf17fa88 1145 $cidRefs['civicrm_mailing'][0] = 'created_id';
1146 $cidRefs['civicrm_mailing'][1] = 'scheduled_id';
1147 $cidRefs['civicrm_mailing'][2] = 'approver_id';
1148 return $cidRefs;
1149 }
1150
0622d221 1151}