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 $this->assign('pay_later_receipt', $this->_params
[0]['is_pay_later'] ?
$this->_values
['event']['pay_later_receipt'] : NULL);
64 CRM_Utils_Hook
::eventDiscount($this, $this->_params
);
66 if (!empty($this->_params
[0]['discount']) && !empty($this->_params
[0]['discount']['applied'])) {
67 $this->set('hookDiscount', $this->_params
[0]['discount']);
68 $this->assign('hookDiscount', $this->_params
[0]['discount']);
71 $this->preProcessExpress();
73 if ($this->_values
['event']['is_monetary']) {
74 $this->_params
[0]['invoiceID'] = $this->get('invoiceID');
76 $this->assign('defaultRole', FALSE);
77 if (CRM_Utils_Array
::value('defaultRole', $this->_params
[0]) == 1) {
78 $this->assign('defaultRole', TRUE);
81 if (empty($this->_params
[0]['participant_role_id']) &&
82 $this->_values
['event']['default_role_id']
84 $this->_params
[0]['participant_role_id'] = $this->_values
['event']['default_role_id'];
87 if (isset($this->_values
['event']['confirm_title'])) {
88 $this->setTitle($this->_values
['event']['confirm_title']);
91 // Personal campaign page
93 $params = CRM_Contribute_Form_Contribution_Confirm
::processPcp($this, $this->_params
[0]);
94 $this->_params
[0] = $params;
97 $this->set('params', $this->_params
);
101 * Pre process function for Paypal Express confirm.
102 * @todo this is just a step in refactor as payment processor specific code does not belong in generic forms
105 * @throws \CRM_Core_Exception
107 private function preProcessExpress() {
108 if ($this->_contributeMode
!== 'express') {
112 // rfp == redirect from paypal
113 // @fixme rfp is probably not required - the getPreApprovalDetails should deal with any payment-processor specific 'stuff'
114 $rfp = CRM_Utils_Request
::retrieve('rfp', 'Boolean', CRM_Core_DAO
::$_nullObject, FALSE, NULL, 'GET');
116 //we lost rfp in case of additional participant. So set it explicitly.
117 if ($rfp || CRM_Utils_Array
::value('additional_participants', $this->_params
[0], FALSE)) {
118 if (!empty($this->_paymentProcessor
) && $this->_paymentProcessor
['object']->supports('preApproval')) {
119 $preApprovalParams = $this->_paymentProcessor
['object']->getPreApprovalDetails($this->get('pre_approval_parameters'));
120 $params = array_merge($this->_params
, $preApprovalParams);
122 CRM_Core_Payment_Form
::mapParams($this->_bltID
, $params, $params, FALSE);
124 // set a few other parameters that are not really specific to this method because we don't know what
125 // will break if we change this.
126 $params['amount'] = $this->_params
[0]['amount'];
127 if (!empty($this->_params
[0]['discount'])) {
128 $params['discount'] = $this->_params
[0]['discount'];
129 $params['discountAmount'] = $this->_params
[0]['discountAmount'];
130 $params['discountMessage'] = $this->_params
[0]['discountMessage'];
133 $params['amount_level'] = $this->_params
[0]['amount_level'];
134 $params['currencyID'] = $this->_params
[0]['currencyID'];
136 // also merge all the other values from the profile fields
137 $values = $this->controller
->exportValues('Register');
140 "street_address-{$this->_bltID}",
141 "city-{$this->_bltID}",
142 "state_province_id-{$this->_bltID}",
143 "postal_code-{$this->_bltID}",
144 "country_id-{$this->_bltID}",
147 foreach ($values as $name => $value) {
149 if (!in_array($name, $skipFields)) {
150 $params[$name] = $value;
152 if (substr($name, 0, 6) == 'price_') {
153 $params[$name] = $this->_params
[0][$name];
156 $this->set('getExpressCheckoutDetails', $params);
158 $this->_params
[0] = array_merge($this->_params
[0], $params);
159 $this->_params
[0]['is_primary'] = 1;
164 * Overwrite action, since we are only showing elements in frozen mode no help display needed.
168 public function getAction() {
169 if ($this->_action
& CRM_Core_Action
::PREVIEW
) {
170 return CRM_Core_Action
::VIEW | CRM_Core_Action
::PREVIEW
;
173 return CRM_Core_Action
::VIEW
;
178 * Build the form object.
180 public function buildQuickForm() {
181 $this->assignToTemplate();
183 if ($this->_values
['event']['is_monetary'] &&
184 ($this->_params
[0]['amount'] ||
$this->_params
[0]['amount'] == 0) &&
185 !$this->_requireApproval
190 foreach ($this->_params
as $k => $v) {
194 $individualTaxAmount = 0;
196 //display tax amount on confirmation page
197 $taxAmount +
= $v['tax_amount'];
199 $this->cleanMoneyFields($v);
204 if (isset($v['billing_' . $name]) &&
207 $v[$name] = $v['billing_' . $name];
211 if (!empty($v['first_name']) && !empty($v['last_name'])) {
212 $append = $v['first_name'] . ' ' . $v['last_name'];
215 //use an email if we have one
216 foreach ($v as $v_key => $v_val) {
217 if (substr($v_key, 0, 6) == 'email-') {
218 $append = $v[$v_key];
223 $this->_amount
[$k]['amount'] = $v['amount'];
224 if (!empty($v['discountAmount'])) {
225 $this->_amount
[$k]['amount'] -= $v['discountAmount'];
228 $this->_amount
[$k]['label'] = preg_replace('/\ 1/', '', $v['amount_level']) . ' - ' . $append;
229 $this->_part
[$k]['info'] = CRM_Utils_Array
::value('first_name', $v) . ' ' . CRM_Utils_Array
::value('last_name', $v);
230 if (empty($v['first_name'])) {
231 $this->_part
[$k]['info'] = $append;
235 $individual[$k]['totalAmtWithTax'] = $this->_amount
[$k]['amount'];
236 $individual[$k]['totalTaxAmt'] = $individualTaxAmount +
$v['tax_amount'];
237 $this->_totalAmount
= $this->_totalAmount +
$this->_amount
[$k]['amount'];
238 if (!empty($v['is_primary'])) {
239 $this->set('primaryParticipantAmount', $this->_amount
[$k]['amount']);
244 if (CRM_Invoicing_Utils
::isInvoicingEnabled()) {
245 $this->assign('totalTaxAmount', $taxAmount);
246 $this->assign('taxTerm', CRM_Invoicing_Utils
::getTaxTerm());
247 $this->assign('individual', $individual);
248 $this->set('individual', $individual);
251 $this->assign('part', $this->_part
);
252 $this->set('part', $this->_part
);
253 $this->assign('amounts', $this->_amount
);
254 $this->assign('totalAmount', $this->_totalAmount
);
255 $this->set('totalAmount', $this->_totalAmount
);
258 if ($this->_priceSetId
&& !CRM_Core_DAO
::getFieldValue('CRM_Price_DAO_PriceSet', $this->_priceSetId
, 'is_quick_config')) {
259 $lineItemForTemplate = [];
260 if (!empty($this->_lineItem
) && is_array($this->_lineItem
)) {
261 foreach ($this->_lineItem
as $key => $value) {
262 if (!empty($value)) {
263 $lineItemForTemplate[$key] = $value;
267 if (!empty($lineItemForTemplate)) {
268 $this->assignLineItemsToTemplate($lineItemForTemplate);
272 //display additional participants profile.
273 self
::assignProfiles($this);
275 //consider total amount.
276 $this->assign('isAmountzero', $this->_totalAmount
<= 0);
278 $contribButton = ts('Continue');
282 'name' => ts('Go Back'),
286 'name' => $contribButton,
293 if (!empty($this->_fields
)) {
294 foreach ($this->_fields
as $name => $dontCare) {
298 $fields["billing_state_province-{$this->_bltID}"] = $fields["billing_country-{$this->_bltID}"] = $fields["email-{$this->_bltID}"] = 1;
299 foreach ($fields as $name => $dontCare) {
300 if (isset($this->_params
[0][$name])) {
301 $defaults[$name] = $this->_params
[0][$name];
302 if (substr($name, 0, 7) == 'custom_') {
303 $timeField = "{$name}_time";
304 if (isset($this->_params
[0][$timeField])) {
305 $defaults[$timeField] = $this->_params
[0][$timeField];
307 if (isset($this->_params
[0]["{$name}_id"])) {
308 $defaults["{$name}_id"] = $this->_params
[0]["{$name}_id"];
311 elseif (in_array($name, CRM_Contact_BAO_Contact
::$_greetingTypes)
312 && !empty($this->_params
[0][$name . '_custom'])
314 $defaults[$name . '_custom'] = $this->_params
[0][$name . '_custom'];
319 $this->setDefaults($defaults);
322 //lets give meaningful status message, CRM-4320.
323 $this->assign('isOnWaitlist', $this->_allowWaitlist
);
324 $this->assign('isRequireApproval', $this->_requireApproval
);
326 // Assign Participant Count to Lineitem Table
327 $this->assign('pricesetFieldsCount', CRM_Price_BAO_PriceSet
::getPricesetCount($this->_priceSetId
));
328 $this->addFormRule(['CRM_Event_Form_Registration_Confirm', 'formRule'], $this);
334 * @param array $fields
335 * @param array $files
336 * @param CRM_Core_Form $self
340 public static function formRule($fields, $files, $self) {
342 $eventFull = CRM_Event_BAO_Participant
::eventFull($self->_eventId
, FALSE, CRM_Utils_Array
::value('has_waitlist', $self->_values
['event']));
343 if ($eventFull && empty($self->_allowConfirmation
)) {
344 if (empty($self->_allowWaitlist
)) {
345 CRM_Utils_System
::redirect(CRM_Utils_System
::url('civicrm/event/register', "reset=1&id={$self->_eventId}", FALSE, NULL, FALSE, TRUE));
348 $self->_feeBlock
= $self->_values
['fee'];
349 CRM_Event_Form_Registration_Register
::formatFieldsForOptionFull($self);
351 if (!empty($self->_priceSetId
) &&
352 !$self->_requireApproval
&& !$self->_allowWaitlist
354 $priceSetErrors = self
::validatePriceSet($self, $self->_params
);
355 if (!empty($priceSetErrors)) {
356 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');
357 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');
358 CRM_Utils_System
::redirect(CRM_Utils_System
::url('civicrm/event/register', "_qf_Register_display=true&qfKey={$fields['qfKey']}"));
362 return empty($priceSetErrors) ?
TRUE : $priceSetErrors;
366 * Process the form submission.
368 * @throws \CiviCRM_API3_Exception
369 * @throws \CRM_Core_Exception
371 public function postProcess() {
372 $now = date('YmdHis');
374 $this->_params
= $this->get('params');
375 $this->cleanMoneyFields($this->_params
);
377 if (!empty($this->_params
[0]['contact_id'])) {
378 // unclear when this would be set & whether it could be checked in getContactID.
379 // perhaps it relates to when cid is in the url
380 //@todo someone who knows add comments on the various contactIDs in this form
381 $contactID = $this->_params
[0]['contact_id'];
384 $contactID = $this->getContactID();
387 // if a discount has been applied, lets now deduct it from the amount
388 // and fix the fee level
389 if (!empty($this->_params
[0]['discount']) && !empty($this->_params
[0]['discount']['applied'])) {
390 foreach ($this->_params
as $k => $v) {
391 if (CRM_Utils_Array
::value('amount', $this->_params
[$k]) > 0 && !empty($this->_params
[$k]['discountAmount'])) {
392 $this->_params
[$k]['amount'] -= $this->_params
[$k]['discountAmount'];
393 $this->_params
[$k]['amount_level'] .= CRM_Utils_Array
::value('discountMessage', $this->_params
[$k]);
396 $this->set('params', $this->_params
);
399 // CRM-4320, lets build array of cancelled additional participant ids
400 // those are drop or skip by primary at the time of confirmation.
401 // get all in and then unset those we want to process.
402 $cancelledIds = $this->_additionalParticipantIds
;
404 $params = $this->_params
;
405 if ($this->_values
['event']['is_monetary']) {
406 $this->set('finalAmount', $this->_amount
);
408 $participantCount = [];
411 //unset the skip participant from params.
412 //build the $participantCount array.
413 //maintain record for all participants.
414 foreach ($params as $participantNum => $record) {
415 if ($record == 'skip') {
416 unset($params[$participantNum]);
417 $participantCount[$participantNum] = 'skip';
419 elseif ($participantNum) {
420 $participantCount[$participantNum] = 'participant';
422 $totalTaxAmount +
= $record['tax_amount'] ??
0;
423 if (!empty($record['is_primary'])) {
424 $taxAmount = &$params[$participantNum]['tax_amount'];
426 //lets get additional participant id to cancel.
427 if ($this->_allowConfirmation
&& is_array($cancelledIds)) {
428 $additionalId = $record['participant_id'] ??
NULL;
429 if ($additionalId && $key = array_search($additionalId, $cancelledIds)) {
430 unset($cancelledIds[$key]);
434 $taxAmount = $totalTaxAmount;
435 $payment = $registerByID = $primaryCurrencyID = $contribution = NULL;
436 $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.');
439 foreach ($params as $key => $value) {
440 CRM_Event_Form_Registration_Confirm
::fixLocationFields($value, $fields, $this);
441 if ($this->_allowWaitlist
442 ||
$this->_requireApproval
443 ||
(!empty($value['is_pay_later']) && !$this->_isBillingAddressRequiredForPayLater
)
444 ||
empty($value['is_primary'])
446 // This is confusing because unnecessary code around it has been removed. It is not
447 // clear why we do this / whether we should.
448 if (!empty($value['is_pay_later'])) {
449 $this->_values
['params']['is_pay_later'] = TRUE;
453 //Unset ContactID for additional participants and set RegisterBy Id.
454 if (empty($value['is_primary'])) {
455 $contactID = $value['contact_id'] ??
NULL;
456 $registerByID = $this->get('registerByID');
458 $value['registered_by_id'] = $registerByID;
462 $value['amount'] = $this->_totalAmount
;
465 $contactID = CRM_Event_Form_Registration_Confirm
::updateContactFields($contactID, $value, $fields, $this);
467 // lets store the contactID in the session
468 // we dont store in userID in case the user is doing multiple
470 // for things like tell a friend
471 if (!$this->getContactID() && !empty($value['is_primary'])) {
472 CRM_Core_Session
::singleton()->set('transaction.userID', $contactID);
475 $value['description'] = ts('Online Event Registration') . ': ' . $this->_values
['event']['title'];
476 $value['accountingCode'] = CRM_Utils_Array
::value('accountingCode',
477 $this->_values
['event']
481 if ($this->_allowWaitlist ||
$this->_requireApproval
) {
482 //get the participant statuses.
483 $waitingStatuses = CRM_Event_PseudoConstant
::participantStatus(NULL, "class = 'Waiting'");
484 if ($this->_allowWaitlist
) {
485 $value['participant_status_id'] = $value['participant_status'] = array_search('On waitlist', $waitingStatuses);
488 $value['participant_status_id'] = $value['participant_status'] = array_search('Awaiting approval', $waitingStatuses);
491 //there might be case user selected pay later and
492 //now becomes part of run time waiting list.
493 $value['is_pay_later'] = FALSE;
495 elseif ($this->_values
['event']['is_monetary']) {
496 // required only if paid event
497 if (is_array($this->_paymentProcessor
)) {
498 $payment = $this->_paymentProcessor
['object'];
500 if (!empty($this->_paymentProcessor
) && $this->_paymentProcessor
['object']->supports('preApproval')) {
501 $preApprovalParams = $this->_paymentProcessor
['object']->getPreApprovalDetails($this->get('pre_approval_parameters'));
502 $value = array_merge($value, $preApprovalParams);
506 if (!empty($value['is_pay_later']) ||
507 $value['amount'] == 0 ||
508 // The concept of contributeMode is deprecated.
509 $this->_contributeMode
== 'checkout' ||
510 $this->_contributeMode
== 'notify'
512 if ($value['amount'] != 0) {
514 //get the participant statuses.
515 $pendingStatuses = CRM_Event_PseudoConstant
::participantStatus(NULL, "class = 'Pending'");
516 $status = !empty($value['is_pay_later']) ?
'Pending from pay later' : 'Pending from incomplete transaction';
517 $value['participant_status_id'] = $value['participant_status'] = array_search($status, $pendingStatuses);
520 elseif (!empty($value['is_primary'])) {
521 CRM_Core_Payment_Form
::mapParams($this->_bltID
, $value, $value, TRUE);
522 // payment email param can be empty for _bltID mapping
523 // thus provide mapping for it with a different email value
524 if (empty($value['email'])) {
525 $value['email'] = CRM_Utils_Array
::valueByRegexKey('/^email-/', $value);
528 // If registering from waitlist participant_id is set but contact_id is not.
529 // We need a contact ID to process the payment so set the "primary" contact ID.
530 if (empty($value['contact_id'])) {
531 $value['contact_id'] = $contactID;
534 if (is_object($payment)) {
535 // Not quite sure why we don't just user $value since it contains the data
537 // @todo ditch $result & retest.
538 list($result, $value) = $this->processPayment($payment, $value);
541 throw new CRM_Core_Exception($paymentObjError);
545 $value['receive_date'] = $now;
546 if ($this->_allowConfirmation
) {
547 $value['participant_register_date'] = $this->_values
['participant']['register_date'];
550 $createContrib = $value['amount'] != 0;
551 // force to create zero amount contribution, CRM-5095
552 if (!$createContrib && ($value['amount'] == 0)
553 && $this->_priceSetId
&& $this->_lineItem
555 $createContrib = TRUE;
558 if ($createContrib && !empty($value['is_primary']) &&
559 !$this->_allowWaitlist
&& !$this->_requireApproval
561 // if paid event add a contribution record
562 //if primary participant contributing additional amount
563 //append (multiple participants) to its fee level. CRM-4196.
564 if (count($params) > 1) {
565 $value['amount_level'] .= ts(' (multiple participants)') . CRM_Core_DAO
::VALUE_SEPARATOR
;
568 //passing contribution id is already registered.
569 $contribution = $this->processContribution($value, $result, $contactID, $pending);
570 $value['contributionID'] = $contribution->id
;
571 $value['receive_date'] = $contribution->receive_date
;
572 $value['trxn_id'] = $contribution->trxn_id
;
573 $value['contributionID'] = $contribution->id
;
575 $value['contactID'] = $contactID;
576 $value['eventID'] = $this->_eventId
;
577 $value['item_name'] = $value['description'];
580 if (!empty($value['contributionID'])) {
581 $this->_values
['contributionId'] = $value['contributionID'];
585 if (!empty($value['is_primary'])) {
586 $primaryCurrencyID = $value['currencyID'] ??
NULL;
588 if (empty($value['currencyID'])) {
589 $value['currencyID'] = $primaryCurrencyID;
592 // CRM-11182 - Confirmation page might not be monetary
593 if ($this->_values
['event']['is_monetary']) {
594 if (!$pending && !empty($value['is_primary']) &&
595 !$this->_allowWaitlist
&& !$this->_requireApproval
597 // transactionID & receive date required while building email template
598 $this->assign('trxn_id', CRM_Utils_Array
::value('trxn_id', $value));
599 $this->assign('receive_date', CRM_Utils_Date
::mysqlToIso(CRM_Utils_Array
::value('receive_date', $value)));
600 $this->set('receiveDate', CRM_Utils_Date
::mysqlToIso(CRM_Utils_Array
::value('receive_date', $value)));
601 $this->set('trxnId', CRM_Utils_Array
::value('trxn_id', $value));
605 $value['fee_amount'] = $value['amount'] ??
NULL;
606 $this->set('value', $value);
608 // handle register date CRM-4320
609 if ($this->_allowConfirmation
) {
610 $registerDate = $params['participant_register_date'] ??
NULL;
612 elseif (!empty($params['participant_register_date']) &&
613 is_array($params['participant_register_date'])
615 $registerDate = CRM_Utils_Date
::format($params['participant_register_date']);
618 $registerDate = date('YmdHis');
620 $this->assign('register_date', $registerDate);
622 $this->confirmPostProcess($contactID, $contribution);
625 //handle if no additional participant.
626 if (!$registerByID) {
627 $registerByID = $this->get('registerByID');
630 $this->set('participantIDs', $this->_participantIDS
);
632 // create line items, CRM-5313
633 if ($this->_priceSetId
&&
634 !empty($this->_lineItem
)
636 // take all processed participant ids.
637 $allParticipantIds = $this->_participantIDS
;
639 // when participant re-walk wizard.
640 if ($this->_allowConfirmation
&&
641 !empty($this->_additionalParticipantIds
)
643 $allParticipantIds = array_merge([$registerByID], $this->_additionalParticipantIds
);
646 $entityTable = 'civicrm_participant';
649 foreach ($this->_lineItem
as $key => $value) {
650 if ($value == 'skip') {
653 if ($entityId = CRM_Utils_Array
::value($key, $allParticipantIds)) {
654 // do cleanup line items if participant re-walking wizard.
655 if ($this->_allowConfirmation
) {
656 CRM_Price_BAO_LineItem
::deleteLineItems($entityId, $entityTable);
658 $lineItem[$this->_priceSetId
] = $value;
659 CRM_Price_BAO_LineItem
::processPriceSet($entityId, $lineItem, $contribution, $entityTable);
661 if (CRM_Invoicing_Utils
::isInvoicingEnabled()) {
662 foreach ($value as $line) {
663 if (isset($line['tax_amount']) && isset($line['tax_rate'])) {
664 $totalTaxAmount = $line['tax_amount'] +
$totalTaxAmount;
665 if (isset($dataArray[$line['tax_rate']])) {
666 $dataArray[$line['tax_rate']] = $dataArray[$line['tax_rate']] + CRM_Utils_Array
::value('tax_amount', $line);
669 $dataArray[$line['tax_rate']] = $line['tax_amount'] ??
NULL;
675 $this->assign('dataArray', $dataArray);
676 $this->assign('totalTaxAmount', $totalTaxAmount);
679 //update status and send mail to cancelled additional participants, CRM-4320
680 if ($this->_allowConfirmation
&& is_array($cancelledIds) && !empty($cancelledIds)) {
681 $cancelledId = array_search('Cancelled',
682 CRM_Event_PseudoConstant
::participantStatus(NULL, "class = 'Negative'")
684 CRM_Event_BAO_Participant
::transitionParticipants($cancelledIds, $cancelledId);
688 if ($this->_action
& CRM_Core_Action
::PREVIEW
) {
692 $primaryParticipant = $this->get('primaryParticipant');
694 if (empty($primaryParticipant['participantID'])) {
695 CRM_Core_Error
::deprecatedFunctionWarning('This line is not logically reachable.');
696 $primaryParticipant['participantID'] = $registerByID;
698 //otherwise send mail Confirmation/Receipt
699 $primaryContactId = $this->get('primaryContactId');
701 // for Transfer checkout.
702 // The concept of contributeMode is deprecated.
703 if (($this->_contributeMode
== 'checkout' ||
704 $this->_contributeMode
== 'notify'
705 ) && empty($params[0]['is_pay_later']) &&
706 !$this->_allowWaitlist
&& !$this->_requireApproval
&&
707 $this->_totalAmount
> 0
710 //build an array of custom profile and assigning it to template
711 $customProfile = CRM_Event_BAO_Event
::buildCustomProfile($registerByID, $this->_values
, NULL, $isTest);
712 if (count($customProfile)) {
713 $this->assign('customProfile', $customProfile);
714 $this->set('customProfile', $customProfile);
717 // do a transfer only if a monetary payment greater than 0
718 if ($this->_values
['event']['is_monetary'] && $primaryParticipant) {
719 if ($payment && is_object($payment)) {
720 //CRM 14512 provide line items of all participants to payment gateway
721 $primaryContactId = $this->get('primaryContactId');
723 //build an array of cId/pId of participants
724 $additionalIDs = CRM_Event_BAO_Event
::buildCustomProfile($registerByID, NULL, $primaryContactId, $isTest, TRUE);
726 //need to copy, since we are unsetting on the way.
727 $copyParticipantCountLines = $participantCount;
729 //lets carry all participant params w/ values.
730 foreach ($additionalIDs as $participantID => $contactId) {
731 $participantNum = $participantID;
732 if ($participantID == $registerByID) {
733 // This is the is primary participant.
737 if ($participantNum = array_search('participant', $copyParticipantCountLines)) {
738 //if no participant found break.
739 if ($participantNum === NULL) {
742 //unset current participant so we don't check them again
743 unset($copyParticipantCountLines[$participantNum]);
746 // get values of line items
747 if ($this->_amount
) {
749 $amount[$participantNum]['label'] = preg_replace('/\ 1/', '', $params[$participantNum]['amount_level']);
750 $amount[$participantNum]['amount'] = $params[$participantNum]['amount'];
751 $params[$participantNum]['amounts'] = $amount;
754 if (!empty($this->_lineItem
)) {
755 $lineItems = $this->_lineItem
;
757 if ($lineItemValue = CRM_Utils_Array
::value($participantNum, $lineItems)) {
758 $lineItem[] = $lineItemValue;
760 $params[$participantNum]['lineItem'] = $lineItem;
763 //only add additional participants and not the primary participant as we already have that
764 //added to $primaryParticipant so that this change doesn't break or require changes to
765 //existing gateway implementations
766 $primaryParticipant['participants_info'][$participantID] = $params[$participantNum];
769 //get event custom field information
770 $groupTree = CRM_Core_BAO_CustomGroup
::getTree('Event', NULL, $this->_eventId
, 0, $this->_values
['event']['event_type_id']);
771 $primaryParticipant['eventCustomFields'] = $groupTree;
773 // call postprocess hook before leaving
774 $this->postProcessHook();
776 $this->processPayment($payment, $primaryParticipant);
779 throw new CRM_Core_Exception($paymentObjError);
785 //build an array of cId/pId of participants
786 $additionalIDs = CRM_Event_BAO_Event
::buildCustomProfile($registerByID,
787 NULL, $primaryContactId, $isTest,
790 //let's send mails to all with meaningful text, CRM-4320.
791 $this->assign('isOnWaitlist', $this->_allowWaitlist
);
792 $this->assign('isRequireApproval', $this->_requireApproval
);
794 //need to copy, since we are unsetting on the way.
795 $copyParticipantCount = $participantCount;
797 //let's carry all participant params w/ values.
798 foreach ($additionalIDs as $participantID => $contactId) {
799 $participantNum = NULL;
800 if ($participantID == $registerByID) {
804 if ($participantNum = array_search('participant', $copyParticipantCount)) {
805 unset($copyParticipantCount[$participantNum]);
808 if ($participantNum === NULL) {
812 //carry the participant submitted values.
813 $this->_values
['params'][$participantID] = $params[$participantNum];
816 foreach ($additionalIDs as $participantID => $contactId) {
818 if ($participantID == $registerByID) {
819 //set as Primary Participant
820 $this->assign('isPrimary', 1);
821 //build an array of custom profile and assigning it to template.
822 $customProfile = CRM_Event_BAO_Event
::buildCustomProfile($participantID, $this->_values
, NULL, $isTest);
824 if (count($customProfile)) {
825 $this->assign('customProfile', $customProfile);
826 $this->set('customProfile', $customProfile);
828 $this->_values
['params']['additionalParticipant'] = FALSE;
831 //take the Additional participant number.
832 if ($participantNum = array_search('participant', $participantCount)) {
833 unset($participantCount[$participantNum]);
835 // Change $this->_values['participant'] to include additional participant values
836 $ids = $participantValues = [];
837 $participantParams = ['id' => $participantID];
838 CRM_Event_BAO_Participant
::getValues($participantParams, $participantValues, $ids);
839 $this->_values
['participant'] = $participantValues[$participantID];
841 $this->assign('isPrimary', 0);
842 $this->assign('customProfile', NULL);
843 //Additional Participant should get only it's payment information
844 if (!empty($this->_amount
)) {
846 $params = $this->get('params');
847 $amount[$participantNum]['label'] = preg_replace('/\ 1/', '', $params[$participantNum]['amount_level']);
848 $amount[$participantNum]['amount'] = $params[$participantNum]['amount'];
849 $this->assign('amounts', $amount);
851 if ($this->_lineItem
) {
852 $lineItems = $this->_lineItem
;
854 if ($lineItemValue = CRM_Utils_Array
::value($participantNum, $lineItems)) {
855 $lineItem[] = $lineItemValue;
857 if (CRM_Invoicing_Utils
::isInvoicingEnabled()) {
858 $individual = $this->get('individual');
859 $dataArray[key($dataArray)] = $individual[$participantNum]['totalTaxAmt'];
860 $this->assign('dataArray', $dataArray);
861 $this->assign('totalAmount', $individual[$participantNum]['totalAmtWithTax']);
862 $this->assign('totalTaxAmount', $individual[$participantNum]['totalTaxAmt']);
863 $this->assign('individual', [$individual[$participantNum]]);
865 $this->assign('lineItem', $lineItem);
867 $this->_values
['params']['additionalParticipant'] = TRUE;
868 $this->assign('isAdditionalParticipant', $this->_values
['params']['additionalParticipant']);
871 //pass these variables since these are run time calculated.
872 $this->_values
['params']['isOnWaitlist'] = $this->_allowWaitlist
;
873 $this->_values
['params']['isRequireApproval'] = $this->_requireApproval
;
875 //send mail to primary as well as additional participants.
876 CRM_Event_BAO_Event
::sendMail($contactId, $this->_values
, $participantID, $isTest);
882 * Process the contribution.
884 * @param array $params
885 * @param array $result
886 * @param int $contactID
887 * @param bool $pending
889 * @return \CRM_Contribute_BAO_Contribution
891 * @throws \CRM_Core_Exception
892 * @throws \CiviCRM_API3_Exception
894 private function processContribution(
895 $params, $result, $contactID,
899 // Note this used to be shared with the backoffice form & no longer is, some code may no longer be required.
900 $transaction = new CRM_Core_Transaction();
902 $now = date('YmdHis');
905 if (!empty($form->_values
['event']['is_email_confirm'])) {
909 // CRM-20264: fetch CC type ID and number (last 4 digit) and assign it back to $params
910 CRM_Contribute_Form_AbstractEditPayment
::formatCreditCardDetails($params);
913 'contact_id' => $contactID,
914 'financial_type_id' => !empty($form->_values
['event']['financial_type_id']) ?
$form->_values
['event']['financial_type_id'] : $params['financial_type_id'],
915 'receive_date' => $now,
916 'total_amount' => $params['amount'],
917 'tax_amount' => $params['tax_amount'],
918 'amount_level' => $params['amount_level'],
919 'invoice_id' => $params['invoiceID'],
920 'currency' => $params['currencyID'],
921 'source' => !empty($params['participant_source']) ?
$params['participant_source'] : $params['description'],
922 'is_pay_later' => CRM_Utils_Array
::value('is_pay_later', $params, 0),
923 'campaign_id' => $params['campaign_id'] ??
NULL,
924 'card_type_id' => $params['card_type_id'] ??
NULL,
925 'pan_truncation' => $params['pan_truncation'] ??
NULL,
926 // The ternary is probably redundant - paymentProcessor should always be set.
927 // For pay-later contributions it will be the pay-later processor.
928 'payment_processor' => $this->_paymentProcessor ?
$this->_paymentProcessor
['id'] : NULL,
929 'payment_instrument_id' => $this->_paymentProcessor ?
$this->_paymentProcessor
['payment_instrument_id'] : NULL,
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 // sudoman hack: re-insert filtered group memberships
1108 $params = CRM_Contact_Form_Edit_TagsAndGroups
::reInsertFilteredGroupMemberships($form->get('id'), 'event', $contactID, TRUE, $params);
1110 $contactID = CRM_Contact_BAO_Contact
::createProfileContact(
1122 foreach (CRM_Contact_BAO_Contact
::$_greetingTypes as $greeting) {
1123 if (!isset($params[$greeting . '_id'])) {
1124 $params[$greeting . '_id'] = CRM_Contact_BAO_Contact_Utils
::defaultGreeting('Individual', $greeting);
1128 $contactID = CRM_Contact_BAO_Contact
::createProfileContact($params,
1136 $form->set('contactID', $contactID);
1139 //get email primary first if exist
1140 $subscriptionEmail = ['email' => CRM_Utils_Array
::value('email-Primary', $params)];
1141 if (!$subscriptionEmail['email']) {
1142 $subscriptionEmail['email'] = $params["email-{$form->_bltID}"] ??
NULL;
1144 // subscribing contact to groups
1145 if (!empty($subscribeGroupIds) && $subscriptionEmail['email']) {
1146 CRM_Mailing_Event_BAO_Subscribe
::commonSubscribe($subscribeGroupIds, $subscriptionEmail, $contactID);
1153 * Assign Profiles to the template.
1155 * @param CRM_Event_Form_Registration_Confirm $form
1157 * @throws \Exception
1159 public static function assignProfiles($form) {
1160 $participantParams = $form->_params
;
1161 $formattedValues = $profileFields = [];
1163 foreach ($participantParams as $participantNum => $participantValue) {
1164 if ($participantNum) {
1165 $prefix1 = 'additional';
1166 $prefix2 = 'additional_';
1172 if ($participantValue !== 'skip') {
1173 //get the customPre profile info
1174 if (!empty($form->_values
[$prefix2 . 'custom_pre_id'])) {
1175 $values = $groupName = [];
1176 CRM_Event_BAO_Event
::displayProfile($participantValue,
1177 $form->_values
[$prefix2 . 'custom_pre_id'],
1183 if (count($values)) {
1184 $formattedValues[$count][$prefix1 . 'CustomPre'] = $values;
1186 $formattedValues[$count][$prefix1 . 'CustomPreGroupTitle'] = $groupName['groupTitle'] ??
NULL;
1188 //get the customPost profile info
1189 if (!empty($form->_values
[$prefix2 . 'custom_post_id'])) {
1190 $values = $groupName = [];
1191 foreach ($form->_values
[$prefix2 . 'custom_post_id'] as $gids) {
1193 CRM_Event_BAO_Event
::displayProfile($participantValue,
1199 $values[$gids] = $val;
1200 $groupName[$gids] = $group;
1203 if (count($values)) {
1204 $formattedValues[$count][$prefix1 . 'CustomPost'] = $values;
1207 if (isset($formattedValues[$count][$prefix1 . 'CustomPre'])) {
1208 $formattedValues[$count][$prefix1 . 'CustomPost'] = array_diff_assoc($formattedValues[$count][$prefix1 . 'CustomPost'],
1209 $formattedValues[$count][$prefix1 . 'CustomPre']
1213 $formattedValues[$count][$prefix1 . 'CustomPostGroupTitle'] = $groupName;
1217 $form->_fields
= $profileFields;
1219 if (!empty($formattedValues)) {
1220 $form->assign('primaryParticipantProfile', $formattedValues[1]);
1221 $form->set('primaryParticipantProfile', $formattedValues[1]);
1223 unset($formattedValues[1]);
1224 $form->assign('addParticipantProfile', $formattedValues);
1225 $form->set('addParticipantProfile', $formattedValues);
1231 * Submit in test mode.
1233 * Do not use this - we have figured out how to emulate form in tests now.
1241 public static function testSubmit($params) {
1242 $form = new CRM_Event_Form_Registration_Confirm();
1243 // This way the mocked up controller ignores the session stuff.
1244 $_SERVER['REQUEST_METHOD'] = 'GET';
1245 $_REQUEST['id'] = $form->_eventId
= $params['id'];
1246 $form->controller
= new CRM_Event_Controller_Registration();
1247 $form->_params
= $params['params'];
1248 // This happens in buildQuickForm so emulate here.
1249 $form->_amount
= $form->_totalAmount
= $params['totalAmount'] ??
0;
1250 $form->set('params', $params['params']);
1251 $form->_values
['custom_pre_id'] = $params['custom_pre_id'] ??
NULL;
1252 $form->_values
['custom_post_id'] = $params['custom_post_id'] ??
NULL;
1253 $form->_values
['event'] = $params['event'] ??
NULL;
1254 $form->_contributeMode
= $params['contributeMode'];
1255 $eventParams = ['id' => $params['id']];
1256 CRM_Event_BAO_Event
::retrieve($eventParams, $form->_values
['event']);
1257 $form->set('registerByID', $params['registerByID']);
1258 if (!empty($params['paymentProcessorObj'])) {
1259 $form->_paymentProcessor
= $params['paymentProcessorObj'];
1261 $form->postProcess();
1266 * Process the payment, redirecting back to the page on error.
1268 * @param \CRM_Core_Payment $payment
1273 private function processPayment($payment, $value) {
1275 $params = $this->prepareParamsForPaymentProcessor($value);
1276 $result = $payment->doPayment($params, 'event');
1277 return [$result, $value];
1279 catch (\Civi\Payment\Exception\PaymentProcessorException
$e) {
1280 Civi
::log()->error('Payment processor exception: ' . $e->getMessage());
1281 CRM_Core_Session
::singleton()->setStatus($e->getMessage());
1282 CRM_Utils_System
::redirect(CRM_Utils_System
::url('civicrm/event/register', "id={$this->_eventId}"));
1288 * Clean money fields from the form.
1290 * @param array $params
1292 protected function cleanMoneyFields(&$params) {
1293 foreach ($this->submittableMoneyFields
as $moneyField) {
1294 foreach ($params as $index => $paramField) {
1295 if (isset($paramField[$moneyField])) {
1296 $params[$index][$moneyField] = CRM_Utils_Rule
::cleanMoney($paramField[$moneyField]);