Merge pull request #6150 from eileenmcnaughton/CRM-16803
[civicrm-core.git] / CRM / Member / BAO / Membership.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
39de6fd5 4 | CiviCRM version 4.6 |
6a488035 5 +--------------------------------------------------------------------+
e7112fa7 6 | Copyright CiviCRM LLC (c) 2004-2015 |
6a488035
TO
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
d25dd0ee 26 */
6a488035
TO
27
28/**
29 *
30 * @package CRM
e7112fa7 31 * @copyright CiviCRM LLC (c) 2004-2015
6a488035
TO
32 * $Id$
33 *
34 */
35class CRM_Member_BAO_Membership extends CRM_Member_DAO_Membership {
36
37 /**
fe482240 38 * Static field for all the membership information that we can potentially import.
6a488035
TO
39 *
40 * @var array
6a488035
TO
41 */
42 static $_importableFields = NULL;
8ef12e64 43
4e636a74 44 static $_renewalActType = NULL;
8ef12e64 45
4e636a74 46 static $_signupActType = NULL;
8ef12e64 47
bb3a214a 48 /**
fe482240 49 * Class constructor.
bb3a214a 50 *
bb3a214a
EM
51 * @return \CRM_Member_DAO_Membership
52 */
53 /**
bb3a214a 54 */
00be9182 55 public function __construct() {
6a488035
TO
56 parent::__construct();
57 }
58
59 /**
fe482240 60 * Takes an associative array and creates a membership object.
6a488035
TO
61 *
62 * the function extracts all the params it needs to initialize the created
63 * membership object. The params array could contain additional unused name/value
64 * pairs
65 *
b2363ea8
TO
66 * @param array $params
67 * (reference ) an assoc array of name/value pairs.
68 * @param array $ids
69 * The array that holds all the db ids.
6a488035 70 *
16b10e64 71 * @return CRM_Member_BAO_Membership
6a488035 72 */
00be9182 73 public static function add(&$params, $ids = array()) {
a5e2b32e 74 $oldStatus = $oldType = NULL;
f880fd16
EM
75 $params['id'] = CRM_Utils_Array::value('id', $params, CRM_Utils_Array::value('membership', $ids));
76 if ($params['id']) {
77 CRM_Utils_Hook::pre('edit', 'Membership', $params['id'], $params);
78 }
79 else {
1acc24d5 80 CRM_Utils_Hook::pre('create', 'Membership', NULL, $params);
f880fd16
EM
81 }
82 $id = $params['id'];
83 // we do this after the hooks are called in case it has been altered
84 if ($id) {
85 $membershipObj = new CRM_Member_DAO_Membership();
86 $membershipObj->id = $id;
6a488035
TO
87 $membershipObj->find();
88 while ($membershipObj->fetch()) {
89 $oldStatus = $membershipObj->status_id;
90 $oldType = $membershipObj->membership_type_id;
91 }
92 }
6a488035
TO
93
94 if (array_key_exists('is_override', $params) && !$params['is_override']) {
95 $params['is_override'] = 'null';
96 }
97
98 $membership = new CRM_Member_BAO_Membership();
99 $membership->copyValues($params);
f880fd16 100 $membership->id = $id;
6a488035
TO
101
102 $membership->save();
103 $membership->free();
104
6a488035
TO
105 if (empty($membership->contact_id) || empty($membership->status_id)) {
106 // this means we are in renewal mode and are just updating the membership
107 // record or this is an API update call and all fields are not present in the update record
b204fd50 108 // however the hooks don't care and want all data CRM-7784
6a488035
TO
109 $tempMembership = new CRM_Member_DAO_Membership();
110 $tempMembership->id = $membership->id;
111 $tempMembership->find(TRUE);
112 $membership = $tempMembership;
113 }
114
115 //get the log start date.
116 //it is set during renewal of membership.
ca58d9b9 117 $logStartDate = CRM_Utils_Array::value('log_start_date', $params);
6a488035 118 $logStartDate = ($logStartDate) ? CRM_Utils_Date::isoToMysql($logStartDate) : CRM_Utils_Date::isoToMysql($membership->start_date);
e0556ebe 119 $values = self::getStatusANDTypeValues($membership->id);
6a488035
TO
120
121 $membershipLog = array(
122 'membership_id' => $membership->id,
123 'status_id' => $membership->status_id,
124 'start_date' => $logStartDate,
125 'end_date' => CRM_Utils_Date::isoToMysql($membership->end_date),
126 'modified_date' => date('Ymd'),
127 'membership_type_id' => $values[$membership->id]['membership_type_id'],
128 'max_related' => $membership->max_related,
129 );
130
131 $session = CRM_Core_Session::singleton();
132 // If we have an authenticated session, set modified_id to that user's contact_id, else set to membership.contact_id
133 if ($session->get('userID')) {
134 $membershipLog['modified_id'] = $session->get('userID');
135 }
136 elseif (!empty($ids['userId'])) {
137 $membershipLog['modified_id'] = $ids['userId'];
138 }
139 else {
140 $membershipLog['modified_id'] = $membership->contact_id;
141 }
142
143 CRM_Member_BAO_MembershipLog::add($membershipLog, CRM_Core_DAO::$_nullArray);
144
145 // reset the group contact cache since smart groups might be affected due to this
146 CRM_Contact_BAO_GroupContactCache::remove();
147
f880fd16 148 if ($id) {
6a488035 149 if ($membership->status_id != $oldStatus) {
4d63cfde 150 $allStatus = CRM_Member_BAO_Membership::buildOptions('status_id', 'get');
6a488035
TO
151 $activityParam = array(
152 'subject' => "Status changed from {$allStatus[$oldStatus]} to {$allStatus[$membership->status_id]}",
153 'source_contact_id' => $membershipLog['modified_id'],
154 'target_contact_id' => $membership->contact_id,
155 'source_record_id' => $membership->id,
4d63cfde 156 'activity_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Change Membership Status'),
6a488035 157 'status_id' => 2,
6a488035
TO
158 'priority_id' => 2,
159 'activity_date_time' => date('Y-m-d H:i:s'),
6a488035 160 );
4d63cfde 161 civicrm_api3('activity', 'create', $activityParam);
6a488035
TO
162 }
163 if (isset($membership->membership_type_id) && $membership->membership_type_id != $oldType) {
4d63cfde 164 $membershipTypes = CRM_Member_BAO_Membership::buildOptions('membership_type_id', 'get');
6a488035
TO
165 $activityParam = array(
166 'subject' => "Type changed from {$membershipTypes[$oldType]} to {$membershipTypes[$membership->membership_type_id]}",
167 'source_contact_id' => $membershipLog['modified_id'],
168 'target_contact_id' => $membership->contact_id,
169 'source_record_id' => $membership->id,
bd38fe3f 170 'activity_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Change Membership Type'),
6a488035 171 'status_id' => 2,
6a488035
TO
172 'priority_id' => 2,
173 'activity_date_time' => date('Y-m-d H:i:s'),
6a488035 174 );
4d63cfde 175 civicrm_api3('activity', 'create', $activityParam);
6a488035
TO
176 }
177 CRM_Utils_Hook::post('edit', 'Membership', $membership->id, $membership);
178 }
179 else {
180 CRM_Utils_Hook::post('create', 'Membership', $membership->id, $membership);
181 }
182
183 return $membership;
184 }
185
186 /**
187 * Given the list of params in the params array, fetch the object
188 * and store the values in the values array
189 *
b2363ea8
TO
190 * @param array $params
191 * Input parameters to find object.
192 * @param array $values
193 * Output values of the object.
194 * @param bool $active
195 * Do you want only active memberships to.
6a488035 196 * be returned
f1e48e7f 197 * @param bool $relatedMemberships
6a488035 198 * @return CRM_Member_BAO_Membership|null the found object or null
6a488035 199 */
f1e48e7f 200 public static function &getValues(&$params, &$values, $active = FALSE, $relatedMemberships = FALSE) {
6a488035
TO
201 if (empty($params)) {
202 return NULL;
203 }
204 $membership = new CRM_Member_BAO_Membership();
205
206 $membership->copyValues($params);
207 $membership->find();
208 $memberships = array();
209 while ($membership->fetch()) {
210 if ($active &&
211 (!CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipStatus',
e0556ebe
TO
212 $membership->status_id,
213 'is_current_member'
214 ))
6a488035
TO
215 ) {
216 continue;
217 }
218
219 CRM_Core_DAO::storeValues($membership, $values[$membership->id]);
220 $memberships[$membership->id] = $membership;
f1e48e7f 221 if ($relatedMemberships && !empty($membership->owner_membership_id)) {
222 $values['owner_membership_ids'][] = $membership->owner_membership_id;
223 }
6a488035
TO
224 }
225
226 return $memberships;
227 }
228
229 /**
fe482240 230 * Takes an associative array and creates a membership object.
6a488035 231 *
b2363ea8
TO
232 * @param array $params
233 * (reference ) an assoc array of name/value pairs.
234 * @param array $ids
235 * The array that holds all the db ids.
8efea814
EM
236 * @param bool $skipRedirect
237 * @param string $activityType
238 *
239 * @throws CRM_Core_Exception
6a488035 240 *
906e6120 241 * @return CRM_Member_BAO_Membership|CRM_Core_Error
6a488035 242 */
00be9182 243 public static function create(&$params, &$ids, $skipRedirect = FALSE, $activityType = 'Membership Signup') {
6a488035
TO
244 // always calculate status if is_override/skipStatusCal is not true.
245 // giving respect to is_override during import. CRM-4012
246
247 // To skip status calculation we should use 'skipStatusCal'.
248 // eg pay later membership, membership update cron CRM-3984
249
8cc574cf 250 if (empty($params['is_override']) && empty($params['skipStatusCal'])) {
0042ddd9 251 $dates = array('start_date', 'end_date', 'join_date');
ca58d9b9 252 $start_date = $end_date = $join_date = NULL; // declare these out of courtesy as IDEs don't pick up the setting of them below
0042ddd9 253 foreach ($dates as $date) {
b5588874 254 $$date = $params[$date] = CRM_Utils_Date::processDate(CRM_Utils_Array::value($date, $params), NULL, TRUE, 'Ymd');
6a488035
TO
255 }
256
257 //fix for CRM-3570, during import exclude the statuses those having is_admin = 1
258 $excludeIsAdmin = CRM_Utils_Array::value('exclude_is_admin', $params, FALSE);
259
260 //CRM-3724 always skip is_admin if is_override != true.
8cc574cf 261 if (!$excludeIsAdmin && empty($params['is_override'])) {
6a488035
TO
262 $excludeIsAdmin = TRUE;
263 }
264
358f8960
MV
265 //CRM-15829 UPDATES
266 // When adding memberships to a contact and If a status is pending then there is no need to perform these calculations. Otherwise it will errernously not realise the pending state and set ot to NEW or GRACE depending on the date ranges.
267 if (isset($params['status_id']) && $params['status_id'] == 5) {
268 $calcStatus['id'] = $params['status_id'];
269 }
270 else {
271 $calcStatus = CRM_Member_BAO_MembershipStatus::getMembershipStatusByDate($start_date, $end_date, $join_date,
272 'today', $excludeIsAdmin, CRM_Utils_Array::value('membership_type_id', $params), $params
273 );
274 }
275
6a488035 276 if (empty($calcStatus)) {
dcc4f6a7 277 // Redirect the form in case of error
278 // @todo this redirect in the BAO layer is really bad & should be moved to the form layer
279 // however since we have no idea how (if) this is triggered we can't safely move / remove it
280 // NB I tried really hard to trigger this error from backoffice membership form in order to test it
281 // and am convinced form validation is complete on that form WRT this error.
282 $errorParams = array(
283 'message_title' => ts('No valid membership status for given dates.'),
284 'legacy_redirect_path' => 'civicrm/contact/view',
285 'legacy_redirect_query' => "reset=1&force=1&cid={$params['contact_id']}&selectedChild=member",
286 );
8c33a68c 287 throw new CRM_Core_Exception(ts('The membership cannot be saved because the status cannot be calculated.'), 0, $errorParams);
6a488035
TO
288 }
289 $params['status_id'] = $calcStatus['id'];
290 }
291
292 // data cleanup only: all verifications on number of related memberships are done upstream in:
293 // CRM_Member_BAO_Membership::createRelatedMemberships()
294 // CRM_Contact_BAO_Relationship::relatedMemberships()
295 if (isset($params['owner_membership_id'])) {
296 unset($params['max_related']);
8efea814
EM
297 }
298 else {
6a488035
TO
299 // if membership allows related, default max_related to value in membership_type
300 if (!array_key_exists('max_related', $params) && !empty($params['membership_type_id'])) {
301 $membershipType = CRM_Member_BAO_MembershipType::getMembershipTypeDetails($params['membership_type_id']);
302 if (isset($membershipType['relationship_type_id'])) {
303 $params['max_related'] = CRM_Utils_Array::value('max_related', $membershipType);
304 }
305 }
306 }
307
308 $transaction = new CRM_Core_Transaction();
309
310 $membership = self::add($params, $ids);
311
312 if (is_a($membership, 'CRM_Core_Error')) {
313 $transaction->rollback();
314 return $membership;
315 }
316
317 // add custom field values
a7488080 318 if (!empty($params['custom']) && is_array($params['custom'])
6a488035
TO
319 ) {
320 CRM_Core_BAO_CustomValueTable::store($params['custom'], 'civicrm_membership', $membership->id);
321 }
322
323 $params['membership_id'] = $membership->id;
324 if (isset($ids['membership'])) {
325 $ids['contribution'] = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipPayment',
326 $ids['membership'],
327 'contribution_id',
328 'membership_id'
329 );
330 }
3c0201c9 331
d5b95619 332 $params['skipLineItem'] = TRUE;
3c0201c9 333
6a488035 334 //record contribution for this membership
8cc574cf 335 if (!empty($params['contribution_status_id']) && empty($params['relate_contribution_id'])) {
8d8bd076 336 $memInfo = array_merge($params, array('membership_id' => $membership->id));
337 $params['contribution'] = self::recordMembershipContribution($memInfo, $ids);
6a488035 338 }
3c0201c9 339
82cc6775
PN
340 if (!empty($params['lineItems'])) {
341 $params['line_item'] = $params['lineItems'];
342 }
343
344 //do cleanup line items if membership edit the Membership type.
345 if (empty($ids['contribution']) && !empty($ids['membership'])) {
346 CRM_Price_BAO_LineItem::deleteLineItems($ids['membership'], 'civicrm_membership');
347 }
3c0201c9 348
82cc6775
PN
349 if (!empty($params['line_item']) && empty($ids['contribution'])) {
350 CRM_Price_BAO_LineItem::processPriceSet($membership->id, $params['line_item'], CRM_Utils_Array::value('contribution', $params));
d5b95619 351 }
6a488035
TO
352
353 //insert payment record for this membership
a7488080 354 if (!empty($params['relate_contribution_id'])) {
e0556ebe 355 CRM_Member_BAO_MembershipPayment::create(array(
353ffa53
TO
356 'membership_id' => $membership->id,
357 'contribution_id' => $params['relate_contribution_id'],
358 ));
6a488035
TO
359 }
360
361 // add activity record only during create mode and renew mode
362 // also add activity if status changed CRM-3984 and CRM-2521
a7488080 363 if (empty($ids['membership']) ||
e0556ebe
TO
364 $activityType == 'Membership Renewal' || !empty($params['createActivity'])
365 ) {
a7488080 366 if (!empty($ids['membership'])) {
ca58d9b9 367 $data = array();
6a488035
TO
368 CRM_Core_DAO::commonRetrieveAll('CRM_Member_DAO_Membership',
369 'id',
370 $membership->id,
371 $data,
372 array('contact_id', 'membership_type_id', 'source')
373 );
374
375 $membership->contact_id = $data[$membership->id]['contact_id'];
376 $membership->membership_type_id = $data[$membership->id]['membership_type_id'];
377 $membership->source = CRM_Utils_Array::value('source', $data[$membership->id]);
378 }
379
380 // since we are going to create activity record w/
381 // individual contact as a target in case of on behalf signup,
382 // so get the copy of organization id, CRM-5551
383 $realMembershipContactId = $membership->contact_id;
384
385 // create activity source = individual, target = org CRM-4027
386 $targetContactID = NULL;
a7488080 387 if (!empty($params['is_for_organization'])) {
6a488035
TO
388 $targetContactID = $membership->contact_id;
389 $membership->contact_id = CRM_Utils_Array::value('userId', $ids);
390 }
391
392 if (empty($membership->contact_id) && (!empty($membership->owner_membership_id))) {
393 $membership->contact_id = $realMembershipContactId;
394 }
395
a7488080 396 if (!empty($ids['membership']) && $activityType != 'Membership Signup') {
6a488035 397 CRM_Activity_BAO_Activity::addActivity($membership, $activityType, $targetContactID);
e0556ebe
TO
398 }
399 elseif (empty($ids['membership'])) {
6a488035
TO
400 CRM_Activity_BAO_Activity::addActivity($membership, $activityType, $targetContactID);
401 }
402
403 // we might created activity record w/ individual
404 // contact as target so update membership object w/
405 // original organization id, CRM-5551
406 $membership->contact_id = $realMembershipContactId;
407 }
408
409 $transaction->commit();
410
411 self::createRelatedMemberships($params, $membership);
412
413 // do not add to recent items for import, CRM-4399
a7488080 414 if (empty($params['skipRecentView'])) {
6a488035
TO
415 $url = CRM_Utils_System::url('civicrm/contact/view/membership',
416 "action=view&reset=1&id={$membership->id}&cid={$membership->contact_id}&context=home"
417 );
e0556ebe 418 if (empty($membership->membership_type_id)) {// ie in an update situation
6a488035
TO
419 $membership->find(TRUE);
420 }
421 $membershipTypes = CRM_Member_PseudoConstant::membershipType();
422 $title = CRM_Contact_BAO_Contact::displayName($membership->contact_id) . ' - ' . ts('Membership Type:') . ' ' . $membershipTypes[$membership->membership_type_id];
423
424 $recentOther = array();
425 if (CRM_Core_Permission::checkActionPermission('CiviMember', CRM_Core_Action::UPDATE)) {
426 $recentOther['editUrl'] = CRM_Utils_System::url('civicrm/contact/view/membership',
427 "action=update&reset=1&id={$membership->id}&cid={$membership->contact_id}&context=home"
428 );
429 }
430 if (CRM_Core_Permission::checkActionPermission('CiviMember', CRM_Core_Action::DELETE)) {
431 $recentOther['deleteUrl'] = CRM_Utils_System::url('civicrm/contact/view/membership',
432 "action=delete&reset=1&id={$membership->id}&cid={$membership->contact_id}&context=home"
433 );
434 }
435
436 // add the recently created Membership
437 CRM_Utils_Recent::add($title,
438 $url,
439 $membership->id,
440 'Membership',
441 $membership->contact_id,
442 NULL,
443 $recentOther
444 );
445 }
446
447 return $membership;
448 }
449
450 /**
fe482240 451 * Check the membership extended through relationship.
6a488035 452 *
b2363ea8
TO
453 * @param int $membershipId
454 * Membership id.
455 * @param int $contactId
456 * Contact id.
ada8a833 457 *
b2363ea8 458 * @param int $action
6a488035 459 *
608e6658 460 * @return array
a6c01b45 461 * array of contact_id of all related contacts.
6a488035 462 */
00be9182 463 public static function checkMembershipRelationship($membershipId, $contactId, $action = CRM_Core_Action::ADD) {
6a488035
TO
464 $contacts = array();
465 $membershipTypeID = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_Membership', $membershipId, 'membership_type_id');
466
467 $membershipType = CRM_Member_BAO_MembershipType::getMembershipTypeDetails($membershipTypeID);
468 $relationships = array();
469 if (isset($membershipType['relationship_type_id'])) {
470 $relationships = CRM_Contact_BAO_Relationship::getRelationship($contactId,
471 CRM_Contact_BAO_Relationship::CURRENT
472 );
473 if ($action & CRM_Core_Action::UPDATE) {
474 $pastRelationships = CRM_Contact_BAO_Relationship::getRelationship($contactId,
475 CRM_Contact_BAO_Relationship::PAST
476 );
477 $relationships = array_merge($relationships, $pastRelationships);
478 }
479 }
480
481 if (!empty($relationships)) {
482 // check for each contact relationships
483 foreach ($relationships as $values) {
484 //get details of the relationship type
485 $relType = array('id' => $values['civicrm_relationship_type_id']);
486 $relValues = array();
487 CRM_Contact_BAO_RelationshipType::retrieve($relType, $relValues);
488 // Check if contact's relationship type exists in membership type
e0556ebe
TO
489 $relTypeDirs = array();
490 $relTypeIds = explode(CRM_Core_DAO::VALUE_SEPARATOR, $membershipType['relationship_type_id']);
6a488035
TO
491 $relDirections = explode(CRM_Core_DAO::VALUE_SEPARATOR, $membershipType['relationship_direction']);
492 $bidirectional = FALSE;
493 foreach ($relTypeIds as $key => $value) {
494 $relTypeDirs[] = $value . '_' . $relDirections[$key];
495 if (in_array($value, $relType) &&
496 $relValues['name_a_b'] == $relValues['name_b_a']
497 ) {
498 $bidirectional = TRUE;
499 break;
500 }
501 }
502 $relTypeDir = $values['civicrm_relationship_type_id'] . '_' . $values['rtype'];
503 if ($bidirectional || in_array($relTypeDir, $relTypeDirs)) {
504 // $values['status'] is going to have value for
505 // current or past relationships.
506 $contacts[$values['cid']] = $values['status'];
507 }
508 }
509 }
510
511 // Sort by contact_id ascending
512 ksort($contacts);
513 return $contacts;
514 }
515
516 /**
fe482240
EM
517 * Retrieve DB object based on input parameters.
518 *
519 * It also stores all the retrieved values in the default array.
6a488035 520 *
b2363ea8
TO
521 * @param array $params
522 * (reference ) an assoc array of name/value pairs.
523 * @param array $defaults
524 * (reference ) an assoc array to hold the name / value pairs.
6a488035 525 * in a hierarchical manner
8efea814 526 *
16b10e64 527 * @return CRM_Member_BAO_Membership
6a488035 528 */
00be9182 529 public static function retrieve(&$params, &$defaults) {
6a488035
TO
530 $membership = new CRM_Member_DAO_Membership();
531
532 $membership->copyValues($params);
533
534 if ($membership->find(TRUE)) {
535 CRM_Core_DAO::storeValues($membership, $defaults);
536
537 //get the membership status and type values.
538 $statusANDType = self::getStatusANDTypeValues($membership->id);
539 foreach (array(
e0556ebe 540 'status',
21dfd5f5 541 'membership_type',
e0556ebe 542 ) as $fld) {
6a488035
TO
543 $defaults[$fld] = CRM_Utils_Array::value($fld, $statusANDType[$membership->id]);
544 }
a7488080 545 if (!empty($statusANDType[$membership->id]['is_current_member'])) {
6a488035
TO
546 $defaults['active'] = TRUE;
547 }
548
549 $membership->free();
550
551 return $membership;
552 }
553
554 return NULL;
555 }
556
557 /**
558 *
c490a46a 559 * get membership status and membership type values
6a488035 560 *
b2363ea8
TO
561 * @param int $membershipId
562 * Membership id of values to return.
6a488035 563 *
a6c01b45 564 * @return array
16b10e64 565 * Array of key value pairs
6a488035 566 */
00be9182 567 public static function getStatusANDTypeValues($membershipId) {
6a488035
TO
568 $values = array();
569 if (!$membershipId) {
570 return $values;
571 }
572 $sql = '
573 SELECT membership.id as id,
574 status.id as status_id,
575 status.label as status,
576 status.is_current_member as is_current_member,
577 type.id as membership_type_id,
578 type.name as membership_type,
579 type.relationship_type_id as relationship_type_id
580 FROM civicrm_membership membership
581INNER JOIN civicrm_membership_status status ON ( status.id = membership.status_id )
582INNER JOIN civicrm_membership_type type ON ( type.id = membership.membership_type_id )
583 WHERE membership.id = %1';
584 $dao = CRM_Core_DAO::executeQuery($sql, array(1 => array($membershipId, 'Positive')));
e0556ebe
TO
585 $properties = array(
586 'status',
587 'status_id',
588 'membership_type',
589 'membership_type_id',
590 'is_current_member',
21dfd5f5 591 'relationship_type_id',
e0556ebe 592 );
6a488035
TO
593 while ($dao->fetch()) {
594 foreach ($properties as $property) {
595 $values[$dao->id][$property] = $dao->$property;
596 }
597 }
598
599 return $values;
600 }
601
3506b6cd 602 /**
100fef9d 603 * Delete membership.
f5e53870 604 * Wrapper for most delete calls. Use this unless you JUST want to delete related memberships w/o deleting the parent.
3506b6cd 605 *
b2363ea8
TO
606 * @param int $membershipId
607 * Membership id that needs to be deleted.
3506b6cd 608 *
3506b6cd 609 *
608e6658 610 * @return int $results id of deleted Membership on success, false otherwise
3506b6cd 611 */
00be9182 612 public static function del($membershipId) {
3506b6cd
DG
613 //delete related first and then delete parent.
614 self::deleteRelatedMemberships($membershipId);
d824fb6e 615 return self::deleteMembership($membershipId);
3506b6cd 616 }
d824fb6e 617
6a488035 618 /**
100fef9d 619 * Delete membership.
6a488035 620 *
b2363ea8
TO
621 * @param int $membershipId
622 * Membership id that needs to be deleted.
6a488035 623 *
6a488035 624 *
608e6658 625 * @return int $results id of deleted Membership on success, false otherwise
6a488035 626 */
00be9182 627 public static function deleteMembership($membershipId) {
46d8f506
DL
628 // CRM-12147, retrieve membership data before we delete it for hooks
629 $params = array('id' => $membershipId);
630 $memValues = array();
631 $memberships = self::getValues($params, $memValues);
3506b6cd 632
46d8f506
DL
633 $membership = $memberships[$membershipId];
634
635 CRM_Utils_Hook::pre('delete', 'Membership', $membershipId, $memValues);
6a488035
TO
636
637 $transaction = new CRM_Core_Transaction();
638
639 $results = NULL;
640 //delete activity record
641 $activityTypes = CRM_Core_PseudoConstant::activityType(TRUE, FALSE, FALSE, 'name');
642
643 $params = array();
e0556ebe
TO
644 $deleteActivity = FALSE;
645 $membershipActivities = array(
46d8f506
DL
646 'Membership Signup',
647 'Membership Renewal',
648 'Change Membership Status',
649 'Change Membership Type',
21dfd5f5 650 'Membership Renewal Reminder',
46d8f506 651 );
e0556ebe 652 foreach ($membershipActivities as $membershipActivity) {
6a488035
TO
653 $activityId = array_search($membershipActivity, $activityTypes);
654 if ($activityId) {
655 $params['activity_type_id'][] = $activityId;
e0556ebe 656 $deleteActivity = TRUE;
6a488035
TO
657 }
658 }
659 if ($deleteActivity) {
660 $params['source_record_id'] = $membershipId;
46d8f506 661 CRM_Activity_BAO_Activity::deleteActivity($params);
6a488035
TO
662 }
663 self::deleteMembershipPayment($membershipId);
664
d0fbc816 665 $results = $membership->delete();
6a488035
TO
666 $transaction->commit();
667
668 CRM_Utils_Hook::post('delete', 'Membership', $membership->id, $membership);
669
670 // delete the recently created Membership
671 $membershipRecent = array(
672 'id' => $membershipId,
673 'type' => 'Membership',
674 );
675 CRM_Utils_Recent::del($membershipRecent);
676
677 return $results;
678 }
679
3506b6cd 680 /**
fe482240 681 * Delete related memberships.
3506b6cd
DG
682 *
683 * @param int $ownerMembershipId
684 * @param int $contactId
685 *
686 * @return null
3506b6cd 687 */
00be9182 688 public static function deleteRelatedMemberships($ownerMembershipId, $contactId = NULL) {
3506b6cd 689 if (!$ownerMembershipId && !$contactId) {
608e6658 690 return FALSE;
3506b6cd
DG
691 }
692
693 $membership = new CRM_Member_DAO_Membership();
694 $membership->owner_membership_id = $ownerMembershipId;
695
696 if ($contactId) {
697 $membership->contact_id = $contactId;
698 }
699
700 $membership->find();
701 while ($membership->fetch()) {
702 //delete related first and then delete parent.
703 self::deleteRelatedMemberships($membership->id);
704 self::deleteMembership($membership->id);
705 }
706 $membership->free();
707 }
708
6a488035 709 /**
100fef9d 710 * Obtain active/inactive memberships from the list of memberships passed to it.
6a488035 711 *
b2363ea8
TO
712 * @param array $memberships
713 * Membership records.
714 * @param string $status
715 * Active or inactive.
6a488035 716 *
a6c01b45
CW
717 * @return array
718 * array of memberships based on status
6a488035 719 */
00be9182 720 public static function activeMembers($memberships, $status = 'active') {
6a488035
TO
721 $actives = array();
722 if ($status == 'active') {
723 foreach ($memberships as $f => $v) {
a7488080 724 if (!empty($v['active'])) {
6a488035
TO
725 $actives[$f] = $v;
726 }
727 }
728 return $actives;
729 }
730 elseif ($status == 'inactive') {
731 foreach ($memberships as $f => $v) {
a7488080 732 if (empty($v['active'])) {
6a488035
TO
733 $actives[$f] = $v;
734 }
735 }
736 return $actives;
737 }
738 return NULL;
739 }
740
741 /**
fe482240 742 * Build Membership Block in Contribution Pages.
6a488035 743 *
b2363ea8
TO
744 * @param CRM_Core_Form $form
745 * Form object.
746 * @param int $pageID
747 * Unused?.
748 * @param int $cid
749 * Contact checked for having a current membership for a particular membership.
750 * @param bool $formItems
751 * @param int $selectedMembershipTypeID
752 * Selected membership id.
753 * @param bool $thankPage
754 * Thank you page.
e46e9a0b
EM
755 * @param null $isTest
756 *
a6c01b45
CW
757 * @return bool
758 * Is this a separate membership payment
6a488035 759 *
6a488035 760 */
608e6658 761 public static function buildMembershipBlock(
500cfe81 762 &$form,
353ffa53
TO
763 $pageID,
764 $cid,
765 $formItems = FALSE,
766 $selectedMembershipTypeID = NULL,
767 $thankPage = FALSE,
768 $isTest = NULL
6a488035
TO
769 ) {
770
771 $separateMembershipPayment = FALSE;
772 if ($form->_membershipBlock) {
773 $form->_currentMemberships = array();
6a488035 774
e0556ebe
TO
775 $membershipBlock = $form->_membershipBlock;
776 $membershipTypeIds = $membershipTypes = $radio = array();
6a488035
TO
777 $membershipPriceset = (!empty($form->_priceSetId) && $form->_useForMember) ? TRUE : FALSE;
778
779 $allowAutoRenewMembership = $autoRenewOption = FALSE;
780 $autoRenewMembershipTypeOptions = array();
781
782 $paymentProcessor = CRM_Core_PseudoConstant::paymentProcessor(FALSE, FALSE, 'is_recur = 1');
783
784 $separateMembershipPayment = CRM_Utils_Array::value('is_separate_payment', $membershipBlock);
785
786 if ($membershipPriceset) {
787 foreach ($form->_priceSet['fields'] as $pField) {
788 if (empty($pField['options'])) {
789 continue;
790 }
791 foreach ($pField['options'] as $opId => $opValues) {
a7488080 792 if (empty($opValues['membership_type_id'])) {
6a488035
TO
793 continue;
794 }
795 $membershipTypeIds[$opValues['membership_type_id']] = $opValues['membership_type_id'];
796 }
797 }
798 }
a7488080 799 elseif (!empty($membershipBlock['membership_types'])) {
6a488035
TO
800 $membershipTypeIds = explode(',', $membershipBlock['membership_types']);
801 }
802
803 if (!empty($membershipTypeIds)) {
804 //set status message if wrong membershipType is included in membershipBlock
805 if (isset($form->_mid) && !$membershipPriceset) {
806 $membershipTypeID = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_Membership',
807 $form->_mid,
808 'membership_type_id'
809 );
810 if (!in_array($membershipTypeID, $membershipTypeIds)) {
811 CRM_Core_Session::setStatus(ts("Oops. The membership you're trying to renew appears to be invalid. Contact your site administrator if you need assistance. If you continue, you will be issued a new membership."), ts('Invalid Membership'), 'error');
812 }
813 }
814
815 $membershipTypeValues = self::buildMembershipTypeValues($form, $membershipTypeIds);
816 $form->_membershipTypeValues = $membershipTypeValues;
817 $endDate = NULL;
818 foreach ($membershipTypeIds as $value) {
819 $memType = $membershipTypeValues[$value];
820 if ($selectedMembershipTypeID != NULL) {
821 if ($memType['id'] == $selectedMembershipTypeID) {
822 $form->assign('minimum_fee',
823 CRM_Utils_Array::value('minimum_fee', $memType)
824 );
825 $form->assign('membership_name', $memType['name']);
826 if (!$thankPage && $cid) {
827 $membership = new CRM_Member_DAO_Membership();
828 $membership->contact_id = $cid;
829 $membership->membership_type_id = $memType['id'];
830 if ($membership->find(TRUE)) {
831 $form->assign('renewal_mode', TRUE);
832 $memType['current_membership'] = $membership->end_date;
833 $form->_currentMemberships[$membership->membership_type_id] = $membership->membership_type_id;
834 }
835 }
836 $membershipTypes[] = $memType;
837 }
838 }
839 elseif ($memType['is_active']) {
840 $javascriptMethod = NULL;
841 $allowAutoRenewOpt = 1;
e0556ebe 842 if (is_array($form->_paymentProcessors)) {
6a488035
TO
843 foreach ($form->_paymentProcessors as $id => $val) {
844 if (!$val['is_recur']) {
e0556ebe 845 $allowAutoRenewOpt = 0;
6a488035 846 continue;
e0556ebe 847 }
6a488035
TO
848 }
849 }
850
851 $javascriptMethod = array('onclick' => "return showHideAutoRenew( this.value );");
e0556ebe 852 $autoRenewMembershipTypeOptions["autoRenewMembershipType_{$value}"] = (int) $allowAutoRenewOpt * CRM_Utils_Array::value($value, CRM_Utils_Array::value('auto_renew', $form->_membershipBlock));;
6a488035
TO
853
854 if ($allowAutoRenewOpt) {
855 $allowAutoRenewMembership = TRUE;
856 }
857
858 //add membership type.
859 $radio[$memType['id']] = $form->createElement('radio', NULL, NULL, NULL,
860 $memType['id'], $javascriptMethod
861 );
862 if ($cid) {
863 $membership = new CRM_Member_DAO_Membership();
864 $membership->contact_id = $cid;
865 $membership->membership_type_id = $memType['id'];
866
867 //show current membership, skip pending and cancelled membership records,
868 //because we take first membership record id for renewal
869 $membership->whereAdd('status_id != 5 AND status_id !=6');
870
871 if (!is_null($isTest)) {
872 $membership->is_test = $isTest;
873 }
874
875 //CRM-4297
876 $membership->orderBy('end_date DESC');
877
878 if ($membership->find(TRUE)) {
879 if (!$membership->end_date) {
880 unset($radio[$memType['id']]);
881 $form->assign('islifetime', TRUE);
882 continue;
883 }
884 $form->assign('renewal_mode', TRUE);
885 $form->_currentMemberships[$membership->membership_type_id] = $membership->membership_type_id;
886 $memType['current_membership'] = $membership->end_date;
887 if (!$endDate) {
888 $endDate = $memType['current_membership'];
889 $form->_defaultMemTypeId = $memType['id'];
890 }
891 if ($memType['current_membership'] < $endDate) {
892 $endDate = $memType['current_membership'];
893 $form->_defaultMemTypeId = $memType['id'];
894 }
895 }
896 }
897 $membershipTypes[] = $memType;
898 }
899 }
900 }
901
902 $form->assign('showRadio', $formItems);
903 if ($formItems) {
904 if (!$membershipPriceset) {
905 if (!$membershipBlock['is_required']) {
906 $form->assign('showRadioNoThanks', TRUE);
907 $radio[''] = $form->createElement('radio', NULL, NULL, NULL, 'no_thanks', NULL);
908 $form->addGroup($radio, 'selectMembership', NULL);
909 }
910 elseif ($membershipBlock['is_required'] && count($radio) == 1) {
911 $temp = array_keys($radio);
912 $form->add('hidden', 'selectMembership', $temp[0], array('id' => 'selectMembership'));
913 $form->assign('singleMembership', TRUE);
914 $form->assign('showRadio', FALSE);
915 }
916 else {
917 $form->addGroup($radio, 'selectMembership', NULL);
918 }
919
920 $form->addRule('selectMembership', ts('Please select one of the memberships.'), 'required');
921 }
922 else {
9da8dc8c 923 $autoRenewOption = CRM_Price_BAO_PriceSet::checkAutoRenewForPriceSet($form->_priceSetId);
6a488035
TO
924 $form->assign('autoRenewOption', $autoRenewOption);
925 }
926
927 if (!$form->_values['is_pay_later'] && is_array($form->_paymentProcessors) && ($allowAutoRenewMembership || $autoRenewOption)) {
928 $form->addElement('checkbox', 'auto_renew', ts('Please renew my membership automatically.'));
929 }
930
931 }
932
933 $form->assign('membershipBlock', $membershipBlock);
934 $form->assign('membershipTypes', $membershipTypes);
935 $form->assign('allowAutoRenewMembership', $allowAutoRenewMembership);
936 $form->assign('autoRenewMembershipTypeOptions', json_encode($autoRenewMembershipTypeOptions));
937
938 //give preference to user submitted auto_renew value.
939 $takeUserSubmittedAutoRenew = (!empty($_POST) || $form->isSubmitted()) ? TRUE : FALSE;
940 $form->assign('takeUserSubmittedAutoRenew', $takeUserSubmittedAutoRenew);
941 }
942
943 return $separateMembershipPayment;
944 }
945
946 /**
fe482240 947 * Return Membership Block info in Contribution Pages.
6a488035 948 *
b2363ea8
TO
949 * @param int $pageID
950 * Contribution page id.
e46e9a0b
EM
951 *
952 * @return array|null
6a488035 953 *
6a488035 954 */
00be9182 955 public static function getMembershipBlock($pageID) {
e0556ebe
TO
956 $membershipBlock = array();
957 $dao = new CRM_Member_DAO_MembershipBlock();
6a488035
TO
958 $dao->entity_table = 'civicrm_contribution_page';
959
960 $dao->entity_id = $pageID;
961 $dao->is_active = 1;
962 if ($dao->find(TRUE)) {
963 CRM_Core_DAO::storeValues($dao, $membershipBlock);
a7488080 964 if (!empty($membershipBlock['membership_types'])) {
6a488035
TO
965 $membershipTypes = unserialize($membershipBlock['membership_types']);
966 if (!is_array($membershipTypes)) {
967 return $membershipBlock;
968 }
969 $memTypes = array();
970 foreach ($membershipTypes as $key => $value) {
971 $membershipBlock['auto_renew'][$key] = $value;
972 $memTypes[$key] = $key;
973 }
974 $membershipBlock['membership_types'] = implode(',', $memTypes);
975 }
976 }
977 else {
978 return NULL;
979 }
980
981 return $membershipBlock;
982 }
983
984 /**
fe482240 985 * Return a current membership of given contact.
c490a46a 986 * NB: if more than one membership meets criteria, a randomly selected one is returned.
6a488035 987 *
b2363ea8
TO
988 * @param int $contactID
989 * Contact id.
990 * @param int $memType
991 * Membership type, null to retrieve all types.
6a488035 992 * @param int $isTest
b2363ea8
TO
993 * @param int $membershipId
994 * If provided, then determine if it is current.
995 * @param bool $onlySameParentOrg
996 * True if only Memberships with same parent org as the $memType wanted, false otherwise.
e46e9a0b
EM
997 *
998 * @return array|bool
6a488035 999 */
00be9182 1000 public static function getContactMembership($contactID, $memType, $isTest, $membershipId = NULL, $onlySameParentOrg = FALSE) {
6a488035
TO
1001 $dao = new CRM_Member_DAO_Membership();
1002 if ($membershipId) {
1003 $dao->id = $membershipId;
1004 }
1005 $dao->contact_id = $contactID;
1006 $dao->membership_type_id = $memType;
1007
1008 //fetch proper membership record.
1009 if ($isTest) {
1010 $dao->is_test = $isTest;
1011 }
1012 else {
1013 $dao->whereAdd('is_test IS NULL OR is_test = 0');
1014 }
5624f515 1015
6a488035 1016 //avoid pending membership as current membership: CRM-3027
b5a62499
PN
1017 $statusIds[] = array_search('Pending', CRM_Member_PseudoConstant::membershipStatus());
1018 if (!$membershipId) {
7ff60806
PN
1019 // CRM-15475
1020 $statusIds[] = array_search(
4c16123d 1021 'Cancelled',
7ff60806 1022 CRM_Member_PseudoConstant::membershipStatus(
4c16123d
EM
1023 NULL,
1024 " name = 'Cancelled' ",
1025 'name',
1026 FALSE,
7ff60806
PN
1027 TRUE
1028 )
1029 );
b5a62499 1030 }
e0556ebe 1031 $dao->whereAdd('status_id NOT IN ( ' . implode(',', $statusIds) . ')');
5624f515 1032
6a488035
TO
1033 // order by start date to find most recent membership first, CRM-4545
1034 $dao->orderBy('start_date DESC');
1035
1036 // CRM-8141
1037 if ($onlySameParentOrg && $memType) {
1038 // require the same parent org as the $memType
1039 $params = array('id' => $memType);
5901ddf9 1040 $membershipType = array();
6a488035
TO
1041 if (CRM_Member_BAO_MembershipType::retrieve($params, $membershipType)) {
1042 $memberTypesSameParentOrg = CRM_Member_BAO_MembershipType::getMembershipTypesByOrg($membershipType['member_of_contact_id']);
1043 $memberTypesSameParentOrgList = implode(',', array_keys($memberTypesSameParentOrg));
1044 $dao->whereAdd('membership_type_id IN (' . $memberTypesSameParentOrgList . ')');
1045 }
1046 }
1047
1048 if ($dao->find(TRUE)) {
1049 $membership = array();
1050 CRM_Core_DAO::storeValues($dao, $membership);
1051 $membership['is_current_member'] = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipStatus',
1052 $membership['status_id'],
1053 'is_current_member', 'id'
1054 );
1055 return $membership;
1056 }
1057
1058 // CRM-8141
1059 if ($onlySameParentOrg && $memType) {
1060 // see if there is a membership that has same parent as $memType but different parent than $membershipID
5682e889 1061 if ($dao->id && CRM_Core_Permission::check('edit memberships')) {
1062 // CRM-10016, This is probably a backend renewal, and make sure we return the same membership thats being renewed.
e0556ebe 1063 $dao->whereAdd();
5682e889 1064 }
1065 else {
1066 unset($dao->id);
1067 }
6a488035
TO
1068
1069 unset($dao->membership_type_id);
1070 if ($dao->find(TRUE)) {
1071 $membership = array();
1072 CRM_Core_DAO::storeValues($dao, $membership);
1073 $membership['is_current_member'] = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipStatus',
1074 $membership['status_id'],
1075 'is_current_member', 'id'
1076 );
1077 return $membership;
1078 }
1079 }
1080 return FALSE;
1081 }
1082
1083 /**
fe482240 1084 * Combine all the importable fields from the lower levels object.
6a488035 1085 *
b2363ea8
TO
1086 * @param string $contactType
1087 * Contact type.
1088 * @param bool $status
6a488035 1089 *
a6c01b45
CW
1090 * @return array
1091 * array of importable Fields
6a488035 1092 */
00be9182 1093 public static function &importableFields($contactType = 'Individual', $status = TRUE) {
6a488035
TO
1094 if (!self::$_importableFields) {
1095 if (!self::$_importableFields) {
1096 self::$_importableFields = array();
1097 }
1098
1099 if (!$status) {
1100 $fields = array('' => array('title' => '- ' . ts('do not import') . ' -'));
1101 }
1102 else {
1103 $fields = array('' => array('title' => '- ' . ts('Membership Fields') . ' -'));
1104 }
1105
1106 $tmpFields = CRM_Member_DAO_Membership::import();
1107 $contactFields = CRM_Contact_BAO_Contact::importableFields($contactType, NULL);
1108
1109 // Using new Dedupe rule.
1110 $ruleParams = array(
1111 'contact_type' => $contactType,
e0556ebe 1112 'used' => 'Unsupervised',
6a488035
TO
1113 );
1114 $fieldsArray = CRM_Dedupe_BAO_Rule::dedupeRuleFields($ruleParams);
1115
1116 $tmpContactField = array();
1117 if (is_array($fieldsArray)) {
1118 foreach ($fieldsArray as $value) {
1119 $customFieldId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField',
1120 $value,
1121 'id',
1122 'column_name'
1123 );
1124 $value = $customFieldId ? 'custom_' . $customFieldId : $value;
1125 $tmpContactField[trim($value)] = CRM_Utils_Array::value(trim($value), $contactFields);
1126 if (!$status) {
1127 $title = $tmpContactField[trim($value)]['title'] . " " . ts('(match to contact)');
1128 }
1129 else {
1130 $title = $tmpContactField[trim($value)]['title'];
1131 }
1132 $tmpContactField[trim($value)]['title'] = $title;
1133 }
1134 }
1135 $tmpContactField['external_identifier'] = $contactFields['external_identifier'];
1136 $tmpContactField['external_identifier']['title'] = $contactFields['external_identifier']['title'] . " " . ts('(match to contact)');
1137
1138 $tmpFields['membership_contact_id']['title'] = $tmpFields['membership_contact_id']['title'] . " " . ts('(match to contact)');;
1139
1140 $fields = array_merge($fields, $tmpContactField);
1141 $fields = array_merge($fields, $tmpFields);
1142 $fields = array_merge($fields, CRM_Core_BAO_CustomField::getFieldsForImport('Membership'));
1143 self::$_importableFields = $fields;
1144 }
1145 return self::$_importableFields;
1146 }
1147
1148 /**
fe482240 1149 * Get all exportable fields.
6a488035
TO
1150 *
1151 * @retun array return array of all exportable fields
6a488035 1152 */
00be9182 1153 public static function &exportableFields() {
6a488035 1154 $expFieldMembership = CRM_Member_DAO_Membership::export();
6a488035
TO
1155
1156 $expFieldsMemType = CRM_Member_DAO_MembershipType::export();
e0556ebe
TO
1157 $fields = array_merge($expFieldMembership, $expFieldsMemType);
1158 $fields = array_merge($fields, $expFieldMembership);
6a488035 1159 $membershipStatus = array(
e0556ebe
TO
1160 'membership_status' => array(
1161 'title' => 'Membership Status',
6a488035
TO
1162 'name' => 'membership_status',
1163 'type' => CRM_Utils_Type::T_STRING,
1164 'where' => 'civicrm_membership_status.name',
21dfd5f5 1165 ),
e0556ebe 1166 );
6a488035
TO
1167 //CRM-6161 fix for customdata export
1168 $fields = array_merge($fields, $membershipStatus, CRM_Core_BAO_CustomField::getFieldsForImport('Membership'));
1169 return $fields;
1170 }
1171
1172 /**
100fef9d 1173 * Get membership joins/renewals for a specified membership
4e636a74
AH
1174 * type. Specifically, retrieves a count of memberships whose "Membership
1175 * Signup" or "Membership Renewal" activity falls in the given date range.
8ef12e64 1176 * Dates match the pattern "yyyy-mm-dd".
6a488035 1177 *
b2363ea8
TO
1178 * @param int $membershipTypeId
1179 * Membership type id.
1180 * @param int $startDate
1181 * Date on which to start counting.
1182 * @param int $endDate
1183 * Date on which to end counting.
e46e9a0b
EM
1184 * @param bool|int $isTest if true, membership is for a test site
1185 * @param bool|int $isOwner if true, only retrieve membership records for owners //LCD
6a488035 1186 *
df8d3074 1187 * @return int
a6c01b45 1188 * the number of members of type $membershipTypeId whose
688d37c6 1189 * start_date is between $startDate and $endDate
6a488035 1190 */
6a488035 1191 public static function getMembershipStarts($membershipTypeId, $startDate, $endDate, $isTest = 0, $isOwner = 0) {
8ef12e64 1192
4e636a74
AH
1193 $testClause = 'membership.is_test = 1';
1194 if (!$isTest) {
1195 $testClause = '( membership.is_test IS NULL OR membership.is_test = 0 )';
1196 }
8ef12e64 1197
4e636a74
AH
1198 if (!self::$_signupActType || !self::$_renewalActType) {
1199 self::_getActTypes();
1200 }
8ef12e64 1201
4e636a74
AH
1202 if (!self::$_signupActType || !self::$_renewalActType) {
1203 return 0;
1204 }
1205
1206 $query = "
1207 SELECT COUNT(DISTINCT membership.id) as member_count
1208 FROM civicrm_membership membership
1209INNER JOIN civicrm_activity activity ON (activity.source_record_id = membership.id AND activity.activity_type_id in (%1, %2))
1210INNER JOIN civicrm_membership_status status ON ( membership.status_id = status.id AND status.is_current_member = 1 )
1211INNER JOIN civicrm_contact contact ON ( contact.id = membership.contact_id AND contact.is_deleted = 0 )
1212 WHERE membership.membership_type_id = %3
1213 AND activity.activity_date_time >= '$startDate' AND activity.activity_date_time <= '$endDate 23:59:59'
1214 AND {$testClause}";
8ef12e64 1215
6a488035 1216 $query .= ($isOwner) ? ' AND owner_membership_id IS NULL' : '';
4e636a74
AH
1217
1218 $params = array(
1219 1 => array(self::$_signupActType, 'Integer'),
1220 2 => array(self::$_renewalActType, 'Integer'),
8ef12e64 1221 3 => array($membershipTypeId, 'Integer'),
6a488035 1222 );
8ef12e64 1223
6a488035 1224 $memberCount = CRM_Core_DAO::singleValueQuery($query, $params);
e0556ebe 1225 return (int) $memberCount;
6a488035
TO
1226 }
1227
1228 /**
100fef9d 1229 * Get a count of membership for a specified membership type,
8ef12e64 1230 * optionally for a specified date. The date must have the form yyyy-mm-dd.
6a488035
TO
1231 *
1232 * If $date is omitted, this function counts as a member anyone whose
1233 * membership status_id indicates they're a current member.
1234 * If $date is given, this function counts as a member anyone who:
1235 * -- Has a start_date before $date and end_date after $date, or
1236 * -- Has a start_date before $date and is currently a member, as indicated
1237 * by the the membership's status_id.
1238 * The second condition takes care of records that have no end_date. These
1239 * are assumed to be lifetime memberships.
1240 *
b2363ea8
TO
1241 * @param int $membershipTypeId
1242 * Membership type id.
1243 * @param string $date
1244 * The date for which to retrieve the count.
e46e9a0b
EM
1245 * @param bool|int $isTest if true, membership is for a test site
1246 * @param bool|int $isOwner if true, only retrieve membership records for owners //LCD
6a488035 1247 *
72b3a70c
CW
1248 * @return int
1249 * the number of members of type $membershipTypeId as of $date.
6a488035
TO
1250 */
1251 public static function getMembershipCount($membershipTypeId, $date = NULL, $isTest = 0, $isOwner = 0) {
4e636a74
AH
1252 if (!CRM_Utils_Rule::date($date)) {
1253 CRM_Core_Error::fatal(ts('Invalid date "%1" (must have form yyyy-mm-dd).', array(1 => $date)));
6a488035
TO
1254 }
1255
e0556ebe
TO
1256 $params = array(
1257 1 => array($membershipTypeId, 'Integer'),
6a488035
TO
1258 2 => array($isTest, 'Boolean'),
1259 );
1260 $query = "SELECT count(civicrm_membership.id ) as member_count
1261FROM civicrm_membership left join civicrm_membership_status on ( civicrm_membership.status_id = civicrm_membership_status.id )
1262WHERE civicrm_membership.membership_type_id = %1
1263AND civicrm_membership.contact_id NOT IN (SELECT id FROM civicrm_contact WHERE is_deleted = 1)
1264AND civicrm_membership.is_test = %2";
1265 if (!$date) {
1266 $query .= " AND civicrm_membership_status.is_current_member = 1";
1267 }
1268 else {
6a488035
TO
1269 $query .= " AND civicrm_membership.start_date <= '$date' AND civicrm_membership_status.is_current_member = 1";
1270 }
1271 // LCD
1272 $query .= ($isOwner) ? ' AND owner_membership_id IS NULL' : '';
1273 $memberCount = CRM_Core_DAO::singleValueQuery($query, $params);
e0556ebe 1274 return (int) $memberCount;
6a488035
TO
1275 }
1276
1277 /**
fe482240 1278 * Function check the status of the membership before adding membership for a contact.
6a488035 1279 *
b2363ea8
TO
1280 * @param int $contactId
1281 * Contact id.
6a488035 1282 *
e46e9a0b 1283 * @return int
6a488035 1284 */
00be9182 1285 public static function statusAvailabilty($contactId) {
6a488035
TO
1286 $membership = new CRM_Member_DAO_MembershipStatus();
1287 $membership->whereAdd('is_active=1');
c905ba98 1288 return $membership->count();
6a488035
TO
1289 }
1290
1291 /**
fe482240 1292 * Process the Memberships.
6a488035 1293 *
b2363ea8
TO
1294 * @param array $membershipParams
1295 * Array of membership fields.
1296 * @param int $contactID
1297 * Contact id.
1298 * @param CRM_Contribute_Form_Contribution_Confirm $form
1299 * Confirmation form object.
e46e9a0b 1300 *
100fef9d 1301 * @param array $premiumParams
e46e9a0b
EM
1302 * @param null $customFieldsFormatted
1303 * @param null $includeFieldTypes
6a488035 1304 *
fa9d0451 1305 * @param array $membershipDetails
705b4205 1306 *
d25e4224 1307 * @param array $membershipTypeIDs
fa9d0451
EM
1308 *
1309 * @param bool $isPaidMembership
d25e4224 1310 * @param array $membershipID
705b4205 1311 *
38b4a5fe
EM
1312 * @param $isProcessSeparateMembershipTransaction
1313 *
100fef9d 1314 * @param int $defaultContributionTypeID
b2363ea8
TO
1315 * @param array $membershipLineItems
1316 * Line items specific to membership payment that is separate to contribution.
c2b5a0af 1317 * @param $isPayLater
705b4205 1318 *
c2b5a0af 1319 * @throws \CRM_Core_Exception
6a488035 1320 */
500cfe81
TO
1321 public static function postProcessMembership(
1322 $membershipParams, $contactID, &$form, $premiumParams,
353ffa53
TO
1323 $customFieldsFormatted = NULL, $includeFieldTypes = NULL, $membershipDetails, $membershipTypeIDs, $isPaidMembership, $membershipID,
1324 $isProcessSeparateMembershipTransaction, $defaultContributionTypeID, $membershipLineItems, $isPayLater) {
e0556ebe
TO
1325 $result = $membershipContribution = NULL;
1326 $isTest = CRM_Utils_Array::value('is_test', $membershipParams, FALSE);
f5d25ee9 1327 $errors = $createdMemberships = array();
3c0201c9 1328
cc789d46
EM
1329 //@todo move this into the calling function & pass in the correct financialTypeID
1330 if (isset($paymentParams['financial_type'])) {
1331 $financialTypeID = $paymentParams['financial_type'];
1332 }
1333 else {
1334 $financialTypeID = $defaultContributionTypeID;
1335 }
1336
f990d7e8 1337 if (CRM_Utils_Array::value('membership_source', $form->_params)) {
1338 $membershipParams['contribution_source'] = $form->_params['membership_source'];
1339 }
1340
fa9d0451 1341 if ($isPaidMembership) {
6a488035
TO
1342 $result = CRM_Contribute_BAO_Contribution_Utils::processConfirm($form, $membershipParams,
1343 $premiumParams, $contactID,
cc789d46
EM
1344 $financialTypeID,
1345 'membership',
1346 array(),
1347 $isTest,
1348 $isPayLater
6a488035 1349 );
d45686b0
EM
1350 if (is_a($result[1], 'CRM_Core_Error')) {
1351 $errors[1] = CRM_Core_Error::getMessages($result[1]);
6a488035 1352 }
d45686b0
EM
1353 elseif (!empty($result[1])) {
1354 // Save the contribution ID so that I can be used in email receipts
1355 // For example, if you need to generate a tax receipt for the donation only.
1356 $form->_values['contribution_other_id'] = $result[1]->id;
1357 //note that this will be over-written if we are using a separate membership transaction. Otherwise there is only one
1358 $membershipContribution = $result[1];
6a488035 1359 }
6a488035 1360 }
6a488035 1361
38b4a5fe 1362 if ($isProcessSeparateMembershipTransaction) {
d45686b0 1363 try {
866f6e8e 1364 $lineItems = $form->_lineItem = $membershipLineItems;
919e8652 1365 if (empty($form->_params['auto_renew']) && !empty($membershipParams['is_recur'])) {
1366 unset($membershipParams['is_recur']);
1367 }
5624f515 1368 $membershipContribution = self::processSecondaryFinancialTransaction($contactID, $form, $membershipParams, $isTest, $membershipLineItems, CRM_Utils_Array::value('minimum_fee', $membershipDetails, 0), CRM_Utils_Array::value('financial_type_id', $membershipDetails));
6a488035 1369 }
d45686b0
EM
1370 catch (CRM_Core_Exception $e) {
1371 $errors[2] = $e->getMessage();
1372 $membershipContribution = NULL;
6a488035
TO
1373 }
1374 }
1375
8d65cef7 1376 $membership = NULL;
423c9872 1377 if (!empty($membershipContribution) && !is_a($membershipContribution, 'CRM_Core_Error')) {
dd9e2b05
EM
1378 $membershipContributionID = $membershipContribution->id;
1379 }
423c9872
C
1380
1381 //@todo - why is this nested so deep? it seems like it could be just set on the calling function on the form layer
1382 if (isset($membershipParams['onbehalf']) && !empty($membershipParams['onbehalf']['member_campaign_id'])) {
1383 $form->_params['campaign_id'] = $membershipParams['onbehalf']['member_campaign_id'];
1384 }
1385 //@todo it should no longer be possible for it to get to this point & membership to not be an array
6e92ff36 1386 if (is_array($membershipTypeIDs) && !empty($membershipContributionID)) {
423c9872 1387 $typesTerms = CRM_Utils_Array::value('types_terms', $membershipParams, array());
d25e4224 1388 foreach ($membershipTypeIDs as $memType) {
423c9872
C
1389 $numTerms = CRM_Utils_Array::value($memType, $typesTerms, 1);
1390 $createdMemberships[$memType] = self::createOrRenewMembership($membershipParams, $contactID, $customFieldsFormatted, $membershipID, $memType, $isTest, $numTerms, $membershipContribution, $form);
6a488035 1391 }
423c9872
C
1392 if ($form->_priceSetId && !empty($form->_useForMember) && !empty($form->_lineItem)) {
1393 foreach ($form->_lineItem[$form->_priceSetId] as & $priceFieldOp) {
1394 if (!empty($priceFieldOp['membership_type_id']) &&
1395 isset($createdMemberships[$priceFieldOp['membership_type_id']])
1396 ) {
1397 $membershipOb = $createdMemberships[$priceFieldOp['membership_type_id']];
3b85fc04
PN
1398 $priceFieldOp['start_date'] = $membershipOb->start_date ? CRM_Utils_Date::customFormat($membershipOb->start_date, '%B %E%f, %Y') : '-';
1399 $priceFieldOp['end_date'] = $membershipOb->end_date ? CRM_Utils_Date::customFormat($membershipOb->end_date, '%B %E%f, %Y') : '-';
423c9872
C
1400 }
1401 else {
1402 $priceFieldOp['start_date'] = $priceFieldOp['end_date'] = 'N/A';
6a488035 1403 }
6a488035 1404 }
423c9872
C
1405 $form->_values['lineItem'] = $form->_lineItem;
1406 $form->assign('lineItem', $form->_lineItem);
6a488035 1407 }
6a488035
TO
1408 }
1409
1410 if (!empty($errors)) {
15c3308b 1411 $message = self::compileErrorMessage($errors);
93a11cd6 1412 throw new CRM_Core_Exception($message);
6a488035 1413 }
8d65cef7
AC
1414 $form->_params['createdMembershipIDs'] = array();
1415
1416 // CRM-7851 - Moved after processing Payment Errors
15c3308b 1417 //@todo - the reasoning for this being here seems a little outdated
8d65cef7
AC
1418 foreach ($createdMemberships as $createdMembership) {
1419 CRM_Core_BAO_CustomValueTable::postProcess(
1420 $form->_params,
1421 CRM_Core_DAO::$_nullArray,
1422 'civicrm_membership',
1423 $createdMembership->id,
1424 'Membership'
1425 );
1426 $form->_params['createdMembershipIDs'][] = $createdMembership->id;
1427 }
e0556ebe 1428 if (count($createdMemberships) == 1) {
1f4d7bcb 1429 //presumably this is only relevant for exactly 1 membership
12dac866 1430 $form->_params['membershipID'] = $createdMembership->id;
1f4d7bcb 1431 }
6a488035 1432
12dac866 1433 //CRM-15232: Check if membership is created and on the basis of it use
1434 //membership reciept template to send payment reciept
1435 if (count($createdMemberships)) {
1436 $form->_values['isMembership'] = TRUE;
1437 }
6a488035
TO
1438 if ($form->_contributeMode == 'notify') {
1439 if ($form->_values['is_monetary'] && $form->_amount > 0.0 && !$form->_params['is_pay_later']) {
dd9e2b05 1440 // call postProcess hook before leaving
6a488035
TO
1441 $form->postProcessHook();
1442 // this does not return
1443 $payment = CRM_Core_Payment::singleton($form->_mode, $form->_paymentProcessor, $form);
1444 $payment->doTransferCheckout($form->_params, 'contribute');
1445 }
1446 }
1447
dd9e2b05
EM
1448 if (isset($membershipContributionID)) {
1449 $form->_values['contribution_id'] = $membershipContributionID;
6a488035
TO
1450 }
1451
ef26b43b
EM
1452 // Refer to CRM-16737. Payment processors 'should' return payment_status_id
1453 // to denote the outcome of the transaction.
1454 //
1455 // In 4.7 trxn_id will no longer denote the outcome & all processor transactions must return an array
1456 // containing payment_status_id.
1457 // In 4.6 support (such as there was) for other ways of denoting payment outcome is retained but the use
1458 // of payment_status_id is strongly encouraged.
a7488080 1459 if (!empty($form->_params['is_recur']) && $form->_contributeMode == 'direct') {
68e61ad6
EM
1460 if (!empty($membershipContribution->trxn_id) && !isset($membershipContribution->payment_status_id)
1461 || (!empty($membershipContribution->payment_status_id) && $membershipContribution->payment_status_id == 1)) {
a7a33651 1462 try {
e0556ebe 1463 civicrm_api3('contribution', 'completetransaction', array(
353ffa53
TO
1464 'id' => $membershipContribution->id,
1465 'trxn_id' => $membershipContribution->trxn_id,
1466 ));
a7a33651
EM
1467 }
1468 catch (CiviCRM_API3_Exception $e) {
1469 // if for any reason it is already completed this will fail - e.g extensions hacking around core not completing transactions prior to CRM-15296
1470 // so let's be gentle here
1471 CRM_Core_Error::debug_log_message('contribution ' . $membershipContribution->id . ' not completed with trxn_id ' . $membershipContribution->trxn_id . ' and message ' . $e->getMessage());
1472 }
1473 }
ef26b43b
EM
1474 // Do not send an email if Recurring transaction is done via Direct Mode
1475 // Email will we sent when the IPN is received.
5901ddf9 1476 return;
6a488035
TO
1477 }
1478
1479 //finally send an email receipt
1480 CRM_Contribute_BAO_ContributionPage::sendMail($contactID,
1481 $form->_values,
1482 $isTest, FALSE,
1483 $includeFieldTypes
1484 );
1485 }
1486
cd125a40
FG
1487 /**
1488 * Function for updating a membership record's contribution_recur_id
1489 *
c866eb5f 1490 * @param CRM_Member_DAO_Membership $membership
5901ddf9 1491 * @param \CRM_Contribute_BAO_Contribution|\CRM_Contribute_DAO_Contribution $contribution
cd125a40
FG
1492 *
1493 * @return void
cd125a40 1494 */
c866eb5f 1495 static public function updateRecurMembership(CRM_Member_DAO_Membership $membership, CRM_Contribute_BAO_Contribution $contribution) {
cd125a40
FG
1496
1497 if (empty($contribution->contribution_recur_id)) {
1498 return;
1499 }
1500
1501 $params = array(
1502 1 => array($contribution->contribution_recur_id, 'Integer'),
1503 2 => array($membership->id, 'Integer'),
1504 );
1505
1506 $sql = "UPDATE civicrm_membership SET contribution_recur_id = %1 WHERE id = %2";
1507 CRM_Core_DAO::executeQuery($sql, $params);
1508 }
1509
6a488035 1510 /**
c98997b9
EM
1511 * @deprecated
1512 * A wrapper for renewing memberships from a form - including the form in the membership processing adds complexity
1513 * as the forms are being forced to pretend similarity
1514 * Try to call the renewMembership directly
e0efd2d0 1515 * @todo - this form method needs to have the interaction with the form layer removed from it
1516 * as a BAO function. Note that the api now supports membership renewals & it is not clear this function does anything
1517 * not done by the membership.create api (with a lot less unit tests)
1518 *
6a488035
TO
1519 * This method will renew / create the membership depending on
1520 * whether the given contact has a membership or not. And will add
1521 * the modified dates for membership and in the log table.
1522 *
b2363ea8
TO
1523 * @param int $contactID
1524 * Id of the contact.
1525 * @param int $membershipTypeID
1526 * Id of the new membership type.
1527 * @param bool $is_test
1528 * If this is test contribution or live contribution.
1529 * @param CRM_Core_Form $form
1530 * Form object.
e46e9a0b 1531 * @param null $changeToday
b2363ea8
TO
1532 * @param int $modifiedID
1533 * Individual contact id in case of On Behalf signup (CRM-4027 ).
e46e9a0b 1534 * @param null $customFieldsFormatted
b2363ea8
TO
1535 * @param int $numRenewTerms
1536 * How many membership terms are being added to end date (default is 1).
1537 * @param int $membershipID
1538 * Membership ID, this should always be passed in & optionality should be removed.
fa9d0451
EM
1539 *
1540 * @throws CRM_Core_Exception
6a488035 1541 *
e46e9a0b 1542 */
608e6658 1543 public static function renewMembershipFormWrapper(
6a488035
TO
1544 $contactID,
1545 $membershipTypeID,
1546 $is_test,
1547 &$form,
1548 $changeToday = NULL,
1549 $modifiedID = NULL,
1550 $customFieldsFormatted = NULL,
fa9d0451 1551 $numRenewTerms = 1,
b9b75df1
SB
1552 $membershipID = NULL,
1553 $pending = FALSE
6a488035
TO
1554 ) {
1555 $statusFormat = '%Y-%m-%d';
e0556ebe
TO
1556 $format = '%Y%m%d';
1557 $ids = array();
fa9d0451 1558 //@todo would be better to make $membershipID a compulsory function param & make form layer responsible for extracting it
e0556ebe 1559 if (!$membershipID && isset($form->_membershipId)) {
fa9d0451
EM
1560 $membershipID = $form->_membershipId;
1561 }
6a488035
TO
1562
1563 //get all active statuses of membership.
1564 $allStatus = CRM_Member_PseudoConstant::membershipStatus();
1565
1566 $membershipTypeDetails = CRM_Member_BAO_MembershipType::getMembershipTypeDetails($membershipTypeID);
1567
1568 // check is it pending. - CRM-4555
b9b75df1 1569 list($pending, $contributionRecurID, $changeToday, $membershipSource, $isPayLater, $campaignId) = self::extractFormValues($form, $changeToday, $membershipTypeDetails, $pending);
c98997b9 1570 list($membership, $renewalMode, $dates) = self::renewMembership($contactID, $membershipTypeID, $is_test, $changeToday, $modifiedID, $customFieldsFormatted, $numRenewTerms, $membershipID, $pending, $allStatus, $membershipTypeDetails, $contributionRecurID, $format, $membershipSource, $ids, $statusFormat, $isPayLater, $campaignId);
1f4d7bcb 1571 $form->set('renewal_mode', $renewalMode);
6a488035
TO
1572 if (!empty($dates)) {
1573 $form->assign('mem_start_date',
1574 CRM_Utils_Date::customFormat($dates['start_date'], $format)
1575 );
1576 $form->assign('mem_end_date',
1577 CRM_Utils_Date::customFormat($dates['end_date'], $format)
1578 );
1579 }
6a488035 1580 return $membership;
c98997b9 1581
6a488035
TO
1582 }
1583
1584 /**
fe482240 1585 * Method to fix membership status of stale membership.
6a488035
TO
1586 *
1587 * This method first checks if the membership is stale. If it is,
1588 * then status will be updated based on existing start and end
1589 * dates and log will be added for the status change.
1590 *
b2363ea8
TO
1591 * @param array $currentMembership
1592 * Reference to the array.
688d37c6
CW
1593 * containing all values of
1594 * the current membership
b2363ea8
TO
1595 * @param array $changeToday
1596 * Array of month, day, year.
688d37c6
CW
1597 * values in case today needs
1598 * to be customised, null otherwise
6a488035
TO
1599 *
1600 * @return void
6a488035 1601 */
00be9182 1602 public static function fixMembershipStatusBeforeRenew(&$currentMembership, $changeToday) {
6a488035
TO
1603 $today = NULL;
1604 if ($changeToday) {
1605 $today = CRM_Utils_Date::processDate($changeToday, NULL, FALSE, 'Y-m-d');
1606 }
1607
1608 $status = CRM_Member_BAO_MembershipStatus::getMembershipStatusByDate(
1609 CRM_Utils_Array::value('start_date', $currentMembership),
1610 CRM_Utils_Array::value('end_date', $currentMembership),
1611 CRM_Utils_Array::value('join_date', $currentMembership),
1612 $today,
5f11bbcc
EM
1613 TRUE,
1614 $currentMembership['membership_type_id'],
1615 $currentMembership
6a488035
TO
1616 );
1617
1618 if (empty($status) ||
1619 empty($status['id'])
1620 ) {
1621 CRM_Core_Error::fatal(ts('Oops, it looks like there is no valid membership status corresponding to the membership start and end dates for this membership. Contact the site administrator for assistance.'));
1622 }
1623
1624 $currentMembership['today_date'] = $today;
1625
1626 if ($status['id'] !== $currentMembership['status_id']) {
1627 $memberDAO = new CRM_Member_DAO_Membership();
1628 $memberDAO->id = $currentMembership['id'];
1629 $memberDAO->find(TRUE);
1630
1631 $memberDAO->status_id = $status['id'];
1632 $memberDAO->join_date = CRM_Utils_Date::isoToMysql($memberDAO->join_date);
1633 $memberDAO->start_date = CRM_Utils_Date::isoToMysql($memberDAO->start_date);
1634 $memberDAO->end_date = CRM_Utils_Date::isoToMysql($memberDAO->end_date);
1635 $memberDAO->save();
1636 CRM_Core_DAO::storeValues($memberDAO, $currentMembership);
1637 $memberDAO->free();
1638
1639 $currentMembership['is_current_member'] = CRM_Core_DAO::getFieldValue(
1640 'CRM_Member_DAO_MembershipStatus',
1641 $currentMembership['status_id'],
1642 'is_current_member'
1643 );
1644 $format = '%Y%m%d';
1645
1646 $logParams = array(
1647 'membership_id' => $currentMembership['id'],
1648 'status_id' => $status['id'],
1649 'start_date' => CRM_Utils_Date::customFormat(
1650 $currentMembership['start_date'],
1651 $format
1652 ),
1653 'end_date' => CRM_Utils_Date::customFormat(
1654 $currentMembership['end_date'],
1655 $format
1656 ),
1657 'modified_date' => CRM_Utils_Date::customFormat(
1658 $currentMembership['today_date'],
1659 $format
1660 ),
1661 'membership_type_id' => $currentMembership['membership_type_id'],
520c1a14 1662 'max_related' => CRM_Utils_Array::value('max_related', $currentMembership, 0),
6a488035
TO
1663 );
1664
1665 $session = CRM_Core_Session::singleton();
1666 // If we have an authenticated session, set modified_id to that user's contact_id, else set to membership.contact_id
1667 if ($session->get('userID')) {
1668 $logParams['modified_id'] = $session->get('userID');
1669 }
1670 else {
1671 $logParams['modified_id'] = $currentMembership['contact_id'];
1672 }
1673 CRM_Member_BAO_MembershipLog::add($logParams, CRM_Core_DAO::$_nullArray);
1674 }
1675 }
1676
1677 /**
fe482240 1678 * Get the contribution page id from the membership record.
6a488035 1679 *
688d37c6 1680 * @param int $membershipID
6a488035 1681 *
a6c01b45 1682 * @return int
688d37c6 1683 * contribution page id
6a488035 1684 */
00be9182 1685 public static function getContributionPageId($membershipID) {
6a488035
TO
1686 $query = "
1687SELECT c.contribution_page_id as pageID
1688 FROM civicrm_membership_payment mp, civicrm_contribution c
1689 WHERE mp.contribution_id = c.id
1690 AND c.contribution_page_id IS NOT NULL
1691 AND mp.membership_id = " . CRM_Utils_Type::escape($membershipID, 'Integer')
e0556ebe 1692 . " ORDER BY mp.id DESC";
6a488035
TO
1693
1694 return CRM_Core_DAO::singleValueQuery($query,
1695 CRM_Core_DAO::$_nullArray
1696 );
1697 }
1698
6a488035 1699 /**
fe482240 1700 * Updated related memberships.
6a488035 1701 *
b2363ea8
TO
1702 * @param int $ownerMembershipId
1703 * Owner Membership Id.
1704 * @param array $params
1705 * Formatted array of key => value.
6a488035 1706 */
00be9182 1707 public static function updateRelatedMemberships($ownerMembershipId, $params) {
6a488035
TO
1708 $membership = new CRM_Member_DAO_Membership();
1709 $membership->owner_membership_id = $ownerMembershipId;
1710 $membership->find();
1711
1712 while ($membership->fetch()) {
1713 $relatedMembership = new CRM_Member_DAO_Membership();
1714 $relatedMembership->id = $membership->id;
1715 $relatedMembership->copyValues($params);
1716 $relatedMembership->save();
1717 $relatedMembership->free();
1718 }
1719
1720 $membership->free();
1721 }
1722
1723 /**
fe482240 1724 * Get list of membership fields for profile.
6a488035
TO
1725 * For now we only allow custom membership fields to be in
1726 * profile
1727 *
e46e9a0b 1728 * @param null $mode
688d37c6 1729 * FIXME: This param is ignored
e46e9a0b 1730 *
a6c01b45
CW
1731 * @return array
1732 * the list of membership fields
6a488035 1733 */
00be9182 1734 public static function getMembershipFields($mode = NULL) {
6a488035
TO
1735 $fields = CRM_Member_DAO_Membership::export();
1736
6a488035
TO
1737 unset($fields['membership_contact_id']);
1738 $fields = array_merge($fields, CRM_Core_BAO_CustomField::getFieldsForImport('Membership'));
1739
1740 $membershipType = CRM_Member_DAO_MembershipType::export();
1741
1742 $membershipStatus = CRM_Member_DAO_MembershipStatus::export();
1743
1744 $fields = array_merge($fields, $membershipType, $membershipStatus);
1745
1746 return $fields;
1747 }
1748
1749 /**
fe482240 1750 * Get the sort name of a contact for a particular membership.
6a488035 1751 *
b2363ea8
TO
1752 * @param int $id
1753 * Id of the membership.
6a488035 1754 *
72b3a70c
CW
1755 * @return null|string
1756 * sort name of the contact if found
6a488035 1757 */
00be9182 1758 public static function sortName($id) {
6a488035
TO
1759 $id = CRM_Utils_Type::escape($id, 'Integer');
1760
1761 $query = "
1762SELECT civicrm_contact.sort_name
1763FROM civicrm_membership, civicrm_contact
1764WHERE civicrm_membership.contact_id = civicrm_contact.id
1765 AND civicrm_membership.id = {$id}
1766";
1767 return CRM_Core_DAO::singleValueQuery($query, CRM_Core_DAO::$_nullArray);
1768 }
1769
1770 /**
fe482240 1771 * Create memberships for related contacts.
6a488035
TO
1772 * takes into account the maximum related memberships
1773 *
b2363ea8
TO
1774 * @param array $params
1775 * Array of key - value pairs.
1776 * @param CRM_Core_DAO $dao
1777 * Membership object.
6a488035 1778 *
72b3a70c
CW
1779 * @return null|array
1780 * array of memberships if created
6a488035 1781 */
00be9182 1782 public static function createRelatedMemberships(&$params, &$dao, $reset = FALSE) {
6a488035 1783 static $relatedContactIds = array();
6d8a45ed
EM
1784 if ($reset) {
1785 // not sure why a static var is in use here - we need a way to reset it from the test suite
1786 $relatedContactIds = array();
608e6658 1787 return FALSE;
6d8a45ed 1788 }
6a488035
TO
1789
1790 $membership = new CRM_Member_DAO_Membership();
1791 $membership->id = $dao->id;
1792
1793 // required since create method doesn't return all the
1794 // parameters in the returned membership object
1795 if (!$membership->find(TRUE)) {
1796 return;
1797 }
1798 $deceasedStatusId = array_search('Deceased', CRM_Member_PseudoConstant::membershipStatus());
1799 // FIXME : While updating/ renewing the
1800 // membership, if the relationship is PAST then
1801 // the membership of the related contact must be
1802 // expired.
1803 // For that, getting Membership Status for which
1804 // is_current_member is 0. It works for the
1805 // generated data as there is only one membership
1806 // status having is_current_member = 0.
1807 // But this wont work exactly if there will be
1808 // more than one status having is_current_member = 0.
1809 $membershipStatus = new CRM_Member_DAO_MembershipStatus();
1810 $membershipStatus->is_current_member = 0;
1811 if ($membershipStatus->find(TRUE)) {
1812 $expiredStatusId = $membershipStatus->id;
e0556ebe
TO
1813 }
1814 else {
6a488035
TO
1815 $expiredStatusId = array_search('Expired', CRM_Member_PseudoConstant::membershipStatus());
1816 }
1817
1818 $allRelatedContacts = array();
1819 $relatedContacts = array();
1820 if (!is_a($membership, 'CRM_Core_Error')) {
1821 $allRelatedContacts = CRM_Member_BAO_Membership::checkMembershipRelationship($membership->id,
1822 $membership->contact_id,
1823 CRM_Utils_Array::value('action', $params)
1824 );
1825 }
1826
1827 // check for loops. CRM-4213
1828 // remove repeated related contacts, which already inherited membership.
1829 $relatedContactIds[$membership->contact_id] = TRUE;
1830 foreach ($allRelatedContacts as $cid => $status) {
a7488080 1831 if (empty($relatedContactIds[$cid])) {
6a488035
TO
1832 $relatedContactIds[$cid] = TRUE;
1833
1834 //don't create membership again for owner contact.
1835 $nestedRelationship = FALSE;
1836 if ($membership->owner_membership_id) {
1837 $nestedRelMembership = new CRM_Member_DAO_Membership();
1838 $nestedRelMembership->id = $membership->owner_membership_id;
1839 $nestedRelMembership->contact_id = $cid;
1840 $nestedRelationship = $nestedRelMembership->find(TRUE);
1841 $nestedRelMembership->free();
1842 }
1843 if (!$nestedRelationship) {
1844 $relatedContacts[$cid] = $status;
1845 }
1846 }
1847 }
1848
1849 //lets cleanup related membership if any.
1850 if (empty($relatedContacts)) {
3506b6cd 1851 self::deleteRelatedMemberships($membership->id);
6a488035
TO
1852 }
1853 else {
1854 // Edit the params array
1855 unset($params['id']);
1856 // Reminder should be sent only to the direct membership
1857 unset($params['reminder_date']);
1858 // unset the custom value ids
1859 if (is_array(CRM_Utils_Array::value('custom', $params))) {
1860 foreach ($params['custom'] as $k => $v) {
1861 unset($params['custom'][$k]['id']);
1862 }
1863 }
1864 if (!isset($params['membership_type_id'])) {
1865 $params['membership_type_id'] = $membership->membership_type_id;
1866 }
1867
1868 // max_related should be set in the parent membership
1869 unset($params['max_related']);
1870 // Number of inherited memberships available - NULL is interpreted as unlimited, '0' as none
1871 $available = ($membership->max_related == NULL ? PHP_INT_MAX : $membership->max_related);
1872 $queue = array(); // will be used to queue potential memberships to be created
1873
1874 foreach ($relatedContacts as $contactId => $relationshipStatus) {
1875 //use existing membership record.
1876 $relMembership = new CRM_Member_DAO_Membership();
1877 $relMembership->contact_id = $contactId;
1878 $relMembership->owner_membership_id = $membership->id;
1879 $relMemIds = array();
1880 if ($relMembership->find(TRUE)) {
1881 $params['id'] = $relMemIds['membership'] = $relMembership->id;
1882 }
1883 $params['contact_id'] = $contactId;
1884 $params['owner_membership_id'] = $membership->id;
1885
1886 // set status_id as it might have been changed for
1887 // past relationship
1888 $params['status_id'] = $membership->status_id;
1889
1890 if ($deceasedStatusId &&
1891 CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $contactId, 'is_deceased')
1892 ) {
1893 $params['status_id'] = $deceasedStatusId;
1894 }
1895 elseif ((CRM_Utils_Array::value('action', $params) & CRM_Core_Action::UPDATE) &&
1896 ($relationshipStatus == CRM_Contact_BAO_Relationship::PAST)
1897 ) {
e0556ebe
TO
1898 $params['status_id'] = $expiredStatusId;
1899 }
6a488035
TO
1900
1901 //don't calculate status again in create( );
1902 $params['skipStatusCal'] = TRUE;
1903
1904 //do create activity if we changed status.
1905 if ($params['status_id'] != $relMembership->status_id) {
1906 $params['createActivity'] = TRUE;
1907 }
1908
1909 // we should not created contribution record for related contacts, CRM-3371
1910 unset($params['contribution_status_id']);
1911
1912 if (($params['status_id'] == $deceasedStatusId) || ($params['status_id'] == $expiredStatusId)) {
1913 // related membership is not active so does not count towards maximum
1914 CRM_Member_BAO_Membership::create($params, $relMemIds);
8efea814
EM
1915 }
1916 else {
6a488035
TO
1917 // related membership already exists, so this is just an update
1918 if (isset($params['id'])) {
1919 if ($available > 0) {
e0556ebe
TO
1920 CRM_Member_BAO_Membership::create($params, $relMemIds);
1921 $available--;
1922 }
608e6658 1923 else {
1924 // we have run out of inherited memberships, so delete extras
f5e53870 1925 self::deleteMembership($params['id']);
6a488035 1926 }
e0556ebe
TO
1927 // we need to first check if there will remain inherited memberships, so queue it up
1928 }
1929 else {
6a488035
TO
1930 $queue[] = $params;
1931 }
1932 }
1933 }
1934 // now go over the queue and create any available related memberships
1935 reset($queue);
1936 while (($available > 0) && ($params = each($queue))) {
1937 CRM_Member_BAO_Membership::create($params['value'], $relMemIds);
e0556ebe 1938 $available--;
6a488035
TO
1939 }
1940 }
1941 }
1942
1943 /**
fe482240 1944 * Delete the record that are associated with this Membership Payment.
6a488035 1945 *
b2363ea8 1946 * @param int $membershipId
6a488035 1947 *
608e6658 1948 * @return object
1949 * $membershipPayment deleted membership payment object
6a488035 1950 */
00be9182 1951 public static function deleteMembershipPayment($membershipId) {
6a488035 1952
608e6658 1953 $membershipPayment = new CRM_Member_DAO_MembershipPayment();
1954 $membershipPayment->membership_id = $membershipId;
1955 $membershipPayment->find();
6a488035 1956
608e6658 1957 while ($membershipPayment->fetch()) {
1958 CRM_Contribute_BAO_Contribution::deleteContribution($membershipPayment->contribution_id);
1959 CRM_Utils_Hook::pre('delete', 'MembershipPayment', $membershipPayment->id, $membershipPayment);
061a8a1e 1960 $membershipPayment->delete();
608e6658 1961 CRM_Utils_Hook::post('delete', 'MembershipPayment', $membershipPayment->id, $membershipPayment);
6a488035 1962 }
608e6658 1963 return $membershipPayment;
6a488035
TO
1964 }
1965
bb3a214a 1966 /**
c490a46a 1967 * @param CRM_Core_Form $form
100fef9d 1968 * @param int $membershipTypeID
bb3a214a
EM
1969 *
1970 * @return array
1971 */
00be9182 1972 public static function &buildMembershipTypeValues(&$form, $membershipTypeID = NULL) {
e0556ebe 1973 $whereClause = " WHERE domain_id = " . CRM_Core_Config::domainID();
6a488035
TO
1974
1975 if (is_array($membershipTypeID)) {
1976 $allIDs = implode(',', $membershipTypeID);
1977 $whereClause .= " AND id IN ( $allIDs )";
1978 }
1979 elseif (is_numeric($membershipTypeID) &&
1980 $membershipTypeID > 0
1981 ) {
1982 $whereClause .= " AND id = $membershipTypeID";
1983 }
1984
1985 $query = "
1986SELECT *
1987FROM civicrm_membership_type
1988 $whereClause;
1989";
1990 $dao = CRM_Core_DAO::executeQuery($query);
1991
1992 $membershipTypeValues = array();
1993 $membershipTypeFields = array(
e0556ebe
TO
1994 'id',
1995 'minimum_fee',
1996 'name',
1997 'is_active',
1998 'description',
1999 'financial_type_id',
2000 'auto_renew',
2001 'member_of_contact_id',
2002 'relationship_type_id',
2003 'relationship_direction',
2004 'max_related',
6a488035
TO
2005 );
2006
2007 while ($dao->fetch()) {
2008 $membershipTypeValues[$dao->id] = array();
2009 foreach ($membershipTypeFields as $mtField) {
2010 $membershipTypeValues[$dao->id][$mtField] = $dao->$mtField;
2011 }
2012 }
2013 $dao->free();
2014
2015 CRM_Utils_Hook::membershipTypeValues($form, $membershipTypeValues);
2016
2017 if (is_numeric($membershipTypeID) &&
2018 $membershipTypeID > 0
2019 ) {
2020 return $membershipTypeValues[$membershipTypeID];
2021 }
2022 else {
2023 return $membershipTypeValues;
2024 }
2025 }
2026
2027 /**
fe482240 2028 * Get membership record count for a Contact.
6a488035 2029 *
c490a46a 2030 * @param int $contactID
b2363ea8 2031 * @param bool $activeOnly
6a488035 2032 *
c490a46a 2033 * @return null|string
6a488035 2034 */
00be9182 2035 public static function getContactMembershipCount($contactID, $activeOnly = FALSE) {
6a488035 2036 $select = "SELECT count(*) FROM civicrm_membership ";
e0556ebe 2037 $where = "WHERE civicrm_membership.contact_id = {$contactID} AND civicrm_membership.is_test = 0 ";
6a488035
TO
2038
2039 // CRM-6627, all status below 3 (active, pending, grace) are considered active
2040 if ($activeOnly) {
2041 $select .= " INNER JOIN civicrm_membership_status ON civicrm_membership.status_id = civicrm_membership_status.id ";
e0556ebe 2042 $where .= " and civicrm_membership_status.is_current_member = 1";
6a488035
TO
2043 }
2044
2045 $query = $select . $where;
2046 return CRM_Core_DAO::singleValueQuery($query);
2047 }
2048
2049 /**
100fef9d 2050 * Check whether payment processor supports
6a488035
TO
2051 * cancellation of membership subscription
2052 *
b2363ea8
TO
2053 * @param int $mid
2054 * Membership id.
6a488035 2055 *
e46e9a0b
EM
2056 * @param bool $isNotCancelled
2057 *
608e6658 2058 * @return bool
6a488035 2059 */
00be9182 2060 public static function isCancelSubscriptionSupported($mid, $isNotCancelled = TRUE) {
6a488035
TO
2061 $cacheKeyString = "$mid";
2062 $cacheKeyString .= $isNotCancelled ? '_1' : '_0';
2063
2064 static $supportsCancel = array();
2065
2066 if (!array_key_exists($cacheKeyString, $supportsCancel)) {
2067 $supportsCancel[$cacheKeyString] = FALSE;
2068 $isCancelled = FALSE;
2069
2070 if ($isNotCancelled) {
2071 $isCancelled = self::isSubscriptionCancelled($mid);
2072 }
2073
2074 $paymentObject = CRM_Financial_BAO_PaymentProcessor::getProcessorForEntity($mid, 'membership', 'obj');
2075 if (!empty($paymentObject)) {
2076 $supportsCancel[$cacheKeyString] = $paymentObject->isSupported('cancelSubscription') && !$isCancelled;
2077 }
2078 }
2079 return $supportsCancel[$cacheKeyString];
2080 }
2081
2082 /**
fe482240 2083 * Check whether subscription is already cancelled.
6a488035 2084 *
b2363ea8
TO
2085 * @param int $mid
2086 * Membership id.
6a488035 2087 *
a6c01b45
CW
2088 * @return string
2089 * contribution status
6a488035 2090 */
00be9182 2091 public static function isSubscriptionCancelled($mid) {
6a488035
TO
2092 $sql = "
2093 SELECT cr.contribution_status_id
2094 FROM civicrm_contribution_recur cr
2095LEFT JOIN civicrm_membership mem ON ( cr.id = mem.contribution_recur_id )
2096 WHERE mem.id = %1 LIMIT 1";
e0556ebe 2097 $params = array(1 => array($mid, 'Integer'));
6a488035 2098 $statusId = CRM_Core_DAO::singleValueQuery($sql, $params);
e0556ebe 2099 $status = CRM_Contribute_PseudoConstant::contributionStatus($statusId, 'name');
6a488035
TO
2100 if ($status == 'Cancelled') {
2101 return TRUE;
2102 }
2103 return FALSE;
2104 }
2105
2106 /**
100fef9d 2107 * Get membership joins for a specified membership
6a488035 2108 * type. Specifically, retrieves a count of still current memberships whose
4e636a74 2109 * join_date and start_date are within a specified date range. Dates match
8ef12e64 2110 * the pattern "yyyy-mm-dd".
6a488035 2111 *
b2363ea8
TO
2112 * @param int $membershipTypeId
2113 * Membership type id.
2114 * @param int $startDate
2115 * Date on which to start counting.
2116 * @param int $endDate
2117 * Date on which to end counting.
e46e9a0b 2118 * @param bool|int $isTest if true, membership is for a test site
6a488035 2119 *
72b3a70c
CW
2120 * @return int
2121 * the number of members of type $membershipTypeId
2122 * whose join_date is between $startDate and $endDate and
2123 * whose start_date is between $startDate and $endDate
6a488035 2124 */
00be9182 2125 public static function getMembershipJoins($membershipTypeId, $startDate, $endDate, $isTest = 0) {
6a488035
TO
2126 $testClause = 'membership.is_test = 1';
2127 if (!$isTest) {
2128 $testClause = '( membership.is_test IS NULL OR membership.is_test = 0 )';
2129 }
4e636a74
AH
2130 if (!self::$_signupActType) {
2131 self::_getActTypes();
2132 }
8ef12e64 2133
4e636a74
AH
2134 if (!self::$_signupActType) {
2135 return 0;
2136 }
6a488035
TO
2137
2138 $query = "
8ef12e64 2139 SELECT COUNT(DISTINCT membership.id) as member_count
6a488035 2140 FROM civicrm_membership membership
8ef12e64 2141INNER JOIN civicrm_activity activity ON (activity.source_record_id = membership.id AND activity.activity_type_id = %1)
6a488035 2142INNER JOIN civicrm_membership_status status ON ( membership.status_id = status.id AND status.is_current_member = 1 )
4e636a74
AH
2143INNER JOIN civicrm_contact contact ON ( contact.id = membership.contact_id AND contact.is_deleted = 0 )
2144 WHERE membership.membership_type_id = %2
2145 AND activity.activity_date_time >= '$startDate' AND activity.activity_date_time <= '$endDate 23:59:59'
6a488035
TO
2146 AND {$testClause}";
2147
4e636a74
AH
2148 $params = array(
2149 1 => array(self::$_signupActType, 'Integer'),
2150 2 => array($membershipTypeId, 'Integer'),
2151 );
2152
6a488035
TO
2153 $memberCount = CRM_Core_DAO::singleValueQuery($query, $params);
2154
e0556ebe 2155 return (int) $memberCount;
6a488035
TO
2156 }
2157
2158 /**
100fef9d 2159 * Get membership renewals for a specified membership
4e636a74 2160 * type. Specifically, retrieves a count of still current memberships
8ef12e64 2161 * whose join_date is before and start_date is within a specified date
4e636a74 2162 * range. Dates match the pattern "yyyy-mm-dd".
6a488035 2163 *
b2363ea8
TO
2164 * @param int $membershipTypeId
2165 * Membership type id.
2166 * @param int $startDate
2167 * Date on which to start counting.
2168 * @param int $endDate
2169 * Date on which to end counting.
e46e9a0b 2170 * @param bool|int $isTest if true, membership is for a test site
6a488035 2171 *
df8d3074 2172 * @return int
a6c01b45 2173 * returns the number of members of type $membershipTypeId
688d37c6
CW
2174 * whose join_date is before $startDate and
2175 * whose start_date is between $startDate and $endDate
6a488035 2176 */
00be9182 2177 public static function getMembershipRenewals($membershipTypeId, $startDate, $endDate, $isTest = 0) {
6a488035
TO
2178 $testClause = 'membership.is_test = 1';
2179 if (!$isTest) {
2180 $testClause = '( membership.is_test IS NULL OR membership.is_test = 0 )';
2181 }
4e636a74
AH
2182 if (!self::$_renewalActType) {
2183 self::_getActTypes();
2184 }
8ef12e64 2185
4e636a74
AH
2186 if (!self::$_renewalActType) {
2187 return 0;
2188 }
6a488035
TO
2189
2190 $query = "
8ef12e64 2191 SELECT COUNT(DISTINCT membership.id) as member_count
6a488035 2192 FROM civicrm_membership membership
4e636a74 2193INNER JOIN civicrm_activity activity ON (activity.source_record_id = membership.id AND activity.activity_type_id = %1)
6a488035
TO
2194INNER JOIN civicrm_membership_status status ON ( membership.status_id = status.id AND status.is_current_member = 1 )
2195INNER JOIN civicrm_contact contact ON ( contact.id = membership.contact_id AND contact.is_deleted = 0 )
4e636a74 2196 WHERE membership.membership_type_id = %2
8ef12e64 2197 AND activity.activity_date_time >= '$startDate' AND activity.activity_date_time <= '$endDate 23:59:59'
6a488035
TO
2198 AND {$testClause}";
2199
4e636a74
AH
2200 $params = array(
2201 1 => array(self::$_renewalActType, 'Integer'),
2202 2 => array($membershipTypeId, 'Integer'),
2203 );
6a488035
TO
2204 $memberCount = CRM_Core_DAO::singleValueQuery($query, $params);
2205
e0556ebe 2206 return (int) $memberCount;
6a488035
TO
2207 }
2208
7273a245 2209 /**
fe482240 2210 * Where a second separate financial transaction is supported we will process it here.
236a2274 2211 *
100fef9d 2212 * @param int $contactID
5624f515 2213 * @param CRM_Contribute_Form_Contribution_Confirm $form
100fef9d 2214 * @param array $tempParams
7273a245 2215 * @param $isTest
100fef9d 2216 * @param array $lineItems
d25e4224 2217 * @param $minimumFee
100fef9d 2218 * @param int $financialTypeID
d25e4224 2219 *
5624f515 2220 * @throws CRM_Core_Exception
d25e4224 2221 * @throws Exception
236a2274 2222 * @return CRM_Contribute_BAO_Contribution
7273a245 2223 */
5624f515 2224 public static function processSecondaryFinancialTransaction($contactID, &$form, $tempParams, $isTest, $lineItems, $minimumFee, $financialTypeID) {
2883c2b2
EM
2225 $financialType = new CRM_Financial_DAO_FinancialType();
2226 $financialType->id = $financialTypeID;
2227 if (!$financialType->find(TRUE)) {
7273a245
EM
2228 CRM_Core_Error::fatal(ts("Could not find a system table"));
2229 }
2230 $tempParams['amount'] = $minimumFee;
2231 $tempParams['invoiceID'] = md5(uniqid(rand(), TRUE));
2232
2233 $result = NULL;
2234 if ($form->_values['is_monetary'] && !$form->_params['is_pay_later'] && $minimumFee > 0.0) {
2235 $payment = CRM_Core_Payment::singleton($form->_mode, $form->_paymentProcessor, $form);
2236
2237 if ($form->_contributeMode == 'express') {
2238 $result = $payment->doExpressCheckout($tempParams);
a19183c1
EM
2239 if (is_a($result, 'CRM_Core_Error')) {
2240 throw new CRM_Core_Exception(CRM_Core_Error::getMessages($result));
2241 }
7273a245
EM
2242 }
2243 else {
a19183c1 2244 $result = $payment->doPayment($tempParams, 'contribute');
7273a245
EM
2245 }
2246 }
2247
a19183c1
EM
2248 //assign receive date when separate membership payment
2249 //and contribution amount not selected.
2250 if ($form->_amount == 0) {
2251 $now = date('YmdHis');
2252 $form->_params['receive_date'] = $now;
2253 $receiveDate = CRM_Utils_Date::mysqlToIso($now);
2254 $form->set('params', $form->_params);
2255 $form->assign('receive_date', $receiveDate);
7273a245 2256 }
7273a245 2257
a19183c1
EM
2258 $form->set('membership_trx_id', $result['trxn_id']);
2259 $form->set('membership_amount', $minimumFee);
2260
2261 $form->assign('membership_trx_id', $result['trxn_id']);
2262 $form->assign('membership_amount', $minimumFee);
2263
2264 // we don't need to create the user twice, so lets disable cms_create_account
2265 // irrespective of the value, CRM-2888
2266 $tempParams['cms_create_account'] = 0;
2267
9cc96227 2268 //CRM-16165, scenarios are
2269 // 1) If contribution is_pay_later and if contribution amount is > 0.0 we set pending = TRUE, vice-versa FALSE
2270 // 2) If not pay later but auto-renewal membership is chosen then pending = TRUE as it later triggers
2271 // pending recurring contribution, vice-versa FALSE
2272 $pending = $form->_params['is_pay_later'] ? (($minimumFee > 0.0) ? TRUE : FALSE) : (!empty($form->_params['auto_renew']) ? TRUE : FALSE);
a19183c1
EM
2273
2274 //set this variable as we are not creating pledge for
2275 //separate membership payment contribution.
2276 //so for differentiating membership contribution from
2277 //main contribution.
2278 $form->_params['separate_membership_payment'] = 1;
2279 $membershipContribution = CRM_Contribute_Form_Contribution_Confirm::processContribution($form,
2280 $tempParams,
2281 $result,
2282 $contactID,
2883c2b2 2283 $financialType,
a19183c1
EM
2284 $pending,
2285 TRUE,
2286 $isTest,
2287 $lineItems
2288 );
2289 return $membershipContribution;
7273a245
EM
2290 }
2291
0547ad7d
EM
2292 /**
2293 * Create linkages between membership & contribution - note this is the wrong place for this code but this is a
2294 * refactoring step. This should be BAO functionality
2295 * @param $membership
2296 * @param $membershipContribution
2297 */
2298 public static function linkMembershipPayment($membership, $membershipContribution) {
e0556ebe 2299 CRM_Member_BAO_MembershipPayment::create(array(
353ffa53
TO
2300 'membership_id' => $membership->id,
2301 'contribution_id' => $membershipContribution->id,
2302 ));
0547ad7d
EM
2303 }
2304
8dde2f13 2305 /**
100fef9d
CW
2306 * @param array $membershipParams
2307 * @param int $contactID
8dde2f13 2308 * @param $customFieldsFormatted
100fef9d 2309 * @param int $membershipID
8dde2f13
EM
2310 * @param $memType
2311 * @param $isTest
2312 * @param $numTerms
2313 * @param $membershipContribution
c490a46a 2314 * @param CRM_Core_Form $form
8dde2f13
EM
2315 *
2316 * @return array
2317 */
e0556ebe 2318 public static function createOrRenewMembership($membershipParams, $contactID, $customFieldsFormatted, $membershipID, $memType, $isTest, $numTerms, $membershipContribution, &$form) {
b9b75df1 2319 if (!empty($membershipContribution)) {
9e98fa85 2320 // CRM-16737 contribution_status_id is the deprecated return parameter from the payment processor. Use
2321 // payment_status_id.
b9b75df1 2322 $pending = ($membershipContribution->contribution_status_id == 2) ? TRUE : FALSE;
9e98fa85 2323 if (isset($membershipContribution->payment_status_id)) {
2324 $pending = ($membershipContribution->payment_status_id == 2) ? TRUE : $pending;
2325 }
b9b75df1 2326 }
c98997b9 2327 $membership = self::renewMembershipFormWrapper($contactID, $memType,
8dde2f13
EM
2328 $isTest, $form, NULL,
2329 CRM_Utils_Array::value('cms_contactID', $membershipParams),
2330 $customFieldsFormatted, $numTerms,
b9b75df1 2331 $membershipID, $pending
8dde2f13
EM
2332 );
2333
8dde2f13 2334 if (!empty($membershipContribution)) {
423c9872
C
2335 // update recurring id for membership record
2336 self::updateRecurMembership($membership, $membershipContribution);
2337
8dde2f13
EM
2338 self::linkMembershipPayment($membership, $membershipContribution);
2339 }
2340 return $membership;
2341 }
2342
15c3308b 2343 /**
fe482240 2344 * Turn array of errors into message string.
15c3308b
EM
2345 *
2346 * @param array $errors
2347 *
15c3308b
EM
2348 * @return string
2349 */
e0556ebe
TO
2350 public static function compileErrorMessage($errors) {
2351 foreach ($errors as $error) {
15c3308b
EM
2352 if (is_string($error)) {
2353 $message[] = $error;
2354 }
2355 }
2356 return ts('Payment Processor Error message') . ': ' . implode('<br/>', $message);
2357 }
2358
c98997b9 2359 /**
fe482240 2360 * Extract relevant values from the form so we can separate form logic from BAO logcis.
688d37c6 2361 *
c490a46a 2362 * @param CRM_Core_Form $form
c98997b9
EM
2363 * @param $changeToday
2364 * @param $membershipTypeDetails
2365 *
2366 * @return array
2367 */
b9b75df1 2368 public static function extractFormValues($form, $changeToday, $membershipTypeDetails, $pending = FALSE) {
c98997b9
EM
2369 //@todo this is a BAO function & should not inspect the form - the form should do this
2370 // & pass required params to the BAO
2371 if (CRM_Utils_Array::value('minimum_fee', $membershipTypeDetails) > 0.0) {
9cc96227 2372 if (((isset($form->_contributeMode) && $form->_contributeMode == 'notify') || !empty($form->_params['is_pay_later'])
c98997b9 2373 ) &&
9cc96227 2374 (($form->_values['is_monetary'] && $form->_amount > 0.0) ||
c98997b9
EM
2375 CRM_Utils_Array::value('record_contribution', $form->_params)
2376 )
2377 ) {
2378 $pending = TRUE;
2379 }
2380 }
2381 $contributionRecurID = isset($form->_params['contributionRecurID']) ? $form->_params['contributionRecurID'] : NULL;
2382
2383 //we renew expired membership, CRM-6277
2384 if (!$changeToday) {
2385 if ($form->get('renewalDate')) {
2386 $changeToday = $form->get('renewalDate');
2387 }
2388 elseif (get_class($form) == 'CRM_Contribute_Form_Contribution_Confirm') {
2389 $changeToday = date('YmdHis');
2390 }
2391 }
2392
c352e9fc 2393 $membershipSource = NULL;
c98997b9
EM
2394 if (!empty($form->_params['membership_source'])) {
2395 $membershipSource = $form->_params['membership_source'];
2396 }
2397 elseif (isset($form->_values['title']) && !empty($form->_values['title'])) {
2398 $membershipSource = ts('Online Contribution:') . ' ' . $form->_values['title'];
2399 }
c352e9fc 2400 $isPayLater = NULL;
e0556ebe 2401 if (isset($form->_params)) {
c352e9fc
C
2402 $isPayLater = CRM_Utils_Array::value('is_pay_later', $form->_params);
2403 }
c98997b9
EM
2404 $campaignId = NULL;
2405 if (isset($form->_values) && is_array($form->_values) && !empty($form->_values)) {
2406 $campaignId = CRM_Utils_Array::value('campaign_id', $form->_params);
2407 if (!array_key_exists('campaign_id', $form->_params)) {
2408 $campaignId = CRM_Utils_Array::value('campaign_id', $form->_values);
2409 }
2410 }
2411 return array($pending, $contributionRecurID, $changeToday, $membershipSource, $isPayLater, $campaignId);
2412 }
2413
2414 /**
b2363ea8 2415 * @param int $contactID
100fef9d 2416 * @param int $membershipTypeID
c98997b9
EM
2417 * @param bool $is_test
2418 * @param $changeToday
b2363ea8 2419 * @param int $modifiedID
c98997b9
EM
2420 * @param $customFieldsFormatted
2421 * @param $numRenewTerms
100fef9d 2422 * @param int $membershipID
c98997b9
EM
2423 * @param $pending
2424 * @param $allStatus
2425 * @param array $membershipTypeDetails
b2363ea8 2426 * @param int $contributionRecurID
c98997b9
EM
2427 * @param $format
2428 * @param $membershipSource
2429 * @param $ids
2430 * @param $statusFormat
2431 * @param $isPayLater
b2363ea8 2432 * @param int $campaignId
c98997b9
EM
2433 *
2434 * @throws CRM_Core_Exception
2435 * @return array
2436 */
2437 public static function renewMembership($contactID, $membershipTypeID, $is_test, $changeToday, $modifiedID, $customFieldsFormatted, $numRenewTerms, $membershipID, $pending, $allStatus, $membershipTypeDetails, $contributionRecurID, $format, $membershipSource, $ids, $statusFormat, $isPayLater, $campaignId) {
2438 $renewalMode = $updateStatusId = FALSE;
2439 $dates = array();
d81c67a2 2440 // CRM-7297 - allow membership type to be be changed during renewal so long as the parent org of new membershipType
c98997b9
EM
2441 // is the same as the parent org of an existing membership of the contact
2442 $currentMembership = CRM_Member_BAO_Membership::getContactMembership($contactID, $membershipTypeID,
2443 $is_test, $membershipID, TRUE
2444 );
2445 if ($currentMembership) {
2446 $activityType = 'Membership Renewal';
2447 $renewalMode = TRUE;
2448
2449 // Do NOT do anything.
2450 //1. membership with status : PENDING/CANCELLED (CRM-2395)
2451 //2. Paylater/IPN renew. CRM-4556.
e0556ebe
TO
2452 if ($pending || in_array($currentMembership['status_id'], array(
2453 array_search('Pending', $allStatus),
7ff60806
PN
2454 // CRM-15475
2455 array_search('Cancelled', CRM_Member_PseudoConstant::membershipStatus(NULL, " name = 'Cancelled' ", 'name', FALSE, TRUE)),
c98997b9
EM
2456 ))
2457 ) {
2458 $membership = new CRM_Member_DAO_Membership();
2459 $membership->id = $currentMembership['id'];
2460 $membership->find(TRUE);
2461
2462 // CRM-8141 create a membership_log entry so that we will know the membership_type_id to change to when payment completed
2463 $format = '%Y%m%d';
2464 // note that we are logging the requested new membership_type_id that may be different than current membership_type_id
2465 // it will be used when payment is received to update the membership_type_id to what was paid for
2466 $logParams = array(
2467 'membership_id' => $membership->id,
2468 'status_id' => $membership->status_id,
2469 'start_date' => CRM_Utils_Date::customFormat(
2470 $membership->start_date,
2471 $format
2472 ),
2473 'end_date' => CRM_Utils_Date::customFormat(
2474 $membership->end_date,
2475 $format
2476 ),
2477 'modified_date' => CRM_Utils_Date::customFormat(
2478 date('Ymd'),
2479 $format
2480 ),
2481 'membership_type_id' => $membershipTypeID,
ef0abb4c 2482 'max_related' => !empty($membershipTypeDetails['max_related']) ? $membershipTypeDetails['max_related'] : NULL,
c98997b9
EM
2483 );
2484 $session = CRM_Core_Session::singleton();
2485 // If we have an authenticated session, set modified_id to that user's contact_id, else set to membership.contact_id
2486 if ($session->get('userID')) {
2487 $logParams['modified_id'] = $session->get('userID');
2488 }
2489 else {
2490 $logParams['modified_id'] = $membership->contact_id;
2491 }
2492 CRM_Member_BAO_MembershipLog::add($logParams, CRM_Core_DAO::$_nullArray);
2493
2494 if (!empty($contributionRecurID)) {
2495 CRM_Core_DAO::setFieldValue('CRM_Member_DAO_Membership', $membership->id,
2496 'contribution_recur_id', $contributionRecurID
2497 );
2498 }
2499
f82cf0ed 2500 return array($membership, $renewalMode, $dates);
c98997b9
EM
2501 }
2502
2503 // Check and fix the membership if it is STALE
2504 self::fixMembershipStatusBeforeRenew($currentMembership, $changeToday);
2505
2506 // Now Renew the membership
2507 if (!$currentMembership['is_current_member']) {
2508 // membership is not CURRENT
2509
2510 // CRM-7297 Membership Upsell - calculate dates based on new membership type
2511 $dates = CRM_Member_BAO_MembershipType::getRenewalDatesForMembershipType($currentMembership['id'],
2512 $changeToday,
2513 $membershipTypeID,
2514 $numRenewTerms
2515 );
2516
2517 $currentMembership['join_date'] = CRM_Utils_Date::customFormat($currentMembership['join_date'], $format);
2518 $currentMembership['start_date'] = CRM_Utils_Array::value('start_date', $dates);
2519 $currentMembership['end_date'] = CRM_Utils_Array::value('end_date', $dates);
2520 $currentMembership['is_test'] = $is_test;
2521
2522 if (!empty($membershipSource)) {
2523 $currentMembership['source'] = $membershipSource;
2524 }
2525 else {
2526 $currentMembership['source'] = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_Membership',
2527 $currentMembership['id'],
2528 'source'
2529 );
2530 }
2531
2532 if (!empty($currentMembership['id'])) {
2533 $ids['membership'] = $currentMembership['id'];
2534 }
2535 $memParams = $currentMembership;
2536 $memParams['membership_type_id'] = $membershipTypeID;
2537
2538 //set the log start date.
2539 $memParams['log_start_date'] = CRM_Utils_Date::customFormat($dates['log_start_date'], $format);
2540 }
2541 else {
2542
2543 // CURRENT Membership
2544 $membership = new CRM_Member_DAO_Membership();
2545 $membership->id = $currentMembership['id'];
2546 $membership->find(TRUE);
2547 // CRM-7297 Membership Upsell - calculate dates based on new membership type
2548 $dates = CRM_Member_BAO_MembershipType::getRenewalDatesForMembershipType($membership->id,
2549 $changeToday,
2550 $membershipTypeID,
2551 $numRenewTerms
2552 );
2553
2554 // Insert renewed dates for CURRENT membership
2555 $memParams = array();
2556 $memParams['join_date'] = CRM_Utils_Date::isoToMysql($membership->join_date);
2557 $memParams['start_date'] = CRM_Utils_Date::isoToMysql($membership->start_date);
2558 $memParams['end_date'] = CRM_Utils_Array::value('end_date', $dates);
2559 $memParams['membership_type_id'] = $membershipTypeID;
2560
2561 //set the log start date.
2562 $memParams['log_start_date'] = CRM_Utils_Date::customFormat($dates['log_start_date'], $format);
2563 if (empty($membership->source)) {
2564 if (!empty($membershipSource)) {
2565 $memParams['source'] = $membershipSource;
2566 }
2567 else {
2568 $memParams['source'] = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_Membership',
2569 $currentMembership['id'],
2570 'source'
2571 );
2572 }
2573 }
2574
2575 if (!empty($currentMembership['id'])) {
2576 $ids['membership'] = $currentMembership['id'];
2577 }
2578 }
2579 //CRM-4555
2580 if ($pending) {
2581 $updateStatusId = array_search('Pending', $allStatus);
2582 }
2583 }
2584 else {
2585 // NEW Membership
2586
2587 $activityType = 'Membership Signup';
2588 $memParams = array(
2589 'contact_id' => $contactID,
2590 'membership_type_id' => $membershipTypeID,
2591 );
2592
2593 if (!$pending) {
2594 $dates = CRM_Member_BAO_MembershipType::getDatesForMembershipType($membershipTypeID, NULL, NULL, NULL, $numRenewTerms);
2595
2596 $memParams['join_date'] = CRM_Utils_Array::value('join_date', $dates);
2597 $memParams['start_date'] = CRM_Utils_Array::value('start_date', $dates);
2598 $memParams['end_date'] = CRM_Utils_Array::value('end_date', $dates);
2599
2600 $status = CRM_Member_BAO_MembershipStatus::getMembershipStatusByDate(CRM_Utils_Date::customFormat($dates['start_date'],
2601 $statusFormat
2602 ),
2603 CRM_Utils_Date::customFormat($dates['end_date'],
2604 $statusFormat
2605 ),
2606 CRM_Utils_Date::customFormat($dates['join_date'],
2607 $statusFormat
2608 ),
2609 'today',
2610 TRUE,
2611 $membershipTypeID,
2612 $memParams
2613 );
2614 $updateStatusId = CRM_Utils_Array::value('id', $status);
2615 }
2616 else {
2617 // if IPN/Pay-Later set status to: PENDING
2618 $updateStatusId = array_search('Pending', $allStatus);
2619 }
2620
2621 if (!empty($membershipSource)) {
2622 $memParams['source'] = $membershipSource;
2623 }
2624 $memParams['contribution_recur_id'] = $contributionRecurID;
2625
2626 $memParams['is_test'] = $is_test;
2627 $memParams['is_pay_later'] = $isPayLater;
2628 }
2629
2630 //CRM-4555
2631 //if we decided status here and want to skip status
2632 //calculation in create( ); then need to pass 'skipStatusCal'.
2633 if ($updateStatusId) {
2634 $memParams['status_id'] = $updateStatusId;
2635 $memParams['skipStatusCal'] = TRUE;
2636 }
2637
2638 //since we are renewing,
2639 //make status override false.
2640 $memParams['is_override'] = FALSE;
2641
2642 //CRM-4027, create log w/ individual contact.
2643 if ($modifiedID) {
2644 $ids['userId'] = $modifiedID;
2645 $memParams['is_for_organization'] = TRUE;
2646 }
2647 else {
2648 $ids['userId'] = $contactID;
2649 }
2650
2651 //inherit campaign from contrib page.
2652 if (isset($campaignId)) {
2653 $memParams['campaign_id'] = $campaignId;
2654 }
2655
2656 $memParams['custom'] = $customFieldsFormatted;
2657 $membership = self::create($memParams, $ids, FALSE, $activityType);
2658
2659 // not sure why this statement is here, seems quite odd :( - Lobo: 12/26/2010
2660 // related to: http://forum.civicrm.org/index.php/topic,11416.msg49072.html#msg49072
2661 $membership->find(TRUE);
2662
2663 return array($membership, $renewalMode, $dates);
2664 }
2665
6a488035 2666 /**
100fef9d 2667 * Process price set and line items.
6a488035 2668 *
100fef9d 2669 * @param int $membershipId
e46e9a0b
EM
2670 * @param $lineItem
2671 *
355ba699 2672 * @return void
6a488035 2673 */
00be9182 2674 public function processPriceSet($membershipId, $lineItem) {
6a488035
TO
2675 //FIXME : need to move this too
2676 if (!$membershipId || !is_array($lineItem)
ca58d9b9 2677 || CRM_Utils_System::isNull($lineItem)
6a488035
TO
2678 ) {
2679 return;
2680 }
2681
2682 foreach ($lineItem as $priceSetId => $values) {
2683 if (!$priceSetId) {
2684 continue;
2685 }
2686 foreach ($values as $line) {
2687 $line['entity_table'] = 'civicrm_membership';
2688 $line['entity_id'] = $membershipId;
2689 CRM_Price_BAO_LineItem::create($line);
2690 }
2691 }
2692 }
2693
2694 /**
fe482240 2695 * Retrieve the contribution id for the associated Membership id.
ca266339 2696 * @todo we should get this off the line item
6a488035 2697 *
b2363ea8
TO
2698 * @param int $membershipId
2699 * Membership id.
6a488035 2700 *
df8d3074 2701 * @return int
a6c01b45 2702 * contribution id
6a488035 2703 */
00be9182 2704 public static function getMembershipContributionId($membershipId) {
6a488035 2705
ca266339
EM
2706 $membershipPayment = new CRM_Member_DAO_MembershipPayment();
2707 $membershipPayment->membership_id = $membershipId;
2708 if ($membershipPayment->find(TRUE)) {
2709 return $membershipPayment->contribution_id;
6a488035
TO
2710 }
2711 return NULL;
2712 }
2713
2714 /**
e0556ebe
TO
2715 * The function checks and updates the status of all membership records for a given domain using the
2716 * calc_membership_status and update_contact_membership APIs.
2717 *
2718 * IMPORTANT:
2719 * Sending renewal reminders has been migrated from this job to the Scheduled Reminders function as of 4.3.
2720 *
a6c01b45 2721 * @return array
e0556ebe 2722 */
00be9182 2723 public static function updateAllMembershipStatus() {
6a488035
TO
2724
2725 //get all active statuses of membership, CRM-3984
e0556ebe
TO
2726 $allStatus = CRM_Member_PseudoConstant::membershipStatus();
2727 $statusLabels = CRM_Member_PseudoConstant::membershipStatus(NULL, NULL, 'label');
2728 $allTypes = CRM_Member_PseudoConstant::membershipType();
6a488035 2729
85b6e9a2 2730 // get only memberships with active membership types
6a488035
TO
2731 $query = "
2732SELECT civicrm_membership.id as membership_id,
2733 civicrm_membership.is_override as is_override,
2734 civicrm_membership.membership_type_id as membership_type_id,
2735 civicrm_membership.status_id as status_id,
2736 civicrm_membership.join_date as join_date,
2737 civicrm_membership.start_date as start_date,
2738 civicrm_membership.end_date as end_date,
2739 civicrm_membership.source as source,
2740 civicrm_contact.id as contact_id,
2741 civicrm_contact.is_deceased as is_deceased,
2742 civicrm_membership.owner_membership_id as owner_membership_id,
2743 civicrm_membership.contribution_recur_id as recur_id
2744FROM civicrm_membership
2745INNER JOIN civicrm_contact ON ( civicrm_membership.contact_id = civicrm_contact.id )
85b6e9a2
DL
2746INNER JOIN civicrm_membership_type ON
2747 (civicrm_membership.membership_type_id = civicrm_membership_type.id AND civicrm_membership_type.is_active = 1)
6a488035
TO
2748WHERE civicrm_membership.is_test = 0";
2749
2750 $params = array();
2751 $dao = CRM_Core_DAO::executeQuery($query, $params);
2752
e0556ebe
TO
2753 $processCount = 0;
2754 $updateCount = 0;
6a488035
TO
2755
2756 $smarty = CRM_Core_Smarty::singleton();
2757
2758 while ($dao->fetch()) {
2759 // echo ".";
2760 $processCount++;
2761
6a488035
TO
2762 // Put common parameters into array for easy access
2763 $memberParams = array(
2764 'id' => $dao->membership_id,
2765 'status_id' => $dao->status_id,
2766 'contact_id' => $dao->contact_id,
2767 'membership_type_id' => $dao->membership_type_id,
2768 'membership_type' => $allTypes[$dao->membership_type_id],
2769 'join_date' => $dao->join_date,
2770 'start_date' => $dao->start_date,
2771 'end_date' => $dao->end_date,
2772 'source' => $dao->source,
2773 'skipStatusCal' => TRUE,
2774 'skipRecentView' => TRUE,
2775 );
2776
2777 $smarty->assign_by_ref('memberParams', $memberParams);
2778
2779 //update membership record to Deceased if contact is deceased
2780 if ($dao->is_deceased) {
2781 // check for 'Deceased' membership status, CRM-5636
2782 $deceaseStatusId = array_search('Deceased', $allStatus);
2783 if (!$deceaseStatusId) {
2784 CRM_Core_Error::fatal(ts("Deceased Membership status is missing or not active. <a href='%1'>Click here to check</a>.", array(1 => CRM_Utils_System::url('civicrm/admin/member/membershipStatus', 'reset=1'))));
2785 }
2786
2787 //process only when status change.
2788 if ($dao->status_id != $deceaseStatusId) {
2789 //take all params that need to save.
2790 $deceasedMembership = $memberParams;
2791 $deceasedMembership['status_id'] = $deceaseStatusId;
2792 $deceasedMembership['createActivity'] = TRUE;
2793 $deceasedMembership['version'] = 3;
2794
2795 //since there is change in status.
2796 $statusChange = array('status_id' => $deceaseStatusId);
2797 $smarty->append_by_ref('memberParams', $statusChange, TRUE);
68cf2de1 2798 unset(
2799 $deceasedMembership['contact_id'],
2800 $deceasedMembership['membership_type_id'],
2801 $deceasedMembership['membership_type'],
2802 $deceasedMembership['join_date'],
2803 $deceasedMembership['start_date'],
2804 $deceasedMembership['end_date'],
2805 $deceasedMembership['source']
2806 );
6a488035
TO
2807
2808 //process membership record.
2809 civicrm_api('membership', 'create', $deceasedMembership);
2810 }
2811 continue;
2812 }
2813
2814 //we fetch related, since we need to check for deceased
2815 //now further processing is handle w/ main membership record.
2816 if ($dao->owner_membership_id) {
2817 continue;
2818 }
2819
2820 //update membership records where status is NOT - Pending OR Cancelled.
2821 //as well as membership is not override.
2822 //skipping Expired membership records -> reduced extra processing( kiran )
2823 if (!$dao->is_override &&
e0556ebe
TO
2824 !in_array($dao->status_id, array(
2825 array_search('Pending', $allStatus),
2826 // CRM-15475
2827 array_search(
2828 'Cancelled',
2829 CRM_Member_PseudoConstant::membershipStatus(NULL, " name = 'Cancelled' ", 'name', FALSE, TRUE)
2830 ),
2831 array_search('Expired', $allStatus),
2832 ))
6a488035
TO
2833 ) {
2834
2835 // CRM-7248: added excludeIsAdmin param to the following fn call to prevent moving to admin statuses
2836 //get the membership status as per id.
2837 $newStatus = civicrm_api('membership_status', 'calc',
2838 array(
e0556ebe
TO
2839 'membership_id' => $dao->membership_id,
2840 'version' => 3,
21dfd5f5 2841 'ignore_admin_only' => FALSE,
e0556ebe 2842 ), TRUE
6a488035
TO
2843 );
2844 $statusId = CRM_Utils_Array::value('id', $newStatus);
2845
2846 //process only when status change.
2847 if ($statusId &&
2848 $statusId != $dao->status_id
2849 ) {
2850 //take all params that need to save.
2851 $memParams = $memberParams;
2852 $memParams['status_id'] = $statusId;
2853 $memParams['createActivity'] = TRUE;
2854 $memParams['version'] = 3;
2855
4daee127 2856 // Unset columns which should remain unchanged from their current saved
2857 // values. This avoids race condition in which these values may have
2858 // been changed by other processes.
2859 unset(
2860 $memParams['contact_id'],
2861 $memParams['membership_type_id'],
2862 $memParams['membership_type'],
2863 $memParams['join_date'],
2864 $memParams['start_date'],
2865 $memParams['end_date'],
2866 $memParams['source']
2867 );
6a488035
TO
2868 //since there is change in status.
2869 $statusChange = array('status_id' => $statusId);
2870 $smarty->append_by_ref('memberParams', $statusChange, TRUE);
2871
2872 //process member record.
2873 civicrm_api('membership', 'create', $memParams);
2874 $updateCount++;
2875 }
2876 }
6a488035
TO
2877 }
2878 $result['is_error'] = 0;
e0556ebe 2879 $result['messages'] = ts('Processed %1 membership records. Updated %2 records.', array(
353ffa53
TO
2880 1 => $processCount,
2881 2 => $updateCount,
2882 ));
6a488035
TO
2883 return $result;
2884 }
2885
e46e9a0b 2886 /**
688d37c6 2887 * Returns the membership types for a particular contact
6a488035
TO
2888 * who has lifetime membership without end date.
2889 *
100fef9d 2890 * @param int $contactID
e46e9a0b
EM
2891 * @param bool $isTest
2892 * @param bool $onlyLifeTime
6a488035 2893 *
e46e9a0b 2894 * @return array
6a488035 2895 */
00be9182 2896 public static function getAllContactMembership($contactID, $isTest = FALSE, $onlyLifeTime = FALSE) {
6a488035
TO
2897 $contactMembershipType = array();
2898 if (!$contactID) {
2899 return $contactMembershipType;
2900 }
2901
e0556ebe 2902 $dao = new CRM_Member_DAO_Membership();
6a488035
TO
2903 $dao->contact_id = $contactID;
2904 $pendingStatusId = array_search('Pending', CRM_Member_PseudoConstant::membershipStatus());
2905 $dao->whereAdd("status_id != $pendingStatusId");
2906
2907 if ($isTest) {
2908 $dao->is_test = $isTest;
2909 }
2910 else {
2911 $dao->whereAdd('is_test IS NULL OR is_test = 0');
2912 }
2913
2914 if ($onlyLifeTime) {
2915 $dao->whereAdd('end_date IS NULL');
2916 }
2917
2918 $dao->find();
2919 while ($dao->fetch()) {
2920 $membership = array();
2921 CRM_Core_DAO::storeValues($dao, $membership);
2922 $contactMembershipType[$dao->membership_type_id] = $membership;
2923 }
2924 return $contactMembershipType;
2925 }
2926
2927 /**
fe482240 2928 * Record contribution record associated with membership.
6a488035 2929 *
b2363ea8
TO
2930 * @param array $params
2931 * Array of submitted params.
2932 * @param array $ids
2933 * (param in process of being removed - try to use params) array of ids.
6a488035 2934 *
02af3683 2935 * @return CRM_Contribute_BAO_Contribution
6a488035 2936 */
e0556ebe 2937 public static function recordMembershipContribution(&$params, $ids = array()) {
d824fb6e 2938 $membershipId = $params['membership_id'];
6a488035
TO
2939 $contributionParams = array();
2940 $config = CRM_Core_Config::singleton();
2941 $contributionParams['currency'] = $config->defaultCurrency;
2942 $contributionParams['receipt_date'] = (CRM_Utils_Array::value('receipt_date', $params)) ? $params['receipt_date'] : 'null';
2943 $contributionParams['source'] = CRM_Utils_Array::value('contribution_source', $params);
6a488035 2944 $contributionParams['non_deductible_amount'] = 'null';
dcb032dc 2945 $contributionParams['payment_processor'] = CRM_Utils_Array::value('payment_processor_id', $params);
d80dbc14 2946 $contributionSoftParams = CRM_Utils_Array::value('soft_credit', $params);
6a488035 2947 $recordContribution = array(
e0556ebe
TO
2948 'contact_id',
2949 'total_amount',
2950 'receive_date',
2951 'financial_type_id',
2952 'payment_instrument_id',
2953 'trxn_id',
2954 'invoice_id',
2955 'is_test',
2956 'contribution_status_id',
2957 'check_number',
2958 'campaign_id',
2959 'is_pay_later',
2960 'membership_id',
2961 'tax_amount',
21dfd5f5 2962 'skipLineItem',
6a488035
TO
2963 );
2964 foreach ($recordContribution as $f) {
2965 $contributionParams[$f] = CRM_Utils_Array::value($f, $params);
2966 }
2967
2968 // make entry in batch entity batch table
a7488080 2969 if (!empty($params['batch_id'])) {
6a488035
TO
2970 $contributionParams['batch_id'] = $params['batch_id'];
2971 }
2972
91ef9be0 2973 if (!empty($params['contribution_contact_id'])) {
2974 // deal with possibility of a different person paying for contribution
2975 $contributionParams['contact_id'] = $params['contribution_contact_id'];
2976 }
2977
a7488080 2978 if (!empty($params['processPriceSet']) &&
6a488035
TO
2979 !empty($params['lineItems'])
2980 ) {
2981 $contributionParams['line_item'] = CRM_Utils_Array::value('lineItems', $params, NULL);
2982 }
2983
2984 $contribution = CRM_Contribute_BAO_Contribution::create($contributionParams, $ids);
2985
c206647d 2986 //CRM-13981, create new soft-credit record as to record payment from different person for this membership
d80dbc14 2987 if (!empty($contributionSoftParams)) {
9e1854a1 2988 if (!empty($params['batch_id'])) {
2989 foreach ($contributionSoftParams as $contributionSoft) {
2990 $contributionSoft['contribution_id'] = $contribution->id;
2991 $contributionSoft['currency'] = $contribution->currency;
2992 CRM_Contribute_BAO_ContributionSoft::add($contributionSoft);
2993 }
2994 }
2995 else {
e0556ebe
TO
2996 $contributionSoftParams['contribution_id'] = $contribution->id;
2997 $contributionSoftParams['currency'] = $contribution->currency;
2998 $contributionSoftParams['amount'] = $contribution->total_amount;
2999 CRM_Contribute_BAO_ContributionSoft::add($contributionSoftParams);
a54e2c55 3000 }
d80dbc14 3001 }
3002
6a488035
TO
3003 // store contribution id
3004 $params['contribution_id'] = $contribution->id;
3005
6a488035 3006 //insert payment record for this membership
8cc574cf 3007 if (empty($ids['contribution']) || !empty($params['is_recur'])) {
e0556ebe 3008 CRM_Member_BAO_MembershipPayment::create(array(
353ffa53
TO
3009 'membership_id' => $membershipId,
3010 'contribution_id' => $contribution->id,
3011 ));
6a488035
TO
3012 }
3013 return $contribution;
3014 }
3015
3016 /**
fe482240 3017 * Record line items for default membership.
6a488035 3018 *
c490a46a 3019 * @param CRM_Core_Form $qf
b2363ea8
TO
3020 * @param array $membershipType
3021 * Array with membership type and organization.
c490a46a 3022 * @param int $priceSetId
e46e9a0b 3023 *
6a488035 3024 */
00be9182 3025 public static function createLineItems(&$qf, $membershipType, &$priceSetId) {
9da8dc8c 3026 $qf->_priceSetId = $priceSetId = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', 'default_membership_type_amount', 'id', 'name');
6a488035 3027 if ($priceSetId) {
9da8dc8c 3028 $qf->_priceSet = $priceSets = current(CRM_Price_BAO_PriceSet::getSetDetail($priceSetId));
6a488035
TO
3029 }
3030 $editedFieldParams = array(
3031 'price_set_id' => $priceSetId,
3032 'name' => $membershipType[0],
3033 );
3034 $editedResults = array();
9da8dc8c 3035 CRM_Price_BAO_PriceField::retrieve($editedFieldParams, $editedResults);
6a488035
TO
3036
3037 if (!empty($editedResults)) {
3038 unset($qf->_priceSet['fields']);
3039 $qf->_priceSet['fields'][$editedResults['id']] = $priceSets['fields'][$editedResults['id']];
3040 unset($qf->_priceSet['fields'][$editedResults['id']]['options']);
3041 $fid = $editedResults['id'];
3042 $editedFieldParams = array(
3043 'price_field_id' => $editedResults['id'],
3044 'membership_type_id' => $membershipType[1],
3045 );
3046 $editedResults = array();
9da8dc8c 3047 CRM_Price_BAO_PriceFieldValue::retrieve($editedFieldParams, $editedResults);
6a488035 3048 $qf->_priceSet['fields'][$fid]['options'][$editedResults['id']] = $priceSets['fields'][$fid]['options'][$editedResults['id']];
a7488080 3049 if (!empty($qf->_params['total_amount'])) {
6a488035
TO
3050 $qf->_priceSet['fields'][$fid]['options'][$editedResults['id']]['amount'] = $qf->_params['total_amount'];
3051 }
3052 }
3053
3054 $fieldID = key($qf->_priceSet['fields']);
3055 $qf->_params['price_' . $fieldID] = CRM_Utils_Array::value('id', $editedResults);
3056 }
8ef12e64 3057
e46e9a0b
EM
3058 /**
3059 * @todo document me - I seem a bit out of date....
3060 */
00be9182 3061 public static function _getActTypes() {
4e636a74
AH
3062 $activityTypes = CRM_Core_PseudoConstant::activityType(TRUE, FALSE, FALSE, 'name');
3063 self::$_renewalActType = CRM_Utils_Array::key('Membership Renewal', $activityTypes);
3064 self::$_signupActType = CRM_Utils_Array::key('Membership Signup', $activityTypes);
3065 }
5624f515 3066
b5a62499
PN
3067 /**
3068 * Get all Cancelled Membership(s) for a contact
3069 *
b2363ea8
TO
3070 * @param int $contactID
3071 * Contact id.
3072 * @param bool $isTest
3073 * Mode of payment.
b5a62499 3074 *
a6c01b45 3075 * @return array
16b10e64 3076 * Array of membership type
b5a62499 3077 */
00be9182 3078 public static function getContactsCancelledMembership($contactID, $isTest = FALSE) {
b5a62499
PN
3079 if (!$contactID) {
3080 return array();
5624f515 3081 }
b5a62499
PN
3082 $query = 'SELECT membership_type_id FROM civicrm_membership WHERE contact_id = %1 AND status_id = %2 AND is_test = %3';
3083 $queryParams = array(
3084 1 => array($contactID, 'Integer'),
7ff60806
PN
3085 2 => array(
3086 // CRM-15475
3087 array_search(
4c16123d 3088 'Cancelled',
7ff60806 3089 CRM_Member_PseudoConstant::membershipStatus(
4c16123d
EM
3090 NULL,
3091 " name = 'Cancelled' ",
3092 'name',
3093 FALSE,
7ff60806
PN
3094 TRUE
3095 )
4c16123d 3096 ),
21dfd5f5 3097 'Integer',
7ff60806 3098 ),
b5a62499
PN
3099 3 => array($isTest, 'Boolean'),
3100 );
5624f515 3101
b5a62499
PN
3102 $dao = CRM_Core_DAO::executeQuery($query, $queryParams);
3103 $cancelledMembershipIds = array();
3104 while ($dao->fetch()) {
3105 $cancelledMembershipIds[] = $dao->membership_type_id;
3106 }
3107 return $cancelledMembershipIds;
3108 }
96025800 3109
6a488035 3110}