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