Test fix
[civicrm-core.git] / tests / phpunit / api / v3 / JobTest.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2016 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
27
28 /**
29 * File for the CiviCRM APIv3 job functions
30 *
31 * @package CiviCRM_APIv3
32 * @subpackage API_Job
33 *
34 * @copyright CiviCRM LLC (c) 2004-2016
35 * @version $Id: Job.php 30879 2010-11-22 15:45:55Z shot $
36 *
37 */
38
39 /**
40 * Class api_v3_JobTest
41 * @group headless
42 */
43 class api_v3_JobTest extends CiviUnitTestCase {
44 protected $_apiversion = 3;
45
46 public $DBResetRequired = FALSE;
47 public $_entity = 'Job';
48 public $_params = array();
49 /**
50 * Created membership type.
51 *
52 * Must be created outside the transaction due to it breaking the transaction.
53 *
54 * @var
55 */
56 public $membershipTypeID;
57
58 public function setUp() {
59 parent::setUp();
60 $this->membershipTypeID = $this->membershipTypeCreate(array('name' => 'General'));
61 $this->useTransaction(TRUE);
62 $this->_params = array(
63 'sequential' => 1,
64 'name' => 'API_Test_Job',
65 'description' => 'A long description written by hand in cursive',
66 'run_frequency' => 'Daily',
67 'api_entity' => 'ApiTestEntity',
68 'api_action' => 'apitestaction',
69 'parameters' => 'Semi-formal explanation of runtime job parameters',
70 'is_active' => 1,
71 );
72 }
73
74 public function tearDown() {
75 parent::tearDown();
76 // The membershipType create breaks transactions so this extra cleanup is needed.
77 $this->membershipTypeDelete(array('id' => $this->membershipTypeID));
78 $this->cleanUpSetUpIDs();
79 }
80
81 /**
82 * Check with no name.
83 */
84 public function testCreateWithoutName() {
85 $params = array(
86 'is_active' => 1,
87 );
88 $this->callAPIFailure('job', 'create', $params,
89 'Mandatory key(s) missing from params array: run_frequency, name, api_entity, api_action'
90 );
91 }
92
93 /**
94 * Create job with an invalid "run_frequency" value.
95 */
96 public function testCreateWithInvalidFrequency() {
97 $params = array(
98 'sequential' => 1,
99 'name' => 'API_Test_Job',
100 'description' => 'A long description written by hand in cursive',
101 'run_frequency' => 'Fortnightly',
102 'api_entity' => 'ApiTestEntity',
103 'api_action' => 'apitestaction',
104 'parameters' => 'Semi-formal explanation of runtime job parameters',
105 'is_active' => 1,
106 );
107 $this->callAPIFailure('job', 'create', $params);
108 }
109
110 /**
111 * Create job.
112 */
113 public function testCreate() {
114 $result = $this->callAPIAndDocument('job', 'create', $this->_params, __FUNCTION__, __FILE__);
115 $this->assertNotNull($result['values'][0]['id']);
116
117 // mutate $params to match expected return value
118 unset($this->_params['sequential']);
119 //assertDBState compares expected values in $result to actual values in the DB
120 $this->assertDBState('CRM_Core_DAO_Job', $result['id'], $this->_params);
121 }
122
123 /**
124 * Check with empty array.
125 */
126 public function testDeleteEmpty() {
127 $params = array();
128 $result = $this->callAPIFailure('job', 'delete', $params);
129 }
130
131 /**
132 * Check with No array.
133 */
134 public function testDeleteParamsNotArray() {
135 $result = $this->callAPIFailure('job', 'delete', 'string');
136 }
137
138 /**
139 * Check if required fields are not passed.
140 */
141 public function testDeleteWithoutRequired() {
142 $params = array(
143 'name' => 'API_Test_PP',
144 'title' => 'API Test Payment Processor',
145 'class_name' => 'CRM_Core_Payment_APITest',
146 );
147
148 $result = $this->callAPIFailure('job', 'delete', $params);
149 $this->assertEquals($result['error_message'], 'Mandatory key(s) missing from params array: id');
150 }
151
152 /**
153 * Check with incorrect required fields.
154 */
155 public function testDeleteWithIncorrectData() {
156 $params = array(
157 'id' => 'abcd',
158 );
159 $result = $this->callAPIFailure('job', 'delete', $params);
160 }
161
162 /**
163 * Check job delete.
164 */
165 public function testDelete() {
166 $createResult = $this->callAPISuccess('job', 'create', $this->_params);
167 $params = array('id' => $createResult['id']);
168 $result = $this->callAPIAndDocument('job', 'delete', $params, __FUNCTION__, __FILE__);
169 $this->assertAPIDeleted($this->_entity, $createResult['id']);
170 }
171
172 /**
173 *
174 * public function testCallUpdateGreetingMissingParams() {
175 * $result = $this->callAPISuccess($this->_entity, 'update_greeting', array('gt' => 1));
176 * $this->assertEquals('Mandatory key(s) missing from params array: ct', $result['error_message']);
177 * }
178 *
179 * public function testCallUpdateGreetingIncorrectParams() {
180 * $result = $this->callAPISuccess($this->_entity, 'update_greeting', array('gt' => 1, 'ct' => 'djkfhdskjfhds'));
181 * $this->assertEquals('ct `djkfhdskjfhds` is not valid.', $result['error_message']);
182 * }
183 * /*
184 * Note that this test is about tesing the metadata / calling of the function & doesn't test the success of the called function
185 */
186 public function testCallUpdateGreetingSuccess() {
187 $result = $this->callAPISuccess($this->_entity, 'update_greeting', array(
188 'gt' => 'postal_greeting',
189 'ct' => 'Individual',
190 ));
191 }
192
193 public function testCallUpdateGreetingCommaSeparatedParamsSuccess() {
194 $gt = 'postal_greeting,email_greeting,addressee';
195 $ct = 'Individual,Household';
196 $result = $this->callAPISuccess($this->_entity, 'update_greeting', array('gt' => $gt, 'ct' => $ct));
197 }
198
199 /**
200 * Test the call reminder success sends more than 25 reminders & is not incorrectly limited.
201 *
202 * Note that this particular test sends the reminders to the additional recipients only
203 * as no real reminder person is configured
204 *
205 * Also note that this is testing a 'job' api so is in this class rather than scheduled_reminder - which
206 * seems a cleaner place to build up a collection of scheduled reminder testing functions. However, it seems
207 * that the api itself would need to be moved to the scheduled_reminder fn to do that with the job wrapper being respected for legacy functions
208 */
209 public function testCallSendReminderSuccessMoreThanDefaultLimit() {
210 $membershipTypeID = $this->membershipTypeCreate();
211 $this->membershipStatusCreate();
212 $createTotal = 30;
213 for ($i = 1; $i <= $createTotal; $i++) {
214 $contactID = $this->individualCreate();
215 $groupID = $this->groupCreate(array('name' => $i, 'title' => $i));
216 $result = $this->callAPISuccess('action_schedule', 'create', array(
217 'title' => " job $i",
218 'subject' => "job $i",
219 'entity_value' => $membershipTypeID,
220 'mapping_id' => 4,
221 'start_action_date' => 'membership_join_date',
222 'start_action_offset' => 0,
223 'start_action_condition' => 'before',
224 'start_action_unit' => 'hour',
225 'group_id' => $groupID,
226 'limit_to' => FALSE,
227 ));
228 $this->callAPISuccess('group_contact', 'create', array(
229 'contact_id' => $contactID,
230 'status' => 'Added',
231 'group_id' => $groupID,
232 ));
233 }
234 $result = $this->callAPISuccess('job', 'send_reminder', array());
235 $successfulCronCount = CRM_Core_DAO::singleValueQuery("SELECT count(*) FROM civicrm_action_log");
236 $this->assertEquals($successfulCronCount, $createTotal);
237 }
238
239 /**
240 * Test scheduled reminders respect limit to (since above identified addition_to handling issue).
241 *
242 * We create 3 contacts - 1 is in our group, 1 has our membership & the chosen one has both
243 * & check that only the chosen one got the reminder
244 */
245 public function testCallSendReminderLimitTo() {
246 $membershipTypeID = $this->membershipTypeCreate();
247 $this->membershipStatusCreate();
248 $createTotal = 3;
249 $groupID = $this->groupCreate(array('name' => 'Texan drawlers', 'title' => 'a...'));
250 for ($i = 1; $i <= $createTotal; $i++) {
251 $contactID = $this->individualCreate();
252 if ($i == 2) {
253 $theChosenOneID = $contactID;
254 }
255 if ($i < 3) {
256 $this->callAPISuccess('group_contact', 'create', array(
257 'contact_id' => $contactID,
258 'status' => 'Added',
259 'group_id' => $groupID,
260 ));
261 }
262 if ($i > 1) {
263 $this->callAPISuccess('membership', 'create', array(
264 'contact_id' => $contactID,
265 'membership_type_id' => $membershipTypeID,
266 'join_date' => 'now',
267 'start_date' => '+ 1 day',
268 )
269 );
270 }
271 }
272 $this->callAPISuccess('action_schedule', 'create', array(
273 'title' => " remind all Texans",
274 'subject' => "drawling renewal",
275 'entity_value' => $membershipTypeID,
276 'mapping_id' => 4,
277 'start_action_date' => 'membership_start_date',
278 'start_action_offset' => 1,
279 'start_action_condition' => 'before',
280 'start_action_unit' => 'day',
281 'group_id' => $groupID,
282 'limit_to' => TRUE,
283 ));
284 $this->callAPISuccess('job', 'send_reminder', array());
285 $successfulCronCount = CRM_Core_DAO::singleValueQuery("SELECT count(*) FROM civicrm_action_log");
286 $this->assertEquals($successfulCronCount, 1);
287 $sentToID = CRM_Core_DAO::singleValueQuery("SELECT contact_id FROM civicrm_action_log");
288 $this->assertEquals($sentToID, $theChosenOneID);
289 }
290
291 public function testCallDisableExpiredRelationships() {
292 $individualID = $this->individualCreate();
293 $orgID = $this->organizationCreate();
294 CRM_Utils_Hook_UnitTests::singleton()->setHook('civicrm_pre', array($this, 'hookPreRelationship'));
295 $relationshipTypeID = $this->callAPISuccess('relationship_type', 'getvalue', array(
296 'return' => 'id',
297 'name_a_b' => 'Employee of',
298 ));
299 $result = $this->callAPISuccess('relationship', 'create', array(
300 'relationship_type_id' => $relationshipTypeID,
301 'contact_id_a' => $individualID,
302 'contact_id_b' => $orgID,
303 'is_active' => 1,
304 'end_date' => 'yesterday',
305 ));
306 $relationshipID = $result['id'];
307 $this->assertEquals('Hooked', $result['values'][$relationshipID]['description']);
308 $this->callAPISuccess($this->_entity, 'disable_expired_relationships', array());
309 $result = $this->callAPISuccess('relationship', 'get', array());
310 $this->assertEquals('Go Go you good thing', $result['values'][$relationshipID]['description']);
311 $this->contactDelete($individualID);
312 $this->contactDelete($orgID);
313 }
314
315 /**
316 * Test the batch merge function.
317 *
318 * We are just checking it returns without error here.
319 */
320 public function testBatchMerge() {
321 $this->callAPISuccess('Job', 'process_batch_merge', array());
322 }
323
324 /**
325 * Test the batch merge function actually works!
326 *
327 * @dataProvider getMergeSets
328 *
329 * @param $dataSet
330 */
331 public function testBatchMergeWorks($dataSet) {
332 foreach ($dataSet['contacts'] as $params) {
333 $this->callAPISuccess('Contact', 'create', $params);
334 }
335
336 $result = $this->callAPISuccess('Job', 'process_batch_merge', array('mode' => $dataSet['mode']));
337 $this->assertEquals($dataSet['skipped'], count($result['values']['skipped']), 'Failed to skip the right number:' . $dataSet['skipped']);
338 $this->assertEquals($dataSet['merged'], count($result['values']['merged']));
339 $result = $this->callAPISuccess('Contact', 'get', array(
340 'contact_sub_type' => 'Student',
341 'sequential' => 1,
342 'is_deceased' => array('IN' => array(0, 1)),
343 'options' => array('sort' => 'id ASC'),
344 ));
345 $this->assertEquals(count($dataSet['expected']), $result['count']);
346 foreach ($dataSet['expected'] as $index => $contact) {
347 foreach ($contact as $key => $value) {
348 if ($key == 'gender_id') {
349 $key = 'gender';
350 }
351 $this->assertEquals($value, $result['values'][$index][$key]);
352 }
353 }
354 }
355
356 /**
357 * Test the batch merge function actually works!
358 *
359 * @dataProvider getMergeSets
360 *
361 * @param $dataSet
362 */
363 public function testBatchMergeConflictOnDeceased($dataSet) {
364 foreach ($dataSet['contacts'] as $params) {
365 $this->callAPISuccess('Contact', 'create', $params);
366 }
367
368 $result = $this->callAPISuccess('Job', 'process_batch_merge', array('mode' => $dataSet['mode']));
369 $this->assertEquals($dataSet['skipped'], count($result['values']['skipped']), 'Failed to skip the right number:' . $dataSet['skipped']);
370 $this->assertEquals($dataSet['merged'], count($result['values']['merged']));
371 $result = $this->callAPISuccess('Contact', 'get', array(
372 'contact_sub_type' => 'Student',
373 'sequential' => 1,
374 'options' => array('sort' => 'id ASC'),
375 ));
376 $this->assertEquals(count($dataSet['expected']), $result['count']);
377 foreach ($dataSet['expected'] as $index => $contact) {
378 foreach ($contact as $key => $value) {
379 // Handle the fact it's in a different field in the return value.
380 if ($key == 'gender_id') {
381 $key = 'gender';
382 }
383 $this->assertEquals($value, $result['values'][$index][$key]);
384 }
385 }
386 }
387
388 /**
389 * Check that the merge carries across various related entities.
390 *
391 * Note the group combinations & expected results:
392 */
393 public function testBatchMergeWithAssets() {
394 $contactID = $this->individualCreate();
395 $contact2ID = $this->individualCreate();
396 $this->contributionCreate(array('contact_id' => $contactID));
397 $this->contributionCreate(array('contact_id' => $contact2ID, 'invoice_id' => '2', 'trxn_id' => 2));
398 $this->contactMembershipCreate(array('contact_id' => $contactID));
399 $this->contactMembershipCreate(array('contact_id' => $contact2ID));
400 $this->activityCreate(array('source_contact_id' => $contactID, 'target_contact_id' => $contactID, 'assignee_contact_id' => $contactID));
401 $this->activityCreate(array('source_contact_id' => $contact2ID, 'target_contact_id' => $contact2ID, 'assignee_contact_id' => $contact2ID));
402 $this->tagCreate(array('name' => 'Tall'));
403 $this->tagCreate(array('name' => 'Short'));
404 $this->entityTagAdd(array('contact_id' => $contactID, 'tag_id' => 'Tall'));
405 $this->entityTagAdd(array('contact_id' => $contact2ID, 'tag_id' => 'Short'));
406 $this->entityTagAdd(array('contact_id' => $contact2ID, 'tag_id' => 'Tall'));
407 $result = $this->callAPISuccess('Job', 'process_batch_merge', array('mode' => 'safe'));
408 $this->assertEquals(0, count($result['values']['skipped']));
409 $this->assertEquals(1, count($result['values']['merged']));
410 $this->callAPISuccessGetCount('Contribution', array('contact_id' => $contactID), 2);
411 $this->callAPISuccessGetCount('Contribution', array('contact_id' => $contact2ID), 0);
412 $this->callAPISuccessGetCount('FinancialItem', array('contact_id' => $contactID), 2);
413 $this->callAPISuccessGetCount('FinancialItem', array('contact_id' => $contact2ID), 0);
414 $this->callAPISuccessGetCount('Membership', array('contact_id' => $contactID), 2);
415 $this->callAPISuccessGetCount('Membership', array('contact_id' => $contact2ID), 0);
416 $this->callAPISuccessGetCount('EntityTag', array('contact_id' => $contactID), 2);
417 $this->callAPISuccessGetCount('EntityTag', array('contact_id' => $contact2ID), 0);
418 // 12 activities is one for each contribution (2), one for each membership (+2 = 4)
419 // 3 for each of the added activities as there are 3 roles (+6 = 10
420 // 2 for the (source & target) contact merged activity (+2 = 12)
421 $this->callAPISuccessGetCount('ActivityContact', array('contact_id' => $contactID), 12);
422 // 2 for the connection to the deleted by merge activity (source & target)
423 $this->callAPISuccessGetCount('ActivityContact', array('contact_id' => $contact2ID), 2);
424 }
425
426 /**
427 * Check that the merge carries across various related entities.
428 *
429 * Note the group combinations 'expected' results:
430 *
431 * Group 0 Added null Added
432 * Group 1 Added Added Added
433 * Group 2 Added Removed **** Added
434 * Group 3 Removed null **** null
435 * Group 4 Removed Added **** Added
436 * Group 5 Removed Removed **** null
437 * Group 6 null Added Added
438 * Group 7 null Removed **** null
439 *
440 * The ones with **** are the ones where I think a case could be made to change the behaviour.
441 */
442 public function testBatchMergeMergesGroups() {
443 $contactID = $this->individualCreate();
444 $contact2ID = $this->individualCreate();
445 $groups = array();
446 for ($i = 0; $i < 8; $i++) {
447 $groups[] = $this->groupCreate(array(
448 'name' => 'mergeGroup' . $i,
449 'title' => 'merge group' . $i,
450 ));
451 }
452
453 $this->callAPISuccess('GroupContact', 'create', array(
454 'contact_id' => $contactID,
455 'group_id' => $groups[0],
456 ));
457 $this->callAPISuccess('GroupContact', 'create', array(
458 'contact_id' => $contactID,
459 'group_id' => $groups[1],
460 ));
461 $this->callAPISuccess('GroupContact', 'create', array(
462 'contact_id' => $contactID,
463 'group_id' => $groups[2],
464 ));
465 $this->callAPISuccess('GroupContact', 'create', array(
466 'contact_id' => $contactID,
467 'group_id' => $groups[3],
468 'status' => 'Removed',
469 ));
470 $this->callAPISuccess('GroupContact', 'create', array(
471 'contact_id' => $contactID,
472 'group_id' => $groups[4],
473 'status' => 'Removed',
474 ));
475 $this->callAPISuccess('GroupContact', 'create', array(
476 'contact_id' => $contactID,
477 'group_id' => $groups[5],
478 'status' => 'Removed',
479 ));
480 $this->callAPISuccess('GroupContact', 'create', array(
481 'contact_id' => $contact2ID,
482 'group_id' => $groups[1],
483 ));
484 $this->callAPISuccess('GroupContact', 'create', array(
485 'contact_id' => $contact2ID,
486 'group_id' => $groups[2],
487 'status' => 'Removed',
488 ));
489 $this->callAPISuccess('GroupContact', 'create', array(
490 'contact_id' => $contact2ID,
491 'group_id' => $groups[4],
492 ));
493 $this->callAPISuccess('GroupContact', 'create', array(
494 'contact_id' => $contact2ID,
495 'group_id' => $groups[5],
496 'status' => 'Removed',
497 ));
498 $this->callAPISuccess('GroupContact', 'create', array(
499 'contact_id' => $contact2ID,
500 'group_id' => $groups[6],
501 ));
502 $this->callAPISuccess('GroupContact', 'create', array(
503 'contact_id' => $contact2ID,
504 'group_id' => $groups[7],
505 'status' => 'Removed',
506 ));
507 $result = $this->callAPISuccess('Job', 'process_batch_merge', array('mode' => 'safe'));
508 $this->assertEquals(0, count($result['values']['skipped']));
509 $this->assertEquals(1, count($result['values']['merged']));
510 $groupResult = $this->callAPISuccess('GroupContact', 'get', array());
511 $this->assertEquals(5, $groupResult['count']);
512 $expectedGroups = array(
513 $groups[0],
514 $groups[1],
515 $groups[2],
516 $groups[4],
517 $groups[6],
518 );
519 foreach ($groupResult['values'] as $groupValues) {
520 $this->assertEquals($contactID, $groupValues['contact_id']);
521 $this->assertEquals('Added', $groupValues['status']);
522 $this->assertTrue(in_array($groupValues['group_id'], $expectedGroups));
523
524 }
525 }
526
527 /**
528 * Test the decisions made for addresses when merging.
529 *
530 * @dataProvider getMergeLocationData
531 *
532 * Scenarios:
533 * (the ones with **** could be disputed as whether it is the best outcome).
534 * 'matching_primary' - Primary matches, including location_type_id. One contact has an additional address.
535 * - result - primary is the shared one. Additional address is retained.
536 * 'matching_primary_reverse' - Primary matches, including location_type_id. Keep both. (opposite order)
537 * - result - primary is the shared one. Additional address is retained.
538 * 'only_one_has_address' - Only one contact has addresses (retain)
539 * - the (only) address is retained
540 * 'only_one_has_address_reverse'
541 * - the (only) address is retained
542 * 'different_primaries_with_different_location_type' Primaries are different but do not clash due to diff type
543 * - result - both addresses kept. The one from the kept (lowest ID) contact is primary
544 * 'different_primaries_with_different_location_type_reverse' Primaries are different but do not clash due to diff type
545 * - result - both addresses kept. The one from the kept (lowest ID) contact is primary
546 * 'different_primaries_location_match_only_one_address' per previous but a second address matches the primary but is not primary
547 * - result - both addresses kept. The one from the kept (lowest ID) contact is primary
548 * 'different_primaries_location_match_only_one_address_reverse' per previous but a second address matches the primary but is not primary
549 * - result - both addresses kept. The one from the kept (lowest ID) contact is primary
550 * 'same_primaries_different_location' Primary addresses are the same but have different location type IDs
551 * - result primary kept with the lowest ID. Other address retained too (to preserve location type info).
552 * 'same_primaries_different_location_reverse' Primary addresses are the same but have different location type IDs
553 * - result primary kept with the lowest ID. Other address retained too (to preserve location type info).
554 *
555 * @param array $dataSet
556 */
557 public function testBatchMergesAddresses($dataSet) {
558 $contactID1 = $this->individualCreate();
559 $contactID2 = $this->individualCreate();
560 foreach ($dataSet['contact_1'] as $address) {
561 $this->callAPISuccess($dataSet['entity'], 'create', array_merge(array('contact_id' => $contactID1), $address));
562 }
563 foreach ($dataSet['contact_2'] as $address) {
564 $this->callAPISuccess($dataSet['entity'], 'create', array_merge(array('contact_id' => $contactID2), $address));
565 }
566
567 $result = $this->callAPISuccess('Job', 'process_batch_merge', array('mode' => 'safe'));
568 $this->assertEquals(1, count($result['values']['merged']));
569 $addresses = $this->callAPISuccess($dataSet['entity'], 'get', array('contact_id' => $contactID1, 'sequential' => 1));
570 $this->assertEquals(count($dataSet['expected']), $addresses['count'], "Did not get the expected result for " . $dataSet['entity'] . (!empty($dataSet['description']) ? " on dataset {$dataSet['description']}" : ''));
571 $locationTypes = $this->callAPISuccess($dataSet['entity'], 'getoptions', array('field' => 'location_type_id'));
572 foreach ($dataSet['expected'] as $index => $expectedAddress) {
573 foreach ($expectedAddress as $key => $value) {
574 if ($key == 'location_type_id') {
575 $this->assertEquals($locationTypes['values'][$addresses['values'][$index][$key]], $value);
576 }
577 else {
578 $this->assertEquals($addresses['values'][$index][$key], $value, "mismatch on $key" . (!empty($dataSet['description']) ? " on dataset {$dataSet['description']}" : ''));
579 }
580 }
581 }
582 }
583
584 /**
585 * Test altering the address decision by hook.
586 *
587 * @dataProvider getMergeLocationData
588 *
589 * @param array $dataSet
590 */
591 public function testBatchMergesAddressesHook($dataSet) {
592 $contactID1 = $this->individualCreate();
593 $contactID2 = $this->individualCreate();
594 $this->contributionCreate(array('contact_id' => $contactID1, 'receive_date' => '2010-01-01', 'invoice_id' => 1, 'trxn_id' => 1));
595 $this->contributionCreate(array('contact_id' => $contactID2, 'receive_date' => '2012-01-01', 'invoice_id' => 2, 'trxn_id' => 2));
596 foreach ($dataSet['contact_1'] as $address) {
597 $this->callAPISuccess($dataSet['entity'], 'create', array_merge(array('contact_id' => $contactID1), $address));
598 }
599 foreach ($dataSet['contact_2'] as $address) {
600 $this->callAPISuccess($dataSet['entity'], 'create', array_merge(array('contact_id' => $contactID2), $address));
601 }
602 $this->hookClass->setHook('civicrm_alterLocationMergeData', array($this, 'hookMostRecentDonor'));
603
604 $result = $this->callAPISuccess('Job', 'process_batch_merge', array('mode' => 'safe'));
605 $this->assertEquals(1, count($result['values']['merged']));
606 $addresses = $this->callAPISuccess($dataSet['entity'], 'get', array('contact_id' => $contactID1, 'sequential' => 1));
607 $this->assertEquals(count($dataSet['expected_hook']), $addresses['count']);
608 $locationTypes = $this->callAPISuccess($dataSet['entity'], 'getoptions', array('field' => 'location_type_id'));
609 foreach ($dataSet['expected_hook'] as $index => $expectedAddress) {
610 foreach ($expectedAddress as $key => $value) {
611 if ($key == 'location_type_id') {
612 $this->assertEquals($locationTypes['values'][$addresses['values'][$index][$key]], $value);
613 }
614 else {
615 $this->assertEquals($value, $addresses['values'][$index][$key], $dataSet['entity'] . ': Unexpected value for ' . $key . (!empty($dataSet['description']) ? " on dataset {$dataSet['description']}" : ''));
616 }
617 }
618 }
619 }
620
621 /**
622 * Test the organization will not be matched to an individual.
623 */
624 public function testBatchMergeWillNotMergeOrganizationToIndividual() {
625 $individual = $this->callAPISuccess('Contact', 'create', array(
626 'contact_type' => 'Individual',
627 'organization_name' => 'Anon',
628 'email' => 'anonymous@hacker.com',
629 ));
630 $organization = $this->callAPISuccess('Contact', 'create', array(
631 'contact_type' => 'Organization',
632 'organization_name' => 'Anon',
633 'email' => 'anonymous@hacker.com',
634 ));
635 $result = $this->callAPISuccess('Job', 'process_batch_merge', array('mode' => 'aggressive'));
636 $this->assertEquals(0, count($result['values']['skipped']));
637 $this->assertEquals(0, count($result['values']['merged']));
638 $this->callAPISuccessGetSingle('Contact', array('id' => $individual['id']));
639 $this->callAPISuccessGetSingle('Contact', array('id' => $organization['id']));
640
641 }
642
643 /**
644 * Test hook allowing modification of the data calculated for merging locations.
645 *
646 * We are testing a nuanced real life situation where the address data of the
647 * most recent donor gets priority - resulting in the primary address being set
648 * to the primary address of the most recent donor and address data on a per
649 * location type basis also being set to the most recent donor. Hook also excludes
650 * a fully matching address with a different location.
651 *
652 * This has been added to the test suite to ensure the code supports more this
653 * type of intervention.
654 *
655 * @param array $blocksDAO
656 * Array of location DAO to be saved. These are arrays in 2 keys 'update' & 'delete'.
657 * @param int $mainId
658 * Contact_id of the contact that survives the merge.
659 * @param int $otherId
660 * Contact_id of the contact that will be absorbed and deleted.
661 * @param array $migrationInfo
662 * Calculated migration info, informational only.
663 *
664 * @return mixed
665 */
666 public function hookMostRecentDonor(&$blocksDAO, $mainId, $otherId, $migrationInfo) {
667
668 $lastDonorID = $this->callAPISuccessGetValue('Contribution', array(
669 'return' => 'contact_id',
670 'contact_id' => array('IN' => array($mainId, $otherId)),
671 'options' => array('sort' => 'receive_date DESC', 'limit' => 1),
672 ));
673 // Since the last donor is not the main ID we are prioritising info from the last donor.
674 // In the test this should always be true - but keep the check in case
675 // something changes that we need to detect.
676 if ($lastDonorID != $mainId) {
677 foreach ($migrationInfo['other_details']['location_blocks'] as $blockType => $blocks) {
678 foreach ($blocks as $block) {
679 if ($block['is_primary']) {
680 $primaryAddressID = $block['id'];
681 if (!empty($migrationInfo['main_details']['location_blocks'][$blockType])) {
682 foreach ($migrationInfo['main_details']['location_blocks'][$blockType] as $mainBlock) {
683 if (empty($blocksDAO[$blockType]['update'][$block['id']]) && $mainBlock['location_type_id'] == $block['location_type_id']) {
684 // This was an address match - we just need to check the is_primary
685 // is true on the matching kept address.
686 $primaryAddressID = $mainBlock['id'];
687 $blocksDAO[$blockType]['update'][$primaryAddressID] = _civicrm_api3_load_DAO($blockType);
688 $blocksDAO[$blockType]['update'][$primaryAddressID]->id = $primaryAddressID;
689 }
690 $mainLocationTypeID = $mainBlock['location_type_id'];
691 // We also want to be more ruthless about removing matching addresses.
692 unset($mainBlock['location_type_id']);
693 if (CRM_Dedupe_Merger::locationIsSame($block, $mainBlock)
694 && (!isset($blocksDAO[$blockType]['update']) || !isset($blocksDAO[$blockType]['update'][$mainBlock['id']]))
695 && (!isset($blocksDAO[$blockType]['delete']) || !isset($blocksDAO[$blockType]['delete'][$mainBlock['id']]))
696 ) {
697 $blocksDAO[$blockType]['delete'][$mainBlock['id']] = _civicrm_api3_load_DAO($blockType);
698 $blocksDAO[$blockType]['delete'][$mainBlock['id']]->id = $mainBlock['id'];
699 }
700 // Arguably the right way to handle this is just to set is_primary for the primary
701 // and for the merge fn to call something like BAO::add & hooks to work etc.
702 // if that happens though this should keep working...
703 elseif ($mainBlock['is_primary'] && $mainLocationTypeID != $block['location_type_id']) {
704 $blocksDAO['address']['update'][$mainBlock['id']] = _civicrm_api3_load_DAO($blockType);
705 $blocksDAO['address']['update'][$mainBlock['id']]->is_primary = 0;
706 $blocksDAO['address']['update'][$mainBlock['id']]->id = $mainBlock['id'];
707 }
708
709 }
710 $blocksDAO[$blockType]['update'][$primaryAddressID]->is_primary = 1;
711 }
712 }
713 }
714 }
715 }
716 }
717
718 /**
719 * Get address combinations for the merge test.
720 *
721 * @return array
722 */
723 public function getMergeLocationData() {
724 $address1 = array('street_address' => 'Buckingham Palace', 'city' => 'London');
725 $address2 = array('street_address' => 'The Doghouse', 'supplemental_address_1' => 'under the blanket');
726 $data = $this->getMergeLocations($address1, $address2, 'Address');
727 $data = array_merge($data, $this->getMergeLocations(array('phone' => '12345', 'phone_type_id' => 1), array('phone' => '678910', 'phone_type_id' => 1), 'Phone'));
728 $data = array_merge($data, $this->getMergeLocations(array('phone' => '12345'), array('phone' => '678910'), 'Phone'));
729 $data = array_merge($data, $this->getMergeLocations(array('email' => 'mini@me.com'), array('email' => 'mini@me.org'), 'Email', array(array(
730 'email' => 'anthony_anderson@civicrm.org',
731 'location_type_id' => 'Home',
732 ))));
733 return $data;
734
735 }
736
737 /**
738 * Test the batch merge does not create duplicate emails.
739 *
740 * Test CRM-18546, a 4.7 regression whereby a merged contact gets duplicate emails.
741 */
742 public function testBatchMergeEmailHandling() {
743 for ($x = 0; $x <= 4; $x++) {
744 $id = $this->individualCreate(array('email' => 'batman@gotham.met'));
745 }
746 $result = $this->callAPISuccess('Job', 'process_batch_merge', array());
747 $this->assertEquals(4, count($result['values']['merged']));
748 $this->callAPISuccessGetCount('Contact', array('email' => 'batman@gotham.met'), 1);
749 $contacts = $this->callAPISuccess('Contact', 'get', array('is_deleted' => 0));
750 $deletedContacts = $this->callAPISuccess('Contact', 'get', array('is_deleted' => 1));
751 $this->callAPISuccessGetCount('Email', array(
752 'email' => 'batman@gotham.met',
753 'contact_id' => array('IN' => array_keys($contacts['values'])),
754 ), 1);
755 $this->callAPISuccessGetCount('Email', array(
756 'email' => 'batman@gotham.met',
757 'contact_id' => array('IN' => array_keys($deletedContacts['values'])),
758 ), 4);
759 }
760
761 /**
762 * Test the batch merge does not fatal on an empty rule.
763 *
764 * @dataProvider getRuleSets
765 *
766 * @param string $contactType
767 * @param string $used
768 * @param bool $isReserved
769 * @param int $threshold
770 */
771 public function testBatchMergeEmptyRule($contactType, $used, $name, $isReserved, $threshold) {
772 $ruleGroup = $this->callAPISuccess('RuleGroup', 'create', array(
773 'contact_type' => $contactType,
774 'threshold' => $threshold,
775 'used' => $used,
776 'name' => $name,
777 'is_reserved' => $isReserved,
778 ));
779 $this->callAPISuccess('Job', 'process_batch_merge', array('rule_group_id' => $ruleGroup['id']));
780 $this->callAPISuccess('RuleGroup', 'delete', array('id' => $ruleGroup['id']));
781 }
782
783 /**
784 * Get the various rule combinations.
785 */
786 public function getRuleSets() {
787 $contactTypes = array('Individual', 'Organization', 'Household');
788 $useds = array('Unsupervised', 'General', 'Supervised');
789 $ruleGroups = array();
790 foreach ($contactTypes as $contactType) {
791 foreach ($useds as $used) {
792 $ruleGroups[] = array($contactType, $used, 'Bob', FALSE, 0);
793 $ruleGroups[] = array($contactType, $used, 'Bob', FALSE, 10);
794 $ruleGroups[] = array($contactType, $used, 'Bob', TRUE, 10);
795 $ruleGroups[] = array($contactType, $used, $contactType . $used, FALSE, 10);
796 $ruleGroups[] = array($contactType, $used, $contactType . $used, TRUE, 10);
797 }
798 }
799 return $ruleGroups;
800 }
801
802 /**
803 * Test the batch merge does not create duplicate emails.
804 *
805 * Test CRM-18546, a 4.7 regression whereby a merged contact gets duplicate emails.
806 */
807 public function testBatchMergeMatchingAddress() {
808 for ($x = 0; $x <= 2; $x++) {
809 $this->individualCreate(array(
810 'api.address.create' => array(
811 'location_type_id' => 'Home',
812 'street_address' => 'Appt 115, The Batcave',
813 'city' => 'Gotham',
814 'postal_code' => 'Nananananana',
815 ),
816 ));
817 }
818 // Different location type, still merge, identical.
819 $this->individualCreate(array(
820 'api.address.create' => array(
821 'location_type_id' => 'Main',
822 'street_address' => 'Appt 115, The Batcave',
823 'city' => 'Gotham',
824 'postal_code' => 'Nananananana',
825 ),
826 ));
827
828 $this->individualCreate(array(
829 'api.address.create' => array(
830 'location_type_id' => 'Home',
831 'street_address' => 'Appt 115, The Batcave',
832 'city' => 'Gotham',
833 'postal_code' => 'Batman',
834 ),
835 ));
836
837 $result = $this->callAPISuccess('Job', 'process_batch_merge', array());
838 $this->assertEquals(3, count($result['values']['merged']));
839 $this->assertEquals(1, count($result['values']['skipped']));
840 $this->callAPISuccessGetCount('Contact', array('street_address' => 'Appt 115, The Batcave'), 2);
841 $contacts = $this->callAPISuccess('Contact', 'get', array('is_deleted' => 0));
842 $deletedContacts = $this->callAPISuccess('Contact', 'get', array('is_deleted' => 1));
843 $this->callAPISuccessGetCount('Address', array(
844 'street_address' => 'Appt 115, The Batcave',
845 'contact_id' => array('IN' => array_keys($contacts['values'])),
846 ), 3);
847
848 $this->callAPISuccessGetCount('Address', array(
849 'street_address' => 'Appt 115, The Batcave',
850 'contact_id' => array('IN' => array_keys($deletedContacts['values'])),
851 ), 2);
852 }
853
854 /**
855 * Test the batch merge by id range.
856 *
857 * We have 2 sets of 5 matches & set the merge only to merge the lower set.
858 */
859 public function testBatchMergeIDRange() {
860 for ($x = 0; $x <= 4; $x++) {
861 $id = $this->individualCreate(array('email' => 'batman@gotham.met'));
862 }
863 for ($x = 0; $x <= 4; $x++) {
864 $this->individualCreate(array('email' => 'robin@gotham.met'));
865 }
866 $result = $this->callAPISuccess('Job', 'process_batch_merge', array('criteria' => array('contact' => array('id' => array('<' => $id)))));
867 $this->assertEquals(4, count($result['values']['merged']));
868 $this->callAPISuccessGetCount('Contact', array('email' => 'batman@gotham.met'), 1);
869 $this->callAPISuccessGetCount('Contact', array('email' => 'robin@gotham.met'), 5);
870 $contacts = $this->callAPISuccess('Contact', 'get', array('is_deleted' => 0));
871 $deletedContacts = $this->callAPISuccess('Contact', 'get', array('is_deleted' => 0));
872 $this->callAPISuccessGetCount('Email', array(
873 'email' => 'batman@gotham.met',
874 'contact_id' => array('IN' => array_keys($contacts['values'])),
875 ), 1);
876 $this->callAPISuccessGetCount('Email', array(
877 'email' => 'batman@gotham.met',
878 'contact_id' => array('IN' => array_keys($deletedContacts['values'])),
879 ), 1);
880 $this->callAPISuccessGetCount('Email', array(
881 'email' => 'robin@gotham.met',
882 'contact_id' => array('IN' => array_keys($contacts['values'])),
883 ), 5);
884
885 }
886
887 /**
888 * Test the batch merge function actually works!
889 *
890 * @dataProvider getMergeSets
891 *
892 * @param $dataSet
893 */
894 public function testBatchMergeWorksCheckPermissionsTrue($dataSet) {
895 CRM_Core_Config::singleton()->userPermissionClass->permissions = array('access CiviCRM', 'administer CiviCRM');
896 foreach ($dataSet['contacts'] as $params) {
897 $this->callAPISuccess('Contact', 'create', $params);
898 }
899
900 $result = $this->callAPISuccess('Job', 'process_batch_merge', array('check_permissions' => 1, 'mode' => $dataSet['mode']));
901 $this->assertEquals(0, count($result['values']['merged']), 'User does not have permission to any contacts, so no merging');
902 $this->assertEquals(0, count($result['values']['skipped']), 'User does not have permission to any contacts, so no skip visibility');
903 }
904
905 /**
906 * Test the batch merge function actually works!
907 *
908 * @dataProvider getMergeSets
909 *
910 * @param $dataSet
911 */
912 public function testBatchMergeWorksCheckPermissionsFalse($dataSet) {
913 CRM_Core_Config::singleton()->userPermissionClass->permissions = array('access CiviCRM', 'edit my contact');
914 foreach ($dataSet['contacts'] as $params) {
915 $this->callAPISuccess('Contact', 'create', $params);
916 }
917
918 $result = $this->callAPISuccess('Job', 'process_batch_merge', array('check_permissions' => 0, 'mode' => $dataSet['mode']));
919 $this->assertEquals($dataSet['skipped'], count($result['values']['skipped']), 'Failed to skip the right number:' . $dataSet['skipped']);
920 $this->assertEquals($dataSet['merged'], count($result['values']['merged']));
921 }
922
923 /**
924 * Get data for batch merge.
925 */
926 public function getMergeSets() {
927 $data = array(
928 array(
929 array(
930 'mode' => 'safe',
931 'contacts' => array(
932 array(
933 'first_name' => 'Michael',
934 'last_name' => 'Jackson',
935 'email' => 'michael@neverland.com',
936 'contact_type' => 'Individual',
937 'contact_sub_type' => 'Student',
938 'api.Address.create' => array(
939 'street_address' => 'big house',
940 'location_type_id' => 'Home',
941 ),
942 ),
943 array(
944 'first_name' => 'Michael',
945 'last_name' => 'Jackson',
946 'email' => 'michael@neverland.com',
947 'contact_type' => 'Individual',
948 'contact_sub_type' => 'Student',
949 ),
950 ),
951 'skipped' => 0,
952 'merged' => 1,
953 'expected' => array(
954 array(
955 'first_name' => 'Michael',
956 'last_name' => 'Jackson',
957 'email' => 'michael@neverland.com',
958 'contact_type' => 'Individual',
959 ),
960 ),
961 ),
962 ),
963 array(
964 array(
965 'mode' => 'safe',
966 'contacts' => array(
967 array(
968 'first_name' => 'Michael',
969 'last_name' => 'Jackson',
970 'email' => 'michael@neverland.com',
971 'contact_type' => 'Individual',
972 'contact_sub_type' => 'Student',
973 'api.Address.create' => array(
974 'street_address' => 'big house',
975 'location_type_id' => 'Home',
976 ),
977 ),
978 array(
979 'first_name' => 'Michael',
980 'last_name' => 'Jackson',
981 'email' => 'michael@neverland.com',
982 'contact_type' => 'Individual',
983 'contact_sub_type' => 'Student',
984 'api.Address.create' => array(
985 'street_address' => 'bigger house',
986 'location_type_id' => 'Home',
987 ),
988 ),
989 ),
990 'skipped' => 1,
991 'merged' => 0,
992 'expected' => array(
993 array(
994 'first_name' => 'Michael',
995 'last_name' => 'Jackson',
996 'email' => 'michael@neverland.com',
997 'contact_type' => 'Individual',
998 'street_address' => 'big house',
999 ),
1000 array(
1001 'first_name' => 'Michael',
1002 'last_name' => 'Jackson',
1003 'email' => 'michael@neverland.com',
1004 'contact_type' => 'Individual',
1005 'street_address' => 'bigger house',
1006 ),
1007 ),
1008 ),
1009 ),
1010 array(
1011 array(
1012 'mode' => 'safe',
1013 'contacts' => array(
1014 array(
1015 'first_name' => 'Michael',
1016 'last_name' => 'Jackson',
1017 'email' => 'michael@neverland.com',
1018 'contact_type' => 'Individual',
1019 'contact_sub_type' => 'Student',
1020 'api.Email.create' => array(
1021 'email' => 'big.slog@work.co.nz',
1022 'location_type_id' => 'Work',
1023 ),
1024 ),
1025 array(
1026 'first_name' => 'Michael',
1027 'last_name' => 'Jackson',
1028 'email' => 'michael@neverland.com',
1029 'contact_type' => 'Individual',
1030 'contact_sub_type' => 'Student',
1031 'api.Email.create' => array(
1032 'email' => 'big.slog@work.com',
1033 'location_type_id' => 'Work',
1034 ),
1035 ),
1036 ),
1037 'skipped' => 1,
1038 'merged' => 0,
1039 'expected' => array(
1040 array(
1041 'first_name' => 'Michael',
1042 'last_name' => 'Jackson',
1043 'email' => 'michael@neverland.com',
1044 'contact_type' => 'Individual',
1045 ),
1046 array(
1047 'first_name' => 'Michael',
1048 'last_name' => 'Jackson',
1049 'email' => 'michael@neverland.com',
1050 'contact_type' => 'Individual',
1051 ),
1052 ),
1053 ),
1054 ),
1055 array(
1056 array(
1057 'mode' => 'safe',
1058 'contacts' => array(
1059 array(
1060 'first_name' => 'Michael',
1061 'last_name' => 'Jackson',
1062 'email' => 'michael@neverland.com',
1063 'contact_type' => 'Individual',
1064 'contact_sub_type' => 'Student',
1065 'api.Phone.create' => array(
1066 'phone' => '123456',
1067 'location_type_id' => 'Work',
1068 ),
1069 ),
1070 array(
1071 'first_name' => 'Michael',
1072 'last_name' => 'Jackson',
1073 'email' => 'michael@neverland.com',
1074 'contact_type' => 'Individual',
1075 'contact_sub_type' => 'Student',
1076 'api.Phone.create' => array(
1077 'phone' => '23456',
1078 'location_type_id' => 'Work',
1079 ),
1080 ),
1081 ),
1082 'skipped' => 1,
1083 'merged' => 0,
1084 'expected' => array(
1085 array(
1086 'first_name' => 'Michael',
1087 'last_name' => 'Jackson',
1088 'email' => 'michael@neverland.com',
1089 'contact_type' => 'Individual',
1090 ),
1091 array(
1092 'first_name' => 'Michael',
1093 'last_name' => 'Jackson',
1094 'email' => 'michael@neverland.com',
1095 'contact_type' => 'Individual',
1096 ),
1097 ),
1098 ),
1099 ),
1100 array(
1101 array(
1102 'mode' => 'aggressive',
1103 'contacts' => array(
1104 array(
1105 'first_name' => 'Michael',
1106 'last_name' => 'Jackson',
1107 'email' => 'michael@neverland.com',
1108 'contact_type' => 'Individual',
1109 'contact_sub_type' => 'Student',
1110 'api.Address.create' => array(
1111 'street_address' => 'big house',
1112 'location_type_id' => 'Home',
1113 ),
1114 ),
1115 array(
1116 'first_name' => 'Michael',
1117 'last_name' => 'Jackson',
1118 'email' => 'michael@neverland.com',
1119 'contact_type' => 'Individual',
1120 'contact_sub_type' => 'Student',
1121 'api.Address.create' => array(
1122 'street_address' => 'bigger house',
1123 'location_type_id' => 'Home',
1124 ),
1125 ),
1126 ),
1127 'skipped' => 0,
1128 'merged' => 1,
1129 'expected' => array(
1130 array(
1131 'first_name' => 'Michael',
1132 'last_name' => 'Jackson',
1133 'email' => 'michael@neverland.com',
1134 'contact_type' => 'Individual',
1135 'street_address' => 'big house',
1136 ),
1137 ),
1138 ),
1139 ),
1140 array(
1141 array(
1142 'mode' => 'safe',
1143 'contacts' => array(
1144 array(
1145 'first_name' => 'Michael',
1146 'last_name' => 'Jackson',
1147 'email' => 'michael@neverland.com',
1148 'contact_type' => 'Individual',
1149 'contact_sub_type' => 'Student',
1150 'api.Address.create' => array(
1151 'street_address' => 'big house',
1152 'location_type_id' => 'Home',
1153 ),
1154 ),
1155 array(
1156 'first_name' => 'Michael',
1157 'last_name' => 'Jackson',
1158 'email' => 'michael@neverland.com',
1159 'contact_type' => 'Individual',
1160 'contact_sub_type' => 'Student',
1161 'is_deceased' => 1,
1162 ),
1163 ),
1164 'skipped' => 1,
1165 'merged' => 0,
1166 'expected' => array(
1167 array(
1168 'first_name' => 'Michael',
1169 'last_name' => 'Jackson',
1170 'email' => 'michael@neverland.com',
1171 'contact_type' => 'Individual',
1172 'is_deceased' => 0,
1173 ),
1174 array(
1175 'first_name' => 'Michael',
1176 'last_name' => 'Jackson',
1177 'email' => 'michael@neverland.com',
1178 'contact_type' => 'Individual',
1179 'is_deceased' => 1,
1180 ),
1181 ),
1182 ),
1183 ),
1184 array(
1185 array(
1186 'mode' => 'safe',
1187 'contacts' => array(
1188 array(
1189 'first_name' => 'Michael',
1190 'last_name' => 'Jackson',
1191 'email' => 'michael@neverland.com',
1192 'contact_type' => 'Individual',
1193 'contact_sub_type' => 'Student',
1194 'api.Address.create' => array(
1195 'street_address' => 'big house',
1196 'location_type_id' => 'Home',
1197 ),
1198 'is_deceased' => 1,
1199 ),
1200 array(
1201 'first_name' => 'Michael',
1202 'last_name' => 'Jackson',
1203 'email' => 'michael@neverland.com',
1204 'contact_type' => 'Individual',
1205 'contact_sub_type' => 'Student',
1206 ),
1207 ),
1208 'skipped' => 1,
1209 'merged' => 0,
1210 'expected' => array(
1211 array(
1212 'first_name' => 'Michael',
1213 'last_name' => 'Jackson',
1214 'email' => 'michael@neverland.com',
1215 'contact_type' => 'Individual',
1216 'is_deceased' => 1,
1217 ),
1218 array(
1219 'first_name' => 'Michael',
1220 'last_name' => 'Jackson',
1221 'email' => 'michael@neverland.com',
1222 'contact_type' => 'Individual',
1223 'is_deceased' => 0,
1224 ),
1225 ),
1226 ),
1227 ),
1228 );
1229
1230 $conflictPairs = array(
1231 'first_name' => 'Dianna',
1232 'last_name' => 'McAndrew',
1233 'middle_name' => 'Prancer',
1234 'birth_date' => '2015-12-25',
1235 'gender_id' => 'Female',
1236 'job_title' => 'Thriller',
1237 );
1238
1239 foreach ($conflictPairs as $key => $value) {
1240 $contactParams = array(
1241 'first_name' => 'Michael',
1242 'middle_name' => 'Dancer',
1243 'last_name' => 'Jackson',
1244 'birth_date' => '2015-02-25',
1245 'email' => 'michael@neverland.com',
1246 'contact_type' => 'Individual',
1247 'contact_sub_type' => array('Student'),
1248 'gender_id' => 'Male',
1249 'job_title' => 'Entertainer',
1250 );
1251 $contact2 = $contactParams;
1252
1253 $contact2[$key] = $value;
1254 $data[$key . '_conflict'] = array(
1255 array(
1256 'mode' => 'safe',
1257 'contacts' => array($contactParams, $contact2),
1258 'skipped' => 1,
1259 'merged' => 0,
1260 'expected' => array($contactParams, $contact2),
1261 ),
1262 );
1263 }
1264
1265 return $data;
1266 }
1267
1268 /**
1269 * @param $op
1270 * @param string $objectName
1271 * @param int $id
1272 * @param array $params
1273 */
1274 public function hookPreRelationship($op, $objectName, $id, &$params) {
1275 if ($op == 'delete') {
1276 return;
1277 }
1278 if ($params['is_active']) {
1279 $params['description'] = 'Hooked';
1280 }
1281 else {
1282 $params['description'] = 'Go Go you good thing';
1283 }
1284 }
1285
1286 /**
1287 * Get the location data set.
1288 *
1289 * @param array $locationParams1
1290 * @param array $locationParams2
1291 * @param string $entity
1292 *
1293 * @return array
1294 */
1295 public function getMergeLocations($locationParams1, $locationParams2, $entity, $additionalExpected = array()) {
1296 $data = array(
1297 array(
1298 'matching_primary' => array(
1299 'entity' => $entity,
1300 'contact_1' => array(
1301 array_merge(array(
1302 'location_type_id' => 'Main',
1303 'is_primary' => 1,
1304 ), $locationParams1),
1305 array_merge(array(
1306 'location_type_id' => 'Work',
1307 'is_primary' => 0,
1308 ), $locationParams2),
1309 ),
1310 'contact_2' => array(
1311 array_merge(array(
1312 'location_type_id' => 'Main',
1313 'is_primary' => 1,
1314 ), $locationParams1),
1315 ),
1316 'expected' => array_merge($additionalExpected, array(
1317 array_merge(array(
1318 'location_type_id' => 'Main',
1319 'is_primary' => 1,
1320 ), $locationParams1),
1321 array_merge(array(
1322 'location_type_id' => 'Work',
1323 'is_primary' => 0,
1324 ), $locationParams2),
1325 )),
1326 'expected_hook' => array_merge($additionalExpected, array(
1327 array_merge(array(
1328 'location_type_id' => 'Main',
1329 'is_primary' => 1,
1330 ), $locationParams1),
1331 array_merge(array(
1332 'location_type_id' => 'Work',
1333 'is_primary' => 0,
1334 ), $locationParams2),
1335 )),
1336 ),
1337 ),
1338 array(
1339 'matching_primary_reverse' => array(
1340 'entity' => $entity,
1341 'contact_1' => array(
1342 array_merge(array(
1343 'location_type_id' => 'Main',
1344 'is_primary' => 1,
1345 ), $locationParams1),
1346 ),
1347 'contact_2' => array(
1348 array_merge(array(
1349 'location_type_id' => 'Main',
1350 'is_primary' => 1,
1351 ), $locationParams1),
1352 array_merge(array(
1353 'location_type_id' => 'Work',
1354 'is_primary' => 0,
1355 ), $locationParams2),
1356 ),
1357 'expected' => array_merge($additionalExpected, array(
1358 array_merge(array(
1359 'location_type_id' => 'Main',
1360 'is_primary' => 1,
1361 ), $locationParams1),
1362 array_merge(array(
1363 'location_type_id' => 'Work',
1364 'is_primary' => 0,
1365 ), $locationParams2),
1366 )),
1367 'expected_hook' => array_merge($additionalExpected, array(
1368 array_merge(array(
1369 'location_type_id' => 'Main',
1370 'is_primary' => 1,
1371 ), $locationParams1),
1372 array_merge(array(
1373 'location_type_id' => 'Work',
1374 'is_primary' => 0,
1375 ), $locationParams2),
1376 )),
1377 ),
1378 ),
1379 array(
1380 'only_one_has_address' => array(
1381 'entity' => $entity,
1382 'contact_1' => array(
1383 array_merge(array(
1384 'location_type_id' => 'Main',
1385 'is_primary' => 1,
1386 ), $locationParams1),
1387 array_merge(array(
1388 'location_type_id' => 'Work',
1389 'is_primary' => 0,
1390 ), $locationParams2),
1391 ),
1392 'contact_2' => array(),
1393 'expected' => array_merge($additionalExpected, array(
1394 array_merge(array(
1395 'location_type_id' => 'Main',
1396 'is_primary' => 1,
1397 ), $locationParams1),
1398 array_merge(array(
1399 'location_type_id' => 'Work',
1400 'is_primary' => 0,
1401 ), $locationParams2),
1402 )),
1403 'expected_hook' => array_merge($additionalExpected, array(
1404 array_merge(array(
1405 'location_type_id' => 'Main',
1406 // When dealing with email we don't have a clean slate - the existing
1407 // primary will be primary.
1408 'is_primary' => ($entity == 'Email' ? 0 : 1),
1409 ), $locationParams1),
1410 array_merge(array(
1411 'location_type_id' => 'Work',
1412 'is_primary' => 0,
1413 ), $locationParams2),
1414 )),
1415 ),
1416 ),
1417 array(
1418 'only_one_has_address_reverse' => array(
1419 'description' => 'The destination contact does not have an address. secondary contact should be merged in.',
1420 'entity' => $entity,
1421 'contact_1' => array(),
1422 'contact_2' => array(
1423 array_merge(array(
1424 'location_type_id' => 'Main',
1425 'is_primary' => 1,
1426 ), $locationParams1),
1427 array_merge(array(
1428 'location_type_id' => 'Work',
1429 'is_primary' => 0,
1430 ), $locationParams2),
1431 ),
1432 'expected' => array_merge($additionalExpected, array(
1433 array_merge(array(
1434 'location_type_id' => 'Main',
1435 // When dealing with email we don't have a clean slate - the existing
1436 // primary will be primary.
1437 'is_primary' => ($entity == 'Email' ? 0 : 1),
1438 ), $locationParams1),
1439 array_merge(array(
1440 'location_type_id' => 'Work',
1441 'is_primary' => 0,
1442 ), $locationParams2),
1443 )),
1444 'expected_hook' => array_merge($additionalExpected, array(
1445 array_merge(array(
1446 'location_type_id' => 'Main',
1447 'is_primary' => 1,
1448 ), $locationParams1),
1449 array_merge(array(
1450 'location_type_id' => 'Work',
1451 'is_primary' => 0,
1452 ), $locationParams2),
1453 )),
1454 ),
1455 ),
1456 array(
1457 'different_primaries_with_different_location_type' => array(
1458 'description' => 'Primaries are different with different location. Keep both addresses. Set primary to be that of lower id',
1459 'entity' => $entity,
1460 'contact_1' => array(
1461 array_merge(array(
1462 'location_type_id' => 'Main',
1463 'is_primary' => 1,
1464 ), $locationParams1),
1465 ),
1466 'contact_2' => array(
1467 array_merge(array(
1468 'location_type_id' => 'Work',
1469 'is_primary' => 1,
1470 ), $locationParams2),
1471 ),
1472 'expected' => array_merge($additionalExpected, array(
1473 array_merge(array(
1474 'location_type_id' => 'Main',
1475 'is_primary' => 1,
1476 ), $locationParams1),
1477 array_merge(array(
1478 'location_type_id' => 'Work',
1479 'is_primary' => 0,
1480 ), $locationParams2),
1481 )),
1482 'expected_hook' => array_merge($additionalExpected, array(
1483 array_merge(array(
1484 'location_type_id' => 'Main',
1485 'is_primary' => 0,
1486 ), $locationParams1),
1487 array_merge(array(
1488 'location_type_id' => 'Work',
1489 'is_primary' => 1,
1490 ), $locationParams2),
1491 )),
1492 ),
1493 ),
1494 array(
1495 'different_primaries_with_different_location_type_reverse' => array(
1496 'entity' => $entity,
1497 'contact_1' => array(
1498 array_merge(array(
1499 'location_type_id' => 'Work',
1500 'is_primary' => 1,
1501 ), $locationParams2),
1502 ),
1503 'contact_2' => array(
1504 array_merge(array(
1505 'location_type_id' => 'Main',
1506 'is_primary' => 1,
1507 ), $locationParams1),
1508 ),
1509 'expected' => array_merge($additionalExpected, array(
1510 array_merge(array(
1511 'location_type_id' => 'Work',
1512 'is_primary' => 1,
1513 ), $locationParams2),
1514 array_merge(array(
1515 'location_type_id' => 'Main',
1516 'is_primary' => 0,
1517 ), $locationParams1),
1518 )),
1519 'expected_hook' => array_merge($additionalExpected, array(
1520 array_merge(array(
1521 'location_type_id' => 'Work',
1522 'is_primary' => 0,
1523 ), $locationParams2),
1524 array_merge(array(
1525 'location_type_id' => 'Main',
1526 'is_primary' => 1,
1527 ), $locationParams1),
1528 )),
1529 ),
1530 ),
1531 array(
1532 'different_primaries_location_match_only_one_address' => array(
1533 'entity' => $entity,
1534 'contact_1' => array(
1535 array_merge(array(
1536 'location_type_id' => 'Main',
1537 'is_primary' => 1,
1538 ), $locationParams1),
1539 array_merge(array(
1540 'location_type_id' => 'Work',
1541 'is_primary' => 0,
1542 ), $locationParams2),
1543 ),
1544 'contact_2' => array(
1545 array_merge(array(
1546 'location_type_id' => 'Work',
1547 'is_primary' => 1,
1548 ), $locationParams2),
1549
1550 ),
1551 'expected' => array_merge($additionalExpected, array(
1552 array_merge(array(
1553 'location_type_id' => 'Main',
1554 'is_primary' => 1,
1555 ), $locationParams1),
1556 array_merge(array(
1557 'location_type_id' => 'Work',
1558 'is_primary' => 0,
1559 ), $locationParams2),
1560 )),
1561 'expected_hook' => array_merge($additionalExpected, array(
1562 array_merge(array(
1563 'location_type_id' => 'Main',
1564 'is_primary' => 0,
1565 ), $locationParams1),
1566 array_merge(array(
1567 'location_type_id' => 'Work',
1568 'is_primary' => 1,
1569 ), $locationParams2),
1570 )),
1571 ),
1572 ),
1573 array(
1574 'different_primaries_location_match_only_one_address_reverse' => array(
1575 'entity' => $entity,
1576 'contact_1' => array(
1577 array_merge(array(
1578 'location_type_id' => 'Work',
1579 'is_primary' => 1,
1580 ), $locationParams2),
1581 ),
1582 'contact_2' => array(
1583 array_merge(array(
1584 'location_type_id' => 'Main',
1585 'is_primary' => 1,
1586 ), $locationParams1),
1587 array_merge(array(
1588 'location_type_id' => 'Work',
1589 'is_primary' => 0,
1590 ), $locationParams2),
1591 ),
1592 'expected' => array_merge($additionalExpected, array(
1593 array_merge(array(
1594 'location_type_id' => 'Work',
1595 'is_primary' => 1,
1596 ), $locationParams2),
1597 array_merge(array(
1598 'location_type_id' => 'Main',
1599 'is_primary' => 0,
1600 ), $locationParams1),
1601 )),
1602 'expected_hook' => array_merge($additionalExpected, array(
1603 array_merge(array(
1604 'location_type_id' => 'Work',
1605 'is_primary' => 0,
1606 ), $locationParams2),
1607 array_merge(array(
1608 'location_type_id' => 'Main',
1609 'is_primary' => 1,
1610 ), $locationParams1),
1611 )),
1612 ),
1613 ),
1614 array(
1615 'same_primaries_different_location' => array(
1616 'entity' => $entity,
1617 'contact_1' => array(
1618 array_merge(array(
1619 'location_type_id' => 'Main',
1620 'is_primary' => 1,
1621 ), $locationParams1),
1622 ),
1623 'contact_2' => array(
1624 array_merge(array(
1625 'location_type_id' => 'Work',
1626 'is_primary' => 1,
1627 ), $locationParams1),
1628
1629 ),
1630 'expected' => array_merge($additionalExpected, array(
1631 array_merge(array(
1632 'location_type_id' => 'Main',
1633 'is_primary' => 1,
1634 ), $locationParams1),
1635 array_merge(array(
1636 'location_type_id' => 'Work',
1637 'is_primary' => 0,
1638 ), $locationParams1),
1639 )),
1640 'expected_hook' => array_merge($additionalExpected, array(
1641 array_merge(array(
1642 'location_type_id' => 'Work',
1643 'is_primary' => 1,
1644 ), $locationParams1),
1645 )),
1646 ),
1647 ),
1648 array(
1649 'same_primaries_different_location_reverse' => array(
1650 'entity' => $entity,
1651 'contact_1' => array(
1652 array_merge(array(
1653 'location_type_id' => 'Work',
1654 'is_primary' => 1,
1655 ), $locationParams1),
1656 ),
1657 'contact_2' => array(
1658 array_merge(array(
1659 'location_type_id' => 'Main',
1660 'is_primary' => 1,
1661 ), $locationParams1),
1662 ),
1663 'expected' => array_merge($additionalExpected, array(
1664 array_merge(array(
1665 'location_type_id' => 'Work',
1666 'is_primary' => 1,
1667 ), $locationParams1),
1668 array_merge(array(
1669 'location_type_id' => 'Main',
1670 'is_primary' => 0,
1671 ), $locationParams1),
1672 )),
1673 'expected_hook' => array_merge($additionalExpected, array(
1674 array_merge(array(
1675 'location_type_id' => 'Main',
1676 'is_primary' => 1,
1677 ), $locationParams1),
1678 )),
1679 ),
1680 ),
1681 );
1682 return $data;
1683 }
1684
1685 }