contacts_events-8.x-1.x-dev/src/Controller/EventController.php
src/Controller/EventController.php
<?php
namespace Drupal\contacts_events\Controller;
use Drupal\commerce_checkout\CheckoutOrderManagerInterface;
use Drupal\commerce_checkout\Resolver\ChainCheckoutFlowResolverInterface;
use Drupal\commerce_order\Entity\OrderInterface;
use Drupal\contacts_events\Entity\EventInterface;
use Drupal\contacts_events\UserBookingsHelper;
use Drupal\contacts_events\Plugin\Commerce\CheckoutFlow\BookingFlowInterface;
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityTypeManager;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Link;
use Drupal\Core\Lock\LockBackendInterface;
use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\Core\Messenger\Messenger;
use Drupal\Core\Routing\RedirectDestination;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Session\AccountProxy;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
* The event booking controller.
*/
class EventController extends ControllerBase {
/**
* Booking settings config object.
*
* @var \Drupal\Core\Config\Config
*/
protected $bookingSettings;
/**
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/**
* The events logger channel.
*
* @var \Drupal\Core\Logger\LoggerChannelInterface
*/
protected $loggerChannel;
/**
* The lock backend.
*
* @var \Drupal\Core\Lock\LockBackendInterface
*/
protected $lock;
/**
* The user bookings helper.
*
* @var \Drupal\contacts_events\UserBookingsHelper
*/
protected $bookingsHelper;
/**
* The checkout order manager.
*
* @var \Drupal\commerce_checkout\CheckoutOrderManagerInterface
*/
protected $checkoutOrderManager;
/**
* The chain checkout flow resolver.
*
* @var \Drupal\commerce_checkout\Resolver\ChainCheckoutFlowResolverInterface
*/
protected $chainCheckoutFlowResolver;
/**
* Constructs a new EventController object.
*/
public function __construct(EntityTypeManager $entity_type_manager, AccountProxy $current_user, RedirectDestination $redirect_destination, ConfigFactory $config_factory, Messenger $messenger, LoggerChannelInterface $logger_channel, ModuleHandlerInterface $module_handler, LockBackendInterface $lock, UserBookingsHelper $user_bookings_helper, CheckoutOrderManagerInterface $checkout_order_manager, ChainCheckoutFlowResolverInterface $checkout_flow_resolver) {
$this->entityTypeManager = $entity_type_manager;
$this->currentUser = $current_user;
$this->redirectDestination = $redirect_destination;
$this->configFactory = $config_factory;
$this->bookingSettings = $this->config('contacts_events.booking_settings');
$this->messenger = $messenger;
$this->loggerChannel = $logger_channel;
$this->moduleHandler = $module_handler;
$this->lock = $lock;
$this->bookingsHelper = $user_bookings_helper;
$this->checkoutOrderManager = $checkout_order_manager;
$this->chainCheckoutFlowResolver = $checkout_flow_resolver;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity_type.manager'),
$container->get('current_user'),
$container->get('redirect.destination'),
$container->get('config.factory'),
$container->get('messenger'),
$container->get('logger.channel.contacts_events'),
$container->get('module_handler'),
$container->get('lock'),
$container->get('contacts_events.user_bookings'),
$container->get('commerce_checkout.checkout_order_manager'),
$container->get('commerce_checkout.chain_checkout_flow_resolver')
);
}
/**
* Book onto an event.
*
* @param \Drupal\contacts_events\Entity\EventInterface $contacts_event
* The event to book for.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* The redirection to the checkour process.
*/
public function book(EventInterface $contacts_event) {
// Give an anonymous user a chance to log in.
if ($this->currentUser->isAnonymous()) {
$url = Url::fromRoute('user.login', [], ['query' => ['destination' => $this->redirectDestination->get()]]);
return new RedirectResponse($url->toString());
}
$access_result = $contacts_event->access('book', $this->currentUser, TRUE);
// Always deny if explicitly forbidden.
if ($access_result->isForbidden()) {
return $this->deniedRedirect($contacts_event, $this->t('Sorry, we were unable to start a booking for %event.', [
'%event' => $contacts_event->label(),
]));
}
// Wait for a lock for the next steps to ensure we don't create two bookings
// for the same event and user.
$lock_id = 'contacts_events_book_now:' . $this->currentUser->id();
if (!$this->lock->acquire($lock_id)) {
$this->lock->wait($lock_id, 10);
if (!$this->lock->acquire($lock_id)) {
return $this->deniedRedirect($contacts_event, $this->t('Sorry, there was a temporary problem starting a booking for %event. Please try again.', [
'%event' => $contacts_event->label(),
]));
}
}
// Otherwise, look for an existing order to redirect to.
$booking = $this->bookingsHelper->findBookingForManager($contacts_event, $this->currentUser);
if ($booking) {
/** @var \Drupal\contacts_events\Plugin\Commerce\CheckoutFlow\BookingFlowInterface $flow */
$flow = $this->getCheckoutFlow($booking);
return new RedirectResponse($flow->getContinueUrl()->toString());
}
if ($this->bookingsHelper->findBookingForTicketHolder($contacts_event, $this->currentUser)) {
$ticket_holder_text = $this->t('You already have a ticket for %event. You can view all your bookings on your %user_dashboard.', [
'%event' => $contacts_event->label(),
'%user_dashboard' => Link::createFromRoute('User Dashboard', 'contacts_events.user_events', ['user' => $this->currentUser->id()])->toString(),
]);
return $this->deniedRedirect($contacts_event, $ticket_holder_text);
}
// If we are allowed to book.
if (!$this->currentUser->hasPermission('can book for contacts_events')) {
// Release the lock as we are not going to create a booking and redirect
// to the event page.
$this->lock->release($lock_id);
return $this->deniedRedirect($contacts_event);
}
// Ensure the system is configured.
if (!$this->checkConfiguration()) {
// Release the lock as we are not going to create a booking.
$this->lock->release($lock_id);
// If we have permission to update settings, give a directive message.
$message = NULL;
if ($this->currentUser->hasPermission('configure contacts events')) {
$link = Link::createFromRoute($this->t('booking settings'), 'contacts_events.contacts_events_booking_settings_form');
$message = $this->t('You must configure the @link before booking onto an event.', [
'@link' => $link->toString(),
]);
}
return $this->deniedRedirect($contacts_event, $message);
}
// Finally allow other modules to deny with a reason.
$args = [$contacts_event, $this->currentUser];
foreach ($this->moduleHandler->getImplementations('contacts_events_deny_booking') as $module) {
$denial_reason = $this->moduleHandler->invoke($module, 'contacts_events_deny_booking', $args);
if ($denial_reason) {
// Release the lock as we are not going to create a booking.
$this->lock->release($lock_id);
return $this->deniedRedirect($contacts_event, $denial_reason);
}
}
// Build a new booking, redirecting direct into the booking process.
$booking = $this->createBooking($contacts_event, $this->currentUser);
// Release the lock now we have created a booking.
$this->lock->release($lock_id);
$flow = $this->getCheckoutFlow($booking);
return new RedirectResponse($flow->getContinueUrl()->toString());
}
/**
* Create a booking for an event and user.
*
* @param \Drupal\contacts_events\Entity\EventInterface $event
* The event entity.
* @param \Drupal\Core\Session\AccountInterface|null $account
* The user. If not provided, we use the current user.
*
* @return \Drupal\commerce_order\Entity\OrderInterface
* The created and saved booking, ready to continue.
*/
protected function createBooking(EventInterface $event, AccountInterface $account = NULL) {
// If we didn't get an account, use the current user.
if (!$account) {
$account = $this->currentUser;
}
// Intial values for the booking.
$values = [
'type' => 'contacts_booking',
'store_id' => $this->bookingSettings->get('store_id'),
'event' => $event->id(),
'uid' => $account->id(),
'checkout_step' => 'tickets',
];
// Create, save and return the booking.
/** @var \Drupal\commerce_order\Entity\OrderInterface $booking */
$booking = $this->entityTypeManager
->getStorage('commerce_order')
->create($values);
// Look for a customer profile we can use for billing records.
if ($profiles = $booking->collectProfiles()) {
if (isset($profiles['billing'])) {
$booking->setBillingProfile($profiles['billing']);
}
}
/** @var \Drupal\contacts_events\Plugin\Commerce\CheckoutFlow\BookingFlowInterface $flow */
$flow_entity = $this->chainCheckoutFlowResolver->resolve($booking);
$flow = $flow_entity->getPlugin();
$flow->setOrder($booking);
$booking->set('checkout_flow', $flow_entity);
$booking->set('checkout_step', $flow->getInitialStep());
$booking->save();
return $booking;
}
/**
* Get the checkout flow for a booking.
*
* @param \Drupal\commerce_order\Entity\OrderInterface $booking
* The booking to get the checkout flow for.
*
* @return \Drupal\commerce_checkout\Plugin\Commerce\CheckoutFlow\CheckoutFlowInterface
* The checkout flow with the relevant booking set.
*/
protected function getCheckoutFlow(OrderInterface $booking) {
$flow = $this->checkoutOrderManager->getCheckoutFlow($booking)->getPlugin();
if ($flow instanceof BookingFlowInterface) {
$flow->setOrder($booking);
}
return $flow;
}
/**
* Check the configuration for bookings.
*
* @return bool
* Whether the configuration is valid.
*/
protected function checkConfiguration() {
// Ensure the system is configured.
if (!$store_id = $this->bookingSettings->get('store_id')) {
// Log a critical error if the store is not configured.
$this->loggerChannel->critical('The system is not correctly configured for bookings.');
return FALSE;
}
return TRUE;
}
/**
* Redirect to the event page with a denied message.
*
* @param \Drupal\contacts_events\Entity\EventInterface $event
* The event entity.
* @param \Drupal\Core\StringTranslation\TranslatableMarkup|string|null|false $message
* The message to show. NULL will use the default and FALSE will not use
* one.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* The redirect response.
*/
protected function deniedRedirect(EventInterface $event, $message = NULL) {
// Set the default message.
if (!isset($message)) {
$message = $this->t('Sorry, we were unable to start a booking for %event', [
'%event' => $event->label(),
]);
}
// Show the message if there is one.
if ($message) {
$this->messenger->addError($message);
}
// Redirect to the event page.
return new RedirectResponse($event->toUrl()->toString());
}
}
