contacts_events-8.x-1.x-dev/modules/accommodation/src/Plugin/Field/FieldWidget/BookingCampingWidget.php
modules/accommodation/src/Plugin/Field/FieldWidget/BookingCampingWidget.php
<?php namespace Drupal\contacts_events_accommodation\Plugin\Field\FieldWidget; use Drupal\commerce_checkout\Plugin\Commerce\CheckoutFlow\CheckoutFlowWithPanesBase; use Drupal\Component\Utility\NestedArray; use Drupal\contacts_events_accommodation\AccommodationHelper; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\WidgetBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; use Symfony\Component\DependencyInjection\ContainerInterface; /** * Widget for camping on bookings. * * @FieldWidget( * id = "order_items_camping", * label = @Translation("Booking Camping"), * field_types = { * "entity_reference" * }, * multiple_values = true * ) */ class BookingCampingWidget extends WidgetBase implements ContainerFactoryPluginInterface { /** * The context for string translation. */ const TRANSLATION_CONTEXT = 'contacts_events_booking_camping_widget_accommodation_error'; /** * The accommodation helper. * * @var \Drupal\contacts_events_accommodation\AccommodationHelper */ protected $accommodationHelper; /** * The current user. * * @var \Drupal\Core\Session\AccountInterface */ protected $currentUser; /** * Constructs the Camping widget object. * * @param string $plugin_id * The plugin_id for the widget. * @param mixed $plugin_definition * The plugin implementation definition. * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition * The definition of the field to which the widget is associated. * @param array $settings * The widget settings. * @param array $third_party_settings * Any third party settings. * @param \Drupal\contacts_events_accommodation\AccommodationHelper $accommodation_helper * The accommodation helper. * @param \Drupal\Core\Session\AccountInterface $current_user * The current user. */ public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, AccommodationHelper $accommodation_helper, AccountInterface $current_user) { parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings); $this->accommodationHelper = $accommodation_helper; $this->currentUser = $current_user; } /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { $widget = new static( $plugin_id, $plugin_definition, $configuration['field_definition'], $configuration['settings'], $configuration['third_party_settings'], $container->get('contacts_events_accommodation.helper'), $container->get('current_user') ); $widget->setMessenger($container->get('messenger')); return $widget; } /** * {@inheritdoc} */ public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { $manage = $this->currentUser->hasPermission('can manage bookings for contacts_events'); $booking_accommodation = $this->accommodationHelper->getBookingHelper($items); $order = $items->getEntity(); $event = $order->get('event')->entity; foreach ($this->accommodationHelper->getAccommodation($event, 'camping') as $id => $accommodation) { $max = $booking_accommodation->getMaxAllowedAccommodation($accommodation); // If we have manage permission, always allow setting more than the // current amount. if ($manage) { $max += 10; } // If we are not allowed any, don't display. if (!$max) { continue; } $element[$id] = [ '#type' => 'select', '#title' => $accommodation->label(), '#description' => $accommodation->getDescription(), '#options' => range(0, $max), '#default_value' => $booking_accommodation->getTotalAccommodation($id), ]; if ($confirmed = $booking_accommodation->getConfirmedAccommodation($id)) { $element[$id]['#options'][$confirmed] = new TranslatableMarkup('@count (confirmed)', [ '@count' => $confirmed, ]); } } // Use a process to add the submit handler. $element['#process'][] = [$this, 'processElement']; // Add element level validation and the data required for it. $element['#element_validate'][] = [$this, 'validateTotal']; $element['#total_delegates'] = $booking_accommodation->getTotalDelegates(); // Set the title/description and make it a fieldset. $element['#type'] = 'fieldset'; $element['#title'] = $this->t('Camping'); // @todo Switch to #description and #description_display => 'before' when // https://www.drupal.org/project/drupal/issues/2396145 is fixed. $element['_description'] = [ '#type' => 'html_tag', '#tag' => 'div', '#attributes' => ['class' => ['description']], '#value' => $this->t('Please select the equipment you would like to bring with you.'), '#weight' => -99, ]; $element['#required'] = FALSE; return $element; } /** * Process callback to add the submit handler. * * @param array $element * The widget element. * @param \Drupal\Core\Form\FormStateInterface $form_state * The form state. * @param array $complete_form * The complete form. * * @return array * The widget element. */ public function processElement(array $element, FormStateInterface $form_state, array &$complete_form) { // If we are inside an inline entity form, we can hook into that. if ($form_state->getFormObject() instanceof CheckoutFlowWithPanesBase) { // Run up our form until we hit our pane and add our submit handler there // for the pane to pick up. $path = $element['#array_parents']; do { $pane_element = &NestedArray::getValue($complete_form, $path); array_pop($path); } while (@$pane_element['#theme'] != 'commerce_checkout_pane' && !empty($path)); // Check we have found the pane element. if ($pane_element['#theme'] == 'commerce_checkout_pane') { $pane_element['#pane_submit'] = []; $handlers = &$pane_element['#pane_submit']; } } elseif (isset($complete_form['actions']['submit']['#submit'])) { $handlers = &$complete_form['actions']['submit']['#submit']; } elseif (isset($complete_form['#submit'])) { $handlers = &$complete_form['#submit']; } // If we have handlers, insert our save handler before the save. if (isset($handlers)) { // Get our array of locations. $locations = $form_state->get('contacts_events_accommodation_camping_parents') ?? []; // Only add the submit handler once. if (empty($locations)) { $position = array_search($search ?? '::save', $handlers) ?: 0; array_splice($handlers, $position, 0, [[$this, 'save']]); } // Store the location in form state so we can ensure we're working with // the correct items. $locations[] = array_slice($element['#parents'], 0, -1); $form_state->set('contacts_events_accommodation_camping_parents', $locations); } return $element; } /** * Element validation to check the total accommodation is not too low or high. * * @param array $element * The camping element. * @param \Drupal\Core\Form\FormStateInterface $form_state * The form state. * @param array $form * The complete form. */ public function validateTotal(array $element, FormStateInterface $form_state, array $form) { $result = $this->accommodationHelper->validateAccommodationTotal($element['#total_delegates'], $form_state->getValue($element['#parents'], [])); $delegates = $this->formatPlural( $element['#total_delegates'], 'delegate', '@count delegates', [], ['context' => self::TRANSLATION_CONTEXT], ); switch ($result) { case AccommodationHelper::VALIDATE_OK: // No error, so nothing to do. break; case AccommodationHelper::VALIDATE_TOO_LITTLE: $error = $this->t( 'It looks like you have not selected enough accommodation for the @count you are booking for.', ['@count' => $delegates], ['context' => self::TRANSLATION_CONTEXT], ); break; case AccommodationHelper::VALIDATE_TOO_MUCH: $error = $this->t( 'It looks like you have selected too much accommodation for the @count you are booking for.', ['@count' => $delegates], ['context' => self::TRANSLATION_CONTEXT], ); break; default: $error = $this->t( 'Sorry, we encountered an unknown error.', [], ['context' => self::TRANSLATION_CONTEXT], ); break; } if (isset($error)) { if ($this->currentUser->hasPermission('can manage bookings for contacts_events')) { $this->messenger()->addWarning($error); } else { $form_state->setError($element, $error); } } } /** * {@inheritdoc} */ public function extractFormValues(FieldItemListInterface $items, array $form, FormStateInterface $form_state) { // Get the field state. $items_to_save = $this->getItemsToSave($form['#parents'], $form_state); // Create or modify the relevant order items. $booking_accommodation = $this->accommodationHelper->getBookingHelper($items); $path = array_merge($form['#parents'], [$this->fieldDefinition->getName()]); $items_to_save = $booking_accommodation->updateUnconfirmedItems($form_state->getValue($path, [])) + $items_to_save; // Store for later and return the values. $this->setItemsToSave($form['#parents'], $form_state, $items_to_save); return $items->getValue(); } /** * Submit callback to save the order items prior to the order being saved. * * @param array $form * The form array. * @param \Drupal\Core\Form\FormStateInterface $form_state * The form state. */ public function save(array $form, FormStateInterface $form_state) { $locations = $form_state->get('contacts_events_accommodation_camping_parents'); foreach ($locations as $parents) { $items_to_save = $this->getItemsToSave($parents, $form_state); // Save any items that need saving/deleting. /** @var \Drupal\commerce_order\Entity\OrderItemInterface $item */ foreach ($items_to_save as $item) { if ((int) $item->getQuantity() === 0 && !$item->isNew()) { $item->delete(); } else { $item->save(); } } $this->setItemsToSave($parents, $form_state, []); } } /** * {@inheritdoc} */ public static function isApplicable(FieldDefinitionInterface $field_definition) { // Only allow for order items on the contacts booking bundle of orders. return $field_definition->getTargetEntityTypeId() == 'commerce_order' && $field_definition->getName() == 'order_items'; } /** * Get the items to save. * * We don't use the field state as inline entity form clears that too early. * * @param array $parents * The form parents of the field widget. * @param \Drupal\Core\Form\FormStateInterface $form_state * The form state. * * @return array * The items to save. */ public function getItemsToSave(array $parents, FormStateInterface $form_state) : array { $path = $parents; array_unshift($path, static::class); $path[] = $this->fieldDefinition->getName(); return $form_state->get($path) ?? []; } /** * Set the items to save. * * We don't use the field state as inline entity form clears that too early. * * @param array $parents * The form parents of the field widget. * @param \Drupal\Core\Form\FormStateInterface $form_state * The form state. * @param array $items * The items to save. */ public function setItemsToSave(array $parents, FormStateInterface $form_state, array $items) : void { $path = $parents; array_unshift($path, static::class); $path[] = $this->fieldDefinition->getName(); $form_state->set($path, $items); } }