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