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