7b4356fba2c7fe145a97df05d07012fd8092eb3f
[civicrm-core.git] / CRM / Contact / Import / Parser / Contact.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work 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 require_once 'CRM/Utils/DeprecatedUtils.php';
12 require_once 'api/v3/utils.php';
13
14 /**
15 *
16 * @package CRM
17 * @copyright CiviCRM LLC https://civicrm.org/licensing
18 */
19
20 /**
21 * class to parse contact csv files
22 */
23 class CRM_Contact_Import_Parser_Contact extends CRM_Contact_Import_Parser {
24
25 use CRM_Contact_Import_MetadataTrait;
26
27 protected $_mapperKeys = [];
28 protected $_mapperLocType = [];
29 protected $_mapperPhoneType;
30 protected $_mapperImProvider;
31 protected $_mapperWebsiteType;
32 protected $_mapperRelated;
33 protected $_mapperRelatedContactType;
34 protected $_mapperRelatedContactDetails;
35 protected $_mapperRelatedContactEmailType;
36 protected $_mapperRelatedContactImProvider;
37 protected $_mapperRelatedContactWebsiteType;
38 protected $_relationships;
39
40 protected $_emailIndex;
41 protected $_firstNameIndex;
42 protected $_lastNameIndex;
43
44 protected $_householdNameIndex;
45 protected $_organizationNameIndex;
46
47 protected $_allEmails;
48
49 protected $_phoneIndex;
50
51 /**
52 * Is update only permitted on an id match.
53 *
54 * Note this historically was true for when id or external identifier was
55 * present. However, CRM-17275 determined that a dedupe-match could over-ride
56 * external identifier.
57 *
58 * @var bool
59 */
60 protected $_updateWithId;
61 protected $_retCode;
62
63 protected $_externalIdentifierIndex;
64 protected $_allExternalIdentifiers;
65 protected $_parseStreetAddress;
66
67 /**
68 * Array of successfully imported contact id's
69 *
70 * @var array
71 */
72 protected $_newContacts;
73
74 /**
75 * Line count id.
76 *
77 * @var int
78 */
79 protected $_lineCount;
80
81 /**
82 * Array of successfully imported related contact id's
83 *
84 * @var array
85 */
86 protected $_newRelatedContacts;
87
88 /**
89 * Array of all the contacts whose street addresses are not parsed.
90 * of this import process
91 * @var array
92 */
93 protected $_unparsedStreetAddressContacts;
94
95 /**
96 * Class constructor.
97 *
98 * @param array $mapperKeys
99 * @param array $mapperLocType
100 * @param array $mapperPhoneType
101 * @param array $mapperImProvider
102 * @param array $mapperRelated
103 * @param array $mapperRelatedContactType
104 * @param array $mapperRelatedContactDetails
105 * @param array $mapperRelatedContactLocType
106 * @param array $mapperRelatedContactPhoneType
107 * @param array $mapperRelatedContactImProvider
108 * @param array $mapperWebsiteType
109 * @param array $mapperRelatedContactWebsiteType
110 */
111 public function __construct(
112 $mapperKeys, $mapperLocType = [], $mapperPhoneType = [], $mapperImProvider = [], $mapperRelated = [], $mapperRelatedContactType = [], $mapperRelatedContactDetails = [], $mapperRelatedContactLocType = [], $mapperRelatedContactPhoneType = [], $mapperRelatedContactImProvider = [],
113 $mapperWebsiteType = [], $mapperRelatedContactWebsiteType = []
114 ) {
115 parent::__construct();
116 $this->_mapperKeys = $mapperKeys;
117 $this->_mapperLocType = &$mapperLocType;
118 $this->_mapperPhoneType = &$mapperPhoneType;
119 $this->_mapperWebsiteType = $mapperWebsiteType;
120 // get IM service provider type id for contact
121 $this->_mapperImProvider = &$mapperImProvider;
122 $this->_mapperRelated = &$mapperRelated;
123 $this->_mapperRelatedContactType = &$mapperRelatedContactType;
124 $this->_mapperRelatedContactDetails = &$mapperRelatedContactDetails;
125 $this->_mapperRelatedContactLocType = &$mapperRelatedContactLocType;
126 $this->_mapperRelatedContactPhoneType = &$mapperRelatedContactPhoneType;
127 $this->_mapperRelatedContactWebsiteType = $mapperRelatedContactWebsiteType;
128 // get IM service provider type id for related contact
129 $this->_mapperRelatedContactImProvider = &$mapperRelatedContactImProvider;
130 }
131
132 /**
133 * The initializer code, called before processing.
134 */
135 public function init() {
136 $this->setFieldMetadata();
137 foreach ($this->getImportableFieldsMetadata() as $name => $field) {
138 $this->addField($name, $field['title'], CRM_Utils_Array::value('type', $field), CRM_Utils_Array::value('headerPattern', $field), CRM_Utils_Array::value('dataPattern', $field), CRM_Utils_Array::value('hasLocationType', $field));
139 }
140
141 $this->_newContacts = [];
142
143 $this->setActiveFields($this->_mapperKeys);
144 $this->setActiveFieldLocationTypes($this->_mapperLocType);
145 $this->setActiveFieldPhoneTypes($this->_mapperPhoneType);
146 $this->setActiveFieldWebsiteTypes($this->_mapperWebsiteType);
147 //set active fields of IM provider of contact
148 $this->setActiveFieldImProviders($this->_mapperImProvider);
149
150 //related info
151 $this->setActiveFieldRelated($this->_mapperRelated);
152 $this->setActiveFieldRelatedContactType($this->_mapperRelatedContactType);
153 $this->setActiveFieldRelatedContactDetails($this->_mapperRelatedContactDetails);
154 $this->setActiveFieldRelatedContactLocType($this->_mapperRelatedContactLocType);
155 $this->setActiveFieldRelatedContactPhoneType($this->_mapperRelatedContactPhoneType);
156 $this->setActiveFieldRelatedContactWebsiteType($this->_mapperRelatedContactWebsiteType);
157 //set active fields of IM provider of related contact
158 $this->setActiveFieldRelatedContactImProvider($this->_mapperRelatedContactImProvider);
159
160 $this->_phoneIndex = -1;
161 $this->_emailIndex = -1;
162 $this->_firstNameIndex = -1;
163 $this->_lastNameIndex = -1;
164 $this->_householdNameIndex = -1;
165 $this->_organizationNameIndex = -1;
166 $this->_externalIdentifierIndex = -1;
167
168 $index = 0;
169 foreach ($this->_mapperKeys as $key) {
170 if (substr($key, 0, 5) == 'email' && substr($key, 0, 14) != 'email_greeting') {
171 $this->_emailIndex = $index;
172 $this->_allEmails = [];
173 }
174 if (substr($key, 0, 5) == 'phone') {
175 $this->_phoneIndex = $index;
176 }
177 if ($key == 'first_name') {
178 $this->_firstNameIndex = $index;
179 }
180 if ($key == 'last_name') {
181 $this->_lastNameIndex = $index;
182 }
183 if ($key == 'household_name') {
184 $this->_householdNameIndex = $index;
185 }
186 if ($key == 'organization_name') {
187 $this->_organizationNameIndex = $index;
188 }
189
190 if ($key == 'external_identifier') {
191 $this->_externalIdentifierIndex = $index;
192 $this->_allExternalIdentifiers = [];
193 }
194 $index++;
195 }
196
197 $this->_updateWithId = FALSE;
198 if (in_array('id', $this->_mapperKeys) || ($this->_externalIdentifierIndex >= 0 && in_array($this->_onDuplicate, [
199 CRM_Import_Parser::DUPLICATE_UPDATE,
200 CRM_Import_Parser::DUPLICATE_FILL,
201 ]))) {
202 $this->_updateWithId = TRUE;
203 }
204
205 $this->_parseStreetAddress = CRM_Utils_Array::value('street_address_parsing', CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'address_options'), FALSE);
206 }
207
208 /**
209 * Handle the values in mapField mode.
210 *
211 * @param array $values
212 * The array of values belonging to this line.
213 *
214 * @return bool
215 */
216 public function mapField(&$values) {
217 return CRM_Import_Parser::VALID;
218 }
219
220 /**
221 * Handle the values in preview mode.
222 *
223 * @param array $values
224 * The array of values belonging to this line.
225 *
226 * @return bool
227 * the result of this processing
228 */
229 public function preview(&$values) {
230 return $this->summary($values);
231 }
232
233 /**
234 * Handle the values in summary mode.
235 *
236 * @param array $values
237 * The array of values belonging to this line.
238 *
239 * @return bool
240 * the result of this processing
241 */
242 public function summary(&$values) {
243 $erroneousField = NULL;
244 $response = $this->setActiveFieldValues($values, $erroneousField);
245
246 $errorMessage = NULL;
247 $errorRequired = FALSE;
248 switch ($this->_contactType) {
249 case 'Individual':
250 $missingNames = [];
251 if ($this->_firstNameIndex < 0 || empty($values[$this->_firstNameIndex])) {
252 $errorRequired = TRUE;
253 $missingNames[] = ts('First Name');
254 }
255 if ($this->_lastNameIndex < 0 || empty($values[$this->_lastNameIndex])) {
256 $errorRequired = TRUE;
257 $missingNames[] = ts('Last Name');
258 }
259 if ($errorRequired) {
260 $and = ' ' . ts('and') . ' ';
261 $errorMessage = ts('Missing required fields:') . ' ' . implode($and, $missingNames);
262 }
263 break;
264
265 case 'Household':
266 if ($this->_householdNameIndex < 0 || empty($values[$this->_householdNameIndex])) {
267 $errorRequired = TRUE;
268 $errorMessage = ts('Missing required fields:') . ' ' . ts('Household Name');
269 }
270 break;
271
272 case 'Organization':
273 if ($this->_organizationNameIndex < 0 || empty($values[$this->_organizationNameIndex])) {
274 $errorRequired = TRUE;
275 $errorMessage = ts('Missing required fields:') . ' ' . ts('Organization Name');
276 }
277 break;
278 }
279
280 $statusFieldName = $this->_statusFieldName;
281
282 if ($this->_emailIndex >= 0) {
283 /* If we don't have the required fields, bail */
284
285 if ($this->_contactType == 'Individual' && !$this->_updateWithId) {
286 if ($errorRequired && empty($values[$this->_emailIndex])) {
287 if ($errorMessage) {
288 $errorMessage .= ' ' . ts('OR') . ' ' . ts('Email Address');
289 }
290 else {
291 $errorMessage = ts('Missing required field:') . ' ' . ts('Email Address');
292 }
293 array_unshift($values, $errorMessage);
294 $importRecordParams = [
295 $statusFieldName => 'ERROR',
296 "${statusFieldName}Msg" => $errorMessage,
297 ];
298 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
299
300 return CRM_Import_Parser::ERROR;
301 }
302 }
303
304 $email = CRM_Utils_Array::value($this->_emailIndex, $values);
305 if ($email) {
306 /* If the email address isn't valid, bail */
307
308 if (!CRM_Utils_Rule::email($email)) {
309 $errorMessage = ts('Invalid Email address');
310 array_unshift($values, $errorMessage);
311 $importRecordParams = [
312 $statusFieldName => 'ERROR',
313 "${statusFieldName}Msg" => $errorMessage,
314 ];
315 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
316
317 return CRM_Import_Parser::ERROR;
318 }
319
320 /* otherwise, count it and move on */
321 $this->_allEmails[$email] = $this->_lineCount;
322 }
323 }
324 elseif ($errorRequired && !$this->_updateWithId) {
325 if ($errorMessage) {
326 $errorMessage .= ' ' . ts('OR') . ' ' . ts('Email Address');
327 }
328 else {
329 $errorMessage = ts('Missing required field:') . ' ' . ts('Email Address');
330 }
331 array_unshift($values, $errorMessage);
332 $importRecordParams = [
333 $statusFieldName => 'ERROR',
334 "${statusFieldName}Msg" => $errorMessage,
335 ];
336 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
337
338 return CRM_Import_Parser::ERROR;
339 }
340
341 //check for duplicate external Identifier
342 $externalID = CRM_Utils_Array::value($this->_externalIdentifierIndex, $values);
343 if ($externalID) {
344 /* If it's a dupe,external Identifier */
345
346 if ($externalDupe = CRM_Utils_Array::value($externalID, $this->_allExternalIdentifiers)) {
347 $errorMessage = ts('External ID conflicts with record %1', [1 => $externalDupe]);
348 array_unshift($values, $errorMessage);
349 $importRecordParams = [
350 $statusFieldName => 'ERROR',
351 "${statusFieldName}Msg" => $errorMessage,
352 ];
353 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
354 return CRM_Import_Parser::ERROR;
355 }
356 //otherwise, count it and move on
357 $this->_allExternalIdentifiers[$externalID] = $this->_lineCount;
358 }
359
360 //Checking error in custom data
361 $params = &$this->getActiveFieldParams();
362 $params['contact_type'] = $this->_contactType;
363 //date-format part ends
364
365 $errorMessage = NULL;
366
367 //CRM-5125
368 //add custom fields for contact sub type
369 $csType = NULL;
370 if (!empty($this->_contactSubType)) {
371 $csType = $this->_contactSubType;
372 }
373
374 //checking error in custom data
375 $this->isErrorInCustomData($params, $errorMessage, $csType, $this->_relationships);
376
377 //checking error in core data
378 $this->isErrorInCoreData($params, $errorMessage);
379 if ($errorMessage) {
380 $tempMsg = "Invalid value for field(s) : $errorMessage";
381 // put the error message in the import record in the DB
382 $importRecordParams = [
383 $statusFieldName => 'ERROR',
384 "${statusFieldName}Msg" => $tempMsg,
385 ];
386 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
387 array_unshift($values, $tempMsg);
388 $errorMessage = NULL;
389 return CRM_Import_Parser::ERROR;
390 }
391
392 //if user correcting errors by walking back
393 //need to reset status ERROR msg to null
394 //now currently we are having valid data.
395 $importRecordParams = [
396 $statusFieldName => 'NEW',
397 ];
398 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
399
400 return CRM_Import_Parser::VALID;
401 }
402
403 /**
404 * Get Array of all the fields that could potentially be part
405 * import process
406 *
407 * @return array
408 */
409 public function getAllFields() {
410 return $this->_fields;
411 }
412
413 /**
414 * Handle the values in import mode.
415 *
416 * @param int $onDuplicate
417 * The code for what action to take on duplicates.
418 * @param array $values
419 * The array of values belonging to this line.
420 *
421 * @param bool $doGeocodeAddress
422 *
423 * @return bool
424 * the result of this processing
425 *
426 * @throws \CiviCRM_API3_Exception
427 * @throws \CRM_Core_Exception
428 */
429 public function import($onDuplicate, &$values, $doGeocodeAddress = FALSE) {
430 $config = CRM_Core_Config::singleton();
431 $this->_unparsedStreetAddressContacts = [];
432 if (!$doGeocodeAddress) {
433 // CRM-5854, reset the geocode method to null to prevent geocoding
434 CRM_Utils_GeocodeProvider::disableForSession();
435 }
436
437 // first make sure this is a valid line
438 //$this->_updateWithId = false;
439 $response = $this->summary($values);
440 $statusFieldName = $this->_statusFieldName;
441
442 if ($response != CRM_Import_Parser::VALID) {
443 $importRecordParams = [
444 $statusFieldName => 'INVALID',
445 "${statusFieldName}Msg" => "Invalid (Error Code: $response)",
446 ];
447 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
448 return $response;
449 }
450
451 $params = &$this->getActiveFieldParams();
452 $formatted = [
453 'contact_type' => $this->_contactType,
454 ];
455
456 $contactFields = CRM_Contact_DAO_Contact::import();
457
458 //check if external identifier exists in database
459 if (!empty($params['external_identifier']) && (!empty($params['id']) || in_array($onDuplicate, [
460 CRM_Import_Parser::DUPLICATE_SKIP,
461 CRM_Import_Parser::DUPLICATE_NOCHECK,
462 ]))) {
463
464 $extIDResult = civicrm_api3('Contact', 'get', [
465 'external_identifier' => $params['external_identifier'],
466 'showAll' => 'all',
467 'return' => ['id', 'contact_is_deleted'],
468 ]);
469 if (isset($extIDResult['id'])) {
470 // record with matching external identifier does exist.
471 $internalCid = $extIDResult['id'];
472 if ($internalCid != CRM_Utils_Array::value('id', $params)) {
473 if ($extIDResult['values'][$internalCid]['contact_is_deleted'] == 1) {
474 // And it is deleted. What to do? If we skip it, they user
475 // will be under the impression that the record exists in
476 // the database, yet they won't be able to find it. If we
477 // don't skip it, the database will try to insert a new record
478 // with an external_identifier that is non-unique. So...
479 // we will update this contact to remove the external_identifier
480 // and let a new record be created.
481 $update_params = ['id' => $internalCid, 'external_identifier' => ''];
482 civicrm_api3('Contact', 'create', $update_params);
483 }
484 else {
485 $errorMessage = ts('External ID already exists in Database.');
486 array_unshift($values, $errorMessage);
487 $importRecordParams = [
488 $statusFieldName => 'ERROR',
489 "${statusFieldName}Msg" => $errorMessage,
490 ];
491 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
492 return CRM_Import_Parser::DUPLICATE;
493 }
494 }
495 }
496 }
497
498 if (!empty($this->_contactSubType)) {
499 $params['contact_sub_type'] = $this->_contactSubType;
500 }
501
502 if ($subType = CRM_Utils_Array::value('contact_sub_type', $params)) {
503 if (CRM_Contact_BAO_ContactType::isExtendsContactType($subType, $this->_contactType, FALSE, 'label')) {
504 $subTypes = CRM_Contact_BAO_ContactType::subTypePairs($this->_contactType, FALSE, NULL);
505 $params['contact_sub_type'] = array_search($subType, $subTypes);
506 }
507 elseif (!CRM_Contact_BAO_ContactType::isExtendsContactType($subType, $this->_contactType)) {
508 $message = "Mismatched or Invalid Contact Subtype.";
509 array_unshift($values, $message);
510 return CRM_Import_Parser::NO_MATCH;
511 }
512 }
513
514 // Get contact id to format common data in update/fill mode,
515 // prioritising a dedupe rule check over an external_identifier check, but falling back on ext id.
516 if ($this->_updateWithId && empty($params['id'])) {
517 try {
518 $possibleMatches = $this->getPossibleContactMatches($params);
519 }
520 catch (CRM_Core_Exception $e) {
521 $errorMessage = $e->getMessage();
522 array_unshift($values, $errorMessage);
523
524 $importRecordParams = [
525 $statusFieldName => 'ERROR',
526 "${statusFieldName}Msg" => $errorMessage,
527 ];
528 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
529 return CRM_Import_Parser::ERROR;
530 }
531 foreach ($possibleMatches as $possibleID) {
532 $params['id'] = $formatted['id'] = $possibleID;
533 }
534 }
535 //format common data, CRM-4062
536 $this->formatCommonData($params, $formatted, $contactFields);
537
538 $relationship = FALSE;
539 $createNewContact = TRUE;
540 // Support Match and Update Via Contact ID
541 if ($this->_updateWithId && isset($params['id'])) {
542 $createNewContact = FALSE;
543 // @todo - it feels like all the rows from here to the end of the IF
544 // could be removed in favour of a simple check for whether the contact_type & id match
545 // the call to the deprecated function seems to add no value other that to do an additional
546 // check for the contact_id & type.
547 $error = _civicrm_api3_deprecated_duplicate_formatted_contact($formatted);
548 if (CRM_Core_Error::isAPIError($error, CRM_Core_ERROR::DUPLICATE_CONTACT)) {
549 if (is_array($error['error_message']['params'][0])) {
550 $matchedIDs = $error['error_message']['params'][0];
551 }
552 else {
553 $matchedIDs = explode(',', $error['error_message']['params'][0]);
554 }
555 if (count($matchedIDs) >= 1) {
556 $updateflag = TRUE;
557 foreach ($matchedIDs as $contactId) {
558 if ($params['id'] == $contactId) {
559 $contactType = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $params['id'], 'contact_type');
560 if ($formatted['contact_type'] == $contactType) {
561 //validation of subtype for update mode
562 //CRM-5125
563 $contactSubType = NULL;
564 if (!empty($params['contact_sub_type'])) {
565 $contactSubType = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $params['id'], 'contact_sub_type');
566 }
567
568 if (!empty($contactSubType) && (!CRM_Contact_BAO_ContactType::isAllowEdit($params['id'], $contactSubType) && $contactSubType != CRM_Utils_Array::value('contact_sub_type', $formatted))) {
569
570 $message = "Mismatched contact SubTypes :";
571 array_unshift($values, $message);
572 $updateflag = FALSE;
573 $this->_retCode = CRM_Import_Parser::NO_MATCH;
574 }
575 else {
576 $updateflag = FALSE;
577 $this->_retCode = CRM_Import_Parser::VALID;
578 }
579 }
580 else {
581 $message = "Mismatched contact Types :";
582 array_unshift($values, $message);
583 $updateflag = FALSE;
584 $this->_retCode = CRM_Import_Parser::NO_MATCH;
585 }
586 }
587 }
588 if ($updateflag) {
589 $message = "Mismatched contact IDs OR Mismatched contact Types :";
590 array_unshift($values, $message);
591 $this->_retCode = CRM_Import_Parser::NO_MATCH;
592 }
593 }
594 }
595 else {
596 $contactType = NULL;
597 if (!empty($params['id'])) {
598 $contactType = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $params['id'], 'contact_type');
599 if ($contactType) {
600 if ($formatted['contact_type'] == $contactType) {
601 //validation of subtype for update mode
602 //CRM-5125
603 $contactSubType = NULL;
604 if (!empty($params['contact_sub_type'])) {
605 $contactSubType = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $params['id'], 'contact_sub_type');
606 }
607
608 if (!empty($contactSubType) && (!CRM_Contact_BAO_ContactType::isAllowEdit($params['id'], $contactSubType) && $contactSubType != CRM_Utils_Array::value('contact_sub_type', $formatted))) {
609
610 $message = "Mismatched contact SubTypes :";
611 array_unshift($values, $message);
612 $this->_retCode = CRM_Import_Parser::NO_MATCH;
613 }
614 else {
615 $newContact = $this->createContact($formatted, $contactFields, $onDuplicate, $params['id'], FALSE, $this->_dedupeRuleGroupID);
616 $this->_retCode = CRM_Import_Parser::VALID;
617 }
618 }
619 else {
620 $message = "Mismatched contact Types :";
621 array_unshift($values, $message);
622 $this->_retCode = CRM_Import_Parser::NO_MATCH;
623 }
624 }
625 else {
626 // we should avoid multiple errors for single record
627 // since we have already retCode and we trying to force again.
628 if ($this->_retCode != CRM_Import_Parser::NO_MATCH) {
629 $message = "No contact found for this contact ID:" . $params['id'];
630 array_unshift($values, $message);
631 $this->_retCode = CRM_Import_Parser::NO_MATCH;
632 }
633 }
634 }
635 else {
636 //CRM-4148
637 //now we want to create new contact on update/fill also.
638 $createNewContact = TRUE;
639 }
640 }
641
642 if (isset($newContact) && is_a($newContact, 'CRM_Contact_BAO_Contact')) {
643 $relationship = TRUE;
644 }
645 elseif (is_a($error, 'CRM_Core_Error')) {
646 $newContact = $error;
647 $relationship = TRUE;
648 }
649 }
650
651 //fixed CRM-4148
652 //now we create new contact in update/fill mode also.
653 $contactID = NULL;
654 if ($createNewContact || ($this->_retCode != CRM_Import_Parser::NO_MATCH && $this->_updateWithId)) {
655
656 //CRM-4430, don't carry if not submitted.
657 foreach (['prefix_id', 'suffix_id', 'gender_id'] as $name) {
658 if (!empty($formatted[$name])) {
659 $options = CRM_Contact_BAO_Contact::buildOptions($name, 'get');
660 if (!isset($options[$formatted[$name]])) {
661 $formatted[$name] = CRM_Utils_Array::key((string) $formatted[$name], $options);
662 }
663 }
664 }
665 if ($this->_updateWithId && !empty($params['id'])) {
666 $contactID = $params['id'];
667 }
668 $newContact = $this->createContact($formatted, $contactFields, $onDuplicate, $contactID, TRUE, $this->_dedupeRuleGroupID);
669 }
670
671 if (isset($newContact) && is_object($newContact) && ($newContact instanceof CRM_Contact_BAO_Contact)) {
672 $relationship = TRUE;
673 $newContact = clone($newContact);
674 $contactID = $newContact->id;
675 $this->_newContacts[] = $contactID;
676
677 //get return code if we create new contact in update mode, CRM-4148
678 if ($this->_updateWithId) {
679 $this->_retCode = CRM_Import_Parser::VALID;
680 }
681 }
682 elseif (isset($newContact) && CRM_Core_Error::isAPIError($newContact, CRM_Core_Error::DUPLICATE_CONTACT)) {
683 // if duplicate, no need of further processing
684 if ($onDuplicate == CRM_Import_Parser::DUPLICATE_SKIP) {
685 $errorMessage = "Skipping duplicate record";
686 array_unshift($values, $errorMessage);
687 $importRecordParams = [
688 $statusFieldName => 'DUPLICATE',
689 "${statusFieldName}Msg" => $errorMessage,
690 ];
691 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
692 return CRM_Import_Parser::DUPLICATE;
693 }
694
695 $relationship = TRUE;
696 // CRM-10433/CRM-20739 - IDs could be string or array; handle accordingly
697 if (!is_array($dupeContactIDs = $newContact['error_message']['params'][0])) {
698 $dupeContactIDs = explode(',', $dupeContactIDs);
699 }
700 $dupeCount = count($dupeContactIDs);
701 $contactID = array_pop($dupeContactIDs);
702 // check to see if we had more than one duplicate contact id.
703 // if we have more than one, the record will be rejected below
704 if ($dupeCount == 1) {
705 // there was only one dupe, we will continue normally...
706 if (!in_array($contactID, $this->_newContacts)) {
707 $this->_newContacts[] = $contactID;
708 }
709 }
710 }
711
712 if ($contactID) {
713 // call import hook
714 $currentImportID = end($values);
715
716 $hookParams = [
717 'contactID' => $contactID,
718 'importID' => $currentImportID,
719 'importTempTable' => $this->_tableName,
720 'fieldHeaders' => $this->_mapperKeys,
721 'fields' => $this->_activeFields,
722 ];
723
724 CRM_Utils_Hook::import('Contact', 'process', $this, $hookParams);
725 }
726
727 if ($relationship) {
728 $primaryContactId = NULL;
729 if (CRM_Core_Error::isAPIError($newContact, CRM_Core_ERROR::DUPLICATE_CONTACT)) {
730 if ($dupeCount == 1 && CRM_Utils_Rule::integer($contactID)) {
731 $primaryContactId = $contactID;
732 }
733 }
734 else {
735 $primaryContactId = $newContact->id;
736 }
737
738 if ((CRM_Core_Error::isAPIError($newContact, CRM_Core_ERROR::DUPLICATE_CONTACT) || is_a($newContact, 'CRM_Contact_BAO_Contact')) && $primaryContactId) {
739
740 //relationship contact insert
741 foreach ($params as $key => $field) {
742 list($id, $first, $second) = CRM_Utils_System::explode('_', $key, 3);
743 if (!($first == 'a' && $second == 'b') && !($first == 'b' && $second == 'a')) {
744 continue;
745 }
746
747 $relationType = new CRM_Contact_DAO_RelationshipType();
748 $relationType->id = $id;
749 $relationType->find(TRUE);
750 $direction = "contact_sub_type_$second";
751
752 $formatting = [
753 'contact_type' => $params[$key]['contact_type'],
754 ];
755
756 //set subtype for related contact CRM-5125
757 if (isset($relationType->$direction)) {
758 //validation of related contact subtype for update mode
759 if ($relCsType = CRM_Utils_Array::value('contact_sub_type', $params[$key]) && $relCsType != $relationType->$direction) {
760 $errorMessage = ts("Mismatched or Invalid contact subtype found for this related contact.");
761 array_unshift($values, $errorMessage);
762 return CRM_Import_Parser::NO_MATCH;
763 }
764 else {
765 $formatting['contact_sub_type'] = $relationType->$direction;
766 }
767 }
768
769 $contactFields = NULL;
770 $contactFields = CRM_Contact_DAO_Contact::import();
771
772 //Relation on the basis of External Identifier.
773 if (empty($params[$key]['id']) && !empty($params[$key]['external_identifier'])) {
774 $params[$key]['id'] = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $params[$key]['external_identifier'], 'id', 'external_identifier');
775 }
776 // check for valid related contact id in update/fill mode, CRM-4424
777 if (in_array($onDuplicate, [
778 CRM_Import_Parser::DUPLICATE_UPDATE,
779 CRM_Import_Parser::DUPLICATE_FILL,
780 ]) && !empty($params[$key]['id'])) {
781 $relatedContactType = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $params[$key]['id'], 'contact_type');
782 if (!$relatedContactType) {
783 $errorMessage = ts("No contact found for this related contact ID: %1", [1 => $params[$key]['id']]);
784 array_unshift($values, $errorMessage);
785 return CRM_Import_Parser::NO_MATCH;
786 }
787 else {
788 //validation of related contact subtype for update mode
789 //CRM-5125
790 $relatedCsType = NULL;
791 if (!empty($formatting['contact_sub_type'])) {
792 $relatedCsType = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $params[$key]['id'], 'contact_sub_type');
793 }
794
795 if (!empty($relatedCsType) && (!CRM_Contact_BAO_ContactType::isAllowEdit($params[$key]['id'], $relatedCsType) &&
796 $relatedCsType != CRM_Utils_Array::value('contact_sub_type', $formatting))
797 ) {
798 $errorMessage = ts("Mismatched or Invalid contact subtype found for this related contact.") . ' ' . ts("ID: %1", [1 => $params[$key]['id']]);
799 array_unshift($values, $errorMessage);
800 return CRM_Import_Parser::NO_MATCH;
801 }
802 else {
803 // get related contact id to format data in update/fill mode,
804 //if external identifier is present, CRM-4423
805 $formatting['id'] = $params[$key]['id'];
806 }
807 }
808 }
809
810 //format common data, CRM-4062
811 $this->formatCommonData($field, $formatting, $contactFields);
812
813 //do we have enough fields to create related contact.
814 $allowToCreate = $this->checkRelatedContactFields($key, $formatting);
815
816 if (!$allowToCreate) {
817 $errorMessage = ts('Related contact required fields are missing.');
818 array_unshift($values, $errorMessage);
819 return CRM_Import_Parser::NO_MATCH;
820 }
821
822 //fixed for CRM-4148
823 if (!empty($params[$key]['id'])) {
824 $contact = [
825 'contact_id' => $params[$key]['id'],
826 ];
827 $defaults = [];
828 $relatedNewContact = CRM_Contact_BAO_Contact::retrieve($contact, $defaults);
829 }
830 else {
831 $relatedNewContact = $this->createContact($formatting, $contactFields, $onDuplicate, NULL, FALSE);
832 }
833
834 if (is_object($relatedNewContact) || ($relatedNewContact instanceof CRM_Contact_BAO_Contact)) {
835 $relatedNewContact = clone($relatedNewContact);
836 }
837
838 $matchedIDs = [];
839 // To update/fill contact, get the matching contact Ids if duplicate contact found
840 // otherwise get contact Id from object of related contact
841 if (is_array($relatedNewContact) && civicrm_error($relatedNewContact)) {
842 if (CRM_Core_Error::isAPIError($relatedNewContact, CRM_Core_ERROR::DUPLICATE_CONTACT)) {
843 $matchedIDs = $relatedNewContact['error_message']['params'][0];
844 if (!is_array($matchedIDs)) {
845 $matchedIDs = explode(',', $matchedIDs);
846 }
847 }
848 else {
849 $errorMessage = $relatedNewContact['error_message'];
850 array_unshift($values, $errorMessage);
851 $importRecordParams = [
852 $statusFieldName => 'ERROR',
853 "${statusFieldName}Msg" => $errorMessage,
854 ];
855 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
856 return CRM_Import_Parser::ERROR;
857 }
858 }
859 else {
860 $matchedIDs[] = $relatedNewContact->id;
861 }
862 // update/fill related contact after getting matching Contact Ids, CRM-4424
863 if (in_array($onDuplicate, [
864 CRM_Import_Parser::DUPLICATE_UPDATE,
865 CRM_Import_Parser::DUPLICATE_FILL,
866 ])) {
867 //validation of related contact subtype for update mode
868 //CRM-5125
869 $relatedCsType = NULL;
870 if (!empty($formatting['contact_sub_type'])) {
871 $relatedCsType = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $matchedIDs[0], 'contact_sub_type');
872 }
873
874 if (!empty($relatedCsType) && (!CRM_Contact_BAO_ContactType::isAllowEdit($matchedIDs[0], $relatedCsType) && $relatedCsType != CRM_Utils_Array::value('contact_sub_type', $formatting))) {
875 $errorMessage = ts("Mismatched or Invalid contact subtype found for this related contact.");
876 array_unshift($values, $errorMessage);
877 return CRM_Import_Parser::NO_MATCH;
878 }
879 else {
880 $updatedContact = $this->createContact($formatting, $contactFields, $onDuplicate, $matchedIDs[0]);
881 }
882 }
883 static $relativeContact = [];
884 if (CRM_Core_Error::isAPIError($relatedNewContact, CRM_Core_ERROR::DUPLICATE_CONTACT)) {
885 if (count($matchedIDs) >= 1) {
886 $relContactId = $matchedIDs[0];
887 //add relative contact to count during update & fill mode.
888 //logic to make count distinct by contact id.
889 if ($this->_newRelatedContacts || !empty($relativeContact)) {
890 $reContact = array_keys($relativeContact, $relContactId);
891
892 if (empty($reContact)) {
893 $this->_newRelatedContacts[] = $relativeContact[] = $relContactId;
894 }
895 }
896 else {
897 $this->_newRelatedContacts[] = $relativeContact[] = $relContactId;
898 }
899 }
900 }
901 else {
902 $relContactId = $relatedNewContact->id;
903 $this->_newRelatedContacts[] = $relativeContact[] = $relContactId;
904 }
905
906 if (CRM_Core_Error::isAPIError($relatedNewContact, CRM_Core_ERROR::DUPLICATE_CONTACT) || ($relatedNewContact instanceof CRM_Contact_BAO_Contact)) {
907 //fix for CRM-1993.Checks for duplicate related contacts
908 if (count($matchedIDs) >= 1) {
909 //if more than one duplicate contact
910 //found, create relationship with first contact
911 // now create the relationship record
912 $relationParams = [];
913 $relationParams = [
914 'relationship_type_id' => $key,
915 'contact_check' => [
916 $relContactId => 1,
917 ],
918 'is_active' => 1,
919 'skipRecentView' => TRUE,
920 ];
921
922 // we only handle related contact success, we ignore failures for now
923 // at some point wold be nice to have related counts as separate
924 $relationIds = [
925 'contact' => $primaryContactId,
926 ];
927
928 list($valid, $invalid, $duplicate, $saved, $relationshipIds) = CRM_Contact_BAO_Relationship::legacyCreateMultiple($relationParams, $relationIds);
929
930 if ($valid || $duplicate) {
931 $relationIds['contactTarget'] = $relContactId;
932 $action = ($duplicate) ? CRM_Core_Action::UPDATE : CRM_Core_Action::ADD;
933 CRM_Contact_BAO_Relationship::relatedMemberships($primaryContactId, $relationParams, $relationIds, $action);
934 }
935
936 //handle current employer, CRM-3532
937 if ($valid) {
938 $allRelationships = CRM_Core_PseudoConstant::relationshipType('name');
939 $relationshipTypeId = str_replace([
940 '_a_b',
941 '_b_a',
942 ], [
943 '',
944 '',
945 ], $key);
946 $relationshipType = str_replace($relationshipTypeId . '_', '', $key);
947 $orgId = $individualId = NULL;
948 if ($allRelationships[$relationshipTypeId]["name_{$relationshipType}"] == 'Employee of') {
949 $orgId = $relContactId;
950 $individualId = $primaryContactId;
951 }
952 elseif ($allRelationships[$relationshipTypeId]["name_{$relationshipType}"] == 'Employer of') {
953 $orgId = $primaryContactId;
954 $individualId = $relContactId;
955 }
956 if ($orgId && $individualId) {
957 $currentEmpParams[$individualId] = $orgId;
958 CRM_Contact_BAO_Contact_Utils::setCurrentEmployer($currentEmpParams);
959 }
960 }
961 }
962 }
963 }
964 }
965 }
966 if ($this->_updateWithId) {
967 //return warning if street address is unparsed, CRM-5886
968 return $this->processMessage($values, $statusFieldName, $this->_retCode);
969 }
970 //dupe checking
971 if (is_array($newContact) && civicrm_error($newContact)) {
972 $code = NULL;
973
974 if (($code = CRM_Utils_Array::value('code', $newContact['error_message'])) && ($code == CRM_Core_Error::DUPLICATE_CONTACT)) {
975 $urls = [];
976 // need to fix at some stage and decide if the error will return an
977 // array or string, crude hack for now
978 if (is_array($newContact['error_message']['params'][0])) {
979 $cids = $newContact['error_message']['params'][0];
980 }
981 else {
982 $cids = explode(',', $newContact['error_message']['params'][0]);
983 }
984
985 foreach ($cids as $cid) {
986 $urls[] = CRM_Utils_System::url('civicrm/contact/view', 'reset=1&cid=' . $cid, TRUE);
987 }
988
989 $url_string = implode("\n", $urls);
990
991 // If we duplicate more than one record, skip no matter what
992 if (count($cids) > 1) {
993 $errorMessage = ts('Record duplicates multiple contacts');
994 $importRecordParams = [
995 $statusFieldName => 'ERROR',
996 "${statusFieldName}Msg" => $errorMessage,
997 ];
998
999 //combine error msg to avoid mismatch between error file columns.
1000 $errorMessage .= "\n" . $url_string;
1001 array_unshift($values, $errorMessage);
1002 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
1003 return CRM_Import_Parser::ERROR;
1004 }
1005
1006 // Params only had one id, so shift it out
1007 $contactId = array_shift($cids);
1008 $cid = NULL;
1009
1010 $vals = ['contact_id' => $contactId];
1011
1012 if ($onDuplicate == CRM_Import_Parser::DUPLICATE_REPLACE) {
1013 civicrm_api('contact', 'delete', $vals);
1014 $cid = CRM_Contact_BAO_Contact::createProfileContact($formatted, $contactFields, $contactId, NULL, NULL, $formatted['contact_type']);
1015 }
1016 elseif ($onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE) {
1017 $newContact = $this->createContact($formatted, $contactFields, $onDuplicate, $contactId);
1018 }
1019 elseif ($onDuplicate == CRM_Import_Parser::DUPLICATE_FILL) {
1020 $newContact = $this->createContact($formatted, $contactFields, $onDuplicate, $contactId);
1021 }
1022 // else skip does nothing and just returns an error code.
1023 if ($cid) {
1024 $contact = [
1025 'contact_id' => $cid,
1026 ];
1027 $defaults = [];
1028 $newContact = CRM_Contact_BAO_Contact::retrieve($contact, $defaults);
1029 }
1030
1031 if (civicrm_error($newContact)) {
1032 if (empty($newContact['error_message']['params'])) {
1033 // different kind of error other than DUPLICATE
1034 $errorMessage = $newContact['error_message'];
1035 array_unshift($values, $errorMessage);
1036 $importRecordParams = [
1037 $statusFieldName => 'ERROR',
1038 "${statusFieldName}Msg" => $errorMessage,
1039 ];
1040 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
1041 return CRM_Import_Parser::ERROR;
1042 }
1043
1044 $contactID = $newContact['error_message']['params'][0];
1045 if (is_array($contactID)) {
1046 $contactID = array_pop($contactID);
1047 }
1048 if (!in_array($contactID, $this->_newContacts)) {
1049 $this->_newContacts[] = $contactID;
1050 }
1051 }
1052 //CRM-262 No Duplicate Checking
1053 if ($onDuplicate == CRM_Import_Parser::DUPLICATE_SKIP) {
1054 array_unshift($values, $url_string);
1055 $importRecordParams = [
1056 $statusFieldName => 'DUPLICATE',
1057 "${statusFieldName}Msg" => "Skipping duplicate record",
1058 ];
1059 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
1060 return CRM_Import_Parser::DUPLICATE;
1061 }
1062
1063 $importRecordParams = [
1064 $statusFieldName => 'IMPORTED',
1065 ];
1066 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
1067 //return warning if street address is not parsed, CRM-5886
1068 return $this->processMessage($values, $statusFieldName, CRM_Import_Parser::VALID);
1069 }
1070 else {
1071 // Not a dupe, so we had an error
1072 $errorMessage = $newContact['error_message'];
1073 array_unshift($values, $errorMessage);
1074 $importRecordParams = [
1075 $statusFieldName => 'ERROR',
1076 "${statusFieldName}Msg" => $errorMessage,
1077 ];
1078 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
1079 return CRM_Import_Parser::ERROR;
1080 }
1081 }
1082 // sleep(3);
1083 return $this->processMessage($values, $statusFieldName, CRM_Import_Parser::VALID);
1084 }
1085
1086 /**
1087 * Get the array of successfully imported contact id's
1088 *
1089 * @return array
1090 */
1091 public function &getImportedContacts() {
1092 return $this->_newContacts;
1093 }
1094
1095 /**
1096 * Get the array of successfully imported related contact id's
1097 *
1098 * @return array
1099 */
1100 public function &getRelatedImportedContacts() {
1101 return $this->_newRelatedContacts;
1102 }
1103
1104 /**
1105 * The initializer code, called before the processing.
1106 */
1107 public function fini() {
1108 }
1109
1110 /**
1111 * Check if an error in custom data.
1112 *
1113 * @param array $params
1114 * @param string $errorMessage
1115 * A string containing all the error-fields.
1116 *
1117 * @param null $csType
1118 * @param null $relationships
1119 */
1120 public static function isErrorInCustomData($params, &$errorMessage, $csType = NULL, $relationships = NULL) {
1121 $dateType = CRM_Core_Session::singleton()->get("dateTypes");
1122
1123 if (!empty($params['contact_sub_type'])) {
1124 $csType = CRM_Utils_Array::value('contact_sub_type', $params);
1125 }
1126
1127 if (empty($params['contact_type'])) {
1128 $params['contact_type'] = 'Individual';
1129 }
1130
1131 // get array of subtypes - CRM-18708
1132 if (in_array($csType, ['Individual', 'Organization', 'Household'])) {
1133 $csType = self::getSubtypes($params['contact_type']);
1134 }
1135
1136 if (is_array($csType)) {
1137 // fetch custom fields for every subtype and add it to $customFields array
1138 // CRM-18708
1139 $customFields = [];
1140 foreach ($csType as $cType) {
1141 $customFields += CRM_Core_BAO_CustomField::getFields($params['contact_type'], FALSE, FALSE, $cType);
1142 }
1143 }
1144 else {
1145 $customFields = CRM_Core_BAO_CustomField::getFields($params['contact_type'], FALSE, FALSE, $csType);
1146 }
1147
1148 $addressCustomFields = CRM_Core_BAO_CustomField::getFields('Address');
1149 $customFields = $customFields + $addressCustomFields;
1150 foreach ($params as $key => $value) {
1151 if ($customFieldID = CRM_Core_BAO_CustomField::getKeyID($key)) {
1152 /* check if it's a valid custom field id */
1153
1154 if (!array_key_exists($customFieldID, $customFields)) {
1155 self::addToErrorMsg(ts('field ID'), $errorMessage);
1156 }
1157 // validate null values for required custom fields of type boolean
1158 if (!empty($customFields[$customFieldID]['is_required']) && (empty($params['custom_' . $customFieldID]) && !is_numeric($params['custom_' . $customFieldID])) && $customFields[$customFieldID]['data_type'] == 'Boolean') {
1159 self::addToErrorMsg($customFields[$customFieldID]['label'] . '::' . $customFields[$customFieldID]['groupTitle'], $errorMessage);
1160 }
1161
1162 //For address custom fields, we do get actual custom field value as an inner array of
1163 //values so need to modify
1164 if (array_key_exists($customFieldID, $addressCustomFields)) {
1165 $value = $value[0][$key];
1166 }
1167 /* validate the data against the CF type */
1168
1169 if ($value) {
1170 if ($customFields[$customFieldID]['data_type'] == 'Date') {
1171 if (array_key_exists($customFieldID, $addressCustomFields) && CRM_Utils_Date::convertToDefaultDate($params[$key][0], $dateType, $key)) {
1172 $value = $params[$key][0][$key];
1173 }
1174 elseif (CRM_Utils_Date::convertToDefaultDate($params, $dateType, $key)) {
1175 $value = $params[$key];
1176 }
1177 else {
1178 self::addToErrorMsg($customFields[$customFieldID]['label'], $errorMessage);
1179 }
1180 }
1181 elseif ($customFields[$customFieldID]['data_type'] == 'Boolean') {
1182 if (CRM_Utils_String::strtoboolstr($value) === FALSE) {
1183 self::addToErrorMsg($customFields[$customFieldID]['label'] . '::' . $customFields[$customFieldID]['groupTitle'], $errorMessage);
1184 }
1185 }
1186 // need not check for label filed import
1187 $htmlType = [
1188 'CheckBox',
1189 'Multi-Select',
1190 'Select',
1191 'Radio',
1192 'Multi-Select State/Province',
1193 'Multi-Select Country',
1194 ];
1195 if (!in_array($customFields[$customFieldID]['html_type'], $htmlType) || $customFields[$customFieldID]['data_type'] == 'Boolean' || $customFields[$customFieldID]['data_type'] == 'ContactReference') {
1196 $valid = CRM_Core_BAO_CustomValue::typecheck($customFields[$customFieldID]['data_type'], $value);
1197 if (!$valid) {
1198 self::addToErrorMsg($customFields[$customFieldID]['label'], $errorMessage);
1199 }
1200 }
1201
1202 // check for values for custom fields for checkboxes and multiselect
1203 if ($customFields[$customFieldID]['html_type'] == 'CheckBox' || $customFields[$customFieldID]['html_type'] == 'Multi-Select') {
1204 $value = trim($value);
1205 $value = str_replace('|', ',', $value);
1206 $mulValues = explode(',', $value);
1207 $customOption = CRM_Core_BAO_CustomOption::getCustomOption($customFieldID, TRUE);
1208 foreach ($mulValues as $v1) {
1209 if (strlen($v1) == 0) {
1210 continue;
1211 }
1212
1213 $flag = FALSE;
1214 foreach ($customOption as $v2) {
1215 if ((strtolower(trim($v2['label'])) == strtolower(trim($v1))) || (strtolower(trim($v2['value'])) == strtolower(trim($v1)))) {
1216 $flag = TRUE;
1217 }
1218 }
1219
1220 if (!$flag) {
1221 self::addToErrorMsg($customFields[$customFieldID]['label'], $errorMessage);
1222 }
1223 }
1224 }
1225 elseif ($customFields[$customFieldID]['html_type'] == 'Select' || ($customFields[$customFieldID]['html_type'] == 'Radio' && $customFields[$customFieldID]['data_type'] != 'Boolean')) {
1226 $customOption = CRM_Core_BAO_CustomOption::getCustomOption($customFieldID, TRUE);
1227 $flag = FALSE;
1228 foreach ($customOption as $v2) {
1229 if ((strtolower(trim($v2['label'])) == strtolower(trim($value))) || (strtolower(trim($v2['value'])) == strtolower(trim($value)))) {
1230 $flag = TRUE;
1231 }
1232 }
1233 if (!$flag) {
1234 self::addToErrorMsg($customFields[$customFieldID]['label'], $errorMessage);
1235 }
1236 }
1237 elseif ($customFields[$customFieldID]['html_type'] == 'Multi-Select State/Province') {
1238 $mulValues = explode(',', $value);
1239 foreach ($mulValues as $stateValue) {
1240 if ($stateValue) {
1241 if (self::in_value(trim($stateValue), CRM_Core_PseudoConstant::stateProvinceAbbreviation()) || self::in_value(trim($stateValue), CRM_Core_PseudoConstant::stateProvince())) {
1242 continue;
1243 }
1244 else {
1245 self::addToErrorMsg($customFields[$customFieldID]['label'], $errorMessage);
1246 }
1247 }
1248 }
1249 }
1250 elseif ($customFields[$customFieldID]['html_type'] == 'Multi-Select Country') {
1251 $mulValues = explode(',', $value);
1252 foreach ($mulValues as $countryValue) {
1253 if ($countryValue) {
1254 CRM_Core_PseudoConstant::populate($countryNames, 'CRM_Core_DAO_Country', TRUE, 'name', 'is_active');
1255 CRM_Core_PseudoConstant::populate($countryIsoCodes, 'CRM_Core_DAO_Country', TRUE, 'iso_code');
1256 $limitCodes = CRM_Core_BAO_Country::countryLimit();
1257
1258 $error = TRUE;
1259 foreach ([
1260 $countryNames,
1261 $countryIsoCodes,
1262 $limitCodes,
1263 ] as $values) {
1264 if (in_array(trim($countryValue), $values)) {
1265 $error = FALSE;
1266 break;
1267 }
1268 }
1269
1270 if ($error) {
1271 self::addToErrorMsg($customFields[$customFieldID]['label'], $errorMessage);
1272 }
1273 }
1274 }
1275 }
1276 }
1277 }
1278 elseif (is_array($params[$key]) && isset($params[$key]["contact_type"])) {
1279 //CRM-5125
1280 //supporting custom data of related contact subtypes
1281 $relation = NULL;
1282 if ($relationships) {
1283 if (array_key_exists($key, $relationships)) {
1284 $relation = $key;
1285 }
1286 elseif (CRM_Utils_Array::key($key, $relationships)) {
1287 $relation = CRM_Utils_Array::key($key, $relationships);
1288 }
1289 }
1290 if (!empty($relation)) {
1291 list($id, $first, $second) = CRM_Utils_System::explode('_', $relation, 3);
1292 $direction = "contact_sub_type_$second";
1293 $relationshipType = new CRM_Contact_BAO_RelationshipType();
1294 $relationshipType->id = $id;
1295 if ($relationshipType->find(TRUE)) {
1296 if (isset($relationshipType->$direction)) {
1297 $params[$key]['contact_sub_type'] = $relationshipType->$direction;
1298 }
1299 }
1300 }
1301
1302 self::isErrorInCustomData($params[$key], $errorMessage, $csType, $relationships);
1303 }
1304 }
1305 }
1306
1307 /**
1308 * Check if value present in all genders or.
1309 * as a substring of any gender value, if yes than return corresponding gender.
1310 * eg value might be m/M, ma/MA, mal/MAL, male return 'Male'
1311 * but if value is 'maleabc' than return false
1312 *
1313 * @param string $gender
1314 * Check this value across gender values.
1315 *
1316 * retunr gender value / false
1317 *
1318 * @return bool
1319 */
1320 public function checkGender($gender) {
1321 $gender = trim($gender, '.');
1322 if (!$gender) {
1323 return FALSE;
1324 }
1325
1326 $allGenders = CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'gender_id');
1327 foreach ($allGenders as $key => $value) {
1328 if (strlen($gender) > strlen($value)) {
1329 continue;
1330 }
1331 if ($gender == $value) {
1332 return $value;
1333 }
1334 if (substr_compare($value, $gender, 0, strlen($gender), TRUE) === 0) {
1335 return $value;
1336 }
1337 }
1338
1339 return FALSE;
1340 }
1341
1342 /**
1343 * Check if an error in Core( non-custom fields ) field
1344 *
1345 * @param array $params
1346 * @param string $errorMessage
1347 * A string containing all the error-fields.
1348 */
1349 public function isErrorInCoreData($params, &$errorMessage) {
1350 foreach ($params as $key => $value) {
1351 if ($value) {
1352 $session = CRM_Core_Session::singleton();
1353 $dateType = $session->get("dateTypes");
1354
1355 switch ($key) {
1356 case 'birth_date':
1357 if (CRM_Utils_Date::convertToDefaultDate($params, $dateType, $key)) {
1358 if (!CRM_Utils_Rule::date($params[$key])) {
1359 self::addToErrorMsg(ts('Birth Date'), $errorMessage);
1360 }
1361 }
1362 else {
1363 self::addToErrorMsg(ts('Birth-Date'), $errorMessage);
1364 }
1365 break;
1366
1367 case 'deceased_date':
1368 if (CRM_Utils_Date::convertToDefaultDate($params, $dateType, $key)) {
1369 if (!CRM_Utils_Rule::date($params[$key])) {
1370 self::addToErrorMsg(ts('Deceased Date'), $errorMessage);
1371 }
1372 }
1373 else {
1374 self::addToErrorMsg(ts('Deceased Date'), $errorMessage);
1375 }
1376 break;
1377
1378 case 'is_deceased':
1379 if (CRM_Utils_String::strtoboolstr($value) === FALSE) {
1380 self::addToErrorMsg(ts('Deceased'), $errorMessage);
1381 }
1382 break;
1383
1384 case 'gender_id':
1385 if (!self::checkGender($value)) {
1386 self::addToErrorMsg(ts('Gender'), $errorMessage);
1387 }
1388 break;
1389
1390 case 'preferred_communication_method':
1391 $preffComm = [];
1392 $preffComm = explode(',', $value);
1393 foreach ($preffComm as $v) {
1394 if (!self::in_value(trim($v), CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'preferred_communication_method'))) {
1395 self::addToErrorMsg(ts('Preferred Communication Method'), $errorMessage);
1396 }
1397 }
1398 break;
1399
1400 case 'preferred_mail_format':
1401 if (!array_key_exists(strtolower($value), array_change_key_case(CRM_Core_SelectValues::pmf(), CASE_LOWER))) {
1402 self::addToErrorMsg(ts('Preferred Mail Format'), $errorMessage);
1403 }
1404 break;
1405
1406 case 'individual_prefix':
1407 case 'prefix_id':
1408 if (!self::in_value($value, CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'prefix_id'))) {
1409 self::addToErrorMsg(ts('Individual Prefix'), $errorMessage);
1410 }
1411 break;
1412
1413 case 'individual_suffix':
1414 case 'suffix_id':
1415 if (!self::in_value($value, CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'suffix_id'))) {
1416 self::addToErrorMsg(ts('Individual Suffix'), $errorMessage);
1417 }
1418 break;
1419
1420 case 'state_province':
1421 if (!empty($value)) {
1422 foreach ($value as $stateValue) {
1423 if ($stateValue['state_province']) {
1424 if (self::in_value($stateValue['state_province'], CRM_Core_PseudoConstant::stateProvinceAbbreviation()) ||
1425 self::in_value($stateValue['state_province'], CRM_Core_PseudoConstant::stateProvince())
1426 ) {
1427 continue;
1428 }
1429 else {
1430 self::addToErrorMsg(ts('State/Province'), $errorMessage);
1431 }
1432 }
1433 }
1434 }
1435 break;
1436
1437 case 'country':
1438 if (!empty($value)) {
1439 foreach ($value as $stateValue) {
1440 if ($stateValue['country']) {
1441 CRM_Core_PseudoConstant::populate($countryNames, 'CRM_Core_DAO_Country', TRUE, 'name', 'is_active');
1442 CRM_Core_PseudoConstant::populate($countryIsoCodes, 'CRM_Core_DAO_Country', TRUE, 'iso_code');
1443 $limitCodes = CRM_Core_BAO_Country::countryLimit();
1444 //If no country is selected in
1445 //localization then take all countries
1446 if (empty($limitCodes)) {
1447 $limitCodes = $countryIsoCodes;
1448 }
1449
1450 if (self::in_value($stateValue['country'], $limitCodes) || self::in_value($stateValue['country'], CRM_Core_PseudoConstant::country())) {
1451 continue;
1452 }
1453 else {
1454 if (self::in_value($stateValue['country'], $countryIsoCodes) || self::in_value($stateValue['country'], $countryNames)) {
1455 self::addToErrorMsg(ts('Country input value is in table but not "available": "This Country is valid but is NOT in the list of Available Countries currently configured for your site. This can be viewed and modifed from Administer > Localization > Languages Currency Locations." '), $errorMessage);
1456 }
1457 else {
1458 self::addToErrorMsg(ts('Country input value not in country table: "The Country value appears to be invalid. It does not match any value in CiviCRM table of countries."'), $errorMessage);
1459 }
1460 }
1461 }
1462 }
1463 }
1464 break;
1465
1466 case 'county':
1467 if (!empty($value)) {
1468 foreach ($value as $county) {
1469 if ($county['county']) {
1470 $countyNames = CRM_Core_PseudoConstant::county();
1471 if (!empty($county['county']) && !in_array($county['county'], $countyNames)) {
1472 self::addToErrorMsg(ts('County input value not in county table: The County value appears to be invalid. It does not match any value in CiviCRM table of counties.'), $errorMessage);
1473 }
1474 }
1475 }
1476 }
1477 break;
1478
1479 case 'geo_code_1':
1480 if (!empty($value)) {
1481 foreach ($value as $codeValue) {
1482 if (!empty($codeValue['geo_code_1'])) {
1483 if (CRM_Utils_Rule::numeric($codeValue['geo_code_1'])) {
1484 continue;
1485 }
1486 else {
1487 self::addToErrorMsg(ts('Geo code 1'), $errorMessage);
1488 }
1489 }
1490 }
1491 }
1492 break;
1493
1494 case 'geo_code_2':
1495 if (!empty($value)) {
1496 foreach ($value as $codeValue) {
1497 if (!empty($codeValue['geo_code_2'])) {
1498 if (CRM_Utils_Rule::numeric($codeValue['geo_code_2'])) {
1499 continue;
1500 }
1501 else {
1502 self::addToErrorMsg(ts('Geo code 2'), $errorMessage);
1503 }
1504 }
1505 }
1506 }
1507 break;
1508
1509 //check for any error in email/postal greeting, addressee,
1510 //custom email/postal greeting, custom addressee, CRM-4575
1511
1512 case 'email_greeting':
1513 $emailGreetingFilter = [
1514 'contact_type' => $this->_contactType,
1515 'greeting_type' => 'email_greeting',
1516 ];
1517 if (!self::in_value($value, CRM_Core_PseudoConstant::greeting($emailGreetingFilter))) {
1518 self::addToErrorMsg(ts('Email Greeting must be one of the configured format options. Check Administer >> System Settings >> Option Groups >> Email Greetings for valid values'), $errorMessage);
1519 }
1520 break;
1521
1522 case 'postal_greeting':
1523 $postalGreetingFilter = [
1524 'contact_type' => $this->_contactType,
1525 'greeting_type' => 'postal_greeting',
1526 ];
1527 if (!self::in_value($value, CRM_Core_PseudoConstant::greeting($postalGreetingFilter))) {
1528 self::addToErrorMsg(ts('Postal Greeting must be one of the configured format options. Check Administer >> System Settings >> Option Groups >> Postal Greetings for valid values'), $errorMessage);
1529 }
1530 break;
1531
1532 case 'addressee':
1533 $addresseeFilter = [
1534 'contact_type' => $this->_contactType,
1535 'greeting_type' => 'addressee',
1536 ];
1537 if (!self::in_value($value, CRM_Core_PseudoConstant::greeting($addresseeFilter))) {
1538 self::addToErrorMsg(ts('Addressee must be one of the configured format options. Check Administer >> System Settings >> Option Groups >> Addressee for valid values'), $errorMessage);
1539 }
1540 break;
1541
1542 case 'email_greeting_custom':
1543 if (array_key_exists('email_greeting', $params)) {
1544 $emailGreetingLabel = key(CRM_Core_OptionGroup::values('email_greeting', TRUE, NULL, NULL, 'AND v.name = "Customized"'));
1545 if (CRM_Utils_Array::value('email_greeting', $params) != $emailGreetingLabel) {
1546 self::addToErrorMsg(ts('Email Greeting - Custom'), $errorMessage);
1547 }
1548 }
1549 break;
1550
1551 case 'postal_greeting_custom':
1552 if (array_key_exists('postal_greeting', $params)) {
1553 $postalGreetingLabel = key(CRM_Core_OptionGroup::values('postal_greeting', TRUE, NULL, NULL, 'AND v.name = "Customized"'));
1554 if (CRM_Utils_Array::value('postal_greeting', $params) != $postalGreetingLabel) {
1555 self::addToErrorMsg(ts('Postal Greeting - Custom'), $errorMessage);
1556 }
1557 }
1558 break;
1559
1560 case 'addressee_custom':
1561 if (array_key_exists('addressee', $params)) {
1562 $addresseeLabel = key(CRM_Core_OptionGroup::values('addressee', TRUE, NULL, NULL, 'AND v.name = "Customized"'));
1563 if (CRM_Utils_Array::value('addressee', $params) != $addresseeLabel) {
1564 self::addToErrorMsg(ts('Addressee - Custom'), $errorMessage);
1565 }
1566 }
1567 break;
1568
1569 case 'url':
1570 if (is_array($value)) {
1571 foreach ($value as $values) {
1572 if (!empty($values['url']) && !CRM_Utils_Rule::url($values['url'])) {
1573 self::addToErrorMsg(ts('Website'), $errorMessage);
1574 break;
1575 }
1576 }
1577 }
1578 break;
1579
1580 case 'do_not_email':
1581 case 'do_not_phone':
1582 case 'do_not_mail':
1583 case 'do_not_sms':
1584 case 'do_not_trade':
1585 if (CRM_Utils_Rule::boolean($value) == FALSE) {
1586 $key = ucwords(str_replace("_", " ", $key));
1587 self::addToErrorMsg($key, $errorMessage);
1588 }
1589 break;
1590
1591 case 'email':
1592 if (is_array($value)) {
1593 foreach ($value as $values) {
1594 if (!empty($values['email']) && !CRM_Utils_Rule::email($values['email'])) {
1595 self::addToErrorMsg($key, $errorMessage);
1596 break;
1597 }
1598 }
1599 }
1600 break;
1601
1602 default:
1603 if (is_array($params[$key]) && isset($params[$key]["contact_type"])) {
1604 //check for any relationship data ,FIX ME
1605 self::isErrorInCoreData($params[$key], $errorMessage);
1606 }
1607 }
1608 }
1609 }
1610 }
1611
1612 /**
1613 * Ckeck a value present or not in a array.
1614 *
1615 * @param $value
1616 * @param $valueArray
1617 *
1618 * @return bool
1619 */
1620 public static function in_value($value, $valueArray) {
1621 foreach ($valueArray as $key => $v) {
1622 //fix for CRM-1514
1623 if (strtolower(trim($v, ".")) == strtolower(trim($value, "."))) {
1624 return TRUE;
1625 }
1626 }
1627 return FALSE;
1628 }
1629
1630 /**
1631 * Build error-message containing error-fields
1632 *
1633 * Once upon a time there was a dev who hadn't heard of implode. That dev wrote this function.
1634 *
1635 * @todo just say no!
1636 *
1637 * @param string $errorName
1638 * A string containing error-field name.
1639 * @param string $errorMessage
1640 * A string containing all the error-fields, where the new errorName is concatenated.
1641 *
1642 */
1643 public static function addToErrorMsg($errorName, &$errorMessage) {
1644 if ($errorMessage) {
1645 $errorMessage .= "; $errorName";
1646 }
1647 else {
1648 $errorMessage = $errorName;
1649 }
1650 }
1651
1652 /**
1653 * Method for creating contact.
1654 *
1655 * @param array $formatted
1656 * @param array $contactFields
1657 * @param int $onDuplicate
1658 * @param int $contactId
1659 * @param bool $requiredCheck
1660 * @param int $dedupeRuleGroupID
1661 *
1662 * @return array|bool|\CRM_Contact_BAO_Contact|\CRM_Core_Error|null
1663 */
1664 public function createContact(&$formatted, &$contactFields, $onDuplicate, $contactId = NULL, $requiredCheck = TRUE, $dedupeRuleGroupID = NULL) {
1665 $dupeCheck = FALSE;
1666 $newContact = NULL;
1667
1668 if (is_null($contactId) && ($onDuplicate != CRM_Import_Parser::DUPLICATE_NOCHECK)) {
1669 $dupeCheck = (bool) ($onDuplicate);
1670 }
1671
1672 //get the prefix id etc if exists
1673 CRM_Contact_BAO_Contact::resolveDefaults($formatted, TRUE);
1674
1675 //@todo direct call to API function not supported.
1676 // setting required check to false, CRM-2839
1677 // plus we do our own required check in import
1678 $error = _civicrm_api3_deprecated_contact_check_params($formatted, $dupeCheck, $dedupeRuleGroupID);
1679
1680 if ((is_null($error)) && (civicrm_error(_civicrm_api3_deprecated_validate_formatted_contact($formatted)))) {
1681 $error = _civicrm_api3_deprecated_validate_formatted_contact($formatted);
1682 }
1683
1684 $newContact = $error;
1685
1686 if (is_null($error)) {
1687 if ($contactId) {
1688 $this->formatParams($formatted, $onDuplicate, (int) $contactId);
1689 }
1690
1691 // Resetting and rebuilding cache could be expensive.
1692 CRM_Core_Config::setPermitCacheFlushMode(FALSE);
1693 $cid = CRM_Contact_BAO_Contact::createProfileContact($formatted, $contactFields, $contactId, NULL, NULL, $formatted['contact_type']);
1694 CRM_Core_Config::setPermitCacheFlushMode(TRUE);
1695
1696 $contact = [
1697 'contact_id' => $cid,
1698 ];
1699
1700 $defaults = [];
1701 $newContact = CRM_Contact_BAO_Contact::retrieve($contact, $defaults);
1702 }
1703
1704 //get the id of the contact whose street address is not parsable, CRM-5886
1705 if ($this->_parseStreetAddress && is_object($newContact) && property_exists($newContact, 'address') && $newContact->address) {
1706 foreach ($newContact->address as $address) {
1707 if (!empty($address['street_address']) && (empty($address['street_number']) || empty($address['street_name']))) {
1708 $this->_unparsedStreetAddressContacts[] = [
1709 'id' => $newContact->id,
1710 'streetAddress' => $address['street_address'],
1711 ];
1712 }
1713 }
1714 }
1715 return $newContact;
1716 }
1717
1718 /**
1719 * Format params for update and fill mode.
1720 *
1721 * @param array $params
1722 * reference to an array containing all the.
1723 * values for import
1724 * @param int $onDuplicate
1725 * @param int $cid
1726 * contact id.
1727 */
1728 public function formatParams(&$params, $onDuplicate, $cid) {
1729 if ($onDuplicate == CRM_Import_Parser::DUPLICATE_SKIP) {
1730 return;
1731 }
1732
1733 $contactParams = [
1734 'contact_id' => $cid,
1735 ];
1736
1737 $defaults = [];
1738 $contactObj = CRM_Contact_BAO_Contact::retrieve($contactParams, $defaults);
1739
1740 $modeUpdate = $modeFill = FALSE;
1741
1742 if ($onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE) {
1743 $modeUpdate = TRUE;
1744 }
1745
1746 if ($onDuplicate == CRM_Import_Parser::DUPLICATE_FILL) {
1747 $modeFill = TRUE;
1748 }
1749
1750 $groupTree = CRM_Core_BAO_CustomGroup::getTree($params['contact_type'], NULL, $cid, 0, NULL);
1751 CRM_Core_BAO_CustomGroup::setDefaults($groupTree, $defaults, FALSE, FALSE);
1752
1753 $locationFields = [
1754 'email' => 'email',
1755 'phone' => 'phone',
1756 'im' => 'name',
1757 'website' => 'website',
1758 'address' => 'address',
1759 ];
1760
1761 $contact = get_object_vars($contactObj);
1762
1763 foreach ($params as $key => $value) {
1764 if ($key == 'id' || $key == 'contact_type') {
1765 continue;
1766 }
1767
1768 if (array_key_exists($key, $locationFields)) {
1769 continue;
1770 }
1771 elseif (in_array($key, [
1772 'email_greeting',
1773 'postal_greeting',
1774 'addressee',
1775 ])) {
1776 // CRM-4575, need to null custom
1777 if ($params["{$key}_id"] != 4) {
1778 $params["{$key}_custom"] = 'null';
1779 }
1780 unset($params[$key]);
1781 }
1782 else {
1783 if ($customFieldId = CRM_Core_BAO_CustomField::getKeyID($key)) {
1784 $custom_params = ['id' => $contact['id'], 'return' => $key];
1785 $getValue = civicrm_api3('Contact', 'getvalue', $custom_params);
1786 if (empty($getValue)) {
1787 unset($getValue);
1788 }
1789 }
1790 else {
1791 $getValue = CRM_Utils_Array::retrieveValueRecursive($contact, $key);
1792 }
1793 if ($key == 'contact_source') {
1794 $params['source'] = $params[$key];
1795 unset($params[$key]);
1796 }
1797
1798 if ($modeFill && isset($getValue)) {
1799 unset($params[$key]);
1800 if ($customFieldId) {
1801 // Extra values must be unset to ensure the values are not
1802 // imported.
1803 unset($params['custom'][$customFieldId]);
1804 }
1805 }
1806 }
1807 }
1808
1809 foreach ($locationFields as $locKeys) {
1810 if (is_array(CRM_Utils_Array::value($locKeys, $params))) {
1811 foreach ($params[$locKeys] as $key => $value) {
1812 if ($modeFill) {
1813 $getValue = CRM_Utils_Array::retrieveValueRecursive($contact, $locKeys);
1814
1815 if (isset($getValue)) {
1816 foreach ($getValue as $cnt => $values) {
1817 if ($locKeys == 'website') {
1818 if (($getValue[$cnt]['website_type_id'] == $params[$locKeys][$key]['website_type_id'])) {
1819 unset($params[$locKeys][$key]);
1820 }
1821 }
1822 else {
1823 if ((!empty($getValue[$cnt]['location_type_id']) && !empty($params[$locKeys][$key]['location_type_id'])) && $getValue[$cnt]['location_type_id'] == $params[$locKeys][$key]['location_type_id']) {
1824 unset($params[$locKeys][$key]);
1825 }
1826 }
1827 }
1828 }
1829 }
1830 }
1831 if (count($params[$locKeys]) == 0) {
1832 unset($params[$locKeys]);
1833 }
1834 }
1835 }
1836 }
1837
1838 /**
1839 * Convert any given date string to default date array.
1840 *
1841 * @param array $params
1842 * Has given date-format.
1843 * @param array $formatted
1844 * Store formatted date in this array.
1845 * @param int $dateType
1846 * Type of date.
1847 * @param string $dateParam
1848 * Index of params.
1849 */
1850 public static function formatCustomDate(&$params, &$formatted, $dateType, $dateParam) {
1851 //fix for CRM-2687
1852 CRM_Utils_Date::convertToDefaultDate($params, $dateType, $dateParam);
1853 $formatted[$dateParam] = CRM_Utils_Date::processDate($params[$dateParam]);
1854 }
1855
1856 /**
1857 * Generate status and error message for unparsed street address records.
1858 *
1859 * @param array $values
1860 * The array of values belonging to each row.
1861 * @param array $statusFieldName
1862 * Store formatted date in this array.
1863 * @param $returnCode
1864 *
1865 * @return int
1866 */
1867 public function processMessage(&$values, $statusFieldName, $returnCode) {
1868 if (empty($this->_unparsedStreetAddressContacts)) {
1869 $importRecordParams = [
1870 $statusFieldName => 'IMPORTED',
1871 ];
1872 }
1873 else {
1874 $errorMessage = ts("Record imported successfully but unable to parse the street address: ");
1875 foreach ($this->_unparsedStreetAddressContacts as $contactInfo => $contactValue) {
1876 $contactUrl = CRM_Utils_System::url('civicrm/contact/add', 'reset=1&action=update&cid=' . $contactValue['id'], TRUE, NULL, FALSE);
1877 $errorMessage .= "\n Contact ID:" . $contactValue['id'] . " <a href=\"$contactUrl\"> " . $contactValue['streetAddress'] . "</a>";
1878 }
1879 array_unshift($values, $errorMessage);
1880 $importRecordParams = [
1881 $statusFieldName => 'ERROR',
1882 "${statusFieldName}Msg" => $errorMessage,
1883 ];
1884 $returnCode = CRM_Import_Parser::UNPARSED_ADDRESS_WARNING;
1885 }
1886 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
1887 return $returnCode;
1888 }
1889
1890 /**
1891 * @param $relKey
1892 * @param array $params
1893 *
1894 * @return bool
1895 */
1896 public function checkRelatedContactFields($relKey, $params) {
1897 //avoid blank contact creation.
1898 $allowToCreate = FALSE;
1899
1900 //build the mapper field array.
1901 static $relatedContactFields = [];
1902 if (!isset($relatedContactFields[$relKey])) {
1903 foreach ($this->_mapperRelated as $key => $name) {
1904 if (!$name) {
1905 continue;
1906 }
1907
1908 if (!empty($relatedContactFields[$name]) && !is_array($relatedContactFields[$name])) {
1909 $relatedContactFields[$name] = [];
1910 }
1911 $fldName = CRM_Utils_Array::value($key, $this->_mapperRelatedContactDetails);
1912 if ($fldName == 'url') {
1913 $fldName = 'website';
1914 }
1915 if ($fldName) {
1916 $relatedContactFields[$name][] = $fldName;
1917 }
1918 }
1919 }
1920
1921 //validate for passed data.
1922 if (is_array($relatedContactFields[$relKey])) {
1923 foreach ($relatedContactFields[$relKey] as $fld) {
1924 if (!empty($params[$fld])) {
1925 $allowToCreate = TRUE;
1926 break;
1927 }
1928 }
1929 }
1930
1931 return $allowToCreate;
1932 }
1933
1934 /**
1935 * get subtypes given the contact type
1936 *
1937 * @param string $contactType
1938 * @return array $subTypes
1939 */
1940 public static function getSubtypes($contactType) {
1941 $subTypes = [];
1942 $types = CRM_Contact_BAO_ContactType::subTypeInfo($contactType);
1943
1944 if (count($types) > 0) {
1945 foreach ($types as $type) {
1946 $subTypes[] = $type['name'];
1947 }
1948 }
1949 return $subTypes;
1950 }
1951
1952 /**
1953 * Get the possible contact matches.
1954 *
1955 * 1) the chosen dedupe rule falling back to
1956 * 2) a check for the external ID.
1957 *
1958 * CRM-17275
1959 *
1960 * @param array $params
1961 *
1962 * @return array
1963 * IDs of possible matches.
1964 *
1965 * @throws \CRM_Core_Exception
1966 * @throws \CiviCRM_API3_Exception
1967 */
1968 protected function getPossibleContactMatches($params) {
1969 $extIDMatch = NULL;
1970
1971 if (!empty($params['external_identifier'])) {
1972 // Check for any match on external id, deleted or otherwise.
1973 $extIDContact = civicrm_api3('Contact', 'get', [
1974 'external_identifier' => $params['external_identifier'],
1975 'showAll' => 'all',
1976 'return' => ['id', 'contact_is_deleted'],
1977 ]);
1978 if (isset($extIDContact['id'])) {
1979 $extIDMatch = $extIDContact['id'];
1980
1981 if ($extIDContact['values'][$extIDMatch]['contact_is_deleted'] == 1) {
1982 // If the contact is deleted, update external identifier to be blank
1983 // to avoid key error from MySQL.
1984 $params = ['id' => $extIDMatch, 'external_identifier' => ''];
1985 civicrm_api3('Contact', 'create', $params);
1986
1987 // And now it is no longer a match.
1988 $extIDMatch = NULL;
1989 }
1990 }
1991 }
1992 $checkParams = ['check_permissions' => FALSE, 'match' => $params];
1993 $checkParams['match']['contact_type'] = $this->_contactType;
1994
1995 $possibleMatches = civicrm_api3('Contact', 'duplicatecheck', $checkParams);
1996 if (!$extIDMatch) {
1997 return array_keys($possibleMatches['values']);
1998 }
1999 if ($possibleMatches['count']) {
2000 if (in_array($extIDMatch, array_keys($possibleMatches['values']))) {
2001 return [$extIDMatch];
2002 }
2003 else {
2004 throw new CRM_Core_Exception(ts(
2005 'Matching this contact based on the de-dupe rule would cause an external ID conflict'));
2006 }
2007 }
2008 return [$extIDMatch];
2009 }
2010
2011 /**
2012 * Format the form mapping parameters ready for the parser.
2013 *
2014 * @param int $count
2015 * Number of rows.
2016 *
2017 * @return array $parserParameters
2018 */
2019 public static function getParameterForParser($count) {
2020 $baseArray = [];
2021 for ($i = 0; $i < $count; $i++) {
2022 $baseArray[$i] = NULL;
2023 }
2024 $parserParameters['mapperLocType'] = $baseArray;
2025 $parserParameters['mapperPhoneType'] = $baseArray;
2026 $parserParameters['mapperImProvider'] = $baseArray;
2027 $parserParameters['mapperWebsiteType'] = $baseArray;
2028 $parserParameters['mapperRelated'] = $baseArray;
2029 $parserParameters['relatedContactType'] = $baseArray;
2030 $parserParameters['relatedContactDetails'] = $baseArray;
2031 $parserParameters['relatedContactLocType'] = $baseArray;
2032 $parserParameters['relatedContactPhoneType'] = $baseArray;
2033 $parserParameters['relatedContactImProvider'] = $baseArray;
2034 $parserParameters['relatedContactWebsiteType'] = $baseArray;
2035
2036 return $parserParameters;
2037
2038 }
2039
2040 /**
2041 * Set field metadata.
2042 */
2043 protected function setFieldMetadata() {
2044 $this->setImportableFieldsMetadata($this->getContactImportMetadata());
2045 // Probably no longer needed but here for now.
2046 $this->_relationships = $this->getRelationships();
2047 }
2048
2049 }