3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2014 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
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. |
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. |
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 +--------------------------------------------------------------------+
31 * @copyright CiviCRM LLC (c) 2004-2014
36 //@todo calling api functions directly is not supported
37 require_once 'api/v3/utils.php';
38 require_once 'api/v3/Contact.php';
41 * class to parse contact csv files
43 class CRM_Contact_Import_Parser_Contact
extends CRM_Contact_Import_Parser
{
44 protected $_mapperKeys;
45 protected $_mapperLocType;
46 protected $_mapperPhoneType;
47 protected $_mapperImProvider;
48 protected $_mapperWebsiteType;
49 protected $_mapperRelated;
50 protected $_mapperRelatedContactType;
51 protected $_mapperRelatedContactDetails;
52 protected $_mapperRelatedContactEmailType;
53 protected $_mapperRelatedContactImProvider;
54 protected $_mapperRelatedContactWebsiteType;
55 protected $_relationships;
57 protected $_emailIndex;
58 protected $_firstNameIndex;
59 protected $_lastNameIndex;
61 protected $_householdNameIndex;
62 protected $_organizationNameIndex;
64 protected $_allEmails;
66 protected $_phoneIndex;
67 protected $_updateWithId;
70 protected $_externalIdentifierIndex;
71 protected $_allExternalIdentifiers;
72 protected $_parseStreetAddress;
75 * Array of successfully imported contact id's
79 protected $_newContacts;
86 protected $_lineCount;
89 * Array of successfully imported related contact id's
93 protected $_newRelatedContacts;
96 * array of all the contacts whose street addresses are not parsed
97 * of this import process
100 protected $_unparsedStreetAddressContacts;
105 function __construct(&$mapperKeys, $mapperLocType = NULL, $mapperPhoneType = NULL, $mapperImProvider = NULL, $mapperRelated = NULL, $mapperRelatedContactType = NULL, $mapperRelatedContactDetails = NULL, $mapperRelatedContactLocType = NULL, $mapperRelatedContactPhoneType = NULL, $mapperRelatedContactImProvider = NULL,
106 $mapperWebsiteType = NULL, $mapperRelatedContactWebsiteType = NULL
108 parent
::__construct();
109 $this->_mapperKeys
= &$mapperKeys;
110 $this->_mapperLocType
= &$mapperLocType;
111 $this->_mapperPhoneType
= &$mapperPhoneType;
112 $this->_mapperWebsiteType
= $mapperWebsiteType;
113 // get IM service provider type id for contact
114 $this->_mapperImProvider
= &$mapperImProvider;
115 $this->_mapperRelated
= &$mapperRelated;
116 $this->_mapperRelatedContactType
= &$mapperRelatedContactType;
117 $this->_mapperRelatedContactDetails
= &$mapperRelatedContactDetails;
118 $this->_mapperRelatedContactLocType
= &$mapperRelatedContactLocType;
119 $this->_mapperRelatedContactPhoneType
= &$mapperRelatedContactPhoneType;
120 $this->_mapperRelatedContactWebsiteType
= $mapperRelatedContactWebsiteType;
121 // get IM service provider type id for related contact
122 $this->_mapperRelatedContactImProvider
= &$mapperRelatedContactImProvider;
126 * the initializer code, called before the processing
132 $contactFields = CRM_Contact_BAO_Contact
::importableFields($this->_contactType
);
133 // exclude the address options disabled in the Address Settings
134 $fields = CRM_Core_BAO_Address
::validateAddressOptions($contactFields);
137 //supporting import for contact subtypes
139 if (!empty($this->_contactSubType
)) {
140 //custom fields for sub type
141 $subTypeFields = CRM_Core_BAO_CustomField
::getFieldsForImport($this->_contactSubType
);
143 if (!empty($subTypeFields)) {
144 foreach ($subTypeFields as $customSubTypeField => $details) {
145 $fields[$customSubTypeField] = $details;
150 //Relationship importables
151 $this->_relationships
= $relations =
152 CRM_Contact_BAO_Relationship
::getContactRelationshipType(
153 NULL, NULL, NULL, $this->_contactType
,
154 FALSE, 'label', TRUE, $this->_contactSubType
158 foreach ($relations as $key => $var) {
159 list($type) = explode('_', $key);
160 $relationshipType[$key]['title'] = $var;
161 $relationshipType[$key]['headerPattern'] = '/' . preg_quote($var, '/') . '/';
162 $relationshipType[$key]['import'] = TRUE;
163 $relationshipType[$key]['relationship_type_id'] = $type;
164 $relationshipType[$key]['related'] = TRUE;
167 if (!empty($relationshipType)) {
168 $fields = array_merge($fields, array(
170 'title' => ts('- related contact info -'),
172 ), $relationshipType);
175 foreach ($fields as $name => $field) {
176 $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));
179 $this->_newContacts
= array();
181 $this->setActiveFields($this->_mapperKeys
);
182 $this->setActiveFieldLocationTypes($this->_mapperLocType
);
183 $this->setActiveFieldPhoneTypes($this->_mapperPhoneType
);
184 $this->setActiveFieldWebsiteTypes($this->_mapperWebsiteType
);
185 //set active fields of IM provider of contact
186 $this->setActiveFieldImProviders($this->_mapperImProvider
);
189 $this->setActiveFieldRelated($this->_mapperRelated
);
190 $this->setActiveFieldRelatedContactType($this->_mapperRelatedContactType
);
191 $this->setActiveFieldRelatedContactDetails($this->_mapperRelatedContactDetails
);
192 $this->setActiveFieldRelatedContactLocType($this->_mapperRelatedContactLocType
);
193 $this->setActiveFieldRelatedContactPhoneType($this->_mapperRelatedContactPhoneType
);
194 $this->setActiveFieldRelatedContactWebsiteType($this->_mapperRelatedContactWebsiteType
);
195 //set active fields of IM provider of related contact
196 $this->setActiveFieldRelatedContactImProvider($this->_mapperRelatedContactImProvider
);
198 $this->_phoneIndex
= -1;
199 $this->_emailIndex
= -1;
200 $this->_firstNameIndex
= -1;
201 $this->_lastNameIndex
= -1;
202 $this->_householdNameIndex
= -1;
203 $this->_organizationNameIndex
= -1;
204 $this->_externalIdentifierIndex
= -1;
207 foreach ($this->_mapperKeys
as $key) {
208 if (substr($key, 0, 5) == 'email' && substr($key, 0, 14) != 'email_greeting') {
209 $this->_emailIndex
= $index;
210 $this->_allEmails
= array();
212 if (substr($key, 0, 5) == 'phone') {
213 $this->_phoneIndex
= $index;
215 if ($key == 'first_name') {
216 $this->_firstNameIndex
= $index;
218 if ($key == 'last_name') {
219 $this->_lastNameIndex
= $index;
221 if ($key == 'household_name') {
222 $this->_householdNameIndex
= $index;
224 if ($key == 'organization_name') {
225 $this->_organizationNameIndex
= $index;
228 if ($key == 'external_identifier') {
229 $this->_externalIdentifierIndex
= $index;
230 $this->_allExternalIdentifiers
= array();
235 $this->_updateWithId
= FALSE;
236 if (in_array('id', $this->_mapperKeys
) ||
($this->_externalIdentifierIndex
>= 0 && in_array($this->_onDuplicate
, array(
237 CRM_Import_Parser
::DUPLICATE_UPDATE
,
238 CRM_Import_Parser
::DUPLICATE_FILL
,
240 $this->_updateWithId
= TRUE;
243 $this->_parseStreetAddress
= CRM_Utils_Array
::value('street_address_parsing', CRM_Core_BAO_Setting
::valueOptions(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
, 'address_options'), FALSE);
247 * handle the values in mapField mode
249 * @param array $values the array of values belonging to this line
254 function mapField(&$values) {
255 return CRM_Import_Parser
::VALID
;
259 * handle the values in preview mode
261 * @param array $values the array of values belonging to this line
263 * @return boolean the result of this processing
266 function preview(&$values) {
267 return $this->summary($values);
271 * handle the values in summary mode
273 * @param array $values the array of values belonging to this line
275 * @return boolean the result of this processing
278 function summary(&$values) {
279 $erroneousField = NULL;
280 $response = $this->setActiveFieldValues($values, $erroneousField);
282 $errorMessage = NULL;
283 $errorRequired = FALSE;
284 switch ($this->_contactType
) {
286 $missingNames = array();
287 if ($this->_firstNameIndex
< 0 ||
empty($values[$this->_firstNameIndex
])) {
288 $errorRequired = TRUE;
289 $missingNames[] = ts('First Name');
291 if ($this->_lastNameIndex
< 0 ||
empty($values[$this->_lastNameIndex
])) {
292 $errorRequired = TRUE;
293 $missingNames[] = ts('Last Name');
295 if ($errorRequired) {
296 $and = ' ' . ts('and') . ' ';
297 $errorMessage = ts('Missing required fields:') . ' ' . implode($and, $missingNames);
302 if ($this->_householdNameIndex
< 0 ||
empty($values[$this->_householdNameIndex
])) {
303 $errorRequired = TRUE;
304 $errorMessage = ts('Missing required fields:') . ' ' . ts('Household Name');
309 if ($this->_organizationNameIndex
< 0 ||
empty($values[$this->_organizationNameIndex
])) {
310 $errorRequired = TRUE;
311 $errorMessage = ts('Missing required fields:') . ' ' . ts('Organization Name');
316 $statusFieldName = $this->_statusFieldName
;
318 if ($this->_emailIndex
>= 0) {
319 /* If we don't have the required fields, bail */
321 if ($this->_contactType
== 'Individual' && !$this->_updateWithId
) {
322 if ($errorRequired && empty($values[$this->_emailIndex
])) {
324 $errorMessage .= ' ' . ts('OR') . ' ' . ts('Email Address');
327 $errorMessage = ts('Missing required field:') . ' ' . ts('Email Address');
329 array_unshift($values, $errorMessage);
330 $importRecordParams = array(
331 $statusFieldName => 'ERROR',
332 "${statusFieldName}Msg" => $errorMessage,
334 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
336 return CRM_Import_Parser
::ERROR
;
340 $email = CRM_Utils_Array
::value($this->_emailIndex
, $values);
342 /* If the email address isn't valid, bail */
344 if (!CRM_Utils_Rule
::email($email)) {
345 $errorMessage = ts('Invalid Email address');
346 array_unshift($values, $errorMessage);
347 $importRecordParams = array(
348 $statusFieldName => 'ERROR',
349 "${statusFieldName}Msg" => $errorMessage,
351 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
353 return CRM_Import_Parser
::ERROR
;
356 /* otherwise, count it and move on */
357 $this->_allEmails
[$email] = $this->_lineCount
;
360 elseif ($errorRequired && !$this->_updateWithId
) {
362 $errorMessage .= ' ' . ts('OR') . ' ' . ts('Email Address');
365 $errorMessage = ts('Missing required field:') . ' ' . ts('Email Address');
367 array_unshift($values, $errorMessage);
368 $importRecordParams = array(
369 $statusFieldName => 'ERROR',
370 "${statusFieldName}Msg" => $errorMessage,
372 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
374 return CRM_Import_Parser
::ERROR
;
377 //check for duplicate external Identifier
378 $externalID = CRM_Utils_Array
::value($this->_externalIdentifierIndex
, $values);
380 /* If it's a dupe,external Identifier */
382 if ($externalDupe = CRM_Utils_Array
::value($externalID, $this->_allExternalIdentifiers
)) {
383 $errorMessage = ts('External Identifier conflicts with record %1', array(1 => $externalDupe));
384 array_unshift($values, $errorMessage);
385 $importRecordParams = array(
386 $statusFieldName => 'ERROR',
387 "${statusFieldName}Msg" => $errorMessage,
389 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
390 return CRM_Import_Parser
::ERROR
;
392 //otherwise, count it and move on
393 $this->_allExternalIdentifiers
[$externalID] = $this->_lineCount
;
396 //Checking error in custom data
397 $params = &$this->getActiveFieldParams();
398 $params['contact_type'] = $this->_contactType
;
399 //date-format part ends
401 $errorMessage = NULL;
404 //add custom fields for contact sub type
406 if (!empty($this->_contactSubType
)) {
407 $csType = $this->_contactSubType
;
410 //checking error in custom data
411 $this->isErrorInCustomData($params, $errorMessage, $csType, $this->_relationships
);
413 //checking error in core data
414 $this->isErrorInCoreData($params, $errorMessage);
416 $tempMsg = "Invalid value for field(s) : $errorMessage";
417 // put the error message in the import record in the DB
418 $importRecordParams = array(
419 $statusFieldName => 'ERROR',
420 "${statusFieldName}Msg" => $tempMsg,
422 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
423 array_unshift($values, $tempMsg);
424 $errorMessage = NULL;
425 return CRM_Import_Parser
::ERROR
;
428 //if user correcting errors by walking back
429 //need to reset status ERROR msg to null
430 //now currently we are having valid data.
431 $importRecordParams = array(
432 $statusFieldName => 'NEW',
434 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
436 return CRM_Import_Parser
::VALID
;
440 * handle the values in import mode
442 * @param int $onDuplicate the code for what action to take on duplicates
443 * @param array $values the array of values belonging to this line
445 * @param bool $doGeocodeAddress
447 * @return boolean the result of this processing
450 function import($onDuplicate, &$values, $doGeocodeAddress = FALSE) {
451 $config = CRM_Core_Config
::singleton();
452 $this->_unparsedStreetAddressContacts
= array();
453 if (!$doGeocodeAddress) {
454 // CRM-5854, reset the geocode method to null to prevent geocoding
455 $config->geocodeMethod
= NULL;
458 // first make sure this is a valid line
459 //$this->_updateWithId = false;
460 $response = $this->summary($values);
461 $statusFieldName = $this->_statusFieldName
;
463 if ($response != CRM_Import_Parser
::VALID
) {
464 $importRecordParams = array(
465 $statusFieldName => 'INVALID',
466 "${statusFieldName}Msg" => "Invalid (Error Code: $response)",
468 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
472 $params = &$this->getActiveFieldParams();
474 'contact_type' => $this->_contactType
,
477 static $contactFields = NULL;
478 if ($contactFields == NULL) {
479 $contactFields = CRM_Contact_DAO_Contact
::import();
482 //check if external identifier exists in database
483 if (!empty($params['external_identifier']) && (!empty($params['id']) ||
in_array($onDuplicate, array(
484 CRM_Import_Parser
::DUPLICATE_SKIP
,
485 CRM_Import_Parser
::DUPLICATE_NOCHECK
,
488 if ($internalCid = CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_Contact', $params['external_identifier'], 'id', 'external_identifier')) {
489 if ($internalCid != CRM_Utils_Array
::value('id', $params)) {
490 $errorMessage = ts('External Identifier already exists in database.');
491 array_unshift($values, $errorMessage);
492 $importRecordParams = array(
493 $statusFieldName => 'ERROR',
494 "${statusFieldName}Msg" => $errorMessage,
496 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
497 return CRM_Import_Parser
::DUPLICATE
;
502 if (!empty($this->_contactSubType
)) {
503 $params['contact_sub_type'] = $this->_contactSubType
;
506 if ($subType = CRM_Utils_Array
::value('contact_sub_type', $params)) {
507 if (CRM_Contact_BAO_ContactType
::isExtendsContactType($subType, $this->_contactType
, FALSE, 'label')) {
508 $subTypes = CRM_Contact_BAO_ContactType
::subTypePairs($this->_contactType
, FALSE, NULL);
509 $params['contact_sub_type'] = array_search($subType, $subTypes);
511 elseif (!CRM_Contact_BAO_ContactType
::isExtendsContactType($subType, $this->_contactType
)) {
512 $message = "Mismatched or Invalid Contact SubType.";
513 array_unshift($values, $message);
514 return CRM_Import_Parser
::NO_MATCH
;
518 //get contact id to format common data in update/fill mode,
519 //if external identifier is present, CRM-4423
520 if ($this->_updateWithId
&& empty($params['id']) && !empty($params['external_identifier'])) {
521 if ($cid = CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_Contact', $params['external_identifier'], 'id', 'external_identifier')) {
522 $formatted['id'] = $cid;
526 //format common data, CRM-4062
527 $this->formatCommonData($params, $formatted, $contactFields);
529 $relationship = FALSE;
530 $createNewContact = TRUE;
531 // Support Match and Update Via Contact ID
532 if ($this->_updateWithId
) {
533 $createNewContact = FALSE;
534 if (empty($params['id']) && !empty($params['external_identifier'])) {
536 $params['id'] = $cid;
539 //update contact if dedupe found contact id, CRM-4148
540 $dedupeParams = $formatted;
542 //special case to check dedupe if external id present.
543 //if we send external id dedupe will stop.
544 unset($dedupeParams['external_identifier']);
545 require_once 'CRM/Utils/DeprecatedUtils.php';
546 $checkDedupe = _civicrm_api3_deprecated_duplicate_formatted_contact($dedupeParams);
547 if (CRM_Core_Error
::isAPIError($checkDedupe, CRM_Core_ERROR
::DUPLICATE_CONTACT
)) {
548 $matchingContactIds = explode(',', $checkDedupe['error_message']['params'][0]);
549 if (count($matchingContactIds) == 1) {
550 $params['id'] = array_pop($matchingContactIds);
553 $message = "More than one matching contact found for given criteria.";
554 array_unshift($values, $message);
555 $this->_retCode
= CRM_Import_Parser
::NO_MATCH
;
559 $createNewContact = TRUE;
564 $error = _civicrm_api3_deprecated_duplicate_formatted_contact($formatted);
565 if (CRM_Core_Error
::isAPIError($error, CRM_Core_ERROR
::DUPLICATE_CONTACT
)) {
566 $matchedIDs = explode(',', $error['error_message']['params'][0]);
567 if (count($matchedIDs) >= 1) {
569 foreach ($matchedIDs as $contactId) {
570 if ($params['id'] == $contactId) {
571 $contactType = CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_Contact', $params['id'], 'contact_type');
573 if ($formatted['contact_type'] == $contactType) {
574 //validation of subtype for update mode
576 $contactSubType = NULL;
577 if (!empty($params['contact_sub_type'])) {
578 $contactSubType = CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_Contact', $params['id'], 'contact_sub_type');
581 if (!empty($contactSubType) && (!CRM_Contact_BAO_ContactType
::isAllowEdit($params['id'], $contactSubType) && $contactSubType != CRM_Utils_Array
::value('contact_sub_type', $formatted))) {
583 $message = "Mismatched contact SubTypes :";
584 array_unshift($values, $message);
586 $this->_retCode
= CRM_Import_Parser
::NO_MATCH
;
589 $newContact = $this->createContact($formatted, $contactFields, $onDuplicate, $contactId, FALSE, $this->_dedupeRuleGroupID
);
591 $this->_retCode
= CRM_Import_Parser
::VALID
;
595 $message = "Mismatched contact Types :";
596 array_unshift($values, $message);
598 $this->_retCode
= CRM_Import_Parser
::NO_MATCH
;
603 $message = "Mismatched contact IDs OR Mismatched contact Types :";
604 array_unshift($values, $message);
605 $this->_retCode
= CRM_Import_Parser
::NO_MATCH
;
611 if (!empty($params['id'])) {
612 $contactType = CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_Contact', $params['id'], 'contact_type');
614 if ($formatted['contact_type'] == $contactType) {
615 //validation of subtype for update mode
617 $contactSubType = NULL;
618 if (!empty($params['contact_sub_type'])) {
619 $contactSubType = CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_Contact', $params['id'], 'contact_sub_type');
622 if (!empty($contactSubType) && (!CRM_Contact_BAO_ContactType
::isAllowEdit($params['id'], $contactSubType) && $contactSubType != CRM_Utils_Array
::value('contact_sub_type', $formatted))) {
624 $message = "Mismatched contact SubTypes :";
625 array_unshift($values, $message);
626 $this->_retCode
= CRM_Import_Parser
::NO_MATCH
;
629 $newContact = $this->createContact($formatted, $contactFields, $onDuplicate, $params['id'], FALSE, $this->_dedupeRuleGroupID
);
630 $this->_retCode
= CRM_Import_Parser
::VALID
;
634 $message = "Mismatched contact Types :";
635 array_unshift($values, $message);
636 $this->_retCode
= CRM_Import_Parser
::NO_MATCH
;
640 // we should avoid multiple errors for single record
641 // since we have already retCode and we trying to force again.
642 if ($this->_retCode
!= CRM_Import_Parser
::NO_MATCH
) {
643 $message = "No contact found for this contact ID:" . $params['id'];
644 array_unshift($values, $message);
645 $this->_retCode
= CRM_Import_Parser
::NO_MATCH
;
651 //now we want to create new contact on update/fill also.
652 $createNewContact = TRUE;
656 if (isset($newContact) && is_a($newContact, 'CRM_Contact_BAO_Contact')) {
657 $relationship = TRUE;
659 elseif (is_a($error, 'CRM_Core_Error')) {
660 $newContact = $error;
661 $relationship = TRUE;
666 //now we create new contact in update/fill mode also.
668 if ($createNewContact ||
($this->_retCode
!= CRM_Import_Parser
::NO_MATCH
&& $this->_updateWithId
)) {
670 //CRM-4430, don't carry if not submitted.
671 foreach (array('prefix_id', 'suffix_id', 'gender_id') as $name) {
672 if (!empty($formatted[$name])) {
673 $options = CRM_Contact_BAO_Contact
::buildOptions($name, 'get');
674 if (!isset($options[$formatted[$name]])) {
675 $formatted[$name] = CRM_Utils_Array
::key((string) $formatted[$name], $options);
679 if ($this->_updateWithId
&& !empty($params['id'])) {
680 $contactID = $params['id'];
682 $newContact = $this->createContact($formatted, $contactFields, $onDuplicate, $contactID, TRUE, $this->_dedupeRuleGroupID
);
685 if (isset($newContact) && is_object($newContact) && ($newContact instanceof CRM_Contact_BAO_Contact
)) {
686 $relationship = TRUE;
687 $newContact = clone($newContact);
688 $contactID = $newContact->id
;
689 $this->_newContacts
[] = $contactID;
691 //get return code if we create new contact in update mode, CRM-4148
692 if ($this->_updateWithId
) {
693 $this->_retCode
= CRM_Import_Parser
::VALID
;
696 elseif (isset($newContact) && CRM_Core_Error
::isAPIError($newContact, CRM_Core_ERROR
::DUPLICATE_CONTACT
)) {
697 // if duplicate, no need of further processing
698 if ($onDuplicate == CRM_Import_Parser
::DUPLICATE_SKIP
) {
699 $errorMessage = "Skipping duplicate record";
700 array_unshift($values, $errorMessage);
701 $importRecordParams = array(
702 $statusFieldName => 'DUPLICATE',
703 "${statusFieldName}Msg" => $errorMessage,
705 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
706 return CRM_Import_Parser
::DUPLICATE
;
709 $relationship = TRUE;
710 # see CRM-10433 - might return comma separate list of all dupes
711 $dupeContactIDs = explode(',',$newContact['error_message']['params'][0]);
712 $dupeCount = count($dupeContactIDs);
713 $contactID = array_pop($dupeContactIDs);
714 // check to see if we had more than one duplicate contact id.
715 // if we have more than one, the record will be rejected below
716 if ($dupeCount == 1) {
717 // there was only one dupe, we will continue normally...
718 if (!in_array($contactID, $this->_newContacts
)) {
719 $this->_newContacts
[] = $contactID;
726 $currentImportID = end($values);
729 'contactID' => $contactID,
730 'importID' => $currentImportID,
731 'importTempTable' => $this->_tableName
,
732 'fieldHeaders' => $this->_mapperKeys
,
733 'fields' => $this->_activeFields
,
736 CRM_Utils_Hook
::import('Contact', 'process', $this, $hookParams);
740 $primaryContactId = NULL;
741 if (CRM_Core_Error
::isAPIError($newContact, CRM_Core_ERROR
::DUPLICATE_CONTACT
)) {
742 if (CRM_Utils_Rule
::integer($newContact['error_message']['params'][0])) {
743 $primaryContactId = $newContact['error_message']['params'][0];
747 $primaryContactId = $newContact->id
;
750 if ((CRM_Core_Error
::isAPIError($newContact, CRM_Core_ERROR
::DUPLICATE_CONTACT
) ||
is_a($newContact, 'CRM_Contact_BAO_Contact')) && $primaryContactId) {
752 //relationship contact insert
753 foreach ($params as $key => $field) {
754 list($id, $first, $second) = CRM_Utils_System
::explode('_', $key, 3);
755 if (!($first == 'a' && $second == 'b') && !($first == 'b' && $second == 'a')) {
759 $relationType = new CRM_Contact_DAO_RelationshipType();
760 $relationType->id
= $id;
761 $relationType->find(TRUE);
762 $direction = "contact_sub_type_$second";
765 'contact_type' => $params[$key]['contact_type'],
768 //set subtype for related contact CRM-5125
769 if (isset($relationType->$direction)) {
770 //validation of related contact subtype for update mode
771 if ($relCsType = CRM_Utils_Array
::value('contact_sub_type', $params[$key]) && $relCsType != $relationType->$direction) {
772 $errorMessage = ts("Mismatched or Invalid contact subtype found for this related contact");
773 array_unshift($values, $errorMessage);
774 return CRM_Import_Parser
::NO_MATCH
;
777 $formatting['contact_sub_type'] = $relationType->$direction;
780 $relationType->free();
782 $contactFields = NULL;
783 $contactFields = CRM_Contact_DAO_Contact
::import();
785 //Relation on the basis of External Identifier.
786 if (empty($params[$key]['id']) && !empty($params[$key]['external_identifier'])) {
787 $params[$key]['id'] = CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_Contact', $params[$key]['external_identifier'], 'id', 'external_identifier');
789 // check for valid related contact id in update/fill mode, CRM-4424
790 if (in_array($onDuplicate, array(
791 CRM_Import_Parser
::DUPLICATE_UPDATE
,
792 CRM_Import_Parser
::DUPLICATE_FILL
,
793 )) && !empty($params[$key]['id'])) {
794 $relatedContactType = CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_Contact', $params[$key]['id'], 'contact_type');
795 if (!$relatedContactType) {
796 $errorMessage = ts("No contact found for this related contact ID: %1", array(1 => $params[$key]['id']));
797 array_unshift($values, $errorMessage);
798 return CRM_Import_Parser
::NO_MATCH
;
801 //validation of related contact subtype for update mode
803 $relatedCsType = NULL;
804 if (!empty($formatting['contact_sub_type'])) {
805 $relatedCsType = CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_Contact', $params[$key]['id'], 'contact_sub_type');
808 if (!empty($relatedCsType) && (!CRM_Contact_BAO_ContactType
::isAllowEdit($params[$key]['id'], $relatedCsType) &&
809 $relatedCsType != CRM_Utils_Array
::value('contact_sub_type', $formatting))) {
810 $errorMessage = ts("Mismatched or Invalid contact subtype found for this related contact ID: %1", array(1 => $params[$key]['id']));
811 array_unshift($values, $errorMessage);
812 return CRM_Import_Parser
::NO_MATCH
;
815 // get related contact id to format data in update/fill mode,
816 //if external identifier is present, CRM-4423
817 $formatting['id'] = $params[$key]['id'];
822 //format common data, CRM-4062
823 $this->formatCommonData($field, $formatting, $contactFields);
825 //do we have enough fields to create related contact.
826 $allowToCreate = $this->checkRelatedContactFields($key, $formatting);
828 if (!$allowToCreate) {
829 $errorMessage = ts('Related contact required fields are missing.');
830 array_unshift($values, $errorMessage);
831 return CRM_Import_Parser
::NO_MATCH
;
835 if (!empty($params[$key]['id'])) {
837 'contact_id' => $params[$key]['id'],
840 $relatedNewContact = CRM_Contact_BAO_Contact
::retrieve($contact, $defaults);
843 $relatedNewContact = $this->createContact($formatting, $contactFields, $onDuplicate, NULL, FALSE);
846 if (is_object($relatedNewContact) ||
($relatedNewContact instanceof CRM_Contact_BAO_Contact
)) {
847 $relatedNewContact = clone($relatedNewContact);
850 $matchedIDs = array();
851 // To update/fill contact, get the matching contact Ids if duplicate contact found
852 // otherwise get contact Id from object of related contact
853 if (is_array($relatedNewContact) && civicrm_error($relatedNewContact)) {
854 if (CRM_Core_Error
::isAPIError($relatedNewContact, CRM_Core_ERROR
::DUPLICATE_CONTACT
)) {
855 $matchedIDs = explode(',', $relatedNewContact['error_message']['params'][0]);
858 $errorMessage = $relatedNewContact['error_message'];
859 array_unshift($values, $errorMessage);
860 $importRecordParams = array(
861 $statusFieldName => 'ERROR',
862 "${statusFieldName}Msg" => $errorMessage,
864 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
865 return CRM_Import_Parser
::ERROR
;
869 $matchedIDs[] = $relatedNewContact->id
;
871 // update/fill related contact after getting matching Contact Ids, CRM-4424
872 if (in_array($onDuplicate, array(
873 CRM_Import_Parser
::DUPLICATE_UPDATE
,
874 CRM_Import_Parser
::DUPLICATE_FILL
,
876 //validation of related contact subtype for update mode
878 $relatedCsType = NULL;
879 if (!empty($formatting['contact_sub_type'])) {
880 $relatedCsType = CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_Contact', $matchedIDs[0], 'contact_sub_type');
883 if (!empty($relatedCsType) && (!CRM_Contact_BAO_ContactType
::isAllowEdit($matchedIDs[0], $relatedCsType) && $relatedCsType != CRM_Utils_Array
::value('contact_sub_type', $formatting))) {
884 $errorMessage = ts("Mismatched or Invalid contact subtype found for this related contact.");
885 array_unshift($values, $errorMessage);
886 return CRM_Import_Parser
::NO_MATCH
;
889 $updatedContact = $this->createContact($formatting, $contactFields, $onDuplicate, $matchedIDs[0]);
892 static $relativeContact = array();
893 if (CRM_Core_Error
::isAPIError($relatedNewContact, CRM_Core_ERROR
::DUPLICATE_CONTACT
)) {
894 if (count($matchedIDs) >= 1) {
895 $relContactId = $matchedIDs[0];
896 //add relative contact to count during update & fill mode.
897 //logic to make count distinct by contact id.
898 if ($this->_newRelatedContacts ||
!empty($relativeContact)) {
899 $reContact = array_keys($relativeContact, $relContactId);
901 if (empty($reContact)) {
902 $this->_newRelatedContacts
[] = $relativeContact[] = $relContactId;
906 $this->_newRelatedContacts
[] = $relativeContact[] = $relContactId;
911 $relContactId = $relatedNewContact->id
;
912 $this->_newRelatedContacts
[] = $relativeContact[] = $relContactId;
915 if (CRM_Core_Error
::isAPIError($relatedNewContact, CRM_Core_ERROR
::DUPLICATE_CONTACT
) ||
($relatedNewContact instanceof CRM_Contact_BAO_Contact
)) {
916 //fix for CRM-1993.Checks for duplicate related contacts
917 if (count($matchedIDs) >= 1) {
918 //if more than one duplicate contact
919 //found, create relationship with first contact
920 // now create the relationship record
921 $relationParams = array();
922 $relationParams = array(
923 'relationship_type_id' => $key,
924 'contact_check' => array(
928 'skipRecentView' => TRUE,
931 // we only handle related contact success, we ignore failures for now
932 // at some point wold be nice to have related counts as separate
933 $relationIds = array(
934 'contact' => $primaryContactId,
937 list($valid, $invalid, $duplicate, $saved, $relationshipIds) = CRM_Contact_BAO_Relationship
::create($relationParams, $relationIds);
939 if ($valid ||
$duplicate) {
940 $relationIds['contactTarget'] = $relContactId;
941 $action = ($duplicate) ? CRM_Core_Action
::UPDATE
: CRM_Core_Action
::ADD
;
942 CRM_Contact_BAO_Relationship
::relatedMemberships($primaryContactId, $relationParams, $relationIds, $action);
945 //handle current employer, CRM-3532
947 $allRelationships = CRM_Core_PseudoConstant
::relationshipType('name');
948 $relationshipTypeId = str_replace(array(
955 $relationshipType = str_replace($relationshipTypeId . '_', '', $key);
956 $orgId = $individualId = NULL;
957 if ($allRelationships[$relationshipTypeId]["name_{$relationshipType}"] == 'Employee of') {
958 $orgId = $relContactId;
959 $individualId = $primaryContactId;
961 elseif ($allRelationships[$relationshipTypeId]["name_{$relationshipType}"] == 'Employer of') {
962 $orgId = $primaryContactId;
963 $individualId = $relContactId;
965 if ($orgId && $individualId) {
966 $currentEmpParams[$individualId] = $orgId;
967 CRM_Contact_BAO_Contact_Utils
::setCurrentEmployer($currentEmpParams);
975 if ($this->_updateWithId
) {
976 //return warning if street address is unparsed, CRM-5886
977 return $this->processMessage($values, $statusFieldName, $this->_retCode
);
980 if (is_array($newContact) && civicrm_error($newContact)) {
983 if (($code = CRM_Utils_Array
::value('code', $newContact['error_message'])) && ($code == CRM_Core_Error
::DUPLICATE_CONTACT
)) {
985 // need to fix at some stage and decide if the error will return an
986 // array or string, crude hack for now
987 if (is_array($newContact['error_message']['params'][0])) {
988 $cids = $newContact['error_message']['params'][0];
991 $cids = explode(',', $newContact['error_message']['params'][0]);
994 foreach ($cids as $cid) {
995 $urls[] = CRM_Utils_System
::url('civicrm/contact/view', 'reset=1&cid=' . $cid, TRUE);
998 $url_string = implode("\n", $urls);
1000 // If we duplicate more than one record, skip no matter what
1001 if (count($cids) > 1) {
1002 $errorMessage = ts('Record duplicates multiple contacts');
1003 $importRecordParams = array(
1004 $statusFieldName => 'ERROR',
1005 "${statusFieldName}Msg" => $errorMessage,
1008 //combine error msg to avoid mismatch between error file columns.
1009 $errorMessage .= "\n" . $url_string;
1010 array_unshift($values, $errorMessage);
1011 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
1012 return CRM_Import_Parser
::ERROR
;
1015 // Params only had one id, so shift it out
1016 $contactId = array_shift($cids);
1019 $vals = array('contact_id' => $contactId);
1021 if ($onDuplicate == CRM_Import_Parser
::DUPLICATE_REPLACE
) {
1022 civicrm_api('contact', 'delete', $vals);
1023 $cid = CRM_Contact_BAO_Contact
::createProfileContact($formatted, $contactFields, $contactId, NULL, NULL, $formatted['contact_type']);
1025 elseif ($onDuplicate == CRM_Import_Parser
::DUPLICATE_UPDATE
) {
1026 $newContact = $this->createContact($formatted, $contactFields, $onDuplicate, $contactId);
1028 elseif ($onDuplicate == CRM_Import_Parser
::DUPLICATE_FILL
) {
1029 $newContact = $this->createContact($formatted, $contactFields, $onDuplicate, $contactId);
1031 // else skip does nothing and just returns an error code.
1036 'contact_id' => $cid,
1038 $defaults = array();
1039 $newContact = CRM_Contact_BAO_Contact
::retrieve($contact, $defaults);
1042 if (civicrm_error($newContact)) {
1043 if (empty($newContact['error_message']['params'])) {
1044 // different kind of error other than DUPLICATE
1045 $errorMessage = $newContact['error_message'];
1046 array_unshift($values, $errorMessage);
1047 $importRecordParams = array(
1048 $statusFieldName => 'ERROR',
1049 "${statusFieldName}Msg" => $errorMessage,
1051 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
1052 return CRM_Import_Parser
::ERROR
;
1055 $contactID = $newContact['error_message']['params'][0];
1056 if (!in_array($contactID, $this->_newContacts
)) {
1057 $this->_newContacts
[] = $contactID;
1060 //CRM-262 No Duplicate Checking
1061 if ($onDuplicate == CRM_Import_Parser
::DUPLICATE_SKIP
) {
1062 array_unshift($values, $url_string);
1063 $importRecordParams = array(
1064 $statusFieldName => 'DUPLICATE',
1065 "${statusFieldName}Msg" => "Skipping duplicate record",
1067 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
1068 return CRM_Import_Parser
::DUPLICATE
;
1071 $importRecordParams = array(
1072 $statusFieldName => 'IMPORTED',
1074 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
1075 //return warning if street address is not parsed, CRM-5886
1076 return $this->processMessage($values, $statusFieldName, CRM_Import_Parser
::VALID
);
1079 // Not a dupe, so we had an error
1080 $errorMessage = $newContact['error_message'];
1081 array_unshift($values, $errorMessage);
1082 $importRecordParams = array(
1083 $statusFieldName => 'ERROR',
1084 "${statusFieldName}Msg" => $errorMessage,
1086 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
1087 return CRM_Import_Parser
::ERROR
;
1091 return $this->processMessage($values, $statusFieldName, CRM_Import_Parser
::VALID
);
1095 * Get the array of successfully imported contact id's
1100 function &getImportedContacts() {
1101 return $this->_newContacts
;
1105 * Get the array of successfully imported related contact id's
1110 function &getRelatedImportedContacts() {
1111 return $this->_newRelatedContacts
;
1115 * the initializer code, called before the processing
1123 * function to check if an error in custom data
1126 * @param String $errorMessage A string containing all the error-fields.
1128 * @param null $csType
1129 * @param null $relationships
1133 static function isErrorInCustomData($params, &$errorMessage, $csType = NULL, $relationships = NULL) {
1134 $session = CRM_Core_Session
::singleton();
1135 $dateType = $session->get("dateTypes");
1137 if (!empty($params['contact_sub_type'])) {
1138 $csType = CRM_Utils_Array
::value('contact_sub_type', $params);
1141 if (empty($params['contact_type'])) {
1142 $params['contact_type'] = 'Individual';
1144 $customFields = CRM_Core_BAO_CustomField
::getFields($params['contact_type'], FALSE, FALSE, $csType);
1146 $addressCustomFields = CRM_Core_BAO_CustomField
::getFields('Address');
1147 $customFields = $customFields +
$addressCustomFields;
1148 foreach ($params as $key => $value) {
1149 if ($customFieldID = CRM_Core_BAO_CustomField
::getKeyID($key)) {
1150 /* check if it's a valid custom field id */
1152 if (!array_key_exists($customFieldID, $customFields)) {
1153 self
::addToErrorMsg(ts('field ID'), $errorMessage);
1155 // validate null values for required custom fields of type boolean
1156 if (!empty($customFields[$customFieldID]['is_required']) && (empty($params['custom_'.$customFieldID]) && !is_numeric($params['custom_'.$customFieldID])) && $customFields[$customFieldID]['data_type'] == 'Boolean') {
1157 self
::addToErrorMsg($customFields[$customFieldID]['label'].'::'.$customFields[$customFieldID]['groupTitle'], $errorMessage);
1160 //For address custom fields, we do get actual custom field value as an inner array of
1161 //values so need to modify
1162 if (array_key_exists($customFieldID, $addressCustomFields)) {
1163 $value = $value[0][$key];
1165 /* validate the data against the CF type */
1168 if ($customFields[$customFieldID]['data_type'] == 'Date') {
1169 if (array_key_exists($customFieldID, $addressCustomFields) && CRM_Utils_Date
::convertToDefaultDate($params[$key][0], $dateType, $key)) {
1170 $value = $params[$key][0][$key];
1172 else if (CRM_Utils_Date
::convertToDefaultDate($params, $dateType, $key)) {
1173 $value = $params[$key];
1176 self
::addToErrorMsg($customFields[$customFieldID]['label'], $errorMessage);
1179 elseif ($customFields[$customFieldID]['data_type'] == 'Boolean') {
1180 if (CRM_Utils_String
::strtoboolstr($value) === FALSE) {
1181 self
::addToErrorMsg($customFields[$customFieldID]['label'].'::'.$customFields[$customFieldID]['groupTitle'], $errorMessage);
1184 // need not check for label filed import
1191 'Multi-Select State/Province',
1192 'Multi-Select Country',
1194 if (!in_array($customFields[$customFieldID]['html_type'], $htmlType) ||
$customFields[$customFieldID]['data_type'] == 'Boolean' ||
$customFields[$customFieldID]['data_type'] == 'ContactReference') {
1195 $valid = CRM_Core_BAO_CustomValue
::typecheck($customFields[$customFieldID]['data_type'], $value);
1197 self
::addToErrorMsg($customFields[$customFieldID]['label'], $errorMessage);
1201 // check for values for custom fields for checkboxes and multiselect
1202 if ($customFields[$customFieldID]['html_type'] == 'CheckBox' ||
$customFields[$customFieldID]['html_type'] == 'AdvMulti-Select' ||
$customFields[$customFieldID]['html_type'] == 'Multi-Select') {
1203 $value = trim($value);
1204 $value = str_replace('|', ',', $value);
1205 $mulValues = explode(',', $value);
1206 $customOption = CRM_Core_BAO_CustomOption
::getCustomOption($customFieldID, TRUE);
1207 foreach ($mulValues as $v1) {
1208 if (strlen($v1) == 0) {
1213 foreach ($customOption as $v2) {
1214 if ((strtolower(trim($v2['label'])) == strtolower(trim($v1))) ||
(strtolower(trim($v2['value'])) == strtolower(trim($v1)))) {
1220 self
::addToErrorMsg($customFields[$customFieldID]['label'], $errorMessage);
1224 elseif ($customFields[$customFieldID]['html_type'] == 'Select' ||
($customFields[$customFieldID]['html_type'] == 'Radio' && $customFields[$customFieldID]['data_type'] != 'Boolean')) {
1225 $customOption = CRM_Core_BAO_CustomOption
::getCustomOption($customFieldID, TRUE);
1227 foreach ($customOption as $v2) {
1228 if ((strtolower(trim($v2['label'])) == strtolower(trim($value))) ||
(strtolower(trim($v2['value'])) == strtolower(trim($value)))) {
1233 self
::addToErrorMsg($customFields[$customFieldID]['label'], $errorMessage);
1236 elseif ($customFields[$customFieldID]['html_type'] == 'Multi-Select State/Province') {
1237 $mulValues = explode(',', $value);
1238 foreach ($mulValues as $stateValue) {
1240 if (self
::in_value(trim($stateValue), CRM_Core_PseudoConstant
::stateProvinceAbbreviation()) || self
::in_value(trim($stateValue), CRM_Core_PseudoConstant
::stateProvince())) {
1244 self
::addToErrorMsg($customFields[$customFieldID]['label'], $errorMessage);
1249 elseif ($customFields[$customFieldID]['html_type'] == 'Multi-Select Country') {
1250 $mulValues = explode(',', $value);
1251 foreach ($mulValues as $countryValue) {
1252 if ($countryValue) {
1253 CRM_Core_PseudoConstant
::populate($countryNames, 'CRM_Core_DAO_Country', TRUE, 'name', 'is_active');
1254 CRM_Core_PseudoConstant
::populate($countryIsoCodes, 'CRM_Core_DAO_Country', TRUE, 'iso_code');
1255 $config = CRM_Core_Config
::singleton();
1256 $limitCodes = $config->countryLimit();
1264 if (in_array(trim($countryValue), $values)) {
1271 self
::addToErrorMsg($customFields[$customFieldID]['label'], $errorMessage);
1278 elseif (is_array($params[$key]) && isset($params[$key]["contact_type"])) {
1280 //supporting custom data of related contact subtypes
1282 if ($relationships) {
1283 if (array_key_exists($key, $relationships)) {
1286 elseif (CRM_Utils_Array
::key($key, $relationships)) {
1287 $relation = CRM_Utils_Array
::key($key, $relationships);
1290 if (!empty($relation)) {
1291 list($id, $first, $second) = CRM_Utils_System
::explode('_', $relation, 3);
1292 $direction = "contact_sub_type_$second";
1293 $relationshipType = new CRM_Contact_BAO_RelationshipType();
1294 $relationshipType->id
= $id;
1295 if ($relationshipType->find(TRUE)) {
1296 if (isset($relationshipType->$direction)) {
1297 $params[$key]['contact_sub_type'] = $relationshipType->$direction;
1300 $relationshipType->free();
1303 self
::isErrorInCustomData($params[$key], $errorMessage, $csType, $relationships);
1309 * Check if value present in all genders or
1310 * as a substring of any gender value, if yes than return corresponding gender.
1311 * eg value might be m/M, ma/MA, mal/MAL, male return 'Male'
1312 * but if value is 'maleabc' than return false
1314 * @param string $gender check this value across gender values.
1316 * retunr gender value / false
1321 public function checkGender($gender) {
1322 $gender = trim($gender, '.');
1327 $allGenders = CRM_Core_PseudoConstant
::get('CRM_Contact_DAO_Contact', 'gender_id');
1328 foreach ($allGenders as $key => $value) {
1329 if (strlen($gender) > strlen($value)) {
1332 if ($gender == $value) {
1335 if (substr_compare($value, $gender, 0, strlen($gender), TRUE) === 0) {
1344 * function to check if an error in Core( non-custom fields ) field
1347 * @param String $errorMessage A string containing all the error-fields.
1351 function isErrorInCoreData($params, &$errorMessage) {
1352 foreach ($params as $key => $value) {
1354 $session = CRM_Core_Session
::singleton();
1355 $dateType = $session->get("dateTypes");
1359 if (CRM_Utils_Date
::convertToDefaultDate($params, $dateType, $key)) {
1360 if (!CRM_Utils_Rule
::date($params[$key])) {
1361 self
::addToErrorMsg(ts('Birth Date'), $errorMessage);
1365 self
::addToErrorMsg(ts('Birth-Date'), $errorMessage);
1369 case 'deceased_date':
1370 if (CRM_Utils_Date
::convertToDefaultDate($params, $dateType, $key)) {
1371 if (!CRM_Utils_Rule
::date($params[$key])) {
1372 self
::addToErrorMsg(ts('Deceased Date'), $errorMessage);
1376 self
::addToErrorMsg(ts('Deceased Date'), $errorMessage);
1381 if (CRM_Utils_String
::strtoboolstr($value) === FALSE) {
1382 self
::addToErrorMsg(ts('Is Deceased'), $errorMessage);
1388 if (!self
::checkGender($value)) {
1389 self
::addToErrorMsg(ts('Gender'), $errorMessage);
1393 case 'preferred_communication_method':
1394 $preffComm = array();
1395 $preffComm = explode(',', $value);
1396 foreach ($preffComm as $v) {
1397 if (!self
::in_value(trim($v), CRM_Core_PseudoConstant
::get('CRM_Contact_DAO_Contact', 'preferred_communication_method'))) {
1398 self
::addToErrorMsg(ts('Preferred Communication Method'), $errorMessage);
1403 case 'preferred_mail_format':
1404 if (!array_key_exists(strtolower($value), array_change_key_case(CRM_Core_SelectValues
::pmf(), CASE_LOWER
))) {
1405 self
::addToErrorMsg(ts('Preferred Mail Format'), $errorMessage);
1409 case 'individual_prefix':
1411 if (!self
::in_value($value, CRM_Core_PseudoConstant
::get('CRM_Contact_DAO_Contact', 'prefix_id'))) {
1412 self
::addToErrorMsg(ts('Individual Prefix'), $errorMessage);
1416 case 'individual_suffix':
1418 if (!self
::in_value($value, CRM_Core_PseudoConstant
::get('CRM_Contact_DAO_Contact', 'suffix_id'))) {
1419 self
::addToErrorMsg(ts('Individual Suffix'), $errorMessage);
1423 case 'state_province':
1424 if (!empty($value)) {
1425 foreach ($value as $stateValue) {
1426 if ($stateValue['state_province']) {
1427 if (self
::in_value($stateValue['state_province'], CRM_Core_PseudoConstant
::stateProvinceAbbreviation()) ||
1428 self
::in_value($stateValue['state_province'], CRM_Core_PseudoConstant
::stateProvince())) {
1432 self
::addToErrorMsg(ts('State / Province'), $errorMessage);
1440 if (!empty($value)) {
1441 foreach ($value as $stateValue) {
1442 if ($stateValue['country']) {
1443 CRM_Core_PseudoConstant
::populate($countryNames, 'CRM_Core_DAO_Country', TRUE, 'name', 'is_active');
1444 CRM_Core_PseudoConstant
::populate($countryIsoCodes, 'CRM_Core_DAO_Country', TRUE, 'iso_code');
1445 $config = CRM_Core_Config
::singleton();
1446 $limitCodes = $config->countryLimit();
1447 //If no country is selected in
1448 //localization then take all countries
1449 if (empty($limitCodes)) {
1450 $limitCodes = $countryIsoCodes;
1453 if (self
::in_value($stateValue['country'], $limitCodes) || self
::in_value($stateValue['country'], CRM_Core_PseudoConstant
::country())) {
1457 if (self
::in_value($stateValue['country'], $countryIsoCodes) || self
::in_value($stateValue['country'], $countryNames)) {
1458 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 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);
1470 if (!empty($value)) {
1471 foreach ($value as $county) {
1472 if ($county['county']) {
1473 $countyNames = CRM_Core_PseudoConstant
::county();
1474 if (!empty($county['county']) && !in_array($county['county'], $countyNames)) {
1475 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);
1483 if (!empty($value)) {
1484 foreach ($value as $codeValue) {
1485 if (!empty($codeValue['geo_code_1'])) {
1486 if (CRM_Utils_Rule
::numeric($codeValue['geo_code_1'])) {
1490 self
::addToErrorMsg(ts('Geo code 1'), $errorMessage);
1498 if (!empty($value)) {
1499 foreach ($value as $codeValue) {
1500 if (!empty($codeValue['geo_code_2'])) {
1501 if (CRM_Utils_Rule
::numeric($codeValue['geo_code_2'])) {
1505 self
::addToErrorMsg(ts('Geo code 2'), $errorMessage);
1511 //check for any error in email/postal greeting, addressee,
1512 //custom email/postal greeting, custom addressee, CRM-4575
1514 case 'email_greeting':
1515 $emailGreetingFilter = array(
1516 'contact_type' => $this->_contactType
,
1517 'greeting_type' => 'email_greeting',
1519 if (!self
::in_value($value, CRM_Core_PseudoConstant
::greeting($emailGreetingFilter))) {
1520 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 case 'postal_greeting':
1525 $postalGreetingFilter = array(
1526 'contact_type' => $this->_contactType
,
1527 'greeting_type' => 'postal_greeting',
1529 if (!self
::in_value($value, CRM_Core_PseudoConstant
::greeting($postalGreetingFilter))) {
1530 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);
1535 $addresseeFilter = array(
1536 'contact_type' => $this->_contactType
,
1537 'greeting_type' => 'addressee',
1539 if (!self
::in_value($value, CRM_Core_PseudoConstant
::greeting($addresseeFilter))) {
1540 self
::addToErrorMsg(ts('Addressee must be one of the configured format options. Check Administer >> System Settings >> Option Groups >> Addressee for valid values'), $errorMessage);
1544 case 'email_greeting_custom':
1545 if (array_key_exists('email_greeting', $params)) {
1546 $emailGreetingLabel = key(CRM_Core_OptionGroup
::values('email_greeting', TRUE, NULL, NULL, 'AND v.name = "Customized"'));
1547 if (CRM_Utils_Array
::value('email_greeting', $params) != $emailGreetingLabel) {
1548 self
::addToErrorMsg(ts('Email Greeting - Custom'), $errorMessage);
1553 case 'postal_greeting_custom':
1554 if (array_key_exists('postal_greeting', $params)) {
1555 $postalGreetingLabel = key(CRM_Core_OptionGroup
::values('postal_greeting', TRUE, NULL, NULL, 'AND v.name = "Customized"'));
1556 if (CRM_Utils_Array
::value('postal_greeting', $params) != $postalGreetingLabel) {
1557 self
::addToErrorMsg(ts('Postal Greeting - Custom'), $errorMessage);
1562 case 'addressee_custom':
1563 if (array_key_exists('addressee', $params)) {
1564 $addresseeLabel = key(CRM_Core_OptionGroup
::values('addressee', TRUE, NULL, NULL, 'AND v.name = "Customized"'));
1565 if (CRM_Utils_Array
::value('addressee', $params) != $addresseeLabel) {
1566 self
::addToErrorMsg(ts('Addressee - Custom'), $errorMessage);
1572 if (is_array($value)) {
1573 foreach ($value as $values) {
1574 if (!empty($values['url']) && !CRM_Utils_Rule
::url($values['url'])) {
1575 self
::addToErrorMsg(ts('Website'), $errorMessage);
1582 case 'do_not_email':
1583 case 'do_not_phone':
1586 case 'do_not_trade':
1587 if (CRM_Utils_Rule
::boolean($value) == FALSE) {
1588 $key = ucwords(str_replace("_", " ", $key));
1589 self
::addToErrorMsg($key, $errorMessage);
1594 if (is_array($value)) {
1595 foreach ($value as $values) {
1596 if (!empty($values['email']) && !CRM_Utils_Rule
::email($values['email'])) {
1597 self
::addToErrorMsg($key, $errorMessage);
1605 if (is_array($params[$key]) && isset($params[$key]["contact_type"])) {
1606 //check for any relationship data ,FIX ME
1607 self
::isErrorInCoreData($params[$key], $errorMessage);
1615 * function to ckeck a value present or not in a array
1618 * @param $valueArray
1620 * @return ture if value present in array or retun false
1624 function in_value($value, $valueArray) {
1625 foreach ($valueArray as $key => $v) {
1627 if (strtolower(trim($v, ".")) == strtolower(trim($value, "."))) {
1635 * function to build error-message containing error-fields
1637 * @param String $errorName A string containing error-field name.
1638 * @param String $errorMessage A string containing all the error-fields, where the new errorName is concatenated.
1643 static function addToErrorMsg($errorName, &$errorMessage) {
1644 if ($errorMessage) {
1645 $errorMessage .= "; $errorName";
1648 $errorMessage = $errorName;
1653 * method for creating contact
1657 function createContact(&$formatted, &$contactFields, $onDuplicate, $contactId = NULL, $requiredCheck = TRUE, $dedupeRuleGroupID = NULL) {
1662 if (is_null($contactId) && ($onDuplicate != CRM_Import_Parser
::DUPLICATE_NOCHECK
)) {
1663 $dupeCheck = (bool)($onDuplicate);
1666 //get the prefix id etc if exists
1667 CRM_Contact_BAO_Contact
::resolveDefaults($formatted, TRUE);
1669 require_once 'CRM/Utils/DeprecatedUtils.php';
1670 //@todo direct call to API function not supported.
1671 // setting required check to false, CRM-2839
1672 // plus we do our own required check in import
1673 $error = _civicrm_api3_deprecated_contact_check_params($formatted, $dupeCheck, TRUE, FALSE, $dedupeRuleGroupID);
1675 if ((is_null($error)) && (civicrm_error(_civicrm_api3_deprecated_validate_formatted_contact($formatted)))) {
1676 $error = _civicrm_api3_deprecated_validate_formatted_contact($formatted);
1679 $newContact = $error;
1681 if (is_null($error)) {
1683 $this->formatParams($formatted, $onDuplicate, (int) $contactId);
1686 // pass doNotResetCache flag since resetting and rebuilding cache could be expensive.
1687 $config = CRM_Core_Config
::singleton();
1688 $config->doNotResetCache
= 1;
1689 $cid = CRM_Contact_BAO_Contact
::createProfileContact($formatted, $contactFields, $contactId, NULL, NULL, $formatted['contact_type']);
1690 $config->doNotResetCache
= 0;
1693 'contact_id' => $cid,
1696 $defaults = array();
1697 $newContact = CRM_Contact_BAO_Contact
::retrieve($contact, $defaults);
1700 //get the id of the contact whose street address is not parsable, CRM-5886
1701 if ($this->_parseStreetAddress
&& is_object($newContact) && property_exists($newContact, 'address') && $newContact->address
) {
1702 foreach ($newContact->address
as $address) {
1703 if (!empty($address['street_address']) && (empty($address['street_number']) ||
empty($address['street_name']))) {
1704 $this->_unparsedStreetAddressContacts
[] = array(
1705 'id' => $newContact->id
,
1706 'streetAddress' => $address['street_address'],
1715 * format params for update and fill mode
1717 * @param $params array referance to an array containg all the
1719 * @param $onDuplicate int
1720 * @param $cid int contact id
1722 function formatParams(&$params, $onDuplicate, $cid) {
1723 if ($onDuplicate == CRM_Import_Parser
::DUPLICATE_SKIP
) {
1727 $contactParams = array(
1728 'contact_id' => $cid,
1731 $defaults = array();
1732 $contactObj = CRM_Contact_BAO_Contact
::retrieve($contactParams, $defaults);
1734 $modeUpdate = $modeFill = FALSE;
1736 if ($onDuplicate == CRM_Import_Parser
::DUPLICATE_UPDATE
) {
1740 if ($onDuplicate == CRM_Import_Parser
::DUPLICATE_FILL
) {
1744 $groupTree = CRM_Core_BAO_CustomGroup
::getTree($params['contact_type'], CRM_Core_DAO
::$_nullObject, $cid, 0, NULL);
1745 CRM_Core_BAO_CustomGroup
::setDefaults($groupTree, $defaults, FALSE, FALSE);
1747 $locationFields = array(
1751 'website' => 'website',
1752 'address' => 'address',
1755 $contact = get_object_vars($contactObj);
1757 foreach ($params as $key => $value) {
1758 if ($key == 'id' ||
$key == 'contact_type') {
1762 if (array_key_exists($key, $locationFields)) {
1765 elseif (in_array($key, array(
1770 // CRM-4575, need to null custom
1771 if ($params["{$key}_id"] != 4) {
1772 $params["{$key}_custom"] = 'null';
1774 unset($params[$key]);
1776 elseif ($customFieldId = CRM_Core_BAO_CustomField
::getKeyID($key)) {
1780 $getValue = CRM_Utils_Array
::retrieveValueRecursive($contact, $key);
1782 if ($key == 'contact_source') {
1783 $params['source'] = $params[$key];
1784 unset($params[$key]);
1787 if ($modeFill && isset($getValue)) {
1788 unset($params[$key]);
1793 foreach ($locationFields as $locKeys) {
1794 if (is_array(CRM_Utils_Array
::value($locKeys, $params))) {
1795 foreach ($params[$locKeys] as $key => $value) {
1797 $getValue = CRM_Utils_Array
::retrieveValueRecursive($contact, $locKeys);
1799 if (isset($getValue)) {
1800 foreach ($getValue as $cnt => $values) {
1801 if ($locKeys == 'website') {
1802 if (($getValue[$cnt]['website_type_id'] == $params[$locKeys][$key]['website_type_id'])) {
1803 unset($params[$locKeys][$key]);
1807 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']) {
1808 unset($params[$locKeys][$key]);
1815 if (count($params[$locKeys]) == 0) {
1816 unset($params[$locKeys]);
1823 * convert any given date string to default date array.
1825 * @param array $params has given date-format
1826 * @param array $formatted store formatted date in this array
1827 * @param int $dateType type of date
1828 * @param string $dateParam index of params
1831 function formatCustomDate(&$params, &$formatted, $dateType, $dateParam) {
1833 CRM_Utils_Date
::convertToDefaultDate($params, $dateType, $dateParam);
1834 $formatted[$dateParam] = CRM_Utils_Date
::processDate($params[$dateParam]);
1838 * format common params data to proper format to store.
1840 * @param array $params contain record values.
1841 * @param array $formatted array of formatted data.
1842 * @param array $contactFields contact DAO fields.
1845 function formatCommonData($params, &$formatted, &$contactFields) {
1847 CRM_Utils_Array
::value('contact_type', $formatted),
1851 //add custom fields for contact sub type
1852 if (!empty($this->_contactSubType
)) {
1853 $csType = $this->_contactSubType
;
1856 if ($relCsType = CRM_Utils_Array
::value('contact_sub_type', $formatted)) {
1857 $csType = $relCsType;
1860 $customFields = CRM_Core_BAO_CustomField
::getFields($formatted['contact_type'], FALSE, FALSE, $csType);
1862 $addressCustomFields = CRM_Core_BAO_CustomField
::getFields('Address');
1863 $customFields = $customFields +
$addressCustomFields;
1865 //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
1867 'email_greeting_custom' => 'email_greeting',
1868 'postal_greeting_custom' => 'postal_greeting',
1869 'addressee_custom' => 'addressee',
1871 foreach ($elements as $k => $v) {
1872 if (array_key_exists($k, $params) && !(array_key_exists($v, $params))) {
1873 $label = key(CRM_Core_OptionGroup
::values($v, TRUE, NULL, NULL, 'AND v.name = "Customized"'));
1874 $params[$v] = $label;
1879 $session = CRM_Core_Session
::singleton();
1880 $dateType = $session->get("dateTypes");
1881 foreach ($params as $key => $val) {
1882 $customFieldID = CRM_Core_BAO_CustomField
::getKeyID($key);
1883 if ($customFieldID &&
1884 !array_key_exists($customFieldID, $addressCustomFields)) {
1885 //we should not update Date to null, CRM-4062
1886 if ($val && ($customFields[$customFieldID]['data_type'] == 'Date')) {
1887 self
::formatCustomDate($params, $formatted, $dateType, $key);
1889 elseif ($customFields[$customFieldID]['data_type'] == 'Boolean') {
1890 if (empty($val) && !is_numeric($val) && $this->_onDuplicate
== CRM_Import_Parser
::DUPLICATE_FILL
) {
1891 //retain earlier value when Import mode is `Fill`
1892 unset($params[$key]);
1895 $params[$key] = CRM_Utils_String
::strtoboolstr($val);
1899 if ($key == 'birth_date' && $val) {
1900 CRM_Utils_Date
::convertToDefaultDate($params, $dateType, $key);
1902 elseif ($key == 'deceased_date' && $val) {
1903 CRM_Utils_Date
::convertToDefaultDate($params, $dateType, $key);
1905 elseif ($key == 'is_deceased' && $val) {
1906 $params[$key] = CRM_Utils_String
::strtoboolstr($val);
1908 elseif ($key == 'gender') {
1910 $params[$key] = $this->checkGender($val);
1915 //now format custom data.
1916 foreach ($params as $key => $field) {
1917 if (is_array($field)) {
1918 $isAddressCustomField = FALSE;
1919 foreach ($field as $value) {
1921 if (is_array($value)) {
1922 foreach ($value as $name => $testForEmpty) {
1923 if ($addressCustomFieldID = CRM_Core_BAO_CustomField
::getKeyID($name)) {
1924 $isAddressCustomField = TRUE;
1927 // check if $value does not contain IM provider or phoneType
1928 if (($name !== 'phone_type_id' ||
$name !== 'provider_id') && ($testForEmpty === '' ||
$testForEmpty == NULL)) {
1939 require_once 'CRM/Utils/DeprecatedUtils.php';
1940 _civicrm_api3_deprecated_add_formatted_param($value, $formatted);
1943 if (!$isAddressCustomField) {
1948 $formatValues = array(
1952 if (($key !== 'preferred_communication_method') && (array_key_exists($key, $contactFields))) {
1953 // due to merging of individual table and
1954 // contact table, we need to avoid
1955 // preferred_communication_method forcefully
1956 $formatValues['contact_type'] = $formatted['contact_type'];
1959 if ($key == 'id' && isset($field)) {
1960 $formatted[$key] = $field;
1962 require_once 'CRM/Utils/DeprecatedUtils.php';
1963 _civicrm_api3_deprecated_add_formatted_param($formatValues, $formatted);
1965 //Handling Custom Data
1966 // note: Address custom fields will be handled separately inside _civicrm_api3_deprecated_add_formatted_param
1967 if (($customFieldID = CRM_Core_BAO_CustomField
::getKeyID($key)) &&
1968 array_key_exists($customFieldID, $customFields) &&
1969 !array_key_exists($customFieldID, $addressCustomFields)) {
1971 $extends = CRM_Utils_Array
::value('extends', $customFields[$customFieldID]);
1972 $htmlType = CRM_Utils_Array
::value( 'html_type', $customFields[$customFieldID] );
1973 switch ( $htmlType ) {
1976 case 'Autocomplete-Select':
1977 if ($customFields[$customFieldID]['data_type'] == 'String') {
1978 $customOption = CRM_Core_BAO_CustomOption
::getCustomOption($customFieldID, TRUE);
1979 foreach ($customOption as $customFldID => $customValue) {
1980 $val = CRM_Utils_Array
::value('value', $customValue);
1981 $label = CRM_Utils_Array
::value('label', $customValue);
1982 $label = strtolower($label);
1983 $value = strtolower(trim($formatted[$key]));
1984 if (($value == $label) ||
($value == strtolower($val))) {
1985 $params[$key] = $formatted[$key] = $val;
1991 case 'AdvMulti-Select':
1992 case 'Multi-Select':
1994 if (!empty($formatted[$key]) && !empty($params[$key])) {
1995 $mulValues = explode( ',', $formatted[$key] );
1996 $customOption = CRM_Core_BAO_CustomOption
::getCustomOption( $customFieldID, true );
1997 $formatted[$key] = array( );
1998 $params[$key] = array( );
1999 foreach ( $mulValues as $v1 ) {
2000 foreach ( $customOption as $v2 ) {
2001 if ( ( strtolower( $v2['label'] ) == strtolower( trim( $v1 ) ) ) ||
2002 ( strtolower( $v2['value'] ) == strtolower( trim( $v1 ) ) ) ) {
2003 if ( $htmlType == 'CheckBox' ) {
2004 $params[$key][$v2['value']] = $formatted[$key][$v2['value']] = 1;
2006 $params[$key][] = $formatted[$key][] = $v2['value'];
2017 if (($customFieldID = CRM_Core_BAO_CustomField
::getKeyID($key)) && array_key_exists($customFieldID, $customFields) &&
2018 !array_key_exists($customFieldID, $addressCustomFields)) {
2019 // @todo calling api functions directly is not supported
2020 _civicrm_api3_custom_format_params($params, $formatted, $extends);
2023 // to check if not update mode and unset the fields with empty value.
2024 if (!$this->_updateWithId
&& array_key_exists('custom', $formatted)) {
2025 foreach ($formatted['custom'] as $customKey => $customvalue) {
2026 if (empty($formatted['custom'][$customKey][- 1]['is_required'])) {
2027 $formatted['custom'][$customKey][-1]['is_required'] = $customFields[$customKey]['is_required'];
2029 $emptyValue = CRM_Utils_Array
::value('value', $customvalue[ - 1]);
2030 if (!isset($emptyValue)) {
2031 unset($formatted['custom'][$customKey]);
2036 // parse street address, CRM-5450
2037 if ($this->_parseStreetAddress
) {
2038 if (array_key_exists('address', $formatted) && is_array($formatted['address'])) {
2039 foreach ($formatted['address'] as $instance => & $address) {
2040 $streetAddress = CRM_Utils_Array
::value('street_address', $address);
2041 if (empty($streetAddress)) {
2044 // parse address field.
2045 $parsedFields = CRM_Core_BAO_Address
::parseStreetAddress($streetAddress);
2047 //street address consider to be parsed properly,
2048 //If we get street_name and street_number.
2049 if (empty($parsedFields['street_name']) ||
empty($parsedFields['street_number'])) {
2050 $parsedFields = array_fill_keys(array_keys($parsedFields), '');
2053 // merge parse address w/ main address block.
2054 $address = array_merge($address, $parsedFields);
2061 * Function to generate status and error message for unparsed street address records.
2063 * @param array $values the array of values belonging to each row
2064 * @param array $statusFieldName store formatted date in this array
2065 * @param $returnCode
2070 function processMessage(&$values, $statusFieldName, $returnCode) {
2071 if (empty($this->_unparsedStreetAddressContacts
)) {
2072 $importRecordParams = array(
2073 $statusFieldName => 'IMPORTED',
2077 $errorMessage = ts("Record imported successfully but unable to parse the street address: ");
2078 foreach ($this->_unparsedStreetAddressContacts
as $contactInfo => $contactValue) {
2079 $contactUrl = CRM_Utils_System
::url('civicrm/contact/add', 'reset=1&action=update&cid=' . $contactValue['id'], TRUE, NULL, FALSE);
2080 $errorMessage .= "\n Contact ID:" . $contactValue['id'] . " <a href=\"$contactUrl\"> " . $contactValue['streetAddress'] . "</a>";
2082 array_unshift($values, $errorMessage);
2083 $importRecordParams = array(
2084 $statusFieldName => 'ERROR',
2085 "${statusFieldName}Msg" => $errorMessage,
2087 $returnCode = CRM_Import_Parser
::UNPARSED_ADDRESS_WARNING
;
2089 $this->updateImportRecord($values[count($values) - 1], $importRecordParams);
2099 function checkRelatedContactFields($relKey, $params) {
2100 //avoid blank contact creation.
2101 $allowToCreate = FALSE;
2103 //build the mapper field array.
2104 static $relatedContactFields = array();
2105 if (!isset($relatedContactFields[$relKey])) {
2106 foreach ($this->_mapperRelated
as $key => $name) {
2111 if (!empty($relatedContactFields[$name]) && !is_array($relatedContactFields[$name])) {
2112 $relatedContactFields[$name] = array();
2114 $fldName = CRM_Utils_Array
::value($key, $this->_mapperRelatedContactDetails
);
2115 if ($fldName == 'url') {
2116 $fldName = 'website';
2119 $relatedContactFields[$name][] = $fldName;
2124 //validate for passed data.
2125 if (is_array($relatedContactFields[$relKey])) {
2126 foreach ($relatedContactFields[$relKey] as $fld) {
2127 if (!empty($params[$fld])) {
2128 $allowToCreate = TRUE;
2134 return $allowToCreate;