contacts_events-8.x-1.x-dev/src/Form/TicketInlineForm.php
src/Form/TicketInlineForm.php
<?php
namespace Drupal\contacts_events\Form;
use Drupal\Component\Utility\NestedArray;
use Drupal\contacts_events\Entity\TicketInterface;
use Drupal\contacts_events\Event\TicketPrepopulateEvent;
use Drupal\contacts_events\OrderStateTrait;
use Drupal\contacts_events\PriceCalculator;
use Drupal\Core\Config\ImmutableConfig;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\inline_entity_form\Element\InlineEntityForm;
use Drupal\inline_entity_form\Form\EntityInlineForm;
use Drupal\user\UserInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* The inline form for Ticket entities.
*/
class TicketInlineForm extends EntityInlineForm {
use TicketFormHookTrait;
use OrderStateTrait;
/**
* The price calculator service.
*
* @var \Drupal\contacts_events\PriceCalculator
*/
protected $priceCalculator;
/**
* Configuration.
*
* @var \Drupal\Core\Config\ImmutableConfig
*/
protected $bookingSettings;
/**
* Event dispatcher.
*
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
protected $dispatcher;
/**
* {@inheritdoc}
*/
public function __construct(EntityFieldManagerInterface $entity_field_manager, EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler, EntityTypeInterface $entity_type, PriceCalculator $price_calculator, ImmutableConfig $booking_settings, EventDispatcherInterface $dispatcher) {
parent::__construct($entity_field_manager, $entity_type_manager, $module_handler, $entity_type);
$this->priceCalculator = $price_calculator;
$this->bookingSettings = $booking_settings;
$this->dispatcher = $dispatcher;
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$container->get('entity_field.manager'),
$container->get('entity_type.manager'),
$container->get('module_handler'),
$entity_type,
$container->get('contacts_events.price_calculator'),
$container->get('config.factory')->get('contacts_events.booking_settings'),
$container->get('event_dispatcher')
);
}
/**
* {@inheritdoc}
*
* @see hook_contacts_events_ticket_form_alter()
*/
public function entityForm(array $entity_form, FormStateInterface $form_state) {
// Get the entity we're building. The item in form state is going to be the
// most up to date, if it's available.
if ($ticket = $form_state->get('ticket')) {
$entity_form['#entity'] = $ticket;
}
else {
$ticket = $entity_form['#entity'];
}
/** @var \Drupal\contacts_events\Entity\TicketInterface $ticket */
// Ensure we have the order item set on the ticket.
if (!$ticket->getOrderItem()) {
// Get the order item form element.
$parents = array_slice($entity_form['#array_parents'], 0, -4);
$order_item_element = &NestedArray::getValue($form_state->getCompleteForm(), $parents);
$ticket->set('order_item', $order_item_element['#entity']);
}
// If this is the first ticket in the booking, and auto-fill first ticket is
// enabled, then pre-populate the ticket info from the booking manager's
// individual profile.
if ($ticket->isNew() && $this->bookingSettings->get('autofill_first_ticket')) {
$order = $ticket->getOrderItem()->getOrder();
if (count($order->getItems()) == 0) {
$this->copyUserDetailsToTicket($order->getCustomer(), $ticket);
}
}
$entity_form = parent::entityForm($entity_form, $form_state);
// Add checkbox for manually confirming the ticket.
$this->addConfirmCheckbox($entity_form, $ticket, $form_state);
// Add our update handler for the price.
TicketForm::addPriceAjax(
$entity_form,
[[static::class, 'rebuildEntity']],
$ticket->id() ?? 'new'
);
$this->alter($entity_form, $form_state, $ticket, $entity_form['#form_mode']);
return $entity_form;
}
/**
* Adds a checkbox allowing staff to manually confirm a ticket.
*
* @param array $form
* Form array.
* @param \Drupal\contacts_events\Entity\TicketInterface $ticket
* Ticket being added/edited.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* Form state.
*/
protected function addConfirmCheckbox(array &$form, TicketInterface $ticket, FormStateInterface $form_state) {
$form_mode = $form['#form_mode'];
// Add in an option that allows staff to confirm tickets.
if ($form_mode == 'booking_admin' && \Drupal::currentUser()->hasPermission('can manage bookings for contacts_events')) {
// Only show the 'confirm ticket' option if the ticket is pending.
if ($ticket->getStatus() == 'pending') {
$form['confirm_wrapper'] = [
'#type' => 'fieldset',
'#weight' => 100,
'#title' => new TranslatableMarkup('Confirm Ticket'),
];
$booking_state = $ticket->getBooking()->getState()->value;
if ($booking_state == 'confirmed' || $booking_state == 'modified') {
$form['confirm_wrapper']['confirm_ticket'] = [
'#type' => 'checkbox',
'#title' => new TranslatableMarkup('By ticking this box, the ticket will be moved from the pending state to the Confirmed state'),
];
// Add a new submit handler to handle confirming the ticket.
// This is done as an ief_element_submit to ensure it runs
// after the 'save' method has finished.
$form['#ief_element_submit'][] = [$this, 'submitConfirmTicket'];
}
else {
// If the booking is unconfirmed, show a message saying the booking
// needs to be confirmed.
$form['confirm_wrapper']['info'] = ['#markup' => new TranslatableMarkup('The booking has not been confirmed. Confirming this booking will also mark the ticket as confirmed. You should do this on the "View" tab.')];
}
}
}
}
/**
* Submission handler to rebuild the form with submitted values.
*
* @param array $form
* The form array.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*/
public static function rebuildEntity(array $form, FormStateInterface $form_state) {
// Get the entity form.
$entity_form_parents = $form_state->getTriggeringElement()['#array_parents'];
array_pop($entity_form_parents);
$entity_form = NestedArray::getValue($form, $entity_form_parents);
/** @var \Drupal\contacts_events\Entity\TicketInterface $ticket */
$ticket = $entity_form['#entity'];
// Get the handler and trigger the rebuild.
$inline_form_handler = InlineEntityForm::getInlineFormHandler($entity_form['#entity_type']);
$inline_form_handler->buildEntity($entity_form, $ticket, $form_state);
// Put the most up to date version into the form state and ensure the order
// item is up to date.
$form_state->set('ticket', $ticket);
$form_state->setRebuild();
}
/**
* {@inheritdoc}
*/
public function buildEntity(array $entity_form, ContentEntityInterface $entity, FormStateInterface $form_state) {
assert($entity instanceof TicketInterface);
parent::buildEntity($entity_form, $entity, $form_state);
// @todo Abstract ticket form acquisition into a trait for use with both
// Ticket forms and Inline Entity forms.
// Clear out contact for new tickets to allow for ajax updates.
if ($entity->isNew()) {
$entity->set('contact', NULL);
}
// Run an early acquisition, as ticket classes may respond to the contact.
$entity->acquire(TRUE);
// Recalculate the price of the ticket using the form handler.
// @todo Something to optimize this and prevent it being calculated
// mulitple time in a single request when nothing changes - perhaps an
// onChange in the item and ticket?
$this->priceCalculator->calculatePrice($entity->getOrderItem());
}
/**
* {@inheritdoc}
*/
public function save(EntityInterface $entity) {
/** @var \Drupal\contacts_events\Entity\TicketInterface $entity */
$order_item = $entity->getOrderItem();
// Update the order item title.
$order_item->set('title', $entity->getOrderItemTitle());
// New ticket and order items reference each other and end in a loop. To
// avoid that, clear the purchased entity off the order item, save it,
// restore it and then save again.
// OrderItemTicketInlineEntityWidget::submitSaveEntity ensures the correct
// order item is tracked in the form.
if ($entity->isNew() && $order_item && $order_item->isNew()) {
// Also set the title, as doing it this way results in it not being
// properly set.
$order_item
->set('purchased_entity', NULL)
->save();
$order_item
->set('purchased_entity', $entity)
->save();
}
// If it's not new, we can simply save.
elseif ($order_item) {
$order_item->save();
}
// Ensure the right order item entity is on the ticket.
$entity->setOrderItem($order_item);
// Now we can proceed to save the ticket.
$entity->save();
}
/**
* Callback for handling confirming the ticket.
*
* If the user selected the 'confirm ticket' checkbox, then confirm it.
* Note that this runs after the save method, so the ticket will already
* have been saved once by the time this is called.
*
* @param array $entity_form
* The entity form array.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* Form state.
*/
public function submitConfirmTicket(array $entity_form, FormStateInterface $form_state) {
$ticket_data = $form_state->getValue($entity_form['#parents']);
if ($ticket_data['confirm_wrapper']['confirm_ticket'] ?? FALSE) {
/** @var \Drupal\contacts_events\Entity\TicketInterface $ticket */
$ticket = $entity_form['#entity'];
/** @var \Drupal\state_machine\Plugin\Field\FieldType\StateItem $state */
$state = $ticket->getOrderItem()->get('state')->first();
$this->applyTransitionIfAllowed($state, 'confirm', TRUE);
}
}
/**
* Copies information from the individual profile to the ticket.
*
* @param \Drupal\user\UserInterface $user
* The user to copy.
* @param \Drupal\contacts_events\Entity\TicketInterface $ticket
* The target ticket.
*/
protected function copyUserDetailsToTicket(UserInterface $user, TicketInterface $ticket) {
if (isset($user->profile_crm_indiv->entity)) {
$profile = $user->get('profile_crm_indiv')->entity;
$ticket->set('contact', $user);
$ticket->set('name', $profile->get('crm_name')->getValue());
$ticket->set('email', $profile->getOwner()->getEmail());
}
// Allow other modules to perform their own prepopulate.
$this->dispatcher->dispatch(TicketPrepopulateEvent::NAME, new TicketPrepopulateEvent($ticket, $user));
}
}
