Commit | Line | Data |
---|---|---|
0622d221 | 1 | <?php |
0622d221 | 2 | |
3 | /** | |
4 | * Class CRM_Dedupe_DedupeMergerTest | |
acb109b7 | 5 | * @group headless |
0622d221 | 6 | */ |
7 | class 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 | |
156 | $_SERVER['REQUEST_METHOD'] = 'GET'; //avoid invalid key error | |
157 | $object = new CRM_Contact_Page_DedupeFind(); | |
158 | $object->set('gid', $this->_groupId); | |
159 | $object->set('rgid', $dao->id); | |
160 | $object->set('action', CRM_Core_Action::UPDATE); | |
64fe2fe0 | 161 | $object->setEmbedded(TRUE); |
0622d221 | 162 | @$object->run(); |
163 | ||
164 | // Retrieve pairs from prev next cache table | |
165 | $select = array('pn.is_selected' => 'is_selected'); | |
b1679439 | 166 | $cacheKeyString = CRM_Dedupe_Merger::getMergeCacheKeyString($dao->id, $this->_groupId); |
0622d221 | 167 | $pnDupePairs = CRM_Core_BAO_PrevNextCache::retrieve($cacheKeyString, NULL, NULL, 0, 0, $select); |
168 | ||
169 | $this->assertEquals(count($foundDupes), count($pnDupePairs), 'Check number of dupe pairs in prev next cache.'); | |
170 | ||
171 | // mark first two pairs as selected | |
172 | CRM_Core_DAO::singleValueQuery("UPDATE civicrm_prevnext_cache SET is_selected = 1 WHERE id IN ({$pnDupePairs[0]['prevnext_id']}, {$pnDupePairs[1]['prevnext_id']})"); | |
173 | ||
174 | $pnDupePairs = CRM_Core_BAO_PrevNextCache::retrieve($cacheKeyString, NULL, NULL, 0, 0, $select); | |
175 | $this->assertEquals($pnDupePairs[0]['is_selected'], 1, 'Check if first record in dupe pairs is marked as selected.'); | |
176 | $this->assertEquals($pnDupePairs[0]['is_selected'], 1, 'Check if second record in dupe pairs is marked as selected.'); | |
177 | ||
178 | // batch merge selected dupes | |
d13a105a | 179 | $result = CRM_Dedupe_Merger::batchMerge($dao->id, $this->_groupId, 'safe', 5, 1); |
0622d221 | 180 | $this->assertEquals(count($result['merged']), 2, 'Check number of merged pairs.'); |
181 | ||
182 | // retrieve pairs from prev next cache table | |
183 | $pnDupePairs = CRM_Core_BAO_PrevNextCache::retrieve($cacheKeyString, NULL, NULL, 0, 0, $select); | |
184 | $this->assertEquals(count($pnDupePairs), 1, 'Check number of remaining dupe pairs in prev next cache.'); | |
185 | ||
186 | $this->deleteDupeContacts(); | |
187 | } | |
188 | ||
c8ec0753 | 189 | /** |
a354251e | 190 | * Test the batch merge. |
c8ec0753 | 191 | */ |
0622d221 | 192 | public function testBatchMergeAllDuplicates() { |
193 | $this->createDupeContacts(); | |
194 | ||
195 | // verify that all contacts have been created separately | |
196 | $this->assertEquals(count($this->_contactIds), 9, 'Check for number of contacts.'); | |
197 | ||
198 | $dao = new CRM_Dedupe_DAO_RuleGroup(); | |
199 | $dao->contact_type = 'Individual'; | |
200 | $dao->name = 'IndividualSupervised'; | |
201 | $dao->is_default = 1; | |
202 | $dao->find(TRUE); | |
203 | ||
204 | $foundDupes = CRM_Dedupe_Finder::dupesInGroup($dao->id, $this->_groupId); | |
205 | ||
206 | // ------------------------------------------------------------------------- | |
207 | // Name and Email (reserved) Matches ( 3 pairs ) | |
208 | // -------------------------------------------------------------------------- | |
209 | // robin - hood - robin@example.com | |
210 | // robin - hood - robin@example.com | |
211 | // little - dale - dale@example.com | |
212 | // little - dale - dale@example.com | |
213 | // will - dale - will@example.com | |
214 | // will - dale - will@example.com | |
215 | // so 3 pairs for - first + last + mail | |
216 | $this->assertEquals(count($foundDupes), 3, 'Check Individual-Supervised dupe rule for dupesInGroup().'); | |
217 | ||
218 | // Run dedupe finder as the browser would | |
219 | $_SERVER['REQUEST_METHOD'] = 'GET'; //avoid invalid key error | |
220 | $object = new CRM_Contact_Page_DedupeFind(); | |
221 | $object->set('gid', $this->_groupId); | |
222 | $object->set('rgid', $dao->id); | |
223 | $object->set('action', CRM_Core_Action::UPDATE); | |
64fe2fe0 | 224 | $object->setEmbedded(TRUE); |
0622d221 | 225 | @$object->run(); |
226 | ||
227 | // Retrieve pairs from prev next cache table | |
228 | $select = array('pn.is_selected' => 'is_selected'); | |
b1679439 | 229 | $cacheKeyString = CRM_Dedupe_Merger::getMergeCacheKeyString($dao->id, $this->_groupId); |
0622d221 | 230 | $pnDupePairs = CRM_Core_BAO_PrevNextCache::retrieve($cacheKeyString, NULL, NULL, 0, 0, $select); |
231 | ||
232 | $this->assertEquals(count($foundDupes), count($pnDupePairs), 'Check number of dupe pairs in prev next cache.'); | |
233 | ||
234 | // batch merge all dupes | |
d13a105a | 235 | $result = CRM_Dedupe_Merger::batchMerge($dao->id, $this->_groupId, 'safe', 5, 2); |
0622d221 | 236 | $this->assertEquals(count($result['merged']), 3, 'Check number of merged pairs.'); |
237 | ||
238 | // retrieve pairs from prev next cache table | |
239 | $pnDupePairs = CRM_Core_BAO_PrevNextCache::retrieve($cacheKeyString, NULL, NULL, 0, 0, $select); | |
240 | $this->assertEquals(count($pnDupePairs), 0, 'Check number of remaining dupe pairs in prev next cache.'); | |
241 | ||
242 | $this->deleteDupeContacts(); | |
243 | } | |
244 | ||
bf17fa88 | 245 | /** |
246 | * The goal of this function is to test that all required tables are returned. | |
247 | */ | |
248 | public function testGetCidRefs() { | |
249 | $this->entityCustomGroupWithSingleFieldCreate(__FUNCTION__, 'Contacts'); | |
250 | $this->assertEquals(array_merge($this->getStaticCIDRefs(), $this->getHackedInCIDRef()), CRM_Dedupe_Merger::cidRefs()); | |
251 | $this->assertEquals(array_merge($this->getCalculatedCIDRefs(), $this->getHackedInCIDRef()), CRM_Dedupe_Merger::cidRefs()); | |
252 | } | |
253 | ||
254 | /** | |
255 | * Get the list of not-really-cid-refs that are currently hacked in. | |
256 | * | |
257 | * This is hacked into getCIDs function. | |
258 | * | |
259 | * @return array | |
260 | */ | |
261 | public function getHackedInCIDRef() { | |
262 | return array( | |
263 | 'civicrm_entity_tag' => array( | |
264 | 0 => 'entity_id', | |
265 | ), | |
266 | ); | |
267 | } | |
268 | ||
2988f5c7 | 269 | /** |
270 | * Test function that gets duplicate pairs. | |
271 | * | |
272 | * It turns out there are 2 code paths retrieving this data so my initial focus is on ensuring | |
273 | * they match. | |
274 | */ | |
275 | public function testGetMatches() { | |
276 | $this->setupMatchData(); | |
277 | $pairs = CRM_Dedupe_Merger::getDuplicatePairs( | |
278 | 1, | |
279 | NULL, | |
280 | TRUE, | |
281 | 25, | |
282 | FALSE | |
283 | ); | |
284 | ||
285 | $this->assertEquals(array( | |
286 | 0 => array( | |
08cde01f | 287 | 'srcID' => $this->contacts[1]['id'], |
2988f5c7 | 288 | 'srcName' => 'Mr. Mickey Mouse II', |
08cde01f | 289 | 'dstID' => $this->contacts[0]['id'], |
2988f5c7 | 290 | 'dstName' => 'Mr. Mickey Mouse II', |
291 | 'weight' => 20, | |
292 | 'canMerge' => TRUE, | |
293 | ), | |
3308aac0 | 294 | 1 => array( |
08cde01f | 295 | 'srcID' => $this->contacts[3]['id'], |
3308aac0 | 296 | 'srcName' => 'Mr. Minnie Mouse II', |
08cde01f | 297 | 'dstID' => $this->contacts[2]['id'], |
3308aac0 | 298 | 'dstName' => 'Mr. Minnie Mouse II', |
299 | 'weight' => 20, | |
300 | 'canMerge' => TRUE, | |
301 | ), | |
2988f5c7 | 302 | ), $pairs); |
303 | } | |
304 | ||
bc0f3965 | 305 | /** |
306 | * Test function that gets organization pairs. | |
307 | * | |
308 | * Note the rule will match on organization_name OR email - hence lots of matches. | |
309 | */ | |
310 | public function testGetOrganizationMatches() { | |
311 | $this->setupMatchData(); | |
312 | $ruleGroups = $this->callAPISuccessGetSingle('RuleGroup', array('contact_type' => 'Organization', 'used' => 'Supervised')); | |
313 | ||
314 | $pairs = CRM_Dedupe_Merger::getDuplicatePairs( | |
315 | $ruleGroups['id'], | |
316 | NULL, | |
317 | TRUE, | |
318 | 25, | |
319 | FALSE | |
320 | ); | |
321 | ||
72475b30 | 322 | $expectedPairs = array( |
bc0f3965 | 323 | 0 => array( |
324 | 'srcID' => $this->contacts[5]['id'], | |
325 | 'srcName' => 'Walt Disney Ltd', | |
326 | 'dstID' => $this->contacts[4]['id'], | |
327 | 'dstName' => 'Walt Disney Ltd', | |
328 | 'weight' => 20, | |
329 | 'canMerge' => TRUE, | |
330 | ), | |
331 | 1 => array( | |
332 | 'srcID' => $this->contacts[7]['id'], | |
333 | 'srcName' => 'Walt Disney', | |
334 | 'dstID' => $this->contacts[6]['id'], | |
335 | 'dstName' => 'Walt Disney', | |
336 | 'weight' => 10, | |
337 | 'canMerge' => TRUE, | |
338 | ), | |
339 | 2 => array( | |
340 | 'srcID' => $this->contacts[6]['id'], | |
341 | 'srcName' => 'Walt Disney', | |
342 | 'dstID' => $this->contacts[4]['id'], | |
343 | 'dstName' => 'Walt Disney Ltd', | |
344 | 'weight' => 10, | |
345 | 'canMerge' => TRUE, | |
346 | ), | |
347 | 3 => array( | |
348 | 'srcID' => $this->contacts[6]['id'], | |
349 | 'srcName' => 'Walt Disney', | |
350 | 'dstID' => $this->contacts[5]['id'], | |
351 | 'dstName' => 'Walt Disney Ltd', | |
352 | 'weight' => 10, | |
353 | 'canMerge' => TRUE, | |
354 | ), | |
72475b30 TO |
355 | ); |
356 | usort($pairs, array(__CLASS__, 'compareDupes')); | |
357 | usort($expectedPairs, array(__CLASS__, 'compareDupes')); | |
358 | $this->assertEquals($expectedPairs, $pairs); | |
359 | } | |
360 | ||
361 | /** | |
362 | * Function to sort $duplicate records in a stable way. | |
363 | * | |
364 | * @param array $a | |
365 | * @param array $b | |
366 | * @return int | |
367 | */ | |
368 | public static function compareDupes($a, $b) { | |
369 | foreach (array('srcName', 'dstName', 'srcID', 'dstID') as $field) { | |
370 | if ($a[$field] != $b[$field]) { | |
371 | return ($a[$field] < $b[$field]) ? 1 : -1; | |
372 | } | |
373 | } | |
374 | return 0; | |
bc0f3965 | 375 | } |
376 | ||
377 | /** | |
378 | * Test function that gets organization duplicate pairs. | |
379 | */ | |
380 | public function testGetOrganizationMatchesInGroup() { | |
381 | $this->setupMatchData(); | |
382 | $ruleGroups = $this->callAPISuccessGetSingle('RuleGroup', array('contact_type' => 'Organization', 'used' => 'Supervised')); | |
383 | ||
384 | $groupID = $this->groupCreate(array('title' => 'she-mice')); | |
385 | ||
386 | $this->callAPISuccess('GroupContact', 'create', array('group_id' => $groupID, 'contact_id' => $this->contacts[4]['id'])); | |
387 | ||
388 | $pairs = CRM_Dedupe_Merger::getDuplicatePairs( | |
389 | $ruleGroups['id'], | |
390 | $groupID, | |
391 | TRUE, | |
392 | 25, | |
393 | FALSE | |
394 | ); | |
395 | ||
396 | $this->assertEquals(array( | |
397 | 0 => array( | |
398 | 'srcID' => $this->contacts[5]['id'], | |
399 | 'srcName' => 'Walt Disney Ltd', | |
400 | 'dstID' => $this->contacts[4]['id'], | |
401 | 'dstName' => 'Walt Disney Ltd', | |
402 | 'weight' => 20, | |
403 | 'canMerge' => TRUE, | |
404 | ), | |
405 | 1 => array( | |
406 | 'srcID' => $this->contacts[6]['id'], | |
407 | 'srcName' => 'Walt Disney', | |
408 | 'dstID' => $this->contacts[4]['id'], | |
409 | 'dstName' => 'Walt Disney Ltd', | |
410 | 'weight' => 10, | |
411 | 'canMerge' => TRUE, | |
412 | ), | |
413 | ), $pairs); | |
be61083d | 414 | |
415 | $this->callAPISuccess('GroupContact', 'create', array('group_id' => $groupID, 'contact_id' => $this->contacts[5]['id'])); | |
416 | CRM_Core_DAO::executeQuery("DELETE FROM civicrm_prevnext_cache"); | |
417 | $pairs = CRM_Dedupe_Merger::getDuplicatePairs( | |
418 | $ruleGroups['id'], | |
419 | $groupID, | |
420 | TRUE, | |
421 | 25, | |
422 | FALSE | |
423 | ); | |
424 | ||
425 | $this->assertEquals(array( | |
426 | 0 => array( | |
427 | 'srcID' => $this->contacts[5]['id'], | |
428 | 'srcName' => 'Walt Disney Ltd', | |
429 | 'dstID' => $this->contacts[4]['id'], | |
430 | 'dstName' => 'Walt Disney Ltd', | |
431 | 'weight' => 20, | |
432 | 'canMerge' => TRUE, | |
433 | ), | |
434 | 1 => array( | |
435 | 'srcID' => $this->contacts[6]['id'], | |
436 | 'srcName' => 'Walt Disney', | |
437 | 'dstID' => $this->contacts[4]['id'], | |
438 | 'dstName' => 'Walt Disney Ltd', | |
439 | 'weight' => 10, | |
440 | 'canMerge' => TRUE, | |
441 | ), | |
442 | 2 => array( | |
443 | 'srcID' => $this->contacts[6]['id'], | |
444 | 'srcName' => 'Walt Disney', | |
445 | 'dstID' => $this->contacts[5]['id'], | |
446 | 'dstName' => 'Walt Disney Ltd', | |
447 | 'weight' => 10, | |
448 | 'canMerge' => TRUE, | |
449 | ), | |
450 | ), $pairs); | |
bc0f3965 | 451 | } |
452 | ||
3308aac0 | 453 | /** |
454 | * Test function that gets duplicate pairs. | |
455 | * | |
456 | * It turns out there are 2 code paths retrieving this data so my initial focus is on ensuring | |
457 | * they match. | |
458 | */ | |
459 | public function testGetMatchesInGroup() { | |
460 | $this->setupMatchData(); | |
461 | ||
462 | $groupID = $this->groupCreate(array('title' => 'she-mice')); | |
463 | ||
464 | $this->callAPISuccess('GroupContact', 'create', array('group_id' => $groupID, 'contact_id' => $this->contacts[3]['id'])); | |
465 | ||
466 | $pairs = CRM_Dedupe_Merger::getDuplicatePairs( | |
467 | 1, | |
468 | $groupID, | |
469 | TRUE, | |
470 | 25, | |
471 | FALSE | |
472 | ); | |
473 | ||
474 | $this->assertEquals(array( | |
475 | 0 => array( | |
476 | 'srcID' => $this->contacts[3]['id'], | |
477 | 'srcName' => 'Mr. Minnie Mouse II', | |
478 | 'dstID' => $this->contacts[2]['id'], | |
479 | 'dstName' => 'Mr. Minnie Mouse II', | |
480 | 'weight' => 20, | |
481 | 'canMerge' => TRUE, | |
482 | ), | |
483 | ), $pairs); | |
484 | } | |
485 | ||
0946ab3f | 486 | /** |
487 | * CRM-19653 : Test that custom field data should/shouldn't be overriden on | |
488 | * selecting/not selecting option to migrate data respectively | |
489 | */ | |
490 | public function testCustomDataOverwrite() { | |
dba77ae8 CR |
491 | // Create Custom Field |
492 | $createGroup = $this->setupCustomGroupForIndividual(); | |
493 | $createField = $this->setupCustomField('Graduation', $createGroup); | |
494 | $customFieldName = "custom_" . $createField['id']; | |
495 | ||
496 | // Contacts setup | |
0946ab3f | 497 | $this->setupMatchData(); |
498 | ||
499 | $originalContactID = $this->contacts[0]['id']; | |
500 | $duplicateContactID1 = $this->contacts[1]['id']; // used as duplicate contact in 1st use-case | |
501 | $duplicateContactID2 = $this->contacts[2]['id']; // used as duplicate contact in 2nd use-case | |
502 | ||
0946ab3f | 503 | // update the text custom field for original contact with value 'abc' |
504 | $this->callAPISuccess('Contact', 'create', array( | |
505 | 'id' => $originalContactID, | |
dba77ae8 | 506 | "{$customFieldName}" => 'abc', |
0946ab3f | 507 | )); |
dba77ae8 CR |
508 | $this->assertCustomFieldValue($originalContactID, 'abc', $customFieldName); |
509 | ||
0946ab3f | 510 | // update the text custom field for duplicate contact 1 with value 'def' |
511 | $this->callAPISuccess('Contact', 'create', array( | |
512 | 'id' => $duplicateContactID1, | |
dba77ae8 | 513 | "{$customFieldName}" => 'def', |
0946ab3f | 514 | )); |
dba77ae8 CR |
515 | $this->assertCustomFieldValue($duplicateContactID1, 'def', $customFieldName); |
516 | ||
0946ab3f | 517 | // update the text custom field for duplicate contact 2 with value 'ghi' |
518 | $this->callAPISuccess('Contact', 'create', array( | |
519 | 'id' => $duplicateContactID2, | |
dba77ae8 | 520 | "{$customFieldName}" => 'ghi', |
0946ab3f | 521 | )); |
dba77ae8 | 522 | $this->assertCustomFieldValue($duplicateContactID2, 'ghi', $customFieldName); |
0946ab3f | 523 | |
524 | /*** USE-CASE 1: DO NOT OVERWRITE CUSTOM FIELD VALUE **/ | |
dba77ae8 | 525 | $this->mergeContacts($originalContactID, $duplicateContactID1, array( |
ee3b1d86 | 526 | "move_{$customFieldName}" => NULL, |
0946ab3f | 527 | )); |
dba77ae8 | 528 | $this->assertCustomFieldValue($originalContactID, 'abc', $customFieldName); |
0946ab3f | 529 | |
530 | /*** USE-CASE 2: OVERWRITE CUSTOM FIELD VALUE **/ | |
dba77ae8 CR |
531 | $this->mergeContacts($originalContactID, $duplicateContactID2, array( |
532 | "move_{$customFieldName}" => 'ghi', | |
533 | )); | |
534 | $this->assertCustomFieldValue($originalContactID, 'ghi', $customFieldName); | |
535 | ||
536 | // cleanup created custom set | |
537 | $this->callAPISuccess('CustomField', 'delete', array('id' => $createField['id'])); | |
538 | $this->callAPISuccess('CustomGroup', 'delete', array('id' => $createGroup['id'])); | |
539 | } | |
540 | ||
541 | /** | |
542 | * Verifies that when a contact with a custom field value is merged into a | |
543 | * contact without a record int its corresponding custom group table, and none | |
544 | * of the custom fields of that custom table are selected, the value is not | |
545 | * merged in. | |
546 | */ | |
547 | public function testMigrationOfUnselectedCustomDataOnEmptyCustomRecord() { | |
548 | // Create Custom Fields | |
549 | $createGroup = $this->setupCustomGroupForIndividual(); | |
550 | $customField1 = $this->setupCustomField('TestField', $createGroup); | |
551 | ||
552 | // Contacts setup | |
553 | $this->setupMatchData(); | |
554 | $originalContactID = $this->contacts[0]['id']; | |
555 | $duplicateContactID = $this->contacts[1]['id']; | |
556 | ||
557 | // Update the text custom fields for duplicate contact | |
558 | $this->callAPISuccess('Contact', 'create', array( | |
559 | 'id' => $duplicateContactID, | |
560 | "custom_{$customField1['id']}" => 'abc', | |
561 | )); | |
562 | $this->assertCustomFieldValue($duplicateContactID, 'abc', "custom_{$customField1['id']}"); | |
563 | ||
564 | $this->mergeContacts($originalContactID, $duplicateContactID, array( | |
ee3b1d86 | 565 | "move_custom_{$customField1['id']}" => NULL, |
dba77ae8 CR |
566 | )); |
567 | $this->assertCustomFieldValue($originalContactID, '', "custom_{$customField1['id']}"); | |
568 | ||
569 | // cleanup created custom set | |
570 | $this->callAPISuccess('CustomField', 'delete', array('id' => $customField1['id'])); | |
571 | $this->callAPISuccess('CustomGroup', 'delete', array('id' => $createGroup['id'])); | |
572 | } | |
573 | ||
574 | /** | |
575 | * Tests that if only part of the custom fields of a custom group are selected | |
576 | * for a merge, only those values are merged, while all other fields of the | |
577 | * custom group retain their original value, specifically for a contact with | |
578 | * no records on the custom group table. | |
579 | */ | |
580 | public function testMigrationOfSomeCustomDataOnEmptyCustomRecord() { | |
581 | // Create Custom Fields | |
582 | $createGroup = $this->setupCustomGroupForIndividual(); | |
583 | $customField1 = $this->setupCustomField('Test1', $createGroup); | |
584 | $customField2 = $this->setupCustomField('Test2', $createGroup); | |
585 | ||
586 | // Contacts setup | |
587 | $this->setupMatchData(); | |
588 | $originalContactID = $this->contacts[0]['id']; | |
589 | $duplicateContactID = $this->contacts[1]['id']; | |
590 | ||
591 | // Update the text custom fields for duplicate contact | |
592 | $this->callAPISuccess('Contact', 'create', array( | |
593 | 'id' => $duplicateContactID, | |
594 | "custom_{$customField1['id']}" => 'abc', | |
595 | "custom_{$customField2['id']}" => 'def', | |
596 | )); | |
597 | $this->assertCustomFieldValue($duplicateContactID, 'abc', "custom_{$customField1['id']}"); | |
598 | $this->assertCustomFieldValue($duplicateContactID, 'def', "custom_{$customField2['id']}"); | |
599 | ||
600 | // Perform merge | |
601 | $this->mergeContacts($originalContactID, $duplicateContactID, array( | |
ee3b1d86 | 602 | "move_custom_{$customField1['id']}" => NULL, |
dba77ae8 CR |
603 | "move_custom_{$customField2['id']}" => 'def', |
604 | )); | |
605 | $this->assertCustomFieldValue($originalContactID, '', "custom_{$customField1['id']}"); | |
606 | $this->assertCustomFieldValue($originalContactID, 'def', "custom_{$customField2['id']}"); | |
607 | ||
608 | // cleanup created custom set | |
609 | $this->callAPISuccess('CustomField', 'delete', array('id' => $customField1['id'])); | |
610 | $this->callAPISuccess('CustomField', 'delete', array('id' => $customField2['id'])); | |
611 | $this->callAPISuccess('CustomGroup', 'delete', array('id' => $createGroup['id'])); | |
612 | } | |
613 | ||
614 | /** | |
615 | * Calls merge method on given contacts, with values given in $params array. | |
616 | * | |
617 | * @param $originalContactID | |
618 | * ID of target contact | |
619 | * @param $duplicateContactID | |
620 | * ID of contact to be merged | |
621 | * @param $params | |
622 | * Array of fields to be merged from source into target contact, of the form | |
623 | * ['move_<fieldName>' => <fieldValue>] | |
624 | */ | |
625 | private function mergeContacts($originalContactID, $duplicateContactID, $params) { | |
626 | $rowsElementsAndInfo = CRM_Dedupe_Merger::getRowsElementsAndInfo($originalContactID, $duplicateContactID); | |
627 | ||
0946ab3f | 628 | $migrationData = array( |
629 | 'main_details' => $rowsElementsAndInfo['main_details'], | |
630 | 'other_details' => $rowsElementsAndInfo['other_details'], | |
0946ab3f | 631 | ); |
dba77ae8 CR |
632 | |
633 | // Migrate data of duplicate contact | |
634 | CRM_Dedupe_Merger::moveAllBelongings($originalContactID, $duplicateContactID, array_merge($migrationData, $params)); | |
635 | } | |
636 | ||
637 | /** | |
638 | * Checks if the expected value for the given field corresponds to what is | |
639 | * stored in the database for the given contact ID. | |
640 | * | |
641 | * @param $contactID | |
642 | * @param $expectedValue | |
643 | * @param $customFieldName | |
644 | */ | |
645 | private function assertCustomFieldValue($contactID, $expectedValue, $customFieldName) { | |
0946ab3f | 646 | $data = $this->callAPISuccess('Contact', 'getsingle', array( |
dba77ae8 | 647 | 'id' => $contactID, |
0946ab3f | 648 | 'return' => array($customFieldName), |
649 | )); | |
0946ab3f | 650 | |
dba77ae8 CR |
651 | $this->assertEquals($expectedValue, $data[$customFieldName], "Custom field value was supposed to be '{$expectedValue}', '{$data[$customFieldName]}' found."); |
652 | } | |
653 | ||
654 | /** | |
655 | * Creates a custom group to run tests on contacts that are individuals. | |
656 | * | |
657 | * @return array | |
658 | * Data for the created custom group record | |
659 | */ | |
660 | private function setupCustomGroupForIndividual() { | |
661 | $customGroup = $this->callAPISuccess('custom_group', 'get', array( | |
662 | 'name' => 'test_group', | |
663 | )); | |
664 | ||
665 | if ($customGroup['count'] > 0) { | |
666 | $this->callAPISuccess('CustomGroup', 'delete', array('id' => $customGroup['id'])); | |
667 | } | |
668 | ||
669 | $customGroup = $this->callAPISuccess('custom_group', 'create', array( | |
670 | 'title' => 'Test_Group', | |
671 | 'name' => 'test_group', | |
672 | 'extends' => array('Individual'), | |
673 | 'style' => 'Inline', | |
674 | 'is_multiple' => FALSE, | |
675 | 'is_active' => 1, | |
676 | )); | |
677 | ||
678 | return $customGroup; | |
679 | } | |
680 | ||
681 | /** | |
682 | * Creates a custom field on the provided custom group with the given field | |
683 | * label. | |
684 | * | |
685 | * @param $fieldLabel | |
686 | * @param $createGroup | |
687 | * | |
688 | * @return array | |
689 | * Data for the created custom field record | |
690 | */ | |
691 | private function setupCustomField($fieldLabel, $createGroup) { | |
692 | return $this->callAPISuccess('custom_field', 'create', array( | |
693 | 'label' => $fieldLabel, | |
694 | 'data_type' => 'Alphanumeric', | |
695 | 'html_type' => 'Text', | |
696 | 'custom_group_id' => $createGroup['id'], | |
697 | )); | |
0946ab3f | 698 | } |
699 | ||
3308aac0 | 700 | /** |
701 | * Set up some contacts for our matching. | |
702 | */ | |
2988f5c7 | 703 | public function setupMatchData() { |
704 | $fixtures = array( | |
705 | array( | |
706 | 'first_name' => 'Mickey', | |
707 | 'last_name' => 'Mouse', | |
708 | 'email' => 'mickey@mouse.com', | |
709 | ), | |
710 | array( | |
711 | 'first_name' => 'Mickey', | |
712 | 'last_name' => 'Mouse', | |
713 | 'email' => 'mickey@mouse.com', | |
714 | ), | |
715 | array( | |
716 | 'first_name' => 'Minnie', | |
717 | 'last_name' => 'Mouse', | |
718 | 'email' => 'mickey@mouse.com', | |
719 | ), | |
3308aac0 | 720 | array( |
721 | 'first_name' => 'Minnie', | |
722 | 'last_name' => 'Mouse', | |
723 | 'email' => 'mickey@mouse.com', | |
724 | ), | |
2988f5c7 | 725 | ); |
726 | foreach ($fixtures as $fixture) { | |
727 | $contactID = $this->individualCreate($fixture); | |
728 | $this->contacts[] = array_merge($fixture, array('id' => $contactID)); | |
bc0f3965 | 729 | } |
730 | $organizationFixtures = array( | |
731 | array( | |
732 | 'organization_name' => 'Walt Disney Ltd', | |
733 | 'email' => 'walt@disney.com', | |
734 | ), | |
735 | array( | |
736 | 'organization_name' => 'Walt Disney Ltd', | |
737 | 'email' => 'walt@disney.com', | |
738 | ), | |
739 | array( | |
740 | 'organization_name' => 'Walt Disney', | |
741 | 'email' => 'walt@disney.com', | |
742 | ), | |
743 | array( | |
744 | 'organization_name' => 'Walt Disney', | |
745 | 'email' => 'walter@disney.com', | |
746 | ), | |
747 | ); | |
748 | foreach ($organizationFixtures as $fixture) { | |
749 | $contactID = $this->organizationCreate($fixture); | |
750 | $this->contacts[] = array_merge($fixture, array('id' => $contactID)); | |
2988f5c7 | 751 | } |
752 | } | |
753 | ||
754 | ||
bf17fa88 | 755 | /** |
756 | * Get the list of tables that refer to the CID. | |
757 | * | |
758 | * This is a statically maintained (in this test list). | |
759 | * | |
760 | * There is also a check against an automated list but having both seems to add extra stability to me. They do | |
761 | * not change often. | |
762 | */ | |
763 | public function getStaticCIDRefs() { | |
764 | return array( | |
765 | 'civicrm_acl_cache' => array( | |
766 | 0 => 'contact_id', | |
767 | ), | |
768 | 'civicrm_acl_contact_cache' => array( | |
769 | 0 => 'user_id', | |
770 | 1 => 'contact_id', | |
771 | ), | |
772 | 'civicrm_action_log' => array( | |
773 | 0 => 'contact_id', | |
774 | ), | |
775 | 'civicrm_activity_contact' => array( | |
776 | 0 => 'contact_id', | |
777 | ), | |
778 | 'civicrm_address' => array( | |
779 | 0 => 'contact_id', | |
780 | ), | |
781 | 'civicrm_batch' => array( | |
782 | 0 => 'created_id', | |
783 | 1 => 'modified_id', | |
784 | ), | |
785 | 'civicrm_campaign' => array( | |
786 | 0 => 'created_id', | |
787 | 1 => 'last_modified_id', | |
788 | ), | |
789 | 'civicrm_case_contact' => array( | |
790 | 0 => 'contact_id', | |
791 | ), | |
792 | 'civicrm_contact' => array( | |
793 | 0 => 'primary_contact_id', | |
794 | 1 => 'employer_id', | |
795 | ), | |
796 | 'civicrm_contribution' => array( | |
797 | 0 => 'contact_id', | |
798 | ), | |
799 | 'civicrm_contribution_page' => array( | |
800 | 0 => 'created_id', | |
801 | ), | |
802 | 'civicrm_contribution_recur' => array( | |
803 | 0 => 'contact_id', | |
804 | ), | |
805 | 'civicrm_contribution_soft' => array( | |
806 | 0 => 'contact_id', | |
807 | ), | |
808 | 'civicrm_custom_group' => array( | |
809 | 0 => 'created_id', | |
810 | ), | |
811 | 'civicrm_dashboard_contact' => array( | |
812 | 0 => 'contact_id', | |
813 | ), | |
814 | 'civicrm_dedupe_exception' => array( | |
815 | 0 => 'contact_id1', | |
816 | 1 => 'contact_id2', | |
817 | ), | |
818 | 'civicrm_domain' => array( | |
819 | 0 => 'contact_id', | |
820 | ), | |
821 | 'civicrm_email' => array( | |
822 | 0 => 'contact_id', | |
823 | ), | |
824 | 'civicrm_event' => array( | |
825 | 0 => 'created_id', | |
826 | ), | |
827 | 'civicrm_event_carts' => array( | |
828 | 0 => 'user_id', | |
829 | ), | |
830 | 'civicrm_financial_account' => array( | |
831 | 0 => 'contact_id', | |
832 | ), | |
833 | 'civicrm_financial_item' => array( | |
834 | 0 => 'contact_id', | |
835 | ), | |
836 | 'civicrm_grant' => array( | |
837 | 0 => 'contact_id', | |
838 | ), | |
839 | 'civicrm_group' => array( | |
840 | 0 => 'created_id', | |
841 | 1 => 'modified_id', | |
842 | ), | |
843 | 'civicrm_group_contact' => array( | |
844 | 0 => 'contact_id', | |
845 | ), | |
846 | 'civicrm_group_contact_cache' => array( | |
847 | 0 => 'contact_id', | |
848 | ), | |
849 | 'civicrm_group_organization' => array( | |
850 | 0 => 'organization_id', | |
851 | ), | |
852 | 'civicrm_im' => array( | |
853 | 0 => 'contact_id', | |
854 | ), | |
855 | 'civicrm_log' => array( | |
856 | 0 => 'modified_id', | |
857 | ), | |
858 | 'civicrm_mailing' => array( | |
859 | 0 => 'created_id', | |
860 | 1 => 'scheduled_id', | |
861 | 2 => 'approver_id', | |
862 | ), | |
863 | 'civicrm_mailing_abtest' => array( | |
864 | 0 => 'created_id', | |
865 | ), | |
866 | 'civicrm_mailing_event_queue' => array( | |
867 | 0 => 'contact_id', | |
868 | ), | |
869 | 'civicrm_mailing_event_subscribe' => array( | |
870 | 0 => 'contact_id', | |
871 | ), | |
872 | 'civicrm_mailing_recipients' => array( | |
873 | 0 => 'contact_id', | |
874 | ), | |
875 | 'civicrm_membership' => array( | |
876 | 0 => 'contact_id', | |
877 | ), | |
878 | 'civicrm_membership_log' => array( | |
879 | 0 => 'modified_id', | |
880 | ), | |
881 | 'civicrm_membership_type' => array( | |
882 | 0 => 'member_of_contact_id', | |
883 | ), | |
884 | 'civicrm_note' => array( | |
885 | 0 => 'contact_id', | |
886 | ), | |
887 | 'civicrm_openid' => array( | |
888 | 0 => 'contact_id', | |
889 | ), | |
890 | 'civicrm_participant' => array( | |
891 | 0 => 'contact_id', | |
101d0fef | 892 | 1 => 'transferred_to_contact_id', //CRM-16761 |
bf17fa88 | 893 | ), |
894 | 'civicrm_payment_token' => array( | |
895 | 0 => 'contact_id', | |
896 | 1 => 'created_id', | |
897 | ), | |
898 | 'civicrm_pcp' => array( | |
899 | 0 => 'contact_id', | |
900 | ), | |
901 | 'civicrm_phone' => array( | |
902 | 0 => 'contact_id', | |
903 | ), | |
904 | 'civicrm_pledge' => array( | |
905 | 0 => 'contact_id', | |
906 | ), | |
907 | 'civicrm_print_label' => array( | |
908 | 0 => 'created_id', | |
909 | ), | |
910 | 'civicrm_relationship' => array( | |
911 | 0 => 'contact_id_a', | |
912 | 1 => 'contact_id_b', | |
913 | ), | |
914 | 'civicrm_report_instance' => array( | |
915 | 0 => 'created_id', | |
916 | 1 => 'owner_id', | |
917 | ), | |
918 | 'civicrm_setting' => array( | |
919 | 0 => 'contact_id', | |
920 | 1 => 'created_id', | |
921 | ), | |
922 | 'civicrm_subscription_history' => array( | |
923 | 0 => 'contact_id', | |
924 | ), | |
925 | 'civicrm_survey' => array( | |
926 | 0 => 'created_id', | |
927 | 1 => 'last_modified_id', | |
928 | ), | |
929 | 'civicrm_tag' => array( | |
930 | 0 => 'created_id', | |
931 | ), | |
932 | 'civicrm_uf_group' => array( | |
933 | 0 => 'created_id', | |
934 | ), | |
935 | 'civicrm_uf_match' => array( | |
936 | 0 => 'contact_id', | |
937 | ), | |
938 | 'civicrm_value_testgetcidref_1' => array( | |
939 | 0 => 'entity_id', | |
940 | ), | |
941 | 'civicrm_website' => array( | |
942 | 0 => 'contact_id', | |
943 | ), | |
944 | ); | |
945 | } | |
946 | ||
947 | /** | |
948 | * Get a list of CIDs that is calculated off the schema. | |
949 | * | |
950 | * Note this is an expensive and table locking query. Should be safe in tests though. | |
951 | */ | |
952 | public function getCalculatedCIDRefs() { | |
953 | $cidRefs = array(); | |
954 | $sql = " | |
955 | SELECT | |
956 | table_name, | |
957 | column_name | |
958 | FROM information_schema.key_column_usage | |
959 | WHERE | |
960 | referenced_table_schema = database() AND | |
961 | referenced_table_name = 'civicrm_contact' AND | |
962 | referenced_column_name = 'id'; | |
963 | "; | |
964 | $dao = CRM_Core_DAO::executeQuery($sql); | |
965 | while ($dao->fetch()) { | |
966 | $cidRefs[$dao->table_name][] = $dao->column_name; | |
967 | } | |
968 | // Do specific re-ordering changes to make this the same as the ref validated one. | |
969 | // The above query orders by FK alphabetically. | |
970 | // There might be cleverer ways to do this but it shouldn't change much. | |
971 | $cidRefs['civicrm_contact'][0] = 'primary_contact_id'; | |
972 | $cidRefs['civicrm_contact'][1] = 'employer_id'; | |
973 | $cidRefs['civicrm_acl_contact_cache'][0] = 'user_id'; | |
974 | $cidRefs['civicrm_acl_contact_cache'][1] = 'contact_id'; | |
975 | $cidRefs['civicrm_mailing'][0] = 'created_id'; | |
976 | $cidRefs['civicrm_mailing'][1] = 'scheduled_id'; | |
977 | $cidRefs['civicrm_mailing'][2] = 'approver_id'; | |
978 | return $cidRefs; | |
979 | } | |
980 | ||
0622d221 | 981 | } |