Commit | Line | Data |
---|---|---|
6a488035 TO |
1 | <?php |
2 | /* | |
3 | +--------------------------------------------------------------------+ | |
bc77d7c0 | 4 | | Copyright CiviCRM LLC. All rights reserved. | |
6a488035 | 5 | | | |
bc77d7c0 TO |
6 | | This work is published under the GNU AGPLv3 license with some | |
7 | | permitted exceptions and without any warranty. For full license | | |
8 | | and copyright information, see https://civicrm.org/licensing | | |
6a488035 | 9 | +--------------------------------------------------------------------+ |
d25dd0ee | 10 | */ |
cdfa6649 | 11 | |
12 | use Civi\Api4\Contact; | |
34f3f22a | 13 | use Civi\Api4\RelationshipType; |
018c9e26 | 14 | use Civi\Api4\StateProvince; |
cdfa6649 | 15 | |
10438107 | 16 | require_once 'api/v3/utils.php'; |
17 | ||
6a488035 TO |
18 | /** |
19 | * | |
20 | * @package CRM | |
ca5cec67 | 21 | * @copyright CiviCRM LLC https://civicrm.org/licensing |
6a488035 TO |
22 | */ |
23 | ||
6a488035 TO |
24 | /** |
25 | * class to parse contact csv files | |
26 | */ | |
68f3bda5 | 27 | class CRM_Contact_Import_Parser_Contact extends CRM_Import_Parser { |
91b4c63e | 28 | |
29 | use CRM_Contact_Import_MetadataTrait; | |
30 | ||
44edf1fc | 31 | protected $_mapperKeys = []; |
5ebaab5d | 32 | protected $_allExternalIdentifiers = []; |
6a488035 TO |
33 | |
34 | /** | |
ceb10dc7 | 35 | * Array of successfully imported contact id's |
6a488035 | 36 | * |
69078420 | 37 | * @var array |
6a488035 | 38 | */ |
1a4443b0 | 39 | protected $_newContacts = []; |
6a488035 TO |
40 | |
41 | /** | |
fe482240 | 42 | * Line count id. |
6a488035 TO |
43 | * |
44 | * @var int | |
45 | */ | |
46 | protected $_lineCount; | |
47 | ||
68f3bda5 EM |
48 | protected $_tableName; |
49 | ||
50 | /** | |
51 | * Total number of lines in file | |
52 | * | |
53 | * @var int | |
54 | */ | |
55 | protected $_rowCount; | |
56 | ||
68f3bda5 | 57 | protected $fieldMetadata = []; |
34f3f22a EM |
58 | |
59 | /** | |
60 | * Relationship labels. | |
61 | * | |
62 | * Temporary cache of labels to reduce queries in getRelationshipLabels. | |
63 | * | |
64 | * @var array | |
65 | * e.g ['5a_b' => 'Employer', '5b_a' => 'Employee'] | |
66 | */ | |
67 | protected $relationshipLabels = []; | |
68 | ||
f363505a EM |
69 | /** |
70 | * Addresses that failed to parse. | |
71 | * | |
72 | * @var array | |
73 | */ | |
74 | private $_unparsedStreetAddressContacts = []; | |
75 | ||
6a488035 | 76 | /** |
186628c3 | 77 | * The initializer code, called before processing. |
6a488035 | 78 | */ |
00be9182 | 79 | public function init() { |
af05126d EM |
80 | // Force re-load of user job. |
81 | unset($this->userJob); | |
52bd01f5 EM |
82 | $this->setFieldMetadata(); |
83 | foreach ($this->getImportableFieldsMetadata() as $name => $field) { | |
84 | $this->addField($name, $field['title'], CRM_Utils_Array::value('type', $field), CRM_Utils_Array::value('headerPattern', $field), CRM_Utils_Array::value('dataPattern', $field), CRM_Utils_Array::value('hasLocationType', $field)); | |
85 | } | |
f363505a | 86 | } |
6a488035 | 87 | |
2a4de39f EM |
88 | /** |
89 | * Get the fields to track the import. | |
90 | * | |
91 | * @return array | |
92 | */ | |
93 | public function getTrackingFields(): array { | |
94 | return [ | |
95 | 'related_contact_created' => 'INT COMMENT "Number of related contacts created"', | |
96 | 'related_contact_matched' => 'INT COMMENT "Number of related contacts found (& potentially updated)"', | |
97 | ]; | |
98 | } | |
99 | ||
f363505a EM |
100 | /** |
101 | * Is street address parsing enabled for the site. | |
102 | */ | |
103 | protected function isParseStreetAddress() : bool { | |
104 | return (bool) (CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'address_options')['street_address_parsing'] ?? FALSE); | |
6a488035 TO |
105 | } |
106 | ||
7e56b830 EM |
107 | /** |
108 | * Is this a case where the user has opted to update existing contacts. | |
109 | * | |
110 | * @return bool | |
111 | * | |
112 | * @throws \API_Exception | |
113 | */ | |
114 | private function isUpdateExistingContacts(): bool { | |
115 | return in_array((int) $this->getSubmittedValue('onDuplicate'), [ | |
116 | CRM_Import_Parser::DUPLICATE_UPDATE, | |
117 | CRM_Import_Parser::DUPLICATE_FILL, | |
118 | ], TRUE); | |
119 | } | |
120 | ||
64623d6c EM |
121 | /** |
122 | * Did the user specify duplicates checking should be skipped, resulting in possible duplicate contacts. | |
123 | * | |
124 | * Note we still need to check for external_identifier as it will hard-fail | |
125 | * if we duplicate. | |
126 | * | |
127 | * @return bool | |
128 | * | |
129 | * @throws \API_Exception | |
130 | */ | |
131 | private function isIgnoreDuplicates(): bool { | |
132 | return ((int) $this->getSubmittedValue('onDuplicate')) === CRM_Import_Parser::DUPLICATE_NOCHECK; | |
133 | } | |
134 | ||
6a488035 | 135 | /** |
fe482240 | 136 | * Handle the values in preview mode. |
6a488035 | 137 | * |
13575591 EM |
138 | * Function will be deprecated in favour of validateValues. |
139 | * | |
77c5b619 TO |
140 | * @param array $values |
141 | * The array of values belonging to this line. | |
6a488035 | 142 | * |
7c550ca0 | 143 | * @return bool |
a6c01b45 | 144 | * the result of this processing |
06ef1cdc | 145 | * CRM_Import_Parser::ERROR or CRM_Import_Parser::VALID |
6a488035 | 146 | */ |
00be9182 | 147 | public function preview(&$values) { |
6a488035 TO |
148 | return $this->summary($values); |
149 | } | |
150 | ||
151 | /** | |
fe482240 | 152 | * Handle the values in summary mode. |
6a488035 | 153 | * |
13575591 EM |
154 | * Function will be deprecated in favour of validateValues. |
155 | * | |
77c5b619 TO |
156 | * @param array $values |
157 | * The array of values belonging to this line. | |
6a488035 | 158 | * |
b0f7df9b | 159 | * @return int |
a6c01b45 | 160 | * the result of this processing |
06ef1cdc | 161 | * CRM_Import_Parser::ERROR or CRM_Import_Parser::VALID |
6a488035 | 162 | */ |
ca275c67 | 163 | public function summary(&$values): int { |
2940ddb3 | 164 | $rowNumber = (int) ($values[array_key_last($values)]); |
b0f7df9b | 165 | try { |
13575591 | 166 | $this->validateValues($values); |
d4c8a770 | 167 | } |
b0f7df9b EM |
168 | catch (CRM_Core_Exception $e) { |
169 | $this->setImportStatus($rowNumber, 'ERROR', $e->getMessage()); | |
170 | array_unshift($values, $e->getMessage()); | |
a05662ef | 171 | return CRM_Import_Parser::ERROR; |
6a488035 | 172 | } |
ca275c67 | 173 | $this->setImportStatus($rowNumber, 'NEW', ''); |
6a488035 | 174 | |
a05662ef | 175 | return CRM_Import_Parser::VALID; |
6a488035 TO |
176 | } |
177 | ||
178 | /** | |
fe482240 | 179 | * Handle the values in import mode. |
6a488035 | 180 | * |
77c5b619 TO |
181 | * @param array $values |
182 | * The array of values belonging to this line. | |
6a488035 | 183 | * |
7c550ca0 | 184 | * @return bool |
a6c01b45 | 185 | * the result of this processing |
a0c6165f | 186 | * |
a0c6165f | 187 | * @throws \CRM_Core_Exception |
cdfa6649 | 188 | * @throws \API_Exception |
6a488035 | 189 | */ |
b75fe839 | 190 | public function import($values) { |
2940ddb3 | 191 | $rowNumber = (int) $values[array_key_last($values)]; |
578e4db3 | 192 | |
be2fb01f | 193 | $this->_unparsedStreetAddressContacts = []; |
dba9288a | 194 | if (!$this->getSubmittedValue('doGeocodeAddress')) { |
6a488035 | 195 | // CRM-5854, reset the geocode method to null to prevent geocoding |
94d2b28e | 196 | CRM_Utils_GeocodeProvider::disableForSession(); |
6a488035 TO |
197 | } |
198 | ||
bf94a235 EM |
199 | try { |
200 | $params = $this->getMappedRow($values); | |
bf94a235 EM |
201 | $formatted = []; |
202 | foreach ($params as $key => $value) { | |
203 | if ($value !== '') { | |
204 | $formatted[$key] = $value; | |
205 | } | |
639e4f37 | 206 | } |
6a488035 | 207 | |
6187f042 | 208 | [$formatted, $params] = $this->processContact($params, $formatted, TRUE); |
e0b8f9a9 | 209 | |
101a203c EM |
210 | //format common data, CRM-4062 |
211 | $this->formatCommonData($params, $formatted); | |
6a488035 | 212 | |
101a203c | 213 | $newContact = $this->createContact($formatted, $params['id'] ?? NULL); |
2a4de39f | 214 | $contactID = $newContact->id; |
6a488035 | 215 | |
101a203c EM |
216 | if ($contactID) { |
217 | // call import hook | |
218 | $currentImportID = end($values); | |
101a203c EM |
219 | $hookParams = [ |
220 | 'contactID' => $contactID, | |
221 | 'importID' => $currentImportID, | |
222 | 'importTempTable' => $this->_tableName, | |
223 | 'fieldHeaders' => $this->_mapperKeys, | |
224 | 'fields' => $this->_activeFields, | |
225 | ]; | |
101a203c EM |
226 | CRM_Utils_Hook::import('Contact', 'process', $this, $hookParams); |
227 | } | |
6a488035 | 228 | |
101a203c | 229 | $primaryContactId = $newContact->id; |
6a488035 | 230 | |
60110bfa EM |
231 | //relationship contact insert |
232 | foreach ($this->getRelatedContactsParams($params) as $key => $field) { | |
233 | $formatting = $field; | |
101a203c | 234 | [$formatting, $field] = $this->processContact($field, $formatting, FALSE); |
6a488035 | 235 | |
60110bfa | 236 | //format common data, CRM-4062 |
101a203c | 237 | $this->formatCommonData($field, $formatting); |
6a488035 | 238 | |
1149a00c | 239 | if (empty($formatting['id']) || $this->isUpdateExistingContacts()) { |
101a203c | 240 | $relatedNewContact = $this->createContact($formatting, $formatting['id']); |
2a4de39f EM |
241 | $formatting['id'] = $relatedNewContact->id; |
242 | } | |
243 | if (empty($relatedContacts[$formatting['id']])) { | |
244 | $relatedContacts[$formatting['id']] = 'new'; | |
60110bfa | 245 | } |
2a4de39f EM |
246 | |
247 | $this->createRelationship($key, $formatting['id'], $primaryContactId); | |
6a488035 TO |
248 | } |
249 | } | |
101a203c EM |
250 | catch (CRM_Core_Exception $e) { |
251 | $this->setImportStatus($rowNumber, $this->getStatus($e->getErrorCode()), $e->getMessage()); | |
252 | return FALSE; | |
253 | } | |
2a4de39f EM |
254 | // We can probably stop catching this once https://github.com/civicrm/civicrm-core/pull/23471 |
255 | // is merged - testImportParserWithExternalIdForRelationship will confirm.... | |
256 | catch (CiviCRM_API3_Exception $e) { | |
257 | $this->setImportStatus($rowNumber, $this->getStatus($e->getErrorCode()), $e->getMessage()); | |
258 | return FALSE; | |
259 | } | |
260 | $extraFields = ['related_contact_created' => 0, 'related_contact_matched' => 0]; | |
261 | foreach ($relatedContacts as $key => $outcome) { | |
262 | if ($outcome === 'new') { | |
263 | $extraFields['related_contact_created']++; | |
264 | } | |
265 | else { | |
266 | $extraFields['related_contact_matched']++; | |
267 | } | |
268 | } | |
269 | $this->setImportStatus($rowNumber, $this->getStatus(CRM_Import_Parser::VALID), $this->getSuccessMessage(), $contactID, $extraFields); | |
b2c28e7f | 270 | return CRM_Import_Parser::VALID; |
6a488035 TO |
271 | } |
272 | ||
3b9a6946 EM |
273 | /** |
274 | * Only called from import now... plus one place outside of core & tests. | |
275 | * | |
276 | * @todo - deprecate more aggressively - will involve copying to the import | |
277 | * class, adding a deprecation notice here & removing from tests. | |
278 | * | |
279 | * Takes an associative array and creates a relationship object. | |
280 | * | |
281 | * @deprecated For single creates use the api instead (it's tested). | |
282 | * For multiple a new variant of this function needs to be written and migrated to as this is a bit | |
283 | * nasty | |
284 | * | |
285 | * @param array $params | |
286 | * (reference ) an assoc array of name/value pairs. | |
287 | * @param array $ids | |
288 | * The array that holds all the db ids. | |
289 | * per http://wiki.civicrm.org/confluence/display/CRM/Database+layer | |
290 | * "we are moving away from the $ids param " | |
291 | * | |
292 | * @return array | |
293 | * @throws \CRM_Core_Exception | |
294 | */ | |
295 | private static function legacyCreateMultiple($params, $ids = []) { | |
296 | // clarify that the only key ever pass in the ids array is 'contact' | |
297 | // There is legacy handling for other keys but a universe search on | |
298 | // calls to this function (not supported to be called from outside core) | |
299 | // only returns 2 calls - one in CRM_Contact_Import_Parser_Contact | |
300 | // and the other in jma grant applications (CRM_Grant_Form_Grant_Confirm) | |
301 | // both only pass in contact as a key here. | |
302 | $contactID = $ids['contact']; | |
303 | unset($ids); | |
304 | // There is only ever one value passed in from the 2 places above that call | |
305 | // this - by clarifying here like this we can cleanup within this | |
306 | // function without having to do more universe searches. | |
307 | $relatedContactID = key($params['contact_check']); | |
308 | ||
309 | // check if the relationship is valid between contacts. | |
310 | // step 1: check if the relationship is valid if not valid skip and keep the count | |
311 | // step 2: check the if two contacts already have a relationship if yes skip and keep the count | |
312 | // step 3: if valid relationship then add the relation and keep the count | |
313 | ||
314 | // step 1 | |
315 | [$contactFields['relationship_type_id'], $firstLetter, $secondLetter] = explode('_', $params['relationship_type_id']); | |
316 | $contactFields['contact_id_' . $firstLetter] = $contactID; | |
317 | $contactFields['contact_id_' . $secondLetter] = $relatedContactID; | |
318 | if (!CRM_Contact_BAO_Relationship::checkRelationshipType($contactFields['contact_id_a'], $contactFields['contact_id_b'], | |
319 | $contactFields['relationship_type_id'])) { | |
320 | return [0, 0]; | |
321 | } | |
322 | ||
323 | if ( | |
324 | CRM_Contact_BAO_Relationship::checkDuplicateRelationship( | |
325 | $contactFields, | |
326 | $contactID, | |
327 | // step 2 | |
328 | $relatedContactID | |
329 | ) | |
330 | ) { | |
331 | return [0, 1]; | |
332 | } | |
333 | ||
334 | $singleInstanceParams = array_merge($params, $contactFields); | |
335 | CRM_Contact_BAO_Relationship::add($singleInstanceParams); | |
336 | return [1, 0]; | |
337 | } | |
338 | ||
54847287 | 339 | /** |
7b033be4 EM |
340 | * Format common params data to the format that was required a very long time ago. |
341 | * | |
342 | * I think the only useful things this function does now are | |
343 | * 1) calls fillPrimary | |
344 | * 2) possibly the street address parsing. | |
345 | * | |
346 | * The other hundred lines do stuff that is done elsewhere. Custom fields | |
347 | * should already be formatted by getTransformedValue and we don't need to | |
348 | * re-rewrite them to a BAO style array since we call the api which does that. | |
349 | * | |
350 | * The call to formatLocationBlock just does the address custom fields which, | |
351 | * are already formatted by this point. | |
352 | * | |
353 | * @deprecated | |
54847287 EM |
354 | * |
355 | * @param array $params | |
356 | * Contain record values. | |
357 | * @param array $formatted | |
358 | * Array of formatted data. | |
54847287 | 359 | */ |
7b033be4 EM |
360 | private function formatCommonData($params, &$formatted) { |
361 | // @todo - remove just about everything in this function. See docblock. | |
e0b8f9a9 | 362 | $customFields = CRM_Core_BAO_CustomField::getFields($formatted['contact_type'], FALSE, FALSE, $formatted['contact_sub_type'] ?? NULL); |
54847287 EM |
363 | |
364 | $addressCustomFields = CRM_Core_BAO_CustomField::getFields('Address'); | |
365 | $customFields = $customFields + $addressCustomFields; | |
366 | ||
54847287 EM |
367 | //format date first |
368 | $session = CRM_Core_Session::singleton(); | |
369 | $dateType = $session->get("dateTypes"); | |
370 | foreach ($params as $key => $val) { | |
371 | $customFieldID = CRM_Core_BAO_CustomField::getKeyID($key); | |
372 | if ($customFieldID && | |
373 | !array_key_exists($customFieldID, $addressCustomFields) | |
374 | ) { | |
375 | //we should not update Date to null, CRM-4062 | |
376 | if ($val && ($customFields[$customFieldID]['data_type'] == 'Date')) { | |
377 | //CRM-21267 | |
4b58c5c4 | 378 | $this->formatCustomDate($params, $formatted, $dateType, $key); |
54847287 EM |
379 | } |
380 | elseif ($customFields[$customFieldID]['data_type'] == 'Boolean') { | |
77c96d86 | 381 | if (empty($val) && !is_numeric($val) && $this->isFillDuplicates()) { |
54847287 EM |
382 | //retain earlier value when Import mode is `Fill` |
383 | unset($params[$key]); | |
384 | } | |
385 | else { | |
386 | $params[$key] = CRM_Utils_String::strtoboolstr($val); | |
387 | } | |
388 | } | |
389 | } | |
54847287 | 390 | } |
018c9e26 | 391 | $metadataBlocks = ['phone', 'im', 'openid', 'email', 'address']; |
639e4f37 EM |
392 | foreach ($metadataBlocks as $block) { |
393 | foreach ($formatted[$block] ?? [] as $blockKey => $blockValues) { | |
394 | if ($blockValues['location_type_id'] === 'Primary') { | |
395 | $this->fillPrimary($formatted[$block][$blockKey], $blockValues, $block, $formatted['id'] ?? NULL); | |
396 | } | |
397 | } | |
398 | } | |
54847287 EM |
399 | //now format custom data. |
400 | foreach ($params as $key => $field) { | |
639e4f37 EM |
401 | if (in_array($key, $metadataBlocks, TRUE)) { |
402 | // This location block is already fully handled at this point. | |
403 | continue; | |
404 | } | |
54847287 EM |
405 | if (is_array($field)) { |
406 | $isAddressCustomField = FALSE; | |
639e4f37 | 407 | |
54847287 EM |
408 | foreach ($field as $value) { |
409 | $break = FALSE; | |
410 | if (is_array($value)) { | |
411 | foreach ($value as $name => $testForEmpty) { | |
412 | if ($addressCustomFieldID = CRM_Core_BAO_CustomField::getKeyID($name)) { | |
413 | $isAddressCustomField = TRUE; | |
414 | break; | |
415 | } | |
639e4f37 EM |
416 | |
417 | if (($testForEmpty === '' || $testForEmpty == NULL)) { | |
54847287 EM |
418 | $break = TRUE; |
419 | break; | |
420 | } | |
421 | } | |
422 | } | |
423 | else { | |
424 | $break = TRUE; | |
425 | } | |
426 | ||
427 | if (!$break) { | |
428 | if (!empty($value['location_type_id'])) { | |
429 | $this->formatLocationBlock($value, $formatted); | |
430 | } | |
54847287 EM |
431 | } |
432 | } | |
433 | if (!$isAddressCustomField) { | |
434 | continue; | |
435 | } | |
436 | } | |
437 | ||
438 | $formatValues = [ | |
439 | $key => $field, | |
440 | ]; | |
441 | ||
54847287 EM |
442 | if ($key == 'id' && isset($field)) { |
443 | $formatted[$key] = $field; | |
444 | } | |
445 | $this->formatContactParameters($formatValues, $formatted); | |
446 | ||
447 | //Handling Custom Data | |
448 | // note: Address custom fields will be handled separately inside formatContactParameters | |
449 | if (($customFieldID = CRM_Core_BAO_CustomField::getKeyID($key)) && | |
450 | array_key_exists($customFieldID, $customFields) && | |
451 | !array_key_exists($customFieldID, $addressCustomFields) | |
452 | ) { | |
453 | ||
454 | $extends = $customFields[$customFieldID]['extends'] ?? NULL; | |
455 | $htmlType = $customFields[$customFieldID]['html_type'] ?? NULL; | |
456 | $dataType = $customFields[$customFieldID]['data_type'] ?? NULL; | |
457 | $serialized = CRM_Core_BAO_CustomField::isSerialized($customFields[$customFieldID]); | |
458 | ||
459 | if (!$serialized && in_array($htmlType, ['Select', 'Radio', 'Autocomplete-Select']) && in_array($dataType, ['String', 'Int'])) { | |
460 | $customOption = CRM_Core_BAO_CustomOption::getCustomOption($customFieldID, TRUE); | |
461 | foreach ($customOption as $customValue) { | |
462 | $val = $customValue['value'] ?? NULL; | |
463 | $label = strtolower($customValue['label'] ?? ''); | |
464 | $value = strtolower(trim($formatted[$key])); | |
465 | if (($value == $label) || ($value == strtolower($val))) { | |
466 | $params[$key] = $formatted[$key] = $val; | |
467 | } | |
468 | } | |
469 | } | |
470 | elseif ($serialized && !empty($formatted[$key]) && !empty($params[$key])) { | |
471 | $mulValues = explode(',', $formatted[$key]); | |
472 | $customOption = CRM_Core_BAO_CustomOption::getCustomOption($customFieldID, TRUE); | |
473 | $formatted[$key] = []; | |
474 | $params[$key] = []; | |
475 | foreach ($mulValues as $v1) { | |
476 | foreach ($customOption as $v2) { | |
477 | if ((strtolower($v2['label']) == strtolower(trim($v1))) || | |
478 | (strtolower($v2['value']) == strtolower(trim($v1))) | |
479 | ) { | |
480 | if ($htmlType == 'CheckBox') { | |
481 | $params[$key][$v2['value']] = $formatted[$key][$v2['value']] = 1; | |
482 | } | |
483 | else { | |
484 | $params[$key][] = $formatted[$key][] = $v2['value']; | |
485 | } | |
486 | } | |
487 | } | |
488 | } | |
489 | } | |
490 | } | |
491 | } | |
492 | ||
493 | if (!empty($key) && ($customFieldID = CRM_Core_BAO_CustomField::getKeyID($key)) && array_key_exists($customFieldID, $customFields) && | |
494 | !array_key_exists($customFieldID, $addressCustomFields) | |
495 | ) { | |
496 | // @todo calling api functions directly is not supported | |
497 | _civicrm_api3_custom_format_params($params, $formatted, $extends); | |
498 | } | |
499 | ||
54847287 | 500 | // parse street address, CRM-5450 |
f363505a | 501 | if ($this->isParseStreetAddress()) { |
54847287 EM |
502 | if (array_key_exists('address', $formatted) && is_array($formatted['address'])) { |
503 | foreach ($formatted['address'] as $instance => & $address) { | |
504 | $streetAddress = $address['street_address'] ?? NULL; | |
505 | if (empty($streetAddress)) { | |
506 | continue; | |
507 | } | |
508 | // parse address field. | |
509 | $parsedFields = CRM_Core_BAO_Address::parseStreetAddress($streetAddress); | |
510 | ||
511 | //street address consider to be parsed properly, | |
512 | //If we get street_name and street_number. | |
513 | if (empty($parsedFields['street_name']) || empty($parsedFields['street_number'])) { | |
514 | $parsedFields = array_fill_keys(array_keys($parsedFields), ''); | |
515 | } | |
516 | ||
517 | // merge parse address w/ main address block. | |
518 | $address = array_merge($address, $parsedFields); | |
519 | } | |
520 | } | |
521 | } | |
522 | } | |
523 | ||
6a488035 | 524 | /** |
100fef9d | 525 | * Build error-message containing error-fields |
6a488035 | 526 | * |
341c643b | 527 | * Once upon a time there was a dev who hadn't heard of implode. That dev wrote this function. |
528 | * | |
529 | * @todo just say no! | |
530 | * | |
77c5b619 TO |
531 | * @param string $errorName |
532 | * A string containing error-field name. | |
533 | * @param string $errorMessage | |
534 | * A string containing all the error-fields, where the new errorName is concatenated. | |
6a488035 | 535 | * |
6a488035 | 536 | */ |
00be9182 | 537 | public static function addToErrorMsg($errorName, &$errorMessage) { |
6a488035 TO |
538 | if ($errorMessage) { |
539 | $errorMessage .= "; $errorName"; | |
540 | } | |
541 | else { | |
542 | $errorMessage = $errorName; | |
543 | } | |
544 | } | |
545 | ||
bf94a235 EM |
546 | /** |
547 | * @param array $params | |
548 | * | |
549 | * @return string|null | |
550 | * @throws \API_Exception | |
551 | * @throws \CRM_Core_Exception | |
552 | * @throws \Civi\API\Exception\NotImplementedException | |
553 | */ | |
554 | protected function validateParams(array $params): ?string { | |
555 | $contacts = array_merge(['0' => $params], $this->getRelatedContactsParams($params)); | |
556 | $errors = []; | |
557 | foreach ($contacts as $value) { | |
558 | // If we are referencing a related contact, or are in update mode then we | |
559 | // don't need all the required fields if we have enough to find an existing contact. | |
560 | $useExistingMatchFields = !empty($value['relationship_type_id']) || $this->isUpdateExistingContacts(); | |
561 | $prefixString = !empty($value['relationship_label']) ? '(' . $value['relationship_label'] . ') ' : ''; | |
562 | $this->validateRequiredContactFields($value['contact_type'], $value, $useExistingMatchFields, $prefixString); | |
563 | ||
564 | $errors = array_merge($errors, $this->getInvalidValuesForContact($value, $prefixString)); | |
565 | if (!empty($value['contact_sub_type']) && !CRM_Contact_BAO_ContactType::isExtendsContactType($value['contact_sub_type'], $value['contact_type'])) { | |
566 | $errors[] = ts('Mismatched or Invalid Contact Subtype.'); | |
567 | } | |
568 | if (!empty($value['relationship_type_id'])) { | |
569 | $requiredSubType = $this->getRelatedContactSubType($value['relationship_type_id'], $value['relationship_direction']); | |
570 | if ($requiredSubType && $value['contact_sub_type'] && $requiredSubType !== $value['contact_sub_type']) { | |
571 | throw new CRM_Core_Exception($prefixString . ts('Mismatched or Invalid contact subtype found for this related contact.')); | |
572 | } | |
573 | } | |
574 | } | |
575 | ||
576 | //check for duplicate external Identifier | |
577 | $externalID = $params['external_identifier'] ?? NULL; | |
578 | if ($externalID) { | |
579 | /* If it's a dupe,external Identifier */ | |
580 | ||
581 | if ($externalDupe = CRM_Utils_Array::value($externalID, $this->_allExternalIdentifiers)) { | |
582 | $errorMessage = ts('External ID conflicts with record %1', [1 => $externalDupe]); | |
583 | throw new CRM_Core_Exception($errorMessage); | |
584 | } | |
585 | //otherwise, count it and move on | |
586 | $this->_allExternalIdentifiers[$externalID] = $this->_lineCount; | |
587 | } | |
588 | ||
589 | //date-format part ends | |
590 | ||
591 | $errorMessage = implode(', ', $errors); | |
592 | ||
593 | //checking error in core data | |
bf94a235 EM |
594 | if ($errorMessage) { |
595 | $tempMsg = "Invalid value for field(s) : $errorMessage"; | |
596 | throw new CRM_Core_Exception($tempMsg); | |
597 | } | |
598 | return $errorMessage; | |
599 | } | |
600 | ||
31fdf296 EM |
601 | /** |
602 | * @param $key | |
603 | * @param $relContactId | |
604 | * @param $primaryContactId | |
605 | * | |
606 | * @throws \CRM_Core_Exception | |
607 | * @throws \CiviCRM_API3_Exception | |
608 | */ | |
609 | protected function createRelationship($key, $relContactId, $primaryContactId): void { | |
610 | //if more than one duplicate contact | |
611 | //found, create relationship with first contact | |
612 | // now create the relationship record | |
613 | $relationParams = [ | |
614 | 'relationship_type_id' => $key, | |
615 | 'contact_check' => [ | |
616 | $relContactId => 1, | |
617 | ], | |
618 | 'is_active' => 1, | |
619 | 'skipRecentView' => TRUE, | |
620 | ]; | |
621 | ||
622 | // we only handle related contact success, we ignore failures for now | |
623 | // at some point wold be nice to have related counts as separate | |
624 | $relationIds = [ | |
625 | 'contact' => $primaryContactId, | |
626 | ]; | |
627 | ||
f363505a | 628 | [$valid, $duplicate] = self::legacyCreateMultiple($relationParams, $relationIds); |
31fdf296 EM |
629 | |
630 | if ($valid || $duplicate) { | |
631 | $relationIds['contactTarget'] = $relContactId; | |
632 | $action = ($duplicate) ? CRM_Core_Action::UPDATE : CRM_Core_Action::ADD; | |
633 | CRM_Contact_BAO_Relationship::relatedMemberships($primaryContactId, $relationParams, $relationIds, $action); | |
634 | } | |
635 | ||
636 | //handle current employer, CRM-3532 | |
637 | if ($valid) { | |
638 | $allRelationships = CRM_Core_PseudoConstant::relationshipType('name'); | |
639 | $relationshipTypeId = str_replace([ | |
640 | '_a_b', | |
641 | '_b_a', | |
642 | ], [ | |
643 | '', | |
644 | '', | |
645 | ], $key); | |
646 | $relationshipType = str_replace($relationshipTypeId . '_', '', $key); | |
647 | $orgId = $individualId = NULL; | |
648 | if ($allRelationships[$relationshipTypeId]["name_{$relationshipType}"] == 'Employee of') { | |
649 | $orgId = $relContactId; | |
650 | $individualId = $primaryContactId; | |
651 | } | |
652 | elseif ($allRelationships[$relationshipTypeId]["name_{$relationshipType}"] == 'Employer of') { | |
653 | $orgId = $primaryContactId; | |
654 | $individualId = $relContactId; | |
655 | } | |
656 | if ($orgId && $individualId) { | |
657 | $currentEmpParams[$individualId] = $orgId; | |
658 | CRM_Contact_BAO_Contact_Utils::setCurrentEmployer($currentEmpParams); | |
659 | } | |
660 | } | |
661 | } | |
662 | ||
6a488035 | 663 | /** |
fe482240 | 664 | * Method for creating contact. |
54957108 | 665 | * |
666 | * @param array $formatted | |
54957108 | 667 | * @param int $contactId |
54957108 | 668 | * |
07b7795e | 669 | * @return \CRM_Contact_BAO_Contact |
eaedbd91 | 670 | * If a duplicate is found an array is returned, otherwise CRM_Contact_BAO_Contact |
6a488035 | 671 | */ |
93751158 | 672 | public function createContact(&$formatted, $contactId = NULL) { |
6a488035 | 673 | |
9789bc5f | 674 | if ($contactId) { |
77c96d86 | 675 | $this->formatParams($formatted, (int) $contactId); |
9789bc5f | 676 | } |
6a488035 | 677 | |
9789bc5f | 678 | // Resetting and rebuilding cache could be expensive. |
679 | CRM_Core_Config::setPermitCacheFlushMode(FALSE); | |
680 | ||
681 | // If a user has logged in, or accessed via a checksum | |
682 | // Then deliberately 'blanking' a value in the profile should remove it from their record | |
683 | // @todo this should either be TRUE or FALSE in the context of import - once | |
684 | // we figure out which we can remove all the rest. | |
685 | // Also note the meaning of this parameter is less than it used to | |
686 | // be following block cleanup. | |
687 | $formatted['updateBlankLocInfo'] = TRUE; | |
688 | if ((CRM_Core_Session::singleton()->get('authSrc') & (CRM_Core_Permission::AUTH_SRC_CHECKSUM + CRM_Core_Permission::AUTH_SRC_LOGIN)) == 0) { | |
689 | $formatted['updateBlankLocInfo'] = FALSE; | |
690 | } | |
29f811de | 691 | |
93751158 | 692 | $contactFields = CRM_Contact_DAO_Contact::import(); |
897732d6 | 693 | [$data, $contactDetails] = $this->formatProfileContactParams($formatted, $contactFields, $contactId, $formatted['contact_type']); |
9789bc5f | 694 | |
695 | // manage is_opt_out | |
696 | if (array_key_exists('is_opt_out', $contactFields) && array_key_exists('is_opt_out', $formatted)) { | |
697 | $wasOptOut = $contactDetails['is_opt_out'] ?? FALSE; | |
698 | $isOptOut = $formatted['is_opt_out']; | |
699 | $data['is_opt_out'] = $isOptOut; | |
700 | // on change, create new civicrm_subscription_history entry | |
701 | if (($wasOptOut != $isOptOut) && !empty($contactDetails['contact_id'])) { | |
702 | $shParams = [ | |
703 | 'contact_id' => $contactDetails['contact_id'], | |
704 | 'status' => $isOptOut ? 'Removed' : 'Added', | |
705 | 'method' => 'Web', | |
706 | ]; | |
707 | CRM_Contact_BAO_SubscriptionHistory::create($shParams); | |
29f811de | 708 | } |
9789bc5f | 709 | } |
29f811de | 710 | |
cdfa6649 | 711 | $contact = civicrm_api3('Contact', 'create', $data); |
712 | $cid = $contact['id']; | |
29f811de | 713 | |
9789bc5f | 714 | CRM_Core_Config::setPermitCacheFlushMode(TRUE); |
6a488035 | 715 | |
9789bc5f | 716 | $contact = [ |
717 | 'contact_id' => $cid, | |
718 | ]; | |
6a488035 | 719 | |
9789bc5f | 720 | $defaults = []; |
721 | $newContact = CRM_Contact_BAO_Contact::retrieve($contact, $defaults); | |
6a488035 TO |
722 | |
723 | //get the id of the contact whose street address is not parsable, CRM-5886 | |
f363505a | 724 | if ($this->isParseStreetAddress() && property_exists($newContact, 'address') && $newContact->address) { |
6a488035 | 725 | foreach ($newContact->address as $address) { |
8cc574cf | 726 | if (!empty($address['street_address']) && (empty($address['street_number']) || empty($address['street_name']))) { |
be2fb01f | 727 | $this->_unparsedStreetAddressContacts[] = [ |
6a488035 TO |
728 | 'id' => $newContact->id, |
729 | 'streetAddress' => $address['street_address'], | |
be2fb01f | 730 | ]; |
6a488035 TO |
731 | } |
732 | } | |
733 | } | |
734 | return $newContact; | |
735 | } | |
736 | ||
b248c095 EM |
737 | /** |
738 | * Legacy format profile contact parameters. | |
739 | * | |
740 | * This is a formerly shared function - most of the stuff in it probably does | |
741 | * nothing but copied here to star unravelling that... | |
742 | * | |
743 | * @param array $params | |
744 | * @param array $fields | |
745 | * @param int|null $contactID | |
b248c095 | 746 | * @param string|null $ctype |
b248c095 EM |
747 | * |
748 | * @return array | |
749 | */ | |
750 | private function formatProfileContactParams( | |
751 | &$params, | |
752 | $fields, | |
753 | $contactID = NULL, | |
897732d6 | 754 | $ctype = NULL |
b248c095 EM |
755 | ) { |
756 | ||
757 | $data = $contactDetails = []; | |
758 | ||
759 | // get the contact details (hier) | |
760 | if ($contactID) { | |
761 | $details = CRM_Contact_BAO_Contact::getHierContactDetails($contactID, $fields); | |
762 | ||
763 | $contactDetails = $details[$contactID]; | |
764 | $data['contact_type'] = $contactDetails['contact_type'] ?? NULL; | |
765 | $data['contact_sub_type'] = $contactDetails['contact_sub_type'] ?? NULL; | |
766 | } | |
767 | else { | |
768 | //we should get contact type only if contact | |
897732d6 | 769 | if ($ctype) { |
b248c095 EM |
770 | $data['contact_type'] = $ctype; |
771 | } | |
772 | else { | |
773 | $data['contact_type'] = 'Individual'; | |
774 | } | |
775 | } | |
776 | ||
777 | //fix contact sub type CRM-5125 | |
778 | if (array_key_exists('contact_sub_type', $params) && | |
779 | !empty($params['contact_sub_type']) | |
780 | ) { | |
781 | $data['contact_sub_type'] = CRM_Utils_Array::implodePadded($params['contact_sub_type']); | |
782 | } | |
783 | elseif (array_key_exists('contact_sub_type_hidden', $params) && | |
784 | !empty($params['contact_sub_type_hidden']) | |
785 | ) { | |
786 | // if profile was used, and had any subtype, we obtain it from there | |
787 | //CRM-13596 - add to existing contact types, rather than overwriting | |
788 | if (empty($data['contact_sub_type'])) { | |
789 | // If we don't have a contact ID the $data['contact_sub_type'] will not be defined... | |
790 | $data['contact_sub_type'] = CRM_Utils_Array::implodePadded($params['contact_sub_type_hidden']); | |
791 | } | |
792 | else { | |
793 | $data_contact_sub_type_arr = CRM_Utils_Array::explodePadded($data['contact_sub_type']); | |
794 | if (!in_array($params['contact_sub_type_hidden'], $data_contact_sub_type_arr)) { | |
795 | //CRM-20517 - make sure contact_sub_type gets the correct delimiters | |
796 | $data['contact_sub_type'] = trim($data['contact_sub_type'], CRM_Core_DAO::VALUE_SEPARATOR); | |
797 | $data['contact_sub_type'] = CRM_Core_DAO::VALUE_SEPARATOR . $data['contact_sub_type'] . CRM_Utils_Array::implodePadded($params['contact_sub_type_hidden']); | |
798 | } | |
799 | } | |
800 | } | |
801 | ||
802 | if ($ctype == 'Organization') { | |
803 | $data['organization_name'] = $contactDetails['organization_name'] ?? NULL; | |
804 | } | |
805 | elseif ($ctype == 'Household') { | |
806 | $data['household_name'] = $contactDetails['household_name'] ?? NULL; | |
807 | } | |
808 | ||
809 | $locationType = []; | |
810 | $count = 1; | |
811 | ||
812 | if ($contactID) { | |
813 | //add contact id | |
814 | $data['contact_id'] = $contactID; | |
815 | $primaryLocationType = CRM_Contact_BAO_Contact::getPrimaryLocationType($contactID); | |
816 | } | |
817 | else { | |
818 | $defaultLocation = CRM_Core_BAO_LocationType::getDefault(); | |
819 | $defaultLocationId = $defaultLocation->id; | |
820 | } | |
821 | ||
822 | $billingLocationTypeId = CRM_Core_BAO_LocationType::getBilling(); | |
823 | ||
b248c095 | 824 | $multiplFields = ['url']; |
b248c095 | 825 | |
b248c095 EM |
826 | $session = CRM_Core_Session::singleton(); |
827 | foreach ($params as $key => $value) { | |
828 | [$fieldName, $locTypeId, $typeId] = CRM_Utils_System::explode('-', $key, 3); | |
829 | ||
830 | if ($locTypeId == 'Primary') { | |
831 | if ($contactID) { | |
639e4f37 | 832 | $locTypeId = CRM_Contact_BAO_Contact::getPrimaryLocationType($contactID, FALSE, 'address'); |
b248c095 EM |
833 | $primaryLocationType = $locTypeId; |
834 | } | |
835 | else { | |
836 | $locTypeId = $defaultLocationId; | |
837 | } | |
838 | } | |
839 | ||
840 | if (is_numeric($locTypeId) && | |
841 | !in_array($fieldName, $multiplFields) && | |
842 | substr($fieldName, 0, 7) != 'custom_' | |
843 | ) { | |
844 | $index = $locTypeId; | |
845 | ||
846 | if (is_numeric($typeId)) { | |
847 | $index .= '-' . $typeId; | |
848 | } | |
849 | if (!in_array($index, $locationType)) { | |
850 | $locationType[$count] = $index; | |
851 | $count++; | |
852 | } | |
853 | ||
854 | $loc = CRM_Utils_Array::key($index, $locationType); | |
855 | ||
4586ae70 | 856 | $blockName = strtolower($this->getFieldEntity($fieldName)); |
b248c095 EM |
857 | |
858 | $data[$blockName][$loc]['location_type_id'] = $locTypeId; | |
859 | ||
860 | //set is_billing true, for location type "Billing" | |
861 | if ($locTypeId == $billingLocationTypeId) { | |
862 | $data[$blockName][$loc]['is_billing'] = 1; | |
863 | } | |
864 | ||
865 | if ($contactID) { | |
866 | //get the primary location type | |
867 | if ($locTypeId == $primaryLocationType) { | |
868 | $data[$blockName][$loc]['is_primary'] = 1; | |
869 | } | |
870 | } | |
871 | elseif ($locTypeId == $defaultLocationId) { | |
872 | $data[$blockName][$loc]['is_primary'] = 1; | |
873 | } | |
874 | ||
639e4f37 | 875 | if (0) { |
b248c095 EM |
876 | } |
877 | else { | |
878 | if ($fieldName === 'state_province') { | |
879 | // CRM-3393 | |
880 | if (is_numeric($value) && ((int ) $value) >= 1000) { | |
881 | $data['address'][$loc]['state_province_id'] = $value; | |
882 | } | |
883 | elseif (empty($value)) { | |
884 | $data['address'][$loc]['state_province_id'] = ''; | |
885 | } | |
886 | else { | |
887 | $data['address'][$loc]['state_province'] = $value; | |
888 | } | |
889 | } | |
24948d41 EM |
890 | elseif ($fieldName === 'country_id') { |
891 | $data['address'][$loc]['country_id'] = $value; | |
b248c095 EM |
892 | } |
893 | elseif ($fieldName === 'county') { | |
894 | $data['address'][$loc]['county_id'] = $value; | |
895 | } | |
896 | elseif ($fieldName == 'address_name') { | |
897 | $data['address'][$loc]['name'] = $value; | |
898 | } | |
899 | elseif (substr($fieldName, 0, 14) === 'address_custom') { | |
900 | $data['address'][$loc][substr($fieldName, 8)] = $value; | |
901 | } | |
902 | else { | |
903 | $data[$blockName][$loc][$fieldName] = $value; | |
904 | } | |
905 | } | |
906 | } | |
907 | else { | |
80e9f1a2 | 908 | if (($customFieldId = CRM_Core_BAO_CustomField::getKeyID($key))) { |
b248c095 EM |
909 | // for autocomplete transfer hidden value instead of label |
910 | if ($params[$key] && isset($params[$key . '_id'])) { | |
911 | $value = $params[$key . '_id']; | |
912 | } | |
913 | ||
914 | // we need to append time with date | |
915 | if ($params[$key] && isset($params[$key . '_time'])) { | |
916 | $value .= ' ' . $params[$key . '_time']; | |
917 | } | |
918 | ||
919 | // if auth source is not checksum / login && $value is blank, do not proceed - CRM-10128 | |
920 | if (($session->get('authSrc') & (CRM_Core_Permission::AUTH_SRC_CHECKSUM + CRM_Core_Permission::AUTH_SRC_LOGIN)) == 0 && | |
921 | ($value == '' || !isset($value)) | |
922 | ) { | |
923 | continue; | |
924 | } | |
925 | ||
926 | $valueId = NULL; | |
b248c095 EM |
927 | |
928 | //CRM-13596 - check for contact_sub_type_hidden first | |
929 | if (array_key_exists('contact_sub_type_hidden', $params)) { | |
930 | $type = $params['contact_sub_type_hidden']; | |
931 | } | |
932 | else { | |
933 | $type = $data['contact_type']; | |
934 | if (!empty($data['contact_sub_type'])) { | |
935 | $type = CRM_Utils_Array::explodePadded($data['contact_sub_type']); | |
936 | } | |
937 | } | |
938 | ||
939 | CRM_Core_BAO_CustomField::formatCustomField($customFieldId, | |
940 | $data['custom'], | |
941 | $value, | |
942 | $type, | |
943 | $valueId, | |
944 | $contactID, | |
945 | FALSE, | |
946 | FALSE | |
947 | ); | |
948 | } | |
949 | elseif ($key === 'edit') { | |
950 | continue; | |
951 | } | |
952 | else { | |
953 | if ($key === 'location') { | |
954 | foreach ($value as $locationTypeId => $field) { | |
955 | foreach ($field as $block => $val) { | |
956 | if ($block === 'address' && array_key_exists('address_name', $val)) { | |
957 | $value[$locationTypeId][$block]['name'] = $value[$locationTypeId][$block]['address_name']; | |
958 | } | |
959 | } | |
960 | } | |
961 | } | |
639e4f37 | 962 | if (in_array($key, ['nick_name', 'job_title', 'middle_name', 'birth_date', 'gender_id', 'current_employer', 'prefix_id', 'suffix_id']) |
b248c095 EM |
963 | && ($value == '' || !isset($value)) && |
964 | ($session->get('authSrc') & (CRM_Core_Permission::AUTH_SRC_CHECKSUM + CRM_Core_Permission::AUTH_SRC_LOGIN)) == 0 || | |
965 | ($key === 'current_employer' && empty($params['current_employer']))) { | |
966 | // CRM-10128: if auth source is not checksum / login && $value is blank, do not fill $data with empty value | |
967 | // to avoid update with empty values | |
968 | continue; | |
969 | } | |
970 | else { | |
971 | $data[$key] = $value; | |
972 | } | |
973 | } | |
974 | } | |
975 | } | |
976 | ||
977 | if (!isset($data['contact_type'])) { | |
978 | $data['contact_type'] = 'Individual'; | |
979 | } | |
980 | ||
981 | //set the values for checkboxes (do_not_email, do_not_mail, do_not_trade, do_not_phone) | |
982 | $privacy = CRM_Core_SelectValues::privacy(); | |
983 | foreach ($privacy as $key => $value) { | |
984 | if (array_key_exists($key, $fields)) { | |
985 | // do not reset values for existing contacts, if fields are added to a profile | |
986 | if (array_key_exists($key, $params)) { | |
987 | $data[$key] = $params[$key]; | |
988 | if (empty($params[$key])) { | |
989 | $data[$key] = 0; | |
990 | } | |
991 | } | |
992 | elseif (!$contactID) { | |
993 | $data[$key] = 0; | |
994 | } | |
995 | } | |
996 | } | |
997 | ||
998 | return [$data, $contactDetails]; | |
999 | } | |
1000 | ||
6a488035 | 1001 | /** |
fe482240 | 1002 | * Format params for update and fill mode. |
6a488035 | 1003 | * |
5a4f6742 CW |
1004 | * @param array $params |
1005 | * reference to an array containing all the. | |
16b10e64 | 1006 | * values for import |
5a4f6742 CW |
1007 | * @param int $cid |
1008 | * contact id. | |
6a488035 | 1009 | */ |
77c96d86 EM |
1010 | public function formatParams(&$params, $cid) { |
1011 | if ($this->isSkipDuplicates()) { | |
6a488035 TO |
1012 | return; |
1013 | } | |
1014 | ||
be2fb01f | 1015 | $contactParams = [ |
6a488035 | 1016 | 'contact_id' => $cid, |
be2fb01f | 1017 | ]; |
6a488035 | 1018 | |
be2fb01f | 1019 | $defaults = []; |
6a488035 TO |
1020 | $contactObj = CRM_Contact_BAO_Contact::retrieve($contactParams, $defaults); |
1021 | ||
77c96d86 | 1022 | $modeFill = $this->isFillDuplicates(); |
6a488035 | 1023 | |
0b330e6d | 1024 | $groupTree = CRM_Core_BAO_CustomGroup::getTree($params['contact_type'], NULL, $cid, 0, NULL); |
6a488035 TO |
1025 | CRM_Core_BAO_CustomGroup::setDefaults($groupTree, $defaults, FALSE, FALSE); |
1026 | ||
be2fb01f | 1027 | $locationFields = [ |
6a488035 | 1028 | 'address' => 'address', |
be2fb01f | 1029 | ]; |
6a488035 TO |
1030 | |
1031 | $contact = get_object_vars($contactObj); | |
1032 | ||
1033 | foreach ($params as $key => $value) { | |
1034 | if ($key == 'id' || $key == 'contact_type') { | |
1035 | continue; | |
1036 | } | |
1037 | ||
1038 | if (array_key_exists($key, $locationFields)) { | |
1039 | continue; | |
1040 | } | |
f3f321c7 | 1041 | |
60110bfa EM |
1042 | if ($customFieldId = CRM_Core_BAO_CustomField::getKeyID($key)) { |
1043 | $custom_params = ['id' => $contact['id'], 'return' => $key]; | |
1044 | $getValue = civicrm_api3('Contact', 'getvalue', $custom_params); | |
1045 | if (empty($getValue)) { | |
1046 | unset($getValue); | |
6a488035 | 1047 | } |
60110bfa EM |
1048 | } |
1049 | else { | |
1050 | $getValue = CRM_Utils_Array::retrieveValueRecursive($contact, $key); | |
1051 | } | |
1052 | if ($key == 'contact_source') { | |
1053 | $params['source'] = $params[$key]; | |
1054 | unset($params[$key]); | |
1055 | } | |
6a488035 | 1056 | |
60110bfa EM |
1057 | if ($modeFill && isset($getValue)) { |
1058 | unset($params[$key]); | |
1059 | if ($customFieldId) { | |
1060 | // Extra values must be unset to ensure the values are not | |
1061 | // imported. | |
1062 | unset($params['custom'][$customFieldId]); | |
6a488035 TO |
1063 | } |
1064 | } | |
1065 | } | |
1066 | ||
1067 | foreach ($locationFields as $locKeys) { | |
e01bf597 | 1068 | if (isset($params[$locKeys]) && is_array($params[$locKeys])) { |
6a488035 TO |
1069 | foreach ($params[$locKeys] as $key => $value) { |
1070 | if ($modeFill) { | |
1071 | $getValue = CRM_Utils_Array::retrieveValueRecursive($contact, $locKeys); | |
1072 | ||
1073 | if (isset($getValue)) { | |
1074 | foreach ($getValue as $cnt => $values) { | |
639e4f37 EM |
1075 | if ((!empty($getValue[$cnt]['location_type_id']) && !empty($params[$locKeys][$key]['location_type_id'])) && $getValue[$cnt]['location_type_id'] == $params[$locKeys][$key]['location_type_id']) { |
1076 | unset($params[$locKeys][$key]); | |
6a488035 TO |
1077 | } |
1078 | } | |
1079 | } | |
1080 | } | |
1081 | } | |
1082 | if (count($params[$locKeys]) == 0) { | |
1083 | unset($params[$locKeys]); | |
1084 | } | |
1085 | } | |
1086 | } | |
1087 | } | |
1088 | ||
6a488035 | 1089 | /** |
b2c28e7f | 1090 | * Get the message for a successful import. |
6a488035 | 1091 | * |
b2c28e7f | 1092 | * @return string |
6a488035 | 1093 | */ |
b2c28e7f EM |
1094 | private function getSuccessMessage(): string { |
1095 | if (!empty($this->_unparsedStreetAddressContacts)) { | |
1096 | $errorMessage = ts('Record imported successfully but unable to parse the street address: '); | |
6a488035 TO |
1097 | foreach ($this->_unparsedStreetAddressContacts as $contactInfo => $contactValue) { |
1098 | $contactUrl = CRM_Utils_System::url('civicrm/contact/add', 'reset=1&action=update&cid=' . $contactValue['id'], TRUE, NULL, FALSE); | |
b2c28e7f | 1099 | $errorMessage .= "\n Contact ID:" . $contactValue['id'] . " <a href=\"$contactUrl\"> " . $contactValue['streetAddress'] . '</a>'; |
6a488035 | 1100 | } |
b2c28e7f | 1101 | return $errorMessage; |
6a488035 | 1102 | } |
b2c28e7f | 1103 | return ''; |
6a488035 TO |
1104 | } |
1105 | ||
65070890 | 1106 | /** |
1107 | * Get the possible contact matches. | |
1108 | * | |
1109 | * 1) the chosen dedupe rule falling back to | |
1110 | * 2) a check for the external ID. | |
1111 | * | |
0e480632 | 1112 | * @see https://issues.civicrm.org/jira/browse/CRM-17275 |
65070890 | 1113 | * |
1114 | * @param array $params | |
64623d6c EM |
1115 | * @param int|null $extIDMatch |
1116 | * @param int|null $dedupeRuleID | |
65070890 | 1117 | * |
64623d6c EM |
1118 | * @return int|null |
1119 | * IDs of a possible. | |
65070890 | 1120 | * |
1121 | * @throws \CRM_Core_Exception | |
1122 | * @throws \CiviCRM_API3_Exception | |
1123 | */ | |
64623d6c EM |
1124 | protected function getPossibleContactMatch(array $params, ?int $extIDMatch, ?int $dedupeRuleID): ?int { |
1125 | $checkParams = ['check_permissions' => FALSE, 'match' => $params, 'dedupe_rule_id' => $dedupeRuleID]; | |
65070890 | 1126 | $possibleMatches = civicrm_api3('Contact', 'duplicatecheck', $checkParams); |
1127 | if (!$extIDMatch) { | |
6187f042 EM |
1128 | if (count($possibleMatches['values']) === 1) { |
1129 | return array_key_last($possibleMatches['values']); | |
1130 | } | |
1131 | if (count($possibleMatches['values']) > 1) { | |
7e29af11 DS |
1132 | throw new CRM_Core_Exception(ts('Record duplicates multiple contacts: ') . implode(',', array_keys($possibleMatches['values'])), CRM_Import_Parser::ERROR); |
1133 | ||
6187f042 EM |
1134 | } |
1135 | return NULL; | |
65070890 | 1136 | } |
1137 | if ($possibleMatches['count']) { | |
c3f7ab62 | 1138 | if (array_key_exists($extIDMatch, $possibleMatches['values'])) { |
64623d6c | 1139 | return $extIDMatch; |
65070890 | 1140 | } |
07b7795e | 1141 | throw new CRM_Core_Exception(ts('Matching this contact based on the de-dupe rule would cause an external ID conflict')); |
65070890 | 1142 | } |
64623d6c | 1143 | return $extIDMatch; |
65070890 | 1144 | } |
1145 | ||
64cafaa3 | 1146 | /** |
1147 | * Set field metadata. | |
1148 | */ | |
1149 | protected function setFieldMetadata() { | |
91b4c63e | 1150 | $this->setImportableFieldsMetadata($this->getContactImportMetadata()); |
64cafaa3 | 1151 | } |
1152 | ||
68f3bda5 EM |
1153 | /** |
1154 | * @param string $name | |
1155 | * @param $title | |
1156 | * @param int $type | |
1157 | * @param string $headerPattern | |
1158 | * @param string $dataPattern | |
1159 | * @param bool $hasLocationType | |
1160 | */ | |
1161 | public function addField( | |
1162 | $name, $title, $type = CRM_Utils_Type::T_INT, | |
1163 | $headerPattern = '//', $dataPattern = '//', | |
1164 | $hasLocationType = FALSE | |
1165 | ) { | |
1166 | $this->_fields[$name] = new CRM_Contact_Import_Field($name, $title, $type, $headerPattern, $dataPattern, $hasLocationType); | |
1167 | if (empty($name)) { | |
1168 | $this->_fields['doNotImport'] = new CRM_Contact_Import_Field($name, $title, $type, $headerPattern, $dataPattern, $hasLocationType); | |
1169 | } | |
1170 | } | |
1171 | ||
1172 | /** | |
1173 | * Store parser values. | |
1174 | * | |
1175 | * @param CRM_Core_Session $store | |
1176 | * | |
1177 | * @param int $mode | |
1178 | */ | |
1179 | public function set($store, $mode = self::MODE_SUMMARY) { | |
68f3bda5 EM |
1180 | } |
1181 | ||
1182 | /** | |
1183 | * Export data to a CSV file. | |
1184 | * | |
1185 | * @param string $fileName | |
1186 | * @param array $header | |
1187 | * @param array $data | |
1188 | */ | |
1189 | public static function exportCSV($fileName, $header, $data) { | |
1190 | ||
1191 | if (file_exists($fileName) && !is_writable($fileName)) { | |
1192 | CRM_Core_Error::movedSiteError($fileName); | |
1193 | } | |
1194 | //hack to remove '_status', '_statusMsg' and '_id' from error file | |
1195 | $errorValues = []; | |
1196 | $dbRecordStatus = ['IMPORTED', 'ERROR', 'DUPLICATE', 'INVALID', 'NEW']; | |
1197 | foreach ($data as $rowCount => $rowValues) { | |
1198 | $count = 0; | |
1199 | foreach ($rowValues as $key => $val) { | |
1200 | if (in_array($val, $dbRecordStatus) && $count == (count($rowValues) - 3)) { | |
1201 | break; | |
1202 | } | |
1203 | $errorValues[$rowCount][$key] = $val; | |
1204 | $count++; | |
1205 | } | |
1206 | } | |
1207 | $data = $errorValues; | |
1208 | ||
1209 | $output = []; | |
1210 | $fd = fopen($fileName, 'w'); | |
1211 | ||
1212 | foreach ($header as $key => $value) { | |
1213 | $header[$key] = "\"$value\""; | |
1214 | } | |
1215 | $config = CRM_Core_Config::singleton(); | |
1216 | $output[] = implode($config->fieldSeparator, $header); | |
1217 | ||
1218 | foreach ($data as $datum) { | |
1219 | foreach ($datum as $key => $value) { | |
1220 | $datum[$key] = "\"$value\""; | |
1221 | } | |
1222 | $output[] = implode($config->fieldSeparator, $datum); | |
1223 | } | |
1224 | fwrite($fd, implode("\n", $output)); | |
1225 | fclose($fd); | |
1226 | } | |
1227 | ||
68f3bda5 EM |
1228 | /** |
1229 | * Format contact parameters. | |
1230 | * | |
1231 | * @todo this function needs re-writing & re-merging into the main function. | |
1232 | * | |
1233 | * Here be dragons. | |
1234 | * | |
1235 | * @param array $values | |
1236 | * @param array $params | |
1237 | * | |
1238 | * @return bool | |
1239 | */ | |
1240 | protected function formatContactParameters(&$values, &$params) { | |
1241 | // Crawl through the possible classes: | |
1242 | // Contact | |
1243 | // Individual | |
1244 | // Household | |
1245 | // Organization | |
1246 | // Location | |
1247 | // Address | |
1248 | ||
68f3bda5 EM |
1249 | // IM |
1250 | // Note | |
1251 | // Custom | |
1252 | ||
1253 | // first add core contact values since for other Civi modules they are not added | |
1254 | $contactFields = CRM_Contact_DAO_Contact::fields(); | |
1255 | _civicrm_api3_store_values($contactFields, $values, $params); | |
1256 | ||
1257 | if (isset($values['contact_type'])) { | |
1258 | // we're an individual/household/org property | |
1259 | ||
1260 | $fields[$values['contact_type']] = CRM_Contact_DAO_Contact::fields(); | |
1261 | ||
1262 | _civicrm_api3_store_values($fields[$values['contact_type']], $values, $params); | |
1263 | return TRUE; | |
1264 | } | |
1265 | ||
1266 | // Cache the various object fields | |
1267 | // @todo - remove this after confirming this is just a compilation of other-wise-cached fields. | |
1268 | static $fields = []; | |
1269 | ||
68f3bda5 EM |
1270 | if (isset($values['note'])) { |
1271 | // add a note field | |
1272 | if (!isset($params['note'])) { | |
1273 | $params['note'] = []; | |
1274 | } | |
1275 | $noteBlock = count($params['note']) + 1; | |
1276 | ||
1277 | $params['note'][$noteBlock] = []; | |
1278 | if (!isset($fields['Note'])) { | |
1279 | $fields['Note'] = CRM_Core_DAO_Note::fields(); | |
1280 | } | |
1281 | ||
1282 | // get the current logged in civicrm user | |
1283 | $session = CRM_Core_Session::singleton(); | |
1284 | $userID = $session->get('userID'); | |
1285 | ||
1286 | if ($userID) { | |
1287 | $values['contact_id'] = $userID; | |
1288 | } | |
1289 | ||
1290 | _civicrm_api3_store_values($fields['Note'], $values, $params['note'][$noteBlock]); | |
1291 | ||
1292 | return TRUE; | |
1293 | } | |
1294 | ||
1295 | // Check for custom field values | |
1296 | $customFields = CRM_Core_BAO_CustomField::getFields(CRM_Utils_Array::value('contact_type', $values), | |
1297 | FALSE, FALSE, NULL, NULL, FALSE, FALSE, FALSE | |
1298 | ); | |
1299 | ||
1300 | foreach ($values as $key => $value) { | |
1301 | if ($customFieldID = CRM_Core_BAO_CustomField::getKeyID($key)) { | |
1302 | // check if it's a valid custom field id | |
1303 | ||
1304 | if (!array_key_exists($customFieldID, $customFields)) { | |
1305 | return civicrm_api3_create_error('Invalid custom field ID'); | |
1306 | } | |
1307 | else { | |
1308 | $params[$key] = $value; | |
1309 | } | |
1310 | } | |
1311 | } | |
1312 | return TRUE; | |
1313 | } | |
1314 | ||
1315 | /** | |
1316 | * Format location block ready for importing. | |
1317 | * | |
7b033be4 EM |
1318 | * Note this formatting should all be by the time the code reaches this point |
1319 | * | |
639e4f37 EM |
1320 | * There is some test coverage for this in |
1321 | * CRM_Contact_Import_Parser_ContactTest e.g. testImportPrimaryAddress. | |
68f3bda5 | 1322 | * |
7b033be4 EM |
1323 | * @deprecated |
1324 | * | |
68f3bda5 | 1325 | * @param array $values |
68f3bda5 EM |
1326 | * |
1327 | * @return bool | |
639e4f37 | 1328 | * @throws \CiviCRM_API3_Exception |
68f3bda5 | 1329 | */ |
7b033be4 EM |
1330 | protected function formatLocationBlock(&$values) { |
1331 | // @todo - remove this function. | |
1332 | // Original explantion ..... | |
68f3bda5 EM |
1333 | // Note: we doing multiple value formatting here for address custom fields, plus putting into right format. |
1334 | // The actual formatting (like date, country ..etc) for address custom fields is taken care of while saving | |
1335 | // the address in CRM_Core_BAO_Address::create method | |
1336 | if (!empty($values['location_type_id'])) { | |
1337 | static $customFields = []; | |
1338 | if (empty($customFields)) { | |
1339 | $customFields = CRM_Core_BAO_CustomField::getFields('Address'); | |
1340 | } | |
1341 | // make a copy of values, as we going to make changes | |
1342 | $newValues = $values; | |
1343 | foreach ($values as $key => $val) { | |
1344 | $customFieldID = CRM_Core_BAO_CustomField::getKeyID($key); | |
1345 | if ($customFieldID && array_key_exists($customFieldID, $customFields)) { | |
1346 | ||
1347 | $htmlType = $customFields[$customFieldID]['html_type'] ?? NULL; | |
1348 | if (CRM_Core_BAO_CustomField::isSerialized($customFields[$customFieldID]) && $val) { | |
1349 | $mulValues = explode(',', $val); | |
1350 | $customOption = CRM_Core_BAO_CustomOption::getCustomOption($customFieldID, TRUE); | |
1351 | $newValues[$key] = []; | |
1352 | foreach ($mulValues as $v1) { | |
1353 | foreach ($customOption as $v2) { | |
1354 | if ((strtolower($v2['label']) == strtolower(trim($v1))) || | |
1355 | (strtolower($v2['value']) == strtolower(trim($v1))) | |
1356 | ) { | |
1357 | if ($htmlType == 'CheckBox') { | |
1358 | $newValues[$key][$v2['value']] = 1; | |
1359 | } | |
1360 | else { | |
1361 | $newValues[$key][] = $v2['value']; | |
1362 | } | |
1363 | } | |
1364 | } | |
1365 | } | |
1366 | } | |
1367 | } | |
1368 | } | |
1369 | // consider new values | |
1370 | $values = $newValues; | |
1371 | } | |
1372 | ||
68f3bda5 EM |
1373 | return TRUE; |
1374 | } | |
1375 | ||
1376 | /** | |
1377 | * Get the field metadata for the relevant entity. | |
1378 | * | |
1379 | * @param string $entity | |
1380 | * | |
1381 | * @return array | |
1382 | */ | |
1383 | protected function getMetadataForEntity($entity) { | |
1384 | if (!isset($this->fieldMetadata[$entity])) { | |
1385 | $className = "CRM_Core_DAO_$entity"; | |
1386 | $this->fieldMetadata[$entity] = $className::fields(); | |
1387 | } | |
1388 | return $this->fieldMetadata[$entity]; | |
1389 | } | |
1390 | ||
1391 | /** | |
1392 | * Fill in the primary location. | |
1393 | * | |
1394 | * If the contact has a primary address we update it. Otherwise | |
1395 | * we add an address of the default location type. | |
1396 | * | |
1397 | * @param array $params | |
1398 | * Address block parameters | |
1399 | * @param array $values | |
1400 | * Input values | |
1401 | * @param string $entity | |
1402 | * - address, email, phone | |
1403 | * @param int|null $contactID | |
1404 | * | |
1405 | * @throws \CiviCRM_API3_Exception | |
1406 | */ | |
1407 | protected function fillPrimary(&$params, $values, $entity, $contactID) { | |
1408 | if ($values['location_type_id'] === 'Primary') { | |
1409 | if ($contactID) { | |
1410 | $primary = civicrm_api3($entity, 'get', [ | |
1411 | 'return' => 'location_type_id', | |
1412 | 'contact_id' => $contactID, | |
1413 | 'is_primary' => 1, | |
1414 | 'sequential' => 1, | |
1415 | ]); | |
1416 | } | |
1417 | $defaultLocationType = CRM_Core_BAO_LocationType::getDefault(); | |
1418 | $params['location_type_id'] = (int) (isset($primary) && $primary['count']) ? $primary['values'][0]['location_type_id'] : $defaultLocationType->id; | |
1419 | $params['is_primary'] = 1; | |
1420 | } | |
1421 | } | |
1422 | ||
34f3f22a EM |
1423 | /** |
1424 | * Get the civicrm_mapping_field appropriate layout for the mapper input. | |
1425 | * | |
1426 | * The input looks something like ['street_address', 1] | |
1427 | * and would be mapped to ['name' => 'street_address', 'location_type_id' => | |
1428 | * 1] | |
1429 | * | |
1430 | * @param array $fieldMapping | |
b6df6c1b EM |
1431 | * Field as submitted on the MapField form - this is a non-associative array, |
1432 | * the keys of which depend on the data/ field. Generally it will be one of | |
1433 | * [$fieldName], | |
1434 | * [$fieldName, $locationTypeID, $phoneTypeIDOrIMProviderIDIfRelevant], | |
1435 | * [$fieldName, $websiteTypeID], | |
1436 | * If the mapping is for a related contact it will be as above but the first | |
1437 | * key will be the relationship key - eg. 5_a_b. | |
34f3f22a EM |
1438 | * @param int $mappingID |
1439 | * @param int $columnNumber | |
1440 | * | |
1441 | * @return array | |
1442 | * @throws \API_Exception | |
1443 | */ | |
1444 | public function getMappingFieldFromMapperInput(array $fieldMapping, int $mappingID, int $columnNumber): array { | |
1445 | $isRelationshipField = preg_match('/\d*_a_b|b_a$/', $fieldMapping[0]); | |
1446 | $fieldName = $isRelationshipField ? $fieldMapping[1] : $fieldMapping[0]; | |
1447 | $locationTypeID = NULL; | |
1448 | $possibleLocationField = $isRelationshipField ? 2 : 1; | |
639e4f37 EM |
1449 | $entity = strtolower($this->getFieldEntity($fieldName)); |
1450 | if ($entity !== 'website' && is_numeric($fieldMapping[$possibleLocationField] ?? NULL)) { | |
34f3f22a EM |
1451 | $locationTypeID = $fieldMapping[$possibleLocationField]; |
1452 | } | |
639e4f37 | 1453 | |
34f3f22a EM |
1454 | return [ |
1455 | 'name' => $fieldName, | |
1456 | 'mapping_id' => $mappingID, | |
1457 | 'relationship_type_id' => $isRelationshipField ? substr($fieldMapping[0], 0, -4) : NULL, | |
1458 | 'relationship_direction' => $isRelationshipField ? substr($fieldMapping[0], -3) : NULL, | |
1459 | 'column_number' => $columnNumber, | |
1460 | 'contact_type' => $this->getContactType(), | |
639e4f37 EM |
1461 | 'website_type_id' => $entity !== 'website' ? NULL : ($isRelationshipField ? $fieldMapping[2] : $fieldMapping[1]), |
1462 | 'phone_type_id' => $entity !== 'phone' ? NULL : ($isRelationshipField ? $fieldMapping[3] : $fieldMapping[2]), | |
1463 | 'im_provider_id' => $entity !== 'im' ? NULL : ($isRelationshipField ? $fieldMapping[3] : $fieldMapping[2]), | |
34f3f22a EM |
1464 | 'location_type_id' => $locationTypeID, |
1465 | ]; | |
1466 | } | |
1467 | ||
1468 | /** | |
1469 | * @param array $mappedField | |
1470 | * Field detail as would be saved in field_mapping table | |
1471 | * or as returned from getMappingFieldFromMapperInput | |
1472 | * | |
1473 | * @return string | |
1474 | * @throws \API_Exception | |
1475 | */ | |
1476 | public function getMappedFieldLabel(array $mappedField): string { | |
1477 | $this->setFieldMetadata(); | |
1478 | $title = []; | |
1479 | if ($mappedField['relationship_type_id']) { | |
1480 | $title[] = $this->getRelationshipLabel($mappedField['relationship_type_id'], $mappedField['relationship_direction']); | |
1481 | } | |
e0b8f9a9 | 1482 | $title[] = $this->getFieldMetadata($mappedField['name'])['title']; |
34f3f22a EM |
1483 | if ($mappedField['location_type_id']) { |
1484 | $title[] = CRM_Core_PseudoConstant::getLabel('CRM_Core_BAO_Address', 'location_type_id', $mappedField['location_type_id']); | |
1485 | } | |
1486 | if ($mappedField['website_type_id']) { | |
1487 | $title[] = CRM_Core_PseudoConstant::getLabel('CRM_Core_BAO_Website', 'website_type_id', $mappedField['website_type_id']); | |
1488 | } | |
1489 | if ($mappedField['phone_type_id']) { | |
1490 | $title[] = CRM_Core_PseudoConstant::getLabel('CRM_Core_BAO_Phone', 'phone_type_id', $mappedField['phone_type_id']); | |
1491 | } | |
1492 | if ($mappedField['im_provider_id']) { | |
0e38b3e4 | 1493 | $title[] = CRM_Core_PseudoConstant::getLabel('CRM_Core_BAO_IM', 'provider_id', $mappedField['im_provider_id']); |
34f3f22a EM |
1494 | } |
1495 | return implode(' - ', $title); | |
1496 | } | |
1497 | ||
1498 | /** | |
1499 | * Get the relevant label for the relationship. | |
1500 | * | |
1501 | * @param int $id | |
1502 | * @param string $direction | |
1503 | * | |
1504 | * @return string | |
1505 | * @throws \API_Exception | |
1506 | */ | |
1507 | protected function getRelationshipLabel(int $id, string $direction): string { | |
1508 | if (empty($this->relationshipLabels[$id . $direction])) { | |
1509 | $this->relationshipLabels[$id . $direction] = | |
1510 | $fieldName = 'label_' . $direction; | |
1511 | $this->relationshipLabels[$id . $direction] = (string) RelationshipType::get(FALSE) | |
1512 | ->addWhere('id', '=', $id) | |
1513 | ->addSelect($fieldName)->execute()->first()[$fieldName]; | |
1514 | } | |
1515 | return $this->relationshipLabels[$id . $direction]; | |
1516 | } | |
1517 | ||
4b3ef371 | 1518 | /** |
baeb2d67 | 1519 | * Transform the input parameters into the form handled by the input routine. |
4b3ef371 EM |
1520 | * |
1521 | * @param array $values | |
1522 | * Input parameters as they come in from the datasource | |
1523 | * eg. ['Bob', 'Smith', 'bob@example.org', '123-456'] | |
1524 | * | |
1525 | * @return array | |
1526 | * Parameters mapped to CiviCRM fields based on the mapping | |
1527 | * and specified contact type. eg. | |
1528 | * [ | |
1529 | * 'contact_type' => 'Individual', | |
1530 | * 'first_name' => 'Bob', | |
1531 | * 'last_name' => 'Smith', | |
1532 | * 'phone' => ['phone' => '123', 'location_type_id' => 1, 'phone_type_id' => 1], | |
1533 | * '5_a_b' => ['contact_type' => 'Organization', 'url' => ['url' => 'https://example.org', 'website_type_id' => 1]] | |
1534 | * 'im' => ['im' => 'my-handle', 'location_type_id' => 1, 'provider_id' => 1], | |
baeb2d67 EM |
1535 | * |
1536 | * @throws \API_Exception | |
4b3ef371 EM |
1537 | */ |
1538 | public function getMappedRow(array $values): array { | |
71deff40 EM |
1539 | $params = ['relationship' => []]; |
1540 | ||
1541 | foreach ($this->getFieldMappings() as $i => $mappedField) { | |
1542 | // The key is in the format 5_a_b where 5 is the relationship_type_id and a_b is the direction. | |
1543 | $relatedContactKey = $mappedField['relationship_type_id'] ? ($mappedField['relationship_type_id'] . '_' . $mappedField['relationship_direction']) : NULL; | |
1544 | $fieldName = $mappedField['name']; | |
1545 | $importedValue = $values[$i]; | |
1546 | if ($fieldName === 'do_not_import' || $importedValue === NULL) { | |
1547 | continue; | |
1548 | } | |
1549 | ||
1550 | $locationFields = ['location_type_id', 'phone_type_id', 'provider_id', 'website_type_id']; | |
1551 | $locationValues = array_filter(array_intersect_key($mappedField, array_fill_keys($locationFields, 1))); | |
1552 | ||
1553 | if ($relatedContactKey) { | |
1554 | if (!isset($params['relationship'][$relatedContactKey])) { | |
1555 | $params['relationship'][$relatedContactKey] = [ | |
1556 | // These will be over-written by any the importer has chosen but defaults are based on the relationship. | |
1557 | 'contact_type' => $this->getRelatedContactType($mappedField['relationship_type_id'], $mappedField['relationship_direction']), | |
1558 | 'contact_sub_type' => $this->getRelatedContactSubType($mappedField['relationship_type_id'], $mappedField['relationship_direction']), | |
1559 | ]; | |
1560 | } | |
1561 | $this->addFieldToParams($params['relationship'][$relatedContactKey], $locationValues, $fieldName, $importedValue); | |
1562 | } | |
1563 | else { | |
1564 | $this->addFieldToParams($params, $locationValues, $fieldName, $importedValue); | |
1565 | } | |
1566 | } | |
1567 | ||
1568 | $this->fillStateProvince($params); | |
1569 | ||
baeb2d67 | 1570 | $params['contact_type'] = $this->getContactType(); |
e0b8f9a9 EM |
1571 | if ($this->getContactSubType()) { |
1572 | $params['contact_sub_type'] = $this->getContactSubType(); | |
1573 | } | |
4b3ef371 EM |
1574 | return $params; |
1575 | } | |
1576 | ||
13575591 EM |
1577 | /** |
1578 | * Validate the import values. | |
1579 | * | |
1580 | * The values array represents a row in the datasource. | |
1581 | * | |
1582 | * @param array $values | |
5ebaab5d EM |
1583 | * |
1584 | * @throws \API_Exception | |
7e56b830 | 1585 | * @throws \CRM_Core_Exception |
13575591 EM |
1586 | */ |
1587 | public function validateValues(array $values): void { | |
13575591 | 1588 | $params = $this->getMappedRow($values); |
bf94a235 | 1589 | $this->validateParams($params); |
13575591 EM |
1590 | } |
1591 | ||
24948d41 EM |
1592 | /** |
1593 | * Get the invalid values in the params for the given contact. | |
1594 | * | |
1595 | * @param array|int|string $value | |
1596 | * @param string $prefixString | |
1597 | * | |
1598 | * @return array | |
1599 | * @throws \API_Exception | |
1600 | * @throws \Civi\API\Exception\NotImplementedException | |
1601 | */ | |
1602 | protected function getInvalidValuesForContact($value, string $prefixString): array { | |
1603 | $errors = []; | |
1604 | foreach ($value as $contactKey => $contactValue) { | |
741beb19 | 1605 | if ($contactKey !== 'relationship') { |
24948d41 EM |
1606 | $result = $this->getInvalidValues($contactValue, $contactKey, $prefixString); |
1607 | if (!empty($result)) { | |
1608 | $errors = array_merge($errors, $result); | |
1609 | } | |
1610 | } | |
1611 | } | |
1612 | return $errors; | |
1613 | } | |
1614 | ||
4473c68d EM |
1615 | /** |
1616 | * Get the field mappings for the import. | |
1617 | * | |
1618 | * This is the same format as saved in civicrm_mapping_field except | |
1619 | * that location_type_id = 'Primary' rather than empty where relevant. | |
f5d4a76c | 1620 | * Also 'im_provider_id' is mapped to the 'real' field name 'provider_id' |
4473c68d EM |
1621 | * |
1622 | * @return array | |
1623 | * @throws \API_Exception | |
1624 | */ | |
1625 | protected function getFieldMappings(): array { | |
1626 | $mappedFields = []; | |
1627 | foreach ($this->getSubmittedValue('mapper') as $i => $mapperRow) { | |
1628 | $mappedField = $this->getMappingFieldFromMapperInput($mapperRow, 0, $i); | |
1629 | if (!$mappedField['location_type_id'] && !empty($this->importableFieldsMetadata[$mappedField['name']]['hasLocationType'])) { | |
1630 | $mappedField['location_type_id'] = 'Primary'; | |
1631 | } | |
f5d4a76c | 1632 | // Just for clarity since 0 is a pseudo-value |
4473c68d | 1633 | unset($mappedField['mapping_id']); |
f5d4a76c EM |
1634 | // Annoyingly the civicrm_mapping_field name for this differs from civicrm_im. |
1635 | // Test cover in `CRM_Contact_Import_Parser_ContactTest::testMapFields` | |
1636 | $mappedField['provider_id'] = $mappedField['im_provider_id']; | |
1637 | unset($mappedField['im_provider_id']); | |
4473c68d EM |
1638 | $mappedFields[] = $mappedField; |
1639 | } | |
1640 | return $mappedFields; | |
1641 | } | |
1642 | ||
40a48df0 EM |
1643 | /** |
1644 | * Get the related contact type. | |
1645 | * | |
1646 | * @param int|null $relationshipTypeID | |
1647 | * @param int|string $relationshipDirection | |
1648 | * | |
1649 | * @return null|string | |
1650 | * | |
1651 | * @throws \API_Exception | |
1652 | */ | |
1653 | protected function getRelatedContactType($relationshipTypeID, $relationshipDirection): ?string { | |
1654 | if (!$relationshipTypeID) { | |
1655 | return NULL; | |
1656 | } | |
7d2012dc | 1657 | $relationshipField = 'contact_type_' . substr($relationshipDirection, -1); |
02f1858b | 1658 | return $this->getRelationshipType($relationshipTypeID)[$relationshipField]; |
7d2012dc EM |
1659 | } |
1660 | ||
77b23b82 EM |
1661 | /** |
1662 | * Get the related contact sub type. | |
1663 | * | |
1664 | * @param int|null $relationshipTypeID | |
1665 | * @param int|string $relationshipDirection | |
1666 | * | |
1667 | * @return null|string | |
1668 | * | |
1669 | * @throws \API_Exception | |
1670 | */ | |
1671 | protected function getRelatedContactSubType(int $relationshipTypeID, $relationshipDirection): ?string { | |
1672 | if (!$relationshipTypeID) { | |
1673 | return NULL; | |
1674 | } | |
1675 | $relationshipField = 'contact_sub_type_' . substr($relationshipDirection, -1); | |
1676 | return $this->getRelationshipType($relationshipTypeID)[$relationshipField]; | |
1677 | } | |
1678 | ||
7d2012dc EM |
1679 | /** |
1680 | * Get the related contact type. | |
1681 | * | |
1682 | * @param int|null $relationshipTypeID | |
1683 | * @param int|string $relationshipDirection | |
1684 | * | |
1685 | * @return null|string | |
1686 | * | |
1687 | * @throws \API_Exception | |
1688 | */ | |
1689 | protected function getRelatedContactLabel($relationshipTypeID, $relationshipDirection): ?string { | |
1690 | $relationshipField = 'label_' . $relationshipDirection; | |
02f1858b | 1691 | return $this->getRelationshipType($relationshipTypeID)[$relationshipField]; |
7d2012dc EM |
1692 | } |
1693 | ||
1694 | /** | |
1695 | * Get the relationship type. | |
1696 | * | |
1697 | * @param int $relationshipTypeID | |
1698 | * | |
1699 | * @return string[] | |
1700 | * @throws \API_Exception | |
1701 | */ | |
1702 | protected function getRelationshipType(int $relationshipTypeID): array { | |
1703 | $cacheKey = 'relationship_type' . $relationshipTypeID; | |
40a48df0 | 1704 | if (!isset(Civi::$statics[__CLASS__][$cacheKey])) { |
40a48df0 EM |
1705 | Civi::$statics[__CLASS__][$cacheKey] = RelationshipType::get(FALSE) |
1706 | ->addWhere('id', '=', $relationshipTypeID) | |
7d2012dc | 1707 | ->addSelect('*')->execute()->first(); |
40a48df0 EM |
1708 | } |
1709 | return Civi::$statics[__CLASS__][$cacheKey]; | |
1710 | } | |
1711 | ||
19f33b09 EM |
1712 | /** |
1713 | * Add the given field to the contact array. | |
1714 | * | |
1715 | * @param array $contactArray | |
1716 | * @param array $locationValues | |
1717 | * @param string $fieldName | |
1718 | * @param mixed $importedValue | |
1719 | * | |
1720 | * @return void | |
1721 | * @throws \API_Exception | |
1722 | */ | |
1723 | private function addFieldToParams(array &$contactArray, array $locationValues, string $fieldName, $importedValue): void { | |
1724 | if (!empty($locationValues)) { | |
018c9e26 | 1725 | $fieldMap = ['country' => 'country_id', 'state_province' => 'state_province_id', 'county' => 'county_id']; |
24948d41 EM |
1726 | $realFieldName = empty($fieldMap[$fieldName]) ? $fieldName : $fieldMap[$fieldName]; |
1727 | $entity = strtolower($this->getFieldEntity($fieldName)); | |
639e4f37 | 1728 | |
24948d41 EM |
1729 | // The entity key is either location_type_id for address, email - eg. 1, or |
1730 | // location_type_id + '_' + phone_type_id or im_provider_id | |
1731 | // or the value for website(since websites are not historically one-per-type) | |
1732 | $entityKey = $locationValues['location_type_id'] ?? $importedValue; | |
1733 | if (!empty($locationValues['phone_type_id']) || !empty($locationValues['provider_id'])) { | |
1734 | $entityKey .= '_' . ($locationValues['phone_type_id'] ?? '' . $locationValues['provider_id'] ?? ''); | |
1735 | } | |
1736 | $fieldValue = $this->getTransformedFieldValue($realFieldName, $importedValue); | |
639e4f37 | 1737 | |
24948d41 EM |
1738 | if (!isset($contactArray[$entity][$entityKey])) { |
1739 | $contactArray[$entity][$entityKey] = $locationValues; | |
1740 | } | |
018c9e26 EM |
1741 | // So im has really non-standard handling... |
1742 | $reallyRealFieldName = $realFieldName === 'im' ? 'name' : $realFieldName; | |
639e4f37 | 1743 | $contactArray[$entity][$entityKey][$reallyRealFieldName] = $fieldValue; |
19f33b09 EM |
1744 | } |
1745 | else { | |
80e9f1a2 | 1746 | $fieldName = array_search($fieldName, $this->getOddlyMappedMetadataFields(), TRUE) ?: $fieldName; |
07b7795e EM |
1747 | $importedValue = $this->getTransformedFieldValue($fieldName, $importedValue); |
1748 | if ($importedValue === '' && !empty($contactArray[$fieldName])) { | |
1749 | // If we have already calculated contact type or subtype based on the relationship | |
1750 | // do not overwrite it with an empty value. | |
1751 | return; | |
1752 | } | |
1753 | $contactArray[$fieldName] = $importedValue; | |
19f33b09 EM |
1754 | } |
1755 | } | |
1756 | ||
02f1858b EM |
1757 | /** |
1758 | * Get any related contacts designated for update. | |
1759 | * | |
1760 | * This extracts the parts that relate to separate related | |
1761 | * contacts from the 'params' array. | |
1762 | * | |
1763 | * It is probably a bit silly not to nest them more clearly in | |
1764 | * `getParams` in the first place & maybe in future we can do that. | |
1765 | * | |
1766 | * @param array $params | |
1767 | * | |
1768 | * @return array | |
1769 | * e.g ['5_a_b' => ['contact_type' => 'Organization', 'organization_name' => 'The Firm']] | |
1770 | * @throws \API_Exception | |
1771 | */ | |
1772 | protected function getRelatedContactsParams(array $params): array { | |
1773 | $relatedContacts = []; | |
07b7795e | 1774 | foreach ($params['relationship'] as $key => $value) { |
02f1858b EM |
1775 | // If the key is a relationship key - eg. 5_a_b or 10_b_a |
1776 | // then the value is an array that describes an existing contact. | |
1777 | // We need to check the fields are present to identify or create this | |
1778 | // contact. | |
1779 | if (preg_match('/^\d+_[a|b]_[a|b]$/', $key)) { | |
1780 | $value['relationship_type_id'] = substr($key, 0, -4); | |
1781 | $value['relationship_direction'] = substr($key, -3); | |
1782 | $value['relationship_label'] = $this->getRelationshipLabel($value['relationship_type_id'], $value['relationship_direction']); | |
1783 | $relatedContacts[$key] = $value; | |
1784 | } | |
1785 | } | |
1786 | return $relatedContacts; | |
1787 | } | |
1788 | ||
64623d6c EM |
1789 | /** |
1790 | * Look up for an existing contact with the given external_identifier. | |
1791 | * | |
1792 | * If the identifier is found on a deleted contact then it is not a match | |
1793 | * but it must be removed from that contact to allow the new contact to | |
1794 | * have that external_identifier. | |
1795 | * | |
1796 | * @param string|null $externalIdentifier | |
ed75aff2 | 1797 | * @param string $contactType |
64623d6c EM |
1798 | * |
1799 | * @return int|null | |
1800 | * | |
ed75aff2 | 1801 | * @throws \CRM_Core_Exception |
64623d6c EM |
1802 | * @throws \CiviCRM_API3_Exception |
1803 | */ | |
ed75aff2 | 1804 | protected function lookupExternalIdentifier(?string $externalIdentifier, string $contactType): ?int { |
64623d6c EM |
1805 | if (!$externalIdentifier) { |
1806 | return NULL; | |
1807 | } | |
1808 | // Check for any match on external id, deleted or otherwise. | |
1809 | $foundContact = civicrm_api3('Contact', 'get', [ | |
1810 | 'external_identifier' => $externalIdentifier, | |
1811 | 'showAll' => 'all', | |
1812 | 'sequential' => TRUE, | |
ed75aff2 | 1813 | 'return' => ['id', 'contact_is_deleted', 'contact_type'], |
64623d6c EM |
1814 | ]); |
1815 | if (empty($foundContact['id'])) { | |
1816 | return NULL; | |
1817 | } | |
1818 | if (!empty($foundContact['values'][0]['contact_is_deleted'])) { | |
1819 | // If the contact is deleted, update external identifier to be blank | |
1820 | // to avoid key error from MySQL. | |
1821 | $params = ['id' => $foundContact['id'], 'external_identifier' => '']; | |
1822 | civicrm_api3('Contact', 'create', $params); | |
1823 | return NULL; | |
1824 | } | |
ed75aff2 EM |
1825 | if ($foundContact['values'][0]['contact_type'] !== $contactType) { |
1826 | throw new CRM_Core_Exception('Mismatched contact Types', CRM_Import_Parser::NO_MATCH); | |
1827 | } | |
64623d6c EM |
1828 | return (int) $foundContact['id']; |
1829 | } | |
1830 | ||
1831 | /** | |
1832 | * Lookup the contact's contact ID. | |
1833 | * | |
1834 | * @param array $params | |
6187f042 | 1835 | * @param bool $isMainContact |
64623d6c EM |
1836 | * |
1837 | * @return int|null | |
1838 | * | |
1839 | * @throws \API_Exception | |
1840 | * @throws \CRM_Core_Exception | |
1841 | * @throws \CiviCRM_API3_Exception | |
6187f042 | 1842 | * @throws \Civi\API\Exception\UnauthorizedException |
64623d6c | 1843 | */ |
6187f042 | 1844 | protected function lookupContactID(array $params, bool $isMainContact): ?int { |
ed75aff2 | 1845 | $extIDMatch = $this->lookupExternalIdentifier($params['external_identifier'] ?? NULL, $params['contact_type']); |
64623d6c EM |
1846 | $contactID = !empty($params['id']) ? (int) $params['id'] : NULL; |
1847 | //check if external identifier exists in database | |
1848 | if ($extIDMatch && $contactID && $extIDMatch !== $contactID) { | |
1849 | throw new CRM_Core_Exception(ts('Existing external ID does not match the imported contact ID.'), CRM_Import_Parser::ERROR); | |
1850 | } | |
6187f042 | 1851 | if ($extIDMatch && $isMainContact && ($this->isSkipDuplicates() || $this->isIgnoreDuplicates())) { |
64623d6c EM |
1852 | throw new CRM_Core_Exception(ts('External ID already exists in Database.'), CRM_Import_Parser::DUPLICATE); |
1853 | } | |
1854 | if ($contactID) { | |
ed75aff2 EM |
1855 | $existingContact = Contact::get(FALSE) |
1856 | ->addWhere('id', '=', $contactID) | |
1857 | // Don't auto-filter deleted - people use import to undelete. | |
1858 | ->addWhere('is_deleted', 'IN', [0, 1]) | |
1859 | ->addSelect('contact_type')->execute()->first(); | |
1860 | if (empty($existingContact['id'])) { | |
1861 | throw new CRM_Core_Exception('No contact found for this contact ID:' . $params['id'], CRM_Import_Parser::NO_MATCH); | |
1862 | } | |
1863 | if ($existingContact['contact_type'] !== $params['contact_type']) { | |
1864 | throw new CRM_Core_Exception('Mismatched contact Types', CRM_Import_Parser::NO_MATCH); | |
1865 | } | |
64623d6c EM |
1866 | return $contactID; |
1867 | } | |
1868 | // Time to see if we can find an existing contact ID to make this an update | |
1869 | // not a create. | |
07b7795e EM |
1870 | if ($extIDMatch || !$this->isIgnoreDuplicates()) { |
1871 | if (isset($params['relationship'])) { | |
1872 | unset($params['relationship']); | |
1873 | } | |
1874 | $id = $this->getPossibleContactMatch($params, $extIDMatch, $this->getSubmittedValue('dedupe_rule_id') ?: NULL); | |
1875 | if ($id && $this->isSkipDuplicates()) { | |
1876 | throw new CRM_Core_Exception(ts('Contact matched by dedupe rule already exists in the database.'), CRM_Import_Parser::DUPLICATE); | |
1877 | } | |
1878 | return $id; | |
64623d6c EM |
1879 | } |
1880 | return NULL; | |
1881 | } | |
1882 | ||
77b23b82 EM |
1883 | /** |
1884 | * @param array $params | |
1885 | * @param array $formatted | |
6187f042 EM |
1886 | * @param bool $isMainContact |
1887 | * | |
77b23b82 EM |
1888 | * @return array[] |
1889 | * @throws \API_Exception | |
1890 | * @throws \CRM_Core_Exception | |
1891 | * @throws \CiviCRM_API3_Exception | |
77b23b82 | 1892 | */ |
6187f042 EM |
1893 | protected function processContact(array $params, array $formatted, bool $isMainContact): array { |
1894 | $params['id'] = $formatted['id'] = $this->lookupContactID($params, $isMainContact); | |
2a4de39f | 1895 | if ($params['id'] && !empty($params['contact_sub_type'])) { |
77b23b82 EM |
1896 | $contactSubType = Contact::get(FALSE) |
1897 | ->addWhere('id', '=', $params['id']) | |
1898 | ->addSelect('contact_sub_type') | |
1899 | ->execute() | |
1900 | ->first()['contact_sub_type']; | |
1901 | if (!empty($contactSubType) && $contactSubType[0] !== $params['contact_sub_type'] && !CRM_Contact_BAO_ContactType::isAllowEdit($params['id'], $contactSubType[0])) { | |
1902 | throw new CRM_Core_Exception('Mismatched contact SubTypes :', CRM_Import_Parser::NO_MATCH); | |
1903 | } | |
1904 | } | |
1905 | return array($formatted, $params); | |
1906 | } | |
1907 | ||
018c9e26 EM |
1908 | /** |
1909 | * Try to get the correct state province using what country information we have. | |
1910 | * | |
1911 | * If the state matches more than one possibility then either the imported | |
1912 | * country of the site country should help us.... | |
1913 | * | |
1914 | * @param string $stateProvince | |
1915 | * @param int|null|string $countryID | |
1916 | * | |
1917 | * @return int|string | |
1918 | * @throws \API_Exception | |
1919 | * @throws \Civi\API\Exception\UnauthorizedException | |
1920 | */ | |
1921 | private function tryToResolveStateProvince(string $stateProvince, $countryID) { | |
1922 | // Try to disambiguate since we likely have the country now. | |
1923 | $possibleStates = $this->ambiguousOptions['state_province_id'][mb_strtolower($stateProvince)]; | |
1924 | if ($countryID) { | |
1925 | return $this->checkStatesForCountry($countryID, $possibleStates) ?: 'invalid_import_value'; | |
1926 | } | |
1927 | // Try the default country next. | |
1928 | $defaultCountryMatch = $this->checkStatesForCountry($this->getSiteDefaultCountry(), $possibleStates); | |
1929 | if ($defaultCountryMatch) { | |
1930 | return $defaultCountryMatch; | |
1931 | } | |
1932 | ||
1933 | if ($this->getAvailableCountries()) { | |
1934 | $countryMatches = []; | |
1935 | foreach ($this->getAvailableCountries() as $availableCountryID) { | |
1936 | $possible = $this->checkStatesForCountry($availableCountryID, $possibleStates); | |
1937 | if ($possible) { | |
1938 | $countryMatches[] = $possible; | |
1939 | } | |
1940 | } | |
1941 | if (count($countryMatches) === 1) { | |
1942 | return reset($countryMatches); | |
1943 | } | |
1944 | ||
1945 | } | |
1946 | return $stateProvince; | |
1947 | } | |
1948 | ||
1949 | /** | |
1950 | * @param array $params | |
1951 | * | |
1952 | * @return array | |
1953 | * @throws \API_Exception | |
1954 | */ | |
1955 | private function fillStateProvince(array &$params): array { | |
1956 | foreach ($params as $key => $value) { | |
1957 | if ($key === 'address') { | |
1958 | foreach ($value as $index => $address) { | |
1959 | $stateProvinceID = $address['state_province_id'] ?? NULL; | |
1960 | if ($stateProvinceID) { | |
1961 | if (!is_numeric($stateProvinceID)) { | |
1962 | $params['address'][$index]['state_province_id'] = $this->tryToResolveStateProvince($stateProvinceID, $address['country_id'] ?? NULL); | |
1963 | } | |
1964 | elseif (!empty($address['country_id']) && is_numeric($address['country_id'])) { | |
1965 | if (!$this->checkStatesForCountry((int) $address['country_id'], [$stateProvinceID])) { | |
1966 | $params['address'][$index]['state_province_id'] = 'invalid_import_value'; | |
1967 | } | |
1968 | } | |
1969 | } | |
1970 | } | |
1971 | } | |
1972 | elseif (is_array($value) && !in_array($key, ['email', 'phone', 'im', 'website', 'openid'], TRUE)) { | |
1973 | $this->fillStateProvince($params[$key]); | |
1974 | } | |
1975 | } | |
1976 | return $params; | |
1977 | } | |
1978 | ||
1979 | /** | |
1980 | * Check is any of the given states correlate to the country. | |
1981 | * | |
1982 | * @param int $countryID | |
1983 | * @param array $possibleStates | |
1984 | * | |
1985 | * @return int|null | |
1986 | * @throws \API_Exception | |
1987 | */ | |
1988 | private function checkStatesForCountry(int $countryID, array $possibleStates) { | |
1989 | foreach ($possibleStates as $index => $state) { | |
1990 | if (!empty($this->statesByCountry[$state])) { | |
1991 | if ($this->statesByCountry[$state] === $countryID) { | |
1992 | return $state; | |
1993 | } | |
1994 | unset($possibleStates[$index]); | |
1995 | } | |
1996 | } | |
1997 | if (!empty($possibleStates)) { | |
1998 | $states = StateProvince::get(FALSE) | |
1999 | ->addSelect('country_id') | |
2000 | ->addWhere('id', 'IN', $possibleStates) | |
2001 | ->execute() | |
2002 | ->indexBy('country_id'); | |
2003 | foreach ($states as $state) { | |
2004 | $this->statesByCountry[$state['id']] = $state['country_id']; | |
2005 | } | |
2006 | foreach ($possibleStates as $state) { | |
2007 | if ($this->statesByCountry[$state] === $countryID) { | |
2008 | return $state; | |
2009 | } | |
2010 | } | |
2011 | } | |
2012 | return FALSE; | |
2013 | } | |
2014 | ||
b2c28e7f EM |
2015 | /** |
2016 | * @param $outcome | |
2017 | * | |
2018 | * @return string | |
2019 | */ | |
2020 | protected function getStatus($outcome): string { | |
2021 | if ($outcome === CRM_Import_Parser::VALID) { | |
f363505a | 2022 | return empty($this->_unparsedStreetAddressContacts) ? 'IMPORTED' : 'warning_unparsed_address'; |
b2c28e7f EM |
2023 | } |
2024 | return [ | |
2025 | CRM_Import_Parser::DUPLICATE => 'DUPLICATE', | |
2026 | CRM_Import_Parser::ERROR => 'ERROR', | |
2027 | CRM_Import_Parser::NO_MATCH => 'invalid_no_match', | |
3592a5e4 | 2028 | ][$outcome] ?? 'ERROR'; |
b2c28e7f EM |
2029 | } |
2030 | ||
6a488035 | 2031 | } |