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