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 * Metadata keyed by field title.
32 protected $metadataByTitle = [];
35 * Get contact type being imported.
39 protected $contactType;
42 * Get contact sub type being imported.
46 protected $contactSubType;
49 * Array of valid relationships for the contact type & subtype.
53 protected $validRelationships = [];
58 * Used for js for quick form.
67 public function getFormName(): string {
68 return $this->formName
;
72 * @param string $formName
74 public function setFormName(string $formName) {
75 $this->formName
= $formName;
81 public function getValidRelationships(): array {
82 if (!isset($this->validRelationships
[$this->getContactType() . '_' . $this->getContactSubType()])) {
83 //Relationship importables
84 $relations = CRM_Contact_BAO_Relationship
::getContactRelationshipType(
85 NULL, NULL, NULL, $this->getContactType(),
86 FALSE, 'label', TRUE, $this->getContactSubType()
89 $this->setValidRelationships($relations);
91 return $this->validRelationships
[$this->getContactType() . '_' . $this->getContactSubType()];
95 * @param array $validRelationships
97 public function setValidRelationships(array $validRelationships) {
98 $this->validRelationships
[$this->getContactType() . '_' . $this->getContactSubType()] = $validRelationships;
102 * Get contact subtype for import.
106 public function getContactSubType(): string {
107 return $this->contactSubType ??
'';
111 * Set contact subtype for import.
113 * @param string $contactSubType
115 public function setContactSubType($contactSubType) {
116 $this->contactSubType
= (string) $contactSubType;
124 protected $mappingID;
129 public function getMetadata(): array {
130 return $this->metadata
;
134 * Setting for metadata.
136 * We wrangle the label for custom fields to include the label since the
137 * metadata trait presents it in a more 'pure' form but the label is appended for importing.
139 * @param array $metadata
141 * @throws \CiviCRM_API3_Exception
143 public function setMetadata(array $metadata) {
144 $fieldDetails = civicrm_api3('CustomField', 'get', [
145 'return' => ['custom_group_id.title'],
146 'options' => ['limit' => 0],
148 foreach ($metadata as $index => $field) {
149 if (!empty($field['custom_field_id'])) {
150 // The 'label' format for import is custom group title :: custom name title
151 $metadata[$index]['name'] = $index;
152 $metadata[$index]['title'] .= ' :: ' . $fieldDetails[$field['custom_field_id']]['custom_group_id.title'];
155 $this->metadata
= $metadata;
161 public function getMappingID(): int {
162 return $this->mappingID
;
166 * @param int $mappingID
168 public function setMappingID(int $mappingID) {
169 $this->mappingID
= $mappingID;
173 * Get the contact type for the import.
177 public function getContactType(): string {
178 return $this->contactType
;
182 * @param string $contactType
184 public function setContactType(string $contactType) {
185 $this->contactType
= $contactType;
189 * Set the contact type according to the constant.
191 * @param int $contactTypeKey
193 public function setContactTypeByConstant($contactTypeKey) {
195 CRM_Import_Parser
::CONTACT_INDIVIDUAL
=> 'Individual',
196 CRM_Import_Parser
::CONTACT_HOUSEHOLD
=> 'Household',
197 CRM_Import_Parser
::CONTACT_ORGANIZATION
=> 'Organization',
199 $this->contactType
= $constantTypeMap[$contactTypeKey];
203 * Get Mapping Fields.
207 * @throws \CiviCRM_API3_Exception
209 public function getMappingFields(): array {
210 if (empty($this->mappingFields
) && !empty($this->getMappingID())) {
211 $this->loadSavedMapping();
213 return $this->mappingFields
;
217 * Set mapping fields.
219 * We do a little cleanup here too.
221 * We ensure that column numbers are set and that the fields are ordered by them.
223 * This would mean the fields could be loaded unsorted.
225 * @param array $mappingFields
227 public function setMappingFields(array $mappingFields) {
229 foreach ($mappingFields as &$mappingField) {
230 if (!isset($mappingField['column_number'])) {
231 $mappingField['column_number'] = $i;
233 if ($mappingField['column_number'] > $i) {
234 $i = $mappingField['column_number'];
238 $this->mappingFields
= $this->rekeyBySortedColumnNumbers($mappingFields);
242 * Get the names of the mapped fields.
244 * @throws \CiviCRM_API3_Exception
246 public function getFieldNames() {
247 return CRM_Utils_Array
::collect('name', $this->getMappingFields());
251 * Get the field name for the given column.
253 * @param int $columnNumber
256 * @throws \CiviCRM_API3_Exception
258 public function getFieldName($columnNumber) {
259 return $this->getFieldNames()[$columnNumber];
263 * Get the field name for the given column.
265 * @param int $columnNumber
268 * @throws \CiviCRM_API3_Exception
270 public function getRelationshipKey($columnNumber) {
271 $field = $this->getMappingFields()[$columnNumber];
272 return empty($field['relationship_type_id']) ?
NULL : $field['relationship_type_id'] . '_' . $field['relationship_direction'];
276 * Get relationship key only if it is valid.
278 * @param int $columnNumber
280 * @return string|null
282 * @throws \CiviCRM_API3_Exception
284 public function getValidRelationshipKey($columnNumber) {
285 $key = $this->getRelationshipKey($columnNumber);
286 return $this->isValidRelationshipKey($key) ?
$key : NULL;
290 * Get the IM Provider ID.
292 * @param int $columnNumber
296 * @throws \CiviCRM_API3_Exception
298 public function getIMProviderID($columnNumber) {
299 return $this->getMappingFields()[$columnNumber]['im_provider_id'] ??
NULL;
305 * @param int $columnNumber
309 * @throws \CiviCRM_API3_Exception
311 public function getPhoneTypeID($columnNumber) {
312 return $this->getMappingFields()[$columnNumber]['phone_type_id'] ??
NULL;
316 * Get the Website Type
318 * @param int $columnNumber
322 * @throws \CiviCRM_API3_Exception
324 public function getWebsiteTypeID($columnNumber) {
325 return $this->getMappingFields()[$columnNumber]['website_type_id'] ??
NULL;
329 * Get the Location Type
331 * Returning 0 rather than null is historical.
333 * @param int $columnNumber
337 * @throws \CiviCRM_API3_Exception
339 public function getLocationTypeID($columnNumber) {
340 return $this->getMappingFields()[$columnNumber]['location_type_id'] ??
0;
344 * Get the IM or Phone type.
346 * We have a field that would be the 'relevant' type - which could be either.
348 * @param int $columnNumber
352 * @throws \CiviCRM_API3_Exception
354 public function getPhoneOrIMTypeID($columnNumber) {
355 return $this->getIMProviderID($columnNumber) ??
$this->getPhoneTypeID($columnNumber);
359 * Get the location types of the mapped fields.
361 * @throws \CiviCRM_API3_Exception
363 public function getFieldLocationTypes() {
364 return CRM_Utils_Array
::collect('location_type_id', $this->getMappingFields());
368 * Get the phone types of the mapped fields.
370 * @throws \CiviCRM_API3_Exception
372 public function getFieldPhoneTypes() {
373 return CRM_Utils_Array
::collect('phone_type_id', $this->getMappingFields());
377 * Get the names of the im_provider fields.
379 * @throws \CiviCRM_API3_Exception
381 public function getFieldIMProviderTypes() {
382 return CRM_Utils_Array
::collect('im_provider_id', $this->getMappingFields());
386 * Get the names of the website fields.
388 * @throws \CiviCRM_API3_Exception
390 public function getFieldWebsiteTypes() {
391 return CRM_Utils_Array
::collect('im_provider_id', $this->getMappingFields());
395 * Get an instance of the importer object.
397 * @return CRM_Contact_Import_Parser_Contact
399 * @throws \CiviCRM_API3_Exception
401 public function getImporterObject() {
402 $importer = new CRM_Contact_Import_Parser_Contact(
403 $this->getFieldNames(),
404 $this->getFieldLocationTypes(),
405 $this->getFieldPhoneTypes(),
406 $this->getFieldIMProviderTypes(),
407 // @todo - figure out related mappings.
408 // $mapperRelated = [], $mapperRelatedContactType = [], $mapperRelatedContactDetails = [], $mapperRelatedContactLocType = [], $mapperRelatedContactPhoneType = [], $mapperRelatedContactImProvider = [],
415 $this->getFieldWebsiteTypes()
416 // $mapperRelatedContactWebsiteType = []
419 $importer->_contactType
= $this->getContactType();
424 * Load the mapping from the datbase into the format that would be received from the UI.
426 * @throws \CiviCRM_API3_Exception
428 protected function loadSavedMapping() {
429 $fields = civicrm_api3('MappingField', 'get', [
430 'mapping_id' => $this->getMappingID(),
431 'options' => ['limit' => 0],
433 foreach ($fields as $index => $field) {
434 // Fix up the fact that for lost reasons we save by label not name.
435 $fields[$index]['label'] = $field['name'];
436 if (empty($field['relationship_type_id'])) {
437 $fields[$index]['name'] = $this->getNameFromLabel($field['name']);
440 // Honour legacy chaos factor.
441 if ($field['name'] === ts('- do not import -')) {
442 // This is why we save names not labels people....
443 $field['name'] = 'do_not_import';
445 $fields[$index]['name'] = strtolower(str_replace(" ", "_", $field['name']));
446 // fix for edge cases, CRM-4954
447 if ($fields[$index]['name'] === 'image_url') {
448 $fields[$index]['name'] = str_replace('url', 'URL', $fields[$index]['name']);
451 $fieldSpec = $this->getMetadata()[$fields[$index]['name']];
452 if (empty($field['location_type_id']) && !empty($fieldSpec['hasLocationType'])) {
453 $fields[$index]['location_type_id'] = 'Primary';
456 $this->mappingFields
= $this->rekeyBySortedColumnNumbers($fields);
460 * Get the titles from metadata.
462 public function getMetadataTitles() {
463 if (empty($this->metadataByTitle
)) {
464 $this->metadataByTitle
= CRM_Utils_Array
::collect('title', $this->getMetadata());
466 return $this->metadataByTitle
;
470 * Rekey the array by the column_number.
472 * @param array $mappingFields
476 protected function rekeyBySortedColumnNumbers(array $mappingFields) {
477 $this->mappingFields
= CRM_Utils_Array
::rekey($mappingFields, 'column_number');
478 ksort($this->mappingFields
);
479 return $this->mappingFields
;
483 * Get the field name from the label.
485 * @param string $label
489 protected function getNameFromLabel($label) {
490 $titleMap = array_flip($this->getMetadataTitles());
491 $label = str_replace(' (match to contact)', '', $label);
492 return $titleMap[$label] ??
'';
496 * Validate the key against the relationships available for the contatct type & subtype.
502 protected function isValidRelationshipKey($key) {
503 return !empty($this->getValidRelationships()[$key]);
507 * Get the relevant js for quickform.
512 * @throws \CiviCRM_API3_Exception
514 public function getQuickFormJSForField($column) {
515 $columnNumbersToHide = [];
516 if ($this->getFieldName($column) === 'do_not_import') {
517 $columnNumbersToHide = [1, 2, 3];
519 elseif ($this->getRelationshipKey($column)) {
520 if (!$this->getWebsiteTypeID($column) && !$this->getLocationTypeID($column)) {
521 $columnNumbersToHide[] = 2;
523 if (!$this->getFieldName($column)) {
524 $columnNumbersToHide[] = 1;
526 if (!$this->getPhoneOrIMTypeID($column)) {
527 $columnNumbersToHide[] = 3;
531 if (!$this->getLocationTypeID($column) && !$this->getWebsiteTypeID($column)) {
532 $columnNumbersToHide[] = 1;
534 if (!$this->getPhoneOrIMTypeID($column)) {
535 $columnNumbersToHide[] = 2;
537 $columnNumbersToHide[] = 3;
541 foreach ($columnNumbersToHide as $columnNumber) {
542 $jsClauses[] = $this->getFormName() . "['mapper[$column][" . $columnNumber . "]'].style.display = 'none';";
544 return empty($jsClauses) ?
'' : implode("\n", $jsClauses) . "\n";
548 * Get the defaults for the column from the saved mapping.
553 * @throws \CiviCRM_API3_Exception
555 public function getSavedQuickformDefaultsForColumn($column) {
556 if ($this->getFieldName($column) === 'do_not_import') {
559 if ($this->getValidRelationshipKey($column)) {
560 if ($this->getWebsiteTypeID($column)) {
561 return [$this->getValidRelationshipKey($column), $this->getFieldName($column), $this->getWebsiteTypeID($column)];
563 return [$this->getValidRelationshipKey($column), $this->getFieldName($column), $this->getLocationTypeID($column), $this->getPhoneOrIMTypeID($column)];
565 if ($this->getWebsiteTypeID($column)) {
566 return [$this->getFieldName($column), $this->getWebsiteTypeID($column)];
568 return [(string) $this->getFieldName($column), $this->getLocationTypeID($column), $this->getPhoneOrIMTypeID($column)];