Merge pull request #18706 from civicrm/5.30
[civicrm-core.git] / tests / phpunit / CRM / Contact / Import / Parser / ContactTest.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This code is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12 /**
13 * @file
14 * File for the CRM_Contact_Imports_Parser_ContactTest class.
15 */
16
17 /**
18 * Test contact import parser.
19 *
20 * @package CiviCRM
21 * @group headless
22 */
23 class CRM_Contact_Import_Parser_ContactTest extends CiviUnitTestCase {
24 use CRMTraits_Custom_CustomDataTrait;
25
26 /**
27 * Main entity for the class.
28 *
29 * @var string
30 */
31 protected $entity = 'Contact';
32
33 /**
34 * Setup function.
35 */
36 public function setUp() {
37 parent::setUp();
38 }
39
40 /**
41 * Tear down after test.
42 *
43 * @throws \CRM_Core_Exception
44 */
45 public function tearDown() {
46 $this->quickCleanup(['civicrm_address', 'civicrm_phone', 'civicrm_email'], TRUE);
47 parent::tearDown();
48 }
49
50 /**
51 * Test that import parser will add contact with employee of relationship.
52 *
53 * @throws \Exception
54 */
55 public function testImportParserWtihEmployeeOfRelationship() {
56 $this->organizationCreate([
57 "organization_name" => "Agileware",
58 "legal_name" => "Agileware",
59 ]);
60 $contactImportValues = [
61 "first_name" => "Alok",
62 "last_name" => "Patel",
63 "Employee of" => "Agileware",
64 ];
65
66 $fields = array_keys($contactImportValues);
67 $values = array_values($contactImportValues);
68 $parser = new CRM_Contact_Import_Parser_Contact($fields, []);
69 $parser->_contactType = 'Individual';
70 $parser->init();
71 $this->mapRelationshipFields($fields, $parser->getAllFields());
72
73 $parser = new CRM_Contact_Import_Parser_Contact($fields, [], [], [], [
74 NULL,
75 NULL,
76 $fields[2],
77 ], [
78 NULL,
79 NULL,
80 "Organization",
81 ], [
82 NULL,
83 NULL,
84 "organization_name",
85 ], [], [], [], [], []);
86
87 $parser->_contactType = 'Individual';
88 $parser->_onDuplicate = CRM_Import_Parser::DUPLICATE_UPDATE;
89 $parser->init();
90
91 $this->assertEquals(CRM_Import_Parser::VALID, $parser->import(CRM_Import_Parser::DUPLICATE_UPDATE, $values), 'Return code from parser import was not as expected');
92 $this->callAPISuccess("Contact", "get", [
93 "first_name" => "Alok",
94 "last_name" => "Patel",
95 "organization_name" => "Agileware",
96 ]);
97 }
98
99 /**
100 * Test that import parser will not fail when same external_identifier found of deleted contact.
101 *
102 * @throws \CRM_Core_Exception
103 */
104 public function testImportParserWtihDeletedContactExternalIdentifier() {
105 $contactId = $this->individualCreate([
106 'external_identifier' => 'ext-1',
107 ]);
108 $this->callAPISuccess('Contact', 'delete', ['id' => $contactId]);
109 list($originalValues, $result) = $this->setUpBaseContact([
110 'external_identifier' => 'ext-1',
111 ]);
112 $originalValues['nick_name'] = 'Old Bill';
113 $this->runImport($originalValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID);
114 $originalValues['id'] = $result['id'];
115 $this->assertEquals('ext-1', $this->callAPISuccessGetValue('Contact', ['id' => $result['id'], 'return' => 'external_identifier']));
116 $this->callAPISuccessGetSingle('Contact', $originalValues);
117 }
118
119 /**
120 * Test import parser will update based on a rule match.
121 *
122 * In this case the contact has no external identifier.
123 *
124 * @throws \Exception
125 */
126 public function testImportParserWithUpdateWithoutExternalIdentifier() {
127 list($originalValues, $result) = $this->setUpBaseContact();
128 $originalValues['nick_name'] = 'Old Bill';
129 $this->runImport($originalValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID);
130 $originalValues['id'] = $result['id'];
131 $this->assertEquals('Old Bill', $this->callAPISuccessGetValue('Contact', ['id' => $result['id'], 'return' => 'nick_name']));
132 $this->callAPISuccessGetSingle('Contact', $originalValues);
133 }
134
135 /**
136 * Test import parser will update contacts with an external identifier.
137 *
138 * This is the basic test where the identifier matches the import parameters.
139 *
140 * @throws \Exception
141 */
142 public function testImportParserWithUpdateWithExternalIdentifier() {
143 list($originalValues, $result) = $this->setUpBaseContact(['external_identifier' => 'windows']);
144
145 $this->assertEquals($result['id'], CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', 'windows', 'id', 'external_identifier', TRUE));
146 $this->assertEquals('windows', $result['external_identifier']);
147
148 $originalValues['nick_name'] = 'Old Bill';
149 $this->runImport($originalValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID);
150 $originalValues['id'] = $result['id'];
151
152 $this->assertEquals('Old Bill', $this->callAPISuccessGetValue('Contact', ['id' => $result['id'], 'return' => 'nick_name']));
153 $this->callAPISuccessGetSingle('Contact', $originalValues);
154 }
155
156 /**
157 * Test import parser will fallback to external identifier.
158 *
159 * In this case no primary match exists (e.g the details are not supplied) so it falls back on external identifier.
160 *
161 * @see https://issues.civicrm.org/jira/browse/CRM-17275
162 *
163 * @throws \Exception
164 */
165 public function testImportParserWithUpdateWithExternalIdentifierButNoPrimaryMatch() {
166 list($originalValues, $result) = $this->setUpBaseContact([
167 'external_identifier' => 'windows',
168 'email' => NULL,
169 ]);
170
171 $this->assertEquals('windows', $result['external_identifier']);
172
173 $originalValues['nick_name'] = 'Old Bill';
174 $this->runImport($originalValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID);
175 $originalValues['id'] = $result['id'];
176
177 $this->assertEquals('Old Bill', $this->callAPISuccessGetValue('Contact', ['id' => $result['id'], 'return' => 'nick_name']));
178 $this->callAPISuccessGetSingle('Contact', $originalValues);
179 }
180
181 /**
182 * Test import parser will fallback to external identifier.
183 *
184 * In this case no primary match exists (e.g the details are not supplied) so it falls back on external identifier.
185 *
186 * @see https://issues.civicrm.org/jira/browse/CRM-17275
187 *
188 * @throws \Exception
189 */
190 public function testImportParserWithUpdateWithContactID() {
191 list($originalValues, $result) = $this->setUpBaseContact([
192 'external_identifier' => '',
193 'email' => NULL,
194 ]);
195 $updateValues = ['id' => $result['id'], 'email' => 'bill@example.com'];
196 // This is some deep weirdness - this sets a flag for updatingBlankLocinfo - allowing input to be blanked
197 // (which IS a good thing but it's pretty weird & all to do with legacy profile stuff).
198 CRM_Core_Session::singleton()->set('authSrc', CRM_Core_Permission::AUTH_SRC_CHECKSUM);
199 $this->runImport($updateValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID, [NULL, 1]);
200 $originalValues['id'] = $result['id'];
201 $this->callAPISuccessGetSingle('Email', ['contact_id' => $originalValues['id'], 'is_primary' => 1]);
202 $this->callAPISuccessGetSingle('Contact', $originalValues);
203 }
204
205 /**
206 * Test that the import parser adds the external identifier where none is set.
207 *
208 * @throws \Exception
209 */
210 public function testImportParserWithUpdateWithNoExternalIdentifier() {
211 list($originalValues, $result) = $this->setUpBaseContact();
212 $originalValues['nick_name'] = 'Old Bill';
213 $originalValues['external_identifier'] = 'windows';
214 $this->runImport($originalValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID);
215 $originalValues['id'] = $result['id'];
216 $this->assertEquals('Old Bill', $this->callAPISuccessGetValue('Contact', ['id' => $result['id'], 'return' => 'nick_name']));
217 $this->callAPISuccessGetSingle('Contact', $originalValues);
218 }
219
220 /**
221 * Test that the import parser changes the external identifier when there is a dedupe match.
222 *
223 * @throws \Exception
224 */
225 public function testImportParserWithUpdateWithChangedExternalIdentifier() {
226 list($contactValues, $result) = $this->setUpBaseContact(['external_identifier' => 'windows']);
227 $contact_id = $result['id'];
228 $contactValues['nick_name'] = 'Old Bill';
229 $contactValues['external_identifier'] = 'android';
230 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID);
231 $contactValues['id'] = $contact_id;
232 $this->assertEquals('Old Bill', $this->callAPISuccessGetValue('Contact', ['id' => $contact_id, 'return' => 'nick_name']));
233 $this->callAPISuccessGetSingle('Contact', $contactValues);
234 }
235
236 /**
237 * Test that the import parser adds the address to the right location.
238 *
239 * @throws \Exception
240 */
241 public function testImportBillingAddress() {
242 list($contactValues) = $this->setUpBaseContact();
243 $contactValues['nick_name'] = 'Old Bill';
244 $contactValues['external_identifier'] = 'android';
245 $contactValues['street_address'] = 'Big Mansion';
246 $contactValues['phone'] = '911';
247 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID, [0 => NULL, 1 => NULL, 2 => NULL, 3 => NULL, 4 => NULL, 5 => 2, 6 => 2]);
248 $address = $this->callAPISuccessGetSingle('Address', ['street_address' => 'Big Mansion']);
249 $this->assertEquals(2, $address['location_type_id']);
250
251 $phone = $this->callAPISuccessGetSingle('Phone', ['phone' => '911']);
252 $this->assertEquals(2, $phone['location_type_id']);
253
254 $contact = $this->callAPISuccessGetSingle('Contact', $contactValues);
255 $this->callAPISuccess('Contact', 'delete', ['id' => $contact['id']]);
256 }
257
258 /**
259 * Test that the not-really-encouraged way of creating locations via contact.create doesn't mess up primaries.
260 */
261 public function testContactLocationBlockHandling() {
262 $id = $this->individualCreate([
263 'phone' => [
264 1 => [
265 'location_type_id' => 1,
266 'phone' => '987654321',
267 ],
268 2 => [
269 'location_type_id' => 2,
270 'phone' => '456-7890',
271 ],
272 ],
273 'im' => [
274 1 => [
275 'location_type_id' => 1,
276 'name' => 'bob',
277 ],
278 2 => [
279 'location_type_id' => 2,
280 'name' => 'fred',
281 ],
282 ],
283 'openid' => [
284 1 => [
285 'location_type_id' => 1,
286 'openid' => 'bob',
287 ],
288 2 => [
289 'location_type_id' => 2,
290 'openid' => 'fred',
291 ],
292 ],
293 'email' => [
294 1 => [
295 'location_type_id' => 1,
296 'email' => 'bob@example.com',
297 ],
298 2 => [
299 'location_type_id' => 2,
300 'email' => 'fred@example.com',
301 ],
302 ],
303 ]);
304 $phones = $this->callAPISuccess('Phone', 'get', ['contact_id' => $id])['values'];
305 $emails = $this->callAPISuccess('Email', 'get', ['contact_id' => $id])['values'];
306 $openIDs = $this->callAPISuccess('OpenID', 'get', ['contact_id' => $id])['values'];
307 $ims = $this->callAPISuccess('IM', 'get', ['contact_id' => $id])['values'];
308 $this->assertCount(2, $phones);
309 $this->assertCount(2, $emails);
310 $this->assertCount(2, $ims);
311 $this->assertCount(2, $openIDs);
312
313 $this->assertLocationValidity();
314 $this->callAPISuccess('Contact', 'create', [
315 'id' => $id,
316 // This is secret code for 'delete this phone'.
317 'updateBlankLocInfo' => TRUE,
318 'phone' => [
319 1 => [
320 'id' => key($phones),
321 ],
322 ],
323 'email' => [
324 1 => [
325 'id' => key($emails),
326 ],
327 ],
328 'im' => [
329 1 => [
330 'id' => key($ims),
331 ],
332 ],
333 'openid' => [
334 1 => [
335 'id' => key($openIDs),
336 ],
337 ],
338 ]);
339 $this->assertLocationValidity();
340 $this->callAPISuccessGetCount('Phone', ['contact_id' => $id], 1);
341 $this->callAPISuccessGetCount('Email', ['contact_id' => $id], 1);
342 $this->callAPISuccessGetCount('OpenID', ['contact_id' => $id], 1);
343 $this->callAPISuccessGetCount('IM', ['contact_id' => $id], 1);
344 }
345
346 /**
347 * Test that the import parser adds the address to the primary location.
348 *
349 * @throws \Exception
350 */
351 public function testImportPrimaryAddress() {
352 list($contactValues) = $this->setUpBaseContact();
353 $contactValues['nick_name'] = 'Old Bill';
354 $contactValues['external_identifier'] = 'android';
355 $contactValues['street_address'] = 'Big Mansion';
356 $contactValues['phone'] = 12334;
357 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID, [0 => NULL, 1 => NULL, 2 => 'Primary', 3 => NULL, 4 => NULL, 5 => 'Primary', 6 => 'Primary']);
358 $address = $this->callAPISuccessGetSingle('Address', ['street_address' => 'Big Mansion']);
359 $this->assertEquals(1, $address['location_type_id']);
360 $this->assertEquals(1, $address['is_primary']);
361
362 $phone = $this->callAPISuccessGetSingle('Phone', ['phone' => '12334']);
363 $this->assertEquals(1, $phone['location_type_id']);
364
365 $this->callAPISuccessGetSingle('Email', ['email' => 'bill.gates@microsoft.com']);
366
367 $contact = $this->callAPISuccessGetSingle('Contact', $contactValues);
368 $this->callAPISuccess('Contact', 'delete', ['id' => $contact['id']]);
369 }
370
371 /**
372 * Test that address location type id is ignored for dedupe purposes on import.
373 *
374 * @throws \Exception
375 */
376 public function testIgnoreLocationTypeId() {
377 // Create a rule that matches on last name and street address.
378 $rgid = $this->createRuleGroup()['id'];
379 $this->callAPISuccess('Rule', 'create', [
380 'dedupe_rule_group_id' => $rgid,
381 'rule_field' => 'last_name',
382 'rule_table' => 'civicrm_contact',
383 'rule_weight' => 4,
384 ]);
385 $this->callAPISuccess('Rule', 'create', [
386 'dedupe_rule_group_id' => $rgid,
387 'rule_field' => 'street_address',
388 'rule_table' => 'civicrm_address',
389 'rule_weight' => 4,
390 ]);
391 // Create a contact with an address of location_type_id 1.
392 $contact1Params = [
393 'contact_type' => 'Individual',
394 'first_name' => 'Original',
395 'last_name' => 'Smith',
396 ];
397 $contact1 = $this->callAPISuccess('Contact', 'create', $contact1Params);
398 $this->callAPISuccess('Address', 'create', [
399 'contact_id' => $contact1['id'],
400 'location_type_id' => 1,
401 'street_address' => 'Big Mansion',
402 ]);
403
404 $contactValues = [
405 'first_name' => 'New',
406 'last_name' => 'Smith',
407 'street_address' => 'Big Mansion',
408 ];
409
410 // We want to import with a location_type_id of 4.
411 $importLocationTypeId = '4';
412 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_SKIP, CRM_Import_Parser::DUPLICATE, [0 => NULL, 1 => NULL, 2 => $importLocationTypeId], NULL, $rgid);
413 $address = $this->callAPISuccessGetSingle('Address', ['street_address' => 'Big Mansion']);
414 $this->assertEquals(1, $address['location_type_id']);
415 $contact = $this->callAPISuccessGetSingle('Contact', $contact1Params);
416 $this->callAPISuccess('Contact', 'delete', ['id' => $contact['id']]);
417 }
418
419 /**
420 * Test that address custom fields can be imported
421 * FIXME: Api4
422 *
423 * @throws \CRM_Core_Exception
424 */
425 public function testAddressWithCustomData() {
426 $ids = $this->entityCustomGroupWithSingleFieldCreate('Address', 'AddressTest.php');
427 list($contactValues) = $this->setUpBaseContact();
428 $contactValues['nick_name'] = 'Old Bill';
429 $contactValues['external_identifier'] = 'android';
430 $contactValues['street_address'] = 'Big Mansion';
431 $contactValues['custom_' . $ids['custom_field_id']] = 'Update';
432 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID, [0 => NULL, 1 => NULL, 2 => NULL, 3 => NULL, 4 => NULL, 5 => 'Primary', 6 => 'Primary']);
433 $address = $this->callAPISuccessGetSingle('Address', ['street_address' => 'Big Mansion', 'return' => 'custom_' . $ids['custom_field_id']]);
434 $this->assertEquals('Update', $address['custom_' . $ids['custom_field_id']]);
435 }
436
437 /**
438 * Test gender works when you specify the label.
439 *
440 * There is an expectation that you can import by label here.
441 *
442 * @throws \CRM_Core_Exception
443 */
444 public function testGenderLabel() {
445 $contactValues = [
446 'first_name' => 'Bill',
447 'last_name' => 'Gates',
448 'email' => 'bill.gates@microsoft.com',
449 'nick_name' => 'Billy-boy',
450 'gender_id' => 'Female',
451 ];
452 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID, [NULL, NULL, 'Primary', NULL, NULL]);
453 $this->callAPISuccessGetSingle('Contact', $contactValues);
454 }
455
456 /**
457 * Test prefix & suffix work when you specify the label.
458 *
459 * There is an expectation that you can import by label here.
460 *
461 * @throws \CRM_Core_Exception
462 * @throws \CiviCRM_API3_Exception
463 */
464 public function testPrefixLabel() {
465 $this->callAPISuccess('OptionValue', 'create', ['option_group_id' => 'individual_prefix', 'name' => 'new_one', 'label' => 'special', 'value' => 70]);
466 $mapping = [
467 ['name' => 'first_name', 'column_number' => 0],
468 ['name' => 'last_name', 'column_number' => 1],
469 ['name' => 'email', 'column_number' => 2, 'location_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Email', 'location_type_id', 'Home')],
470 ['name' => 'prefix_id', 'column_number' => 3],
471 ['name' => 'suffix_id', 'column_number' => 4],
472 ];
473 $processor = new CRM_Import_ImportProcessor();
474 $processor->setMappingFields($mapping);
475 $processor->setContactType('Individual');
476 $importer = $processor->getImporterObject();
477
478 $contactValues = [
479 'Bill',
480 'Gates',
481 'bill.gates@microsoft.com',
482 'special',
483 'III',
484 ];
485 $importer->import(CRM_Import_Parser::DUPLICATE_NOCHECK, $contactValues);
486
487 $contact = $this->callAPISuccessGetSingle('Contact', ['first_name' => 'Bill', 'prefix_id' => 'new_one', 'suffix_id' => 'III']);
488 $this->assertEquals('special Bill Gates III', $contact['display_name']);
489 }
490
491 /**
492 * Test that labels work for importing custom data.
493 *
494 * @throws \CRM_Core_Exception
495 */
496 public function testCustomDataLabel() {
497 $this->createCustomGroupWithFieldOfType([], 'select');
498 $contactValues = [
499 'first_name' => 'Bill',
500 'last_name' => 'Gates',
501 'email' => 'bill.gates@microsoft.com',
502 'nick_name' => 'Billy-boy',
503 $this->getCustomFieldName('select') => 'Yellow',
504 ];
505 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID, [NULL, NULL, 'Primary', NULL, NULL]);
506 $contact = $this->callAPISuccessGetSingle('Contact', array_merge($contactValues, ['return' => $this->getCustomFieldName('select')]));
507 $this->assertEquals('Y', $contact[$this->getCustomFieldName('select')]);
508 }
509
510 /**
511 * Test that names work for importing custom data.
512 *
513 * @throws \CRM_Core_Exception
514 */
515 public function testCustomDataName() {
516 $this->createCustomGroupWithFieldOfType([], 'select');
517 $contactValues = [
518 'first_name' => 'Bill',
519 'last_name' => 'Gates',
520 'email' => 'bill.gates@microsoft.com',
521 'nick_name' => 'Billy-boy',
522 $this->getCustomFieldName('select') => 'Y',
523 ];
524 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID, [NULL, NULL, 'Primary', NULL, NULL]);
525 $contact = $this->callAPISuccessGetSingle('Contact', array_merge($contactValues, ['return' => $this->getCustomFieldName('select')]));
526 $this->assertEquals('Y', $contact[$this->getCustomFieldName('select')]);
527 }
528
529 /**
530 * Test that the import parser adds the address to the primary location.
531 *
532 * @throws \Exception
533 */
534 public function testImportDeceased() {
535 list($contactValues) = $this->setUpBaseContact();
536 CRM_Core_Session::singleton()->set("dateTypes", 1);
537 $contactValues['birth_date'] = '1910-12-17';
538 $contactValues['deceased_date'] = '2010-12-17';
539 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID);
540 $contact = $this->callAPISuccessGetSingle('Contact', $contactValues);
541 $this->assertEquals('1910-12-17', $contact['birth_date']);
542 $this->assertEquals('2010-12-17', $contact['deceased_date']);
543 $this->assertEquals(1, $contact['is_deceased']);
544 $this->callAPISuccess('Contact', 'delete', ['id' => $contact['id']]);
545 }
546
547 /**
548 * Test that the import parser adds the address to the primary location.
549 *
550 * @throws \Exception
551 */
552 public function testImportTwoAddressFirstPrimary() {
553 list($contactValues) = $this->setUpBaseContact();
554 $contactValues['nick_name'] = 'Old Bill';
555 $contactValues['external_identifier'] = 'android';
556 $contactValues['street_address'] = 'Big Mansion';
557 $contactValues['phone'] = 12334;
558 $fields = array_keys($contactValues);
559 $contactValues['street_address_2'] = 'Teeny Mansion';
560 $contactValues['phone_2'] = 4444;
561 $fields[] = 'street_address';
562 $fields[] = 'phone';
563 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID, [0 => NULL, 1 => NULL, 2 => NULL, 3 => NULL, 4 => NULL, 5 => 'Primary', 6 => 'Primary', 7 => 3, 8 => 3], $fields);
564 $contact = $this->callAPISuccessGetSingle('Contact', ['external_identifier' => 'android']);
565 $address = $this->callAPISuccess('Address', 'get', ['contact_id' => $contact['id'], 'sequential' => 1]);
566
567 $this->assertEquals(3, $address['values'][0]['location_type_id']);
568 $this->assertEquals(0, $address['values'][0]['is_primary']);
569 $this->assertEquals('Teeny Mansion', $address['values'][0]['street_address']);
570
571 $this->assertEquals(1, $address['values'][1]['location_type_id']);
572 $this->assertEquals(1, $address['values'][1]['is_primary']);
573 $this->assertEquals('Big Mansion', $address['values'][1]['street_address']);
574
575 $phone = $this->callAPISuccess('Phone', 'get', ['contact_id' => $contact['id'], 'sequential' => 1]);
576 $this->assertEquals(1, $phone['values'][0]['location_type_id']);
577 $this->assertEquals(1, $phone['values'][0]['is_primary']);
578 $this->assertEquals(12334, $phone['values'][0]['phone']);
579 $this->assertEquals(3, $phone['values'][1]['location_type_id']);
580 $this->assertEquals(0, $phone['values'][1]['is_primary']);
581 $this->assertEquals(4444, $phone['values'][1]['phone']);
582
583 $this->callAPISuccess('Contact', 'delete', ['id' => $contact['id']]);
584 }
585
586 /**
587 * Test importing 2 phones of different types.
588 *
589 * @throws \CRM_Core_Exception
590 * @throws \CiviCRM_API3_Exception
591 */
592 public function testImportTwoPhonesDifferentTypes() {
593 $processor = new CRM_Import_ImportProcessor();
594 $processor->setContactType('Individual');
595 $processor->setMappingFields(
596 [
597 ['name' => 'first_name'],
598 ['name' => 'last_name'],
599 ['name' => 'email'],
600 ['name' => 'phone', 'location_type_id' => 1, 'phone_type_id' => 2],
601 ['name' => 'phone', 'location_type_id' => 1, 'phone_type_id' => 1],
602 ]
603 );
604 $importer = $processor->getImporterObject();
605 $fields = ['First Name', 'new last name', 'bob@example.com', '1234', '5678'];
606 $importer->import(CRM_Import_Parser::DUPLICATE_UPDATE, $fields);
607 $contact = $this->callAPISuccessGetSingle('Contact', ['last_name' => 'new last name']);
608 $phones = $this->callAPISuccess('Phone', 'get', ['contact_id' => $contact['id']])['values'];
609 $this->assertCount(2, $phones);
610 }
611
612 /**
613 * Test that the import parser adds the address to the primary location.
614 *
615 * @throws \Exception
616 */
617 public function testImportTwoAddressSecondPrimary() {
618 list($contactValues) = $this->setUpBaseContact();
619 $contactValues['nick_name'] = 'Old Bill';
620 $contactValues['external_identifier'] = 'android';
621 $contactValues['street_address'] = 'Big Mansion';
622 $contactValues['phone'] = 12334;
623 $fields = array_keys($contactValues);
624 $contactValues['street_address_2'] = 'Teeny Mansion';
625 $contactValues['phone_2'] = 4444;
626 $fields[] = 'street_address';
627 $fields[] = 'phone';
628 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID, [0 => NULL, 1 => NULL, 2 => NULL, 3 => NULL, 4 => NULL, 5 => 3, 6 => 3, 7 => 'Primary', 8 => 'Primary'], $fields);
629 $contact = $this->callAPISuccessGetSingle('Contact', ['external_identifier' => 'android']);
630 $address = $this->callAPISuccess('Address', 'get', ['contact_id' => $contact['id'], 'sequential' => 1])['values'];
631
632 $this->assertEquals(1, $address[1]['location_type_id']);
633 $this->assertEquals(1, $address[1]['is_primary']);
634 $this->assertEquals('Teeny Mansion', $address[1]['street_address']);
635
636 $this->assertEquals(3, $address[0]['location_type_id']);
637 $this->assertEquals(0, $address[0]['is_primary']);
638 $this->assertEquals('Big Mansion', $address[0]['street_address']);
639
640 $phone = $this->callAPISuccess('Phone', 'get', ['contact_id' => $contact['id'], 'sequential' => 1, 'options' => ['sort' => 'is_primary DESC']])['values'];
641 $this->assertEquals(3, $phone[1]['location_type_id']);
642 $this->assertEquals(0, $phone[1]['is_primary']);
643 $this->assertEquals(12334, $phone[1]['phone']);
644 $this->assertEquals(1, $phone[0]['location_type_id']);
645 $this->assertEquals(1, $phone[0]['is_primary']);
646 $this->assertEquals(4444, $phone[0]['phone']);
647
648 $this->callAPISuccess('Contact', 'delete', ['id' => $contact['id']]);
649 }
650
651 /**
652 * Test that the import parser updates the address on the existing primary location.
653 *
654 * @throws \Exception
655 */
656 public function testImportPrimaryAddressUpdate() {
657 list($contactValues) = $this->setUpBaseContact(['external_identifier' => 'android']);
658 $contactValues['email'] = 'melinda.gates@microsoft.com';
659 $contactValues['phone'] = '98765';
660 $contactValues['external_identifier'] = 'android';
661 $contactValues['street_address'] = 'Big Mansion';
662 $contactValues['city'] = 'Big City';
663 $contactID = $this->callAPISuccessGetValue('Contact', ['external_identifier' => 'android', 'return' => 'id']);
664 $originalAddress = $this->callAPISuccess('Address', 'create', ['location_type_id' => 2, 'street_address' => 'small house', 'contact_id' => $contactID]);
665 $originalPhone = $this->callAPISuccess('phone', 'create', ['location_type_id' => 2, 'phone' => '1234', 'contact_id' => $contactID]);
666 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID, [0 => NULL, 1 => NULL, 2 => 'Primary', 3 => NULL, 4 => NULL, 5 => 'Primary', 6 => 'Primary', 7 => 'Primary']);
667 $phone = $this->callAPISuccessGetSingle('Phone', ['phone' => '98765']);
668 $this->assertEquals(2, $phone['location_type_id']);
669 $this->assertEquals($originalPhone['id'], $phone['id']);
670 $email = $this->callAPISuccess('Email', 'getsingle', ['contact_id' => $contactID]);
671 $address = $this->callAPISuccessGetSingle('Address', ['street_address' => 'Big Mansion']);
672 $this->assertEquals(2, $address['location_type_id']);
673 $this->assertEquals($originalAddress['id'], $address['id']);
674 $this->assertEquals('Big City', $address['city']);
675 $this->callAPISuccessGetSingle('Contact', $contactValues);
676 }
677
678 /**
679 * Test the determination of whether a custom field is valid.
680 */
681 public function testCustomFieldValidation() {
682 $errorMessage = [];
683 $customGroup = $this->customGroupCreate([
684 'extends' => 'Contact',
685 'title' => 'ABC',
686 ]);
687 $customField = $this->customFieldOptionValueCreate($customGroup, 'fieldABC', ['html_type' => 'Select', 'serialize' => 1]);
688 $params = [
689 'custom_' . $customField['id'] => 'Label1|Label2',
690 ];
691 CRM_Contact_Import_Parser_Contact::isErrorInCustomData($params, $errorMessage);
692 $this->assertEquals([], $errorMessage);
693 }
694
695 /**
696 * Test that setting duplicate action to fill doesn't blow away data
697 * that exists, but does fill in where it's empty.
698 *
699 * @throw \Exception
700 */
701 public function testImportFill() {
702 // Create a custom field group for testing.
703 $this->createCustomGroup([
704 'title' => 'importFillGroup',
705 'extends' => 'Individual',
706 'is_active' => TRUE,
707 ]);
708 $customGroupID = $this->ids['CustomGroup']['importFillGroup'];
709
710 // Add two custom fields.
711 $api_params = [
712 'custom_group_id' => $customGroupID,
713 'label' => 'importFillField1',
714 'html_type' => 'Select',
715 'data_type' => 'String',
716 'option_values' => [
717 'foo' => 'Foo',
718 'bar' => 'Bar',
719 ],
720 ];
721 $result = $this->callAPISuccess('custom_field', 'create', $api_params);
722 $customField1 = $result['id'];
723
724 $api_params = [
725 'custom_group_id' => $customGroupID,
726 'label' => 'importFillField2',
727 'html_type' => 'Select',
728 'data_type' => 'String',
729 'option_values' => [
730 'baz' => 'Baz',
731 'boo' => 'Boo',
732 ],
733 ];
734 $result = $this->callAPISuccess('custom_field', 'create', $api_params);
735 $customField2 = $result['id'];
736
737 // Now set up values.
738 $original_gender = 'Male';
739 $original_custom1 = 'foo';
740 $original_email = 'test-import-fill@example.org';
741
742 $import_gender = 'Female';
743 $import_custom1 = 'bar';
744 $import_job_title = 'Chief data importer';
745 $import_custom2 = 'baz';
746
747 // Create contact with both one known core field and one custom
748 // field filled in.
749 $api_params = [
750 'contact_type' => 'Individual',
751 'email' => $original_email,
752 'gender' => $original_gender,
753 'custom_' . $customField1 => $original_custom1,
754 ];
755 $result = $this->callAPISuccess('contact', 'create', $api_params);
756 $contact_id = $result['id'];
757
758 // Run an import.
759 $import = [
760 'email' => $original_email,
761 'gender_id' => $import_gender,
762 'custom_' . $customField1 => $import_custom1,
763 'job_title' => $import_job_title,
764 'custom_' . $customField2 => $import_custom2,
765 ];
766
767 $this->runImport($import, CRM_Import_Parser::DUPLICATE_FILL, CRM_Import_Parser::VALID);
768
769 $expected = [
770 'gender' => $original_gender,
771 'custom_' . $customField1 => $original_custom1,
772 'job_title' => $import_job_title,
773 'custom_' . $customField2 => $import_custom2,
774 ];
775
776 $params = [
777 'id' => $contact_id,
778 'return' => [
779 'gender',
780 'custom_' . $customField1,
781 'job_title',
782 'custom_' . $customField2,
783 ],
784 ];
785 $result = civicrm_api3('Contact', 'get', $params);
786 $values = array_pop($result['values']);
787 foreach ($expected as $field => $expected_value) {
788 if (!isset($values[$field])) {
789 $given_value = NULL;
790 }
791 else {
792 $given_value = $values[$field];
793 }
794 // We expect:
795 // gender: Male
796 // job_title: Chief Data Importer
797 // importFillField1: foo
798 // importFillField2: baz
799 $this->assertEquals($expected_value, $given_value, "$field properly handled during Fill import");
800 }
801 }
802
803 /**
804 * CRM-19888 default country should be used if ambigous.
805 */
806 public function testImportAmbiguousStateCountry() {
807 $this->callAPISuccess('Setting', 'create', ['defaultContactCountry' => 1228]);
808 $countries = CRM_Core_PseudoConstant::country(FALSE, FALSE);
809 $this->callAPISuccess('Setting', 'create', ['countryLimit' => [array_search('United States', $countries), array_search('Guyana', $countries), array_search('Netherlands', $countries)]]);
810 $this->callAPISuccess('Setting', 'create', ['provinceLimit' => [array_search('United States', $countries), array_search('Guyana', $countries), array_search('Netherlands', $countries)]]);
811 $mapper = [0 => NULL, 1 => NULL, 2 => 'Primary', 3 => NULL];
812 list($contactValues) = $this->setUpBaseContact();
813 $fields = array_keys($contactValues);
814 $addressValues = [
815 'street_address' => 'PO Box 2716',
816 'city' => 'Midway',
817 'state_province' => 'UT',
818 'postal_code' => 84049,
819 'country' => 'United States',
820 ];
821 $locationTypes = $this->callAPISuccess('Address', 'getoptions', ['field' => 'location_type_id']);
822 $locationTypes = $locationTypes['values'];
823 foreach ($addressValues as $field => $value) {
824 $contactValues['home_' . $field] = $value;
825 $mapper[] = array_search('Home', $locationTypes);
826 $contactValues['work_' . $field] = $value;
827 $mapper[] = array_search('Work', $locationTypes);
828 $fields[] = $field;
829 $fields[] = $field;
830 }
831 $contactValues['work_country'] = '';
832
833 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID, $mapper, $fields);
834 $addresses = $this->callAPISuccess('Address', 'get', ['contact_id' => ['>' => 2], 'sequential' => 1]);
835 $this->assertEquals(2, $addresses['count']);
836 $this->assertEquals(array_search('United States', $countries), $addresses['values'][0]['country_id']);
837 $this->assertEquals(array_search('United States', $countries), $addresses['values'][1]['country_id']);
838 }
839
840 /**
841 * Run the import parser.
842 *
843 * @param array $originalValues
844 *
845 * @param int $onDuplicateAction
846 * @param int $expectedResult
847 * @param array|null $mapperLocType
848 * Array of location types that map to the input arrays.
849 * @param array|null $fields
850 * Array of field names. Will be calculated from $originalValues if not passed in, but
851 * that method does not cope with duplicates.
852 * @param int|null $ruleGroupId
853 * To test against a specific dedupe rule group, pass its ID as this argument.
854 */
855 protected function runImport($originalValues, $onDuplicateAction, $expectedResult, $mapperLocType = [], $fields = NULL, int $ruleGroupId = NULL) {
856 if (!$fields) {
857 $fields = array_keys($originalValues);
858 }
859 $values = array_values($originalValues);
860 $parser = new CRM_Contact_Import_Parser_Contact($fields, $mapperLocType);
861 $parser->_contactType = 'Individual';
862 $parser->_dedupeRuleGroupID = $ruleGroupId;
863 $parser->_onDuplicate = $onDuplicateAction;
864 $parser->init();
865 $this->assertEquals($expectedResult, $parser->import($onDuplicateAction, $values), 'Return code from parser import was not as expected');
866 }
867
868 /**
869 * @param array $fields Array of fields to be imported
870 * @param array $allfields Array of all fields which can be part of import
871 */
872 private function mapRelationshipFields(&$fields, $allfields) {
873 foreach ($allfields as $key => $fieldtocheck) {
874 $elementIndex = array_search($fieldtocheck->_title, $fields);
875 if ($elementIndex !== FALSE) {
876 $fields[$elementIndex] = $key;
877 }
878 }
879 }
880
881 /**
882 * Set up the underlying contact.
883 *
884 * @param array $params
885 * Optional extra parameters to set.
886 *
887 * @return array
888 * @throws \CRM_Core_Exception
889 */
890 protected function setUpBaseContact($params = []) {
891 $originalValues = array_merge([
892 'first_name' => 'Bill',
893 'last_name' => 'Gates',
894 'email' => 'bill.gates@microsoft.com',
895 'nick_name' => 'Billy-boy',
896 ], $params);
897 $this->runImport($originalValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID);
898 $result = $this->callAPISuccessGetSingle('Contact', $originalValues);
899 return [$originalValues, $result];
900 }
901
902 }