Merge pull request #1373 from ravishnair/CRM-13037
[civicrm-core.git] / CRM / Contribute / BAO / Contribution.php
CommitLineData
6a488035 1<?php
6a488035
TO
2/*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.3 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2013 |
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-2013
32 * $Id$
33 *
34 */
35class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution {
36
37 /**
38 * static field for all the contribution information that we can potentially import
39 *
40 * @var array
41 * @static
42 */
43 static $_importableFields = NULL;
44
45 /**
46 * static field for all the contribution information that we can potentially export
47 *
48 * @var array
49 * @static
50 */
51 static $_exportableFields = NULL;
52
53 /**
54 * field for all the objects related to this contribution
55 * @var array of objects (e.g membership object, participant object)
56 */
57 public $_relatedObjects = array();
58
59 /**
60 * field for the component - either 'event' (participant) or 'contribute'
61 * (any item related to a contribution page e.g. membership, pledge, contribution)
62 * This is used for composing messages because they have dependency on the
63 * contribution_page or event page - although over time we may eliminate that
64 *
65 * @var string component or event
66 */
67 public $_component = NULL;
68
69 /*
70 * construct method
71 */
72 function __construct() {
73 parent::__construct();
74 }
75
76 /**
77 * takes an associative array and creates a contribution object
78 *
79 * the function extract all the params it needs to initialize the create a
80 * contribution object. the params array could contain additional unused name/value
81 * pairs
82 *
83 * @param array $params (reference ) an assoc array of name/value pairs
84 * @param array $ids the array that holds all the db ids
85 *
86 * @return object CRM_Contribute_BAO_Contribution object
87 * @access public
88 * @static
89 */
504a78f6 90 static function add(&$params, $ids = array()) {
6a488035
TO
91 if (empty($params)) {
92 return;
93 }
504a78f6 94 //per http://wiki.civicrm.org/confluence/display/CRM/Database+layer we are moving away from $ids array
95 $contributionID = CRM_Utils_Array::value('contribution', $ids, CRM_Utils_Array::value('id', $params));
6a488035
TO
96
97 $duplicates = array();
504a78f6 98 if (self::checkDuplicate($params, $duplicates, $contributionID)) {
6a488035
TO
99 $error = CRM_Core_Error::singleton();
100 $d = implode(', ', $duplicates);
101 $error->push(CRM_Core_Error::DUPLICATE_CONTRIBUTION,
102 'Fatal',
103 array($d),
104 "Duplicate error - existing contribution record(s) have a matching Transaction ID or Invoice ID. Contribution record ID(s) are: $d"
105 );
106 return $error;
107 }
108
109 // first clean up all the money fields
110 $moneyFields = array(
111 'total_amount',
112 'net_amount',
113 'fee_amount',
114 'non_deductible_amount',
115 );
ae06c851 116
6a488035
TO
117 //if priceset is used, no need to cleanup money
118 if (CRM_Utils_Array::value('skipCleanMoney', $params)) {
119 unset($moneyFields[0]);
120 }
121
122 foreach ($moneyFields as $field) {
123 if (isset($params[$field])) {
124 $params[$field] = CRM_Utils_Rule::cleanMoney($params[$field]);
125 }
126 }
127
128 if (CRM_Utils_Array::value('payment_instrument_id', $params)) {
129 $paymentInstruments = CRM_Contribute_PseudoConstant::paymentInstrument('name');
130 if ($params['payment_instrument_id'] != array_search('Check', $paymentInstruments)) {
131 $params['check_number'] = 'null';
132 }
133 }
134
135 // contribution status is missing, choose Completed as default status
9e12d5ee 136 // do this for create mode only
137 if (!CRM_Utils_Array::value('contribution', $ids) && !CRM_Utils_Array::value('contribution_status_id', $params)) {
6a488035
TO
138 $params['contribution_status_id'] = CRM_Core_OptionGroup::getValue('contribution_status', 'Completed', 'name');
139 }
140
504a78f6 141 if ($contributionID) {
142 CRM_Utils_Hook::pre('edit', 'Contribution', $contributionID, $params);
6a488035
TO
143 }
144 else {
145 CRM_Utils_Hook::pre('create', 'Contribution', NULL, $params);
146 }
147
148 $contribution = new CRM_Contribute_BAO_Contribution();
149 $contribution->copyValues($params);
150
504a78f6 151 $contribution->id = $contributionID;
6a488035
TO
152
153 if (!CRM_Utils_Rule::currencyCode($contribution->currency)) {
154 $config = CRM_Core_Config::singleton();
155 $contribution->currency = $config->defaultCurrency;
156 }
157
504a78f6 158 if ($contributionID) {
159 $params['prevContribution'] = self::getValues(array('id' => $contributionID), CRM_Core_DAO::$_nullArray, CRM_Core_DAO::$_nullArray);
6a488035
TO
160 }
161
162 $result = $contribution->save();
163
164 // Add financial_trxn details as part of fix for CRM-4724
165 $contribution->trxn_result_code = CRM_Utils_Array::value('trxn_result_code', $params);
166 $contribution->payment_processor = CRM_Utils_Array::value('payment_processor', $params);
167
168 //add Account details
169 $params['contribution'] = $contribution;
170 self::recordFinancialAccounts($params, $ids);
171
6a488035
TO
172 // reset the group contact cache for this group
173 CRM_Contact_BAO_GroupContactCache::remove();
174
504a78f6 175 if ($contributionID) {
6a488035
TO
176 CRM_Utils_Hook::post('edit', 'Contribution', $contribution->id, $contribution);
177 }
178 else {
179 CRM_Utils_Hook::post('create', 'Contribution', $contribution->id, $contribution);
180 }
181
182 return $result;
183 }
184
185 /**
186 * Given the list of params in the params array, fetch the object
187 * and store the values in the values array
188 *
189 * @param array $params input parameters to find object
190 * @param array $values output values of the object
191 * @param array $ids the array that holds all the db ids
192 *
193 * @return CRM_Contribute_BAO_Contribution|null the found object or null
194 * @access public
195 * @static
196 */
504a78f6 197 static function &getValues($params, &$values, &$ids) {
6a488035
TO
198 if (empty($params)) {
199 return NULL;
200 }
201 $contribution = new CRM_Contribute_BAO_Contribution();
202
203 $contribution->copyValues($params);
204
205 if ($contribution->find(TRUE)) {
206 $ids['contribution'] = $contribution->id;
207
208 CRM_Core_DAO::storeValues($contribution, $values);
209
210 return $contribution;
211 }
212 return NULL;
213 }
214
215 /**
216 * takes an associative array and creates a contribution object
217 *
218 * @param array $params (reference ) an assoc array of name/value pairs
219 * @param array $ids the array that holds all the db ids
220 *
221 * @return object CRM_Contribute_BAO_Contribution object
222 * @access public
223 * @static
224 */
504a78f6 225 static function &create(&$params, $ids = array()) {
6a488035
TO
226 $dateFields = array('receive_date', 'cancel_date', 'receipt_date', 'thankyou_date');
227 foreach ($dateFields as $df) {
228 if (isset($params[$df])) {
229 $params[$df] = CRM_Utils_Date::isoToMysql($params[$df]);
230 }
231 }
232
6a488035
TO
233 $transaction = new CRM_Core_Transaction();
234
6a488035
TO
235 $contribution = self::add($params, $ids);
236
237 if (is_a($contribution, 'CRM_Core_Error')) {
238 $transaction->rollback();
239 return $contribution;
240 }
241
242 $params['contribution_id'] = $contribution->id;
243
244 if (CRM_Utils_Array::value('custom', $params) &&
245 is_array($params['custom'])
246 ) {
247 CRM_Core_BAO_CustomValueTable::store($params['custom'], 'civicrm_contribution', $contribution->id);
248 }
249
250 $session = CRM_Core_Session::singleton();
251
252 if (CRM_Utils_Array::value('note', $params)) {
6a488035
TO
253 $noteParams = array(
254 'entity_table' => 'civicrm_contribution',
255 'note' => $params['note'],
256 'entity_id' => $contribution->id,
257 'contact_id' => $session->get('userID'),
258 'modified_date' => date('Ymd'),
259 );
260 if (!$noteParams['contact_id']) {
261 $noteParams['contact_id'] = $params['contact_id'];
262 }
504a78f6 263 CRM_Core_BAO_Note::add($noteParams);
6a488035
TO
264 }
265
266 // make entry in batch entity batch table
267 if (CRM_Utils_Array::value('batch_id', $params)) {
268 // in some update cases we need to get extra fields - ie an update that doesn't pass in all these params
269 $titleFields = array(
270 'contact_id',
271 'total_amount',
272 'currency',
273 'financial_type_id',
274 );
d37ade2e 275 $retrieveRequired = 0;
6a488035
TO
276 foreach ($titleFields as $titleField) {
277 if(!isset($contribution->$titleField)){
d37ade2e 278 $retrieveRequired = 1;
6a488035
TO
279 break;
280 }
281 }
d37ade2e 282 if ($retrieveRequired == 1) {
2cfc0f58 283 $contribution->find(TRUE);
6a488035
TO
284 }
285 }
286
287 // check if activity record exist for this contribution, if
288 // not add activity
289 $activity = new CRM_Activity_DAO_Activity();
290 $activity->source_record_id = $contribution->id;
291 $activity->activity_type_id = CRM_Core_OptionGroup::getValue('activity_type',
292 'Contribution',
293 'name'
294 );
295 if (!$activity->find()) {
296 CRM_Activity_BAO_Activity::addActivity($contribution, 'Offline');
297 }
2cfc0f58 298
6a488035 299 // Handle soft credit and / or link to personal campaign page
2cfc0f58 300 list($type, $softIDs) = CRM_Contribute_BAO_ContributionSoft::getSoftCreditType($contribution->id);
301 if ($pcp = CRM_Utils_Array::value('pcp', $params)) {
7f880799 302 if (!empty($type) && $type == 'soft') {
2cfc0f58 303 $deleteParams = array('contribution_id' => $contribution->id);
304 CRM_Contribute_BAO_ContributionSoft::del($deleteParams);
6a488035 305 }
2cfc0f58 306 $softParams = array();
307 $softParams['contribution_id'] = $contribution->id;
308 $softParams['pcp_id'] = $pcp['pcp_made_through_id'];
309 $softParams['contact_id'] = CRM_Core_DAO::getFieldValue('CRM_PCP_DAO_PCP',
310 $pcp['pcp_made_through_id'], 'contact_id'
311 );
312 $softParams['currency'] = $contribution->currency;
313 $softParams['amount'] = $contribution->total_amount;
314 $softParams['pcp_display_in_roll'] = CRM_Utils_Array::value('pcp_display_in_roll', $pcp);
315 $softParams['pcp_roll_nickname'] = CRM_Utils_Array::value('pcp_roll_nickname', $pcp);
316 $softParams['pcp_personal_note'] = CRM_Utils_Array::value('pcp_personal_note', $pcp);
317 CRM_Contribute_BAO_ContributionSoft::add($softParams);
318 }
319 elseif (CRM_Utils_Array::value('soft_credit', $params)) {
320 $softParams = $params['soft_credit'];
7f880799 321
322 if (!empty($softIDs)) {
323 foreach ( $softIDs as $softID) {
324 if (!in_array($softID, $params['soft_credit_ids'])) {
325 $deleteParams = array('id' => $softID);
326 CRM_Contribute_BAO_ContributionSoft::del($deleteParams);
327 }
ae06c851 328 }
6a488035 329 }
2cfc0f58 330
331 foreach ($softParams as $softParam) {
332 $softParam['contribution_id'] = $contribution->id;
333 $softParam['currency'] = $contribution->currency;
7f880799 334 $softParam['pcp_id'] = 'null';
335 $softParam['pcp_display_in_roll'] = 'null';
336 $softParam['pcp_roll_nickname'] = 'null';
337 $softParam['pcp_personal_note'] = 'NULL';
2cfc0f58 338 CRM_Contribute_BAO_ContributionSoft::add($softParam);
339 }
6a488035 340 }
2cfc0f58 341
6a488035
TO
342 $transaction->commit();
343
344 // do not add to recent items for import, CRM-4399
345 if (!CRM_Utils_Array::value('skipRecentView', $params)) {
346 $url = CRM_Utils_System::url('civicrm/contact/view/contribution',
347 "action=view&reset=1&id={$contribution->id}&cid={$contribution->contact_id}&context=home"
348 );
349 // in some update cases we need to get extra fields - ie an update that doesn't pass in all these params
350 $titleFields = array(
351 'contact_id',
352 'total_amount',
353 'currency',
354 'financial_type_id',
355 );
d37ade2e 356 $retrieveRequired = 0;
6a488035
TO
357 foreach ($titleFields as $titleField) {
358 if(!isset($contribution->$titleField)){
d37ade2e 359 $retrieveRequired = 1;
6a488035
TO
360 break;
361 }
362 }
d37ade2e 363 if($retrieveRequired == 1){
2cfc0f58 364 $contribution->find(TRUE);
6a488035
TO
365 }
366 $contributionTypes = CRM_Contribute_PseudoConstant::financialType();
367 $title = CRM_Contact_BAO_Contact::displayName($contribution->contact_id) . ' - (' . CRM_Utils_Money::format($contribution->total_amount, $contribution->currency) . ' ' . ' - ' . $contributionTypes[$contribution->financial_type_id] . ')';
368
369 $recentOther = array();
370 if (CRM_Core_Permission::checkActionPermission('CiviContribute', CRM_Core_Action::UPDATE)) {
371 $recentOther['editUrl'] = CRM_Utils_System::url('civicrm/contact/view/contribution',
372 "action=update&reset=1&id={$contribution->id}&cid={$contribution->contact_id}&context=home"
373 );
374 }
375
376 if (CRM_Core_Permission::checkActionPermission('CiviContribute', CRM_Core_Action::DELETE)) {
377 $recentOther['deleteUrl'] = CRM_Utils_System::url('civicrm/contact/view/contribution',
378 "action=delete&reset=1&id={$contribution->id}&cid={$contribution->contact_id}&context=home"
379 );
380 }
381
382 // add the recently created Contribution
383 CRM_Utils_Recent::add($title,
384 $url,
385 $contribution->id,
386 'Contribution',
387 $contribution->contact_id,
388 NULL,
389 $recentOther
390 );
391 }
392
393 return $contribution;
394 }
395
396 /**
397 * Get the values for pseudoconstants for name->value and reverse.
398 *
399 * @param array $defaults (reference) the default values, some of which need to be resolved.
400 * @param boolean $reverse true if we want to resolve the values in the reverse direction (value -> name)
401 *
402 * @return void
403 * @access public
404 * @static
405 */
406 static function resolveDefaults(&$defaults, $reverse = FALSE) {
407 self::lookupValue($defaults, 'financial_type', CRM_Contribute_PseudoConstant::financialType(), $reverse);
408 self::lookupValue($defaults, 'payment_instrument', CRM_Contribute_PseudoConstant::paymentInstrument(), $reverse);
409 self::lookupValue($defaults, 'contribution_status', CRM_Contribute_PseudoConstant::contributionStatus(), $reverse);
410 self::lookupValue($defaults, 'pcp', CRM_Contribute_PseudoConstant::pcPage(), $reverse);
411 }
412
413 /**
414 * This function is used to convert associative array names to values
415 * and vice-versa.
416 *
417 * This function is used by both the web form layer and the api. Note that
418 * the api needs the name => value conversion, also the view layer typically
419 * requires value => name conversion
420 */
421 static function lookupValue(&$defaults, $property, &$lookup, $reverse) {
422 $id = $property . '_id';
423
424 $src = $reverse ? $property : $id;
425 $dst = $reverse ? $id : $property;
426
427 if (!array_key_exists($src, $defaults)) {
428 return FALSE;
429 }
430
431 $look = $reverse ? array_flip($lookup) : $lookup;
432
433 if (is_array($look)) {
434 if (!array_key_exists($defaults[$src], $look)) {
435 return FALSE;
436 }
437 }
438 $defaults[$dst] = $look[$defaults[$src]];
439 return TRUE;
440 }
441
442 /**
443 * Takes a bunch of params that are needed to match certain criteria and
444 * retrieves the relevant objects. We'll tweak this function to be more
445 * full featured over a period of time. This is the inverse function of
446 * create. It also stores all the retrieved values in the default array
447 *
448 * @param array $params (reference ) an assoc array of name/value pairs
449 * @param array $defaults (reference ) an assoc array to hold the name / value pairs
450 * in a hierarchical manner
451 * @param array $ids (reference) the array that holds all the db ids
452 *
453 * @return object CRM_Contribute_BAO_Contribution object
454 * @access public
455 * @static
456 */
457 static function retrieve(&$params, &$defaults, &$ids) {
458 $contribution = CRM_Contribute_BAO_Contribution::getValues($params, $defaults, $ids);
459 return $contribution;
460 }
461
462 /**
463 * combine all the importable fields from the lower levels object
464 *
465 * The ordering is important, since currently we do not have a weight
466 * scheme. Adding weight is super important and should be done in the
467 * next week or so, before this can be called complete.
468 *
469 * @return array array of importable Fields
470 * @access public
471 * @static
472 */
c2585c5b 473 static function &importableFields($contactType = 'Individual', $status = TRUE) {
6a488035
TO
474 if (!self::$_importableFields) {
475 if (!self::$_importableFields) {
476 self::$_importableFields = array();
477 }
478
479 if (!$status) {
480 $fields = array('' => array('title' => ts('- do not import -')));
481 }
482 else {
483 $fields = array('' => array('title' => ts('- Contribution Fields -')));
484 }
485
486 $note = CRM_Core_DAO_Note::import();
487 $tmpFields = CRM_Contribute_DAO_Contribution::import();
488 unset($tmpFields['option_value']);
489 $optionFields = CRM_Core_OptionValue::getFields($mode = 'contribute');
c2585c5b 490 $contactFields = CRM_Contact_BAO_Contact::importableFields($contactType, NULL);
6a488035
TO
491
492 // Using new Dedupe rule.
493 $ruleParams = array(
c2585c5b 494 'contact_type' => $contactType,
6a488035
TO
495 'used' => 'Unsupervised',
496 );
497 $fieldsArray = CRM_Dedupe_BAO_Rule::dedupeRuleFields($ruleParams);
c2585c5b 498 $tmpContactField = array();
6a488035
TO
499 if (is_array($fieldsArray)) {
500 foreach ($fieldsArray as $value) {
501 //skip if there is no dupe rule
502 if ($value == 'none') {
503 continue;
504 }
505 $customFieldId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField',
506 $value,
507 'id',
508 'column_name'
509 );
510 $value = $customFieldId ? 'custom_' . $customFieldId : $value;
c2585c5b 511 $tmpContactField[trim($value)] = $contactFields[trim($value)];
6a488035 512 if (!$status) {
c2585c5b 513 $title = $tmpContactField[trim($value)]['title'] . ' ' . ts('(match to contact)');
6a488035
TO
514 }
515 else {
c2585c5b 516 $title = $tmpContactField[trim($value)]['title'];
6a488035 517 }
c2585c5b 518 $tmpContactField[trim($value)]['title'] = $title;
6a488035
TO
519 }
520 }
521
c2585c5b 522 $tmpContactField['external_identifier'] = $contactFields['external_identifier'];
523 $tmpContactField['external_identifier']['title'] = $contactFields['external_identifier']['title'] . ' ' . ts('(match to contact)');
6a488035 524 $tmpFields['contribution_contact_id']['title'] = $tmpFields['contribution_contact_id']['title'] . ' ' . ts('(match to contact)');
c2585c5b 525 $fields = array_merge($fields, $tmpContactField);
6a488035
TO
526 $fields = array_merge($fields, $tmpFields);
527 $fields = array_merge($fields, $note);
528 $fields = array_merge($fields, $optionFields);
529 $fields = array_merge($fields, CRM_Financial_DAO_FinancialType::export());
530 $fields = array_merge($fields, CRM_Core_BAO_CustomField::getFieldsForImport('Contribution'));
531 self::$_importableFields = $fields;
532 }
533 return self::$_importableFields;
534 }
535
536 static function &exportableFields() {
537 if (!self::$_exportableFields) {
538 if (!self::$_exportableFields) {
539 self::$_exportableFields = array();
540 }
541
542 $impFields = CRM_Contribute_DAO_Contribution::export();
543 $expFieldProduct = CRM_Contribute_DAO_Product::export();
544 $expFieldsContrib = CRM_Contribute_DAO_ContributionProduct::export();
545 $typeField = CRM_Financial_DAO_FinancialType::export();
546 $financialAccount = CRM_Financial_DAO_FinancialAccount::export();
547 $optionField = CRM_Core_OptionValue::getFields($mode = 'contribute');
548 $contributionStatus = array(
549 'contribution_status' => array(
550 'title' => ts('Contribution Status'),
551 'name' => 'contribution_status',
552 'data_type' => CRM_Utils_Type::T_STRING
553 ));
554
555 $contributionNote = array(
556 'contribution_note' =>
557 array(
558 'title' => ts('Contribution Note'),
559 'name' => 'contribution_note',
560 'data_type' => CRM_Utils_Type::T_TEXT
561 )
562 );
563
564 $contributionRecurId = array(
565 'contribution_recur_id' =>
566 array(
567 'title' => ts('Recurring Contributions ID'),
568 'name' => 'contribution_recur_id',
569 'where' => 'civicrm_contribution.contribution_recur_id',
570 'data_type' => CRM_Utils_Type::T_INT
571 ));
572
573 $extraFields = array(
574 'contribution_campaign' =>
575 array(
576 'title' => ts('Campaign Title')
577 ),
578 'contribution_batch' =>
579 array(
580 'title' => ts('Batch Name')
581 )
582 );
583
584 $fields = array_merge($impFields, $typeField, $contributionStatus, $optionField, $expFieldProduct,
585 $expFieldsContrib, $contributionNote, $contributionRecurId, $extraFields, $financialAccount,
586 CRM_Core_BAO_CustomField::getFieldsForImport('Contribution')
587 );
588
589 self::$_exportableFields = $fields;
590 }
591
592 return self::$_exportableFields;
593 }
594
595 static function getTotalAmountAndCount($status = NULL, $startDate = NULL, $endDate = NULL) {
596 $where = array();
597 switch ($status) {
598 case 'Valid':
599 $where[] = 'contribution_status_id = 1';
600 break;
601
602 case 'Cancelled':
603 $where[] = 'contribution_status_id = 3';
604 break;
605 }
606
607 if ($startDate) {
608 $where[] = "receive_date >= '" . CRM_Utils_Type::escape($startDate, 'Timestamp') . "'";
609 }
610 if ($endDate) {
611 $where[] = "receive_date <= '" . CRM_Utils_Type::escape($endDate, 'Timestamp') . "'";
612 }
613
614 $whereCond = implode(' AND ', $where);
615
616 $query = "
617 SELECT sum( total_amount ) as total_amount,
618 count( civicrm_contribution.id ) as total_count,
619 currency
620 FROM civicrm_contribution
621INNER JOIN civicrm_contact contact ON ( contact.id = civicrm_contribution.contact_id )
622 WHERE $whereCond
623 AND ( is_test = 0 OR is_test IS NULL )
624 AND contact.is_deleted = 0
625 GROUP BY currency
626";
627
628 $dao = CRM_Core_DAO::executeQuery($query, CRM_Core_DAO::$_nullArray);
629 $amount = array();
630 $count = 0;
631 while ($dao->fetch()) {
632 $count += $dao->total_count;
633 $amount[] = CRM_Utils_Money::format($dao->total_amount, $dao->currency);
634 }
635 if ($count) {
636 return array('amount' => implode(', ', $amount),
637 'count' => $count,
638 );
639 }
640 return NULL;
641 }
642
643 /**
644 * Delete the indirect records associated with this contribution first
645 *
646 * @return $results no of deleted Contribution on success, false otherwise
647 * @access public
648 * @static
649 */
650 static function deleteContribution($id) {
651 CRM_Utils_Hook::pre('delete', 'Contribution', $id, CRM_Core_DAO::$_nullArray);
652
653 $transaction = new CRM_Core_Transaction();
654
655 $results = NULL;
656 //delete activity record
657 $params = array(
658 'source_record_id' => $id,
659 // activity type id for contribution
660 'activity_type_id' => 6,
661 );
662
663 CRM_Activity_BAO_Activity::deleteActivity($params);
664
665 //delete billing address if exists for this contribution.
666 self::deleteAddress($id);
667
668 //update pledge and pledge payment, CRM-3961
669 CRM_Pledge_BAO_PledgePayment::resetPledgePayment($id);
670
671 // remove entry from civicrm_price_set_entity, CRM-5095
9da8dc8c 672 if (CRM_Price_BAO_PriceSet::getFor('civicrm_contribution', $id)) {
673 CRM_Price_BAO_PriceSet::removeFrom('civicrm_contribution', $id);
6a488035
TO
674 }
675 // cleanup line items.
676 $participantId = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_ParticipantPayment', $id, 'participant_id', 'contribution_id');
677
de1c25e1
PN
678 // delete any related entity_financial_trxn, financial_trxn and financial_item records.
679 CRM_Core_BAO_FinancialTrxn::deleteFinancialTrxn($id);
6a488035
TO
680
681 if ($participantId) {
682 CRM_Price_BAO_LineItem::deleteLineItems($participantId, 'civicrm_participant');
683 }
684 else {
685 CRM_Price_BAO_LineItem::deleteLineItems($id, 'civicrm_contribution');
686 }
687
688 //delete note.
689 $note = CRM_Core_BAO_Note::getNote($id, 'civicrm_contribution');
690 $noteId = key($note);
691 if ($noteId) {
692 CRM_Core_BAO_Note::del($noteId, FALSE);
693 }
694
695 $dao = new CRM_Contribute_DAO_Contribution();
696 $dao->id = $id;
697
698 $results = $dao->delete();
699
700 $transaction->commit();
701
702 CRM_Utils_Hook::post('delete', 'Contribution', $dao->id, $dao);
703
704 // delete the recently created Contribution
705 $contributionRecent = array(
706 'id' => $id,
707 'type' => 'Contribution',
708 );
709 CRM_Utils_Recent::del($contributionRecent);
710
711 return $results;
712 }
713
714 /**
715 * Check if there is a contribution with the same trxn_id or invoice_id
716 *
717 * @param array $params (reference ) an assoc array of name/value pairs
718 * @param array $duplicates (reference ) store ids of duplicate contribs
719 *
720 * @return boolean true if duplicate, false otherwise
721 * @access public
722 * static
723 */
724 static function checkDuplicate($input, &$duplicates, $id = NULL) {
725 if (!$id) {
726 $id = CRM_Utils_Array::value('id', $input);
727 }
728 $trxn_id = CRM_Utils_Array::value('trxn_id', $input);
729 $invoice_id = CRM_Utils_Array::value('invoice_id', $input);
730
731 $clause = array();
732 $input = array();
733
734 if ($trxn_id) {
735 $clause[] = "trxn_id = %1";
736 $input[1] = array($trxn_id, 'String');
737 }
738
739 if ($invoice_id) {
740 $clause[] = "invoice_id = %2";
741 $input[2] = array($invoice_id, 'String');
742 }
743
744 if (empty($clause)) {
745 return FALSE;
746 }
747
748 $clause = implode(' OR ', $clause);
749 if ($id) {
750 $clause = "( $clause ) AND id != %3";
751 $input[3] = array($id, 'Integer');
752 }
753
754 $query = "SELECT id FROM civicrm_contribution WHERE $clause";
755 $dao = CRM_Core_DAO::executeQuery($query, $input);
756 $result = FALSE;
757 while ($dao->fetch()) {
758 $duplicates[] = $dao->id;
759 $result = TRUE;
760 }
761 return $result;
762 }
763
764 /**
765 * takes an associative array and creates a contribution_product object
766 *
767 * the function extract all the params it needs to initialize the create a
768 * contribution_product object. the params array could contain additional unused name/value
769 * pairs
770 *
771 * @param array $params (reference ) an assoc array of name/value pairs
772 *
773 * @return object CRM_Contribute_BAO_ContributionProduct object
774 * @access public
775 * @static
776 */
777 static function addPremium(&$params) {
778 $contributionProduct = new CRM_Contribute_DAO_ContributionProduct();
779 $contributionProduct->copyValues($params);
780 return $contributionProduct->save();
781 }
782
783 /**
784 * Function to get list of contribution fields for profile
785 * For now we only allow custom contribution fields to be in
786 * profile
787 *
788 * @param boolean $addExtraFields true if special fields needs to be added
789 *
790 * @return return the list of contribution fields
791 * @static
792 * @access public
793 */
794 static function getContributionFields($addExtraFields = TRUE) {
795 $contributionFields = CRM_Contribute_DAO_Contribution::export();
796 $contributionFields = array_merge($contributionFields, CRM_Core_OptionValue::getFields($mode = 'contribute'));
797
798 if ($addExtraFields) {
799 $contributionFields = array_merge($contributionFields, self::getSpecialContributionFields());
800 }
801
802 $contributionFields = array_merge($contributionFields, CRM_Financial_DAO_FinancialType::export());
803
804 foreach ($contributionFields as $key => $var) {
805 if ($key == 'contribution_contact_id') {
806 continue;
807 }
808 elseif ($key == 'contribution_campaign_id') {
809 $var['title'] = ts('Campaign');
810 }
811 $fields[$key] = $var;
812 }
813
814 $fields = array_merge($fields, CRM_Core_BAO_CustomField::getFieldsForImport('Contribution'));
815 return $fields;
816 }
817
818 /**
819 * Function to add extra fields specific to contribtion
820 *
821 * @static
822 */
823 static function getSpecialContributionFields() {
824 $extraFields = array(
825 'honor_contact_name' => array(
826 'name' => 'honor_contact_name',
827 'title' => 'Honor Contact Name',
828 'headerPattern' => '/^honor_contact_name$/i',
829 'where' => 'civicrm_contact_c.display_name',
830 ),
831 'honor_contact_email' => array(
832 'name' => 'honor_contact_email',
833 'title' => 'Honor Contact Email',
834 'headerPattern' => '/^honor_contact_email$/i',
835 'where' => 'honor_email.email',
836 ),
837 'honor_contact_id' => array(
838 'name' => 'honor_contact_id',
839 'title' => 'Honor Contact ID',
840 'headerPattern' => '/^honor_contact_id$/i',
841 'where' => 'civicrm_contribution.honor_contact_id',
842 ),
843 'honor_type_label' => array(
844 'name' => 'honor_type_label',
845 'title' => 'Honor Type Label',
846 'headerPattern' => '/^honor_type_label$/i',
847 'where' => 'honor_type.label',
848 ),
849 'soft_credit_name' => array(
850 'name' => 'soft_credit_name',
851 'title' => 'Soft Credit Name',
852 'headerPattern' => '/^soft_credit_name$/i',
853 'where' => 'civicrm_contact_d.display_name',
854 ),
855 'soft_credit_email' => array(
856 'name' => 'soft_credit_email',
857 'title' => 'Soft Credit Email',
858 'headerPattern' => '/^soft_credit_email$/i',
859 'where' => 'soft_email.email',
860 ),
861 'soft_credit_phone' => array(
862 'name' => 'soft_credit_phone',
863 'title' => 'Soft Credit Phone',
864 'headerPattern' => '/^soft_credit_phone$/i',
865 'where' => 'soft_phone.phone',
866 ),
867 'soft_credit_contact_id' => array(
868 'name' => 'soft_credit_contact_id',
869 'title' => 'Soft Credit Contact ID',
870 'headerPattern' => '/^soft_credit_contact_id$/i',
871 'where' => 'civicrm_contribution_soft.contact_id',
872 ),
873 );
874
875 return $extraFields;
876 }
877
878 static function getCurrentandGoalAmount($pageID) {
879 $query = "
880SELECT p.goal_amount as goal, sum( c.total_amount ) as total
881 FROM civicrm_contribution_page p,
882 civicrm_contribution c
883 WHERE p.id = c.contribution_page_id
884 AND p.id = %1
885 AND c.cancel_date is null
886GROUP BY p.id
887";
888
889 $config = CRM_Core_Config::singleton();
890 $params = array(1 => array($pageID, 'Integer'));
891 $dao = CRM_Core_DAO::executeQuery($query, $params);
892
893 if ($dao->fetch()) {
894 return array($dao->goal, $dao->total);
895 }
896 else {
897 return array(NULL, NULL);
898 }
899 }
900
901 /**
902 * Function to create is honor of
903 *
904 * @param array $params associated array of fields (by reference)
905 * @param int $honorId honor Id
906 * @param array $honorParams any params that should be send to the create function
907 *
908 * @return contact id
909 */
910 static function createHonorContact(&$params, $honorId = NULL, $honorParams = array()) {
911 $honorParams = array_merge(
912 array(
913 'first_name' => $params['honor_first_name'],
914 'last_name' => $params['honor_last_name'],
915 'prefix_id' => $params['honor_prefix_id'],
916 'email-Primary' => $params['honor_email'],
917 ),
918 $honorParams
919 );
920 if (!$honorId) {
921 $honorParams['email'] = $params['honor_email'];
922
923 $dedupeParams = CRM_Dedupe_Finder::formatParams($honorParams, 'Individual');
924 $dedupeParams['check_permission'] = FALSE;
925 $ids = CRM_Dedupe_Finder::dupesByParams($dedupeParams, 'Individual');
926
927 // if we find more than one contact, use the first one
928 $honorId = CRM_Utils_Array::value(0, $ids);
929 }
930
931 $contactID = CRM_Contact_BAO_Contact::createProfileContact(
932 $honorParams,
933 CRM_Core_DAO::$_nullArray,
934 $honorId
935 );
936 return $contactID;
937 }
938
939 /**
940 * Function to get list of contribution In Honor of contact Ids
941 *
942 * @param int $honorId In Honor of Contact ID
943 *
944 * @return return the list of contribution fields
945 *
946 * @access public
947 * @static
948 */
949 static function getHonorContacts($honorId) {
950 $params = array();
951 $honorDAO = new CRM_Contribute_DAO_Contribution();
952 $honorDAO->honor_contact_id = $honorId;
953 $honorDAO->find();
954
955 $status = CRM_Contribute_PseudoConstant::contributionStatus($honorDAO->contribution_status_id);
956 $type = CRM_Contribute_PseudoConstant::financialType();
957
958 while ($honorDAO->fetch()) {
959 $params[$honorDAO->id]['honorId'] = $honorDAO->contact_id;
960 $params[$honorDAO->id]['display_name'] = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $honorDAO->contact_id, 'display_name');
961 $params[$honorDAO->id]['type'] = $type[$honorDAO->financial_type_id];
962 $params[$honorDAO->id]['type_id'] = $honorDAO->financial_type_id;
963 $params[$honorDAO->id]['amount'] = CRM_Utils_Money::format($honorDAO->total_amount, $honorDAO->currency);
964 $params[$honorDAO->id]['source'] = $honorDAO->source;
965 $params[$honorDAO->id]['receive_date'] = $honorDAO->receive_date;
966 $params[$honorDAO->id]['contribution_status'] = CRM_Utils_Array::value($honorDAO->contribution_status_id, $status);
967 }
968
969 return $params;
970 }
971
972 /**
973 * function to get the sort name of a contact for a particular contribution
974 *
975 * @param int $id id of the contribution
976 *
977 * @return null|string sort name of the contact if found
978 * @static
979 * @access public
980 */
981 static function sortName($id) {
982 $id = CRM_Utils_Type::escape($id, 'Integer');
983
984 $query = "
985SELECT civicrm_contact.sort_name
986FROM civicrm_contribution, civicrm_contact
987WHERE civicrm_contribution.contact_id = civicrm_contact.id
988 AND civicrm_contribution.id = {$id}
989";
990 return CRM_Core_DAO::singleValueQuery($query, CRM_Core_DAO::$_nullArray);
991 }
992
993 static function annual($contactID) {
994 if (is_array($contactID)) {
995 $contactIDs = implode(',', $contactID);
996 }
997 else {
998 $contactIDs = $contactID;
999 }
1000
1001 $config = CRM_Core_Config::singleton();
1002 $startDate = $endDate = NULL;
1003
1004 $currentMonth = date('m');
1005 $currentDay = date('d');
1006 if ((int ) $config->fiscalYearStart['M'] > $currentMonth ||
1007 ((int ) $config->fiscalYearStart['M'] == $currentMonth &&
1008 (int ) $config->fiscalYearStart['d'] > $currentDay
1009 )
1010 ) {
1011 $year = date('Y') - 1;
1012 }
1013 else {
1014 $year = date('Y');
1015 }
1016 $nextYear = $year + 1;
1017
1018 if ($config->fiscalYearStart) {
1019 if ($config->fiscalYearStart['M'] < 10) {
1020 $config->fiscalYearStart['M'] = '0' . $config->fiscalYearStart['M'];
1021 }
1022 if ($config->fiscalYearStart['d'] < 10) {
1023 $config->fiscalYearStart['d'] = '0' . $config->fiscalYearStart['d'];
1024 }
1025 $monthDay = $config->fiscalYearStart['M'] . $config->fiscalYearStart['d'];
1026 }
1027 else {
1028 $monthDay = '0101';
1029 }
1030 $startDate = "$year$monthDay";
1031 $endDate = "$nextYear$monthDay";
1032
1033 $query = "
1034 SELECT count(*) as count,
1035 sum(total_amount) as amount,
1036 avg(total_amount) as average,
1037 currency
1038 FROM civicrm_contribution b
1039 WHERE b.contact_id IN ( $contactIDs )
1040 AND b.contribution_status_id = 1
1041 AND b.is_test = 0
1042 AND b.receive_date >= $startDate
1043 AND b.receive_date < $endDate
1044 GROUP BY currency
1045 ";
1046 $dao = CRM_Core_DAO::executeQuery($query, CRM_Core_DAO::$_nullArray);
1047 $count = 0;
1048 $amount = $average = array();
1049 while ($dao->fetch()) {
1050 if ($dao->count > 0 && $dao->amount > 0) {
1051 $count += $dao->count;
1052 $amount[] = CRM_Utils_Money::format($dao->amount, $dao->currency);
1053 $average[] = CRM_Utils_Money::format($dao->average, $dao->currency);
1054 }
1055 }
1056 if ($count > 0) {
1057 return array(
1058 $count,
1059 implode(',&nbsp;', $amount),
1060 implode(',&nbsp;', $average),
1061 );
1062 }
1063 return array(0, 0, 0);
1064 }
1065
1066 /**
1067 * Check if there is a contribution with the params passed in.
1068 * Used for trxn_id,invoice_id and contribution_id
1069 *
1070 * @param array $params an assoc array of name/value pairs
1071 *
1072 * @return array contribution id if success else NULL
1073 * @access public
1074 * static
1075 */
1076 static function checkDuplicateIds($params) {
1077 $dao = new CRM_Contribute_DAO_Contribution();
1078
1079 $clause = array();
1080 $input = array();
1081 foreach ($params as $k => $v) {
1082 if ($v) {
1083 $clause[] = "$k = '$v'";
1084 }
1085 }
1086 $clause = implode(' AND ', $clause);
1087 $query = "SELECT id FROM civicrm_contribution WHERE $clause";
1088 $dao = CRM_Core_DAO::executeQuery($query, $input);
1089
1090 while ($dao->fetch()) {
1091 $result = $dao->id;
1092 return $result;
1093 }
1094 return NULL;
1095 }
1096
1097 /**
1098 * Function to get the contribution details for component export
1099 *
1100 * @param int $exportMode export mode
1101 * @param string $componentIds component ids
1102 *
1103 * @return array associated array
1104 *
1105 * @static
1106 * @access public
1107 */
1108 static function getContributionDetails($exportMode, $componentIds) {
1109 $paymentDetails = array();
1110 $componentClause = ' IN ( ' . implode(',', $componentIds) . ' ) ';
1111
1112 if ($exportMode == CRM_Export_Form_Select::EVENT_EXPORT) {
1113 $componentSelect = " civicrm_participant_payment.participant_id id";
1114 $additionalClause = "
1115INNER JOIN civicrm_participant_payment ON (civicrm_contribution.id = civicrm_participant_payment.contribution_id
1116AND civicrm_participant_payment.participant_id {$componentClause} )
1117";
1118 }
1119 elseif ($exportMode == CRM_Export_Form_Select::MEMBER_EXPORT) {
1120 $componentSelect = " civicrm_membership_payment.membership_id id";
1121 $additionalClause = "
1122INNER JOIN civicrm_membership_payment ON (civicrm_contribution.id = civicrm_membership_payment.contribution_id
1123AND civicrm_membership_payment.membership_id {$componentClause} )
1124";
1125 }
1126 elseif ($exportMode == CRM_Export_Form_Select::PLEDGE_EXPORT) {
1127 $componentSelect = " civicrm_pledge_payment.id id";
1128 $additionalClause = "
1129INNER JOIN civicrm_pledge_payment ON (civicrm_contribution.id = civicrm_pledge_payment.contribution_id
1130AND civicrm_pledge_payment.pledge_id {$componentClause} )
1131";
1132 }
1133
1134 $query = " SELECT total_amount, contribution_status.name as status_id, contribution_status.label as status, payment_instrument.name as payment_instrument, receive_date,
1135 trxn_id, {$componentSelect}
1136FROM civicrm_contribution
1137LEFT JOIN civicrm_option_group option_group_payment_instrument ON ( option_group_payment_instrument.name = 'payment_instrument')
1138LEFT JOIN civicrm_option_value payment_instrument ON (civicrm_contribution.payment_instrument_id = payment_instrument.value
1139 AND option_group_payment_instrument.id = payment_instrument.option_group_id )
1140LEFT JOIN civicrm_option_group option_group_contribution_status ON (option_group_contribution_status.name = 'contribution_status')
1141LEFT JOIN civicrm_option_value contribution_status ON (civicrm_contribution.contribution_status_id = contribution_status.value
1142 AND option_group_contribution_status.id = contribution_status.option_group_id )
1143{$additionalClause}
1144";
1145
1146 $dao = CRM_Core_DAO::executeQuery($query, CRM_Core_DAO::$_nullArray);
1147
1148 while ($dao->fetch()) {
1149 $paymentDetails[$dao->id] = array(
1150 'total_amount' => $dao->total_amount,
1151 'contribution_status' => $dao->status,
1152 'receive_date' => $dao->receive_date,
1153 'pay_instru' => $dao->payment_instrument,
1154 'trxn_id' => $dao->trxn_id,
1155 );
1156 }
1157
1158 return $paymentDetails;
1159 }
1160
1161 /**
1162 * Function to create address associated with contribution record.
1163 * @param array $params an associated array
1164 * @param int $billingID $billingLocationTypeID
1165 *
1166 * @return address id
1167 * @static
1168 */
1169 static function createAddress(&$params, $billingLocationTypeID) {
1170 $billingFields = array(
1171 'street_address',
1172 'city',
1173 'state_province_id',
1174 'postal_code',
1175 'country_id',
1176 );
1177
1178 //build address array
1179 $addressParams = array();
1180 $addressParams['location_type_id'] = $billingLocationTypeID;
1181 $addressParams['is_billing'] = 1;
3fb990f4
RN
1182
1183 $billingFirstName = CRM_Utils_Array::value('billing_first_name', $params);
1184 $billingMiddleName = CRM_Utils_Array::value('billing_middle_name', $params);
1185 $billingLastName = CRM_Utils_Array::value('billing_last_name', $params);
1186 $addressParams['address_name'] = "{$billingFirstName}" . CRM_Core_DAO::VALUE_SEPARATOR . "{$billingMiddleName}" . CRM_Core_DAO::VALUE_SEPARATOR . "{$billingLastName}";
6a488035
TO
1187
1188 foreach ($billingFields as $value) {
3fb990f4 1189 $addressParams[$value] = CRM_Utils_Array::value("billing_{$value}-{$billingLocationTypeID}", $params);
6a488035
TO
1190 }
1191
1192 $address = CRM_Core_BAO_Address::add($addressParams, FALSE);
1193
1194 return $address->id;
1195 }
1196
6a488035
TO
1197 /**
1198 * Delete billing address record related contribution
1199 *
1200 * @param int $contact_id contact id
1201 * @param int $contribution_id contributionId
1202 * @access public
1203 * @static
1204 */
1205 static function deleteAddress($contributionId = NULL, $contactId = NULL) {
1206 $clauses = array();
1207 $contactJoin = NULL;
1208
1209 if ($contributionId) {
1210 $clauses[] = "cc.id = {$contributionId}";
1211 }
1212
1213 if ($contactId) {
1214 $clauses[] = "cco.id = {$contactId}";
1215 $contactJoin = "INNER JOIN civicrm_contact cco ON cc.contact_id = cco.id";
1216 }
1217
1218 if (empty($clauses)) {
1219 CRM_Core_Error::fatal();
1220 }
1221
1222 $condition = implode(' OR ', $clauses);
1223
1224 $query = "
1225SELECT ca.id
1226FROM civicrm_address ca
1227INNER JOIN civicrm_contribution cc ON cc.address_id = ca.id
1228 $contactJoin
1229WHERE $condition
1230";
1231 $dao = CRM_Core_DAO::executeQuery($query);
1232
1233 while ($dao->fetch()) {
1234 $params = array('id' => $dao->id);
1235 CRM_Core_BAO_Block::blockDelete('Address', $params);
1236 }
1237 }
1238
1239 /**
1240 * This function check online pending contribution associated w/
1241 * Online Event Registration or Online Membership signup.
1242 *
1243 * @param int $componentId participant/membership id.
1244 * @param string $componentName Event/Membership.
1245 *
1246 * @return $contributionId pending contribution id.
1247 * @static
1248 */
1249 static function checkOnlinePendingContribution($componentId, $componentName) {
1250 $contributionId = NULL;
1251 if (!$componentId ||
1252 !in_array($componentName, array('Event', 'Membership'))
1253 ) {
1254 return $contributionId;
1255 }
1256
1257 if ($componentName == 'Event') {
1258 $idName = 'participant_id';
1259 $componentTable = 'civicrm_participant';
1260 $paymentTable = 'civicrm_participant_payment';
1261 $source = ts('Online Event Registration');
1262 }
1263
1264 if ($componentName == 'Membership') {
1265 $idName = 'membership_id';
1266 $componentTable = 'civicrm_membership';
1267 $paymentTable = 'civicrm_membership_payment';
1268 $source = ts('Online Contribution');
1269 }
1270
1271 $pendingStatusId = array_search('Pending', CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name'));
1272
1273 $query = "
1274 SELECT component.id as {$idName},
1275 componentPayment.contribution_id as contribution_id,
1276 contribution.source source,
1277 contribution.contribution_status_id as contribution_status_id,
1278 contribution.is_pay_later as is_pay_later
1279 FROM $componentTable component
1280LEFT JOIN $paymentTable componentPayment ON ( componentPayment.{$idName} = component.id )
1281LEFT JOIN civicrm_contribution contribution ON ( componentPayment.contribution_id = contribution.id )
1282 WHERE component.id = {$componentId}";
1283
1284 $dao = CRM_Core_DAO::executeQuery($query);
1285
1286 while ($dao->fetch()) {
1287 if ($dao->contribution_id &&
1288 $dao->is_pay_later &&
1289 $dao->contribution_status_id == $pendingStatusId &&
1290 strpos($dao->source, $source) !== FALSE
1291 ) {
1292 $contributionId = $dao->contribution_id;
1293 $dao->free();
1294 }
1295 }
1296
1297 return $contributionId;
1298 }
1299
1300 /**
1301 * This function update contribution as well as related objects.
1302 */
1303 function transitionComponents($params, $processContributionObject = FALSE) {
1304 // get minimum required values.
1305 $contactId = CRM_Utils_Array::value('contact_id', $params);
1306 $componentId = CRM_Utils_Array::value('component_id', $params);
1307 $componentName = CRM_Utils_Array::value('componentName', $params);
1308 $contributionId = CRM_Utils_Array::value('contribution_id', $params);
1309 $contributionStatusId = CRM_Utils_Array::value('contribution_status_id', $params);
1310
1311 // if we already processed contribution object pass previous status id.
1312 $previousContriStatusId = CRM_Utils_Array::value('previous_contribution_status_id', $params);
1313
1314 $updateResult = array();
1315
1316 $contributionStatuses = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name');
1317
1318 // we process only ( Completed, Cancelled, or Failed ) contributions.
1319 if (!$contributionId ||
1320 !in_array($contributionStatusId, array(array_search('Completed', $contributionStatuses),
1321 array_search('Cancelled', $contributionStatuses),
1322 array_search('Failed', $contributionStatuses),
1323 ))
1324 ) {
1325 return $updateResult;
1326 }
1327
1328 if (!$componentName || !$componentId) {
1329 // get the related component details.
1330 $componentDetails = self::getComponentDetails($contributionId);
1331 }
1332 else {
1333 $componentDetails['contact_id'] = $contactId;
1334 $componentDetails['component'] = $componentName;
1335
1336 if ($componentName == 'event') {
1337 $componentDetails['participant'] = $componentId;
1338 }
1339 else {
1340 $componentDetails['membership'] = $componentId;
1341 }
1342 }
1343
1344 if (CRM_Utils_Array::value('contact_id', $componentDetails)) {
1345 $componentDetails['contact_id'] = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution',
1346 $contributionId,
1347 'contact_id'
1348 );
1349 }
1350
1351 // do check for required ids.
1352 if (!CRM_Utils_Array::value('membership', $componentDetails) &&
1353 !CRM_Utils_Array::value('participant', $componentDetails) &&
1354 !CRM_Utils_Array::value('pledge_payment', $componentDetails) ||
1355 !CRM_Utils_Array::value('contact_id', $componentDetails)
1356 ) {
1357 return $updateResult;
1358 }
1359
1360 //now we are ready w/ required ids, start processing.
1361
1362 $baseIPN = new CRM_Core_Payment_BaseIPN();
1363
1364 $input = $ids = $objects = array();
1365
1366 $input['component'] = CRM_Utils_Array::value('component', $componentDetails);
1367 $ids['contribution'] = $contributionId;
1368 $ids['contact'] = CRM_Utils_Array::value('contact_id', $componentDetails);
1369 $ids['membership'] = CRM_Utils_Array::value('membership', $componentDetails);
1370 $ids['participant'] = CRM_Utils_Array::value('participant', $componentDetails);
1371 $ids['event'] = CRM_Utils_Array::value('event', $componentDetails);
1372 $ids['pledge_payment'] = CRM_Utils_Array::value('pledge_payment', $componentDetails);
1373 $ids['contributionRecur'] = NULL;
1374 $ids['contributionPage'] = NULL;
1375
1376 if (!$baseIPN->validateData($input, $ids, $objects, FALSE)) {
1377 CRM_Core_Error::fatal();
1378 }
1379
1380 $memberships = &$objects['membership'];
1381 $participant = &$objects['participant'];
1382 $pledgePayment = &$objects['pledge_payment'];
1383 $contribution = &$objects['contribution'];
1384
1385 if ($pledgePayment) {
1386 $pledgePaymentIDs = array();
1387 foreach ($pledgePayment as $key => $object) {
1388 $pledgePaymentIDs[] = $object->id;
1389 }
1390 $pledgeID = $pledgePayment[0]->pledge_id;
1391 }
1392
6a488035
TO
1393 $membershipStatuses = CRM_Member_PseudoConstant::membershipStatus();
1394
1395 if ($participant) {
1396 $participantStatuses = CRM_Event_PseudoConstant::participantStatus();
1397 $oldStatus = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Participant',
1398 $participant->id,
1399 'status_id'
1400 );
1401 }
1402 // we might want to process contribution object.
1403 $processContribution = FALSE;
1404 if ($contributionStatusId == array_search('Cancelled', $contributionStatuses)) {
1405 if (is_array($memberships)) {
1406 foreach ($memberships as $membership) {
1407 if ($membership) {
1408 $membership->status_id = array_search('Cancelled', $membershipStatuses);
1409 $membership->save();
1410
1411 $updateResult['updatedComponents']['CiviMember'] = $membership->status_id;
1412 if ($processContributionObject) {
1413 $processContribution = TRUE;
1414 }
1415 }
1416 }
1417 }
1418
1419 if ($participant) {
1420 $updatedStatusId = array_search('Cancelled', $participantStatuses);
1421 CRM_Event_BAO_Participant::updateParticipantStatus($participant->id, $oldStatus, $updatedStatusId, TRUE);
1422
1423 $updateResult['updatedComponents']['CiviEvent'] = $updatedStatusId;
1424 if ($processContributionObject) {
1425 $processContribution = TRUE;
1426 }
1427 }
1428
1429 if ($pledgePayment) {
1430 CRM_Pledge_BAO_PledgePayment::updatePledgePaymentStatus($pledgeID, $pledgePaymentIDs, $contributionStatusId);
1431
1432 $updateResult['updatedComponents']['CiviPledge'] = $contributionStatusId;
1433 if ($processContributionObject) {
1434 $processContribution = TRUE;
1435 }
1436 }
1437 }
1438 elseif ($contributionStatusId == array_search('Failed', $contributionStatuses)) {
1439 if (is_array($memberships)) {
1440 foreach ($memberships as $membership) {
1441 if ($membership) {
1442 $membership->status_id = array_search('Expired', $membershipStatuses);
1443 $membership->save();
1444
1445 $updateResult['updatedComponents']['CiviMember'] = $membership->status_id;
1446 if ($processContributionObject) {
1447 $processContribution = TRUE;
1448 }
1449 }
1450 }
1451 }
1452 if ($participant) {
1453 $updatedStatusId = array_search('Cancelled', $participantStatuses);
1454 CRM_Event_BAO_Participant::updateParticipantStatus($participant->id, $oldStatus, $updatedStatusId, TRUE);
1455
1456 $updateResult['updatedComponents']['CiviEvent'] = $updatedStatusId;
1457 if ($processContributionObject) {
1458 $processContribution = TRUE;
1459 }
1460 }
1461
1462 if ($pledgePayment) {
1463 CRM_Pledge_BAO_PledgePayment::updatePledgePaymentStatus($pledgeID, $pledgePaymentIDs, $contributionStatusId);
1464
1465 $updateResult['updatedComponents']['CiviPledge'] = $contributionStatusId;
1466 if ($processContributionObject) {
1467 $processContribution = TRUE;
1468 }
1469 }
1470 }
1471 elseif ($contributionStatusId == array_search('Completed', $contributionStatuses)) {
1472
1473 // only pending contribution related object processed.
1474 if ($previousContriStatusId &&
1475 ($previousContriStatusId != array_search('Pending', $contributionStatuses))
1476 ) {
1477 // this is case when we already processed contribution object.
1478 return $updateResult;
1479 }
1480 elseif (!$previousContriStatusId &&
1481 $contribution->contribution_status_id != array_search('Pending', $contributionStatuses)
1482 ) {
1483 // this is case when we are going to process contribution object later.
1484 return $updateResult;
1485 }
1486
1487 if (is_array($memberships)) {
1488 foreach ($memberships as $membership) {
1489 if ($membership) {
1490 $format = '%Y%m%d';
1491
1492 //CRM-4523
1493 $currentMembership = CRM_Member_BAO_Membership::getContactMembership($membership->contact_id,
1494 $membership->membership_type_id,
1495 $membership->is_test, $membership->id
1496 );
1497
1498 // CRM-8141 update the membership type with the value recorded in log when membership created/renewed
1499 // this picks up membership type changes during renewals
1500 $sql = "
1501 SELECT membership_type_id
1502 FROM civicrm_membership_log
1503 WHERE membership_id=$membership->id
1504 ORDER BY id DESC
1505 LIMIT 1;";
1506 $dao = new CRM_Core_DAO;
1507 $dao->query($sql);
1508 if ($dao->fetch()) {
1509 if (!empty($dao->membership_type_id)) {
1510 $membership->membership_type_id = $dao->membership_type_id;
1511 $membership->save();
1512 }
1513 }
1514 // else fall back to using current membership type
1515 $dao->free();
1516
9c09f5b7
AH
1517 // Figure out number of terms
1518 $numterms = 1;
1519 $lineitems = CRM_Price_BAO_LineItem::getLineItems($contributionId, 'contribution');
1520 foreach ($lineitems as $lineitem) {
1521 if ($membership->membership_type_id == CRM_Utils_Array::value('membership_type_id', $lineitem)) {
1522 $numterms = CRM_Utils_Array::value('membership_num_terms', $lineitem);
2d77a516 1523
9c09f5b7
AH
1524 // in case membership_num_terms comes through as null or zero
1525 $numterms = $numterms >= 1 ? $numterms : 1;
1526 break;
1527 }
1528 }
1529
6a488035 1530 if ($currentMembership) {
9c09f5b7
AH
1531 CRM_Member_BAO_Membership::fixMembershipStatusBeforeRenew($currentMembership, NULL);
1532 $dates = CRM_Member_BAO_MembershipType::getRenewalDatesForMembershipType($membership->id, NULL, NULL, $numterms);
6a488035
TO
1533 $dates['join_date'] = CRM_Utils_Date::customFormat($currentMembership['join_date'], $format);
1534 }
1535 else {
c2585c5b 1536 $dates = CRM_Member_BAO_MembershipType::getDatesForMembershipType($membership->membership_type_id, NULL, NULL, NULL, $numterms);
6a488035
TO
1537 }
1538
1539 //get the status for membership.
1540 $calcStatus = CRM_Member_BAO_MembershipStatus::getMembershipStatusByDate($dates['start_date'],
1541 $dates['end_date'],
1542 $dates['join_date'],
1543 'today',
1544 TRUE
1545 );
1546
c2585c5b 1547 $formattedParams = array(
6a488035
TO
1548 'status_id' => CRM_Utils_Array::value('id', $calcStatus,
1549 array_search('Current', $membershipStatuses)
1550 ),
1551 'join_date' => CRM_Utils_Date::customFormat($dates['join_date'], $format),
1552 'start_date' => CRM_Utils_Date::customFormat($dates['start_date'], $format),
1553 'end_date' => CRM_Utils_Date::customFormat($dates['end_date'], $format),
1554 );
1555
c2585c5b 1556 CRM_Utils_Hook::pre('edit', 'Membership', $membership->id, $formattedParams);
6a488035 1557
c2585c5b 1558 $membership->copyValues($formattedParams);
6a488035
TO
1559 $membership->save();
1560
1561 //updating the membership log
1562 $membershipLog = array();
c2585c5b 1563 $membershipLog = $formattedParams;
e5ab073b 1564 $logStartDate = CRM_Utils_Date::customFormat(CRM_Utils_Array::value('log_start_date', $dates), $format);
c2585c5b 1565 $logStartDate = ($logStartDate) ? CRM_Utils_Date::isoToMysql($logStartDate) : $formattedParams['start_date'];
6a488035
TO
1566
1567 $membershipLog['start_date'] = $logStartDate;
1568 $membershipLog['membership_id'] = $membership->id;
1569 $membershipLog['modified_id'] = $membership->contact_id;
1570 $membershipLog['modified_date'] = date('Ymd');
1571 $membershipLog['membership_type_id'] = $membership->membership_type_id;
1572
1573 CRM_Member_BAO_MembershipLog::add($membershipLog, CRM_Core_DAO::$_nullArray);
1574
1575 //update related Memberships.
c2585c5b 1576 CRM_Member_BAO_Membership::updateRelatedMemberships($membership->id, $formattedParams);
6a488035
TO
1577
1578 $updateResult['membership_end_date'] = CRM_Utils_Date::customFormat($dates['end_date'],
1579 '%B %E%f, %Y'
1580 );
1581 $updateResult['updatedComponents']['CiviMember'] = $membership->status_id;
1582 if ($processContributionObject) {
1583 $processContribution = TRUE;
1584 }
1585
1586 CRM_Utils_Hook::post('edit', 'Membership', $membership->id, $membership);
1587 }
1588 }
1589 }
1590
1591 if ($participant) {
1592 $updatedStatusId = array_search('Registered', $participantStatuses);
1593 CRM_Event_BAO_Participant::updateParticipantStatus($participant->id, $oldStatus, $updatedStatusId, TRUE);
1594
1595 $updateResult['updatedComponents']['CiviEvent'] = $updatedStatusId;
1596 if ($processContributionObject) {
1597 $processContribution = TRUE;
1598 }
1599 }
1600
1601 if ($pledgePayment) {
1602 CRM_Pledge_BAO_PledgePayment::updatePledgePaymentStatus($pledgeID, $pledgePaymentIDs, $contributionStatusId);
1603
1604 $updateResult['updatedComponents']['CiviPledge'] = $contributionStatusId;
1605 if ($processContributionObject) {
1606 $processContribution = TRUE;
1607 }
1608 }
1609 }
1610
1611 // process contribution object.
1612 if ($processContribution) {
1613 $contributionParams = array();
1614 $fields = array(
1615 'contact_id', 'total_amount', 'receive_date', 'is_test', 'campaign_id',
1616 'payment_instrument_id', 'trxn_id', 'invoice_id', 'financial_type_id',
1617 'contribution_status_id', 'non_deductible_amount', 'receipt_date', 'check_number',
1618 );
1619 foreach ($fields as $field) {
1620 if (!CRM_Utils_Array::value($field, $params)) {
1621 continue;
1622 }
1623 $contributionParams[$field] = $params[$field];
1624 }
1625
1626 $ids = array('contribution' => $contributionId);
1627 $contribution = CRM_Contribute_BAO_Contribution::create($contributionParams, $ids);
1628 }
1629
1630 return $updateResult;
1631 }
1632
1633 /**
1634 * This function returns all contribution related object ids.
1635 */
1636 function getComponentDetails($contributionId) {
1637 $componentDetails = $pledgePayment = array();
1638 if (!$contributionId) {
1639 return $componentDetails;
1640 }
1641
1642 $query = "
1643 SELECT c.id as contribution_id,
1644 c.contact_id as contact_id,
1645 mp.membership_id as membership_id,
1646 m.membership_type_id as membership_type_id,
1647 pp.participant_id as participant_id,
1648 p.event_id as event_id,
1649 pgp.id as pledge_payment_id
1650 FROM civicrm_contribution c
1651 LEFT JOIN civicrm_membership_payment mp ON mp.contribution_id = c.id
1652 LEFT JOIN civicrm_participant_payment pp ON pp.contribution_id = c.id
1653 LEFT JOIN civicrm_participant p ON pp.participant_id = p.id
1654 LEFT JOIN civicrm_membership m ON m.id = mp.membership_id
1655 LEFT JOIN civicrm_pledge_payment pgp ON pgp.contribution_id = c.id
1656 WHERE c.id = $contributionId";
1657
1658 $dao = CRM_Core_DAO::executeQuery($query);
1659 $componentDetails = array();
1660
1661 while ($dao->fetch()) {
1662 $componentDetails['component'] = $dao->participant_id ? 'event' : 'contribute';
1663 $componentDetails['contact_id'] = $dao->contact_id;
1664 if ($dao->event_id) {
1665 $componentDetails['event'] = $dao->event_id;
1666 }
1667 if ($dao->participant_id) {
1668 $componentDetails['participant'] = $dao->participant_id;
1669 }
1670 if ($dao->membership_id) {
1671 if (!isset($componentDetails['membership'])) {
1672 $componentDetails['membership'] = $componentDetails['membership_type'] = array();
1673 }
1674 $componentDetails['membership'][] = $dao->membership_id;
1675 $componentDetails['membership_type'][] = $dao->membership_type_id;
1676 }
1677 if ($dao->pledge_payment_id) {
1678 $pledgePayment[] = $dao->pledge_payment_id;
1679 }
1680 }
1681
1682 if ($pledgePayment) {
1683 $componentDetails['pledge_payment'] = $pledgePayment;
1684 }
1685
1686 return $componentDetails;
1687 }
1688
1689 static function contributionCount($contactId, $includeSoftCredit = TRUE, $includeHonoree = TRUE) {
1690 if (!$contactId) {
1691 return 0;
1692 }
1693
1694 $fromClause = "civicrm_contribution contribution";
1695 $whereConditions = array("contribution.contact_id = {$contactId}");
1696 if ($includeSoftCredit) {
1697 $fromClause .= " LEFT JOIN civicrm_contribution_soft softContribution
1698 ON ( contribution.id = softContribution.contribution_id )";
1699 $whereConditions[] = " softContribution.contact_id = {$contactId}";
1700 }
1701 if ($includeHonoree) {
1702 $whereConditions[] = " contribution.honor_contact_id = {$contactId}";
1703 }
1704 $whereClause = " contribution.is_test = 0 AND ( " . implode(' OR ', $whereConditions) . " )";
1705
1706 $query = "
1707 SELECT count( contribution.id ) count
1708 FROM {$fromClause}
1709 WHERE {$whereClause}";
1710
1711 return CRM_Core_DAO::singleValueQuery($query);
1712 }
1713
1714 /**
1715 * Function to get individual id for onbehalf contribution
1716 *
1717 * @param int $contributionId contribution id
c2585c5b 1718 * @param int $contributorId contributor id
6a488035
TO
1719 *
1720 * @return array $ids containing organization id and individual id
1721 * @access public
1722 */
1723 function getOnbehalfIds($contributionId, $contributorId = NULL) {
1724
1725 $ids = array();
1726
1727 if (!$contributionId) {
1728 return $ids;
1729 }
1730
1731 // fetch contributor id if null
1732 if (!$contributorId) {
1733 $contributorId = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution',
1734 $contributionId, 'contact_id'
1735 );
1736 }
1737
1738 $activityTypeIds = CRM_Core_PseudoConstant::activityType(TRUE, FALSE, FALSE, 'name');
1739 $activityTypeId = array_search('Contribution', $activityTypeIds);
1740
1741 if ($activityTypeId && $contributorId) {
1742 $activityQuery = "
2d77a516
DL
1743SELECT civicrm_activity_contact.contact_id
1744 FROM civicrm_activity_contact
1745INNER JOIN civicrm_activity ON civicrm_activity_contact.activity_id = civicrm_activity.id
1746 WHERE civicrm_activity.activity_type_id = %1
1747 AND civicrm_activity.source_record_id = %2
1748 AND civicrm_activity_contact.record_type_id = %3
1749";
6a488035 1750
e7e657f0 1751 $activityContacts = CRM_Core_OptionGroup::values('activity_contacts', FALSE, FALSE, FALSE, NULL, 'name');
2d77a516
DL
1752 $sourceID = CRM_Utils_Array::key('Activity Source', $activityContacts);
1753
1754 $params = array(
1755 1 => array($activityTypeId, 'Integer'),
6a488035 1756 2 => array($contributionId, 'Integer'),
2d77a516 1757 3 => array($sourceID, 'Integer'),
6a488035
TO
1758 );
1759
1760 $sourceContactId = CRM_Core_DAO::singleValueQuery($activityQuery, $params);
1761
1762 // for on behalf contribution source is individual and contributor is organization
1763 if ($sourceContactId && $sourceContactId != $contributorId) {
1764 $relationshipTypeIds = CRM_Core_PseudoConstant::relationshipType('name');
1765 // get rel type id for employee of relation
1766 foreach ($relationshipTypeIds as $id => $typeVals) {
1767 if ($typeVals['name_a_b'] == 'Employee of') {
1768 $relationshipTypeId = $id;
1769 break;
1770 }
1771 }
1772
1773 $rel = new CRM_Contact_DAO_Relationship();
1774 $rel->relationship_type_id = $relationshipTypeId;
1775 $rel->contact_id_a = $sourceContactId;
1776 $rel->contact_id_b = $contributorId;
1777 if ($rel->find(TRUE)) {
1778 $ids['individual_id'] = $rel->contact_id_a;
1779 $ids['organization_id'] = $rel->contact_id_b;
1780 }
1781 }
1782 }
1783
1784 return $ids;
1785 }
1786
1787 /**
1788 * @return array
1789 * @static
1790 */
1791 static function getContributionDates() {
1792 $config = CRM_Core_Config::singleton();
1793 $currentMonth = date('m');
1794 $currentDay = date('d');
1795 if ((int ) $config->fiscalYearStart['M'] > $currentMonth ||
1796 ((int ) $config->fiscalYearStart['M'] == $currentMonth &&
1797 (int ) $config->fiscalYearStart['d'] > $currentDay
1798 )
1799 ) {
1800 $year = date('Y') - 1;
1801 }
1802 else {
1803 $year = date('Y');
1804 }
1805 $year = array('Y' => $year);
1806 $yearDate = $config->fiscalYearStart;
1807 $yearDate = array_merge($year, $yearDate);
1808 $yearDate = CRM_Utils_Date::format($yearDate);
1809
1810 $monthDate = date('Ym') . '01';
1811
1812 $now = date('Ymd');
1813
1814 return array(
1815 'now' => $now,
1816 'yearDate' => $yearDate,
1817 'monthDate' => $monthDate,
1818 );
1819 }
1820
1821 /*
1822 * Load objects relations to contribution object
1823 * Objects are stored in the $_relatedObjects property
1824 * In the first instance we are just moving functionality from BASEIpn -
1825 * see http://issues.civicrm.org/jira/browse/CRM-9996
1826 *
1827 * @param array $input Input as delivered from Payment Processor
1828 * @param array $ids Ids as Loaded by Payment Processor
1829 * @param boolean $required Is Payment processor / contribution page required
1830 * @param boolean $loadAll - load all related objects - even where id not passed in? (allows API to call this)
1831 * Note that the unit test for the BaseIPN class tests this function
1832 */
1833 function loadRelatedObjects(&$input, &$ids, $required = FALSE, $loadAll = false) {
1834 if($loadAll){
1835 $ids = array_merge($this->getComponentDetails($this->id),$ids);
1836 if(empty($ids['contact']) && isset($this->contact_id)){
1837 $ids['contact'] = $this->contact_id;
1838 }
1839 }
1840 if (empty($this->_component)) {
1841 if (! empty($ids['event'])) {
1842 $this->_component = 'event';
1843 }
1844 else {
1845 $this->_component = strtolower(CRM_Utils_Array::value('component', $input, 'contribute'));
1846 }
1847 }
1848 $paymentProcessorID = CRM_Utils_Array::value('paymentProcessor', $ids);
1849 $contributionType = new CRM_Financial_BAO_FinancialType();
1850 $contributionType->id = $this->financial_type_id;
1851 if (!$contributionType->find(TRUE)) {
1852 throw new Exception("Could not find financial type record: " . $this->financial_type_id);
1853 }
1854 if (!empty($ids['contact'])) {
1855 $this->_relatedObjects['contact'] = new CRM_Contact_BAO_Contact();
1856 $this->_relatedObjects['contact']->id = $ids['contact'];
1857 $this->_relatedObjects['contact']->find(TRUE);
1858 }
1859 $this->_relatedObjects['contributionType'] = $contributionType;
1860
1861 if ($this->_component == 'contribute') {
1862 // retrieve the other optional objects first so
1863 // stuff down the line can use this info and do things
1864 // CRM-6056
1865 //in any case get the memberships associated with the contribution
1866 //because we now support multiple memberships w/ price set
1867 // see if there are any other memberships to be considered for same contribution.
1868 $query = "
1869 SELECT membership_id
1870 FROM civicrm_membership_payment
1871WHERE contribution_id = %1 ";
1872 $params = array(1 => array($this->id, 'Integer'));
1873
1874 $dao = CRM_Core_DAO::executeQuery($query, $params );
1875 while ($dao->fetch()) {
1876 if ($dao->membership_id) {
1877 if (!is_array($ids['membership'])) {
1878 $ids['membership'] = array();
1879 }
1880 $ids['membership'][] = $dao->membership_id;
1881 }
1882 }
1883
1884 if (array_key_exists('membership', $ids) && is_array($ids['membership'])) {
1885 foreach ($ids['membership'] as $id) {
1886 if (!empty($id)) {
1887 $membership = new CRM_Member_BAO_Membership();
1888 $membership->id = $id;
1889 if (!$membership->find(TRUE)) {
1890 throw new Exception("Could not find membership record: $id");
1891 }
1892 $membership->join_date = CRM_Utils_Date::isoToMysql($membership->join_date);
1893 $membership->start_date = CRM_Utils_Date::isoToMysql($membership->start_date);
1894 $membership->end_date = CRM_Utils_Date::isoToMysql($membership->end_date);
1895 $this->_relatedObjects['membership'][$membership->membership_type_id] = $membership;
1896 $membership->free();
1897 }
1898 }
1899 }
1900
1901 if (!empty($ids['pledge_payment'])) {
1902
1903 foreach ($ids['pledge_payment'] as $key => $paymentID) {
1904 if (empty($paymentID)) {
1905 continue;
1906 }
1907 $payment = new CRM_Pledge_BAO_PledgePayment();
1908 $payment->id = $paymentID;
1909 if (!$payment->find(TRUE)) {
1910 throw new Exception("Could not find pledge payment record: " . $paymentID);
1911 }
1912 $this->_relatedObjects['pledge_payment'][] = $payment;
1913 }
1914 }
1915
1916 if (!empty($ids['contributionRecur'])) {
1917 $recur = new CRM_Contribute_BAO_ContributionRecur();
1918 $recur->id = $ids['contributionRecur'];
1919 if (!$recur->find(TRUE)) {
1920 throw new Exception("Could not find recur record: " . $ids['contributionRecur']);
1921 }
1922 $this->_relatedObjects['contributionRecur'] = &$recur;
1923 //get payment processor id from recur object.
1924 $paymentProcessorID = $recur->payment_processor_id;
1925 }
1926 //for normal contribution get the payment processor id.
1927 if (!$paymentProcessorID) {
1928 if ($this->contribution_page_id) {
1929 // get the payment processor id from contribution page
1930 $paymentProcessorID = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_ContributionPage',
1931 $this->contribution_page_id,
1932 'payment_processor'
1933 );
1934 }
1935 //fail to load payment processor id.
1936 elseif (!CRM_Utils_Array::value('pledge_payment', $ids)) {
1937 $loadObjectSuccess = TRUE;
1938 if ($required) {
1939 throw new Exception("Could not find contribution page for contribution record: " . $this->id);
1940 }
1941 return $loadObjectSuccess;
1942 }
1943 }
1944 }
1945 else {
1946 // we are in event mode
1947 // make sure event exists and is valid
1948 $event = new CRM_Event_BAO_Event();
1949 $event->id = $ids['event'];
1950 if ($ids['event'] &&
1951 !$event->find(TRUE)
1952 ) {
1953 throw new Exception("Could not find event: " . $ids['event']);
1954 }
1955
1956 $this->_relatedObjects['event'] = &$event;
1957
1958 $participant = new CRM_Event_BAO_Participant();
1959 $participant->id = $ids['participant'];
1960 if ($ids['participant'] &&
1961 !$participant->find(TRUE)
1962 ) {
1963 throw new Exception("Could not find participant: " . $ids['participant']);
1964 }
1965 $participant->register_date = CRM_Utils_Date::isoToMysql($participant->register_date);
1966
1967 $this->_relatedObjects['participant'] = &$participant;
1968
1969 if (!$paymentProcessorID) {
1970 $paymentProcessorID = $this->_relatedObjects['event']->payment_processor;
1971 }
1972 }
1973
1974 $loadObjectSuccess = TRUE;
1975 if ($paymentProcessorID) {
1976 $paymentProcessor = CRM_Financial_BAO_PaymentProcessor::getPayment($paymentProcessorID,
1977 $this->is_test ? 'test' : 'live'
1978 );
1979 $ids['paymentProcessor'] = $paymentProcessorID;
1980 $this->_relatedObjects['paymentProcessor'] = &$paymentProcessor;
1981 }
1982 elseif ($required) {
1983 $loadObjectSuccess = FALSE;
1984 throw new Exception("Could not find payment processor for contribution record: " . $this->id);
1985 }
1986
1987 return $loadObjectSuccess;
1988 }
1989
1990 /*
1991 * Create array of message information - ie. return html version, txt version, to field
1992 *
1993 * @param array $input incoming information
1994 * - is_recur - should this be treated as recurring (not sure why you wouldn't
1995 * just check presence of recur object but maintaining legacy approach
1996 * to be careful)
1997 * @param array $ids IDs of related objects
1998 * @param array $values any values that may have already been compiled by calling process
1999 * This is augmented by values 'gathered' by gatherMessageValues
2000 * @param bool $returnMessageText distinguishes between whether to send message or return
2001 * message text. We are working towards this function ALWAYS returning message text & calling
2002 * function doing emails / pdfs with it
2003 * @return array $messageArray - messages
2004 */
2005 function composeMessageArray(&$input, &$ids, &$values, $recur = FALSE, $returnMessageText = TRUE) {
2006 if (empty($this->_relatedObjects)) {
2007 $this->loadRelatedObjects($input, $ids);
2008 }
2009 if (empty($this->_component)) {
2010 $this->_component = CRM_Utils_Array::value('component', $input);
2011 }
2012
2013 //not really sure what params might be passed in but lets merge em into values
2014 $values = array_merge($this->_gatherMessageValues($input, $values, $ids), $values);
2015 $template = CRM_Core_Smarty::singleton();
2016 $this->_assignMessageVariablesToTemplate($values, $input, $template, $recur, $returnMessageText);
2017 //what does recur 'mean here - to do with payment processor return functionality but
2018 // what is the importance
2019 if ($recur && !empty($this->_relatedObjects['paymentProcessor'])) {
2020 $paymentObject = &CRM_Core_Payment::singleton(
2021 $this->is_test ? 'test' : 'live',
2022 $this->_relatedObjects['paymentProcessor']
2023 );
2024
2025 $entityID = $entity = NULL;
2026 if (isset($ids['contribution'])) {
2027 $entity = 'contribution';
2028 $entityID = $ids['contribution'];
2029 }
2030 if (isset($ids['membership']) && $ids['membership']) {
2031 $entity = 'membership';
2032 $entityID = $ids['membership'];
2033 }
2034
2035 $url = $paymentObject->subscriptionURL($entityID, $entity);
2036 $template->assign('cancelSubscriptionUrl', $url);
2037
2038 $url = $paymentObject->subscriptionURL($entityID, $entity, 'billing');
2039 $template->assign('updateSubscriptionBillingUrl', $url);
2040
2041 $url = $paymentObject->subscriptionURL($entityID, $entity, 'update');
2042 $template->assign('updateSubscriptionUrl', $url);
2043
2044 if ($this->_relatedObjects['paymentProcessor']['billing_mode'] & CRM_Core_Payment::BILLING_MODE_FORM) {
2045 //direct mode showing billing block, so use directIPN for temporary
2046 $template->assign('contributeMode', 'directIPN');
2047 }
2048 }
2049 // todo remove strtolower - check consistency
2050 if (strtolower($this->_component) == 'event') {
2051 return CRM_Event_BAO_Event::sendMail($ids['contact'], $values,
2052 $this->_relatedObjects['participant']->id, $this->is_test, $returnMessageText
2053 );
2054 }
2055 else {
2056 $values['contribution_id'] = $this->id;
2057 if (CRM_Utils_Array::value('related_contact', $ids)) {
2058 $values['related_contact'] = $ids['related_contact'];
2059 if (isset($ids['onbehalf_dupe_alert'])) {
2060 $values['onbehalf_dupe_alert'] = $ids['onbehalf_dupe_alert'];
2061 }
2062 $entityBlock = array(
2063 'contact_id' => $ids['contact'],
2064 'location_type_id' => CRM_Core_DAO::getFieldValue('CRM_Core_DAO_LocationType',
2065 'Home', 'id', 'name'
2066 ),
2067 );
2068 $address = CRM_Core_BAO_Address::getValues($entityBlock);
2069 $template->assign('onBehalfAddress', $address[$entityBlock['location_type_id']]['display']);
2070 }
2071 $isTest = FALSE;
2072 if ($this->is_test) {
2073 $isTest = TRUE;
2074 }
2075 if (!empty($this->_relatedObjects['membership'])) {
2076 foreach ($this->_relatedObjects['membership'] as $membership) {
2077 if ($membership->id) {
2078 $values['membership_id'] = $membership->id;
2079
2080 // need to set the membership values here
2081 $template->assign('membership_assign', 1);
2082 $template->assign('membership_name',
2083 CRM_Member_PseudoConstant::membershipType($membership->membership_type_id)
2084 );
2085 $template->assign('mem_start_date', $membership->start_date);
2086 $template->assign('mem_join_date', $membership->join_date);
2087 $template->assign('mem_end_date', $membership->end_date);
2088 $membership_status = CRM_Member_PseudoConstant::membershipStatus($membership->status_id, NULL, 'label');
2089 $template->assign('mem_status', $membership_status);
2090 if ($membership_status == 'Pending' && $membership->is_pay_later == 1) {
2091 $template->assign('is_pay_later', 1);
2092 }
2093
2094 // if separate payment there are two contributions recorded and the
2095 // admin will need to send a receipt for each of them separately.
2096 // we dont link the two in the db (but can potentially infer it if needed)
2097 $template->assign('is_separate_payment', 0);
2098
2099 if ($recur && $paymentObject) {
2100 $url = $paymentObject->subscriptionURL($membership->id, 'membership');
2101 $template->assign('cancelSubscriptionUrl', $url);
2102 $url = $paymentObject->subscriptionURL($membership->id, 'membership', 'billing');
2103 $template->assign('updateSubscriptionBillingUrl', $url);
2104 $url = $paymentObject->subscriptionURL($entityID, $entity, 'update');
2105 $template->assign('updateSubscriptionUrl', $url);
2106 }
2107
2108 $result = CRM_Contribute_BAO_ContributionPage::sendMail($ids['contact'], $values, $isTest, $returnMessageText);
2109
2110 return $result;
2111 // otherwise if its about sending emails, continue sending without return, as we
2112 // don't want to exit the loop.
2113 }
2114 }
2115 }
2116 else {
2117 return CRM_Contribute_BAO_ContributionPage::sendMail($ids['contact'], $values, $isTest, $returnMessageText);
2118 }
2119 }
2120 }
2121
2122 /*
2123 * Gather values for contribution mail - this function has been created
2124 * as part of CRM-9996 refactoring as a step towards simplifying the composeMessage function
2125 * Values related to the contribution in question are gathered
2126 *
2127 * @param array $input input into function (probably from payment processor)
2128 * @param array $ids the set of ids related to the inpurt
2129 *
2130 * @return array $values
2131 *
2132 * NB don't add direct calls to the function as we intend to change the signature
2133 */
2134 function _gatherMessageValues($input, &$values, $ids = array()) {
2135 // set display address of contributor
2136 if ($this->address_id) {
2137 $addressParams = array('id' => $this->address_id);
2138 $addressDetails = CRM_Core_BAO_Address::getValues($addressParams, FALSE, 'id');
2139 $addressDetails = array_values($addressDetails);
2140 $values['address'] = $addressDetails[0]['display'];
2141 }
2142 if ($this->_component == 'contribute') {
2143 if (isset($this->contribution_page_id)) {
2144 CRM_Contribute_BAO_ContributionPage::setValues(
2145 $this->contribution_page_id,
2146 $values
2147 );
2148 if ($this->contribution_page_id) {
2149 // CRM-8254 - override default currency if applicable
2150 $config = CRM_Core_Config::singleton();
2151 $config->defaultCurrency = CRM_Utils_Array::value(
2152 'currency',
2153 $values,
2154 $config->defaultCurrency
2155 );
2156 }
2157 }
2158 // no contribution page -probably back office
2159 else {
2160 // Handle re-print receipt for offline contributions (call from PDF.php - no contribution_page_id)
2161 $values['is_email_receipt'] = 1;
2162 $values['title'] = 'Contribution';
2163 }
2164 // set lineItem for contribution
2165 if ($this->id) {
2166 $lineItem = CRM_Price_BAO_LineItem::getLineItems($this->id, 'contribution', 1);
2167 if (!empty($lineItem)) {
2168 $itemId = key($lineItem);
2169 foreach ($lineItem as &$eachItem) {
2170 if (array_key_exists($eachItem['membership_type_id'], $this->_relatedObjects['membership']) ) {
2171 $eachItem['join_date'] = CRM_Utils_Date::customFormat($this->_relatedObjects['membership'][$eachItem['membership_type_id']]->join_date);
2172 $eachItem['start_date'] = CRM_Utils_Date::customFormat($this->_relatedObjects['membership'][$eachItem['membership_type_id']]->start_date);
2173 $eachItem['end_date'] = CRM_Utils_Date::customFormat($this->_relatedObjects['membership'][$eachItem['membership_type_id']]->end_date);
2174 }
2175 }
2176 $values['lineItem'][0] = $lineItem;
9da8dc8c 2177 $values['priceSetID'] = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceField', $lineItem[$itemId]['price_field_id'], 'price_set_id');
6a488035
TO
2178 }
2179 }
2180
2181 $relatedContact = CRM_Contribute_BAO_Contribution::getOnbehalfIds(
2182 $this->id,
2183 $this->contact_id
2184 );
2185 // if this is onbehalf of contribution then set related contact
2186 if (CRM_Utils_Array::value('individual_id', $relatedContact)) {
2187 $values['related_contact'] = $ids['related_contact'] = $relatedContact['individual_id'];
2188 }
2189 }
2190 else {
2191 // event
2192 $eventParams = array(
2193 'id' => $this->_relatedObjects['event']->id,
2194 );
2195 $values['event'] = array();
2196
2197 CRM_Event_BAO_Event::retrieve($eventParams, $values['event']);
2198
2199 //get location details
2200 $locationParams = array(
2201 'entity_id' => $this->_relatedObjects['event']->id,
2202 'entity_table' => 'civicrm_event',
2203 );
2204 $values['location'] = CRM_Core_BAO_Location::getValues($locationParams);
2205
2206 $ufJoinParams = array(
2207 'entity_table' => 'civicrm_event',
2208 'entity_id' => $ids['event'],
2209 'module' => 'CiviEvent',
2210 );
2211
2212 list($custom_pre_id,
2213 $custom_post_ids
2214 ) = CRM_Core_BAO_UFJoin::getUFGroupIds($ufJoinParams);
2215
2216 $values['custom_pre_id'] = $custom_pre_id;
2217 $values['custom_post_id'] = $custom_post_ids;
2218
2219 // set lineItem for event contribution
2220 if ($this->id) {
2221 $participantIds = CRM_Event_BAO_Participant::getParticipantIds($this->id);
2222 if (!empty($participantIds)) {
2223 foreach ($participantIds as $pIDs) {
2224 $lineItem = CRM_Price_BAO_LineItem::getLineItems($pIDs);
2225 if (!CRM_Utils_System::isNull($lineItem)) {
2226 $values['lineItem'][] = $lineItem;
2227 }
2228 }
2229 }
2230 }
2231 }
2232
2233 return $values;
2234 }
2235
2236 /**
2237 * Apply variables for message to smarty template - this function is part of analysing what is in the huge
2238 * function & breaking it down into manageable chunks. Eventually it will be refactored into something else
2239 * Note we send directly from this function in some cases because it is only partly refactored
2240 * Don't call this function directly as the signature will change
2241 */
2242 function _assignMessageVariablesToTemplate(&$values, $input, &$template, $recur = FALSE, $returnMessageText = True) {
2243 $template->assign('first_name', $this->_relatedObjects['contact']->first_name);
2244 $template->assign('last_name', $this->_relatedObjects['contact']->last_name);
2245 $template->assign('displayName', $this->_relatedObjects['contact']->display_name);
2246 if (!empty($values['lineItem']) && !empty($this->_relatedObjects['membership'])) {
2247 $template->assign('useForMember', true);
2248 }
2249 //assign honor infomation to receiptmessage
2250 $honorID = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution',
2251 $this->id,
2252 'honor_contact_id'
2253 );
2254 if (!empty($honorID)) {
2255
2256 $honorDefault = $honorIds = array();
2257 $honorIds['contribution'] = $this->id;
2258 $idParams = array('id' => $honorID, 'contact_id' => $honorID);
2259 CRM_Contact_BAO_Contact::retrieve($idParams, $honorDefault, $honorIds);
cbf48754 2260 $honorType = CRM_Core_PseudoConstant::get('CRM_Contribute_DAO_Contribution', 'honor_type_id');
6a488035
TO
2261
2262 $template->assign('honor_block_is_active', 1);
2263 if (CRM_Utils_Array::value('prefix_id', $honorDefault)) {
e6c4755b 2264 $prefix = CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'prefix_id');
6a488035
TO
2265 $template->assign('honor_prefix', $prefix[$honorDefault['prefix_id']]);
2266 }
2267 $template->assign('honor_first_name', CRM_Utils_Array::value('first_name', $honorDefault));
2268 $template->assign('honor_last_name', CRM_Utils_Array::value('last_name', $honorDefault));
2269 $template->assign('honor_email', CRM_Utils_Array::value('email', $honorDefault['email'][1]));
2270 $template->assign('honor_type', $honorType[$this->honor_type_id]);
2271 }
2272
2273 $dao = new CRM_Contribute_DAO_ContributionProduct();
2274 $dao->contribution_id = $this->id;
2275 if ($dao->find(TRUE)) {
2276 $premiumId = $dao->product_id;
2277 $template->assign('option', $dao->product_option);
2278
2279 $productDAO = new CRM_Contribute_DAO_Product();
2280 $productDAO->id = $premiumId;
2281 $productDAO->find(TRUE);
2282 $template->assign('selectPremium', TRUE);
2283 $template->assign('product_name', $productDAO->name);
2284 $template->assign('price', $productDAO->price);
2285 $template->assign('sku', $productDAO->sku);
2286 }
2287 $template->assign('title', CRM_Utils_Array::value('title',$values));
2288 $amount = CRM_Utils_Array::value('total_amount', $input,(CRM_Utils_Array::value('amount', $input)),null);
2289 if(empty($amount) && isset($this->total_amount)){
2290 $amount = $this->total_amount;
2291 }
2292 $template->assign('amount', $amount);
2293 // add the new contribution values
2294 if (strtolower($this->_component) == 'contribute') {
2295 //PCP Info
2296 $softDAO = new CRM_Contribute_DAO_ContributionSoft();
2297 $softDAO->contribution_id = $this->id;
2298 if ($softDAO->find(TRUE)) {
2299 $template->assign('pcpBlock', TRUE);
2300 $template->assign('pcp_display_in_roll', $softDAO->pcp_display_in_roll);
2301 $template->assign('pcp_roll_nickname', $softDAO->pcp_roll_nickname);
2302 $template->assign('pcp_personal_note', $softDAO->pcp_personal_note);
2303
2304 //assign the pcp page title for email subject
2305 $pcpDAO = new CRM_PCP_DAO_PCP();
2306 $pcpDAO->id = $softDAO->pcp_id;
2307 if ($pcpDAO->find(TRUE)) {
2308 $template->assign('title', $pcpDAO->title);
2309 }
2310 }
2311 }
2312
2313 if ($this->financial_type_id) {
2314 $values['financial_type_id'] = $this->financial_type_id;
2315 }
2316
2317
2318 $template->assign('trxn_id', $this->trxn_id);
2319 $template->assign('receive_date',
2320 CRM_Utils_Date::mysqlToIso($this->receive_date)
2321 );
2322 $template->assign('contributeMode', 'notify');
2323 $template->assign('action', $this->is_test ? 1024 : 1);
2324 $template->assign('receipt_text',
2325 CRM_Utils_Array::value('receipt_text',
2326 $values
2327 )
2328 );
2329 $template->assign('is_monetary', 1);
2330 $template->assign('is_recur', (bool) $recur);
2331 $template->assign('currency', $this->currency);
2332 $template->assign('address', CRM_Utils_Address::format($input));
2333 if ($this->_component == 'event') {
2334 $template->assign('title', $values['event']['title']);
2335 $participantRoles = CRM_Event_PseudoConstant::participantRole();
2336 $viewRoles = array();
2337 foreach (explode(CRM_Core_DAO::VALUE_SEPARATOR, $this->_relatedObjects['participant']->role_id) as $k => $v) {
2338 $viewRoles[] = $participantRoles[$v];
2339 }
2340 $values['event']['participant_role'] = implode(', ', $viewRoles);
2341 $template->assign('event', $values['event']);
2342 $template->assign('location', $values['location']);
2343 $template->assign('customPre', $values['custom_pre_id']);
2344 $template->assign('customPost', $values['custom_post_id']);
2345
2346 $isTest = FALSE;
2347 if ($this->_relatedObjects['participant']->is_test) {
2348 $isTest = TRUE;
2349 }
2350
2351 $values['params'] = array();
2352 //to get email of primary participant.
2353 $primaryEmail = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_Email', $this->_relatedObjects['participant']->contact_id, 'email', 'contact_id');
2354 $primaryAmount[] = array('label' => $this->_relatedObjects['participant']->fee_level . ' - ' . $primaryEmail, 'amount' => $this->_relatedObjects['participant']->fee_amount);
2355 //build an array of cId/pId of participants
2356 $additionalIDs = CRM_Event_BAO_Event::buildCustomProfile($this->_relatedObjects['participant']->id, NULL, $this->_relatedObjects['contact']->id, $isTest, TRUE);
2357 unset($additionalIDs[$this->_relatedObjects['participant']->id]);
2358 //send receipt to additional participant if exists
2359 if (count($additionalIDs)) {
2360 $template->assign('isPrimary', 0);
2361 $template->assign('customProfile', NULL);
2362 //set additionalParticipant true
2363 $values['params']['additionalParticipant'] = TRUE;
2364 foreach ($additionalIDs as $pId => $cId) {
2365 $amount = array();
2366 //to change the status pending to completed
2367 $additional = new CRM_Event_DAO_Participant();
2368 $additional->id = $pId;
2369 $additional->contact_id = $cId;
2370 $additional->find(TRUE);
2371 $additional->register_date = $this->_relatedObjects['participant']->register_date;
2372 $additional->status_id = 1;
2373 $additionalParticipantInfo = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_Email', $additional->contact_id, 'email', 'contact_id');
2374 //if additional participant dont have email
2375 //use display name.
2376 if (!$additionalParticipantInfo) {
2377 $additionalParticipantInfo = CRM_Contact_BAO_Contact::displayName($additional->contact_id);
2378 }
2379 $amount[0] = array('label' => $additional->fee_level, 'amount' => $additional->fee_amount);
2380 $primaryAmount[] = array('label' => $additional->fee_level . ' - ' . $additionalParticipantInfo, 'amount' => $additional->fee_amount);
2381 $additional->save();
2382 $additional->free();
2383 $template->assign('amount', $amount);
2384 CRM_Event_BAO_Event::sendMail($cId, $values, $pId, $isTest, $returnMessageText);
2385 }
2386 }
2387
2388 //build an array of custom profile and assigning it to template
2389 $customProfile = CRM_Event_BAO_Event::buildCustomProfile($this->_relatedObjects['participant']->id, $values, NULL, $isTest);
2390
2391 if (count($customProfile)) {
2392 $template->assign('customProfile', $customProfile);
2393 }
2394
2395 // for primary contact
2396 $values['params']['additionalParticipant'] = FALSE;
2397 $template->assign('isPrimary', 1);
2398 $template->assign('amount', $primaryAmount);
2399 $template->assign('register_date', CRM_Utils_Date::isoToMysql($this->_relatedObjects['participant']->register_date));
2400 if ($this->payment_instrument_id) {
2401 $paymentInstrument = CRM_Contribute_PseudoConstant::paymentInstrument();
2402 $template->assign('paidBy', $paymentInstrument[$this->payment_instrument_id]);
2403 }
2404 // carry paylater, since we did not created billing,
2405 // so need to pull email from primary location, CRM-4395
2406 $values['params']['is_pay_later'] = $this->_relatedObjects['participant']->is_pay_later;
2407 }
2408 return $template;
2409 }
2410
2411 /**
2412 * Function to check whether payment processor supports
2413 * cancellation of contribution subscription
2414 *
2415 * @param int $contributionId contribution id
2416 *
2417 * @return boolean
2418 * @access public
2419 * @static
2420 */
2421 static function isCancelSubscriptionSupported($contributionId, $isNotCancelled = TRUE) {
2422 $cacheKeyString = "$contributionId";
2423 $cacheKeyString .= $isNotCancelled ? '_1' : '_0';
2424
2425 static $supportsCancel = array();
2426
2427 if (!array_key_exists($cacheKeyString, $supportsCancel)) {
2428 $supportsCancel[$cacheKeyString] = FALSE;
2429 $isCancelled = FALSE;
2430
2431 if ($isNotCancelled) {
2432 $isCancelled = self::isSubscriptionCancelled($contributionId);
2433 }
2434
2435 $paymentObject = CRM_Financial_BAO_PaymentProcessor::getProcessorForEntity($contributionId, 'contribute', 'obj');
2436 if (!empty($paymentObject)) {
2437 $supportsCancel[$cacheKeyString] = $paymentObject->isSupported('cancelSubscription') && !$isCancelled;
2438 }
2439 }
2440 return $supportsCancel[$cacheKeyString];
2441 }
2442
2443 /**
2444 * Function to check whether subscription is already cancelled
2445 *
2446 * @param int $contributionId contribution id
2447 *
2448 * @return string $status contribution status
2449 * @access public
2450 * @static
2451 */
2452 static function isSubscriptionCancelled($contributionId) {
2453 $sql = "
2454 SELECT cr.contribution_status_id
2455 FROM civicrm_contribution_recur cr
2456 LEFT JOIN civicrm_contribution con ON ( cr.id = con.contribution_recur_id )
2457 WHERE con.id = %1 LIMIT 1";
2458 $params = array(1 => array($contributionId, 'Integer'));
2459 $statusId = CRM_Core_DAO::singleValueQuery($sql, $params);
2460 $status = CRM_Contribute_PseudoConstant::contributionStatus($statusId);
2461 if ($status == 'Cancelled') {
2462 return TRUE;
2463 }
2464 return FALSE;
2465 }
2466
2467 /**
2468 * Function to create all financial accounts entry
2469 *
2470 * @param array $params contribution object, line item array and params for trxn
2471 *
6a488035
TO
2472 *
2473 * @access public
2474 * @static
2475 */
504a78f6 2476 static function recordFinancialAccounts(&$params) {
6a488035 2477 $skipRecords = $update = FALSE;
d37ade2e 2478 $additionalParticipantId = array();
6a488035
TO
2479 $contributionStatuses = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name');
2480
2481 if (CRM_Utils_Array::value('contribution_mode', $params) == 'participant') {
2482 $entityId = $params['participant_id'];
2483 $entityTable = 'civicrm_participant';
d37ade2e 2484 $additionalParticipantId = CRM_Event_BAO_Participant::getAdditionalParticipantIds($entityId);
6a488035
TO
2485 }
2486 else {
2487 $entityId = $params['contribution']->id;
2488 $entityTable = 'civicrm_contribution';
2489 }
4d34aefa 2490
464bb009 2491 $entityID[] = $entityId;
d37ade2e 2492 if (!empty($additionalParticipantId)) {
2493 $entityID += $additionalParticipantId;
464bb009 2494 }
4d34aefa 2495 // prevContribution appears to mean - original contribution object- ie copy of contribution from before the update started that is being updated
6a488035
TO
2496 if (!CRM_Utils_Array::value('prevContribution', $params)) {
2497 $entityID = NULL;
2498 }
e005ab6b
PN
2499 else {
2500 $update = TRUE;
2501 }
4d34aefa 2502
6a488035 2503 // build line item array if its not set in $params
d37ade2e 2504 if (!CRM_Utils_Array::value('line_item', $params) || $additionalParticipantId) {
6a488035
TO
2505 CRM_Price_BAO_LineItem::getLineItemArray($params, $entityID, str_replace('civicrm_', '', $entityTable));
2506 }
2507
2508 if (CRM_Utils_Array::value('contribution_status_id', $params) != array_search('Failed', $contributionStatuses) &&
2509 !(CRM_Utils_Array::value('contribution_status_id', $params) == array_search('Pending', $contributionStatuses) && !$params['contribution']->is_pay_later)) {
2510 $skipRecords = TRUE;
2511 if (CRM_Utils_Array::value('contribution_status_id', $params) == array_search('Pending', $contributionStatuses)) {
f743a6eb 2512 $relationTypeId = key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Accounts Receivable Account is' "));
6a488035
TO
2513 $params['to_financial_account_id'] = CRM_Contribute_PseudoConstant::financialAccountType($params['financial_type_id'], $relationTypeId);
2514 }
2515 elseif (CRM_Utils_Array::value('payment_processor', $params)) {
2516 $params['to_financial_account_id'] = CRM_Financial_BAO_FinancialTypeAccount::getFinancialAccount($params['payment_processor'], 'civicrm_payment_processor', 'financial_account_id');
2517 }
2518 elseif (CRM_Utils_Array::value('payment_instrument_id', $params)) {
2519 $params['to_financial_account_id'] = CRM_Financial_BAO_FinancialTypeAccount::getInstrumentFinancialAccount($params['payment_instrument_id']);
2520 }
2521 else {
ac7514c2
PN
2522 $relationTypeId = key(CRM_Core_PseudoConstant::accountOptionValues('financial_account_type', NULL, " AND v.name LIKE 'Asset' "));
2523 $queryParams = array(1 => array($relationTypeId, 'Integer'));
2524 $params['to_financial_account_id'] = CRM_Core_DAO::singleValueQuery("SELECT id FROM civicrm_financial_account WHERE is_default = 1 AND financial_account_type_id = %1", $queryParams);
6a488035
TO
2525 }
2526
2527 $totalAmount = CRM_Utils_Array::value('total_amount', $params);
2528 if (!isset($totalAmount) && CRM_Utils_Array::value('prevContribution', $params)) {
2529 $totalAmount = $params['total_amount'] = $params['prevContribution']->total_amount;
2530 }
2531 //build financial transaction params
2532 $trxnParams = array(
2533 'contribution_id' => $params['contribution']->id,
2534 'to_financial_account_id' => $params['to_financial_account_id'],
2535 'trxn_date' => date('YmdHis'),
2536 'total_amount' => $totalAmount,
2537 'fee_amount' => CRM_Utils_Array::value('fee_amount', $params),
2538 'net_amount' => CRM_Utils_Array::value('net_amount', $params),
2539 'currency' => $params['contribution']->currency,
2540 'trxn_id' => $params['contribution']->trxn_id,
2541 'status_id' => $params['contribution']->contribution_status_id,
12921438 2542 'payment_instrument_id' => $params['contribution']->payment_instrument_id,
6a488035
TO
2543 'check_number' => CRM_Utils_Array::value('check_number', $params),
2544 );
2545
2546 if (CRM_Utils_Array::value('payment_processor', $params)) {
8ef12e64 2547 $trxnParams['payment_processor_id'] = $params['payment_processor'];
6a488035
TO
2548 }
2549 $params['trxnParams'] = $trxnParams;
2550
2551 if (CRM_Utils_Array::value('prevContribution', $params)) {
2552
2553 //if Change contribution amount
2554 if (array_key_exists('total_amount', $params) && isset($params['total_amount']) &&
2555 $params['total_amount'] != $params['prevContribution']->total_amount) {
2556 //Update Financial Records
2557 self::updateFinancialAccounts($params, 'changedAmount');
2558 }
2559
2560 //Update contribution status
2561 if (CRM_Utils_Array::value('contribution_status_id', $params) &&
2562 $params['prevContribution']->contribution_status_id != $params['contribution']->contribution_status_id) {
2563 //Update Financial Records
2564 self::updateFinancialAccounts($params, 'changedStatus');
2565 }
2566
2567 // change Payment Instrument for a Completed contribution
2568 // first handle special case when contribution is changed from Pending to Completed status when initial payment
2569 // instrument is null and now new payment instrument is added along with the payment
2570 if (array_key_exists('payment_instrument_id', $params)) {
2571 if (CRM_Utils_System::isNull($params['prevContribution']->payment_instrument_id) &&
2572 !CRM_Utils_System::isNull($params['contribution']->payment_instrument_id)) {
2573 //check if status is changed from Pending to Completed
2574 // do not update payment instrument changes for Pending to Completed
2575 if (!($params['contribution']->contribution_status_id == array_search('Completed', $contributionStatuses) &&
2576 $params['prevContribution']->contribution_status_id == array_search('Pending', $contributionStatuses))) {
2577 // for all other statuses create new financial records
2578 self::updateFinancialAccounts($params, 'changePaymentInstrument');
2579 }
2580 }
2581 else if ((!CRM_Utils_System::isNull($params['contribution']->payment_instrument_id) ||
2582 !CRM_Utils_System::isNull($params['prevContribution']->payment_instrument_id)) &&
2583 $params['contribution']->payment_instrument_id != $params['prevContribution']->payment_instrument_id) {
2584 // for any other payment instrument changes create new financial records
2585 self::updateFinancialAccounts($params, 'changePaymentInstrument');
2586 }
2587 else if (!CRM_Utils_System::isNull($params['contribution']->check_number) &&
2588 $params['contribution']->check_number != $params['prevContribution']->check_number) {
2589 // another special case when check number is changed, create new financial records
2590 // create financial trxn with negative amount
2591 $params['trxnParams']['total_amount'] = - $trxnParams['total_amount'];
2592 $params['trxnParams']['check_number'] = $params['prevContribution']->check_number;
2593 self::updateFinancialAccounts($params, 'changePaymentInstrument');
2594 // create financial trxn with positive amount
2595 $params['trxnParams']['check_number'] = $params['contribution']->check_number;
2596 $params['total_amount'] = $params['trxnParams']['total_amount'] = $trxnParams['total_amount'];
2597 self::updateFinancialAccounts($params, 'changePaymentInstrument');
2598 }
2599 }
2600
2601 //if financial type is changed
2602 if (CRM_Utils_Array::value('financial_type_id', $params) &&
2603 $params['contribution']->financial_type_id != $params['prevContribution']->financial_type_id) {
f743a6eb 2604 $incomeTypeId = key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Income Account is' "));
6a488035
TO
2605 $oldFinancialAccount = CRM_Contribute_PseudoConstant::financialAccountType($params['prevContribution']->financial_type_id, $incomeTypeId);
2606 $newFinancialAccount = CRM_Contribute_PseudoConstant::financialAccountType($params['financial_type_id'], $incomeTypeId);
2607 if ($oldFinancialAccount != $newFinancialAccount) {
2608 $params['total_amount'] = 0;
2609 if ($params['contribution']->contribution_status_id == array_search('Pending', $contributionStatuses)) {
2610 $params['trxnParams']['to_financial_account_id'] = CRM_Contribute_PseudoConstant::financialAccountType(
2611 $params['prevContribution']->financial_type_id, $relationTypeId);
2612 }
2613 self::updateFinancialAccounts($params, 'changeFinancialType');
2614 $params['trxnParams']['to_financial_account_id'] = $trxnParams['to_financial_account_id'];
2615 $params['financial_account_id'] = $newFinancialAccount;
2616 $params['total_amount'] = $params['trxnParams']['total_amount'] = $trxnParams['total_amount'];
2617 self::updateFinancialAccounts($params);
2618 }
2619 }
6a488035
TO
2620 }
2621
2622 if (!$update) {
2623 //records finanical trxn and entity financial trxn
2624 $financialTxn = CRM_Core_BAO_FinancialTrxn::create($trxnParams);
2625 $params['entity_id'] = $financialTxn->id;
2626 }
e005ab6b
PN
2627 }
2628 // record line items and finacial items
2629 if (!CRM_Utils_Array::value('skipLineItem', $params)) {
2630 CRM_Price_BAO_LineItem::processPriceSet($entityId, CRM_Utils_Array::value('line_item', $params), $params['contribution'], $entityTable, $update);
6a488035
TO
2631 }
2632
2633 // create batch entry if batch_id is passed
2634 if (CRM_Utils_Array::value('batch_id', $params)) {
2635 $entityParams = array(
2636 'batch_id' => $params['batch_id'],
2637 'entity_table' => 'civicrm_financial_trxn',
2638 'entity_id' => $financialTxn->id,
e005ab6b 2639 );
6a488035
TO
2640 CRM_Batch_BAO_Batch::addBatchEntity($entityParams);
2641 }
2642
2643 // when a fee is charged
2644 if (CRM_Utils_Array::value('fee_amount', $params) && (!CRM_Utils_Array::value('prevContribution', $params)
2645 || $params['contribution']->fee_amount != $params['prevContribution']->fee_amount) && $skipRecords) {
2646 CRM_Core_BAO_FinancialTrxn::recordFees($params);
2647 }
2648
2649 if (CRM_Utils_Array::value('prevContribution', $params) && $entityTable == 'civicrm_participant'
2650 && $params['prevContribution']->contribution_status_id != $params['contribution']->contribution_status_id) {
2651 $eventID = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Participant', $entityId, 'event_id');
2652 $feeLevel[] = str_replace('\ 1', '', $params['prevContribution']->amount_level);
2653 CRM_Event_BAO_Participant::createDiscountTrxn($eventID, $params, $feeLevel);
2654 }
2655 unset($params['line_item']);
2656 }
2657
2658 /**
2659 * Function to update all financial accounts entry
2660 *
2661 * @param array $params contribution object, line item array and params for trxn
2662 *
2663 * @param string $context update scenarios
2664 *
2665 * @access public
2666 * @static
2667 */
2668 static function updateFinancialAccounts(&$params, $context = NULL, $skipTrxn = NULL) {
2669 $itemAmount = $trxnID = NULL;
2670 //get all the statuses
2671 $contributionStatus = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name');
2672 if ($params['prevContribution']->contribution_status_id == array_search('Pending', $contributionStatus) &&
2673 $params['contribution']->contribution_status_id == array_search('Completed', $contributionStatus)
2674 && $context == 'changePaymentInstrument') {
2675 return;
2676 }
2677 if ($context == 'changedAmount' || $context == 'changeFinancialType') {
2678 $itemAmount = $params['trxnParams']['total_amount'] = $params['total_amount'] - $params['prevContribution']->total_amount;
2679 }
2680 if ($context == 'changedStatus') {
2681 //get all the statuses
2682 $contributionStatus = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name');
2683
2684 if ($params['prevContribution']->contribution_status_id == array_search('Completed', $contributionStatus)
2685 && ($params['contribution']->contribution_status_id == array_search('Refunded', $contributionStatus)
2686 || $params['contribution']->contribution_status_id == array_search('Cancelled', $contributionStatus))) {
2687
2688 $params['trxnParams']['total_amount'] = - $params['total_amount'];
2689 }
504a78f6 2690 elseif ($params['prevContribution']->contribution_status_id == array_search('Pending', $contributionStatus)
bf45dbe8 2691 && $params['prevContribution']->is_pay_later) {
6a488035
TO
2692 $financialTypeID = CRM_Utils_Array::value('financial_type_id', $params) ? $params['financial_type_id'] : $params['prevContribution']->financial_type_id;
2693 if ($params['contribution']->contribution_status_id == array_search('Cancelled', $contributionStatus)) {
2694 $params['trxnParams']['to_financial_account_id'] = NULL;
2695 $params['trxnParams']['total_amount'] = - $params['total_amount'];
2696 }
8ef12e64 2697 $relationTypeId = key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Accounts Receivable Account is' "));
6a488035
TO
2698 $params['trxnParams']['from_financial_account_id'] = CRM_Contribute_PseudoConstant::financialAccountType(
2699 $financialTypeID, $relationTypeId);
2700 }
2701 $itemAmount = $params['trxnParams']['total_amount'];
2702 }
2703 elseif ($context == 'changePaymentInstrument') {
2704 if ($params['prevContribution']->payment_instrument_id != null
2705 && $params['prevContribution']->contribution_status_id == array_search('Pending', $contributionStatus)
2706 && $params['contribution']->contribution_status_id == array_search('Pending', $contributionStatus)) {
f743a6eb 2707 $relationTypeId = key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Accounts Receivable Account is' "));
6a488035
TO
2708 $params['trxnParams']['from_financial_account_id'] = CRM_Contribute_PseudoConstant::financialAccountType($params['financial_type_id'], $relationTypeId);
2709 }
2710 elseif ($params['prevContribution']->payment_instrument_id != null) {
2711 $params['trxnParams']['from_financial_account_id'] =
2712 CRM_Financial_BAO_FinancialTypeAccount::getInstrumentFinancialAccount(
2713 $params['prevContribution']->payment_instrument_id);
2714 }
2715 else {
2716 $params['trxnParams']['from_financial_account_id'] = CRM_Core_DAO::singleValueQuery(
2717 "SELECT id FROM civicrm_financial_account WHERE is_default = 1");
2718 }
2719 }
2720
2721 $trxn = CRM_Core_BAO_FinancialTrxn::create($params['trxnParams']);
2722 $params['entity_id'] = $trxn->id;
2723
2724 if ($context == 'changedStatus') {
2725 if (($params['prevContribution']->contribution_status_id == array_search('Pending', $contributionStatus)) &&
2726 ($params['contribution']->contribution_status_id == array_search('Completed', $contributionStatus))) {
464bb009
PN
2727 $query = "UPDATE civicrm_financial_item SET status_id = %1 WHERE entity_id = %2 and entity_table = 'civicrm_line_item'";
2728 $sql = "SELECT id, amount FROM civicrm_financial_item WHERE entity_id = %1 and entity_table = 'civicrm_line_item'";
2d77a516 2729
464bb009
PN
2730 $entityParams = array(
2731 'entity_table' => 'civicrm_financial_item',
2732 'financial_trxn_id' => $trxn->id,
2733 );
6a488035
TO
2734 foreach ($params['line_item'] as $fieldId => $fields) {
2735 foreach ($fields as $fieldValueId => $fieldValues) {
2736 $fparams = array(
2737 1 => array(CRM_Core_OptionGroup::getValue('financial_item_status', 'Paid', 'name'), 'Integer'),
2738 2 => array($fieldValues['id'], 'Integer'),
2739 );
6a488035 2740 CRM_Core_DAO::executeQuery($query, $fparams);
464bb009
PN
2741 $fparams = array(
2742 1 => array($fieldValues['id'], 'Integer'),
2743 );
2744 $financialItem = CRM_Core_DAO::executeQuery($sql, $fparams);
2745 while ($financialItem->fetch()) {
2746 $entityParams['entity_id'] = $financialItem->id;
2747 $entityParams['amount'] = $financialItem->amount;
2748 CRM_Financial_BAO_FinancialItem::createEntityTrxn($entityParams);
2749 }
6a488035
TO
2750 }
2751 }
2752 return;
2753 }
2754 }
2755 if ($context != 'changePaymentInstrument') {
2756 $itemParams['entity_table'] = 'civicrm_line_item';
2757 $trxnIds['id'] = $params['entity_id'];
2758 foreach ($params['line_item'] as $fieldId => $fields) {
2759 foreach ($fields as $fieldValueId => $fieldValues) {
2760 $prevParams['entity_id'] = $fieldValues['id'];
2761 $prevfinancialItem = CRM_Financial_BAO_FinancialItem::retrieve($prevParams, CRM_Core_DAO::$_nullArray);
2762
2763 $receiveDate = CRM_Utils_Date::isoToMysql($params['prevContribution']->receive_date);
2764 if ($params['contribution']->receive_date) {
2765 $receiveDate = CRM_Utils_Date::isoToMysql($params['contribution']->receive_date);
2766 }
2767
2768 $financialAccount = $prevfinancialItem->financial_account_id;
2769 if (CRM_Utils_Array::value('financial_account_id', $params)) {
2770 $financialAccount = $params['financial_account_id'];
2771 }
2772
2773 $currency = $params['prevContribution']->currency;
2774 if ($params['contribution']->currency) {
2775 $currency = $params['contribution']->currency;
2776 }
2777 if (CRM_Utils_Array::value('is_quick_config', $params)) {
2778 $amount = $itemAmount;
2779 if (!$amount) {
2780 $amount = $params['total_amount'];
2781 }
2782 }
2783 else {
2784 $diff = 1;
2785 if ($context == 'changeFinancialType' || $params['contribution']->contribution_status_id == array_search('Cancelled', $contributionStatus)) {
2786 $diff = -1;
2787 }
2788 $amount = $diff * $fieldValues['line_total'];
2789 }
2790
2791 $itemParams = array(
2792 'transaction_date' => $receiveDate,
2793 'contact_id' => $params['prevContribution']->contact_id,
2794 'currency' => $currency,
2795 'amount' => $amount,
2796 'description' => $prevfinancialItem->description,
2797 'status_id' => $prevfinancialItem->status_id,
2798 'financial_account_id' => $financialAccount,
2799 'entity_table' => 'civicrm_line_item',
2800 'entity_id' => $fieldValues['id']
2801 );
2802 CRM_Financial_BAO_FinancialItem::create($itemParams, NULL, $trxnIds);
2803 }
2804 }
2805 }
2806 }
2807
2808 /**
2809 * Function to check status validation on update of a contribution
2810 *
2811 * @param array $values previous form values before submit
2812 *
2813 * @param array $fields the input form values
2814 *
2815 * @param array $errors list of errors
2816 *
2817 * @access public
2818 * @static
2819 */
2820 static function checkStatusValidation($values, &$fields, &$errors) {
c71ae314
PN
2821 if (CRM_Utils_System::isNull($values) && CRM_Utils_Array::value('id', $fields)) {
2822 $values['contribution_status_id'] = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $fields['id'], 'contribution_status_id');
2823 if ($values['contribution_status_id'] == $fields['contribution_status_id']) {
2824 return FALSE;
2825 }
2826 }
6a488035
TO
2827 $contributionStatuses = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name');
2828 $checkStatus = array(
2829 'Cancelled' => array('Completed', 'Refunded'),
2830 'Completed' => array('Cancelled', 'Refunded'),
2831 'Pending' => array('Cancelled', 'Completed', 'Failed'),
2832 'Refunded' => array('Cancelled', 'Completed')
2833 );
2834
2835 if (!in_array($contributionStatuses[$fields['contribution_status_id']], $checkStatus[$contributionStatuses[$values['contribution_status_id']]])) {
2836 $errors['contribution_status_id'] = ts("Cannot change contribution status from %1 to %2.", array(1 => $contributionStatuses[$values['contribution_status_id']], 2 => $contributionStatuses[$fields['contribution_status_id']]));
2837 }
2838 }
c3d24ba7
PN
2839
2840 /**
2841 * Function to delete contribution of contact
2842 *
2843 * CRM-12155
2844 *
2d77a516 2845 * @param integer $contactId contact id
c3d24ba7
PN
2846 *
2847 * @access public
2848 * @static
2849 */
2850 static function deleteContactContribution($contactId) {
2851 $contribution = new CRM_Contribute_DAO_Contribution();
2852 $contribution->contact_id = $contactId;
2853 $contribution->find();
2854 while ($contribution->fetch()) {
2855 self::deleteContribution($contribution->id);
2856 }
2857 }
6a488035 2858}