Convert the type on the UserJob entity to be a string
[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 use Civi\Api4\Address;
18 use Civi\Api4\Contact;
19 use Civi\Api4\ContactType;
20 use Civi\Api4\Email;
21 use Civi\Api4\Group;
22 use Civi\Api4\GroupContact;
23 use Civi\Api4\IM;
24 use Civi\Api4\LocationType;
25 use Civi\Api4\OpenID;
26 use Civi\Api4\Phone;
27 use Civi\Api4\Relationship;
28 use Civi\Api4\RelationshipType;
29 use Civi\Api4\UserJob;
30 use Civi\Api4\Website;
31
32 /**
33 * Test contact import parser.
34 *
35 * @package CiviCRM
36 * @group headless
37 */
38 class CRM_Contact_Import_Parser_ContactTest extends CiviUnitTestCase {
39 use CRMTraits_Custom_CustomDataTrait;
40 use CRMTraits_Import_ParserTrait;
41
42 /**
43 * Main entity for the class.
44 *
45 * @var string
46 */
47 protected $entity = 'Contact';
48
49 /**
50 * Array of existing relationships.
51 *
52 * @var array
53 */
54 private $relationships = [];
55
56 /**
57 * Tear down after test.
58 */
59 public function tearDown(): void {
60 $this->quickCleanup(['civicrm_address', 'civicrm_phone', 'civicrm_openid', 'civicrm_email', 'civicrm_user_job', 'civicrm_relationship', 'civicrm_im', 'civicrm_website', 'civicrm_queue', 'civicrm_queue_item'], TRUE);
61 RelationshipType::delete()->addWhere('name_a_b', '=', 'Dad to')->execute();
62 ContactType::delete()->addWhere('name', '=', 'baby')->execute();
63 parent::tearDown();
64 }
65
66 /**
67 * Test that import parser will add contact with employee of relationship.
68 *
69 * @throws \API_Exception
70 * @throws \CRM_Core_Exception
71 * @throws \CiviCRM_API3_Exception
72 */
73 public function testImportParserWithEmployeeOfRelationship(): void {
74 $this->organizationCreate([
75 'organization_name' => 'Agileware',
76 'legal_name' => 'Agileware',
77 ]);
78 $contactImportValues = [
79 'first_name' => 'Alok',
80 'last_name' => 'Patel',
81 'Employee of' => 'Agileware',
82 ];
83
84 $fields = array_keys($contactImportValues);
85 $values = array_values($contactImportValues);
86 $userJobID = $this->getUserJobID([
87 'mapper' => [['first_name'], ['last_name'], ['5_a_b', 'organization_name']],
88 'onDuplicate' => CRM_Import_Parser::DUPLICATE_UPDATE,
89 ]);
90
91 $parser = new CRM_Contact_Import_Parser_Contact($fields);
92 $parser->setUserJobID($userJobID);
93 $parser->init();
94
95 $this->assertEquals(CRM_Import_Parser::VALID, $parser->import($values), 'Return code from parser import was not as expected');
96 $this->callAPISuccessGetSingle('Contact', [
97 'first_name' => 'Alok',
98 'last_name' => 'Patel',
99 'organization_name' => 'Agileware',
100 ]);
101 }
102
103 /**
104 * Test that import parser will not fail when same external_identifier found
105 * of deleted contact.
106 *
107 * @throws \API_Exception
108 * @throws \CRM_Core_Exception
109 * @throws \CiviCRM_API3_Exception
110 */
111 public function testImportParserWithDeletedContactExternalIdentifier(): void {
112 $contactId = $this->individualCreate([
113 'external_identifier' => 'ext-1',
114 ]);
115 $this->callAPISuccess('Contact', 'delete', ['id' => $contactId]);
116 [$originalValues, $result] = $this->setUpBaseContact([
117 'external_identifier' => 'ext-1',
118 ]);
119 $originalValues['nick_name'] = 'Old Bill';
120 $this->runImport($originalValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID);
121 $originalValues['id'] = $result['id'];
122 $this->assertEquals('ext-1', $this->callAPISuccessGetValue('Contact', ['id' => $result['id'], 'return' => 'external_identifier']));
123 $this->callAPISuccessGetSingle('Contact', $originalValues);
124 }
125
126 /**
127 * Test import parser will update based on a rule match.
128 *
129 * In this case the contact has no external identifier.
130 *
131 * @throws \API_Exception
132 * @throws \CRM_Core_Exception
133 * @throws \CiviCRM_API3_Exception
134 */
135 public function testImportParserWithUpdateWithoutExternalIdentifier(): void {
136 [$originalValues, $result] = $this->setUpBaseContact();
137 $originalValues['nick_name'] = 'Old Bill';
138 $this->runImport($originalValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID);
139 $originalValues['id'] = $result['id'];
140 $this->assertEquals('Old Bill', $this->callAPISuccessGetValue('Contact', ['id' => $result['id'], 'return' => 'nick_name']));
141 $this->callAPISuccessGetSingle('Contact', $originalValues);
142 }
143
144 /**
145 * Test import parser will update based on a custom rule match.
146 *
147 * In this case the contact has no external identifier.
148 *
149 * @throws \API_Exception
150 * @throws \CRM_Core_Exception
151 * @throws \CiviCRM_API3_Exception
152 */
153 public function testImportParserWithUpdateWithCustomRule(): void {
154 $this->createCustomGroupWithFieldsOfAllTypes();
155
156 $ruleGroup = $this->callAPISuccess('RuleGroup', 'create', [
157 'contact_type' => 'Individual',
158 'threshold' => 10,
159 'used' => 'General',
160 'name' => 'TestRule',
161 'title' => 'TestRule',
162 'is_reserved' => 0,
163 ]);
164 $this->callAPISuccess('Rule', 'create', [
165 'dedupe_rule_group_id' => $ruleGroup['id'],
166 'rule_table' => $this->getCustomGroupTable(),
167 'rule_weight' => 10,
168 'rule_field' => $this->getCustomFieldColumnName('text'),
169 ]);
170
171 $extra = [
172 $this->getCustomFieldName('select_string') => 'Yellow',
173 $this->getCustomFieldName('text') => 'Duplicate',
174 ];
175
176 [$originalValues, $result] = $this->setUpBaseContact($extra);
177
178 $contactValues = [
179 'first_name' => 'Tim',
180 'last_name' => 'Cook',
181 'email' => 'tim.cook@apple.com',
182 'nick_name' => 'Steve',
183 $this->getCustomFieldName('select_string') => 'Red',
184 $this->getCustomFieldName('text') => 'Duplicate',
185 ];
186
187 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID, [], NULL, $ruleGroup['id']);
188 $contactValues['id'] = $result['id'];
189 $this->assertEquals('R', $this->callAPISuccessGetValue('Contact', ['id' => $result['id'], 'return' => $this->getCustomFieldName('select_string')]));
190 $this->callAPISuccessGetSingle('Contact', $contactValues);
191
192 $foundDupes = CRM_Dedupe_Finder::dupes($ruleGroup['id']);
193 $this->assertCount(0, $foundDupes);
194 }
195
196 /**
197 * Test import parser will update based on a custom rule match.
198 *
199 * In this case the contact has no external identifier.
200 *
201 * @throws \API_Exception
202 * @throws \CRM_Core_Exception
203 * @throws \CiviCRM_API3_Exception
204 */
205 public function testImportParserWithUpdateWithCustomRuleNoExternalIDMatch(): void {
206 $this->createCustomGroupWithFieldsOfAllTypes();
207
208 $ruleGroup = $this->callAPISuccess('RuleGroup', 'create', [
209 'contact_type' => 'Individual',
210 'threshold' => 10,
211 'used' => 'General',
212 'name' => 'TestRule',
213 'title' => 'TestRule',
214 'is_reserved' => 0,
215 ]);
216 $this->callAPISuccess('Rule', 'create', [
217 'dedupe_rule_group_id' => $ruleGroup['id'],
218 'rule_table' => $this->getCustomGroupTable(),
219 'rule_weight' => 10,
220 'rule_field' => $this->getCustomFieldColumnName('text'),
221 ]);
222
223 $extra = [
224 $this->getCustomFieldName('select_string') => 'Yellow',
225 $this->getCustomFieldName('text') => 'Duplicate',
226 'external_identifier' => 'ext-2',
227 ];
228
229 [$originalValues, $result] = $this->setUpBaseContact($extra);
230
231 $contactValues = [
232 'first_name' => 'Tim',
233 'last_name' => 'Cook',
234 'email' => 'tim.cook@apple.com',
235 'nick_name' => 'Steve',
236 'external_identifier' => 'ext-1',
237 $this->getCustomFieldName('select_string') => 'Red',
238 $this->getCustomFieldName('text') => 'Duplicate',
239 ];
240
241 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID, [], NULL, $ruleGroup['id']);
242 $contactValues['id'] = $result['id'];
243 $this->assertEquals('R', $this->callAPISuccessGetValue('Contact', ['id' => $result['id'], 'return' => $this->getCustomFieldName('select_string')]));
244 $this->callAPISuccessGetSingle('Contact', $contactValues);
245
246 $foundDupes = CRM_Dedupe_Finder::dupes($ruleGroup['id']);
247 $this->assertCount(0, $foundDupes);
248 }
249
250 /**
251 * Test import parser will update contacts with an external identifier.
252 *
253 * This is the basic test where the identifier matches the import parameters.
254 *
255 * @throws \Exception
256 */
257 public function testImportParserWithUpdateWithExternalIdentifier(): void {
258 [$originalValues, $result] = $this->setUpBaseContact(['external_identifier' => 'windows']);
259
260 $this->assertEquals($result['id'], CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', 'windows', 'id', 'external_identifier', TRUE));
261 $this->assertEquals('windows', $result['external_identifier']);
262
263 $originalValues['nick_name'] = 'Old Bill';
264 $this->runImport($originalValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID);
265 $originalValues['id'] = $result['id'];
266
267 $this->assertEquals('Old Bill', $this->callAPISuccessGetValue('Contact', ['id' => $result['id'], 'return' => 'nick_name']));
268 $this->callAPISuccessGetSingle('Contact', $originalValues);
269 }
270
271 /**
272 * Test updating an existing contact with external_identifier match but subtype mismatch.
273 *
274 * The subtype is updated, as there is no conflicting contact data.
275 *
276 * @throws \Exception
277 */
278 public function testImportParserWithUpdateWithExternalIdentifierSubtypeChange(): void {
279 $contactID = $this->individualCreate(['external_identifier' => 'billy', 'first_name' => 'William', 'contact_sub_type' => 'Parent']);
280 $this->runImport([
281 'external_identifier' => 'billy',
282 'nick_name' => 'Old Bill',
283 'contact_sub_type' => 'Staff',
284 ], CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID);
285 $contact = $this->callAPISuccessGetSingle('Contact', ['id' => $contactID]);
286 $this->assertEquals('Old Bill', $contact['nick_name']);
287 $this->assertEquals('William', $contact['first_name']);
288 $this->assertEquals('billy', $contact['external_identifier']);
289 $this->assertEquals(['Staff'], $contact['contact_sub_type']);
290 }
291
292 /**
293 * Test updating an existing contact with external_identifier match but subtype mismatch.
294 *
295 * The subtype is not updated, as there is conflicting contact data.
296 *
297 * @throws \Exception
298 */
299 public function testImportParserUpdateWithExternalIdentifierSubtypeChangeFail(): void {
300 $contactID = $this->individualCreate(['external_identifier' => 'billy', 'first_name' => 'William', 'contact_sub_type' => 'Parent']);
301 $this->addChild($contactID);
302
303 $this->runImport([
304 'external_identifier' => 'billy',
305 'nick_name' => 'Old Bill',
306 'contact_sub_type' => 'Staff',
307 ], CRM_Import_Parser::DUPLICATE_UPDATE, FALSE);
308 $contact = $this->callAPISuccessGetSingle('Contact', ['id' => $contactID]);
309 $this->assertEquals('', $contact['nick_name']);
310 $this->assertEquals(['Parent'], $contact['contact_sub_type']);
311 }
312
313 /**
314 * Test updating an existing contact with external_identifier match but subtype mismatch.
315 *
316 * The subtype is not updated, as there is conflicting contact data.
317 */
318 public function testImportParserUpdateWithExistingRelatedMatch(): void {
319 $contactID = $this->individualCreate([
320 'external_identifier' => 'billy',
321 'first_name' => 'William',
322 'last_name' => 'The Kid',
323 'email' => 'billy-the-kid@example.com',
324 'contact_sub_type' => 'Parent',
325 ]);
326 $this->addChild($contactID);
327 $this->importCSV('individual_related_create.csv', [
328 ['first_name'], ['last_name'], [$this->relationships['Dad to'], 'first_name'], [$this->relationships['Dad to'], 'last_name'], [$this->relationships['Dad to'], 'email'],
329 ], [
330 'onDuplicate' => CRM_Import_Parser::DUPLICATE_SKIP,
331 ]);
332 $dataSource = $this->getDataSource();
333 $row = $dataSource->getRow();
334 $this->assertEquals('IMPORTED', $row['_status']);
335 $row = $dataSource->getRow();
336 $this->assertEquals('IMPORTED', $row['_status']);
337 $row = $dataSource->getRow();
338 $this->assertEquals('IMPORTED', $row['_status']);
339 $row = $dataSource->getRow();
340 // currently Error with the message (Dad to) Missing required fields: Last Name OR Email Address OR External Identifier
341 // $this->assertEquals('IMPORTED', $row['_status']);
342 }
343
344 /**
345 * Test updating an existing contact with external_identifier match but subtype mismatch.
346 *
347 * @throws \Exception
348 */
349 public function testImportParserWithUpdateWithTypeMismatch(): void {
350 $contactID = $this->organizationCreate(['external_identifier' => 'billy']);
351 $this->runImport([
352 'external_identifier' => 'billy',
353 'nick_name' => 'Old Bill',
354 ], CRM_Import_Parser::DUPLICATE_UPDATE, FALSE);
355 $contact = $this->callAPISuccessGetSingle('Contact', ['id' => $contactID]);
356 $this->assertEquals('', $contact['nick_name']);
357 $this->assertEquals('billy', $contact['external_identifier']);
358 $this->assertEquals('Organization', $contact['contact_type']);
359
360 $this->runImport([
361 'id' => $contactID,
362 'nick_name' => 'Old Bill',
363 ], CRM_Import_Parser::DUPLICATE_UPDATE, FALSE);
364 $contact = $this->callAPISuccessGetSingle('Contact', ['id' => $contactID]);
365 $this->assertEquals('', $contact['nick_name']);
366 $this->assertEquals('billy', $contact['external_identifier']);
367 $this->assertEquals('Organization', $contact['contact_type']);
368
369 }
370
371 /**
372 * Test import parser will fallback to external identifier.
373 *
374 * In this case no primary match exists (e.g the details are not supplied) so it falls back on external identifier.
375 *
376 * @see https://issues.civicrm.org/jira/browse/CRM-17275
377 *
378 * @throws \Exception
379 */
380 public function testImportParserWithUpdateWithExternalIdentifierButNoPrimaryMatch(): void {
381 [$originalValues, $result] = $this->setUpBaseContact([
382 'external_identifier' => 'windows',
383 'email' => NULL,
384 ]);
385
386 $this->assertEquals('windows', $result['external_identifier']);
387
388 $originalValues['nick_name'] = 'Old Bill';
389 $this->runImport($originalValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID);
390 $originalValues['id'] = $result['id'];
391
392 $this->assertEquals('Old Bill', $this->callAPISuccessGetValue('Contact', ['id' => $result['id'], 'return' => 'nick_name']));
393 $this->callAPISuccessGetSingle('Contact', $originalValues);
394 }
395
396 /**
397 * Test import parser will fallback to external identifier.
398 *
399 * In this case no primary match exists (e.g the details are not supplied) so it falls back on external identifier.
400 *
401 * @see https://issues.civicrm.org/jira/browse/CRM-17275
402 *
403 * @throws \Exception
404 */
405 public function testImportParserWithUpdateWithContactID(): void {
406 [$originalValues, $result] = $this->setUpBaseContact([
407 'external_identifier' => '',
408 'email' => NULL,
409 ]);
410 $updateValues = ['id' => $result['id'], 'email' => 'bill@example.com'];
411 // This is some deep weirdness - this sets a flag for updatingBlankLocinfo - allowing input to be blanked
412 // (which IS a good thing but it's pretty weird & all to do with legacy profile stuff).
413 CRM_Core_Session::singleton()->set('authSrc', CRM_Core_Permission::AUTH_SRC_CHECKSUM);
414 $this->runImport($updateValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID);
415 $originalValues['id'] = $result['id'];
416 $this->callAPISuccessGetSingle('Email', ['contact_id' => $originalValues['id'], 'is_primary' => 1]);
417 $this->callAPISuccessGetSingle('Contact', $originalValues);
418 }
419
420 /**
421 * Test that the import parser adds the external identifier where none is set.
422 *
423 * @throws \Exception
424 */
425 public function testImportParserWithUpdateWithNoExternalIdentifier(): void {
426 [$originalValues, $result] = $this->setUpBaseContact();
427 $originalValues['nick_name'] = 'Old Bill';
428 $originalValues['external_identifier'] = 'windows';
429 $this->runImport($originalValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID);
430 $originalValues['id'] = $result['id'];
431 $this->assertEquals('Old Bill', $this->callAPISuccessGetValue('Contact', ['id' => $result['id'], 'return' => 'nick_name']));
432 $this->callAPISuccessGetSingle('Contact', $originalValues);
433 }
434
435 /**
436 * Test that the import parser changes the external identifier when there is a dedupe match.
437 *
438 * @throws \Exception
439 */
440 public function testImportParserWithUpdateWithChangedExternalIdentifier(): void {
441 [$contactValues, $result] = $this->setUpBaseContact(['external_identifier' => 'windows']);
442 $contact_id = $result['id'];
443 $contactValues['nick_name'] = 'Old Bill';
444 $contactValues['external_identifier'] = 'android';
445 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID);
446 $contactValues['id'] = $contact_id;
447 $this->assertEquals('Old Bill', $this->callAPISuccessGetValue('Contact', ['id' => $contact_id, 'return' => 'nick_name']));
448 $this->callAPISuccessGetSingle('Contact', $contactValues);
449 }
450
451 /**
452 * Test that the import parser adds the address to the right location.
453 *
454 * @throws \API_Exception
455 * @throws \CRM_Core_Exception
456 * @throws \CiviCRM_API3_Exception
457 */
458 public function testImportBillingAddress(): void {
459 [$contactValues] = $this->setUpBaseContact();
460 $contactValues['nick_name'] = 'Old Bill';
461 $contactValues['external_identifier'] = 'android';
462 $contactValues['street_address'] = 'Big Mansion';
463 $contactValues['phone'] = '911';
464 $mapper = $this->getFieldMappingFromInput($contactValues, 2);
465 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID, $mapper);
466 $address = $this->callAPISuccessGetSingle('Address', ['street_address' => 'Big Mansion']);
467 $this->assertEquals(2, $address['location_type_id']);
468
469 $phone = $this->callAPISuccessGetSingle('Phone', ['phone' => '911']);
470 $this->assertEquals(2, $phone['location_type_id']);
471
472 $contact = $this->callAPISuccessGetSingle('Contact', $contactValues);
473 $this->callAPISuccess('Contact', 'delete', ['id' => $contact['id']]);
474 }
475
476 /**
477 * Test that the not-really-encouraged way of creating locations via contact.create doesn't mess up primaries.
478 */
479 public function testContactLocationBlockHandling(): void {
480 $id = $this->individualCreate([
481 'phone' => [
482 1 => [
483 'location_type_id' => 1,
484 'phone' => '987654321',
485 ],
486 2 => [
487 'location_type_id' => 2,
488 'phone' => '456-7890',
489 ],
490 ],
491 'im' => [
492 1 => [
493 'location_type_id' => 1,
494 'name' => 'bob',
495 ],
496 2 => [
497 'location_type_id' => 2,
498 'name' => 'fred',
499 ],
500 ],
501 'openid' => [
502 1 => [
503 'location_type_id' => 1,
504 'openid' => 'bob',
505 ],
506 2 => [
507 'location_type_id' => 2,
508 'openid' => 'fred',
509 ],
510 ],
511 'email' => [
512 1 => [
513 'location_type_id' => 1,
514 'email' => 'bob@example.com',
515 ],
516 2 => [
517 'location_type_id' => 2,
518 'email' => 'fred@example.com',
519 ],
520 ],
521 ]);
522 $phones = $this->callAPISuccess('Phone', 'get', ['contact_id' => $id])['values'];
523 $emails = $this->callAPISuccess('Email', 'get', ['contact_id' => $id])['values'];
524 $openIDs = $this->callAPISuccess('OpenID', 'get', ['contact_id' => $id])['values'];
525 $ims = $this->callAPISuccess('IM', 'get', ['contact_id' => $id])['values'];
526 $this->assertCount(2, $phones);
527 $this->assertCount(2, $emails);
528 $this->assertCount(2, $ims);
529 $this->assertCount(2, $openIDs);
530
531 $this->assertLocationValidity();
532 $this->callAPISuccess('Contact', 'create', [
533 'id' => $id,
534 // This is secret code for 'delete this phone'.
535 'updateBlankLocInfo' => TRUE,
536 'phone' => [
537 1 => [
538 'id' => key($phones),
539 ],
540 ],
541 'email' => [
542 1 => [
543 'id' => key($emails),
544 ],
545 ],
546 'im' => [
547 1 => [
548 'id' => key($ims),
549 ],
550 ],
551 'openid' => [
552 1 => [
553 'id' => key($openIDs),
554 ],
555 ],
556 ]);
557 $this->assertLocationValidity();
558 $this->callAPISuccessGetCount('Phone', ['contact_id' => $id], 1);
559 $this->callAPISuccessGetCount('Email', ['contact_id' => $id], 1);
560 $this->callAPISuccessGetCount('OpenID', ['contact_id' => $id], 1);
561 $this->callAPISuccessGetCount('IM', ['contact_id' => $id], 1);
562 }
563
564 /**
565 * Test that the import parser adds the address to the primary location.
566 *
567 * @throws \Exception
568 */
569 public function testImportPrimaryAddress(): void {
570 [$contactValues] = $this->setUpBaseContact();
571 $contactValues['nick_name'] = 'Old Bill';
572 $contactValues['external_identifier'] = 'android';
573 $contactValues['street_address'] = 'Big Mansion';
574 $contactValues['phone'] = 12334;
575 $mapper = $this->getFieldMappingFromInput($contactValues);
576 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID, $mapper);
577 $address = $this->callAPISuccessGetSingle('Address', ['street_address' => 'Big Mansion']);
578 $this->assertEquals(1, $address['location_type_id']);
579 $this->assertEquals(1, $address['is_primary']);
580
581 $phone = $this->callAPISuccessGetSingle('Phone', ['phone' => '12334']);
582 $this->assertEquals(1, $phone['location_type_id']);
583
584 $this->callAPISuccessGetSingle('Email', ['email' => 'bill.gates@microsoft.com']);
585
586 $contact = $this->callAPISuccessGetSingle('Contact', $contactValues);
587 $this->callAPISuccess('Contact', 'delete', ['id' => $contact['id']]);
588 }
589
590 /**
591 * Test that address location type id is ignored for dedupe purposes on import.
592 *
593 * @throws \Exception
594 */
595 public function testIgnoreLocationTypeId(): void {
596 // Create a rule that matches on last name and street address.
597 $rgid = $this->createRuleGroup()['id'];
598 $this->callAPISuccess('Rule', 'create', [
599 'dedupe_rule_group_id' => $rgid,
600 'rule_field' => 'last_name',
601 'rule_table' => 'civicrm_contact',
602 'rule_weight' => 4,
603 ]);
604 $this->callAPISuccess('Rule', 'create', [
605 'dedupe_rule_group_id' => $rgid,
606 'rule_field' => 'street_address',
607 'rule_table' => 'civicrm_address',
608 'rule_weight' => 4,
609 ]);
610 // Create a contact with an address of location_type_id 1.
611 $contact1Params = [
612 'contact_type' => 'Individual',
613 'first_name' => 'Original',
614 'last_name' => 'Smith',
615 ];
616 $contact1 = $this->callAPISuccess('Contact', 'create', $contact1Params);
617 $this->callAPISuccess('Address', 'create', [
618 'contact_id' => $contact1['id'],
619 'location_type_id' => 1,
620 'street_address' => 'Big Mansion',
621 ]);
622
623 $contactValues = [
624 'first_name' => 'New',
625 'last_name' => 'Smith',
626 'street_address' => 'Big Mansion',
627 ];
628
629 // We want to import with a location_type_id of 4.
630 $fieldMapping = $this->getFieldMappingFromInput($contactValues, 4);
631 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_SKIP, CRM_Import_Parser::DUPLICATE, $fieldMapping, NULL, $rgid);
632 $address = $this->callAPISuccessGetSingle('Address', ['street_address' => 'Big Mansion']);
633 $this->assertEquals(1, $address['location_type_id']);
634 $contact = $this->callAPISuccessGetSingle('Contact', $contact1Params);
635 $this->callAPISuccess('Contact', 'delete', ['id' => $contact['id']]);
636 }
637
638 /**
639 * Test that address custom fields can be imported
640 * FIXME: Api4
641 *
642 * @throws \CRM_Core_Exception
643 */
644 public function testAddressWithCustomData(): void {
645 $ids = $this->entityCustomGroupWithSingleFieldCreate('Address', 'AddressTest.php');
646 [$contactValues] = $this->setUpBaseContact();
647 $contactValues['nick_name'] = 'Old Bill';
648 $contactValues['external_identifier'] = 'android';
649 $contactValues['street_address'] = 'Big Mansion';
650 $contactValues['custom_' . $ids['custom_field_id']] = 'Update';
651 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID);
652 $address = $this->callAPISuccessGetSingle('Address', ['street_address' => 'Big Mansion', 'return' => 'custom_' . $ids['custom_field_id']]);
653 $this->assertEquals('Update', $address['custom_' . $ids['custom_field_id']]);
654 }
655
656 /**
657 * Test gender works when you specify the label.
658 *
659 * There is an expectation that you can import by label here.
660 *
661 * @throws \CRM_Core_Exception
662 */
663 public function testGenderLabel() {
664 $contactValues = [
665 'first_name' => 'Bill',
666 'last_name' => 'Gates',
667 'email' => 'bill.gates@microsoft.com',
668 'nick_name' => 'Billy-boy',
669 'gender_id' => 'Female',
670 ];
671 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID);
672 $this->callAPISuccessGetSingle('Contact', $contactValues);
673 }
674
675 /**
676 * Test greeting imports.
677 *
678 * @throws \API_Exception
679 * @throws \CRM_Core_Exception
680 * @throws \CiviCRM_API3_Exception
681 */
682 public function testGreetings(): void {
683 $contactValues = [
684 'first_name' => 'Bill',
685 'last_name' => 'Gates',
686 // id = 2
687 'email_greeting' => 'Dear {contact.prefix_id:label} {contact.first_name} {contact.last_name}',
688 // id = 3
689 'postal_greeting' => 'Dear {contact.prefix_id:label} {contact.last_name}',
690 // id = 1
691 'addressee' => '{contact.prefix_id:label}{ }{contact.first_name}{ }{contact.middle_name}{ }{contact.last_name}{ }{contact.suffix_id:label}',
692 5 => 1,
693 ];
694 $userJobID = $this->getUserJobID([
695 'mapper' => [['first_name'], ['last_name'], ['email_greeting'], ['postal_greeting'], ['addressee']],
696 'onDuplicate' => CRM_Import_Parser::DUPLICATE_UPDATE,
697 ]);
698 $parser = new CRM_Contact_Import_Parser_Contact();
699 $parser->setUserJobID($userJobID);
700 $values = array_values($contactValues);
701 $parser->import($values);
702 $contact = Contact::get(FALSE)->addWhere('last_name', '=', 'Gates')->addSelect('email_greeting_id', 'postal_greeting_id', 'addressee_id')->execute()->first();
703 $this->assertEquals(2, $contact['email_greeting_id']);
704 $this->assertEquals(3, $contact['postal_greeting_id']);
705 $this->assertEquals(1, $contact['addressee_id']);
706
707 Contact::delete()->addWhere('id', '=', $contact['id'])->setUseTrash(TRUE)->execute();
708
709 // Now try again with numbers.
710 $values[2] = 2;
711 $values[3] = 3;
712 $values[4] = 1;
713 $parser->import($values);
714 $contact = Contact::get(FALSE)->addWhere('last_name', '=', 'Gates')->addSelect('email_greeting_id', 'postal_greeting_id', 'addressee_id')->execute()->first();
715 $this->assertEquals(2, $contact['email_greeting_id']);
716 $this->assertEquals(3, $contact['postal_greeting_id']);
717 $this->assertEquals(1, $contact['addressee_id']);
718
719 }
720
721 /**
722 * Test prefix & suffix work when you specify the label.
723 *
724 * There is an expectation that you can import by label here.
725 *
726 * @throws \API_Exception
727 * @throws \CRM_Core_Exception
728 * @throws \CiviCRM_API3_Exception
729 */
730 public function testPrefixLabel(): void {
731 $this->callAPISuccess('OptionValue', 'create', ['option_group_id' => 'individual_prefix', 'name' => 'new_one', 'label' => 'special', 'value' => 70]);
732 $mapping = [
733 ['name' => 'first_name', 'column_number' => 0],
734 ['name' => 'last_name', 'column_number' => 1],
735 ['name' => 'email', 'column_number' => 2, 'location_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Email', 'location_type_id', 'Home')],
736 ['name' => 'prefix_id', 'column_number' => 3],
737 ['name' => 'suffix_id', 'column_number' => 4],
738 ];
739 $mapperInput = [['first_name'], ['last_name'], ['email', CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Email', 'location_type_id', 'Home')], ['prefix_id'], ['suffix_id']];
740
741 $processor = new CRM_Import_ImportProcessor();
742 $processor->setMappingFields($mapping);
743 $userJobID = $this->getUserJobID(['mapper' => $mapperInput, 'onDuplicate' => CRM_Import_Parser::DUPLICATE_NOCHECK]);
744 $processor->setUserJobID($userJobID);
745 $importer = $processor->getImporterObject();
746
747 $contactValues = [
748 'Bill',
749 'Gates',
750 'bill.gates@microsoft.com',
751 'special',
752 'III',
753 ];
754 $importer->import($contactValues);
755
756 $contact = $this->callAPISuccessGetSingle('Contact', ['first_name' => 'Bill', 'prefix_id' => 'new_one', 'suffix_id' => 'III']);
757 $this->assertEquals('special Bill Gates III', $contact['display_name']);
758 }
759
760 /**
761 * Test that labels work for importing custom data.
762 *
763 * @throws \API_Exception
764 * @throws \CRM_Core_Exception
765 * @throws \CiviCRM_API3_Exception
766 */
767 public function testCustomDataLabel(): void {
768 $this->createCustomGroupWithFieldOfType([], 'select');
769 $contactValues = [
770 'first_name' => 'Bill',
771 'last_name' => 'Gates',
772 'email' => 'bill.gates@microsoft.com',
773 'nick_name' => 'Billy-boy',
774 $this->getCustomFieldName('select') => 'Yellow',
775 ];
776 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID);
777 $contact = $this->callAPISuccessGetSingle('Contact', array_merge($contactValues, ['return' => $this->getCustomFieldName('select')]));
778 $this->assertEquals('Y', $contact[$this->getCustomFieldName('select')]);
779 }
780
781 /**
782 * Test that names work for importing custom data.
783 *
784 * @throws \CRM_Core_Exception
785 */
786 public function testCustomDataName() {
787 $this->createCustomGroupWithFieldOfType([], 'select');
788 $contactValues = [
789 'first_name' => 'Bill',
790 'last_name' => 'Gates',
791 'email' => 'bill.gates@microsoft.com',
792 'nick_name' => 'Billy-boy',
793 $this->getCustomFieldName('select') => 'Y',
794 ];
795 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID);
796 $contact = $this->callAPISuccessGetSingle('Contact', array_merge($contactValues, ['return' => $this->getCustomFieldName('select')]));
797 $this->assertEquals('Y', $contact[$this->getCustomFieldName('select')]);
798 }
799
800 /**
801 * Test importing in the Preferred Language Field
802 *
803 * @throws \CRM_Core_Exception
804 */
805 public function testPreferredLanguageImport() {
806 $contactValues = [
807 'first_name' => 'Bill',
808 'last_name' => 'Gates',
809 'email' => 'bill.gates@microsoft.com',
810 'nick_name' => 'Billy-boy',
811 'preferred_language' => 'English (Australia)',
812 ];
813 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID);
814 }
815
816 /**
817 * Test that the import parser adds the address to the primary location.
818 *
819 * @throws \Exception
820 */
821 public function testImportDeceased() {
822 [$contactValues] = $this->setUpBaseContact();
823 CRM_Core_Session::singleton()->set("dateTypes", 1);
824 $contactValues['birth_date'] = '1910-12-17';
825 $contactValues['deceased_date'] = '2010-12-17';
826 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID);
827 $contact = $this->callAPISuccessGetSingle('Contact', $contactValues);
828 $this->assertEquals('1910-12-17', $contact['birth_date']);
829 $this->assertEquals('2010-12-17', $contact['deceased_date']);
830 $this->assertEquals(1, $contact['is_deceased']);
831 $this->callAPISuccess('Contact', 'delete', ['id' => $contact['id']]);
832 }
833
834 /**
835 * Test that the import parser adds the address to the primary location.
836 *
837 * @throws \Exception
838 */
839 public function testImportTwoAddressFirstPrimary(): void {
840 [$contactValues] = $this->setUpBaseContact();
841 $contactValues['nick_name'] = 'Old Bill';
842 $contactValues['external_identifier'] = 'android';
843
844 $contactValues['street_address'] = 'Big Mansion';
845 $contactValues['phone'] = 12334;
846
847 $fieldMapping = $this->getFieldMappingFromInput($contactValues);
848 $contactValues['street_address_2'] = 'Teeny Mansion';
849 $fieldMapping[] = ['name' => 'street_address', 'location_type_id' => 3];
850 $contactValues['phone_2'] = 4444;
851 $fieldMapping[] = ['name' => 'phone', 'location_type_id' => 3, 'phone_type_id' => 1];
852
853 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID, $fieldMapping);
854 $contact = $this->callAPISuccessGetSingle('Contact', ['external_identifier' => 'android']);
855 $address = $this->callAPISuccess('Address', 'get', ['contact_id' => $contact['id'], 'sequential' => 1]);
856
857 $this->assertEquals(3, $address['values'][0]['location_type_id']);
858 $this->assertEquals(0, $address['values'][0]['is_primary']);
859 $this->assertEquals('Teeny Mansion', $address['values'][0]['street_address']);
860
861 $this->assertEquals(1, $address['values'][1]['location_type_id']);
862 $this->assertEquals(1, $address['values'][1]['is_primary']);
863 $this->assertEquals('Big Mansion', $address['values'][1]['street_address']);
864
865 $phone = $this->callAPISuccess('Phone', 'get', ['contact_id' => $contact['id'], 'sequential' => 1]);
866 $this->assertEquals(1, $phone['values'][0]['location_type_id']);
867 $this->assertEquals(1, $phone['values'][0]['is_primary']);
868 $this->assertEquals(12334, $phone['values'][0]['phone']);
869 $this->assertEquals(3, $phone['values'][1]['location_type_id']);
870 $this->assertEquals(0, $phone['values'][1]['is_primary']);
871 $this->assertEquals(4444, $phone['values'][1]['phone']);
872
873 $this->callAPISuccess('Contact', 'delete', ['id' => $contact['id']]);
874 }
875
876 /**
877 * Test importing 2 phones of different types.
878 *
879 * @throws \API_Exception
880 * @throws \CRM_Core_Exception
881 * @throws \CiviCRM_API3_Exception
882 */
883 public function testImportTwoPhonesDifferentTypes(): void {
884 $processor = new CRM_Import_ImportProcessor();
885 $processor->setUserJobID($this->getUserJobID([
886 'mapper' => [['first_name'], ['last_name'], ['email'], ['phone', 1, 2], ['phone', 1, 1]],
887 'onDuplicate' => CRM_Import_Parser::DUPLICATE_UPDATE,
888 ]));
889 $processor->setMappingFields(
890 [
891 ['name' => 'first_name'],
892 ['name' => 'last_name'],
893 ['name' => 'email'],
894 ['name' => 'phone', 'location_type_id' => 1, 'phone_type_id' => 2],
895 ['name' => 'phone', 'location_type_id' => 1, 'phone_type_id' => 1],
896 ]
897 );
898 $importer = $processor->getImporterObject();
899 $fields = ['First Name', 'new last name', 'bob@example.com', '1234', '5678'];
900 $importer->import($fields);
901 $contact = $this->callAPISuccessGetSingle('Contact', ['last_name' => 'new last name']);
902 $phones = $this->callAPISuccess('Phone', 'get', ['contact_id' => $contact['id']])['values'];
903 $this->assertCount(2, $phones);
904 }
905
906 /**
907 * Test that the import parser adds the address to the primary location.
908 *
909 * @throws \Exception
910 */
911 public function testImportTwoAddressSecondPrimary(): void {
912 [$contactValues] = $this->setUpBaseContact();
913 $contactValues['nick_name'] = 'Old Bill';
914 $contactValues['external_identifier'] = 'android';
915 $contactValues['street_address'] = 'Big Mansion';
916 $contactValues['phone'] = 12334;
917
918 $fieldMapping = $this->getFieldMappingFromInput($contactValues, 3);
919
920 $contactValues['street_address_2'] = 'Teeny Mansion';
921 $fieldMapping[] = ['name' => 'street_address', 'location_type_id' => 'Primary'];
922 $contactValues['phone_2'] = 4444;
923 $fieldMapping[] = ['name' => 'phone', 'location_type_id' => 'Primary', 'phone_type_id' => 1];
924
925 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID, $fieldMapping);
926 $contact = $this->callAPISuccessGetSingle('Contact', ['external_identifier' => 'android']);
927 $address = $this->callAPISuccess('Address', 'get', ['contact_id' => $contact['id'], 'sequential' => 1])['values'];
928
929 $this->assertEquals(1, $address[1]['location_type_id']);
930 $this->assertEquals(1, $address[1]['is_primary']);
931 $this->assertEquals('Teeny Mansion', $address[1]['street_address']);
932
933 $this->assertEquals(3, $address[0]['location_type_id']);
934 $this->assertEquals(0, $address[0]['is_primary']);
935 $this->assertEquals('Big Mansion', $address[0]['street_address']);
936
937 $phone = $this->callAPISuccess('Phone', 'get', ['contact_id' => $contact['id'], 'sequential' => 1, 'options' => ['sort' => 'is_primary DESC']])['values'];
938 $this->assertEquals(3, $phone[1]['location_type_id']);
939 $this->assertEquals(0, $phone[1]['is_primary']);
940 $this->assertEquals(12334, $phone[1]['phone']);
941 $this->assertEquals(1, $phone[0]['location_type_id']);
942 $this->assertEquals(1, $phone[0]['is_primary']);
943 $this->assertEquals(4444, $phone[0]['phone']);
944
945 $this->callAPISuccess('Contact', 'delete', ['id' => $contact['id']]);
946 }
947
948 /**
949 * Test that the import parser updates the address on the existing primary location.
950 *
951 * @throws \Exception
952 */
953 public function testImportPrimaryAddressUpdate(): void {
954 [$contactValues] = $this->setUpBaseContact(['external_identifier' => 'android']);
955 $contactValues['email'] = 'melinda.gates@microsoft.com';
956 $contactValues['phone'] = '98765';
957 $contactValues['external_identifier'] = 'android';
958 $contactValues['street_address'] = 'Big Mansion';
959 $contactValues['city'] = 'Big City';
960 $contactID = $this->callAPISuccessGetValue('Contact', ['external_identifier' => 'android', 'return' => 'id']);
961 $originalAddress = $this->callAPISuccess('Address', 'create', ['location_type_id' => 2, 'street_address' => 'small house', 'contact_id' => $contactID]);
962 $originalPhone = $this->callAPISuccess('phone', 'create', ['location_type_id' => 2, 'phone' => '1234', 'contact_id' => $contactID, 'phone_type_id' => 1]);
963 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID, []);
964 $phone = $this->callAPISuccessGetSingle('Phone', ['phone' => '98765']);
965 $this->assertEquals(2, $phone['location_type_id']);
966 $this->assertEquals($originalPhone['id'], $phone['id']);
967 $email = $this->callAPISuccess('Email', 'getsingle', ['contact_id' => $contactID]);
968 $address = $this->callAPISuccessGetSingle('Address', ['street_address' => 'Big Mansion']);
969 $this->assertEquals(2, $address['location_type_id']);
970 $this->assertEquals($originalAddress['id'], $address['id']);
971 $this->assertEquals('Big City', $address['city']);
972 $this->callAPISuccessGetSingle('Contact', $contactValues);
973 }
974
975 /**
976 * Test the determination of whether a custom field is valid.
977 */
978 public function testCustomFieldValidation(): void {
979 $errorMessage = '';
980 $customGroup = $this->customGroupCreate([
981 'extends' => 'Contact',
982 'title' => 'ABC',
983 ]);
984 $customField = $this->customFieldOptionValueCreate($customGroup, 'fieldABC', ['html_type' => 'Select', 'serialize' => 1]);
985 $params = [
986 'custom_' . $customField['id'] => 'Label1|Label2',
987 ];
988 $parser = new CRM_Contact_Import_Parser_Contact();
989 $parser->isErrorInCustomData($params, $errorMessage);
990 $this->assertEquals(NULL, $errorMessage);
991 }
992
993 /**
994 * Test the import validation.
995 *
996 * @dataProvider validateDataProvider
997 *
998 * @param string $csv
999 * @param array $mapper Mapping as entered on MapField form.
1000 * e.g [['first_name']['email', 1]].
1001 * {@see \CRM_Contact_Import_Parser_Contact::getMappingFieldFromMapperInput}
1002 * @param string $expectedError
1003 * @param array $submittedValues
1004 *
1005 *
1006 * @throws \API_Exception
1007 */
1008 public function testValidation(string $csv, array $mapper, string $expectedError = '', $submittedValues = []): void {
1009 try {
1010 $this->validateCSV($csv, $mapper, $submittedValues);
1011 }
1012 catch (CRM_Core_Exception $e) {
1013 $this->assertSame($expectedError, $e->getMessage());
1014 return;
1015 }
1016 if ($expectedError) {
1017 $this->fail('expected error :' . $expectedError);
1018 }
1019 }
1020
1021 /**
1022 * Get combinations to test for validation.
1023 *
1024 * @return array[]
1025 */
1026 public function validateDataProvider(): array {
1027 return [
1028 'individual_required' => [
1029 'csv' => 'individual_invalid_missing_name.csv',
1030 'mapper' => [['last_name']],
1031 'expected_error' => 'Missing required fields: First Name OR Email Address',
1032 ],
1033 'individual_related_required_met' => [
1034 'csv' => 'individual_valid_with_related_email.csv',
1035 'mapper' => [['first_name'], ['last_name'], ['1_a_b', 'email']],
1036 'expected_error' => '',
1037 ],
1038 'individual_related_required_not_met' => [
1039 'csv' => 'individual_invalid_with_related_phone.csv',
1040 'mapper' => [['first_name'], ['last_name'], ['1_a_b', 'phone', 1, 2]],
1041 'expected_error' => '(Child of) Missing required fields: First Name and Last Name OR Email Address OR External Identifier',
1042 ],
1043 'individual_bad_email' => [
1044 'csv' => 'individual_invalid_email.csv',
1045 'mapper' => [['email', 1], ['first_name'], ['last_name']],
1046 'expected_error' => 'Invalid value for field(s) : Email',
1047 ],
1048 'individual_related_bad_email' => [
1049 'csv' => 'individual_invalid_related_email.csv',
1050 'mapper' => [['1_a_b', 'email', 1], ['first_name'], ['last_name']],
1051 'expected_error' => 'Invalid value for field(s) : (Child of) Email',
1052 ],
1053 'individual_invalid_external_identifier_only' => [
1054 // External identifier is only enough in upgrade mode.
1055 'csv' => 'individual_invalid_external_identifier_only.csv',
1056 'mapper' => [['external_identifier'], ['gender_id']],
1057 'expected_error' => 'Missing required fields: First Name and Last Name OR Email Address',
1058 ],
1059 'individual_invalid_external_identifier_only_update_mode' => [
1060 // External identifier only enough in upgrade mode, so no error here.
1061 'csv' => 'individual_invalid_external_identifier_only.csv',
1062 'mapper' => [['external_identifier'], ['gender_id']],
1063 'expected_error' => '',
1064 'submitted_values' => ['onDuplicate' => CRM_Import_Parser::DUPLICATE_UPDATE],
1065 ],
1066 'organization_email_no_organization_name' => [
1067 // Email is only enough in upgrade mode.
1068 'csv' => 'organization_email_no_organization_name.csv',
1069 'mapper' => [['email'], ['phone', 1, 1]],
1070 'expected_error' => 'Missing required fields: Organization Name',
1071 'submitted_values' => ['onDuplicate' => CRM_Import_Parser::DUPLICATE_SKIP, 'contactType' => CRM_Import_Parser::CONTACT_ORGANIZATION],
1072 ],
1073 'organization_email_no_organization_name_update_mode' => [
1074 // Email is enough in upgrade mode (at least to pass validate).
1075 'csv' => 'organization_email_no_organization_name.csv',
1076 'mapper' => [['email'], ['phone', 1, 1]],
1077 'expected_error' => '',
1078 'submitted_values' => ['onDuplicate' => CRM_Import_Parser::DUPLICATE_UPDATE, 'contactType' => CRM_Import_Parser::CONTACT_ORGANIZATION],
1079 ],
1080 ];
1081 }
1082
1083 /**
1084 * Test the import.
1085 *
1086 * @dataProvider importDataProvider
1087 *
1088 * @throws \API_Exception
1089 * @throws \CRM_Core_Exception
1090 * @throws \League\Csv\CannotInsertRecord
1091 */
1092 public function testImport($csv, $mapper, $expectedOutcomes = [], $submittedValues = []): void {
1093 $this->importCSV($csv, $mapper, $submittedValues);
1094 $dataSource = new CRM_Import_DataSource_CSV(UserJob::get(FALSE)->setSelect(['id'])->execute()->first()['id']);
1095 foreach ($expectedOutcomes as $outcome => $count) {
1096 $this->assertEquals($dataSource->getRowCount([$outcome]), $count);
1097 }
1098 ob_start();
1099 $_REQUEST['user_job_id'] = $dataSource->getUserJobID();
1100 $_REQUEST['status'] = array_key_first($expectedOutcomes);
1101 try {
1102 CRM_Import_Forms::outputCSV();
1103 }
1104 catch (CRM_Core_Exception_PrematureExitException $e) {
1105 // For now just check it got this far without error.
1106 ob_end_clean();
1107 return;
1108 }
1109 ob_end_clean();
1110 $this->fail('Should have resulted in a premature exit exception');
1111 }
1112
1113 /**
1114 * @throws \API_Exception
1115 * @throws \CRM_Core_Exception
1116 */
1117 public function testImportContactToGroup(): void {
1118 $this->individualCreate();
1119 $this->importCSV('contact_id_only.csv', [['id']], [
1120 'newGroupName' => 'My New Group',
1121 ]);
1122 $dataSource = new CRM_Import_DataSource_CSV(UserJob::get(FALSE)->setSelect(['id'])->execute()->first()['id']);
1123 $row = $dataSource->getRow();
1124 $this->assertEquals('IMPORTED', $row['_status']);
1125 $group = Group::get()->addWhere('title', '=', 'My New Group')->execute()->first();
1126 $this->assertCount(1, GroupContact::get()->addWhere('group_id', '=', $group['id'])->execute());
1127 }
1128
1129 /**
1130 * Get combinations to test for validation.
1131 *
1132 * @return array[]
1133 */
1134 public function importDataProvider(): array {
1135 return [
1136 'column_names_casing.csv' => [
1137 'csv' => 'column_names_casing.csv',
1138 'mapper' => [['first_name'], ['last_name'], ['do_not_import'], ['do_not_import'], ['do_not_import'], ['do_not_import']],
1139 'expected_outcomes' => [CRM_Import_Parser::VALID => 1],
1140 ],
1141 'individual_unicode.csv' => [
1142 'csv' => 'individual_unicode.csv',
1143 'mapper' => [['first_name'], ['last_name'], ['url', 1], ['country', 1]],
1144 'expected_outcomes' => [CRM_Import_Parser::VALID => 1],
1145 ],
1146 'individual_invalid_sub_type' => [
1147 'csv' => 'individual_invalid_contact_sub_type.csv',
1148 'mapper' => [['first_name'], ['last_name'], ['contact_sub_type']],
1149 'expected_outcomes' => [CRM_Import_Parser::ERROR => 1],
1150 ],
1151 //Record duplicates multiple contacts
1152 'organization_multiple_duplicates_invalid' => [
1153 'csv' => 'organization_multiple_duplicates_invalid.csv',
1154 'mapper' => [['organization_name'], ['email']],
1155 'expected_outcomes' => [
1156 CRM_Import_Parser::VALID => 2,
1157 CRM_Import_Parser::ERROR => 1,
1158 ],
1159 'submitted_values' => [
1160 'contactType' => CRM_Import_Parser::CONTACT_ORGANIZATION,
1161 ],
1162 ],
1163 //Matching this contact based on the de-dupe rule would cause an external ID conflict
1164 'individual_invalid_external_identifier_email_mismatch' => [
1165 'csv' => 'individual_invalid_external_identifier_email_mismatch.csv',
1166 'mapper' => [['first_name'], ['last_name'], ['email'], ['external_identifier']],
1167 'expected_outcomes' => [
1168 CRM_Import_Parser::VALID => 2,
1169 CRM_Import_Parser::ERROR => 1,
1170 ],
1171 ],
1172 ];
1173 }
1174
1175 /**
1176 * Test the handling of validation when importing genders.
1177 *
1178 * If it's not gonna import it should fail at the validation stage...
1179 *
1180 * @throws \API_Exception
1181 * @throws \CRM_Core_Exception
1182 */
1183 public function testImportGenders(): void {
1184 $mapper = [
1185 ['first_name'],
1186 ['last_name'],
1187 ['gender_id'],
1188 ['1_a_b', 'first_name'],
1189 ['1_a_b', 'last_name'],
1190 ['1_a_b', 'gender_id'],
1191 ['do_not_import'],
1192 ];
1193 $csv = 'individual_genders.csv';
1194 $this->validateMultiRowCsv($csv, $mapper, 'gender');
1195
1196 $this->importCSV($csv, $mapper);
1197 $contacts = Contact::get()
1198 ->addWhere('first_name', '=', 'Madame')
1199 ->addSelect('gender_id:name')->execute();
1200 foreach ($contacts as $contact) {
1201 $this->assertEquals('Female', $contact['gender_id:name']);
1202 }
1203 $this->assertCount(8, $contacts);
1204 }
1205
1206 /**
1207 * Test importing state country & county.
1208 *
1209 * @throws \API_Exception
1210 * @throws \CRM_Core_Exception
1211 */
1212 public function testImportCountryStateCounty(): void {
1213 $childKey = $this->getRelationships()['Child of']['id'] . '_a_b';
1214 $addressCustomGroupID = $this->createCustomGroup(['extends' => 'Address', 'name' => 'Address']);
1215 $contactCustomGroupID = $this->createCustomGroup(['extends' => 'Contact', 'name' => 'Contact']);
1216 $addressCustomFieldID = $this->createCountryCustomField(['custom_group_id' => $addressCustomGroupID])['id'];
1217 $contactCustomFieldID = $this->createMultiCountryCustomField(['custom_group_id' => $contactCustomGroupID])['id'];
1218 $contactStateCustomFieldID = $this->createStateCustomField(['custom_group_id' => $contactCustomGroupID])['id'];
1219 $customField = 'custom_' . $contactCustomFieldID;
1220 $addressCustomField = 'custom_' . $addressCustomFieldID;
1221 $contactStateCustomField = 'custom_' . $contactStateCustomFieldID;
1222
1223 $mapper = [
1224 ['first_name'],
1225 ['last_name'],
1226 ['email'],
1227 ['county'],
1228 ['country'],
1229 ['state_province'],
1230 [$contactStateCustomField],
1231 [$customField],
1232 [$addressCustomField],
1233 // [$addressCustomField, 'state_province'],
1234 ['do_not_import'],
1235 [$childKey, 'first_name'],
1236 [$childKey, 'last_name'],
1237 [$childKey, 'email'],
1238 [$childKey, 'state_province'],
1239 [$childKey, 'country'],
1240 [$childKey, 'county'],
1241 // [$childKey, $addressCustomField, 'country'],
1242 ['do_not_import'],
1243 // [$childKey, $addressCustomField, 'state_province'],
1244 ['do_not_import'],
1245 // [$childKey, $customField, 'country'],
1246 ['do_not_import'],
1247 // [$childKey, $customField, 'state_province'],
1248 ['do_not_import'],
1249 // mapField Form expects all fields to be mapped.
1250 ['do_not_import'],
1251 ['do_not_import'],
1252 ];
1253 $csv = 'individual_country_state_county_with_related.csv';
1254 $this->validateMultiRowCsv($csv, $mapper, 'error_value');
1255
1256 $this->importCSV($csv, $mapper);
1257 $contacts = $this->getImportedContacts();
1258 foreach ($contacts as $contact) {
1259 $this->assertEquals(1013, $contact['address'][0]['country_id']);
1260 $this->assertEquals(1640, $contact['address'][0]['state_province_id']);
1261 }
1262 $this->assertCount(2, $contacts);
1263 $dataSource = $this->getDataSource();
1264 $dataSource->setOffset(4);
1265 $dataSource->setLimit(1);
1266 $row = $dataSource->getRow();
1267 $this->assertEquals(1, $row['_related_contact_matched']);
1268 }
1269
1270 /**
1271 * Test date validation.
1272 *
1273 * @dataProvider dateDataProvider
1274 *
1275 * @param string $csv
1276 * @param int $dateType
1277 *
1278 * @throws \API_Exception
1279 * @throws \CRM_Core_Exception
1280 */
1281 public function testValidateDateData($csv, $dateType): void {
1282 $addressCustomGroupID = $this->createCustomGroup(['extends' => 'Address', 'name' => 'Address']);
1283 $contactCustomGroupID = $this->createCustomGroup(['extends' => 'Contact', 'name' => 'Contact']);
1284 $addressCustomFieldID = $this->createDateCustomField(['custom_group_id' => $addressCustomGroupID])['id'];
1285 $contactCustomFieldID = $this->createDateCustomField(['custom_group_id' => $contactCustomGroupID])['id'];
1286 $mapper = [
1287 ['first_name'],
1288 ['last_name'],
1289 ['birth_date'],
1290 ['deceased_date'],
1291 ['custom_' . $contactCustomFieldID],
1292 ['custom_' . $addressCustomFieldID, 1],
1293 ['street_address', 1],
1294 ['do_not_import'],
1295 ];
1296 // Date types should be picked up from submitted values but still some clean up to do.
1297 CRM_Core_Session::singleton()->set('dateTypes', $dateType);
1298 $this->validateMultiRowCsv($csv, $mapper, 'custom_date_one', ['dateFormats' => $dateType]);
1299 $fields = [
1300 'contact_id.birth_date',
1301 'contact_id.deceased_date',
1302 'contact_id.is_deceased',
1303 'contact_id.custom_' . $contactCustomFieldID,
1304 $addressCustomFieldID,
1305 ];
1306 $contacts = Address::get()->addWhere('contact_id.first_name', '=', 'Joe')->setSelect($fields)->execute();
1307 foreach ($contacts as $contact) {
1308 foreach ($fields as $field) {
1309 if ($field === 'contact_is_deceased') {
1310 $this->assertTrue($contact[$field]);
1311 }
1312 else {
1313 $this->assertEquals('2008-09-01', $contact[$field]);
1314 }
1315 }
1316 }
1317 }
1318
1319 /**
1320 * @throws \API_Exception
1321 */
1322 public function testImportContactSubTypes(): void {
1323 ContactType::create()->setValues([
1324 'name' => 'baby',
1325 'label' => 'Infant',
1326 'parent_id:name' => 'Individual',
1327 ])->execute();
1328 $mapper = [
1329 ['first_name'],
1330 ['last_name'],
1331 ['5_a_b', 'organization_name'],
1332 ['contact_sub_type'],
1333 ['5_a_b', 'contact_sub_type'],
1334 // mapField Form expects all fields to be mapped.
1335 ['do_not_import'],
1336 ['do_not_import'],
1337 ['do_not_import'],
1338 ];
1339 $csv = 'individual_contact_sub_types.csv';
1340 $field = 'contact_sub_type';
1341
1342 $this->validateMultiRowCsv($csv, $mapper, $field);
1343 $this->importCSV($csv, $mapper);
1344 $contacts = Contact::get()
1345 ->addWhere('last_name', '=', 'Green')
1346 ->addSelect('contact_sub_type:name')->execute();
1347 foreach ($contacts as $contact) {
1348 $this->assertEquals(['baby'], $contact['contact_sub_type:name']);
1349 }
1350 $this->assertCount(3, $contacts);
1351 }
1352
1353 /**
1354 * Data provider for date tests.
1355 *
1356 * @return array[]
1357 */
1358 public function dateDataProvider(): array {
1359 return [
1360 'type_1' => ['csv' => 'individual_dates_type1.csv', 'dateType' => CRM_Core_Form_Date::DATE_yyyy_mm_dd],
1361 'type_2' => ['csv' => 'individual_dates_type2.csv', 'dateType' => CRM_Core_Form_Date::DATE_mm_dd_yy],
1362 'type_4' => ['csv' => 'individual_dates_type4.csv', 'dateType' => CRM_Core_Form_Date::DATE_mm_dd_yyyy],
1363 'type_8' => ['csv' => 'individual_dates_type8.csv', 'dateType' => CRM_Core_Form_Date::DATE_Month_dd_yyyy],
1364 'type_16' => ['csv' => 'individual_dates_type16.csv', 'dateType' => CRM_Core_Form_Date::DATE_dd_mon_yy],
1365 'type_32' => ['csv' => 'individual_dates_type32.csv', 'dateType' => CRM_Core_Form_Date::DATE_dd_mm_yyyy],
1366 ];
1367 }
1368
1369 /**
1370 * Test location importing, including for related contacts.
1371 *
1372 * @throws \API_Exception
1373 */
1374 public function testImportLocations(): void {
1375 $csv = 'individual_locations_with_related.csv';
1376 $relationships = $this->getRelationships();
1377
1378 $childKey = $relationships['Child of']['id'] . '_a_b';
1379 $siblingKey = $relationships['Sibling of']['id'] . '_a_b';
1380 $employeeKey = $relationships['Employee of']['id'] . '_a_b';
1381 $locations = LocationType::get()->execute()->indexBy('name');
1382 $phoneTypeID = CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Phone', 'phone_type_id', 'Phone');
1383 $mobileTypeID = CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Phone', 'phone_type_id', 'Mobile');
1384 $skypeTypeID = CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_IM', 'provider_id', 'Skype');
1385 $mainWebsiteTypeID = CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Website', 'website_type_id', 'Main');
1386 $linkedInTypeID = CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Website', 'website_type_id', 'LinkedIn');
1387 $homeID = $locations['Home']['id'];
1388 $workID = $locations['Work']['id'];
1389 $mapper = [
1390 ['first_name'],
1391 ['last_name'],
1392 ['birth_date'],
1393 ['street_address', $homeID],
1394 ['city', $homeID],
1395 ['postal_code', $homeID],
1396 ['country', $homeID],
1397 ['state_province', $homeID],
1398 // No location type ID means 'Primary'
1399 ['email'],
1400 ['signature_text'],
1401 ['im', NULL, $skypeTypeID],
1402 ['url', $mainWebsiteTypeID],
1403 ['phone', $homeID, $phoneTypeID],
1404 ['phone_ext', $homeID, $phoneTypeID],
1405 [$childKey, 'first_name'],
1406 [$childKey, 'last_name'],
1407 [$childKey, 'street_address'],
1408 [$childKey, 'city'],
1409 [$childKey, 'country'],
1410 [$childKey, 'state_province'],
1411 [$childKey, 'email', $homeID],
1412 [$childKey, 'signature_text', $homeID],
1413 [$childKey, 'im', $homeID, $skypeTypeID],
1414 [$childKey, 'url', $linkedInTypeID],
1415 // Same location type, different phone typ in these phones
1416 [$childKey, 'phone', $homeID, $phoneTypeID],
1417 [$childKey, 'phone_ext', $homeID, $phoneTypeID],
1418 [$childKey, 'phone', $homeID, $mobileTypeID],
1419 [$childKey, 'phone_ext', $homeID, $mobileTypeID],
1420 [$siblingKey, 'street_address', $homeID],
1421 [$siblingKey, 'city', $homeID],
1422 [$siblingKey, 'country', $homeID],
1423 [$siblingKey, 'state_province', $homeID],
1424 [$siblingKey, 'email', $homeID],
1425 [$siblingKey, 'signature_text', $homeID],
1426 [$siblingKey, 'im', $homeID, $skypeTypeID],
1427 // The 2 is website_type_id (yes, small hard-coding cheat)
1428 [$siblingKey, 'url', $linkedInTypeID],
1429 [$siblingKey, 'phone', $workID, $phoneTypeID],
1430 [$siblingKey, 'phone_ext', $workID, $phoneTypeID],
1431 [$employeeKey, 'organization_name'],
1432 [$employeeKey, 'url', $mainWebsiteTypeID],
1433 [$employeeKey, 'email', $homeID],
1434 [$employeeKey, 'do_not_import'],
1435 [$employeeKey, 'street_address', $homeID],
1436 [$employeeKey, 'supplemental_address_1', $homeID],
1437 [$employeeKey, 'do_not_import'],
1438 // Second website, different type.
1439 [$employeeKey, 'url', $linkedInTypeID],
1440 ['openid'],
1441 ];
1442 $this->validateCSV($csv, $mapper);
1443
1444 $this->importCSV($csv, $mapper);
1445 $contacts = $this->getImportedContacts();
1446 $this->assertCount(4, $contacts);
1447 $this->assertCount(1, $contacts['Susie Jones']['phone']);
1448 $this->assertEquals('123', $contacts['Susie Jones']['phone'][0]['phone_ext']);
1449 $this->assertCount(2, $contacts['Mum Jones']['phone']);
1450 $this->assertCount(1, $contacts['sis@example.com']['phone']);
1451 $this->assertCount(0, $contacts['Soccer Superstars']['phone']);
1452 $this->assertCount(1, $contacts['Susie Jones']['website']);
1453 $this->assertCount(1, $contacts['Mum Jones']['website']);
1454 $this->assertCount(0, $contacts['sis@example.com']['website']);
1455 $this->assertCount(2, $contacts['Soccer Superstars']['website']);
1456 $this->assertCount(1, $contacts['Susie Jones']['email']);
1457 $this->assertEquals('Regards', $contacts['Susie Jones']['email'][0]['signature_text']);
1458 $this->assertCount(1, $contacts['Mum Jones']['email']);
1459 $this->assertCount(1, $contacts['sis@example.com']['email']);
1460 $this->assertCount(1, $contacts['Soccer Superstars']['email']);
1461 $this->assertCount(1, $contacts['Susie Jones']['im']);
1462 $this->assertCount(1, $contacts['Mum Jones']['im']);
1463 $this->assertCount(0, $contacts['sis@example.com']['im']);
1464 $this->assertCount(0, $contacts['Soccer Superstars']['im']);
1465 $this->assertCount(1, $contacts['Susie Jones']['address']);
1466 $this->assertCount(1, $contacts['Mum Jones']['address']);
1467 $this->assertCount(1, $contacts['sis@example.com']['address']);
1468 $this->assertCount(1, $contacts['Soccer Superstars']['address']);
1469 $this->assertCount(1, $contacts['Susie Jones']['openid']);
1470 }
1471
1472 /**
1473 * Test that setting duplicate action to fill doesn't blow away data
1474 * that exists, but does fill in where it's empty.
1475 *
1476 * @throw \Exception
1477 */
1478 public function testImportFill(): void {
1479 // Create a custom field group for testing.
1480 $this->createCustomGroup([
1481 'title' => 'importFillGroup',
1482 'extends' => 'Individual',
1483 'is_active' => TRUE,
1484 ]);
1485 $customGroupID = $this->ids['CustomGroup']['importFillGroup'];
1486
1487 // Add two custom fields.
1488 $api_params = [
1489 'custom_group_id' => $customGroupID,
1490 'label' => 'importFillField1',
1491 'html_type' => 'Select',
1492 'data_type' => 'String',
1493 'option_values' => [
1494 'foo' => 'Foo',
1495 'bar' => 'Bar',
1496 ],
1497 ];
1498 $result = $this->callAPISuccess('custom_field', 'create', $api_params);
1499 $customField1 = $result['id'];
1500
1501 $api_params = [
1502 'custom_group_id' => $customGroupID,
1503 'label' => 'importFillField2',
1504 'html_type' => 'Select',
1505 'data_type' => 'String',
1506 'option_values' => [
1507 'baz' => 'Baz',
1508 'boo' => 'Boo',
1509 ],
1510 ];
1511 $result = $this->callAPISuccess('custom_field', 'create', $api_params);
1512 $customField2 = $result['id'];
1513
1514 // Now set up values.
1515 $original_gender = 'Male';
1516 $original_custom1 = 'foo';
1517 $original_email = 'test-import-fill@example.org';
1518
1519 $import_gender = 'Female';
1520 $import_custom1 = 'bar';
1521 $import_job_title = 'Chief data importer';
1522 $import_custom2 = 'baz';
1523
1524 // Create contact with both one known core field and one custom
1525 // field filled in.
1526 $api_params = [
1527 'contact_type' => 'Individual',
1528 'email' => $original_email,
1529 'gender' => $original_gender,
1530 'custom_' . $customField1 => $original_custom1,
1531 ];
1532 $result = $this->callAPISuccess('contact', 'create', $api_params);
1533 $contact_id = $result['id'];
1534
1535 // Run an import.
1536 $import = [
1537 'email' => $original_email,
1538 'gender_id' => $import_gender,
1539 'custom_' . $customField1 => $import_custom1,
1540 'job_title' => $import_job_title,
1541 'custom_' . $customField2 => $import_custom2,
1542 ];
1543
1544 $this->runImport($import, CRM_Import_Parser::DUPLICATE_FILL, CRM_Import_Parser::VALID);
1545
1546 $expected = [
1547 'gender' => $original_gender,
1548 'custom_' . $customField1 => $original_custom1,
1549 'job_title' => $import_job_title,
1550 'custom_' . $customField2 => $import_custom2,
1551 ];
1552
1553 $params = [
1554 'id' => $contact_id,
1555 'return' => [
1556 'gender',
1557 'custom_' . $customField1,
1558 'job_title',
1559 'custom_' . $customField2,
1560 ],
1561 ];
1562 $result = civicrm_api3('Contact', 'get', $params);
1563 $values = array_pop($result['values']);
1564 foreach ($expected as $field => $expected_value) {
1565 if (!isset($values[$field])) {
1566 $given_value = NULL;
1567 }
1568 else {
1569 $given_value = $values[$field];
1570 }
1571 // We expect:
1572 // gender: Male
1573 // job_title: Chief Data Importer
1574 // importFillField1: foo
1575 // importFillField2: baz
1576 $this->assertEquals($expected_value, $given_value, "$field properly handled during Fill import");
1577 }
1578 }
1579
1580 /**
1581 * CRM-19888 default country should be used if ambiguous.
1582 *
1583 * @throws \API_Exception
1584 * @throws \CRM_Core_Exception
1585 * @throws \CiviCRM_API3_Exception
1586 */
1587 public function testImportAmbiguousStateCountry(): void {
1588 $this->callAPISuccess('Setting', 'create', ['defaultContactCountry' => 1228]);
1589 $countries = CRM_Core_PseudoConstant::country(FALSE, FALSE);
1590 $this->callAPISuccess('Setting', 'create', ['countryLimit' => [array_search('United States', $countries, TRUE), array_search('Guyana', $countries, TRUE), array_search('Netherlands', $countries, TRUE)]]);
1591 $this->callAPISuccess('Setting', 'create', ['provinceLimit' => [array_search('United States', $countries, TRUE), array_search('Guyana', $countries, TRUE), array_search('Netherlands', $countries, TRUE)]]);
1592 [$contactValues] = $this->setUpBaseContact();
1593
1594 // Set up the field mapping - this looks like an array per mapping as saved in
1595 // civicrm_mapping_field - eg ['name' => 'street_address', 'location_type_id' => 1],
1596 $fieldMapping = [];
1597 foreach (array_keys($contactValues) as $fieldName) {
1598 $fieldMapping[] = ['name' => $fieldName];
1599 }
1600
1601 $addressValues = [
1602 'street_address' => 'PO Box 2716',
1603 'city' => 'Midway',
1604 'state_province' => 'UT',
1605 'postal_code' => 84049,
1606 'country' => 'United States',
1607 ];
1608
1609 $homeLocationTypeID = CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Address', 'location_type_id', 'Home');
1610 $workLocationTypeID = CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Address', 'location_type_id', 'Work');
1611 foreach ($addressValues as $field => $value) {
1612 $contactValues['home_' . $field] = $value;
1613 $contactValues['work_' . $field] = $value;
1614 $fieldMapping[] = ['name' => $field, 'location_type_id' => $homeLocationTypeID];
1615 $fieldMapping[] = ['name' => $field, 'location_type_id' => $workLocationTypeID];
1616 }
1617 // The value is set to nothing to show it will be calculated.
1618 $contactValues['work_country'] = '';
1619
1620 $this->runImport($contactValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID, $fieldMapping);
1621 $addresses = $this->callAPISuccess('Address', 'get', ['contact_id' => ['>' => 2], 'sequential' => 1]);
1622 $this->assertEquals(2, $addresses['count']);
1623 $this->assertEquals(array_search('United States', $countries, TRUE), $addresses['values'][0]['country_id']);
1624 $this->assertEquals(array_search('United States', $countries, TRUE), $addresses['values'][1]['country_id']);
1625 }
1626
1627 /**
1628 * Test importing fields with various options.
1629 *
1630 * Ensure we can import multiple preferred_communication_methods, single
1631 * gender, and single preferred language using both labels and values.
1632 *
1633 * @throws \API_Exception
1634 * @throws \CRM_Core_Exception
1635 * @throws \CiviCRM_API3_Exception
1636 */
1637 public function testImportFieldsWithVariousOptions(): void {
1638 $processor = new CRM_Import_ImportProcessor();
1639 $processor->setUserJobID($this->getUserJobID([
1640 'mapper' => [['first_name'], ['last_name'], ['preferred_communication_method'], ['gender_id'], ['preferred_language']],
1641 'onDuplicate' => CRM_Import_Parser::DUPLICATE_NOCHECK,
1642 ]));
1643 $processor->setMappingFields(
1644 [
1645 ['name' => 'first_name'],
1646 ['name' => 'last_name'],
1647 ['name' => 'preferred_communication_method'],
1648 ['name' => 'gender_id'],
1649 ['name' => 'preferred_language'],
1650 ]
1651 );
1652 $importer = $processor->getImporterObject();
1653 $fields = ['Ima', 'Texter', 'SMS,Phone', 'Female', 'Danish'];
1654 $importer->import($fields);
1655 $contact = $this->callAPISuccessGetSingle('Contact', ['last_name' => 'Texter']);
1656
1657 $this->assertEquals([4, 1], $contact['preferred_communication_method'], 'Import multiple preferred communication methods using labels.');
1658 $this->assertEquals(1, $contact['gender_id'], 'Import gender with label.');
1659 $this->assertEquals('da_DK', $contact['preferred_language'], 'Import preferred language with label.');
1660 $this->callAPISuccess('Contact', 'delete', ['id' => $contact['id']]);
1661
1662 $importer = $processor->getImporterObject();
1663 $fields = ['Ima', 'Texter', '4,1', '1', 'da_DK'];
1664 $importer->import($fields);
1665 $contact = $this->callAPISuccessGetSingle('Contact', ['last_name' => 'Texter']);
1666
1667 $this->assertEquals([4, 1], $contact['preferred_communication_method'], 'Import multiple preferred communication methods using values.');
1668 $this->assertEquals(1, $contact['gender_id'], 'Import gender with id.');
1669 $this->assertEquals('da_DK', $contact['preferred_language'], 'Import preferred language with value.');
1670 }
1671
1672 /**
1673 * Run the import parser.
1674 *
1675 * @param array $originalValues
1676 *
1677 * @param int $onDuplicateAction
1678 * @param int $expectedResult
1679 * @param array|null $fieldMapping
1680 * Array of field mappings in the format used in civicrm_mapping_field.
1681 * @param array|null $fields
1682 * Array of field names. Will be calculated from $originalValues if not passed in, but
1683 * that method does not cope with duplicates.
1684 * @param int|null $ruleGroupId
1685 * To test against a specific dedupe rule group, pass its ID as this argument.
1686 *
1687 * @throws \API_Exception
1688 * @throws \CRM_Core_Exception
1689 * @throws \CiviCRM_API3_Exception
1690 */
1691 protected function runImport(array $originalValues, $onDuplicateAction, $expectedResult, $fieldMapping = [], $fields = NULL, int $ruleGroupId = NULL): void {
1692 $values = array_values($originalValues);
1693 // Stand in for row number.
1694 $values[] = 1;
1695
1696 if ($fieldMapping) {
1697 $fields = [];
1698 foreach ($fieldMapping as $mappedField) {
1699 $fields[] = $mappedField['name'];
1700 }
1701 $mapper = $this->getMapperFromFieldMappingFormat($fieldMapping);
1702 }
1703 else {
1704 if (!$fields) {
1705 $fields = array_keys($originalValues);
1706 }
1707 $mapper = [];
1708 foreach ($fields as $field) {
1709 $mapper[] = [
1710 $field,
1711 in_array($field, ['phone', 'email'], TRUE) ? 'Primary' : NULL,
1712 $field === 'phone' ? 1 : NULL,
1713 ];
1714 }
1715 }
1716 $this->userJobID = $this->getUserJobID(['mapper' => $mapper, 'onDuplicate' => $onDuplicateAction, 'dedupe_rule_id' => $ruleGroupId]);
1717 $parser = new CRM_Contact_Import_Parser_Contact();
1718 $parser->setUserJobID($this->userJobID);
1719 $parser->_dedupeRuleGroupID = $ruleGroupId;
1720 $parser->init();
1721
1722 $result = $parser->import($values);
1723 $dataSource = $this->getDataSource();
1724 if ($result === FALSE && $expectedResult !== FALSE) {
1725 // Import is moving away from returning a status - this is a better way to check
1726 $this->assertGreaterThan(0, $dataSource->getRowCount([$expectedResult]));
1727 return;
1728 }
1729 $this->assertEquals($expectedResult, $result, 'Return code from parser import was not as expected');
1730 }
1731
1732 /**
1733 * @param string $csv
1734 * @param array $mapper Mapping as entered on MapField form.
1735 * e.g [['first_name']['email', 1]].
1736 * {@see \CRM_Contact_Import_Parser_Contact::getMappingFieldFromMapperInput}
1737 * @param array $submittedValues
1738 *
1739 * @return array
1740 * @throws \API_Exception
1741 * @throws \Civi\API\Exception\UnauthorizedException
1742 */
1743 protected function getDataSourceAndParser(string $csv, array $mapper, array $submittedValues): array {
1744 $userJobID = $this->getUserJobID(array_merge([
1745 'uploadFile' => ['name' => __DIR__ . '/../Form/data/' . $csv],
1746 'skipColumnHeader' => TRUE,
1747 'fieldSeparator' => ',',
1748 'onDuplicate' => CRM_Import_Parser::DUPLICATE_SKIP,
1749 'contactType' => CRM_Import_Parser::CONTACT_INDIVIDUAL,
1750 'mapper' => $mapper,
1751 'dataSource' => 'CRM_Import_DataSource_CSV',
1752 ], $submittedValues));
1753
1754 $dataSource = new CRM_Import_DataSource_CSV($userJobID);
1755 $parser = new CRM_Contact_Import_Parser_Contact();
1756 $parser->setUserJobID($userJobID);
1757 $parser->init();
1758 return [$dataSource, $parser];
1759 }
1760
1761 /**
1762 * @param int $contactID
1763 *
1764 * @throws \API_Exception
1765 */
1766 protected function addChild(int $contactID): void {
1767 $relatedContactID = $this->individualCreate();
1768 $relationshipTypeID = RelationshipType::create()->setValues([
1769 'name_a_b' => 'Dad to',
1770 'name_b_a' => 'Sleep destroyer of',
1771 'contact_type_a' => 'Individual',
1772 'contact_type_b' => 'Individual',
1773 'contact_sub_type_a' => 'Parent',
1774 ])->execute()->first()['id'];
1775 Relationship::create()->setValues([
1776 'relationship_type_id' => $relationshipTypeID,
1777 'contact_id_a' => $contactID,
1778 'contact_id_b' => $relatedContactID,
1779 ])->execute();
1780 $this->relationships['Dad to'] = $relationshipTypeID . '_a_b';
1781 }
1782
1783 /**
1784 * @return array
1785 * @throws \API_Exception
1786 * @throws \Civi\API\Exception\UnauthorizedException
1787 */
1788 private function getRelationships(): array {
1789 if (empty($this->relationships)) {
1790 $this->relationships = (array) RelationshipType::get()
1791 ->addSelect('name_a_b', 'id')
1792 ->execute()
1793 ->indexBy('name_a_b');
1794 }
1795 return $this->relationships;
1796 }
1797
1798 /**
1799 * Get the mapper array from the field mapping array format.
1800 *
1801 * The fieldMapping format is the same as the civicrm_mapping_field
1802 * table and is readable - eg ['name' => 'street_address', 'location_type_id' => 1].
1803 *
1804 * The mapper format is converted to the array that would be submitted by the form
1805 * and is keyed by row number with the meaning of the fields depending on
1806 * the selection.
1807 *
1808 * @param array $fieldMapping
1809 *
1810 * @return array
1811 */
1812 protected function getMapperFromFieldMappingFormat($fieldMapping): array {
1813 $mapper = [];
1814 foreach ($fieldMapping as $mapping) {
1815 $mappedRow = [];
1816 if (!empty($mapping['relationship_type_id'])) {
1817 $mappedRow[] = $mapping['relationship_type_id'] . $mapping['relationship_direction'];
1818 }
1819 $mappedRow[] = $mapping['name'];
1820 if (!empty($mapping['location_type_id'])) {
1821 $mappedRow[] = $mapping['location_type_id'];
1822 }
1823 elseif (in_array($mapping['name'], ['email', 'phone'], TRUE)) {
1824 // Lets make it easy on test writers by assuming primary if not specified.
1825 $mappedRow[] = 'Primary';
1826 }
1827 if (!empty($mapping['im_provider_id'])) {
1828 $mappedRow[] = $mapping['im_provider_id'];
1829 }
1830 if (!empty($mapping['phone_type_id'])) {
1831 $mappedRow[] = $mapping['phone_type_id'];
1832 }
1833 if (!empty($mapping['website_type_id'])) {
1834 $mappedRow[] = $mapping['website_type_id'];
1835 }
1836 $mapper[] = $mappedRow;
1837 }
1838 return $mapper;
1839 }
1840
1841 /**
1842 * Get a suitable mapper for the array with location defaults.
1843 *
1844 * This function is designed for when 'good assumptions' are required rather
1845 * than careful mapping.
1846 *
1847 * @param array $contactValues
1848 * @param string|int $defaultLocationType
1849 *
1850 * @return array
1851 */
1852 protected function getFieldMappingFromInput(array $contactValues, $defaultLocationType = 'Primary'): array {
1853 $mapper = [];
1854 foreach (array_keys($contactValues) as $fieldName) {
1855 $mapping = ['name' => $fieldName];
1856 $addressFields = $this->callAPISuccess('Address', 'getfields', [])['values'];
1857 unset($addressFields['contact_id'], $addressFields['id'], $addressFields['location_type_id']);
1858 $locationFields = array_merge(['email', 'phone', 'im', 'openid'], array_keys($addressFields));
1859 if (in_array($fieldName, $locationFields, TRUE)) {
1860 $mapping['location_type_id'] = $defaultLocationType;
1861 }
1862 if ($fieldName === 'phone') {
1863 $mapping['phone_type_id'] = 1;
1864 }
1865 $mapper[] = $mapping;
1866 }
1867 return $mapper;
1868 }
1869
1870 /**
1871 * Test mapping fields within the Parser class.
1872 *
1873 * @throws \API_Exception
1874 * @throws \Civi\API\Exception\UnauthorizedException
1875 */
1876 public function testMapFields(): void {
1877 $parser = new CRM_Contact_Import_Parser_Contact(
1878 // Array of field names
1879 ['first_name', 'phone', NULL, 'im', NULL],
1880 // Array of location types, ie columns 2 & 4 have types.
1881 [NULL, 1, NULL, 1, NULL],
1882 // Array of phone types
1883 [NULL, 1, NULL, NULL, NULL],
1884 // Array of im provider types
1885 [NULL, NULL, NULL, 1, NULL],
1886 // Array of filled in relationship values.
1887 [NULL, NULL, '5_a_b', NULL, '5_a_b'],
1888 // Array of the contact type to map to - note this can be determined from ^^
1889 [NULL, NULL, 'Organization', NULL, 'Organization'],
1890 // Related contact field names
1891 [NULL, NULL, 'url', NULL, 'phone'],
1892 // Related contact location types
1893 [NULL, NULL, NULL, NULL, 1],
1894 // Related contact phone types
1895 [NULL, NULL, NULL, NULL, 1],
1896 // Related contact im provider types
1897 [NULL, NULL, NULL, NULL, NULL],
1898 // Website types
1899 [NULL, NULL, NULL, NULL, NULL],
1900 // Related contact website types
1901 [NULL, NULL, 1, NULL, NULL]
1902 );
1903 $parser->setUserJobID($this->getUserJobID([
1904 'mapper' => [
1905 ['first_name'],
1906 ['phone', 1, 1],
1907 ['5_a_b', 'url', 1],
1908 ['im', 1, 1],
1909 ['5_a_b', 'phone', 1, 1],
1910 ],
1911 ]));
1912 $parser->init();
1913 $params = $parser->getMappedRow(
1914 ['Bob', '123', 'https://example.org', 'my-handle', '456']
1915 );
1916 $this->assertEquals([
1917 'first_name' => 'Bob',
1918 'phone' => [
1919 '1_1' => [
1920 'phone' => '123',
1921 'location_type_id' => 1,
1922 'phone_type_id' => 1,
1923 ],
1924 ],
1925 'relationship' => [
1926 '5_a_b' => [
1927 'contact_type' => 'Organization',
1928 'contact_sub_type' => NULL,
1929 'website' => [
1930 'https://example.org' => [
1931 'url' => 'https://example.org',
1932 'website_type_id' => 1,
1933 ],
1934 ],
1935 'phone' => [
1936 '1_1' => [
1937 'phone' => '456',
1938 'location_type_id' => 1,
1939 'phone_type_id' => 1,
1940 ],
1941 ],
1942 ],
1943 ],
1944 'im' => [
1945 '1_1' => [
1946 'name' => 'my-handle',
1947 'location_type_id' => 1,
1948 'provider_id' => 1,
1949 ],
1950 ],
1951 'contact_type' => 'Individual',
1952 ], $params);
1953 }
1954
1955 /**
1956 * Test that import parser will not match the imported primary to
1957 * an existing contact via the related contacts fields.
1958 *
1959 * Currently fails because CRM_Dedupe_Finder::formatParams($input, $contactType);
1960 * called in getDuplicateContacts flattens the contact array adding the
1961 * related contacts values to the primary contact.
1962 *
1963 * https://github.com/civicrm/civicrm-core/blob/ca13ec46eae2042604e4e106c6cb3dc0439db3e2/CRM/Dedupe/Finder.php#L238
1964 *
1965 * @throws \API_Exception
1966 * @throws \CRM_Core_Exception
1967 * @throws \CiviCRM_API3_Exception
1968 * @throws \Civi\API\Exception\UnauthorizedException
1969 */
1970 public function testImportParserDoesNotMatchPrimaryToRelated(): void {
1971 $this->individualCreate([
1972 'first_name' => 'Bob',
1973 'last_name' => 'Dobbs',
1974 'email' => 'tim.cook@apple.com',
1975 ]);
1976
1977 $mapper = [
1978 ['first_name'],
1979 ['last_name'],
1980 ['1_a_b', 'email'],
1981 ];
1982 $values = ['Alok', 'Patel', 'tim.cook@apple.com', 1];
1983
1984 $userJobID = $this->getUserJobID([
1985 'mapper' => $mapper,
1986 'onDuplicate' => CRM_Import_Parser::DUPLICATE_UPDATE,
1987 ]);
1988
1989 $parser = new CRM_Contact_Import_Parser_Contact();
1990 $parser->setUserJobID($userJobID);
1991 $parser->init();
1992 $parser->import($values);
1993 $this->callAPISuccessGetSingle('Contact', [
1994 'first_name' => 'Bob',
1995 'last_name' => 'Dobbs',
1996 'email' => 'tim.cook@apple.com',
1997 ]);
1998 $contact = $this->callAPISuccessGetSingle('Contact', ['first_name' => 'Alok', 'last_name' => 'Patel']);
1999 $this->assertEmpty($contact['email']);
2000 }
2001
2002 /**
2003 * Set up the underlying contact.
2004 *
2005 * @param array $params
2006 * Optional extra parameters to set.
2007 *
2008 * @return array
2009 * @throws \CRM_Core_Exception
2010 */
2011 protected function setUpBaseContact($params = []) {
2012 $originalValues = array_merge([
2013 'first_name' => 'Bill',
2014 'last_name' => 'Gates',
2015 'email' => 'bill.gates@microsoft.com',
2016 'nick_name' => 'Billy-boy',
2017 ], $params);
2018 $this->runImport($originalValues, CRM_Import_Parser::DUPLICATE_UPDATE, CRM_Import_Parser::VALID);
2019 $result = $this->callAPISuccessGetSingle('Contact', $originalValues);
2020 return [$originalValues, $result];
2021 }
2022
2023 /**
2024 * @return mixed
2025 * @throws \API_Exception
2026 * @throws \Civi\API\Exception\UnauthorizedException
2027 */
2028 protected function getUserJobID($submittedValues = []) {
2029 $userJobID = UserJob::create()->setValues([
2030 'metadata' => [
2031 'submitted_values' => array_merge([
2032 'contactType' => CRM_Import_Parser::CONTACT_INDIVIDUAL,
2033 'contactSubType' => '',
2034 'doGeocodeAddress' => 0,
2035 'disableUSPS' => 0,
2036 'dataSource' => 'CRM_Import_DataSource_SQL',
2037 'sqlQuery' => 'SELECT first_name FROM civicrm_contact',
2038 'onDuplicate' => CRM_Import_Parser::DUPLICATE_SKIP,
2039 'dedupe_rule_id' => NULL,
2040 'dateFormats' => CRM_Core_Form_Date::DATE_yyyy_mm_dd,
2041 ], $submittedValues),
2042 ],
2043 'status_id:name' => 'draft',
2044 'job_type' => 'contact_import',
2045 ])->execute()->first()['id'];
2046 if ($submittedValues['dataSource'] ?? NULL === 'CRM_Import_DataSource') {
2047 $dataSource = new CRM_Import_DataSource_CSV($userJobID);
2048 }
2049 else {
2050 $dataSource = new CRM_Import_DataSource_SQL($userJobID);
2051 }
2052 $dataSource->initialize();
2053 return $userJobID;
2054 }
2055
2056 /**
2057 * Test geocode validation.
2058 *
2059 * @throws \API_Exception
2060 * @throws \CRM_Core_Exception
2061 */
2062 public function testImportGeocodes(): void {
2063 $mapper = [
2064 ['first_name'],
2065 ['last_name'],
2066 ['geo_code_1', 1],
2067 ['geo_code_2', 1],
2068 ];
2069 $csv = 'individual_geocode.csv';
2070 $this->validateMultiRowCsv($csv, $mapper, 'GeoCode2');
2071 }
2072
2073 /**
2074 * Validate the csv file values.
2075 *
2076 * @param string $csv Name of csv file.
2077 * @param array $mapper Mapping as entered on MapField form.
2078 * e.g [['first_name']['email', 1]].
2079 * {@see \CRM_Contact_Import_Parser_Contact::getMappingFieldFromMapperInput}
2080 * @param array $submittedValues
2081 * Any submitted values overrides.
2082 *
2083 * @throws \API_Exception
2084 */
2085 protected function validateCSV(string $csv, array $mapper, array $submittedValues = []): void {
2086 [$dataSource, $parser] = $this->getDataSourceAndParser($csv, $mapper, $submittedValues);
2087 $parser->validateValues(array_values($dataSource->getRow()));
2088 }
2089
2090 /**
2091 * Import the csv file values.
2092 *
2093 * This function uses a flow that mimics the UI flow.
2094 *
2095 * @param string $csv Name of csv file.
2096 * @param array $mapper Mapping as entered on MapField form.
2097 * e.g [['first_name']['email', 1]].
2098 * {@see \CRM_Contact_Import_Parser_Contact::getMappingFieldFromMapperInput}
2099 * @param array $submittedValues
2100 */
2101 protected function importCSV(string $csv, array $mapper, array $submittedValues = []): void {
2102 $submittedValues = array_merge([
2103 'uploadFile' => ['name' => __DIR__ . '/../Form/data/' . $csv],
2104 'skipColumnHeader' => TRUE,
2105 'fieldSeparator' => ',',
2106 'contactType' => CRM_Import_Parser::CONTACT_INDIVIDUAL,
2107 'mapper' => $mapper,
2108 'dataSource' => 'CRM_Import_DataSource_CSV',
2109 'file' => ['name' => $csv],
2110 'dateFormats' => CRM_Core_Form_Date::DATE_yyyy_mm_dd,
2111 'onDuplicate' => CRM_Import_Parser::DUPLICATE_UPDATE,
2112 'groups' => [],
2113 ], $submittedValues);
2114 $form = $this->getFormObject('CRM_Contact_Import_Form_DataSource', $submittedValues);
2115 $values = $_SESSION['_' . $form->controller->_name . '_container']['values'];
2116
2117 $form->buildForm();
2118 $form->postProcess();
2119 $this->userJobID = $form->getUserJobID();
2120
2121 // This gets reset in DataSource so re-do....
2122 $_SESSION['_' . $form->controller->_name . '_container']['values'] = $values;
2123
2124 /* @var CRM_Contact_Import_Form_MapField $form */
2125 $form = $this->getFormObject('CRM_Contact_Import_Form_MapField', $submittedValues);
2126
2127 $form->setUserJobID($this->userJobID);
2128 $form->buildForm();
2129 $form->postProcess();
2130 /* @var CRM_Contact_Import_Form_MapField $form */
2131 $form = $this->getFormObject('CRM_Contact_Import_Form_Preview', $submittedValues);
2132 $form->setUserJobID($this->userJobID);
2133 $form->buildForm();
2134
2135 try {
2136 $form->postProcess();
2137 }
2138 catch (CRM_Core_Exception_PrematureExitException $e) {
2139 $queue = Civi::queue('user_job_' . $this->userJobID);
2140 $runner = new CRM_Queue_Runner([
2141 'queue' => $queue,
2142 'errorMode' => CRM_Queue_Runner::ERROR_ABORT,
2143 ]);
2144 $runner->runAll();
2145 }
2146 }
2147
2148 /**
2149 * Validate a csv with multiple rows in it.
2150 *
2151 * @param string $csv
2152 * @param array $mapper Mapping as entered on MapField form.
2153 * e.g [['first_name']['email', 1]].
2154 * @param string $field
2155 * Name of the field whose data should be output in the error message.
2156 * @param array $submittedValues
2157 * Values submitted in the form process.
2158 *
2159 * @throws \API_Exception
2160 * @throws \CRM_Core_Exception
2161 * @throws \Civi\API\Exception\UnauthorizedException
2162 */
2163 private function validateMultiRowCsv(string $csv, array $mapper, string $field, $submittedValues = []): void {
2164 /* @var CRM_Import_DataSource_CSV $dataSource */
2165 /* @var \CRM_Contact_Import_Parser_Contact $parser */
2166 [$dataSource, $parser] = $this->getDataSourceAndParser($csv, $mapper, $submittedValues);
2167 while ($values = $dataSource->getRow()) {
2168 try {
2169 $parser->validateValues(array_values($values));
2170 if ($values['expected'] !== 'Valid') {
2171 $this->fail($values[$field] . ' should not have been valid');
2172 }
2173 }
2174 catch (CRM_Core_Exception $e) {
2175 if ($values['expected'] !== 'Invalid') {
2176 $this->fail($values[$field] . ' should have been valid');
2177 }
2178 }
2179 }
2180 UserJob::delete()->addWhere('id', '=', $parser->getUserJobID())->execute();
2181 }
2182
2183 /**
2184 * Get the contacts we imported (Susie Jones & family).
2185 *
2186 * @return array
2187 * @throws \API_Exception
2188 */
2189 public function getImportedContacts(): array {
2190 return (array) Contact::get()
2191 ->addWhere('display_name', 'IN', [
2192 'Susie Jones',
2193 'Mum Jones',
2194 'sis@example.com',
2195 'Soccer Superstars',
2196 ])
2197 ->addChain('phone', Phone::get()->addWhere('contact_id', '=', '$id'))
2198 ->addChain('address', Address::get()->addWhere('contact_id', '=', '$id'))
2199 ->addChain('website', Website::get()->addWhere('contact_id', '=', '$id'))
2200 ->addChain('im', IM::get()->addWhere('contact_id', '=', '$id'))
2201 ->addChain('email', Email::get()->addWhere('contact_id', '=', '$id'))
2202 ->addChain('openid', OpenID::get()->addWhere('contact_id', '=', '$id'))
2203 ->execute()->indexBy('display_name');
2204 }
2205
2206 /**
2207 * Test that import parser will not throw error if Related Contact is not found via passed in External ID.
2208 *
2209 * If the organization is present it will create it - otherwise fail without error.
2210 *
2211 * @dataProvider getBooleanDataProvider
2212 *
2213 * @throws \API_Exception
2214 * @throws \CRM_Core_Exception
2215 * @throws \CiviCRM_API3_Exception
2216 */
2217 public function testImportParserWithExternalIdForRelationship(bool $isOrganizationProvided): void {
2218 $contactImportValues = [
2219 'first_name' => 'Alok',
2220 'last_name' => 'Patel',
2221 'Employee of' => 'related external identifier',
2222 'organization_name' => $isOrganizationProvided ? 'Big shop' : '',
2223 ];
2224
2225 $mapper = [
2226 ['first_name'],
2227 ['last_name'],
2228 ['5_a_b', 'external_identifier'],
2229 ['5_a_b', 'organization_name'],
2230 ];
2231
2232 $values = array_values($contactImportValues);
2233 $userJobID = $this->getUserJobID([
2234 'mapper' => $mapper,
2235 ]);
2236
2237 $parser = new CRM_Contact_Import_Parser_Contact();
2238 $parser->setUserJobID($userJobID);
2239 $parser->init();
2240
2241 $parser->import($values);
2242 $this->callAPISuccessGetCount('Contact', ['organization_name' => 'Big shop'], $isOrganizationProvided ? 2 : 0);
2243 }
2244
2245 }