Move default fetching to Processor class
[civicrm-core.git] / CRM / Import / ImportProcessor.php
1 <?php
2
3 /**
4 * Class CRM_Import_ImportProcessor.
5 *
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.
9 *
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.
12 */
13 class CRM_Import_ImportProcessor {
14
15 /**
16 * An array of fields in the format used in the table civicrm_mapping_field.
17 *
18 * @var array
19 */
20 protected $mappingFields = [];
21
22 /**
23 * @var array
24 */
25 protected $metadata = [];
26
27 /**
28 * Metadata keyed by field title.
29 *
30 * @var array
31 */
32 protected $metadataByTitle = [];
33
34 /**
35 * Get contact type being imported.
36 *
37 * @var string
38 */
39 protected $contactType;
40
41 /**
42 * Get contact sub type being imported.
43 *
44 * @var string
45 */
46 protected $contactSubType;
47
48 /**
49 * Array of valid relationships for the contact type & subtype.
50 *
51 * @var array
52 */
53 protected $validRelationships = [];
54
55 /**
56 * Name of the form.
57 *
58 * Used for js for quick form.
59 *
60 * @var string
61 */
62 protected $formName;
63
64 /**
65 * @return string
66 */
67 public function getFormName(): string {
68 return $this->formName;
69 }
70
71 /**
72 * @param string $formName
73 */
74 public function setFormName(string $formName) {
75 $this->formName = $formName;
76 }
77
78 /**
79 * @return array
80 */
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()
87 );
88 asort($relations);
89 $this->setValidRelationships($relations);
90 }
91 return $this->validRelationships[$this->getContactType() . '_' . $this->getContactSubType()];
92 }
93
94 /**
95 * @param array $validRelationships
96 */
97 public function setValidRelationships(array $validRelationships) {
98 $this->validRelationships[$this->getContactType() . '_' . $this->getContactSubType()] = $validRelationships;
99 }
100
101 /**
102 * Get contact subtype for import.
103 *
104 * @return string
105 */
106 public function getContactSubType(): string {
107 return $this->contactSubType ?? '';
108 }
109
110 /**
111 * Set contact subtype for import.
112 *
113 * @param string $contactSubType
114 */
115 public function setContactSubType(string $contactSubType) {
116 $this->contactSubType = $contactSubType;
117 }
118
119 /**
120 * Saved Mapping ID.
121 *
122 * @var int
123 */
124 protected $mappingID;
125
126 /**
127 * @return array
128 */
129 public function getMetadata(): array {
130 return $this->metadata;
131 }
132
133 /**
134 * Setting for metadata.
135 *
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.
138 *
139 * @param array $metadata
140 *
141 * @throws \CiviCRM_API3_Exception
142 */
143 public function setMetadata(array $metadata) {
144 $fieldDetails = civicrm_api3('CustomField', 'get', [
145 'return' => ['custom_group_id.title'],
146 'options' => ['limit' => 0],
147 ])['values'];
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'];
153 }
154 }
155 $this->metadata = $metadata;
156 }
157
158 /**
159 * @return int
160 */
161 public function getMappingID(): int {
162 return $this->mappingID;
163 }
164
165 /**
166 * @param int $mappingID
167 */
168 public function setMappingID(int $mappingID) {
169 $this->mappingID = $mappingID;
170 }
171
172 /**
173 * @return string
174 */
175 public function getContactType(): string {
176 return $this->contactType;
177 }
178
179 /**
180 * @param string $contactType
181 */
182 public function setContactType(string $contactType) {
183 $this->contactType = $contactType;
184 }
185
186 /**
187 * Set the contact type according to the constant.
188 *
189 * @param int $contactTypeKey
190 */
191 public function setContactTypeByConstant($contactTypeKey) {
192 $constantTypeMap = [
193 CRM_Import_Parser::CONTACT_INDIVIDUAL => 'Individual',
194 CRM_Import_Parser::CONTACT_HOUSEHOLD => 'Household',
195 CRM_Import_Parser::CONTACT_ORGANIZATION => 'Organization',
196 ];
197 $this->contactType = $constantTypeMap[$contactTypeKey];
198 }
199
200 /**
201 * Get Mapping Fields.
202 *
203 * @return array
204 *
205 * @throws \CiviCRM_API3_Exception
206 */
207 public function getMappingFields(): array {
208 if (empty($this->mappingFields) && !empty($this->getMappingID())) {
209 $this->loadSavedMapping();
210 }
211 return $this->mappingFields;
212 }
213
214 /**
215 * @param array $mappingFields
216 */
217 public function setMappingFields(array $mappingFields) {
218 $this->mappingFields = $this->rekeyBySortedColumnNumbers($mappingFields);
219 }
220
221 /**
222 * Get the names of the mapped fields.
223 *
224 * @throws \CiviCRM_API3_Exception
225 */
226 public function getFieldNames() {
227 return CRM_Utils_Array::collect('name', $this->getMappingFields());
228 }
229
230 /**
231 * Get the field name for the given column.
232 *
233 * @param int $columnNumber
234 *
235 * @return string
236 * @throws \CiviCRM_API3_Exception
237 */
238 public function getFieldName($columnNumber) {
239 return $this->getFieldNames()[$columnNumber];
240 }
241
242 /**
243 * Get the field name for the given column.
244 *
245 * @param int $columnNumber
246 *
247 * @return string
248 * @throws \CiviCRM_API3_Exception
249 */
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'];
253 }
254
255 /**
256 * Get relationship key only if it is valid.
257 *
258 * @param int $columnNumber
259 *
260 * @return string|null
261 *
262 * @throws \CiviCRM_API3_Exception
263 */
264 public function getValidRelationshipKey($columnNumber) {
265 $key = $this->getRelationshipKey($columnNumber);
266 return $this->isValidRelationshipKey($key) ? $key : NULL;
267 }
268
269 /**
270 * Get the IM Provider ID.
271 *
272 * @param int $columnNumber
273 *
274 * @return int
275 *
276 * @throws \CiviCRM_API3_Exception
277 */
278 public function getIMProviderID($columnNumber) {
279 return $this->getMappingFields()[$columnNumber]['im_provider_id'] ?? NULL;
280 }
281
282 /**
283 * Get the Phone Type
284 *
285 * @param int $columnNumber
286 *
287 * @return int
288 *
289 * @throws \CiviCRM_API3_Exception
290 */
291 public function getPhoneTypeID($columnNumber) {
292 return $this->getMappingFields()[$columnNumber]['phone_type_id'] ?? NULL;
293 }
294
295 /**
296 * Get the Website Type
297 *
298 * @param int $columnNumber
299 *
300 * @return int
301 *
302 * @throws \CiviCRM_API3_Exception
303 */
304 public function getWebsiteTypeID($columnNumber) {
305 return $this->getMappingFields()[$columnNumber]['website_type_id'] ?? NULL;
306 }
307
308 /**
309 * Get the Location Type
310 *
311 * Returning 0 rather than null is historical.
312 *
313 * @param int $columnNumber
314 *
315 * @return int
316 *
317 * @throws \CiviCRM_API3_Exception
318 */
319 public function getLocationTypeID($columnNumber) {
320 return $this->getMappingFields()[$columnNumber]['location_type_id'] ?? 0;
321 }
322
323 /**
324 * Get the IM or Phone type.
325 *
326 * We have a field that would be the 'relevant' type - which could be either.
327 *
328 * @param int $columnNumber
329 *
330 * @return int
331 *
332 * @throws \CiviCRM_API3_Exception
333 */
334 public function getPhoneOrIMTypeID($columnNumber) {
335 return $this->getIMProviderID($columnNumber) ?? $this->getPhoneTypeID($columnNumber);
336 }
337
338 /**
339 * Get the location types of the mapped fields.
340 *
341 * @throws \CiviCRM_API3_Exception
342 */
343 public function getFieldLocationTypes() {
344 return CRM_Utils_Array::collect('location_type_id', $this->getMappingFields());
345 }
346
347 /**
348 * Get the phone types of the mapped fields.
349 *
350 * @throws \CiviCRM_API3_Exception
351 */
352 public function getFieldPhoneTypes() {
353 return CRM_Utils_Array::collect('phone_type_id', $this->getMappingFields());
354 }
355
356 /**
357 * Get the names of the im_provider fields.
358 *
359 * @throws \CiviCRM_API3_Exception
360 */
361 public function getFieldIMProviderTypes() {
362 return CRM_Utils_Array::collect('im_provider_id', $this->getMappingFields());
363 }
364
365 /**
366 * Get the names of the website fields.
367 *
368 * @throws \CiviCRM_API3_Exception
369 */
370 public function getFieldWebsiteTypes() {
371 return CRM_Utils_Array::collect('im_provider_id', $this->getMappingFields());
372 }
373
374 /**
375 * Get an instance of the importer object.
376 *
377 * @return CRM_Contact_Import_Parser_Contact
378 *
379 * @throws \CiviCRM_API3_Exception
380 */
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 = [],
389 [],
390 [],
391 [],
392 [],
393 [],
394 [],
395 $this->getFieldWebsiteTypes()
396 // $mapperRelatedContactWebsiteType = []
397 );
398 $importer->init();
399 $importer->_contactType = $this->getContactType();
400 return $importer;
401 }
402
403 /**
404 * Load the mapping from the datbase into the format that would be received from the UI.
405 *
406 * @throws \CiviCRM_API3_Exception
407 */
408 protected function loadSavedMapping() {
409 $fields = civicrm_api3('MappingField', 'get', [
410 'mapping_id' => $this->getMappingID(),
411 'options' => ['limit' => 0],
412 ])['values'];
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']);
418 }
419 else {
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']);
425 }
426 }
427 $fieldSpec = $this->getMetadata()[$fields[$index]['name']];
428 if (empty($field['location_type_id']) && !empty($fieldSpec['hasLocationType'])) {
429 $fields[$index]['location_type_id'] = 'Primary';
430 }
431 }
432 $this->mappingFields = $this->rekeyBySortedColumnNumbers($fields);
433 }
434
435 /**
436 * Get the titles from metadata.
437 */
438 public function getMetadataTitles() {
439 if (empty($this->metadataByTitle)) {
440 $this->metadataByTitle = CRM_Utils_Array::collect('title', $this->getMetadata());
441 }
442 return $this->metadataByTitle;
443 }
444
445 /**
446 * Rekey the array by the column_number.
447 *
448 * @param array $mappingFields
449 *
450 * @return array
451 */
452 protected function rekeyBySortedColumnNumbers(array $mappingFields) {
453 $this->mappingFields = CRM_Utils_Array::rekey($mappingFields, 'column_number');
454 ksort($this->mappingFields);
455 return $this->mappingFields;
456 }
457
458 /**
459 * Get the field name from the label.
460 *
461 * @param string $label
462 *
463 * @return string
464 */
465 protected function getNameFromLabel($label) {
466 $titleMap = array_flip($this->getMetadataTitles());
467 return $titleMap[$label] ?? '';
468 }
469
470 /**
471 * Validate the key against the relationships available for the contatct type & subtype.
472 *
473 * @param string $key
474 *
475 * @return bool
476 */
477 protected function isValidRelationshipKey($key) {
478 return !empty($this->getValidRelationships()[$key]) ? TRUE : FALSE;
479 }
480
481 /**
482 * Get the relevant js for quickform.
483 *
484 * @param int $column
485 *
486 * @return string
487 * @throws \CiviCRM_API3_Exception
488 */
489 public function getQuickFormJSForField($column) {
490 $columnNumbersToHide = [];
491
492 if (!$this->getLocationTypeID($column) && !$this->getWebsiteTypeID($column)) {
493 $columnNumbersToHide[] = 1;
494 }
495 if (!$this->getPhoneOrIMTypeID($column)) {
496 $columnNumbersToHide[] = 2;
497 }
498 $columnNumbersToHide[] = 3;
499
500 $jsClauses = [];
501 foreach ($columnNumbersToHide as $columnNumber) {
502 $jsClauses[] = $this->getFormName() . "['mapper[$column][" . $columnNumber . "]'].style.display = 'none';";
503 }
504 return implode("\n", $jsClauses) . "\n";
505 }
506
507 /**
508 * Get the defaults for the column from the saved mapping.
509 *
510 * @param int $column
511 *
512 * @return array
513 * @throws \CiviCRM_API3_Exception
514 */
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)];
519 }
520 return [$this->getValidRelationshipKey($column), $this->getFieldName($column), $this->getLocationTypeID($column), $this->getPhoneOrIMTypeID($column)];
521 }
522 if ($this->getWebsiteTypeID($column)) {
523 return [$this->getFieldName($column), $this->getWebsiteTypeID($column)];
524 }
525 return [(string) $this->getFieldName($column), $this->getLocationTypeID($column), $this->getPhoneOrIMTypeID($column)];
526 }
527
528 }