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