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