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