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 +--------------------------------------------------------------------+
14 * @copyright CiviCRM LLC https://civicrm.org/licensing
18 * This class generates form components for processing Event.
20 class CRM_Event_Form_Registration_Confirm
extends CRM_Event_Form_Registration
{
23 * The values for the contribution db object.
36 public $submitOnce = TRUE;
39 * Monetary fields that may be submitted.
41 * These should get a standardised format in the beginPostProcess function.
43 * These fields are common to many forms. Some may override this.
46 protected $submittableMoneyFields = ['total_amount', 'net_amount', 'non_deductible_amount', 'fee_amount', 'tax_amount', 'amount'];
49 * Set variables up before form is built.
51 public function preProcess() {
54 // lineItem isn't set until Register postProcess
55 $this->_lineItem
= $this->get('lineItem');
57 $this->_params
= $this->get('params');
58 $this->_params
[0]['tax_amount'] = $this->get('tax_amount');
60 $this->_params
[0]['is_pay_later'] = $this->get('is_pay_later');
61 $this->assign('is_pay_later', $this->_params
[0]['is_pay_later']);
62 if ($this->_params
[0]['is_pay_later']) {
63 $this->assign('pay_later_receipt', $this->_values
['event']['pay_later_receipt']);
66 CRM_Utils_Hook
::eventDiscount($this, $this->_params
);
68 if (!empty($this->_params
[0]['discount']) && !empty($this->_params
[0]['discount']['applied'])) {
69 $this->set('hookDiscount', $this->_params
[0]['discount']);
70 $this->assign('hookDiscount', $this->_params
[0]['discount']);
73 $this->preProcessExpress();
75 if ($this->_values
['event']['is_monetary']) {
76 $this->_params
[0]['invoiceID'] = $this->get('invoiceID');
78 $this->assign('defaultRole', FALSE);
79 if (CRM_Utils_Array
::value('defaultRole', $this->_params
[0]) == 1) {
80 $this->assign('defaultRole', TRUE);
83 if (empty($this->_params
[0]['participant_role_id']) &&
84 $this->_values
['event']['default_role_id']
86 $this->_params
[0]['participant_role_id'] = $this->_values
['event']['default_role_id'];
89 if (isset($this->_values
['event']['confirm_title'])) {
90 CRM_Utils_System
::setTitle($this->_values
['event']['confirm_title']);
93 // Personal campaign page
95 $params = CRM_Contribute_Form_Contribution_Confirm
::processPcp($this, $this->_params
[0]);
96 $this->_params
[0] = $params;
99 $this->set('params', $this->_params
);
103 * Pre process function for Paypal Express confirm.
104 * @todo this is just a step in refactor as payment processor specific code does not belong in generic forms
107 * @throws \CRM_Core_Exception
109 private function preProcessExpress() {
110 if ($this->_contributeMode
!== 'express') {
114 // rfp == redirect from paypal
115 // @fixme rfp is probably not required - the getPreApprovalDetails should deal with any payment-processor specific 'stuff'
116 $rfp = CRM_Utils_Request
::retrieve('rfp', 'Boolean', CRM_Core_DAO
::$_nullObject, FALSE, NULL, 'GET');
118 //we lost rfp in case of additional participant. So set it explicitly.
119 if ($rfp || CRM_Utils_Array
::value('additional_participants', $this->_params
[0], FALSE)) {
120 if (!empty($this->_paymentProcessor
) && $this->_paymentProcessor
['object']->supports('preApproval')) {
121 $preApprovalParams = $this->_paymentProcessor
['object']->getPreApprovalDetails($this->get('pre_approval_parameters'));
122 $params = array_merge($this->_params
, $preApprovalParams);
124 CRM_Core_Payment_Form
::mapParams($this->_bltID
, $params, $params, FALSE);
126 // set a few other parameters that are not really specific to this method because we don't know what
127 // will break if we change this.
128 $params['amount'] = $this->_params
[0]['amount'];
129 if (!empty($this->_params
[0]['discount'])) {
130 $params['discount'] = $this->_params
[0]['discount'];
131 $params['discountAmount'] = $this->_params
[0]['discountAmount'];
132 $params['discountMessage'] = $this->_params
[0]['discountMessage'];
135 $params['amount_level'] = $this->_params
[0]['amount_level'];
136 $params['currencyID'] = $this->_params
[0]['currencyID'];
138 // also merge all the other values from the profile fields
139 $values = $this->controller
->exportValues('Register');
142 "street_address-{$this->_bltID}",
143 "city-{$this->_bltID}",
144 "state_province_id-{$this->_bltID}",
145 "postal_code-{$this->_bltID}",
146 "country_id-{$this->_bltID}",
149 foreach ($values as $name => $value) {
151 if (!in_array($name, $skipFields)) {
152 $params[$name] = $value;
154 if (substr($name, 0, 6) == 'price_') {
155 $params[$name] = $this->_params
[0][$name];
158 $this->set('getExpressCheckoutDetails', $params);
160 $this->_params
[0] = array_merge($this->_params
[0], $params);
161 $this->_params
[0]['is_primary'] = 1;
166 * Overwrite action, since we are only showing elements in frozen mode no help display needed.
170 public function getAction() {
171 if ($this->_action
& CRM_Core_Action
::PREVIEW
) {
172 return CRM_Core_Action
::VIEW | CRM_Core_Action
::PREVIEW
;
175 return CRM_Core_Action
::VIEW
;
180 * Build the form object.
182 public function buildQuickForm() {
183 $this->assignToTemplate();
185 if ($this->_values
['event']['is_monetary'] &&
186 ($this->_params
[0]['amount'] ||
$this->_params
[0]['amount'] == 0) &&
187 !$this->_requireApproval
192 foreach ($this->_params
as $k => $v) {
196 $individualTaxAmount = 0;
197 //display tax amount on confirmation page
198 $taxAmount +
= $v['tax_amount'];
200 $this->cleanMoneyFields($v);
205 if (isset($v['billing_' . $name]) &&
208 $v[$name] = $v['billing_' . $name];
212 if (!empty($v['first_name']) && !empty($v['last_name'])) {
213 $append = $v['first_name'] . ' ' . $v['last_name'];
216 //use an email if we have one
217 foreach ($v as $v_key => $v_val) {
218 if (substr($v_key, 0, 6) == 'email-') {
219 $append = $v[$v_key];
224 $this->_amount
[$k]['amount'] = $v['amount'];
225 if (!empty($v['discountAmount'])) {
226 $this->_amount
[$k]['amount'] -= $v['discountAmount'];
229 $this->_amount
[$k]['label'] = preg_replace('/\ 1/', '', $v['amount_level']) . ' - ' . $append;
230 $this->_part
[$k]['info'] = CRM_Utils_Array
::value('first_name', $v) . ' ' . CRM_Utils_Array
::value('last_name', $v);
231 if (empty($v['first_name'])) {
232 $this->_part
[$k]['info'] = $append;
236 $individual[$k]['totalAmtWithTax'] = $this->_amount
[$k]['amount'];
237 $individual[$k]['totalTaxAmt'] = $individualTaxAmount +
$v['tax_amount'];
238 $this->_totalAmount
= $this->_totalAmount +
$this->_amount
[$k]['amount'];
239 if (!empty($v['is_primary'])) {
240 $this->set('primaryParticipantAmount', $this->_amount
[$k]['amount']);
245 if (CRM_Invoicing_Utils
::isInvoicingEnabled()) {
246 $this->assign('totalTaxAmount', $taxAmount);
247 $this->assign('taxTerm', CRM_Invoicing_Utils
::getTaxTerm());
248 $this->assign('individual', $individual);
249 $this->set('individual', $individual);
252 $this->assign('part', $this->_part
);
253 $this->set('part', $this->_part
);
254 $this->assign('amounts', $this->_amount
);
255 $this->assign('totalAmount', $this->_totalAmount
);
256 $this->set('totalAmount', $this->_totalAmount
);
259 if ($this->_priceSetId
&& !CRM_Core_DAO
::getFieldValue('CRM_Price_DAO_PriceSet', $this->_priceSetId
, 'is_quick_config')) {
260 $lineItemForTemplate = [];
261 if (!empty($this->_lineItem
) && is_array($this->_lineItem
)) {
262 foreach ($this->_lineItem
as $key => $value) {
263 if (!empty($value)) {
264 $lineItemForTemplate[$key] = $value;
268 if (!empty($lineItemForTemplate)) {
269 $this->assignLineItemsToTemplate($lineItemForTemplate);
273 //display additional participants profile.
274 self
::assignProfiles($this);
276 //consider total amount.
277 $this->assign('isAmountzero', $this->_totalAmount
<= 0);
279 $contribButton = ts('Continue');
283 'name' => ts('Go Back'),
287 'name' => $contribButton,
294 if (!empty($this->_fields
)) {
295 foreach ($this->_fields
as $name => $dontCare) {
299 $fields["billing_state_province-{$this->_bltID}"] = $fields["billing_country-{$this->_bltID}"] = $fields["email-{$this->_bltID}"] = 1;
300 foreach ($fields as $name => $dontCare) {
301 if (isset($this->_params
[0][$name])) {
302 $defaults[$name] = $this->_params
[0][$name];
303 if (substr($name, 0, 7) == 'custom_') {
304 $timeField = "{$name}_time";
305 if (isset($this->_params
[0][$timeField])) {
306 $defaults[$timeField] = $this->_params
[0][$timeField];
308 if (isset($this->_params
[0]["{$name}_id"])) {
309 $defaults["{$name}_id"] = $this->_params
[0]["{$name}_id"];
312 elseif (in_array($name, CRM_Contact_BAO_Contact
::$_greetingTypes)
313 && !empty($this->_params
[0][$name . '_custom'])
315 $defaults[$name . '_custom'] = $this->_params
[0][$name . '_custom'];
320 $this->setDefaults($defaults);
323 //lets give meaningful status message, CRM-4320.
324 $this->assign('isOnWaitlist', $this->_allowWaitlist
);
325 $this->assign('isRequireApproval', $this->_requireApproval
);
327 // Assign Participant Count to Lineitem Table
328 $this->assign('pricesetFieldsCount', CRM_Price_BAO_PriceSet
::getPricesetCount($this->_priceSetId
));
329 $this->addFormRule(['CRM_Event_Form_Registration_Confirm', 'formRule'], $this);
335 * @param array $fields
336 * @param array $files
337 * @param CRM_Core_Form $self
341 public static function formRule($fields, $files, $self) {
343 $eventFull = CRM_Event_BAO_Participant
::eventFull($self->_eventId
, FALSE, CRM_Utils_Array
::value('has_waitlist', $self->_values
['event']));
344 if ($eventFull && empty($self->_allowConfirmation
)) {
345 if (empty($self->_allowWaitlist
)) {
346 CRM_Utils_System
::redirect(CRM_Utils_System
::url('civicrm/event/register', "reset=1&id={$self->_eventId}", FALSE, NULL, FALSE, TRUE));
349 $self->_feeBlock
= $self->_values
['fee'];
350 CRM_Event_Form_Registration_Register
::formatFieldsForOptionFull($self);
352 if (!empty($self->_priceSetId
) &&
353 !$self->_requireApproval
&& !$self->_allowWaitlist
355 $priceSetErrors = self
::validatePriceSet($self, $self->_params
);
356 if (!empty($priceSetErrors)) {
357 CRM_Core_Session
::setStatus(ts('You have been returned to the start of the registration process and any sold out events have been removed from your selections. You will not be able to continue until you review your booking and select different events if you wish.'), ts('Unfortunately some of your options have now sold out for one or more participants.'), 'error');
358 CRM_Core_Session
::setStatus(ts('Please note that the options which are marked or selected are sold out for participant being viewed.'), ts('Sold out:'), 'error');
359 CRM_Utils_System
::redirect(CRM_Utils_System
::url('civicrm/event/register', "_qf_Register_display=true&qfKey={$fields['qfKey']}"));
363 return empty($priceSetErrors) ?
TRUE : $priceSetErrors;
367 * Process the form submission.
369 * @throws \CiviCRM_API3_Exception
370 * @throws \CRM_Core_Exception
372 public function postProcess() {
373 $now = date('YmdHis');
375 $this->_params
= $this->get('params');
376 $this->cleanMoneyFields($this->_params
);
378 if (!empty($this->_params
[0]['contact_id'])) {
379 // unclear when this would be set & whether it could be checked in getContactID.
380 // perhaps it relates to when cid is in the url
381 //@todo someone who knows add comments on the various contactIDs in this form
382 $contactID = $this->_params
[0]['contact_id'];
385 $contactID = $this->getContactID();
388 // if a discount has been applied, lets now deduct it from the amount
389 // and fix the fee level
390 if (!empty($this->_params
[0]['discount']) && !empty($this->_params
[0]['discount']['applied'])) {
391 foreach ($this->_params
as $k => $v) {
392 if (CRM_Utils_Array
::value('amount', $this->_params
[$k]) > 0 && !empty($this->_params
[$k]['discountAmount'])) {
393 $this->_params
[$k]['amount'] -= $this->_params
[$k]['discountAmount'];
394 $this->_params
[$k]['amount_level'] .= CRM_Utils_Array
::value('discountMessage', $this->_params
[$k]);
397 $this->set('params', $this->_params
);
400 // CRM-4320, lets build array of cancelled additional participant ids
401 // those are drop or skip by primary at the time of confirmation.
402 // get all in and then unset those we want to process.
403 $cancelledIds = $this->_additionalParticipantIds
;
405 $params = $this->_params
;
406 if ($this->_values
['event']['is_monetary']) {
407 $this->set('finalAmount', $this->_amount
);
409 $participantCount = [];
412 //unset the skip participant from params.
413 //build the $participantCount array.
414 //maintain record for all participants.
415 foreach ($params as $participantNum => $record) {
416 if ($record == 'skip') {
417 unset($params[$participantNum]);
418 $participantCount[$participantNum] = 'skip';
420 elseif ($participantNum) {
421 $participantCount[$participantNum] = 'participant';
423 $totalTaxAmount +
= CRM_Utils_Array
::value('tax_amount', $record, 0);
424 if (!empty($record['is_primary'])) {
425 $taxAmount = &$params[$participantNum]['tax_amount'];
427 //lets get additional participant id to cancel.
428 if ($this->_allowConfirmation
&& is_array($cancelledIds)) {
429 $additonalId = $record['participant_id'] ??
NULL;
430 if ($additonalId && $key = array_search($additonalId, $cancelledIds)) {
431 unset($cancelledIds[$key]);
435 $taxAmount = $totalTaxAmount;
436 $payment = $registerByID = $primaryCurrencyID = $contribution = NULL;
437 $paymentObjError = ts('The system did not record payment details for this payment and so could not process the transaction. Please report this error to the site administrator.');
440 foreach ($params as $key => $value) {
441 CRM_Event_Form_Registration_Confirm
::fixLocationFields($value, $fields, $this);
442 if ($this->_allowWaitlist
443 ||
$this->_requireApproval
444 ||
(!empty($value['is_pay_later']) && !$this->_isBillingAddressRequiredForPayLater
)
445 ||
empty($value['is_primary'])
447 // This is confusing because unnecessary code around it has been removed. It is not
448 // clear why we do this / whether we should.
449 if (!empty($value['is_pay_later'])) {
450 $this->_values
['params']['is_pay_later'] = TRUE;
454 //Unset ContactID for additional participants and set RegisterBy Id.
455 if (empty($value['is_primary'])) {
456 $contactID = $value['contact_id'] ??
NULL;
457 $registerByID = $this->get('registerByID');
459 $value['registered_by_id'] = $registerByID;
463 $value['amount'] = $this->_totalAmount
;
466 $contactID = CRM_Event_Form_Registration_Confirm
::updateContactFields($contactID, $value, $fields, $this);
468 // lets store the contactID in the session
469 // we dont store in userID in case the user is doing multiple
471 // for things like tell a friend
472 if (!$this->getContactID() && !empty($value['is_primary'])) {
473 CRM_Core_Session
::singleton()->set('transaction.userID', $contactID);
476 $value['description'] = ts('Online Event Registration') . ': ' . $this->_values
['event']['title'];
477 $value['accountingCode'] = CRM_Utils_Array
::value('accountingCode',
478 $this->_values
['event']
482 if ($this->_allowWaitlist ||
$this->_requireApproval
) {
483 //get the participant statuses.
484 $waitingStatuses = CRM_Event_PseudoConstant
::participantStatus(NULL, "class = 'Waiting'");
485 if ($this->_allowWaitlist
) {
486 $value['participant_status_id'] = $value['participant_status'] = array_search('On waitlist', $waitingStatuses);
489 $value['participant_status_id'] = $value['participant_status'] = array_search('Awaiting approval', $waitingStatuses);
492 //there might be case user selected pay later and
493 //now becomes part of run time waiting list.
494 $value['is_pay_later'] = FALSE;
496 elseif ($this->_values
['event']['is_monetary']) {
497 // required only if paid event
498 if (is_array($this->_paymentProcessor
)) {
499 $payment = $this->_paymentProcessor
['object'];
501 if (!empty($this->_paymentProcessor
) && $this->_paymentProcessor
['object']->supports('preApproval')) {
502 $preApprovalParams = $this->_paymentProcessor
['object']->getPreApprovalDetails($this->get('pre_approval_parameters'));
503 $value = array_merge($value, $preApprovalParams);
507 if (!empty($value['is_pay_later']) ||
508 $value['amount'] == 0 ||
509 // The concept of contributeMode is deprecated.
510 $this->_contributeMode
== 'checkout' ||
511 $this->_contributeMode
== 'notify'
513 if ($value['amount'] != 0) {
515 //get the participant statuses.
516 $pendingStatuses = CRM_Event_PseudoConstant
::participantStatus(NULL, "class = 'Pending'");
517 $status = !empty($value['is_pay_later']) ?
'Pending from pay later' : 'Pending from incomplete transaction';
518 $value['participant_status_id'] = $value['participant_status'] = array_search($status, $pendingStatuses);
521 elseif (!empty($value['is_primary'])) {
522 CRM_Core_Payment_Form
::mapParams($this->_bltID
, $value, $value, TRUE);
523 // payment email param can be empty for _bltID mapping
524 // thus provide mapping for it with a different email value
525 if (empty($value['email'])) {
526 $value['email'] = CRM_Utils_Array
::valueByRegexKey('/^email-/', $value);
529 if (is_object($payment)) {
530 // Not quite sure why we don't just user $value since it contains the data
532 // @todo ditch $result & retest.
533 list($result, $value) = $this->processPayment($payment, $value);
536 throw new CRM_Core_Exception($paymentObjError);
540 $value['receive_date'] = $now;
541 if ($this->_allowConfirmation
) {
542 $value['participant_register_date'] = $this->_values
['participant']['register_date'];
545 $createContrib = $value['amount'] != 0;
546 // force to create zero amount contribution, CRM-5095
547 if (!$createContrib && ($value['amount'] == 0)
548 && $this->_priceSetId
&& $this->_lineItem
550 $createContrib = TRUE;
553 if ($createContrib && !empty($value['is_primary']) &&
554 !$this->_allowWaitlist
&& !$this->_requireApproval
556 // if paid event add a contribution record
557 //if primary participant contributing additional amount
558 //append (multiple participants) to its fee level. CRM-4196.
559 if (count($params) > 1) {
560 $value['amount_level'] .= ts(' (multiple participants)') . CRM_Core_DAO
::VALUE_SEPARATOR
;
563 //passing contribution id is already registered.
564 $contribution = $this->processContribution($this, $value, $result, $contactID, $pending, $this->_paymentProcessor
);
565 $value['contributionID'] = $contribution->id
;
566 $value['contributionTypeID'] = $contribution->financial_type_id
;
567 $value['receive_date'] = $contribution->receive_date
;
568 $value['trxn_id'] = $contribution->trxn_id
;
569 $value['contributionID'] = $contribution->id
;
570 $value['contributionTypeID'] = $contribution->financial_type_id
;
572 $value['contactID'] = $contactID;
573 $value['eventID'] = $this->_eventId
;
574 $value['item_name'] = $value['description'];
577 if (!empty($value['contributionID'])) {
578 $this->_values
['contributionId'] = $value['contributionID'];
582 if (!empty($value['is_primary'])) {
583 $primaryCurrencyID = $value['currencyID'] ??
NULL;
585 if (empty($value['currencyID'])) {
586 $value['currencyID'] = $primaryCurrencyID;
589 // CRM-11182 - Confirmation page might not be monetary
590 if ($this->_values
['event']['is_monetary']) {
591 if (!$pending && !empty($value['is_primary']) &&
592 !$this->_allowWaitlist
&& !$this->_requireApproval
594 // transactionID & receive date required while building email template
595 $this->assign('trxn_id', CRM_Utils_Array
::value('trxn_id', $value));
596 $this->assign('receive_date', CRM_Utils_Date
::mysqlToIso(CRM_Utils_Array
::value('receive_date', $value)));
597 $this->set('receiveDate', CRM_Utils_Date
::mysqlToIso(CRM_Utils_Array
::value('receive_date', $value)));
598 $this->set('trxnId', CRM_Utils_Array
::value('trxn_id', $value));
602 $value['fee_amount'] = $value['amount'] ??
NULL;
603 $this->set('value', $value);
605 // handle register date CRM-4320
606 if ($this->_allowConfirmation
) {
607 $registerDate = $params['participant_register_date'] ??
NULL;
609 elseif (!empty($params['participant_register_date']) &&
610 is_array($params['participant_register_date'])
612 $registerDate = CRM_Utils_Date
::format($params['participant_register_date']);
615 $registerDate = date('YmdHis');
617 $this->assign('register_date', $registerDate);
619 $this->confirmPostProcess($contactID, $contribution);
622 //handle if no additional participant.
623 if (!$registerByID) {
624 $registerByID = $this->get('registerByID');
627 $this->set('participantIDs', $this->_participantIDS
);
629 // create line items, CRM-5313
630 if ($this->_priceSetId
&&
631 !empty($this->_lineItem
)
633 // take all processed participant ids.
634 $allParticipantIds = $this->_participantIDS
;
636 // when participant re-walk wizard.
637 if ($this->_allowConfirmation
&&
638 !empty($this->_additionalParticipantIds
)
640 $allParticipantIds = array_merge([$registerByID], $this->_additionalParticipantIds
);
643 $entityTable = 'civicrm_participant';
646 foreach ($this->_lineItem
as $key => $value) {
647 if ($value == 'skip') {
650 if ($entityId = CRM_Utils_Array
::value($key, $allParticipantIds)) {
651 // do cleanup line items if participant re-walking wizard.
652 if ($this->_allowConfirmation
) {
653 CRM_Price_BAO_LineItem
::deleteLineItems($entityId, $entityTable);
655 $lineItem[$this->_priceSetId
] = $value;
656 CRM_Price_BAO_LineItem
::processPriceSet($entityId, $lineItem, $contribution, $entityTable);
658 if (CRM_Invoicing_Utils
::isInvoicingEnabled()) {
659 foreach ($value as $line) {
660 if (isset($line['tax_amount']) && isset($line['tax_rate'])) {
661 $totalTaxAmount = $line['tax_amount'] +
$totalTaxAmount;
662 if (isset($dataArray[$line['tax_rate']])) {
663 $dataArray[$line['tax_rate']] = $dataArray[$line['tax_rate']] + CRM_Utils_Array
::value('tax_amount', $line);
666 $dataArray[$line['tax_rate']] = $line['tax_amount'] ??
NULL;
672 $this->assign('dataArray', $dataArray);
673 $this->assign('totalTaxAmount', $totalTaxAmount);
676 //update status and send mail to cancelled additional participants, CRM-4320
677 if ($this->_allowConfirmation
&& is_array($cancelledIds) && !empty($cancelledIds)) {
678 $cancelledId = array_search('Cancelled',
679 CRM_Event_PseudoConstant
::participantStatus(NULL, "class = 'Negative'")
681 CRM_Event_BAO_Participant
::transitionParticipants($cancelledIds, $cancelledId);
685 if ($this->_action
& CRM_Core_Action
::PREVIEW
) {
689 $primaryParticipant = $this->get('primaryParticipant');
691 if (empty($primaryParticipant['participantID'])) {
692 CRM_Core_Error
::deprecatedFunctionWarning('This line is not logically reachable.');
693 $primaryParticipant['participantID'] = $registerByID;
695 //otherwise send mail Confirmation/Receipt
696 $primaryContactId = $this->get('primaryContactId');
698 // for Transfer checkout.
699 // The concept of contributeMode is deprecated.
700 if (($this->_contributeMode
== 'checkout' ||
701 $this->_contributeMode
== 'notify'
702 ) && empty($params[0]['is_pay_later']) &&
703 !$this->_allowWaitlist
&& !$this->_requireApproval
&&
704 $this->_totalAmount
> 0
707 //build an array of custom profile and assigning it to template
708 $customProfile = CRM_Event_BAO_Event
::buildCustomProfile($registerByID, $this->_values
, NULL, $isTest);
709 if (count($customProfile)) {
710 $this->assign('customProfile', $customProfile);
711 $this->set('customProfile', $customProfile);
714 // do a transfer only if a monetary payment greater than 0
715 if ($this->_values
['event']['is_monetary'] && $primaryParticipant) {
716 if ($payment && is_object($payment)) {
717 //CRM 14512 provide line items of all participants to payment gateway
718 $primaryContactId = $this->get('primaryContactId');
720 //build an array of cId/pId of participants
721 $additionalIDs = CRM_Event_BAO_Event
::buildCustomProfile($registerByID, NULL, $primaryContactId, $isTest, TRUE);
723 //need to copy, since we are unsetting on the way.
724 $copyParticipantCountLines = $participantCount;
726 //lets carry all participant params w/ values.
727 foreach ($additionalIDs as $participantID => $contactId) {
728 $participantNum = $participantID;
729 if ($participantID == $registerByID) {
730 // This is the is primary participant.
734 if ($participantNum = array_search('participant', $copyParticipantCountLines)) {
735 //if no participant found break.
736 if ($participantNum === NULL) {
739 //unset current participant so we don't check them again
740 unset($copyParticipantCountLines[$participantNum]);
743 // get values of line items
744 if ($this->_amount
) {
746 $amount[$participantNum]['label'] = preg_replace('/\ 1/', '', $params[$participantNum]['amount_level']);
747 $amount[$participantNum]['amount'] = $params[$participantNum]['amount'];
748 $params[$participantNum]['amounts'] = $amount;
751 if (!empty($this->_lineItem
)) {
752 $lineItems = $this->_lineItem
;
754 if ($lineItemValue = CRM_Utils_Array
::value($participantNum, $lineItems)) {
755 $lineItem[] = $lineItemValue;
757 $params[$participantNum]['lineItem'] = $lineItem;
760 //only add additional particpants and not the primary particpant as we already have that
761 //added to $primaryParticipant so that this change doesn't break or require changes to
762 //existing gateway implementations
763 $primaryParticipant['participants_info'][$participantID] = $params[$participantNum];
766 //get event custom field information
767 $groupTree = CRM_Core_BAO_CustomGroup
::getTree('Event', NULL, $this->_eventId
, 0, $this->_values
['event']['event_type_id']);
768 $primaryParticipant['eventCustomFields'] = $groupTree;
770 // call postprocess hook before leaving
771 $this->postProcessHook();
773 $this->processPayment($payment, $primaryParticipant);
776 throw new CRM_Core_Exception($paymentObjError);
782 //build an array of cId/pId of participants
783 $additionalIDs = CRM_Event_BAO_Event
::buildCustomProfile($registerByID,
784 NULL, $primaryContactId, $isTest,
787 //let's send mails to all with meaningful text, CRM-4320.
788 $this->assign('isOnWaitlist', $this->_allowWaitlist
);
789 $this->assign('isRequireApproval', $this->_requireApproval
);
791 //need to copy, since we are unsetting on the way.
792 $copyParticipantCount = $participantCount;
794 //let's carry all participant params w/ values.
795 foreach ($additionalIDs as $participantID => $contactId) {
796 $participantNum = NULL;
797 if ($participantID == $registerByID) {
801 if ($participantNum = array_search('participant', $copyParticipantCount)) {
802 unset($copyParticipantCount[$participantNum]);
805 if ($participantNum === NULL) {
809 //carry the participant submitted values.
810 $this->_values
['params'][$participantID] = $params[$participantNum];
813 foreach ($additionalIDs as $participantID => $contactId) {
815 if ($participantID == $registerByID) {
816 //set as Primary Participant
817 $this->assign('isPrimary', 1);
818 //build an array of custom profile and assigning it to template.
819 $customProfile = CRM_Event_BAO_Event
::buildCustomProfile($participantID, $this->_values
, NULL, $isTest);
821 if (count($customProfile)) {
822 $this->assign('customProfile', $customProfile);
823 $this->set('customProfile', $customProfile);
825 $this->_values
['params']['additionalParticipant'] = FALSE;
828 //take the Additional participant number.
829 if ($participantNum = array_search('participant', $participantCount)) {
830 unset($participantCount[$participantNum]);
832 // Change $this->_values['participant'] to include additional participant values
833 $ids = $participantValues = [];
834 $participantParams = ['id' => $participantID];
835 CRM_Event_BAO_Participant
::getValues($participantParams, $participantValues, $ids);
836 $this->_values
['participant'] = $participantValues[$participantID];
838 $this->assign('isPrimary', 0);
839 $this->assign('customProfile', NULL);
840 //Additional Participant should get only it's payment information
841 if (!empty($this->_amount
)) {
843 $params = $this->get('params');
844 $amount[$participantNum]['label'] = preg_replace('/\ 1/', '', $params[$participantNum]['amount_level']);
845 $amount[$participantNum]['amount'] = $params[$participantNum]['amount'];
846 $this->assign('amounts', $amount);
848 if ($this->_lineItem
) {
849 $lineItems = $this->_lineItem
;
851 if ($lineItemValue = CRM_Utils_Array
::value($participantNum, $lineItems)) {
852 $lineItem[] = $lineItemValue;
854 if (CRM_Invoicing_Utils
::isInvoicingEnabled()) {
855 $individual = $this->get('individual');
856 $dataArray[key($dataArray)] = $individual[$participantNum]['totalTaxAmt'];
857 $this->assign('dataArray', $dataArray);
858 $this->assign('totalAmount', $individual[$participantNum]['totalAmtWithTax']);
859 $this->assign('totalTaxAmount', $individual[$participantNum]['totalTaxAmt']);
860 $this->assign('individual', [$individual[$participantNum]]);
862 $this->assign('lineItem', $lineItem);
864 $this->_values
['params']['additionalParticipant'] = TRUE;
865 $this->assign('isAdditionalParticipant', $this->_values
['params']['additionalParticipant']);
868 //pass these variables since these are run time calculated.
869 $this->_values
['params']['isOnWaitlist'] = $this->_allowWaitlist
;
870 $this->_values
['params']['isRequireApproval'] = $this->_requireApproval
;
872 //send mail to primary as well as additional participants.
873 CRM_Event_BAO_Event
::sendMail($contactId, $this->_values
, $participantID, $isTest);
879 * Process the contribution.
881 * @param CRM_Core_Form $form
882 * @param array $params
883 * @param array $result
884 * @param int $contactID
885 * @param bool $pending
886 * @param array $paymentProcessor
888 * @return \CRM_Contribute_BAO_Contribution
890 * @throws \CRM_Core_Exception
891 * @throws \CiviCRM_API3_Exception
893 protected function processContribution(
894 &$form, $params, $result, $contactID,
896 $paymentProcessor = NULL
898 // Note this used to be shared with the backoffice form & no longer is, some code may no longer be required.
899 $transaction = new CRM_Core_Transaction();
901 $now = date('YmdHis');
904 if (!empty($form->_values
['event']['is_email_confirm'])) {
908 // CRM-20264: fetch CC type ID and number (last 4 digit) and assign it back to $params
909 CRM_Contribute_Form_AbstractEditPayment
::formatCreditCardDetails($params);
912 'contact_id' => $contactID,
913 'financial_type_id' => !empty($form->_values
['event']['financial_type_id']) ?
$form->_values
['event']['financial_type_id'] : $params['financial_type_id'],
914 'receive_date' => $now,
915 'total_amount' => $params['amount'],
916 'tax_amount' => $params['tax_amount'],
917 'amount_level' => $params['amount_level'],
918 'invoice_id' => $params['invoiceID'],
919 'currency' => $params['currencyID'],
920 'source' => !empty($params['participant_source']) ?
$params['participant_source'] : $params['description'],
921 'is_pay_later' => CRM_Utils_Array
::value('is_pay_later', $params, 0),
922 'campaign_id' => $params['campaign_id'] ??
NULL,
923 'card_type_id' => $params['card_type_id'] ??
NULL,
924 'pan_truncation' => $params['pan_truncation'] ??
NULL,
927 if ($paymentProcessor) {
928 $contribParams['payment_instrument_id'] = $paymentProcessor['payment_instrument_id'];
929 $contribParams['payment_processor'] = $paymentProcessor['id'];
932 if (!$pending && $result) {
934 'fee_amount' => $result['fee_amount'] ??
NULL,
935 'trxn_id' => $result['trxn_id'],
936 'receipt_date' => $receiptDate,
940 $allStatuses = CRM_Contribute_PseudoConstant
::contributionStatus(NULL, 'name');
941 $contribParams['contribution_status_id'] = array_search('Completed', $allStatuses);
943 $contribParams['contribution_status_id'] = array_search('Pending', $allStatuses);
946 $contribParams['is_test'] = 0;
947 if ($form->_action
& CRM_Core_Action
::PREVIEW || CRM_Utils_Array
::value('mode', $params) == 'test') {
948 $contribParams['is_test'] = 1;
951 if (!empty($contribParams['invoice_id'])) {
952 $contribParams['id'] = CRM_Core_DAO
::getFieldValue('CRM_Contribute_DAO_Contribution',
953 $contribParams['invoice_id'],
959 if (Civi
::settings()->get('deferred_revenue_enabled')) {
960 $eventStartDate = CRM_Utils_Array
::value(
962 CRM_Utils_Array
::value(
967 if (strtotime($eventStartDate) > strtotime(date('Ymt'))) {
968 $contribParams['revenue_recognition_date'] = date('Ymd', strtotime($eventStartDate));
971 //create an contribution address
972 // The concept of contributeMode is deprecated. Elsewhere we use the function processBillingAddress() - although
973 // currently that is only inherited by back-office forms.
974 if ($form->_contributeMode
!= 'notify' && empty($params['is_pay_later'])) {
975 $contribParams['address_id'] = CRM_Contribute_BAO_Contribution
::createAddress($params, $form->_bltID
);
978 $contribParams['skipLineItem'] = 1;
979 $contribParams['skipCleanMoney'] = 1;
980 // create contribution record
981 $contribution = CRM_Contribute_BAO_Contribution
::add($contribParams);
983 CRM_Event_BAO_Participant
::createDiscountTrxn($form->_eventId
, $contribParams, NULL, CRM_Price_BAO_PriceSet
::parseFirstPriceSetValueIDFromParams($params));
985 // process soft credit / pcp pages
986 if (!empty($params['pcp_made_through_id'])) {
987 CRM_Contribute_BAO_ContributionSoft
::formatSoftCreditParams($params, $form);
988 CRM_Contribute_BAO_ContributionSoft
::processSoftContribution($params, $contribution);
991 $transaction->commit();
993 return $contribution;
997 * Fix the Location Fields.
999 * @todo Reconcile with the contribution method formatParamsForPaymentProcessor
1000 * rather than adding different logic to check when to keep the billing
1001 * fields. There might be a difference in handling guest/multiple
1002 * participants though.
1004 * @param array $params
1005 * @param array $fields
1006 * @param CRM_Core_Form $form
1008 public static function fixLocationFields(&$params, &$fields, &$form) {
1009 if (!empty($form->_fields
)) {
1010 foreach ($form->_fields
as $name => $dontCare) {
1015 // If there's no 'first_name' in the profile then overwrite the names from
1016 // the billing fields (if they are set)
1017 if (is_array($fields)) {
1018 if (!array_key_exists('first_name', $fields)) {
1019 $nameFields = ['first_name', 'middle_name', 'last_name'];
1020 foreach ($nameFields as $name) {
1022 if (array_key_exists("billing_$name", $params)) {
1023 $params[$name] = $params["billing_{$name}"];
1024 $params['preserveDBName'] = TRUE;
1030 // Add the billing names to the billing address, if a billing name is set
1031 if (!empty($params['billing_first_name'])) {
1032 $params["address_name-{$form->_bltID}"] = $params['billing_first_name'] . ' ' . CRM_Utils_Array
::value('billing_middle_name', $params) . ' ' . CRM_Utils_Array
::value('billing_last_name', $params);
1033 $fields["address_name-{$form->_bltID}"] = 1;
1036 $fields["email-{$form->_bltID}"] = 1;
1037 $fields['email-Primary'] = 1;
1039 //if its pay later or additional participant set email address as primary.
1040 if ((!empty($params['is_pay_later']) ||
empty($params['is_primary']) ||
1041 !$form->_values
['event']['is_monetary'] ||
1042 $form->_allowWaitlist ||
1043 $form->_requireApproval
1044 ) && !empty($params["email-{$form->_bltID}"])
1046 $params['email-Primary'] = $params["email-{$form->_bltID}"];
1051 * Update contact fields.
1053 * @param int $contactID
1054 * @param array $params
1055 * @param array $fields
1056 * @param CRM_Core_Form $form
1060 public static function updateContactFields($contactID, $params, $fields, &$form) {
1061 //add the contact to group, if add to group is selected for a
1062 //particular uf group
1064 // get the add to groups
1067 if (!empty($form->_fields
)) {
1068 foreach ($form->_fields
as $key => $value) {
1069 if (!empty($value['add_to_group_id'])) {
1070 $addToGroups[$value['add_to_group_id']] = $value['add_to_group_id'];
1075 // check for profile double opt-in and get groups to be subscribed
1076 $subscribeGroupIds = CRM_Core_BAO_UFGroup
::getDoubleOptInGroupIds($params, $contactID);
1078 foreach ($addToGroups as $k) {
1079 if (array_key_exists($k, $subscribeGroupIds)) {
1080 unset($addToGroups[$k]);
1084 // since we are directly adding contact to group lets unset it from mailing
1085 if (!empty($addToGroups)) {
1086 foreach ($addToGroups as $groupId) {
1087 if (isset($subscribeGroupIds[$groupId])) {
1088 unset($subscribeGroupIds[$groupId]);
1093 $ctype = CRM_Core_DAO
::getFieldValue(
1094 'CRM_Contact_DAO_Contact',
1099 if (array_key_exists('contact_id', $params) && empty($params['contact_id'])) {
1100 // we unset this here because the downstream function ignores the contactID we give it
1101 // if it is set & it is difficult to understand the implications of 'fixing' this downstream
1102 // but if we are passing a contact id into this function it's reasonable to assume we don't
1104 unset($params['contact_id']);
1107 $contactID = CRM_Contact_BAO_Contact
::createProfileContact(
1119 foreach (CRM_Contact_BAO_Contact
::$_greetingTypes as $greeting) {
1120 if (!isset($params[$greeting . '_id'])) {
1121 $params[$greeting . '_id'] = CRM_Contact_BAO_Contact_Utils
::defaultGreeting('Individual', $greeting);
1125 $contactID = CRM_Contact_BAO_Contact
::createProfileContact($params,
1133 $form->set('contactID', $contactID);
1136 //get email primary first if exist
1137 $subscriptionEmail = ['email' => CRM_Utils_Array
::value('email-Primary', $params)];
1138 if (!$subscriptionEmail['email']) {
1139 $subscriptionEmail['email'] = $params["email-{$form->_bltID}"] ??
NULL;
1141 // subscribing contact to groups
1142 if (!empty($subscribeGroupIds) && $subscriptionEmail['email']) {
1143 CRM_Mailing_Event_BAO_Subscribe
::commonSubscribe($subscribeGroupIds, $subscriptionEmail, $contactID);
1150 * Assign Profiles to the template.
1152 * @param CRM_Event_Form_Registration_Confirm $form
1154 * @throws \Exception
1156 public static function assignProfiles($form) {
1157 $participantParams = $form->_params
;
1158 $formattedValues = $profileFields = [];
1160 foreach ($participantParams as $participantNum => $participantValue) {
1161 if ($participantNum) {
1162 $prefix1 = 'additional';
1163 $prefix2 = 'additional_';
1169 if ($participantValue !== 'skip') {
1170 //get the customPre profile info
1171 if (!empty($form->_values
[$prefix2 . 'custom_pre_id'])) {
1172 $values = $groupName = [];
1173 CRM_Event_BAO_Event
::displayProfile($participantValue,
1174 $form->_values
[$prefix2 . 'custom_pre_id'],
1180 if (count($values)) {
1181 $formattedValues[$count][$prefix1 . 'CustomPre'] = $values;
1183 $formattedValues[$count][$prefix1 . 'CustomPreGroupTitle'] = $groupName['groupTitle'] ??
NULL;
1185 //get the customPost profile info
1186 if (!empty($form->_values
[$prefix2 . 'custom_post_id'])) {
1187 $values = $groupName = [];
1188 foreach ($form->_values
[$prefix2 . 'custom_post_id'] as $gids) {
1190 CRM_Event_BAO_Event
::displayProfile($participantValue,
1196 $values[$gids] = $val;
1197 $groupName[$gids] = $group;
1200 if (count($values)) {
1201 $formattedValues[$count][$prefix1 . 'CustomPost'] = $values;
1204 if (isset($formattedValues[$count][$prefix1 . 'CustomPre'])) {
1205 $formattedValues[$count][$prefix1 . 'CustomPost'] = array_diff_assoc($formattedValues[$count][$prefix1 . 'CustomPost'],
1206 $formattedValues[$count][$prefix1 . 'CustomPre']
1210 $formattedValues[$count][$prefix1 . 'CustomPostGroupTitle'] = $groupName;
1214 $form->_fields
= $profileFields;
1216 if (!empty($formattedValues)) {
1217 $form->assign('primaryParticipantProfile', $formattedValues[1]);
1218 $form->set('primaryParticipantProfile', $formattedValues[1]);
1220 unset($formattedValues[1]);
1221 $form->assign('addParticipantProfile', $formattedValues);
1222 $form->set('addParticipantProfile', $formattedValues);
1228 * Submit in test mode.
1232 public static function testSubmit($params) {
1233 $form = new CRM_Event_Form_Registration_Confirm();
1234 // This way the mocked up controller ignores the session stuff.
1235 $_SERVER['REQUEST_METHOD'] = 'GET';
1236 $_REQUEST['id'] = $form->_eventId
= $params['id'];
1237 $form->controller
= new CRM_Event_Controller_Registration();
1238 $form->_params
= $params['params'];
1239 // This happens in buildQuickForm so emulate here.
1240 $form->_amount
= $form->_totalAmount
= CRM_Utils_Rule
::cleanMoney(CRM_Utils_Array
::value('totalAmount', $params));
1241 $form->set('params', $params['params']);
1242 $form->_values
['custom_pre_id'] = $params['custom_pre_id'] ??
NULL;
1243 $form->_values
['custom_post_id'] = $params['custom_post_id'] ??
NULL;
1244 $form->_values
['event'] = $params['event'] ??
NULL;
1245 $form->_contributeMode
= $params['contributeMode'];
1246 $eventParams = ['id' => $params['id']];
1247 CRM_Event_BAO_Event
::retrieve($eventParams, $form->_values
['event']);
1248 $form->set('registerByID', $params['registerByID']);
1249 if (!empty($params['paymentProcessorObj'])) {
1250 $form->_paymentProcessor
= $params['paymentProcessorObj'];
1252 $form->postProcess();
1256 * Process the payment, redirecting back to the page on error.
1258 * @param \CRM_Core_Payment $payment
1263 private function processPayment($payment, $value) {
1265 $params = $this->prepareParamsForPaymentProcessor($value);
1266 $result = $payment->doPayment($params, 'event');
1267 return [$result, $value];
1269 catch (\Civi\Payment\Exception\PaymentProcessorException
$e) {
1270 Civi
::log()->error('Payment processor exception: ' . $e->getMessage());
1271 CRM_Core_Session
::singleton()->setStatus($e->getMessage());
1272 CRM_Utils_System
::redirect(CRM_Utils_System
::url('civicrm/event/register', "id={$this->_eventId}"));
1278 * Clean money fields from the form.
1280 * @param array $params
1282 protected function cleanMoneyFields(&$params) {
1283 foreach ($this->submittableMoneyFields
as $moneyField) {
1284 foreach ($params as $index => $paramField) {
1285 if (isset($paramField[$moneyField])) {
1286 $params[$index][$moneyField] = CRM_Utils_Rule
::cleanMoney($paramField[$moneyField]);