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 | ||
01c21f7e EM |
18 | use Civi\Api4\Contact; |
19 | use Civi\Api4\Email; | |
20 | ||
6a488035 | 21 | /** |
74ab7ba8 | 22 | * Class to parse contribution csv files. |
6a488035 | 23 | */ |
8dc9763a | 24 | class CRM_Contribute_Import_Parser_Contribution extends CRM_Import_Parser { |
6a488035 TO |
25 | |
26 | protected $_mapperKeys; | |
27 | ||
6a488035 | 28 | /** |
ceb10dc7 | 29 | * Array of successfully imported contribution id's |
6a488035 | 30 | * |
1330f57a | 31 | * @var array |
6a488035 TO |
32 | */ |
33 | protected $_newContributions; | |
34 | ||
35 | /** | |
74ab7ba8 EM |
36 | * Class constructor. |
37 | * | |
38 | * @param $mapperKeys | |
6a488035 | 39 | */ |
01c21f7e | 40 | public function __construct($mapperKeys = []) { |
6a488035 | 41 | parent::__construct(); |
01c21f7e | 42 | $this->_mapperKeys = $mapperKeys; |
6a488035 TO |
43 | } |
44 | ||
8dc9763a EM |
45 | /** |
46 | * Contribution-specific result codes | |
47 | * @see CRM_Import_Parser result code constants | |
48 | */ | |
49 | const SOFT_CREDIT = 512, SOFT_CREDIT_ERROR = 1024, PLEDGE_PAYMENT = 2048, PLEDGE_PAYMENT_ERROR = 4096; | |
50 | ||
8dc9763a EM |
51 | /** |
52 | * Separator being used | |
53 | * @var string | |
54 | */ | |
55 | protected $_separator; | |
56 | ||
57 | /** | |
58 | * Total number of lines in file | |
59 | * @var int | |
60 | */ | |
61 | protected $_lineCount; | |
62 | ||
63 | /** | |
64 | * Running total number of valid soft credit rows | |
65 | * @var int | |
66 | */ | |
67 | protected $_validSoftCreditRowCount; | |
68 | ||
69 | /** | |
70 | * Running total number of invalid soft credit rows | |
71 | * @var int | |
72 | */ | |
73 | protected $_invalidSoftCreditRowCount; | |
74 | ||
75 | /** | |
76 | * Running total number of valid pledge payment rows | |
77 | * @var int | |
78 | */ | |
79 | protected $_validPledgePaymentRowCount; | |
80 | ||
81 | /** | |
82 | * Running total number of invalid pledge payment rows | |
83 | * @var int | |
84 | */ | |
85 | protected $_invalidPledgePaymentRowCount; | |
86 | ||
87 | /** | |
88 | * Array of pledge payment error lines, bounded by MAX_ERROR | |
89 | * @var array | |
90 | */ | |
91 | protected $_pledgePaymentErrors; | |
92 | ||
93 | /** | |
94 | * Array of pledge payment error lines, bounded by MAX_ERROR | |
95 | * @var array | |
96 | */ | |
97 | protected $_softCreditErrors; | |
98 | ||
99 | /** | |
100 | * Filename of pledge payment error data | |
101 | * | |
102 | * @var string | |
103 | */ | |
104 | protected $_pledgePaymentErrorsFileName; | |
105 | ||
106 | /** | |
107 | * Filename of soft credit error data | |
108 | * | |
109 | * @var string | |
110 | */ | |
111 | protected $_softCreditErrorsFileName; | |
112 | ||
113 | /** | |
114 | * Whether the file has a column header or not | |
115 | * | |
116 | * @var bool | |
117 | */ | |
118 | protected $_haveColumnHeader; | |
119 | ||
120 | /** | |
121 | * @param string $fileName | |
122 | * @param string $separator | |
123 | * @param $mapper | |
124 | * @param bool $skipColumnHeader | |
125 | * @param int $mode | |
126 | * @param int $contactType | |
127 | * @param int $onDuplicate | |
128 | * @param int $statusID | |
8dc9763a EM |
129 | * |
130 | * @return mixed | |
131 | * @throws Exception | |
132 | */ | |
133 | public function run( | |
134 | $fileName, | |
135 | $separator, | |
6d283ebd | 136 | $mapper, |
8dc9763a EM |
137 | $skipColumnHeader = FALSE, |
138 | $mode = self::MODE_PREVIEW, | |
139 | $contactType = self::CONTACT_INDIVIDUAL, | |
140 | $onDuplicate = self::DUPLICATE_SKIP, | |
8daa4f36 | 141 | $statusID = NULL |
8dc9763a | 142 | ) { |
01c21f7e EM |
143 | // Since $this->_contactType is still being called directly do a get call |
144 | // here to make sure it is instantiated. | |
145 | $this->getContactType(); | |
8dc9763a EM |
146 | |
147 | $this->init(); | |
148 | ||
149 | $this->_haveColumnHeader = $skipColumnHeader; | |
150 | ||
06ef1cdc | 151 | $this->_lineCount = $this->_validSoftCreditRowCount = $this->_validPledgePaymentRowCount = 0; |
8dc9763a | 152 | $this->_invalidRowCount = $this->_validCount = $this->_invalidSoftCreditRowCount = $this->_invalidPledgePaymentRowCount = 0; |
da8d3d49 | 153 | $this->_totalCount = 0; |
8dc9763a EM |
154 | |
155 | $this->_errors = []; | |
156 | $this->_warnings = []; | |
8dc9763a EM |
157 | $this->_pledgePaymentErrors = []; |
158 | $this->_softCreditErrors = []; | |
159 | if ($statusID) { | |
160 | $this->progressImport($statusID); | |
161 | $startTimestamp = $currTimestamp = $prevTimestamp = time(); | |
162 | } | |
163 | ||
8dc9763a EM |
164 | if ($mode == self::MODE_MAPFIELD) { |
165 | $this->_rows = []; | |
166 | } | |
167 | else { | |
168 | $this->_activeFieldCount = count($this->_activeFields); | |
169 | } | |
170 | ||
8daa4f36 EM |
171 | $dataSource = $this->getDataSourceObject(); |
172 | $totalRowCount = $dataSource->getRowCount(['new']); | |
173 | $dataSource->setStatuses(['new']); | |
8dc9763a | 174 | |
8daa4f36 EM |
175 | while ($row = $dataSource->getRow()) { |
176 | $values = array_values($row); | |
177 | $this->_lineCount++; | |
8dc9763a EM |
178 | |
179 | $this->_totalCount++; | |
180 | ||
181 | if ($mode == self::MODE_MAPFIELD) { | |
4ad623fc | 182 | $returnCode = CRM_Import_Parser::VALID; |
8dc9763a | 183 | } |
5f97768e EM |
184 | // Note that import summary appears to be unused |
185 | elseif ($mode == self::MODE_PREVIEW || $mode == self::MODE_SUMMARY) { | |
8dc9763a EM |
186 | $returnCode = $this->summary($values); |
187 | } | |
188 | elseif ($mode == self::MODE_IMPORT) { | |
189 | $returnCode = $this->import($onDuplicate, $values); | |
190 | if ($statusID && (($this->_lineCount % 50) == 0)) { | |
191 | $prevTimestamp = $this->progressImport($statusID, FALSE, $startTimestamp, $prevTimestamp, $totalRowCount); | |
192 | } | |
193 | } | |
194 | else { | |
195 | $returnCode = self::ERROR; | |
196 | } | |
197 | ||
198 | // note that a line could be valid but still produce a warning | |
199 | if ($returnCode == self::VALID) { | |
200 | $this->_validCount++; | |
201 | if ($mode == self::MODE_MAPFIELD) { | |
202 | $this->_rows[] = $values; | |
203 | $this->_activeFieldCount = max($this->_activeFieldCount, count($values)); | |
204 | } | |
205 | } | |
206 | ||
207 | if ($returnCode == self::SOFT_CREDIT) { | |
208 | $this->_validSoftCreditRowCount++; | |
209 | $this->_validCount++; | |
210 | if ($mode == self::MODE_MAPFIELD) { | |
211 | $this->_rows[] = $values; | |
212 | $this->_activeFieldCount = max($this->_activeFieldCount, count($values)); | |
213 | } | |
214 | } | |
215 | ||
216 | if ($returnCode == self::PLEDGE_PAYMENT) { | |
217 | $this->_validPledgePaymentRowCount++; | |
218 | $this->_validCount++; | |
219 | if ($mode == self::MODE_MAPFIELD) { | |
220 | $this->_rows[] = $values; | |
221 | $this->_activeFieldCount = max($this->_activeFieldCount, count($values)); | |
222 | } | |
223 | } | |
224 | ||
8dc9763a EM |
225 | if ($returnCode == self::ERROR) { |
226 | $this->_invalidRowCount++; | |
227 | $recordNumber = $this->_lineCount; | |
228 | if ($this->_haveColumnHeader) { | |
229 | $recordNumber--; | |
230 | } | |
231 | array_unshift($values, $recordNumber); | |
232 | $this->_errors[] = $values; | |
233 | } | |
234 | ||
235 | if ($returnCode == self::PLEDGE_PAYMENT_ERROR) { | |
236 | $this->_invalidPledgePaymentRowCount++; | |
237 | $recordNumber = $this->_lineCount; | |
238 | if ($this->_haveColumnHeader) { | |
239 | $recordNumber--; | |
240 | } | |
241 | array_unshift($values, $recordNumber); | |
242 | $this->_pledgePaymentErrors[] = $values; | |
243 | } | |
244 | ||
245 | if ($returnCode == self::SOFT_CREDIT_ERROR) { | |
246 | $this->_invalidSoftCreditRowCount++; | |
247 | $recordNumber = $this->_lineCount; | |
248 | if ($this->_haveColumnHeader) { | |
249 | $recordNumber--; | |
250 | } | |
251 | array_unshift($values, $recordNumber); | |
252 | $this->_softCreditErrors[] = $values; | |
253 | } | |
254 | ||
8dc9763a | 255 | if ($returnCode == self::DUPLICATE) { |
8dc9763a EM |
256 | $this->_duplicateCount++; |
257 | $recordNumber = $this->_lineCount; | |
258 | if ($this->_haveColumnHeader) { | |
259 | $recordNumber--; | |
260 | } | |
261 | array_unshift($values, $recordNumber); | |
262 | $this->_duplicates[] = $values; | |
263 | if ($onDuplicate != self::DUPLICATE_SKIP) { | |
264 | $this->_validCount++; | |
265 | } | |
266 | } | |
8dc9763a EM |
267 | } |
268 | ||
8dc9763a EM |
269 | if ($mode == self::MODE_PREVIEW || $mode == self::MODE_IMPORT) { |
270 | $customHeaders = $mapper; | |
271 | ||
272 | $customfields = CRM_Core_BAO_CustomField::getFields('Contribution'); | |
273 | foreach ($customHeaders as $key => $value) { | |
274 | if ($id = CRM_Core_BAO_CustomField::getKeyID($value)) { | |
275 | $customHeaders[$key] = $customfields[$id][0]; | |
276 | } | |
277 | } | |
278 | if ($this->_invalidRowCount) { | |
279 | // removed view url for invlaid contacts | |
280 | $headers = array_merge([ | |
281 | ts('Line Number'), | |
282 | ts('Reason'), | |
283 | ], $customHeaders); | |
284 | $this->_errorFileName = self::errorFileName(self::ERROR); | |
285 | self::exportCSV($this->_errorFileName, $headers, $this->_errors); | |
286 | } | |
287 | ||
288 | if ($this->_invalidPledgePaymentRowCount) { | |
289 | // removed view url for invlaid contacts | |
290 | $headers = array_merge([ | |
291 | ts('Line Number'), | |
292 | ts('Reason'), | |
293 | ], $customHeaders); | |
294 | $this->_pledgePaymentErrorsFileName = self::errorFileName(self::PLEDGE_PAYMENT_ERROR); | |
295 | self::exportCSV($this->_pledgePaymentErrorsFileName, $headers, $this->_pledgePaymentErrors); | |
296 | } | |
297 | ||
298 | if ($this->_invalidSoftCreditRowCount) { | |
299 | // removed view url for invlaid contacts | |
300 | $headers = array_merge([ | |
301 | ts('Line Number'), | |
302 | ts('Reason'), | |
303 | ], $customHeaders); | |
304 | $this->_softCreditErrorsFileName = self::errorFileName(self::SOFT_CREDIT_ERROR); | |
305 | self::exportCSV($this->_softCreditErrorsFileName, $headers, $this->_softCreditErrors); | |
306 | } | |
307 | ||
8dc9763a EM |
308 | if ($this->_duplicateCount) { |
309 | $headers = array_merge([ | |
310 | ts('Line Number'), | |
311 | ts('View Contribution URL'), | |
312 | ], $customHeaders); | |
313 | ||
314 | $this->_duplicateFileName = self::errorFileName(self::DUPLICATE); | |
315 | self::exportCSV($this->_duplicateFileName, $headers, $this->_duplicates); | |
316 | } | |
317 | } | |
8dc9763a EM |
318 | } |
319 | ||
320 | /** | |
321 | * Given a list of the importable field keys that the user has selected | |
322 | * set the active fields array to this list | |
323 | * | |
324 | * @param array $fieldKeys mapped array of values | |
325 | */ | |
326 | public function setActiveFields($fieldKeys) { | |
327 | $this->_activeFieldCount = count($fieldKeys); | |
328 | foreach ($fieldKeys as $key) { | |
329 | if (empty($this->_fields[$key])) { | |
330 | $this->_activeFields[] = new CRM_Contribute_Import_Field('', ts('- do not import -')); | |
331 | } | |
332 | else { | |
333 | $this->_activeFields[] = clone($this->_fields[$key]); | |
334 | } | |
335 | } | |
336 | } | |
337 | ||
338 | /** | |
01c21f7e | 339 | * Get the field mappings for the import. |
8dc9763a | 340 | * |
01c21f7e EM |
341 | * This is the same format as saved in civicrm_mapping_field except |
342 | * that location_type_id = 'Primary' rather than empty where relevant. | |
343 | * Also 'im_provider_id' is mapped to the 'real' field name 'provider_id' | |
8dc9763a EM |
344 | * |
345 | * @return array | |
01c21f7e | 346 | * @throws \API_Exception |
8dc9763a | 347 | */ |
01c21f7e EM |
348 | protected function getFieldMappings(): array { |
349 | $mappedFields = []; | |
350 | foreach ($this->getSubmittedValue('mapper') as $i => $mapperRow) { | |
351 | $mappedField = $this->getMappingFieldFromMapperInput($mapperRow, 0, $i); | |
352 | // Just for clarity since 0 is a pseudo-value | |
353 | unset($mappedField['mapping_id']); | |
354 | $mappedFields[] = $mappedField; | |
8dc9763a | 355 | } |
01c21f7e | 356 | return $mappedFields; |
8dc9763a EM |
357 | } |
358 | ||
288db2d2 EM |
359 | /** |
360 | * Transform the input parameters into the form handled by the input routine. | |
361 | * | |
362 | * @param array $values | |
363 | * Input parameters as they come in from the datasource | |
364 | * eg. ['Bob', 'Smith', 'bob@example.org', '123-456'] | |
365 | * | |
366 | * @return array | |
367 | * Parameters mapped to CiviCRM fields based on the mapping. eg. | |
368 | * [ | |
369 | * 'total_amount' => '1230.99', | |
370 | * 'financial_type_id' => 1, | |
371 | * 'external_identifier' => 'abcd', | |
372 | * 'soft_credit' => [3 => ['external_identifier' => '123', 'soft_credit_type_id' => 1]] | |
373 | * | |
374 | * @throws \API_Exception | |
375 | */ | |
376 | public function getMappedRow(array $values): array { | |
377 | $params = []; | |
378 | foreach ($this->getFieldMappings() as $i => $mappedField) { | |
379 | if (!empty($mappedField['soft_credit_match_field'])) { | |
380 | $params['soft_credit'][$i] = ['soft_credit_type_id' => $mappedField['soft_credit_type_id'], $mappedField['soft_credit_match_field'] => $values[$i]]; | |
381 | } | |
382 | else { | |
1c82489b | 383 | $params[$this->getFieldMetadata($mappedField['name'])['name']] = $this->getTransformedFieldValue($mappedField['name'], $values[$i]); |
288db2d2 EM |
384 | } |
385 | } | |
386 | return $params; | |
387 | } | |
388 | ||
8dc9763a EM |
389 | /** |
390 | * @param string $name | |
391 | * @param $title | |
392 | * @param int $type | |
393 | * @param string $headerPattern | |
394 | * @param string $dataPattern | |
395 | */ | |
396 | public function addField($name, $title, $type = CRM_Utils_Type::T_INT, $headerPattern = '//', $dataPattern = '//') { | |
397 | if (empty($name)) { | |
398 | $this->_fields['doNotImport'] = new CRM_Contribute_Import_Field($name, $title, $type, $headerPattern, $dataPattern); | |
399 | } | |
400 | else { | |
401 | $tempField = CRM_Contact_BAO_Contact::importableFields('All', NULL); | |
402 | if (!array_key_exists($name, $tempField)) { | |
403 | $this->_fields[$name] = new CRM_Contribute_Import_Field($name, $title, $type, $headerPattern, $dataPattern); | |
404 | } | |
405 | else { | |
406 | $this->_fields[$name] = new CRM_Contact_Import_Field($name, $title, $type, $headerPattern, $dataPattern, | |
407 | CRM_Utils_Array::value('hasLocationType', $tempField[$name]) | |
408 | ); | |
409 | } | |
410 | } | |
411 | } | |
412 | ||
413 | /** | |
414 | * Store parser values. | |
415 | * | |
416 | * @param CRM_Core_Session $store | |
417 | * | |
418 | * @param int $mode | |
419 | */ | |
420 | public function set($store, $mode = self::MODE_SUMMARY) { | |
8dc9763a EM |
421 | $store->set('totalRowCount', $this->_totalCount); |
422 | $store->set('validRowCount', $this->_validCount); | |
423 | $store->set('invalidRowCount', $this->_invalidRowCount); | |
8daa4f36 | 424 | |
8dc9763a EM |
425 | $store->set('invalidSoftCreditRowCount', $this->_invalidSoftCreditRowCount); |
426 | $store->set('validSoftCreditRowCount', $this->_validSoftCreditRowCount); | |
427 | $store->set('invalidPledgePaymentRowCount', $this->_invalidPledgePaymentRowCount); | |
428 | $store->set('validPledgePaymentRowCount', $this->_validPledgePaymentRowCount); | |
8dc9763a EM |
429 | |
430 | switch ($this->_contactType) { | |
431 | case 'Individual': | |
432 | $store->set('contactType', CRM_Import_Parser::CONTACT_INDIVIDUAL); | |
433 | break; | |
434 | ||
435 | case 'Household': | |
436 | $store->set('contactType', CRM_Import_Parser::CONTACT_HOUSEHOLD); | |
437 | break; | |
438 | ||
439 | case 'Organization': | |
440 | $store->set('contactType', CRM_Import_Parser::CONTACT_ORGANIZATION); | |
441 | } | |
442 | ||
443 | if ($this->_invalidRowCount) { | |
444 | $store->set('errorsFileName', $this->_errorFileName); | |
445 | } | |
8dc9763a EM |
446 | if (isset($this->_rows) && !empty($this->_rows)) { |
447 | $store->set('dataValues', $this->_rows); | |
448 | } | |
449 | ||
450 | if ($this->_invalidPledgePaymentRowCount) { | |
451 | $store->set('pledgePaymentErrorsFileName', $this->_pledgePaymentErrorsFileName); | |
452 | } | |
453 | ||
454 | if ($this->_invalidSoftCreditRowCount) { | |
455 | $store->set('softCreditErrorsFileName', $this->_softCreditErrorsFileName); | |
456 | } | |
457 | ||
458 | if ($mode == self::MODE_IMPORT) { | |
459 | $store->set('duplicateRowCount', $this->_duplicateCount); | |
460 | if ($this->_duplicateCount) { | |
461 | $store->set('duplicatesFileName', $this->_duplicateFileName); | |
462 | } | |
463 | } | |
464 | } | |
465 | ||
466 | /** | |
467 | * Export data to a CSV file. | |
468 | * | |
469 | * @param string $fileName | |
470 | * @param array $header | |
471 | * @param array $data | |
472 | */ | |
473 | public static function exportCSV($fileName, $header, $data) { | |
474 | $output = []; | |
475 | $fd = fopen($fileName, 'w'); | |
476 | ||
477 | foreach ($header as $key => $value) { | |
478 | $header[$key] = "\"$value\""; | |
479 | } | |
480 | $config = CRM_Core_Config::singleton(); | |
481 | $output[] = implode($config->fieldSeparator, $header); | |
482 | ||
483 | foreach ($data as $datum) { | |
484 | foreach ($datum as $key => $value) { | |
485 | if (isset($value[0]) && is_array($value)) { | |
486 | foreach ($value[0] as $k1 => $v1) { | |
487 | if ($k1 == 'location_type_id') { | |
488 | continue; | |
489 | } | |
490 | $datum[$k1] = $v1; | |
491 | } | |
492 | } | |
493 | else { | |
494 | $datum[$key] = "\"$value\""; | |
495 | } | |
496 | } | |
497 | $output[] = implode($config->fieldSeparator, $datum); | |
498 | } | |
499 | fwrite($fd, implode("\n", $output)); | |
500 | fclose($fd); | |
501 | } | |
502 | ||
503 | /** | |
504 | * Determines the file extension based on error code. | |
505 | * | |
506 | * @param int $type | |
507 | * Error code constant. | |
508 | * | |
509 | * @return string | |
510 | */ | |
511 | public static function errorFileName($type) { | |
512 | $fileName = NULL; | |
513 | if (empty($type)) { | |
514 | return $fileName; | |
515 | } | |
516 | ||
517 | $config = CRM_Core_Config::singleton(); | |
518 | $fileName = $config->uploadDir . "sqlImport"; | |
519 | ||
520 | switch ($type) { | |
521 | case self::SOFT_CREDIT_ERROR: | |
522 | $fileName .= '.softCreditErrors'; | |
523 | break; | |
524 | ||
525 | case self::PLEDGE_PAYMENT_ERROR: | |
526 | $fileName .= '.pledgePaymentErrors'; | |
527 | break; | |
528 | ||
529 | default: | |
530 | $fileName = parent::errorFileName($type); | |
531 | break; | |
532 | } | |
533 | ||
534 | return $fileName; | |
535 | } | |
536 | ||
537 | /** | |
538 | * Determines the file name based on error code. | |
539 | * | |
540 | * @param int $type | |
541 | * Error code constant. | |
542 | * | |
543 | * @return string | |
544 | */ | |
545 | public static function saveFileName($type) { | |
546 | $fileName = NULL; | |
547 | if (empty($type)) { | |
548 | return $fileName; | |
549 | } | |
550 | ||
551 | switch ($type) { | |
552 | case self::SOFT_CREDIT_ERROR: | |
553 | $fileName = 'Import_Soft_Credit_Errors.csv'; | |
554 | break; | |
555 | ||
556 | case self::PLEDGE_PAYMENT_ERROR: | |
557 | $fileName = 'Import_Pledge_Payment_Errors.csv'; | |
558 | break; | |
559 | ||
560 | default: | |
561 | $fileName = parent::saveFileName($type); | |
562 | break; | |
563 | } | |
564 | ||
565 | return $fileName; | |
566 | } | |
567 | ||
6a488035 | 568 | /** |
100fef9d | 569 | * The initializer code, called before the processing |
6a488035 | 570 | */ |
00be9182 | 571 | public function init() { |
73edfc10 EM |
572 | $this->setFieldMetadata(); |
573 | foreach ($this->getImportableFieldsMetadata() as $name => $field) { | |
6a488035 TO |
574 | $this->addField($name, $field['title'], $field['type'], $field['headerPattern'], $field['dataPattern']); |
575 | } | |
576 | ||
be2fb01f | 577 | $this->_newContributions = []; |
6a488035 TO |
578 | |
579 | $this->setActiveFields($this->_mapperKeys); | |
6a488035 TO |
580 | } |
581 | ||
73edfc10 EM |
582 | /** |
583 | * Set field metadata. | |
584 | */ | |
585 | protected function setFieldMetadata() { | |
586 | if (empty($this->importableFieldsMetadata)) { | |
587 | $fields = CRM_Contribute_BAO_Contribution::importableFields($this->_contactType, FALSE); | |
588 | ||
589 | $fields = array_merge($fields, | |
590 | [ | |
591 | 'soft_credit' => [ | |
592 | 'title' => ts('Soft Credit'), | |
593 | 'softCredit' => TRUE, | |
594 | 'headerPattern' => '/Soft Credit/i', | |
595 | ], | |
596 | ] | |
597 | ); | |
598 | ||
599 | // add pledge fields only if its is enabled | |
600 | if (CRM_Core_Permission::access('CiviPledge')) { | |
601 | $pledgeFields = [ | |
602 | 'pledge_payment' => [ | |
603 | 'title' => ts('Pledge Payment'), | |
604 | 'headerPattern' => '/Pledge Payment/i', | |
605 | ], | |
606 | 'pledge_id' => [ | |
607 | 'title' => ts('Pledge ID'), | |
608 | 'headerPattern' => '/Pledge ID/i', | |
609 | ], | |
610 | ]; | |
611 | ||
612 | $fields = array_merge($fields, $pledgeFields); | |
613 | } | |
614 | foreach ($fields as $name => $field) { | |
615 | $fields[$name] = array_merge([ | |
616 | 'type' => CRM_Utils_Type::T_INT, | |
617 | 'dataPattern' => '//', | |
618 | 'headerPattern' => '//', | |
619 | ], $field); | |
620 | } | |
621 | $this->importableFieldsMetadata = $fields; | |
622 | } | |
623 | } | |
624 | ||
6a488035 | 625 | /** |
fe482240 | 626 | * Handle the values in summary mode. |
6a488035 | 627 | * |
014c4014 TO |
628 | * @param array $values |
629 | * The array of values belonging to this line. | |
6a488035 | 630 | * |
5f97768e EM |
631 | * @return int |
632 | * CRM_Import_Parser::VALID or CRM_Import_Parser::ERROR | |
6a488035 | 633 | */ |
00be9182 | 634 | public function summary(&$values) { |
8daa4f36 | 635 | $rowNumber = (int) ($values[array_key_last($values)]); |
01c21f7e | 636 | $params = $this->getMappedRow($values); |
1c82489b | 637 | $errorMessage = implode(';', $this->getInvalidValues($params)); |
6a488035 TO |
638 | $params['contact_type'] = 'Contribution'; |
639 | ||
6a488035 TO |
640 | if ($errorMessage) { |
641 | $tempMsg = "Invalid value for field(s) : $errorMessage"; | |
642 | array_unshift($values, $tempMsg); | |
643 | $errorMessage = NULL; | |
8daa4f36 | 644 | $this->setImportStatus($rowNumber, 'ERROR', $tempMsg); |
a05662ef | 645 | return CRM_Import_Parser::ERROR; |
6a488035 TO |
646 | } |
647 | ||
a05662ef | 648 | return CRM_Import_Parser::VALID; |
6a488035 TO |
649 | } |
650 | ||
651 | /** | |
fe482240 | 652 | * Handle the values in import mode. |
6a488035 | 653 | * |
014c4014 TO |
654 | * @param int $onDuplicate |
655 | * The code for what action to take on duplicates. | |
656 | * @param array $values | |
657 | * The array of values belonging to this line. | |
6a488035 | 658 | * |
5f97768e EM |
659 | * @return int |
660 | * the result of this processing - one of | |
661 | * - CRM_Import_Parser::VALID | |
662 | * - CRM_Import_Parser::ERROR | |
663 | * - CRM_Import_Parser::SOFT_CREDIT_ERROR | |
664 | * - CRM_Import_Parser::PLEDGE_PAYMENT_ERROR | |
665 | * - CRM_Import_Parser::DUPLICATE | |
666 | * - CRM_Import_Parser::SOFT_CREDIT (successful creation) | |
667 | * - CRM_Import_Parser::PLEDGE_PAYMENT (successful creation) | |
6a488035 | 668 | */ |
00be9182 | 669 | public function import($onDuplicate, &$values) { |
8daa4f36 | 670 | $rowNumber = (int) ($values[array_key_last($values)]); |
6a488035 TO |
671 | // first make sure this is a valid line |
672 | $response = $this->summary($values); | |
a05662ef | 673 | if ($response != CRM_Import_Parser::VALID) { |
5f97768e | 674 | return CRM_Import_Parser::ERROR; |
6a488035 TO |
675 | } |
676 | ||
01c21f7e | 677 | $params = $this->getMappedRow($values); |
1c82489b | 678 | $formatted = array_merge(['version' => 3, 'skipRecentView' => TRUE, 'skipCleanMoney' => FALSE, 'contribution_id' => $params['id'] ?? NULL], $params); |
c2e1f9ef | 679 | //CRM-10994 |
680 | if (isset($params['total_amount']) && $params['total_amount'] == 0) { | |
1ba834a8 | 681 | $params['total_amount'] = '0.00'; |
c2e1f9ef | 682 | } |
fed96c11 | 683 | $this->formatInput($params, $formatted); |
6a488035 | 684 | |
be2fb01f | 685 | $paramValues = []; |
6a488035 TO |
686 | foreach ($params as $key => $field) { |
687 | if ($field == NULL || $field === '') { | |
688 | continue; | |
689 | } | |
690 | $paramValues[$key] = $field; | |
691 | } | |
692 | ||
693 | //import contribution record according to select contact type | |
a05662ef | 694 | if ($onDuplicate == CRM_Import_Parser::DUPLICATE_SKIP && |
8cc574cf | 695 | (!empty($paramValues['contribution_contact_id']) || !empty($paramValues['external_identifier'])) |
6a488035 TO |
696 | ) { |
697 | $paramValues['contact_type'] = $this->_contactType; | |
698 | } | |
a05662ef | 699 | elseif ($onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE && |
1221efe9 | 700 | (!empty($paramValues['contribution_id']) || !empty($values['trxn_id']) || !empty($paramValues['invoice_id'])) |
6a488035 TO |
701 | ) { |
702 | $paramValues['contact_type'] = $this->_contactType; | |
703 | } | |
a7488080 | 704 | elseif (!empty($paramValues['pledge_payment'])) { |
6a488035 TO |
705 | $paramValues['contact_type'] = $this->_contactType; |
706 | } | |
707 | ||
708 | //need to pass $onDuplicate to check import mode. | |
a7488080 | 709 | if (!empty($paramValues['pledge_payment'])) { |
6a488035 TO |
710 | $paramValues['onDuplicate'] = $onDuplicate; |
711 | } | |
01c21f7e EM |
712 | try { |
713 | $formatError = $this->deprecatedFormatParams($paramValues, $formatted, TRUE, $onDuplicate); | |
714 | } | |
715 | catch (CRM_Core_Exception $e) { | |
716 | array_unshift($values, $e->getMessage()); | |
717 | $errorMapping = ['soft_credit' => self::SOFT_CREDIT_ERROR, 'pledge_payment' => self::PLEDGE_PAYMENT_ERROR]; | |
8daa4f36 | 718 | $this->setImportStatus($rowNumber, $errorMapping[$e->getErrorCode()] ?? CRM_Import_Parser::ERROR, $e->getMessage()); |
01c21f7e EM |
719 | return $errorMapping[$e->getErrorCode()] ?? CRM_Import_Parser::ERROR; |
720 | } | |
6a488035 TO |
721 | |
722 | if ($formatError) { | |
723 | array_unshift($values, $formatError['error_message']); | |
724 | if (CRM_Utils_Array::value('error_data', $formatError) == 'soft_credit') { | |
8dc9763a | 725 | return self::SOFT_CREDIT_ERROR; |
6a488035 | 726 | } |
69f296a4 | 727 | if (CRM_Utils_Array::value('error_data', $formatError) == 'pledge_payment') { |
8dc9763a | 728 | return self::PLEDGE_PAYMENT_ERROR; |
6a488035 | 729 | } |
8daa4f36 | 730 | $this->setImportStatus($rowNumber, 'ERROR', ''); |
a05662ef | 731 | return CRM_Import_Parser::ERROR; |
6a488035 TO |
732 | } |
733 | ||
f6fc1b15 | 734 | if ($onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE) { |
6a488035 | 735 | //fix for CRM-2219 - Update Contribution |
a05662ef | 736 | // onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE |
1221efe9 | 737 | if (!empty($paramValues['invoice_id']) || !empty($paramValues['trxn_id']) || !empty($paramValues['contribution_id'])) { |
be2fb01f | 738 | $dupeIds = [ |
6b409353 CW |
739 | 'id' => $paramValues['contribution_id'] ?? NULL, |
740 | 'trxn_id' => $paramValues['trxn_id'] ?? NULL, | |
741 | 'invoice_id' => $paramValues['invoice_id'] ?? NULL, | |
be2fb01f | 742 | ]; |
6a488035 TO |
743 | $ids['contribution'] = CRM_Contribute_BAO_Contribution::checkDuplicateIds($dupeIds); |
744 | ||
745 | if ($ids['contribution']) { | |
746 | $formatted['id'] = $ids['contribution']; | |
6a488035 | 747 | //process note |
a7488080 | 748 | if (!empty($paramValues['note'])) { |
be2fb01f | 749 | $noteID = []; |
6a488035 TO |
750 | $contactID = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $ids['contribution'], 'contact_id'); |
751 | $daoNote = new CRM_Core_BAO_Note(); | |
752 | $daoNote->entity_table = 'civicrm_contribution'; | |
753 | $daoNote->entity_id = $ids['contribution']; | |
754 | if ($daoNote->find(TRUE)) { | |
755 | $noteID['id'] = $daoNote->id; | |
756 | } | |
757 | ||
be2fb01f | 758 | $noteParams = [ |
6a488035 TO |
759 | 'entity_table' => 'civicrm_contribution', |
760 | 'note' => $paramValues['note'], | |
761 | 'entity_id' => $ids['contribution'], | |
762 | 'contact_id' => $contactID, | |
be2fb01f | 763 | ]; |
6a488035 TO |
764 | CRM_Core_BAO_Note::add($noteParams, $noteID); |
765 | unset($formatted['note']); | |
766 | } | |
767 | ||
768 | //need to check existing soft credit contribution, CRM-3968 | |
1221efe9 | 769 | if (!empty($formatted['soft_credit'])) { |
be2fb01f | 770 | $dupeSoftCredit = [ |
1221efe9 | 771 | 'contact_id' => $formatted['soft_credit'], |
6a488035 | 772 | 'contribution_id' => $ids['contribution'], |
be2fb01f | 773 | ]; |
8ef12e64 | 774 | |
1221efe9 | 775 | //Delete all existing soft Contribution from contribution_soft table for pcp_id is_null |
91bb24a7 | 776 | $existingSoftCredit = CRM_Contribute_BAO_ContributionSoft::getSoftContribution($dupeSoftCredit['contribution_id']); |
9b873358 TO |
777 | if (isset($existingSoftCredit['soft_credit']) && !empty($existingSoftCredit['soft_credit'])) { |
778 | foreach ($existingSoftCredit['soft_credit'] as $key => $existingSoftCreditValues) { | |
1221efe9 | 779 | if (!empty($existingSoftCreditValues['soft_credit_id'])) { |
be2fb01f | 780 | civicrm_api3('ContributionSoft', 'delete', [ |
1221efe9 | 781 | 'id' => $existingSoftCreditValues['soft_credit_id'], |
782 | 'pcp_id' => NULL, | |
be2fb01f | 783 | ]); |
1221efe9 | 784 | } |
785 | } | |
6a488035 TO |
786 | } |
787 | } | |
788 | ||
70d43afb | 789 | $formatted['id'] = $ids['contribution']; |
f6fc1b15 JM |
790 | |
791 | $newContribution = civicrm_api3('contribution', 'create', $formatted); | |
792 | $this->_newContributions[] = $newContribution['id']; | |
6a488035 TO |
793 | |
794 | //return soft valid since we need to show how soft credits were added | |
1221efe9 | 795 | if (!empty($formatted['soft_credit'])) { |
8dc9763a | 796 | return self::SOFT_CREDIT; |
6a488035 TO |
797 | } |
798 | ||
799 | // process pledge payment assoc w/ the contribution | |
672b72ea | 800 | return $this->processPledgePayments($formatted); |
6a488035 | 801 | } |
69f296a4 | 802 | $labels = [ |
803 | 'id' => 'Contribution ID', | |
804 | 'trxn_id' => 'Transaction ID', | |
805 | 'invoice_id' => 'Invoice ID', | |
806 | ]; | |
807 | foreach ($dupeIds as $k => $v) { | |
808 | if ($v) { | |
809 | $errorMsg[] = "$labels[$k] $v"; | |
6a488035 | 810 | } |
6a488035 | 811 | } |
69f296a4 | 812 | $errorMsg = implode(' AND ', $errorMsg); |
813 | array_unshift($values, 'Matching Contribution record not found for ' . $errorMsg . '. Row was skipped.'); | |
8daa4f36 | 814 | $this->setImportStatus($rowNumber, 'ERROR', 'Matching Contribution record not found for ' . $errorMsg . '. Row was skipped.'); |
69f296a4 | 815 | return CRM_Import_Parser::ERROR; |
6a488035 TO |
816 | } |
817 | } | |
818 | ||
01c21f7e | 819 | if (empty($formatted['contact_id'])) { |
6a488035 | 820 | |
56316747 | 821 | $error = $this->checkContactDuplicate($paramValues); |
6a488035 TO |
822 | |
823 | if (CRM_Core_Error::isAPIError($error, CRM_Core_ERROR::DUPLICATE_CONTACT)) { | |
824 | $matchedIDs = explode(',', $error['error_message']['params'][0]); | |
825 | if (count($matchedIDs) > 1) { | |
826 | array_unshift($values, 'Multiple matching contact records detected for this row. The contribution was not imported'); | |
8daa4f36 | 827 | $this->setImportStatus($rowNumber, 'ERROR', 'Multiple matching contact records detected for this row. The contribution was not imported'); |
a05662ef | 828 | return CRM_Import_Parser::ERROR; |
6a488035 | 829 | } |
69f296a4 | 830 | $cid = $matchedIDs[0]; |
831 | $formatted['contact_id'] = $cid; | |
832 | ||
833 | $newContribution = civicrm_api('contribution', 'create', $formatted); | |
834 | if (civicrm_error($newContribution)) { | |
835 | if (is_array($newContribution['error_message'])) { | |
836 | array_unshift($values, $newContribution['error_message']['message']); | |
837 | if ($newContribution['error_message']['params'][0]) { | |
8daa4f36 | 838 | $this->setImportStatus($rowNumber, 'DUPLICATE', $newContribution['error_message']['message']); |
69f296a4 | 839 | return CRM_Import_Parser::DUPLICATE; |
6a488035 TO |
840 | } |
841 | } | |
69f296a4 | 842 | else { |
843 | array_unshift($values, $newContribution['error_message']); | |
8daa4f36 | 844 | $this->setImportStatus($rowNumber, 'ERROR', $newContribution['error_message']); |
69f296a4 | 845 | return CRM_Import_Parser::ERROR; |
6a488035 | 846 | } |
69f296a4 | 847 | } |
6a488035 | 848 | |
69f296a4 | 849 | $this->_newContributions[] = $newContribution['id']; |
850 | $formatted['contribution_id'] = $newContribution['id']; | |
6a488035 | 851 | |
69f296a4 | 852 | //return soft valid since we need to show how soft credits were added |
853 | if (!empty($formatted['soft_credit'])) { | |
8dc9763a | 854 | return self::SOFT_CREDIT; |
6a488035 | 855 | } |
69f296a4 | 856 | |
857 | // process pledge payment assoc w/ the contribution | |
672b72ea | 858 | return $this->processPledgePayments($formatted); |
6a488035 | 859 | } |
6a488035 | 860 | |
69f296a4 | 861 | // Using new Dedupe rule. |
862 | $ruleParams = [ | |
863 | 'contact_type' => $this->_contactType, | |
864 | 'used' => 'Unsupervised', | |
865 | ]; | |
61194d45 | 866 | $fieldsArray = CRM_Dedupe_BAO_DedupeRule::dedupeRuleFields($ruleParams); |
69f296a4 | 867 | $disp = NULL; |
868 | foreach ($fieldsArray as $value) { | |
869 | if (array_key_exists(trim($value), $params)) { | |
870 | $paramValue = $params[trim($value)]; | |
871 | if (is_array($paramValue)) { | |
872 | $disp .= $params[trim($value)][0][trim($value)] . " "; | |
6a488035 TO |
873 | } |
874 | else { | |
69f296a4 | 875 | $disp .= $params[trim($value)] . " "; |
6a488035 TO |
876 | } |
877 | } | |
6a488035 | 878 | } |
69f296a4 | 879 | |
880 | if (!empty($params['external_identifier'])) { | |
881 | if ($disp) { | |
882 | $disp .= "AND {$params['external_identifier']}"; | |
6a488035 TO |
883 | } |
884 | else { | |
69f296a4 | 885 | $disp = $params['external_identifier']; |
6a488035 TO |
886 | } |
887 | } | |
8daa4f36 EM |
888 | $errorMessage = 'No matching Contact found for (' . $disp . ')'; |
889 | $this->setImportStatus($rowNumber, 'ERROR', $errorMessage); | |
890 | array_unshift($values, $errorMessage); | |
69f296a4 | 891 | return CRM_Import_Parser::ERROR; |
892 | } | |
6a488035 | 893 | |
69f296a4 | 894 | if (!empty($paramValues['external_identifier'])) { |
895 | $checkCid = new CRM_Contact_DAO_Contact(); | |
896 | $checkCid->external_identifier = $paramValues['external_identifier']; | |
897 | $checkCid->find(TRUE); | |
898 | if ($checkCid->id != $formatted['contact_id']) { | |
8daa4f36 EM |
899 | $errorMessage = 'Mismatch of External ID:' . $paramValues['external_identifier'] . ' and Contact Id:' . $formatted['contact_id']; |
900 | array_unshift($values, $errorMessage); | |
901 | $this->setImportStatus($rowNumber, 'ERROR', $errorMessage); | |
69f296a4 | 902 | return CRM_Import_Parser::ERROR; |
903 | } | |
904 | } | |
905 | $newContribution = civicrm_api('contribution', 'create', $formatted); | |
906 | if (civicrm_error($newContribution)) { | |
907 | if (is_array($newContribution['error_message'])) { | |
908 | array_unshift($values, $newContribution['error_message']['message']); | |
909 | if ($newContribution['error_message']['params'][0]) { | |
8daa4f36 | 910 | $this->setImportStatus($rowNumber, 'DUPLICATE', ''); |
69f296a4 | 911 | return CRM_Import_Parser::DUPLICATE; |
912 | } | |
6a488035 | 913 | } |
69f296a4 | 914 | else { |
915 | array_unshift($values, $newContribution['error_message']); | |
8daa4f36 | 916 | $this->setImportStatus($rowNumber, 'ERROR', $newContribution['error_message']); |
69f296a4 | 917 | return CRM_Import_Parser::ERROR; |
918 | } | |
919 | } | |
6a488035 | 920 | |
69f296a4 | 921 | $this->_newContributions[] = $newContribution['id']; |
922 | $formatted['contribution_id'] = $newContribution['id']; | |
6a488035 | 923 | |
69f296a4 | 924 | //return soft valid since we need to show how soft credits were added |
925 | if (!empty($formatted['soft_credit'])) { | |
8dc9763a | 926 | return self::SOFT_CREDIT; |
6a488035 | 927 | } |
69f296a4 | 928 | |
929 | // process pledge payment assoc w/ the contribution | |
672b72ea | 930 | return $this->processPledgePayments($formatted); |
6a488035 TO |
931 | } |
932 | ||
933 | /** | |
74ab7ba8 EM |
934 | * Process pledge payments. |
935 | * | |
936 | * @param array $formatted | |
937 | * | |
938 | * @return int | |
6a488035 | 939 | */ |
672b72ea | 940 | private function processPledgePayments(array $formatted) { |
8cc574cf | 941 | if (!empty($formatted['pledge_payment_id']) && !empty($formatted['pledge_id'])) { |
6a488035 | 942 | //get completed status |
593dbb07 | 943 | $completeStatusID = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'); |
6a488035 TO |
944 | |
945 | //need to update payment record to map contribution_id | |
946 | CRM_Core_DAO::setFieldValue('CRM_Pledge_DAO_PledgePayment', $formatted['pledge_payment_id'], | |
947 | 'contribution_id', $formatted['contribution_id'] | |
948 | ); | |
949 | ||
950 | CRM_Pledge_BAO_PledgePayment::updatePledgePaymentStatus($formatted['pledge_id'], | |
be2fb01f | 951 | [$formatted['pledge_payment_id']], |
6a488035 TO |
952 | $completeStatusID, |
953 | NULL, | |
954 | $formatted['total_amount'] | |
955 | ); | |
956 | ||
8dc9763a | 957 | return self::PLEDGE_PAYMENT; |
6a488035 TO |
958 | } |
959 | } | |
960 | ||
961 | /** | |
ceb10dc7 | 962 | * Get the array of successfully imported contribution id's |
6a488035 TO |
963 | * |
964 | * @return array | |
6a488035 | 965 | */ |
00be9182 | 966 | public function &getImportedContributions() { |
6a488035 TO |
967 | return $this->_newContributions; |
968 | } | |
969 | ||
1004b689 | 970 | /** |
971 | * Format date fields from input to mysql. | |
972 | * | |
973 | * @param array $params | |
974 | * | |
975 | * @return array | |
976 | * Error messages, if any. | |
977 | */ | |
978 | public function formatDateFields(&$params) { | |
341c643b | 979 | $errorMessage = []; |
1004b689 | 980 | $dateType = CRM_Core_Session::singleton()->get('dateTypes'); |
981 | foreach ($params as $key => $val) { | |
982 | if ($val) { | |
983 | switch ($key) { | |
984 | case 'receive_date': | |
985 | if ($dateValue = CRM_Utils_Date::formatDate($params[$key], $dateType)) { | |
986 | $params[$key] = $dateValue; | |
987 | } | |
988 | else { | |
341c643b | 989 | $errorMessage[] = ts('Receive Date'); |
1004b689 | 990 | } |
991 | break; | |
992 | ||
993 | case 'cancel_date': | |
994 | if ($dateValue = CRM_Utils_Date::formatDate($params[$key], $dateType)) { | |
995 | $params[$key] = $dateValue; | |
996 | } | |
997 | else { | |
341c643b | 998 | $errorMessage[] = ts('Cancel Date'); |
1004b689 | 999 | } |
1000 | break; | |
1001 | ||
1002 | case 'receipt_date': | |
1003 | if ($dateValue = CRM_Utils_Date::formatDate($params[$key], $dateType)) { | |
1004 | $params[$key] = $dateValue; | |
1005 | } | |
1006 | else { | |
341c643b | 1007 | $errorMessage[] = ts('Receipt date'); |
1004b689 | 1008 | } |
1009 | break; | |
1010 | ||
1011 | case 'thankyou_date': | |
1012 | if ($dateValue = CRM_Utils_Date::formatDate($params[$key], $dateType)) { | |
1013 | $params[$key] = $dateValue; | |
1014 | } | |
1015 | else { | |
341c643b | 1016 | $errorMessage[] = ts('Thankyou Date'); |
1004b689 | 1017 | } |
1018 | break; | |
1019 | } | |
1020 | } | |
1021 | } | |
1022 | return $errorMessage; | |
1023 | } | |
1024 | ||
1025 | /** | |
1026 | * Format input params to suit api handling. | |
1027 | * | |
4fc2ce46 | 1028 | * Over time all the parts of deprecatedFormatParams |
1004b689 | 1029 | * and all the parts of the import function on this class that relate to |
1030 | * reformatting input should be moved here and tests should be added in | |
1031 | * CRM_Contribute_Import_Parser_ContributionTest. | |
1032 | * | |
1033 | * @param array $params | |
fed96c11 | 1034 | * @param array $formatted |
1004b689 | 1035 | */ |
fed96c11 | 1036 | public function formatInput(&$params, &$formatted = []) { |
1004b689 | 1037 | $dateType = CRM_Core_Session::singleton()->get('dateTypes'); |
1038 | $customDataType = !empty($params['contact_type']) ? $params['contact_type'] : 'Contribution'; | |
1039 | $customFields = CRM_Core_BAO_CustomField::getFields($customDataType); | |
1040 | // @todo call formatDateFields & move custom data handling there. | |
4fc2ce46 | 1041 | // Also note error handling for dates is currently in deprecatedFormatParams |
1004b689 | 1042 | // we should use the error handling in formatDateFields. |
1043 | foreach ($params as $key => $val) { | |
1044 | // @todo - call formatDateFields instead. | |
1045 | if ($val) { | |
1046 | switch ($key) { | |
1047 | case 'receive_date': | |
1048 | case 'cancel_date': | |
1049 | case 'receipt_date': | |
1050 | case 'thankyou_date': | |
1051 | $params[$key] = CRM_Utils_Date::formatDate($params[$key], $dateType); | |
1052 | break; | |
1053 | ||
1054 | case 'pledge_payment': | |
1055 | $params[$key] = CRM_Utils_String::strtobool($val); | |
1056 | break; | |
1057 | ||
1058 | } | |
1059 | if ($customFieldID = CRM_Core_BAO_CustomField::getKeyID($key)) { | |
1060 | if ($customFields[$customFieldID]['data_type'] == 'Date') { | |
fed96c11 | 1061 | CRM_Contact_Import_Parser_Contact::formatCustomDate($params, $formatted, $dateType, $key); |
1004b689 | 1062 | unset($params[$key]); |
1063 | } | |
1064 | elseif ($customFields[$customFieldID]['data_type'] == 'Boolean') { | |
1065 | $params[$key] = CRM_Utils_String::strtoboolstr($val); | |
1066 | } | |
1067 | } | |
1068 | } | |
1069 | } | |
1070 | } | |
1071 | ||
4fc2ce46 | 1072 | /** |
1073 | * take the input parameter list as specified in the data model and | |
1074 | * convert it into the same format that we use in QF and BAO object | |
1075 | * | |
1076 | * @param array $params | |
5e21e0f3 BT |
1077 | * Associative array of property name/value |
1078 | * pairs to insert in new contact. | |
4fc2ce46 | 1079 | * @param array $values |
1080 | * The reformatted properties that we can use internally. | |
4fc2ce46 | 1081 | * @param bool $create |
5e21e0f3 | 1082 | * @param int $onDuplicate |
4fc2ce46 | 1083 | * |
1084 | * @return array|CRM_Error | |
01c21f7e | 1085 | * @throws \CRM_Core_Exception |
4fc2ce46 | 1086 | */ |
1087 | private function deprecatedFormatParams($params, &$values, $create = FALSE, $onDuplicate = NULL) { | |
1088 | require_once 'CRM/Utils/DeprecatedUtils.php'; | |
1089 | // copy all the contribution fields as is | |
1090 | require_once 'api/v3/utils.php'; | |
4fc2ce46 | 1091 | |
1092 | foreach ($params as $key => $value) { | |
1093 | // ignore empty values or empty arrays etc | |
1094 | if (CRM_Utils_System::isNull($value)) { | |
1095 | continue; | |
1096 | } | |
1097 | ||
4fc2ce46 | 1098 | switch ($key) { |
01c21f7e | 1099 | case 'contact_id': |
4fc2ce46 | 1100 | if (!CRM_Utils_Rule::integer($value)) { |
1101 | return civicrm_api3_create_error("contact_id not valid: $value"); | |
1102 | } | |
1103 | $dao = new CRM_Core_DAO(); | |
1104 | $qParams = []; | |
1105 | $svq = $dao->singleValueQuery("SELECT is_deleted FROM civicrm_contact WHERE id = $value", | |
1106 | $qParams | |
1107 | ); | |
1108 | if (!isset($svq)) { | |
1109 | return civicrm_api3_create_error("Invalid Contact ID: There is no contact record with contact_id = $value."); | |
1110 | } | |
1111 | elseif ($svq == 1) { | |
1112 | return civicrm_api3_create_error("Invalid Contact ID: contact_id $value is a soft-deleted contact."); | |
1113 | } | |
01c21f7e | 1114 | $values['contact_id'] = $value; |
4fc2ce46 | 1115 | break; |
1116 | ||
1117 | case 'contact_type': | |
1118 | // import contribution record according to select contact type | |
1119 | require_once 'CRM/Contact/DAO/Contact.php'; | |
1120 | $contactType = new CRM_Contact_DAO_Contact(); | |
9c1bc317 CW |
1121 | $contactId = $params['contribution_contact_id'] ?? NULL; |
1122 | $externalId = $params['external_identifier'] ?? NULL; | |
1123 | $email = $params['email'] ?? NULL; | |
4fc2ce46 | 1124 | //when insert mode check contact id or external identifier |
1125 | if ($contactId || $externalId) { | |
1126 | $contactType->id = $contactId; | |
1127 | $contactType->external_identifier = $externalId; | |
1128 | if ($contactType->find(TRUE)) { | |
1129 | if ($params['contact_type'] != $contactType->contact_type) { | |
1130 | return civicrm_api3_create_error("Contact Type is wrong: $contactType->contact_type"); | |
1131 | } | |
1132 | } | |
1133 | } | |
1134 | elseif ($email) { | |
1135 | if (!CRM_Utils_Rule::email($email)) { | |
1136 | return civicrm_api3_create_error("Invalid email address $email provided. Row was skipped"); | |
1137 | } | |
1138 | ||
1139 | // get the contact id from duplicate contact rule, if more than one contact is returned | |
1140 | // we should return error, since current interface allows only one-one mapping | |
69f296a4 | 1141 | $emailParams = [ |
1142 | 'email' => $email, | |
1143 | 'contact_type' => $params['contact_type'], | |
1144 | ]; | |
4fc2ce46 | 1145 | $checkDedupe = _civicrm_api3_deprecated_duplicate_formatted_contact($emailParams); |
1146 | if (!$checkDedupe['is_error']) { | |
1147 | return civicrm_api3_create_error("Invalid email address(doesn't exist) $email. Row was skipped"); | |
1148 | } | |
69f296a4 | 1149 | $matchingContactIds = explode(',', $checkDedupe['error_message']['params'][0]); |
1150 | if (count($matchingContactIds) > 1) { | |
1151 | return civicrm_api3_create_error("Invalid email address(duplicate) $email. Row was skipped"); | |
1152 | } | |
1153 | if (count($matchingContactIds) == 1) { | |
1154 | $params['contribution_contact_id'] = $matchingContactIds[0]; | |
4fc2ce46 | 1155 | } |
1156 | } | |
1157 | elseif (!empty($params['contribution_id']) || !empty($params['trxn_id']) || !empty($params['invoice_id'])) { | |
1158 | // when update mode check contribution id or trxn id or | |
1159 | // invoice id | |
1160 | $contactId = new CRM_Contribute_DAO_Contribution(); | |
1161 | if (!empty($params['contribution_id'])) { | |
1162 | $contactId->id = $params['contribution_id']; | |
1163 | } | |
1164 | elseif (!empty($params['trxn_id'])) { | |
1165 | $contactId->trxn_id = $params['trxn_id']; | |
1166 | } | |
1167 | elseif (!empty($params['invoice_id'])) { | |
1168 | $contactId->invoice_id = $params['invoice_id']; | |
1169 | } | |
1170 | if ($contactId->find(TRUE)) { | |
1171 | $contactType->id = $contactId->contact_id; | |
1172 | if ($contactType->find(TRUE)) { | |
1173 | if ($params['contact_type'] != $contactType->contact_type) { | |
1174 | return civicrm_api3_create_error("Contact Type is wrong: $contactType->contact_type"); | |
1175 | } | |
1176 | } | |
1177 | } | |
1178 | } | |
1179 | else { | |
1180 | if ($onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE) { | |
1181 | return civicrm_api3_create_error("Empty Contribution and Invoice and Transaction ID. Row was skipped."); | |
1182 | } | |
1183 | } | |
1184 | break; | |
1185 | ||
4fc2ce46 | 1186 | case 'non_deductible_amount': |
1187 | case 'total_amount': | |
1188 | case 'fee_amount': | |
1189 | case 'net_amount': | |
8e2ac367 | 1190 | // @todo add test like testPaymentTypeLabel & remove these lines as we can anticipate error will still be caught & handled. |
4fc2ce46 | 1191 | if (!CRM_Utils_Rule::money($value)) { |
1192 | return civicrm_api3_create_error("$key not a valid amount: $value"); | |
1193 | } | |
1194 | break; | |
1195 | ||
1196 | case 'currency': | |
1197 | if (!CRM_Utils_Rule::currencyCode($value)) { | |
1198 | return civicrm_api3_create_error("currency not a valid code: $value"); | |
1199 | } | |
1200 | break; | |
1201 | ||
4fc2ce46 | 1202 | case 'soft_credit': |
1203 | // import contribution record according to select contact type | |
1204 | // validate contact id and external identifier. | |
01c21f7e EM |
1205 | foreach ($value as $softKey => $softParam) { |
1206 | $values['soft_credit'][$softKey] = [ | |
1207 | 'contact_id' => $this->lookupMatchingContact($softParam), | |
1208 | 'soft_credit_type_id' => $softParam['soft_credit_type_id'], | |
1209 | ]; | |
4fc2ce46 | 1210 | } |
1211 | break; | |
1212 | ||
1213 | case 'pledge_payment': | |
1214 | case 'pledge_id': | |
1215 | ||
1216 | // giving respect to pledge_payment flag. | |
1217 | if (empty($params['pledge_payment'])) { | |
c237e286 | 1218 | break; |
4fc2ce46 | 1219 | } |
1220 | ||
1221 | // get total amount of from import fields | |
9c1bc317 | 1222 | $totalAmount = $params['total_amount'] ?? NULL; |
4fc2ce46 | 1223 | |
9c1bc317 | 1224 | $onDuplicate = $params['onDuplicate'] ?? NULL; |
4fc2ce46 | 1225 | |
1226 | // we need to get contact id $contributionContactID to | |
1227 | // retrieve pledge details as well as to validate pledge ID | |
1228 | ||
1229 | // first need to check for update mode | |
1230 | if ($onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE && | |
1231 | ($params['contribution_id'] || $params['trxn_id'] || $params['invoice_id']) | |
1232 | ) { | |
1233 | $contribution = new CRM_Contribute_DAO_Contribution(); | |
1234 | if ($params['contribution_id']) { | |
1235 | $contribution->id = $params['contribution_id']; | |
1236 | } | |
1237 | elseif ($params['trxn_id']) { | |
1238 | $contribution->trxn_id = $params['trxn_id']; | |
1239 | } | |
1240 | elseif ($params['invoice_id']) { | |
1241 | $contribution->invoice_id = $params['invoice_id']; | |
1242 | } | |
1243 | ||
1244 | if ($contribution->find(TRUE)) { | |
1245 | $contributionContactID = $contribution->contact_id; | |
1246 | if (!$totalAmount) { | |
1247 | $totalAmount = $contribution->total_amount; | |
1248 | } | |
1249 | } | |
1250 | else { | |
1251 | return civicrm_api3_create_error('No match found for specified contact in pledge payment data. Row was skipped.'); | |
1252 | } | |
1253 | } | |
1254 | else { | |
1255 | // first get the contact id for given contribution record. | |
1256 | if (!empty($params['contribution_contact_id'])) { | |
1257 | $contributionContactID = $params['contribution_contact_id']; | |
1258 | } | |
1259 | elseif (!empty($params['external_identifier'])) { | |
1260 | require_once 'CRM/Contact/DAO/Contact.php'; | |
1261 | $contact = new CRM_Contact_DAO_Contact(); | |
1262 | $contact->external_identifier = $params['external_identifier']; | |
1263 | if ($contact->find(TRUE)) { | |
1264 | $contributionContactID = $params['contribution_contact_id'] = $values['contribution_contact_id'] = $contact->id; | |
1265 | } | |
1266 | else { | |
1267 | return civicrm_api3_create_error('No match found for specified contact in pledge payment data. Row was skipped.'); | |
1268 | } | |
1269 | } | |
1270 | else { | |
1271 | // we need to get contribution contact using de dupe | |
95519b12 | 1272 | $error = $this->checkContactDuplicate($params); |
4fc2ce46 | 1273 | |
1274 | if (isset($error['error_message']['params'][0])) { | |
1275 | $matchedIDs = explode(',', $error['error_message']['params'][0]); | |
1276 | ||
1277 | // check if only one contact is found | |
1278 | if (count($matchedIDs) > 1) { | |
1279 | return civicrm_api3_create_error($error['error_message']['message']); | |
1280 | } | |
69f296a4 | 1281 | $contributionContactID = $params['contribution_contact_id'] = $values['contribution_contact_id'] = $matchedIDs[0]; |
4fc2ce46 | 1282 | } |
1283 | else { | |
1284 | return civicrm_api3_create_error('No match found for specified contact in contribution data. Row was skipped.'); | |
1285 | } | |
1286 | } | |
1287 | } | |
1288 | ||
1289 | if (!empty($params['pledge_id'])) { | |
1290 | if (CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_Pledge', $params['pledge_id'], 'contact_id') != $contributionContactID) { | |
1291 | return civicrm_api3_create_error('Invalid Pledge ID provided. Contribution row was skipped.'); | |
1292 | } | |
1293 | $values['pledge_id'] = $params['pledge_id']; | |
1294 | } | |
1295 | else { | |
1296 | // check if there are any pledge related to this contact, with payments pending or in progress | |
1297 | require_once 'CRM/Pledge/BAO/Pledge.php'; | |
1298 | $pledgeDetails = CRM_Pledge_BAO_Pledge::getContactPledges($contributionContactID); | |
1299 | ||
1300 | if (empty($pledgeDetails)) { | |
1301 | return civicrm_api3_create_error('No open pledges found for this contact. Contribution row was skipped.'); | |
1302 | } | |
69f296a4 | 1303 | if (count($pledgeDetails) > 1) { |
4fc2ce46 | 1304 | return civicrm_api3_create_error('This contact has more than one open pledge. Unable to determine which pledge to apply the contribution to. Contribution row was skipped.'); |
1305 | } | |
1306 | ||
1307 | // this mean we have only one pending / in progress pledge | |
1308 | $values['pledge_id'] = $pledgeDetails[0]; | |
1309 | } | |
1310 | ||
1311 | // we need to check if oldest payment amount equal to contribution amount | |
1312 | require_once 'CRM/Pledge/BAO/PledgePayment.php'; | |
1313 | $pledgePaymentDetails = CRM_Pledge_BAO_PledgePayment::getOldestPledgePayment($values['pledge_id']); | |
1314 | ||
1315 | if ($pledgePaymentDetails['amount'] == $totalAmount) { | |
1316 | $values['pledge_payment_id'] = $pledgePaymentDetails['id']; | |
1317 | } | |
1318 | else { | |
1319 | return civicrm_api3_create_error('Contribution and Pledge Payment amount mismatch for this record. Contribution row was skipped.'); | |
1320 | } | |
1321 | break; | |
1322 | ||
cb9cdda8 KJ |
1323 | case 'contribution_campaign_id': |
1324 | if (empty(CRM_Core_DAO::getFieldValue('CRM_Campaign_DAO_Campaign', $params['contribution_campaign_id']))) { | |
1325 | return civicrm_api3_create_error('Invalid Campaign ID provided. Contribution row was skipped.'); | |
1326 | } | |
1327 | $values['contribution_campaign_id'] = $params['contribution_campaign_id']; | |
1328 | break; | |
1329 | ||
4fc2ce46 | 1330 | } |
1331 | } | |
1332 | ||
1333 | if (array_key_exists('note', $params)) { | |
1334 | $values['note'] = $params['note']; | |
1335 | } | |
1336 | ||
1337 | if ($create) { | |
1338 | // CRM_Contribute_BAO_Contribution::add() handles contribution_source | |
1339 | // So, if $values contains contribution_source, convert it to source | |
1340 | $changes = ['contribution_source' => 'source']; | |
1341 | ||
1342 | foreach ($changes as $orgVal => $changeVal) { | |
1343 | if (isset($values[$orgVal])) { | |
1344 | $values[$changeVal] = $values[$orgVal]; | |
1345 | unset($values[$orgVal]); | |
1346 | } | |
1347 | } | |
1348 | } | |
1349 | ||
1350 | return NULL; | |
1351 | } | |
1352 | ||
73edfc10 EM |
1353 | /** |
1354 | * Get the civicrm_mapping_field appropriate layout for the mapper input. | |
1355 | * | |
1356 | * The input looks something like ['street_address', 1] | |
1357 | * and would be mapped to ['name' => 'street_address', 'location_type_id' => | |
1358 | * 1] | |
1359 | * | |
1360 | * @param array $fieldMapping | |
1361 | * @param int $mappingID | |
1362 | * @param int $columnNumber | |
1363 | * | |
1364 | * @return array | |
1365 | * @throws \API_Exception | |
1366 | */ | |
1367 | public function getMappingFieldFromMapperInput(array $fieldMapping, int $mappingID, int $columnNumber): array { | |
73edfc10 EM |
1368 | return [ |
1369 | 'name' => $fieldMapping[0], | |
1370 | 'mapping_id' => $mappingID, | |
1371 | 'column_number' => $columnNumber, | |
1372 | // The name of the field to match the soft credit on is (crazily) | |
1373 | // stored in 'contact_type' | |
1374 | 'contact_type' => $fieldMapping[1] ?? NULL, | |
1375 | // We also store the field in a sensible key, even if it isn't saved sensibly. | |
1376 | 'soft_credit_match_field' => $fieldMapping[1] ?? NULL, | |
1377 | // This field is actually not saved at all :-( It is lost each time. | |
1378 | 'soft_credit_type_id' => $fieldMapping[2] ?? NULL, | |
1379 | ]; | |
1380 | } | |
1381 | ||
01c21f7e EM |
1382 | /** |
1383 | * Lookup matching contact. | |
1384 | * | |
1385 | * This looks up the matching contact from the contact id, external identifier | |
1386 | * or email. For the email a straight email search is done - this is equivalent | |
1387 | * to what happens on a dedupe rule lookup when the only field is 'email' - but | |
1388 | * we can't be sure the rule is 'just email' - and we are not collecting the | |
1389 | * fields for any other lookup in the case of soft credits (if we | |
1390 | * extend this function to main-contact-lookup we can handle full dedupe | |
1391 | * lookups - but note the error messages will need tweaking. | |
1392 | * | |
1393 | * @param array $params | |
1394 | * | |
1395 | * @return int | |
1396 | * Contact ID | |
1397 | * | |
1398 | * @throws \API_Exception | |
1399 | * @throws \CRM_Core_Exception | |
1400 | */ | |
1401 | private function lookupMatchingContact(array $params): int { | |
1402 | $lookupField = !empty($params['contact_id']) ? 'contact_id' : (!empty($params['external_identifier']) ? 'external_identifier' : 'email'); | |
1403 | if (empty($params['email'])) { | |
1404 | $contact = Contact::get(FALSE)->addSelect('id') | |
8daa4f36 | 1405 | ->addWhere($lookupField === 'contact_id' ? 'id' : $lookupField, '=', $params[$lookupField]) |
01c21f7e EM |
1406 | ->execute(); |
1407 | if (count($contact) !== 1) { | |
1408 | throw new CRM_Core_Exception(ts("Soft Credit %1 - %2 doesn't exist. Row was skipped.", | |
1409 | [ | |
1410 | 1 => $this->getFieldMetadata($lookupField), | |
1411 | 2 => $params['contact_id'] ?? $params['external_identifier'], | |
1412 | ])); | |
1413 | } | |
1414 | return $contact->first()['id']; | |
1415 | } | |
1416 | ||
1417 | if (!CRM_Utils_Rule::email($params['email'])) { | |
1418 | throw new CRM_Core_Exception(ts('Invalid email address %1 provided for Soft Credit. Row was skipped'), [1 => $params['email']]); | |
1419 | } | |
1420 | $emails = Email::get(FALSE) | |
1421 | ->addWhere('contact_id.is_deleted', '=', 0) | |
1422 | ->addWhere('contact_id.contact_type', '=', $this->getContactType()) | |
1423 | ->addWhere('email', '=', $params['email']) | |
1424 | ->addSelect('contact_id')->execute(); | |
1425 | if (count($emails) === 0) { | |
1426 | throw new CRM_Core_Exception(ts("Invalid email address(doesn't exist) %1 for Soft Credit. Row was skipped", [1 => $params['email']])); | |
1427 | } | |
1428 | if (count($emails) > 1) { | |
1429 | throw new CRM_Core_Exception(ts('Invalid email address(duplicate) %1 for Soft Credit. Row was skipped', [1 => $params['email']])); | |
1430 | } | |
1431 | return $emails->first()['contact_id']; | |
1432 | } | |
1433 | ||
8bf4c85b EM |
1434 | /** |
1435 | * @param array $mappedField | |
1436 | * Field detail as would be saved in field_mapping table | |
1437 | * or as returned from getMappingFieldFromMapperInput | |
1438 | * | |
1439 | * @return string | |
1440 | * @throws \API_Exception | |
1441 | */ | |
1442 | public function getMappedFieldLabel(array $mappedField): string { | |
1443 | if (empty($this->importableFieldsMetadata)) { | |
1444 | $this->setFieldMetadata(); | |
1445 | } | |
1446 | $title = []; | |
1447 | $title[] = $this->getFieldMetadata($mappedField['name'])['title']; | |
1448 | if ($mappedField['soft_credit_match_field']) { | |
1449 | $title[] = $this->getFieldMetadata($mappedField['soft_credit_match_field'])['title']; | |
1450 | } | |
1451 | if ($mappedField['soft_credit_type_id']) { | |
1452 | $title[] = CRM_Core_PseudoConstant::getLabel('CRM_Contribute_BAO_ContributionSoft', 'soft_credit_type_id', $mappedField['soft_credit_type_id']); | |
1453 | } | |
1454 | ||
1455 | return implode(' - ', $title); | |
1456 | } | |
1457 | ||
8daa4f36 EM |
1458 | /** |
1459 | * Get the metadata field for which importable fields does not key the actual field name. | |
1460 | * | |
1461 | * @return string[] | |
1462 | */ | |
1463 | protected function getOddlyMappedMetadataFields(): array { | |
1464 | $uniqueNames = ['contribution_id', 'contribution_contact_id', 'contribution_cancel_date', 'contribution_source', 'contribution_check_number']; | |
1465 | $fields = []; | |
1466 | foreach ($uniqueNames as $name) { | |
1467 | $fields[$this->importableFieldsMetadata[$name]['name']] = $name; | |
1468 | } | |
1469 | // Include the parent fields as they could be present if required for matching ...in theory. | |
1470 | return array_merge($fields, parent::getOddlyMappedMetadataFields()); | |
1471 | } | |
1472 | ||
6a488035 | 1473 | } |