Merge pull request #23337 from eileenmcnaughton/import
[civicrm-core.git] / CRM / Import / ImportProcessor.php
CommitLineData
0b0285b1 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 */
13class 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
92bde46a 22 /**
23 * @var array
24 */
25 protected $metadata = [];
26
52bd01f5
EM
27 /**
28 * Id of the created user job.
29 *
30 * @var int
31 */
32 protected $userJobID;
33
34 /**
35 * @return int
36 */
37 public function getUserJobID(): int {
38 return $this->userJobID;
39 }
40
41 /**
42 * @param int $userJobID
43 */
44 public function setUserJobID(int $userJobID): void {
45 $this->userJobID = $userJobID;
46 }
47
92bde46a 48 /**
49 * Metadata keyed by field title.
50 *
51 * @var array
52 */
53 protected $metadataByTitle = [];
54
0b0285b1 55 /**
56 * Get contact type being imported.
57 *
58 * @var string
59 */
60 protected $contactType;
61
92bde46a 62 /**
63 * Get contact sub type being imported.
64 *
65 * @var string
66 */
67 protected $contactSubType;
68
69 /**
70 * Array of valid relationships for the contact type & subtype.
71 *
72 * @var array
73 */
74 protected $validRelationships = [];
75
76 /**
77 * Name of the form.
78 *
79 * Used for js for quick form.
80 *
81 * @var string
82 */
83 protected $formName;
84
85 /**
86 * @return string
87 */
88 public function getFormName(): string {
89 return $this->formName;
90 }
91
92 /**
93 * @param string $formName
94 */
95 public function setFormName(string $formName) {
96 $this->formName = $formName;
97 }
98
99 /**
100 * @return array
101 */
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()
108 );
109 asort($relations);
110 $this->setValidRelationships($relations);
111 }
112 return $this->validRelationships[$this->getContactType() . '_' . $this->getContactSubType()];
113 }
114
115 /**
116 * @param array $validRelationships
117 */
118 public function setValidRelationships(array $validRelationships) {
119 $this->validRelationships[$this->getContactType() . '_' . $this->getContactSubType()] = $validRelationships;
120 }
121
122 /**
123 * Get contact subtype for import.
124 *
125 * @return string
126 */
127 public function getContactSubType(): string {
b1498fda 128 return $this->contactSubType ?? '';
92bde46a 129 }
130
131 /**
132 * Set contact subtype for import.
133 *
134 * @param string $contactSubType
135 */
a98c8ecb 136 public function setContactSubType($contactSubType) {
137 $this->contactSubType = (string) $contactSubType;
92bde46a 138 }
139
140 /**
141 * Saved Mapping ID.
142 *
143 * @var int
144 */
145 protected $mappingID;
146
147 /**
148 * @return array
149 */
150 public function getMetadata(): array {
151 return $this->metadata;
152 }
153
154 /**
155 * Setting for metadata.
156 *
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.
159 *
160 * @param array $metadata
161 *
162 * @throws \CiviCRM_API3_Exception
163 */
164 public function setMetadata(array $metadata) {
165 $fieldDetails = civicrm_api3('CustomField', 'get', [
166 'return' => ['custom_group_id.title'],
167 'options' => ['limit' => 0],
168 ])['values'];
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'];
174 }
175 }
176 $this->metadata = $metadata;
177 }
178
179 /**
180 * @return int
181 */
182 public function getMappingID(): int {
183 return $this->mappingID;
184 }
185
186 /**
187 * @param int $mappingID
188 */
189 public function setMappingID(int $mappingID) {
190 $this->mappingID = $mappingID;
191 }
192
0b0285b1 193 /**
a7b9cf38 194 * Get the contact type for the import.
195 *
0b0285b1 196 * @return string
197 */
198 public function getContactType(): string {
199 return $this->contactType;
200 }
201
202 /**
203 * @param string $contactType
204 */
205 public function setContactType(string $contactType) {
206 $this->contactType = $contactType;
207 }
208
209 /**
92bde46a 210 * Set the contact type according to the constant.
211 *
212 * @param int $contactTypeKey
213 */
214 public function setContactTypeByConstant($contactTypeKey) {
215 $constantTypeMap = [
216 CRM_Import_Parser::CONTACT_INDIVIDUAL => 'Individual',
217 CRM_Import_Parser::CONTACT_HOUSEHOLD => 'Household',
218 CRM_Import_Parser::CONTACT_ORGANIZATION => 'Organization',
219 ];
220 $this->contactType = $constantTypeMap[$contactTypeKey];
221 }
222
223 /**
224 * Get Mapping Fields.
225 *
0b0285b1 226 * @return array
92bde46a 227 *
228 * @throws \CiviCRM_API3_Exception
0b0285b1 229 */
230 public function getMappingFields(): array {
92bde46a 231 if (empty($this->mappingFields) && !empty($this->getMappingID())) {
232 $this->loadSavedMapping();
233 }
0b0285b1 234 return $this->mappingFields;
235 }
236
237 /**
a7b9cf38 238 * Set mapping fields.
239 *
240 * We do a little cleanup here too.
241 *
242 * We ensure that column numbers are set and that the fields are ordered by them.
243 *
244 * This would mean the fields could be loaded unsorted.
245 *
0b0285b1 246 * @param array $mappingFields
247 */
248 public function setMappingFields(array $mappingFields) {
a7b9cf38 249 $i = 0;
250 foreach ($mappingFields as &$mappingField) {
251 if (!isset($mappingField['column_number'])) {
252 $mappingField['column_number'] = $i;
253 }
254 if ($mappingField['column_number'] > $i) {
255 $i = $mappingField['column_number'];
256 }
257 $i++;
258 }
92bde46a 259 $this->mappingFields = $this->rekeyBySortedColumnNumbers($mappingFields);
0b0285b1 260 }
261
262 /**
263 * Get the names of the mapped fields.
92bde46a 264 *
265 * @throws \CiviCRM_API3_Exception
0b0285b1 266 */
267 public function getFieldNames() {
268 return CRM_Utils_Array::collect('name', $this->getMappingFields());
269 }
270
92bde46a 271 /**
272 * Get the field name for the given column.
273 *
274 * @param int $columnNumber
275 *
276 * @return string
277 * @throws \CiviCRM_API3_Exception
278 */
279 public function getFieldName($columnNumber) {
280 return $this->getFieldNames()[$columnNumber];
281 }
282
283 /**
284 * Get the field name for the given column.
285 *
286 * @param int $columnNumber
287 *
288 * @return string
289 * @throws \CiviCRM_API3_Exception
290 */
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'];
294 }
295
296 /**
297 * Get relationship key only if it is valid.
298 *
299 * @param int $columnNumber
300 *
301 * @return string|null
302 *
303 * @throws \CiviCRM_API3_Exception
304 */
305 public function getValidRelationshipKey($columnNumber) {
306 $key = $this->getRelationshipKey($columnNumber);
307 return $this->isValidRelationshipKey($key) ? $key : NULL;
308 }
309
310 /**
311 * Get the IM Provider ID.
312 *
313 * @param int $columnNumber
314 *
315 * @return int
316 *
317 * @throws \CiviCRM_API3_Exception
318 */
319 public function getIMProviderID($columnNumber) {
320 return $this->getMappingFields()[$columnNumber]['im_provider_id'] ?? NULL;
321 }
322
323 /**
324 * Get the Phone Type
325 *
326 * @param int $columnNumber
327 *
328 * @return int
329 *
330 * @throws \CiviCRM_API3_Exception
331 */
332 public function getPhoneTypeID($columnNumber) {
333 return $this->getMappingFields()[$columnNumber]['phone_type_id'] ?? NULL;
334 }
335
336 /**
337 * Get the Website Type
338 *
339 * @param int $columnNumber
340 *
341 * @return int
342 *
343 * @throws \CiviCRM_API3_Exception
344 */
345 public function getWebsiteTypeID($columnNumber) {
346 return $this->getMappingFields()[$columnNumber]['website_type_id'] ?? NULL;
347 }
348
349 /**
350 * Get the Location Type
351 *
352 * Returning 0 rather than null is historical.
353 *
354 * @param int $columnNumber
355 *
356 * @return int
357 *
358 * @throws \CiviCRM_API3_Exception
359 */
360 public function getLocationTypeID($columnNumber) {
361 return $this->getMappingFields()[$columnNumber]['location_type_id'] ?? 0;
362 }
363
364 /**
365 * Get the IM or Phone type.
366 *
367 * We have a field that would be the 'relevant' type - which could be either.
368 *
369 * @param int $columnNumber
370 *
371 * @return int
372 *
373 * @throws \CiviCRM_API3_Exception
374 */
375 public function getPhoneOrIMTypeID($columnNumber) {
376 return $this->getIMProviderID($columnNumber) ?? $this->getPhoneTypeID($columnNumber);
377 }
378
0b0285b1 379 /**
380 * Get the location types of the mapped fields.
92bde46a 381 *
382 * @throws \CiviCRM_API3_Exception
0b0285b1 383 */
384 public function getFieldLocationTypes() {
385 return CRM_Utils_Array::collect('location_type_id', $this->getMappingFields());
386 }
387
388 /**
389 * Get the phone types of the mapped fields.
92bde46a 390 *
391 * @throws \CiviCRM_API3_Exception
0b0285b1 392 */
393 public function getFieldPhoneTypes() {
394 return CRM_Utils_Array::collect('phone_type_id', $this->getMappingFields());
395 }
396
397 /**
398 * Get the names of the im_provider fields.
92bde46a 399 *
400 * @throws \CiviCRM_API3_Exception
0b0285b1 401 */
402 public function getFieldIMProviderTypes() {
403 return CRM_Utils_Array::collect('im_provider_id', $this->getMappingFields());
404 }
405
406 /**
407 * Get the names of the website fields.
92bde46a 408 *
409 * @throws \CiviCRM_API3_Exception
0b0285b1 410 */
411 public function getFieldWebsiteTypes() {
412 return CRM_Utils_Array::collect('im_provider_id', $this->getMappingFields());
413 }
414
415 /**
416 * Get an instance of the importer object.
417 *
418 * @return CRM_Contact_Import_Parser_Contact
92bde46a 419 *
420 * @throws \CiviCRM_API3_Exception
0b0285b1 421 */
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 = [],
430 [],
431 [],
432 [],
433 [],
434 [],
435 [],
436 $this->getFieldWebsiteTypes()
437 // $mapperRelatedContactWebsiteType = []
438 );
52bd01f5 439 $importer->setUserJobID($this->getUserJobID());
0b0285b1 440 $importer->init();
0b0285b1 441 return $importer;
442 }
443
92bde46a 444 /**
445 * Load the mapping from the datbase into the format that would be received from the UI.
446 *
447 * @throws \CiviCRM_API3_Exception
448 */
449 protected function loadSavedMapping() {
450 $fields = civicrm_api3('MappingField', 'get', [
451 'mapping_id' => $this->getMappingID(),
452 'options' => ['limit' => 0],
453 ])['values'];
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']);
459 }
460 else {
461 // Honour legacy chaos factor.
471ddc82 462 if ($field['name'] === ts('- do not import -')) {
463 // This is why we save names not labels people....
464 $field['name'] = 'do_not_import';
465 }
92bde46a 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']);
470 }
471 }
472 $fieldSpec = $this->getMetadata()[$fields[$index]['name']];
473 if (empty($field['location_type_id']) && !empty($fieldSpec['hasLocationType'])) {
474 $fields[$index]['location_type_id'] = 'Primary';
475 }
476 }
477 $this->mappingFields = $this->rekeyBySortedColumnNumbers($fields);
478 }
479
480 /**
481 * Get the titles from metadata.
482 */
483 public function getMetadataTitles() {
484 if (empty($this->metadataByTitle)) {
485 $this->metadataByTitle = CRM_Utils_Array::collect('title', $this->getMetadata());
486 }
487 return $this->metadataByTitle;
488 }
489
490 /**
491 * Rekey the array by the column_number.
492 *
493 * @param array $mappingFields
494 *
495 * @return array
496 */
497 protected function rekeyBySortedColumnNumbers(array $mappingFields) {
498 $this->mappingFields = CRM_Utils_Array::rekey($mappingFields, 'column_number');
499 ksort($this->mappingFields);
cd41fa5b 500 return $this->mappingFields;
92bde46a 501 }
502
503 /**
504 * Get the field name from the label.
505 *
506 * @param string $label
507 *
508 * @return string
509 */
510 protected function getNameFromLabel($label) {
511 $titleMap = array_flip($this->getMetadataTitles());
4bef4a25 512 $label = str_replace(' (match to contact)', '', $label);
92bde46a 513 return $titleMap[$label] ?? '';
514 }
515
516 /**
517 * Validate the key against the relationships available for the contatct type & subtype.
518 *
519 * @param string $key
520 *
521 * @return bool
522 */
523 protected function isValidRelationshipKey($key) {
f7dbf5d9 524 return !empty($this->getValidRelationships()[$key]);
92bde46a 525 }
526
3c238265 527 /**
528 * Get the relevant js for quickform.
529 *
530 * @param int $column
531 *
532 * @return string
533 * @throws \CiviCRM_API3_Exception
534 */
535 public function getQuickFormJSForField($column) {
536 $columnNumbersToHide = [];
471ddc82 537 if ($this->getFieldName($column) === 'do_not_import') {
538 $columnNumbersToHide = [1, 2, 3];
539 }
540 elseif ($this->getRelationshipKey($column)) {
a9f13f01 541 if (!$this->getWebsiteTypeID($column) && !$this->getLocationTypeID($column)) {
542 $columnNumbersToHide[] = 2;
543 }
544 if (!$this->getFieldName($column)) {
545 $columnNumbersToHide[] = 1;
546 }
547 if (!$this->getPhoneOrIMTypeID($column)) {
548 $columnNumbersToHide[] = 3;
549 }
3c238265 550 }
a9f13f01 551 else {
552 if (!$this->getLocationTypeID($column) && !$this->getWebsiteTypeID($column)) {
553 $columnNumbersToHide[] = 1;
554 }
555 if (!$this->getPhoneOrIMTypeID($column)) {
556 $columnNumbersToHide[] = 2;
557 }
558 $columnNumbersToHide[] = 3;
3c238265 559 }
3c238265 560
561 $jsClauses = [];
562 foreach ($columnNumbersToHide as $columnNumber) {
563 $jsClauses[] = $this->getFormName() . "['mapper[$column][" . $columnNumber . "]'].style.display = 'none';";
564 }
a9f13f01 565 return empty($jsClauses) ? '' : implode("\n", $jsClauses) . "\n";
3c238265 566 }
567
df90a4dd 568 /**
569 * Get the defaults for the column from the saved mapping.
570 *
571 * @param int $column
572 *
573 * @return array
574 * @throws \CiviCRM_API3_Exception
575 */
576 public function getSavedQuickformDefaultsForColumn($column) {
471ddc82 577 if ($this->getFieldName($column) === 'do_not_import') {
92e44801 578 return [];
579 }
e013bf20 580 if ($this->getValidRelationshipKey($column)) {
581 if ($this->getWebsiteTypeID($column)) {
582 return [$this->getValidRelationshipKey($column), $this->getFieldName($column), $this->getWebsiteTypeID($column)];
583 }
584 return [$this->getValidRelationshipKey($column), $this->getFieldName($column), $this->getLocationTypeID($column), $this->getPhoneOrIMTypeID($column)];
585 }
df90a4dd 586 if ($this->getWebsiteTypeID($column)) {
587 return [$this->getFieldName($column), $this->getWebsiteTypeID($column)];
588 }
589 return [(string) $this->getFieldName($column), $this->getLocationTypeID($column), $this->getPhoneOrIMTypeID($column)];
590 }
591
0b0285b1 592}