WIP Failing test on importing Preferred Language field using Option value label
[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 public function testImportParserWithEmployeeOfRelationship(): void {
54 $this->organizationCreate([
55 'organization_name' => 'Agileware',
56 'legal_name' => 'Agileware',
57 ]);
58 $contactImportValues = [
59 "first_name" => "Alok",
60 "last_name" => "Patel",
61 "Employee of" => "Agileware",
62 ];
63
64 $fields = array_keys($contactImportValues);
65 $values = array_values($contactImportValues);
66 $parser = new CRM_Contact_Import_Parser_Contact($fields, []);
67 $parser->_contactType = 'Individual';
68 $parser->init();
69 $this->mapRelationshipFields($fields, $parser->getAllFields());
70
71 $parser = new CRM_Contact_Import_Parser_Contact($fields, [], [], [], [
72 NULL,
73 NULL,
74 $fields[2],
75 ], [
76 NULL,
77 NULL,
78 "Organization",
79 ], [
80 NULL,
81 NULL,
82 "organization_name",
83 ], [], [], [], [], []);
84
85 $parser->_contactType = 'Individual';
86 $parser->_onDuplicate = CRM_Import_Parser::DUPLICATE_UPDATE;
87 $parser->init();
88
89 $this->assertEquals(CRM_Import_Parser::VALID, $parser->import(CRM_Import_Parser::DUPLICATE_UPDATE, $values), 'Return code from parser import was not as expected');
90 $this->callAPISuccess("Contact", "get", [
91 "first_name" => "Alok",
92 "last_name" => "Patel",
93 "organization_name" => "Agileware",
94 ]);
95 }
96
97 /**
98 * Test that import parser will not fail when same external_identifier found
99 * of deleted contact.
100 *
101 * @throws \CRM_Core_Exception
102 * @throws \CiviCRM_API3_Exception
103 */
104 public function testImportParserWithDeletedContactExternalIdentifier(): void {
105 $contactId = $this->individualCreate([
106 'external_identifier' => 'ext-1',
107 ]);
108 $this->callAPISuccess('Contact', 'delete', ['id' => $contactId]);
109 [$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 \CRM_Core_Exception
125 */
126 public function testImportParserWithUpdateWithoutExternalIdentifier(): void {
127 [$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 [$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 [$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 [$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 [$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 [$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 [$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 [$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 [$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 importing in the Preferred Language Field
531 *
532 * @throws \CRM_Core_Exception
533 */
534 public function testPreferredLanguageImport() {
535 $contactValues = [
536 'first_name' => 'Bill',
537 'last_name' => 'Gates',
538 'email' => 'bill.gates@microsoft.com',
539 'nick_name' => 'Billy-boy',
540 'preferred_language' => 'English (Australia)',
541 ];
542 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID, [NULL, NULL, 'Primary', NULL, NULL]);
543 }
544
545 /**
546 * Test that the import parser adds the address to the primary location.
547 *
548 * @throws \Exception
549 */
550 public function testImportDeceased() {
551 [$contactValues] = $this->setUpBaseContact();
552 CRM_Core_Session::singleton()->set("dateTypes", 1);
553 $contactValues['birth_date'] = '1910-12-17';
554 $contactValues['deceased_date'] = '2010-12-17';
555 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID);
556 $contact = $this->callAPISuccessGetSingle('Contact', $contactValues);
557 $this->assertEquals('1910-12-17', $contact['birth_date']);
558 $this->assertEquals('2010-12-17', $contact['deceased_date']);
559 $this->assertEquals(1, $contact['is_deceased']);
560 $this->callAPISuccess('Contact', 'delete', ['id' => $contact['id']]);
561 }
562
563 /**
564 * Test that the import parser adds the address to the primary location.
565 *
566 * @throws \Exception
567 */
568 public function testImportTwoAddressFirstPrimary() {
569 [$contactValues] = $this->setUpBaseContact();
570 $contactValues['nick_name'] = 'Old Bill';
571 $contactValues['external_identifier'] = 'android';
572 $contactValues['street_address'] = 'Big Mansion';
573 $contactValues['phone'] = 12334;
574 $fields = array_keys($contactValues);
575 $contactValues['street_address_2'] = 'Teeny Mansion';
576 $contactValues['phone_2'] = 4444;
577 $fields[] = 'street_address';
578 $fields[] = 'phone';
579 $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);
580 $contact = $this->callAPISuccessGetSingle('Contact', ['external_identifier' => 'android']);
581 $address = $this->callAPISuccess('Address', 'get', ['contact_id' => $contact['id'], 'sequential' => 1]);
582
583 $this->assertEquals(3, $address['values'][0]['location_type_id']);
584 $this->assertEquals(0, $address['values'][0]['is_primary']);
585 $this->assertEquals('Teeny Mansion', $address['values'][0]['street_address']);
586
587 $this->assertEquals(1, $address['values'][1]['location_type_id']);
588 $this->assertEquals(1, $address['values'][1]['is_primary']);
589 $this->assertEquals('Big Mansion', $address['values'][1]['street_address']);
590
591 $phone = $this->callAPISuccess('Phone', 'get', ['contact_id' => $contact['id'], 'sequential' => 1]);
592 $this->assertEquals(1, $phone['values'][0]['location_type_id']);
593 $this->assertEquals(1, $phone['values'][0]['is_primary']);
594 $this->assertEquals(12334, $phone['values'][0]['phone']);
595 $this->assertEquals(3, $phone['values'][1]['location_type_id']);
596 $this->assertEquals(0, $phone['values'][1]['is_primary']);
597 $this->assertEquals(4444, $phone['values'][1]['phone']);
598
599 $this->callAPISuccess('Contact', 'delete', ['id' => $contact['id']]);
600 }
601
602 /**
603 * Test importing 2 phones of different types.
604 *
605 * @throws \CRM_Core_Exception
606 * @throws \CiviCRM_API3_Exception
607 */
608 public function testImportTwoPhonesDifferentTypes() {
609 $processor = new CRM_Import_ImportProcessor();
610 $processor->setContactType('Individual');
611 $processor->setMappingFields(
612 [
613 ['name' => 'first_name'],
614 ['name' => 'last_name'],
615 ['name' => 'email'],
616 ['name' => 'phone', 'location_type_id' => 1, 'phone_type_id' => 2],
617 ['name' => 'phone', 'location_type_id' => 1, 'phone_type_id' => 1],
618 ]
619 );
620 $importer = $processor->getImporterObject();
621 $fields = ['First Name', 'new last name', 'bob@example.com', '1234', '5678'];
622 $importer->import(CRM_Import_Parser::DUPLICATE_UPDATE, $fields);
623 $contact = $this->callAPISuccessGetSingle('Contact', ['last_name' => 'new last name']);
624 $phones = $this->callAPISuccess('Phone', 'get', ['contact_id' => $contact['id']])['values'];
625 $this->assertCount(2, $phones);
626 }
627
628 /**
629 * Test that the import parser adds the address to the primary location.
630 *
631 * @throws \Exception
632 */
633 public function testImportTwoAddressSecondPrimary() {
634 [$contactValues] = $this->setUpBaseContact();
635 $contactValues['nick_name'] = 'Old Bill';
636 $contactValues['external_identifier'] = 'android';
637 $contactValues['street_address'] = 'Big Mansion';
638 $contactValues['phone'] = 12334;
639 $fields = array_keys($contactValues);
640 $contactValues['street_address_2'] = 'Teeny Mansion';
641 $contactValues['phone_2'] = 4444;
642 $fields[] = 'street_address';
643 $fields[] = 'phone';
644 $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);
645 $contact = $this->callAPISuccessGetSingle('Contact', ['external_identifier' => 'android']);
646 $address = $this->callAPISuccess('Address', 'get', ['contact_id' => $contact['id'], 'sequential' => 1])['values'];
647
648 $this->assertEquals(1, $address[1]['location_type_id']);
649 $this->assertEquals(1, $address[1]['is_primary']);
650 $this->assertEquals('Teeny Mansion', $address[1]['street_address']);
651
652 $this->assertEquals(3, $address[0]['location_type_id']);
653 $this->assertEquals(0, $address[0]['is_primary']);
654 $this->assertEquals('Big Mansion', $address[0]['street_address']);
655
656 $phone = $this->callAPISuccess('Phone', 'get', ['contact_id' => $contact['id'], 'sequential' => 1, 'options' => ['sort' => 'is_primary DESC']])['values'];
657 $this->assertEquals(3, $phone[1]['location_type_id']);
658 $this->assertEquals(0, $phone[1]['is_primary']);
659 $this->assertEquals(12334, $phone[1]['phone']);
660 $this->assertEquals(1, $phone[0]['location_type_id']);
661 $this->assertEquals(1, $phone[0]['is_primary']);
662 $this->assertEquals(4444, $phone[0]['phone']);
663
664 $this->callAPISuccess('Contact', 'delete', ['id' => $contact['id']]);
665 }
666
667 /**
668 * Test that the import parser updates the address on the existing primary location.
669 *
670 * @throws \Exception
671 */
672 public function testImportPrimaryAddressUpdate() {
673 [$contactValues] = $this->setUpBaseContact(['external_identifier' => 'android']);
674 $contactValues['email'] = 'melinda.gates@microsoft.com';
675 $contactValues['phone'] = '98765';
676 $contactValues['external_identifier'] = 'android';
677 $contactValues['street_address'] = 'Big Mansion';
678 $contactValues['city'] = 'Big City';
679 $contactID = $this->callAPISuccessGetValue('Contact', ['external_identifier' => 'android', 'return' => 'id']);
680 $originalAddress = $this->callAPISuccess('Address', 'create', ['location_type_id' => 2, 'street_address' => 'small house', 'contact_id' => $contactID]);
681 $originalPhone = $this->callAPISuccess('phone', 'create', ['location_type_id' => 2, 'phone' => '1234', 'contact_id' => $contactID]);
682 $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']);
683 $phone = $this->callAPISuccessGetSingle('Phone', ['phone' => '98765']);
684 $this->assertEquals(2, $phone['location_type_id']);
685 $this->assertEquals($originalPhone['id'], $phone['id']);
686 $email = $this->callAPISuccess('Email', 'getsingle', ['contact_id' => $contactID]);
687 $address = $this->callAPISuccessGetSingle('Address', ['street_address' => 'Big Mansion']);
688 $this->assertEquals(2, $address['location_type_id']);
689 $this->assertEquals($originalAddress['id'], $address['id']);
690 $this->assertEquals('Big City', $address['city']);
691 $this->callAPISuccessGetSingle('Contact', $contactValues);
692 }
693
694 /**
695 * Test the determination of whether a custom field is valid.
696 */
697 public function testCustomFieldValidation() {
698 $errorMessage = [];
699 $customGroup = $this->customGroupCreate([
700 'extends' => 'Contact',
701 'title' => 'ABC',
702 ]);
703 $customField = $this->customFieldOptionValueCreate($customGroup, 'fieldABC', ['html_type' => 'Select', 'serialize' => 1]);
704 $params = [
705 'custom_' . $customField['id'] => 'Label1|Label2',
706 ];
707 CRM_Contact_Import_Parser_Contact::isErrorInCustomData($params, $errorMessage);
708 $this->assertEquals([], $errorMessage);
709 }
710
711 /**
712 * Test that setting duplicate action to fill doesn't blow away data
713 * that exists, but does fill in where it's empty.
714 *
715 * @throw \Exception
716 */
717 public function testImportFill() {
718 // Create a custom field group for testing.
719 $this->createCustomGroup([
720 'title' => 'importFillGroup',
721 'extends' => 'Individual',
722 'is_active' => TRUE,
723 ]);
724 $customGroupID = $this->ids['CustomGroup']['importFillGroup'];
725
726 // Add two custom fields.
727 $api_params = [
728 'custom_group_id' => $customGroupID,
729 'label' => 'importFillField1',
730 'html_type' => 'Select',
731 'data_type' => 'String',
732 'option_values' => [
733 'foo' => 'Foo',
734 'bar' => 'Bar',
735 ],
736 ];
737 $result = $this->callAPISuccess('custom_field', 'create', $api_params);
738 $customField1 = $result['id'];
739
740 $api_params = [
741 'custom_group_id' => $customGroupID,
742 'label' => 'importFillField2',
743 'html_type' => 'Select',
744 'data_type' => 'String',
745 'option_values' => [
746 'baz' => 'Baz',
747 'boo' => 'Boo',
748 ],
749 ];
750 $result = $this->callAPISuccess('custom_field', 'create', $api_params);
751 $customField2 = $result['id'];
752
753 // Now set up values.
754 $original_gender = 'Male';
755 $original_custom1 = 'foo';
756 $original_email = 'test-import-fill@example.org';
757
758 $import_gender = 'Female';
759 $import_custom1 = 'bar';
760 $import_job_title = 'Chief data importer';
761 $import_custom2 = 'baz';
762
763 // Create contact with both one known core field and one custom
764 // field filled in.
765 $api_params = [
766 'contact_type' => 'Individual',
767 'email' => $original_email,
768 'gender' => $original_gender,
769 'custom_' . $customField1 => $original_custom1,
770 ];
771 $result = $this->callAPISuccess('contact', 'create', $api_params);
772 $contact_id = $result['id'];
773
774 // Run an import.
775 $import = [
776 'email' => $original_email,
777 'gender_id' => $import_gender,
778 'custom_' . $customField1 => $import_custom1,
779 'job_title' => $import_job_title,
780 'custom_' . $customField2 => $import_custom2,
781 ];
782
783 $this->runImport($import, CRM_Import_Parser::DUPLICATE_FILL, CRM_Import_Parser::VALID);
784
785 $expected = [
786 'gender' => $original_gender,
787 'custom_' . $customField1 => $original_custom1,
788 'job_title' => $import_job_title,
789 'custom_' . $customField2 => $import_custom2,
790 ];
791
792 $params = [
793 'id' => $contact_id,
794 'return' => [
795 'gender',
796 'custom_' . $customField1,
797 'job_title',
798 'custom_' . $customField2,
799 ],
800 ];
801 $result = civicrm_api3('Contact', 'get', $params);
802 $values = array_pop($result['values']);
803 foreach ($expected as $field => $expected_value) {
804 if (!isset($values[$field])) {
805 $given_value = NULL;
806 }
807 else {
808 $given_value = $values[$field];
809 }
810 // We expect:
811 // gender: Male
812 // job_title: Chief Data Importer
813 // importFillField1: foo
814 // importFillField2: baz
815 $this->assertEquals($expected_value, $given_value, "$field properly handled during Fill import");
816 }
817 }
818
819 /**
820 * CRM-19888 default country should be used if ambigous.
821 *
822 * @throws \CRM_Core_Exception
823 */
824 public function testImportAmbiguousStateCountry(): void {
825 $this->callAPISuccess('Setting', 'create', ['defaultContactCountry' => 1228]);
826 $countries = CRM_Core_PseudoConstant::country(FALSE, FALSE);
827 $this->callAPISuccess('Setting', 'create', ['countryLimit' => [array_search('United States', $countries), array_search('Guyana', $countries), array_search('Netherlands', $countries)]]);
828 $this->callAPISuccess('Setting', 'create', ['provinceLimit' => [array_search('United States', $countries), array_search('Guyana', $countries), array_search('Netherlands', $countries)]]);
829 $mapper = [0 => NULL, 1 => NULL, 2 => 'Primary', 3 => NULL];
830 [$contactValues] = $this->setUpBaseContact();
831 $fields = array_keys($contactValues);
832 $addressValues = [
833 'street_address' => 'PO Box 2716',
834 'city' => 'Midway',
835 'state_province' => 'UT',
836 'postal_code' => 84049,
837 'country' => 'United States',
838 ];
839 $locationTypes = $this->callAPISuccess('Address', 'getoptions', ['field' => 'location_type_id']);
840 $locationTypes = $locationTypes['values'];
841 foreach ($addressValues as $field => $value) {
842 $contactValues['home_' . $field] = $value;
843 $mapper[] = array_search('Home', $locationTypes);
844 $contactValues['work_' . $field] = $value;
845 $mapper[] = array_search('Work', $locationTypes);
846 $fields[] = $field;
847 $fields[] = $field;
848 }
849 $contactValues['work_country'] = '';
850
851 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID, $mapper, $fields);
852 $addresses = $this->callAPISuccess('Address', 'get', ['contact_id' => ['>' => 2], 'sequential' => 1]);
853 $this->assertEquals(2, $addresses['count']);
854 $this->assertEquals(array_search('United States', $countries), $addresses['values'][0]['country_id']);
855 $this->assertEquals(array_search('United States', $countries), $addresses['values'][1]['country_id']);
856 }
857
858 /**
859 * Run the import parser.
860 *
861 * @param array $originalValues
862 *
863 * @param int $onDuplicateAction
864 * @param int $expectedResult
865 * @param array|null $mapperLocType
866 * Array of location types that map to the input arrays.
867 * @param array|null $fields
868 * Array of field names. Will be calculated from $originalValues if not passed in, but
869 * that method does not cope with duplicates.
870 * @param int|null $ruleGroupId
871 * To test against a specific dedupe rule group, pass its ID as this argument.
872 *
873 * @throws \CRM_Core_Exception
874 * @throws \CiviCRM_API3_Exception
875 */
876 protected function runImport(array $originalValues, $onDuplicateAction, $expectedResult, $mapperLocType = [], $fields = NULL, int $ruleGroupId = NULL): void {
877 if (!$fields) {
878 $fields = array_keys($originalValues);
879 }
880 $values = array_values($originalValues);
881 $parser = new CRM_Contact_Import_Parser_Contact($fields, $mapperLocType);
882 $parser->_contactType = 'Individual';
883 $parser->_dedupeRuleGroupID = $ruleGroupId;
884 $parser->_onDuplicate = $onDuplicateAction;
885 $parser->init();
886 $this->assertEquals($expectedResult, $parser->import($onDuplicateAction, $values), 'Return code from parser import was not as expected');
887 }
888
889 /**
890 * @param array $fields Array of fields to be imported
891 * @param array $allfields Array of all fields which can be part of import
892 */
893 private function mapRelationshipFields(&$fields, $allfields) {
894 foreach ($allfields as $key => $fieldtocheck) {
895 $elementIndex = array_search($fieldtocheck->_title, $fields);
896 if ($elementIndex !== FALSE) {
897 $fields[$elementIndex] = $key;
898 }
899 }
900 }
901
902 /**
903 * Set up the underlying contact.
904 *
905 * @param array $params
906 * Optional extra parameters to set.
907 *
908 * @return array
909 * @throws \CRM_Core_Exception
910 */
911 protected function setUpBaseContact($params = []) {
912 $originalValues = array_merge([
913 'first_name' => 'Bill',
914 'last_name' => 'Gates',
915 'email' => 'bill.gates@microsoft.com',
916 'nick_name' => 'Billy-boy',
917 ], $params);
918 $this->runImport($originalValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID);
919 $result = $this->callAPISuccessGetSingle('Contact', $originalValues);
920 return [$originalValues, $result];
921 }
922
923 }