contacts_events-8.x-1.x-dev/src/Form/TicketForm.php
src/Form/TicketForm.php
<?php
namespace Drupal\contacts_events\Form;
use Drupal\Component\Utility\NestedArray;
use Drupal\contacts_events\Element\AjaxUpdate;
use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
/**
* Form controller for Ticket edit forms.
*
* @ingroup contacts_events
*/
class TicketForm extends ContentEntityForm {
use TicketFormHookTrait;
/**
* The ticket entity.
*
* @var \Drupal\contacts_events\Entity\TicketInterface
*/
protected $entity;
/**
* {@inheritdoc}
*
* @see hook_contacts_events_ticket_form_alter()
*/
public function buildForm(array $form, FormStateInterface $form_state) {
/** @var \Drupal\contacts_events\Entity\Ticket $entity */
$form = parent::buildForm($form, $form_state);
// Add our update handler for the price.
static::addPriceAjax($form, ['::rebuildForm']);
$this->alter($form, $form_state, $this->entity, $this->getOperation());
return $form;
}
/**
* Add the price update ajax handler.
*
* @param array $form
* The entity form.
* @param array $submit_handlers
* An array of submit handlers.
* @param string|null $unique_suffix
* A unique suffix for the update name and wrapper ID.
*/
public static function addPriceAjax(array &$form, array $submit_handlers, $unique_suffix = NULL) {
// If the price map isn't on the form, we wont do anything.
if (!isset($form['mapped_price'])) {
return;
}
// Add our update handler for the price.
$price_update = AjaxUpdate::createElement();
$form['price_update'] = &$price_update->getRenderArray('price_update', $unique_suffix);
$form['price_update']['#submit'] = $submit_handlers;
// Ensure messages are shown at the top of the relevant part of the form.
$form['messages'] = [
'#type' => 'container',
'#id' => $price_update->getIdWithUniqueSuffix('ticket-messages'),
'#weight' => -99,
];
// Only actually output the messages for AJAX requests.
if (\Drupal::request()->isXmlHttpRequest()) {
$form['messages']['messages'] = ['#type' => 'status_messages'];
}
$price_update->registerElementToUpdate($form['messages']);
// Register the mapped price to be updated.
$form['mapped_price']['#id'] = 'ticket-price-ajax-wrapper' . ($unique_suffix ? '-' . $unique_suffix : '');
$price_update->registerElementToUpdate($form['mapped_price']);
$price_update->registerElementToRespondTo($form['mapped_price']['widget'][0]['class'], [], $form['mapped_price']['widget']['#parents']);
if (isset($form['mapped_price']['widget'][0]['class_overridden'])) {
$price_update->registerElementToRespondTo($form['mapped_price']['widget'][0]['class_overridden'], [], FALSE);
}
if (isset($form['mapped_price']['widget'][0]['class_full'])) {
$price_update->registerElementToRespondTo($form['mapped_price']['widget'][0]['class_full'], [], FALSE);
}
if (isset($form['mapped_price']['widget'][0]['booking_window_overridden'])) {
$price_update->registerElementToRespondTo($form['mapped_price']['widget'][0]['booking_window_overridden'], [], FALSE);
}
if (isset($form['mapped_price']['widget'][0]['booking_window_full'])) {
$price_update->registerElementToRespondTo($form['mapped_price']['widget'][0]['booking_window_full'], [], FALSE);
}
// Register the date of birth to trigger an update.
if (isset($form['date_of_birth'])) {
$element = &$form['date_of_birth']['widget'][0]['value'];
switch ($element['#type']) {
// Datetime uses a single element. Assume the default is the same.
case 'datetime':
default:
$options = [
'event' => 'delayed.change',
'no_disable' => TRUE,
'disable-refocus' => TRUE,
];
$price_update->registerElementToRespondTo($form['date_of_birth']['widget'][0]['value'], $options, $form['date_of_birth']['widget']['#parents']);
$element['#attached']['library'][] = 'contacts_events/delayed_events';
// Mark the date value element as #ajax_processed as #ajax is copied
// to the children, resulting in a double submission.
$element['#ajax_processed'] = TRUE;
break;
// The datelist element gets exanded, so we need to add our AJAX after
// the expansion.
case 'datelist':
/** @var \Drupal\Core\Render\ElementInfoManagerInterface $info */
$info = \Drupal::service('plugin.manager.element_info');
$element['#process'] = $info->getInfoProperty($element['#type'], '#process');
$element['#process'][] = [static::class, 'processDateComponentAjax'];
$price_update->registerLimitValidationErrors($form['date_of_birth']['widget']['#parents']);
break;
}
}
// Register the price override to trigger an update.
if (isset($form['price_override'])) {
$options = [
'event' => 'delayed.keyup',
'no_disable' => TRUE,
'disable-refocus' => TRUE,
];
$price_update->registerElementToRespondTo($form['price_override']['widget'][0], $options, $form['price_override']['widget']['#parents']);
if (isset($form['date_of_birth'])) {
$form['date_of_birth']['widget'][0]['value']['#attached']['library'][] = 'contacts_events/delayed_events';
}
}
// Register the email address to trigger an update.
if (isset($form['email'])) {
$options = [
'event' => 'change',
'no_disable' => TRUE,
'disable-refocus' => TRUE,
];
$price_update->registerElementToRespondTo($form['email']['widget'][0]['value'], $options, $form['email']['widget']['#parents']);
}
}
/**
* Add the AJAX callbacks to components of a date field.
*
* @param array $element
* The form element whose value is being processed.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array $complete_form
* The complete form structure.
*/
public static function processDateComponentAjax(array &$element, FormStateInterface $form_state, array &$complete_form) {
$options = [
'event' => 'delayed.change',
'no_disable' => TRUE,
'disable-refocus' => TRUE,
// Group all the date components in a single delayed AJAX event and
// increase the timeout.
'delayed_group_id' => $element['#id'],
'delayed_time' => 1000,
];
$element['#attached']['library'][] = 'contacts_events/delayed_events';
// Get the ajax update element for the price for the ticket form.
$ticket_form = &static::getTicketForm($complete_form, $element);
/** @var \Drupal\contacts_events\Element\AjaxUpdate $price_update */
if (isset($ticket_form['price_update']['#element']) && $price_update = $ticket_form['price_update']['#element']) {
foreach (Element::children($element) as $component) {
$price_update->registerElementToRespondTo($element[$component], $options, FALSE);
}
}
return $element;
}
/**
* Form submission handler to rebuild the form.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
public function rebuildForm(array &$form, FormStateInterface $form_state) {
$this->submitForm($form, $form_state);
$form_state->setRebuild();
}
/**
* {@inheritdoc}
*/
public function buildEntity(array $form, FormStateInterface $form_state) {
/** @var \Drupal\contacts_events\Entity\TicketInterface $entity */
$entity = parent::buildEntity($form, $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);
// Get the order item, ensuring our working ticket is set on it.
$order_item = $entity->getOrderItem();
$order_item->set('purchased_entity', $entity);
// Recalculate the price and mapping.
\Drupal::service('contacts_events.price_calculator')->calculatePrice($order_item);
return $entity;
}
/**
* {@inheritdoc}
*
* @todo Handle the create scenario or prevent it entirely.
*/
public function save(array $form, FormStateInterface $form_state) {
$entity = $this->entity;
$status = parent::save($form, $form_state);
// Save the order item.
$entity->getOrderItem()->save();
switch ($status) {
case SAVED_NEW:
$this->messenger()->addMessage($this->t('Created the %label Ticket.', [
'%label' => $entity->label(),
]));
break;
default:
$this->messenger()->addMessage($this->t('Saved the %label Ticket.', [
'%label' => $entity->label(),
]));
}
$form_state->setRedirect('entity.contacts_ticket.canonical', ['contacts_ticket' => $entity->id()]);
}
/**
* Get the closest ticket form to an element.
*
* @param array $complete_form
* The complete form.
* @param array $element
* The element we want the closest ticket form for.
*
* @return array
* The ticket form.
*/
public static function &getTicketForm(array &$complete_form, array $element) {
$array_parents = $element['#array_parents'];
do {
array_pop($array_parents);
$form = &NestedArray::getValue($complete_form, $array_parents);
$entity_type = $form['#entity_type'] ?? FALSE;
} while ($entity_type != 'contacts_ticket' && count($array_parents) > 1);
return $form;
}
}
