25e43ade5baec0d0ff63121239114f517a60b584
[civicrm-core.git] / CRM / Member / Form / Membership.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
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 |
9 +--------------------------------------------------------------------+
10 */
11
12 /**
13 *
14 * @package CRM
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
16 */
17
18 /**
19 * This class generates form components for offline membership form.
20 */
21 class CRM_Member_Form_Membership extends CRM_Member_Form {
22
23 protected $_memType = NULL;
24
25 protected $_onlinePendingContributionId;
26
27 public $_mode;
28
29 public $_contributeMode = 'direct';
30
31 protected $_recurMembershipTypes;
32
33 protected $_memTypeSelected;
34
35 /**
36 * Display name of the member.
37 *
38 * @var string
39 */
40 protected $_memberDisplayName = NULL;
41
42 /**
43 * email of the person paying for the membership (used for receipts)
44 * @var string
45 */
46 protected $_memberEmail = NULL;
47
48 /**
49 * Contact ID of the member.
50 *
51 * @var int
52 */
53 public $_contactID = NULL;
54
55 /**
56 * Display name of the person paying for the membership (used for receipts)
57 *
58 * @var string
59 */
60 protected $_contributorDisplayName = NULL;
61
62 /**
63 * email of the person paying for the membership (used for receipts)
64 * @var string
65 */
66 protected $_contributorEmail = NULL;
67
68 /**
69 * email of the person paying for the membership (used for receipts)
70 *
71 * @var int
72 */
73 protected $_contributorContactID = NULL;
74
75 /**
76 * ID of the person the receipt is to go to.
77 *
78 * @var int
79 */
80 protected $_receiptContactId = NULL;
81
82 /**
83 * Keep a class variable for ALL membership IDs so
84 * postProcess hook function can do something with it
85 *
86 * @var array
87 */
88 protected $_membershipIDs = [];
89
90 /**
91 * Set entity fields to be assigned to the form.
92 */
93 protected function setEntityFields() {
94 $this->entityFields = [
95 'join_date' => [
96 'name' => 'join_date',
97 'description' => ts('Member Since'),
98 ],
99 'start_date' => [
100 'name' => 'start_date',
101 'description' => ts('Start Date'),
102 ],
103 'end_date' => [
104 'name' => 'end_date',
105 'description' => ts('End Date'),
106 ],
107 ];
108 }
109
110 /**
111 * Set the delete message.
112 *
113 * We do this from the constructor in order to do a translation.
114 */
115 public function setDeleteMessage() {
116 $this->deleteMessage = '<span class="font-red bold">'
117 . ts("WARNING: Deleting this membership will also delete any related payment (contribution) records." . ts("This action cannot be undone.")
118 . '</span><p>'
119 . ts("Consider modifying the membership status instead if you want to maintain an audit trail and avoid losing payment data. You can set the status to Cancelled by editing the membership and clicking the Status Override checkbox.")
120 . '</p><p>'
121 . ts("Click 'Delete' if you want to continue.") . '</p>');
122 }
123
124 /**
125 * Overriding this entity trait function as not yet tested.
126 *
127 * We continue to rely on legacy handling.
128 */
129 public function addCustomDataToForm() {}
130
131 /**
132 * Overriding this entity trait function as not yet tested.
133 *
134 * We continue to rely on legacy handling.
135 */
136 public function addFormButtons() {}
137
138 /**
139 * Get selected membership type from the form values.
140 *
141 * @param array $priceSet
142 * @param array $params
143 *
144 * @return array
145 */
146 public static function getSelectedMemberships($priceSet, $params) {
147 $memTypeSelected = [];
148 $priceFieldIDS = self::getPriceFieldIDs($params, $priceSet);
149 if (isset($params['membership_type_id']) && !empty($params['membership_type_id'][1])) {
150 $memTypeSelected = [$params['membership_type_id'][1] => $params['membership_type_id'][1]];
151 }
152 else {
153 foreach ($priceFieldIDS as $priceFieldId) {
154 if ($id = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue', $priceFieldId, 'membership_type_id')) {
155 $memTypeSelected[$id] = $id;
156 }
157 }
158 }
159 return $memTypeSelected;
160 }
161
162 /**
163 * Extract price set fields and values from $params.
164 *
165 * @param array $params
166 * @param array $priceSet
167 *
168 * @return array
169 */
170 public static function getPriceFieldIDs($params, $priceSet) {
171 $priceFieldIDS = [];
172 if (isset($priceSet['fields']) && is_array($priceSet['fields'])) {
173 foreach ($priceSet['fields'] as $fieldId => $field) {
174 if (!empty($params['price_' . $fieldId])) {
175 if (is_array($params['price_' . $fieldId])) {
176 foreach ($params['price_' . $fieldId] as $priceFldVal => $isSet) {
177 if ($isSet) {
178 $priceFieldIDS[] = $priceFldVal;
179 }
180 }
181 }
182 elseif (!$field['is_enter_qty']) {
183 $priceFieldIDS[] = $params['price_' . $fieldId];
184 }
185 }
186 }
187 }
188 return $priceFieldIDS;
189 }
190
191 /**
192 * Form preProcess function.
193 */
194 public function preProcess() {
195 // This string makes up part of the class names, differentiating them (not sure why) from the membership fields.
196 $this->assign('formClass', 'membership');
197 parent::preProcess();
198
199 // get price set id.
200 $this->_priceSetId = CRM_Utils_Array::value('priceSetId', $_GET);
201 $this->set('priceSetId', $this->_priceSetId);
202 $this->assign('priceSetId', $this->_priceSetId);
203
204 if ($this->_action & CRM_Core_Action::DELETE) {
205 $contributionID = CRM_Member_BAO_Membership::getMembershipContributionId($this->_id);
206 // check delete permission for contribution
207 if ($this->_id && $contributionID && !CRM_Core_Permission::checkActionPermission('CiviContribute', $this->_action)) {
208 CRM_Core_Error::statusBounce(ts("This Membership is linked to a contribution. You must have 'delete in CiviContribute' permission in order to delete this record."));
209 }
210 }
211
212 if ($this->_action & CRM_Core_Action::ADD) {
213 if ($this->_contactID) {
214 //check whether contact has a current membership so we can alert user that they may want to do a renewal instead
215 $contactMemberships = [];
216 $memParams = ['contact_id' => $this->_contactID];
217 CRM_Member_BAO_Membership::getValues($memParams, $contactMemberships, TRUE);
218 $cMemTypes = [];
219 foreach ($contactMemberships as $mem) {
220 $cMemTypes[] = $mem['membership_type_id'];
221 }
222 if (count($cMemTypes) > 0) {
223 $memberorgs = CRM_Member_BAO_MembershipType::getMemberOfContactByMemTypes($cMemTypes);
224 $mems_by_org = [];
225 foreach ($contactMemberships as $mem) {
226 $mem['member_of_contact_id'] = CRM_Utils_Array::value($mem['membership_type_id'], $memberorgs);
227 if (!empty($mem['membership_end_date'])) {
228 $mem['membership_end_date'] = CRM_Utils_Date::customFormat($mem['membership_end_date']);
229 }
230 $mem['membership_type'] = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipType',
231 $mem['membership_type_id'],
232 'name', 'id'
233 );
234 $mem['membership_status'] = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipStatus',
235 $mem['status_id'],
236 'label', 'id'
237 );
238 $mem['renewUrl'] = CRM_Utils_System::url('civicrm/contact/view/membership',
239 "reset=1&action=renew&cid={$this->_contactID}&id={$mem['id']}&context=membership&selectedChild=member"
240 . ($this->_mode ? '&mode=live' : '')
241 );
242 $mem['membershipTab'] = CRM_Utils_System::url('civicrm/contact/view',
243 "reset=1&force=1&cid={$this->_contactID}&selectedChild=member"
244 );
245 $mems_by_org[$mem['member_of_contact_id']] = $mem;
246 }
247 $this->assign('existingContactMemberships', $mems_by_org);
248 }
249 }
250 else {
251 // In standalone mode we don't have a contact id yet so lookup will be done client-side with this script:
252 $resources = CRM_Core_Resources::singleton();
253 $resources->addScriptFile('civicrm', 'templates/CRM/Member/Form/MembershipStandalone.js');
254 $passthru = [
255 'typeorgs' => CRM_Member_BAO_MembershipType::getMembershipTypeOrganization(),
256 'memtypes' => CRM_Core_PseudoConstant::get('CRM_Member_BAO_Membership', 'membership_type_id'),
257 'statuses' => CRM_Core_PseudoConstant::get('CRM_Member_BAO_Membership', 'status_id'),
258 ];
259 $resources->addSetting(['existingMems' => $passthru]);
260 }
261 }
262
263 if (!$this->_memType) {
264 $params = CRM_Utils_Request::exportValues();
265 if (!empty($params['membership_type_id'][1])) {
266 $this->_memType = $params['membership_type_id'][1];
267 }
268 }
269
270 // Add custom data to form
271 CRM_Custom_Form_CustomData::addToForm($this, $this->_memType);
272
273 // CRM-4395, get the online pending contribution id.
274 $this->_onlinePendingContributionId = NULL;
275 if (!$this->_mode && $this->_id && ($this->_action & CRM_Core_Action::UPDATE)) {
276 $this->_onlinePendingContributionId = CRM_Contribute_BAO_Contribution::checkOnlinePendingContribution($this->_id,
277 'Membership'
278 );
279 }
280 $this->assign('onlinePendingContributionId', $this->_onlinePendingContributionId);
281
282 $this->setPageTitle(ts('Membership'));
283 }
284
285 /**
286 * Set default values for the form.
287 */
288 public function setDefaultValues() {
289
290 if ($this->_priceSetId) {
291 return CRM_Price_BAO_PriceSet::setDefaultPriceSet($this, $defaults);
292 }
293
294 $defaults = parent::setDefaultValues();
295
296 //setting default join date and receive date
297 if ($this->_action == CRM_Core_Action::ADD) {
298 $defaults['receive_date'] = date('Y-m-d H:i:s');
299 }
300
301 $defaults['num_terms'] = 1;
302
303 if (!empty($defaults['id'])) {
304 if ($this->_onlinePendingContributionId) {
305 $defaults['record_contribution'] = $this->_onlinePendingContributionId;
306 }
307 else {
308 $contributionId = CRM_Core_DAO::singleValueQuery("
309 SELECT contribution_id
310 FROM civicrm_membership_payment
311 WHERE membership_id = $this->_id
312 ORDER BY contribution_id
313 DESC limit 1");
314
315 if ($contributionId) {
316 $defaults['record_contribution'] = $contributionId;
317 }
318 }
319 }
320 else {
321 if ($this->_contactID) {
322 $defaults['contact_id'] = $this->_contactID;
323 }
324 }
325
326 //set Soft Credit Type to Gift by default
327 $scTypes = CRM_Core_OptionGroup::values("soft_credit_type");
328 $defaults['soft_credit_type_id'] = CRM_Utils_Array::value(ts('Gift'), array_flip($scTypes));
329
330 //CRM-13420
331 if (empty($defaults['payment_instrument_id'])) {
332 $defaults['payment_instrument_id'] = key(CRM_Core_OptionGroup::values('payment_instrument', FALSE, FALSE, FALSE, 'AND is_default = 1'));
333 }
334
335 // User must explicitly choose to send a receipt in both add and update mode.
336 $defaults['send_receipt'] = 0;
337
338 if ($this->_action & CRM_Core_Action::UPDATE) {
339 // in this mode by default uncheck this checkbox
340 unset($defaults['record_contribution']);
341 }
342
343 $subscriptionCancelled = FALSE;
344 if (!empty($defaults['id'])) {
345 $subscriptionCancelled = CRM_Member_BAO_Membership::isSubscriptionCancelled($this->_id);
346 }
347
348 $alreadyAutoRenew = FALSE;
349 if (!empty($defaults['contribution_recur_id']) && !$subscriptionCancelled) {
350 $defaults['auto_renew'] = 1;
351 $alreadyAutoRenew = TRUE;
352 }
353 $this->assign('alreadyAutoRenew', $alreadyAutoRenew);
354
355 $this->assign('member_is_test', CRM_Utils_Array::value('member_is_test', $defaults));
356
357 $this->assign('membership_status_id', CRM_Utils_Array::value('status_id', $defaults));
358
359 if (!empty($defaults['is_pay_later'])) {
360 $this->assign('is_pay_later', TRUE);
361 }
362 if ($this->_mode) {
363 $defaults = $this->getBillingDefaults($defaults);
364 // hack to simplify credit card entry for testing
365 // $defaults['credit_card_type'] = 'Visa';
366 // $defaults['credit_card_number'] = '4807731747657838';
367 // $defaults['cvv2'] = '000';
368 // $defaults['credit_card_exp_date'] = array( 'Y' => '2012', 'M' => '05' );
369 }
370
371 //setting default join date if there is no join date
372 if (empty($defaults['join_date'])) {
373 $defaults['join_date'] = date('Y-m-d');
374 }
375
376 if (!empty($defaults['membership_end_date'])) {
377 $this->assign('endDate', $defaults['membership_end_date']);
378 }
379
380 return $defaults;
381 }
382
383 /**
384 * Build the form object.
385 */
386 public function buildQuickForm() {
387
388 $this->buildQuickEntityForm();
389 $this->assign('currency', CRM_Core_BAO_Country::defaultCurrencySymbol());
390 $isUpdateToExistingRecurringMembership = $this->isUpdateToExistingRecurringMembership();
391 // build price set form.
392 $buildPriceSet = FALSE;
393 if ($this->_priceSetId || !empty($_POST['price_set_id'])) {
394 if (!empty($_POST['price_set_id'])) {
395 $buildPriceSet = TRUE;
396 }
397 $getOnlyPriceSetElements = TRUE;
398 if (!$this->_priceSetId) {
399 $this->_priceSetId = $_POST['price_set_id'];
400 $getOnlyPriceSetElements = FALSE;
401 }
402
403 $this->set('priceSetId', $this->_priceSetId);
404 CRM_Price_BAO_PriceSet::buildPriceSet($this);
405
406 $optionsMembershipTypes = [];
407 foreach ($this->_priceSet['fields'] as $pField) {
408 if (empty($pField['options'])) {
409 continue;
410 }
411 foreach ($pField['options'] as $opId => $opValues) {
412 $optionsMembershipTypes[$opId] = CRM_Utils_Array::value('membership_type_id', $opValues, 0);
413 }
414 }
415
416 $this->assign('autoRenewOption', CRM_Price_BAO_PriceSet::checkAutoRenewForPriceSet($this->_priceSetId));
417
418 $this->assign('optionsMembershipTypes', $optionsMembershipTypes);
419 $this->assign('contributionType', CRM_Utils_Array::value('financial_type_id', $this->_priceSet));
420
421 // get only price set form elements.
422 if ($getOnlyPriceSetElements) {
423 return;
424 }
425 }
426
427 // use to build form during form rule.
428 $this->assign('buildPriceSet', $buildPriceSet);
429
430 if ($this->_action & CRM_Core_Action::ADD) {
431 $buildPriceSet = FALSE;
432 $priceSets = CRM_Price_BAO_PriceSet::getAssoc(FALSE, 'CiviMember');
433 if (!empty($priceSets)) {
434 $buildPriceSet = TRUE;
435 }
436
437 if ($buildPriceSet) {
438 $this->add('select', 'price_set_id', ts('Choose price set'),
439 [
440 '' => ts('Choose price set'),
441 ] + $priceSets,
442 NULL, ['onchange' => "buildAmount( this.value );"]
443 );
444 }
445 $this->assign('hasPriceSets', $buildPriceSet);
446 }
447
448 if ($this->_action & CRM_Core_Action::DELETE) {
449 $this->addButtons([
450 [
451 'type' => 'next',
452 'name' => ts('Delete'),
453 'spacing' => '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;',
454 'isDefault' => TRUE,
455 ],
456 [
457 'type' => 'cancel',
458 'name' => ts('Cancel'),
459 ],
460 ]);
461 return;
462 }
463
464 $contactField = $this->addEntityRef('contact_id', ts('Member'), ['create' => TRUE, 'api' => ['extra' => ['email']]], TRUE);
465 if ($this->_context != 'standalone') {
466 $contactField->freeze();
467 }
468
469 $selOrgMemType[0][0] = $selMemTypeOrg[0] = ts('- select -');
470
471 // Throw status bounce when no Membership type or priceset is present
472 if (CRM_Financial_BAO_FinancialType::isACLFinancialTypeStatus()
473 && empty($this->allMembershipTypeDetails) && empty($priceSets)
474 ) {
475 CRM_Core_Error::statusBounce(ts('You do not have all the permissions needed for this page.'));
476 }
477 // retrieve all memberships
478 $allMembershipInfo = [];
479 foreach ($this->allMembershipTypeDetails as $key => $values) {
480 if ($this->_mode && empty($values['minimum_fee'])) {
481 continue;
482 }
483 else {
484 $memberOfContactId = CRM_Utils_Array::value('member_of_contact_id', $values);
485 if (empty($selMemTypeOrg[$memberOfContactId])) {
486 $selMemTypeOrg[$memberOfContactId] = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact',
487 $memberOfContactId,
488 'display_name',
489 'id'
490 );
491
492 $selOrgMemType[$memberOfContactId][0] = ts('- select -');
493 }
494 if (empty($selOrgMemType[$memberOfContactId][$key])) {
495 $selOrgMemType[$memberOfContactId][$key] = CRM_Utils_Array::value('name', $values);
496 }
497 }
498 $totalAmount = CRM_Utils_Array::value('minimum_fee', $values);
499 //CRM-18827 - override the default value if total_amount is submitted
500 if (!empty($this->_submitValues['total_amount'])) {
501 $totalAmount = $this->_submitValues['total_amount'];
502 }
503 // build membership info array, which is used when membership type is selected to:
504 // - set the payment information block
505 // - set the max related block
506 $allMembershipInfo[$key] = [
507 'financial_type_id' => CRM_Utils_Array::value('financial_type_id', $values),
508 'total_amount' => CRM_Utils_Money::format($totalAmount, NULL, '%a'),
509 'total_amount_numeric' => $totalAmount,
510 'auto_renew' => CRM_Utils_Array::value('auto_renew', $values),
511 'has_related' => isset($values['relationship_type_id']),
512 'max_related' => CRM_Utils_Array::value('max_related', $values),
513 ];
514 }
515
516 $this->assign('allMembershipInfo', json_encode($allMembershipInfo));
517
518 // show organization by default, if only one organization in
519 // the list
520 if (count($selMemTypeOrg) == 2) {
521 unset($selMemTypeOrg[0], $selOrgMemType[0][0]);
522 }
523 //sort membership organization and type, CRM-6099
524 natcasesort($selMemTypeOrg);
525 foreach ($selOrgMemType as $index => $orgMembershipType) {
526 natcasesort($orgMembershipType);
527 $selOrgMemType[$index] = $orgMembershipType;
528 }
529
530 $memTypeJs = [
531 'onChange' => "buildMaxRelated(this.value,true); CRM.buildCustomData('Membership', this.value);",
532 ];
533
534 if (!empty($this->_recurPaymentProcessors)) {
535 $memTypeJs['onChange'] = "" . $memTypeJs['onChange'] . " buildAutoRenew(this.value, null, '{$this->_mode}');";
536 }
537
538 $this->add('text', 'max_related', ts('Max related'),
539 CRM_Core_DAO::getAttribute('CRM_Member_DAO_Membership', 'max_related')
540 );
541
542 $sel = &$this->addElement('hierselect',
543 'membership_type_id',
544 ts('Membership Organization and Type'),
545 $memTypeJs
546 );
547
548 $sel->setOptions([$selMemTypeOrg, $selOrgMemType]);
549 if ($isUpdateToExistingRecurringMembership) {
550 $sel->freeze();
551 }
552
553 if ($this->_action & CRM_Core_Action::ADD) {
554 $this->add('number', 'num_terms', ts('Number of Terms'), ['size' => 6]);
555 }
556
557 $this->add('text', 'source', ts('Source'),
558 CRM_Core_DAO::getAttribute('CRM_Member_DAO_Membership', 'source')
559 );
560
561 //CRM-7362 --add campaigns.
562 $campaignId = NULL;
563 if ($this->_id) {
564 $campaignId = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_Membership', $this->_id, 'campaign_id');
565 }
566 CRM_Campaign_BAO_Campaign::addCampaign($this, $campaignId);
567
568 if (!$this->_mode) {
569 $this->add('select', 'status_id', ts('Membership Status'),
570 ['' => ts('- select -')] + CRM_Member_PseudoConstant::membershipStatus(NULL, NULL, 'label')
571 );
572
573 $statusOverride = $this->addElement('select', 'is_override', ts('Status Override?'),
574 CRM_Member_StatusOverrideTypes::getSelectOptions()
575 );
576 if ($statusOverride && $isUpdateToExistingRecurringMembership) {
577 $statusOverride->freeze();
578 }
579
580 $this->add('datepicker', 'status_override_end_date', ts('Status Override End Date'), '', FALSE, ['minDate' => time(), 'time' => FALSE]);
581
582 $this->addElement('checkbox', 'record_contribution', ts('Record Membership Payment?'));
583
584 $this->add('text', 'total_amount', ts('Amount'));
585 $this->addRule('total_amount', ts('Please enter a valid amount.'), 'money');
586
587 $this->add('datepicker', 'receive_date', ts('Received'), [], FALSE, ['time' => TRUE]);
588
589 $this->add('select', 'payment_instrument_id',
590 ts('Payment Method'),
591 ['' => ts('- select -')] + CRM_Contribute_PseudoConstant::paymentInstrument(),
592 FALSE, ['onChange' => "return showHideByValue('payment_instrument_id','4','checkNumber','table-row','select',false);"]
593 );
594 $this->add('text', 'trxn_id', ts('Transaction ID'));
595 $this->addRule('trxn_id', ts('Transaction ID already exists in Database.'),
596 'objectExists', [
597 'CRM_Contribute_DAO_Contribution',
598 $this->_id,
599 'trxn_id',
600 ]
601 );
602
603 $this->add('select', 'contribution_status_id',
604 ts('Payment Status'), CRM_Contribute_BAO_Contribution_Utils::getContributionStatuses('membership')
605 );
606 $this->add('text', 'check_number', ts('Check Number'),
607 CRM_Core_DAO::getAttribute('CRM_Contribute_DAO_Contribution', 'check_number')
608 );
609 }
610 else {
611 //add field for amount to allow an amount to be entered that differs from minimum
612 $this->add('text', 'total_amount', ts('Amount'));
613 }
614 $this->add('select', 'financial_type_id',
615 ts('Financial Type'),
616 ['' => ts('- select -')] + CRM_Financial_BAO_FinancialType::getAvailableFinancialTypes($financialTypes, $this->_action)
617 );
618
619 $this->addElement('checkbox', 'is_different_contribution_contact', ts('Record Payment from a Different Contact?'));
620
621 $this->addSelect('soft_credit_type_id', ['entity' => 'contribution_soft']);
622 $this->addEntityRef('soft_credit_contact_id', ts('Payment From'), ['create' => TRUE]);
623
624 $this->addElement('checkbox',
625 'send_receipt',
626 ts('Send Confirmation and Receipt?'), NULL,
627 ['onclick' => "showEmailOptions()"]
628 );
629
630 $this->add('select', 'from_email_address', ts('Receipt From'), $this->_fromEmails);
631
632 $this->add('textarea', 'receipt_text', ts('Receipt Message'));
633
634 // Retrieve the name and email of the contact - this will be the TO for receipt email
635 if ($this->_contactID) {
636 list($this->_memberDisplayName,
637 $this->_memberEmail
638 ) = CRM_Contact_BAO_Contact_Location::getEmailDetails($this->_contactID);
639
640 $this->assign('emailExists', $this->_memberEmail);
641 $this->assign('displayName', $this->_memberDisplayName);
642 }
643
644 if ($isUpdateToExistingRecurringMembership && CRM_Member_BAO_Membership::isCancelSubscriptionSupported($this->_id)) {
645 $this->assign('cancelAutoRenew',
646 CRM_Utils_System::url('civicrm/contribute/unsubscribe', "reset=1&mid={$this->_id}")
647 );
648 }
649
650 $this->assign('isRecur', $isUpdateToExistingRecurringMembership);
651
652 $this->addFormRule(['CRM_Member_Form_Membership', 'formRule'], $this);
653 $mailingInfo = Civi::settings()->get('mailing_backend');
654 $this->assign('isEmailEnabledForSite', ($mailingInfo['outBound_option'] != 2));
655
656 parent::buildQuickForm();
657 }
658
659 /**
660 * Validation.
661 *
662 * @param array $params
663 * (ref.) an assoc array of name/value pairs.
664 *
665 * @param array $files
666 * @param CRM_Member_Form_Membership $self
667 *
668 * @throws CiviCRM_API3_Exception
669 * @return bool|array
670 * mixed true or array of errors
671 */
672 public static function formRule($params, $files, $self) {
673 $errors = [];
674
675 $priceSetId = self::getPriceSetID($params);
676 $priceSetDetails = self::getPriceSetDetails($params);
677
678 $selectedMemberships = self::getSelectedMemberships($priceSetDetails[$priceSetId], $params);
679
680 if (!empty($params['price_set_id'])) {
681 CRM_Price_BAO_PriceField::priceSetValidation($priceSetId, $params, $errors);
682
683 $priceFieldIDS = self::getPriceFieldIDs($params, $priceSetDetails[$priceSetId]);
684
685 if (!empty($priceFieldIDS)) {
686 $ids = implode(',', $priceFieldIDS);
687
688 $count = CRM_Price_BAO_PriceSet::getMembershipCount($ids);
689 foreach ($count as $occurrence) {
690 if ($occurrence > 1) {
691 $errors['_qf_default'] = ts('Select at most one option associated with the same membership type.');
692 }
693 }
694 }
695 // Return error if empty $self->_memTypeSelected
696 if (empty($errors) && empty($selectedMemberships)) {
697 $errors['_qf_default'] = ts('Select at least one membership option.');
698 }
699 if (!$self->_mode && empty($params['record_contribution'])) {
700 $errors['record_contribution'] = ts('Record Membership Payment is required when you use a price set.');
701 }
702 }
703 else {
704 if (empty($params['membership_type_id'][1])) {
705 $errors['membership_type_id'] = ts('Please select a membership type.');
706 }
707 $numterms = CRM_Utils_Array::value('num_terms', $params);
708 if ($numterms && intval($numterms) != $numterms) {
709 $errors['num_terms'] = ts('Please enter an integer for the number of terms.');
710 }
711
712 if (($self->_mode || isset($params['record_contribution'])) && empty($params['financial_type_id'])) {
713 $errors['financial_type_id'] = ts('Please enter the financial Type.');
714 }
715 }
716
717 if (!empty($errors) && (count($selectedMemberships) > 1)) {
718 $memberOfContacts = CRM_Member_BAO_MembershipType::getMemberOfContactByMemTypes($selectedMemberships);
719 $duplicateMemberOfContacts = array_count_values($memberOfContacts);
720 foreach ($duplicateMemberOfContacts as $countDuplicate) {
721 if ($countDuplicate > 1) {
722 $errors['_qf_default'] = ts('Please do not select more than one membership associated with the same organization.');
723 }
724 }
725 }
726
727 if (!empty($errors)) {
728 return $errors;
729 }
730
731 if (!empty($params['record_contribution']) && empty($params['payment_instrument_id'])) {
732 $errors['payment_instrument_id'] = ts('Payment Method is a required field.');
733 }
734
735 if (!empty($params['is_different_contribution_contact'])) {
736 if (empty($params['soft_credit_type_id'])) {
737 $errors['soft_credit_type_id'] = ts('Please Select a Soft Credit Type');
738 }
739 if (empty($params['soft_credit_contact_id'])) {
740 $errors['soft_credit_contact_id'] = ts('Please select a contact');
741 }
742 }
743
744 if (!empty($params['payment_processor_id'])) {
745 // validate payment instrument (e.g. credit card number)
746 CRM_Core_Payment_Form::validatePaymentInstrument($params['payment_processor_id'], $params, $errors, NULL);
747 }
748
749 $joinDate = NULL;
750 if (!empty($params['join_date'])) {
751
752 $joinDate = CRM_Utils_Date::processDate($params['join_date']);
753
754 foreach ($selectedMemberships as $memType) {
755 $startDate = NULL;
756 if (!empty($params['start_date'])) {
757 $startDate = CRM_Utils_Date::processDate($params['start_date']);
758 }
759
760 // if end date is set, ensure that start date is also set
761 // and that end date is later than start date
762 $endDate = NULL;
763 if (!empty($params['end_date'])) {
764 $endDate = CRM_Utils_Date::processDate($params['end_date']);
765 }
766
767 $membershipDetails = CRM_Member_BAO_MembershipType::getMembershipTypeDetails($memType);
768
769 if ($startDate && CRM_Utils_Array::value('period_type', $membershipDetails) == 'rolling') {
770 if ($startDate < $joinDate) {
771 $errors['start_date'] = ts('Start date must be the same or later than Member since.');
772 }
773 }
774
775 if ($endDate) {
776 if ($membershipDetails['duration_unit'] == 'lifetime') {
777 // Check if status is NOT cancelled or similar. For lifetime memberships, there is no automated
778 // process to update status based on end-date. The user must change the status now.
779 $result = civicrm_api3('MembershipStatus', 'get', [
780 'sequential' => 1,
781 'is_current_member' => 0,
782 ]);
783 $tmp_statuses = $result['values'];
784 $status_ids = [];
785 foreach ($tmp_statuses as $cur_stat) {
786 $status_ids[] = $cur_stat['id'];
787 }
788
789 if (empty($params['status_id']) || in_array($params['status_id'], $status_ids) == FALSE) {
790 $errors['status_id'] = ts('Please enter a status that does NOT represent a current membership status.');
791 }
792
793 if (!empty($params['is_override']) && !CRM_Member_StatusOverrideTypes::isPermanent($params['is_override'])) {
794 $errors['is_override'] = ts('Because you set an End Date for a lifetime membership, This must be set to "Override Permanently"');
795 }
796 }
797 else {
798 if (!$startDate) {
799 $errors['start_date'] = ts('Start date must be set if end date is set.');
800 }
801 if ($endDate < $startDate) {
802 $errors['end_date'] = ts('End date must be the same or later than start date.');
803 }
804 }
805 }
806
807 // Default values for start and end dates if not supplied on the form.
808 $defaultDates = CRM_Member_BAO_MembershipType::getDatesForMembershipType($memType,
809 $joinDate,
810 $startDate,
811 $endDate
812 );
813
814 if (!$startDate) {
815 $startDate = CRM_Utils_Array::value('start_date',
816 $defaultDates
817 );
818 }
819 if (!$endDate) {
820 $endDate = CRM_Utils_Array::value('end_date',
821 $defaultDates
822 );
823 }
824
825 //CRM-3724, check for availability of valid membership status.
826 if ((empty($params['is_override']) || CRM_Member_StatusOverrideTypes::isNo($params['is_override'])) && !isset($errors['_qf_default'])) {
827 $calcStatus = CRM_Member_BAO_MembershipStatus::getMembershipStatusByDate($startDate,
828 $endDate,
829 $joinDate,
830 'today',
831 TRUE,
832 $memType,
833 $params
834 );
835 if (empty($calcStatus)) {
836 $url = CRM_Utils_System::url('civicrm/admin/member/membershipStatus', 'reset=1&action=browse');
837 $errors['_qf_default'] = ts('There is no valid Membership Status available for selected membership dates.');
838 $status = ts('Oops, it looks like there is no valid membership status available for the given membership dates. You can <a href="%1">Configure Membership Status Rules</a>.', [1 => $url]);
839 if (!$self->_mode) {
840 $status .= ' ' . ts('OR You can sign up by setting Status Override? to something other than "NO".');
841 }
842 CRM_Core_Session::setStatus($status, ts('Membership Status Error'), 'error');
843 }
844 }
845 }
846 }
847 else {
848 $errors['join_date'] = ts('Please enter the Member Since.');
849 }
850
851 if (!empty($params['is_override']) && CRM_Member_StatusOverrideTypes::isOverridden($params['is_override']) && empty($params['status_id'])) {
852 $errors['status_id'] = ts('Please enter the Membership status.');
853 }
854
855 if (!empty($params['is_override']) && CRM_Member_StatusOverrideTypes::isUntilDate($params['is_override'])) {
856 if (empty($params['status_override_end_date'])) {
857 $errors['status_override_end_date'] = ts('Please enter the Membership override end date.');
858 }
859 }
860
861 //total amount condition arise when membership type having no
862 //minimum fee
863 if (isset($params['record_contribution'])) {
864 if (CRM_Utils_System::isNull($params['total_amount'])) {
865 $errors['total_amount'] = ts('Please enter the contribution.');
866 }
867 }
868
869 // validate contribution status for 'Failed'.
870 if ($self->_onlinePendingContributionId && !empty($params['record_contribution']) &&
871 (CRM_Utils_Array::value('contribution_status_id', $params) ==
872 array_search('Failed', CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name'))
873 )
874 ) {
875 $errors['contribution_status_id'] = ts('Please select a valid payment status before updating.');
876 }
877
878 return empty($errors) ? TRUE : $errors;
879 }
880
881 /**
882 * Process the form submission.
883 */
884 public function postProcess() {
885 if ($this->_action & CRM_Core_Action::DELETE) {
886 CRM_Member_BAO_Membership::del($this->_id);
887 return;
888 }
889 // get the submitted form values.
890 $this->_params = $this->controller->exportValues($this->_name);
891 $this->prepareStatusOverrideValues();
892
893 $this->submit();
894
895 $this->setUserContext();
896 }
897
898 /**
899 * Prepares the values related to status override.
900 */
901 private function prepareStatusOverrideValues() {
902 $this->setOverrideDateValue();
903 $this->convertIsOverrideValue();
904 }
905
906 /**
907 * Sets status override end date to empty value if
908 * the selected override option is not 'until date'.
909 */
910 private function setOverrideDateValue() {
911 if (!CRM_Member_StatusOverrideTypes::isUntilDate(CRM_Utils_Array::value('is_override', $this->_params))) {
912 $this->_params['status_override_end_date'] = '';
913 }
914 }
915
916 /**
917 * Convert the value of selected (status override?)
918 * option to TRUE if it indicate an overridden status
919 * or FALSE otherwise.
920 */
921 private function convertIsOverrideValue() {
922 $this->_params['is_override'] = CRM_Member_StatusOverrideTypes::isOverridden($this->_params['is_override']);
923 }
924
925 /**
926 * Send email receipt.
927 *
928 * @deprecated
929 * This function is shared with Batch_Entry which has limited overlap
930 * & needs rationalising.
931 *
932 * @param CRM_Core_Form $form
933 * Form object.
934 * @param array $formValues
935 * @param object $membership
936 * Object.
937 * @param array $customValues
938 *
939 * @return bool
940 * true if mail was sent successfully
941 */
942 public static function emailReceipt(&$form, &$formValues, &$membership, $customValues = NULL) {
943 // retrieve 'from email id' for acknowledgement
944 $receiptFrom = CRM_Utils_Array::value('from_email_address', $formValues);
945
946 // @todo figure out how much of the stuff below is genuinely shared with the batch form & a logical shared place.
947 if (!empty($formValues['payment_instrument_id'])) {
948 $paymentInstrument = CRM_Contribute_PseudoConstant::paymentInstrument();
949 $formValues['paidBy'] = $paymentInstrument[$formValues['payment_instrument_id']];
950 }
951
952 $form->assign('customValues', $customValues);
953
954 if ($form->_mode) {
955 // @todo move this outside shared code as Batch entry just doesn't
956 $form->assign('address', CRM_Utils_Address::getFormattedBillingAddressFieldsFromParameters(
957 $form->_params,
958 $form->_bltID
959 ));
960
961 $date = CRM_Utils_Date::format($form->_params['credit_card_exp_date']);
962 $date = CRM_Utils_Date::mysqlToIso($date);
963 $form->assign('credit_card_exp_date', $date);
964 $form->assign('credit_card_number',
965 CRM_Utils_System::mungeCreditCard($form->_params['credit_card_number'])
966 );
967 $form->assign('credit_card_type', $form->_params['credit_card_type']);
968 $form->assign('contributeMode', 'direct');
969 $form->assign('isAmountzero', 0);
970 $form->assign('is_pay_later', 0);
971 $form->assign('isPrimary', 1);
972 }
973
974 $form->assign('module', 'Membership');
975 $form->assign('contactID', $formValues['contact_id']);
976
977 $form->assign('membershipID', CRM_Utils_Array::value('membership_id', $form->_params, CRM_Utils_Array::value('membership_id', $form->_defaultValues)));
978
979 if (!empty($formValues['contribution_id'])) {
980 $form->assign('contributionID', $formValues['contribution_id']);
981 }
982 elseif (isset($form->_onlinePendingContributionId)) {
983 $form->assign('contributionID', $form->_onlinePendingContributionId);
984 }
985
986 if (!empty($formValues['contribution_status_id'])) {
987 $form->assign('contributionStatusID', $formValues['contribution_status_id']);
988 $form->assign('contributionStatus', CRM_Contribute_PseudoConstant::contributionStatus($formValues['contribution_status_id'], 'name'));
989 }
990
991 if (!empty($formValues['is_renew'])) {
992 $form->assign('receiptType', 'membership renewal');
993 }
994 else {
995 $form->assign('receiptType', 'membership signup');
996 }
997 $form->assign('receive_date', CRM_Utils_Array::value('receive_date', $formValues));
998 $form->assign('formValues', $formValues);
999
1000 if (empty($lineItem)) {
1001 $form->assign('mem_start_date', CRM_Utils_Date::customFormat($membership->start_date, '%B %E%f, %Y'));
1002 if (!CRM_Utils_System::isNull($membership->end_date)) {
1003 $form->assign('mem_end_date', CRM_Utils_Date::customFormat($membership->end_date, '%B %E%f, %Y'));
1004 }
1005 $form->assign('membership_name', CRM_Member_PseudoConstant::membershipType($membership->membership_type_id));
1006 }
1007
1008 // @todo - if we have to figure out if this is for batch processing it doesn't belong in the shared function.
1009 $isBatchProcess = is_a($form, 'CRM_Batch_Form_Entry');
1010 if ((empty($form->_contributorDisplayName) || empty($form->_contributorEmail)) || $isBatchProcess) {
1011 // in this case the form is being called statically from the batch editing screen
1012 // having one class in the form layer call another statically is not greate
1013 // & we should aim to move this function to the BAO layer in future.
1014 // however, we can assume that the contact_id passed in by the batch
1015 // function will be the recipient
1016 list($form->_contributorDisplayName, $form->_contributorEmail)
1017 = CRM_Contact_BAO_Contact_Location::getEmailDetails($formValues['contact_id']);
1018 if (empty($form->_receiptContactId) || $isBatchProcess) {
1019 $form->_receiptContactId = $formValues['contact_id'];
1020 }
1021 }
1022 // @todo determine isEmailPdf in calling function.
1023 $template = CRM_Core_Smarty::singleton();
1024 $taxAmt = $template->get_template_vars('dataArray');
1025 $eventTaxAmt = $template->get_template_vars('totalTaxAmount');
1026 $prefixValue = Civi::settings()->get('contribution_invoice_settings');
1027 $invoicing = CRM_Utils_Array::value('invoicing', $prefixValue);
1028 if ((!empty($taxAmt) || isset($eventTaxAmt)) && (isset($invoicing) && isset($prefixValue['is_email_pdf']))) {
1029 $isEmailPdf = TRUE;
1030 }
1031 else {
1032 $isEmailPdf = FALSE;
1033 }
1034
1035 list($mailSend, $subject, $message, $html) = CRM_Core_BAO_MessageTemplate::sendTemplate(
1036 [
1037 'groupName' => 'msg_tpl_workflow_membership',
1038 'valueName' => 'membership_offline_receipt',
1039 'contactId' => $form->_receiptContactId,
1040 'from' => $receiptFrom,
1041 'toName' => $form->_contributorDisplayName,
1042 'toEmail' => $form->_contributorEmail,
1043 'PDFFilename' => ts('receipt') . '.pdf',
1044 'isEmailPdf' => $isEmailPdf,
1045 'contributionId' => $formValues['contribution_id'],
1046 'isTest' => (bool) ($form->_action & CRM_Core_Action::PREVIEW),
1047 ]
1048 );
1049
1050 return TRUE;
1051 }
1052
1053 /**
1054 * Submit function.
1055 *
1056 * This is also accessed by unit tests.
1057 */
1058 public function submit() {
1059 $isTest = ($this->_mode == 'test') ? 1 : 0;
1060 $this->storeContactFields($this->_params);
1061 $this->beginPostProcess();
1062 $joinDate = $startDate = $endDate = NULL;
1063 $membershipTypes = $membership = $calcDate = [];
1064 $membershipType = NULL;
1065 $paymentInstrumentID = $this->_paymentProcessor['object']->getPaymentInstrumentID();
1066 $params = $softParams = $ids = [];
1067
1068 $mailSend = FALSE;
1069 $this->processBillingAddress();
1070 $formValues = $this->_params;
1071 $formValues = $this->setPriceSetParameters($formValues);
1072
1073 if ($this->_id) {
1074 $ids['membership'] = $params['id'] = $this->_id;
1075 }
1076 $ids['userId'] = CRM_Core_Session::singleton()->get('userID');
1077
1078 // Set variables that we normally get from context.
1079 // In form mode these are set in preProcess.
1080 //TODO: set memberships, fixme
1081 $this->setContextVariables($formValues);
1082
1083 $this->_memTypeSelected = self::getSelectedMemberships(
1084 $this->_priceSet,
1085 $formValues
1086 );
1087 if (empty($formValues['financial_type_id'])) {
1088 $formValues['financial_type_id'] = $this->_priceSet['financial_type_id'];
1089 }
1090
1091 $config = CRM_Core_Config::singleton();
1092
1093 $membershipTypeValues = [];
1094 foreach ($this->_memTypeSelected as $memType) {
1095 $membershipTypeValues[$memType]['membership_type_id'] = $memType;
1096 }
1097
1098 //take the required membership recur values.
1099 if ($this->_mode && !empty($formValues['auto_renew'])) {
1100 $params['is_recur'] = $formValues['is_recur'] = TRUE;
1101
1102 $count = 0;
1103 foreach ($this->_memTypeSelected as $memType) {
1104 $recurMembershipTypeValues = CRM_Utils_Array::value($memType,
1105 $this->allMembershipTypeDetails, []
1106 );
1107 if (!$recurMembershipTypeValues['auto_renew']) {
1108 continue;
1109 }
1110 foreach ([
1111 'frequency_interval' => 'duration_interval',
1112 'frequency_unit' => 'duration_unit',
1113 ] as $mapVal => $mapParam) {
1114 $membershipTypeValues[$memType][$mapVal] = $recurMembershipTypeValues[$mapParam];
1115
1116 if (!$count) {
1117 $formValues[$mapVal] = CRM_Utils_Array::value($mapParam,
1118 $recurMembershipTypeValues
1119 );
1120 }
1121 }
1122 $count++;
1123 }
1124 }
1125
1126 $isQuickConfig = $this->_priceSet['is_quick_config'];
1127
1128 $termsByType = [];
1129
1130 $lineItem = [$this->_priceSetId => []];
1131
1132 // BEGIN Fix for dev/core/issues/860
1133 // Prepare fee block and call buildAmount hook - based on CRM_Price_BAO_PriceSet::buildPriceSet().
1134 CRM_Price_BAO_PriceSet::applyACLFinancialTypeStatusToFeeBlock($this->_priceSet['fields']);
1135 CRM_Utils_Hook::buildAmount('membership', $this, $this->_priceSet['fields']);
1136 // END Fix for dev/core/issues/860
1137
1138 CRM_Price_BAO_PriceSet::processAmount($this->_priceSet['fields'],
1139 $formValues, $lineItem[$this->_priceSetId], $this->_priceSetId);
1140
1141 if (!empty($formValues['tax_amount'])) {
1142 $params['tax_amount'] = $formValues['tax_amount'];
1143 }
1144 $params['total_amount'] = CRM_Utils_Array::value('amount', $formValues);
1145 if (!empty($lineItem[$this->_priceSetId])) {
1146 foreach ($lineItem[$this->_priceSetId] as &$li) {
1147 if (!empty($li['membership_type_id'])) {
1148 if (!empty($li['membership_num_terms'])) {
1149 $termsByType[$li['membership_type_id']] = $li['membership_num_terms'];
1150 }
1151 }
1152
1153 ///CRM-11529 for quick config backoffice transactions
1154 //when financial_type_id is passed in form, update the
1155 //lineitems with the financial type selected in form
1156 $submittedFinancialType = CRM_Utils_Array::value('financial_type_id', $formValues);
1157 if ($isQuickConfig && $submittedFinancialType) {
1158 $li['financial_type_id'] = $submittedFinancialType;
1159 }
1160 }
1161 }
1162
1163 $params['contact_id'] = $this->_contactID;
1164
1165 $fields = [
1166 'status_id',
1167 'source',
1168 'is_override',
1169 'status_override_end_date',
1170 'campaign_id',
1171 ];
1172
1173 foreach ($fields as $f) {
1174 $params[$f] = CRM_Utils_Array::value($f, $formValues);
1175 }
1176
1177 // fix for CRM-3724
1178 // when is_override false ignore is_admin statuses during membership
1179 // status calculation. similarly we did fix for import in CRM-3570.
1180 if (empty($params['is_override'])) {
1181 $params['exclude_is_admin'] = TRUE;
1182 }
1183
1184 // process date params to mysql date format.
1185 $dateTypes = [
1186 'join_date' => 'joinDate',
1187 'start_date' => 'startDate',
1188 'end_date' => 'endDate',
1189 ];
1190 foreach ($dateTypes as $dateField => $dateVariable) {
1191 $$dateVariable = CRM_Utils_Date::processDate($formValues[$dateField]);
1192 }
1193
1194 $memTypeNumTerms = empty($termsByType) ? CRM_Utils_Array::value('num_terms', $formValues) : NULL;
1195
1196 $calcDates = [];
1197 foreach ($this->_memTypeSelected as $memType) {
1198 if (empty($memTypeNumTerms)) {
1199 $memTypeNumTerms = CRM_Utils_Array::value($memType, $termsByType, 1);
1200 }
1201 $calcDates[$memType] = CRM_Member_BAO_MembershipType::getDatesForMembershipType($memType,
1202 $joinDate, $startDate, $endDate, $memTypeNumTerms
1203 );
1204 }
1205
1206 foreach ($calcDates as $memType => $calcDate) {
1207 foreach (array_keys($dateTypes) as $d) {
1208 //first give priority to form values then calDates.
1209 $date = CRM_Utils_Array::value($d, $formValues);
1210 if (!$date) {
1211 $date = CRM_Utils_Array::value($d, $calcDate);
1212 }
1213
1214 $membershipTypeValues[$memType][$d] = CRM_Utils_Date::processDate($date);
1215 }
1216 }
1217
1218 foreach ($this->_memTypeSelected as $memType) {
1219 if (array_key_exists('max_related', $formValues)) {
1220 // max related memberships - take from form or inherit from membership type
1221 $membershipTypeValues[$memType]['max_related'] = CRM_Utils_Array::value('max_related', $formValues);
1222 }
1223 $membershipTypeValues[$memType]['custom'] = CRM_Core_BAO_CustomField::postProcess($formValues,
1224 $this->_id,
1225 'Membership'
1226 );
1227 $membershipTypes[$memType] = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipType',
1228 $memType
1229 );
1230 }
1231
1232 $membershipType = implode(', ', $membershipTypes);
1233
1234 // Retrieve the name and email of the current user - this will be the FROM for the receipt email
1235 list($userName) = CRM_Contact_BAO_Contact_Location::getEmailDetails($ids['userId']);
1236
1237 //CRM-13981, allow different person as a soft-contributor of chosen type
1238 if ($this->_contributorContactID != $this->_contactID) {
1239 $params['contribution_contact_id'] = $this->_contributorContactID;
1240 if (!empty($formValues['soft_credit_type_id'])) {
1241 $softParams['soft_credit_type_id'] = $formValues['soft_credit_type_id'];
1242 $softParams['contact_id'] = $this->_contactID;
1243 }
1244 }
1245
1246 $pendingMembershipStatusId = CRM_Core_PseudoConstant::getKey('CRM_Member_BAO_Membership', 'status_id', 'Pending');
1247
1248 if (!empty($formValues['record_contribution'])) {
1249 $recordContribution = [
1250 'total_amount',
1251 'financial_type_id',
1252 'payment_instrument_id',
1253 'trxn_id',
1254 'contribution_status_id',
1255 'check_number',
1256 'campaign_id',
1257 'receive_date',
1258 'card_type_id',
1259 'pan_truncation',
1260 ];
1261
1262 foreach ($recordContribution as $f) {
1263 $params[$f] = CRM_Utils_Array::value($f, $formValues);
1264 }
1265
1266 if (!$this->_onlinePendingContributionId) {
1267 if (empty($formValues['source'])) {
1268 $params['contribution_source'] = ts('%1 Membership: Offline signup (by %2)', [
1269 1 => $membershipType,
1270 2 => $userName,
1271 ]);
1272 }
1273 else {
1274 $params['contribution_source'] = $formValues['source'];
1275 }
1276 }
1277
1278 $completedContributionStatusId = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed');
1279 if (empty($params['is_override']) &&
1280 CRM_Utils_Array::value('contribution_status_id', $params) != $completedContributionStatusId
1281 ) {
1282 $params['status_id'] = $pendingMembershipStatusId;
1283 $params['skipStatusCal'] = TRUE;
1284 $params['is_pay_later'] = 1;
1285 $this->assign('is_pay_later', 1);
1286 }
1287
1288 if (!empty($formValues['send_receipt'])) {
1289 $params['receipt_date'] = CRM_Utils_Array::value('receive_date', $formValues);
1290 }
1291
1292 //insert financial type name in receipt.
1293 $formValues['contributionType_name'] = CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_FinancialType',
1294 $formValues['financial_type_id']
1295 );
1296 }
1297
1298 // process line items, until no previous line items.
1299 if (!empty($lineItem)) {
1300 $params['lineItems'] = $lineItem;
1301 $params['processPriceSet'] = TRUE;
1302 }
1303 $createdMemberships = [];
1304 if ($this->_mode) {
1305 $params['total_amount'] = CRM_Utils_Array::value('total_amount', $formValues, 0);
1306
1307 //CRM-20264 : Store CC type and number (last 4 digit) during backoffice or online payment
1308 $params['card_type_id'] = CRM_Utils_Array::value('card_type_id', $this->_params);
1309 $params['pan_truncation'] = CRM_Utils_Array::value('pan_truncation', $this->_params);
1310
1311 if (!$isQuickConfig) {
1312 $params['financial_type_id'] = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet',
1313 $this->_priceSetId,
1314 'financial_type_id'
1315 );
1316 }
1317 else {
1318 $params['financial_type_id'] = CRM_Utils_Array::value('financial_type_id', $formValues);
1319 }
1320
1321 //get the payment processor id as per mode. Try removing in favour of beginPostProcess.
1322 $params['payment_processor_id'] = $formValues['payment_processor_id'] = $this->_paymentProcessor['id'];
1323 $params['register_date'] = date('YmdHis');
1324
1325 // add all the additional payment params we need
1326 $formValues['amount'] = $params['total_amount'];
1327 // @todo this is a candidate for beginPostProcessFunction.
1328 $formValues['currencyID'] = $config->defaultCurrency;
1329 $formValues['description'] = ts("Contribution submitted by a staff person using member's credit card for signup");
1330 $formValues['invoiceID'] = md5(uniqid(rand(), TRUE));
1331 $formValues['financial_type_id'] = $params['financial_type_id'];
1332
1333 // at this point we've created a contact and stored its address etc
1334 // all the payment processors expect the name and address to be in the
1335 // so we copy stuff over to first_name etc.
1336 $paymentParams = $formValues;
1337 $paymentParams['contactID'] = $this->_contributorContactID;
1338 //CRM-10377 if payment is by an alternate contact then we need to set that person
1339 // as the contact in the payment params
1340 if ($this->_contributorContactID != $this->_contactID) {
1341 if (!empty($formValues['soft_credit_type_id'])) {
1342 $softParams['contact_id'] = $params['contact_id'];
1343 $softParams['soft_credit_type_id'] = $formValues['soft_credit_type_id'];
1344 }
1345 }
1346 if (!empty($formValues['send_receipt'])) {
1347 $paymentParams['email'] = $this->_contributorEmail;
1348 }
1349
1350 // This is a candidate for shared beginPostProcess function.
1351 // @todo Do we need this now we have $this->formatParamsForPaymentProcessor() ?
1352 CRM_Core_Payment_Form::mapParams($this->_bltID, $formValues, $paymentParams, TRUE);
1353 // CRM-7137 -for recurring membership,
1354 // we do need contribution and recurring records.
1355 $result = NULL;
1356 if (!empty($paymentParams['is_recur'])) {
1357 $financialType = new CRM_Financial_DAO_FinancialType();
1358 $financialType->id = $params['financial_type_id'];
1359 $financialType->find(TRUE);
1360 $this->_params = $formValues;
1361
1362 $contribution = CRM_Contribute_Form_Contribution_Confirm::processFormContribution($this,
1363 $paymentParams,
1364 NULL,
1365 [
1366 'contact_id' => $this->_contributorContactID,
1367 'line_item' => $lineItem,
1368 'is_test' => $isTest,
1369 'campaign_id' => CRM_Utils_Array::value('campaign_id', $paymentParams),
1370 'contribution_page_id' => CRM_Utils_Array::value('contribution_page_id', $formValues),
1371 'source' => CRM_Utils_Array::value('source', $paymentParams, CRM_Utils_Array::value('description', $paymentParams)),
1372 'thankyou_date' => CRM_Utils_Array::value('thankyou_date', $paymentParams),
1373 'payment_instrument_id' => $paymentInstrumentID,
1374 ],
1375 $financialType,
1376 FALSE,
1377 $this->_bltID,
1378 TRUE
1379 );
1380
1381 //create new soft-credit record, CRM-13981
1382 if ($softParams) {
1383 $softParams['contribution_id'] = $contribution->id;
1384 $softParams['currency'] = $contribution->currency;
1385 $softParams['amount'] = $contribution->total_amount;
1386 CRM_Contribute_BAO_ContributionSoft::add($softParams);
1387 }
1388
1389 $paymentParams['contactID'] = $this->_contactID;
1390 $paymentParams['contributionID'] = $contribution->id;
1391 $paymentParams['contributionTypeID'] = $contribution->financial_type_id;
1392 $paymentParams['contributionPageID'] = $contribution->contribution_page_id;
1393 $paymentParams['contributionRecurID'] = $contribution->contribution_recur_id;
1394 $params['contribution_id'] = $paymentParams['contributionID'];
1395 $params['contribution_recur_id'] = $paymentParams['contributionRecurID'];
1396 }
1397 $paymentStatus = NULL;
1398
1399 if ($params['total_amount'] > 0.0) {
1400 $payment = $this->_paymentProcessor['object'];
1401 try {
1402 $result = $payment->doPayment($paymentParams);
1403 $formValues = array_merge($formValues, $result);
1404 $paymentStatus = CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $formValues['payment_status_id']);
1405 // Assign amount to template if payment was successful.
1406 $this->assign('amount', $params['total_amount']);
1407 }
1408 catch (\Civi\Payment\Exception\PaymentProcessorException $e) {
1409 if (!empty($paymentParams['contributionID'])) {
1410 CRM_Contribute_BAO_Contribution::failPayment($paymentParams['contributionID'], $this->_contactID,
1411 $e->getMessage());
1412 }
1413 if (!empty($paymentParams['contributionRecurID'])) {
1414 CRM_Contribute_BAO_ContributionRecur::deleteRecurContribution($paymentParams['contributionRecurID']);
1415 }
1416
1417 CRM_Core_Session::singleton()->setStatus($e->getMessage());
1418 CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contact/view/membership',
1419 "reset=1&action=add&cid={$this->_contactID}&context=membership&mode={$this->_mode}"
1420 ));
1421
1422 }
1423 }
1424
1425 if ($paymentStatus !== 'Completed') {
1426 $params['status_id'] = $pendingMembershipStatusId;
1427 $params['skipStatusCal'] = TRUE;
1428 // unset send-receipt option, since receipt will be sent when ipn is received.
1429 unset($formValues['send_receipt'], $formValues['send_receipt']);
1430 //as membership is pending set dates to null.
1431 $memberDates = [
1432 'join_date' => 'joinDate',
1433 'start_date' => 'startDate',
1434 'end_date' => 'endDate',
1435 ];
1436 foreach ($memberDates as $dv) {
1437 $$dv = NULL;
1438 foreach ($this->_memTypeSelected as $memType) {
1439 $membershipTypeValues[$memType][$dv] = NULL;
1440 }
1441 }
1442 }
1443 $now = date('YmdHis');
1444 $params['receive_date'] = date('Y-m-d H:i:s');
1445 $params['invoice_id'] = $formValues['invoiceID'];
1446 $params['contribution_source'] = ts('%1 Membership Signup: Credit card or direct debit (by %2)',
1447 [1 => $membershipType, 2 => $userName]
1448 );
1449 $params['source'] = $formValues['source'] ? $formValues['source'] : $params['contribution_source'];
1450 $params['trxn_id'] = CRM_Utils_Array::value('trxn_id', $result);
1451 $params['is_test'] = ($this->_mode == 'live') ? 0 : 1;
1452 if (!empty($formValues['send_receipt'])) {
1453 $params['receipt_date'] = $now;
1454 }
1455 else {
1456 $params['receipt_date'] = NULL;
1457 }
1458
1459 $this->set('params', $formValues);
1460 $this->assign('trxn_id', CRM_Utils_Array::value('trxn_id', $result));
1461 $this->assign('receive_date',
1462 CRM_Utils_Date::mysqlToIso($params['receive_date'])
1463 );
1464
1465 // required for creating membership for related contacts
1466 $params['action'] = $this->_action;
1467
1468 //create membership record.
1469 $count = 0;
1470 foreach ($this->_memTypeSelected as $memType) {
1471 if ($count &&
1472 ($relateContribution = CRM_Member_BAO_Membership::getMembershipContributionId($membership->id))
1473 ) {
1474 $membershipTypeValues[$memType]['relate_contribution_id'] = $relateContribution;
1475 }
1476
1477 $membershipParams = array_merge($membershipTypeValues[$memType], $params);
1478 //CRM-15366
1479 if (!empty($softParams) && empty($paymentParams['is_recur'])) {
1480 $membershipParams['soft_credit'] = $softParams;
1481 }
1482 if (isset($result['fee_amount'])) {
1483 $membershipParams['fee_amount'] = $result['fee_amount'];
1484 }
1485 // This is required to trigger the recording of the membership contribution in the
1486 // CRM_Member_BAO_Membership::Create function.
1487 // @todo stop setting this & 'teach' the create function to respond to something
1488 // appropriate as part of our 2-step always create the pending contribution & then finally add the payment
1489 // process -
1490 // @see http://wiki.civicrm.org/confluence/pages/viewpage.action?pageId=261062657#Payments&AccountsRoadmap-Movetowardsalwaysusinga2-steppaymentprocess
1491 $membershipParams['contribution_status_id'] = CRM_Utils_Array::value('payment_status_id', $result);
1492 if (!empty($paymentParams['is_recur'])) {
1493 // The earlier process created the line items (although we want to get rid of the earlier one in favour
1494 // of a single path!
1495 unset($membershipParams['lineItems']);
1496 }
1497 $membershipParams['payment_instrument_id'] = $paymentInstrumentID;
1498 // @todo stop passing $ids (membership and userId only are set above)
1499 $membership = CRM_Member_BAO_Membership::create($membershipParams, $ids);
1500 $params['contribution'] = CRM_Utils_Array::value('contribution', $membershipParams);
1501 unset($params['lineItems']);
1502 $this->_membershipIDs[] = $membership->id;
1503 $createdMemberships[$memType] = $membership;
1504 $count++;
1505 }
1506
1507 }
1508 else {
1509 $params['action'] = $this->_action;
1510 if ($this->_onlinePendingContributionId && !empty($formValues['record_contribution'])) {
1511
1512 // update membership as well as contribution object, CRM-4395
1513 $params['contribution_id'] = $this->_onlinePendingContributionId;
1514 $params['componentId'] = $params['id'];
1515 $params['componentName'] = 'contribute';
1516 $result = CRM_Contribute_BAO_Contribution::transitionComponents($params, TRUE);
1517 if (!empty($result) && !empty($params['contribution_id'])) {
1518 $lineItem = [];
1519 $lineItems = CRM_Price_BAO_LineItem::getLineItemsByContributionID($params['contribution_id']);
1520 $itemId = key($lineItems);
1521 $priceSetId = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceField', $lineItems[$itemId]['price_field_id'], 'price_set_id');
1522
1523 $lineItems[$itemId]['unit_price'] = $params['total_amount'];
1524 $lineItems[$itemId]['line_total'] = $params['total_amount'];
1525 $lineItems[$itemId]['id'] = $itemId;
1526 $lineItem[$priceSetId] = $lineItems;
1527 $contributionBAO = new CRM_Contribute_BAO_Contribution();
1528 $contributionBAO->id = $params['contribution_id'];
1529 $contributionBAO->contact_id = $params['contact_id'];
1530 $contributionBAO->find();
1531 CRM_Price_BAO_LineItem::processPriceSet($params['contribution_id'], $lineItem, $contributionBAO, 'civicrm_membership');
1532
1533 //create new soft-credit record, CRM-13981
1534 if ($softParams) {
1535 $softParams['contribution_id'] = $params['contribution_id'];
1536 while ($contributionBAO->fetch()) {
1537 $softParams['currency'] = $contributionBAO->currency;
1538 $softParams['amount'] = $contributionBAO->total_amount;
1539 }
1540 CRM_Contribute_BAO_ContributionSoft::add($softParams);
1541 }
1542 }
1543
1544 //carry updated membership object.
1545 $membership = new CRM_Member_DAO_Membership();
1546 $membership->id = $this->_id;
1547 $membership->find(TRUE);
1548
1549 $cancelled = TRUE;
1550 if ($membership->end_date) {
1551 //display end date w/ status message.
1552 $endDate = $membership->end_date;
1553
1554 if (!in_array($membership->status_id, [
1555 // CRM-15475
1556 array_search('Cancelled', CRM_Member_PseudoConstant::membershipStatus(NULL, " name = 'Cancelled' ", 'name', FALSE, TRUE)),
1557 array_search('Expired', CRM_Member_PseudoConstant::membershipStatus()),
1558 ])
1559 ) {
1560 $cancelled = FALSE;
1561 }
1562 }
1563 // suppress form values in template.
1564 $this->assign('cancelled', $cancelled);
1565
1566 $createdMemberships[] = $membership;
1567 }
1568 else {
1569 $count = 0;
1570 foreach ($this->_memTypeSelected as $memType) {
1571 if ($count && !empty($formValues['record_contribution']) &&
1572 ($relateContribution = CRM_Member_BAO_Membership::getMembershipContributionId($membership->id))
1573 ) {
1574 $membershipTypeValues[$memType]['relate_contribution_id'] = $relateContribution;
1575 }
1576
1577 // @todo figure out why recieve_date isn't being set right here.
1578 if (empty($params['receive_date'])) {
1579 $params['receive_date'] = date('Y-m-d H:i:s');
1580 }
1581 $membershipParams = array_merge($params, $membershipTypeValues[$memType]);
1582 if (!empty($formValues['int_amount'])) {
1583 $init_amount = [];
1584 foreach ($formValues as $key => $value) {
1585 if (strstr($key, 'txt-price')) {
1586 $init_amount[$key] = $value;
1587 }
1588 }
1589 $membershipParams['init_amount'] = $init_amount;
1590 }
1591
1592 if (!empty($softParams)) {
1593 $membershipParams['soft_credit'] = $softParams;
1594 }
1595 // @todo stop passing $ids (membership and userId only are set above)
1596 $membership = CRM_Member_BAO_Membership::create($membershipParams, $ids);
1597 $params['contribution'] = CRM_Utils_Array::value('contribution', $membershipParams);
1598 unset($params['lineItems']);
1599 // skip line item creation for next interation since line item(s) are already created.
1600 $params['skipLineItem'] = TRUE;
1601
1602 $this->_membershipIDs[] = $membership->id;
1603 $createdMemberships[$memType] = $membership;
1604 $count++;
1605 }
1606 }
1607 }
1608 $isRecur = CRM_Utils_Array::value('is_recur', $params);
1609 if (($this->_action & CRM_Core_Action::UPDATE)) {
1610 $this->addStatusMessage($this->getStatusMessageForUpdate($membership, $endDate));
1611 }
1612 elseif (($this->_action & CRM_Core_Action::ADD)) {
1613 $this->addStatusMessage($this->getStatusMessageForCreate($endDate, $membershipTypes, $createdMemberships,
1614 $isRecur, $calcDates));
1615 }
1616
1617 if (!empty($lineItem[$this->_priceSetId])) {
1618 $invoiceSettings = Civi::settings()->get('contribution_invoice_settings');
1619 $invoicing = CRM_Utils_Array::value('invoicing', $invoiceSettings);
1620 $taxAmount = FALSE;
1621 $totalTaxAmount = 0;
1622 foreach ($lineItem[$this->_priceSetId] as & $priceFieldOp) {
1623 if (!empty($priceFieldOp['membership_type_id'])) {
1624 $priceFieldOp['start_date'] = $membershipTypeValues[$priceFieldOp['membership_type_id']]['start_date'] ? CRM_Utils_Date::customFormat($membershipTypeValues[$priceFieldOp['membership_type_id']]['start_date'], '%B %E%f, %Y') : '-';
1625 $priceFieldOp['end_date'] = $membershipTypeValues[$priceFieldOp['membership_type_id']]['end_date'] ? CRM_Utils_Date::customFormat($membershipTypeValues[$priceFieldOp['membership_type_id']]['end_date'], '%B %E%f, %Y') : '-';
1626 }
1627 else {
1628 $priceFieldOp['start_date'] = $priceFieldOp['end_date'] = 'N/A';
1629 }
1630 if ($invoicing && isset($priceFieldOp['tax_amount'])) {
1631 $taxAmount = TRUE;
1632 $totalTaxAmount += $priceFieldOp['tax_amount'];
1633 }
1634 }
1635 if ($invoicing) {
1636 $dataArray = [];
1637 foreach ($lineItem[$this->_priceSetId] as $key => $value) {
1638 if (isset($value['tax_amount']) && isset($value['tax_rate'])) {
1639 if (isset($dataArray[$value['tax_rate']])) {
1640 $dataArray[$value['tax_rate']] = $dataArray[$value['tax_rate']] + CRM_Utils_Array::value('tax_amount', $value);
1641 }
1642 else {
1643 $dataArray[$value['tax_rate']] = CRM_Utils_Array::value('tax_amount', $value);
1644 }
1645 }
1646 }
1647 if ($taxAmount) {
1648 $this->assign('totalTaxAmount', $totalTaxAmount);
1649 // Not sure why would need this on Submit.... unless it's being used when sending mails in which case this is the wrong place
1650 $this->assign('taxTerm', $this->getSalesTaxTerm());
1651 }
1652 $this->assign('dataArray', $dataArray);
1653 }
1654 }
1655 $this->assign('lineItem', !empty($lineItem) && !$isQuickConfig ? $lineItem : FALSE);
1656
1657 $receiptSend = FALSE;
1658 $contributionId = CRM_Member_BAO_Membership::getMembershipContributionId($membership->id);
1659 $membershipIds = $this->_membershipIDs;
1660 if ($contributionId && !empty($membershipIds)) {
1661 $contributionDetails = CRM_Contribute_BAO_Contribution::getContributionDetails(
1662 CRM_Export_Form_Select::MEMBER_EXPORT, $this->_membershipIDs);
1663 if ($contributionDetails[$membership->id]['contribution_status'] == 'Completed') {
1664 $receiptSend = TRUE;
1665 }
1666 }
1667
1668 $receiptSent = FALSE;
1669 if (!empty($formValues['send_receipt']) && $receiptSend) {
1670 $formValues['contact_id'] = $this->_contactID;
1671 $formValues['contribution_id'] = $contributionId;
1672 // We really don't need a distinct receipt_text_signup vs receipt_text_renewal as they are
1673 // handled in the receipt. But by setting one we avoid breaking templates for now
1674 // although at some point we should switch in the templates.
1675 $formValues['receipt_text_signup'] = $formValues['receipt_text'];
1676 // send email receipt
1677 $this->assignBillingName();
1678 $mailSend = $this->emailMembershipReceipt($formValues, $membership);
1679 $receiptSent = TRUE;
1680 }
1681
1682 // finally set membership id if already not set
1683 if (!$this->_id) {
1684 $this->_id = $membership->id;
1685 }
1686
1687 $this->updateContributionOnMembershipTypeChange($params, $membership);
1688 if ($receiptSent && $mailSend) {
1689 $this->addStatusMessage(ts('A membership confirmation and receipt has been sent to %1.', [1 => $this->_contributorEmail]));
1690 }
1691
1692 CRM_Core_Session::setStatus($this->getStatusMessage(), ts('Complete'), 'success');
1693 $this->setStatusMessage($membership);
1694 }
1695
1696 /**
1697 * Update related contribution of a membership if update_contribution_on_membership_type_change
1698 * contribution setting is enabled and type is changed on edit
1699 *
1700 * @param array $inputParams
1701 * submitted form values
1702 * @param CRM_Member_DAO_Membership $membership
1703 * Updated membership object
1704 *
1705 */
1706 protected function updateContributionOnMembershipTypeChange($inputParams, $membership) {
1707 if (Civi::settings()->get('update_contribution_on_membership_type_change') &&
1708 // on update
1709 ($this->_action & CRM_Core_Action::UPDATE) &&
1710 // if ID is present
1711 $this->_id &&
1712 // if selected membership doesn't match with earlier membership
1713 !in_array($this->_memType, $this->_memTypeSelected)
1714 ) {
1715 if (!empty($inputParams['is_recur'])) {
1716 CRM_Core_Session::setStatus(ts('Associated recurring contribution cannot be updated on membership type change.', ts('Error'), 'error'));
1717 return;
1718 }
1719
1720 // fetch lineitems by updated membership ID
1721 $lineItems = CRM_Price_BAO_LineItem::getLineItems($membership->id, 'membership');
1722 // retrieve the related contribution ID
1723 $contributionID = CRM_Core_DAO::getFieldValue(
1724 'CRM_Member_DAO_MembershipPayment',
1725 $membership->id,
1726 'contribution_id',
1727 'membership_id'
1728 );
1729 // get price fields of chosen price-set
1730 $priceSetDetails = CRM_Utils_Array::value(
1731 $this->_priceSetId,
1732 CRM_Price_BAO_PriceSet::getSetDetail(
1733 $this->_priceSetId,
1734 TRUE,
1735 TRUE
1736 )
1737 );
1738
1739 // add price field information in $inputParams
1740 self::addPriceFieldByMembershipType($inputParams, $priceSetDetails['fields'], $membership->membership_type_id);
1741
1742 // update related contribution and financial records
1743 CRM_Price_BAO_LineItem::changeFeeSelections(
1744 $inputParams,
1745 $membership->id,
1746 'membership',
1747 $contributionID,
1748 $priceSetDetails['fields'],
1749 $lineItems
1750 );
1751 CRM_Core_Session::setStatus(ts('Associated contribution is updated on membership type change.'), ts('Success'), 'success');
1752 }
1753 }
1754
1755 /**
1756 * Add selected price field information in $formValues
1757 *
1758 * @param array $formValues
1759 * submitted form values
1760 * @param array $priceFields
1761 * Price fields of selected Priceset ID
1762 * @param int $membershipTypeID
1763 * Selected membership type ID
1764 *
1765 */
1766 public static function addPriceFieldByMembershipType(&$formValues, $priceFields, $membershipTypeID) {
1767 foreach ($priceFields as $priceFieldID => $priceField) {
1768 if (isset($priceField['options']) && count($priceField['options'])) {
1769 foreach ($priceField['options'] as $option) {
1770 if ($option['membership_type_id'] == $membershipTypeID) {
1771 $formValues["price_{$priceFieldID}"] = $option['id'];
1772 break;
1773 }
1774 }
1775 }
1776 }
1777 }
1778
1779 /**
1780 * Set context in session.
1781 */
1782 protected function setUserContext() {
1783 $buttonName = $this->controller->getButtonName();
1784 $session = CRM_Core_Session::singleton();
1785
1786 if ($this->_context == 'standalone') {
1787 if ($buttonName == $this->getButtonName('upload', 'new')) {
1788 $session->replaceUserContext(CRM_Utils_System::url('civicrm/member/add',
1789 'reset=1&action=add&context=standalone'
1790 ));
1791 }
1792 else {
1793 $session->replaceUserContext(CRM_Utils_System::url('civicrm/contact/view',
1794 "reset=1&cid={$this->_contactID}&selectedChild=member"
1795 ));
1796 }
1797 }
1798 elseif ($buttonName == $this->getButtonName('upload', 'new')) {
1799 $session->replaceUserContext(CRM_Utils_System::url('civicrm/contact/view/membership',
1800 "reset=1&action=add&context=membership&cid={$this->_contactID}"
1801 ));
1802 }
1803 }
1804
1805 /**
1806 * Get status message for updating membership.
1807 *
1808 * @param CRM_Member_BAO_Membership $membership
1809 * @param string $endDate
1810 *
1811 * @return string
1812 */
1813 protected function getStatusMessageForUpdate($membership, $endDate) {
1814 // End date can be modified by hooks, so if end date is set then use it.
1815 $endDate = ($membership->end_date) ? $membership->end_date : $endDate;
1816
1817 $statusMsg = ts('Membership for %1 has been updated.', [1 => $this->_memberDisplayName]);
1818 if ($endDate && $endDate !== 'null') {
1819 $endDate = CRM_Utils_Date::customFormat($endDate);
1820 $statusMsg .= ' ' . ts('The membership End Date is %1.', [1 => $endDate]);
1821 }
1822 return $statusMsg;
1823 }
1824
1825 /**
1826 * Get status message for create action.
1827 *
1828 * @param string $endDate
1829 * @param array $membershipTypes
1830 * @param array $createdMemberships
1831 * @param bool $isRecur
1832 * @param array $calcDates
1833 *
1834 * @return array|string
1835 */
1836 protected function getStatusMessageForCreate($endDate, $membershipTypes, $createdMemberships,
1837 $isRecur, $calcDates) {
1838 // FIX ME: fix status messages
1839
1840 $statusMsg = [];
1841 foreach ($membershipTypes as $memType => $membershipType) {
1842 $statusMsg[$memType] = ts('%1 membership for %2 has been added.', [
1843 1 => $membershipType,
1844 2 => $this->_memberDisplayName,
1845 ]);
1846
1847 $membership = $createdMemberships[$memType];
1848 $memEndDate = ($membership->end_date) ? $membership->end_date : $endDate;
1849
1850 //get the end date from calculated dates.
1851 if (!$memEndDate && !$isRecur) {
1852 $memEndDate = CRM_Utils_Array::value('end_date', $calcDates[$memType]);
1853 }
1854
1855 if ($memEndDate && $memEndDate !== 'null') {
1856 $memEndDate = CRM_Utils_Date::customFormat($memEndDate);
1857 $statusMsg[$memType] .= ' ' . ts('The new membership End Date is %1.', [1 => $memEndDate]);
1858 }
1859 }
1860 $statusMsg = implode('<br/>', $statusMsg);
1861 return $statusMsg;
1862 }
1863
1864 /**
1865 * @param $membership
1866 */
1867 protected function setStatusMessage($membership) {
1868 //CRM-15187
1869 // display message when membership type is changed
1870 if (($this->_action & CRM_Core_Action::UPDATE) && $this->_id && !in_array($this->_memType, $this->_memTypeSelected)) {
1871 $lineItem = CRM_Price_BAO_LineItem::getLineItems($this->_id, 'membership');
1872 $maxID = max(array_keys($lineItem));
1873 $lineItem = $lineItem[$maxID];
1874 $membershipTypeDetails = $this->allMembershipTypeDetails[$membership->membership_type_id];
1875 if ($membershipTypeDetails['financial_type_id'] != $lineItem['financial_type_id']) {
1876 CRM_Core_Session::setStatus(
1877 ts('The financial types associated with the old and new membership types are different. You may want to edit the contribution associated with this membership to adjust its financial type.'),
1878 ts('Warning')
1879 );
1880 }
1881 if ($membershipTypeDetails['minimum_fee'] != $lineItem['line_total']) {
1882 CRM_Core_Session::setStatus(
1883 ts('The cost of the old and new membership types are different. You may want to edit the contribution associated with this membership to adjust its amount.'),
1884 ts('Warning')
1885 );
1886 }
1887 }
1888 }
1889
1890 /**
1891 * @return bool
1892 */
1893 protected function isUpdateToExistingRecurringMembership() {
1894 $isRecur = FALSE;
1895 if ($this->_action & CRM_Core_Action::UPDATE
1896 && CRM_Core_DAO::getFieldValue('CRM_Member_DAO_Membership', $this->getEntityId(),
1897 'contribution_recur_id')
1898 && !CRM_Member_BAO_Membership::isSubscriptionCancelled($this->getEntityId())) {
1899
1900 $isRecur = TRUE;
1901 }
1902 return $isRecur;
1903 }
1904
1905 /**
1906 * Send a receipt for the membership.
1907 *
1908 * @param array $formValues
1909 * @param \CRM_Member_BAO_Membership $membership
1910 *
1911 * @return bool
1912 */
1913 protected function emailMembershipReceipt($formValues, $membership) {
1914 $customValues = $this->getCustomValuesForReceipt($formValues, $membership);
1915
1916 return self::emailReceipt($this, $formValues, $membership, $customValues);
1917 }
1918
1919 /**
1920 * Filter the custom values from the input parameters (for display in the email).
1921 *
1922 * @todo figure out why the scary code this calls does & document.
1923 *
1924 * @param array $formValues
1925 * @param \CRM_Member_BAO_Membership $membership
1926 * @return array
1927 */
1928 protected function getCustomValuesForReceipt($formValues, $membership) {
1929 $customFields = $customValues = [];
1930 if (property_exists($this, '_groupTree')
1931 && !empty($this->_groupTree)
1932 ) {
1933 foreach ($this->_groupTree as $groupID => $group) {
1934 if ($groupID == 'info') {
1935 continue;
1936 }
1937 foreach ($group['fields'] as $k => $field) {
1938 $field['title'] = $field['label'];
1939 $customFields["custom_{$k}"] = $field;
1940 }
1941 }
1942 }
1943
1944 $members = [['member_id', '=', $membership->id, 0, 0]];
1945 // check whether its a test drive
1946 if ($this->_mode == 'test') {
1947 $members[] = ['member_test', '=', 1, 0, 0];
1948 }
1949
1950 CRM_Core_BAO_UFGroup::getValues($formValues['contact_id'], $customFields, $customValues, FALSE, $members);
1951 return $customValues;
1952 }
1953
1954 }