Merge pull request #21513 from JKingsnorth/core-2846-1-improve-start-end-date-validation
[civicrm-core.git] / CRM / Contribute / Form / Contribution / ThankYou.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
6a488035 5 | |
bc77d7c0
TO
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 |
6a488035 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
6a488035
TO
11
12/**
13 *
14 * @package CRM
ca5cec67 15 * @copyright CiviCRM LLC https://civicrm.org/licensing
6a488035
TO
16 */
17
18/**
95cdcc0f 19 * Form for thank-you / success page - 3rd step of online contribution process.
6a488035
TO
20 */
21class CRM_Contribute_Form_Contribution_ThankYou extends CRM_Contribute_Form_ContributionBase {
22
23 /**
fe482240 24 * Membership price set status.
1330f57a 25 * @var bool
6a488035
TO
26 */
27 public $_useForMember;
28
507a7734
SL
29 /**
30 * Tranxaaction Id of the current contribution
1330f57a 31 * @var string
507a7734
SL
32 */
33 public $_trxnId;
34
6a488035 35 /**
fe482240 36 * Set variables up before form is built.
6a488035
TO
37 */
38 public function preProcess() {
39 parent::preProcess();
40
353ffa53 41 $this->_params = $this->get('params');
6a488035 42 $this->_lineItem = $this->get('lineItem');
376b971e 43 $this->_useForMember = $this->get('useForMember');
353ffa53 44 $is_deductible = $this->get('is_deductible');
6a488035
TO
45 $this->assign('is_deductible', $is_deductible);
46 $this->assign('thankyou_title', CRM_Utils_Array::value('thankyou_title', $this->_values));
47 $this->assign('thankyou_text', CRM_Utils_Array::value('thankyou_text', $this->_values));
48 $this->assign('thankyou_footer', CRM_Utils_Array::value('thankyou_footer', $this->_values));
49 $this->assign('max_reminders', CRM_Utils_Array::value('max_reminders', $this->_values));
50 $this->assign('initial_reminder_day', CRM_Utils_Array::value('initial_reminder_day', $this->_values));
7e2e2551 51 $this->setTitle(CRM_Utils_Array::value('thankyou_title', $this->_values));
f4aaa82a 52 // Make the contributionPageID available to the template
6a488035
TO
53 $this->assign('contributionPageID', $this->_id);
54 $this->assign('isShare', $this->_values['is_share']);
55
56 $this->_params['is_pay_later'] = $this->get('is_pay_later');
57 $this->assign('is_pay_later', $this->_params['is_pay_later']);
58 if ($this->_params['is_pay_later']) {
59 $this->assign('pay_later_receipt', $this->_values['pay_later_receipt']);
60 }
de5ce535 61 $this->assign('is_for_organization', CRM_Utils_Array::value('is_for_organization', $this->_params));
6a488035
TO
62 }
63
64 /**
100fef9d 65 * Overwrite action, since we are only showing elements in frozen mode
6a488035
TO
66 * no help display needed
67 *
68 * @return int
6a488035 69 */
00be9182 70 public function getAction() {
6a488035
TO
71 if ($this->_action & CRM_Core_Action::PREVIEW) {
72 return CRM_Core_Action::VIEW | CRM_Core_Action::PREVIEW;
73 }
74 else {
75 return CRM_Core_Action::VIEW;
76 }
77 }
78
79 /**
fe482240 80 * Build the form object.
6a488035
TO
81 */
82 public function buildQuickForm() {
8684f612 83 // FIXME: Some of this code is identical to Confirm.php and should be broken out into a shared function
6a488035 84 $this->assignToTemplate();
2f44fc96 85 $this->_ccid = $this->get('ccid');
353ffa53
TO
86 $productID = $this->get('productID');
87 $option = $this->get('option');
6a488035
TO
88 $membershipTypeID = $this->get('membershipTypeID');
89 $this->assign('receiptFromEmail', CRM_Utils_Array::value('receipt_from_email', $this->_values));
90
91 if ($productID) {
92 CRM_Contribute_BAO_Premium::buildPremiumBlock($this, $this->_id, FALSE, $productID, $option);
93 }
6a488035 94
e77a5a36 95 $params = $this->_params;
ec5e7bcf 96 $invoicing = CRM_Invoicing_Utils::isInvoicingEnabled();
147b56dc
MW
97 // Make a copy of line items array to use for display only
98 $tplLineItems = $this->_lineItem;
03b412ae
PB
99 if ($invoicing) {
100 $getTaxDetails = FALSE;
147b56dc
MW
101 foreach ($this->_lineItem as $key => $value) {
102 foreach ($value as $k => $v) {
03b412ae
PB
103 if (isset($v['tax_rate'])) {
104 if ($v['tax_rate'] != '') {
105 $getTaxDetails = TRUE;
147b56dc
MW
106 // Cast to float to display without trailing zero decimals
107 $tplLineItems[$key][$k]['tax_rate'] = (float) $v['tax_rate'];
03b412ae
PB
108 }
109 }
110 }
111 }
112 $this->assign('getTaxDetails', $getTaxDetails);
ec5e7bcf 113 $this->assign('taxTerm', CRM_Invoicing_Utils::getTaxTerm());
03b412ae
PB
114 $this->assign('totalTaxAmount', $params['tax_amount']);
115 }
147b56dc
MW
116
117 if ($this->_priceSetId && !CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $this->_priceSetId, 'is_quick_config')) {
118 $this->assign('lineItem', $tplLineItems);
119 }
120 else {
121 if (is_array($membershipTypeID)) {
122 $membershipTypeID = current($membershipTypeID);
123 }
124 $this->assign('is_quick_config', 1);
125 $this->_params['is_quick_config'] = 1;
126 }
127 $this->assign('priceSetID', $this->_priceSetId);
128 $this->assign('useForMember', $this->get('useForMember'));
129
4779abb3 130 if (!empty($this->_values['honoree_profile_id']) && !empty($params['soft_credit_type_id'])) {
133e2c99 131 $softCreditTypes = CRM_Core_OptionGroup::values("soft_credit_type", FALSE);
132
133e2c99 133 $this->assign('soft_credit_type', $softCreditTypes[$params['soft_credit_type_id']]);
4779abb3 134 CRM_Contribute_BAO_ContributionSoft::formatHonoreeProfileFields($this, $params['honor']);
6a488035 135
be2fb01f 136 $fieldTypes = ['Contact'];
4779abb3 137 $fieldTypes[] = CRM_Core_BAO_UFGroup::getContactType($this->_values['honoree_profile_id']);
138 $this->buildCustom($this->_values['honoree_profile_id'], 'honoreeProfileFields', TRUE, 'honor', $fieldTypes);
6a488035
TO
139 }
140
141 $qParams = "reset=1&amp;id={$this->_id}";
142 //pcp elements
143 if ($this->_pcpId) {
144 $qParams .= "&amp;pcpId={$this->_pcpId}";
145 $this->assign('pcpBlock', TRUE);
be2fb01f 146 foreach ([
1330f57a
SL
147 'pcp_display_in_roll',
148 'pcp_is_anonymous',
149 'pcp_roll_nickname',
150 'pcp_personal_note',
151 ] as $val) {
a7488080 152 if (!empty($this->_params[$val])) {
6a488035
TO
153 $this->assign($val, $this->_params[$val]);
154 }
155 }
156 }
157
481a74f4 158 $this->assign('qParams', $qParams);
6a488035
TO
159
160 if ($membershipTypeID) {
353ffa53 161 $transactionID = $this->get('membership_trx_id');
6a488035 162 $membershipAmount = $this->get('membership_amount');
353ffa53 163 $renewalMode = $this->get('renewal_mode');
6a488035
TO
164 $this->assign('membership_trx_id', $transactionID);
165 $this->assign('membership_amount', $membershipAmount);
166 $this->assign('renewal_mode', $renewalMode);
167
42e3a033 168 $this->buildMembershipBlock(
5b757295 169 $this->_membershipContactID,
6a488035 170 $membershipTypeID,
5b757295 171 NULL
6a488035 172 );
9cc96227 173
174 if (!empty($params['auto_renew'])) {
175 $this->assign('auto_renew', TRUE);
176 }
6a488035
TO
177 }
178
179 $this->_separateMembershipPayment = $this->get('separateMembershipPayment');
180 $this->assign("is_separate_payment", $this->_separateMembershipPayment);
181
2f44fc96 182 if (empty($this->_ccid)) {
183 $this->buildCustom($this->_values['custom_pre_id'], 'customPre', TRUE);
184 $this->buildCustom($this->_values['custom_post_id'], 'customPost', TRUE);
185 }
2454bb63 186 if (!empty($this->_values['onbehalf_profile_id']) &&
187 !empty($params['onbehalf']) &&
188 ($this->_values['is_for_organization'] == 2 ||
189 !empty($params['is_for_organization'])
2f44fc96 190 ) && empty($this->_ccid)
2454bb63 191 ) {
be2fb01f 192 $fieldTypes = ['Contact', 'Organization'];
6a488035 193 $contactSubType = CRM_Contact_BAO_ContactType::subTypes('Organization');
353ffa53 194 $fieldTypes = array_merge($fieldTypes, $contactSubType);
6a488035 195 if (is_array($this->_membershipBlock) && !empty($this->_membershipBlock)) {
be2fb01f 196 $fieldTypes = array_merge($fieldTypes, ['Membership']);
6a488035
TO
197 }
198 else {
be2fb01f 199 $fieldTypes = array_merge($fieldTypes, ['Contribution']);
6a488035
TO
200 }
201
4779abb3 202 $this->buildCustom($this->_values['onbehalf_profile_id'], 'onbehalfProfile', TRUE, 'onbehalf', $fieldTypes);
6a488035
TO
203 }
204
9c1bc317 205 $this->_trxnId = $this->_params['trxn_id'] ?? NULL;
507a7734
SL
206
207 $this->assign('trxn_id', $this->_trxnId);
208
6a488035
TO
209 $this->assign('receive_date',
210 CRM_Utils_Date::mysqlToIso(CRM_Utils_Array::value('receive_date', $this->_params))
211 );
212
be2fb01f
CW
213 $defaults = [];
214 $fields = [];
6a488035 215 foreach ($this->_fields as $name => $dontCare) {
133e2c99 216 if ($name != 'onbehalf' || $name != 'honor') {
6a488035
TO
217 $fields[$name] = 1;
218 }
219 }
220 $fields['state_province'] = $fields['country'] = $fields['email'] = 1;
221 $contact = $this->_params = $this->controller->exportValues('Main');
222
223 foreach ($fields as $name => $dontCare) {
133e2c99 224 if (isset($contact[$name])) {
6a488035
TO
225 $defaults[$name] = $contact[$name];
226 if (substr($name, 0, 7) == 'custom_') {
227 $timeField = "{$name}_time";
228 if (isset($contact[$timeField])) {
229 $defaults[$timeField] = $contact[$timeField];
230 }
231 }
be2fb01f 232 elseif (in_array($name, [
1330f57a
SL
233 'addressee',
234 'email_greeting',
235 'postal_greeting',
236 ]) && !empty($contact[$name . '_custom'])
353ffa53 237 ) {
6a488035
TO
238 $defaults[$name . '_custom'] = $contact[$name . '_custom'];
239 }
240 }
241 }
242
6a488035 243 $this->_submitValues = array_merge($this->_submitValues, $defaults);
6cc679d2 244
6a488035 245 $this->setDefaults($defaults);
eea16664 246
6a488035
TO
247 $values['entity_id'] = $this->_id;
248 $values['entity_table'] = 'civicrm_contribution_page';
249
250 CRM_Friend_BAO_Friend::retrieve($values, $data);
251 $tellAFriend = FALSE;
252 if ($this->_pcpId) {
253 if ($this->_pcpBlock['is_tellfriend_enabled']) {
254 $this->assign('friendText', ts('Tell a Friend'));
255 $subUrl = "eid={$this->_pcpId}&blockId={$this->_pcpBlock['id']}&pcomponent=pcp";
256 $tellAFriend = TRUE;
257 }
258 }
a7488080 259 elseif (!empty($data['is_active'])) {
6a488035
TO
260 $friendText = $data['title'];
261 $this->assign('friendText', $friendText);
262 $subUrl = "eid={$this->_id}&pcomponent=contribute";
263 $tellAFriend = TRUE;
264 }
265
266 if ($tellAFriend) {
267 if ($this->_action & CRM_Core_Action::PREVIEW) {
268 $url = CRM_Utils_System::url("civicrm/friend",
269 "reset=1&action=preview&{$subUrl}"
270 );
271 }
272 else {
273 $url = CRM_Utils_System::url("civicrm/friend",
274 "reset=1&{$subUrl}"
275 );
276 }
277 $this->assign('friendURL', $url);
278 }
279
b2bfcb28 280 $this->assign('isPendingOutcome', $this->isPendingOutcome($params));
281 $this->freeze();
282
283 // can we blow away the session now to prevent hackery
284 // CRM-9491
285 $this->controller->reset();
286 }
287
4b238552
MW
288 /**
289 * Build Membership Block in Contribution Pages.
290 * @todo this was shared on CRM_Contribute_Form_ContributionBase but we are refactoring and simplifying for each
291 * step (main/confirm/thankyou)
292 *
293 * @param int $cid
294 * Contact checked for having a current membership for a particular membership.
4b238552
MW
295 * @param int|array $selectedMembershipTypeID
296 * Selected membership id.
4b238552
MW
297 * @param null $isTest
298 *
299 * @return bool
300 * Is this a separate membership payment
301 *
302 * @throws \CiviCRM_API3_Exception
303 * @throws \CRM_Core_Exception
304 */
e622655c 305 private function buildMembershipBlock($cid, $selectedMembershipTypeID = NULL, $isTest = NULL) {
4b238552
MW
306 $separateMembershipPayment = FALSE;
307 if ($this->_membershipBlock) {
308 $this->_currentMemberships = [];
309
310 $membershipTypeIds = $membershipTypes = $radio = $radioOptAttrs = [];
311 $membershipPriceset = (!empty($this->_priceSetId) && $this->_useForMember);
312
4b238552
MW
313 $autoRenewMembershipTypeOptions = [];
314
315 $separateMembershipPayment = $this->_membershipBlock['is_separate_payment'] ?? NULL;
316
317 if ($membershipPriceset) {
318 foreach ($this->_priceSet['fields'] as $pField) {
319 if (empty($pField['options'])) {
320 continue;
321 }
322 foreach ($pField['options'] as $opId => $opValues) {
323 if (empty($opValues['membership_type_id'])) {
324 continue;
325 }
326 $membershipTypeIds[$opValues['membership_type_id']] = $opValues['membership_type_id'];
327 }
328 }
329 }
330 elseif (!empty($this->_membershipBlock['membership_types'])) {
331 $membershipTypeIds = explode(',', $this->_membershipBlock['membership_types']);
332 }
333
334 if (!empty($membershipTypeIds)) {
335 //set status message if wrong membershipType is included in membershipBlock
336 if (isset($this->_mid) && !$membershipPriceset) {
337 $membershipTypeID = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_Membership',
338 $this->_mid,
339 'membership_type_id'
340 );
341 if (!in_array($membershipTypeID, $membershipTypeIds)) {
342 CRM_Core_Session::setStatus(ts("Oops. The membership you're trying to renew appears to be invalid. Contact your site administrator if you need assistance. If you continue, you will be issued a new membership."), ts('Invalid Membership'), 'error');
343 }
344 }
345
346 $membershipTypeValues = CRM_Member_BAO_Membership::buildMembershipTypeValues($this, $membershipTypeIds);
347 $this->_membershipTypeValues = $membershipTypeValues;
348 $endDate = NULL;
349
350 // Check if we support auto-renew on this contribution page
351 // FIXME: If any of the payment processors do NOT support recurring you cannot setup an
352 // auto-renew payment even if that processor is not selected.
353 $allowAutoRenewOpt = TRUE;
354 if (is_array($this->_paymentProcessors)) {
355 foreach ($this->_paymentProcessors as $id => $val) {
356 if ($id && !$val['is_recur']) {
357 $allowAutoRenewOpt = FALSE;
358 }
359 }
360 }
361 foreach ($membershipTypeIds as $value) {
362 $memType = $membershipTypeValues[$value];
363 if ($selectedMembershipTypeID != NULL) {
364 if ($memType['id'] == $selectedMembershipTypeID) {
365 $this->assign('minimum_fee', $memType['minimum_fee'] ?? NULL);
366 $this->assign('membership_name', $memType['name']);
4b238552
MW
367 $membershipTypes[] = $memType;
368 }
369 }
370 elseif ($memType['is_active']) {
371
372 if ($allowAutoRenewOpt) {
373 $javascriptMethod = ['onclick' => "return showHideAutoRenew( this.value );"];
374 $isAvailableAutoRenew = $this->_membershipBlock['auto_renew'][$value] ?? 1;
375 $autoRenewMembershipTypeOptions["autoRenewMembershipType_{$value}"] = (int) $memType['auto_renew'] * $isAvailableAutoRenew;
376 $allowAutoRenewMembership = TRUE;
377 }
378 else {
379 $javascriptMethod = NULL;
380 $autoRenewMembershipTypeOptions["autoRenewMembershipType_{$value}"] = 0;
381 }
382
383 //add membership type.
384 $radio[$memType['id']] = NULL;
385 $radioOptAttrs[$memType['id']] = $javascriptMethod;
386 if ($cid) {
387 $membership = new CRM_Member_DAO_Membership();
388 $membership->contact_id = $cid;
389 $membership->membership_type_id = $memType['id'];
390
391 //show current membership, skip pending and cancelled membership records,
392 //because we take first membership record id for renewal
393 $membership->whereAdd('status_id != 5 AND status_id !=6');
394
395 if (!is_null($isTest)) {
396 $membership->is_test = $isTest;
397 }
398
399 //CRM-4297
400 $membership->orderBy('end_date DESC');
401
402 if ($membership->find(TRUE)) {
403 if (!$membership->end_date) {
404 unset($radio[$memType['id']]);
405 unset($radioOptAttrs[$memType['id']]);
406 $this->assign('islifetime', TRUE);
407 continue;
408 }
409 $this->assign('renewal_mode', TRUE);
410 $this->_currentMemberships[$membership->membership_type_id] = $membership->membership_type_id;
411 $memType['current_membership'] = $membership->end_date;
412 if (!$endDate) {
413 $endDate = $memType['current_membership'];
414 $this->_defaultMemTypeId = $memType['id'];
415 }
416 if ($memType['current_membership'] < $endDate) {
417 $endDate = $memType['current_membership'];
418 $this->_defaultMemTypeId = $memType['id'];
419 }
420 }
421 }
422 $membershipTypes[] = $memType;
423 }
424 }
425 }
426
427 $this->assign('membershipBlock', $this->_membershipBlock);
e622655c 428 $this->assign('showRadio', FALSE);
4b238552 429 $this->assign('membershipTypes', $membershipTypes);
4b238552
MW
430 $this->assign('autoRenewMembershipTypeOptions', json_encode($autoRenewMembershipTypeOptions));
431 //give preference to user submitted auto_renew value.
432 $takeUserSubmittedAutoRenew = (!empty($_POST) || $this->isSubmitted());
433 $this->assign('takeUserSubmittedAutoRenew', $takeUserSubmittedAutoRenew);
434
435 // Assign autorenew option (0:hide,1:optional,2:required) so we can use it in confirmation etc.
436 $autoRenewOption = CRM_Price_BAO_PriceSet::checkAutoRenewForPriceSet($this->_priceSetId);
437 //$selectedMembershipTypeID is retrieved as an array for membership priceset if multiple
438 //options for different organisation is selected on the contribution page.
439 if (is_numeric($selectedMembershipTypeID) && isset($membershipTypeValues[$selectedMembershipTypeID]['auto_renew'])) {
440 $this->assign('autoRenewOption', $membershipTypeValues[$selectedMembershipTypeID]['auto_renew']);
441 }
442 else {
443 $this->assign('autoRenewOption', $autoRenewOption);
444 }
4b238552
MW
445 }
446
447 return $separateMembershipPayment;
448 }
449
b2bfcb28 450 /**
451 * Is the outcome of this contribution still pending.
452 *
453 * @param array $params
454 *
455 * @return bool
456 */
457 protected function isPendingOutcome(array $params): bool {
458 if (empty($params['payment_processor_id'])) {
459 return FALSE;
460 }
7a666802 461 try {
462 // A payment notification update could have come in at any time. Check at the last minute.
b2bfcb28 463 civicrm_api3('Contribution', 'getvalue', [
6b409353 464 'id' => $params['contributionID'] ?? NULL,
b2bfcb28 465 'contribution_status_id' => 'Pending',
466 'is_test' => '',
467 'return' => 'id',
6b409353 468 'invoice_id' => $params['invoiceID'] ?? NULL,
be2fb01f 469 ]);
b2bfcb28 470 return TRUE;
7a666802 471 }
472 catch (CiviCRM_API3_Exception $e) {
b2bfcb28 473 return FALSE;
7a666802 474 }
6a488035 475 }
96025800 476
6a488035 477}