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