dev/core#1187 Fix bug where import will not do 2 phone types of the same location
[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
27 /**
28 * Metadata keyed by field title.
29 *
30 * @var array
31 */
32 protected $metadataByTitle = [];
33
0b0285b1 34 /**
35 * Get contact type being imported.
36 *
37 * @var string
38 */
39 protected $contactType;
40
92bde46a 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 {
b1498fda 107 return $this->contactSubType ?? '';
92bde46a 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
0b0285b1 172 /**
a7b9cf38 173 * Get the contact type for the import.
174 *
0b0285b1 175 * @return string
176 */
177 public function getContactType(): string {
178 return $this->contactType;
179 }
180
181 /**
182 * @param string $contactType
183 */
184 public function setContactType(string $contactType) {
185 $this->contactType = $contactType;
186 }
187
188 /**
92bde46a 189 * Set the contact type according to the constant.
190 *
191 * @param int $contactTypeKey
192 */
193 public function setContactTypeByConstant($contactTypeKey) {
194 $constantTypeMap = [
195 CRM_Import_Parser::CONTACT_INDIVIDUAL => 'Individual',
196 CRM_Import_Parser::CONTACT_HOUSEHOLD => 'Household',
197 CRM_Import_Parser::CONTACT_ORGANIZATION => 'Organization',
198 ];
199 $this->contactType = $constantTypeMap[$contactTypeKey];
200 }
201
202 /**
203 * Get Mapping Fields.
204 *
0b0285b1 205 * @return array
92bde46a 206 *
207 * @throws \CiviCRM_API3_Exception
0b0285b1 208 */
209 public function getMappingFields(): array {
92bde46a 210 if (empty($this->mappingFields) && !empty($this->getMappingID())) {
211 $this->loadSavedMapping();
212 }
0b0285b1 213 return $this->mappingFields;
214 }
215
216 /**
a7b9cf38 217 * Set mapping fields.
218 *
219 * We do a little cleanup here too.
220 *
221 * We ensure that column numbers are set and that the fields are ordered by them.
222 *
223 * This would mean the fields could be loaded unsorted.
224 *
0b0285b1 225 * @param array $mappingFields
226 */
227 public function setMappingFields(array $mappingFields) {
a7b9cf38 228 $i = 0;
229 foreach ($mappingFields as &$mappingField) {
230 if (!isset($mappingField['column_number'])) {
231 $mappingField['column_number'] = $i;
232 }
233 if ($mappingField['column_number'] > $i) {
234 $i = $mappingField['column_number'];
235 }
236 $i++;
237 }
92bde46a 238 $this->mappingFields = $this->rekeyBySortedColumnNumbers($mappingFields);
0b0285b1 239 }
240
241 /**
242 * Get the names of the mapped fields.
92bde46a 243 *
244 * @throws \CiviCRM_API3_Exception
0b0285b1 245 */
246 public function getFieldNames() {
247 return CRM_Utils_Array::collect('name', $this->getMappingFields());
248 }
249
92bde46a 250 /**
251 * Get the field name for the given column.
252 *
253 * @param int $columnNumber
254 *
255 * @return string
256 * @throws \CiviCRM_API3_Exception
257 */
258 public function getFieldName($columnNumber) {
259 return $this->getFieldNames()[$columnNumber];
260 }
261
262 /**
263 * Get the field name for the given column.
264 *
265 * @param int $columnNumber
266 *
267 * @return string
268 * @throws \CiviCRM_API3_Exception
269 */
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'];
273 }
274
275 /**
276 * Get relationship key only if it is valid.
277 *
278 * @param int $columnNumber
279 *
280 * @return string|null
281 *
282 * @throws \CiviCRM_API3_Exception
283 */
284 public function getValidRelationshipKey($columnNumber) {
285 $key = $this->getRelationshipKey($columnNumber);
286 return $this->isValidRelationshipKey($key) ? $key : NULL;
287 }
288
289 /**
290 * Get the IM Provider ID.
291 *
292 * @param int $columnNumber
293 *
294 * @return int
295 *
296 * @throws \CiviCRM_API3_Exception
297 */
298 public function getIMProviderID($columnNumber) {
299 return $this->getMappingFields()[$columnNumber]['im_provider_id'] ?? NULL;
300 }
301
302 /**
303 * Get the Phone Type
304 *
305 * @param int $columnNumber
306 *
307 * @return int
308 *
309 * @throws \CiviCRM_API3_Exception
310 */
311 public function getPhoneTypeID($columnNumber) {
312 return $this->getMappingFields()[$columnNumber]['phone_type_id'] ?? NULL;
313 }
314
315 /**
316 * Get the Website Type
317 *
318 * @param int $columnNumber
319 *
320 * @return int
321 *
322 * @throws \CiviCRM_API3_Exception
323 */
324 public function getWebsiteTypeID($columnNumber) {
325 return $this->getMappingFields()[$columnNumber]['website_type_id'] ?? NULL;
326 }
327
328 /**
329 * Get the Location Type
330 *
331 * Returning 0 rather than null is historical.
332 *
333 * @param int $columnNumber
334 *
335 * @return int
336 *
337 * @throws \CiviCRM_API3_Exception
338 */
339 public function getLocationTypeID($columnNumber) {
340 return $this->getMappingFields()[$columnNumber]['location_type_id'] ?? 0;
341 }
342
343 /**
344 * Get the IM or Phone type.
345 *
346 * We have a field that would be the 'relevant' type - which could be either.
347 *
348 * @param int $columnNumber
349 *
350 * @return int
351 *
352 * @throws \CiviCRM_API3_Exception
353 */
354 public function getPhoneOrIMTypeID($columnNumber) {
355 return $this->getIMProviderID($columnNumber) ?? $this->getPhoneTypeID($columnNumber);
356 }
357
0b0285b1 358 /**
359 * Get the location types of the mapped fields.
92bde46a 360 *
361 * @throws \CiviCRM_API3_Exception
0b0285b1 362 */
363 public function getFieldLocationTypes() {
364 return CRM_Utils_Array::collect('location_type_id', $this->getMappingFields());
365 }
366
367 /**
368 * Get the phone types of the mapped fields.
92bde46a 369 *
370 * @throws \CiviCRM_API3_Exception
0b0285b1 371 */
372 public function getFieldPhoneTypes() {
373 return CRM_Utils_Array::collect('phone_type_id', $this->getMappingFields());
374 }
375
376 /**
377 * Get the names of the im_provider fields.
92bde46a 378 *
379 * @throws \CiviCRM_API3_Exception
0b0285b1 380 */
381 public function getFieldIMProviderTypes() {
382 return CRM_Utils_Array::collect('im_provider_id', $this->getMappingFields());
383 }
384
385 /**
386 * Get the names of the website fields.
92bde46a 387 *
388 * @throws \CiviCRM_API3_Exception
0b0285b1 389 */
390 public function getFieldWebsiteTypes() {
391 return CRM_Utils_Array::collect('im_provider_id', $this->getMappingFields());
392 }
393
394 /**
395 * Get an instance of the importer object.
396 *
397 * @return CRM_Contact_Import_Parser_Contact
92bde46a 398 *
399 * @throws \CiviCRM_API3_Exception
0b0285b1 400 */
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 = [],
409 [],
410 [],
411 [],
412 [],
413 [],
414 [],
415 $this->getFieldWebsiteTypes()
416 // $mapperRelatedContactWebsiteType = []
417 );
418 $importer->init();
419 $importer->_contactType = $this->getContactType();
420 return $importer;
421 }
422
92bde46a 423 /**
424 * Load the mapping from the datbase into the format that would be received from the UI.
425 *
426 * @throws \CiviCRM_API3_Exception
427 */
428 protected function loadSavedMapping() {
429 $fields = civicrm_api3('MappingField', 'get', [
430 'mapping_id' => $this->getMappingID(),
431 'options' => ['limit' => 0],
432 ])['values'];
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']);
438 }
439 else {
440 // Honour legacy chaos factor.
441 $fields[$index]['name'] = strtolower(str_replace(" ", "_", $field['name']));
442 // fix for edge cases, CRM-4954
443 if ($fields[$index]['name'] === 'image_url') {
444 $fields[$index]['name'] = str_replace('url', 'URL', $fields[$index]['name']);
445 }
446 }
447 $fieldSpec = $this->getMetadata()[$fields[$index]['name']];
448 if (empty($field['location_type_id']) && !empty($fieldSpec['hasLocationType'])) {
449 $fields[$index]['location_type_id'] = 'Primary';
450 }
451 }
452 $this->mappingFields = $this->rekeyBySortedColumnNumbers($fields);
453 }
454
455 /**
456 * Get the titles from metadata.
457 */
458 public function getMetadataTitles() {
459 if (empty($this->metadataByTitle)) {
460 $this->metadataByTitle = CRM_Utils_Array::collect('title', $this->getMetadata());
461 }
462 return $this->metadataByTitle;
463 }
464
465 /**
466 * Rekey the array by the column_number.
467 *
468 * @param array $mappingFields
469 *
470 * @return array
471 */
472 protected function rekeyBySortedColumnNumbers(array $mappingFields) {
473 $this->mappingFields = CRM_Utils_Array::rekey($mappingFields, 'column_number');
474 ksort($this->mappingFields);
cd41fa5b 475 return $this->mappingFields;
92bde46a 476 }
477
478 /**
479 * Get the field name from the label.
480 *
481 * @param string $label
482 *
483 * @return string
484 */
485 protected function getNameFromLabel($label) {
486 $titleMap = array_flip($this->getMetadataTitles());
487 return $titleMap[$label] ?? '';
488 }
489
490 /**
491 * Validate the key against the relationships available for the contatct type & subtype.
492 *
493 * @param string $key
494 *
495 * @return bool
496 */
497 protected function isValidRelationshipKey($key) {
498 return !empty($this->getValidRelationships()[$key]) ? TRUE : FALSE;
499 }
500
3c238265 501 /**
502 * Get the relevant js for quickform.
503 *
504 * @param int $column
505 *
506 * @return string
507 * @throws \CiviCRM_API3_Exception
508 */
509 public function getQuickFormJSForField($column) {
510 $columnNumbersToHide = [];
511
512 if (!$this->getLocationTypeID($column) && !$this->getWebsiteTypeID($column)) {
513 $columnNumbersToHide[] = 1;
514 }
515 if (!$this->getPhoneOrIMTypeID($column)) {
516 $columnNumbersToHide[] = 2;
517 }
518 $columnNumbersToHide[] = 3;
519
520 $jsClauses = [];
521 foreach ($columnNumbersToHide as $columnNumber) {
522 $jsClauses[] = $this->getFormName() . "['mapper[$column][" . $columnNumber . "]'].style.display = 'none';";
523 }
524 return implode("\n", $jsClauses) . "\n";
525 }
526
0b0285b1 527}