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