Merge pull request #18979 from eileenmcnaughton/trans
[civicrm-core.git] / CRM / Contribute / Form / Contribution / Main.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
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 +--------------------------------------------------------------------+
10 */
11
12 /**
13 *
14 * @package CRM
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
16 */
17
18 /**
19 * This class generates form components for processing a Contribution.
20 */
21 class CRM_Contribute_Form_Contribution_Main extends CRM_Contribute_Form_ContributionBase {
22
23 /**
24 * Define default MembershipType Id.
25 * @var int
26 */
27 public $_defaultMemTypeId;
28
29 public $_paymentProcessors;
30
31 public $_membershipTypeValues;
32
33 public $_useForMember;
34
35 /**
36 * Array of payment related fields to potentially display on this form (generally credit card or debit card fields). This is rendered via billingBlock.tpl
37 * @var array
38 */
39 public $_paymentFields = [];
40
41 protected $_paymentProcessorID;
42 protected $_snippet;
43
44 /**
45 * Set variables up before form is built.
46 */
47 public function preProcess() {
48 parent::preProcess();
49
50 $this->_paymentProcessors = $this->get('paymentProcessors');
51 $this->preProcessPaymentOptions();
52
53 $this->assignFormVariablesByContributionID();
54
55 // Make the contributionPageID available to the template
56 $this->assign('contributionPageID', $this->_id);
57 $this->assign('ccid', $this->_ccid);
58 $this->assign('isShare', CRM_Utils_Array::value('is_share', $this->_values));
59 $this->assign('isConfirmEnabled', CRM_Utils_Array::value('is_confirm_enabled', $this->_values));
60
61 $this->assign('reset', CRM_Utils_Request::retrieve('reset', 'Boolean'));
62 $this->assign('mainDisplay', CRM_Utils_Request::retrieve('_qf_Main_display', 'Boolean',
63 CRM_Core_DAO::$_nullObject));
64
65 if (!empty($this->_pcpInfo['id']) && !empty($this->_pcpInfo['intro_text'])) {
66 $this->assign('intro_text', $this->_pcpInfo['intro_text']);
67 }
68 elseif (!empty($this->_values['intro_text'])) {
69 $this->assign('intro_text', $this->_values['intro_text']);
70 }
71
72 $qParams = "reset=1&amp;id={$this->_id}";
73 if ($pcpId = CRM_Utils_Array::value('pcp_id', $this->_pcpInfo)) {
74 $qParams .= "&amp;pcpId={$pcpId}";
75 }
76 $this->assign('qParams', $qParams);
77
78 if (!empty($this->_values['footer_text'])) {
79 $this->assign('footer_text', $this->_values['footer_text']);
80 }
81 }
82
83 /**
84 * Set the default values.
85 */
86 public function setDefaultValues() {
87 // check if the user is registered and we have a contact ID
88 $contactID = $this->getContactID();
89
90 if (!empty($contactID)) {
91 $fields = [];
92 $removeCustomFieldTypes = ['Contribution', 'Membership'];
93 $contribFields = CRM_Contribute_BAO_Contribution::getContributionFields();
94
95 // remove component related fields
96 foreach ($this->_fields as $name => $fieldInfo) {
97 //don't set custom data Used for Contribution (CRM-1344)
98 if (substr($name, 0, 7) == 'custom_') {
99 $id = substr($name, 7);
100 if (!CRM_Core_BAO_CustomGroup::checkCustomField($id, $removeCustomFieldTypes)) {
101 continue;
102 }
103 // ignore component fields
104 }
105 elseif (array_key_exists($name, $contribFields) || (substr($name, 0, 11) == 'membership_') || (substr($name, 0, 13) == 'contribution_')) {
106 continue;
107 }
108 $fields[$name] = $fieldInfo;
109 }
110
111 if (!empty($fields)) {
112 CRM_Core_BAO_UFGroup::setProfileDefaults($contactID, $fields, $this->_defaults);
113 }
114
115 $billingDefaults = $this->getProfileDefaults('Billing', $contactID);
116 $this->_defaults = array_merge($this->_defaults, $billingDefaults);
117 }
118 if (!empty($this->_ccid) && !empty($this->_pendingAmount)) {
119 $this->_defaults['total_amount'] = CRM_Utils_Money::format($this->_pendingAmount, NULL, '%a');
120 }
121
122 /*
123 * hack to simplify credit card entry for testing
124 *
125 * $this->_defaults['credit_card_type'] = 'Visa';
126 * $this->_defaults['amount'] = 168;
127 * $this->_defaults['credit_card_number'] = '4111111111111111';
128 * $this->_defaults['cvv2'] = '000';
129 * $this->_defaults['credit_card_exp_date'] = array('Y' => date('Y')+1, 'M' => '05');
130 * // hack to simplify direct debit entry for testing
131 * $this->_defaults['account_holder'] = 'Max Müller';
132 * $this->_defaults['bank_account_number'] = '12345678';
133 * $this->_defaults['bank_identification_number'] = '12030000';
134 * $this->_defaults['bank_name'] = 'Bankname';
135 */
136
137 //build set default for pledge overdue payment.
138 if (!empty($this->_values['pledge_id'])) {
139 //used to record completed pledge payment ids used later for honor default
140 $completedContributionIds = [];
141 $pledgePayments = CRM_Pledge_BAO_PledgePayment::getPledgePayments($this->_values['pledge_id']);
142
143 $paymentAmount = 0;
144 $duePayment = FALSE;
145 foreach ($pledgePayments as $payId => $value) {
146 if ($value['status'] == 'Overdue') {
147 $this->_defaults['pledge_amount'][$payId] = 1;
148 $paymentAmount += $value['scheduled_amount'];
149 }
150 elseif (!$duePayment && $value['status'] == 'Pending') {
151 $this->_defaults['pledge_amount'][$payId] = 1;
152 $paymentAmount += $value['scheduled_amount'];
153 $duePayment = TRUE;
154 }
155 elseif ($value['status'] == 'Completed' && $value['contribution_id']) {
156 $completedContributionIds[] = $value['contribution_id'];
157 }
158 }
159 $this->_defaults['price_' . $this->_priceSetId] = $paymentAmount;
160
161 if (count($completedContributionIds)) {
162 $softCredit = [];
163 foreach ($completedContributionIds as $id) {
164 $softCredit = CRM_Contribute_BAO_ContributionSoft::getSoftContribution($id);
165 }
166 if (isset($softCredit['soft_credit'])) {
167 $this->_defaults['soft_credit_type_id'] = $softCredit['soft_credit'][1]['soft_credit_type'];
168
169 //since honoree profile fieldname of fields are prefixed with 'honor'
170 //we need to reformat the fieldname to append prefix during setting default values
171 CRM_Core_BAO_UFGroup::setProfileDefaults(
172 $softCredit['soft_credit'][1]['contact_id'],
173 CRM_Core_BAO_UFGroup::getFields($this->_honoreeProfileId),
174 $defaults
175 );
176 foreach ($defaults as $fieldName => $value) {
177 $this->_defaults['honor[' . $fieldName . ']'] = $value;
178 }
179 }
180 }
181 }
182 elseif (!empty($this->_values['pledge_block_id'])) {
183 //set default to one time contribution.
184 $this->_defaults['is_pledge'] = 0;
185 }
186
187 // to process Custom data that are appended to URL
188 $getDefaults = CRM_Core_BAO_CustomGroup::extractGetParams($this, "'Contact', 'Individual', 'Contribution'");
189 $this->_defaults = array_merge($this->_defaults, $getDefaults);
190
191 $config = CRM_Core_Config::singleton();
192 // set default country from config if no country set
193 if (empty($this->_defaults["billing_country_id-{$this->_bltID}"])) {
194 $this->_defaults["billing_country_id-{$this->_bltID}"] = $config->defaultContactCountry;
195 }
196
197 // set default state/province from config if no state/province set
198 if (empty($this->_defaults["billing_state_province_id-{$this->_bltID}"])) {
199 $this->_defaults["billing_state_province_id-{$this->_bltID}"] = $config->defaultContactStateProvince;
200 }
201
202 $entityId = $memtypeID = NULL;
203 if ($this->_priceSetId) {
204 if (($this->_useForMember && !empty($this->_currentMemberships)) || $this->_defaultMemTypeId) {
205 $selectedCurrentMemTypes = [];
206 foreach ($this->_priceSet['fields'] as $key => $val) {
207 foreach ($val['options'] as $keys => $values) {
208 $opMemTypeId = $values['membership_type_id'] ?? NULL;
209 $priceFieldName = 'price_' . $values['price_field_id'];
210 $priceFieldValue = CRM_Price_BAO_PriceSet::getPriceFieldValueFromURL($this, $priceFieldName);
211 if (!empty($priceFieldValue)) {
212 CRM_Price_BAO_PriceSet::setDefaultPriceSetField($priceFieldName, $priceFieldValue, $val['html_type'], $this->_defaults);
213 // break here to prevent overwriting of default due to 'is_default'
214 // option configuration or setting of current membership or
215 // membership for related organization.
216 // The value sent via URL get's higher priority.
217 break;
218 }
219 elseif ($opMemTypeId &&
220 in_array($opMemTypeId, $this->_currentMemberships) &&
221 !in_array($opMemTypeId, $selectedCurrentMemTypes)
222 ) {
223 CRM_Price_BAO_PriceSet::setDefaultPriceSetField($priceFieldName, $keys, $val['html_type'], $this->_defaults);
224 $memtypeID = $selectedCurrentMemTypes[] = $values['membership_type_id'];
225 }
226 elseif (!empty($values['is_default']) && !$opMemTypeId && (!isset($this->_defaults[$priceFieldName]) ||
227 ($val['html_type'] == 'CheckBox' && !isset($this->_defaults[$priceFieldName][$keys])))) {
228 CRM_Price_BAO_PriceSet::setDefaultPriceSetField($priceFieldName, $keys, $val['html_type'], $this->_defaults);
229 $memtypeID = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue', $this->_defaults[$priceFieldName], 'membership_type_id');
230 }
231 }
232 }
233 $entityId = CRM_Utils_Array::value('id', CRM_Member_BAO_Membership::getContactMembership($contactID, $memtypeID, NULL));
234 }
235 else {
236 CRM_Price_BAO_PriceSet::setDefaultPriceSet($this, $this->_defaults);
237 }
238 }
239
240 //set custom field defaults set by admin if value is not set
241 if (!empty($this->_fields)) {
242 //load default campaign from page.
243 if (array_key_exists('contribution_campaign_id', $this->_fields)) {
244 $this->_defaults['contribution_campaign_id'] = $this->_values['campaign_id'] ?? NULL;
245 }
246
247 //set custom field defaults
248 foreach ($this->_fields as $name => $field) {
249 if ($customFieldID = CRM_Core_BAO_CustomField::getKeyID($name)) {
250 if (!isset($this->_defaults[$name])) {
251 CRM_Core_BAO_CustomField::setProfileDefaults($customFieldID, $name, $this->_defaults,
252 $entityId, CRM_Profile_Form::MODE_REGISTER
253 );
254 }
255 }
256 }
257 }
258
259 if (!empty($this->_paymentProcessors)) {
260 foreach ($this->_paymentProcessors as $pid => $value) {
261 if (!empty($value['is_default'])) {
262 $this->_defaults['payment_processor_id'] = $pid;
263 }
264 }
265 }
266
267 return $this->_defaults;
268 }
269
270 /**
271 * Build the form object.
272 */
273 public function buildQuickForm() {
274 // build profiles first so that we can determine address fields etc
275 // and then show copy address checkbox
276 if (empty($this->_ccid)) {
277 $this->buildCustom($this->_values['custom_pre_id'], 'customPre');
278 $this->buildCustom($this->_values['custom_post_id'], 'customPost');
279
280 // CRM-18399: used by template to pass pre profile id as a url arg
281 $this->assign('custom_pre_id', $this->_values['custom_pre_id']);
282
283 $this->buildComponentForm($this->_id, $this);
284 }
285
286 if (count($this->_paymentProcessors) >= 1 && !$this->get_template_vars("isCaptcha") && $this->hasToAddForcefully()) {
287 if (!$this->_userID) {
288 $this->enableCaptchaOnForm();
289 }
290 else {
291 $this->displayCaptchaWarning();
292 }
293 }
294
295 // Build payment processor form
296 CRM_Core_Payment_ProcessorForm::buildQuickForm($this);
297
298 $config = CRM_Core_Config::singleton();
299
300 $contactID = $this->getContactID();
301 if ($contactID) {
302 $this->assign('contact_id', $contactID);
303 $this->assign('display_name', CRM_Contact_BAO_Contact::displayName($contactID));
304 }
305
306 $this->applyFilter('__ALL__', 'trim');
307 if (empty($this->_ccid)) {
308 if ($this->_emailExists == FALSE) {
309 $this->add('text', "email-{$this->_bltID}",
310 ts('Email Address'),
311 ['size' => 30, 'maxlength' => 60, 'class' => 'email'],
312 TRUE
313 );
314 $this->assign('showMainEmail', TRUE);
315 $this->addRule("email-{$this->_bltID}", ts('Email is not valid.'), 'email');
316 }
317 }
318 else {
319 $this->addElement('hidden', "email-{$this->_bltID}", 1);
320 $this->add('text', 'total_amount', ts('Total Amount'), ['readonly' => TRUE], FALSE);
321 }
322 $pps = $this->getProcessors();
323 $this->addPaymentProcessorFieldsToForm();
324 if (!empty($pps) && count($pps) === 1) {
325 $ppKeys = array_keys($pps);
326 $currentPP = array_pop($ppKeys);
327 if ($currentPP === 0) {
328 $this->assign('is_pay_later', $this->_values['is_pay_later']);
329 $this->assign('pay_later_text', $this->getPayLaterLabel());
330 }
331 }
332
333 if ($contactID === 0) {
334 $this->addCidZeroOptions();
335 }
336
337 //build pledge block.
338 $this->_useForMember = 0;
339 //don't build membership block when pledge_id is passed
340 if (empty($this->_values['pledge_id']) && empty($this->_ccid)) {
341 $this->_separateMembershipPayment = FALSE;
342 if (in_array('CiviMember', $config->enableComponents)) {
343 $isTest = 0;
344 if ($this->_action & CRM_Core_Action::PREVIEW) {
345 $isTest = 1;
346 }
347
348 if ($this->_priceSetId &&
349 (CRM_Core_Component::getComponentID('CiviMember') == CRM_Utils_Array::value('extends', $this->_priceSet))
350 ) {
351 $this->_useForMember = 1;
352 $this->set('useForMember', $this->_useForMember);
353 }
354
355 $this->_separateMembershipPayment = $this->buildMembershipBlock(
356 $this->_membershipContactID,
357 TRUE, NULL, FALSE,
358 $isTest
359 );
360 }
361 $this->set('separateMembershipPayment', $this->_separateMembershipPayment);
362 }
363 $this->assign('useForMember', $this->_useForMember);
364 // If we configured price set for contribution page
365 // we are not allow membership signup as well as any
366 // other contribution amount field, CRM-5095
367 if (isset($this->_priceSetId) && $this->_priceSetId) {
368 $this->add('hidden', 'priceSetId', $this->_priceSetId);
369 // build price set form.
370 $this->set('priceSetId', $this->_priceSetId);
371 if (empty($this->_ccid)) {
372 CRM_Price_BAO_PriceSet::buildPriceSet($this);
373 }
374 if ($this->_values['is_monetary'] &&
375 $this->_values['is_recur'] && empty($this->_values['pledge_id'])
376 ) {
377 self::buildRecur($this);
378 }
379 }
380
381 if ($this->_priceSetId && empty($this->_ccid)) {
382 $is_quick_config = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $this->_priceSetId, 'is_quick_config');
383 if ($is_quick_config) {
384 $this->_useForMember = 0;
385 $this->set('useForMember', $this->_useForMember);
386 }
387 }
388
389 //we allow premium for pledge during pledge creation only.
390 if (empty($this->_values['pledge_id']) && empty($this->_ccid)) {
391 CRM_Contribute_BAO_Premium::buildPremiumBlock($this, $this->_id, TRUE);
392 }
393
394 //don't build pledge block when mid is passed
395 if (!$this->_mid && empty($this->_ccid)) {
396 $config = CRM_Core_Config::singleton();
397 if (in_array('CiviPledge', $config->enableComponents) && !empty($this->_values['pledge_block_id'])) {
398 CRM_Pledge_BAO_PledgeBlock::buildPledgeBlock($this);
399 }
400 }
401
402 //to create an cms user
403 if (!$this->_contactID && empty($this->_ccid)) {
404 $createCMSUser = FALSE;
405
406 if ($this->_values['custom_pre_id']) {
407 $profileID = $this->_values['custom_pre_id'];
408 $createCMSUser = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFGroup', $profileID, 'is_cms_user');
409 }
410
411 if (!$createCMSUser &&
412 $this->_values['custom_post_id']
413 ) {
414 if (!is_array($this->_values['custom_post_id'])) {
415 $profileIDs = [$this->_values['custom_post_id']];
416 }
417 else {
418 $profileIDs = $this->_values['custom_post_id'];
419 }
420 foreach ($profileIDs as $pid) {
421 if (CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFGroup', $pid, 'is_cms_user')) {
422 $profileID = $pid;
423 $createCMSUser = TRUE;
424 break;
425 }
426 }
427 }
428
429 if ($createCMSUser) {
430 CRM_Core_BAO_CMSUser::buildForm($this, $profileID, TRUE);
431 }
432 }
433 if ($this->_pcpId && empty($this->_ccid)) {
434 if (CRM_PCP_BAO_PCP::displayName($this->_pcpId)) {
435 $pcp_supporter_text = CRM_PCP_BAO_PCP::getPcpSupporterText($this->_pcpId, $this->_id, 'contribute');
436 $this->assign('pcpSupporterText', $pcp_supporter_text);
437 }
438 $prms = ['id' => $this->_pcpId];
439 CRM_Core_DAO::commonRetrieve('CRM_PCP_DAO_PCP', $prms, $pcpInfo);
440 if ($pcpInfo['is_honor_roll']) {
441 $this->assign('isHonor', TRUE);
442 $this->add('checkbox', 'pcp_display_in_roll', ts('Show my contribution in the public honor roll'), NULL, NULL,
443 ['onclick' => "showHideByValue('pcp_display_in_roll','','nameID|nickID|personalNoteID','block','radio',false); pcpAnonymous( );"]
444 );
445 $extraOption = ['onclick' => "return pcpAnonymous( );"];
446 $this->addRadio('pcp_is_anonymous', NULL, [ts('Include my name and message'), ts('List my contribution anonymously')], [], '&nbsp;&nbsp;&nbsp;', FALSE, [$extraOption, $extraOption]);
447
448 $this->add('text', 'pcp_roll_nickname', ts('Name'), ['maxlength' => 30]);
449 $this->addField('pcp_personal_note', ['entity' => 'ContributionSoft', 'context' => 'create', 'style' => 'height: 3em; width: 40em;']);
450 }
451 }
452 if (empty($this->_values['fee']) && empty($this->_ccid)) {
453 throw new CRM_Core_Exception(ts('This page does not have any price fields configured or you may not have permission for them. Please contact the site administrator for more details.'));
454 }
455
456 //we have to load confirm contribution button in template
457 //when multiple payment processor as the user
458 //can toggle with payment processor selection
459 $billingModePaymentProcessors = 0;
460 if (!empty($this->_paymentProcessors)) {
461 foreach ($this->_paymentProcessors as $key => $values) {
462 if ($values['billing_mode'] == CRM_Core_Payment::BILLING_MODE_BUTTON) {
463 $billingModePaymentProcessors++;
464 }
465 }
466 }
467
468 if ($billingModePaymentProcessors && count($this->_paymentProcessors) == $billingModePaymentProcessors) {
469 $allAreBillingModeProcessors = TRUE;
470 }
471 else {
472 $allAreBillingModeProcessors = FALSE;
473 }
474
475 if (!($allAreBillingModeProcessors && !$this->_values['is_pay_later'])) {
476 $submitButton = [
477 'type' => 'upload',
478 'name' => ts('Contribute'),
479 'spacing' => '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;',
480 'isDefault' => TRUE,
481 ];
482 if (!empty($this->_values['is_confirm_enabled'])) {
483 $submitButton['name'] = ts('Review your contribution');
484 $submitButton['icon'] = 'fa-chevron-right';
485 }
486 // Add submit-once behavior when confirm page disabled
487 if (empty($this->_values['is_confirm_enabled'])) {
488 $this->submitOnce = TRUE;
489 }
490 //change button name for updating contribution
491 if (!empty($this->_ccid)) {
492 $submitButton['name'] = ts('Confirm Payment');
493 }
494 $this->addButtons([$submitButton]);
495 }
496
497 $this->addFormRule(['CRM_Contribute_Form_Contribution_Main', 'formRule'], $this);
498 }
499
500 /**
501 * Build elements to collect information for recurring contributions.
502 *
503 *
504 * @param CRM_Core_Form $form
505 */
506 public static function buildRecur(&$form) {
507 $attributes = CRM_Core_DAO::getAttribute('CRM_Contribute_DAO_ContributionRecur');
508 $className = get_class($form);
509
510 $form->assign('is_recur_interval', CRM_Utils_Array::value('is_recur_interval', $form->_values));
511 $form->assign('is_recur_installments', CRM_Utils_Array::value('is_recur_installments', $form->_values));
512 $paymentObject = $form->getVar('_paymentObject');
513 if ($paymentObject) {
514 $form->assign('recurringHelpText', $paymentObject->getText('contributionPageRecurringHelp', [
515 'is_recur_installments' => !empty($form->_values['is_recur_installments']),
516 'is_email_receipt' => !empty($form->_values['is_email_receipt']),
517 ]));
518 }
519
520 $form->add('checkbox', 'is_recur', ts('I want to contribute this amount'), NULL);
521
522 if (!empty($form->_values['is_recur_interval']) || $className == 'CRM_Contribute_Form_Contribution') {
523 $form->add('text', 'frequency_interval', ts('Every'), $attributes['frequency_interval'] + ['aria-label' => ts('Every')]);
524 $form->addRule('frequency_interval', ts('Frequency must be a whole number (EXAMPLE: Every 3 months).'), 'integer');
525 }
526 else {
527 // make sure frequency_interval is submitted as 1 if given no choice to user.
528 $form->add('hidden', 'frequency_interval', 1);
529 }
530
531 $frUnits = $form->_values['recur_frequency_unit'] ?? NULL;
532 if (empty($frUnits) &&
533 $className == 'CRM_Contribute_Form_Contribution'
534 ) {
535 $frUnits = implode(CRM_Core_DAO::VALUE_SEPARATOR,
536 CRM_Core_OptionGroup::values('recur_frequency_units')
537 );
538 }
539
540 $unitVals = explode(CRM_Core_DAO::VALUE_SEPARATOR, $frUnits);
541
542 // CRM 10860, display text instead of a dropdown if there's only 1 frequency unit
543 if (count($unitVals) == 1) {
544 $form->assign('one_frequency_unit', TRUE);
545 $unit = $unitVals[0];
546 $form->add('hidden', 'frequency_unit', $unit);
547 if (!empty($form->_values['is_recur_interval']) || $className == 'CRM_Contribute_Form_Contribution') {
548 $unit .= "(s)";
549 }
550 $form->assign('frequency_unit', $unit);
551 }
552 else {
553 $form->assign('one_frequency_unit', FALSE);
554 $units = [];
555 $frequencyUnits = CRM_Core_OptionGroup::values('recur_frequency_units', FALSE, FALSE, TRUE);
556 foreach ($unitVals as $key => $val) {
557 if (array_key_exists($val, $frequencyUnits)) {
558 $units[$val] = $frequencyUnits[$val];
559 if (!empty($form->_values['is_recur_interval']) || $className == 'CRM_Contribute_Form_Contribution') {
560 $units[$val] = "{$frequencyUnits[$val]}(s)";
561 }
562 }
563 }
564 $frequencyUnit = &$form->addElement('select', 'frequency_unit', NULL, $units, ['aria-label' => ts('Frequency Unit')]);
565 }
566
567 // FIXME: Ideally we should freeze select box if there is only
568 // one option but looks there is some problem /w QF freeze.
569 //if ( count( $units ) == 1 ) {
570 //$frequencyUnit->freeze( );
571 //}
572
573 $form->add('text', 'installments', ts('installments'),
574 $attributes['installments']
575 );
576 $form->addRule('installments', ts('Number of installments must be a whole number.'), 'integer');
577 }
578
579 /**
580 * Global form rule.
581 *
582 * @param array $fields
583 * The input form values.
584 * @param array $files
585 * The uploaded files if any.
586 * @param \CRM_Contribute_Form_Contribution_Main $self
587 *
588 * @return bool|array
589 * true if no errors, else array of errors
590 */
591 public static function formRule($fields, $files, $self) {
592 $errors = [];
593 $amount = self::computeAmount($fields, $self->_values);
594 if (!empty($fields['auto_renew']) && empty($fields['payment_processor_id'])) {
595 $errors['auto_renew'] = ts('You cannot have auto-renewal on if you are paying later.');
596 }
597
598 if ((!empty($fields['selectMembership']) &&
599 $fields['selectMembership'] != 'no_thanks'
600 ) ||
601 (!empty($fields['priceSetId']) &&
602 $self->_useForMember
603 )
604 ) {
605 $isTest = $self->_action & CRM_Core_Action::PREVIEW;
606 $lifeMember = CRM_Member_BAO_Membership::getAllContactMembership($self->_membershipContactID, $isTest, TRUE);
607
608 $membershipOrgDetails = CRM_Member_BAO_MembershipType::getAllMembershipTypes();
609 $unallowedOrgs = [];
610 foreach (array_keys($lifeMember) as $memTypeId) {
611 $unallowedOrgs[] = $membershipOrgDetails[$memTypeId]['member_of_contact_id'];
612 }
613 }
614
615 //check for atleast one pricefields should be selected
616 if (!empty($fields['priceSetId']) && empty($self->_ccid)) {
617 $priceField = new CRM_Price_DAO_PriceField();
618 $priceField->price_set_id = $fields['priceSetId'];
619 $priceField->orderBy('weight');
620 $priceField->find();
621
622 $check = [];
623 $membershipIsActive = TRUE;
624 $previousId = $otherAmount = FALSE;
625 while ($priceField->fetch()) {
626
627 if ($self->isQuickConfig() && ($priceField->name == 'contribution_amount' || $priceField->name == 'membership_amount')) {
628 $previousId = $priceField->id;
629 if ($priceField->name == 'membership_amount' && !$priceField->is_active) {
630 $membershipIsActive = FALSE;
631 }
632 }
633 if ($priceField->name == 'other_amount') {
634 if ($self->_quickConfig && empty($fields["price_{$priceField->id}"]) &&
635 array_key_exists("price_{$previousId}", $fields) && isset($fields["price_{$previousId}"]) && $self->_values['fee'][$previousId]['name'] == 'contribution_amount' && empty($fields["price_{$previousId}"])
636 ) {
637 $otherAmount = $priceField->id;
638 }
639 elseif (!empty($fields["price_{$priceField->id}"])) {
640 $otherAmountVal = CRM_Utils_Rule::cleanMoney($fields["price_{$priceField->id}"]);
641 $min = $self->_values['min_amount'] ?? NULL;
642 $max = $self->_values['max_amount'] ?? NULL;
643 if ($min && $otherAmountVal < $min) {
644 $errors["price_{$priceField->id}"] = ts('Contribution amount must be at least %1',
645 [1 => $min]
646 );
647 }
648 if ($max && $otherAmountVal > $max) {
649 $errors["price_{$priceField->id}"] = ts('Contribution amount cannot be more than %1.',
650 [1 => $max]
651 );
652 }
653 }
654 }
655 if (!empty($fields["price_{$priceField->id}"]) || ($previousId == $priceField->id && isset($fields["price_{$previousId}"])
656 && empty($fields["price_{$previousId}"]))
657 ) {
658 $check[] = $priceField->id;
659 }
660 }
661
662 $currentMemberships = NULL;
663 if ($membershipIsActive) {
664 $is_test = $self->_mode != 'live' ? 1 : 0;
665 $memContactID = $self->_membershipContactID;
666
667 // For anonymous user check using dedupe rule
668 // if user has Cancelled Membership
669 if (!$memContactID) {
670 $memContactID = CRM_Contact_BAO_Contact::getFirstDuplicateContact($fields, 'Individual', 'Unsupervised', [], FALSE);
671 }
672 $currentMemberships = CRM_Member_BAO_Membership::getContactsCancelledMembership($memContactID,
673 $is_test
674 );
675
676 foreach ($self->_values['fee'] as $fieldKey => $fieldValue) {
677 if ($fieldValue['html_type'] != 'Text' && !empty($fields['price_' . $fieldKey])) {
678 if (!is_array($fields['price_' . $fieldKey]) && isset($fieldValue['options'][$fields['price_' . $fieldKey]])) {
679 if (array_key_exists('membership_type_id', $fieldValue['options'][$fields['price_' . $fieldKey]])
680 && in_array($fieldValue['options'][$fields['price_' . $fieldKey]]['membership_type_id'], $currentMemberships)
681 ) {
682 $errors['price_' . $fieldKey] = ts('Your %1 membership was previously cancelled and can not be renewed online. Please contact the site administrator for assistance.', [1 => CRM_Member_PseudoConstant::membershipType($fieldValue['options'][$fields['price_' . $fieldKey]]['membership_type_id'])]);
683 }
684 }
685 else {
686 if (is_array($fields['price_' . $fieldKey])) {
687 foreach (array_keys($fields['price_' . $fieldKey]) as $key) {
688 if (array_key_exists('membership_type_id', $fieldValue['options'][$key])
689 && in_array($fieldValue['options'][$key]['membership_type_id'], $currentMemberships)
690 ) {
691 $errors['price_' . $fieldKey] = ts('Your %1 membership was previously cancelled and can not be renewed online. Please contact the site administrator for assistance.', [1 => CRM_Member_PseudoConstant::membershipType($fieldValue['options'][$key]['membership_type_id'])]);
692 }
693 }
694 }
695 }
696 }
697 }
698 }
699
700 // CRM-12233
701 if ($membershipIsActive && empty($self->_membershipBlock['is_required'])
702 && $self->_values['amount_block_is_active']
703 ) {
704 $membershipFieldId = $contributionFieldId = $errorKey = $otherFieldId = NULL;
705 foreach ($self->_values['fee'] as $fieldKey => $fieldValue) {
706 // if 'No thank you' membership is selected then set $membershipFieldId
707 if ($fieldValue['name'] == 'membership_amount' && CRM_Utils_Array::value('price_' . $fieldKey, $fields) == 0) {
708 $membershipFieldId = $fieldKey;
709 }
710 elseif ($membershipFieldId) {
711 if ($fieldValue['name'] == 'other_amount') {
712 $otherFieldId = $fieldKey;
713 }
714 elseif ($fieldValue['name'] == 'contribution_amount') {
715 $contributionFieldId = $fieldKey;
716 }
717
718 if (!$errorKey || CRM_Utils_Array::value('price_' . $contributionFieldId, $fields) == '0') {
719 $errorKey = $fieldKey;
720 }
721 }
722 }
723 // $membershipFieldId is set and additional amount is 'No thank you' or NULL then throw error
724 if ($membershipFieldId && !(CRM_Utils_Array::value('price_' . $contributionFieldId, $fields, -1) > 0) && empty($fields['price_' . $otherFieldId])) {
725 $errors["price_{$errorKey}"] = ts('Additional Contribution is required.');
726 }
727 }
728 if (empty($check) && empty($self->_ccid)) {
729 if ($self->_useForMember == 1 && $membershipIsActive) {
730 $errors['_qf_default'] = ts('Select at least one option from Membership Type(s).');
731 }
732 else {
733 $errors['_qf_default'] = ts('Select at least one option from Contribution(s).');
734 }
735 }
736 if ($otherAmount && !empty($check)) {
737 $errors["price_{$otherAmount}"] = ts('Amount is required field.');
738 }
739
740 if ($self->_useForMember == 1 && !empty($check) && $membershipIsActive) {
741 $priceFieldIDS = [];
742 $priceFieldMemTypes = [];
743
744 foreach ($self->_priceSet['fields'] as $priceId => $value) {
745 if (!empty($fields['price_' . $priceId]) || ($self->_quickConfig && $value['name'] == 'membership_amount' && empty($self->_membershipBlock['is_required']))) {
746 if (!empty($fields['price_' . $priceId]) && is_array($fields['price_' . $priceId])) {
747 foreach ($fields['price_' . $priceId] as $priceFldVal => $isSet) {
748 if ($isSet) {
749 $priceFieldIDS[] = $priceFldVal;
750 }
751 }
752 }
753 elseif (!$value['is_enter_qty'] && !empty($fields['price_' . $priceId])) {
754 // The check for {!$value['is_enter_qty']} is done since, quantity fields allow entering
755 // quantity. And the quantity can't be conisdered as civicrm_price_field_value.id, CRM-9577
756 $priceFieldIDS[] = $fields['price_' . $priceId];
757 }
758
759 if (!empty($value['options'])) {
760 foreach ($value['options'] as $val) {
761 if (!empty($val['membership_type_id']) && (
762 ($fields['price_' . $priceId] == $val['id']) ||
763 (isset($fields['price_' . $priceId]) && !empty($fields['price_' . $priceId][$val['id']]))
764 )
765 ) {
766 $priceFieldMemTypes[] = $val['membership_type_id'];
767 }
768 }
769 }
770 }
771 }
772
773 if (!empty($lifeMember)) {
774 foreach ($priceFieldIDS as $priceFieldId) {
775 if (($id = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue', $priceFieldId, 'membership_type_id')) &&
776 in_array($membershipOrgDetails[$id]['member_of_contact_id'], $unallowedOrgs)
777 ) {
778 $errors['_qf_default'] = ts('You already have a lifetime membership and cannot select a membership with a shorter term.');
779 break;
780 }
781 }
782 }
783
784 if (!empty($priceFieldIDS)) {
785 $ids = implode(',', $priceFieldIDS);
786
787 $priceFieldIDS['id'] = $fields['priceSetId'];
788 $self->set('memberPriceFieldIDS', $priceFieldIDS);
789 $count = CRM_Price_BAO_PriceSet::getMembershipCount($ids);
790 foreach ($count as $id => $occurrence) {
791 if ($occurrence > 1) {
792 $errors['_qf_default'] = ts('You have selected multiple memberships for the same organization or entity. Please review your selections and choose only one membership per entity. Contact the site administrator if you need assistance.');
793 break;
794 }
795 }
796 }
797
798 if (empty($priceFieldMemTypes) && $self->_membershipBlock['is_required'] == 1) {
799 $errors['_qf_default'] = ts('Please select at least one membership option.');
800 }
801 }
802
803 CRM_Price_BAO_PriceSet::processAmount($self->_values['fee'],
804 $fields, $lineItem
805 );
806
807 $minAmt = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $fields['priceSetId'], 'min_amount');
808 if ($fields['amount'] < 0) {
809 $errors['_qf_default'] = ts('Contribution can not be less than zero. Please select the options accordingly');
810 }
811 elseif (!empty($minAmt) && $fields['amount'] < $minAmt) {
812 $errors['_qf_default'] = ts('A minimum amount of %1 should be selected from Contribution(s).', [
813 1 => CRM_Utils_Money::format($minAmt),
814 ]);
815 }
816
817 $amount = $fields['amount'];
818 }
819
820 if (isset($fields['selectProduct']) &&
821 $fields['selectProduct'] != 'no_thanks'
822 ) {
823 $productDAO = new CRM_Contribute_DAO_Product();
824 $productDAO->id = $fields['selectProduct'];
825 $productDAO->find(TRUE);
826 $min_amount = $productDAO->min_contribution;
827
828 if ($amount < $min_amount) {
829 $errors['selectProduct'] = ts('The premium you have selected requires a minimum contribution of %1', [1 => CRM_Utils_Money::format($min_amount)]);
830 CRM_Core_Session::setStatus($errors['selectProduct']);
831 }
832 }
833
834 //CRM-16285 - Function to handle validation errors on form, for recurring contribution field.
835 CRM_Contribute_BAO_ContributionRecur::validateRecurContribution($fields, $files, $self, $errors);
836
837 if (!empty($fields['is_recur']) && empty($fields['payment_processor_id'])) {
838 $errors['_qf_default'] = ts('You cannot set up a recurring contribution if you are not paying online by credit card.');
839 }
840
841 // validate PCP fields - if not anonymous, we need a nick name value
842 if ($self->_pcpId && !empty($fields['pcp_display_in_roll']) &&
843 empty($fields['pcp_is_anonymous']) &&
844 CRM_Utils_Array::value('pcp_roll_nickname', $fields) == ''
845 ) {
846 $errors['pcp_roll_nickname'] = ts('Please enter a name to include in the Honor Roll, or select \'contribute anonymously\'.');
847 }
848
849 // return if this is express mode
850 $config = CRM_Core_Config::singleton();
851 if ($self->_paymentProcessor &&
852 (int) $self->_paymentProcessor['billing_mode'] & CRM_Core_Payment::BILLING_MODE_BUTTON
853 ) {
854 if (!empty($fields[$self->_expressButtonName . '_x']) || !empty($fields[$self->_expressButtonName . '_y']) ||
855 !empty($fields[$self->_expressButtonName])
856 ) {
857 return $errors;
858 }
859 }
860
861 //validate the pledge fields.
862 if (!empty($self->_values['pledge_block_id'])) {
863 //validation for pledge payment.
864 if (!empty($self->_values['pledge_id'])) {
865 if (empty($fields['pledge_amount'])) {
866 $errors['pledge_amount'] = ts('At least one payment option needs to be checked.');
867 }
868 }
869 elseif (!empty($fields['is_pledge'])) {
870 if (CRM_Utils_Rule::positiveInteger(CRM_Utils_Array::value('pledge_installments', $fields)) == FALSE) {
871 $errors['pledge_installments'] = ts('Please enter a valid number of pledge installments.');
872 }
873 else {
874 if (!isset($fields['pledge_installments'])) {
875 $errors['pledge_installments'] = ts('Pledge Installments is required field.');
876 }
877 elseif (CRM_Utils_Array::value('pledge_installments', $fields) == 1) {
878 $errors['pledge_installments'] = ts('Pledges consist of multiple scheduled payments. Select one-time contribution if you want to make your gift in a single payment.');
879 }
880 elseif (empty($fields['pledge_installments'])) {
881 $errors['pledge_installments'] = ts('Pledge Installments field must be > 1.');
882 }
883 }
884
885 //validation for Pledge Frequency Interval.
886 if (CRM_Utils_Rule::positiveInteger(CRM_Utils_Array::value('pledge_frequency_interval', $fields)) == FALSE) {
887 $errors['pledge_frequency_interval'] = ts('Please enter a valid Pledge Frequency Interval.');
888 }
889 else {
890 if (!isset($fields['pledge_frequency_interval'])) {
891 $errors['pledge_frequency_interval'] = ts('Pledge Frequency Interval. is required field.');
892 }
893 elseif (empty($fields['pledge_frequency_interval'])) {
894 $errors['pledge_frequency_interval'] = ts('Pledge frequency interval field must be > 0');
895 }
896 }
897 }
898 }
899
900 // if the user has chosen a free membership or the amount is less than zero
901 // i.e. we don't need to validate payment related fields or profiles.
902 if ((float) $amount <= 0.0) {
903 return $errors;
904 }
905
906 if (!isset($fields['payment_processor_id'])) {
907 $errors['payment_processor_id'] = ts('Payment Method is a required field.');
908 }
909 else {
910 CRM_Core_Payment_Form::validatePaymentInstrument(
911 $fields['payment_processor_id'],
912 $fields,
913 $errors,
914 (!$self->_isBillingAddressRequiredForPayLater ? NULL : 'billing')
915 );
916 }
917
918 foreach (CRM_Contact_BAO_Contact::$_greetingTypes as $greeting) {
919 if ($greetingType = CRM_Utils_Array::value($greeting, $fields)) {
920 $customizedValue = CRM_Core_PseudoConstant::getKey('CRM_Contact_BAO_Contact', $greeting . '_id', 'Customized');
921 if ($customizedValue == $greetingType && empty($fielse[$greeting . '_custom'])) {
922 $errors[$greeting . '_custom'] = ts('Custom %1 is a required field if %1 is of type Customized.',
923 [1 => ucwords(str_replace('_', " ", $greeting))]
924 );
925 }
926 }
927 }
928
929 return empty($errors) ? TRUE : $errors;
930 }
931
932 /**
933 * Compute amount to be paid.
934 *
935 * @param array $params
936 * @param array $formValues
937 *
938 * @return int|mixed|null|string
939 */
940 public static function computeAmount($params, $formValues) {
941 $amount = 0;
942 // First clean up the other amount field if present.
943 if (isset($params['amount_other'])) {
944 $params['amount_other'] = CRM_Utils_Rule::cleanMoney($params['amount_other']);
945 }
946
947 if (CRM_Utils_Array::value('amount', $params) == 'amount_other_radio' || !empty($params['amount_other'])) {
948 $amount = $params['amount_other'];
949 }
950 elseif (!empty($params['pledge_amount'])) {
951 foreach ($params['pledge_amount'] as $paymentId => $dontCare) {
952 $amount += CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_PledgePayment', $paymentId, 'scheduled_amount');
953 }
954 }
955 else {
956 if (!empty($formValues['amount'])) {
957 $amountID = $params['amount'] ?? NULL;
958
959 if ($amountID) {
960 // @todo - stop setting amount level in this function & call the CRM_Price_BAO_PriceSet::getAmountLevel
961 // function to get correct amount level consistently. Remove setting of the amount level in
962 // CRM_Price_BAO_PriceSet::processAmount. Extend the unit tests in CRM_Price_BAO_PriceSetTest
963 // to cover all variants.
964 $params['amount_level'] = $formValues[$amountID]['label'] ?? NULL;
965 $amount = $formValues[$amountID]['value'] ?? NULL;
966 }
967 }
968 }
969 return $amount;
970 }
971
972 /**
973 * Process the form submission.
974 */
975 public function postProcess() {
976 // we first reset the confirm page so it accepts new values
977 $this->controller->resetPage('Confirm');
978
979 // get the submitted form values.
980 $params = $this->controller->exportValues($this->_name);
981 $this->submit($params);
982
983 if (empty($this->_values['is_confirm_enabled'])) {
984 $this->skipToThankYouPage();
985 }
986
987 }
988
989 /**
990 * Submit function.
991 *
992 * This is the guts of the postProcess made also accessible to the test suite.
993 *
994 * @param array $params
995 * Submitted values.
996 *
997 * @throws \CiviCRM_API3_Exception
998 */
999 public function submit($params) {
1000 //carry campaign from profile.
1001 if (array_key_exists('contribution_campaign_id', $params)) {
1002 $params['campaign_id'] = $params['contribution_campaign_id'];
1003 }
1004
1005 $params['currencyID'] = CRM_Core_Config::singleton()->defaultCurrency;
1006
1007 // @todo refactor this & leverage it from the unit tests.
1008 if (!empty($params['priceSetId'])) {
1009 $is_quick_config = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $this->_priceSetId, 'is_quick_config');
1010 if ($is_quick_config) {
1011 $priceField = new CRM_Price_DAO_PriceField();
1012 $priceField->price_set_id = $params['priceSetId'];
1013 $priceField->orderBy('weight');
1014 $priceField->find();
1015
1016 $priceOptions = [];
1017 while ($priceField->fetch()) {
1018 CRM_Price_BAO_PriceFieldValue::getValues($priceField->id, $priceOptions);
1019 if (($selectedPriceOptionID = CRM_Utils_Array::value("price_{$priceField->id}", $params)) != FALSE && $selectedPriceOptionID > 0) {
1020 switch ($priceField->name) {
1021 case 'membership_amount':
1022 $this->_params['selectMembership'] = $params['selectMembership'] = $priceOptions[$selectedPriceOptionID]['membership_type_id'] ?? NULL;
1023 $this->set('selectMembership', $params['selectMembership']);
1024
1025 case 'contribution_amount':
1026 $params['amount'] = $selectedPriceOptionID;
1027 if ($priceField->name == 'contribution_amount' ||
1028 ($priceField->name == 'membership_amount' &&
1029 CRM_Utils_Array::value('is_separate_payment', $this->_membershipBlock) == 0)
1030 ) {
1031 $this->_values['amount'] = $priceOptions[$selectedPriceOptionID]['amount'] ?? NULL;
1032 }
1033 $this->_values[$selectedPriceOptionID]['value'] = $priceOptions[$selectedPriceOptionID]['amount'] ?? NULL;
1034 $this->_values[$selectedPriceOptionID]['label'] = $priceOptions[$selectedPriceOptionID]['label'] ?? NULL;
1035 $this->_values[$selectedPriceOptionID]['amount_id'] = $priceOptions[$selectedPriceOptionID]['id'] ?? NULL;
1036 $this->_values[$selectedPriceOptionID]['weight'] = $priceOptions[$selectedPriceOptionID]['weight'] ?? NULL;
1037 break;
1038
1039 case 'other_amount':
1040 $params['amount_other'] = $selectedPriceOptionID;
1041 break;
1042 }
1043 }
1044 }
1045 }
1046 }
1047
1048 if (!empty($this->_ccid) && !empty($this->_pendingAmount)) {
1049 $params['amount'] = $this->_pendingAmount;
1050 }
1051 else {
1052 // from here on down, $params['amount'] holds a monetary value (or null) rather than an option ID
1053 $params['amount'] = self::computeAmount($params, $this->_values);
1054 }
1055
1056 $params['separate_amount'] = $params['amount'];
1057 // @todo - stepping through the code indicates that amount is always set before this point so it never matters.
1058 // Move more of the above into this function...
1059 $params['amount'] = $this->getMainContributionAmount($params);
1060 //If the membership & contribution is used in contribution page & not separate payment
1061 $memPresent = $membershipLabel = $fieldOption = $is_quick_config = NULL;
1062 $proceFieldAmount = 0;
1063 if (property_exists($this, '_separateMembershipPayment') && $this->_separateMembershipPayment == 0) {
1064 $is_quick_config = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $this->_priceSetId, 'is_quick_config');
1065 if ($is_quick_config) {
1066 foreach ($this->_priceSet['fields'] as $fieldKey => $fieldVal) {
1067 if ($fieldVal['name'] == 'membership_amount' && !empty($params['price_' . $fieldKey])) {
1068 $fieldId = $fieldVal['id'];
1069 $fieldOption = $params['price_' . $fieldId];
1070 $proceFieldAmount += $fieldVal['options'][$this->_submitValues['price_' . $fieldId]]['amount'];
1071 $memPresent = TRUE;
1072 }
1073 else {
1074 if (!empty($params['price_' . $fieldKey]) && $memPresent && ($fieldVal['name'] == 'other_amount' || $fieldVal['name'] == 'contribution_amount')) {
1075 $fieldId = $fieldVal['id'];
1076 if ($fieldVal['name'] == 'other_amount') {
1077 $proceFieldAmount += $this->_submitValues['price_' . $fieldId];
1078 }
1079 elseif ($fieldVal['name'] == 'contribution_amount' && $this->_submitValues['price_' . $fieldId] > 0) {
1080 $proceFieldAmount += $fieldVal['options'][$this->_submitValues['price_' . $fieldId]]['amount'];
1081 }
1082 unset($params['price_' . $fieldId]);
1083 break;
1084 }
1085 }
1086 }
1087 }
1088 }
1089
1090 if (!isset($params['amount_other'])) {
1091 $this->set('amount_level', CRM_Utils_Array::value('amount_level', $params));
1092 }
1093
1094 if (!empty($this->_ccid)) {
1095 $this->set('lineItem', $this->_lineItem);
1096 }
1097 elseif ($priceSetId = CRM_Utils_Array::value('priceSetId', $params)) {
1098 $lineItem = [];
1099 $is_quick_config = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $priceSetId, 'is_quick_config');
1100 if ($is_quick_config) {
1101 foreach ($this->_values['fee'] as $key => & $val) {
1102 if ($val['name'] == 'other_amount' && $val['html_type'] == 'Text' && !empty($params['price_' . $key])) {
1103 // Clean out any currency symbols.
1104 $params['price_' . $key] = CRM_Utils_Rule::cleanMoney($params['price_' . $key]);
1105 if ($params['price_' . $key] != 0) {
1106 foreach ($val['options'] as $optionKey => & $options) {
1107 $options['amount'] = $params['price_' . $key] ?? NULL;
1108 break;
1109 }
1110 }
1111 $params['price_' . $key] = 1;
1112 break;
1113 }
1114 }
1115 }
1116
1117 if ($this->_membershipBlock) {
1118 $this->processAmountAndGetAutoRenew($this->_values['fee'], $params, $lineItem[$priceSetId], $priceSetId);
1119 }
1120 else {
1121 CRM_Price_BAO_PriceSet::processAmount($this->_values['fee'], $params, $lineItem[$priceSetId], $priceSetId);
1122 }
1123
1124 if ($params['tax_amount']) {
1125 $this->set('tax_amount', $params['tax_amount']);
1126 }
1127
1128 if ($proceFieldAmount) {
1129 $lineItem[$params['priceSetId']][$fieldOption]['unit_price'] = $proceFieldAmount;
1130 $lineItem[$params['priceSetId']][$fieldOption]['line_total'] = $proceFieldAmount;
1131 if (isset($lineItem[$params['priceSetId']][$fieldOption]['tax_amount'])) {
1132 $proceFieldAmount += $lineItem[$params['priceSetId']][$fieldOption]['tax_amount'];
1133 }
1134 if (!$this->_membershipBlock['is_separate_payment']) {
1135 //require when separate membership not used
1136 $params['amount'] = $proceFieldAmount;
1137 }
1138 }
1139 $this->set('lineItem', $lineItem);
1140 }
1141
1142 if ($params['amount'] != 0 && (($this->_values['is_pay_later'] &&
1143 empty($this->_paymentProcessor) &&
1144 !array_key_exists('hidden_processor', $params)) ||
1145 empty($params['payment_processor_id']))
1146 ) {
1147 $params['is_pay_later'] = 1;
1148 }
1149 else {
1150 $params['is_pay_later'] = 0;
1151 }
1152
1153 // Would be nice to someday understand the point of this set.
1154 $this->set('is_pay_later', $params['is_pay_later']);
1155 // assign pay later stuff
1156 $this->_params['is_pay_later'] = CRM_Utils_Array::value('is_pay_later', $params, FALSE);
1157 $this->assign('is_pay_later', $params['is_pay_later']);
1158 if ($params['is_pay_later']) {
1159 $this->assign('pay_later_text', $this->_values['pay_later_text']);
1160 $this->assign('pay_later_receipt', CRM_Utils_Array::value('pay_later_receipt', $this->_values));
1161 }
1162
1163 if ($this->_membershipBlock['is_separate_payment'] && !empty($params['separate_amount'])) {
1164 $this->set('amount', $params['separate_amount']);
1165 }
1166 else {
1167 $this->set('amount', $params['amount']);
1168 }
1169
1170 // generate and set an invoiceID for this transaction
1171 $invoiceID = md5(uniqid(rand(), TRUE));
1172 $this->set('invoiceID', $invoiceID);
1173 $params['invoiceID'] = $invoiceID;
1174 $title = !empty($this->_values['frontend_title']) ? $this->_values['frontend_title'] : $this->_values['title'];
1175 $params['description'] = ts('Online Contribution') . ': ' . ((!empty($this->_pcpInfo['title']) ? $this->_pcpInfo['title'] : $title));
1176 $params['button'] = $this->controller->getButtonName();
1177 // required only if is_monetary and valid positive amount
1178 if ($this->_values['is_monetary'] &&
1179 !empty($this->_paymentProcessor) &&
1180 ((float) $params['amount'] > 0.0 || $this->hasSeparateMembershipPaymentAmount($params))
1181 ) {
1182 // The concept of contributeMode is deprecated - as should be the 'is_monetary' setting.
1183 $this->setContributeMode();
1184 // Really this setting of $this->_params & params within it should be done earlier on in the function
1185 // probably the values determined here should be reused in confirm postProcess as there is no opportunity to alter anything
1186 // on the confirm page. However as we are dealing with a stable release we go as close to where it is used
1187 // as possible.
1188 // In general the form has a lack of clarity of the logic of why things are set on the form in some cases &
1189 // the logic around when $this->_params is used compared to other params arrays.
1190 $this->_params = array_merge($params, $this->_params);
1191 $this->setRecurringMembershipParams();
1192 if ($this->_paymentProcessor &&
1193 $this->_paymentProcessor['object']->supports('preApproval')
1194 ) {
1195 $this->handlePreApproval($this->_params);
1196 }
1197 }
1198 }
1199
1200 /**
1201 * Assign the billing mode to the template.
1202 *
1203 * This is required for legacy support for contributeMode in templates.
1204 *
1205 * The goal is to remove this parameter & use more relevant parameters.
1206 */
1207 protected function setContributeMode() {
1208 switch ($this->_paymentProcessor['billing_mode']) {
1209 case CRM_Core_Payment::BILLING_MODE_FORM:
1210 $this->set('contributeMode', 'direct');
1211 break;
1212
1213 case CRM_Core_Payment::BILLING_MODE_BUTTON:
1214 $this->set('contributeMode', 'express');
1215 break;
1216
1217 case CRM_Core_Payment::BILLING_MODE_NOTIFY:
1218 $this->set('contributeMode', 'notify');
1219 break;
1220 }
1221
1222 }
1223
1224 /**
1225 * Process confirm function and pass browser to the thank you page.
1226 */
1227 protected function skipToThankYouPage() {
1228 // call the post process hook for the main page before we switch to confirm
1229 $this->postProcessHook();
1230
1231 // build the confirm page
1232 $confirmForm = &$this->controller->_pages['Confirm'];
1233 $confirmForm->preProcess();
1234 $confirmForm->buildQuickForm();
1235
1236 // the confirmation page is valid
1237 $data = &$this->controller->container();
1238 $data['valid']['Confirm'] = 1;
1239
1240 // confirm the contribution
1241 // mainProcess calls the hook also
1242 $confirmForm->mainProcess();
1243 $qfKey = $this->controller->_key;
1244
1245 // redirect to thank you page
1246 CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contribute/transact', "_qf_ThankYou_display=1&qfKey=$qfKey", TRUE, NULL, FALSE));
1247 }
1248
1249 /**
1250 * Set form variables if contribution ID is found
1251 */
1252 public function assignFormVariablesByContributionID() {
1253 if (empty($this->_ccid)) {
1254 return;
1255 }
1256 if (!$this->getContactID()) {
1257 CRM_Core_Error::statusBounce(ts("Returning since there is no contact attached to this contribution id."));
1258 }
1259
1260 $paymentBalance = CRM_Contribute_BAO_Contribution::getContributionBalance($this->_ccid);
1261 //bounce if the contribution is not pending.
1262 if ((int) $paymentBalance <= 0) {
1263 CRM_Core_Error::statusBounce(ts("Returning since contribution has already been handled."));
1264 }
1265 if (!empty($paymentBalance)) {
1266 $this->_pendingAmount = $paymentBalance;
1267 $this->assign('pendingAmount', $this->_pendingAmount);
1268 }
1269
1270 if ($taxAmount = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $this->_ccid, 'tax_amount')) {
1271 $this->set('tax_amount', $taxAmount);
1272 $this->assign('taxAmount', $taxAmount);
1273 }
1274
1275 $lineItems = CRM_Price_BAO_LineItem::getLineItemsByContributionID($this->_ccid);
1276 foreach (array_keys($lineItems) as $id) {
1277 $lineItems[$id]['id'] = $id;
1278 }
1279 $itemId = key($lineItems);
1280 if ($itemId && !empty($lineItems[$itemId]['price_field_id'])) {
1281 $this->_priceSetId = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceField', $lineItems[$itemId]['price_field_id'], 'price_set_id');
1282 }
1283
1284 if (!empty($lineItems[$itemId]['price_field_id'])) {
1285 $this->_lineItem[$this->_priceSetId] = $lineItems;
1286 }
1287 $isQuickConfig = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $this->_priceSetId, 'is_quick_config');
1288 $this->assign('lineItem', $this->_lineItem);
1289 $this->assign('is_quick_config', $isQuickConfig);
1290 $this->assign('priceSetID', $this->_priceSetId);
1291 }
1292
1293 /**
1294 * Function for unit tests on the postProcess function.
1295 *
1296 * @param array $params
1297 *
1298 * @throws \CiviCRM_API3_Exception
1299 */
1300 public function testSubmit($params) {
1301 $_SERVER['REQUEST_METHOD'] = 'GET';
1302 $this->controller = new CRM_Contribute_Controller_Contribution();
1303 $this->submit($params);
1304 }
1305
1306 /**
1307 * Has a separate membership payment amount been configured.
1308 *
1309 * @param array $params
1310 *
1311 * @return mixed
1312 * @throws \CiviCRM_API3_Exception
1313 */
1314 protected function hasSeparateMembershipPaymentAmount($params) {
1315 return $this->_separateMembershipPayment && (int) CRM_Member_BAO_MembershipType::getMembershipType($params['selectMembership'])['minimum_fee'];
1316 }
1317
1318 }