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