[REF][Import] add formatted parameter to formatInput
[civicrm-core.git] / CRM / Contribute / Import / Parser / Contribution.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
27
28 /**
29 *
30 * @package CRM
31 * @copyright CiviCRM LLC (c) 2004-2019
32 */
33
34 /**
35 * Class to parse contribution csv files.
36 */
37 class CRM_Contribute_Import_Parser_Contribution extends CRM_Contribute_Import_Parser {
38
39 protected $_mapperKeys;
40
41 private $_contactIdIndex;
42 private $_totalAmountIndex;
43 private $_contributionTypeIndex;
44
45 protected $_mapperSoftCredit;
46 //protected $_mapperPhoneType;
47
48 /**
49 * Array of successfully imported contribution id's
50 *
51 * @var array
52 */
53 protected $_newContributions;
54
55 /**
56 * Class constructor.
57 *
58 * @param $mapperKeys
59 * @param array $mapperSoftCredit
60 * @param null $mapperPhoneType
61 * @param array $mapperSoftCreditType
62 */
63 public function __construct(&$mapperKeys, $mapperSoftCredit = [], $mapperPhoneType = NULL, $mapperSoftCreditType = []) {
64 parent::__construct();
65 $this->_mapperKeys = &$mapperKeys;
66 $this->_mapperSoftCredit = &$mapperSoftCredit;
67 $this->_mapperSoftCreditType = &$mapperSoftCreditType;
68 }
69
70 /**
71 * The initializer code, called before the processing
72 */
73 public function init() {
74 $fields = CRM_Contribute_BAO_Contribution::importableFields($this->_contactType, FALSE);
75
76 $fields = array_merge($fields,
77 [
78 'soft_credit' => [
79 'title' => ts('Soft Credit'),
80 'softCredit' => TRUE,
81 'headerPattern' => '/Soft Credit/i',
82 ],
83 ]
84 );
85
86 // add pledge fields only if its is enabled
87 if (CRM_Core_Permission::access('CiviPledge')) {
88 $pledgeFields = [
89 'pledge_payment' => [
90 'title' => ts('Pledge Payment'),
91 'headerPattern' => '/Pledge Payment/i',
92 ],
93 'pledge_id' => [
94 'title' => ts('Pledge ID'),
95 'headerPattern' => '/Pledge ID/i',
96 ],
97 ];
98
99 $fields = array_merge($fields, $pledgeFields);
100 }
101 foreach ($fields as $name => $field) {
102 $field['type'] = CRM_Utils_Array::value('type', $field, CRM_Utils_Type::T_INT);
103 $field['dataPattern'] = CRM_Utils_Array::value('dataPattern', $field, '//');
104 $field['headerPattern'] = CRM_Utils_Array::value('headerPattern', $field, '//');
105 $this->addField($name, $field['title'], $field['type'], $field['headerPattern'], $field['dataPattern']);
106 }
107
108 $this->_newContributions = [];
109
110 $this->setActiveFields($this->_mapperKeys);
111 $this->setActiveFieldSoftCredit($this->_mapperSoftCredit);
112 $this->setActiveFieldSoftCreditType($this->_mapperSoftCreditType);
113
114 // FIXME: we should do this in one place together with Form/MapField.php
115 $this->_contactIdIndex = -1;
116 $this->_totalAmountIndex = -1;
117 $this->_contributionTypeIndex = -1;
118
119 $index = 0;
120 foreach ($this->_mapperKeys as $key) {
121 switch ($key) {
122 case 'contribution_contact_id':
123 $this->_contactIdIndex = $index;
124 break;
125
126 case 'total_amount':
127 $this->_totalAmountIndex = $index;
128 break;
129
130 case 'financial_type':
131 $this->_contributionTypeIndex = $index;
132 break;
133 }
134 $index++;
135 }
136 }
137
138 /**
139 * Handle the values in mapField mode.
140 *
141 * @param array $values
142 * The array of values belonging to this line.
143 *
144 * @return bool
145 */
146 public function mapField(&$values) {
147 return CRM_Import_Parser::VALID;
148 }
149
150 /**
151 * Handle the values in preview mode.
152 *
153 * @param array $values
154 * The array of values belonging to this line.
155 *
156 * @return bool
157 * the result of this processing
158 */
159 public function preview(&$values) {
160 return $this->summary($values);
161 }
162
163 /**
164 * Handle the values in summary mode.
165 *
166 * @param array $values
167 * The array of values belonging to this line.
168 *
169 * @return bool
170 * the result of this processing
171 */
172 public function summary(&$values) {
173 $erroneousField = NULL;
174 $response = $this->setActiveFieldValues($values, $erroneousField);
175
176 $params = &$this->getActiveFieldParams();
177 $errorMessage = NULL;
178
179 //for date-Formats
180 $errorMessage = implode('; ', $this->formatDateFields($params));
181 //date-Format part ends
182
183 $params['contact_type'] = 'Contribution';
184
185 //checking error in custom data
186 CRM_Contact_Import_Parser_Contact::isErrorInCustomData($params, $errorMessage);
187
188 if ($errorMessage) {
189 $tempMsg = "Invalid value for field(s) : $errorMessage";
190 array_unshift($values, $tempMsg);
191 $errorMessage = NULL;
192 return CRM_Import_Parser::ERROR;
193 }
194
195 return CRM_Import_Parser::VALID;
196 }
197
198 /**
199 * Handle the values in import mode.
200 *
201 * @param int $onDuplicate
202 * The code for what action to take on duplicates.
203 * @param array $values
204 * The array of values belonging to this line.
205 *
206 * @return bool
207 * the result of this processing
208 */
209 public function import($onDuplicate, &$values) {
210 // first make sure this is a valid line
211 $response = $this->summary($values);
212 if ($response != CRM_Import_Parser::VALID) {
213 return $response;
214 }
215
216 $params = &$this->getActiveFieldParams();
217 $formatted = ['version' => 3, 'skipRecentView' => TRUE, 'skipCleanMoney' => FALSE];
218
219 //CRM-10994
220 if (isset($params['total_amount']) && $params['total_amount'] == 0) {
221 $params['total_amount'] = '0.00';
222 }
223 $this->formatInput($params, $formatted);
224
225 static $indieFields = NULL;
226 if ($indieFields == NULL) {
227 $tempIndieFields = CRM_Contribute_DAO_Contribution::import();
228 $indieFields = $tempIndieFields;
229 }
230
231 $paramValues = [];
232 foreach ($params as $key => $field) {
233 if ($field == NULL || $field === '') {
234 continue;
235 }
236 $paramValues[$key] = $field;
237 }
238
239 //import contribution record according to select contact type
240 if ($onDuplicate == CRM_Import_Parser::DUPLICATE_SKIP &&
241 (!empty($paramValues['contribution_contact_id']) || !empty($paramValues['external_identifier']))
242 ) {
243 $paramValues['contact_type'] = $this->_contactType;
244 }
245 elseif ($onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE &&
246 (!empty($paramValues['contribution_id']) || !empty($values['trxn_id']) || !empty($paramValues['invoice_id']))
247 ) {
248 $paramValues['contact_type'] = $this->_contactType;
249 }
250 elseif (!empty($params['soft_credit'])) {
251 $paramValues['contact_type'] = $this->_contactType;
252 }
253 elseif (!empty($paramValues['pledge_payment'])) {
254 $paramValues['contact_type'] = $this->_contactType;
255 }
256
257 //need to pass $onDuplicate to check import mode.
258 if (!empty($paramValues['pledge_payment'])) {
259 $paramValues['onDuplicate'] = $onDuplicate;
260 }
261 $formatError = $this->deprecatedFormatParams($paramValues, $formatted, TRUE, $onDuplicate);
262
263 if ($formatError) {
264 array_unshift($values, $formatError['error_message']);
265 if (CRM_Utils_Array::value('error_data', $formatError) == 'soft_credit') {
266 return CRM_Contribute_Import_Parser::SOFT_CREDIT_ERROR;
267 }
268 elseif (CRM_Utils_Array::value('error_data', $formatError) == 'pledge_payment') {
269 return CRM_Contribute_Import_Parser::PLEDGE_PAYMENT_ERROR;
270 }
271 return CRM_Import_Parser::ERROR;
272 }
273
274 if ($onDuplicate != CRM_Import_Parser::DUPLICATE_UPDATE) {
275 $formatted['custom'] = CRM_Core_BAO_CustomField::postProcess($formatted,
276 NULL,
277 'Contribution'
278 );
279 }
280 else {
281 //fix for CRM-2219 - Update Contribution
282 // onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE
283 if (!empty($paramValues['invoice_id']) || !empty($paramValues['trxn_id']) || !empty($paramValues['contribution_id'])) {
284 $dupeIds = [
285 'id' => CRM_Utils_Array::value('contribution_id', $paramValues),
286 'trxn_id' => CRM_Utils_Array::value('trxn_id', $paramValues),
287 'invoice_id' => CRM_Utils_Array::value('invoice_id', $paramValues),
288 ];
289
290 $ids['contribution'] = CRM_Contribute_BAO_Contribution::checkDuplicateIds($dupeIds);
291
292 if ($ids['contribution']) {
293 $formatted['id'] = $ids['contribution'];
294 $formatted['custom'] = CRM_Core_BAO_CustomField::postProcess($formatted,
295 $formatted['id'],
296 'Contribution'
297 );
298 //process note
299 if (!empty($paramValues['note'])) {
300 $noteID = [];
301 $contactID = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $ids['contribution'], 'contact_id');
302 $daoNote = new CRM_Core_BAO_Note();
303 $daoNote->entity_table = 'civicrm_contribution';
304 $daoNote->entity_id = $ids['contribution'];
305 if ($daoNote->find(TRUE)) {
306 $noteID['id'] = $daoNote->id;
307 }
308
309 $noteParams = [
310 'entity_table' => 'civicrm_contribution',
311 'note' => $paramValues['note'],
312 'entity_id' => $ids['contribution'],
313 'contact_id' => $contactID,
314 ];
315 CRM_Core_BAO_Note::add($noteParams, $noteID);
316 unset($formatted['note']);
317 }
318
319 //need to check existing soft credit contribution, CRM-3968
320 if (!empty($formatted['soft_credit'])) {
321 $dupeSoftCredit = [
322 'contact_id' => $formatted['soft_credit'],
323 'contribution_id' => $ids['contribution'],
324 ];
325
326 //Delete all existing soft Contribution from contribution_soft table for pcp_id is_null
327 $existingSoftCredit = CRM_Contribute_BAO_ContributionSoft::getSoftContribution($dupeSoftCredit['contribution_id']);
328 if (isset($existingSoftCredit['soft_credit']) && !empty($existingSoftCredit['soft_credit'])) {
329 foreach ($existingSoftCredit['soft_credit'] as $key => $existingSoftCreditValues) {
330 if (!empty($existingSoftCreditValues['soft_credit_id'])) {
331 civicrm_api3('ContributionSoft', 'delete', [
332 'id' => $existingSoftCreditValues['soft_credit_id'],
333 'pcp_id' => NULL,
334 ]);
335 }
336 }
337 }
338 }
339
340 $newContribution = CRM_Contribute_BAO_Contribution::create($formatted, $ids);
341 $this->_newContributions[] = $newContribution->id;
342
343 //return soft valid since we need to show how soft credits were added
344 if (!empty($formatted['soft_credit'])) {
345 return CRM_Contribute_Import_Parser::SOFT_CREDIT;
346 }
347
348 // process pledge payment assoc w/ the contribution
349 return self::processPledgePayments($formatted);
350
351 return CRM_Import_Parser::VALID;
352 }
353 else {
354 $labels = [
355 'id' => 'Contribution ID',
356 'trxn_id' => 'Transaction ID',
357 'invoice_id' => 'Invoice ID',
358 ];
359 foreach ($dupeIds as $k => $v) {
360 if ($v) {
361 $errorMsg[] = "$labels[$k] $v";
362 }
363 }
364 $errorMsg = implode(' AND ', $errorMsg);
365 array_unshift($values, 'Matching Contribution record not found for ' . $errorMsg . '. Row was skipped.');
366 return CRM_Import_Parser::ERROR;
367 }
368 }
369 }
370
371 if ($this->_contactIdIndex < 0) {
372 // set the contact type if its not set
373 if (!isset($paramValues['contact_type'])) {
374 $paramValues['contact_type'] = $this->_contactType;
375 }
376
377 $error = $this->checkContactDuplicate($paramValues);
378
379 if (CRM_Core_Error::isAPIError($error, CRM_Core_ERROR::DUPLICATE_CONTACT)) {
380 $matchedIDs = explode(',', $error['error_message']['params'][0]);
381 if (count($matchedIDs) > 1) {
382 array_unshift($values, 'Multiple matching contact records detected for this row. The contribution was not imported');
383 return CRM_Import_Parser::ERROR;
384 }
385 else {
386 $cid = $matchedIDs[0];
387 $formatted['contact_id'] = $cid;
388
389 $newContribution = civicrm_api('contribution', 'create', $formatted);
390 if (civicrm_error($newContribution)) {
391 if (is_array($newContribution['error_message'])) {
392 array_unshift($values, $newContribution['error_message']['message']);
393 if ($newContribution['error_message']['params'][0]) {
394 return CRM_Import_Parser::DUPLICATE;
395 }
396 }
397 else {
398 array_unshift($values, $newContribution['error_message']);
399 return CRM_Import_Parser::ERROR;
400 }
401 }
402
403 $this->_newContributions[] = $newContribution['id'];
404 $formatted['contribution_id'] = $newContribution['id'];
405
406 //return soft valid since we need to show how soft credits were added
407 if (!empty($formatted['soft_credit'])) {
408 return CRM_Contribute_Import_Parser::SOFT_CREDIT;
409 }
410
411 // process pledge payment assoc w/ the contribution
412 return self::processPledgePayments($formatted);
413
414 return CRM_Import_Parser::VALID;
415 }
416 }
417 else {
418 // Using new Dedupe rule.
419 $ruleParams = [
420 'contact_type' => $this->_contactType,
421 'used' => 'Unsupervised',
422 ];
423 $fieldsArray = CRM_Dedupe_BAO_Rule::dedupeRuleFields($ruleParams);
424 $disp = NULL;
425 foreach ($fieldsArray as $value) {
426 if (array_key_exists(trim($value), $params)) {
427 $paramValue = $params[trim($value)];
428 if (is_array($paramValue)) {
429 $disp .= $params[trim($value)][0][trim($value)] . " ";
430 }
431 else {
432 $disp .= $params[trim($value)] . " ";
433 }
434 }
435 }
436
437 if (!empty($params['external_identifier'])) {
438 if ($disp) {
439 $disp .= "AND {$params['external_identifier']}";
440 }
441 else {
442 $disp = $params['external_identifier'];
443 }
444 }
445
446 array_unshift($values, 'No matching Contact found for (' . $disp . ')');
447 return CRM_Import_Parser::ERROR;
448 }
449 }
450 else {
451 if (!empty($paramValues['external_identifier'])) {
452 $checkCid = new CRM_Contact_DAO_Contact();
453 $checkCid->external_identifier = $paramValues['external_identifier'];
454 $checkCid->find(TRUE);
455 if ($checkCid->id != $formatted['contact_id']) {
456 array_unshift($values, 'Mismatch of External ID:' . $paramValues['external_identifier'] . ' and Contact Id:' . $formatted['contact_id']);
457 return CRM_Import_Parser::ERROR;
458 }
459 }
460 $newContribution = civicrm_api('contribution', 'create', $formatted);
461 if (civicrm_error($newContribution)) {
462 if (is_array($newContribution['error_message'])) {
463 array_unshift($values, $newContribution['error_message']['message']);
464 if ($newContribution['error_message']['params'][0]) {
465 return CRM_Import_Parser::DUPLICATE;
466 }
467 }
468 else {
469 array_unshift($values, $newContribution['error_message']);
470 return CRM_Import_Parser::ERROR;
471 }
472 }
473
474 $this->_newContributions[] = $newContribution['id'];
475 $formatted['contribution_id'] = $newContribution['id'];
476
477 //return soft valid since we need to show how soft credits were added
478 if (!empty($formatted['soft_credit'])) {
479 return CRM_Contribute_Import_Parser::SOFT_CREDIT;
480 }
481
482 // process pledge payment assoc w/ the contribution
483 return self::processPledgePayments($formatted);
484
485 return CRM_Import_Parser::VALID;
486 }
487 }
488
489 /**
490 * Process pledge payments.
491 *
492 * @param array $formatted
493 *
494 * @return int
495 */
496 public function processPledgePayments(&$formatted) {
497 if (!empty($formatted['pledge_payment_id']) && !empty($formatted['pledge_id'])) {
498 //get completed status
499 $completeStatusID = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed');
500
501 //need to update payment record to map contribution_id
502 CRM_Core_DAO::setFieldValue('CRM_Pledge_DAO_PledgePayment', $formatted['pledge_payment_id'],
503 'contribution_id', $formatted['contribution_id']
504 );
505
506 CRM_Pledge_BAO_PledgePayment::updatePledgePaymentStatus($formatted['pledge_id'],
507 [$formatted['pledge_payment_id']],
508 $completeStatusID,
509 NULL,
510 $formatted['total_amount']
511 );
512
513 return CRM_Contribute_Import_Parser::PLEDGE_PAYMENT;
514 }
515 }
516
517 /**
518 * Get the array of successfully imported contribution id's
519 *
520 * @return array
521 */
522 public function &getImportedContributions() {
523 return $this->_newContributions;
524 }
525
526 /**
527 * The initializer code, called before the processing.
528 */
529 public function fini() {
530 }
531
532 /**
533 * Format date fields from input to mysql.
534 *
535 * @param array $params
536 *
537 * @return array
538 * Error messages, if any.
539 */
540 public function formatDateFields(&$params) {
541 $errorMessage = [];
542 $dateType = CRM_Core_Session::singleton()->get('dateTypes');
543 foreach ($params as $key => $val) {
544 if ($val) {
545 switch ($key) {
546 case 'receive_date':
547 if ($dateValue = CRM_Utils_Date::formatDate($params[$key], $dateType)) {
548 $params[$key] = $dateValue;
549 }
550 else {
551 $errorMessage[] = ts('Receive Date');
552 }
553 break;
554
555 case 'cancel_date':
556 if ($dateValue = CRM_Utils_Date::formatDate($params[$key], $dateType)) {
557 $params[$key] = $dateValue;
558 }
559 else {
560 $errorMessage[] = ts('Cancel Date');
561 }
562 break;
563
564 case 'receipt_date':
565 if ($dateValue = CRM_Utils_Date::formatDate($params[$key], $dateType)) {
566 $params[$key] = $dateValue;
567 }
568 else {
569 $errorMessage[] = ts('Receipt date');
570 }
571 break;
572
573 case 'thankyou_date':
574 if ($dateValue = CRM_Utils_Date::formatDate($params[$key], $dateType)) {
575 $params[$key] = $dateValue;
576 }
577 else {
578 $errorMessage[] = ts('Thankyou Date');
579 }
580 break;
581 }
582 }
583 }
584 return $errorMessage;
585 }
586
587 /**
588 * Format input params to suit api handling.
589 *
590 * Over time all the parts of deprecatedFormatParams
591 * and all the parts of the import function on this class that relate to
592 * reformatting input should be moved here and tests should be added in
593 * CRM_Contribute_Import_Parser_ContributionTest.
594 *
595 * @param array $params
596 * @param array $formatted
597 */
598 public function formatInput(&$params, &$formatted = []) {
599 $dateType = CRM_Core_Session::singleton()->get('dateTypes');
600 $customDataType = !empty($params['contact_type']) ? $params['contact_type'] : 'Contribution';
601 $customFields = CRM_Core_BAO_CustomField::getFields($customDataType);
602 // @todo call formatDateFields & move custom data handling there.
603 // Also note error handling for dates is currently in deprecatedFormatParams
604 // we should use the error handling in formatDateFields.
605 foreach ($params as $key => $val) {
606 // @todo - call formatDateFields instead.
607 if ($val) {
608 switch ($key) {
609 case 'receive_date':
610 case 'cancel_date':
611 case 'receipt_date':
612 case 'thankyou_date':
613 $params[$key] = CRM_Utils_Date::formatDate($params[$key], $dateType);
614 break;
615
616 case 'pledge_payment':
617 $params[$key] = CRM_Utils_String::strtobool($val);
618 break;
619
620 }
621 if ($customFieldID = CRM_Core_BAO_CustomField::getKeyID($key)) {
622 if ($customFields[$customFieldID]['data_type'] == 'Date') {
623 CRM_Contact_Import_Parser_Contact::formatCustomDate($params, $formatted, $dateType, $key);
624 unset($params[$key]);
625 }
626 elseif ($customFields[$customFieldID]['data_type'] == 'Boolean') {
627 $params[$key] = CRM_Utils_String::strtoboolstr($val);
628 }
629 }
630 }
631 }
632 }
633
634 /**
635 * take the input parameter list as specified in the data model and
636 * convert it into the same format that we use in QF and BAO object
637 *
638 * @param array $params
639 * Associative array of property name/value.
640 * pairs to insert in new contact.
641 * @param array $values
642 * The reformatted properties that we can use internally.
643 * '
644 *
645 * @param bool $create
646 * @param null $onDuplicate
647 *
648 * @return array|CRM_Error
649 */
650 private function deprecatedFormatParams($params, &$values, $create = FALSE, $onDuplicate = NULL) {
651 require_once 'CRM/Utils/DeprecatedUtils.php';
652 // copy all the contribution fields as is
653 require_once 'api/v3/utils.php';
654 $fields = CRM_Contribute_DAO_Contribution::fields();
655
656 _civicrm_api3_store_values($fields, $params, $values);
657
658 require_once 'CRM/Core/OptionGroup.php';
659 $customFields = CRM_Core_BAO_CustomField::getFields('Contribution', FALSE, FALSE, NULL, NULL, FALSE, FALSE, FALSE);
660
661 foreach ($params as $key => $value) {
662 // ignore empty values or empty arrays etc
663 if (CRM_Utils_System::isNull($value)) {
664 continue;
665 }
666
667 // Handling Custom Data
668 if ($customFieldID = CRM_Core_BAO_CustomField::getKeyID($key)) {
669 $values[$key] = $value;
670 $type = $customFields[$customFieldID]['html_type'];
671 if ($type == 'CheckBox' || $type == 'Multi-Select') {
672 $mulValues = explode(',', $value);
673 $customOption = CRM_Core_BAO_CustomOption::getCustomOption($customFieldID, TRUE);
674 $values[$key] = [];
675 foreach ($mulValues as $v1) {
676 foreach ($customOption as $customValueID => $customLabel) {
677 $customValue = $customLabel['value'];
678 if ((strtolower($customLabel['label']) == strtolower(trim($v1))) ||
679 (strtolower($customValue) == strtolower(trim($v1)))
680 ) {
681 if ($type == 'CheckBox') {
682 $values[$key][$customValue] = 1;
683 }
684 else {
685 $values[$key][] = $customValue;
686 }
687 }
688 }
689 }
690 }
691 elseif ($type == 'Select' || $type == 'Radio' ||
692 ($type == 'Autocomplete-Select' &&
693 $customFields[$customFieldID]['data_type'] == 'String'
694 )
695 ) {
696 $customOption = CRM_Core_BAO_CustomOption::getCustomOption($customFieldID, TRUE);
697 foreach ($customOption as $customFldID => $customValue) {
698 $val = CRM_Utils_Array::value('value', $customValue);
699 $label = CRM_Utils_Array::value('label', $customValue);
700 $label = strtolower($label);
701 $value = strtolower(trim($value));
702 if (($value == $label) || ($value == strtolower($val))) {
703 $values[$key] = $val;
704 }
705 }
706 }
707 }
708
709 switch ($key) {
710 case 'contribution_contact_id':
711 if (!CRM_Utils_Rule::integer($value)) {
712 return civicrm_api3_create_error("contact_id not valid: $value");
713 }
714 $dao = new CRM_Core_DAO();
715 $qParams = [];
716 $svq = $dao->singleValueQuery("SELECT is_deleted FROM civicrm_contact WHERE id = $value",
717 $qParams
718 );
719 if (!isset($svq)) {
720 return civicrm_api3_create_error("Invalid Contact ID: There is no contact record with contact_id = $value.");
721 }
722 elseif ($svq == 1) {
723 return civicrm_api3_create_error("Invalid Contact ID: contact_id $value is a soft-deleted contact.");
724 }
725
726 $values['contact_id'] = $values['contribution_contact_id'];
727 unset($values['contribution_contact_id']);
728 break;
729
730 case 'contact_type':
731 // import contribution record according to select contact type
732 require_once 'CRM/Contact/DAO/Contact.php';
733 $contactType = new CRM_Contact_DAO_Contact();
734 $contactId = CRM_Utils_Array::value('contribution_contact_id', $params);
735 $externalId = CRM_Utils_Array::value('external_identifier', $params);
736 $email = CRM_Utils_Array::value('email', $params);
737 //when insert mode check contact id or external identifier
738 if ($contactId || $externalId) {
739 $contactType->id = $contactId;
740 $contactType->external_identifier = $externalId;
741 if ($contactType->find(TRUE)) {
742 if ($params['contact_type'] != $contactType->contact_type) {
743 return civicrm_api3_create_error("Contact Type is wrong: $contactType->contact_type");
744 }
745 }
746 }
747 elseif ($email) {
748 if (!CRM_Utils_Rule::email($email)) {
749 return civicrm_api3_create_error("Invalid email address $email provided. Row was skipped");
750 }
751
752 // get the contact id from duplicate contact rule, if more than one contact is returned
753 // we should return error, since current interface allows only one-one mapping
754 $emailParams = ['email' => $email, 'contact_type' => $params['contact_type']];
755 $checkDedupe = _civicrm_api3_deprecated_duplicate_formatted_contact($emailParams);
756 if (!$checkDedupe['is_error']) {
757 return civicrm_api3_create_error("Invalid email address(doesn't exist) $email. Row was skipped");
758 }
759 else {
760 $matchingContactIds = explode(',', $checkDedupe['error_message']['params'][0]);
761 if (count($matchingContactIds) > 1) {
762 return civicrm_api3_create_error("Invalid email address(duplicate) $email. Row was skipped");
763 }
764 elseif (count($matchingContactIds) == 1) {
765 $params['contribution_contact_id'] = $matchingContactIds[0];
766 }
767 }
768 }
769 elseif (!empty($params['contribution_id']) || !empty($params['trxn_id']) || !empty($params['invoice_id'])) {
770 // when update mode check contribution id or trxn id or
771 // invoice id
772 $contactId = new CRM_Contribute_DAO_Contribution();
773 if (!empty($params['contribution_id'])) {
774 $contactId->id = $params['contribution_id'];
775 }
776 elseif (!empty($params['trxn_id'])) {
777 $contactId->trxn_id = $params['trxn_id'];
778 }
779 elseif (!empty($params['invoice_id'])) {
780 $contactId->invoice_id = $params['invoice_id'];
781 }
782 if ($contactId->find(TRUE)) {
783 $contactType->id = $contactId->contact_id;
784 if ($contactType->find(TRUE)) {
785 if ($params['contact_type'] != $contactType->contact_type) {
786 return civicrm_api3_create_error("Contact Type is wrong: $contactType->contact_type");
787 }
788 }
789 }
790 }
791 else {
792 if ($onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE) {
793 return civicrm_api3_create_error("Empty Contribution and Invoice and Transaction ID. Row was skipped.");
794 }
795 }
796 break;
797
798 case 'receive_date':
799 case 'cancel_date':
800 case 'receipt_date':
801 case 'thankyou_date':
802 if (!CRM_Utils_Rule::dateTime($value)) {
803 return civicrm_api3_create_error("$key not a valid date: $value");
804 }
805 break;
806
807 case 'non_deductible_amount':
808 case 'total_amount':
809 case 'fee_amount':
810 case 'net_amount':
811 if (!CRM_Utils_Rule::money($value)) {
812 return civicrm_api3_create_error("$key not a valid amount: $value");
813 }
814 break;
815
816 case 'currency':
817 if (!CRM_Utils_Rule::currencyCode($value)) {
818 return civicrm_api3_create_error("currency not a valid code: $value");
819 }
820 break;
821
822 case 'financial_type':
823 require_once 'CRM/Contribute/PseudoConstant.php';
824 $contriTypes = CRM_Contribute_PseudoConstant::financialType();
825 foreach ($contriTypes as $val => $type) {
826 if (strtolower($value) == strtolower($type)) {
827 $values['financial_type_id'] = $val;
828 break;
829 }
830 }
831 if (empty($values['financial_type_id'])) {
832 return civicrm_api3_create_error("Financial Type is not valid: $value");
833 }
834 break;
835
836 case 'payment_instrument':
837 require_once 'CRM/Core/PseudoConstant.php';
838 $values['payment_instrument_id'] = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'payment_instrument_id', $value);
839 if (empty($values['payment_instrument_id'])) {
840 return civicrm_api3_create_error("Payment Instrument is not valid: $value");
841 }
842 break;
843
844 case 'contribution_status_id':
845 require_once 'CRM/Core/PseudoConstant.php';
846 if (!$values['contribution_status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $value)) {
847 return civicrm_api3_create_error("Contribution Status is not valid: $value");
848 }
849 break;
850
851 case 'soft_credit':
852 // import contribution record according to select contact type
853 // validate contact id and external identifier.
854 $value[$key] = $mismatchContactType = $softCreditContactIds = '';
855 if (isset($params[$key]) && is_array($params[$key])) {
856 foreach ($params[$key] as $softKey => $softParam) {
857 $contactId = CRM_Utils_Array::value('contact_id', $softParam);
858 $externalId = CRM_Utils_Array::value('external_identifier', $softParam);
859 $email = CRM_Utils_Array::value('email', $softParam);
860 if ($contactId || $externalId) {
861 require_once 'CRM/Contact/DAO/Contact.php';
862 $contact = new CRM_Contact_DAO_Contact();
863 $contact->id = $contactId;
864 $contact->external_identifier = $externalId;
865 $errorMsg = NULL;
866 if (!$contact->find(TRUE)) {
867 $field = $contactId ? ts('Contact ID') : ts('External ID');
868 $errorMsg = ts("Soft Credit %1 - %2 doesn't exist. Row was skipped.",
869 [1 => $field, 2 => $contactId ? $contactId : $externalId]);
870 }
871
872 if ($errorMsg) {
873 return civicrm_api3_create_error($errorMsg);
874 }
875
876 // finally get soft credit contact id.
877 $values[$key][$softKey] = $softParam;
878 $values[$key][$softKey]['contact_id'] = $contact->id;
879 }
880 elseif ($email) {
881 if (!CRM_Utils_Rule::email($email)) {
882 return civicrm_api3_create_error("Invalid email address $email provided for Soft Credit. Row was skipped");
883 }
884
885 // get the contact id from duplicate contact rule, if more than one contact is returned
886 // we should return error, since current interface allows only one-one mapping
887 $emailParams = ['email' => $email, 'contact_type' => $params['contact_type']];
888 $checkDedupe = _civicrm_api3_deprecated_duplicate_formatted_contact($emailParams);
889 if (!$checkDedupe['is_error']) {
890 return civicrm_api3_create_error("Invalid email address(doesn't exist) $email for Soft Credit. Row was skipped");
891 }
892 else {
893 $matchingContactIds = explode(',', $checkDedupe['error_message']['params'][0]);
894 if (count($matchingContactIds) > 1) {
895 return civicrm_api3_create_error("Invalid email address(duplicate) $email for Soft Credit. Row was skipped");
896 }
897 elseif (count($matchingContactIds) == 1) {
898 $contactId = $matchingContactIds[0];
899 unset($softParam['email']);
900 $values[$key][$softKey] = $softParam + ['contact_id' => $contactId];
901 }
902 }
903 }
904 }
905 }
906 break;
907
908 case 'pledge_payment':
909 case 'pledge_id':
910
911 // giving respect to pledge_payment flag.
912 if (empty($params['pledge_payment'])) {
913 continue;
914 }
915
916 // get total amount of from import fields
917 $totalAmount = CRM_Utils_Array::value('total_amount', $params);
918
919 $onDuplicate = CRM_Utils_Array::value('onDuplicate', $params);
920
921 // we need to get contact id $contributionContactID to
922 // retrieve pledge details as well as to validate pledge ID
923
924 // first need to check for update mode
925 if ($onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE &&
926 ($params['contribution_id'] || $params['trxn_id'] || $params['invoice_id'])
927 ) {
928 $contribution = new CRM_Contribute_DAO_Contribution();
929 if ($params['contribution_id']) {
930 $contribution->id = $params['contribution_id'];
931 }
932 elseif ($params['trxn_id']) {
933 $contribution->trxn_id = $params['trxn_id'];
934 }
935 elseif ($params['invoice_id']) {
936 $contribution->invoice_id = $params['invoice_id'];
937 }
938
939 if ($contribution->find(TRUE)) {
940 $contributionContactID = $contribution->contact_id;
941 if (!$totalAmount) {
942 $totalAmount = $contribution->total_amount;
943 }
944 }
945 else {
946 return civicrm_api3_create_error('No match found for specified contact in pledge payment data. Row was skipped.');
947 }
948 }
949 else {
950 // first get the contact id for given contribution record.
951 if (!empty($params['contribution_contact_id'])) {
952 $contributionContactID = $params['contribution_contact_id'];
953 }
954 elseif (!empty($params['external_identifier'])) {
955 require_once 'CRM/Contact/DAO/Contact.php';
956 $contact = new CRM_Contact_DAO_Contact();
957 $contact->external_identifier = $params['external_identifier'];
958 if ($contact->find(TRUE)) {
959 $contributionContactID = $params['contribution_contact_id'] = $values['contribution_contact_id'] = $contact->id;
960 }
961 else {
962 return civicrm_api3_create_error('No match found for specified contact in pledge payment data. Row was skipped.');
963 }
964 }
965 else {
966 // we need to get contribution contact using de dupe
967 $error = _civicrm_api3_deprecated_check_contact_dedupe($params);
968
969 if (isset($error['error_message']['params'][0])) {
970 $matchedIDs = explode(',', $error['error_message']['params'][0]);
971
972 // check if only one contact is found
973 if (count($matchedIDs) > 1) {
974 return civicrm_api3_create_error($error['error_message']['message']);
975 }
976 else {
977 $contributionContactID = $params['contribution_contact_id'] = $values['contribution_contact_id'] = $matchedIDs[0];
978 }
979 }
980 else {
981 return civicrm_api3_create_error('No match found for specified contact in contribution data. Row was skipped.');
982 }
983 }
984 }
985
986 if (!empty($params['pledge_id'])) {
987 if (CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_Pledge', $params['pledge_id'], 'contact_id') != $contributionContactID) {
988 return civicrm_api3_create_error('Invalid Pledge ID provided. Contribution row was skipped.');
989 }
990 $values['pledge_id'] = $params['pledge_id'];
991 }
992 else {
993 // check if there are any pledge related to this contact, with payments pending or in progress
994 require_once 'CRM/Pledge/BAO/Pledge.php';
995 $pledgeDetails = CRM_Pledge_BAO_Pledge::getContactPledges($contributionContactID);
996
997 if (empty($pledgeDetails)) {
998 return civicrm_api3_create_error('No open pledges found for this contact. Contribution row was skipped.');
999 }
1000 elseif (count($pledgeDetails) > 1) {
1001 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.');
1002 }
1003
1004 // this mean we have only one pending / in progress pledge
1005 $values['pledge_id'] = $pledgeDetails[0];
1006 }
1007
1008 // we need to check if oldest payment amount equal to contribution amount
1009 require_once 'CRM/Pledge/BAO/PledgePayment.php';
1010 $pledgePaymentDetails = CRM_Pledge_BAO_PledgePayment::getOldestPledgePayment($values['pledge_id']);
1011
1012 if ($pledgePaymentDetails['amount'] == $totalAmount) {
1013 $values['pledge_payment_id'] = $pledgePaymentDetails['id'];
1014 }
1015 else {
1016 return civicrm_api3_create_error('Contribution and Pledge Payment amount mismatch for this record. Contribution row was skipped.');
1017 }
1018 break;
1019
1020 default:
1021 break;
1022 }
1023 }
1024
1025 if (array_key_exists('note', $params)) {
1026 $values['note'] = $params['note'];
1027 }
1028
1029 if ($create) {
1030 // CRM_Contribute_BAO_Contribution::add() handles contribution_source
1031 // So, if $values contains contribution_source, convert it to source
1032 $changes = ['contribution_source' => 'source'];
1033
1034 foreach ($changes as $orgVal => $changeVal) {
1035 if (isset($values[$orgVal])) {
1036 $values[$changeVal] = $values[$orgVal];
1037 unset($values[$orgVal]);
1038 }
1039 }
1040 }
1041
1042 return NULL;
1043 }
1044
1045 }