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