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