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