3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
19 * Class to parse contribution csv files.
21 class CRM_Contribute_Import_Parser_Contribution
extends CRM_Contribute_Import_Parser
{
23 protected $_mapperKeys;
25 private $_contactIdIndex;
26 private $_totalAmountIndex;
27 private $_contributionTypeIndex;
29 protected $_mapperSoftCredit;
30 //protected $_mapperPhoneType;
33 * Array of successfully imported contribution id's
37 protected $_newContributions;
43 * @param array $mapperSoftCredit
44 * @param null $mapperPhoneType
45 * @param array $mapperSoftCreditType
47 public function __construct(&$mapperKeys, $mapperSoftCredit = [], $mapperPhoneType = NULL, $mapperSoftCreditType = []) {
48 parent
::__construct();
49 $this->_mapperKeys
= &$mapperKeys;
50 $this->_mapperSoftCredit
= &$mapperSoftCredit;
51 $this->_mapperSoftCreditType
= &$mapperSoftCreditType;
55 * The initializer code, called before the processing
57 public function init() {
58 $fields = CRM_Contribute_BAO_Contribution
::importableFields($this->_contactType
, FALSE);
60 $fields = array_merge($fields,
63 'title' => ts('Soft Credit'),
65 'headerPattern' => '/Soft Credit/i',
70 // add pledge fields only if its is enabled
71 if (CRM_Core_Permission
::access('CiviPledge')) {
74 'title' => ts('Pledge Payment'),
75 'headerPattern' => '/Pledge Payment/i',
78 'title' => ts('Pledge ID'),
79 'headerPattern' => '/Pledge ID/i',
83 $fields = array_merge($fields, $pledgeFields);
85 foreach ($fields as $name => $field) {
86 $field['type'] = CRM_Utils_Array
::value('type', $field, CRM_Utils_Type
::T_INT
);
87 $field['dataPattern'] = CRM_Utils_Array
::value('dataPattern', $field, '//');
88 $field['headerPattern'] = CRM_Utils_Array
::value('headerPattern', $field, '//');
89 $this->addField($name, $field['title'], $field['type'], $field['headerPattern'], $field['dataPattern']);
92 $this->_newContributions
= [];
94 $this->setActiveFields($this->_mapperKeys
);
95 $this->setActiveFieldSoftCredit($this->_mapperSoftCredit
);
96 $this->setActiveFieldSoftCreditType($this->_mapperSoftCreditType
);
98 // FIXME: we should do this in one place together with Form/MapField.php
99 $this->_contactIdIndex
= -1;
100 $this->_totalAmountIndex
= -1;
101 $this->_contributionTypeIndex
= -1;
104 foreach ($this->_mapperKeys
as $key) {
106 case 'contribution_contact_id':
107 $this->_contactIdIndex
= $index;
111 $this->_totalAmountIndex
= $index;
114 case 'financial_type':
115 $this->_contributionTypeIndex
= $index;
123 * Handle the values in mapField mode.
125 * @param array $values
126 * The array of values belonging to this line.
130 public function mapField(&$values) {
131 return CRM_Import_Parser
::VALID
;
135 * Handle the values in preview mode.
137 * @param array $values
138 * The array of values belonging to this line.
141 * the result of this processing
143 public function preview(&$values) {
144 return $this->summary($values);
148 * Handle the values in summary mode.
150 * @param array $values
151 * The array of values belonging to this line.
154 * the result of this processing
156 public function summary(&$values) {
157 $erroneousField = NULL;
158 $response = $this->setActiveFieldValues($values, $erroneousField);
160 $params = &$this->getActiveFieldParams();
161 $errorMessage = NULL;
164 $errorMessage = implode('; ', $this->formatDateFields($params));
165 //date-Format part ends
167 $params['contact_type'] = 'Contribution';
169 //checking error in custom data
170 CRM_Contact_Import_Parser_Contact
::isErrorInCustomData($params, $errorMessage);
173 $tempMsg = "Invalid value for field(s) : $errorMessage";
174 array_unshift($values, $tempMsg);
175 $errorMessage = NULL;
176 return CRM_Import_Parser
::ERROR
;
179 return CRM_Import_Parser
::VALID
;
183 * Handle the values in import mode.
185 * @param int $onDuplicate
186 * The code for what action to take on duplicates.
187 * @param array $values
188 * The array of values belonging to this line.
191 * the result of this processing
193 public function import($onDuplicate, &$values) {
194 // first make sure this is a valid line
195 $response = $this->summary($values);
196 if ($response != CRM_Import_Parser
::VALID
) {
200 $params = &$this->getActiveFieldParams();
201 $formatted = ['version' => 3, 'skipRecentView' => TRUE, 'skipCleanMoney' => FALSE];
204 if (isset($params['total_amount']) && $params['total_amount'] == 0) {
205 $params['total_amount'] = '0.00';
207 $this->formatInput($params, $formatted);
209 static $indieFields = NULL;
210 if ($indieFields == NULL) {
211 $tempIndieFields = CRM_Contribute_DAO_Contribution
::import();
212 $indieFields = $tempIndieFields;
216 foreach ($params as $key => $field) {
217 if ($field == NULL ||
$field === '') {
220 $paramValues[$key] = $field;
223 //import contribution record according to select contact type
224 if ($onDuplicate == CRM_Import_Parser
::DUPLICATE_SKIP
&&
225 (!empty($paramValues['contribution_contact_id']) ||
!empty($paramValues['external_identifier']))
227 $paramValues['contact_type'] = $this->_contactType
;
229 elseif ($onDuplicate == CRM_Import_Parser
::DUPLICATE_UPDATE
&&
230 (!empty($paramValues['contribution_id']) ||
!empty($values['trxn_id']) ||
!empty($paramValues['invoice_id']))
232 $paramValues['contact_type'] = $this->_contactType
;
234 elseif (!empty($params['soft_credit'])) {
235 $paramValues['contact_type'] = $this->_contactType
;
237 elseif (!empty($paramValues['pledge_payment'])) {
238 $paramValues['contact_type'] = $this->_contactType
;
241 //need to pass $onDuplicate to check import mode.
242 if (!empty($paramValues['pledge_payment'])) {
243 $paramValues['onDuplicate'] = $onDuplicate;
245 $formatError = $this->deprecatedFormatParams($paramValues, $formatted, TRUE, $onDuplicate);
248 array_unshift($values, $formatError['error_message']);
249 if (CRM_Utils_Array
::value('error_data', $formatError) == 'soft_credit') {
250 return CRM_Contribute_Import_Parser
::SOFT_CREDIT_ERROR
;
252 elseif (CRM_Utils_Array
::value('error_data', $formatError) == 'pledge_payment') {
253 return CRM_Contribute_Import_Parser
::PLEDGE_PAYMENT_ERROR
;
255 return CRM_Import_Parser
::ERROR
;
258 if ($onDuplicate != CRM_Import_Parser
::DUPLICATE_UPDATE
) {
259 $formatted['custom'] = CRM_Core_BAO_CustomField
::postProcess($formatted,
265 //fix for CRM-2219 - Update Contribution
266 // onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE
267 if (!empty($paramValues['invoice_id']) ||
!empty($paramValues['trxn_id']) ||
!empty($paramValues['contribution_id'])) {
269 'id' => $paramValues['contribution_id'] ??
NULL,
270 'trxn_id' => $paramValues['trxn_id'] ??
NULL,
271 'invoice_id' => $paramValues['invoice_id'] ??
NULL,
274 $ids['contribution'] = CRM_Contribute_BAO_Contribution
::checkDuplicateIds($dupeIds);
276 if ($ids['contribution']) {
277 $formatted['id'] = $ids['contribution'];
278 $formatted['custom'] = CRM_Core_BAO_CustomField
::postProcess($formatted,
283 if (!empty($paramValues['note'])) {
285 $contactID = CRM_Core_DAO
::getFieldValue('CRM_Contribute_DAO_Contribution', $ids['contribution'], 'contact_id');
286 $daoNote = new CRM_Core_BAO_Note();
287 $daoNote->entity_table
= 'civicrm_contribution';
288 $daoNote->entity_id
= $ids['contribution'];
289 if ($daoNote->find(TRUE)) {
290 $noteID['id'] = $daoNote->id
;
294 'entity_table' => 'civicrm_contribution',
295 'note' => $paramValues['note'],
296 'entity_id' => $ids['contribution'],
297 'contact_id' => $contactID,
299 CRM_Core_BAO_Note
::add($noteParams, $noteID);
300 unset($formatted['note']);
303 //need to check existing soft credit contribution, CRM-3968
304 if (!empty($formatted['soft_credit'])) {
306 'contact_id' => $formatted['soft_credit'],
307 'contribution_id' => $ids['contribution'],
310 //Delete all existing soft Contribution from contribution_soft table for pcp_id is_null
311 $existingSoftCredit = CRM_Contribute_BAO_ContributionSoft
::getSoftContribution($dupeSoftCredit['contribution_id']);
312 if (isset($existingSoftCredit['soft_credit']) && !empty($existingSoftCredit['soft_credit'])) {
313 foreach ($existingSoftCredit['soft_credit'] as $key => $existingSoftCreditValues) {
314 if (!empty($existingSoftCreditValues['soft_credit_id'])) {
315 civicrm_api3('ContributionSoft', 'delete', [
316 'id' => $existingSoftCreditValues['soft_credit_id'],
324 $formatted['id'] = $ids['contribution'];
325 $newContribution = CRM_Contribute_BAO_Contribution
::create($formatted);
326 $this->_newContributions
[] = $newContribution->id
;
328 //return soft valid since we need to show how soft credits were added
329 if (!empty($formatted['soft_credit'])) {
330 return CRM_Contribute_Import_Parser
::SOFT_CREDIT
;
333 // process pledge payment assoc w/ the contribution
334 return self
::processPledgePayments($formatted);
336 return CRM_Import_Parser
::VALID
;
340 'id' => 'Contribution ID',
341 'trxn_id' => 'Transaction ID',
342 'invoice_id' => 'Invoice ID',
344 foreach ($dupeIds as $k => $v) {
346 $errorMsg[] = "$labels[$k] $v";
349 $errorMsg = implode(' AND ', $errorMsg);
350 array_unshift($values, 'Matching Contribution record not found for ' . $errorMsg . '. Row was skipped.');
351 return CRM_Import_Parser
::ERROR
;
356 if ($this->_contactIdIndex
< 0) {
357 // set the contact type if its not set
358 if (!isset($paramValues['contact_type'])) {
359 $paramValues['contact_type'] = $this->_contactType
;
362 $error = $this->checkContactDuplicate($paramValues);
364 if (CRM_Core_Error
::isAPIError($error, CRM_Core_ERROR
::DUPLICATE_CONTACT
)) {
365 $matchedIDs = explode(',', $error['error_message']['params'][0]);
366 if (count($matchedIDs) > 1) {
367 array_unshift($values, 'Multiple matching contact records detected for this row. The contribution was not imported');
368 return CRM_Import_Parser
::ERROR
;
371 $cid = $matchedIDs[0];
372 $formatted['contact_id'] = $cid;
374 $newContribution = civicrm_api('contribution', 'create', $formatted);
375 if (civicrm_error($newContribution)) {
376 if (is_array($newContribution['error_message'])) {
377 array_unshift($values, $newContribution['error_message']['message']);
378 if ($newContribution['error_message']['params'][0]) {
379 return CRM_Import_Parser
::DUPLICATE
;
383 array_unshift($values, $newContribution['error_message']);
384 return CRM_Import_Parser
::ERROR
;
388 $this->_newContributions
[] = $newContribution['id'];
389 $formatted['contribution_id'] = $newContribution['id'];
391 //return soft valid since we need to show how soft credits were added
392 if (!empty($formatted['soft_credit'])) {
393 return CRM_Contribute_Import_Parser
::SOFT_CREDIT
;
396 // process pledge payment assoc w/ the contribution
397 return self
::processPledgePayments($formatted);
399 return CRM_Import_Parser
::VALID
;
403 // Using new Dedupe rule.
405 'contact_type' => $this->_contactType
,
406 'used' => 'Unsupervised',
408 $fieldsArray = CRM_Dedupe_BAO_Rule
::dedupeRuleFields($ruleParams);
410 foreach ($fieldsArray as $value) {
411 if (array_key_exists(trim($value), $params)) {
412 $paramValue = $params[trim($value)];
413 if (is_array($paramValue)) {
414 $disp .= $params[trim($value)][0][trim($value)] . " ";
417 $disp .= $params[trim($value)] . " ";
422 if (!empty($params['external_identifier'])) {
424 $disp .= "AND {$params['external_identifier']}";
427 $disp = $params['external_identifier'];
431 array_unshift($values, 'No matching Contact found for (' . $disp . ')');
432 return CRM_Import_Parser
::ERROR
;
436 if (!empty($paramValues['external_identifier'])) {
437 $checkCid = new CRM_Contact_DAO_Contact();
438 $checkCid->external_identifier
= $paramValues['external_identifier'];
439 $checkCid->find(TRUE);
440 if ($checkCid->id
!= $formatted['contact_id']) {
441 array_unshift($values, 'Mismatch of External ID:' . $paramValues['external_identifier'] . ' and Contact Id:' . $formatted['contact_id']);
442 return CRM_Import_Parser
::ERROR
;
445 $newContribution = civicrm_api('contribution', 'create', $formatted);
446 if (civicrm_error($newContribution)) {
447 if (is_array($newContribution['error_message'])) {
448 array_unshift($values, $newContribution['error_message']['message']);
449 if ($newContribution['error_message']['params'][0]) {
450 return CRM_Import_Parser
::DUPLICATE
;
454 array_unshift($values, $newContribution['error_message']);
455 return CRM_Import_Parser
::ERROR
;
459 $this->_newContributions
[] = $newContribution['id'];
460 $formatted['contribution_id'] = $newContribution['id'];
462 //return soft valid since we need to show how soft credits were added
463 if (!empty($formatted['soft_credit'])) {
464 return CRM_Contribute_Import_Parser
::SOFT_CREDIT
;
467 // process pledge payment assoc w/ the contribution
468 return self
::processPledgePayments($formatted);
470 return CRM_Import_Parser
::VALID
;
475 * Process pledge payments.
477 * @param array $formatted
481 public function processPledgePayments(&$formatted) {
482 if (!empty($formatted['pledge_payment_id']) && !empty($formatted['pledge_id'])) {
483 //get completed status
484 $completeStatusID = CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed');
486 //need to update payment record to map contribution_id
487 CRM_Core_DAO
::setFieldValue('CRM_Pledge_DAO_PledgePayment', $formatted['pledge_payment_id'],
488 'contribution_id', $formatted['contribution_id']
491 CRM_Pledge_BAO_PledgePayment
::updatePledgePaymentStatus($formatted['pledge_id'],
492 [$formatted['pledge_payment_id']],
495 $formatted['total_amount']
498 return CRM_Contribute_Import_Parser
::PLEDGE_PAYMENT
;
503 * Get the array of successfully imported contribution id's
507 public function &getImportedContributions() {
508 return $this->_newContributions
;
512 * The initializer code, called before the processing.
514 public function fini() {
518 * Format date fields from input to mysql.
520 * @param array $params
523 * Error messages, if any.
525 public function formatDateFields(&$params) {
527 $dateType = CRM_Core_Session
::singleton()->get('dateTypes');
528 foreach ($params as $key => $val) {
532 if ($dateValue = CRM_Utils_Date
::formatDate($params[$key], $dateType)) {
533 $params[$key] = $dateValue;
536 $errorMessage[] = ts('Receive Date');
541 if ($dateValue = CRM_Utils_Date
::formatDate($params[$key], $dateType)) {
542 $params[$key] = $dateValue;
545 $errorMessage[] = ts('Cancel Date');
550 if ($dateValue = CRM_Utils_Date
::formatDate($params[$key], $dateType)) {
551 $params[$key] = $dateValue;
554 $errorMessage[] = ts('Receipt date');
558 case 'thankyou_date':
559 if ($dateValue = CRM_Utils_Date
::formatDate($params[$key], $dateType)) {
560 $params[$key] = $dateValue;
563 $errorMessage[] = ts('Thankyou Date');
569 return $errorMessage;
573 * Format input params to suit api handling.
575 * Over time all the parts of deprecatedFormatParams
576 * and all the parts of the import function on this class that relate to
577 * reformatting input should be moved here and tests should be added in
578 * CRM_Contribute_Import_Parser_ContributionTest.
580 * @param array $params
581 * @param array $formatted
583 public function formatInput(&$params, &$formatted = []) {
584 $dateType = CRM_Core_Session
::singleton()->get('dateTypes');
585 $customDataType = !empty($params['contact_type']) ?
$params['contact_type'] : 'Contribution';
586 $customFields = CRM_Core_BAO_CustomField
::getFields($customDataType);
587 // @todo call formatDateFields & move custom data handling there.
588 // Also note error handling for dates is currently in deprecatedFormatParams
589 // we should use the error handling in formatDateFields.
590 foreach ($params as $key => $val) {
591 // @todo - call formatDateFields instead.
597 case 'thankyou_date':
598 $params[$key] = CRM_Utils_Date
::formatDate($params[$key], $dateType);
601 case 'pledge_payment':
602 $params[$key] = CRM_Utils_String
::strtobool($val);
606 if ($customFieldID = CRM_Core_BAO_CustomField
::getKeyID($key)) {
607 if ($customFields[$customFieldID]['data_type'] == 'Date') {
608 CRM_Contact_Import_Parser_Contact
::formatCustomDate($params, $formatted, $dateType, $key);
609 unset($params[$key]);
611 elseif ($customFields[$customFieldID]['data_type'] == 'Boolean') {
612 $params[$key] = CRM_Utils_String
::strtoboolstr($val);
620 * take the input parameter list as specified in the data model and
621 * convert it into the same format that we use in QF and BAO object
623 * @param array $params
624 * Associative array of property name/value.
625 * pairs to insert in new contact.
626 * @param array $values
627 * The reformatted properties that we can use internally.
630 * @param bool $create
631 * @param null $onDuplicate
633 * @return array|CRM_Error
635 private function deprecatedFormatParams($params, &$values, $create = FALSE, $onDuplicate = NULL) {
636 require_once 'CRM/Utils/DeprecatedUtils.php';
637 // copy all the contribution fields as is
638 require_once 'api/v3/utils.php';
639 $fields = CRM_Core_DAO
::getExportableFieldsWithPseudoConstants('CRM_Contribute_BAO_Contribution');
641 _civicrm_api3_store_values($fields, $params, $values);
643 $customFields = CRM_Core_BAO_CustomField
::getFields('Contribution', FALSE, FALSE, NULL, NULL, FALSE, FALSE, FALSE);
645 foreach ($params as $key => $value) {
646 // ignore empty values or empty arrays etc
647 if (CRM_Utils_System
::isNull($value)) {
651 // Handling Custom Data
652 if ($customFieldID = CRM_Core_BAO_CustomField
::getKeyID($key)) {
653 $values[$key] = $value;
654 $type = $customFields[$customFieldID]['html_type'];
655 if (CRM_Core_BAO_CustomField
::isSerialized($customFields[$customFieldID])) {
656 $values[$key] = self
::unserializeCustomValue($customFieldID, $value, $type);
658 elseif ($type == 'Select' ||
$type == 'Radio' ||
659 ($type == 'Autocomplete-Select' &&
660 $customFields[$customFieldID]['data_type'] == 'String'
663 $customOption = CRM_Core_BAO_CustomOption
::getCustomOption($customFieldID, TRUE);
664 foreach ($customOption as $customFldID => $customValue) {
665 $val = $customValue['value'] ??
NULL;
666 $label = $customValue['label'] ??
NULL;
667 $label = strtolower($label);
668 $value = strtolower(trim($value));
669 if (($value == $label) ||
($value == strtolower($val))) {
670 $values[$key] = $val;
677 case 'contribution_contact_id':
678 if (!CRM_Utils_Rule
::integer($value)) {
679 return civicrm_api3_create_error("contact_id not valid: $value");
681 $dao = new CRM_Core_DAO();
683 $svq = $dao->singleValueQuery("SELECT is_deleted FROM civicrm_contact WHERE id = $value",
687 return civicrm_api3_create_error("Invalid Contact ID: There is no contact record with contact_id = $value.");
690 return civicrm_api3_create_error("Invalid Contact ID: contact_id $value is a soft-deleted contact.");
693 $values['contact_id'] = $values['contribution_contact_id'];
694 unset($values['contribution_contact_id']);
698 // import contribution record according to select contact type
699 require_once 'CRM/Contact/DAO/Contact.php';
700 $contactType = new CRM_Contact_DAO_Contact();
701 $contactId = $params['contribution_contact_id'] ??
NULL;
702 $externalId = $params['external_identifier'] ??
NULL;
703 $email = $params['email'] ??
NULL;
704 //when insert mode check contact id or external identifier
705 if ($contactId ||
$externalId) {
706 $contactType->id
= $contactId;
707 $contactType->external_identifier
= $externalId;
708 if ($contactType->find(TRUE)) {
709 if ($params['contact_type'] != $contactType->contact_type
) {
710 return civicrm_api3_create_error("Contact Type is wrong: $contactType->contact_type");
715 if (!CRM_Utils_Rule
::email($email)) {
716 return civicrm_api3_create_error("Invalid email address $email provided. Row was skipped");
719 // get the contact id from duplicate contact rule, if more than one contact is returned
720 // we should return error, since current interface allows only one-one mapping
721 $emailParams = ['email' => $email, 'contact_type' => $params['contact_type']];
722 $checkDedupe = _civicrm_api3_deprecated_duplicate_formatted_contact($emailParams);
723 if (!$checkDedupe['is_error']) {
724 return civicrm_api3_create_error("Invalid email address(doesn't exist) $email. Row was skipped");
727 $matchingContactIds = explode(',', $checkDedupe['error_message']['params'][0]);
728 if (count($matchingContactIds) > 1) {
729 return civicrm_api3_create_error("Invalid email address(duplicate) $email. Row was skipped");
731 elseif (count($matchingContactIds) == 1) {
732 $params['contribution_contact_id'] = $matchingContactIds[0];
736 elseif (!empty($params['contribution_id']) ||
!empty($params['trxn_id']) ||
!empty($params['invoice_id'])) {
737 // when update mode check contribution id or trxn id or
739 $contactId = new CRM_Contribute_DAO_Contribution();
740 if (!empty($params['contribution_id'])) {
741 $contactId->id
= $params['contribution_id'];
743 elseif (!empty($params['trxn_id'])) {
744 $contactId->trxn_id
= $params['trxn_id'];
746 elseif (!empty($params['invoice_id'])) {
747 $contactId->invoice_id
= $params['invoice_id'];
749 if ($contactId->find(TRUE)) {
750 $contactType->id
= $contactId->contact_id
;
751 if ($contactType->find(TRUE)) {
752 if ($params['contact_type'] != $contactType->contact_type
) {
753 return civicrm_api3_create_error("Contact Type is wrong: $contactType->contact_type");
759 if ($onDuplicate == CRM_Import_Parser
::DUPLICATE_UPDATE
) {
760 return civicrm_api3_create_error("Empty Contribution and Invoice and Transaction ID. Row was skipped.");
768 case 'thankyou_date':
769 if (!CRM_Utils_Rule
::dateTime($value)) {
770 return civicrm_api3_create_error("$key not a valid date: $value");
774 case 'non_deductible_amount':
778 // @todo add test like testPaymentTypeLabel & remove these lines as we can anticipate error will still be caught & handled.
779 if (!CRM_Utils_Rule
::money($value)) {
780 return civicrm_api3_create_error("$key not a valid amount: $value");
785 if (!CRM_Utils_Rule
::currencyCode($value)) {
786 return civicrm_api3_create_error("currency not a valid code: $value");
790 case 'financial_type':
791 // @todo add test like testPaymentTypeLabel & remove these lines in favour of 'default' part of switch.
792 require_once 'CRM/Contribute/PseudoConstant.php';
793 $contriTypes = CRM_Contribute_PseudoConstant
::financialType();
794 foreach ($contriTypes as $val => $type) {
795 if (strtolower($value) == strtolower($type)) {
796 $values['financial_type_id'] = $val;
800 if (empty($values['financial_type_id'])) {
801 return civicrm_api3_create_error("Financial Type is not valid: $value");
806 // import contribution record according to select contact type
807 // validate contact id and external identifier.
808 $value[$key] = $mismatchContactType = $softCreditContactIds = '';
809 if (isset($params[$key]) && is_array($params[$key])) {
810 foreach ($params[$key] as $softKey => $softParam) {
811 $contactId = $softParam['contact_id'] ??
NULL;
812 $externalId = $softParam['external_identifier'] ??
NULL;
813 $email = $softParam['email'] ??
NULL;
814 if ($contactId ||
$externalId) {
815 require_once 'CRM/Contact/DAO/Contact.php';
816 $contact = new CRM_Contact_DAO_Contact();
817 $contact->id
= $contactId;
818 $contact->external_identifier
= $externalId;
820 if (!$contact->find(TRUE)) {
821 $field = $contactId ?
ts('Contact ID') : ts('External ID');
822 $errorMsg = ts("Soft Credit %1 - %2 doesn't exist. Row was skipped.",
823 [1 => $field, 2 => $contactId ?
$contactId : $externalId]);
827 return civicrm_api3_create_error($errorMsg);
830 // finally get soft credit contact id.
831 $values[$key][$softKey] = $softParam;
832 $values[$key][$softKey]['contact_id'] = $contact->id
;
835 if (!CRM_Utils_Rule
::email($email)) {
836 return civicrm_api3_create_error("Invalid email address $email provided for Soft Credit. Row was skipped");
839 // get the contact id from duplicate contact rule, if more than one contact is returned
840 // we should return error, since current interface allows only one-one mapping
841 $emailParams = ['email' => $email, 'contact_type' => $params['contact_type']];
842 $checkDedupe = _civicrm_api3_deprecated_duplicate_formatted_contact($emailParams);
843 if (!$checkDedupe['is_error']) {
844 return civicrm_api3_create_error("Invalid email address(doesn't exist) $email for Soft Credit. Row was skipped");
847 $matchingContactIds = explode(',', $checkDedupe['error_message']['params'][0]);
848 if (count($matchingContactIds) > 1) {
849 return civicrm_api3_create_error("Invalid email address(duplicate) $email for Soft Credit. Row was skipped");
851 elseif (count($matchingContactIds) == 1) {
852 $contactId = $matchingContactIds[0];
853 unset($softParam['email']);
854 $values[$key][$softKey] = $softParam +
['contact_id' => $contactId];
862 case 'pledge_payment':
865 // giving respect to pledge_payment flag.
866 if (empty($params['pledge_payment'])) {
870 // get total amount of from import fields
871 $totalAmount = $params['total_amount'] ??
NULL;
873 $onDuplicate = $params['onDuplicate'] ??
NULL;
875 // we need to get contact id $contributionContactID to
876 // retrieve pledge details as well as to validate pledge ID
878 // first need to check for update mode
879 if ($onDuplicate == CRM_Import_Parser
::DUPLICATE_UPDATE
&&
880 ($params['contribution_id'] ||
$params['trxn_id'] ||
$params['invoice_id'])
882 $contribution = new CRM_Contribute_DAO_Contribution();
883 if ($params['contribution_id']) {
884 $contribution->id
= $params['contribution_id'];
886 elseif ($params['trxn_id']) {
887 $contribution->trxn_id
= $params['trxn_id'];
889 elseif ($params['invoice_id']) {
890 $contribution->invoice_id
= $params['invoice_id'];
893 if ($contribution->find(TRUE)) {
894 $contributionContactID = $contribution->contact_id
;
896 $totalAmount = $contribution->total_amount
;
900 return civicrm_api3_create_error('No match found for specified contact in pledge payment data. Row was skipped.');
904 // first get the contact id for given contribution record.
905 if (!empty($params['contribution_contact_id'])) {
906 $contributionContactID = $params['contribution_contact_id'];
908 elseif (!empty($params['external_identifier'])) {
909 require_once 'CRM/Contact/DAO/Contact.php';
910 $contact = new CRM_Contact_DAO_Contact();
911 $contact->external_identifier
= $params['external_identifier'];
912 if ($contact->find(TRUE)) {
913 $contributionContactID = $params['contribution_contact_id'] = $values['contribution_contact_id'] = $contact->id
;
916 return civicrm_api3_create_error('No match found for specified contact in pledge payment data. Row was skipped.');
920 // we need to get contribution contact using de dupe
921 $error = _civicrm_api3_deprecated_check_contact_dedupe($params);
923 if (isset($error['error_message']['params'][0])) {
924 $matchedIDs = explode(',', $error['error_message']['params'][0]);
926 // check if only one contact is found
927 if (count($matchedIDs) > 1) {
928 return civicrm_api3_create_error($error['error_message']['message']);
931 $contributionContactID = $params['contribution_contact_id'] = $values['contribution_contact_id'] = $matchedIDs[0];
935 return civicrm_api3_create_error('No match found for specified contact in contribution data. Row was skipped.');
940 if (!empty($params['pledge_id'])) {
941 if (CRM_Core_DAO
::getFieldValue('CRM_Pledge_DAO_Pledge', $params['pledge_id'], 'contact_id') != $contributionContactID) {
942 return civicrm_api3_create_error('Invalid Pledge ID provided. Contribution row was skipped.');
944 $values['pledge_id'] = $params['pledge_id'];
947 // check if there are any pledge related to this contact, with payments pending or in progress
948 require_once 'CRM/Pledge/BAO/Pledge.php';
949 $pledgeDetails = CRM_Pledge_BAO_Pledge
::getContactPledges($contributionContactID);
951 if (empty($pledgeDetails)) {
952 return civicrm_api3_create_error('No open pledges found for this contact. Contribution row was skipped.');
954 elseif (count($pledgeDetails) > 1) {
955 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.');
958 // this mean we have only one pending / in progress pledge
959 $values['pledge_id'] = $pledgeDetails[0];
962 // we need to check if oldest payment amount equal to contribution amount
963 require_once 'CRM/Pledge/BAO/PledgePayment.php';
964 $pledgePaymentDetails = CRM_Pledge_BAO_PledgePayment
::getOldestPledgePayment($values['pledge_id']);
966 if ($pledgePaymentDetails['amount'] == $totalAmount) {
967 $values['pledge_payment_id'] = $pledgePaymentDetails['id'];
970 return civicrm_api3_create_error('Contribution and Pledge Payment amount mismatch for this record. Contribution row was skipped.');
975 // Hande name or label for fields with options.
976 if (isset($fields[$key]) &&
977 // Yay - just for a surprise we are inconsistent on whether we pass the pseudofield (payment_instrument)
978 // or the field name (contribution_status_id)
979 (!empty($fields[$key]['is_pseudofield_for']) ||
!empty($fields[$key]['pseudoconstant']))
981 $realField = $fields[$key]['is_pseudofield_for'] ??
$key;
982 $realFieldSpec = $fields[$realField];
983 $values[$key] = $this->parsePseudoConstantField($value, $realFieldSpec);
989 if (array_key_exists('note', $params)) {
990 $values['note'] = $params['note'];
994 // CRM_Contribute_BAO_Contribution::add() handles contribution_source
995 // So, if $values contains contribution_source, convert it to source
996 $changes = ['contribution_source' => 'source'];
998 foreach ($changes as $orgVal => $changeVal) {
999 if (isset($values[$orgVal])) {
1000 $values[$changeVal] = $values[$orgVal];
1001 unset($values[$orgVal]);