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