3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
19 * This class generates form components for offline membership form.
21 class CRM_Member_Form_Membership
extends CRM_Member_Form
{
23 protected $_memType = NULL;
25 protected $_onlinePendingContributionId;
29 public $_contributeMode = 'direct';
31 protected $_recurMembershipTypes;
33 protected $_memTypeSelected;
36 * Display name of the member.
40 protected $_memberDisplayName = NULL;
43 * email of the person paying for the membership (used for receipts)
46 protected $_memberEmail = NULL;
49 * Contact ID of the member.
53 public $_contactID = NULL;
56 * Display name of the person paying for the membership (used for receipts)
60 protected $_contributorDisplayName = NULL;
63 * email of the person paying for the membership (used for receipts)
66 protected $_contributorEmail = NULL;
69 * email of the person paying for the membership (used for receipts)
73 protected $_contributorContactID = NULL;
76 * ID of the person the receipt is to go to.
80 protected $_receiptContactId = NULL;
83 * Keep a class variable for ALL membership IDs so
84 * postProcess hook function can do something with it
88 protected $_membershipIDs = [];
91 * Set entity fields to be assigned to the form.
93 protected function setEntityFields() {
94 $this->entityFields
= [
96 'name' => 'join_date',
97 'description' => ts('Member Since'),
100 'name' => 'start_date',
101 'description' => ts('Start Date'),
104 'name' => 'end_date',
105 'description' => ts('End Date'),
111 * Set the delete message.
113 * We do this from the constructor in order to do a translation.
115 public function setDeleteMessage() {
116 $this->deleteMessage
= '<span class="font-red bold">'
117 . ts("WARNING: Deleting this membership will also delete any related payment (contribution) records." . ts("This action cannot be undone.")
119 . ts("Consider modifying the membership status instead if you want to maintain an audit trail and avoid losing payment data. You can set the status to Cancelled by editing the membership and clicking the Status Override checkbox.")
121 . ts("Click 'Delete' if you want to continue.") . '</p>');
125 * Overriding this entity trait function as not yet tested.
127 * We continue to rely on legacy handling.
129 public function addCustomDataToForm() {}
132 * Overriding this entity trait function as not yet tested.
134 * We continue to rely on legacy handling.
136 public function addFormButtons() {}
139 * Get selected membership type from the form values.
141 * @param array $priceSet
142 * @param array $params
146 public static function getSelectedMemberships($priceSet, $params) {
147 $memTypeSelected = [];
148 $priceFieldIDS = self
::getPriceFieldIDs($params, $priceSet);
149 if (isset($params['membership_type_id']) && !empty($params['membership_type_id'][1])) {
150 $memTypeSelected = [$params['membership_type_id'][1] => $params['membership_type_id'][1]];
153 foreach ($priceFieldIDS as $priceFieldId) {
154 if ($id = CRM_Core_DAO
::getFieldValue('CRM_Price_DAO_PriceFieldValue', $priceFieldId, 'membership_type_id')) {
155 $memTypeSelected[$id] = $id;
159 return $memTypeSelected;
163 * Extract price set fields and values from $params.
165 * @param array $params
166 * @param array $priceSet
170 public static function getPriceFieldIDs($params, $priceSet) {
172 if (isset($priceSet['fields']) && is_array($priceSet['fields'])) {
173 foreach ($priceSet['fields'] as $fieldId => $field) {
174 if (!empty($params['price_' . $fieldId])) {
175 if (is_array($params['price_' . $fieldId])) {
176 foreach ($params['price_' . $fieldId] as $priceFldVal => $isSet) {
178 $priceFieldIDS[] = $priceFldVal;
182 elseif (!$field['is_enter_qty']) {
183 $priceFieldIDS[] = $params['price_' . $fieldId];
188 return $priceFieldIDS;
192 * Form preProcess function.
194 public function preProcess() {
195 // This string makes up part of the class names, differentiating them (not sure why) from the membership fields.
196 $this->assign('formClass', 'membership');
197 parent
::preProcess();
200 $this->_priceSetId
= CRM_Utils_Array
::value('priceSetId', $_GET);
201 $this->set('priceSetId', $this->_priceSetId
);
202 $this->assign('priceSetId', $this->_priceSetId
);
204 if ($this->_action
& CRM_Core_Action
::DELETE
) {
205 $contributionID = CRM_Member_BAO_Membership
::getMembershipContributionId($this->_id
);
206 // check delete permission for contribution
207 if ($this->_id
&& $contributionID && !CRM_Core_Permission
::checkActionPermission('CiviContribute', $this->_action
)) {
208 CRM_Core_Error
::statusBounce(ts("This Membership is linked to a contribution. You must have 'delete in CiviContribute' permission in order to delete this record."));
212 if ($this->_action
& CRM_Core_Action
::ADD
) {
213 if ($this->_contactID
) {
214 //check whether contact has a current membership so we can alert user that they may want to do a renewal instead
215 $contactMemberships = [];
216 $memParams = ['contact_id' => $this->_contactID
];
217 CRM_Member_BAO_Membership
::getValues($memParams, $contactMemberships, TRUE);
219 foreach ($contactMemberships as $mem) {
220 $cMemTypes[] = $mem['membership_type_id'];
222 if (count($cMemTypes) > 0) {
223 $memberorgs = CRM_Member_BAO_MembershipType
::getMemberOfContactByMemTypes($cMemTypes);
225 foreach ($contactMemberships as $mem) {
226 $mem['member_of_contact_id'] = CRM_Utils_Array
::value($mem['membership_type_id'], $memberorgs);
227 if (!empty($mem['membership_end_date'])) {
228 $mem['membership_end_date'] = CRM_Utils_Date
::customFormat($mem['membership_end_date']);
230 $mem['membership_type'] = CRM_Core_DAO
::getFieldValue('CRM_Member_DAO_MembershipType',
231 $mem['membership_type_id'],
234 $mem['membership_status'] = CRM_Core_DAO
::getFieldValue('CRM_Member_DAO_MembershipStatus',
238 $mem['renewUrl'] = CRM_Utils_System
::url('civicrm/contact/view/membership',
239 "reset=1&action=renew&cid={$this->_contactID}&id={$mem['id']}&context=membership&selectedChild=member"
240 . ($this->_mode ?
'&mode=live' : '')
242 $mem['membershipTab'] = CRM_Utils_System
::url('civicrm/contact/view',
243 "reset=1&force=1&cid={$this->_contactID}&selectedChild=member"
245 $mems_by_org[$mem['member_of_contact_id']] = $mem;
247 $this->assign('existingContactMemberships', $mems_by_org);
251 // In standalone mode we don't have a contact id yet so lookup will be done client-side with this script:
252 $resources = CRM_Core_Resources
::singleton();
253 $resources->addScriptFile('civicrm', 'templates/CRM/Member/Form/MembershipStandalone.js');
255 'typeorgs' => CRM_Member_BAO_MembershipType
::getMembershipTypeOrganization(),
256 'memtypes' => CRM_Core_PseudoConstant
::get('CRM_Member_BAO_Membership', 'membership_type_id'),
257 'statuses' => CRM_Core_PseudoConstant
::get('CRM_Member_BAO_Membership', 'status_id'),
259 $resources->addSetting(['existingMems' => $passthru]);
263 if (!$this->_memType
) {
264 $params = CRM_Utils_Request
::exportValues();
265 if (!empty($params['membership_type_id'][1])) {
266 $this->_memType
= $params['membership_type_id'][1];
270 // Add custom data to form
271 CRM_Custom_Form_CustomData
::addToForm($this, $this->_memType
);
273 // CRM-4395, get the online pending contribution id.
274 $this->_onlinePendingContributionId
= NULL;
275 if (!$this->_mode
&& $this->_id
&& ($this->_action
& CRM_Core_Action
::UPDATE
)) {
276 $this->_onlinePendingContributionId
= CRM_Contribute_BAO_Contribution
::checkOnlinePendingContribution($this->_id
,
280 $this->assign('onlinePendingContributionId', $this->_onlinePendingContributionId
);
282 $this->setPageTitle(ts('Membership'));
286 * Set default values for the form.
288 public function setDefaultValues() {
290 if ($this->_priceSetId
) {
291 return CRM_Price_BAO_PriceSet
::setDefaultPriceSet($this, $defaults);
294 $defaults = parent
::setDefaultValues();
296 //setting default join date and receive date
297 if ($this->_action
== CRM_Core_Action
::ADD
) {
298 $defaults['receive_date'] = date('Y-m-d H:i:s');
301 $defaults['num_terms'] = 1;
303 if (!empty($defaults['id'])) {
304 if ($this->_onlinePendingContributionId
) {
305 $defaults['record_contribution'] = $this->_onlinePendingContributionId
;
308 $contributionId = CRM_Core_DAO
::singleValueQuery("
309 SELECT contribution_id
310 FROM civicrm_membership_payment
311 WHERE membership_id = $this->_id
312 ORDER BY contribution_id
315 if ($contributionId) {
316 $defaults['record_contribution'] = $contributionId;
321 if ($this->_contactID
) {
322 $defaults['contact_id'] = $this->_contactID
;
326 //set Soft Credit Type to Gift by default
327 $scTypes = CRM_Core_OptionGroup
::values("soft_credit_type");
328 $defaults['soft_credit_type_id'] = CRM_Utils_Array
::value(ts('Gift'), array_flip($scTypes));
331 if (empty($defaults['payment_instrument_id'])) {
332 $defaults['payment_instrument_id'] = key(CRM_Core_OptionGroup
::values('payment_instrument', FALSE, FALSE, FALSE, 'AND is_default = 1'));
335 // User must explicitly choose to send a receipt in both add and update mode.
336 $defaults['send_receipt'] = 0;
338 if ($this->_action
& CRM_Core_Action
::UPDATE
) {
339 // in this mode by default uncheck this checkbox
340 unset($defaults['record_contribution']);
343 $subscriptionCancelled = FALSE;
344 if (!empty($defaults['id'])) {
345 $subscriptionCancelled = CRM_Member_BAO_Membership
::isSubscriptionCancelled($this->_id
);
348 $alreadyAutoRenew = FALSE;
349 if (!empty($defaults['contribution_recur_id']) && !$subscriptionCancelled) {
350 $defaults['auto_renew'] = 1;
351 $alreadyAutoRenew = TRUE;
353 $this->assign('alreadyAutoRenew', $alreadyAutoRenew);
355 $this->assign('member_is_test', CRM_Utils_Array
::value('member_is_test', $defaults));
357 $this->assign('membership_status_id', CRM_Utils_Array
::value('status_id', $defaults));
359 if (!empty($defaults['is_pay_later'])) {
360 $this->assign('is_pay_later', TRUE);
363 $defaults = $this->getBillingDefaults($defaults);
364 // hack to simplify credit card entry for testing
365 // $defaults['credit_card_type'] = 'Visa';
366 // $defaults['credit_card_number'] = '4807731747657838';
367 // $defaults['cvv2'] = '000';
368 // $defaults['credit_card_exp_date'] = array( 'Y' => '2012', 'M' => '05' );
371 //setting default join date if there is no join date
372 if (empty($defaults['join_date'])) {
373 $defaults['join_date'] = date('Y-m-d');
376 if (!empty($defaults['membership_end_date'])) {
377 $this->assign('endDate', $defaults['membership_end_date']);
384 * Build the form object.
386 public function buildQuickForm() {
388 $this->buildQuickEntityForm();
389 $this->assign('currency', CRM_Core_BAO_Country
::defaultCurrencySymbol());
390 $isUpdateToExistingRecurringMembership = $this->isUpdateToExistingRecurringMembership();
391 // build price set form.
392 $buildPriceSet = FALSE;
393 if ($this->_priceSetId ||
!empty($_POST['price_set_id'])) {
394 if (!empty($_POST['price_set_id'])) {
395 $buildPriceSet = TRUE;
397 $getOnlyPriceSetElements = TRUE;
398 if (!$this->_priceSetId
) {
399 $this->_priceSetId
= $_POST['price_set_id'];
400 $getOnlyPriceSetElements = FALSE;
403 $this->set('priceSetId', $this->_priceSetId
);
404 CRM_Price_BAO_PriceSet
::buildPriceSet($this);
406 $optionsMembershipTypes = [];
407 foreach ($this->_priceSet
['fields'] as $pField) {
408 if (empty($pField['options'])) {
411 foreach ($pField['options'] as $opId => $opValues) {
412 $optionsMembershipTypes[$opId] = CRM_Utils_Array
::value('membership_type_id', $opValues, 0);
416 $this->assign('autoRenewOption', CRM_Price_BAO_PriceSet
::checkAutoRenewForPriceSet($this->_priceSetId
));
418 $this->assign('optionsMembershipTypes', $optionsMembershipTypes);
419 $this->assign('contributionType', CRM_Utils_Array
::value('financial_type_id', $this->_priceSet
));
421 // get only price set form elements.
422 if ($getOnlyPriceSetElements) {
427 // use to build form during form rule.
428 $this->assign('buildPriceSet', $buildPriceSet);
430 if ($this->_action
& CRM_Core_Action
::ADD
) {
431 $buildPriceSet = FALSE;
432 $priceSets = CRM_Price_BAO_PriceSet
::getAssoc(FALSE, 'CiviMember');
433 if (!empty($priceSets)) {
434 $buildPriceSet = TRUE;
437 if ($buildPriceSet) {
438 $this->add('select', 'price_set_id', ts('Choose price set'),
440 '' => ts('Choose price set'),
442 NULL, ['onchange' => "buildAmount( this.value );"]
445 $this->assign('hasPriceSets', $buildPriceSet);
448 if ($this->_action
& CRM_Core_Action
::DELETE
) {
452 'name' => ts('Delete'),
453 'spacing' => ' ',
458 'name' => ts('Cancel'),
464 $contactField = $this->addEntityRef('contact_id', ts('Member'), ['create' => TRUE, 'api' => ['extra' => ['email']]], TRUE);
465 if ($this->_context
!= 'standalone') {
466 $contactField->freeze();
469 $selOrgMemType[0][0] = $selMemTypeOrg[0] = ts('- select -');
471 // Throw status bounce when no Membership type or priceset is present
472 if (CRM_Financial_BAO_FinancialType
::isACLFinancialTypeStatus()
473 && empty($this->allMembershipTypeDetails
) && empty($priceSets)
475 CRM_Core_Error
::statusBounce(ts('You do not have all the permissions needed for this page.'));
477 // retrieve all memberships
478 $allMembershipInfo = [];
479 foreach ($this->allMembershipTypeDetails
as $key => $values) {
480 if ($this->_mode
&& empty($values['minimum_fee'])) {
484 $memberOfContactId = CRM_Utils_Array
::value('member_of_contact_id', $values);
485 if (empty($selMemTypeOrg[$memberOfContactId])) {
486 $selMemTypeOrg[$memberOfContactId] = CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_Contact',
492 $selOrgMemType[$memberOfContactId][0] = ts('- select -');
494 if (empty($selOrgMemType[$memberOfContactId][$key])) {
495 $selOrgMemType[$memberOfContactId][$key] = CRM_Utils_Array
::value('name', $values);
498 $totalAmount = CRM_Utils_Array
::value('minimum_fee', $values);
499 //CRM-18827 - override the default value if total_amount is submitted
500 if (!empty($this->_submitValues
['total_amount'])) {
501 $totalAmount = $this->_submitValues
['total_amount'];
503 // build membership info array, which is used when membership type is selected to:
504 // - set the payment information block
505 // - set the max related block
506 $allMembershipInfo[$key] = [
507 'financial_type_id' => CRM_Utils_Array
::value('financial_type_id', $values),
508 'total_amount' => CRM_Utils_Money
::format($totalAmount, NULL, '%a'),
509 'total_amount_numeric' => $totalAmount,
510 'auto_renew' => CRM_Utils_Array
::value('auto_renew', $values),
511 'has_related' => isset($values['relationship_type_id']),
512 'max_related' => CRM_Utils_Array
::value('max_related', $values),
516 $this->assign('allMembershipInfo', json_encode($allMembershipInfo));
518 // show organization by default, if only one organization in
520 if (count($selMemTypeOrg) == 2) {
521 unset($selMemTypeOrg[0], $selOrgMemType[0][0]);
523 //sort membership organization and type, CRM-6099
524 natcasesort($selMemTypeOrg);
525 foreach ($selOrgMemType as $index => $orgMembershipType) {
526 natcasesort($orgMembershipType);
527 $selOrgMemType[$index] = $orgMembershipType;
531 'onChange' => "buildMaxRelated(this.value,true); CRM.buildCustomData('Membership', this.value);",
534 if (!empty($this->_recurPaymentProcessors
)) {
535 $memTypeJs['onChange'] = "" . $memTypeJs['onChange'] . " buildAutoRenew(this.value, null, '{$this->_mode}');";
538 $this->add('text', 'max_related', ts('Max related'),
539 CRM_Core_DAO
::getAttribute('CRM_Member_DAO_Membership', 'max_related')
542 $sel = &$this->addElement('hierselect',
543 'membership_type_id',
544 ts('Membership Organization and Type'),
548 $sel->setOptions([$selMemTypeOrg, $selOrgMemType]);
549 if ($isUpdateToExistingRecurringMembership) {
553 if ($this->_action
& CRM_Core_Action
::ADD
) {
554 $this->add('number', 'num_terms', ts('Number of Terms'), ['size' => 6]);
557 $this->add('text', 'source', ts('Source'),
558 CRM_Core_DAO
::getAttribute('CRM_Member_DAO_Membership', 'source')
561 //CRM-7362 --add campaigns.
564 $campaignId = CRM_Core_DAO
::getFieldValue('CRM_Member_DAO_Membership', $this->_id
, 'campaign_id');
566 CRM_Campaign_BAO_Campaign
::addCampaign($this, $campaignId);
569 $this->add('select', 'status_id', ts('Membership Status'),
570 ['' => ts('- select -')] + CRM_Member_PseudoConstant
::membershipStatus(NULL, NULL, 'label')
573 $statusOverride = $this->addElement('select', 'is_override', ts('Status Override?'),
574 CRM_Member_StatusOverrideTypes
::getSelectOptions()
576 if ($statusOverride && $isUpdateToExistingRecurringMembership) {
577 $statusOverride->freeze();
580 $this->add('datepicker', 'status_override_end_date', ts('Status Override End Date'), '', FALSE, ['minDate' => time(), 'time' => FALSE]);
582 $this->addElement('checkbox', 'record_contribution', ts('Record Membership Payment?'));
584 $this->add('text', 'total_amount', ts('Amount'));
585 $this->addRule('total_amount', ts('Please enter a valid amount.'), 'money');
587 $this->add('datepicker', 'receive_date', ts('Received'), [], FALSE, ['time' => TRUE]);
589 $this->add('select', 'payment_instrument_id',
590 ts('Payment Method'),
591 ['' => ts('- select -')] + CRM_Contribute_PseudoConstant
::paymentInstrument(),
592 FALSE, ['onChange' => "return showHideByValue('payment_instrument_id','4','checkNumber','table-row','select',false);"]
594 $this->add('text', 'trxn_id', ts('Transaction ID'));
595 $this->addRule('trxn_id', ts('Transaction ID already exists in Database.'),
597 'CRM_Contribute_DAO_Contribution',
603 $this->add('select', 'contribution_status_id',
604 ts('Payment Status'), CRM_Contribute_BAO_Contribution_Utils
::getContributionStatuses('membership')
606 $this->add('text', 'check_number', ts('Check Number'),
607 CRM_Core_DAO
::getAttribute('CRM_Contribute_DAO_Contribution', 'check_number')
611 //add field for amount to allow an amount to be entered that differs from minimum
612 $this->add('text', 'total_amount', ts('Amount'));
614 $this->add('select', 'financial_type_id',
615 ts('Financial Type'),
616 ['' => ts('- select -')] + CRM_Financial_BAO_FinancialType
::getAvailableFinancialTypes($financialTypes, $this->_action
)
619 $this->addElement('checkbox', 'is_different_contribution_contact', ts('Record Payment from a Different Contact?'));
621 $this->addSelect('soft_credit_type_id', ['entity' => 'contribution_soft']);
622 $this->addEntityRef('soft_credit_contact_id', ts('Payment From'), ['create' => TRUE]);
624 $this->addElement('checkbox',
626 ts('Send Confirmation and Receipt?'), NULL,
627 ['onclick' => "showEmailOptions()"]
630 $this->add('select', 'from_email_address', ts('Receipt From'), $this->_fromEmails
);
632 $this->add('textarea', 'receipt_text', ts('Receipt Message'));
634 // Retrieve the name and email of the contact - this will be the TO for receipt email
635 if ($this->_contactID
) {
636 list($this->_memberDisplayName
,
638 ) = CRM_Contact_BAO_Contact_Location
::getEmailDetails($this->_contactID
);
640 $this->assign('emailExists', $this->_memberEmail
);
641 $this->assign('displayName', $this->_memberDisplayName
);
644 if ($isUpdateToExistingRecurringMembership && CRM_Member_BAO_Membership
::isCancelSubscriptionSupported($this->_id
)) {
645 $this->assign('cancelAutoRenew',
646 CRM_Utils_System
::url('civicrm/contribute/unsubscribe', "reset=1&mid={$this->_id}")
650 $this->assign('isRecur', $isUpdateToExistingRecurringMembership);
652 $this->addFormRule(['CRM_Member_Form_Membership', 'formRule'], $this);
653 $mailingInfo = Civi
::settings()->get('mailing_backend');
654 $this->assign('isEmailEnabledForSite', ($mailingInfo['outBound_option'] != 2));
656 parent
::buildQuickForm();
662 * @param array $params
663 * (ref.) an assoc array of name/value pairs.
665 * @param array $files
666 * @param CRM_Member_Form_Membership $self
668 * @throws CiviCRM_API3_Exception
670 * mixed true or array of errors
672 public static function formRule($params, $files, $self) {
675 $priceSetId = self
::getPriceSetID($params);
676 $priceSetDetails = self
::getPriceSetDetails($params);
678 $selectedMemberships = self
::getSelectedMemberships($priceSetDetails[$priceSetId], $params);
680 if (!empty($params['price_set_id'])) {
681 CRM_Price_BAO_PriceField
::priceSetValidation($priceSetId, $params, $errors);
683 $priceFieldIDS = self
::getPriceFieldIDs($params, $priceSetDetails[$priceSetId]);
685 if (!empty($priceFieldIDS)) {
686 $ids = implode(',', $priceFieldIDS);
688 $count = CRM_Price_BAO_PriceSet
::getMembershipCount($ids);
689 foreach ($count as $occurrence) {
690 if ($occurrence > 1) {
691 $errors['_qf_default'] = ts('Select at most one option associated with the same membership type.');
695 // Return error if empty $self->_memTypeSelected
696 if (empty($errors) && empty($selectedMemberships)) {
697 $errors['_qf_default'] = ts('Select at least one membership option.');
699 if (!$self->_mode
&& empty($params['record_contribution'])) {
700 $errors['record_contribution'] = ts('Record Membership Payment is required when you use a price set.');
704 if (empty($params['membership_type_id'][1])) {
705 $errors['membership_type_id'] = ts('Please select a membership type.');
707 $numterms = CRM_Utils_Array
::value('num_terms', $params);
708 if ($numterms && intval($numterms) != $numterms) {
709 $errors['num_terms'] = ts('Please enter an integer for the number of terms.');
712 if (($self->_mode ||
isset($params['record_contribution'])) && empty($params['financial_type_id'])) {
713 $errors['financial_type_id'] = ts('Please enter the financial Type.');
717 if (!empty($errors) && (count($selectedMemberships) > 1)) {
718 $memberOfContacts = CRM_Member_BAO_MembershipType
::getMemberOfContactByMemTypes($selectedMemberships);
719 $duplicateMemberOfContacts = array_count_values($memberOfContacts);
720 foreach ($duplicateMemberOfContacts as $countDuplicate) {
721 if ($countDuplicate > 1) {
722 $errors['_qf_default'] = ts('Please do not select more than one membership associated with the same organization.');
727 if (!empty($errors)) {
731 if (!empty($params['record_contribution']) && empty($params['payment_instrument_id'])) {
732 $errors['payment_instrument_id'] = ts('Payment Method is a required field.');
735 if (!empty($params['is_different_contribution_contact'])) {
736 if (empty($params['soft_credit_type_id'])) {
737 $errors['soft_credit_type_id'] = ts('Please Select a Soft Credit Type');
739 if (empty($params['soft_credit_contact_id'])) {
740 $errors['soft_credit_contact_id'] = ts('Please select a contact');
744 if (!empty($params['payment_processor_id'])) {
745 // validate payment instrument (e.g. credit card number)
746 CRM_Core_Payment_Form
::validatePaymentInstrument($params['payment_processor_id'], $params, $errors, NULL);
750 if (!empty($params['join_date'])) {
752 $joinDate = CRM_Utils_Date
::processDate($params['join_date']);
754 foreach ($selectedMemberships as $memType) {
756 if (!empty($params['start_date'])) {
757 $startDate = CRM_Utils_Date
::processDate($params['start_date']);
760 // if end date is set, ensure that start date is also set
761 // and that end date is later than start date
763 if (!empty($params['end_date'])) {
764 $endDate = CRM_Utils_Date
::processDate($params['end_date']);
767 $membershipDetails = CRM_Member_BAO_MembershipType
::getMembershipTypeDetails($memType);
769 if ($startDate && CRM_Utils_Array
::value('period_type', $membershipDetails) == 'rolling') {
770 if ($startDate < $joinDate) {
771 $errors['start_date'] = ts('Start date must be the same or later than Member since.');
776 if ($membershipDetails['duration_unit'] == 'lifetime') {
777 // Check if status is NOT cancelled or similar. For lifetime memberships, there is no automated
778 // process to update status based on end-date. The user must change the status now.
779 $result = civicrm_api3('MembershipStatus', 'get', [
781 'is_current_member' => 0,
783 $tmp_statuses = $result['values'];
785 foreach ($tmp_statuses as $cur_stat) {
786 $status_ids[] = $cur_stat['id'];
789 if (empty($params['status_id']) ||
in_array($params['status_id'], $status_ids) == FALSE) {
790 $errors['status_id'] = ts('Please enter a status that does NOT represent a current membership status.');
793 if (!empty($params['is_override']) && !CRM_Member_StatusOverrideTypes
::isPermanent($params['is_override'])) {
794 $errors['is_override'] = ts('Because you set an End Date for a lifetime membership, This must be set to "Override Permanently"');
799 $errors['start_date'] = ts('Start date must be set if end date is set.');
801 if ($endDate < $startDate) {
802 $errors['end_date'] = ts('End date must be the same or later than start date.');
807 // Default values for start and end dates if not supplied on the form.
808 $defaultDates = CRM_Member_BAO_MembershipType
::getDatesForMembershipType($memType,
815 $startDate = CRM_Utils_Array
::value('start_date',
820 $endDate = CRM_Utils_Array
::value('end_date',
825 //CRM-3724, check for availability of valid membership status.
826 if ((empty($params['is_override']) || CRM_Member_StatusOverrideTypes
::isNo($params['is_override'])) && !isset($errors['_qf_default'])) {
827 $calcStatus = CRM_Member_BAO_MembershipStatus
::getMembershipStatusByDate($startDate,
835 if (empty($calcStatus)) {
836 $url = CRM_Utils_System
::url('civicrm/admin/member/membershipStatus', 'reset=1&action=browse');
837 $errors['_qf_default'] = ts('There is no valid Membership Status available for selected membership dates.');
838 $status = ts('Oops, it looks like there is no valid membership status available for the given membership dates. You can <a href="%1">Configure Membership Status Rules</a>.', [1 => $url]);
840 $status .= ' ' . ts('OR You can sign up by setting Status Override? to something other than "NO".');
842 CRM_Core_Session
::setStatus($status, ts('Membership Status Error'), 'error');
848 $errors['join_date'] = ts('Please enter the Member Since.');
851 if (!empty($params['is_override']) && CRM_Member_StatusOverrideTypes
::isOverridden($params['is_override']) && empty($params['status_id'])) {
852 $errors['status_id'] = ts('Please enter the Membership status.');
855 if (!empty($params['is_override']) && CRM_Member_StatusOverrideTypes
::isUntilDate($params['is_override'])) {
856 if (empty($params['status_override_end_date'])) {
857 $errors['status_override_end_date'] = ts('Please enter the Membership override end date.');
861 //total amount condition arise when membership type having no
863 if (isset($params['record_contribution'])) {
864 if (CRM_Utils_System
::isNull($params['total_amount'])) {
865 $errors['total_amount'] = ts('Please enter the contribution.');
869 // validate contribution status for 'Failed'.
870 if ($self->_onlinePendingContributionId
&& !empty($params['record_contribution']) &&
871 (CRM_Utils_Array
::value('contribution_status_id', $params) ==
872 array_search('Failed', CRM_Contribute_PseudoConstant
::contributionStatus(NULL, 'name'))
875 $errors['contribution_status_id'] = ts('Please select a valid payment status before updating.');
878 return empty($errors) ?
TRUE : $errors;
882 * Process the form submission.
884 public function postProcess() {
885 if ($this->_action
& CRM_Core_Action
::DELETE
) {
886 CRM_Member_BAO_Membership
::del($this->_id
);
889 // get the submitted form values.
890 $this->_params
= $this->controller
->exportValues($this->_name
);
891 $this->prepareStatusOverrideValues();
895 $this->setUserContext();
899 * Prepares the values related to status override.
901 private function prepareStatusOverrideValues() {
902 $this->setOverrideDateValue();
903 $this->convertIsOverrideValue();
907 * Sets status override end date to empty value if
908 * the selected override option is not 'until date'.
910 private function setOverrideDateValue() {
911 if (!CRM_Member_StatusOverrideTypes
::isUntilDate(CRM_Utils_Array
::value('is_override', $this->_params
))) {
912 $this->_params
['status_override_end_date'] = '';
917 * Convert the value of selected (status override?)
918 * option to TRUE if it indicate an overridden status
919 * or FALSE otherwise.
921 private function convertIsOverrideValue() {
922 $this->_params
['is_override'] = CRM_Member_StatusOverrideTypes
::isOverridden($this->_params
['is_override']);
926 * Send email receipt.
929 * This function is shared with Batch_Entry which has limited overlap
930 * & needs rationalising.
932 * @param CRM_Core_Form $form
934 * @param array $formValues
935 * @param object $membership
937 * @param array $customValues
940 * true if mail was sent successfully
942 public static function emailReceipt(&$form, &$formValues, &$membership, $customValues = NULL) {
943 // retrieve 'from email id' for acknowledgement
944 $receiptFrom = CRM_Utils_Array
::value('from_email_address', $formValues);
946 // @todo figure out how much of the stuff below is genuinely shared with the batch form & a logical shared place.
947 if (!empty($formValues['payment_instrument_id'])) {
948 $paymentInstrument = CRM_Contribute_PseudoConstant
::paymentInstrument();
949 $formValues['paidBy'] = $paymentInstrument[$formValues['payment_instrument_id']];
952 $form->assign('customValues', $customValues);
955 // @todo move this outside shared code as Batch entry just doesn't
956 $form->assign('address', CRM_Utils_Address
::getFormattedBillingAddressFieldsFromParameters(
961 $date = CRM_Utils_Date
::format($form->_params
['credit_card_exp_date']);
962 $date = CRM_Utils_Date
::mysqlToIso($date);
963 $form->assign('credit_card_exp_date', $date);
964 $form->assign('credit_card_number',
965 CRM_Utils_System
::mungeCreditCard($form->_params
['credit_card_number'])
967 $form->assign('credit_card_type', $form->_params
['credit_card_type']);
968 $form->assign('contributeMode', 'direct');
969 $form->assign('isAmountzero', 0);
970 $form->assign('is_pay_later', 0);
971 $form->assign('isPrimary', 1);
974 $form->assign('module', 'Membership');
975 $form->assign('contactID', $formValues['contact_id']);
977 $form->assign('membershipID', CRM_Utils_Array
::value('membership_id', $form->_params
, CRM_Utils_Array
::value('membership_id', $form->_defaultValues
)));
979 if (!empty($formValues['contribution_id'])) {
980 $form->assign('contributionID', $formValues['contribution_id']);
982 elseif (isset($form->_onlinePendingContributionId
)) {
983 $form->assign('contributionID', $form->_onlinePendingContributionId
);
986 if (!empty($formValues['contribution_status_id'])) {
987 $form->assign('contributionStatusID', $formValues['contribution_status_id']);
988 $form->assign('contributionStatus', CRM_Contribute_PseudoConstant
::contributionStatus($formValues['contribution_status_id'], 'name'));
991 if (!empty($formValues['is_renew'])) {
992 $form->assign('receiptType', 'membership renewal');
995 $form->assign('receiptType', 'membership signup');
997 $form->assign('receive_date', CRM_Utils_Array
::value('receive_date', $formValues));
998 $form->assign('formValues', $formValues);
1000 if (empty($lineItem)) {
1001 $form->assign('mem_start_date', CRM_Utils_Date
::customFormat($membership->start_date
, '%B %E%f, %Y'));
1002 if (!CRM_Utils_System
::isNull($membership->end_date
)) {
1003 $form->assign('mem_end_date', CRM_Utils_Date
::customFormat($membership->end_date
, '%B %E%f, %Y'));
1005 $form->assign('membership_name', CRM_Member_PseudoConstant
::membershipType($membership->membership_type_id
));
1008 // @todo - if we have to figure out if this is for batch processing it doesn't belong in the shared function.
1009 $isBatchProcess = is_a($form, 'CRM_Batch_Form_Entry');
1010 if ((empty($form->_contributorDisplayName
) ||
empty($form->_contributorEmail
)) ||
$isBatchProcess) {
1011 // in this case the form is being called statically from the batch editing screen
1012 // having one class in the form layer call another statically is not greate
1013 // & we should aim to move this function to the BAO layer in future.
1014 // however, we can assume that the contact_id passed in by the batch
1015 // function will be the recipient
1016 list($form->_contributorDisplayName
, $form->_contributorEmail
)
1017 = CRM_Contact_BAO_Contact_Location
::getEmailDetails($formValues['contact_id']);
1018 if (empty($form->_receiptContactId
) ||
$isBatchProcess) {
1019 $form->_receiptContactId
= $formValues['contact_id'];
1022 // @todo determine isEmailPdf in calling function.
1023 $template = CRM_Core_Smarty
::singleton();
1024 $taxAmt = $template->get_template_vars('dataArray');
1025 $eventTaxAmt = $template->get_template_vars('totalTaxAmount');
1026 $prefixValue = Civi
::settings()->get('contribution_invoice_settings');
1027 $invoicing = CRM_Utils_Array
::value('invoicing', $prefixValue);
1028 if ((!empty($taxAmt) ||
isset($eventTaxAmt)) && (isset($invoicing) && isset($prefixValue['is_email_pdf']))) {
1032 $isEmailPdf = FALSE;
1035 list($mailSend, $subject, $message, $html) = CRM_Core_BAO_MessageTemplate
::sendTemplate(
1037 'groupName' => 'msg_tpl_workflow_membership',
1038 'valueName' => 'membership_offline_receipt',
1039 'contactId' => $form->_receiptContactId
,
1040 'from' => $receiptFrom,
1041 'toName' => $form->_contributorDisplayName
,
1042 'toEmail' => $form->_contributorEmail
,
1043 'PDFFilename' => ts('receipt') . '.pdf',
1044 'isEmailPdf' => $isEmailPdf,
1045 'contributionId' => $formValues['contribution_id'],
1046 'isTest' => (bool) ($form->_action
& CRM_Core_Action
::PREVIEW
),
1056 * This is also accessed by unit tests.
1058 public function submit() {
1059 $isTest = ($this->_mode
== 'test') ?
1 : 0;
1060 $this->storeContactFields($this->_params
);
1061 $this->beginPostProcess();
1062 $joinDate = $startDate = $endDate = NULL;
1063 $membershipTypes = $membership = $calcDate = [];
1064 $membershipType = NULL;
1065 $paymentInstrumentID = $this->_paymentProcessor
['object']->getPaymentInstrumentID();
1066 $params = $softParams = $ids = [];
1069 $this->processBillingAddress();
1070 $formValues = $this->_params
;
1071 $formValues = $this->setPriceSetParameters($formValues);
1074 $ids['membership'] = $params['id'] = $this->_id
;
1076 $ids['userId'] = CRM_Core_Session
::singleton()->get('userID');
1078 // Set variables that we normally get from context.
1079 // In form mode these are set in preProcess.
1080 //TODO: set memberships, fixme
1081 $this->setContextVariables($formValues);
1083 $this->_memTypeSelected
= self
::getSelectedMemberships(
1087 if (empty($formValues['financial_type_id'])) {
1088 $formValues['financial_type_id'] = $this->_priceSet
['financial_type_id'];
1091 $config = CRM_Core_Config
::singleton();
1093 $membershipTypeValues = [];
1094 foreach ($this->_memTypeSelected
as $memType) {
1095 $membershipTypeValues[$memType]['membership_type_id'] = $memType;
1098 //take the required membership recur values.
1099 if ($this->_mode
&& !empty($formValues['auto_renew'])) {
1100 $params['is_recur'] = $formValues['is_recur'] = TRUE;
1103 foreach ($this->_memTypeSelected
as $memType) {
1104 $recurMembershipTypeValues = CRM_Utils_Array
::value($memType,
1105 $this->allMembershipTypeDetails
, []
1107 if (!$recurMembershipTypeValues['auto_renew']) {
1111 'frequency_interval' => 'duration_interval',
1112 'frequency_unit' => 'duration_unit',
1113 ] as $mapVal => $mapParam) {
1114 $membershipTypeValues[$memType][$mapVal] = $recurMembershipTypeValues[$mapParam];
1117 $formValues[$mapVal] = CRM_Utils_Array
::value($mapParam,
1118 $recurMembershipTypeValues
1126 $isQuickConfig = $this->_priceSet
['is_quick_config'];
1130 $lineItem = [$this->_priceSetId
=> []];
1132 // BEGIN Fix for dev/core/issues/860
1133 // Prepare fee block and call buildAmount hook - based on CRM_Price_BAO_PriceSet::buildPriceSet().
1134 CRM_Price_BAO_PriceSet
::applyACLFinancialTypeStatusToFeeBlock($this->_priceSet
['fields']);
1135 CRM_Utils_Hook
::buildAmount('membership', $this, $this->_priceSet
['fields']);
1136 // END Fix for dev/core/issues/860
1138 CRM_Price_BAO_PriceSet
::processAmount($this->_priceSet
['fields'],
1139 $formValues, $lineItem[$this->_priceSetId
], $this->_priceSetId
);
1141 if (!empty($formValues['tax_amount'])) {
1142 $params['tax_amount'] = $formValues['tax_amount'];
1144 $params['total_amount'] = CRM_Utils_Array
::value('amount', $formValues);
1145 if (!empty($lineItem[$this->_priceSetId
])) {
1146 foreach ($lineItem[$this->_priceSetId
] as &$li) {
1147 if (!empty($li['membership_type_id'])) {
1148 if (!empty($li['membership_num_terms'])) {
1149 $termsByType[$li['membership_type_id']] = $li['membership_num_terms'];
1153 ///CRM-11529 for quick config backoffice transactions
1154 //when financial_type_id is passed in form, update the
1155 //lineitems with the financial type selected in form
1156 $submittedFinancialType = CRM_Utils_Array
::value('financial_type_id', $formValues);
1157 if ($isQuickConfig && $submittedFinancialType) {
1158 $li['financial_type_id'] = $submittedFinancialType;
1163 $params['contact_id'] = $this->_contactID
;
1169 'status_override_end_date',
1173 foreach ($fields as $f) {
1174 $params[$f] = CRM_Utils_Array
::value($f, $formValues);
1178 // when is_override false ignore is_admin statuses during membership
1179 // status calculation. similarly we did fix for import in CRM-3570.
1180 if (empty($params['is_override'])) {
1181 $params['exclude_is_admin'] = TRUE;
1184 // process date params to mysql date format.
1186 'join_date' => 'joinDate',
1187 'start_date' => 'startDate',
1188 'end_date' => 'endDate',
1190 foreach ($dateTypes as $dateField => $dateVariable) {
1191 $
$dateVariable = CRM_Utils_Date
::processDate($formValues[$dateField]);
1194 $memTypeNumTerms = empty($termsByType) ? CRM_Utils_Array
::value('num_terms', $formValues) : NULL;
1197 foreach ($this->_memTypeSelected
as $memType) {
1198 if (empty($memTypeNumTerms)) {
1199 $memTypeNumTerms = CRM_Utils_Array
::value($memType, $termsByType, 1);
1201 $calcDates[$memType] = CRM_Member_BAO_MembershipType
::getDatesForMembershipType($memType,
1202 $joinDate, $startDate, $endDate, $memTypeNumTerms
1206 foreach ($calcDates as $memType => $calcDate) {
1207 foreach (array_keys($dateTypes) as $d) {
1208 //first give priority to form values then calDates.
1209 $date = CRM_Utils_Array
::value($d, $formValues);
1211 $date = CRM_Utils_Array
::value($d, $calcDate);
1214 $membershipTypeValues[$memType][$d] = CRM_Utils_Date
::processDate($date);
1218 foreach ($this->_memTypeSelected
as $memType) {
1219 if (array_key_exists('max_related', $formValues)) {
1220 // max related memberships - take from form or inherit from membership type
1221 $membershipTypeValues[$memType]['max_related'] = CRM_Utils_Array
::value('max_related', $formValues);
1223 $membershipTypeValues[$memType]['custom'] = CRM_Core_BAO_CustomField
::postProcess($formValues,
1227 $membershipTypes[$memType] = CRM_Core_DAO
::getFieldValue('CRM_Member_DAO_MembershipType',
1232 $membershipType = implode(', ', $membershipTypes);
1234 // Retrieve the name and email of the current user - this will be the FROM for the receipt email
1235 list($userName) = CRM_Contact_BAO_Contact_Location
::getEmailDetails($ids['userId']);
1237 //CRM-13981, allow different person as a soft-contributor of chosen type
1238 if ($this->_contributorContactID
!= $this->_contactID
) {
1239 $params['contribution_contact_id'] = $this->_contributorContactID
;
1240 if (!empty($formValues['soft_credit_type_id'])) {
1241 $softParams['soft_credit_type_id'] = $formValues['soft_credit_type_id'];
1242 $softParams['contact_id'] = $this->_contactID
;
1246 $pendingMembershipStatusId = CRM_Core_PseudoConstant
::getKey('CRM_Member_BAO_Membership', 'status_id', 'Pending');
1248 if (!empty($formValues['record_contribution'])) {
1249 $recordContribution = [
1251 'financial_type_id',
1252 'payment_instrument_id',
1254 'contribution_status_id',
1262 foreach ($recordContribution as $f) {
1263 $params[$f] = CRM_Utils_Array
::value($f, $formValues);
1266 if (!$this->_onlinePendingContributionId
) {
1267 if (empty($formValues['source'])) {
1268 $params['contribution_source'] = ts('%1 Membership: Offline signup (by %2)', [
1269 1 => $membershipType,
1274 $params['contribution_source'] = $formValues['source'];
1278 $completedContributionStatusId = CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed');
1279 if (empty($params['is_override']) &&
1280 CRM_Utils_Array
::value('contribution_status_id', $params) != $completedContributionStatusId
1282 $params['status_id'] = $pendingMembershipStatusId;
1283 $params['skipStatusCal'] = TRUE;
1284 $params['is_pay_later'] = 1;
1285 $this->assign('is_pay_later', 1);
1288 if (!empty($formValues['send_receipt'])) {
1289 $params['receipt_date'] = CRM_Utils_Array
::value('receive_date', $formValues);
1292 //insert financial type name in receipt.
1293 $formValues['contributionType_name'] = CRM_Core_DAO
::getFieldValue('CRM_Financial_DAO_FinancialType',
1294 $formValues['financial_type_id']
1298 // process line items, until no previous line items.
1299 if (!empty($lineItem)) {
1300 $params['lineItems'] = $lineItem;
1301 $params['processPriceSet'] = TRUE;
1303 $createdMemberships = [];
1305 $params['total_amount'] = CRM_Utils_Array
::value('total_amount', $formValues, 0);
1307 //CRM-20264 : Store CC type and number (last 4 digit) during backoffice or online payment
1308 $params['card_type_id'] = CRM_Utils_Array
::value('card_type_id', $this->_params
);
1309 $params['pan_truncation'] = CRM_Utils_Array
::value('pan_truncation', $this->_params
);
1311 if (!$isQuickConfig) {
1312 $params['financial_type_id'] = CRM_Core_DAO
::getFieldValue('CRM_Price_DAO_PriceSet',
1318 $params['financial_type_id'] = CRM_Utils_Array
::value('financial_type_id', $formValues);
1321 //get the payment processor id as per mode. Try removing in favour of beginPostProcess.
1322 $params['payment_processor_id'] = $formValues['payment_processor_id'] = $this->_paymentProcessor
['id'];
1323 $params['register_date'] = date('YmdHis');
1325 // add all the additional payment params we need
1326 $formValues['amount'] = $params['total_amount'];
1327 // @todo this is a candidate for beginPostProcessFunction.
1328 $formValues['currencyID'] = $config->defaultCurrency
;
1329 $formValues['description'] = ts("Contribution submitted by a staff person using member's credit card for signup");
1330 $formValues['invoiceID'] = md5(uniqid(rand(), TRUE));
1331 $formValues['financial_type_id'] = $params['financial_type_id'];
1333 // at this point we've created a contact and stored its address etc
1334 // all the payment processors expect the name and address to be in the
1335 // so we copy stuff over to first_name etc.
1336 $paymentParams = $formValues;
1337 $paymentParams['contactID'] = $this->_contributorContactID
;
1338 //CRM-10377 if payment is by an alternate contact then we need to set that person
1339 // as the contact in the payment params
1340 if ($this->_contributorContactID
!= $this->_contactID
) {
1341 if (!empty($formValues['soft_credit_type_id'])) {
1342 $softParams['contact_id'] = $params['contact_id'];
1343 $softParams['soft_credit_type_id'] = $formValues['soft_credit_type_id'];
1346 if (!empty($formValues['send_receipt'])) {
1347 $paymentParams['email'] = $this->_contributorEmail
;
1350 // This is a candidate for shared beginPostProcess function.
1351 // @todo Do we need this now we have $this->formatParamsForPaymentProcessor() ?
1352 CRM_Core_Payment_Form
::mapParams($this->_bltID
, $formValues, $paymentParams, TRUE);
1353 // CRM-7137 -for recurring membership,
1354 // we do need contribution and recurring records.
1356 if (!empty($paymentParams['is_recur'])) {
1357 $financialType = new CRM_Financial_DAO_FinancialType();
1358 $financialType->id
= $params['financial_type_id'];
1359 $financialType->find(TRUE);
1360 $this->_params
= $formValues;
1362 $contribution = CRM_Contribute_Form_Contribution_Confirm
::processFormContribution($this,
1366 'contact_id' => $this->_contributorContactID
,
1367 'line_item' => $lineItem,
1368 'is_test' => $isTest,
1369 'campaign_id' => CRM_Utils_Array
::value('campaign_id', $paymentParams),
1370 'contribution_page_id' => CRM_Utils_Array
::value('contribution_page_id', $formValues),
1371 'source' => CRM_Utils_Array
::value('source', $paymentParams, CRM_Utils_Array
::value('description', $paymentParams)),
1372 'thankyou_date' => CRM_Utils_Array
::value('thankyou_date', $paymentParams),
1373 'payment_instrument_id' => $paymentInstrumentID,
1381 //create new soft-credit record, CRM-13981
1383 $softParams['contribution_id'] = $contribution->id
;
1384 $softParams['currency'] = $contribution->currency
;
1385 $softParams['amount'] = $contribution->total_amount
;
1386 CRM_Contribute_BAO_ContributionSoft
::add($softParams);
1389 $paymentParams['contactID'] = $this->_contactID
;
1390 $paymentParams['contributionID'] = $contribution->id
;
1391 $paymentParams['contributionTypeID'] = $contribution->financial_type_id
;
1392 $paymentParams['contributionPageID'] = $contribution->contribution_page_id
;
1393 $paymentParams['contributionRecurID'] = $contribution->contribution_recur_id
;
1394 $params['contribution_id'] = $paymentParams['contributionID'];
1395 $params['contribution_recur_id'] = $paymentParams['contributionRecurID'];
1397 $paymentStatus = NULL;
1399 if ($params['total_amount'] > 0.0) {
1400 $payment = $this->_paymentProcessor
['object'];
1402 $result = $payment->doPayment($paymentParams);
1403 $formValues = array_merge($formValues, $result);
1404 $paymentStatus = CRM_Core_PseudoConstant
::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $formValues['payment_status_id']);
1405 // Assign amount to template if payment was successful.
1406 $this->assign('amount', $params['total_amount']);
1408 catch (\Civi\Payment\Exception\PaymentProcessorException
$e) {
1409 if (!empty($paymentParams['contributionID'])) {
1410 CRM_Contribute_BAO_Contribution
::failPayment($paymentParams['contributionID'], $this->_contactID
,
1413 if (!empty($paymentParams['contributionRecurID'])) {
1414 CRM_Contribute_BAO_ContributionRecur
::deleteRecurContribution($paymentParams['contributionRecurID']);
1417 CRM_Core_Session
::singleton()->setStatus($e->getMessage());
1418 CRM_Utils_System
::redirect(CRM_Utils_System
::url('civicrm/contact/view/membership',
1419 "reset=1&action=add&cid={$this->_contactID}&context=membership&mode={$this->_mode}"
1425 if ($paymentStatus !== 'Completed') {
1426 $params['status_id'] = $pendingMembershipStatusId;
1427 $params['skipStatusCal'] = TRUE;
1428 // unset send-receipt option, since receipt will be sent when ipn is received.
1429 unset($formValues['send_receipt'], $formValues['send_receipt']);
1430 //as membership is pending set dates to null.
1432 'join_date' => 'joinDate',
1433 'start_date' => 'startDate',
1434 'end_date' => 'endDate',
1436 foreach ($memberDates as $dv) {
1438 foreach ($this->_memTypeSelected
as $memType) {
1439 $membershipTypeValues[$memType][$dv] = NULL;
1443 $now = date('YmdHis');
1444 $params['receive_date'] = date('Y-m-d H:i:s');
1445 $params['invoice_id'] = $formValues['invoiceID'];
1446 $params['contribution_source'] = ts('%1 Membership Signup: Credit card or direct debit (by %2)',
1447 [1 => $membershipType, 2 => $userName]
1449 $params['source'] = $formValues['source'] ?
$formValues['source'] : $params['contribution_source'];
1450 $params['trxn_id'] = CRM_Utils_Array
::value('trxn_id', $result);
1451 $params['is_test'] = ($this->_mode
== 'live') ?
0 : 1;
1452 if (!empty($formValues['send_receipt'])) {
1453 $params['receipt_date'] = $now;
1456 $params['receipt_date'] = NULL;
1459 $this->set('params', $formValues);
1460 $this->assign('trxn_id', CRM_Utils_Array
::value('trxn_id', $result));
1461 $this->assign('receive_date',
1462 CRM_Utils_Date
::mysqlToIso($params['receive_date'])
1465 // required for creating membership for related contacts
1466 $params['action'] = $this->_action
;
1468 //create membership record.
1470 foreach ($this->_memTypeSelected
as $memType) {
1472 ($relateContribution = CRM_Member_BAO_Membership
::getMembershipContributionId($membership->id
))
1474 $membershipTypeValues[$memType]['relate_contribution_id'] = $relateContribution;
1477 $membershipParams = array_merge($membershipTypeValues[$memType], $params);
1479 if (!empty($softParams) && empty($paymentParams['is_recur'])) {
1480 $membershipParams['soft_credit'] = $softParams;
1482 if (isset($result['fee_amount'])) {
1483 $membershipParams['fee_amount'] = $result['fee_amount'];
1485 // This is required to trigger the recording of the membership contribution in the
1486 // CRM_Member_BAO_Membership::Create function.
1487 // @todo stop setting this & 'teach' the create function to respond to something
1488 // appropriate as part of our 2-step always create the pending contribution & then finally add the payment
1490 // @see http://wiki.civicrm.org/confluence/pages/viewpage.action?pageId=261062657#Payments&AccountsRoadmap-Movetowardsalwaysusinga2-steppaymentprocess
1491 $membershipParams['contribution_status_id'] = CRM_Utils_Array
::value('payment_status_id', $result);
1492 if (!empty($paymentParams['is_recur'])) {
1493 // The earlier process created the line items (although we want to get rid of the earlier one in favour
1494 // of a single path!
1495 unset($membershipParams['lineItems']);
1497 $membershipParams['payment_instrument_id'] = $paymentInstrumentID;
1498 // @todo stop passing $ids (membership and userId only are set above)
1499 $membership = CRM_Member_BAO_Membership
::create($membershipParams, $ids);
1500 $params['contribution'] = CRM_Utils_Array
::value('contribution', $membershipParams);
1501 unset($params['lineItems']);
1502 $this->_membershipIDs
[] = $membership->id
;
1503 $createdMemberships[$memType] = $membership;
1509 $params['action'] = $this->_action
;
1510 if ($this->_onlinePendingContributionId
&& !empty($formValues['record_contribution'])) {
1512 // update membership as well as contribution object, CRM-4395
1513 $params['contribution_id'] = $this->_onlinePendingContributionId
;
1514 $params['componentId'] = $params['id'];
1515 $params['componentName'] = 'contribute';
1516 $result = CRM_Contribute_BAO_Contribution
::transitionComponents($params, TRUE);
1517 if (!empty($result) && !empty($params['contribution_id'])) {
1519 $lineItems = CRM_Price_BAO_LineItem
::getLineItemsByContributionID($params['contribution_id']);
1520 $itemId = key($lineItems);
1521 $priceSetId = CRM_Core_DAO
::getFieldValue('CRM_Price_DAO_PriceField', $lineItems[$itemId]['price_field_id'], 'price_set_id');
1523 $lineItems[$itemId]['unit_price'] = $params['total_amount'];
1524 $lineItems[$itemId]['line_total'] = $params['total_amount'];
1525 $lineItems[$itemId]['id'] = $itemId;
1526 $lineItem[$priceSetId] = $lineItems;
1527 $contributionBAO = new CRM_Contribute_BAO_Contribution();
1528 $contributionBAO->id
= $params['contribution_id'];
1529 $contributionBAO->contact_id
= $params['contact_id'];
1530 $contributionBAO->find();
1531 CRM_Price_BAO_LineItem
::processPriceSet($params['contribution_id'], $lineItem, $contributionBAO, 'civicrm_membership');
1533 //create new soft-credit record, CRM-13981
1535 $softParams['contribution_id'] = $params['contribution_id'];
1536 while ($contributionBAO->fetch()) {
1537 $softParams['currency'] = $contributionBAO->currency
;
1538 $softParams['amount'] = $contributionBAO->total_amount
;
1540 CRM_Contribute_BAO_ContributionSoft
::add($softParams);
1544 //carry updated membership object.
1545 $membership = new CRM_Member_DAO_Membership();
1546 $membership->id
= $this->_id
;
1547 $membership->find(TRUE);
1550 if ($membership->end_date
) {
1551 //display end date w/ status message.
1552 $endDate = $membership->end_date
;
1554 if (!in_array($membership->status_id
, [
1556 array_search('Cancelled', CRM_Member_PseudoConstant
::membershipStatus(NULL, " name = 'Cancelled' ", 'name', FALSE, TRUE)),
1557 array_search('Expired', CRM_Member_PseudoConstant
::membershipStatus()),
1563 // suppress form values in template.
1564 $this->assign('cancelled', $cancelled);
1566 $createdMemberships[] = $membership;
1570 foreach ($this->_memTypeSelected
as $memType) {
1571 if ($count && !empty($formValues['record_contribution']) &&
1572 ($relateContribution = CRM_Member_BAO_Membership
::getMembershipContributionId($membership->id
))
1574 $membershipTypeValues[$memType]['relate_contribution_id'] = $relateContribution;
1577 // @todo figure out why recieve_date isn't being set right here.
1578 if (empty($params['receive_date'])) {
1579 $params['receive_date'] = date('Y-m-d H:i:s');
1581 $membershipParams = array_merge($params, $membershipTypeValues[$memType]);
1582 if (!empty($formValues['int_amount'])) {
1584 foreach ($formValues as $key => $value) {
1585 if (strstr($key, 'txt-price')) {
1586 $init_amount[$key] = $value;
1589 $membershipParams['init_amount'] = $init_amount;
1592 if (!empty($softParams)) {
1593 $membershipParams['soft_credit'] = $softParams;
1595 // @todo stop passing $ids (membership and userId only are set above)
1596 $membership = CRM_Member_BAO_Membership
::create($membershipParams, $ids);
1597 $params['contribution'] = CRM_Utils_Array
::value('contribution', $membershipParams);
1598 unset($params['lineItems']);
1599 // skip line item creation for next interation since line item(s) are already created.
1600 $params['skipLineItem'] = TRUE;
1602 $this->_membershipIDs
[] = $membership->id
;
1603 $createdMemberships[$memType] = $membership;
1608 $isRecur = CRM_Utils_Array
::value('is_recur', $params);
1609 if (($this->_action
& CRM_Core_Action
::UPDATE
)) {
1610 $this->addStatusMessage($this->getStatusMessageForUpdate($membership, $endDate));
1612 elseif (($this->_action
& CRM_Core_Action
::ADD
)) {
1613 $this->addStatusMessage($this->getStatusMessageForCreate($endDate, $membershipTypes, $createdMemberships,
1614 $isRecur, $calcDates));
1617 if (!empty($lineItem[$this->_priceSetId
])) {
1618 $invoiceSettings = Civi
::settings()->get('contribution_invoice_settings');
1619 $invoicing = CRM_Utils_Array
::value('invoicing', $invoiceSettings);
1621 $totalTaxAmount = 0;
1622 foreach ($lineItem[$this->_priceSetId
] as & $priceFieldOp) {
1623 if (!empty($priceFieldOp['membership_type_id'])) {
1624 $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') : '-';
1625 $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') : '-';
1628 $priceFieldOp['start_date'] = $priceFieldOp['end_date'] = 'N/A';
1630 if ($invoicing && isset($priceFieldOp['tax_amount'])) {
1632 $totalTaxAmount +
= $priceFieldOp['tax_amount'];
1637 foreach ($lineItem[$this->_priceSetId
] as $key => $value) {
1638 if (isset($value['tax_amount']) && isset($value['tax_rate'])) {
1639 if (isset($dataArray[$value['tax_rate']])) {
1640 $dataArray[$value['tax_rate']] = $dataArray[$value['tax_rate']] + CRM_Utils_Array
::value('tax_amount', $value);
1643 $dataArray[$value['tax_rate']] = CRM_Utils_Array
::value('tax_amount', $value);
1648 $this->assign('totalTaxAmount', $totalTaxAmount);
1649 // Not sure why would need this on Submit.... unless it's being used when sending mails in which case this is the wrong place
1650 $this->assign('taxTerm', $this->getSalesTaxTerm());
1652 $this->assign('dataArray', $dataArray);
1655 $this->assign('lineItem', !empty($lineItem) && !$isQuickConfig ?
$lineItem : FALSE);
1657 $receiptSend = FALSE;
1658 $contributionId = CRM_Member_BAO_Membership
::getMembershipContributionId($membership->id
);
1659 $membershipIds = $this->_membershipIDs
;
1660 if ($contributionId && !empty($membershipIds)) {
1661 $contributionDetails = CRM_Contribute_BAO_Contribution
::getContributionDetails(
1662 CRM_Export_Form_Select
::MEMBER_EXPORT
, $this->_membershipIDs
);
1663 if ($contributionDetails[$membership->id
]['contribution_status'] == 'Completed') {
1664 $receiptSend = TRUE;
1668 $receiptSent = FALSE;
1669 if (!empty($formValues['send_receipt']) && $receiptSend) {
1670 $formValues['contact_id'] = $this->_contactID
;
1671 $formValues['contribution_id'] = $contributionId;
1672 // We really don't need a distinct receipt_text_signup vs receipt_text_renewal as they are
1673 // handled in the receipt. But by setting one we avoid breaking templates for now
1674 // although at some point we should switch in the templates.
1675 $formValues['receipt_text_signup'] = $formValues['receipt_text'];
1676 // send email receipt
1677 $this->assignBillingName();
1678 $mailSend = $this->emailMembershipReceipt($formValues, $membership);
1679 $receiptSent = TRUE;
1682 // finally set membership id if already not set
1684 $this->_id
= $membership->id
;
1687 $this->updateContributionOnMembershipTypeChange($params, $membership);
1688 if ($receiptSent && $mailSend) {
1689 $this->addStatusMessage(ts('A membership confirmation and receipt has been sent to %1.', [1 => $this->_contributorEmail
]));
1692 CRM_Core_Session
::setStatus($this->getStatusMessage(), ts('Complete'), 'success');
1693 $this->setStatusMessage($membership);
1697 * Update related contribution of a membership if update_contribution_on_membership_type_change
1698 * contribution setting is enabled and type is changed on edit
1700 * @param array $inputParams
1701 * submitted form values
1702 * @param CRM_Member_DAO_Membership $membership
1703 * Updated membership object
1706 protected function updateContributionOnMembershipTypeChange($inputParams, $membership) {
1707 if (Civi
::settings()->get('update_contribution_on_membership_type_change') &&
1709 ($this->_action
& CRM_Core_Action
::UPDATE
) &&
1712 // if selected membership doesn't match with earlier membership
1713 !in_array($this->_memType
, $this->_memTypeSelected
)
1715 if (!empty($inputParams['is_recur'])) {
1716 CRM_Core_Session
::setStatus(ts('Associated recurring contribution cannot be updated on membership type change.', ts('Error'), 'error'));
1720 // fetch lineitems by updated membership ID
1721 $lineItems = CRM_Price_BAO_LineItem
::getLineItems($membership->id
, 'membership');
1722 // retrieve the related contribution ID
1723 $contributionID = CRM_Core_DAO
::getFieldValue(
1724 'CRM_Member_DAO_MembershipPayment',
1729 // get price fields of chosen price-set
1730 $priceSetDetails = CRM_Utils_Array
::value(
1732 CRM_Price_BAO_PriceSet
::getSetDetail(
1739 // add price field information in $inputParams
1740 self
::addPriceFieldByMembershipType($inputParams, $priceSetDetails['fields'], $membership->membership_type_id
);
1742 // update related contribution and financial records
1743 CRM_Price_BAO_LineItem
::changeFeeSelections(
1748 $priceSetDetails['fields'],
1751 CRM_Core_Session
::setStatus(ts('Associated contribution is updated on membership type change.'), ts('Success'), 'success');
1756 * Add selected price field information in $formValues
1758 * @param array $formValues
1759 * submitted form values
1760 * @param array $priceFields
1761 * Price fields of selected Priceset ID
1762 * @param int $membershipTypeID
1763 * Selected membership type ID
1766 public static function addPriceFieldByMembershipType(&$formValues, $priceFields, $membershipTypeID) {
1767 foreach ($priceFields as $priceFieldID => $priceField) {
1768 if (isset($priceField['options']) && count($priceField['options'])) {
1769 foreach ($priceField['options'] as $option) {
1770 if ($option['membership_type_id'] == $membershipTypeID) {
1771 $formValues["price_{$priceFieldID}"] = $option['id'];
1780 * Set context in session.
1782 protected function setUserContext() {
1783 $buttonName = $this->controller
->getButtonName();
1784 $session = CRM_Core_Session
::singleton();
1786 if ($this->_context
== 'standalone') {
1787 if ($buttonName == $this->getButtonName('upload', 'new')) {
1788 $session->replaceUserContext(CRM_Utils_System
::url('civicrm/member/add',
1789 'reset=1&action=add&context=standalone'
1793 $session->replaceUserContext(CRM_Utils_System
::url('civicrm/contact/view',
1794 "reset=1&cid={$this->_contactID}&selectedChild=member"
1798 elseif ($buttonName == $this->getButtonName('upload', 'new')) {
1799 $session->replaceUserContext(CRM_Utils_System
::url('civicrm/contact/view/membership',
1800 "reset=1&action=add&context=membership&cid={$this->_contactID}"
1806 * Get status message for updating membership.
1808 * @param CRM_Member_BAO_Membership $membership
1809 * @param string $endDate
1813 protected function getStatusMessageForUpdate($membership, $endDate) {
1814 // End date can be modified by hooks, so if end date is set then use it.
1815 $endDate = ($membership->end_date
) ?
$membership->end_date
: $endDate;
1817 $statusMsg = ts('Membership for %1 has been updated.', [1 => $this->_memberDisplayName
]);
1818 if ($endDate && $endDate !== 'null') {
1819 $endDate = CRM_Utils_Date
::customFormat($endDate);
1820 $statusMsg .= ' ' . ts('The membership End Date is %1.', [1 => $endDate]);
1826 * Get status message for create action.
1828 * @param string $endDate
1829 * @param array $membershipTypes
1830 * @param array $createdMemberships
1831 * @param bool $isRecur
1832 * @param array $calcDates
1834 * @return array|string
1836 protected function getStatusMessageForCreate($endDate, $membershipTypes, $createdMemberships,
1837 $isRecur, $calcDates) {
1838 // FIX ME: fix status messages
1841 foreach ($membershipTypes as $memType => $membershipType) {
1842 $statusMsg[$memType] = ts('%1 membership for %2 has been added.', [
1843 1 => $membershipType,
1844 2 => $this->_memberDisplayName
,
1847 $membership = $createdMemberships[$memType];
1848 $memEndDate = ($membership->end_date
) ?
$membership->end_date
: $endDate;
1850 //get the end date from calculated dates.
1851 if (!$memEndDate && !$isRecur) {
1852 $memEndDate = CRM_Utils_Array
::value('end_date', $calcDates[$memType]);
1855 if ($memEndDate && $memEndDate !== 'null') {
1856 $memEndDate = CRM_Utils_Date
::customFormat($memEndDate);
1857 $statusMsg[$memType] .= ' ' . ts('The new membership End Date is %1.', [1 => $memEndDate]);
1860 $statusMsg = implode('<br/>', $statusMsg);
1865 * @param $membership
1867 protected function setStatusMessage($membership) {
1869 // display message when membership type is changed
1870 if (($this->_action
& CRM_Core_Action
::UPDATE
) && $this->_id
&& !in_array($this->_memType
, $this->_memTypeSelected
)) {
1871 $lineItem = CRM_Price_BAO_LineItem
::getLineItems($this->_id
, 'membership');
1872 $maxID = max(array_keys($lineItem));
1873 $lineItem = $lineItem[$maxID];
1874 $membershipTypeDetails = $this->allMembershipTypeDetails
[$membership->membership_type_id
];
1875 if ($membershipTypeDetails['financial_type_id'] != $lineItem['financial_type_id']) {
1876 CRM_Core_Session
::setStatus(
1877 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.'),
1881 if ($membershipTypeDetails['minimum_fee'] != $lineItem['line_total']) {
1882 CRM_Core_Session
::setStatus(
1883 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.'),
1893 protected function isUpdateToExistingRecurringMembership() {
1895 if ($this->_action
& CRM_Core_Action
::UPDATE
1896 && CRM_Core_DAO
::getFieldValue('CRM_Member_DAO_Membership', $this->getEntityId(),
1897 'contribution_recur_id')
1898 && !CRM_Member_BAO_Membership
::isSubscriptionCancelled($this->getEntityId())) {
1906 * Send a receipt for the membership.
1908 * @param array $formValues
1909 * @param \CRM_Member_BAO_Membership $membership
1913 protected function emailMembershipReceipt($formValues, $membership) {
1914 $customValues = $this->getCustomValuesForReceipt($formValues, $membership);
1916 return self
::emailReceipt($this, $formValues, $membership, $customValues);
1920 * Filter the custom values from the input parameters (for display in the email).
1922 * @todo figure out why the scary code this calls does & document.
1924 * @param array $formValues
1925 * @param \CRM_Member_BAO_Membership $membership
1928 protected function getCustomValuesForReceipt($formValues, $membership) {
1929 $customFields = $customValues = [];
1930 if (property_exists($this, '_groupTree')
1931 && !empty($this->_groupTree
)
1933 foreach ($this->_groupTree
as $groupID => $group) {
1934 if ($groupID == 'info') {
1937 foreach ($group['fields'] as $k => $field) {
1938 $field['title'] = $field['label'];
1939 $customFields["custom_{$k}"] = $field;
1944 $members = [['member_id', '=', $membership->id
, 0, 0]];
1945 // check whether its a test drive
1946 if ($this->_mode
== 'test') {
1947 $members[] = ['member_test', '=', 1, 0, 0];
1950 CRM_Core_BAO_UFGroup
::getValues($formValues['contact_id'], $customFields, $customValues, FALSE, $members);
1951 return $customValues;