Merge pull request #4887 from pratikshad/broken-webtest
[civicrm-core.git] / CRM / Contribute / BAO / ContributionRecur.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.6 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2014 |
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-2014
32 * $Id$
33 *
34 */
35 class CRM_Contribute_BAO_ContributionRecur extends CRM_Contribute_DAO_ContributionRecur {
36
37 /**
38 * Create recurring contribution
39 *
40 * @param array $params
41 * (reference ) an assoc array of name/value pairs.
42 *
43 * @return object
44 * activity contact object
45 */
46 public static function create(&$params) {
47 return self::add($params);
48 }
49
50 /**
51 * Takes an associative array and creates a contribution object
52 *
53 * the function extract all the params it needs to initialize the create a
54 * contribution object. the params array could contain additional unused name/value
55 * pairs
56 *
57 * @param array $params
58 * (reference ) an assoc array of name/value pairs.
59 *
60 * @return CRM_Contribute_BAO_Contribution
61 * @static
62 * @todo move hook calls / extended logic to create - requires changing calls to call create not add
63 */
64 public static function add(&$params) {
65 if (!empty($params['id'])) {
66 CRM_Utils_Hook::pre('edit', 'ContributionRecur', $params['id'], $params);
67 }
68 else {
69 CRM_Utils_Hook::pre('create', 'ContributionRecur', NULL, $params);
70 }
71
72 // make sure we're not creating a new recurring contribution with the same transaction ID
73 // or invoice ID as an existing recurring contribution
74 $duplicates = array();
75 if (self::checkDuplicate($params, $duplicates)) {
76 $error = CRM_Core_Error::singleton();
77 $d = implode(', ', $duplicates);
78 $error->push(CRM_Core_Error::DUPLICATE_CONTRIBUTION,
79 'Fatal',
80 array($d),
81 "Found matching recurring contribution(s): $d"
82 );
83 return $error;
84 }
85
86 $recurring = new CRM_Contribute_BAO_ContributionRecur();
87 $recurring->copyValues($params);
88 $recurring->id = CRM_Utils_Array::value('id', $params);
89
90 // set currency for CRM-1496
91 if (!isset($recurring->currency)) {
92 $config = CRM_Core_Config::singleton();
93 $recurring->currency = $config->defaultCurrency;
94 }
95 $result = $recurring->save();
96
97 if (!empty($params['id'])) {
98 CRM_Utils_Hook::post('edit', 'ContributionRecur', $recurring->id, $recurring);
99 }
100 else {
101 CRM_Utils_Hook::post('create', 'ContributionRecur', $recurring->id, $recurring);
102 }
103
104 return $result;
105 }
106
107 /**
108 * Check if there is a recurring contribution with the same trxn_id or invoice_id
109 *
110 * @param array $params
111 * (reference ) an assoc array of name/value pairs.
112 * @param array $duplicates
113 * (reference ) store ids of duplicate contribs.
114 *
115 * @return boolean
116 * true if duplicate, false otherwise
117 * @static
118 */
119 public static function checkDuplicate($params, &$duplicates) {
120 $id = CRM_Utils_Array::value('id', $params);
121 $trxn_id = CRM_Utils_Array::value('trxn_id', $params);
122 $invoice_id = CRM_Utils_Array::value('invoice_id', $params);
123
124 $clause = array();
125 $params = array();
126
127 if ($trxn_id) {
128 $clause[] = "trxn_id = %1";
129 $params[1] = array($trxn_id, 'String');
130 }
131
132 if ($invoice_id) {
133 $clause[] = "invoice_id = %2";
134 $params[2] = array($invoice_id, 'String');
135 }
136
137 if (empty($clause)) {
138 return FALSE;
139 }
140
141 $clause = implode(' OR ', $clause);
142 if ($id) {
143 $clause = "( $clause ) AND id != %3";
144 $params[3] = array($id, 'Integer');
145 }
146
147 $query = "SELECT id FROM civicrm_contribution_recur WHERE $clause";
148 $dao = CRM_Core_DAO::executeQuery($query, $params);
149 $result = FALSE;
150 while ($dao->fetch()) {
151 $duplicates[] = $dao->id;
152 $result = TRUE;
153 }
154 return $result;
155 }
156
157 /**
158 * @param int $id
159 * @param $mode
160 *
161 * @return array|null
162 */
163 public static function getPaymentProcessor($id, $mode) {
164 //FIX ME:
165 $sql = "
166 SELECT r.payment_processor_id
167 FROM civicrm_contribution_recur r
168 WHERE r.id = %1";
169 $params = array(1 => array($id, 'Integer'));
170 $paymentProcessorID = &CRM_Core_DAO::singleValueQuery($sql,
171 $params
172 );
173 if (!$paymentProcessorID) {
174 return NULL;
175 }
176
177 return CRM_Financial_BAO_PaymentProcessor::getPayment($paymentProcessorID, $mode);
178 }
179
180 /**
181 * Get the number of installment done/completed for each recurring contribution
182 *
183 * @param array $ids
184 * (reference ) an array of recurring contribution ids.
185 *
186 * @return array
187 * an array of recurring ids count
188 * @static
189 */
190 public static function getCount(&$ids) {
191 $recurID = implode(',', $ids);
192 $totalCount = array();
193
194 $query = "
195 SELECT contribution_recur_id, count( contribution_recur_id ) as commpleted
196 FROM civicrm_contribution
197 WHERE contribution_recur_id IN ( {$recurID}) AND is_test = 0
198 GROUP BY contribution_recur_id";
199
200 $res = CRM_Core_DAO::executeQuery($query, CRM_Core_DAO::$_nullArray);
201
202 while ($res->fetch()) {
203 $totalCount[$res->contribution_recur_id] = $res->commpleted;
204 }
205 return $totalCount;
206 }
207
208 /**
209 * Delete Recurring contribution.
210 *
211 * @param int $recurId
212 *
213 * @return bool
214 * @static
215 */
216 public static function deleteRecurContribution($recurId) {
217 $result = FALSE;
218 if (!$recurId) {
219 return $result;
220 }
221
222 $recur = new CRM_Contribute_DAO_ContributionRecur();
223 $recur->id = $recurId;
224 $result = $recur->delete();
225
226 return $result;
227 }
228
229 /**
230 * Cancel Recurring contribution.
231 *
232 * @param int $recurId
233 * Recur contribution id.
234 * @param array $objects
235 * An array of objects that is to be cancelled like.
236 * contribution, membership, event. At least contribution object is a must.
237 *
238 * @param array $activityParams
239 *
240 * @return bool
241 * @static
242 */
243 public static function cancelRecurContribution($recurId, $objects, $activityParams = array()) {
244 if (!$recurId) {
245 return FALSE;
246 }
247
248 $contributionStatus = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name');
249 $canceledId = array_search('Cancelled', $contributionStatus);
250 $recur = new CRM_Contribute_DAO_ContributionRecur();
251 $recur->id = $recurId;
252 $recur->whereAdd("contribution_status_id != $canceledId");
253
254 if ($recur->find(TRUE)) {
255 $transaction = new CRM_Core_Transaction();
256 $recur->contribution_status_id = $canceledId;
257 $recur->start_date = CRM_Utils_Date::isoToMysql($recur->start_date);
258 $recur->create_date = CRM_Utils_Date::isoToMysql($recur->create_date);
259 $recur->modified_date = CRM_Utils_Date::isoToMysql($recur->modified_date);
260 $recur->cancel_date = date('YmdHis');
261 $recur->save();
262
263 $dao = CRM_Contribute_BAO_ContributionRecur::getSubscriptionDetails($recurId);
264 if ($dao && $dao->recur_id) {
265 $details = CRM_Utils_Array::value('details', $activityParams);
266 if ($dao->auto_renew && $dao->membership_id) {
267 // its auto-renewal membership mode
268 $membershipTypes = CRM_Member_PseudoConstant::membershipType();
269 $membershipType = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_Membership', $dao->membership_id, 'membership_type_id');
270 $membershipType = CRM_Utils_Array::value($membershipType, $membershipTypes);
271 $details .= '
272 <br/>' . ts('Automatic renewal of %1 membership cancelled.', array(1 => $membershipType));
273 }
274 else {
275 $details .= '
276 <br/>' . ts('The recurring contribution of %1, every %2 %3 has been cancelled.', array(
277 1 => $dao->amount,
278 2 => $dao->frequency_interval,
279 3 => $dao->frequency_unit,
280 ));
281 }
282 $activityParams = array(
283 'source_contact_id' => $dao->contact_id,
284 'source_record_id' => CRM_Utils_Array::value('source_record_id', $activityParams),
285 'activity_type_id' => CRM_Core_OptionGroup::getValue('activity_type',
286 'Cancel Recurring Contribution',
287 'name'
288 ),
289 'subject' => CRM_Utils_Array::value('subject', $activityParams, ts('Recurring contribution cancelled')),
290 'details' => $details,
291 'activity_date_time' => date('YmdHis'),
292 'status_id' => CRM_Core_OptionGroup::getValue('activity_status',
293 'Completed',
294 'name'
295 ),
296 );
297 $session = CRM_Core_Session::singleton();
298 $cid = $session->get('userID');
299 if ($cid) {
300 $activityParams['target_contact_id'][] = $activityParams['source_contact_id'];
301 $activityParams['source_contact_id'] = $cid;
302 }
303 CRM_Activity_BAO_Activity::create($activityParams);
304 }
305
306 // if there are associated objects, cancel them as well
307 if ($objects == CRM_Core_DAO::$_nullObject) {
308 $transaction->commit();
309 return TRUE;
310 }
311 else {
312 $baseIPN = new CRM_Core_Payment_BaseIPN();
313 return $baseIPN->cancelled($objects, $transaction);
314 }
315 }
316 else {
317 // if already cancelled, return true
318 $recur->whereAdd();
319 $recur->whereAdd("contribution_status_id = $canceledId");
320 if ($recur->find(TRUE)) {
321 return TRUE;
322 }
323 }
324
325 return FALSE;
326 }
327
328 /**
329 * Get list of recurring contribution of contact Ids
330 *
331 * @param int $contactId
332 * Contact ID.
333 *
334 * @return array
335 * list of recurring contribution fields
336 *
337 * @static
338 */
339 public static function getRecurContributions($contactId) {
340 $params = array();
341 $recurDAO = new CRM_Contribute_DAO_ContributionRecur();
342 $recurDAO->contact_id = $contactId;
343 $recurDAO->find();
344 $contributionStatus = CRM_Contribute_PseudoConstant::contributionStatus();
345
346 while ($recurDAO->fetch()) {
347 $params[$recurDAO->id]['id'] = $recurDAO->id;
348 $params[$recurDAO->id]['contactId'] = $recurDAO->contact_id;
349 $params[$recurDAO->id]['start_date'] = $recurDAO->start_date;
350 $params[$recurDAO->id]['end_date'] = $recurDAO->end_date;
351 $params[$recurDAO->id]['next_sched_contribution_date'] = $recurDAO->next_sched_contribution_date;
352 $params[$recurDAO->id]['amount'] = $recurDAO->amount;
353 $params[$recurDAO->id]['currency'] = $recurDAO->currency;
354 $params[$recurDAO->id]['frequency_unit'] = $recurDAO->frequency_unit;
355 $params[$recurDAO->id]['frequency_interval'] = $recurDAO->frequency_interval;
356 $params[$recurDAO->id]['installments'] = $recurDAO->installments;
357 $params[$recurDAO->id]['contribution_status_id'] = $recurDAO->contribution_status_id;
358 $params[$recurDAO->id]['contribution_status'] = CRM_Utils_Array::value($recurDAO->contribution_status_id, $contributionStatus);
359 $params[$recurDAO->id]['is_test'] = $recurDAO->is_test;
360 $params[$recurDAO->id]['payment_processor_id'] = $recurDAO->payment_processor_id;
361 }
362
363 return $params;
364 }
365
366 /**
367 * @param int $entityID
368 * @param string $entity
369 *
370 * @return null|Object
371 */
372 public static function getSubscriptionDetails($entityID, $entity = 'recur') {
373 $sql = "
374 SELECT rec.id as recur_id,
375 rec.processor_id as subscription_id,
376 rec.frequency_interval,
377 rec.installments,
378 rec.frequency_unit,
379 rec.amount,
380 rec.is_test,
381 rec.auto_renew,
382 rec.currency,
383 con.id as contribution_id,
384 con.contribution_page_id,
385 rec.contact_id,
386 mp.membership_id";
387
388 if ($entity == 'recur') {
389 $sql .= "
390 FROM civicrm_contribution_recur rec
391 LEFT JOIN civicrm_contribution con ON ( con.contribution_recur_id = rec.id )
392 LEFT JOIN civicrm_membership_payment mp ON ( mp.contribution_id = con.id )
393 WHERE rec.id = %1
394 GROUP BY rec.id";
395 }
396 elseif ($entity == 'contribution') {
397 $sql .= "
398 FROM civicrm_contribution con
399 INNER JOIN civicrm_contribution_recur rec ON ( con.contribution_recur_id = rec.id )
400 LEFT JOIN civicrm_membership_payment mp ON ( mp.contribution_id = con.id )
401 WHERE con.id = %1";
402 }
403 elseif ($entity == 'membership') {
404 $sql .= "
405 FROM civicrm_membership_payment mp
406 INNER JOIN civicrm_membership mem ON ( mp.membership_id = mem.id )
407 INNER JOIN civicrm_contribution_recur rec ON ( mem.contribution_recur_id = rec.id )
408 INNER JOIN civicrm_contribution con ON ( con.id = mp.contribution_id )
409 WHERE mp.membership_id = %1";
410 }
411
412 $dao = CRM_Core_DAO::executeQuery($sql, array(1 => array($entityID, 'Integer')));
413 if ($dao->fetch()) {
414 return $dao;
415 }
416 else {
417 return CRM_Core_DAO::$_nullObject;
418 }
419 }
420
421 public static function setSubscriptionContext() {
422 // handle context redirection for subscription url
423 $session = CRM_Core_Session::singleton();
424 if ($session->get('userID')) {
425 $url = FALSE;
426 $cid = CRM_Utils_Request::retrieve('cid', 'Integer');
427 $mid = CRM_Utils_Request::retrieve('mid', 'Integer');
428 $qfkey = CRM_Utils_Request::retrieve('key', 'String');
429 $context = CRM_Utils_Request::retrieve('context', 'String');
430 if ($cid) {
431 switch ($context) {
432 case 'contribution':
433 $url = CRM_Utils_System::url('civicrm/contact/view',
434 "reset=1&selectedChild=contribute&cid={$cid}"
435 );
436 break;
437
438 case 'membership':
439 $url = CRM_Utils_System::url('civicrm/contact/view',
440 "reset=1&selectedChild=member&cid={$cid}"
441 );
442 break;
443
444 case 'dashboard':
445 $url = CRM_Utils_System::url('civicrm/user', "reset=1&id={$cid}");
446 break;
447 }
448 }
449 if ($mid) {
450 switch ($context) {
451 case 'dashboard':
452 $url = CRM_Utils_System::url('civicrm/member', "force=1&context={$context}&key={$qfkey}");
453 break;
454
455 case 'search':
456 $url = CRM_Utils_System::url('civicrm/member/search', "force=1&context={$context}&key={$qfkey}");
457 break;
458 }
459 }
460 if ($url) {
461 $session->pushUserContext($url);
462 }
463 }
464 }
465 }