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