Commit | Line | Data |
---|---|---|
6a488035 TO |
1 | <?php |
2 | /* | |
3 | +--------------------------------------------------------------------+ | |
bc77d7c0 | 4 | | Copyright CiviCRM LLC. All rights reserved. | |
6a488035 | 5 | | | |
bc77d7c0 TO |
6 | | This work is published under the GNU AGPLv3 license with some | |
7 | | permitted exceptions and without any warranty. For full license | | |
8 | | and copyright information, see https://civicrm.org/licensing | | |
6a488035 | 9 | +--------------------------------------------------------------------+ |
d25dd0ee | 10 | */ |
6a488035 TO |
11 | |
12 | /** | |
13 | * | |
14 | * @package CRM | |
ca5cec67 | 15 | * @copyright CiviCRM LLC https://civicrm.org/licensing |
6a488035 TO |
16 | */ |
17 | ||
18 | /** | |
347e061b | 19 | * This class gets the name of the file to upload. |
6a488035 | 20 | */ |
b26295b8 | 21 | class CRM_Contribute_Import_Form_MapField extends CRM_Import_Form_MapField { |
6a488035 | 22 | |
be3c7034 | 23 | /** |
24 | * Check if required fields are present. | |
25 | * | |
26 | * @param CRM_Contribute_Import_Form_MapField $self | |
27 | * @param string $contactORContributionId | |
28 | * @param array $importKeys | |
29 | * @param array $errors | |
30 | * @param int $weightSum | |
31 | * @param int $threshold | |
32 | * @param string $fieldMessage | |
33 | * | |
34 | * @return array | |
35 | */ | |
36 | protected static function checkRequiredFields($self, string $contactORContributionId, array $importKeys, array $errors, int $weightSum, $threshold, string $fieldMessage): array { | |
37 | // FIXME: should use the schema titles, not redeclare them | |
38 | $requiredFields = [ | |
39 | $contactORContributionId == 'contribution_id' ? 'contribution_id' : 'contribution_contact_id' => $contactORContributionId == 'contribution_id' ? ts('Contribution ID') : ts('Contact ID'), | |
40 | 'total_amount' => ts('Total Amount'), | |
cbc11a37 | 41 | 'financial_type_id' => ts('Financial Type'), |
be3c7034 | 42 | ]; |
43 | ||
44 | foreach ($requiredFields as $field => $title) { | |
45 | if (!in_array($field, $importKeys)) { | |
46 | if (empty($errors['_qf_default'])) { | |
47 | $errors['_qf_default'] = ''; | |
48 | } | |
49 | if ($field == $contactORContributionId) { | |
50 | if (!($weightSum >= $threshold || in_array('external_identifier', $importKeys)) && | |
51 | $self->_onDuplicate != CRM_Import_Parser::DUPLICATE_UPDATE | |
52 | ) { | |
53 | $errors['_qf_default'] .= ts('Missing required contact matching fields.') . " $fieldMessage " . ts('(Sum of all weights should be greater than or equal to threshold: %1).', [1 => $threshold]) . '<br />'; | |
54 | } | |
55 | elseif ($self->_onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE && | |
56 | !(in_array('invoice_id', $importKeys) || in_array('trxn_id', $importKeys) || | |
57 | in_array('contribution_id', $importKeys) | |
58 | ) | |
59 | ) { | |
60 | $errors['_qf_default'] .= ts('Invoice ID or Transaction ID or Contribution ID are required to match to the existing contribution records in Update mode.') . '<br />'; | |
61 | } | |
62 | } | |
63 | else { | |
64 | $errors['_qf_default'] .= ts('Missing required field: %1', [1 => $title]) . '<br />'; | |
65 | } | |
66 | } | |
67 | } | |
68 | return $errors; | |
69 | } | |
70 | ||
6a488035 | 71 | /** |
fe482240 | 72 | * Set variables up before form is built. |
6a488035 TO |
73 | */ |
74 | public function preProcess() { | |
8daa4f36 | 75 | parent::preProcess(); |
6a488035 TO |
76 | |
77 | $this->_columnCount = $this->get('columnCount'); | |
8daa4f36 EM |
78 | $skipColumnHeader = $this->getSubmittedValue('skipColumnHeader'); |
79 | $this->_onDuplicate = $this->getSubmittedValue('onDuplicate'); | |
80 | $this->assign('skipColumnHeader', $skipColumnHeader); | |
6a488035 | 81 | |
cbc11a37 | 82 | $highlightedFields = ['financial_type_id', 'total_amount']; |
6a488035 TO |
83 | //CRM-2219 removing other required fields since for updation only |
84 | //invoice id or trxn id or contribution id is required. | |
a05662ef | 85 | if ($this->_onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE) { |
98ca76db | 86 | $remove = [ |
87 | 'contribution_contact_id', | |
88 | 'email', | |
89 | 'first_name', | |
90 | 'last_name', | |
91 | 'external_identifier', | |
92 | ]; | |
6a488035 TO |
93 | foreach ($remove as $value) { |
94 | unset($this->_mapperFields[$value]); | |
95 | } | |
96 | ||
97 | //modify field title only for update mode. CRM-3245 | |
98ca76db | 98 | foreach ([ |
1330f57a SL |
99 | 'contribution_id', |
100 | 'invoice_id', | |
101 | 'trxn_id', | |
102 | ] as $key) { | |
6a488035 TO |
103 | $this->_mapperFields[$key] .= ' (match to contribution record)'; |
104 | $highlightedFields[] = $key; | |
105 | } | |
106 | } | |
a05662ef | 107 | elseif ($this->_onDuplicate == CRM_Import_Parser::DUPLICATE_SKIP) { |
6a488035 | 108 | unset($this->_mapperFields['contribution_id']); |
98ca76db | 109 | $highlightedFieldsArray = [ |
353ffa53 TO |
110 | 'contribution_contact_id', |
111 | 'email', | |
112 | 'first_name', | |
113 | 'last_name', | |
389bcebf | 114 | 'external_identifier', |
98ca76db | 115 | ]; |
6a488035 TO |
116 | foreach ($highlightedFieldsArray as $name) { |
117 | $highlightedFields[] = $name; | |
118 | } | |
119 | } | |
120 | ||
121 | // modify field title for contribution status | |
122 | $this->_mapperFields['contribution_status_id'] = ts('Contribution Status'); | |
123 | ||
124 | $this->assign('highlightedFields', $highlightedFields); | |
125 | } | |
126 | ||
127 | /** | |
fe482240 | 128 | * Build the form object. |
ad05d047 | 129 | * |
130 | * @throws \CiviCRM_API3_Exception | |
6a488035 TO |
131 | */ |
132 | public function buildQuickForm() { | |
5003f9ab EM |
133 | $savedMappingID = $this->getSubmittedValue('savedMapping'); |
134 | ||
ad05d047 | 135 | $this->buildSavedMappingFields($savedMappingID); |
6a488035 | 136 | |
98ca76db | 137 | $this->addFormRule([ |
138 | 'CRM_Contribute_Import_Form_MapField', | |
139 | 'formRule', | |
140 | ], $this); | |
6a488035 TO |
141 | |
142 | //-------- end of saved mapping stuff --------- | |
143 | ||
98ca76db | 144 | $defaults = []; |
91bb24a7 | 145 | $mapperKeys = array_keys($this->_mapperFields); |
8daa4f36 EM |
146 | $hasHeaders = $this->getSubmittedValue('skipColumnHeader'); |
147 | $headerPatterns = $this->getHeaderPatterns(); | |
148 | $dataPatterns = $this->getDataPatterns(); | |
149 | $mapperKeysValues = $this->getSubmittedValue('mapper'); | |
150 | $columnHeaders = $this->getColumnHeaders(); | |
6a488035 | 151 | |
6a488035 | 152 | /* Initialize all field usages to false */ |
6a488035 TO |
153 | foreach ($mapperKeys as $key) { |
154 | $this->_fieldUsed[$key] = FALSE; | |
155 | } | |
6a488035 TO |
156 | $sel1 = $this->_mapperFields; |
157 | ||
158 | if (!$this->get('onDuplicate')) { | |
159 | unset($sel1['id']); | |
160 | unset($sel1['contribution_id']); | |
161 | } | |
162 | ||
6a488035 | 163 | $softCreditFields['contact_id'] = ts('Contact ID'); |
7b99ead3 | 164 | $softCreditFields['external_identifier'] = ts('External ID'); |
377fa510 | 165 | $softCreditFields['email'] = ts('Email'); |
6a488035 TO |
166 | |
167 | $sel2['soft_credit'] = $softCreditFields; | |
377fa510 | 168 | $sel3['soft_credit']['contact_id'] = $sel3['soft_credit']['external_identifier'] = $sel3['soft_credit']['email'] = CRM_Core_OptionGroup::values('soft_credit_type'); |
1221efe9 | 169 | $sel4 = NULL; |
6a488035 TO |
170 | |
171 | // end of soft credit section | |
6a488035 TO |
172 | $js = "<script type='text/javascript'>\n"; |
173 | $formName = 'document.forms.' . $this->_name; | |
174 | ||
175 | //used to warn for mismatch column count or mismatch mapping | |
176 | $warning = 0; | |
177 | ||
8daa4f36 | 178 | foreach ($columnHeaders as $i => $columnHeader) { |
98ca76db | 179 | $sel = &$this->addElement('hierselect', "mapper[$i]", ts('Mapper for Field %1', [1 => $i]), NULL); |
6a488035 TO |
180 | $jsSet = FALSE; |
181 | if ($this->get('savedMapping')) { | |
77a9ae24 | 182 | [$mappingName, $mappingContactType] = CRM_Core_BAO_Mapping::getMappingFields($savedMappingID); |
cb342aae | 183 | |
184 | $mappingName = $mappingName[1]; | |
185 | $mappingContactType = $mappingContactType[1]; | |
6a488035 | 186 | if (isset($mappingName[$i])) { |
73edfc10 EM |
187 | if ($mappingName[$i] != ts('do_not_import')) { |
188 | $softField = $mappingContactType[$i] ?? ''; | |
6a488035 TO |
189 | |
190 | if (!$softField) { | |
191 | $js .= "{$formName}['mapper[$i][1]'].style.display = 'none';\n"; | |
192 | } | |
193 | ||
194 | $js .= "{$formName}['mapper[$i][2]'].style.display = 'none';\n"; | |
195 | $js .= "{$formName}['mapper[$i][3]'].style.display = 'none';\n"; | |
98ca76db | 196 | $defaults["mapper[$i]"] = [ |
73edfc10 EM |
197 | $mappingName[$i], |
198 | $softField, | |
199 | // Since the soft credit type id is not stored we can't load it here. | |
200 | '', | |
98ca76db | 201 | ]; |
6a488035 TO |
202 | $jsSet = TRUE; |
203 | } | |
204 | else { | |
98ca76db | 205 | $defaults["mapper[$i]"] = []; |
6a488035 TO |
206 | } |
207 | if (!$jsSet) { | |
208 | for ($k = 1; $k < 4; $k++) { | |
209 | $js .= "{$formName}['mapper[$i][$k]'].style.display = 'none';\n"; | |
210 | } | |
211 | } | |
212 | } | |
213 | else { | |
214 | // this load section to help mapping if we ran out of saved columns when doing Load Mapping | |
377fa510 | 215 | $js .= "swapOptions($formName, 'mapper[$i]', 0, 3, 'hs_mapper_0_');\n"; |
6a488035 TO |
216 | |
217 | if ($hasHeaders) { | |
8daa4f36 | 218 | $defaults["mapper[$i]"] = [$this->defaultFromHeader($columnHeader, $headerPatterns)]; |
6a488035 TO |
219 | } |
220 | else { | |
98ca76db | 221 | $defaults["mapper[$i]"] = [$this->defaultFromData($dataPatterns, $i)]; |
6a488035 TO |
222 | } |
223 | } | |
224 | //end of load mapping | |
225 | } | |
226 | else { | |
377fa510 | 227 | $js .= "swapOptions($formName, 'mapper[$i]', 0, 3, 'hs_mapper_0_');\n"; |
6a488035 | 228 | if ($hasHeaders) { |
377fa510 | 229 | // do array search first to see if has mapped key |
8daa4f36 | 230 | $columnKey = array_search($columnHeader, $this->_mapperFields); |
377fa510 | 231 | if (isset($this->_fieldUsed[$columnKey])) { |
232 | $defaults["mapper[$i]"] = $columnKey; | |
233 | $this->_fieldUsed[$key] = TRUE; | |
234 | } | |
235 | else { | |
236 | // Infer the default from the column names if we have them | |
98ca76db | 237 | $defaults["mapper[$i]"] = [ |
8daa4f36 | 238 | $this->defaultFromHeader($columnHeader, $headerPatterns), |
377fa510 | 239 | 0, |
98ca76db | 240 | ]; |
377fa510 | 241 | } |
6a488035 TO |
242 | } |
243 | else { | |
244 | // Otherwise guess the default from the form of the data | |
98ca76db | 245 | $defaults["mapper[$i]"] = [ |
6a488035 | 246 | $this->defaultFromData($dataPatterns, $i), |
6a488035 | 247 | 0, |
98ca76db | 248 | ]; |
6a488035 | 249 | } |
4b58c5c4 EM |
250 | if (!empty($mapperKeysValues) && ($mapperKeysValues[$i][0] ?? NULL) === 'soft_credit') { |
251 | $softCreditField = $mapperKeysValues[$i][1]; | |
252 | $softCreditTypeID = $mapperKeysValues[$i][2]; | |
253 | $js .= "cj('#mapper_" . $i . "_1').val($softCreditField);\n"; | |
254 | $js .= "cj('#mapper_" . $i . "_2').val($softCreditTypeID);\n"; | |
377fa510 | 255 | } |
6a488035 | 256 | } |
98ca76db | 257 | $sel->setOptions([$sel1, $sel2, $sel3, $sel4]); |
6a488035 TO |
258 | } |
259 | $js .= "</script>\n"; | |
260 | $this->assign('initHideBoxes', $js); | |
261 | ||
262 | //set warning if mismatch in more than | |
263 | if (isset($mappingName)) { | |
264 | if (($this->_columnCount != count($mappingName))) { | |
265 | $warning++; | |
266 | } | |
267 | } | |
268 | if ($warning != 0 && $this->get('savedMapping')) { | |
269 | $session = CRM_Core_Session::singleton(); | |
270 | $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.')); | |
271 | } | |
272 | else { | |
273 | $session = CRM_Core_Session::singleton(); | |
274 | $session->setStatus(NULL); | |
275 | } | |
276 | ||
277 | $this->setDefaults($defaults); | |
278 | ||
9b324cef | 279 | $this->addFormButtons(); |
6a488035 TO |
280 | } |
281 | ||
282 | /** | |
fe482240 | 283 | * Global validation rules for the form. |
6a488035 | 284 | * |
014c4014 TO |
285 | * @param array $fields |
286 | * Posted values of the form. | |
6a488035 | 287 | * |
2a6da8d7 | 288 | * @param $files |
e8cf95b4 | 289 | * @param self $self |
2a6da8d7 | 290 | * |
a6c01b45 CW |
291 | * @return array |
292 | * list of errors to be posted back to the form | |
6a488035 | 293 | */ |
00be9182 | 294 | public static function formRule($fields, $files, $self) { |
98ca76db | 295 | $errors = []; |
6a488035 | 296 | $fieldMessage = NULL; |
1221efe9 | 297 | $contactORContributionId = $self->_onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE ? 'contribution_id' : 'contribution_contact_id'; |
6a488035 | 298 | if (!array_key_exists('savedMapping', $fields)) { |
98ca76db | 299 | $importKeys = []; |
6a488035 TO |
300 | foreach ($fields['mapper'] as $mapperPart) { |
301 | $importKeys[] = $mapperPart[0]; | |
302 | } | |
303 | ||
98ca76db | 304 | $params = [ |
91bb24a7 | 305 | 'used' => 'Unsupervised', |
6e7ba2ee | 306 | 'contact_type' => $self->getContactType(), |
98ca76db | 307 | ]; |
77a9ae24 | 308 | [$ruleFields, $threshold] = CRM_Dedupe_BAO_DedupeRuleGroup::dedupeRuleFieldsWeight($params); |
6a488035 TO |
309 | $weightSum = 0; |
310 | foreach ($importKeys as $key => $val) { | |
311 | if (array_key_exists($val, $ruleFields)) { | |
312 | $weightSum += $ruleFields[$val]; | |
313 | } | |
1221efe9 | 314 | if ($val == "soft_credit") { |
315 | $mapperKey = CRM_Utils_Array::key('soft_credit', $importKeys); | |
377fa510 | 316 | if (empty($fields['mapper'][$mapperKey][1])) { |
1221efe9 | 317 | if (empty($errors['_qf_default'])) { |
318 | $errors['_qf_default'] = ''; | |
319 | } | |
320 | $errors['_qf_default'] .= ts('Missing required fields: Soft Credit') . '<br />'; | |
321 | } | |
322 | } | |
6a488035 TO |
323 | } |
324 | foreach ($ruleFields as $field => $weight) { | |
325 | $fieldMessage .= ' ' . $field . '(weight ' . $weight . ')'; | |
326 | } | |
be3c7034 | 327 | $errors = self::checkRequiredFields($self, $contactORContributionId, $importKeys, $errors, $weightSum, $threshold, $fieldMessage); |
6a488035 TO |
328 | |
329 | //at least one field should be mapped during update. | |
a05662ef | 330 | if ($self->_onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE) { |
6a488035 TO |
331 | $atleastOne = FALSE; |
332 | foreach ($self->_mapperFields as $key => $field) { | |
333 | if (in_array($key, $importKeys) && | |
98ca76db | 334 | !in_array($key, [ |
335 | 'doNotImport', | |
336 | 'contribution_id', | |
337 | 'invoice_id', | |
338 | 'trxn_id', | |
339 | ]) | |
6a488035 TO |
340 | ) { |
341 | $atleastOne = TRUE; | |
342 | break; | |
343 | } | |
344 | } | |
345 | if (!$atleastOne) { | |
346 | $errors['_qf_default'] .= ts('At least one contribution field needs to be mapped for update during update mode.') . '<br />'; | |
347 | } | |
348 | } | |
349 | } | |
350 | ||
a7488080 | 351 | if (!empty($fields['saveMapping'])) { |
9c1bc317 | 352 | $nameField = $fields['saveMappingName'] ?? NULL; |
6a488035 TO |
353 | if (empty($nameField)) { |
354 | $errors['saveMappingName'] = ts('Name is required to save Import Mapping'); | |
355 | } | |
356 | else { | |
95f52e3b | 357 | if (CRM_Core_BAO_Mapping::checkMapping($nameField, CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Mapping', 'mapping_type_id', 'Import Contribution'))) { |
6a488035 TO |
358 | $errors['saveMappingName'] = ts('Duplicate Import Contribution Mapping Name'); |
359 | } | |
360 | } | |
361 | } | |
362 | ||
363 | if (!empty($errors)) { | |
364 | if (!empty($errors['saveMappingName'])) { | |
365 | $_flag = 1; | |
366 | $assignError = new CRM_Core_Page(); | |
367 | $assignError->assign('mappingDetailsError', $_flag); | |
368 | } | |
377fa510 | 369 | if (!empty($errors['_qf_default'])) { |
370 | CRM_Core_Session::setStatus($errors['_qf_default'], ts("Error"), "error"); | |
371 | return $errors; | |
372 | } | |
6a488035 TO |
373 | } |
374 | ||
375 | return TRUE; | |
376 | } | |
377 | ||
378 | /** | |
b7dde70c EM |
379 | * Get the mapping name per the civicrm_mapping_field.type_id option group. |
380 | * | |
381 | * @return string | |
6a488035 | 382 | */ |
b7dde70c EM |
383 | public function getMappingTypeName(): string { |
384 | return 'Import Contribution'; | |
6a488035 | 385 | } |
96025800 | 386 | |
73edfc10 EM |
387 | /** |
388 | * @return \CRM_Contribute_Import_Parser_Contribution | |
389 | */ | |
390 | protected function getParser(): CRM_Contribute_Import_Parser_Contribution { | |
83a1c234 EM |
391 | if (!$this->parser) { |
392 | $this->parser = new CRM_Contribute_Import_Parser_Contribution(); | |
393 | $this->parser->setUserJobID($this->getUserJobID()); | |
394 | $this->parser->init(); | |
395 | } | |
396 | return $this->parser; | |
73edfc10 EM |
397 | } |
398 | ||
6a488035 | 399 | } |