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