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