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