4 * Class CRM_Custom_Import_Parser_Api
6 class CRM_Custom_Import_Parser_Api
extends CRM_Import_Parser
{
8 protected $_entity = '';
9 protected $_fields = [];
10 protected $_requiredFields = [];
11 protected $_dateFields = [];
12 protected $_multipleCustomData = '';
15 * Params for the current entity being prepared for the api.
18 protected $_params = [];
30 * Separator being used
33 protected $_separator;
36 * Total number of lines in file
39 protected $_lineCount;
42 * Whether the file has a column header or not
46 protected $_haveColumnHeader;
51 * @param array $mapperKeys
52 * @param null $mapperLocType
53 * @param null $mapperPhoneType
55 public function __construct(&$mapperKeys = [], $mapperLocType = NULL, $mapperPhoneType = NULL) {
56 parent
::__construct();
57 $this->_mapperKeys
= &$mapperKeys;
60 public function setFields() {
61 $customGroupID = $this->_multipleCustomData
;
62 $importableFields = $this->getGroupFieldsForImport($customGroupID, $this);
63 $this->_fields
= array_merge([
64 'do_not_import' => ['title' => ts('- do not import -')],
65 'contact_id' => ['title' => ts('Contact ID')],
66 'external_identifier' => ['title' => ts('External Identifier')],
67 ], $importableFields);
71 * The initializer code, called before the processing
75 public function init() {
77 $fields = $this->_fields
;
78 $hasLocationType = FALSE;
80 foreach ($fields as $name => $field) {
81 $field['type'] = CRM_Utils_Array
::value('type', $field, CRM_Utils_Type
::T_INT
);
82 $field['dataPattern'] = CRM_Utils_Array
::value('dataPattern', $field, '//');
83 $field['headerPattern'] = CRM_Utils_Array
::value('headerPattern', $field, '//');
84 $this->addField($name, $field['title'], $field['type'], $field['headerPattern'], $field['dataPattern'], $hasLocationType);
86 $this->setActiveFields($this->_mapperKeys
);
90 * Handle the values in preview mode.
92 * @param array $values
93 * The array of values belonging to this line.
96 * the result of this processing
98 public function preview(&$values) {
99 return $this->summary($values);
103 * @param array $values
104 * The array of values belonging to this line.
107 * the result of this processing
108 * It is called from both the preview & the import actions
110 * @see CRM_Custom_Import_Parser_BaseClass::summary()
112 public function summary(&$values) {
113 $this->setActiveFieldValues($values);
114 $errorRequired = FALSE;
116 $this->_params
= &$this->getActiveFieldParams();
118 $this->_updateWithId
= FALSE;
119 $this->_parseStreetAddress
= CRM_Utils_Array
::value('street_address_parsing', CRM_Core_BAO_Setting
::valueOptions(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
, 'address_options'), FALSE);
121 $this->_params
= $this->getActiveFieldParams();
122 foreach ($this->_requiredFields
as $requiredField) {
123 if (empty($this->_params
[$requiredField])) {
124 $errorRequired = TRUE;
125 $missingField .= ' ' . $requiredField;
126 CRM_Contact_Import_Parser_Contact
::addToErrorMsg($this->_entity
, $requiredField);
130 if ($errorRequired) {
131 array_unshift($values, ts('Missing required field(s) :') . $missingField);
132 return CRM_Import_Parser
::ERROR
;
135 $errorMessage = NULL;
137 $contactType = $this->_contactType ?
$this->_contactType
: 'Organization';
138 CRM_Contact_Import_Parser_Contact
::isErrorInCustomData($this->_params +
['contact_type' => $contactType], $errorMessage, $this->_contactSubType
, NULL);
142 $tempMsg = "Invalid value for field(s) : $errorMessage";
143 array_unshift($values, $tempMsg);
144 $errorMessage = NULL;
145 return CRM_Import_Parser
::ERROR
;
147 return CRM_Import_Parser
::VALID
;
151 * Handle the values in import mode.
153 * @param int $onDuplicate
154 * The code for what action to take on duplicates.
155 * @param array $values
156 * The array of values belonging to this line.
159 * the result of this processing
161 public function import($onDuplicate, &$values) {
162 $response = $this->summary($values);
163 if ($response != CRM_Import_Parser
::VALID
) {
167 $this->_updateWithId
= FALSE;
168 $this->_parseStreetAddress
= CRM_Utils_Array
::value('street_address_parsing', CRM_Core_BAO_Setting
::valueOptions(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
, 'address_options'), FALSE);
170 $contactType = $this->_contactType ?
$this->_contactType
: 'Organization';
172 'contact_type' => $contactType,
175 if (isset($this->_params
['external_identifier']) && !isset($this->_params
['contact_id'])) {
176 $checkCid = new CRM_Contact_DAO_Contact();
177 $checkCid->external_identifier
= $this->_params
['external_identifier'];
178 $checkCid->find(TRUE);
179 $formatted['id'] = $checkCid->id
;
182 $formatted['id'] = $this->_params
['contact_id'];
185 $this->formatCommonData($this->_params
, $formatted);
186 foreach ($formatted['custom'] as $key => $val) {
187 $this->_params
['custom_' . $key] = $val[-1]['value'];
189 $this->_params
['skipRecentView'] = TRUE;
190 $this->_params
['check_permissions'] = TRUE;
191 $this->_params
['entity_id'] = $formatted['id'];
193 civicrm_api3('custom_value', 'create', $this->_params
);
195 catch (CiviCRM_API3_Exception
$e) {
196 $error = $e->getMessage();
197 array_unshift($values, $error);
198 return CRM_Import_Parser
::ERROR
;
203 * Adapted from CRM_Contact_Import_Parser_Contact::formatCommonData
205 * TODO: Is this function even necessary? All values get passed to the api anyway.
207 * @param array $params
208 * Contain record values.
209 * @param array $formatted
210 * Array of formatted data.
212 private function formatCommonData($params, &$formatted) {
214 $customFields = CRM_Core_BAO_CustomField
::getFields(NULL);
217 $session = CRM_Core_Session
::singleton();
218 $dateType = $session->get("dateTypes");
219 foreach ($params as $key => $val) {
220 $customFieldID = CRM_Core_BAO_CustomField
::getKeyID($key);
221 if ($customFieldID) {
222 //we should not update Date to null, CRM-4062
223 if ($val && ($customFields[$customFieldID]['data_type'] == 'Date')) {
225 CRM_Contact_Import_Parser_Contact
::formatCustomDate($params, $formatted, $dateType, $key);
227 elseif ($customFields[$customFieldID]['data_type'] == 'Boolean') {
228 if (empty($val) && !is_numeric($val)) {
229 //retain earlier value when Import mode is `Fill`
230 unset($params[$key]);
233 $params[$key] = CRM_Utils_String
::strtoboolstr($val);
239 //now format custom data.
240 foreach ($params as $key => $field) {
242 if ($key == 'id' && isset($field)) {
243 $formatted[$key] = $field;
246 //Handling Custom Data
247 if (($customFieldID = CRM_Core_BAO_CustomField
::getKeyID($key)) &&
248 array_key_exists($customFieldID, $customFields)
251 $extends = $customFields[$customFieldID]['extends'] ??
NULL;
252 $htmlType = $customFields[$customFieldID]['html_type'] ??
NULL;
253 $dataType = $customFields[$customFieldID]['data_type'] ??
NULL;
254 $serialized = CRM_Core_BAO_CustomField
::isSerialized($customFields[$customFieldID]);
256 if (!$serialized && in_array($htmlType, ['Select', 'Radio', 'Autocomplete-Select']) && in_array($dataType, ['String', 'Int'])) {
257 $customOption = CRM_Core_BAO_CustomOption
::getCustomOption($customFieldID, TRUE);
258 foreach ($customOption as $customValue) {
259 $val = $customValue['value'] ??
NULL;
260 $label = strtolower($customValue['label'] ??
'');
261 $value = strtolower(trim($formatted[$key]));
262 if (($value == $label) ||
($value == strtolower($val))) {
263 $params[$key] = $formatted[$key] = $val;
267 elseif ($serialized && !empty($formatted[$key]) && !empty($params[$key])) {
268 $mulValues = explode(',', $formatted[$key]);
269 $customOption = CRM_Core_BAO_CustomOption
::getCustomOption($customFieldID, TRUE);
270 $formatted[$key] = [];
272 foreach ($mulValues as $v1) {
273 foreach ($customOption as $v2) {
274 if ((strtolower($v2['label']) == strtolower(trim($v1))) ||
275 (strtolower($v2['value']) == strtolower(trim($v1)))
277 if ($htmlType == 'CheckBox') {
278 $params[$key][$v2['value']] = $formatted[$key][$v2['value']] = 1;
281 $params[$key][] = $formatted[$key][] = $v2['value'];
290 if (!empty($key) && ($customFieldID = CRM_Core_BAO_CustomField
::getKeyID($key)) && array_key_exists($customFieldID, $customFields)) {
291 // @todo calling api functions directly is not supported
292 _civicrm_api3_custom_format_params($params, $formatted, $extends);
298 * @param string $entity
300 public function setEntity($entity) {
301 $this->_entity
= $entity;
302 $this->_multipleCustomData
= $entity;
306 * Return the field ids and names (with groups) for import purpose.
314 public function getGroupFieldsForImport($id) {
315 $importableFields = [];
316 $params = ['custom_group_id' => $id];
317 $allFields = civicrm_api3('custom_field', 'get', $params);
318 $fields = $allFields['values'];
319 foreach ($fields as $id => $values) {
320 $datatype = $values['data_type'] ??
NULL;
321 if ($datatype == 'File') {
324 /* generate the key for the fields array */
326 $regexp = preg_replace('/[.,;:!?]/', '', CRM_Utils_Array
::value(0, $values));
327 $importableFields[$key] = [
329 'title' => $values['label'] ??
NULL,
330 'headerPattern' => '/' . preg_quote($regexp, '/') . '/',
332 'custom_field_id' => $id,
333 'options_per_line' => $values['options_per_line'] ??
NULL,
334 'data_type' => $values['data_type'] ??
NULL,
335 'html_type' => $values['html_type'] ??
NULL,
336 'is_search_range' => $values['is_search_range'] ??
NULL,
338 if (CRM_Utils_Array
::value('html_type', $values) == 'Select Date') {
339 $importableFields[$key]['date_format'] = $values['date_format'] ??
NULL;
340 $importableFields[$key]['time_format'] = $values['time_format'] ??
NULL;
341 $this->_dateFields
[] = $key;
344 return $importableFields;
348 * @param string $fileName
349 * @param string $separator
351 * @param bool $skipColumnHeader
352 * @param int|string $mode
353 * @param int|string $contactType
354 * @param int $onDuplicate
363 $skipColumnHeader = FALSE,
364 $mode = self
::MODE_PREVIEW
,
365 $contactType = self
::CONTACT_INDIVIDUAL
,
366 $onDuplicate = self
::DUPLICATE_SKIP
368 if (!is_array($fileName)) {
369 throw new CRM_Core_Exception('Unable to determine import file');
371 $fileName = $fileName['name'];
373 switch ($contactType) {
374 case CRM_Import_Parser
::CONTACT_INDIVIDUAL
:
375 $this->_contactType
= 'Individual';
378 case CRM_Import_Parser
::CONTACT_HOUSEHOLD
:
379 $this->_contactType
= 'Household';
382 case CRM_Import_Parser
::CONTACT_ORGANIZATION
:
383 $this->_contactType
= 'Organization';
387 $this->_haveColumnHeader
= $skipColumnHeader;
389 $this->_separator
= $separator;
391 $fd = fopen($fileName, "r");
396 $this->_lineCount
= $this->_warningCount
= 0;
397 $this->_invalidRowCount
= $this->_validCount
= 0;
398 $this->_totalCount
= 0;
401 $this->_warnings
= [];
403 $this->_fileSize
= number_format(filesize($fileName) / 1024.0, 2);
405 if ($mode == self
::MODE_MAPFIELD
) {
409 $this->_activeFieldCount
= count($this->_activeFields
);
415 $values = fgetcsv($fd, 8192, $separator);
420 self
::encloseScrub($values);
422 // skip column header if we're not in mapfield mode
423 if ($mode != self
::MODE_MAPFIELD
&& $skipColumnHeader) {
424 $skipColumnHeader = FALSE;
428 /* trim whitespace around the values */
431 foreach ($values as $k => $v) {
432 $values[$k] = trim($v, " \t\r\n");
435 if (CRM_Utils_System
::isNull($values)) {
439 $this->_totalCount++
;
441 if ($mode == self
::MODE_MAPFIELD
) {
442 $returnCode = CRM_Import_Parser
::VALID
;
444 elseif ($mode == self
::MODE_PREVIEW
) {
445 $returnCode = $this->preview($values);
447 elseif ($mode == self
::MODE_SUMMARY
) {
448 $returnCode = $this->summary($values);
450 elseif ($mode == self
::MODE_IMPORT
) {
451 $returnCode = $this->import($onDuplicate, $values);
454 $returnCode = self
::ERROR
;
457 // note that a line could be valid but still produce a warning
458 if ($returnCode & self
::VALID
) {
459 $this->_validCount++
;
460 if ($mode == self
::MODE_MAPFIELD
) {
461 $this->_rows
[] = $values;
462 $this->_activeFieldCount
= max($this->_activeFieldCount
, count($values));
466 if ($returnCode & self
::WARNING
) {
467 $this->_warningCount++
;
468 if ($this->_warningCount
< $this->_maxWarningCount
) {
469 $this->_warnings
[] = $this->_lineCount
;
473 if ($returnCode & self
::ERROR
) {
474 $this->_invalidRowCount++
;
475 $recordNumber = $this->_lineCount
;
476 if ($this->_haveColumnHeader
) {
479 array_unshift($values, $recordNumber);
480 $this->_errors
[] = $values;
483 if ($returnCode & self
::DUPLICATE
) {
484 $this->_duplicateCount++
;
485 $recordNumber = $this->_lineCount
;
486 if ($this->_haveColumnHeader
) {
489 array_unshift($values, $recordNumber);
490 $this->_duplicates
[] = $values;
491 if ($onDuplicate != self
::DUPLICATE_SKIP
) {
492 $this->_validCount++
;
496 // if we are done processing the maxNumber of lines, break
497 if ($this->_maxLinesToProcess
> 0 && $this->_validCount
>= $this->_maxLinesToProcess
) {
504 if ($mode == self
::MODE_PREVIEW ||
$mode == self
::MODE_IMPORT
) {
505 $customHeaders = $mapper;
507 $customfields = CRM_Core_BAO_CustomField
::getFields('Activity');
508 foreach ($customHeaders as $key => $value) {
509 if ($id = CRM_Core_BAO_CustomField
::getKeyID($value)) {
510 $customHeaders[$key] = $customfields[$id][0];
513 if ($this->_invalidRowCount
) {
514 // removed view url for invlaid contacts
515 $headers = array_merge([
519 $this->_errorFileName
= self
::errorFileName(self
::ERROR
);
520 CRM_Contact_Import_Parser_Contact
::exportCSV($this->_errorFileName
, $headers, $this->_errors
);
523 if ($this->_duplicateCount
) {
524 $headers = array_merge([
526 ts('View Activity History URL'),
529 $this->_duplicateFileName
= self
::errorFileName(self
::DUPLICATE
);
530 CRM_Contact_Import_Parser_Contact
::exportCSV($this->_duplicateFileName
, $headers, $this->_duplicates
);
536 * Given a list of the importable field keys that the user has selected
537 * set the active fields array to this list
539 * @param array $fieldKeys mapped array of values
543 public function setActiveFields($fieldKeys) {
544 $this->_activeFieldCount
= count($fieldKeys);
545 foreach ($fieldKeys as $key) {
546 if (empty($this->_fields
[$key])) {
547 $this->_activeFields
[] = new CRM_Custom_Import_Field('', ts('- do not import -'));
550 $this->_activeFields
[] = clone($this->_fields
[$key]);
556 * Format the field values for input to the api.
559 * (reference ) associative array of name/value pairs
561 public function &getActiveFieldParams() {
563 for ($i = 0; $i < $this->_activeFieldCount
; $i++
) {
564 if (isset($this->_activeFields
[$i]->_value
)
565 && !isset($params[$this->_activeFields
[$i]->_name
])
566 && !isset($this->_activeFields
[$i]->_related
)
568 $params[$this->_activeFields
[$i]->_name
] = $this->_activeFields
[$i]->_value
;
575 * Store parser values.
577 * @param CRM_Core_Session $store
583 public function set($store, $mode = self
::MODE_SUMMARY
) {
584 $store->set('fileSize', $this->_fileSize
);
585 $store->set('lineCount', $this->_lineCount
);
586 $store->set('separator', $this->_separator
);
587 $store->set('fields', $this->getSelectValues());
589 $store->set('headerPatterns', $this->getHeaderPatterns());
590 $store->set('dataPatterns', $this->getDataPatterns());
591 $store->set('columnCount', $this->_activeFieldCount
);
592 $store->set('_entity', $this->_entity
);
593 $store->set('totalRowCount', $this->_totalCount
);
594 $store->set('validRowCount', $this->_validCount
);
595 $store->set('invalidRowCount', $this->_invalidRowCount
);
597 switch ($this->_contactType
) {
599 $store->set('contactType', CRM_Import_Parser
::CONTACT_INDIVIDUAL
);
603 $store->set('contactType', CRM_Import_Parser
::CONTACT_HOUSEHOLD
);
607 $store->set('contactType', CRM_Import_Parser
::CONTACT_ORGANIZATION
);
610 if ($this->_invalidRowCount
) {
611 $store->set('errorsFileName', $this->_errorFileName
);
614 if (isset($this->_rows
) && !empty($this->_rows
)) {
615 $store->set('dataValues', $this->_rows
);
618 if ($mode == self
::MODE_IMPORT
) {
619 $store->set('duplicateRowCount', $this->_duplicateCount
);
620 if ($this->_duplicateCount
) {
621 $store->set('duplicatesFileName', $this->_duplicateFileName
);
627 * @param string $name
630 * @param string $headerPattern
631 * @param string $dataPattern
632 * @param bool $hasLocationType
634 public function addField(
635 $name, $title, $type = CRM_Utils_Type
::T_INT
,
636 $headerPattern = '//', $dataPattern = '//',
637 $hasLocationType = FALSE
639 $this->_fields
[$name] = new CRM_Custom_Import_Field($name, $title, $type, $headerPattern, $dataPattern, $hasLocationType);
641 $this->_fields
['doNotImport'] = new CRM_Custom_Import_Field($name, $title, $type, $headerPattern, $dataPattern, $hasLocationType);