Commit | Line | Data |
---|---|---|
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 | */ | |
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 | ||
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 | } |