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; } }