recurring_events-2.0.x-dev/modules/recurring_events_registration/src/Form/RegistrantForm.php
modules/recurring_events_registration/src/Form/RegistrantForm.php
<?php
declare(strict_types=1);
namespace Drupal\recurring_events_registration\Form;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\content_moderation\ModerationInformation;
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Routing\TrustedRedirectResponse;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Url;
use Drupal\recurring_events_registration\Enum\RegistrationType;
use Drupal\recurring_events_registration\NotificationService;
use Drupal\recurring_events_registration\RegistrationCreationService;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Form controller for Registrant edit forms.
*
* @ingroup recurring_events_registration
*/
class RegistrantForm extends ContentEntityForm {
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.repository'),
$container->get('entity_type.bundle.info'),
$container->get('datetime.time'),
$container->get('current_route_match'),
$container->get('entity_type.manager'),
$container->get('current_user'),
$container->get('recurring_events_registration.creation_service'),
$container->get('config.factory'),
$container->get('entity_field.manager'),
$container->get('recurring_events_registration.notification_service'),
$container->get('content_moderation.moderation_information', ContainerInterface::NULL_ON_INVALID_REFERENCE)
);
}
public function __construct(
EntityRepositoryInterface $entity_repository,
EntityTypeBundleInfoInterface $entity_type_bundle_info,
TimeInterface $time,
RouteMatchInterface $routeMatch,
EntityTypeManagerInterface $entityTypeManager,
protected AccountProxyInterface $currentUser,
protected RegistrationCreationService $creationService,
protected ConfigFactory $config,
protected EntityFieldManagerInterface $fieldManager,
protected NotificationService $notificationService,
protected ?ModerationInformation $moderationInformation = NULL,
) {
parent::__construct($entity_repository, $entity_type_bundle_info, $time);
$this->routeMatch = $routeMatch;
$this->entityTypeManager = $entityTypeManager;
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form = parent::buildForm($form, $form_state);
/** @var \Drupal\recurring_events_registration\Entity\Registrant $entity */
$entity = $this->entity;
$event_instance = $this->routeMatch->getParameter('eventinstance');
$editing = !$entity->isNew();
if (empty($event_instance)) {
return;
}
// Use the registration creation service to grab relevant data.
$this->creationService->setEventInstance($event_instance);
$availability = $event_instance->availability_count->getValue()[0]['value'];
$waitlist = $this->creationService->hasWaitlist();
$registration_open = $this->creationService->registrationIsOpen();
$reg_type = $this->creationService->getRegistrationType();
$form['notifications'] = [
'#type' => 'container',
'#weight' => -100,
'#attributes' => [
'class' => ['registration-notifications'],
],
// Do not show notifications if we are in edit mode.
'#printed' => $editing,
];
// If space has run out, but there is a waitlist.
$form['notifications']['waitlist_notification'] = [
'#type' => 'container',
'#access' => ($availability == 0 && $waitlist && $registration_open),
'#attributes' => [
'class' => ['registration-notification-message'],
],
'title' => [
'#type' => 'markup',
'#prefix' => '<h3 class="registration-notice-title">',
'#markup' => $this->t('Registration full.'),
'#suffix' => '</h3>',
],
'message' => [
'#type' => 'markup',
'#prefix' => '<p class="registration-message">',
'#markup' => $this->t('Unfortunately, there are no spaces left for this @type. However, we can add you to the waitlist. If a space becomes available, the first registrant on the waitlist will be automatically registered.', [
'@type' => $reg_type === RegistrationType::Series ? 'series' : 'event',
]),
'#suffix' => '</p>',
],
];
// If space has run out, but there is no waitlist.
$form['notifications']['availability_notification'] = [
'#type' => 'container',
'#attributes' => [
'class' => ['registration-notification-message'],
],
'#access' => ($availability == 0 && !$waitlist && $registration_open),
'title' => [
'#type' => 'markup',
'#prefix' => '<h3 class="registration-notice-title">',
'#markup' => $this->t('Registration full.'),
'#suffix' => '</h3>',
],
'message' => [
'#type' => 'markup',
'#prefix' => '<p class="registration-message">',
'#markup' => $this->t('Unfortunately, this @type is at capacity and there are no spaces available.', [
'@type' => $reg_type === RegistrationType::Series ? 'series' : 'event',
]),
'#suffix' => '</p>',
],
];
// If registration is not open.
$form['notifications']['registration_closed'] = [
'#type' => 'container',
'#attributes' => [
'class' => ['registration-notification-message'],
],
'#access' => !$registration_open,
'title' => [
'#type' => 'markup',
'#prefix' => '<h3 class="registration-notice-title">',
'#markup' => $this->t('Registration is closed.'),
'#suffix' => '</h3>',
],
'message' => [
'#type' => 'markup',
'#prefix' => '<p class="registration-message">',
'#markup' => $this->t('Unfortunately, registration for this @type is closed.', [
'@type' => $reg_type === RegistrationType::Series ? 'series' : 'event',
]),
'#suffix' => '</p>',
],
];
if ($this->config('recurring_events_registration.registrant.config')->get('show_capacity')) {
$form['availability'] = [
'#type' => 'markup',
'#prefix' => '<span class="registration-availability">',
'#markup' => $this->t('Spaces Available: @availability', [
'@availability' => ($availability == -1) ? $this->t('Unlimited') : $availability,
]),
'#suffix' => '</span>',
'#weight' => -99,
];
}
$add_to_waitlist = ($availability == 0 && $waitlist) ? 1 : 0;
$form['add_to_waitlist'] = [
'#type' => 'hidden',
'#value' => $add_to_waitlist,
'#weight' => 98,
];
$link = $event_instance->toLink($this->t('Go Back to Event Details'));
$form['back_link'] = [
'#type' => 'markup',
'#prefix' => '<span class="registration-back-link">',
'#markup' => $link->toString(),
'#suffix' => '</span>',
'#weight' => 100,
];
if ($this->currentUser->hasPermission('modify registrant waitlist') && $waitlist) {
$form['add_to_waitlist']['#type'] = 'select';
$form['add_to_waitlist']['#options'] = [
1 => $this->t('Yes'),
0 => $this->t('No'),
];
$form['add_to_waitlist']['#title'] = $this->t('Add user to waitlist');
$value = !$entity->isNew()
? ($entity->getWaitlist() ? 1 : 0)
: $add_to_waitlist;
$form['add_to_waitlist']['#default_value'] = $value;
unset($form['add_to_waitlist']['#value']);
}
$this->hideFormFields($form, $form_state);
// Because the form gets modified depending on the number of registrations
// we need to prevent caching.
$form['#cache'] = ['max-age' => 0];
$form_state->setCached(FALSE);
$save_label = $this->t('Register');
if ($editing) {
$save_label = $this->t('Update Registration');
}
$form['actions']['submit']['#value'] = $save_label;
// Hide the form if user is not allowed to register for this series.
$permitted_roles = $this->creationService->registrationPermittedRoles();
$role_permitted = empty($permitted_roles);
if (!$role_permitted) {
$user_roles = $this->currentUser->getRoles();
if (in_array('administrator', $user_roles)) {
$role_permitted = TRUE;
}
else {
foreach ($user_roles as $user_role) {
if (in_array($user_role, $permitted_roles)) {
$role_permitted = TRUE;
break;
}
}
}
}
if (!$role_permitted) {
$this->messenger()->addWarning('You are not allowed to register for events in this series.');
$form['#disabled'] = TRUE;
}
return $form;
}
/**
* Hide form fields depending on registration status.
*
* @var array $form
* The form configuration array.
* @var Drupal\Core\Form\FormStateInterface $form_state
* The form state interface.
*/
protected function hideFormFields(array &$form, FormStateInterface $form_state) {
/** @var \Drupal\recurring_events_registration\Entity\Registrant $entity */
$entity = $this->entity;
$new = $entity->isNew();
if ($new) {
$event_instance = $this->routeMatch->getParameter('eventinstance');
}
else {
$event_instance = $entity->getEventInstance();
}
$form_fields = $this->fieldManager->getFieldDefinitions('registrant', $this->entity->getBundle());
$availability = $event_instance?->availability_count->getValue()[0]['value'] ?? 0;
$waitlist = $this->creationService->hasWaitlist();
$registration_open = $this->creationService->registrationIsOpen();
// Prevent the form being displayed if registration is closed, or there are
// no spaces left, and no waitlist.
if ((($availability === 0 && !$waitlist) || !$registration_open) && $new) {
foreach ($form_fields as $field_name => $field) {
if (isset($form[$field_name]) && $new) {
$form[$field_name]['#printed'] = TRUE;
}
}
$form['actions']['#printed'] = TRUE;
if (isset($form['availability'])) {
$form['availability']['#printed'] = TRUE;
}
if (isset($form['add_to_waitlist'])) {
$form['add_to_waitlist']['#printed'] = TRUE;
}
}
if (!$this->currentUser->hasPermission('modify registrant author')) {
$form['user_id']['#access'] = FALSE;
}
if (!$this->currentUser->hasPermission('administer registrant entity')) {
$form['revision_information']['#access'] = FALSE;
$form['status']['#access'] = FALSE;
}
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
/** @var \Drupal\recurring_events_registration\Entity\Registrant $entity */
$entity = parent::validateForm($form, $form_state);
// Only perform the checks if the entity is new.
if ($entity->isNew()) {
$event_instance = $this->routeMatch->getParameter('eventinstance');
$event_series = $event_instance->getEventSeries();
// Use the registration creation service to grab relevant data.
$this->creationService->setEventInstance($event_instance);
// Just to be sure we have a fresh copy of the event series.
$this->creationService->setEventSeries($event_series);
$availability = $event_instance->availability_count->getValue()[0]['value'];
$waitlist = $this->creationService->hasWaitlist();
$registration_open = $this->creationService->registrationIsOpen();
$add_to_waitlist = $form_state->getValue('add_to_waitlist');
// Registration has closed.
if (!$registration_open) {
$form_state->setError($form, $this->t('Unfortunately, registration has closed.'));
}
// Capacity is full, there is a waitlist, but user was not being added to
// the waitlist.
elseif (!$add_to_waitlist && $availability == 0 && $waitlist) {
$form_state->setError($form, $this->t('Unfortunately, this event is now full and you must join the waitlist.'));
}
// There are no spaces left, and there is no waitlist.
elseif ($availability == 0 && !$waitlist) {
$form_state->setError($form, $this->t('Unfortunately, this event is now full.'));
}
}
else {
if ($this->currentUser->hasPermission('modify registrant waitlist')) {
// Update the user's waitlist value.
$entity->setWaitlist((bool) $form_state->getValue('add_to_waitlist'));
}
}
$unique_email_address = $this->creationService->registrationUniqueEmailAddress();
if ($unique_email_address) {
$email_address = $form_state->getValue('email');
$ignored_registrant_id = ($entity->isNew() ? NULL : (int) $entity->id());
$existing_registration_id = $this->creationService->hasUserRegisteredByEmail($email_address[0]['value'], $ignored_registrant_id);
if ($existing_registration_id) {
// If a registration already exists for the email display an error.
$form_state->setErrorByName('email', $this->t("You've already registered for this event."));
}
}
return $entity;
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
$event_instance = $this->routeMatch->getParameter('eventinstance');
$event_series = $event_instance->getEventSeries();
/** @var \Drupal\recurring_events_registration\Entity\RegistrantInterface $entity */
$entity = $this->entity;
if (isset($this->notificationService) && isset($entity)) {
$this->notificationService->setEntity($entity);
}
// Use the registration creation service to grab relevant data.
$this->creationService->setEventInstance($event_instance);
// Just to be sure we have a fresh copy of the event series.
$this->creationService->setEventSeries($event_series);
// Only allow creation or modification of registrants if registration is
// still open and there is availability or a waitlist. Or, if the user has
// the 'administer any registrant' permission.
$registration_open = $this->creationService->registrationIsOpen();
$availability = $event_instance->availability_count->getValue()[0]['value'];
$waitlist = $this->creationService->hasWaitlist();
$registration_possible = $registration_open && ($availability > 0 || $availability == -1 || $waitlist);
$user_is_admin = $this->currentUser->hasPermission('administer any registrant');
if ($user_is_admin || $registration_possible) {
$add_to_waitlist = (bool) $form_state->getValue('add_to_waitlist');
$reg_type = $this->creationService->getRegistrationType();
$this->entity->setEventSeries($event_series);
$this->entity->setEventInstance($event_instance);
$this->entity->setWaitlist($add_to_waitlist);
$this->entity->setRegistrationType($reg_type);
$status = parent::save($form, $form_state);
switch ($status) {
case SAVED_NEW:
$message = $this->config('recurring_events_registration.registrant.config')->get('successfully_registered');
if ($add_to_waitlist) {
$message = $this->config('recurring_events_registration.registrant.config')->get('successfully_registered_waitlist');
}
break;
case SAVED_UPDATED:
$message = $this->t('Registrant successfully updated');
break;
default:
$message = $this->config('recurring_events_registration.registrant.config')->get('successfully_registered');
if ($add_to_waitlist) {
$message = $this->config('recurring_events_registration.registrant.config')->get('successfully_registered_waitlist');
}
break;
}
$this->messenger()->addMessage(new FormattableMarkup($this->notificationService->parseTokenizedString($message), []));
return $status;
}
else {
if ($this->entity->isNew()) {
$message = $this->config('recurring_events_registration.registrant.config')->get('registration_closed');
$this->messenger()->addMessage(new FormattableMarkup($this->notificationService->parseTokenizedString($message), []));
}
else {
$message = $this->t('Unable to update registrant');
$this->messenger()->addError($message);
}
}
$redirect_choice = $this->config('recurring_events_registration.registrant.config')->get('insert_redirect_choice');
switch ($redirect_choice) {
case 'instance':
$form_state->setRedirect('entity.eventinstance.canonical', ['eventinstance' => $event_instance->id()]);
break;
case 'series':
$form_state->setRedirect('entity.eventseries.canonical', ['eventseries' => $event_series->id()]);
break;
case 'other':
$url = $this->config('recurring_events_registration.registrant.config')->get('insert_redirect_other');
$response = new TrustedRedirectResponse(Url::fromUri($url)->toString());
$form_state->setResponse($response);
break;
default:
$form_state->setRedirectUrl(Url::fromRoute('<current>'));
break;
}
// @todo Remove when https://www.drupal.org/node/3173241 drops.
if ($this->moderationInformation) {
if ($this->moderationInformation->hasPendingRevision($entity) && $entity->hasLinkTemplate('latest-version')) {
$form_state->setRedirect('entity.registrant.latest_version', [
'eventinstance' => $entity->getEventInstance()?->id() ?? 0,
'registrant' => $entity->id(),
]);
}
}
return $this->entity->isNew() ? SAVED_NEW : SAVED_UPDATED;
}
}
