CRM-16737, CRM-15296 support for payment_status_id as the new official way to
[civicrm-core.git] / CRM / Member / BAO / Membership.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.6 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2015 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
27
28 /**
29 *
30 * @package CRM
31 * @copyright CiviCRM LLC (c) 2004-2015
32 * $Id$
33 *
34 */
35 class CRM_Member_BAO_Membership extends CRM_Member_DAO_Membership {
36
37 /**
38 * Static field for all the membership information that we can potentially import.
39 *
40 * @var array
41 */
42 static $_importableFields = NULL;
43
44 static $_renewalActType = NULL;
45
46 static $_signupActType = NULL;
47
48 /**
49 * Class constructor.
50 *
51 * @return \CRM_Member_DAO_Membership
52 */
53 /**
54 */
55 public function __construct() {
56 parent::__construct();
57 }
58
59 /**
60 * Takes an associative array and creates a membership object.
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 *
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.
70 *
71 * @return CRM_Member_BAO_Membership
72 */
73 public static function add(&$params, $ids = array()) {
74 $oldStatus = $oldType = NULL;
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 {
80 CRM_Utils_Hook::pre('create', 'Membership', NULL, $params);
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;
87 $membershipObj->find();
88 while ($membershipObj->fetch()) {
89 $oldStatus = $membershipObj->status_id;
90 $oldType = $membershipObj->membership_type_id;
91 }
92 }
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);
100 $membership->id = $id;
101
102 $membership->save();
103 $membership->free();
104
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
108 // however the hooks don't care and want all data CRM-7784
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.
117 $logStartDate = CRM_Utils_Array::value('log_start_date', $params);
118 $logStartDate = ($logStartDate) ? CRM_Utils_Date::isoToMysql($logStartDate) : CRM_Utils_Date::isoToMysql($membership->start_date);
119 $values = self::getStatusANDTypeValues($membership->id);
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
148 if ($id) {
149 if ($membership->status_id != $oldStatus) {
150 $allStatus = CRM_Member_BAO_Membership::buildOptions('status_id', 'get');
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,
156 'activity_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Change Membership Status'),
157 'status_id' => 2,
158 'priority_id' => 2,
159 'activity_date_time' => date('Y-m-d H:i:s'),
160 );
161 civicrm_api3('activity', 'create', $activityParam);
162 }
163 if (isset($membership->membership_type_id) && $membership->membership_type_id != $oldType) {
164 $membershipTypes = CRM_Member_BAO_Membership::buildOptions('membership_type_id', 'get');
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,
170 'activity_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Change Membership Type'),
171 'status_id' => 2,
172 'priority_id' => 2,
173 'activity_date_time' => date('Y-m-d H:i:s'),
174 );
175 civicrm_api3('activity', 'create', $activityParam);
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 *
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.
196 * be returned
197 * @param bool $relatedMemberships
198 * @return CRM_Member_BAO_Membership|null the found object or null
199 */
200 public static function &getValues(&$params, &$values, $active = FALSE, $relatedMemberships = FALSE) {
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',
212 $membership->status_id,
213 'is_current_member'
214 ))
215 ) {
216 continue;
217 }
218
219 CRM_Core_DAO::storeValues($membership, $values[$membership->id]);
220 $memberships[$membership->id] = $membership;
221 if ($relatedMemberships && !empty($membership->owner_membership_id)) {
222 $values['owner_membership_ids'][] = $membership->owner_membership_id;
223 }
224 }
225
226 return $memberships;
227 }
228
229 /**
230 * Takes an associative array and creates a membership object.
231 *
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.
236 * @param bool $skipRedirect
237 * @param string $activityType
238 *
239 * @throws CRM_Core_Exception
240 *
241 * @return CRM_Member_BAO_Membership|CRM_Core_Error
242 */
243 public static function create(&$params, &$ids, $skipRedirect = FALSE, $activityType = 'Membership Signup') {
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
250 if (empty($params['is_override']) && empty($params['skipStatusCal'])) {
251 $dates = array('start_date', 'end_date', 'join_date');
252 $start_date = $end_date = $join_date = NULL; // declare these out of courtesy as IDEs don't pick up the setting of them below
253 foreach ($dates as $date) {
254 $$date = $params[$date] = CRM_Utils_Date::processDate(CRM_Utils_Array::value($date, $params), NULL, TRUE, 'Ymd');
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.
261 if (!$excludeIsAdmin && empty($params['is_override'])) {
262 $excludeIsAdmin = TRUE;
263 }
264
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
276 if (empty($calcStatus)) {
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 );
287 throw new CRM_Core_Exception(ts('The membership cannot be saved because the status cannot be calculated.'), 0, $errorParams);
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']);
297 }
298 else {
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
318 if (!empty($params['custom']) && is_array($params['custom'])
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 }
331
332 $params['skipLineItem'] = TRUE;
333
334 //record contribution for this membership
335 if (!empty($params['contribution_status_id']) && empty($params['relate_contribution_id'])) {
336 $memInfo = array_merge($params, array('membership_id' => $membership->id));
337 $params['contribution'] = self::recordMembershipContribution($memInfo, $ids);
338 }
339
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 }
348
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));
351 }
352
353 //insert payment record for this membership
354 if (!empty($params['relate_contribution_id'])) {
355 CRM_Member_BAO_MembershipPayment::create(array(
356 'membership_id' => $membership->id,
357 'contribution_id' => $params['relate_contribution_id'],
358 ));
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
363 if (empty($ids['membership']) ||
364 $activityType == 'Membership Renewal' || !empty($params['createActivity'])
365 ) {
366 if (!empty($ids['membership'])) {
367 $data = array();
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;
387 if (!empty($params['is_for_organization'])) {
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
396 if (!empty($ids['membership']) && $activityType != 'Membership Signup') {
397 CRM_Activity_BAO_Activity::addActivity($membership, $activityType, $targetContactID);
398 }
399 elseif (empty($ids['membership'])) {
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
414 if (empty($params['skipRecentView'])) {
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 );
418 if (empty($membership->membership_type_id)) {// ie in an update situation
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 /**
451 * Check the membership extended through relationship.
452 *
453 * @param int $membershipId
454 * Membership id.
455 * @param int $contactId
456 * Contact id.
457 *
458 * @param int $action
459 *
460 * @return array
461 * array of contact_id of all related contacts.
462 */
463 public static function checkMembershipRelationship($membershipId, $contactId, $action = CRM_Core_Action::ADD) {
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
489 $relTypeDirs = array();
490 $relTypeIds = explode(CRM_Core_DAO::VALUE_SEPARATOR, $membershipType['relationship_type_id']);
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 /**
517 * Retrieve DB object based on input parameters.
518 *
519 * It also stores all the retrieved values in the default array.
520 *
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.
525 * in a hierarchical manner
526 *
527 * @return CRM_Member_BAO_Membership
528 */
529 public static function retrieve(&$params, &$defaults) {
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(
540 'status',
541 'membership_type',
542 ) as $fld) {
543 $defaults[$fld] = CRM_Utils_Array::value($fld, $statusANDType[$membership->id]);
544 }
545 if (!empty($statusANDType[$membership->id]['is_current_member'])) {
546 $defaults['active'] = TRUE;
547 }
548
549 $membership->free();
550
551 return $membership;
552 }
553
554 return NULL;
555 }
556
557 /**
558 *
559 * get membership status and membership type values
560 *
561 * @param int $membershipId
562 * Membership id of values to return.
563 *
564 * @return array
565 * Array of key value pairs
566 */
567 public static function getStatusANDTypeValues($membershipId) {
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
581 INNER JOIN civicrm_membership_status status ON ( status.id = membership.status_id )
582 INNER 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')));
585 $properties = array(
586 'status',
587 'status_id',
588 'membership_type',
589 'membership_type_id',
590 'is_current_member',
591 'relationship_type_id',
592 );
593 while ($dao->fetch()) {
594 foreach ($properties as $property) {
595 $values[$dao->id][$property] = $dao->$property;
596 }
597 }
598
599 return $values;
600 }
601
602 /**
603 * Delete membership.
604 * Wrapper for most delete calls. Use this unless you JUST want to delete related memberships w/o deleting the parent.
605 *
606 * @param int $membershipId
607 * Membership id that needs to be deleted.
608 *
609 *
610 * @return int $results id of deleted Membership on success, false otherwise
611 */
612 public static function del($membershipId) {
613 //delete related first and then delete parent.
614 self::deleteRelatedMemberships($membershipId);
615 return self::deleteMembership($membershipId);
616 }
617
618 /**
619 * Delete membership.
620 *
621 * @param int $membershipId
622 * Membership id that needs to be deleted.
623 *
624 *
625 * @return int $results id of deleted Membership on success, false otherwise
626 */
627 public static function deleteMembership($membershipId) {
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);
632
633 $membership = $memberships[$membershipId];
634
635 CRM_Utils_Hook::pre('delete', 'Membership', $membershipId, $memValues);
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();
644 $deleteActivity = FALSE;
645 $membershipActivities = array(
646 'Membership Signup',
647 'Membership Renewal',
648 'Change Membership Status',
649 'Change Membership Type',
650 'Membership Renewal Reminder',
651 );
652 foreach ($membershipActivities as $membershipActivity) {
653 $activityId = array_search($membershipActivity, $activityTypes);
654 if ($activityId) {
655 $params['activity_type_id'][] = $activityId;
656 $deleteActivity = TRUE;
657 }
658 }
659 if ($deleteActivity) {
660 $params['source_record_id'] = $membershipId;
661 CRM_Activity_BAO_Activity::deleteActivity($params);
662 }
663 self::deleteMembershipPayment($membershipId);
664
665 $results = $membership->delete();
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
680 /**
681 * Delete related memberships.
682 *
683 * @param int $ownerMembershipId
684 * @param int $contactId
685 *
686 * @return null
687 */
688 public static function deleteRelatedMemberships($ownerMembershipId, $contactId = NULL) {
689 if (!$ownerMembershipId && !$contactId) {
690 return FALSE;
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
709 /**
710 * Obtain active/inactive memberships from the list of memberships passed to it.
711 *
712 * @param array $memberships
713 * Membership records.
714 * @param string $status
715 * Active or inactive.
716 *
717 * @return array
718 * array of memberships based on status
719 */
720 public static function activeMembers($memberships, $status = 'active') {
721 $actives = array();
722 if ($status == 'active') {
723 foreach ($memberships as $f => $v) {
724 if (!empty($v['active'])) {
725 $actives[$f] = $v;
726 }
727 }
728 return $actives;
729 }
730 elseif ($status == 'inactive') {
731 foreach ($memberships as $f => $v) {
732 if (empty($v['active'])) {
733 $actives[$f] = $v;
734 }
735 }
736 return $actives;
737 }
738 return NULL;
739 }
740
741 /**
742 * Build Membership Block in Contribution Pages.
743 *
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.
755 * @param null $isTest
756 *
757 * @return bool
758 * Is this a separate membership payment
759 *
760 */
761 public static function buildMembershipBlock(
762 &$form,
763 $pageID,
764 $cid,
765 $formItems = FALSE,
766 $selectedMembershipTypeID = NULL,
767 $thankPage = FALSE,
768 $isTest = NULL
769 ) {
770
771 $separateMembershipPayment = FALSE;
772 if ($form->_membershipBlock) {
773 $form->_currentMemberships = array();
774
775 $membershipBlock = $form->_membershipBlock;
776 $membershipTypeIds = $membershipTypes = $radio = array();
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) {
792 if (empty($opValues['membership_type_id'])) {
793 continue;
794 }
795 $membershipTypeIds[$opValues['membership_type_id']] = $opValues['membership_type_id'];
796 }
797 }
798 }
799 elseif (!empty($membershipBlock['membership_types'])) {
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;
842 if (is_array($form->_paymentProcessors)) {
843 foreach ($form->_paymentProcessors as $id => $val) {
844 if (!$val['is_recur']) {
845 $allowAutoRenewOpt = 0;
846 continue;
847 }
848 }
849 }
850
851 $javascriptMethod = array('onclick' => "return showHideAutoRenew( this.value );");
852 $autoRenewMembershipTypeOptions["autoRenewMembershipType_{$value}"] = (int) $allowAutoRenewOpt * CRM_Utils_Array::value($value, CRM_Utils_Array::value('auto_renew', $form->_membershipBlock));;
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 {
923 $autoRenewOption = CRM_Price_BAO_PriceSet::checkAutoRenewForPriceSet($form->_priceSetId);
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 /**
947 * Return Membership Block info in Contribution Pages.
948 *
949 * @param int $pageID
950 * Contribution page id.
951 *
952 * @return array|null
953 *
954 */
955 public static function getMembershipBlock($pageID) {
956 $membershipBlock = array();
957 $dao = new CRM_Member_DAO_MembershipBlock();
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);
964 if (!empty($membershipBlock['membership_types'])) {
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 /**
985 * Return a current membership of given contact.
986 * NB: if more than one membership meets criteria, a randomly selected one is returned.
987 *
988 * @param int $contactID
989 * Contact id.
990 * @param int $memType
991 * Membership type, null to retrieve all types.
992 * @param int $isTest
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.
997 *
998 * @return array|bool
999 */
1000 public static function getContactMembership($contactID, $memType, $isTest, $membershipId = NULL, $onlySameParentOrg = FALSE) {
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 }
1015
1016 //avoid pending membership as current membership: CRM-3027
1017 $statusIds[] = array_search('Pending', CRM_Member_PseudoConstant::membershipStatus());
1018 if (!$membershipId) {
1019 // CRM-15475
1020 $statusIds[] = array_search(
1021 'Cancelled',
1022 CRM_Member_PseudoConstant::membershipStatus(
1023 NULL,
1024 " name = 'Cancelled' ",
1025 'name',
1026 FALSE,
1027 TRUE
1028 )
1029 );
1030 }
1031 $dao->whereAdd('status_id NOT IN ( ' . implode(',', $statusIds) . ')');
1032
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);
1040 $membershipType = array();
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
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.
1063 $dao->whereAdd();
1064 }
1065 else {
1066 unset($dao->id);
1067 }
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 /**
1084 * Combine all the importable fields from the lower levels object.
1085 *
1086 * @param string $contactType
1087 * Contact type.
1088 * @param bool $status
1089 *
1090 * @return array
1091 * array of importable Fields
1092 */
1093 public static function &importableFields($contactType = 'Individual', $status = TRUE) {
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,
1112 'used' => 'Unsupervised',
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 /**
1149 * Get all exportable fields.
1150 *
1151 * @retun array return array of all exportable fields
1152 */
1153 public static function &exportableFields() {
1154 $expFieldMembership = CRM_Member_DAO_Membership::export();
1155
1156 $expFieldsMemType = CRM_Member_DAO_MembershipType::export();
1157 $fields = array_merge($expFieldMembership, $expFieldsMemType);
1158 $fields = array_merge($fields, $expFieldMembership);
1159 $membershipStatus = array(
1160 'membership_status' => array(
1161 'title' => 'Membership Status',
1162 'name' => 'membership_status',
1163 'type' => CRM_Utils_Type::T_STRING,
1164 'where' => 'civicrm_membership_status.name',
1165 ),
1166 );
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 /**
1173 * Get membership joins/renewals for a specified membership
1174 * type. Specifically, retrieves a count of memberships whose "Membership
1175 * Signup" or "Membership Renewal" activity falls in the given date range.
1176 * Dates match the pattern "yyyy-mm-dd".
1177 *
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.
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
1186 *
1187 * @return int
1188 * the number of members of type $membershipTypeId whose
1189 * start_date is between $startDate and $endDate
1190 */
1191 public static function getMembershipStarts($membershipTypeId, $startDate, $endDate, $isTest = 0, $isOwner = 0) {
1192
1193 $testClause = 'membership.is_test = 1';
1194 if (!$isTest) {
1195 $testClause = '( membership.is_test IS NULL OR membership.is_test = 0 )';
1196 }
1197
1198 if (!self::$_signupActType || !self::$_renewalActType) {
1199 self::_getActTypes();
1200 }
1201
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
1209 INNER JOIN civicrm_activity activity ON (activity.source_record_id = membership.id AND activity.activity_type_id in (%1, %2))
1210 INNER JOIN civicrm_membership_status status ON ( membership.status_id = status.id AND status.is_current_member = 1 )
1211 INNER 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}";
1215
1216 $query .= ($isOwner) ? ' AND owner_membership_id IS NULL' : '';
1217
1218 $params = array(
1219 1 => array(self::$_signupActType, 'Integer'),
1220 2 => array(self::$_renewalActType, 'Integer'),
1221 3 => array($membershipTypeId, 'Integer'),
1222 );
1223
1224 $memberCount = CRM_Core_DAO::singleValueQuery($query, $params);
1225 return (int) $memberCount;
1226 }
1227
1228 /**
1229 * Get a count of membership for a specified membership type,
1230 * optionally for a specified date. The date must have the form yyyy-mm-dd.
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 *
1241 * @param int $membershipTypeId
1242 * Membership type id.
1243 * @param string $date
1244 * The date for which to retrieve the count.
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
1247 *
1248 * @return int
1249 * the number of members of type $membershipTypeId as of $date.
1250 */
1251 public static function getMembershipCount($membershipTypeId, $date = NULL, $isTest = 0, $isOwner = 0) {
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)));
1254 }
1255
1256 $params = array(
1257 1 => array($membershipTypeId, 'Integer'),
1258 2 => array($isTest, 'Boolean'),
1259 );
1260 $query = "SELECT count(civicrm_membership.id ) as member_count
1261 FROM civicrm_membership left join civicrm_membership_status on ( civicrm_membership.status_id = civicrm_membership_status.id )
1262 WHERE civicrm_membership.membership_type_id = %1
1263 AND civicrm_membership.contact_id NOT IN (SELECT id FROM civicrm_contact WHERE is_deleted = 1)
1264 AND civicrm_membership.is_test = %2";
1265 if (!$date) {
1266 $query .= " AND civicrm_membership_status.is_current_member = 1";
1267 }
1268 else {
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);
1274 return (int) $memberCount;
1275 }
1276
1277 /**
1278 * Function check the status of the membership before adding membership for a contact.
1279 *
1280 * @param int $contactId
1281 * Contact id.
1282 *
1283 * @return int
1284 */
1285 public static function statusAvailabilty($contactId) {
1286 $membership = new CRM_Member_DAO_MembershipStatus();
1287 $membership->whereAdd('is_active=1');
1288 return $membership->count();
1289 }
1290
1291 /**
1292 * Process the Memberships.
1293 *
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.
1300 *
1301 * @param array $premiumParams
1302 * @param null $customFieldsFormatted
1303 * @param null $includeFieldTypes
1304 *
1305 * @param array $membershipDetails
1306 *
1307 * @param array $membershipTypeIDs
1308 *
1309 * @param bool $isPaidMembership
1310 * @param array $membershipID
1311 *
1312 * @param $isProcessSeparateMembershipTransaction
1313 *
1314 * @param int $defaultContributionTypeID
1315 * @param array $membershipLineItems
1316 * Line items specific to membership payment that is separate to contribution.
1317 * @param $isPayLater
1318 *
1319 * @throws \CRM_Core_Exception
1320 */
1321 public static function postProcessMembership(
1322 $membershipParams, $contactID, &$form, $premiumParams,
1323 $customFieldsFormatted = NULL, $includeFieldTypes = NULL, $membershipDetails, $membershipTypeIDs, $isPaidMembership, $membershipID,
1324 $isProcessSeparateMembershipTransaction, $defaultContributionTypeID, $membershipLineItems, $isPayLater) {
1325 $result = $membershipContribution = NULL;
1326 $isTest = CRM_Utils_Array::value('is_test', $membershipParams, FALSE);
1327 $errors = $createdMemberships = array();
1328
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
1337 if (CRM_Utils_Array::value('membership_source', $form->_params)) {
1338 $membershipParams['contribution_source'] = $form->_params['membership_source'];
1339 }
1340
1341 if ($isPaidMembership) {
1342 $result = CRM_Contribute_BAO_Contribution_Utils::processConfirm($form, $membershipParams,
1343 $premiumParams, $contactID,
1344 $financialTypeID,
1345 'membership',
1346 array(),
1347 $isTest,
1348 $isPayLater
1349 );
1350 if (is_a($result[1], 'CRM_Core_Error')) {
1351 $errors[1] = CRM_Core_Error::getMessages($result[1]);
1352 }
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];
1359 }
1360 }
1361
1362 if ($isProcessSeparateMembershipTransaction) {
1363 try {
1364 $lineItems = $form->_lineItem = $membershipLineItems;
1365 if (empty($form->_params['auto_renew']) && !empty($membershipParams['is_recur'])) {
1366 unset($membershipParams['is_recur']);
1367 }
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));
1369 }
1370 catch (CRM_Core_Exception $e) {
1371 $errors[2] = $e->getMessage();
1372 $membershipContribution = NULL;
1373 }
1374 }
1375
1376 $membership = NULL;
1377 if (!empty($membershipContribution) && !is_a($membershipContribution, 'CRM_Core_Error')) {
1378 $membershipContributionID = $membershipContribution->id;
1379 }
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
1386 if (is_array($membershipTypeIDs) && !empty($membershipContributionID)) {
1387 $typesTerms = CRM_Utils_Array::value('types_terms', $membershipParams, array());
1388 foreach ($membershipTypeIDs as $memType) {
1389 $numTerms = CRM_Utils_Array::value($memType, $typesTerms, 1);
1390 $createdMemberships[$memType] = self::createOrRenewMembership($membershipParams, $contactID, $customFieldsFormatted, $membershipID, $memType, $isTest, $numTerms, $membershipContribution, $form);
1391 }
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']];
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') : '-';
1400 }
1401 else {
1402 $priceFieldOp['start_date'] = $priceFieldOp['end_date'] = 'N/A';
1403 }
1404 }
1405 $form->_values['lineItem'] = $form->_lineItem;
1406 $form->assign('lineItem', $form->_lineItem);
1407 }
1408 }
1409
1410 if (!empty($errors)) {
1411 $message = self::compileErrorMessage($errors);
1412 throw new CRM_Core_Exception($message);
1413 }
1414 $form->_params['createdMembershipIDs'] = array();
1415
1416 // CRM-7851 - Moved after processing Payment Errors
1417 //@todo - the reasoning for this being here seems a little outdated
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 }
1428 if (count($createdMemberships) == 1) {
1429 //presumably this is only relevant for exactly 1 membership
1430 $form->_params['membershipID'] = $createdMembership->id;
1431 }
1432
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 }
1438 if ($form->_contributeMode == 'notify') {
1439 if ($form->_values['is_monetary'] && $form->_amount > 0.0 && !$form->_params['is_pay_later']) {
1440 // call postProcess hook before leaving
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
1448 if (isset($membershipContributionID)) {
1449 $form->_values['contribution_id'] = $membershipContributionID;
1450 }
1451
1452 // Do not send an email if Recurring transaction is done via Direct Mode
1453 // Email will we sent when the IPN is received.
1454 if (!empty($form->_params['is_recur']) && $form->_contributeMode == 'direct') {
1455 if (!empty($membershipContribution->trxn_id) && !isset($membershipContribution->payment_status_id)
1456 || (!empty($membershipContribution->payment_status_id) && $membershipContribution->payment_status_id == 1)) {
1457 try {
1458 civicrm_api3('contribution', 'completetransaction', array(
1459 'id' => $membershipContribution->id,
1460 'trxn_id' => $membershipContribution->trxn_id,
1461 ));
1462 }
1463 catch (CiviCRM_API3_Exception $e) {
1464 // if for any reason it is already completed this will fail - e.g extensions hacking around core not completing transactions prior to CRM-15296
1465 // so let's be gentle here
1466 CRM_Core_Error::debug_log_message('contribution ' . $membershipContribution->id . ' not completed with trxn_id ' . $membershipContribution->trxn_id . ' and message ' . $e->getMessage());
1467 }
1468 }
1469 return;
1470 }
1471
1472 //finally send an email receipt
1473 CRM_Contribute_BAO_ContributionPage::sendMail($contactID,
1474 $form->_values,
1475 $isTest, FALSE,
1476 $includeFieldTypes
1477 );
1478 }
1479
1480 /**
1481 * Function for updating a membership record's contribution_recur_id
1482 *
1483 * @param CRM_Member_DAO_Membership $membership
1484 * @param \CRM_Contribute_BAO_Contribution|\CRM_Contribute_DAO_Contribution $contribution
1485 *
1486 * @return void
1487 */
1488 static public function updateRecurMembership(CRM_Member_DAO_Membership $membership, CRM_Contribute_BAO_Contribution $contribution) {
1489
1490 if (empty($contribution->contribution_recur_id)) {
1491 return;
1492 }
1493
1494 $params = array(
1495 1 => array($contribution->contribution_recur_id, 'Integer'),
1496 2 => array($membership->id, 'Integer'),
1497 );
1498
1499 $sql = "UPDATE civicrm_membership SET contribution_recur_id = %1 WHERE id = %2";
1500 CRM_Core_DAO::executeQuery($sql, $params);
1501 }
1502
1503 /**
1504 * @deprecated
1505 * A wrapper for renewing memberships from a form - including the form in the membership processing adds complexity
1506 * as the forms are being forced to pretend similarity
1507 * Try to call the renewMembership directly
1508 * @todo - this form method needs to have the interaction with the form layer removed from it
1509 * as a BAO function. Note that the api now supports membership renewals & it is not clear this function does anything
1510 * not done by the membership.create api (with a lot less unit tests)
1511 *
1512 * This method will renew / create the membership depending on
1513 * whether the given contact has a membership or not. And will add
1514 * the modified dates for membership and in the log table.
1515 *
1516 * @param int $contactID
1517 * Id of the contact.
1518 * @param int $membershipTypeID
1519 * Id of the new membership type.
1520 * @param bool $is_test
1521 * If this is test contribution or live contribution.
1522 * @param CRM_Core_Form $form
1523 * Form object.
1524 * @param null $changeToday
1525 * @param int $modifiedID
1526 * Individual contact id in case of On Behalf signup (CRM-4027 ).
1527 * @param null $customFieldsFormatted
1528 * @param int $numRenewTerms
1529 * How many membership terms are being added to end date (default is 1).
1530 * @param int $membershipID
1531 * Membership ID, this should always be passed in & optionality should be removed.
1532 *
1533 * @throws CRM_Core_Exception
1534 *
1535 */
1536 public static function renewMembershipFormWrapper(
1537 $contactID,
1538 $membershipTypeID,
1539 $is_test,
1540 &$form,
1541 $changeToday = NULL,
1542 $modifiedID = NULL,
1543 $customFieldsFormatted = NULL,
1544 $numRenewTerms = 1,
1545 $membershipID = NULL,
1546 $pending = FALSE
1547 ) {
1548 $statusFormat = '%Y-%m-%d';
1549 $format = '%Y%m%d';
1550 $ids = array();
1551 //@todo would be better to make $membershipID a compulsory function param & make form layer responsible for extracting it
1552 if (!$membershipID && isset($form->_membershipId)) {
1553 $membershipID = $form->_membershipId;
1554 }
1555
1556 //get all active statuses of membership.
1557 $allStatus = CRM_Member_PseudoConstant::membershipStatus();
1558
1559 $membershipTypeDetails = CRM_Member_BAO_MembershipType::getMembershipTypeDetails($membershipTypeID);
1560
1561 // check is it pending. - CRM-4555
1562 list($pending, $contributionRecurID, $changeToday, $membershipSource, $isPayLater, $campaignId) = self::extractFormValues($form, $changeToday, $membershipTypeDetails, $pending);
1563 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);
1564 $form->set('renewal_mode', $renewalMode);
1565 if (!empty($dates)) {
1566 $form->assign('mem_start_date',
1567 CRM_Utils_Date::customFormat($dates['start_date'], $format)
1568 );
1569 $form->assign('mem_end_date',
1570 CRM_Utils_Date::customFormat($dates['end_date'], $format)
1571 );
1572 }
1573 return $membership;
1574
1575 }
1576
1577 /**
1578 * Method to fix membership status of stale membership.
1579 *
1580 * This method first checks if the membership is stale. If it is,
1581 * then status will be updated based on existing start and end
1582 * dates and log will be added for the status change.
1583 *
1584 * @param array $currentMembership
1585 * Reference to the array.
1586 * containing all values of
1587 * the current membership
1588 * @param array $changeToday
1589 * Array of month, day, year.
1590 * values in case today needs
1591 * to be customised, null otherwise
1592 *
1593 * @return void
1594 */
1595 public static function fixMembershipStatusBeforeRenew(&$currentMembership, $changeToday) {
1596 $today = NULL;
1597 if ($changeToday) {
1598 $today = CRM_Utils_Date::processDate($changeToday, NULL, FALSE, 'Y-m-d');
1599 }
1600
1601 $status = CRM_Member_BAO_MembershipStatus::getMembershipStatusByDate(
1602 CRM_Utils_Array::value('start_date', $currentMembership),
1603 CRM_Utils_Array::value('end_date', $currentMembership),
1604 CRM_Utils_Array::value('join_date', $currentMembership),
1605 $today,
1606 TRUE,
1607 $currentMembership['membership_type_id'],
1608 $currentMembership
1609 );
1610
1611 if (empty($status) ||
1612 empty($status['id'])
1613 ) {
1614 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.'));
1615 }
1616
1617 $currentMembership['today_date'] = $today;
1618
1619 if ($status['id'] !== $currentMembership['status_id']) {
1620 $memberDAO = new CRM_Member_DAO_Membership();
1621 $memberDAO->id = $currentMembership['id'];
1622 $memberDAO->find(TRUE);
1623
1624 $memberDAO->status_id = $status['id'];
1625 $memberDAO->join_date = CRM_Utils_Date::isoToMysql($memberDAO->join_date);
1626 $memberDAO->start_date = CRM_Utils_Date::isoToMysql($memberDAO->start_date);
1627 $memberDAO->end_date = CRM_Utils_Date::isoToMysql($memberDAO->end_date);
1628 $memberDAO->save();
1629 CRM_Core_DAO::storeValues($memberDAO, $currentMembership);
1630 $memberDAO->free();
1631
1632 $currentMembership['is_current_member'] = CRM_Core_DAO::getFieldValue(
1633 'CRM_Member_DAO_MembershipStatus',
1634 $currentMembership['status_id'],
1635 'is_current_member'
1636 );
1637 $format = '%Y%m%d';
1638
1639 $logParams = array(
1640 'membership_id' => $currentMembership['id'],
1641 'status_id' => $status['id'],
1642 'start_date' => CRM_Utils_Date::customFormat(
1643 $currentMembership['start_date'],
1644 $format
1645 ),
1646 'end_date' => CRM_Utils_Date::customFormat(
1647 $currentMembership['end_date'],
1648 $format
1649 ),
1650 'modified_date' => CRM_Utils_Date::customFormat(
1651 $currentMembership['today_date'],
1652 $format
1653 ),
1654 'membership_type_id' => $currentMembership['membership_type_id'],
1655 'max_related' => CRM_Utils_Array::value('max_related', $currentMembership, 0),
1656 );
1657
1658 $session = CRM_Core_Session::singleton();
1659 // If we have an authenticated session, set modified_id to that user's contact_id, else set to membership.contact_id
1660 if ($session->get('userID')) {
1661 $logParams['modified_id'] = $session->get('userID');
1662 }
1663 else {
1664 $logParams['modified_id'] = $currentMembership['contact_id'];
1665 }
1666 CRM_Member_BAO_MembershipLog::add($logParams, CRM_Core_DAO::$_nullArray);
1667 }
1668 }
1669
1670 /**
1671 * Get the contribution page id from the membership record.
1672 *
1673 * @param int $membershipID
1674 *
1675 * @return int
1676 * contribution page id
1677 */
1678 public static function getContributionPageId($membershipID) {
1679 $query = "
1680 SELECT c.contribution_page_id as pageID
1681 FROM civicrm_membership_payment mp, civicrm_contribution c
1682 WHERE mp.contribution_id = c.id
1683 AND c.contribution_page_id IS NOT NULL
1684 AND mp.membership_id = " . CRM_Utils_Type::escape($membershipID, 'Integer')
1685 . " ORDER BY mp.id DESC";
1686
1687 return CRM_Core_DAO::singleValueQuery($query,
1688 CRM_Core_DAO::$_nullArray
1689 );
1690 }
1691
1692 /**
1693 * Updated related memberships.
1694 *
1695 * @param int $ownerMembershipId
1696 * Owner Membership Id.
1697 * @param array $params
1698 * Formatted array of key => value.
1699 */
1700 public static function updateRelatedMemberships($ownerMembershipId, $params) {
1701 $membership = new CRM_Member_DAO_Membership();
1702 $membership->owner_membership_id = $ownerMembershipId;
1703 $membership->find();
1704
1705 while ($membership->fetch()) {
1706 $relatedMembership = new CRM_Member_DAO_Membership();
1707 $relatedMembership->id = $membership->id;
1708 $relatedMembership->copyValues($params);
1709 $relatedMembership->save();
1710 $relatedMembership->free();
1711 }
1712
1713 $membership->free();
1714 }
1715
1716 /**
1717 * Get list of membership fields for profile.
1718 * For now we only allow custom membership fields to be in
1719 * profile
1720 *
1721 * @param null $mode
1722 * FIXME: This param is ignored
1723 *
1724 * @return array
1725 * the list of membership fields
1726 */
1727 public static function getMembershipFields($mode = NULL) {
1728 $fields = CRM_Member_DAO_Membership::export();
1729
1730 unset($fields['membership_contact_id']);
1731 $fields = array_merge($fields, CRM_Core_BAO_CustomField::getFieldsForImport('Membership'));
1732
1733 $membershipType = CRM_Member_DAO_MembershipType::export();
1734
1735 $membershipStatus = CRM_Member_DAO_MembershipStatus::export();
1736
1737 $fields = array_merge($fields, $membershipType, $membershipStatus);
1738
1739 return $fields;
1740 }
1741
1742 /**
1743 * Get the sort name of a contact for a particular membership.
1744 *
1745 * @param int $id
1746 * Id of the membership.
1747 *
1748 * @return null|string
1749 * sort name of the contact if found
1750 */
1751 public static function sortName($id) {
1752 $id = CRM_Utils_Type::escape($id, 'Integer');
1753
1754 $query = "
1755 SELECT civicrm_contact.sort_name
1756 FROM civicrm_membership, civicrm_contact
1757 WHERE civicrm_membership.contact_id = civicrm_contact.id
1758 AND civicrm_membership.id = {$id}
1759 ";
1760 return CRM_Core_DAO::singleValueQuery($query, CRM_Core_DAO::$_nullArray);
1761 }
1762
1763 /**
1764 * Create memberships for related contacts.
1765 * takes into account the maximum related memberships
1766 *
1767 * @param array $params
1768 * Array of key - value pairs.
1769 * @param CRM_Core_DAO $dao
1770 * Membership object.
1771 *
1772 * @return null|array
1773 * array of memberships if created
1774 */
1775 public static function createRelatedMemberships(&$params, &$dao, $reset = FALSE) {
1776 static $relatedContactIds = array();
1777 if ($reset) {
1778 // not sure why a static var is in use here - we need a way to reset it from the test suite
1779 $relatedContactIds = array();
1780 return FALSE;
1781 }
1782
1783 $membership = new CRM_Member_DAO_Membership();
1784 $membership->id = $dao->id;
1785
1786 // required since create method doesn't return all the
1787 // parameters in the returned membership object
1788 if (!$membership->find(TRUE)) {
1789 return;
1790 }
1791 $deceasedStatusId = array_search('Deceased', CRM_Member_PseudoConstant::membershipStatus());
1792 // FIXME : While updating/ renewing the
1793 // membership, if the relationship is PAST then
1794 // the membership of the related contact must be
1795 // expired.
1796 // For that, getting Membership Status for which
1797 // is_current_member is 0. It works for the
1798 // generated data as there is only one membership
1799 // status having is_current_member = 0.
1800 // But this wont work exactly if there will be
1801 // more than one status having is_current_member = 0.
1802 $membershipStatus = new CRM_Member_DAO_MembershipStatus();
1803 $membershipStatus->is_current_member = 0;
1804 if ($membershipStatus->find(TRUE)) {
1805 $expiredStatusId = $membershipStatus->id;
1806 }
1807 else {
1808 $expiredStatusId = array_search('Expired', CRM_Member_PseudoConstant::membershipStatus());
1809 }
1810
1811 $allRelatedContacts = array();
1812 $relatedContacts = array();
1813 if (!is_a($membership, 'CRM_Core_Error')) {
1814 $allRelatedContacts = CRM_Member_BAO_Membership::checkMembershipRelationship($membership->id,
1815 $membership->contact_id,
1816 CRM_Utils_Array::value('action', $params)
1817 );
1818 }
1819
1820 // check for loops. CRM-4213
1821 // remove repeated related contacts, which already inherited membership.
1822 $relatedContactIds[$membership->contact_id] = TRUE;
1823 foreach ($allRelatedContacts as $cid => $status) {
1824 if (empty($relatedContactIds[$cid])) {
1825 $relatedContactIds[$cid] = TRUE;
1826
1827 //don't create membership again for owner contact.
1828 $nestedRelationship = FALSE;
1829 if ($membership->owner_membership_id) {
1830 $nestedRelMembership = new CRM_Member_DAO_Membership();
1831 $nestedRelMembership->id = $membership->owner_membership_id;
1832 $nestedRelMembership->contact_id = $cid;
1833 $nestedRelationship = $nestedRelMembership->find(TRUE);
1834 $nestedRelMembership->free();
1835 }
1836 if (!$nestedRelationship) {
1837 $relatedContacts[$cid] = $status;
1838 }
1839 }
1840 }
1841
1842 //lets cleanup related membership if any.
1843 if (empty($relatedContacts)) {
1844 self::deleteRelatedMemberships($membership->id);
1845 }
1846 else {
1847 // Edit the params array
1848 unset($params['id']);
1849 // Reminder should be sent only to the direct membership
1850 unset($params['reminder_date']);
1851 // unset the custom value ids
1852 if (is_array(CRM_Utils_Array::value('custom', $params))) {
1853 foreach ($params['custom'] as $k => $v) {
1854 unset($params['custom'][$k]['id']);
1855 }
1856 }
1857 if (!isset($params['membership_type_id'])) {
1858 $params['membership_type_id'] = $membership->membership_type_id;
1859 }
1860
1861 // max_related should be set in the parent membership
1862 unset($params['max_related']);
1863 // Number of inherited memberships available - NULL is interpreted as unlimited, '0' as none
1864 $available = ($membership->max_related == NULL ? PHP_INT_MAX : $membership->max_related);
1865 $queue = array(); // will be used to queue potential memberships to be created
1866
1867 foreach ($relatedContacts as $contactId => $relationshipStatus) {
1868 //use existing membership record.
1869 $relMembership = new CRM_Member_DAO_Membership();
1870 $relMembership->contact_id = $contactId;
1871 $relMembership->owner_membership_id = $membership->id;
1872 $relMemIds = array();
1873 if ($relMembership->find(TRUE)) {
1874 $params['id'] = $relMemIds['membership'] = $relMembership->id;
1875 }
1876 $params['contact_id'] = $contactId;
1877 $params['owner_membership_id'] = $membership->id;
1878
1879 // set status_id as it might have been changed for
1880 // past relationship
1881 $params['status_id'] = $membership->status_id;
1882
1883 if ($deceasedStatusId &&
1884 CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $contactId, 'is_deceased')
1885 ) {
1886 $params['status_id'] = $deceasedStatusId;
1887 }
1888 elseif ((CRM_Utils_Array::value('action', $params) & CRM_Core_Action::UPDATE) &&
1889 ($relationshipStatus == CRM_Contact_BAO_Relationship::PAST)
1890 ) {
1891 $params['status_id'] = $expiredStatusId;
1892 }
1893
1894 //don't calculate status again in create( );
1895 $params['skipStatusCal'] = TRUE;
1896
1897 //do create activity if we changed status.
1898 if ($params['status_id'] != $relMembership->status_id) {
1899 $params['createActivity'] = TRUE;
1900 }
1901
1902 // we should not created contribution record for related contacts, CRM-3371
1903 unset($params['contribution_status_id']);
1904
1905 if (($params['status_id'] == $deceasedStatusId) || ($params['status_id'] == $expiredStatusId)) {
1906 // related membership is not active so does not count towards maximum
1907 CRM_Member_BAO_Membership::create($params, $relMemIds);
1908 }
1909 else {
1910 // related membership already exists, so this is just an update
1911 if (isset($params['id'])) {
1912 if ($available > 0) {
1913 CRM_Member_BAO_Membership::create($params, $relMemIds);
1914 $available--;
1915 }
1916 else {
1917 // we have run out of inherited memberships, so delete extras
1918 self::deleteMembership($params['id']);
1919 }
1920 // we need to first check if there will remain inherited memberships, so queue it up
1921 }
1922 else {
1923 $queue[] = $params;
1924 }
1925 }
1926 }
1927 // now go over the queue and create any available related memberships
1928 reset($queue);
1929 while (($available > 0) && ($params = each($queue))) {
1930 CRM_Member_BAO_Membership::create($params['value'], $relMemIds);
1931 $available--;
1932 }
1933 }
1934 }
1935
1936 /**
1937 * Delete the record that are associated with this Membership Payment.
1938 *
1939 * @param int $membershipId
1940 *
1941 * @return object
1942 * $membershipPayment deleted membership payment object
1943 */
1944 public static function deleteMembershipPayment($membershipId) {
1945
1946 $membershipPayment = new CRM_Member_DAO_MembershipPayment();
1947 $membershipPayment->membership_id = $membershipId;
1948 $membershipPayment->find();
1949
1950 while ($membershipPayment->fetch()) {
1951 CRM_Contribute_BAO_Contribution::deleteContribution($membershipPayment->contribution_id);
1952 CRM_Utils_Hook::pre('delete', 'MembershipPayment', $membershipPayment->id, $membershipPayment);
1953 $membershipPayment->delete();
1954 CRM_Utils_Hook::post('delete', 'MembershipPayment', $membershipPayment->id, $membershipPayment);
1955 }
1956 return $membershipPayment;
1957 }
1958
1959 /**
1960 * @param CRM_Core_Form $form
1961 * @param int $membershipTypeID
1962 *
1963 * @return array
1964 */
1965 public static function &buildMembershipTypeValues(&$form, $membershipTypeID = NULL) {
1966 $whereClause = " WHERE domain_id = " . CRM_Core_Config::domainID();
1967
1968 if (is_array($membershipTypeID)) {
1969 $allIDs = implode(',', $membershipTypeID);
1970 $whereClause .= " AND id IN ( $allIDs )";
1971 }
1972 elseif (is_numeric($membershipTypeID) &&
1973 $membershipTypeID > 0
1974 ) {
1975 $whereClause .= " AND id = $membershipTypeID";
1976 }
1977
1978 $query = "
1979 SELECT *
1980 FROM civicrm_membership_type
1981 $whereClause;
1982 ";
1983 $dao = CRM_Core_DAO::executeQuery($query);
1984
1985 $membershipTypeValues = array();
1986 $membershipTypeFields = array(
1987 'id',
1988 'minimum_fee',
1989 'name',
1990 'is_active',
1991 'description',
1992 'financial_type_id',
1993 'auto_renew',
1994 'member_of_contact_id',
1995 'relationship_type_id',
1996 'relationship_direction',
1997 'max_related',
1998 );
1999
2000 while ($dao->fetch()) {
2001 $membershipTypeValues[$dao->id] = array();
2002 foreach ($membershipTypeFields as $mtField) {
2003 $membershipTypeValues[$dao->id][$mtField] = $dao->$mtField;
2004 }
2005 }
2006 $dao->free();
2007
2008 CRM_Utils_Hook::membershipTypeValues($form, $membershipTypeValues);
2009
2010 if (is_numeric($membershipTypeID) &&
2011 $membershipTypeID > 0
2012 ) {
2013 return $membershipTypeValues[$membershipTypeID];
2014 }
2015 else {
2016 return $membershipTypeValues;
2017 }
2018 }
2019
2020 /**
2021 * Get membership record count for a Contact.
2022 *
2023 * @param int $contactID
2024 * @param bool $activeOnly
2025 *
2026 * @return null|string
2027 */
2028 public static function getContactMembershipCount($contactID, $activeOnly = FALSE) {
2029 $select = "SELECT count(*) FROM civicrm_membership ";
2030 $where = "WHERE civicrm_membership.contact_id = {$contactID} AND civicrm_membership.is_test = 0 ";
2031
2032 // CRM-6627, all status below 3 (active, pending, grace) are considered active
2033 if ($activeOnly) {
2034 $select .= " INNER JOIN civicrm_membership_status ON civicrm_membership.status_id = civicrm_membership_status.id ";
2035 $where .= " and civicrm_membership_status.is_current_member = 1";
2036 }
2037
2038 $query = $select . $where;
2039 return CRM_Core_DAO::singleValueQuery($query);
2040 }
2041
2042 /**
2043 * Check whether payment processor supports
2044 * cancellation of membership subscription
2045 *
2046 * @param int $mid
2047 * Membership id.
2048 *
2049 * @param bool $isNotCancelled
2050 *
2051 * @return bool
2052 */
2053 public static function isCancelSubscriptionSupported($mid, $isNotCancelled = TRUE) {
2054 $cacheKeyString = "$mid";
2055 $cacheKeyString .= $isNotCancelled ? '_1' : '_0';
2056
2057 static $supportsCancel = array();
2058
2059 if (!array_key_exists($cacheKeyString, $supportsCancel)) {
2060 $supportsCancel[$cacheKeyString] = FALSE;
2061 $isCancelled = FALSE;
2062
2063 if ($isNotCancelled) {
2064 $isCancelled = self::isSubscriptionCancelled($mid);
2065 }
2066
2067 $paymentObject = CRM_Financial_BAO_PaymentProcessor::getProcessorForEntity($mid, 'membership', 'obj');
2068 if (!empty($paymentObject)) {
2069 $supportsCancel[$cacheKeyString] = $paymentObject->isSupported('cancelSubscription') && !$isCancelled;
2070 }
2071 }
2072 return $supportsCancel[$cacheKeyString];
2073 }
2074
2075 /**
2076 * Check whether subscription is already cancelled.
2077 *
2078 * @param int $mid
2079 * Membership id.
2080 *
2081 * @return string
2082 * contribution status
2083 */
2084 public static function isSubscriptionCancelled($mid) {
2085 $sql = "
2086 SELECT cr.contribution_status_id
2087 FROM civicrm_contribution_recur cr
2088 LEFT JOIN civicrm_membership mem ON ( cr.id = mem.contribution_recur_id )
2089 WHERE mem.id = %1 LIMIT 1";
2090 $params = array(1 => array($mid, 'Integer'));
2091 $statusId = CRM_Core_DAO::singleValueQuery($sql, $params);
2092 $status = CRM_Contribute_PseudoConstant::contributionStatus($statusId, 'name');
2093 if ($status == 'Cancelled') {
2094 return TRUE;
2095 }
2096 return FALSE;
2097 }
2098
2099 /**
2100 * Get membership joins for a specified membership
2101 * type. Specifically, retrieves a count of still current memberships whose
2102 * join_date and start_date are within a specified date range. Dates match
2103 * the pattern "yyyy-mm-dd".
2104 *
2105 * @param int $membershipTypeId
2106 * Membership type id.
2107 * @param int $startDate
2108 * Date on which to start counting.
2109 * @param int $endDate
2110 * Date on which to end counting.
2111 * @param bool|int $isTest if true, membership is for a test site
2112 *
2113 * @return int
2114 * the number of members of type $membershipTypeId
2115 * whose join_date is between $startDate and $endDate and
2116 * whose start_date is between $startDate and $endDate
2117 */
2118 public static function getMembershipJoins($membershipTypeId, $startDate, $endDate, $isTest = 0) {
2119 $testClause = 'membership.is_test = 1';
2120 if (!$isTest) {
2121 $testClause = '( membership.is_test IS NULL OR membership.is_test = 0 )';
2122 }
2123 if (!self::$_signupActType) {
2124 self::_getActTypes();
2125 }
2126
2127 if (!self::$_signupActType) {
2128 return 0;
2129 }
2130
2131 $query = "
2132 SELECT COUNT(DISTINCT membership.id) as member_count
2133 FROM civicrm_membership membership
2134 INNER JOIN civicrm_activity activity ON (activity.source_record_id = membership.id AND activity.activity_type_id = %1)
2135 INNER JOIN civicrm_membership_status status ON ( membership.status_id = status.id AND status.is_current_member = 1 )
2136 INNER JOIN civicrm_contact contact ON ( contact.id = membership.contact_id AND contact.is_deleted = 0 )
2137 WHERE membership.membership_type_id = %2
2138 AND activity.activity_date_time >= '$startDate' AND activity.activity_date_time <= '$endDate 23:59:59'
2139 AND {$testClause}";
2140
2141 $params = array(
2142 1 => array(self::$_signupActType, 'Integer'),
2143 2 => array($membershipTypeId, 'Integer'),
2144 );
2145
2146 $memberCount = CRM_Core_DAO::singleValueQuery($query, $params);
2147
2148 return (int) $memberCount;
2149 }
2150
2151 /**
2152 * Get membership renewals for a specified membership
2153 * type. Specifically, retrieves a count of still current memberships
2154 * whose join_date is before and start_date is within a specified date
2155 * range. Dates match the pattern "yyyy-mm-dd".
2156 *
2157 * @param int $membershipTypeId
2158 * Membership type id.
2159 * @param int $startDate
2160 * Date on which to start counting.
2161 * @param int $endDate
2162 * Date on which to end counting.
2163 * @param bool|int $isTest if true, membership is for a test site
2164 *
2165 * @return int
2166 * returns the number of members of type $membershipTypeId
2167 * whose join_date is before $startDate and
2168 * whose start_date is between $startDate and $endDate
2169 */
2170 public static function getMembershipRenewals($membershipTypeId, $startDate, $endDate, $isTest = 0) {
2171 $testClause = 'membership.is_test = 1';
2172 if (!$isTest) {
2173 $testClause = '( membership.is_test IS NULL OR membership.is_test = 0 )';
2174 }
2175 if (!self::$_renewalActType) {
2176 self::_getActTypes();
2177 }
2178
2179 if (!self::$_renewalActType) {
2180 return 0;
2181 }
2182
2183 $query = "
2184 SELECT COUNT(DISTINCT membership.id) as member_count
2185 FROM civicrm_membership membership
2186 INNER JOIN civicrm_activity activity ON (activity.source_record_id = membership.id AND activity.activity_type_id = %1)
2187 INNER JOIN civicrm_membership_status status ON ( membership.status_id = status.id AND status.is_current_member = 1 )
2188 INNER JOIN civicrm_contact contact ON ( contact.id = membership.contact_id AND contact.is_deleted = 0 )
2189 WHERE membership.membership_type_id = %2
2190 AND activity.activity_date_time >= '$startDate' AND activity.activity_date_time <= '$endDate 23:59:59'
2191 AND {$testClause}";
2192
2193 $params = array(
2194 1 => array(self::$_renewalActType, 'Integer'),
2195 2 => array($membershipTypeId, 'Integer'),
2196 );
2197 $memberCount = CRM_Core_DAO::singleValueQuery($query, $params);
2198
2199 return (int) $memberCount;
2200 }
2201
2202 /**
2203 * Where a second separate financial transaction is supported we will process it here.
2204 *
2205 * @param int $contactID
2206 * @param CRM_Contribute_Form_Contribution_Confirm $form
2207 * @param array $tempParams
2208 * @param $isTest
2209 * @param array $lineItems
2210 * @param $minimumFee
2211 * @param int $financialTypeID
2212 *
2213 * @throws CRM_Core_Exception
2214 * @throws Exception
2215 * @return CRM_Contribute_BAO_Contribution
2216 */
2217 public static function processSecondaryFinancialTransaction($contactID, &$form, $tempParams, $isTest, $lineItems, $minimumFee, $financialTypeID) {
2218 $financialType = new CRM_Financial_DAO_FinancialType();
2219 $financialType->id = $financialTypeID;
2220 if (!$financialType->find(TRUE)) {
2221 CRM_Core_Error::fatal(ts("Could not find a system table"));
2222 }
2223 $tempParams['amount'] = $minimumFee;
2224 $tempParams['invoiceID'] = md5(uniqid(rand(), TRUE));
2225
2226 $result = NULL;
2227 if ($form->_values['is_monetary'] && !$form->_params['is_pay_later'] && $minimumFee > 0.0) {
2228 $payment = CRM_Core_Payment::singleton($form->_mode, $form->_paymentProcessor, $form);
2229
2230 if ($form->_contributeMode == 'express') {
2231 $result = $payment->doExpressCheckout($tempParams);
2232 if (is_a($result, 'CRM_Core_Error')) {
2233 throw new CRM_Core_Exception(CRM_Core_Error::getMessages($result));
2234 }
2235 }
2236 else {
2237 $result = $payment->doPayment($tempParams, 'contribute');
2238 }
2239 }
2240
2241 //assign receive date when separate membership payment
2242 //and contribution amount not selected.
2243 if ($form->_amount == 0) {
2244 $now = date('YmdHis');
2245 $form->_params['receive_date'] = $now;
2246 $receiveDate = CRM_Utils_Date::mysqlToIso($now);
2247 $form->set('params', $form->_params);
2248 $form->assign('receive_date', $receiveDate);
2249 }
2250
2251 $form->set('membership_trx_id', $result['trxn_id']);
2252 $form->set('membership_amount', $minimumFee);
2253
2254 $form->assign('membership_trx_id', $result['trxn_id']);
2255 $form->assign('membership_amount', $minimumFee);
2256
2257 // we don't need to create the user twice, so lets disable cms_create_account
2258 // irrespective of the value, CRM-2888
2259 $tempParams['cms_create_account'] = 0;
2260
2261 //CRM-16165, scenarios are
2262 // 1) If contribution is_pay_later and if contribution amount is > 0.0 we set pending = TRUE, vice-versa FALSE
2263 // 2) If not pay later but auto-renewal membership is chosen then pending = TRUE as it later triggers
2264 // pending recurring contribution, vice-versa FALSE
2265 $pending = $form->_params['is_pay_later'] ? (($minimumFee > 0.0) ? TRUE : FALSE) : (!empty($form->_params['auto_renew']) ? TRUE : FALSE);
2266
2267 //set this variable as we are not creating pledge for
2268 //separate membership payment contribution.
2269 //so for differentiating membership contribution from
2270 //main contribution.
2271 $form->_params['separate_membership_payment'] = 1;
2272 $membershipContribution = CRM_Contribute_Form_Contribution_Confirm::processContribution($form,
2273 $tempParams,
2274 $result,
2275 $contactID,
2276 $financialType,
2277 $pending,
2278 TRUE,
2279 $isTest,
2280 $lineItems
2281 );
2282 return $membershipContribution;
2283 }
2284
2285 /**
2286 * Create linkages between membership & contribution - note this is the wrong place for this code but this is a
2287 * refactoring step. This should be BAO functionality
2288 * @param $membership
2289 * @param $membershipContribution
2290 */
2291 public static function linkMembershipPayment($membership, $membershipContribution) {
2292 CRM_Member_BAO_MembershipPayment::create(array(
2293 'membership_id' => $membership->id,
2294 'contribution_id' => $membershipContribution->id,
2295 ));
2296 }
2297
2298 /**
2299 * @param array $membershipParams
2300 * @param int $contactID
2301 * @param $customFieldsFormatted
2302 * @param int $membershipID
2303 * @param $memType
2304 * @param $isTest
2305 * @param $numTerms
2306 * @param $membershipContribution
2307 * @param CRM_Core_Form $form
2308 *
2309 * @return array
2310 */
2311 public static function createOrRenewMembership($membershipParams, $contactID, $customFieldsFormatted, $membershipID, $memType, $isTest, $numTerms, $membershipContribution, &$form) {
2312 if (!empty($membershipContribution)) {
2313 $pending = ($membershipContribution->contribution_status_id == 2) ? TRUE : FALSE;
2314 }
2315 $membership = self::renewMembershipFormWrapper($contactID, $memType,
2316 $isTest, $form, NULL,
2317 CRM_Utils_Array::value('cms_contactID', $membershipParams),
2318 $customFieldsFormatted, $numTerms,
2319 $membershipID, $pending
2320 );
2321
2322 if (!empty($membershipContribution)) {
2323 // update recurring id for membership record
2324 self::updateRecurMembership($membership, $membershipContribution);
2325
2326 self::linkMembershipPayment($membership, $membershipContribution);
2327 }
2328 return $membership;
2329 }
2330
2331 /**
2332 * Turn array of errors into message string.
2333 *
2334 * @param array $errors
2335 *
2336 * @return string
2337 */
2338 public static function compileErrorMessage($errors) {
2339 foreach ($errors as $error) {
2340 if (is_string($error)) {
2341 $message[] = $error;
2342 }
2343 }
2344 return ts('Payment Processor Error message') . ': ' . implode('<br/>', $message);
2345 }
2346
2347 /**
2348 * Extract relevant values from the form so we can separate form logic from BAO logcis.
2349 *
2350 * @param CRM_Core_Form $form
2351 * @param $changeToday
2352 * @param $membershipTypeDetails
2353 *
2354 * @return array
2355 */
2356 public static function extractFormValues($form, $changeToday, $membershipTypeDetails, $pending = FALSE) {
2357 //@todo this is a BAO function & should not inspect the form - the form should do this
2358 // & pass required params to the BAO
2359 if (CRM_Utils_Array::value('minimum_fee', $membershipTypeDetails) > 0.0) {
2360 if (((isset($form->_contributeMode) && $form->_contributeMode == 'notify') || !empty($form->_params['is_pay_later'])
2361 ) &&
2362 (($form->_values['is_monetary'] && $form->_amount > 0.0) ||
2363 CRM_Utils_Array::value('record_contribution', $form->_params)
2364 )
2365 ) {
2366 $pending = TRUE;
2367 }
2368 }
2369 $contributionRecurID = isset($form->_params['contributionRecurID']) ? $form->_params['contributionRecurID'] : NULL;
2370
2371 //we renew expired membership, CRM-6277
2372 if (!$changeToday) {
2373 if ($form->get('renewalDate')) {
2374 $changeToday = $form->get('renewalDate');
2375 }
2376 elseif (get_class($form) == 'CRM_Contribute_Form_Contribution_Confirm') {
2377 $changeToday = date('YmdHis');
2378 }
2379 }
2380
2381 $membershipSource = NULL;
2382 if (!empty($form->_params['membership_source'])) {
2383 $membershipSource = $form->_params['membership_source'];
2384 }
2385 elseif (isset($form->_values['title']) && !empty($form->_values['title'])) {
2386 $membershipSource = ts('Online Contribution:') . ' ' . $form->_values['title'];
2387 }
2388 $isPayLater = NULL;
2389 if (isset($form->_params)) {
2390 $isPayLater = CRM_Utils_Array::value('is_pay_later', $form->_params);
2391 }
2392 $campaignId = NULL;
2393 if (isset($form->_values) && is_array($form->_values) && !empty($form->_values)) {
2394 $campaignId = CRM_Utils_Array::value('campaign_id', $form->_params);
2395 if (!array_key_exists('campaign_id', $form->_params)) {
2396 $campaignId = CRM_Utils_Array::value('campaign_id', $form->_values);
2397 }
2398 }
2399 return array($pending, $contributionRecurID, $changeToday, $membershipSource, $isPayLater, $campaignId);
2400 }
2401
2402 /**
2403 * @param int $contactID
2404 * @param int $membershipTypeID
2405 * @param bool $is_test
2406 * @param $changeToday
2407 * @param int $modifiedID
2408 * @param $customFieldsFormatted
2409 * @param $numRenewTerms
2410 * @param int $membershipID
2411 * @param $pending
2412 * @param $allStatus
2413 * @param array $membershipTypeDetails
2414 * @param int $contributionRecurID
2415 * @param $format
2416 * @param $membershipSource
2417 * @param $ids
2418 * @param $statusFormat
2419 * @param $isPayLater
2420 * @param int $campaignId
2421 *
2422 * @throws CRM_Core_Exception
2423 * @return array
2424 */
2425 public static function renewMembership($contactID, $membershipTypeID, $is_test, $changeToday, $modifiedID, $customFieldsFormatted, $numRenewTerms, $membershipID, $pending, $allStatus, $membershipTypeDetails, $contributionRecurID, $format, $membershipSource, $ids, $statusFormat, $isPayLater, $campaignId) {
2426 $renewalMode = $updateStatusId = FALSE;
2427 $dates = array();
2428 // CRM-7297 - allow membership type to be be changed during renewal so long as the parent org of new membershipType
2429 // is the same as the parent org of an existing membership of the contact
2430 $currentMembership = CRM_Member_BAO_Membership::getContactMembership($contactID, $membershipTypeID,
2431 $is_test, $membershipID, TRUE
2432 );
2433 if ($currentMembership) {
2434 $activityType = 'Membership Renewal';
2435 $renewalMode = TRUE;
2436
2437 // Do NOT do anything.
2438 //1. membership with status : PENDING/CANCELLED (CRM-2395)
2439 //2. Paylater/IPN renew. CRM-4556.
2440 if ($pending || in_array($currentMembership['status_id'], array(
2441 array_search('Pending', $allStatus),
2442 // CRM-15475
2443 array_search('Cancelled', CRM_Member_PseudoConstant::membershipStatus(NULL, " name = 'Cancelled' ", 'name', FALSE, TRUE)),
2444 ))
2445 ) {
2446 $membership = new CRM_Member_DAO_Membership();
2447 $membership->id = $currentMembership['id'];
2448 $membership->find(TRUE);
2449
2450 // CRM-8141 create a membership_log entry so that we will know the membership_type_id to change to when payment completed
2451 $format = '%Y%m%d';
2452 // note that we are logging the requested new membership_type_id that may be different than current membership_type_id
2453 // it will be used when payment is received to update the membership_type_id to what was paid for
2454 $logParams = array(
2455 'membership_id' => $membership->id,
2456 'status_id' => $membership->status_id,
2457 'start_date' => CRM_Utils_Date::customFormat(
2458 $membership->start_date,
2459 $format
2460 ),
2461 'end_date' => CRM_Utils_Date::customFormat(
2462 $membership->end_date,
2463 $format
2464 ),
2465 'modified_date' => CRM_Utils_Date::customFormat(
2466 date('Ymd'),
2467 $format
2468 ),
2469 'membership_type_id' => $membershipTypeID,
2470 'max_related' => !empty($membershipTypeDetails['max_related']) ? $membershipTypeDetails['max_related'] : NULL,
2471 );
2472 $session = CRM_Core_Session::singleton();
2473 // If we have an authenticated session, set modified_id to that user's contact_id, else set to membership.contact_id
2474 if ($session->get('userID')) {
2475 $logParams['modified_id'] = $session->get('userID');
2476 }
2477 else {
2478 $logParams['modified_id'] = $membership->contact_id;
2479 }
2480 CRM_Member_BAO_MembershipLog::add($logParams, CRM_Core_DAO::$_nullArray);
2481
2482 if (!empty($contributionRecurID)) {
2483 CRM_Core_DAO::setFieldValue('CRM_Member_DAO_Membership', $membership->id,
2484 'contribution_recur_id', $contributionRecurID
2485 );
2486 }
2487
2488 return array($membership, $renewalMode, $dates);
2489 }
2490
2491 // Check and fix the membership if it is STALE
2492 self::fixMembershipStatusBeforeRenew($currentMembership, $changeToday);
2493
2494 // Now Renew the membership
2495 if (!$currentMembership['is_current_member']) {
2496 // membership is not CURRENT
2497
2498 // CRM-7297 Membership Upsell - calculate dates based on new membership type
2499 $dates = CRM_Member_BAO_MembershipType::getRenewalDatesForMembershipType($currentMembership['id'],
2500 $changeToday,
2501 $membershipTypeID,
2502 $numRenewTerms
2503 );
2504
2505 $currentMembership['join_date'] = CRM_Utils_Date::customFormat($currentMembership['join_date'], $format);
2506 $currentMembership['start_date'] = CRM_Utils_Array::value('start_date', $dates);
2507 $currentMembership['end_date'] = CRM_Utils_Array::value('end_date', $dates);
2508 $currentMembership['is_test'] = $is_test;
2509
2510 if (!empty($membershipSource)) {
2511 $currentMembership['source'] = $membershipSource;
2512 }
2513 else {
2514 $currentMembership['source'] = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_Membership',
2515 $currentMembership['id'],
2516 'source'
2517 );
2518 }
2519
2520 if (!empty($currentMembership['id'])) {
2521 $ids['membership'] = $currentMembership['id'];
2522 }
2523 $memParams = $currentMembership;
2524 $memParams['membership_type_id'] = $membershipTypeID;
2525
2526 //set the log start date.
2527 $memParams['log_start_date'] = CRM_Utils_Date::customFormat($dates['log_start_date'], $format);
2528 }
2529 else {
2530
2531 // CURRENT Membership
2532 $membership = new CRM_Member_DAO_Membership();
2533 $membership->id = $currentMembership['id'];
2534 $membership->find(TRUE);
2535 // CRM-7297 Membership Upsell - calculate dates based on new membership type
2536 $dates = CRM_Member_BAO_MembershipType::getRenewalDatesForMembershipType($membership->id,
2537 $changeToday,
2538 $membershipTypeID,
2539 $numRenewTerms
2540 );
2541
2542 // Insert renewed dates for CURRENT membership
2543 $memParams = array();
2544 $memParams['join_date'] = CRM_Utils_Date::isoToMysql($membership->join_date);
2545 $memParams['start_date'] = CRM_Utils_Date::isoToMysql($membership->start_date);
2546 $memParams['end_date'] = CRM_Utils_Array::value('end_date', $dates);
2547 $memParams['membership_type_id'] = $membershipTypeID;
2548
2549 //set the log start date.
2550 $memParams['log_start_date'] = CRM_Utils_Date::customFormat($dates['log_start_date'], $format);
2551 if (empty($membership->source)) {
2552 if (!empty($membershipSource)) {
2553 $memParams['source'] = $membershipSource;
2554 }
2555 else {
2556 $memParams['source'] = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_Membership',
2557 $currentMembership['id'],
2558 'source'
2559 );
2560 }
2561 }
2562
2563 if (!empty($currentMembership['id'])) {
2564 $ids['membership'] = $currentMembership['id'];
2565 }
2566 }
2567 //CRM-4555
2568 if ($pending) {
2569 $updateStatusId = array_search('Pending', $allStatus);
2570 }
2571 }
2572 else {
2573 // NEW Membership
2574
2575 $activityType = 'Membership Signup';
2576 $memParams = array(
2577 'contact_id' => $contactID,
2578 'membership_type_id' => $membershipTypeID,
2579 );
2580
2581 if (!$pending) {
2582 $dates = CRM_Member_BAO_MembershipType::getDatesForMembershipType($membershipTypeID, NULL, NULL, NULL, $numRenewTerms);
2583
2584 $memParams['join_date'] = CRM_Utils_Array::value('join_date', $dates);
2585 $memParams['start_date'] = CRM_Utils_Array::value('start_date', $dates);
2586 $memParams['end_date'] = CRM_Utils_Array::value('end_date', $dates);
2587
2588 $status = CRM_Member_BAO_MembershipStatus::getMembershipStatusByDate(CRM_Utils_Date::customFormat($dates['start_date'],
2589 $statusFormat
2590 ),
2591 CRM_Utils_Date::customFormat($dates['end_date'],
2592 $statusFormat
2593 ),
2594 CRM_Utils_Date::customFormat($dates['join_date'],
2595 $statusFormat
2596 ),
2597 'today',
2598 TRUE,
2599 $membershipTypeID,
2600 $memParams
2601 );
2602 $updateStatusId = CRM_Utils_Array::value('id', $status);
2603 }
2604 else {
2605 // if IPN/Pay-Later set status to: PENDING
2606 $updateStatusId = array_search('Pending', $allStatus);
2607 }
2608
2609 if (!empty($membershipSource)) {
2610 $memParams['source'] = $membershipSource;
2611 }
2612 $memParams['contribution_recur_id'] = $contributionRecurID;
2613
2614 $memParams['is_test'] = $is_test;
2615 $memParams['is_pay_later'] = $isPayLater;
2616 }
2617
2618 //CRM-4555
2619 //if we decided status here and want to skip status
2620 //calculation in create( ); then need to pass 'skipStatusCal'.
2621 if ($updateStatusId) {
2622 $memParams['status_id'] = $updateStatusId;
2623 $memParams['skipStatusCal'] = TRUE;
2624 }
2625
2626 //since we are renewing,
2627 //make status override false.
2628 $memParams['is_override'] = FALSE;
2629
2630 //CRM-4027, create log w/ individual contact.
2631 if ($modifiedID) {
2632 $ids['userId'] = $modifiedID;
2633 $memParams['is_for_organization'] = TRUE;
2634 }
2635 else {
2636 $ids['userId'] = $contactID;
2637 }
2638
2639 //inherit campaign from contrib page.
2640 if (isset($campaignId)) {
2641 $memParams['campaign_id'] = $campaignId;
2642 }
2643
2644 $memParams['custom'] = $customFieldsFormatted;
2645 $membership = self::create($memParams, $ids, FALSE, $activityType);
2646
2647 // not sure why this statement is here, seems quite odd :( - Lobo: 12/26/2010
2648 // related to: http://forum.civicrm.org/index.php/topic,11416.msg49072.html#msg49072
2649 $membership->find(TRUE);
2650
2651 return array($membership, $renewalMode, $dates);
2652 }
2653
2654 /**
2655 * Process price set and line items.
2656 *
2657 * @param int $membershipId
2658 * @param $lineItem
2659 *
2660 * @return void
2661 */
2662 public function processPriceSet($membershipId, $lineItem) {
2663 //FIXME : need to move this too
2664 if (!$membershipId || !is_array($lineItem)
2665 || CRM_Utils_System::isNull($lineItem)
2666 ) {
2667 return;
2668 }
2669
2670 foreach ($lineItem as $priceSetId => $values) {
2671 if (!$priceSetId) {
2672 continue;
2673 }
2674 foreach ($values as $line) {
2675 $line['entity_table'] = 'civicrm_membership';
2676 $line['entity_id'] = $membershipId;
2677 CRM_Price_BAO_LineItem::create($line);
2678 }
2679 }
2680 }
2681
2682 /**
2683 * Retrieve the contribution id for the associated Membership id.
2684 * @todo we should get this off the line item
2685 *
2686 * @param int $membershipId
2687 * Membership id.
2688 *
2689 * @return int
2690 * contribution id
2691 */
2692 public static function getMembershipContributionId($membershipId) {
2693
2694 $membershipPayment = new CRM_Member_DAO_MembershipPayment();
2695 $membershipPayment->membership_id = $membershipId;
2696 if ($membershipPayment->find(TRUE)) {
2697 return $membershipPayment->contribution_id;
2698 }
2699 return NULL;
2700 }
2701
2702 /**
2703 * The function checks and updates the status of all membership records for a given domain using the
2704 * calc_membership_status and update_contact_membership APIs.
2705 *
2706 * IMPORTANT:
2707 * Sending renewal reminders has been migrated from this job to the Scheduled Reminders function as of 4.3.
2708 *
2709 * @return array
2710 */
2711 public static function updateAllMembershipStatus() {
2712
2713 //get all active statuses of membership, CRM-3984
2714 $allStatus = CRM_Member_PseudoConstant::membershipStatus();
2715 $statusLabels = CRM_Member_PseudoConstant::membershipStatus(NULL, NULL, 'label');
2716 $allTypes = CRM_Member_PseudoConstant::membershipType();
2717
2718 // get only memberships with active membership types
2719 $query = "
2720 SELECT civicrm_membership.id as membership_id,
2721 civicrm_membership.is_override as is_override,
2722 civicrm_membership.membership_type_id as membership_type_id,
2723 civicrm_membership.status_id as status_id,
2724 civicrm_membership.join_date as join_date,
2725 civicrm_membership.start_date as start_date,
2726 civicrm_membership.end_date as end_date,
2727 civicrm_membership.source as source,
2728 civicrm_contact.id as contact_id,
2729 civicrm_contact.is_deceased as is_deceased,
2730 civicrm_membership.owner_membership_id as owner_membership_id,
2731 civicrm_membership.contribution_recur_id as recur_id
2732 FROM civicrm_membership
2733 INNER JOIN civicrm_contact ON ( civicrm_membership.contact_id = civicrm_contact.id )
2734 INNER JOIN civicrm_membership_type ON
2735 (civicrm_membership.membership_type_id = civicrm_membership_type.id AND civicrm_membership_type.is_active = 1)
2736 WHERE civicrm_membership.is_test = 0";
2737
2738 $params = array();
2739 $dao = CRM_Core_DAO::executeQuery($query, $params);
2740
2741 $processCount = 0;
2742 $updateCount = 0;
2743
2744 $smarty = CRM_Core_Smarty::singleton();
2745
2746 while ($dao->fetch()) {
2747 // echo ".";
2748 $processCount++;
2749
2750 // Put common parameters into array for easy access
2751 $memberParams = array(
2752 'id' => $dao->membership_id,
2753 'status_id' => $dao->status_id,
2754 'contact_id' => $dao->contact_id,
2755 'membership_type_id' => $dao->membership_type_id,
2756 'membership_type' => $allTypes[$dao->membership_type_id],
2757 'join_date' => $dao->join_date,
2758 'start_date' => $dao->start_date,
2759 'end_date' => $dao->end_date,
2760 'source' => $dao->source,
2761 'skipStatusCal' => TRUE,
2762 'skipRecentView' => TRUE,
2763 );
2764
2765 $smarty->assign_by_ref('memberParams', $memberParams);
2766
2767 //update membership record to Deceased if contact is deceased
2768 if ($dao->is_deceased) {
2769 // check for 'Deceased' membership status, CRM-5636
2770 $deceaseStatusId = array_search('Deceased', $allStatus);
2771 if (!$deceaseStatusId) {
2772 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'))));
2773 }
2774
2775 //process only when status change.
2776 if ($dao->status_id != $deceaseStatusId) {
2777 //take all params that need to save.
2778 $deceasedMembership = $memberParams;
2779 $deceasedMembership['status_id'] = $deceaseStatusId;
2780 $deceasedMembership['createActivity'] = TRUE;
2781 $deceasedMembership['version'] = 3;
2782
2783 //since there is change in status.
2784 $statusChange = array('status_id' => $deceaseStatusId);
2785 $smarty->append_by_ref('memberParams', $statusChange, TRUE);
2786 unset(
2787 $deceasedMembership['contact_id'],
2788 $deceasedMembership['membership_type_id'],
2789 $deceasedMembership['membership_type'],
2790 $deceasedMembership['join_date'],
2791 $deceasedMembership['start_date'],
2792 $deceasedMembership['end_date'],
2793 $deceasedMembership['source']
2794 );
2795
2796 //process membership record.
2797 civicrm_api('membership', 'create', $deceasedMembership);
2798 }
2799 continue;
2800 }
2801
2802 //we fetch related, since we need to check for deceased
2803 //now further processing is handle w/ main membership record.
2804 if ($dao->owner_membership_id) {
2805 continue;
2806 }
2807
2808 //update membership records where status is NOT - Pending OR Cancelled.
2809 //as well as membership is not override.
2810 //skipping Expired membership records -> reduced extra processing( kiran )
2811 if (!$dao->is_override &&
2812 !in_array($dao->status_id, array(
2813 array_search('Pending', $allStatus),
2814 // CRM-15475
2815 array_search(
2816 'Cancelled',
2817 CRM_Member_PseudoConstant::membershipStatus(NULL, " name = 'Cancelled' ", 'name', FALSE, TRUE)
2818 ),
2819 array_search('Expired', $allStatus),
2820 ))
2821 ) {
2822
2823 // CRM-7248: added excludeIsAdmin param to the following fn call to prevent moving to admin statuses
2824 //get the membership status as per id.
2825 $newStatus = civicrm_api('membership_status', 'calc',
2826 array(
2827 'membership_id' => $dao->membership_id,
2828 'version' => 3,
2829 'ignore_admin_only' => FALSE,
2830 ), TRUE
2831 );
2832 $statusId = CRM_Utils_Array::value('id', $newStatus);
2833
2834 //process only when status change.
2835 if ($statusId &&
2836 $statusId != $dao->status_id
2837 ) {
2838 //take all params that need to save.
2839 $memParams = $memberParams;
2840 $memParams['status_id'] = $statusId;
2841 $memParams['createActivity'] = TRUE;
2842 $memParams['version'] = 3;
2843
2844 // Unset columns which should remain unchanged from their current saved
2845 // values. This avoids race condition in which these values may have
2846 // been changed by other processes.
2847 unset(
2848 $memParams['contact_id'],
2849 $memParams['membership_type_id'],
2850 $memParams['membership_type'],
2851 $memParams['join_date'],
2852 $memParams['start_date'],
2853 $memParams['end_date'],
2854 $memParams['source']
2855 );
2856 //since there is change in status.
2857 $statusChange = array('status_id' => $statusId);
2858 $smarty->append_by_ref('memberParams', $statusChange, TRUE);
2859
2860 //process member record.
2861 civicrm_api('membership', 'create', $memParams);
2862 $updateCount++;
2863 }
2864 }
2865 }
2866 $result['is_error'] = 0;
2867 $result['messages'] = ts('Processed %1 membership records. Updated %2 records.', array(
2868 1 => $processCount,
2869 2 => $updateCount,
2870 ));
2871 return $result;
2872 }
2873
2874 /**
2875 * Returns the membership types for a particular contact
2876 * who has lifetime membership without end date.
2877 *
2878 * @param int $contactID
2879 * @param bool $isTest
2880 * @param bool $onlyLifeTime
2881 *
2882 * @return array
2883 */
2884 public static function getAllContactMembership($contactID, $isTest = FALSE, $onlyLifeTime = FALSE) {
2885 $contactMembershipType = array();
2886 if (!$contactID) {
2887 return $contactMembershipType;
2888 }
2889
2890 $dao = new CRM_Member_DAO_Membership();
2891 $dao->contact_id = $contactID;
2892 $pendingStatusId = array_search('Pending', CRM_Member_PseudoConstant::membershipStatus());
2893 $dao->whereAdd("status_id != $pendingStatusId");
2894
2895 if ($isTest) {
2896 $dao->is_test = $isTest;
2897 }
2898 else {
2899 $dao->whereAdd('is_test IS NULL OR is_test = 0');
2900 }
2901
2902 if ($onlyLifeTime) {
2903 $dao->whereAdd('end_date IS NULL');
2904 }
2905
2906 $dao->find();
2907 while ($dao->fetch()) {
2908 $membership = array();
2909 CRM_Core_DAO::storeValues($dao, $membership);
2910 $contactMembershipType[$dao->membership_type_id] = $membership;
2911 }
2912 return $contactMembershipType;
2913 }
2914
2915 /**
2916 * Record contribution record associated with membership.
2917 *
2918 * @param array $params
2919 * Array of submitted params.
2920 * @param array $ids
2921 * (param in process of being removed - try to use params) array of ids.
2922 *
2923 * @return CRM_Contribute_BAO_Contribution
2924 */
2925 public static function recordMembershipContribution(&$params, $ids = array()) {
2926 $membershipId = $params['membership_id'];
2927 $contributionParams = array();
2928 $config = CRM_Core_Config::singleton();
2929 $contributionParams['currency'] = $config->defaultCurrency;
2930 $contributionParams['receipt_date'] = (CRM_Utils_Array::value('receipt_date', $params)) ? $params['receipt_date'] : 'null';
2931 $contributionParams['source'] = CRM_Utils_Array::value('contribution_source', $params);
2932 $contributionParams['non_deductible_amount'] = 'null';
2933 $contributionParams['payment_processor'] = CRM_Utils_Array::value('payment_processor_id', $params);
2934 $contributionSoftParams = CRM_Utils_Array::value('soft_credit', $params);
2935 $recordContribution = array(
2936 'contact_id',
2937 'total_amount',
2938 'receive_date',
2939 'financial_type_id',
2940 'payment_instrument_id',
2941 'trxn_id',
2942 'invoice_id',
2943 'is_test',
2944 'contribution_status_id',
2945 'check_number',
2946 'campaign_id',
2947 'is_pay_later',
2948 'membership_id',
2949 'tax_amount',
2950 'skipLineItem',
2951 );
2952 foreach ($recordContribution as $f) {
2953 $contributionParams[$f] = CRM_Utils_Array::value($f, $params);
2954 }
2955
2956 // make entry in batch entity batch table
2957 if (!empty($params['batch_id'])) {
2958 $contributionParams['batch_id'] = $params['batch_id'];
2959 }
2960
2961 if (!empty($params['contribution_contact_id'])) {
2962 // deal with possibility of a different person paying for contribution
2963 $contributionParams['contact_id'] = $params['contribution_contact_id'];
2964 }
2965
2966 if (!empty($params['processPriceSet']) &&
2967 !empty($params['lineItems'])
2968 ) {
2969 $contributionParams['line_item'] = CRM_Utils_Array::value('lineItems', $params, NULL);
2970 }
2971
2972 $contribution = CRM_Contribute_BAO_Contribution::create($contributionParams, $ids);
2973
2974 //CRM-13981, create new soft-credit record as to record payment from different person for this membership
2975 if (!empty($contributionSoftParams)) {
2976 if (!empty($params['batch_id'])) {
2977 foreach ($contributionSoftParams as $contributionSoft) {
2978 $contributionSoft['contribution_id'] = $contribution->id;
2979 $contributionSoft['currency'] = $contribution->currency;
2980 CRM_Contribute_BAO_ContributionSoft::add($contributionSoft);
2981 }
2982 }
2983 else {
2984 $contributionSoftParams['contribution_id'] = $contribution->id;
2985 $contributionSoftParams['currency'] = $contribution->currency;
2986 $contributionSoftParams['amount'] = $contribution->total_amount;
2987 CRM_Contribute_BAO_ContributionSoft::add($contributionSoftParams);
2988 }
2989 }
2990
2991 // store contribution id
2992 $params['contribution_id'] = $contribution->id;
2993
2994 //insert payment record for this membership
2995 if (empty($ids['contribution']) || !empty($params['is_recur'])) {
2996 CRM_Member_BAO_MembershipPayment::create(array(
2997 'membership_id' => $membershipId,
2998 'contribution_id' => $contribution->id,
2999 ));
3000 }
3001 return $contribution;
3002 }
3003
3004 /**
3005 * Record line items for default membership.
3006 *
3007 * @param CRM_Core_Form $qf
3008 * @param array $membershipType
3009 * Array with membership type and organization.
3010 * @param int $priceSetId
3011 *
3012 */
3013 public static function createLineItems(&$qf, $membershipType, &$priceSetId) {
3014 $qf->_priceSetId = $priceSetId = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', 'default_membership_type_amount', 'id', 'name');
3015 if ($priceSetId) {
3016 $qf->_priceSet = $priceSets = current(CRM_Price_BAO_PriceSet::getSetDetail($priceSetId));
3017 }
3018 $editedFieldParams = array(
3019 'price_set_id' => $priceSetId,
3020 'name' => $membershipType[0],
3021 );
3022 $editedResults = array();
3023 CRM_Price_BAO_PriceField::retrieve($editedFieldParams, $editedResults);
3024
3025 if (!empty($editedResults)) {
3026 unset($qf->_priceSet['fields']);
3027 $qf->_priceSet['fields'][$editedResults['id']] = $priceSets['fields'][$editedResults['id']];
3028 unset($qf->_priceSet['fields'][$editedResults['id']]['options']);
3029 $fid = $editedResults['id'];
3030 $editedFieldParams = array(
3031 'price_field_id' => $editedResults['id'],
3032 'membership_type_id' => $membershipType[1],
3033 );
3034 $editedResults = array();
3035 CRM_Price_BAO_PriceFieldValue::retrieve($editedFieldParams, $editedResults);
3036 $qf->_priceSet['fields'][$fid]['options'][$editedResults['id']] = $priceSets['fields'][$fid]['options'][$editedResults['id']];
3037 if (!empty($qf->_params['total_amount'])) {
3038 $qf->_priceSet['fields'][$fid]['options'][$editedResults['id']]['amount'] = $qf->_params['total_amount'];
3039 }
3040 }
3041
3042 $fieldID = key($qf->_priceSet['fields']);
3043 $qf->_params['price_' . $fieldID] = CRM_Utils_Array::value('id', $editedResults);
3044 }
3045
3046 /**
3047 * @todo document me - I seem a bit out of date....
3048 */
3049 public static function _getActTypes() {
3050 $activityTypes = CRM_Core_PseudoConstant::activityType(TRUE, FALSE, FALSE, 'name');
3051 self::$_renewalActType = CRM_Utils_Array::key('Membership Renewal', $activityTypes);
3052 self::$_signupActType = CRM_Utils_Array::key('Membership Signup', $activityTypes);
3053 }
3054
3055 /**
3056 * Get all Cancelled Membership(s) for a contact
3057 *
3058 * @param int $contactID
3059 * Contact id.
3060 * @param bool $isTest
3061 * Mode of payment.
3062 *
3063 * @return array
3064 * Array of membership type
3065 */
3066 public static function getContactsCancelledMembership($contactID, $isTest = FALSE) {
3067 if (!$contactID) {
3068 return array();
3069 }
3070 $query = 'SELECT membership_type_id FROM civicrm_membership WHERE contact_id = %1 AND status_id = %2 AND is_test = %3';
3071 $queryParams = array(
3072 1 => array($contactID, 'Integer'),
3073 2 => array(
3074 // CRM-15475
3075 array_search(
3076 'Cancelled',
3077 CRM_Member_PseudoConstant::membershipStatus(
3078 NULL,
3079 " name = 'Cancelled' ",
3080 'name',
3081 FALSE,
3082 TRUE
3083 )
3084 ),
3085 'Integer',
3086 ),
3087 3 => array($isTest, 'Boolean'),
3088 );
3089
3090 $dao = CRM_Core_DAO::executeQuery($query, $queryParams);
3091 $cancelledMembershipIds = array();
3092 while ($dao->fetch()) {
3093 $cancelledMembershipIds[] = $dao->membership_type_id;
3094 }
3095 return $cancelledMembershipIds;
3096 }
3097
3098 }