4 * Class CRM_Import_ImportProcessor.
6 * Import processor class. This is intended to provide a sanitising wrapper around
7 * the form-oriented import classes. In particular it is intended to provide a clear translation
8 * between the saved mapping field format and the quick form & parser formats.
10 * In the first instance this is only being used in unit tests but the intent is to migrate
11 * to it on a trajectory similar to the ExportProcessor so it is not in the tests.
13 class CRM_Import_ImportProcessor
{
16 * An array of fields in the format used in the table civicrm_mapping_field.
20 protected $mappingFields = [];
25 protected $metadata = [];
28 * Id of the created user job.
37 public function getUserJobID(): int {
38 return $this->userJobID
;
42 * @param int $userJobID
44 public function setUserJobID(int $userJobID): void
{
45 $this->userJobID
= $userJobID;
49 * Metadata keyed by field title.
53 protected $metadataByTitle = [];
56 * Get contact type being imported.
60 protected $contactType;
63 * Get contact sub type being imported.
67 protected $contactSubType;
70 * Array of valid relationships for the contact type & subtype.
74 protected $validRelationships = [];
79 * Used for js for quick form.
88 public function getFormName(): string {
89 return $this->formName
;
93 * @param string $formName
95 public function setFormName(string $formName) {
96 $this->formName
= $formName;
102 public function getValidRelationships(): array {
103 if (!isset($this->validRelationships
[$this->getContactType() . '_' . $this->getContactSubType()])) {
104 //Relationship importables
105 $relations = CRM_Contact_BAO_Relationship
::getContactRelationshipType(
106 NULL, NULL, NULL, $this->getContactType(),
107 FALSE, 'label', TRUE, $this->getContactSubType()
110 $this->setValidRelationships($relations);
112 return $this->validRelationships
[$this->getContactType() . '_' . $this->getContactSubType()];
116 * @param array $validRelationships
118 public function setValidRelationships(array $validRelationships) {
119 $this->validRelationships
[$this->getContactType() . '_' . $this->getContactSubType()] = $validRelationships;
123 * Get contact subtype for import.
127 public function getContactSubType(): string {
128 return $this->contactSubType ??
'';
132 * Set contact subtype for import.
134 * @param string $contactSubType
136 public function setContactSubType($contactSubType) {
137 $this->contactSubType
= (string) $contactSubType;
145 protected $mappingID;
150 public function getMetadata(): array {
151 return $this->metadata
;
155 * Setting for metadata.
157 * We wrangle the label for custom fields to include the label since the
158 * metadata trait presents it in a more 'pure' form but the label is appended for importing.
160 * @param array $metadata
162 * @throws \CiviCRM_API3_Exception
164 public function setMetadata(array $metadata) {
165 $fieldDetails = civicrm_api3('CustomField', 'get', [
166 'return' => ['custom_group_id.title'],
167 'options' => ['limit' => 0],
169 foreach ($metadata as $index => $field) {
170 if (!empty($field['custom_field_id'])) {
171 // The 'label' format for import is custom group title :: custom name title
172 $metadata[$index]['name'] = $index;
173 $metadata[$index]['title'] .= ' :: ' . $fieldDetails[$field['custom_field_id']]['custom_group_id.title'];
176 $this->metadata
= $metadata;
182 public function getMappingID(): int {
183 return $this->mappingID
;
187 * @param int $mappingID
189 public function setMappingID(int $mappingID) {
190 $this->mappingID
= $mappingID;
194 * Get the contact type for the import.
198 public function getContactType(): string {
199 return $this->contactType
;
203 * @param string $contactType
205 public function setContactType(string $contactType) {
206 $this->contactType
= $contactType;
210 * Set the contact type according to the constant.
212 * @param int $contactTypeKey
214 public function setContactTypeByConstant($contactTypeKey) {
216 CRM_Import_Parser
::CONTACT_INDIVIDUAL
=> 'Individual',
217 CRM_Import_Parser
::CONTACT_HOUSEHOLD
=> 'Household',
218 CRM_Import_Parser
::CONTACT_ORGANIZATION
=> 'Organization',
220 $this->contactType
= $constantTypeMap[$contactTypeKey];
224 * Get Mapping Fields.
228 * @throws \CiviCRM_API3_Exception
230 public function getMappingFields(): array {
231 if (empty($this->mappingFields
) && !empty($this->getMappingID())) {
232 $this->loadSavedMapping();
234 return $this->mappingFields
;
238 * Set mapping fields.
240 * We do a little cleanup here too.
242 * We ensure that column numbers are set and that the fields are ordered by them.
244 * This would mean the fields could be loaded unsorted.
246 * @param array $mappingFields
248 public function setMappingFields(array $mappingFields) {
250 foreach ($mappingFields as &$mappingField) {
251 if (!isset($mappingField['column_number'])) {
252 $mappingField['column_number'] = $i;
254 if ($mappingField['column_number'] > $i) {
255 $i = $mappingField['column_number'];
259 $this->mappingFields
= $this->rekeyBySortedColumnNumbers($mappingFields);
263 * Get the names of the mapped fields.
265 * @throws \CiviCRM_API3_Exception
267 public function getFieldNames() {
268 return CRM_Utils_Array
::collect('name', $this->getMappingFields());
272 * Get the field name for the given column.
274 * @param int $columnNumber
277 * @throws \CiviCRM_API3_Exception
279 public function getFieldName($columnNumber) {
280 return $this->getFieldNames()[$columnNumber];
284 * Get the field name for the given column.
286 * @param int $columnNumber
289 * @throws \CiviCRM_API3_Exception
291 public function getRelationshipKey($columnNumber) {
292 $field = $this->getMappingFields()[$columnNumber];
293 return empty($field['relationship_type_id']) ?
NULL : $field['relationship_type_id'] . '_' . $field['relationship_direction'];
297 * Get relationship key only if it is valid.
299 * @param int $columnNumber
301 * @return string|null
303 * @throws \CiviCRM_API3_Exception
305 public function getValidRelationshipKey($columnNumber) {
306 $key = $this->getRelationshipKey($columnNumber);
307 return $this->isValidRelationshipKey($key) ?
$key : NULL;
311 * Get the IM Provider ID.
313 * @param int $columnNumber
317 * @throws \CiviCRM_API3_Exception
319 public function getIMProviderID($columnNumber) {
320 return $this->getMappingFields()[$columnNumber]['im_provider_id'] ??
NULL;
326 * @param int $columnNumber
330 * @throws \CiviCRM_API3_Exception
332 public function getPhoneTypeID($columnNumber) {
333 return $this->getMappingFields()[$columnNumber]['phone_type_id'] ??
NULL;
337 * Get the Website Type
339 * @param int $columnNumber
343 * @throws \CiviCRM_API3_Exception
345 public function getWebsiteTypeID($columnNumber) {
346 return $this->getMappingFields()[$columnNumber]['website_type_id'] ??
NULL;
350 * Get the Location Type
352 * Returning 0 rather than null is historical.
354 * @param int $columnNumber
358 * @throws \CiviCRM_API3_Exception
360 public function getLocationTypeID($columnNumber) {
361 return $this->getMappingFields()[$columnNumber]['location_type_id'] ??
0;
365 * Get the IM or Phone type.
367 * We have a field that would be the 'relevant' type - which could be either.
369 * @param int $columnNumber
373 * @throws \CiviCRM_API3_Exception
375 public function getPhoneOrIMTypeID($columnNumber) {
376 return $this->getIMProviderID($columnNumber) ??
$this->getPhoneTypeID($columnNumber);
380 * Get the location types of the mapped fields.
382 * @throws \CiviCRM_API3_Exception
384 public function getFieldLocationTypes() {
385 return CRM_Utils_Array
::collect('location_type_id', $this->getMappingFields());
389 * Get the phone types of the mapped fields.
391 * @throws \CiviCRM_API3_Exception
393 public function getFieldPhoneTypes() {
394 return CRM_Utils_Array
::collect('phone_type_id', $this->getMappingFields());
398 * Get the names of the im_provider fields.
400 * @throws \CiviCRM_API3_Exception
402 public function getFieldIMProviderTypes() {
403 return CRM_Utils_Array
::collect('im_provider_id', $this->getMappingFields());
407 * Get the names of the website fields.
409 * @throws \CiviCRM_API3_Exception
411 public function getFieldWebsiteTypes() {
412 return CRM_Utils_Array
::collect('im_provider_id', $this->getMappingFields());
416 * Get an instance of the importer object.
418 * @return CRM_Contact_Import_Parser_Contact
420 * @throws \CiviCRM_API3_Exception
422 public function getImporterObject() {
423 $importer = new CRM_Contact_Import_Parser_Contact(
424 $this->getFieldNames(),
425 $this->getFieldLocationTypes(),
426 $this->getFieldPhoneTypes(),
427 $this->getFieldIMProviderTypes(),
428 // @todo - figure out related mappings.
429 // $mapperRelated = [], $mapperRelatedContactType = [], $mapperRelatedContactDetails = [], $mapperRelatedContactLocType = [], $mapperRelatedContactPhoneType = [], $mapperRelatedContactImProvider = [],
436 $this->getFieldWebsiteTypes()
437 // $mapperRelatedContactWebsiteType = []
439 $importer->setUserJobID($this->getUserJobID());
445 * Load the mapping from the datbase into the format that would be received from the UI.
447 * @throws \CiviCRM_API3_Exception
449 protected function loadSavedMapping() {
450 $fields = civicrm_api3('MappingField', 'get', [
451 'mapping_id' => $this->getMappingID(),
452 'options' => ['limit' => 0],
454 foreach ($fields as $index => $field) {
455 // Fix up the fact that for lost reasons we save by label not name.
456 $fields[$index]['label'] = $field['name'];
457 if (empty($field['relationship_type_id'])) {
458 $fields[$index]['name'] = $this->getNameFromLabel($field['name']);
461 // Honour legacy chaos factor.
462 if ($field['name'] === ts('- do not import -')) {
463 // This is why we save names not labels people....
464 $field['name'] = 'do_not_import';
466 $fields[$index]['name'] = strtolower(str_replace(" ", "_", $field['name']));
467 // fix for edge cases, CRM-4954
468 if ($fields[$index]['name'] === 'image_url') {
469 $fields[$index]['name'] = str_replace('url', 'URL', $fields[$index]['name']);
472 $fieldSpec = $this->getMetadata()[$fields[$index]['name']];
473 if (empty($field['location_type_id']) && !empty($fieldSpec['hasLocationType'])) {
474 $fields[$index]['location_type_id'] = 'Primary';
477 $this->mappingFields
= $this->rekeyBySortedColumnNumbers($fields);
481 * Get the titles from metadata.
483 public function getMetadataTitles() {
484 if (empty($this->metadataByTitle
)) {
485 $this->metadataByTitle
= CRM_Utils_Array
::collect('title', $this->getMetadata());
487 return $this->metadataByTitle
;
491 * Rekey the array by the column_number.
493 * @param array $mappingFields
497 protected function rekeyBySortedColumnNumbers(array $mappingFields) {
498 $this->mappingFields
= CRM_Utils_Array
::rekey($mappingFields, 'column_number');
499 ksort($this->mappingFields
);
500 return $this->mappingFields
;
504 * Get the field name from the label.
506 * @param string $label
510 protected function getNameFromLabel($label) {
511 $titleMap = array_flip($this->getMetadataTitles());
512 $label = str_replace(' (match to contact)', '', $label);
513 return $titleMap[$label] ??
'';
517 * Validate the key against the relationships available for the contatct type & subtype.
523 protected function isValidRelationshipKey($key) {
524 return !empty($this->getValidRelationships()[$key]);
528 * Get the relevant js for quickform.
533 * @throws \CiviCRM_API3_Exception
535 public function getQuickFormJSForField($column) {
536 $columnNumbersToHide = [];
537 if ($this->getFieldName($column) === 'do_not_import') {
538 $columnNumbersToHide = [1, 2, 3];
540 elseif ($this->getRelationshipKey($column)) {
541 if (!$this->getWebsiteTypeID($column) && !$this->getLocationTypeID($column)) {
542 $columnNumbersToHide[] = 2;
544 if (!$this->getFieldName($column)) {
545 $columnNumbersToHide[] = 1;
547 if (!$this->getPhoneOrIMTypeID($column)) {
548 $columnNumbersToHide[] = 3;
552 if (!$this->getLocationTypeID($column) && !$this->getWebsiteTypeID($column)) {
553 $columnNumbersToHide[] = 1;
555 if (!$this->getPhoneOrIMTypeID($column)) {
556 $columnNumbersToHide[] = 2;
558 $columnNumbersToHide[] = 3;
562 foreach ($columnNumbersToHide as $columnNumber) {
563 $jsClauses[] = $this->getFormName() . "['mapper[$column][" . $columnNumber . "]'].style.display = 'none';";
565 return empty($jsClauses) ?
'' : implode("\n", $jsClauses) . "\n";
569 * Get the defaults for the column from the saved mapping.
574 * @throws \CiviCRM_API3_Exception
576 public function getSavedQuickformDefaultsForColumn($column) {
577 if ($this->getFieldName($column) === 'do_not_import') {
580 if ($this->getValidRelationshipKey($column)) {
581 if ($this->getWebsiteTypeID($column)) {
582 return [$this->getValidRelationshipKey($column), $this->getFieldName($column), $this->getWebsiteTypeID($column)];
584 return [$this->getValidRelationshipKey($column), $this->getFieldName($column), $this->getLocationTypeID($column), $this->getPhoneOrIMTypeID($column)];
586 if ($this->getWebsiteTypeID($column)) {
587 return [$this->getFieldName($column), $this->getWebsiteTypeID($column)];
589 return [(string) $this->getFieldName($column), $this->getLocationTypeID($column), $this->getPhoneOrIMTypeID($column)];