3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
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 +--------------------------------------------------------------------+
14 * File for the CRM_Contact_Imports_Parser_ContactTest class.
18 * Test contact import parser.
23 class CRM_Contact_Import_Parser_ContactTest
extends CiviUnitTestCase
{
24 use CRMTraits_Custom_CustomDataTrait
;
27 * Main entity for the class.
31 protected $entity = 'Contact';
36 public function setUp() {
41 * Tear down after test.
43 * @throws \CRM_Core_Exception
45 public function tearDown() {
46 $this->quickCleanup(['civicrm_address', 'civicrm_phone', 'civicrm_email'], TRUE);
51 * Test that import parser will add contact with employee of relationship.
53 public function testImportParserWithEmployeeOfRelationship(): void
{
54 $this->organizationCreate([
55 'organization_name' => 'Agileware',
56 'legal_name' => 'Agileware',
58 $contactImportValues = [
59 "first_name" => "Alok",
60 "last_name" => "Patel",
61 "Employee of" => "Agileware",
64 $fields = array_keys($contactImportValues);
65 $values = array_values($contactImportValues);
66 $parser = new CRM_Contact_Import_Parser_Contact($fields, []);
67 $parser->_contactType
= 'Individual';
69 $this->mapRelationshipFields($fields, $parser->getAllFields());
71 $parser = new CRM_Contact_Import_Parser_Contact($fields, [], [], [], [
83 ], [], [], [], [], []);
85 $parser->_contactType
= 'Individual';
86 $parser->_onDuplicate
= CRM_Import_Parser
::DUPLICATE_UPDATE
;
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",
98 * Test that import parser will not fail when same external_identifier found
101 * @throws \CRM_Core_Exception
102 * @throws \CiviCRM_API3_Exception
104 public function testImportParserWithDeletedContactExternalIdentifier(): void
{
105 $contactId = $this->individualCreate([
106 'external_identifier' => 'ext-1',
108 $this->callAPISuccess('Contact', 'delete', ['id' => $contactId]);
109 [$originalValues, $result] = $this->setUpBaseContact([
110 'external_identifier' => 'ext-1',
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);
120 * Test import parser will update based on a rule match.
122 * In this case the contact has no external identifier.
124 * @throws \CRM_Core_Exception
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);
136 * Test import parser will update contacts with an external identifier.
138 * This is the basic test where the identifier matches the import parameters.
142 public function testImportParserWithUpdateWithExternalIdentifier() {
143 [$originalValues, $result] = $this->setUpBaseContact(['external_identifier' => 'windows']);
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']);
148 $originalValues['nick_name'] = 'Old Bill';
149 $this->runImport($originalValues, CRM_Import_Parser
::DUPLICATE_UPDATE
, CRM_Import_Parser
::VALID
);
150 $originalValues['id'] = $result['id'];
152 $this->assertEquals('Old Bill', $this->callAPISuccessGetValue('Contact', ['id' => $result['id'], 'return' => 'nick_name']));
153 $this->callAPISuccessGetSingle('Contact', $originalValues);
157 * Test import parser will fallback to external identifier.
159 * In this case no primary match exists (e.g the details are not supplied) so it falls back on external identifier.
161 * @see https://issues.civicrm.org/jira/browse/CRM-17275
165 public function testImportParserWithUpdateWithExternalIdentifierButNoPrimaryMatch() {
166 [$originalValues, $result] = $this->setUpBaseContact([
167 'external_identifier' => 'windows',
171 $this->assertEquals('windows', $result['external_identifier']);
173 $originalValues['nick_name'] = 'Old Bill';
174 $this->runImport($originalValues, CRM_Import_Parser
::DUPLICATE_UPDATE
, CRM_Import_Parser
::VALID
);
175 $originalValues['id'] = $result['id'];
177 $this->assertEquals('Old Bill', $this->callAPISuccessGetValue('Contact', ['id' => $result['id'], 'return' => 'nick_name']));
178 $this->callAPISuccessGetSingle('Contact', $originalValues);
182 * Test import parser will fallback to external identifier.
184 * In this case no primary match exists (e.g the details are not supplied) so it falls back on external identifier.
186 * @see https://issues.civicrm.org/jira/browse/CRM-17275
190 public function testImportParserWithUpdateWithContactID() {
191 [$originalValues, $result] = $this->setUpBaseContact([
192 'external_identifier' => '',
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);
206 * Test that the import parser adds the external identifier where none is set.
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);
221 * Test that the import parser changes the external identifier when there is a dedupe match.
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);
237 * Test that the import parser adds the address to the right location.
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']);
251 $phone = $this->callAPISuccessGetSingle('Phone', ['phone' => '911']);
252 $this->assertEquals(2, $phone['location_type_id']);
254 $contact = $this->callAPISuccessGetSingle('Contact', $contactValues);
255 $this->callAPISuccess('Contact', 'delete', ['id' => $contact['id']]);
259 * Test that the not-really-encouraged way of creating locations via contact.create doesn't mess up primaries.
261 public function testContactLocationBlockHandling() {
262 $id = $this->individualCreate([
265 'location_type_id' => 1,
266 'phone' => '987654321',
269 'location_type_id' => 2,
270 'phone' => '456-7890',
275 'location_type_id' => 1,
279 'location_type_id' => 2,
285 'location_type_id' => 1,
289 'location_type_id' => 2,
295 'location_type_id' => 1,
296 'email' => 'bob@example.com',
299 'location_type_id' => 2,
300 'email' => 'fred@example.com',
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);
313 $this->assertLocationValidity();
314 $this->callAPISuccess('Contact', 'create', [
316 // This is secret code for 'delete this phone'.
317 'updateBlankLocInfo' => TRUE,
320 'id' => key($phones),
325 'id' => key($emails),
335 'id' => key($openIDs),
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);
347 * Test that the import parser adds the address to the primary location.
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']);
362 $phone = $this->callAPISuccessGetSingle('Phone', ['phone' => '12334']);
363 $this->assertEquals(1, $phone['location_type_id']);
365 $this->callAPISuccessGetSingle('Email', ['email' => 'bill.gates@microsoft.com']);
367 $contact = $this->callAPISuccessGetSingle('Contact', $contactValues);
368 $this->callAPISuccess('Contact', 'delete', ['id' => $contact['id']]);
372 * Test that address location type id is ignored for dedupe purposes on import.
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',
385 $this->callAPISuccess('Rule', 'create', [
386 'dedupe_rule_group_id' => $rgid,
387 'rule_field' => 'street_address',
388 'rule_table' => 'civicrm_address',
391 // Create a contact with an address of location_type_id 1.
393 'contact_type' => 'Individual',
394 'first_name' => 'Original',
395 'last_name' => 'Smith',
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',
405 'first_name' => 'New',
406 'last_name' => 'Smith',
407 'street_address' => 'Big Mansion',
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']]);
420 * Test that address custom fields can be imported
423 * @throws \CRM_Core_Exception
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']]);
438 * Test gender works when you specify the label.
440 * There is an expectation that you can import by label here.
442 * @throws \CRM_Core_Exception
444 public function testGenderLabel() {
446 'first_name' => 'Bill',
447 'last_name' => 'Gates',
448 'email' => 'bill.gates@microsoft.com',
449 'nick_name' => 'Billy-boy',
450 'gender_id' => 'Female',
452 $this->runImport($contactValues, CRM_Import_Parser
::DUPLICATE_UPDATE
, CRM_Import_Parser
::VALID
, [NULL, NULL, 'Primary', NULL, NULL]);
453 $this->callAPISuccessGetSingle('Contact', $contactValues);
457 * Test prefix & suffix work when you specify the label.
459 * There is an expectation that you can import by label here.
461 * @throws \CRM_Core_Exception
462 * @throws \CiviCRM_API3_Exception
464 public function testPrefixLabel() {
465 $this->callAPISuccess('OptionValue', 'create', ['option_group_id' => 'individual_prefix', 'name' => 'new_one', 'label' => 'special', 'value' => 70]);
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],
473 $processor = new CRM_Import_ImportProcessor();
474 $processor->setMappingFields($mapping);
475 $processor->setContactType('Individual');
476 $importer = $processor->getImporterObject();
481 'bill.gates@microsoft.com',
485 $importer->import(CRM_Import_Parser
::DUPLICATE_NOCHECK
, $contactValues);
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']);
492 * Test that labels work for importing custom data.
494 * @throws \CRM_Core_Exception
496 public function testCustomDataLabel() {
497 $this->createCustomGroupWithFieldOfType([], 'select');
499 'first_name' => 'Bill',
500 'last_name' => 'Gates',
501 'email' => 'bill.gates@microsoft.com',
502 'nick_name' => 'Billy-boy',
503 $this->getCustomFieldName('select') => 'Yellow',
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')]);
511 * Test that names work for importing custom data.
513 * @throws \CRM_Core_Exception
515 public function testCustomDataName() {
516 $this->createCustomGroupWithFieldOfType([], 'select');
518 'first_name' => 'Bill',
519 'last_name' => 'Gates',
520 'email' => 'bill.gates@microsoft.com',
521 'nick_name' => 'Billy-boy',
522 $this->getCustomFieldName('select') => 'Y',
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')]);
530 * Test importing in the Preferred Language Field
532 * @throws \CRM_Core_Exception
534 public function testPreferredLanguageImport() {
536 'first_name' => 'Bill',
537 'last_name' => 'Gates',
538 'email' => 'bill.gates@microsoft.com',
539 'nick_name' => 'Billy-boy',
540 'preferred_language' => 'English (Australia)',
542 $this->runImport($contactValues, CRM_Import_Parser
::DUPLICATE_UPDATE
, CRM_Import_Parser
::VALID
, [NULL, NULL, 'Primary', NULL, NULL]);
546 * Test that the import parser adds the address to the primary location.
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']]);
564 * Test that the import parser adds the address to the primary location.
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';
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]);
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']);
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']);
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']);
599 $this->callAPISuccess('Contact', 'delete', ['id' => $contact['id']]);
603 * Test importing 2 phones of different types.
605 * @throws \CRM_Core_Exception
606 * @throws \CiviCRM_API3_Exception
608 public function testImportTwoPhonesDifferentTypes() {
609 $processor = new CRM_Import_ImportProcessor();
610 $processor->setContactType('Individual');
611 $processor->setMappingFields(
613 ['name' => 'first_name'],
614 ['name' => 'last_name'],
616 ['name' => 'phone', 'location_type_id' => 1, 'phone_type_id' => 2],
617 ['name' => 'phone', 'location_type_id' => 1, 'phone_type_id' => 1],
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);
629 * Test that the import parser adds the address to the primary location.
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';
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'];
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']);
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']);
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']);
664 $this->callAPISuccess('Contact', 'delete', ['id' => $contact['id']]);
668 * Test that the import parser updates the address on the existing primary location.
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);
695 * Test the determination of whether a custom field is valid.
697 public function testCustomFieldValidation() {
699 $customGroup = $this->customGroupCreate([
700 'extends' => 'Contact',
703 $customField = $this->customFieldOptionValueCreate($customGroup, 'fieldABC', ['html_type' => 'Select', 'serialize' => 1]);
705 'custom_' . $customField['id'] => 'Label1|Label2',
707 CRM_Contact_Import_Parser_Contact
::isErrorInCustomData($params, $errorMessage);
708 $this->assertEquals([], $errorMessage);
712 * Test that setting duplicate action to fill doesn't blow away data
713 * that exists, but does fill in where it's empty.
717 public function testImportFill() {
718 // Create a custom field group for testing.
719 $this->createCustomGroup([
720 'title' => 'importFillGroup',
721 'extends' => 'Individual',
724 $customGroupID = $this->ids
['CustomGroup']['importFillGroup'];
726 // Add two custom fields.
728 'custom_group_id' => $customGroupID,
729 'label' => 'importFillField1',
730 'html_type' => 'Select',
731 'data_type' => 'String',
737 $result = $this->callAPISuccess('custom_field', 'create', $api_params);
738 $customField1 = $result['id'];
741 'custom_group_id' => $customGroupID,
742 'label' => 'importFillField2',
743 'html_type' => 'Select',
744 'data_type' => 'String',
750 $result = $this->callAPISuccess('custom_field', 'create', $api_params);
751 $customField2 = $result['id'];
753 // Now set up values.
754 $original_gender = 'Male';
755 $original_custom1 = 'foo';
756 $original_email = 'test-import-fill@example.org';
758 $import_gender = 'Female';
759 $import_custom1 = 'bar';
760 $import_job_title = 'Chief data importer';
761 $import_custom2 = 'baz';
763 // Create contact with both one known core field and one custom
766 'contact_type' => 'Individual',
767 'email' => $original_email,
768 'gender' => $original_gender,
769 'custom_' . $customField1 => $original_custom1,
771 $result = $this->callAPISuccess('contact', 'create', $api_params);
772 $contact_id = $result['id'];
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,
783 $this->runImport($import, CRM_Import_Parser
::DUPLICATE_FILL
, CRM_Import_Parser
::VALID
);
786 'gender' => $original_gender,
787 'custom_' . $customField1 => $original_custom1,
788 'job_title' => $import_job_title,
789 'custom_' . $customField2 => $import_custom2,
796 'custom_' . $customField1,
798 'custom_' . $customField2,
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])) {
808 $given_value = $values[$field];
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");
820 * CRM-19888 default country should be used if ambigous.
822 * @throws \CRM_Core_Exception
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);
833 'street_address' => 'PO Box 2716',
835 'state_province' => 'UT',
836 'postal_code' => 84049,
837 'country' => 'United States',
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);
849 $contactValues['work_country'] = '';
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']);
859 * Run the import parser.
861 * @param array $originalValues
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.
873 * @throws \CRM_Core_Exception
874 * @throws \CiviCRM_API3_Exception
876 protected function runImport(array $originalValues, $onDuplicateAction, $expectedResult, $mapperLocType = [], $fields = NULL, int $ruleGroupId = NULL): void
{
878 $fields = array_keys($originalValues);
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;
886 $this->assertEquals($expectedResult, $parser->import($onDuplicateAction, $values), 'Return code from parser import was not as expected');
890 * @param array $fields Array of fields to be imported
891 * @param array $allfields Array of all fields which can be part of import
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;
903 * Set up the underlying contact.
905 * @param array $params
906 * Optional extra parameters to set.
909 * @throws \CRM_Core_Exception
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',
918 $this->runImport($originalValues, CRM_Import_Parser
::DUPLICATE_UPDATE
, CRM_Import_Parser
::VALID
);
919 $result = $this->callAPISuccessGetSingle('Contact', $originalValues);
920 return [$originalValues, $result];