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