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