Commit | Line | Data |
---|---|---|
6a488035 TO |
1 | <?php |
2 | /* | |
3 | +--------------------------------------------------------------------+ | |
bc77d7c0 | 4 | | Copyright CiviCRM LLC. All rights reserved. | |
6a488035 | 5 | | | |
bc77d7c0 TO |
6 | | This work is published under the GNU AGPLv3 license with some | |
7 | | permitted exceptions and without any warranty. For full license | | |
8 | | and copyright information, see https://civicrm.org/licensing | | |
6a488035 | 9 | +--------------------------------------------------------------------+ |
d25dd0ee | 10 | */ |
6a488035 TO |
11 | |
12 | /** | |
13 | * | |
14 | * @package CRM | |
ca5cec67 | 15 | * @copyright CiviCRM LLC https://civicrm.org/licensing |
6a488035 | 16 | */ |
6552bd20 | 17 | class CRM_Member_BAO_MembershipType extends CRM_Member_DAO_MembershipType implements \Civi\Test\HookInterface { |
6a488035 TO |
18 | |
19 | /** | |
4c429c82 | 20 | * Static holder for the default Membership Type. |
971e129b | 21 | * @var int |
6a488035 | 22 | */ |
971e129b | 23 | public static $_defaultMembershipType = NULL; |
6a488035 | 24 | |
971e129b | 25 | public static $_membershipTypeInfo = []; |
6a488035 | 26 | |
6a488035 | 27 | /** |
4f940304 | 28 | * Retrieve DB object and copy to defaults array. |
6a488035 | 29 | * |
b2363ea8 | 30 | * @param array $params |
4f940304 | 31 | * Array of criteria values. |
b2363ea8 | 32 | * @param array $defaults |
4f940304 | 33 | * Array to be populated with found values. |
6a488035 | 34 | * |
4f940304 CW |
35 | * @return self|null |
36 | * The DAO object, if found. | |
37 | * | |
38 | * @deprecated | |
6a488035 | 39 | */ |
4f940304 CW |
40 | public static function retrieve($params, &$defaults) { |
41 | return self::commonRetrieve(self::class, $params, $defaults); | |
6a488035 TO |
42 | } |
43 | ||
44 | /** | |
4c429c82 | 45 | * Update the is_active flag in the db. |
6a488035 | 46 | * |
b2363ea8 TO |
47 | * @param int $id |
48 | * Id of the database record. | |
49 | * @param bool $is_active | |
50 | * Value we want to set the is_active field. | |
6a488035 | 51 | * |
8a4fede3 | 52 | * @return bool |
53 | * true if we found and updated the object, else false | |
6a488035 | 54 | */ |
00be9182 | 55 | public static function setIsActive($id, $is_active) { |
6a488035 TO |
56 | return CRM_Core_DAO::setFieldValue('CRM_Member_DAO_MembershipType', $id, 'is_active', $is_active); |
57 | } | |
58 | ||
59 | /** | |
4c429c82 | 60 | * Add the membership types. |
6a488035 | 61 | * |
b2363ea8 TO |
62 | * @param array $params |
63 | * Reference array contains the values submitted by the form. | |
64 | * @param array $ids | |
65 | * Array contains the id (deprecated). | |
6a488035 | 66 | * |
000e4e8d MW |
67 | * @return \CRM_Member_DAO_MembershipType |
68 | * @throws \CiviCRM_API3_Exception | |
6a488035 | 69 | */ |
be2fb01f | 70 | public static function add(&$params, $ids = []) { |
000e4e8d MW |
71 | // DEPRECATED Check if membershipType ID was passed in via $ids |
72 | if (empty($params['id'])) { | |
73 | if (isset($ids['membershipType'])) { | |
74 | Civi::log()->warning('Deprecated: Passing membershipType by $ids array in CRM_Member_BAO_MembershipType::add'); | |
75 | } | |
9c1bc317 | 76 | $params['id'] = $ids['membershipType'] ?? NULL; |
000e4e8d MW |
77 | } |
78 | ||
79 | $hook = empty($params['id']) ? 'create' : 'edit'; | |
80 | CRM_Utils_Hook::pre($hook, 'MembershipType', CRM_Utils_Array::value('id', $params), $params); | |
81 | ||
9c1bc317 | 82 | $membershipTypeId = $params['id'] ?? NULL; |
000e4e8d | 83 | |
0c3819e1 EM |
84 | if (!$membershipTypeId && !isset($params['domain_id'])) { |
85 | $params['domain_id'] = CRM_Core_Config::domainID(); | |
36e3b794 | 86 | } |
6a488035 | 87 | |
301db4f8 | 88 | // $previousID is the old organization id for membership type i.e 'member_of_contact_id'. This is used when an organization is changed. |
6a488035 | 89 | $previousID = NULL; |
000e4e8d MW |
90 | if ($membershipTypeId) { |
91 | $previousID = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipType', $membershipTypeId, 'member_of_contact_id'); | |
6a488035 TO |
92 | } |
93 | ||
000e4e8d MW |
94 | // action is taken depending upon the mode |
95 | $membershipType = new CRM_Member_DAO_MembershipType(); | |
96 | $membershipType->copyValues($params); | |
6a488035 | 97 | $membershipType->save(); |
000e4e8d MW |
98 | |
99 | if ($membershipTypeId) { | |
806e9b71 EM |
100 | // on update we may need to retrieve some details for the price field function - otherwise we get e-notices on attempts to retrieve |
101 | // name etc - the presence of previous id tells us this is an update | |
be2fb01f | 102 | $params = array_merge(civicrm_api3('membership_type', 'getsingle', ['id' => $membershipType->id]), $params); |
806e9b71 | 103 | } |
301db4f8 | 104 | self::createMembershipPriceField($params, $previousID, $membershipType->id); |
6a488035 | 105 | // update all price field value for quick config when membership type is set CRM-11718 |
000e4e8d MW |
106 | if ($membershipTypeId) { |
107 | self::updateAllPriceFieldValue($membershipTypeId, $params); | |
6a488035 | 108 | } |
000e4e8d MW |
109 | |
110 | CRM_Utils_Hook::post($hook, 'MembershipType', $membershipType->id, $membershipType); | |
111 | ||
452b9e04 | 112 | self::flush(); |
6a488035 TO |
113 | return $membershipType; |
114 | } | |
115 | ||
452b9e04 | 116 | /** |
4c429c82 EM |
117 | * Flush anywhere that membership types might be cached. |
118 | * | |
452b9e04 EM |
119 | * @throws \CiviCRM_API3_Exception |
120 | */ | |
c8881afb | 121 | public static function flush() { |
452b9e04 | 122 | CRM_Member_PseudoConstant::membershipType(NULL, TRUE); |
be2fb01f CW |
123 | civicrm_api3('membership', 'getfields', ['cache_clear' => 1, 'fieldname' => 'membership_type_id']); |
124 | civicrm_api3('profile', 'getfields', ['action' => 'submit', 'cache_clear' => 1]); | |
eb151aab | 125 | Civi::cache('metadata')->clear(); |
452b9e04 EM |
126 | } |
127 | ||
6a488035 | 128 | /** |
4c429c82 | 129 | * Delete membership Types. |
6a488035 TO |
130 | * |
131 | * @param int $membershipTypeId | |
77b97be7 | 132 | * |
406a87bd | 133 | * @deprecated |
77b97be7 | 134 | * @throws CRM_Core_Exception |
406a87bd | 135 | * @return bool |
6a488035 | 136 | */ |
00be9182 | 137 | public static function del($membershipTypeId) { |
406a87bd CW |
138 | try { |
139 | static::deleteRecord(['id' => $membershipTypeId]); | |
140 | return TRUE; | |
6a488035 | 141 | } |
406a87bd CW |
142 | catch (CRM_Core_Exception $e) { |
143 | return FALSE; | |
6a488035 | 144 | } |
6a488035 TO |
145 | } |
146 | ||
6552bd20 CW |
147 | /** |
148 | * Callback for hook_civicrm_pre(). | |
149 | * @param \Civi\Core\Event\PreEvent $event | |
150 | * @throws CRM_Core_Exception | |
151 | */ | |
152 | public static function on_hook_civicrm_pre(\Civi\Core\Event\PreEvent $event) { | |
153 | if ($event->action === 'delete' && $event->entity === 'RelationshipType') { | |
154 | // When deleting relationship type, remove from membership types | |
155 | $mems = civicrm_api3('MembershipType', 'get', [ | |
156 | 'relationship_type_id' => ['LIKE' => "%{$event->id}%"], | |
157 | 'return' => ['id', 'relationship_type_id', 'relationship_direction'], | |
158 | ]); | |
159 | foreach ($mems['values'] as $membershipType) { | |
160 | $pos = array_search($event->id, $membershipType['relationship_type_id']); | |
161 | // Api call may have returned false positives but currently the relationship_type_id uses | |
162 | // nonstandard serialization which makes anything more accurate impossible. | |
163 | if ($pos !== FALSE) { | |
164 | unset($membershipType['relationship_type_id'][$pos], $membershipType['relationship_direction'][$pos]); | |
165 | civicrm_api3('MembershipType', 'create', $membershipType); | |
166 | } | |
167 | } | |
168 | } | |
406a87bd CW |
169 | if ($event->action === 'delete' && $event->entity === 'MembershipType') { |
170 | // Check dependencies. | |
171 | $check = FALSE; | |
172 | $status = []; | |
173 | $dependency = [ | |
174 | 'Membership' => 'membership_type_id', | |
175 | 'MembershipBlock' => 'membership_type_default', | |
176 | ]; | |
177 | ||
178 | foreach ($dependency as $name => $field) { | |
179 | $baoString = 'CRM_Member_BAO_' . $name; | |
180 | $dao = new $baoString(); | |
181 | $dao->$field = $event->id; | |
182 | if ($dao->find(TRUE)) { | |
183 | $check = TRUE; | |
184 | $status[] = $name; | |
185 | } | |
186 | } | |
187 | if ($check) { | |
188 | $cnt = 1; | |
189 | $message = ts('This membership type cannot be deleted due to following reason(s):'); | |
190 | if (in_array('Membership', $status)) { | |
191 | $findMembersURL = CRM_Utils_System::url('civicrm/member/search', 'reset=1'); | |
192 | $deleteURL = CRM_Utils_System::url('civicrm/contact/search/advanced', 'reset=1'); | |
193 | $message .= '<br/>' . ts('%3. There are some contacts who have this membership type assigned to them. Search for contacts with this membership type from <a href=\'%1\'>Find Members</a>. If you are still getting this message after deleting these memberships, there may be contacts in the Trash (deleted) with this membership type. Try using <a href="%2">Advanced Search</a> and checking "Search in Trash".', [ | |
194 | 1 => $findMembersURL, | |
195 | 2 => $deleteURL, | |
196 | 3 => $cnt, | |
197 | ]); | |
198 | $cnt++; | |
199 | } | |
200 | ||
201 | if (in_array('MembershipBlock', $status)) { | |
202 | $deleteURL = CRM_Utils_System::url('civicrm/admin/contribute', 'reset=1'); | |
203 | $message .= ts('%2. This Membership Type is used in an <a href=\'%1\'>Online Contribution page</a>. Uncheck this membership type in the Memberships tab.', [ | |
204 | 1 => $deleteURL, | |
205 | 2 => $cnt, | |
206 | ]); | |
207 | throw new CRM_Core_Exception($message); | |
208 | } | |
209 | } | |
210 | CRM_Utils_Weight::delWeight('CRM_Member_DAO_MembershipType', $event->id); | |
211 | } | |
6552bd20 CW |
212 | } |
213 | ||
6a488035 | 214 | /** |
4c429c82 | 215 | * Convert membership type's 'start day' & 'rollover day' to human readable formats. |
6a488035 | 216 | * |
b2363ea8 TO |
217 | * @param array $membershipType |
218 | * An array of membershipType-details. | |
6a488035 | 219 | */ |
00be9182 | 220 | public static function convertDayFormat(&$membershipType) { |
be2fb01f | 221 | $periodDays = [ |
6a488035 TO |
222 | 'fixed_period_start_day', |
223 | 'fixed_period_rollover_day', | |
be2fb01f | 224 | ]; |
6a488035 TO |
225 | foreach ($membershipType as $id => $details) { |
226 | foreach ($periodDays as $pDay) { | |
a7488080 | 227 | if (!empty($details[$pDay])) { |
6a488035 | 228 | if ($details[$pDay] > 31) { |
353ffa53 TO |
229 | $month = substr($details[$pDay], 0, strlen($details[$pDay]) - 2); |
230 | $day = substr($details[$pDay], -2); | |
be2fb01f | 231 | $monthMap = [ |
6a488035 TO |
232 | '1' => 'Jan', |
233 | '2' => 'Feb', | |
234 | '3' => 'Mar', | |
235 | '4' => 'Apr', | |
236 | '5' => 'May', | |
237 | '6' => 'Jun', | |
238 | '7' => 'Jul', | |
239 | '8' => 'Aug', | |
240 | '9' => 'Sep', | |
241 | '10' => 'Oct', | |
242 | '11' => 'Nov', | |
243 | '12' => 'Dec', | |
be2fb01f | 244 | ]; |
6a488035 TO |
245 | $membershipType[$id][$pDay] = $monthMap[$month] . ' ' . $day; |
246 | } | |
247 | else { | |
248 | $membershipType[$id][$pDay] = $details[$pDay]; | |
249 | } | |
250 | } | |
251 | } | |
252 | } | |
253 | } | |
254 | ||
255 | /** | |
4c429c82 | 256 | * Get membership Types. |
6a488035 | 257 | * |
eb151aab | 258 | * @deprecated use getAllMembershipTypes. |
259 | * | |
77b97be7 EM |
260 | * @param bool $public |
261 | * | |
262 | * @return array | |
6a488035 | 263 | */ |
00be9182 | 264 | public static function getMembershipTypes($public = TRUE) { |
be2fb01f | 265 | $membershipTypes = []; |
6a488035 TO |
266 | $membershipType = new CRM_Member_DAO_MembershipType(); |
267 | $membershipType->is_active = 1; | |
268 | if ($public) { | |
269 | $membershipType->visibility = 'Public'; | |
270 | } | |
271 | $membershipType->orderBy(' weight'); | |
272 | $membershipType->find(); | |
273 | while ($membershipType->fetch()) { | |
274 | $membershipTypes[$membershipType->id] = $membershipType->name; | |
275 | } | |
6a488035 TO |
276 | return $membershipTypes; |
277 | } | |
278 | ||
279 | /** | |
4c429c82 | 280 | * Get membership Type Details. |
6a488035 | 281 | * |
eb151aab | 282 | * @deprecated use getMembershipType. |
283 | * | |
6a488035 | 284 | * @param int $membershipTypeId |
77b97be7 EM |
285 | * |
286 | * @return array|null | |
6a488035 | 287 | */ |
00be9182 | 288 | public static function getMembershipTypeDetails($membershipTypeId) { |
be2fb01f | 289 | $membershipTypeDetails = []; |
6a488035 TO |
290 | |
291 | $membershipType = new CRM_Member_DAO_MembershipType(); | |
292 | $membershipType->is_active = 1; | |
293 | $membershipType->id = $membershipTypeId; | |
294 | if ($membershipType->find(TRUE)) { | |
295 | CRM_Core_DAO::storeValues($membershipType, $membershipTypeDetails); | |
6a488035 TO |
296 | return $membershipTypeDetails; |
297 | } | |
298 | else { | |
299 | return NULL; | |
300 | } | |
301 | } | |
302 | ||
303 | /** | |
4c429c82 | 304 | * Calculate start date and end date for new membership. |
6a488035 | 305 | * |
b2363ea8 TO |
306 | * @param int $membershipTypeId |
307 | * Membership type id. | |
87ad884a | 308 | * @param string $joinDate |
b2363ea8 | 309 | * Member since ( in mysql date format ). |
87ad884a | 310 | * @param string $startDate |
b2363ea8 | 311 | * Start date ( in mysql date format ). |
da6b46f4 | 312 | * @param null $endDate |
b2363ea8 TO |
313 | * @param int $numRenewTerms |
314 | * How many membership terms are being added to end date (default is 1). | |
6a488035 | 315 | * |
a6c01b45 CW |
316 | * @return array |
317 | * associated array with start date, end date and join date for the membership | |
6a488035 TO |
318 | */ |
319 | public static function getDatesForMembershipType($membershipTypeId, $joinDate = NULL, $startDate = NULL, $endDate = NULL, $numRenewTerms = 1) { | |
320 | $membershipTypeDetails = self::getMembershipTypeDetails($membershipTypeId); | |
321 | ||
4c429c82 | 322 | // Convert all dates to 'Y-m-d' format. |
be2fb01f | 323 | foreach ([ |
c5c263ca AH |
324 | 'joinDate', |
325 | 'startDate', | |
326 | 'endDate', | |
be2fb01f | 327 | ] as $dateParam) { |
6a488035 TO |
328 | if (!empty($$dateParam)) { |
329 | $$dateParam = CRM_Utils_Date::processDate($$dateParam, NULL, FALSE, 'Y-m-d'); | |
330 | } | |
331 | } | |
332 | if (!$joinDate) { | |
42347843 | 333 | $joinDate = CRM_Utils_Time::date('Y-m-d'); |
6a488035 TO |
334 | } |
335 | $actualStartDate = $joinDate; | |
336 | if ($startDate) { | |
337 | $actualStartDate = $startDate; | |
338 | } | |
339 | ||
340 | $fixed_period_rollover = FALSE; | |
341 | if (CRM_Utils_Array::value('period_type', $membershipTypeDetails) == 'rolling') { | |
342 | if (!$startDate) { | |
343 | $startDate = $joinDate; | |
344 | } | |
345 | $actualStartDate = $startDate; | |
346 | } | |
347 | elseif (CRM_Utils_Array::value('period_type', $membershipTypeDetails) == 'fixed') { | |
5ae96933 DG |
348 | // calculate start date |
349 | // if !$startDate then use $joinDate | |
350 | $toDay = explode('-', (empty($startDate) ? $joinDate : $startDate)); | |
6a488035 TO |
351 | $year = $toDay[0]; |
352 | $month = $toDay[1]; | |
f7660a39 | 353 | $day = $toDay[2]; |
6a488035 TO |
354 | |
355 | if ($membershipTypeDetails['duration_unit'] == 'year') { | |
356 | ||
357 | //get start fixed day | |
358 | $startMonth = substr($membershipTypeDetails['fixed_period_start_day'], 0, | |
359 | strlen($membershipTypeDetails['fixed_period_start_day']) - 2 | |
360 | ); | |
361 | $startDay = substr($membershipTypeDetails['fixed_period_start_day'], -2); | |
362 | ||
f7660a39 | 363 | if (date('Y-m-d', mktime(0, 0, 0, $startMonth, $startDay, $year)) <= date('Y-m-d', mktime(0, 0, 0, $month, $day, $year))) { |
4c429c82 | 364 | $actualStartDate = date('Y-m-d', mktime(0, 0, 0, $startMonth, $startDay, $year)); |
f7660a39 DG |
365 | } |
366 | else { | |
4c429c82 | 367 | $actualStartDate = date('Y-m-d', mktime(0, 0, 0, $startMonth, $startDay, $year - 1)); |
f7660a39 | 368 | } |
82d11b3f | 369 | |
4c429c82 | 370 | $fixed_period_rollover = self::isDuringFixedAnnualRolloverPeriod($joinDate, $membershipTypeDetails, $year, $actualStartDate); |
6a488035 TO |
371 | |
372 | if (!$startDate) { | |
373 | $startDate = $actualStartDate; | |
374 | } | |
375 | } | |
376 | elseif ($membershipTypeDetails['duration_unit'] == 'month') { | |
377 | // Check if we are on or after rollover day of the month - CRM-10585 | |
378 | // If so, set fixed_period_rollover TRUE so we increment end_date month below. | |
379 | $dateParts = explode('-', $actualStartDate); | |
9b873358 | 380 | if ($dateParts[2] >= $membershipTypeDetails['fixed_period_rollover_day']) { |
b09fe5ed | 381 | $fixed_period_rollover = TRUE; |
6a488035 | 382 | } |
03e04002 | 383 | |
6a488035 TO |
384 | // Start date is always first day of actualStartDate month |
385 | if (!$startDate) { | |
386 | $actualStartDate = $startDate = $year . '-' . $month . '-01'; | |
387 | } | |
388 | } | |
389 | } | |
390 | ||
4c429c82 | 391 | // Calculate end date if it is not passed by user. |
6a488035 TO |
392 | if (!$endDate) { |
393 | //end date calculation | |
353ffa53 TO |
394 | $date = explode('-', $actualStartDate); |
395 | $year = $date[0]; | |
6a488035 | 396 | $month = $date[1]; |
353ffa53 | 397 | $day = $date[2]; |
6a488035 TO |
398 | |
399 | switch ($membershipTypeDetails['duration_unit']) { | |
400 | case 'year': | |
401 | $year = $year + ($numRenewTerms * $membershipTypeDetails['duration_interval']); | |
402 | //extend membership date by duration interval. | |
403 | if ($fixed_period_rollover) { | |
404 | $year += 1; | |
405 | } | |
406 | break; | |
407 | ||
408 | case 'month': | |
409 | $month = $month + ($numRenewTerms * $membershipTypeDetails['duration_interval']); | |
410 | //duration interval is month | |
411 | if ($fixed_period_rollover) { | |
412 | //CRM-10585 | |
413 | $month += 1; | |
414 | } | |
415 | break; | |
416 | ||
417 | case 'day': | |
418 | $day = $day + ($numRenewTerms * $membershipTypeDetails['duration_interval']); | |
419 | ||
420 | if ($fixed_period_rollover) { | |
421 | //Fix Me: Currently we don't allow rollover if | |
422 | //duration interval is day | |
423 | } | |
424 | break; | |
425 | } | |
426 | ||
427 | if ($membershipTypeDetails['duration_unit'] == 'lifetime') { | |
428 | $endDate = NULL; | |
429 | } | |
430 | else { | |
431 | $endDate = date('Y-m-d', mktime(0, 0, 0, $month, $day - 1, $year)); | |
432 | } | |
433 | } | |
434 | ||
be2fb01f | 435 | $membershipDates = [ |
c8881afb | 436 | 'start_date' => CRM_Utils_Date::customFormat($startDate, '%Y%m%d'), |
edc9b94e EM |
437 | 'end_date' => CRM_Utils_Date::customFormat($endDate, '%Y%m%d'), |
438 | 'join_date' => CRM_Utils_Date::customFormat($joinDate, '%Y%m%d'), | |
be2fb01f | 439 | ]; |
6a488035 TO |
440 | |
441 | return $membershipDates; | |
442 | } | |
443 | ||
87ad884a | 444 | /** |
fe482240 | 445 | * Does this membership start between the rollover date and the start of the next period. |
87ad884a EM |
446 | * (in which case they will get an extra membership period) |
447 | * ie if annual memberships run June - May & the rollover is in May memberships between | |
448 | * May and June will return TRUE and between June and May will return FALSE | |
449 | * | |
450 | * @param string $startDate start date of current membership period | |
87ad884a | 451 | * @param array $membershipTypeDetails |
81d97e72 | 452 | * @param int $year |
e9ac29eb | 453 | * @param string $actualStartDate |
87ad884a EM |
454 | * @return bool is this in the window where the membership gets an extra part-period added |
455 | */ | |
70a87708 | 456 | public static function isDuringFixedAnnualRolloverPeriod($startDate, $membershipTypeDetails, $year, $actualStartDate) { |
87ad884a EM |
457 | |
458 | $rolloverMonth = substr($membershipTypeDetails['fixed_period_rollover_day'], 0, | |
459 | strlen($membershipTypeDetails['fixed_period_rollover_day']) - 2 | |
460 | ); | |
461 | $rolloverDay = substr($membershipTypeDetails['fixed_period_rollover_day'], -2); | |
462 | ||
8454b618 | 463 | $calculatedRolloverDate = date('Y-m-d', mktime(0, 0, 0, $rolloverMonth, $rolloverDay, $year)); |
87ad884a EM |
464 | |
465 | //CRM-7825 -membership date rules are : | |
466 | //1. Membership should not be start in future. | |
467 | //2. rollover window should be subset of membership window. | |
468 | ||
87ad884a EM |
469 | //get the fixed end date here. |
470 | $dateParts = explode('-', $actualStartDate); | |
8454b618 | 471 | $endDateOfFirstYearMembershipPeriod = date('Y-m-d', mktime(0, 0, 0, |
87ad884a EM |
472 | $dateParts[1], |
473 | $dateParts[2] - 1, | |
6f1c82f5 | 474 | $dateParts[0] + 1 |
87ad884a EM |
475 | )); |
476 | ||
8454b618 EM |
477 | //we know the month and day of the rollover date but not the year (we're just |
478 | //using the start date year at the moment. So if it's after the end | |
479 | // of the first year of membership then it's the next period & we'll adjust back a year. If it's | |
480 | // before the start_date then it's too early & we'll adjust forward. | |
481 | if ($endDateOfFirstYearMembershipPeriod < $calculatedRolloverDate) { | |
482 | $calculatedRolloverDate = date('Y-m-d', mktime(0, 0, 0, $rolloverMonth, $rolloverDay, $year - 1)); | |
87ad884a | 483 | } |
8454b618 EM |
484 | if ($calculatedRolloverDate < $actualStartDate) { |
485 | $calculatedRolloverDate = date('Y-m-d', mktime(0, 0, 0, $rolloverMonth, $rolloverDay, $year + 1)); | |
87ad884a EM |
486 | } |
487 | ||
8454b618 | 488 | if ($calculatedRolloverDate <= $startDate) { |
87ad884a EM |
489 | return TRUE; |
490 | } | |
491 | return FALSE; | |
492 | } | |
493 | ||
6a488035 | 494 | /** |
055e2441 | 495 | * Calculate start date and end date for membership updates. |
496 | * | |
497 | * Note that this function is called by the api for any membership update although it was | |
498 | * originally written for renewal (which feels a bit fragile but hey....). | |
6a488035 TO |
499 | * |
500 | * @param int $membershipId | |
501 | * @param $changeToday | |
b4d1a1bf | 502 | * If provided, specify an alternative date to use as "today" for renewal |
b2363ea8 TO |
503 | * @param int $membershipTypeID |
504 | * If provided, overrides the membership type of the $membershipID membership. | |
505 | * @param int $numRenewTerms | |
506 | * How many membership terms are being added to end date (default is 1). | |
6a488035 TO |
507 | * |
508 | * CRM-7297 Membership Upsell - Added $membershipTypeID param to facilitate calculations of dates when membership type changes | |
509 | * | |
5396af74 | 510 | * @return array |
a6c01b45 | 511 | * array fo the start date, end date and join date of the membership |
6a488035 TO |
512 | */ |
513 | public static function getRenewalDatesForMembershipType($membershipId, $changeToday = NULL, $membershipTypeID = NULL, $numRenewTerms = 1) { | |
be2fb01f | 514 | $params = ['id' => $membershipId]; |
6a488035 | 515 | $membershipDetails = CRM_Member_BAO_Membership::getValues($params, $values); |
055e2441 | 516 | $membershipDetails = $membershipDetails[$membershipId]; |
517 | $statusID = $membershipDetails->status_id; | |
be2fb01f | 518 | $membershipDates = []; |
055e2441 | 519 | if (!empty($membershipDetails->join_date)) { |
520 | $membershipDates['join_date'] = CRM_Utils_Date::customFormat($membershipDetails->join_date, '%Y%m%d'); | |
521 | } | |
6a488035 TO |
522 | |
523 | $oldPeriodType = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipType', | |
353ffa53 | 524 | CRM_Core_DAO::getFieldValue('CRM_Member_DAO_Membership', $membershipId, 'membership_type_id'), 'period_type'); |
6a488035 TO |
525 | |
526 | // CRM-7297 Membership Upsell | |
527 | if (is_null($membershipTypeID)) { | |
055e2441 | 528 | $membershipTypeDetails = self::getMembershipTypeDetails($membershipDetails->membership_type_id); |
6a488035 TO |
529 | } |
530 | else { | |
531 | $membershipTypeDetails = self::getMembershipTypeDetails($membershipTypeID); | |
532 | } | |
533 | $statusDetails = CRM_Member_BAO_MembershipStatus::getMembershipStatus($statusID); | |
534 | ||
535 | if ($statusDetails['is_current_member'] == 1) { | |
055e2441 | 536 | $startDate = $membershipDetails->start_date; |
6a488035 TO |
537 | // CRM=7297 Membership Upsell: we need to handle null end_date in case we are switching |
538 | // from a lifetime to a different membership type | |
055e2441 | 539 | if (is_null($membershipDetails->end_date)) { |
42347843 | 540 | $date = CRM_Utils_Time::date('Y-m-d'); |
6a488035 TO |
541 | } |
542 | else { | |
055e2441 | 543 | $date = $membershipDetails->end_date; |
6a488035 | 544 | } |
353ffa53 | 545 | $date = explode('-', $date); |
3bd63a4b MW |
546 | // We have to add 1 day first in case it's the end of the month, then subtract afterwards |
547 | // eg. 2018-02-28 should renew to 2018-03-31, if we just added 1 month we'd get 2018-03-28 | |
6a488035 | 548 | $logStartDate = date('Y-m-d', mktime(0, 0, 0, |
353ffa53 TO |
549 | (double) $date[1], |
550 | (double) ($date[2] + 1), | |
551 | (double) $date[0] | |
552 | )); | |
6a488035 | 553 | |
353ffa53 TO |
554 | $date = explode('-', $logStartDate); |
555 | $year = $date[0]; | |
6a488035 | 556 | $month = $date[1]; |
353ffa53 | 557 | $day = $date[2]; |
6a488035 TO |
558 | |
559 | switch ($membershipTypeDetails['duration_unit']) { | |
560 | case 'year': | |
561 | //need to check if the upsell is from rolling to fixed and adjust accordingly | |
481a74f4 | 562 | if ($membershipTypeDetails['period_type'] == 'fixed' && $oldPeriodType == 'rolling') { |
6a488035 TO |
563 | $month = substr($membershipTypeDetails['fixed_period_start_day'], 0, strlen($membershipTypeDetails['fixed_period_start_day']) - 2); |
564 | $day = substr($membershipTypeDetails['fixed_period_start_day'], -2); | |
565 | $year += 1; | |
0db6c3e1 TO |
566 | } |
567 | else { | |
b09fe5ed | 568 | $year = $year + ($numRenewTerms * $membershipTypeDetails['duration_interval']); |
6a488035 TO |
569 | } |
570 | break; | |
571 | ||
572 | case 'month': | |
573 | $month = $month + ($numRenewTerms * $membershipTypeDetails['duration_interval']); | |
574 | break; | |
575 | ||
576 | case 'day': | |
577 | $day = $day + ($numRenewTerms * $membershipTypeDetails['duration_interval']); | |
578 | break; | |
579 | } | |
580 | if ($membershipTypeDetails['duration_unit'] == 'lifetime') { | |
581 | $endDate = NULL; | |
582 | } | |
583 | else { | |
584 | $endDate = date('Y-m-d', mktime(0, 0, 0, | |
353ffa53 TO |
585 | $month, |
586 | $day - 1, | |
587 | $year | |
588 | )); | |
6a488035 | 589 | } |
42347843 | 590 | $today = CRM_Utils_Time::date('Y-m-d'); |
6a488035 TO |
591 | $membershipDates['today'] = CRM_Utils_Date::customFormat($today, '%Y%m%d'); |
592 | $membershipDates['start_date'] = CRM_Utils_Date::customFormat($startDate, '%Y%m%d'); | |
593 | $membershipDates['end_date'] = CRM_Utils_Date::customFormat($endDate, '%Y%m%d'); | |
594 | $membershipDates['log_start_date'] = CRM_Utils_Date::customFormat($logStartDate, '%Y%m%d'); | |
595 | } | |
596 | else { | |
42347843 | 597 | $today = CRM_Utils_Time::date('Y-m-d'); |
6a488035 TO |
598 | if ($changeToday) { |
599 | $today = CRM_Utils_Date::processDate($changeToday, NULL, FALSE, 'Y-m-d'); | |
600 | } | |
601 | // Calculate new start/end dates when join date is today | |
602 | $renewalDates = self::getDatesForMembershipType($membershipTypeDetails['id'], | |
603 | $today, NULL, NULL, $numRenewTerms | |
604 | ); | |
6a488035 TO |
605 | $membershipDates['today'] = CRM_Utils_Date::customFormat($today, '%Y%m%d'); |
606 | $membershipDates['start_date'] = $renewalDates['start_date']; | |
607 | $membershipDates['end_date'] = $renewalDates['end_date']; | |
608 | $membershipDates['log_start_date'] = $renewalDates['start_date']; | |
41dcb974 | 609 | // CRM-18503 - set join_date as today in case the membership type is fixed. |
610 | if ($membershipTypeDetails['period_type'] == 'fixed' && !isset($membershipDates['join_date'])) { | |
611 | $membershipDates['join_date'] = $renewalDates['join_date']; | |
612 | } | |
6a488035 | 613 | } |
055e2441 | 614 | if (!isset($membershipDates['join_date'])) { |
615 | $membershipDates['join_date'] = $membershipDates['start_date']; | |
616 | } | |
6a488035 TO |
617 | |
618 | return $membershipDates; | |
619 | } | |
620 | ||
6a488035 | 621 | /** |
fe482240 | 622 | * Retrieve all Membership Types with Member of Contact id. |
6a488035 | 623 | * |
5396af74 | 624 | * @param array $membershipTypes |
625 | * array of membership type ids | |
626 | * @return array | |
a6c01b45 | 627 | * array of the details of membership types with Member of Contact id |
6a488035 | 628 | */ |
00be9182 | 629 | public static function getMemberOfContactByMemTypes($membershipTypes) { |
be2fb01f | 630 | $memTypeOrganizations = []; |
6a488035 | 631 | if (empty($membershipTypes)) { |
b204fd50 | 632 | return $memTypeOrganizations; |
6a488035 TO |
633 | } |
634 | ||
635 | $result = CRM_Core_DAO::executeQuery("SELECT id, member_of_contact_id FROM civicrm_membership_type WHERE id IN (" . implode(',', $membershipTypes) . ")"); | |
636 | while ($result->fetch()) { | |
b204fd50 | 637 | $memTypeOrganizations[$result->id] = $result->member_of_contact_id; |
6a488035 TO |
638 | } |
639 | ||
b204fd50 | 640 | return $memTypeOrganizations; |
6a488035 TO |
641 | } |
642 | ||
77b97be7 | 643 | /** |
e0b2d43b | 644 | * The function returns all the Organization for all membershipTypes . |
6a488035 | 645 | * |
c490a46a | 646 | * @param int $membershipTypeId |
77b97be7 EM |
647 | * |
648 | * @return array | |
6a488035 | 649 | */ |
00be9182 | 650 | public static function getMembershipTypeOrganization($membershipTypeId = NULL) { |
be2fb01f | 651 | $allMembershipTypes = []; |
6a488035 TO |
652 | |
653 | $membershipType = new CRM_Member_DAO_MembershipType(); | |
654 | ||
655 | if (isset($membershipTypeId)) { | |
656 | $membershipType->id = $membershipTypeId; | |
657 | } | |
658 | $membershipType->find(); | |
659 | ||
660 | while ($membershipType->fetch()) { | |
b204fd50 | 661 | $allMembershipTypes[$membershipType->id] = $membershipType->member_of_contact_id; |
6a488035 | 662 | } |
b204fd50 | 663 | return $allMembershipTypes; |
6a488035 TO |
664 | } |
665 | ||
666 | /** | |
87ad884a | 667 | * Function to retrieve organization and associated membership |
6a488035 TO |
668 | * types |
669 | * | |
a6c01b45 CW |
670 | * @return array |
671 | * arrays of organization and membership types | |
6a488035 | 672 | * |
6a488035 | 673 | */ |
00be9182 | 674 | public static function getMembershipTypeInfo() { |
6a488035 | 675 | if (!self::$_membershipTypeInfo) { |
be2fb01f | 676 | $orgs = $types = []; |
6a488035 TO |
677 | |
678 | $query = 'SELECT memType.id, memType.name, memType.member_of_contact_id, c.sort_name | |
679 | FROM civicrm_membership_type memType INNER JOIN civicrm_contact c ON c.id = memType.member_of_contact_id | |
680 | WHERE memType.is_active = 1 '; | |
481a74f4 | 681 | $dao = CRM_Core_DAO::executeQuery($query); |
6a488035 TO |
682 | while ($dao->fetch()) { |
683 | $orgs[$dao->member_of_contact_id] = $dao->sort_name; | |
684 | $types[$dao->member_of_contact_id][$dao->id] = $dao->name; | |
685 | } | |
686 | ||
be2fb01f | 687 | self::$_membershipTypeInfo = [$orgs, $types]; |
6a488035 TO |
688 | } |
689 | return self::$_membershipTypeInfo; | |
690 | } | |
691 | ||
bb3a214a | 692 | /** |
c490a46a | 693 | * @param array $params |
100fef9d CW |
694 | * @param int $previousID |
695 | * @param int $membershipTypeId | |
bb3a214a | 696 | */ |
301db4f8 | 697 | public static function createMembershipPriceField($params, $previousID, $membershipTypeId) { |
6a488035 | 698 | |
9da8dc8c | 699 | $priceSetId = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', 'default_membership_type_amount', 'id', 'name'); |
6a488035 | 700 | |
a7488080 | 701 | if (!empty($params['member_of_contact_id'])) { |
6a488035 TO |
702 | $fieldName = $params['member_of_contact_id']; |
703 | } | |
704 | else { | |
705 | $fieldName = $previousID; | |
706 | } | |
353ffa53 TO |
707 | $fieldLabel = 'Membership Amount'; |
708 | $optionsIds = NULL; | |
be2fb01f | 709 | $fieldParams = [ |
6a488035 TO |
710 | 'price_set_id ' => $priceSetId, |
711 | 'name' => $fieldName, | |
be2fb01f CW |
712 | ]; |
713 | $results = []; | |
9da8dc8c | 714 | CRM_Price_BAO_PriceField::retrieve($fieldParams, $results); |
6a488035 | 715 | if (empty($results)) { |
be2fb01f | 716 | $fieldParams = []; |
6a488035 TO |
717 | $fieldParams['label'] = $fieldLabel; |
718 | $fieldParams['name'] = $fieldName; | |
719 | $fieldParams['price_set_id'] = $priceSetId; | |
720 | $fieldParams['html_type'] = 'Radio'; | |
721 | $fieldParams['is_display_amounts'] = $fieldParams['is_required'] = 0; | |
722 | $fieldParams['weight'] = $fieldParams['option_weight'][1] = 1; | |
723 | $fieldParams['option_label'][1] = $params['name']; | |
9c1bc317 | 724 | $fieldParams['option_description'][1] = $params['description'] ?? NULL; |
03e04002 | 725 | |
6a488035 TO |
726 | $fieldParams['membership_type_id'][1] = $membershipTypeId; |
727 | $fieldParams['option_amount'][1] = empty($params['minimum_fee']) ? 0 : $params['minimum_fee']; | |
9c1bc317 | 728 | $fieldParams['financial_type_id'] = $params['financial_type_id'] ?? NULL; |
6a488035 TO |
729 | |
730 | if ($previousID) { | |
731 | CRM_Member_Form_MembershipType::checkPreviousPriceField($previousID, $priceSetId, $membershipTypeId, $optionsIds); | |
9c1bc317 | 732 | $fieldParams['option_id'] = $optionsIds['option_id'] ?? NULL; |
6a488035 | 733 | } |
806e9b71 | 734 | CRM_Price_BAO_PriceField::create($fieldParams); |
03e04002 | 735 | } |
6a488035 TO |
736 | else { |
737 | $fieldID = $results['id']; | |
be2fb01f | 738 | $fieldValueParams = [ |
6a488035 TO |
739 | 'price_field_id' => $fieldID, |
740 | 'membership_type_id' => $membershipTypeId, | |
be2fb01f CW |
741 | ]; |
742 | $results = []; | |
9da8dc8c | 743 | CRM_Price_BAO_PriceFieldValue::retrieve($fieldValueParams, $results); |
6a488035 | 744 | if (!empty($results)) { |
353ffa53 | 745 | $results['label'] = $results['name'] = $params['name']; |
6a488035 | 746 | $results['amount'] = empty($params['minimum_fee']) ? 0 : $params['minimum_fee']; |
03e04002 | 747 | } |
6a488035 | 748 | else { |
be2fb01f | 749 | $results = [ |
6a488035 TO |
750 | 'price_field_id' => $fieldID, |
751 | 'name' => $params['name'], | |
752 | 'label' => $params['name'], | |
753 | 'amount' => empty($params['minimum_fee']) ? 0 : $params['minimum_fee'], | |
754 | 'membership_type_id' => $membershipTypeId, | |
755 | 'is_active' => 1, | |
be2fb01f | 756 | ]; |
6a488035 TO |
757 | } |
758 | ||
759 | if ($previousID) { | |
760 | CRM_Member_Form_MembershipType::checkPreviousPriceField($previousID, $priceSetId, $membershipTypeId, $optionsIds); | |
a7488080 | 761 | if (!empty($optionsIds['option_id'])) { |
f3601685 | 762 | $results['id'] = current($optionsIds['option_id']); |
6a488035 TO |
763 | } |
764 | } | |
9c1bc317 CW |
765 | $results['financial_type_id'] = $params['financial_type_id'] ?? NULL; |
766 | $results['description'] = $params['description'] ?? NULL; | |
f3601685 | 767 | CRM_Price_BAO_PriceFieldValue::add($results); |
6a488035 TO |
768 | } |
769 | } | |
770 | ||
be2e0c6a TO |
771 | /** |
772 | * This function updates all price field value for quick config | |
6a488035 TO |
773 | * price set which has membership type |
774 | * | |
87ad884a | 775 | * @param int $membershipTypeId membership type id |
6a488035 | 776 | * |
87ad884a | 777 | * @param array $params |
6a488035 | 778 | */ |
00be9182 | 779 | public static function updateAllPriceFieldValue($membershipTypeId, $params) { |
be2fb01f CW |
780 | $defaults = []; |
781 | $fieldsToUpdate = [ | |
5afcf706 JP |
782 | 'financial_type_id' => 'financial_type_id', |
783 | 'name' => 'label', | |
784 | 'minimum_fee' => 'amount', | |
785 | 'description' => 'description', | |
786 | 'visibility' => 'visibility_id', | |
be2fb01f | 787 | ]; |
5afcf706 JP |
788 | $priceFieldValueBAO = new CRM_Price_BAO_PriceFieldValue(); |
789 | $priceFieldValueBAO->membership_type_id = $membershipTypeId; | |
790 | $priceFieldValueBAO->find(); | |
791 | while ($priceFieldValueBAO->fetch()) { | |
be2fb01f | 792 | $updateParams = [ |
5afcf706 JP |
793 | 'id' => $priceFieldValueBAO->id, |
794 | 'price_field_id' => $priceFieldValueBAO->price_field_id, | |
be2fb01f | 795 | ]; |
5afcf706 | 796 | //Get priceset details. |
be2fb01f | 797 | $fieldParams = ['fid' => $priceFieldValueBAO->price_field_id]; |
5afcf706 | 798 | $setID = CRM_Price_BAO_PriceSet::getSetId($fieldParams); |
be2fb01f | 799 | $setParams = ['id' => $setID]; |
5afcf706 JP |
800 | $setValues = CRM_Price_BAO_PriceSet::retrieve($setParams, $defaults); |
801 | if (!empty($setValues->is_quick_config) && $setValues->name != 'default_membership_type_amount') { | |
802 | foreach ($fieldsToUpdate as $key => $value) { | |
803 | if ($value == 'visibility_id' && !empty($params['visibility'])) { | |
804 | $updateParams['visibility_id'] = CRM_Price_BAO_PriceField::getVisibilityOptionID(strtolower($params['visibility'])); | |
805 | } | |
806 | else { | |
9c1bc317 | 807 | $updateParams[$value] = $params[$key] ?? NULL; |
5afcf706 | 808 | } |
0e09eaf5 | 809 | } |
5afcf706 | 810 | CRM_Price_BAO_PriceFieldValue::add($updateParams); |
0e09eaf5 PN |
811 | } |
812 | } | |
03e04002 | 813 | } |
96025800 | 814 | |
eb151aab | 815 | /** |
816 | * Cached wrapper for membership types. | |
817 | * | |
818 | * Since this is used from the batched script caching helps. | |
819 | * | |
24eeccd0 | 820 | * Caching is by domain - if that hits any issues we should add a new function getDomainMembershipTypes |
821 | * or similar rather than 'just add another param'! but this is closer to earlier behaviour so 'should' be OK. | |
822 | * | |
e0b2d43b MW |
823 | * @return array |
824 | * List of membershipType details keyed by membershipTypeID | |
eb151aab | 825 | * @throws \CiviCRM_API3_Exception |
826 | */ | |
d568dbe0 EM |
827 | public static function getAllMembershipTypes(): array { |
828 | $cacheString = __CLASS__ . __FUNCTION__ . CRM_Core_Config::domainID() . '_' . CRM_Core_I18n::getLocale(); | |
24eeccd0 | 829 | if (!Civi::cache('metadata')->has($cacheString)) { |
322b59f0 | 830 | $types = civicrm_api3('MembershipType', 'get', ['options' => ['limit' => 0, 'sort' => 'weight']])['values']; |
49bbb50e | 831 | $taxRates = CRM_Core_PseudoConstant::getTaxRates(); |
2d227fa3 | 832 | $keys = ['description', 'relationship_type_id', 'relationship_direction', 'max_related', 'auto_renew']; |
322b59f0 | 833 | // In order to avoid down-stream e-notices we undo api v3 filtering of NULL values. This is covered |
834 | // in Unit tests & ideally we might switch to apiv4 but I would argue we should build caching | |
835 | // of metadata entities like this directly into apiv4. | |
836 | foreach ($types as $id => $type) { | |
837 | foreach ($keys as $key) { | |
838 | if (!isset($type[$key])) { | |
839 | $types[$id][$key] = NULL; | |
840 | } | |
841 | } | |
842 | if (isset($type['contribution_type_id'])) { | |
843 | unset($types[$id]['contribution_type_id']); | |
844 | } | |
49bbb50e | 845 | $types[$id]['tax_rate'] = (float) ($taxRates[$type['financial_type_id']] ?? 0.0); |
846 | $multiplier = 1; | |
847 | if ($types[$id]['tax_rate'] !== 0.0) { | |
848 | $multiplier += ($types[$id]['tax_rate'] / 100); | |
849 | } | |
4e089fd0 AM |
850 | if (!array_key_exists('minimum_fee', $types[$id])) { |
851 | $types[$id]['minimum_fee'] = 0; | |
852 | } | |
49bbb50e | 853 | $types[$id]['minimum_fee_with_tax'] = (float) $types[$id]['minimum_fee'] * $multiplier; |
322b59f0 | 854 | } |
24eeccd0 | 855 | Civi::cache('metadata')->set($cacheString, $types); |
eb151aab | 856 | } |
24eeccd0 | 857 | return Civi::cache('metadata')->get($cacheString); |
eb151aab | 858 | } |
859 | ||
860 | /** | |
861 | * Get a specific membership type (leveraging the cache). | |
862 | * | |
863 | * @param int $id | |
864 | * | |
865 | * @return mixed | |
866 | * @throws \CiviCRM_API3_Exception | |
867 | */ | |
868 | public static function getMembershipType($id) { | |
869 | return self::getAllMembershipTypes()[$id]; | |
870 | } | |
871 | ||
6a488035 | 872 | } |