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