Fix fatal when saving import mapping with a relationship but 'Primary' relationship...
[civicrm-core.git] / CRM / Contact / Import / Form / MapField.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
fee14197 4 | CiviCRM version 5 |
6a488035 5 +--------------------------------------------------------------------+
6b83d5bd 6 | Copyright CiviCRM LLC (c) 2004-2019 |
6a488035
TO
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
d25dd0ee 26 */
6a488035
TO
27
28/**
29 *
30 * @package CRM
6b83d5bd 31 * @copyright CiviCRM LLC (c) 2004-2019
6a488035
TO
32 */
33
34/**
f12c6f7d 35 * This class gets the name of the file to upload.
6a488035 36 */
b26295b8 37class CRM_Contact_Import_Form_MapField extends CRM_Import_Form_MapField {
6a488035 38
a2ca56ec 39 use CRM_Contact_Import_MetadataTrait;
6a488035
TO
40
41 /**
100fef9d 42 * An array of all contact fields with
6a488035
TO
43 * formatted custom field names.
44 *
45 * @var array
6a488035 46 */
d4c8a770 47 protected $_formattedFieldNames;
6a488035
TO
48
49 /**
fe482240 50 * On duplicate.
6a488035
TO
51 *
52 * @var int
53 */
54 public $_onDuplicate;
55
56 protected $_dedupeFields;
57
817cf27c 58 protected static $customFields;
59
6a488035 60 /**
fe482240 61 * Attempt to match header labels with our mapper fields.
2b4bc760 62 *
b26295b8 63 * FIXME: This is essentially the same function as parent::defaultFromHeader
6a488035 64 *
2b4bc760 65 * @param string $columnName name of column header
66 * @param array $patterns pattern to match for the column
6a488035
TO
67 *
68 * @return string
6a488035 69 */
56dd62a0 70 public function defaultFromColumnName($columnName, $patterns) {
6a488035
TO
71
72 if (!preg_match('/^[a-z0-9 ]$/i', $columnName)) {
73 if ($columnKey = array_search($columnName, $this->_mapperFields)) {
74 $this->_fieldUsed[$columnKey] = TRUE;
75 return $columnKey;
76 }
77 }
78
79 foreach ($patterns as $key => $re) {
80 // Skip empty key/patterns
81 if (!$key || !$re || strlen("$re") < 5) {
82 continue;
83 }
84
85 if (preg_match($re, $columnName)) {
86 $this->_fieldUsed[$key] = TRUE;
87 return $key;
88 }
89 }
90 return '';
91 }
92
6a488035 93 /**
fe482240 94 * Set variables up before form is built.
6a488035
TO
95 */
96 public function preProcess() {
97 $dataSource = $this->get('dataSource');
98 $skipColumnHeader = $this->get('skipColumnHeader');
99 $this->_mapperFields = $this->get('fields');
100 $this->_importTableName = $this->get('importTableName');
101 $this->_onDuplicate = $this->get('onDuplicate');
c16da28a 102 $highlightedFields = [];
6a488035
TO
103 $highlightedFields[] = 'email';
104 $highlightedFields[] = 'external_identifier';
105 //format custom field names, CRM-2676
106 switch ($this->get('contactType')) {
a05662ef 107 case CRM_Import_Parser::CONTACT_INDIVIDUAL:
353ffa53 108 $contactType = 'Individual';
6a488035
TO
109 $highlightedFields[] = 'first_name';
110 $highlightedFields[] = 'last_name';
111 break;
112
a05662ef 113 case CRM_Import_Parser::CONTACT_HOUSEHOLD:
6a488035
TO
114 $contactType = 'Household';
115 $highlightedFields[] = 'household_name';
116 break;
117
a05662ef 118 case CRM_Import_Parser::CONTACT_ORGANIZATION:
6a488035
TO
119 $contactType = 'Organization';
120 $highlightedFields[] = 'organization_name';
121 break;
122 }
123 $this->_contactType = $contactType;
a05662ef 124 if ($this->_onDuplicate == CRM_Import_Parser::DUPLICATE_SKIP) {
6a488035
TO
125 unset($this->_mapperFields['id']);
126 }
127 else {
128 $highlightedFields[] = 'id';
129 }
130
a05662ef 131 if ($this->_onDuplicate != CRM_Import_Parser::DUPLICATE_NOCHECK) {
6a488035 132 //Mark Dedupe Rule Fields as required, since it's used in matching contact
c16da28a 133 foreach (['Individual', 'Household', 'Organization'] as $cType) {
134 $ruleParams = [
6a488035 135 'contact_type' => $cType,
353ffa53 136 'used' => 'Unsupervised',
c16da28a 137 ];
6a488035
TO
138 $this->_dedupeFields[$cType] = CRM_Dedupe_BAO_Rule::dedupeRuleFields($ruleParams);
139 }
140
141 //Modify mapper fields title if fields are present in dedupe rule
142 if (is_array($this->_dedupeFields[$contactType])) {
143 foreach ($this->_dedupeFields[$contactType] as $val) {
144 if ($valTitle = CRM_Utils_Array::value($val, $this->_mapperFields)) {
145 $this->_mapperFields[$val] = $valTitle . ' (match to contact)';
146 }
147 }
148 }
149 }
7b00a95d 150 // retrieve and highlight required custom fields
817cf27c 151 $formattedFieldNames = $this->formatCustomFieldName($this->_mapperFields);
152 self::$customFields = CRM_Core_BAO_CustomField::getFields($this->_contactType);
22e263ad 153 foreach (self::$customFields as $key => $attr) {
817cf27c 154 if (!empty($attr['is_required'])) {
155 $highlightedFields[] = "custom_$key";
156 }
157 }
6a488035 158 $this->assign('highlightedFields', $highlightedFields);
817cf27c 159 $this->_formattedFieldNames[$contactType] = $this->_mapperFields = array_merge($this->_mapperFields, $formattedFieldNames);
6a488035 160
c16da28a 161 $columnNames = [];
6a488035
TO
162 //get original col headers from csv if present.
163 if ($dataSource == 'CRM_Import_DataSource_CSV' && $skipColumnHeader) {
164 $columnNames = $this->get('originalColHeader');
165 }
166 else {
167 // get the field names from the temp. DB table
168 $dao = new CRM_Core_DAO();
169 $db = $dao->getDatabaseConnection();
170
171 $columnsQuery = "SHOW FIELDS FROM $this->_importTableName
172 WHERE Field NOT LIKE '\_%'";
173 $columnsResult = $db->query($columnsQuery);
174 while ($row = $columnsResult->fetchRow(DB_FETCHMODE_ASSOC)) {
175 $columnNames[] = $row['Field'];
176 }
177 }
178
179 $showColNames = TRUE;
4009859d 180 if ($dataSource === 'CRM_Import_DataSource_CSV' && !$skipColumnHeader) {
6a488035
TO
181 $showColNames = FALSE;
182 }
183 $this->assign('showColNames', $showColNames);
184
185 $this->_columnCount = count($columnNames);
186 $this->_columnNames = $columnNames;
187 $this->assign('columnNames', $columnNames);
188 //$this->_columnCount = $this->get( 'columnCount' );
189 $this->assign('columnCount', $this->_columnCount);
190 $this->_dataValues = $this->get('dataValues');
191 $this->assign('dataValues', $this->_dataValues);
192 $this->assign('rowDisplayCount', 2);
193 }
194
195 /**
fe482240 196 * Build the form object.
a2ca56ec 197 *
198 * @throws \CiviCRM_API3_Exception
6a488035
TO
199 */
200 public function buildQuickForm() {
a2ca56ec 201 $savedMappingID = (int) $this->get('savedMapping');
6a488035 202 //to save the current mappings
a2ca56ec 203 if (!$savedMappingID) {
6a488035
TO
204 $saveDetailsName = ts('Save this field mapping');
205 $this->applyFilter('saveMappingName', 'trim');
206 $this->add('text', 'saveMappingName', ts('Name'));
207 $this->add('text', 'saveMappingDesc', ts('Description'));
208 }
209 else {
210 $savedMapping = $this->get('savedMapping');
211
6ebecfea 212 list($mappingName, $mappingContactType, $mappingLocation, $mappingPhoneType, $mappingImProvider, $mappingRelation, $mappingOperator, $mappingValue, $mappingWebsiteType) = CRM_Core_BAO_Mapping::getMappingFields($savedMapping, TRUE);
6a488035
TO
213
214 //get loaded Mapping Fields
353ffa53 215 $mappingName = CRM_Utils_Array::value(1, $mappingName);
353ffa53 216 $mappingLocation = CRM_Utils_Array::value(1, $mappingLocation);
353ffa53 217 $mappingRelation = CRM_Utils_Array::value(1, $mappingRelation);
6a488035
TO
218 $mappingWebsiteType = CRM_Utils_Array::value(1, $mappingWebsiteType);
219
220 $this->assign('loadedMapping', $savedMapping);
221 $this->set('loadedMapping', $savedMapping);
222
c16da28a 223 $params = ['id' => $savedMapping];
224 $temp = [];
6a488035
TO
225 $mappingDetails = CRM_Core_BAO_Mapping::retrieve($params, $temp);
226
227 $this->assign('savedName', $mappingDetails->name);
228
229 $this->add('hidden', 'mappingId', $savedMapping);
230
231 $this->addElement('checkbox', 'updateMapping', ts('Update this field mapping'), NULL);
232 $saveDetailsName = ts('Save as a new field mapping');
233 $this->add('text', 'saveMappingName', ts('Name'));
234 $this->add('text', 'saveMappingDesc', ts('Description'));
235 }
236
c16da28a 237 $this->addElement('checkbox', 'saveMapping', $saveDetailsName, NULL, ['onclick' => "showSaveDetails(this)"]);
6a488035 238
c16da28a 239 $this->addFormRule(['CRM_Contact_Import_Form_MapField', 'formRule']);
6a488035
TO
240
241 //-------- end of saved mapping stuff ---------
242
c16da28a 243 $defaults = [];
353ffa53
TO
244 $mapperKeys = array_keys($this->_mapperFields);
245 $hasColumnNames = !empty($this->_columnNames);
246 $columnPatterns = $this->get('columnPatterns');
247 $dataPatterns = $this->get('dataPatterns');
6a488035
TO
248 $hasLocationTypes = $this->get('fieldTypes');
249
c16da28a 250 $this->_location_types = ['Primary' => ts('Primary')] + CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id');
6a488035
TO
251 $defaultLocationType = CRM_Core_BAO_LocationType::getDefault();
252
31b8ac82 253 // Pass default location to js
6a488035 254 if ($defaultLocationType) {
31b8ac82
CW
255 $this->assign('defaultLocationType', $defaultLocationType->id);
256 $this->assign('defaultLocationTypeLabel', $this->_location_types[$defaultLocationType->id]);
6a488035
TO
257 }
258
259 /* Initialize all field usages to false */
6a488035
TO
260 foreach ($mapperKeys as $key) {
261 $this->_fieldUsed[$key] = FALSE;
262 }
263
264 $sel1 = $this->_mapperFields;
265 $sel2[''] = NULL;
266
353ffa53
TO
267 $phoneTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Phone', 'phone_type_id');
268 $imProviders = CRM_Core_PseudoConstant::get('CRM_Core_DAO_IM', 'provider_id');
cbf48754 269 $websiteTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Website', 'website_type_id');
6a488035
TO
270
271 foreach ($this->_location_types as $key => $value) {
272 $sel3['phone'][$key] = &$phoneTypes;
273 //build array for IM service provider type for contact
274 $sel3['im'][$key] = &$imProviders;
275 }
276
277 $sel4 = NULL;
278
279 // store and cache all relationship types
280 $contactRelation = new CRM_Contact_DAO_RelationshipType();
281 $contactRelation->find();
282 while ($contactRelation->fetch()) {
c16da28a 283 $contactRelationCache[$contactRelation->id] = [];
6a488035
TO
284 $contactRelationCache[$contactRelation->id]['contact_type_a'] = $contactRelation->contact_type_a;
285 $contactRelationCache[$contactRelation->id]['contact_sub_type_a'] = $contactRelation->contact_sub_type_a;
286 $contactRelationCache[$contactRelation->id]['contact_type_b'] = $contactRelation->contact_type_b;
287 $contactRelationCache[$contactRelation->id]['contact_sub_type_b'] = $contactRelation->contact_sub_type_b;
288 }
c16da28a 289 $highlightedFields = $highlightedRelFields = [];
6a488035
TO
290
291 $highlightedFields['email'] = 'All';
292 $highlightedFields['external_identifier'] = 'All';
293 $highlightedFields['first_name'] = 'Individual';
294 $highlightedFields['last_name'] = 'Individual';
295 $highlightedFields['household_name'] = 'Household';
296 $highlightedFields['organization_name'] = 'Organization';
297
298 foreach ($mapperKeys as $key) {
299 // check if there is a _a_b or _b_a in the key
300 if (strpos($key, '_a_b') || strpos($key, '_b_a')) {
301 list($id, $first, $second) = explode('_', $key);
302 }
303 else {
304 $id = $first = $second = NULL;
305 }
4009859d 306 if (($first === 'a' && $second === 'b') || ($first === 'b' && $second === 'a')) {
6a488035
TO
307 $cType = $contactRelationCache[$id]["contact_type_{$second}"];
308
309 //CRM-5125 for contact subtype specific relationshiptypes
310 $cSubType = NULL;
a7488080 311 if (!empty($contactRelationCache[$id]["contact_sub_type_{$second}"])) {
6a488035
TO
312 $cSubType = $contactRelationCache[$id]["contact_sub_type_{$second}"];
313 }
314
315 if (!$cType) {
316 $cType = 'All';
317 }
318
6a488035
TO
319 $relatedFields = CRM_Contact_BAO_Contact::importableFields($cType);
320 unset($relatedFields['']);
c16da28a 321 $values = [];
6a488035
TO
322 foreach ($relatedFields as $name => $field) {
323 $values[$name] = $field['title'];
324 if (isset($hasLocationTypes[$name])) {
325 $sel3[$key][$name] = $this->_location_types;
326 }
4009859d 327 elseif ($name === 'url') {
6a488035
TO
328 $sel3[$key][$name] = $websiteTypes;
329 }
330 else {
331 $sel3[$name] = NULL;
332 }
333 }
334
335 //fix to append custom group name to field name, CRM-2676
a7488080 336 if (empty($this->_formattedFieldNames[$cType]) || $cType == $this->_contactType) {
6a488035
TO
337 $this->_formattedFieldNames[$cType] = $this->formatCustomFieldName($values);
338 }
339
340 $this->_formattedFieldNames[$cType] = array_merge($values, $this->_formattedFieldNames[$cType]);
341
342 //Modified the Relationship fields if the fields are
343 //present in dedupe rule
8cc574cf 344 if ($this->_onDuplicate != CRM_Import_Parser::DUPLICATE_NOCHECK && !empty($this->_dedupeFields[$cType]) &&
6a488035
TO
345 is_array($this->_dedupeFields[$cType])
346 ) {
c16da28a 347 static $cTypeArray = [];
6a488035
TO
348 if ($cType != $this->_contactType && !in_array($cType, $cTypeArray)) {
349 foreach ($this->_dedupeFields[$cType] as $val) {
350 if ($valTitle = CRM_Utils_Array::value($val, $this->_formattedFieldNames[$cType])) {
351 $this->_formattedFieldNames[$cType][$val] = $valTitle . ' (match to contact)';
352 }
353 }
354 $cTypeArray[] = $cType;
355 }
356 }
357
358 foreach ($highlightedFields as $k => $v) {
4009859d 359 if ($v == $cType || $v === 'All') {
6a488035
TO
360 $highlightedRelFields[$key][] = $k;
361 }
362 }
363 $this->assign('highlightedRelFields', $highlightedRelFields);
364 $sel2[$key] = $this->_formattedFieldNames[$cType];
365
366 if (!empty($cSubType)) {
367 //custom fields for sub type
368 $subTypeFields = CRM_Core_BAO_CustomField::getFieldsForImport($cSubType);
369
370 if (!empty($subTypeFields)) {
371 $subType = NULL;
372 foreach ($subTypeFields as $customSubTypeField => $details) {
373 $subType[$customSubTypeField] = $details['title'];
374 $sel2[$key] = array_merge($sel2[$key], $this->formatCustomFieldName($subType));
375 }
376 }
377 }
378
379 foreach ($this->_location_types as $k => $value) {
380 $sel4[$key]['phone'][$k] = &$phoneTypes;
381 //build array of IM service provider for related contact
382 $sel4[$key]['im'][$k] = &$imProviders;
383 }
384 }
385 else {
386 $options = NULL;
d454582f 387 if (!empty($hasLocationTypes[$key])) {
6a488035
TO
388 $options = $this->_location_types;
389 }
4009859d 390 elseif ($key === 'url') {
6a488035
TO
391 $options = $websiteTypes;
392 }
393 $sel2[$key] = $options;
394 }
395 }
396
397 $js = "<script type='text/javascript'>\n";
398 $formName = 'document.forms.' . $this->_name;
399 //used to warn for mismatch column count or mismatch mapping
0b786e49 400 CRM_Core_Session::singleton()->setStatus(NULL);
a2ca56ec 401 $processor = new CRM_Import_ImportProcessor();
402 $processor->setMappingID($savedMappingID);
403 $processor->setFormName($formName);
404 $processor->setMetadata($this->getContactImportMetadata());
0b786e49 405
6a488035 406 for ($i = 0; $i < $this->_columnCount; $i++) {
c16da28a 407 $sel = &$this->addElement('hierselect', "mapper[$i]", ts('Mapper for Field %1', [1 => $i]), NULL);
6a488035 408
0e3fc022 409 if ($this->get('savedMapping')) {
cd41fa5b 410 list($defaults, $js) = $this->loadSavedMapping($processor, $mappingName, $i, $mappingRelation, $mappingWebsiteType, $mappingLocation, $defaults, $js, $hasColumnNames, $dataPatterns, $columnPatterns);
6a488035
TO
411 }
412 else {
413 $js .= "swapOptions($formName, 'mapper[$i]', 0, 3, 'hs_mapper_0_');\n";
414 if ($hasColumnNames) {
415 // do array search first to see if has mapped key
6a488035
TO
416 $columnKey = array_search($this->_columnNames[$i], $this->_mapperFields);
417 if (isset($this->_fieldUsed[$columnKey])) {
418 $defaults["mapper[$i]"] = $columnKey;
419 $this->_fieldUsed[$key] = TRUE;
420 }
421 else {
422 // Infer the default from the column names if we have them
c16da28a 423 $defaults["mapper[$i]"] = [
6a488035
TO
424 $this->defaultFromColumnName($this->_columnNames[$i],
425 $columnPatterns
426 ),
427 0,
c16da28a 428 ];
6a488035
TO
429 }
430 }
431 else {
432 // Otherwise guess the default from the form of the data
c16da28a 433 $defaults["mapper[$i]"] = [
6a488035
TO
434 $this->defaultFromData($dataPatterns, $i),
435 // $defaultLocationType->id
436 0,
c16da28a 437 ];
6a488035
TO
438 }
439 }
c16da28a 440 $sel->setOptions([$sel1, $sel2, $sel3, $sel4]);
6a488035
TO
441 }
442
443 $js .= "</script>\n";
444 $this->assign('initHideBoxes', $js);
445
446 //set warning if mismatch in more than
447 if (isset($mappingName) &&
448 ($this->_columnCount != count($mappingName))
449 ) {
0b786e49 450 CRM_Core_Session::singleton()->setStatus(ts('The data columns in this import file appear to be different from the saved mapping. Please verify that you have selected the correct saved mapping before continuing.'));
6a488035
TO
451 }
452
453 $this->setDefaults($defaults);
454
c16da28a 455 $this->addButtons([
456 [
457 'type' => 'back',
458 'name' => ts('Previous'),
459 ],
460 [
461 'type' => 'next',
462 'name' => ts('Continue'),
463 'spacing' => '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;',
464 'isDefault' => TRUE,
465 ],
466 [
467 'type' => 'cancel',
468 'name' => ts('Cancel'),
469 ],
470 ]);
6a488035
TO
471 }
472
473 /**
fe482240 474 * Global validation rules for the form.
6a488035 475 *
77c5b619
TO
476 * @param array $fields
477 * Posted values of the form.
6a488035 478 *
a6c01b45
CW
479 * @return array
480 * list of errors to be posted back to the form
6a488035 481 */
00be9182 482 public static function formRule($fields) {
c16da28a 483 $errors = [];
a7488080 484 if (!empty($fields['saveMapping'])) {
6a488035
TO
485 $nameField = CRM_Utils_Array::value('saveMappingName', $fields);
486 if (empty($nameField)) {
487 $errors['saveMappingName'] = ts('Name is required to save Import Mapping');
488 }
489 else {
c0a760fc 490 $mappingTypeId = CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Mapping', 'mapping_type_id', 'Import Contact');
6a488035
TO
491 if (CRM_Core_BAO_Mapping::checkMapping($nameField, $mappingTypeId)) {
492 $errors['saveMappingName'] = ts('Duplicate Import Mapping Name');
493 }
494 }
495 }
496 $template = CRM_Core_Smarty::singleton();
a7488080 497 if (!empty($fields['saveMapping'])) {
6a488035
TO
498 $template->assign('isCheked', TRUE);
499 }
500
501 if (!empty($errors)) {
502 $_flag = 1;
503 $assignError = new CRM_Core_Page();
504 $assignError->assign('mappingDetailsError', $_flag);
505 return $errors;
506 }
507 else {
508 return TRUE;
509 }
510 }
511
512 /**
f12c6f7d 513 * Process the mapped fields and map it into the uploaded file.
6a488035
TO
514 */
515 public function postProcess() {
516 $params = $this->controller->exportValues('MapField');
517
518 //reload the mapfield if load mapping is pressed
519 if (!empty($params['savedMapping'])) {
520 $this->set('savedMapping', $params['savedMapping']);
521 $this->controller->resetPage($this->_name);
522 return;
523 }
353ffa53 524 $mapperKeys = $this->controller->exportValue($this->_name, 'mapper');
52356383 525
526 $parser = $this->submit($params, $mapperKeys);
527
528 // add all the necessary variables to the form
529 $parser->set($this);
530 }
531
532 /**
533 * Format custom field name.
534 *
535 * Combine group and field name to avoid conflict.
536 *
537 * @param array $fields
538 *
539 * @return array
540 */
56dd62a0 541 public function formatCustomFieldName($fields) {
52356383 542 //CRM-2676, replacing the conflict for same custom field name from different custom group.
c16da28a 543 $fieldIds = $formattedFieldNames = [];
52356383 544 foreach ($fields as $key => $value) {
545 if ($customFieldId = CRM_Core_BAO_CustomField::getKeyID($key)) {
546 $fieldIds[] = $customFieldId;
547 }
548 }
549
550 if (!empty($fieldIds) && is_array($fieldIds)) {
551 $groupTitles = CRM_Core_BAO_CustomGroup::getGroupTitles($fieldIds);
552
553 if (!empty($groupTitles)) {
554 foreach ($groupTitles as $fId => $values) {
555 $key = "custom_{$fId}";
556 $groupTitle = $values['groupTitle'];
557 $formattedFieldNames[$key] = $fields[$key] . ' :: ' . $groupTitle;
558 }
559 }
560 }
561
562 return $formattedFieldNames;
563 }
564
565 /**
566 * Main submit function.
567 *
568 * Extracted to add testing & start refactoring.
569 *
570 * @param $params
571 * @param $mapperKeys
572 *
573 * @return \CRM_Contact_Import_Parser_Contact
449fda1c 574 * @throws \CiviCRM_API3_Exception
52356383 575 */
576 public function submit($params, $mapperKeys) {
c16da28a 577 $mapper = $mapperKeysMain = $locations = [];
1881b7b0 578 $parserParameters = CRM_Contact_Import_Parser_Contact::getParameterForParser($this->_columnCount);
6a488035 579
353ffa53
TO
580 $phoneTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Phone', 'phone_type_id');
581 $imProviders = CRM_Core_PseudoConstant::get('CRM_Core_DAO_IM', 'provider_id');
582 $websiteTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Website', 'website_type_id');
b2b0530a 583 $locationTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id');
6ebecfea 584 $locationTypes['Primary'] = ts('Primary');
6a488035 585
6a488035 586 for ($i = 0; $i < $this->_columnCount; $i++) {
6a488035 587
353ffa53
TO
588 $fldName = CRM_Utils_Array::value(0, $mapperKeys[$i]);
589 $selOne = CRM_Utils_Array::value(1, $mapperKeys[$i]);
590 $selTwo = CRM_Utils_Array::value(2, $mapperKeys[$i]);
591 $selThree = CRM_Utils_Array::value(3, $mapperKeys[$i]);
592 $mapper[$i] = $this->_mapperFields[$mapperKeys[$i][0]];
6a488035
TO
593 $mapperKeysMain[$i] = $fldName;
594
595 //need to differentiate non location elements.
6ebecfea 596 if ($selOne && (is_numeric($selOne) || $selOne === 'Primary')) {
4009859d 597 if ($fldName === 'url') {
1881b7b0 598 $parserParameters['mapperWebsiteType'][$i] = $websiteTypes[$selOne];
6a488035
TO
599 }
600 else {
1881b7b0 601 $locations[$i] = $locationTypes[$selOne];
602 $parserParameters['mapperLocType'][$i] = $selOne;
6a488035 603 if ($selTwo && is_numeric($selTwo)) {
4009859d 604 if ($fldName === 'phone') {
1881b7b0 605 $parserParameters['mapperPhoneType'][$i] = $phoneTypes[$selTwo];
6a488035 606 }
4009859d 607 elseif ($fldName === 'im') {
1881b7b0 608 $parserParameters['mapperImProvider'][$i] = $imProviders[$selTwo];
6a488035
TO
609 }
610 }
611 }
612 }
613
614 //relationship contact mapper info.
615 list($id, $first, $second) = CRM_Utils_System::explode('_', $fldName, 3);
4009859d 616 if (($first === 'a' && $second === 'b') ||
617 ($first === 'b' && $second === 'a')
6a488035 618 ) {
1881b7b0 619 $parserParameters['mapperRelated'][$i] = $this->_mapperFields[$fldName];
6a488035 620 if ($selOne) {
4009859d 621 if ($selOne === 'url') {
1881b7b0 622 $parserParameters['relatedContactWebsiteType'][$i] = $websiteTypes[$selTwo];
6a488035
TO
623 }
624 else {
1881b7b0 625 $parserParameters['relatedContactLocType'][$i] = CRM_Utils_Array::value($selTwo, $locationTypes);
6a488035 626 if ($selThree) {
4009859d 627 if ($selOne === 'phone') {
1881b7b0 628 $parserParameters['relatedContactPhoneType'][$i] = $phoneTypes[$selThree];
6a488035 629 }
4009859d 630 elseif ($selOne === 'im') {
1881b7b0 631 $parserParameters['relatedContactImProvider'][$i] = $imProviders[$selThree];
6a488035
TO
632 }
633 }
634 }
635
636 //get the related contact type.
637 $relationType = new CRM_Contact_DAO_RelationshipType();
638 $relationType->id = $id;
639 $relationType->find(TRUE);
1881b7b0 640 $parserParameters['relatedContactType'][$i] = $relationType->{"contact_type_$second"};
641 $parserParameters['relatedContactDetails'][$i] = $this->_formattedFieldNames[$parserParameters['relatedContactType'][$i]][$selOne];
6a488035
TO
642 }
643 }
6a488035
TO
644 }
645
646 $this->set('columnNames', $this->_columnNames);
1881b7b0 647 $this->set('websites', $parserParameters['mapperWebsiteType']);
648 $this->set('locations', $locations);
649 $this->set('phones', $parserParameters['mapperPhoneType']);
650 $this->set('ims', $parserParameters['mapperImProvider']);
651 $this->set('related', $parserParameters['mapperRelated']);
652 $this->set('relatedContactType', $parserParameters['relatedContactType']);
653 $this->set('relatedContactDetails', $parserParameters['relatedContactDetails']);
654 $this->set('relatedContactLocType', $parserParameters['relatedContactLocType']);
655 $this->set('relatedContactPhoneType', $parserParameters['relatedContactPhoneType']);
656 $this->set('relatedContactImProvider', $parserParameters['relatedContactImProvider']);
657 $this->set('relatedContactWebsiteType', $parserParameters['relatedContactWebsiteType']);
658 $this->set('mapper', $mapper);
6a488035
TO
659
660 // store mapping Id to display it in the preview page
661 $this->set('loadMappingId', CRM_Utils_Array::value('mappingId', $params));
662
663 //Updating Mapping Records
a7488080 664 if (!empty($params['updateMapping'])) {
6a488035 665
6a488035
TO
666 $mappingFields = new CRM_Core_DAO_MappingField();
667 $mappingFields->mapping_id = $params['mappingId'];
668 $mappingFields->find();
669
c16da28a 670 $mappingFieldsId = [];
6a488035
TO
671 while ($mappingFields->fetch()) {
672 if ($mappingFields->id) {
673 $mappingFieldsId[$mappingFields->column_number] = $mappingFields->id;
674 }
675 }
676
677 for ($i = 0; $i < $this->_columnCount; $i++) {
678 $updateMappingFields = new CRM_Core_DAO_MappingField();
ce80b209 679 $updateMappingFields->id = CRM_Utils_Array::value($i, $mappingFieldsId);
6a488035
TO
680 $updateMappingFields->mapping_id = $params['mappingId'];
681 $updateMappingFields->column_number = $i;
682
683 $mapperKeyParts = explode('_', $mapperKeys[$i][0], 3);
353ffa53
TO
684 $id = isset($mapperKeyParts[0]) ? $mapperKeyParts[0] : NULL;
685 $first = isset($mapperKeyParts[1]) ? $mapperKeyParts[1] : NULL;
686 $second = isset($mapperKeyParts[2]) ? $mapperKeyParts[2] : NULL;
6a488035
TO
687 if (($first == 'a' && $second == 'b') || ($first == 'b' && $second == 'a')) {
688 $updateMappingFields->relationship_type_id = $id;
689 $updateMappingFields->relationship_direction = "{$first}_{$second}";
690 $updateMappingFields->name = ucwords(str_replace("_", " ", $mapperKeys[$i][1]));
691 // get phoneType id and provider id separately
692 // before updating mappingFields of phone and IM for related contact, CRM-3140
693 if (CRM_Utils_Array::value('1', $mapperKeys[$i]) == 'url') {
694 $updateMappingFields->website_type_id = isset($mapperKeys[$i][2]) ? $mapperKeys[$i][2] : NULL;
695 }
696 else {
697 if (CRM_Utils_Array::value('1', $mapperKeys[$i]) == 'phone') {
698 $updateMappingFields->phone_type_id = isset($mapperKeys[$i][3]) ? $mapperKeys[$i][3] : NULL;
699 }
700 elseif (CRM_Utils_Array::value('1', $mapperKeys[$i]) == 'im') {
701 $updateMappingFields->im_provider_id = isset($mapperKeys[$i][3]) ? $mapperKeys[$i][3] : NULL;
702 }
af88ad41 703 $updateMappingFields->location_type_id = isset($mapperKeys[$i][2]) && is_numeric($mapperKeys[$i][2]) ? $mapperKeys[$i][2] : NULL;
6a488035
TO
704 }
705 }
706 else {
707 $updateMappingFields->name = $mapper[$i];
708 $updateMappingFields->relationship_type_id = 'NULL';
709 $updateMappingFields->relationship_type_direction = 'NULL';
b44e3f84 710 // to store phoneType id and provider id separately
6a488035
TO
711 // before updating mappingFields for phone and IM, CRM-3140
712 if (CRM_Utils_Array::value('0', $mapperKeys[$i]) == 'url') {
713 $updateMappingFields->website_type_id = isset($mapperKeys[$i][1]) ? $mapperKeys[$i][1] : NULL;
714 }
715 else {
716 if (CRM_Utils_Array::value('0', $mapperKeys[$i]) == 'phone') {
717 $updateMappingFields->phone_type_id = isset($mapperKeys[$i][2]) ? $mapperKeys[$i][2] : NULL;
718 }
719 elseif (CRM_Utils_Array::value('0', $mapperKeys[$i]) == 'im') {
720 $updateMappingFields->im_provider_id = isset($mapperKeys[$i][2]) ? $mapperKeys[$i][2] : NULL;
721 }
6ebecfea 722 $locationTypeID = $parserParameters['mapperLocType'][$i];
723 // location_type_id is NULL for non-location fields, and for Primary location.
724 $updateMappingFields->location_type_id = is_numeric($locationTypeID) ? $locationTypeID : 'null';
6a488035
TO
725 }
726 }
727 $updateMappingFields->save();
728 }
729 }
730
731 //Saving Mapping Details and Records
a7488080 732 if (!empty($params['saveMapping'])) {
c16da28a 733 $mappingParams = [
6a488035
TO
734 'name' => $params['saveMappingName'],
735 'description' => $params['saveMappingDesc'],
37353970 736 'mapping_type_id' => 'Import Contact',
c16da28a 737 ];
6a488035 738
37353970 739 $saveMapping = civicrm_api3('Mapping', 'create', $mappingParams);
6a488035 740
6a488035
TO
741 $contactType = $this->get('contactType');
742 switch ($contactType) {
a05662ef 743 case CRM_Import_Parser::CONTACT_INDIVIDUAL:
6a488035
TO
744 $cType = 'Individual';
745 break;
746
a05662ef 747 case CRM_Import_Parser::CONTACT_HOUSEHOLD:
6a488035
TO
748 $cType = 'Household';
749 break;
750
a05662ef 751 case CRM_Import_Parser::CONTACT_ORGANIZATION:
6a488035
TO
752 $cType = 'Organization';
753 }
754
449fda1c 755 $mappingID = NULL;
6a488035 756 for ($i = 0; $i < $this->_columnCount; $i++) {
449fda1c 757 $mappingID = $this->saveMappingField($mapperKeys, $saveMapping, $cType, $i, $mapper, $parserParameters);
6a488035 758 }
449fda1c 759 $this->set('savedMapping', $mappingID);
6a488035
TO
760 }
761
1881b7b0 762 $parser = new CRM_Contact_Import_Parser_Contact($mapperKeysMain, $parserParameters['mapperLocType'], $parserParameters['mapperPhoneType'],
763 $parserParameters['mapperImProvider'], $parserParameters['mapperRelated'], $parserParameters['relatedContactType'],
764 $parserParameters['relatedContactDetails'], $parserParameters['relatedContactLocType'],
765 $parserParameters['relatedContactPhoneType'], $parserParameters['relatedContactImProvider'],
766 $parserParameters['mapperWebsiteType'], $parserParameters['relatedContactWebsiteType']
6a488035
TO
767 );
768
769 $primaryKeyName = $this->get('primaryKeyName');
770 $statusFieldName = $this->get('statusFieldName');
771 $parser->run($this->_importTableName,
772 $mapper,
a05662ef 773 CRM_Import_Parser::MODE_PREVIEW,
6a488035
TO
774 $this->get('contactType'),
775 $primaryKeyName,
776 $statusFieldName,
777 $this->_onDuplicate,
778 NULL, NULL, FALSE,
719a6fec 779 CRM_Contact_Import_Parser::DEFAULT_TIMEOUT,
6a488035
TO
780 $this->get('contactSubType'),
781 $this->get('dedupe')
782 );
52356383 783 return $parser;
6a488035 784 }
96025800 785
449fda1c 786 /**
787 * @param $mapperKeys
788 * @param array $saveMapping
789 * @param string $cType
790 * @param int $i
791 * @param array $mapper
792 * @param array $parserParameters
793 *
794 * @return int
795 */
796 protected function saveMappingField($mapperKeys, array $saveMapping, string $cType, int $i, array $mapper, array $parserParameters): int {
797 $saveMappingFields = new CRM_Core_DAO_MappingField();
798 $saveMappingFields->mapping_id = $saveMapping['id'];
799 $saveMappingFields->contact_type = $cType;
800 $saveMappingFields->column_number = $i;
801
802 $mapperKeyParts = explode('_', $mapperKeys[$i][0], 3);
803 $id = isset($mapperKeyParts[0]) ? $mapperKeyParts[0] : NULL;
804 $first = isset($mapperKeyParts[1]) ? $mapperKeyParts[1] : NULL;
805 $second = isset($mapperKeyParts[2]) ? $mapperKeyParts[2] : NULL;
806 if (($first == 'a' && $second == 'b') || ($first == 'b' && $second == 'a')) {
807 $saveMappingFields->name = ucwords(str_replace("_", " ", $mapperKeys[$i][1]));
808 $saveMappingFields->relationship_type_id = $id;
809 $saveMappingFields->relationship_direction = "{$first}_{$second}";
810 // to get phoneType id and provider id separately
811 // before saving mappingFields of phone and IM for related contact, CRM-3140
812 if (CRM_Utils_Array::value('1', $mapperKeys[$i]) == 'url') {
813 $saveMappingFields->website_type_id = isset($mapperKeys[$i][2]) ? $mapperKeys[$i][2] : NULL;
814 }
815 else {
816 if (CRM_Utils_Array::value('1', $mapperKeys[$i]) == 'phone') {
817 $saveMappingFields->phone_type_id = isset($mapperKeys[$i][3]) ? $mapperKeys[$i][3] : NULL;
818 }
819 elseif (CRM_Utils_Array::value('1', $mapperKeys[$i]) == 'im') {
820 $saveMappingFields->im_provider_id = isset($mapperKeys[$i][3]) ? $mapperKeys[$i][3] : NULL;
821 }
822 $saveMappingFields->location_type_id = (isset($mapperKeys[$i][2]) && $mapperKeys[$i][2] !== 'Primary') ? $mapperKeys[$i][2] : NULL;
823 }
824 }
825 else {
826 $saveMappingFields->name = $mapper[$i];
827 $locationTypeID = $parserParameters['mapperLocType'][$i];
828 // to get phoneType id and provider id separately
829 // before saving mappingFields of phone and IM, CRM-3140
830 if (CRM_Utils_Array::value('0', $mapperKeys[$i]) == 'url') {
831 $saveMappingFields->website_type_id = isset($mapperKeys[$i][1]) ? $mapperKeys[$i][1] : NULL;
832 }
833 else {
834 if (CRM_Utils_Array::value('0', $mapperKeys[$i]) == 'phone') {
835 $saveMappingFields->phone_type_id = isset($mapperKeys[$i][2]) ? $mapperKeys[$i][2] : NULL;
836 }
837 elseif (CRM_Utils_Array::value('0', $mapperKeys[$i]) == 'im') {
838 $saveMappingFields->im_provider_id = isset($mapperKeys[$i][2]) ? $mapperKeys[$i][2] : NULL;
839 }
840 $saveMappingFields->location_type_id = is_numeric($locationTypeID) ? $locationTypeID : NULL;
841 }
842 $saveMappingFields->relationship_type_id = NULL;
843 }
844 $saveMappingFields->save();
845 return $saveMappingFields->mapping_id;
846 }
847
0e3fc022 848 /**
a2ca56ec 849 * @param \CRM_Import_ImportProcessor $processor
0e3fc022 850 * @param $mappingName
851 * @param int $i
852 * @param $mappingRelation
853 * @param $mappingWebsiteType
854 * @param $mappingLocation
0e3fc022 855 * @param array $defaults
0e3fc022 856 * @param string $js
857 * @param bool $hasColumnNames
011d9c1e 858 * @param array $dataPatterns
859 * @param array $columnPatterns
0e3fc022 860 *
861 * @return array
cd41fa5b 862 * @throws \CiviCRM_API3_Exception
0e3fc022 863 */
cd41fa5b 864 public function loadSavedMapping($processor, $mappingName, $i, $mappingRelation, $mappingWebsiteType, $mappingLocation, $defaults, $js, $hasColumnNames, $dataPatterns, $columnPatterns) {
0e3fc022 865 $jsSet = FALSE;
a2ca56ec 866 $formName = $processor->getFormName();
0e3fc022 867 if (isset($mappingName[$i])) {
868 if ($mappingName[$i] != ts('- do not import -')) {
869
870 if (isset($mappingRelation[$i])) {
871 // relationship mapping
872 switch ($this->get('contactType')) {
873 case CRM_Import_Parser::CONTACT_INDIVIDUAL:
874 $contactType = 'Individual';
875 break;
876
877 case CRM_Import_Parser::CONTACT_HOUSEHOLD:
878 $contactType = 'Household';
879 break;
880
881 case CRM_Import_Parser::CONTACT_ORGANIZATION:
882 $contactType = 'Organization';
883 }
884 //CRM-5125
885 $contactSubType = NULL;
886 if ($this->get('contactSubType')) {
887 $contactSubType = $this->get('contactSubType');
888 }
889
890 $relations = CRM_Contact_BAO_Relationship::getContactRelationshipType(NULL, NULL, NULL, $contactType,
891 FALSE, 'label', TRUE, $contactSubType
892 );
893
894 foreach ($relations as $key => $var) {
895 if ($key == $mappingRelation[$i]) {
896 $relation = $key;
897 break;
898 }
899 }
900
901 $contactDetails = strtolower(str_replace(" ", "_", $mappingName[$i]));
902 $websiteTypeId = isset($mappingWebsiteType[$i]) ? $mappingWebsiteType[$i] : NULL;
903 $locationId = isset($mappingLocation[$i]) ? $mappingLocation[$i] : 0;
cd41fa5b 904 $phoneType = $processor->getPhoneTypeID($i);
905 $imProvider = $processor->getIMProviderID($i);
0e3fc022 906
907 if ($websiteTypeId) {
908 $defaults["mapper[$i]"] = [$relation, $contactDetails, $websiteTypeId];
909 if (!$websiteTypeId) {
910 $js .= "{$formName}['mapper[$i][2]'].style.display = 'none';\n";
911 }
912 }
913 else {
914 // default for IM/phone when mapping with relation is true
915 $typeId = NULL;
916 if (isset($phoneType)) {
917 $typeId = $phoneType;
918 }
919 elseif (isset($imProvider)) {
920 $typeId = $imProvider;
921 }
922 $defaults["mapper[$i]"] = [$relation, $contactDetails, $locationId, $typeId];
923 if (!$locationId) {
924 $js .= "{$formName}['mapper[$i][2]'].style.display = 'none';\n";
925 }
926 }
927 // fix for edge cases, CRM-4954
928 if ($contactDetails == 'image_url') {
929 $contactDetails = str_replace('url', 'URL', $contactDetails);
930 }
931
932 if (!$contactDetails) {
933 $js .= "{$formName}['mapper[$i][1]'].style.display = 'none';\n";
934 }
935
936 if ((!$phoneType) && (!$imProvider)) {
937 $js .= "{$formName}['mapper[$i][3]'].style.display = 'none';\n";
938 }
939 //$js .= "{$formName}['mapper[$i][3]'].style.display = 'none';\n";
940 $jsSet = TRUE;
941 }
942 else {
cb0908fc 943 $mappingHeader = array_keys((array) $this->_mapperFields, $mappingName[$i]);
0e3fc022 944 $websiteTypeId = isset($mappingWebsiteType[$i]) ? $mappingWebsiteType[$i] : NULL;
945 $locationId = isset($mappingLocation[$i]) ? $mappingLocation[$i] : 0;
cd41fa5b 946 $phoneType = $processor->getPhoneTypeID($i);
947 $imProvider = $processor->getIMProviderID($i);
0e3fc022 948
949 if ($websiteTypeId) {
0e3fc022 950 $defaults["mapper[$i]"] = [$mappingHeader[0], $websiteTypeId];
951 }
952 else {
953 if (!$locationId) {
954 $js .= "{$formName}['mapper[$i][1]'].style.display = 'none';\n";
955 }
956 //default for IM/phone without related contact
957 $typeId = NULL;
958 if (isset($phoneType)) {
959 $typeId = $phoneType;
960 }
961 elseif (isset($imProvider)) {
962 $typeId = $imProvider;
963 }
aa4efdff 964 $defaults["mapper[$i]"] = [$mappingHeader[0] ?? '', $locationId, $typeId];
0e3fc022 965 }
966
967 if ((!$phoneType) && (!$imProvider)) {
968 $js .= "{$formName}['mapper[$i][2]'].style.display = 'none';\n";
969 }
970
971 $js .= "{$formName}['mapper[$i][3]'].style.display = 'none';\n";
972
973 $jsSet = TRUE;
974 }
975 }
976 else {
977 $defaults["mapper[$i]"] = [];
978 }
979 if (!$jsSet) {
980 for ($k = 1; $k < 4; $k++) {
981 $js .= "{$formName}['mapper[$i][$k]'].style.display = 'none';\n";
982 }
983 }
984 }
985 else {
986 // this load section to help mapping if we ran out of saved columns when doing Load Mapping
987 $js .= "swapOptions($formName, 'mapper[$i]', 0, 3, 'hs_mapper_0_');\n";
988
989 if ($hasColumnNames) {
990 $defaults["mapper[$i]"] = [$this->defaultFromColumnName($this->_columnNames[$i], $columnPatterns)];
991 }
992 else {
993 $defaults["mapper[$i]"] = [$this->defaultFromData($dataPatterns, $i)];
994 }
995 }
cb0908fc 996 return [$defaults, $js];
0e3fc022 997 }
998
6a488035 999}