Add extra shared import functions
[civicrm-core.git] / CRM / Import / Form / MapField.php
CommitLineData
b26295b8
CW
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
b26295b8 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 |
b26295b8 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
b26295b8
CW
11
12/**
b26295b8 13 * @package CRM
ca5cec67 14 * @copyright CiviCRM LLC https://civicrm.org/licensing
b26295b8
CW
15 */
16
17/**
2b4bc760 18 * This class gets the name of the file to upload.
19 *
b26295b8
CW
20 * TODO: CRM-11254 - There's still a lot of duplicate code in the 5 child classes that should be moved here
21 */
5e8faabc 22abstract class CRM_Import_Form_MapField extends CRM_Import_Forms {
b26295b8
CW
23
24 /**
100fef9d 25 * Cache of preview data values
b26295b8
CW
26 *
27 * @var array
b26295b8
CW
28 */
29 protected $_dataValues;
30
31 /**
100fef9d 32 * Mapper fields
b26295b8
CW
33 *
34 * @var array
b26295b8
CW
35 */
36 protected $_mapperFields;
37
b26295b8 38 /**
100fef9d 39 * Number of columns in import file
b26295b8
CW
40 *
41 * @var int
b26295b8
CW
42 */
43 protected $_columnCount;
44
45 /**
100fef9d 46 * Column headers, if we have them
b26295b8
CW
47 *
48 * @var array
b26295b8
CW
49 */
50 protected $_columnHeaders;
51
52 /**
100fef9d 53 * An array of booleans to keep track of whether a field has been used in
b26295b8
CW
54 * form building already.
55 *
56 * @var array
b26295b8
CW
57 */
58 protected $_fieldUsed;
59
60 /**
2b4bc760 61 * Return a descriptive name for the page, used in wizard header.
b26295b8
CW
62 *
63 * @return string
b26295b8
CW
64 */
65 public function getTitle() {
66 return ts('Match Fields');
67 }
68
8d0967f5
EM
69 /**
70 * Shared preProcess code.
71 */
72 public function preProcess() {
73 $this->assignMapFieldVariables();
77a9ae24
EM
74 $this->_mapperFields = $this->getAvailableFields();
75 asort($this->_mapperFields);
8d0967f5
EM
76 parent::preProcess();
77 }
78
992a3d9e
EM
79 /**
80 * Process the mapped fields and map it into the uploaded file
81 * preview the file and extract some summary statistics
82 *
83 * @return void
84 * @noinspection PhpDocSignatureInspection
85 * @noinspection PhpUnhandledExceptionInspection
86 */
87 public function postProcess() {
88 $this->updateUserJobMetadata('submitted_values', $this->getSubmittedValues());
89 $this->saveMapping($this->getMappingTypeName());
90 $parser = $this->getParser();
91 $parser->init();
92 $parser->validate();
93 }
94
b26295b8 95 /**
fe482240 96 * Attempt to match header labels with our mapper fields.
b26295b8 97 *
79d7553f 98 * @param string $header
99 * @param array $patterns
b26295b8
CW
100 *
101 * @return string
b26295b8
CW
102 */
103 public function defaultFromHeader($header, &$patterns) {
104 foreach ($patterns as $key => $re) {
105 // Skip empty key/patterns
106 if (!$key || !$re || strlen("$re") < 5) {
107 continue;
108 }
109
110 // Scan through the headerPatterns defined in the schema for a match
111 if (preg_match($re, $header)) {
112 $this->_fieldUsed[$key] = TRUE;
113 return $key;
114 }
115 }
116 return '';
117 }
118
119 /**
fe482240 120 * Guess at the field names given the data and patterns from the schema.
b26295b8 121 *
79d7553f 122 * @param array $patterns
123 * @param string $index
b26295b8
CW
124 *
125 * @return string
b26295b8 126 */
56dd62a0 127 public function defaultFromData($patterns, $index) {
353ffa53 128 $best = '';
b26295b8 129 $bestHits = 0;
353ffa53 130 $n = count($this->_dataValues);
b26295b8
CW
131
132 foreach ($patterns as $key => $re) {
133 // Skip empty key/patterns
134 if (!$key || !$re || strlen("$re") < 5) {
135 continue;
136 }
137
138 /* Take a vote over the preview data set */
139 $hits = 0;
140 for ($i = 0; $i < $n; $i++) {
141 if (isset($this->_dataValues[$i][$index])) {
142 if (preg_match($re, $this->_dataValues[$i][$index])) {
143 $hits++;
144 }
145 }
146 }
147 if ($hits > $bestHits) {
148 $bestHits = $hits;
149 $best = $key;
150 }
151 }
152
153 if ($best != '') {
154 $this->_fieldUsed[$best] = TRUE;
155 }
156 return $best;
157 }
158
ad05d047 159 /**
160 * Add the saved mapping fields to the form.
161 *
162 * @param int|null $savedMappingID
163 *
164 * @throws \CiviCRM_API3_Exception
165 */
166 protected function buildSavedMappingFields($savedMappingID) {
167 //to save the current mappings
168 if (!$savedMappingID) {
169 $saveDetailsName = ts('Save this field mapping');
170 $this->applyFilter('saveMappingName', 'trim');
171 $this->add('text', 'saveMappingName', ts('Name'));
172 $this->add('text', 'saveMappingDesc', ts('Description'));
173 }
174 else {
175 $savedMapping = $this->get('savedMapping');
176
177 $mappingName = (string) civicrm_api3('Mapping', 'getvalue', ['id' => $savedMappingID, 'return' => 'name']);
178 $this->set('loadedMapping', $savedMapping);
ad05d047 179 $this->add('hidden', 'mappingId', $savedMappingID);
180
181 $this->addElement('checkbox', 'updateMapping', ts('Update this field mapping'), NULL);
182 $saveDetailsName = ts('Save as a new field mapping');
183 $this->add('text', 'saveMappingName', ts('Name'));
184 $this->add('text', 'saveMappingDesc', ts('Description'));
185 }
262b7f26 186 $this->assign('savedMappingName', $mappingName ?? NULL);
ad05d047 187 $this->addElement('checkbox', 'saveMapping', $saveDetailsName, NULL, ['onclick' => "showSaveDetails(this)"]);
188 }
189
02a237ce 190 /**
191 * Validate that sufficient fields have been supplied to match to a contact.
192 *
193 * @param string $contactType
194 * @param array $importKeys
195 *
196 * @return string
197 * Message if insufficient fields are present. Empty string otherwise.
198 */
199 protected static function validateRequiredContactMatchFields(string $contactType, array $importKeys): string {
61194d45 200 [$ruleFields, $threshold] = CRM_Dedupe_BAO_DedupeRuleGroup::dedupeRuleFieldsWeight([
02a237ce 201 'used' => 'Unsupervised',
202 'contact_type' => $contactType,
203 ]);
204 $weightSum = 0;
205 foreach ($importKeys as $key => $val) {
206 if (array_key_exists($val, $ruleFields)) {
207 $weightSum += $ruleFields[$val];
208 }
209 }
210 $fieldMessage = '';
211 foreach ($ruleFields as $field => $weight) {
212 $fieldMessage .= ' ' . $field . '(weight ' . $weight . ')';
213 }
214 if ($weightSum < $threshold) {
215 return $fieldMessage . ' ' . ts('(Sum of all weights should be greater than or equal to threshold: %1).', array(
216 1 => $threshold,
217 ));
218 }
219 return '';
220 }
221
73edfc10
EM
222 /**
223 * Get the field mapped to the savable format.
224 *
225 * @param array $fieldMapping
226 * @param int $mappingID
227 * @param int $columnNumber
228 *
229 * @return array
230 * @throws \CRM_Core_Exception
231 */
232 protected function getMappedField(array $fieldMapping, int $mappingID, int $columnNumber): array {
233 return $this->getParser()->getMappingFieldFromMapperInput($fieldMapping, $mappingID, $columnNumber);
234 }
235
236 /**
237 * Save the mapping field.
238 *
239 * @param int $mappingID
240 * @param int $columnNumber
241 * @param bool $isUpdate
242 *
243 * @throws \API_Exception
244 * @throws \CRM_Core_Exception
245 */
246 protected function saveMappingField(int $mappingID, int $columnNumber, bool $isUpdate = FALSE): void {
247 $fieldMapping = (array) $this->getSubmittedValue('mapper')[$columnNumber];
248 $mappedField = $this->getMappedField($fieldMapping, $mappingID, $columnNumber);
992a3d9e
EM
249 if (empty($mappedField['name'])) {
250 $mappedField['name'] = 'do_not_import';
251 }
73edfc10
EM
252 if ($isUpdate) {
253 Civi\Api4\MappingField::update(FALSE)
254 ->setValues($mappedField)
255 ->addWhere('column_number', '=', $columnNumber)
256 ->addWhere('mapping_id', '=', $mappingID)
257 ->execute();
258 }
259 else {
260 Civi\Api4\MappingField::create(FALSE)
261 ->setValues($mappedField)->execute();
262 }
263 }
264
77a9ae24
EM
265 /**
266 * Save the Field Mapping.
267 *
268 * @param string $mappingType
269 *
270 * @throws \API_Exception
271 * @throws \CRM_Core_Exception
272 */
273 protected function saveMapping(string $mappingType): void {
274 //Updating Mapping Records
275 if ($this->getSubmittedValue('updateMapping')) {
276 foreach (array_keys($this->getColumnHeaders()) as $i) {
277 $this->saveMappingField($this->getSubmittedValue('mappingId'), $i, TRUE);
278 }
279 }
280 //Saving Mapping Details and Records
281 if ($this->getSubmittedValue('saveMapping')) {
282 $mappingParams = [
283 'name' => $this->getSubmittedValue('saveMappingName'),
284 'description' => $this->getSubmittedValue('saveMappingDesc'),
285 'mapping_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Mapping', 'mapping_type_id', $mappingType),
286 ];
287 $saveMapping = CRM_Core_BAO_Mapping::add($mappingParams);
288
289 foreach (array_keys($this->getColumnHeaders()) as $i) {
290 $this->saveMappingField($saveMapping->id, $i, FALSE);
291 }
292 $this->set('savedMapping', $saveMapping->id);
293 }
294 }
295
b26295b8 296}