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(string $contactSubType) {
116 $this->contactSubType
= $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;
175 public function getContactType(): string {
176 return $this->contactType
;
180 * @param string $contactType
182 public function setContactType(string $contactType) {
183 $this->contactType
= $contactType;
187 * Set the contact type according to the constant.
189 * @param int $contactTypeKey
191 public function setContactTypeByConstant($contactTypeKey) {
193 CRM_Import_Parser
::CONTACT_INDIVIDUAL
=> 'Individual',
194 CRM_Import_Parser
::CONTACT_HOUSEHOLD
=> 'Household',
195 CRM_Import_Parser
::CONTACT_ORGANIZATION
=> 'Organization',
197 $this->contactType
= $constantTypeMap[$contactTypeKey];
201 * Get Mapping Fields.
205 * @throws \CiviCRM_API3_Exception
207 public function getMappingFields(): array {
208 if (empty($this->mappingFields
) && !empty($this->getMappingID())) {
209 $this->loadSavedMapping();
211 return $this->mappingFields
;
215 * @param array $mappingFields
217 public function setMappingFields(array $mappingFields) {
218 $this->mappingFields
= $this->rekeyBySortedColumnNumbers($mappingFields);
222 * Get the names of the mapped fields.
224 * @throws \CiviCRM_API3_Exception
226 public function getFieldNames() {
227 return CRM_Utils_Array
::collect('name', $this->getMappingFields());
231 * Get the field name for the given column.
233 * @param int $columnNumber
236 * @throws \CiviCRM_API3_Exception
238 public function getFieldName($columnNumber) {
239 return $this->getFieldNames()[$columnNumber];
243 * Get the field name for the given column.
245 * @param int $columnNumber
248 * @throws \CiviCRM_API3_Exception
250 public function getRelationshipKey($columnNumber) {
251 $field = $this->getMappingFields()[$columnNumber];
252 return empty($field['relationship_type_id']) ?
NULL : $field['relationship_type_id'] . '_' . $field['relationship_direction'];
256 * Get relationship key only if it is valid.
258 * @param int $columnNumber
260 * @return string|null
262 * @throws \CiviCRM_API3_Exception
264 public function getValidRelationshipKey($columnNumber) {
265 $key = $this->getRelationshipKey($columnNumber);
266 return $this->isValidRelationshipKey($key) ?
$key : NULL;
270 * Get the IM Provider ID.
272 * @param int $columnNumber
276 * @throws \CiviCRM_API3_Exception
278 public function getIMProviderID($columnNumber) {
279 return $this->getMappingFields()[$columnNumber]['im_provider_id'] ??
NULL;
285 * @param int $columnNumber
289 * @throws \CiviCRM_API3_Exception
291 public function getPhoneTypeID($columnNumber) {
292 return $this->getMappingFields()[$columnNumber]['phone_type_id'] ??
NULL;
296 * Get the Website Type
298 * @param int $columnNumber
302 * @throws \CiviCRM_API3_Exception
304 public function getWebsiteTypeID($columnNumber) {
305 return $this->getMappingFields()[$columnNumber]['website_type_id'] ??
NULL;
309 * Get the Location Type
311 * Returning 0 rather than null is historical.
313 * @param int $columnNumber
317 * @throws \CiviCRM_API3_Exception
319 public function getLocationTypeID($columnNumber) {
320 return $this->getMappingFields()[$columnNumber]['location_type_id'] ??
0;
324 * Get the IM or Phone type.
326 * We have a field that would be the 'relevant' type - which could be either.
328 * @param int $columnNumber
332 * @throws \CiviCRM_API3_Exception
334 public function getPhoneOrIMTypeID($columnNumber) {
335 return $this->getIMProviderID($columnNumber) ??
$this->getPhoneTypeID($columnNumber);
339 * Get the location types of the mapped fields.
341 * @throws \CiviCRM_API3_Exception
343 public function getFieldLocationTypes() {
344 return CRM_Utils_Array
::collect('location_type_id', $this->getMappingFields());
348 * Get the phone types of the mapped fields.
350 * @throws \CiviCRM_API3_Exception
352 public function getFieldPhoneTypes() {
353 return CRM_Utils_Array
::collect('phone_type_id', $this->getMappingFields());
357 * Get the names of the im_provider fields.
359 * @throws \CiviCRM_API3_Exception
361 public function getFieldIMProviderTypes() {
362 return CRM_Utils_Array
::collect('im_provider_id', $this->getMappingFields());
366 * Get the names of the website fields.
368 * @throws \CiviCRM_API3_Exception
370 public function getFieldWebsiteTypes() {
371 return CRM_Utils_Array
::collect('im_provider_id', $this->getMappingFields());
375 * Get an instance of the importer object.
377 * @return CRM_Contact_Import_Parser_Contact
379 * @throws \CiviCRM_API3_Exception
381 public function getImporterObject() {
382 $importer = new CRM_Contact_Import_Parser_Contact(
383 $this->getFieldNames(),
384 $this->getFieldLocationTypes(),
385 $this->getFieldPhoneTypes(),
386 $this->getFieldIMProviderTypes(),
387 // @todo - figure out related mappings.
388 // $mapperRelated = [], $mapperRelatedContactType = [], $mapperRelatedContactDetails = [], $mapperRelatedContactLocType = [], $mapperRelatedContactPhoneType = [], $mapperRelatedContactImProvider = [],
395 $this->getFieldWebsiteTypes()
396 // $mapperRelatedContactWebsiteType = []
399 $importer->_contactType
= $this->getContactType();
404 * Load the mapping from the datbase into the format that would be received from the UI.
406 * @throws \CiviCRM_API3_Exception
408 protected function loadSavedMapping() {
409 $fields = civicrm_api3('MappingField', 'get', [
410 'mapping_id' => $this->getMappingID(),
411 'options' => ['limit' => 0],
413 foreach ($fields as $index => $field) {
414 // Fix up the fact that for lost reasons we save by label not name.
415 $fields[$index]['label'] = $field['name'];
416 if (empty($field['relationship_type_id'])) {
417 $fields[$index]['name'] = $this->getNameFromLabel($field['name']);
420 // Honour legacy chaos factor.
421 $fields[$index]['name'] = strtolower(str_replace(" ", "_", $field['name']));
422 // fix for edge cases, CRM-4954
423 if ($fields[$index]['name'] === 'image_url') {
424 $fields[$index]['name'] = str_replace('url', 'URL', $fields[$index]['name']);
427 $fieldSpec = $this->getMetadata()[$fields[$index]['name']];
428 if (empty($field['location_type_id']) && !empty($fieldSpec['hasLocationType'])) {
429 $fields[$index]['location_type_id'] = 'Primary';
432 $this->mappingFields
= $this->rekeyBySortedColumnNumbers($fields);
436 * Get the titles from metadata.
438 public function getMetadataTitles() {
439 if (empty($this->metadataByTitle
)) {
440 $this->metadataByTitle
= CRM_Utils_Array
::collect('title', $this->getMetadata());
442 return $this->metadataByTitle
;
446 * Rekey the array by the column_number.
448 * @param array $mappingFields
452 protected function rekeyBySortedColumnNumbers(array $mappingFields) {
453 $this->mappingFields
= CRM_Utils_Array
::rekey($mappingFields, 'column_number');
454 ksort($this->mappingFields
);
455 return $this->mappingFields
;
459 * Get the field name from the label.
461 * @param string $label
465 protected function getNameFromLabel($label) {
466 $titleMap = array_flip($this->getMetadataTitles());
467 return $titleMap[$label] ??
'';
471 * Validate the key against the relationships available for the contatct type & subtype.
477 protected function isValidRelationshipKey($key) {
478 return !empty($this->getValidRelationships()[$key]) ?
TRUE : FALSE;
482 * Get the relevant js for quickform.
487 * @throws \CiviCRM_API3_Exception
489 public function getQuickFormJSForField($column) {
490 $columnNumbersToHide = [];
492 if (!$this->getLocationTypeID($column) && !$this->getWebsiteTypeID($column)) {
493 $columnNumbersToHide[] = 1;
495 if (!$this->getPhoneOrIMTypeID($column)) {
496 $columnNumbersToHide[] = 2;
498 $columnNumbersToHide[] = 3;
501 foreach ($columnNumbersToHide as $columnNumber) {
502 $jsClauses[] = $this->getFormName() . "['mapper[$column][" . $columnNumber . "]'].style.display = 'none';";
504 return implode("\n", $jsClauses) . "\n";
508 * Get the defaults for the column from the saved mapping.
513 * @throws \CiviCRM_API3_Exception
515 public function getSavedQuickformDefaultsForColumn($column) {
516 if ($this->getValidRelationshipKey($column)) {
517 if ($this->getWebsiteTypeID($column)) {
518 return [$this->getValidRelationshipKey($column), $this->getFieldName($column), $this->getWebsiteTypeID($column)];
520 return [$this->getValidRelationshipKey($column), $this->getFieldName($column), $this->getLocationTypeID($column), $this->getPhoneOrIMTypeID($column)];
522 if ($this->getWebsiteTypeID($column)) {
523 return [$this->getFieldName($column), $this->getWebsiteTypeID($column)];
525 return [(string) $this->getFieldName($column), $this->getLocationTypeID($column), $this->getPhoneOrIMTypeID($column)];