Merge pull request #4892 from colemanw/INFRA-132
[civicrm-core.git] / CRM / Contribute / Import / Parser / Contribution.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
39de6fd5 4 | CiviCRM version 4.6 |
6a488035 5 +--------------------------------------------------------------------+
06b69b18 6 | Copyright CiviCRM LLC (c) 2004-2014 |
6a488035
TO
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
06b69b18 31 * @copyright CiviCRM LLC (c) 2004-2014
6a488035
TO
32 * $Id$
33 *
34 */
35
36/**
37 * class to parse contribution csv files
38 */
39class CRM_Contribute_Import_Parser_Contribution extends CRM_Contribute_Import_Parser {
40
41 protected $_mapperKeys;
42
43 private $_contactIdIndex;
44 private $_totalAmountIndex;
45 private $_contributionTypeIndex;
46
47 protected $_mapperSoftCredit;
48 //protected $_mapperPhoneType;
49
50 /**
ceb10dc7 51 * Array of successfully imported contribution id's
6a488035
TO
52 *
53 * @array
54 */
55 protected $_newContributions;
56
57 /**
100fef9d 58 * Class constructor
6a488035 59 */
00be9182 60 public function __construct(&$mapperKeys, $mapperSoftCredit = NULL, $mapperPhoneType = NULL, $mapperSoftCreditType = NULL) {
6a488035
TO
61 parent::__construct();
62 $this->_mapperKeys = &$mapperKeys;
63 $this->_mapperSoftCredit = &$mapperSoftCredit;
1221efe9 64 $this->_mapperSoftCreditType = &$mapperSoftCreditType;
6a488035
TO
65 }
66
67 /**
100fef9d 68 * The initializer code, called before the processing
6a488035
TO
69 *
70 * @return void
6a488035 71 */
00be9182 72 public function init() {
6a488035
TO
73 $fields = CRM_Contribute_BAO_Contribution::importableFields($this->_contactType, FALSE);
74
75 $fields = array_merge($fields,
91bb24a7 76 array(
77 'soft_credit' => array(
78 'title' => ts('Soft Credit'),
79 'softCredit' => TRUE,
80 'headerPattern' => '/Soft Credit/i',
21dfd5f5 81 ),
91bb24a7 82 )
6a488035
TO
83 );
84
85 // add pledge fields only if its is enabled
86 if (CRM_Core_Permission::access('CiviPledge')) {
91bb24a7 87 $pledgeFields = array(
88 'pledge_payment' => array(
89 'title' => ts('Pledge Payment'),
6a488035
TO
90 'headerPattern' => '/Pledge Payment/i',
91 ),
91bb24a7 92 'pledge_id' => array(
93 'title' => ts('Pledge ID'),
6a488035
TO
94 'headerPattern' => '/Pledge ID/i',
95 ),
96 );
97
98 $fields = array_merge($fields, $pledgeFields);
99 }
100 foreach ($fields as $name => $field) {
101 $field['type'] = CRM_Utils_Array::value('type', $field, CRM_Utils_Type::T_INT);
102 $field['dataPattern'] = CRM_Utils_Array::value('dataPattern', $field, '//');
103 $field['headerPattern'] = CRM_Utils_Array::value('headerPattern', $field, '//');
104 $this->addField($name, $field['title'], $field['type'], $field['headerPattern'], $field['dataPattern']);
105 }
106
107 $this->_newContributions = array();
108
109 $this->setActiveFields($this->_mapperKeys);
110 $this->setActiveFieldSoftCredit($this->_mapperSoftCredit);
1221efe9 111 $this->setActiveFieldSoftCreditType($this->_mapperSoftCreditType);
6a488035
TO
112
113 // FIXME: we should do this in one place together with Form/MapField.php
114 $this->_contactIdIndex = -1;
115 $this->_totalAmountIndex = -1;
116 $this->_contributionTypeIndex = -1;
117
118 $index = 0;
119 foreach ($this->_mapperKeys as $key) {
120 switch ($key) {
121 case 'contribution_contact_id':
122 $this->_contactIdIndex = $index;
123 break;
124
125 case 'total_amount':
126 $this->_totalAmountIndex = $index;
127 break;
128
129 case 'financial_type':
130 $this->_contributionTypeIndex = $index;
131 break;
132 }
133 $index++;
134 }
135 }
136
137 /**
100fef9d 138 * Handle the values in mapField mode
6a488035 139 *
014c4014
TO
140 * @param array $values
141 * The array of values belonging to this line.
6a488035
TO
142 *
143 * @return boolean
6a488035 144 */
00be9182 145 public function mapField(&$values) {
a05662ef 146 return CRM_Import_Parser::VALID;
6a488035
TO
147 }
148
149 /**
100fef9d 150 * Handle the values in preview mode
6a488035 151 *
014c4014
TO
152 * @param array $values
153 * The array of values belonging to this line.
6a488035
TO
154 *
155 * @return boolean the result of this processing
6a488035 156 */
00be9182 157 public function preview(&$values) {
6a488035
TO
158 return $this->summary($values);
159 }
160
161 /**
100fef9d 162 * Handle the values in summary mode
6a488035 163 *
014c4014
TO
164 * @param array $values
165 * The array of values belonging to this line.
6a488035
TO
166 *
167 * @return boolean the result of this processing
6a488035 168 */
00be9182 169 public function summary(&$values) {
6a488035
TO
170 $erroneousField = NULL;
171 $response = $this->setActiveFieldValues($values, $erroneousField);
172
173 $params = &$this->getActiveFieldParams();
174 $errorMessage = NULL;
175
176 //for date-Formats
177 $session = CRM_Core_Session::singleton();
178 $dateType = $session->get('dateTypes');
179 foreach ($params as $key => $val) {
180 if ($val) {
181 switch ($key) {
182 case 'receive_date':
183 if ($dateValue = CRM_Utils_Date::formatDate($params[$key], $dateType)) {
184 $params[$key] = $dateValue;
185 }
186 else {
719a6fec 187 CRM_Contact_Import_Parser_Contact::addToErrorMsg('Receive Date', $errorMessage);
6a488035
TO
188 }
189 break;
190
191 case 'cancel_date':
192 if ($dateValue = CRM_Utils_Date::formatDate($params[$key], $dateType)) {
ceb10dc7 193 $params[$key] = $dateValue;
6a488035
TO
194 }
195 else {
719a6fec 196 CRM_Contact_Import_Parser_Contact::addToErrorMsg('Cancel Date', $errorMessage);
6a488035
TO
197 }
198 break;
199
200 case 'receipt_date':
201 if ($dateValue = CRM_Utils_Date::formatDate($params[$key], $dateType)) {
202 $params[$key] = $dateValue;
203 }
204 else {
719a6fec 205 CRM_Contact_Import_Parser_Contact::addToErrorMsg('Receipt date', $errorMessage);
6a488035
TO
206 }
207 break;
208
209 case 'thankyou_date':
210 if ($dateValue = CRM_Utils_Date::formatDate($params[$key], $dateType)) {
211 $params[$key] = $dateValue;
212 }
213 else {
719a6fec 214 CRM_Contact_Import_Parser_Contact::addToErrorMsg('Thankyou Date', $errorMessage);
6a488035
TO
215 }
216 break;
217 }
218 }
219 }
220 //date-Format part ends
221
222 $params['contact_type'] = 'Contribution';
223
224 //checking error in custom data
719a6fec 225 CRM_Contact_Import_Parser_Contact::isErrorInCustomData($params, $errorMessage);
6a488035
TO
226
227 if ($errorMessage) {
228 $tempMsg = "Invalid value for field(s) : $errorMessage";
229 array_unshift($values, $tempMsg);
230 $errorMessage = NULL;
a05662ef 231 return CRM_Import_Parser::ERROR;
6a488035
TO
232 }
233
a05662ef 234 return CRM_Import_Parser::VALID;
6a488035
TO
235 }
236
237 /**
100fef9d 238 * Handle the values in import mode
6a488035 239 *
014c4014
TO
240 * @param int $onDuplicate
241 * The code for what action to take on duplicates.
242 * @param array $values
243 * The array of values belonging to this line.
6a488035
TO
244 *
245 * @return boolean the result of this processing
6a488035 246 */
00be9182 247 public function import($onDuplicate, &$values) {
6a488035
TO
248 // first make sure this is a valid line
249 $response = $this->summary($values);
a05662ef 250 if ($response != CRM_Import_Parser::VALID) {
6a488035
TO
251 return $response;
252 }
253
254 $params = &$this->getActiveFieldParams();
255 $formatted = array('version' => 3);
256
257 // don't add to recent items, CRM-4399
258 $formatted['skipRecentView'] = TRUE;
259
260 //for date-Formats
261 $session = CRM_Core_Session::singleton();
262 $dateType = $session->get('dateTypes');
263
264 $customFields = CRM_Core_BAO_CustomField::getFields(CRM_Utils_Array::value('contact_type', $params));
265
c2e1f9ef 266 //CRM-10994
267 if (isset($params['total_amount']) && $params['total_amount'] == 0) {
1ba834a8 268 $params['total_amount'] = '0.00';
c2e1f9ef 269 }
6a488035
TO
270 foreach ($params as $key => $val) {
271 if ($val) {
272 switch ($key) {
ad8eaec9 273 case 'receive_date':
274 case 'cancel_date':
275 case 'receipt_date':
276 case 'thankyou_date':
277 $params[$key] = CRM_Utils_Date::formatDate($params[$key], $dateType);
278 break;
279 case 'pledge_payment':
280 $params[$key] = CRM_Utils_String::strtobool($val);
281 break;
6a488035
TO
282 }
283 if ($customFieldID = CRM_Core_BAO_CustomField::getKeyID($key)) {
284 if ($customFields[$customFieldID]['data_type'] == 'Date') {
719a6fec 285 CRM_Contact_Import_Parser_Contact::formatCustomDate($params, $formatted, $dateType, $key);
6a488035
TO
286 unset($params[$key]);
287 }
288 elseif ($customFields[$customFieldID]['data_type'] == 'Boolean') {
289 $params[$key] = CRM_Utils_String::strtoboolstr($val);
290 }
291 }
292 }
293 }
294 //date-Format part ends
295
296 static $indieFields = NULL;
297 if ($indieFields == NULL) {
298 $tempIndieFields = CRM_Contribute_DAO_Contribution::import();
299 $indieFields = $tempIndieFields;
300 }
301
302 $paramValues = array();
303 foreach ($params as $key => $field) {
304 if ($field == NULL || $field === '') {
305 continue;
306 }
307 $paramValues[$key] = $field;
308 }
309
310 //import contribution record according to select contact type
a05662ef 311 if ($onDuplicate == CRM_Import_Parser::DUPLICATE_SKIP &&
8cc574cf 312 (!empty($paramValues['contribution_contact_id']) || !empty($paramValues['external_identifier']))
6a488035
TO
313 ) {
314 $paramValues['contact_type'] = $this->_contactType;
315 }
a05662ef 316 elseif ($onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE &&
1221efe9 317 (!empty($paramValues['contribution_id']) || !empty($values['trxn_id']) || !empty($paramValues['invoice_id']))
6a488035
TO
318 ) {
319 $paramValues['contact_type'] = $this->_contactType;
320 }
321 elseif (!empty($params['soft_credit'])) {
322 $paramValues['contact_type'] = $this->_contactType;
323 }
a7488080 324 elseif (!empty($paramValues['pledge_payment'])) {
6a488035
TO
325 $paramValues['contact_type'] = $this->_contactType;
326 }
327
328 //need to pass $onDuplicate to check import mode.
a7488080 329 if (!empty($paramValues['pledge_payment'])) {
6a488035
TO
330 $paramValues['onDuplicate'] = $onDuplicate;
331 }
332 require_once 'CRM/Utils/DeprecatedUtils.php';
1221efe9 333 $formatError = _civicrm_api3_deprecated_formatted_param($paramValues, $formatted, TRUE, $onDuplicate);
6a488035
TO
334
335 if ($formatError) {
336 array_unshift($values, $formatError['error_message']);
337 if (CRM_Utils_Array::value('error_data', $formatError) == 'soft_credit') {
338 return CRM_Contribute_Import_Parser::SOFT_CREDIT_ERROR;
339 }
340 elseif (CRM_Utils_Array::value('error_data', $formatError) == 'pledge_payment') {
341 return CRM_Contribute_Import_Parser::PLEDGE_PAYMENT_ERROR;
342 }
a05662ef 343 return CRM_Import_Parser::ERROR;
6a488035
TO
344 }
345
a05662ef 346 if ($onDuplicate != CRM_Import_Parser::DUPLICATE_UPDATE) {
6a488035
TO
347 $formatted['custom'] = CRM_Core_BAO_CustomField::postProcess($formatted,
348 CRM_Core_DAO::$_nullObject,
349 NULL,
350 'Contribution'
351 );
352 }
353 else {
354 //fix for CRM-2219 - Update Contribution
a05662ef 355 // onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE
1221efe9 356 if (!empty($paramValues['invoice_id']) || !empty($paramValues['trxn_id']) || !empty($paramValues['contribution_id'])) {
6a488035
TO
357 $dupeIds = array(
358 'id' => CRM_Utils_Array::value('contribution_id', $paramValues),
359 'trxn_id' => CRM_Utils_Array::value('trxn_id', $paramValues),
360 'invoice_id' => CRM_Utils_Array::value('invoice_id', $paramValues),
361 );
362
363 $ids['contribution'] = CRM_Contribute_BAO_Contribution::checkDuplicateIds($dupeIds);
364
365 if ($ids['contribution']) {
366 $formatted['id'] = $ids['contribution'];
367 $formatted['custom'] = CRM_Core_BAO_CustomField::postProcess($formatted,
368 CRM_Core_DAO::$_nullObject,
369 $formatted['id'],
370 'Contribution'
371 );
372 //process note
a7488080 373 if (!empty($paramValues['note'])) {
6a488035
TO
374 $noteID = array();
375 $contactID = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $ids['contribution'], 'contact_id');
376 $daoNote = new CRM_Core_BAO_Note();
377 $daoNote->entity_table = 'civicrm_contribution';
378 $daoNote->entity_id = $ids['contribution'];
379 if ($daoNote->find(TRUE)) {
380 $noteID['id'] = $daoNote->id;
381 }
382
383 $noteParams = array(
384 'entity_table' => 'civicrm_contribution',
385 'note' => $paramValues['note'],
386 'entity_id' => $ids['contribution'],
387 'contact_id' => $contactID,
388 );
389 CRM_Core_BAO_Note::add($noteParams, $noteID);
390 unset($formatted['note']);
391 }
392
393 //need to check existing soft credit contribution, CRM-3968
1221efe9 394 if (!empty($formatted['soft_credit'])) {
6a488035 395 $dupeSoftCredit = array(
1221efe9 396 'contact_id' => $formatted['soft_credit'],
6a488035
TO
397 'contribution_id' => $ids['contribution'],
398 );
8ef12e64 399
1221efe9 400 //Delete all existing soft Contribution from contribution_soft table for pcp_id is_null
91bb24a7 401 $existingSoftCredit = CRM_Contribute_BAO_ContributionSoft::getSoftContribution($dupeSoftCredit['contribution_id']);
9b873358
TO
402 if (isset($existingSoftCredit['soft_credit']) && !empty($existingSoftCredit['soft_credit'])) {
403 foreach ($existingSoftCredit['soft_credit'] as $key => $existingSoftCreditValues) {
1221efe9 404 if (!empty($existingSoftCreditValues['soft_credit_id'])) {
405 $deleteParams = array(
406 'id' => $existingSoftCreditValues['soft_credit_id'],
407 'pcp_id' => NULL,
408 );
409 CRM_Contribute_BAO_ContributionSoft::del($deleteParams);
410 }
411 }
6a488035
TO
412 }
413 }
414
415 $newContribution = CRM_Contribute_BAO_Contribution::create($formatted, $ids);
6a488035
TO
416 $this->_newContributions[] = $newContribution->id;
417
418 //return soft valid since we need to show how soft credits were added
1221efe9 419 if (!empty($formatted['soft_credit'])) {
6a488035
TO
420 return CRM_Contribute_Import_Parser::SOFT_CREDIT;
421 }
422
423 // process pledge payment assoc w/ the contribution
424 return self::processPledgePayments($formatted);
425
a05662ef 426 return CRM_Import_Parser::VALID;
6a488035
TO
427 }
428 else {
429 $labels = array(
430 'id' => 'Contribution ID',
431 'trxn_id' => 'Transaction ID',
432 'invoice_id' => 'Invoice ID',
433 );
434 foreach ($dupeIds as $k => $v) {
435 if ($v) {
436 $errorMsg[] = "$labels[$k] $v";
437 }
438 }
439 $errorMsg = implode(' AND ', $errorMsg);
440 array_unshift($values, 'Matching Contribution record not found for ' . $errorMsg . '. Row was skipped.');
a05662ef 441 return CRM_Import_Parser::ERROR;
6a488035
TO
442 }
443 }
444 }
445
446 if ($this->_contactIdIndex < 0) {
447 // set the contact type if its not set
448 if (!isset($paramValues['contact_type'])) {
449 $paramValues['contact_type'] = $this->_contactType;
450 }
451
452 $paramValues['version'] = 3;
453 //retrieve contact id using contact dedupe rule
454 require_once 'CRM/Utils/DeprecatedUtils.php';
455 $error = _civicrm_api3_deprecated_check_contact_dedupe($paramValues);
456
457 if (CRM_Core_Error::isAPIError($error, CRM_Core_ERROR::DUPLICATE_CONTACT)) {
458 $matchedIDs = explode(',', $error['error_message']['params'][0]);
459 if (count($matchedIDs) > 1) {
460 array_unshift($values, 'Multiple matching contact records detected for this row. The contribution was not imported');
a05662ef 461 return CRM_Import_Parser::ERROR;
6a488035
TO
462 }
463 else {
464 $cid = $matchedIDs[0];
465 $formatted['contact_id'] = $cid;
466
467 $newContribution = civicrm_api('contribution', 'create', $formatted);
468 if (civicrm_error($newContribution)) {
469 if (is_array($newContribution['error_message'])) {
470 array_unshift($values, $newContribution['error_message']['message']);
471 if ($newContribution['error_message']['params'][0]) {
a05662ef 472 return CRM_Import_Parser::DUPLICATE;
6a488035
TO
473 }
474 }
475 else {
476 array_unshift($values, $newContribution['error_message']);
a05662ef 477 return CRM_Import_Parser::ERROR;
6a488035
TO
478 }
479 }
480
481 $this->_newContributions[] = $newContribution['id'];
482 $formatted['contribution_id'] = $newContribution['id'];
483
484 //return soft valid since we need to show how soft credits were added
1221efe9 485 if (!empty($formatted['soft_credit'])) {
6a488035
TO
486 return CRM_Contribute_Import_Parser::SOFT_CREDIT;
487 }
488
489 // process pledge payment assoc w/ the contribution
490 return self::processPledgePayments($formatted);
491
a05662ef 492 return CRM_Import_Parser::VALID;
6a488035
TO
493 }
494 }
495 else {
496 // Using new Dedupe rule.
497 $ruleParams = array(
498 'contact_type' => $this->_contactType,
499 'used' => 'Unsupervised',
500 );
501 $fieldsArray = CRM_Dedupe_BAO_Rule::dedupeRuleFields($ruleParams);
1221efe9 502 $disp = NULL;
6a488035
TO
503 foreach ($fieldsArray as $value) {
504 if (array_key_exists(trim($value), $params)) {
505 $paramValue = $params[trim($value)];
506 if (is_array($paramValue)) {
507 $disp .= $params[trim($value)][0][trim($value)] . " ";
508 }
509 else {
510 $disp .= $params[trim($value)] . " ";
511 }
512 }
513 }
514
a7488080 515 if (!empty($params['external_identifier'])) {
6a488035
TO
516 if ($disp) {
517 $disp .= "AND {$params['external_identifier']}";
518 }
519 else {
520 $disp = $params['external_identifier'];
521 }
522 }
523
524 array_unshift($values, 'No matching Contact found for (' . $disp . ')');
a05662ef 525 return CRM_Import_Parser::ERROR;
6a488035
TO
526 }
527 }
528 else {
a7488080 529 if (!empty($paramValues['external_identifier'])) {
6a488035
TO
530 $checkCid = new CRM_Contact_DAO_Contact();
531 $checkCid->external_identifier = $paramValues['external_identifier'];
532 $checkCid->find(TRUE);
533 if ($checkCid->id != $formatted['contact_id']) {
534 array_unshift($values, 'Mismatch of External identifier :' . $paramValues['external_identifier'] . ' and Contact Id:' . $formatted['contact_id']);
a05662ef 535 return CRM_Import_Parser::ERROR;
6a488035
TO
536 }
537 }
538 $newContribution = civicrm_api('contribution', 'create', $formatted);
539 if (civicrm_error($newContribution)) {
540 if (is_array($newContribution['error_message'])) {
541 array_unshift($values, $newContribution['error_message']['message']);
542 if ($newContribution['error_message']['params'][0]) {
a05662ef 543 return CRM_Import_Parser::DUPLICATE;
6a488035
TO
544 }
545 }
546 else {
547 array_unshift($values, $newContribution['error_message']);
a05662ef 548 return CRM_Import_Parser::ERROR;
6a488035
TO
549 }
550 }
551
552 $this->_newContributions[] = $newContribution['id'];
553 $formatted['contribution_id'] = $newContribution['id'];
554
555 //return soft valid since we need to show how soft credits were added
1221efe9 556 if (!empty($formatted['soft_credit'])) {
6a488035
TO
557 return CRM_Contribute_Import_Parser::SOFT_CREDIT;
558 }
559
560 // process pledge payment assoc w/ the contribution
561 return self::processPledgePayments($formatted);
562
a05662ef 563 return CRM_Import_Parser::VALID;
6a488035
TO
564 }
565 }
566
567 /**
100fef9d 568 * Process pledge payments
6a488035 569 */
00be9182 570 public function processPledgePayments(&$formatted) {
8cc574cf 571 if (!empty($formatted['pledge_payment_id']) && !empty($formatted['pledge_id'])) {
6a488035
TO
572 //get completed status
573 $completeStatusID = CRM_Core_OptionGroup::getValue('contribution_status', 'Completed', 'name');
574
575 //need to update payment record to map contribution_id
576 CRM_Core_DAO::setFieldValue('CRM_Pledge_DAO_PledgePayment', $formatted['pledge_payment_id'],
577 'contribution_id', $formatted['contribution_id']
578 );
579
580 CRM_Pledge_BAO_PledgePayment::updatePledgePaymentStatus($formatted['pledge_id'],
581 array($formatted['pledge_payment_id']),
582 $completeStatusID,
583 NULL,
584 $formatted['total_amount']
585 );
586
587 return CRM_Contribute_Import_Parser::PLEDGE_PAYMENT;
588 }
589 }
590
591 /**
ceb10dc7 592 * Get the array of successfully imported contribution id's
6a488035
TO
593 *
594 * @return array
6a488035 595 */
00be9182 596 public function &getImportedContributions() {
6a488035
TO
597 return $this->_newContributions;
598 }
599
600 /**
100fef9d 601 * The initializer code, called before the processing
6a488035
TO
602 *
603 * @return void
6a488035 604 */
00be9182 605 public function fini() {}
6a488035 606}