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_Register
extends CRM_Event_Form_Registration
{
23 * The fields involved in this page.
30 * The status message that user view.
34 protected $_waitlistMsg;
35 protected $_requireApprovalMsg;
38 * Deprecated parameter that we hope to remove.
45 * Skip duplicate check.
47 * This can be set using hook_civicrm_buildForm() to override the registration dupe check.
50 * @see https://issues.civicrm.org/jira/browse/CRM-7604
52 public $_skipDupeRegistrationCheck = FALSE;
54 public $_paymentProcessorID;
57 * Show fee block or not.
71 * Array of payment related fields to potentially display on this form (generally credit card or debit card fields).
73 * This is rendered via billingBlock.tpl.
77 public $_paymentFields = [];
80 * Is this submission incurring no costs.
82 * @param array $fields
83 * @param \CRM_Event_Form_Registration_Register $form
87 protected static function isZeroAmount($fields, $form): bool {
88 $isZeroAmount = FALSE;
89 if (!empty($fields['priceSetId'])) {
90 if (empty($fields['amount'])) {
94 elseif (!empty($fields['amount']) &&
95 (isset($form->_values
['discount'][$fields['amount']])
96 && CRM_Utils_Array
::value('value', $form->_values
['discount'][$fields['amount']]) == 0
101 elseif (!empty($fields['amount']) &&
102 (isset($form->_values
['fee'][$fields['amount']])
103 && CRM_Utils_Array
::value('value', $form->_values
['fee'][$fields['amount']]) == 0
106 $isZeroAmount = TRUE;
108 return $isZeroAmount;
112 * Get the contact id for the registration.
114 * @param array $fields
115 * @param CRM_Event_Form_Registration $form
116 * @param bool $isAdditional
120 public static function getRegistrationContactID($fields, $form, $isAdditional) {
122 if (!$isAdditional) {
123 $contactID = $form->getContactID();
125 if (!$contactID && is_array($fields) && $fields) {
126 $contactID = CRM_Contact_BAO_Contact
::getFirstDuplicateContact($fields, 'Individual', 'Unsupervised', [], FALSE, CRM_Utils_Array
::value('dedupe_rule_group_id', $form->_values
['event']), ['event_id' => CRM_Utils_Array
::value('id', $form->_values
['event'])]);
132 * Get the active UFGroups (profiles) on this form
133 * Many forms load one or more UFGroups (profiles).
134 * This provides a standard function to retrieve the IDs of those profiles from the form
135 * so that you can implement things such as "is is_captcha field set on any of the active profiles on this form?"
137 * NOT SUPPORTED FOR USE OUTSIDE CORE EXTENSIONS - Added for reCAPTCHA core extension.
141 public function getUFGroupIDs() {
143 if (!empty($this->_values
['custom_pre_id'])) {
144 $ufGroupIDs[] = $this->_values
['custom_pre_id'];
146 if (!empty($this->_values
['custom_post_id'])) {
147 // custom_post_id can be an array (because we can have multiple for events).
148 // It is handled as array for contribution page as well though they don't support multiple profiles.
149 if (!is_array($this->_values
['custom_post_id'])) {
150 $ufGroupIDs[] = $this->_values
['custom_post_id'];
153 $ufGroupIDs = array_merge($ufGroupIDs, $this->_values
['custom_post_id']);
160 * Set variables up before form is built.
162 * @throws \CRM_Core_Exception
164 public function preProcess() {
165 parent
::preProcess();
168 //here we can't use parent $this->_allowWaitlist as user might
169 //walk back and we might set this value in this postProcess.
170 //(we set when spaces < group count and want to allow become part of waiting )
171 $eventFull = CRM_Event_BAO_Participant
::eventFull($this->_eventId
, FALSE, CRM_Utils_Array
::value('has_waitlist', $this->_values
['event']));
173 // Get payment processors if appropriate for this event
174 // We hide the payment fields if the event is full or requires approval,
175 // and the current user has not yet been approved CRM-12279
176 $this->_noFees
= (($eventFull ||
$this->_requireApproval
) && !$this->_allowConfirmation
);
177 $this->_paymentProcessors
= $this->_noFees ?
[] : $this->get('paymentProcessors');
178 $this->preProcessPaymentOptions();
180 $this->_allowWaitlist
= FALSE;
181 if ($eventFull && !$this->_allowConfirmation
&& !empty($this->_values
['event']['has_waitlist'])) {
182 $this->_allowWaitlist
= TRUE;
183 $this->_waitlistMsg
= $this->_values
['event']['waitlist_text'] ??
NULL;
184 if (!$this->_waitlistMsg
) {
185 $this->_waitlistMsg
= ts('This event is currently full. However you can register now and get added to a waiting list. You will be notified if spaces become available.');
188 $this->set('allowWaitlist', $this->_allowWaitlist
);
190 //To check if the user is already registered for the event(CRM-2426)
191 if (!$this->_skipDupeRegistrationCheck
) {
192 self
::checkRegistration(NULL, $this);
195 $this->assign('availableRegistrations', $this->_availableRegistrations
);
197 // get the participant values from EventFees.php, CRM-4320
198 if ($this->_allowConfirmation
) {
199 CRM_Event_Form_EventFees
::preProcess($this);
204 * Set default values for the form.
208 * @throws \CRM_Core_Exception
209 * @throws \CiviCRM_API3_Exception
211 public function setDefaultValues() {
212 $this->_defaults
= [];
213 if (!$this->_allowConfirmation
&& $this->_requireApproval
) {
214 $this->_defaults
['bypass_payment'] = 1;
216 $contactID = $this->getContactID();
217 CRM_Core_Payment_Form
::setDefaultValues($this, $contactID);
219 CRM_Event_BAO_Participant
::formatFieldsAndSetProfileDefaults($contactID, $this);
221 // Set default payment processor as default payment_processor radio button value
222 if (!empty($this->_paymentProcessors
)) {
223 foreach ($this->_paymentProcessors
as $pid => $value) {
224 if (!empty($value['is_default'])) {
225 $this->_defaults
['payment_processor_id'] = $pid;
230 //if event is monetary and pay later is enabled and payment
231 //processor is not available then freeze the pay later checkbox with
233 if (!empty($this->_values
['event']['is_pay_later']) &&
234 !is_array($this->_paymentProcessor
)
236 $this->_defaults
['is_pay_later'] = 1;
239 //set custom field defaults
240 if (!empty($this->_fields
)) {
241 //load default campaign from page.
242 if (array_key_exists('participant_campaign_id', $this->_fields
)) {
243 $this->_defaults
['participant_campaign_id'] = CRM_Utils_Array
::value('campaign_id',
244 $this->_values
['event']
248 foreach ($this->_fields
as $name => $field) {
249 if ($customFieldID = CRM_Core_BAO_CustomField
::getKeyID($name)) {
251 if (!isset($this->_defaults
[$name])) {
252 CRM_Core_BAO_CustomField
::setProfileDefaults($customFieldID, $name, $this->_defaults
,
253 NULL, CRM_Profile_Form
::MODE_REGISTER
260 //fix for CRM-3088, default value for discount set.
262 if (!empty($this->_values
['discount'])) {
263 $discountId = CRM_Core_BAO_Discount
::findSet($this->_eventId
, 'civicrm_event');
265 if (isset($this->_values
['event']['default_discount_fee_id'])) {
266 $discountKey = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_OptionValue',
267 $this->_values
['event']['default_discount_fee_id'],
271 $this->_defaults
['amount'] = key(array_slice($this->_values
['discount'][$discountId],
272 $discountKey - 1, $discountKey, TRUE
278 // add this event's default participant role to defaults array
279 // (for cases where participant_role field is included in form via profile)
280 if ($this->_values
['event']['default_role_id']) {
281 $this->_defaults
['participant_role']
282 = $this->_defaults
['participant_role_id'] = $this->_values
['event']['default_role_id'];
284 if ($this->_priceSetId
&& !empty($this->_feeBlock
)) {
285 foreach ($this->_feeBlock
as $key => $val) {
286 if (empty($val['options'])) {
289 $optionFullIds = CRM_Utils_Array
::value('option_full_ids', $val, []);
290 foreach ($val['options'] as $keys => $values) {
291 $priceFieldName = 'price_' . $values['price_field_id'];
292 $priceFieldValue = CRM_Price_BAO_PriceSet
::getPriceFieldValueFromURL($this, $priceFieldName);
293 if (!empty($priceFieldValue)) {
294 CRM_Price_BAO_PriceSet
::setDefaultPriceSetField($priceFieldName, $priceFieldValue, $val['html_type'], $this->_defaults
);
295 // break here to prevent overwriting of default due to 'is_default'
296 // option configuration. The value sent via URL get's higher priority.
300 if ($values['is_default'] && empty($values['is_full'])) {
301 if ($val['html_type'] == 'CheckBox') {
302 $this->_defaults
["price_{$key}"][$keys] = 1;
305 $this->_defaults
["price_{$key}"] = $keys;
310 $unsetSubmittedOptions[$val['id']] = $optionFullIds;
312 //reset values for all options those are full.
313 CRM_Event_Form_Registration
::resetElementValue($unsetSubmittedOptions, $this);
316 //set default participant fields, CRM-4320.
317 $hasAdditionalParticipants = FALSE;
318 if ($this->_allowConfirmation
) {
319 $this->_contactId
= $contactID;
320 $this->_discountId
= $discountId;
321 $forcePayLater = CRM_Utils_Array
::value('is_pay_later', $this->_defaults
, FALSE);
322 $this->_defaults
= array_merge($this->_defaults
, CRM_Event_Form_EventFees
::setDefaultValues($this));
323 $this->_defaults
['is_pay_later'] = $forcePayLater;
325 if ($this->_additionalParticipantIds
) {
326 $hasAdditionalParticipants = TRUE;
327 $this->_defaults
['additional_participants'] = count($this->_additionalParticipantIds
);
330 $this->assign('hasAdditionalParticipants', $hasAdditionalParticipants);
332 // //hack to simplify credit card entry for testing
333 // $this->_defaults['credit_card_type'] = 'Visa';
334 // $this->_defaults['credit_card_number'] = '4807731747657838';
335 // $this->_defaults['cvv2'] = '000';
336 // $this->_defaults['credit_card_exp_date'] = array( 'Y' => '2010', 'M' => '05' );
338 // to process Custom data that are appended to URL
339 $getDefaults = CRM_Core_BAO_CustomGroup
::extractGetParams($this, "'Contact', 'Individual', 'Contribution', 'Participant'");
340 if (!empty($getDefaults)) {
341 $this->_defaults
= array_merge($this->_defaults
, $getDefaults);
344 return $this->_defaults
;
348 * Build the form object.
350 public function buildQuickForm() {
351 // build profiles first so that we can determine address fields etc
352 // and then show copy address checkbox
353 $this->buildCustom($this->_values
['custom_pre_id'], 'customPre');
354 $this->buildCustom($this->_values
['custom_post_id'], 'customPost');
356 // CRM-18399: used by template to pass pre profile id as a url arg
357 $this->assign('custom_pre_id', $this->_values
['custom_pre_id']);
359 // Required for currency formatting in the JS layer
361 // Required for currency formatting in the JS layer
362 // this is a temporary fix intended to resolve a regression quickly
363 // And assigning moneyFormat for js layer formatting
364 // will only work until that is done.
365 // https://github.com/civicrm/civicrm-core/pull/19151
366 $this->assign('moneyFormat', CRM_Utils_Money
::format(1234.56));
368 CRM_Core_Payment_ProcessorForm
::buildQuickForm($this);
370 $contactID = $this->getContactID();
372 $this->assign('contact_id', $contactID);
373 $this->assign('display_name', CRM_Contact_BAO_Contact
::displayName($contactID));
376 $bypassPayment = $allowGroupOnWaitlist = $isAdditionalParticipants = FALSE;
377 if ($this->_values
['event']['is_multiple_registrations']) {
378 // don't allow to add additional during confirmation if not preregistered.
379 if (!$this->_allowConfirmation ||
$this->_additionalParticipantIds
) {
380 // CRM-17745: Make maximum additional participants configurable
381 // Label is value + 1, since the code sees this is ADDITIONAL participants (in addition to "self")
382 $additionalOptions = [];
383 $additionalOptions[''] = 1;
384 for ($i = 1; $i <= $this->_values
['event']['max_additional_participants']; $i++
) {
385 $additionalOptions[$i] = $i +
1;
387 $this->add('select', 'additional_participants',
388 ts('How many people are you registering?'),
392 $isAdditionalParticipants = TRUE;
396 if (!$this->_allowConfirmation
) {
397 $bypassPayment = TRUE;
400 //hack to allow group to register w/ waiting
401 if ((!empty($this->_values
['event']['is_multiple_registrations']) ||
404 !$this->_allowConfirmation
&&
405 is_numeric($this->_availableRegistrations
) && !empty($this->_values
['event']['has_waitlist'])
407 $bypassPayment = TRUE;
408 //case might be group become as a part of waitlist.
409 //If not waitlist then they require admin approve.
410 $allowGroupOnWaitlist = TRUE;
411 $this->_waitlistMsg
= ts("This event has only %1 space(s) left. If you continue and register more than %1 people (including yourself ), the whole group will be wait listed. Or, you can reduce the number of people you are registering to %1 to avoid being put on the waiting list.", [1 => $this->_availableRegistrations
]);
413 if ($this->_requireApproval
) {
414 $this->_requireApprovalMsg
= CRM_Utils_Array
::value('approval_req_text', $this->_values
['event'],
415 ts('Registration for this event requires approval. Once your registration(s) have been reviewed, you will receive an email with a link to a web page where you can complete the registration process.')
420 //case where only approval needed - no waitlist.
421 if ($this->_requireApproval
&&
422 !$this->_allowWaitlist
&& !$bypassPayment
424 $this->_requireApprovalMsg
= CRM_Utils_Array
::value('approval_req_text', $this->_values
['event'],
425 ts('Registration for this event requires approval. Once your registration has been reviewed, you will receive an email with a link to a web page where you can complete the registration process.')
429 //lets display status to primary page only.
430 $this->assign('waitlistMsg', $this->_waitlistMsg
);
431 $this->assign('requireApprovalMsg', $this->_requireApprovalMsg
);
432 $this->assign('allowGroupOnWaitlist', $allowGroupOnWaitlist);
433 $this->assign('isAdditionalParticipants', $isAdditionalParticipants);
435 if ($this->_values
['event']['is_monetary']) {
436 self
::buildAmount($this);
439 if ($contactID === 0 && !$this->_values
['event']['is_multiple_registrations']) {
440 //@todo we are blocking for multiple registrations because we haven't tested
441 $this->addCIDZeroOptions();
444 if ($this->_values
['event']['is_monetary']) {
445 $this->addPaymentProcessorFieldsToForm();
448 $this->addElement('hidden', 'bypass_payment', NULL, ['id' => 'bypass_payment']);
450 $this->assign('bypassPayment', $bypassPayment);
453 $createCMSUser = FALSE;
455 if ($this->_values
['custom_pre_id']) {
456 $profileID = $this->_values
['custom_pre_id'];
457 $createCMSUser = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_UFGroup', $profileID, 'is_cms_user');
460 if (!$createCMSUser &&
461 $this->_values
['custom_post_id']
463 if (!is_array($this->_values
['custom_post_id'])) {
464 $profileIDs = [$this->_values
['custom_post_id']];
467 $profileIDs = $this->_values
['custom_post_id'];
469 foreach ($profileIDs as $pid) {
470 if (CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_UFGroup', $pid, 'is_cms_user')) {
472 $createCMSUser = TRUE;
478 if ($createCMSUser) {
479 CRM_Core_BAO_CMSUser
::buildForm($this, $profileID, TRUE);
483 //we have to load confirm contribution button in template
484 //when multiple payment processor as the user
485 //can toggle with payment processor selection
486 $billingModePaymentProcessors = 0;
487 if (!CRM_Utils_System
::isNull($this->_paymentProcessors
)) {
488 foreach ($this->_paymentProcessors
as $key => $values) {
489 if ($values['billing_mode'] == CRM_Core_Payment
::BILLING_MODE_BUTTON
) {
490 $billingModePaymentProcessors++
;
495 if ($billingModePaymentProcessors && count($this->_paymentProcessors
) == $billingModePaymentProcessors) {
496 $allAreBillingModeProcessors = TRUE;
499 $allAreBillingModeProcessors = FALSE;
502 if (!$allAreBillingModeProcessors ||
!empty($this->_values
['event']['is_pay_later']) ||
$bypassPayment
505 //freeze button to avoid multiple calls.
506 if (empty($this->_values
['event']['is_monetary'])) {
507 $this->submitOnce
= TRUE;
510 // CRM-11182 - Optional confirmation screen
511 // Change button label depending on whether the next action is confirm or register
514 'spacing' => ' ',
518 !$this->_values
['event']['is_multiple_registrations']
519 && !$this->_values
['event']['is_monetary']
520 && !$this->_values
['event']['is_confirm_enabled']
522 $buttonParams['name'] = ts('Register');
525 $buttonParams['name'] = ts('Review');
526 $buttonParams['icon'] = 'fa-chevron-right';
529 $this->addButtons([$buttonParams]);
532 $this->addFormRule(['CRM_Event_Form_Registration_Register', 'formRule'], $this);
533 $this->unsavedChangesWarn
= TRUE;
537 CRM_PCP_BAO_PCP
::buildPcp($this->_pcpId
, $this);
542 * Build the radio/text form elements for the amount field
544 * @param CRM_Event_Form_Registration_Register $form
546 * @param bool $required
547 * True if you want to add formRule.
548 * @param int $discountId
549 * Discount id for the event.
551 * @throws \CRM_Core_Exception
553 public static function buildAmount(&$form, $required = TRUE, $discountId = NULL) {
554 // build amount only when needed, skip incase of event full and waitlisting is enabled
555 // and few other conditions check preProcess()
556 if (property_exists($form, '_noFees') && $form->_noFees
) {
560 //if payment done, no need to build the fee block.
561 if (!empty($form->_paymentId
)) {
562 //fix to display line item in update mode.
563 $form->assign('priceSet', $form->_priceSet ??
NULL);
567 $feeFields = $form->_values
['fee'] ??
NULL;
569 if (is_array($feeFields)) {
570 $form->_feeBlock
= &$form->_values
['fee'];
573 //check for discount.
574 $discountedFee = $form->_values
['discount'] ??
NULL;
575 if (is_array($discountedFee) && !empty($discountedFee)) {
577 $form->_discountId
= $discountId = CRM_Core_BAO_Discount
::findSet($form->_eventId
, 'civicrm_event');
580 $form->_feeBlock
= &$form->_values
['discount'][$discountId];
583 if (!is_array($form->_feeBlock
)) {
584 $form->_feeBlock
= [];
587 //its time to call the hook.
588 CRM_Utils_Hook
::buildAmount('event', $form, $form->_feeBlock
);
590 //reset required if participant is skipped.
591 $button = substr($form->controller
->getButtonName(), -4);
592 if ($required && $button == 'skip') {
596 $className = CRM_Utils_System
::getClassName($form);
598 //build the priceset fields.
599 if (isset($form->_priceSetId
) && $form->_priceSetId
) {
601 //format price set fields across option full.
602 self
::formatFieldsForOptionFull($form);
604 if (!empty($form->_priceSet
['is_quick_config'])) {
605 $form->_quickConfig
= $form->_priceSet
['is_quick_config'];
607 $form->add('hidden', 'priceSetId', $form->_priceSetId
);
609 // CRM-14492 Admin price fields should show up on event registration if user has 'administer CiviCRM' permissions
610 $adminFieldVisible = FALSE;
611 if (CRM_Core_Permission
::check('administer CiviCRM data')) {
612 $adminFieldVisible = TRUE;
615 $hideAdminValues = TRUE;
616 if (CRM_Core_Permission
::check('edit event participants')) {
617 $hideAdminValues = FALSE;
620 foreach ($form->_feeBlock
as $field) {
621 // public AND admin visibility fields are included for back-office registration and back-office change selections
622 if (CRM_Utils_Array
::value('visibility', $field) == 'public' ||
623 (CRM_Utils_Array
::value('visibility', $field) == 'admin' && $adminFieldVisible == TRUE) ||
624 $className == 'CRM_Event_Form_Participant' ||
625 $className === 'CRM_Event_Form_Task_Register' ||
626 $className == 'CRM_Event_Form_ParticipantFeeSelection'
628 $fieldId = $field['id'];
629 $elementName = 'price_' . $fieldId;
631 $isRequire = $field['is_required'] ??
NULL;
632 if ($button == 'skip') {
636 //user might modified w/ hook.
637 $options = $field['options'] ??
NULL;
638 $formClasses = ['CRM_Event_Form_Participant', 'CRM_Event_Form_Task_Register', 'CRM_Event_Form_ParticipantFeeSelection'];
640 if (!is_array($options)) {
643 elseif ($hideAdminValues && !in_array($className, $formClasses)) {
644 $publicVisibilityID = CRM_Price_BAO_PriceField
::getVisibilityOptionID('public');
645 $adminVisibilityID = CRM_Price_BAO_PriceField
::getVisibilityOptionID('admin');
647 foreach ($options as $key => $currentOption) {
648 $optionVisibility = CRM_Utils_Array
::value('visibility_id', $currentOption, $publicVisibilityID);
649 if ($optionVisibility == $adminVisibilityID) {
650 unset($options[$key]);
655 $optionFullIds = CRM_Utils_Array
::value('option_full_ids', $field, []);
657 //soft suppress required rule when option is full.
658 if (!empty($optionFullIds) && (count($options) == count($optionFullIds))) {
661 if (!empty($options)) {
663 CRM_Price_BAO_PriceField
::addQuickFormElement($form,
675 $form->_priceSet
['id'] = $form->_priceSet
['id'] ??
$form->_priceSetId
;
676 $form->assign('priceSet', $form->_priceSet
);
679 $eventFeeBlockValues = $elements = $elementJS = [];
680 foreach ($form->_feeBlock
as $fee) {
681 if (is_array($fee)) {
684 $totalAmountJs = NULL;
685 if ($className == 'CRM_Event_Form_Participant' ||
$className === 'CRM_Event_Form_Task_Register') {
686 $totalAmountJs = ['onClick' => "fillTotalAmount(" . $fee['value'] . ")"];
689 $eventFeeBlockValues['amount_id_' . $fee['amount_id']] = $fee['value'];
690 $elements[$fee['amount_id']] = CRM_Utils_Money
::format($fee['value']) . ' ' . $fee['label'];
691 $elementJS[$fee['amount_id']] = $totalAmountJs;
694 $form->assign('eventFeeBlockValues', json_encode($eventFeeBlockValues));
696 $form->_defaults
['amount'] = $form->_values
['event']['default_fee_id'] ??
NULL;
697 $element = &$form->addRadio('amount', ts('Event Fee(s)'), $elements, [], '<br />', FALSE, $elementJS);
698 if (isset($form->_online
) && $form->_online
) {
702 $form->addRule('amount', ts('Fee Level is a required field.'), 'required');
708 * @param CRM_Event_Form_Registration $form
710 public static function formatFieldsForOptionFull(&$form) {
711 $priceSet = $form->get('priceSet');
712 $priceSetId = $form->get('priceSetId');
713 $defaultPricefieldIds = [];
714 if (!empty($form->_values
['line_items'])) {
715 foreach ($form->_values
['line_items'] as $lineItem) {
716 $defaultPricefieldIds[] = $lineItem['price_field_value_id'];
720 !is_array($priceSet) ||
721 empty($priceSet) ||
empty($priceSet['optionsMaxValueTotal'])
726 $skipParticipants = $formattedPriceSetDefaults = [];
727 if (!empty($form->_allowConfirmation
) && (isset($form->_pId
) ||
isset($form->_additionalParticipantId
))) {
728 $participantId = $form->_pId ??
$form->_additionalParticipantId
;
729 $pricesetDefaults = CRM_Event_Form_EventFees
::setDefaultPriceSet($participantId,
732 // modify options full to respect the selected fields
733 // options on confirmation.
734 $formattedPriceSetDefaults = self
::formatPriceSetParams($form, $pricesetDefaults);
736 // to skip current registered participants fields option count on confirmation.
737 $skipParticipants[] = $form->_participantId
;
738 if (!empty($form->_additionalParticipantIds
)) {
739 $skipParticipants = array_merge($skipParticipants, $form->_additionalParticipantIds
);
743 $className = CRM_Utils_System
::getClassName($form);
745 //get the current price event price set options count.
746 $currentOptionsCount = self
::getPriceSetOptionCount($form);
747 $recordedOptionsCount = CRM_Event_BAO_Participant
::priceSetOptionsCount($form->_eventId
, $skipParticipants);
748 $optionFullTotalAmount = 0;
749 $currentParticipantNo = (int) substr($form->_name
, 12);
750 foreach ($form->_feeBlock
as & $field) {
752 $fieldId = $field['id'];
753 if (!is_array($field['options'])) {
756 foreach ($field['options'] as & $option) {
757 $optId = $option['id'];
758 $count = CRM_Utils_Array
::value('count', $option, 0);
759 $maxValue = CRM_Utils_Array
::value('max_value', $option, 0);
760 $dbTotalCount = CRM_Utils_Array
::value($optId, $recordedOptionsCount, 0);
761 $currentTotalCount = CRM_Utils_Array
::value($optId, $currentOptionsCount, 0);
763 $totalCount = $currentTotalCount +
$dbTotalCount;
766 (($totalCount >= $maxValue) &&
767 (empty($form->_lineItem
[$currentParticipantNo][$optId]['price_field_id']) ||
$dbTotalCount >= $maxValue))
770 $optionFullIds[$optId] = $optId;
771 if ($field['html_type'] != 'Select') {
772 if (in_array($optId, $defaultPricefieldIds)) {
773 $optionFullTotalAmount +
= CRM_Utils_Array
::value('amount', $option);
777 if (!empty($defaultPricefieldIds) && in_array($optId, $defaultPricefieldIds)) {
778 unset($optionFullIds[$optId]);
782 //here option is not full,
783 //but we don't want to allow participant to increase
784 //seats at the time of re-walking registration.
786 !empty($form->_allowConfirmation
) &&
787 !empty($formattedPriceSetDefaults)
789 if (empty($formattedPriceSetDefaults["price_{$field}"]) ||
empty($formattedPriceSetDefaults["price_{$fieldId}"][$optId])) {
790 $optionFullIds[$optId] = $optId;
794 $option['is_full'] = $isFull;
795 $option['db_total_count'] = $dbTotalCount;
796 $option['total_option_count'] = $dbTotalCount +
$currentTotalCount;
799 //ignore option full for offline registration.
800 if ($className == 'CRM_Event_Form_Participant' ||
$className === 'CRM_Event_Form_Task_Register') {
804 //finally get option ids in.
805 $field['option_full_ids'] = $optionFullIds;
807 $form->assign('optionFullTotalAmount', $optionFullTotalAmount);
813 * @param array $fields
814 * The input form values.
815 * @param array $files
816 * The uploaded files if any.
817 * @param \CRM_Event_Form_Registration_Register $form
820 * true if no errors, else array of errors
822 * @throws \CRM_Core_Exception
824 public static function formRule($fields, $files, $form) {
826 //check that either an email or firstname+lastname is included in the form(CRM-9587)
827 self
::checkProfileComplete($fields, $errors, $form->_eventId
);
828 //To check if the user is already registered for the event(CRM-2426)
829 if (!$form->_skipDupeRegistrationCheck
) {
830 self
::checkRegistration($fields, $form);
832 //check for availability of registrations.
833 if (!$form->_allowConfirmation
&& empty($fields['bypass_payment']) &&
834 is_numeric($form->_availableRegistrations
) &&
835 CRM_Utils_Array
::value('additional_participants', $fields) >= $form->_availableRegistrations
837 $errors['additional_participants'] = ts("There is only enough space left on this event for %1 participant(s).", [1 => $form->_availableRegistrations
]);
840 $numberAdditionalParticipants = $fields['additional_participants'] ??
0;
842 if ($numberAdditionalParticipants && !CRM_Utils_Rule
::positiveInteger($fields['additional_participants'])) {
843 $errors['additional_participants'] = ts('Please enter a whole number for Number of additional people.');
846 // during confirmation don't allow to increase additional participants, CRM-4320
847 if ($form->_allowConfirmation
&& $numberAdditionalParticipants &&
848 is_array($form->_additionalParticipantIds
) &&
849 $numberAdditionalParticipants > count($form->_additionalParticipantIds
)
851 $errors['additional_participants'] = ts("It looks like you are trying to increase the number of additional people you are registering for. You can confirm registration for a maximum of %1 additional people.", [1 => count($form->_additionalParticipantIds
)]);
854 //don't allow to register w/ waiting if enough spaces available.
855 if (!empty($fields['bypass_payment']) && $form->_allowConfirmation
) {
856 if (!is_numeric($form->_availableRegistrations
) ||
857 (empty($fields['priceSetId']) && CRM_Utils_Array
::value('additional_participants', $fields) < $form->_availableRegistrations
)
859 $errors['bypass_payment'] = ts("You have not been added to the waiting list because there are spaces available for this event. We recommend registering yourself for an available space instead.");
863 // priceset validations
864 if (!empty($fields['priceSetId']) &&
865 !$form->_requireApproval
&& !$form->_allowWaitlist
868 $formatted = self
::formatPriceSetParams($form, $fields);
869 $ppParams = [$formatted];
870 $priceSetErrors = self
::validatePriceSet($form, $ppParams);
871 $primaryParticipantCount = self
::getParticipantCount($form, $ppParams);
873 //get price set fields errors in.
874 $errors = array_merge($errors, CRM_Utils_Array
::value(0, $priceSetErrors, []));
876 $totalParticipants = $primaryParticipantCount;
877 if ($numberAdditionalParticipants) {
878 $totalParticipants +
= $numberAdditionalParticipants;
881 if (empty($fields['bypass_payment']) &&
882 !$form->_allowConfirmation
&&
883 is_numeric($form->_availableRegistrations
) &&
884 $form->_availableRegistrations
< $totalParticipants
886 $errors['_qf_default'] = ts("Only %1 Registrations available.", [1 => $form->_availableRegistrations
]);
890 CRM_Price_BAO_PriceSet
::processAmount($form->_values
['fee'], $fields, $lineItem);
892 $minAmt = CRM_Core_DAO
::getFieldValue('CRM_Price_DAO_PriceSet', $fields['priceSetId'], 'min_amount');
893 if ($fields['amount'] < 0) {
894 $errors['_qf_default'] = ts('Event Fee(s) can not be less than zero. Please select the options accordingly');
896 elseif (!empty($minAmt) && $fields['amount'] < $minAmt) {
897 $errors['_qf_default'] = ts('A minimum amount of %1 should be selected from Event Fee(s).', [
898 1 => CRM_Utils_Money
::format($minAmt),
902 foreach (CRM_Contact_BAO_Contact
::$_greetingTypes as $greeting) {
903 if ($greetingType = CRM_Utils_Array
::value($greeting, $fields)) {
904 $customizedValue = CRM_Core_PseudoConstant
::getKey('CRM_Contact_BAO_Contact', $greeting . '_id', 'Customized');
905 if ($customizedValue == $greetingType && empty($fields[$greeting . '_custom'])) {
906 $errors[$greeting . '_custom'] = ts('Custom %1 is a required field if %1 is of type Customized.',
907 [1 => ucwords(str_replace('_', ' ', $greeting))]
913 // @todo - can we remove the 'is_monetary' concept?
914 if ($form->_values
['event']['is_monetary']) {
915 if (empty($form->_requireApproval
) && !empty($fields['amount']) && $fields['amount'] > 0 &&
916 !isset($fields['payment_processor_id'])) {
917 $errors['payment_processor_id'] = ts('Please select a Payment Method');
920 if (self
::isZeroAmount($fields, $form)) {
921 return empty($errors) ?
TRUE : $errors;
924 // also return if zero fees for valid members
925 if (!empty($fields['bypass_payment']) ||
926 (!$form->_allowConfirmation
&& ($form->_requireApproval ||
$form->_allowWaitlist
))
928 return empty($errors) ?
TRUE : $errors;
930 CRM_Core_Payment_Form
::validatePaymentInstrument(
931 $fields['payment_processor_id'],
934 (!$form->_isBillingAddressRequiredForPayLater ?
NULL : 'billing')
938 return empty($errors) ?
TRUE : $errors;
942 * Check if profiles are complete when event registration occurs(CRM-9587).
944 * @param array $fields
945 * @param array $errors
946 * @param int $eventId
948 public static function checkProfileComplete($fields, &$errors, $eventId) {
950 foreach ($fields as $fieldname => $fieldvalue) {
951 if (substr($fieldname, 0, 6) == 'email-' && $fieldvalue) {
952 $email = $fieldvalue;
956 if (!$email && !(!empty($fields['first_name']) && !empty($fields['last_name']))) {
957 $defaults = $params = ['id' => $eventId];
958 CRM_Event_BAO_Event
::retrieve($params, $defaults);
959 $message = ts("Mandatory fields (first name and last name, OR email address) are missing from this form.");
960 $errors['_qf_default'] = $message;
965 * Process the form submission.
967 public function postProcess() {
968 // get the submitted form values.
969 $params = $this->controller
->exportValues($this->_name
);
971 //set as Primary participant
972 $params['is_primary'] = 1;
974 if ($this->_values
['event']['is_pay_later']
975 && (!array_key_exists('hidden_processor', $params) ||
$params['payment_processor_id'] == 0)
977 $params['is_pay_later'] = 1;
980 $params['is_pay_later'] = 0;
983 $this->set('is_pay_later', $params['is_pay_later']);
985 // assign pay later stuff
986 $this->_params
['is_pay_later'] = CRM_Utils_Array
::value('is_pay_later', $params, FALSE);
987 $this->assign('is_pay_later', $params['is_pay_later']);
988 $this->assign('pay_later_text', $params['is_pay_later'] ?
$this->_values
['event']['pay_later_text'] : NULL);
989 $this->assign('pay_later_receipt', $params['is_pay_later'] ?
$this->_values
['event']['pay_later_receipt'] : NULL);
991 if (!$this->_allowConfirmation
) {
992 // check if the participant is already registered
993 if (!$this->_skipDupeRegistrationCheck
) {
994 $params['contact_id'] = self
::getRegistrationContactID($params, $this, FALSE);
998 if (!empty($params['image_URL'])) {
999 CRM_Contact_BAO_Contact
::processImageParams($params);
1002 //carry campaign to partcipants.
1003 if (array_key_exists('participant_campaign_id', $params)) {
1004 $params['campaign_id'] = $params['participant_campaign_id'];
1007 $params['campaign_id'] = $this->_values
['event']['campaign_id'] ??
NULL;
1010 //hack to allow group to register w/ waiting
1011 $primaryParticipantCount = self
::getParticipantCount($this, $params);
1013 $totalParticipants = $primaryParticipantCount;
1014 if (!empty($params['additional_participants'])) {
1015 $totalParticipants +
= $params['additional_participants'];
1017 if (!$this->_allowConfirmation
&& !empty($params['bypass_payment']) &&
1018 is_numeric($this->_availableRegistrations
) &&
1019 $totalParticipants > $this->_availableRegistrations
1021 $this->_allowWaitlist
= TRUE;
1022 $this->set('allowWaitlist', TRUE);
1025 //carry participant id if pre-registered.
1026 if ($this->_allowConfirmation
&& $this->_participantId
) {
1027 $params['participant_id'] = $this->_participantId
;
1030 $params['defaultRole'] = 1;
1031 if (array_key_exists('participant_role', $params)) {
1032 $params['participant_role_id'] = $params['participant_role'];
1035 if (array_key_exists('participant_role_id', $params)) {
1036 $params['defaultRole'] = 0;
1038 if (empty($params['participant_role_id']) &&
1039 $this->_values
['event']['default_role_id']
1041 $params['participant_role_id'] = $this->_values
['event']['default_role_id'];
1044 $config = CRM_Core_Config
::singleton();
1045 $params['currencyID'] = $config->defaultCurrency
;
1047 if ($this->_values
['event']['is_monetary']) {
1048 // we first reset the confirm page so it accepts new values
1049 $this->controller
->resetPage('Confirm');
1051 //added for discount
1052 $discountId = CRM_Core_BAO_Discount
::findSet($this->_eventId
, 'civicrm_event');
1053 $params['amount_level'] = $this->getAmountLevel($params, $discountId);
1054 if (!empty($this->_values
['discount'][$discountId])) {
1055 $params['discount_id'] = $discountId;
1056 $params['amount'] = $this->_values
['discount'][$discountId][$params['amount']]['value'];
1058 elseif (empty($params['priceSetId'])) {
1059 if (!empty($params['amount'])) {
1060 $params['amount'] = $this->_values
['fee'][$params['amount']]['value'];
1063 $params['amount'] = '';
1068 CRM_Price_BAO_PriceSet
::processAmount($this->_values
['fee'], $params, $lineItem);
1069 if ($params['tax_amount']) {
1070 $this->set('tax_amount', $params['tax_amount']);
1072 $submittedLineItems = $this->get('lineItem');
1073 if (!empty($submittedLineItems) && is_array($submittedLineItems)) {
1074 $submittedLineItems[0] = $lineItem;
1077 $submittedLineItems = [$lineItem];
1079 $submittedLineItems = array_filter($submittedLineItems);
1080 $this->set('lineItem', $submittedLineItems);
1081 $this->set('lineItemParticipantsCount', [$primaryParticipantCount]);
1084 $this->set('amount', $params['amount']);
1085 $this->set('amount_level', $params['amount_level']);
1087 // generate and set an invoiceID for this transaction
1088 $invoiceID = md5(uniqid(rand(), TRUE));
1089 $this->set('invoiceID', $invoiceID);
1091 if ($this->_paymentProcessor
) {
1092 $payment = $this->_paymentProcessor
['object'];
1093 $payment->setBaseReturnUrl('civicrm/event/register');
1096 // ContributeMode is a deprecated concept. It is short-hand for a bunch of
1097 // assumptions we are working to remove.
1098 $this->set('contributeMode', 'direct');
1100 if ($this->_values
['event']['is_monetary']) {
1101 $params['currencyID'] = $config->defaultCurrency
;
1102 $params['invoiceID'] = $invoiceID;
1104 $this->_params
= $this->get('params');
1105 // Set the button so we know what
1106 $params['button'] = $this->controller
->getButtonName();
1107 if (!empty($this->_params
) && is_array($this->_params
)) {
1108 $this->_params
[0] = $params;
1111 $this->_params
= [];
1112 $this->_params
[] = $params;
1114 $this->set('params', $this->_params
);
1115 if ($this->_paymentProcessor
&&
1116 // Actually we don't really need to check if it supports pre-approval - we could just call
1117 // it regardless as the function we call re-acts tot the rests of the preApproval call.
1118 $this->_paymentProcessor
['object']->supports('preApproval')
1119 && !$this->_allowWaitlist
&&
1120 !$this->_requireApproval
1123 // The concept of contributeMode is deprecated - but still needs removal from the message templates.
1124 $this->set('contributeMode', 'express');
1126 // Send Event Name & Id in Params
1127 $params['eventName'] = $this->_values
['event']['title'];
1128 $params['eventId'] = $this->_values
['event']['id'];
1130 $params['cancelURL'] = CRM_Utils_System
::url('civicrm/event/register',
1131 "_qf_Register_display=1&qfKey={$this->controller->_key}",
1134 if (!empty($params['additional_participants'])) {
1135 $urlArgs = "_qf_Participant_1_display=1&rfp=1&qfKey={$this->controller->_key}";
1138 $urlArgs = "_qf_Confirm_display=1&rfp=1&qfKey={$this->controller->_key}";
1140 $params['returnURL'] = CRM_Utils_System
::url('civicrm/event/register',
1144 $params['invoiceID'] = $invoiceID;
1146 $params['component'] = 'event';
1147 // This code is duplicated multiple places and should be consolidated.
1148 $params = $this->prepareParamsForPaymentProcessor($params);
1149 $this->handlePreApproval($params);
1151 elseif ($this->_paymentProcessor
&&
1152 (int) $this->_paymentProcessor
['billing_mode'] & CRM_Core_Payment
::BILLING_MODE_NOTIFY
1154 // The concept of contributeMode is deprecated - but still needs removal from the message templates.
1155 $this->set('contributeMode', 'notify');
1159 $params['description'] = ts('Online Event Registration') . ' ' . $this->_values
['event']['title'];
1161 $this->_params
= [];
1162 $this->_params
[] = $params;
1163 $this->set('params', $this->_params
);
1166 empty($params['additional_participants'])
1167 // CRM-11182 - Optional confirmation screen
1168 && !$this->_values
['event']['is_confirm_enabled']
1170 $this->processRegistration($this->_params
);
1174 // If registering > 1 participant, give status message
1175 if (!empty($params['additional_participants'])) {
1176 $statusMsg = ts('Registration information for participant 1 has been saved.');
1177 CRM_Core_Session
::setStatus($statusMsg, ts('Saved'), 'success');
1182 * Method to check if the user is already registered for the event.
1183 * and if result found redirect to the event info page
1185 * @param array $fields
1186 * The input form values(anonymous user).
1187 * @param CRM_Event_Form_Registration_Register $form
1189 * @param bool $isAdditional
1190 * Treat isAdditional participants a bit differently.
1194 public static function checkRegistration($fields, $form, $isAdditional = FALSE) {
1195 // CRM-3907, skip check for preview registrations
1196 // CRM-4320 participant need to walk wizard
1198 ($form->getPaymentMode() === 'test' ||
$form->_allowConfirmation
)
1203 $contactID = self
::getRegistrationContactID($fields, $form, $isAdditional);
1206 $participant = new CRM_Event_BAO_Participant();
1207 $participant->contact_id
= $contactID;
1208 $participant->event_id
= $form->_values
['event']['id'];
1209 if (!empty($fields['participant_role']) && is_numeric($fields['participant_role'])) {
1210 $participant->role_id
= $fields['participant_role'];
1213 $participant->role_id
= $form->_values
['event']['default_role_id'];
1215 $participant->is_test
= 0;
1216 $participant->find();
1217 // Event#30 - Anyone whose status type has `is_counted` OR is on the waitlist should be considered as registered.
1218 $statusTypes = CRM_Event_PseudoConstant
::participantStatus(NULL, 'is_counted = 1') + CRM_Event_PseudoConstant
::participantStatus(NULL, "name = 'On waitlist'");
1219 while ($participant->fetch()) {
1220 if (array_key_exists($participant->status_id
, $statusTypes)) {
1221 if (!$isAdditional && !$form->_values
['event']['allow_same_participant_emails']) {
1222 $registerUrl = CRM_Utils_System
::url('civicrm/event/register',
1223 "reset=1&id={$form->_values['event']['id']}&cid=0"
1225 if ($form->_pcpId
) {
1226 $registerUrl .= '&pcpId=' . $form->_pcpId
;
1228 $registrationType = (CRM_Event_PseudoConstant
::getKey('CRM_Event_BAO_Participant', 'participant_status_id', 'On waitlist') == $participant->status_id
) ?
'waitlisted' : 'registered';
1229 if ($registrationType == 'waitlisted') {
1230 $status = ts("It looks like you are already waitlisted for this event. If you want to change your registration, or you feel that you've received this message in error, please contact the site administrator.");
1233 $status = ts("It looks like you are already registered for this event. If you want to change your registration, or you feel that you've received this message in error, please contact the site administrator.");
1235 $status .= ' ' . ts('You can also <a href="%1">register another participant</a>.', [1 => $registerUrl]);
1236 CRM_Core_Session
::singleton()->setStatus($status, '', 'alert');
1237 $url = CRM_Utils_System
::url('civicrm/event/info',
1238 "reset=1&id={$form->_values['event']['id']}&noFullMsg=true"
1240 if ($form->_action
& CRM_Core_Action
::PREVIEW
) {
1241 $url .= '&action=preview';
1244 if ($form->_pcpId
) {
1245 $url .= '&pcpId=' . $form->_pcpId
;
1248 CRM_Utils_System
::redirect($url);
1251 if ($isAdditional) {
1252 $status = ts("It looks like this participant is already registered for this event. If you want to change your registration, or you feel that you've received this message in error, please contact the site administrator.");
1253 CRM_Core_Session
::singleton()->setStatus($status, '', 'alert');
1254 return $participant->id
;