contacts_events-8.x-1.x-dev/src/Plugin/Field/FieldWidget/BookingWindowsWidget.php
src/Plugin/Field/FieldWidget/BookingWindowsWidget.php
<?php
namespace Drupal\contacts_events\Plugin\Field\FieldWidget;
use Drupal\contacts_events\Plugin\Field\FieldType\BookingWindowsItemList;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Security\TrustedCallbackInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Plugin implementation of the 'booking_windows' widget.
*
* @FieldWidget(
* id = "booking_windows",
* label = @Translation("Booking windows"),
* field_types = {
* "booking_windows"
* }
* )
*/
class BookingWindowsWidget extends WidgetBase implements TrustedCallbackInterface {
/**
* The date format storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $dateStorage;
/**
* The element info manager.
*
* @var \Drupal\Core\Render\ElementInfoManagerInterface
*/
protected $elementInfo;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
$widget = parent::create($container, $configuration, $plugin_id, $plugin_definition);
$widget->dateStorage = $container->get('entity_type.manager')->getStorage('date_format');
$widget->elementInfo = $container->get('plugin.manager.element_info');
return $widget;
}
/**
* {@inheritdoc}
*/
public static function trustedCallbacks() {
return ['preRenderWrapConfirmed'];
}
/**
* {@inheritdoc}
*/
protected function formMultipleElements(FieldItemListInterface $items, array &$form, FormStateInterface $form_state) {
$elements = parent::formMultipleElements($items, $form, $form_state);
// Don't show the additional element by default.
unset($elements[$elements['#max_delta']]);
return $elements;
}
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$element['#process'][] = [static::class, 'processElement'];
$element['#pre_render'][] = [static::class, 'preRenderWrapConfirmed'];
$element['#element_validate'][] = [static::class, 'validateElement'];
$element['label'] = [
'#type' => 'textfield',
'#title' => $this->t('Label'),
'#default_value' => $items[$delta]->label ?? NULL,
'#size' => $this->getSetting('size'),
'#placeholder' => $this->getSetting('placeholder'),
'#maxlength' => $this->getFieldSetting('max_length'),
'#attributes' => ['class' => ['js-text-full', 'text-full']],
'#weight' => 0,
];
$element['id'] = [
'#type' => 'machine_name',
'#default_value' => $items[$delta]->id ?? NULL,
'#machine_name' => [
'exists' => BookingWindowsItemList::class . '::checkUnique',
],
'#disabled' => isset($items[$delta]->id),
];
$date_base = [
'#type' => 'datetime',
'#required' => FALSE,
'#default_value' => NULL,
'#date_increment' => 1,
'#date_date_callbacks' => [],
'#date_time_callbacks' => [],
];
// Identify the type of date and time elements to use.
switch ($this->getFieldSetting('datetime_type')) {
case DateTimeItem::DATETIME_TYPE_DATE:
$date_base['#date_timezone'] = DateTimeItemInterface::STORAGE_TIMEZONE;
$date_base['#date_date_element'] = 'date';
$date_base['#date_time_element'] = 'none';
$date_base['#date_date_format'] = $this->dateStorage->load('html_date')->getPattern();
$date_base['#date_time_format'] = '';
break;
default:
$date_base['#date_timezone'] = date_default_timezone_get();
$date_base['#date_date_element'] = 'date';
$date_base['#date_time_element'] = 'time';
$date_base['#date_date_format'] = $this->dateStorage->load('html_date')->getPattern();
$date_base['#date_time_format'] = $this->dateStorage->load('html_time')->getPattern();
break;
}
$element['cut_off'] = [
'#title' => $this->t('Paid in full by'),
'#description' => $this->t('This booking window will be locked if the item is paid in full by this date.'),
'#weight' => 1,
] + $date_base;
if (isset($items[$delta]->date)) {
$element['cut_off']['#default_value'] = $this->createDefaultValue($items[$delta]->date, $element['cut_off']['#date_timezone']);
}
$element['use_confirmed'] = [
'#type' => 'checkbox',
'#title' => $this->t('Require confirmation by an earlier date'),
'#default_value' => $items[$delta]->cut_off !== $items[$delta]->cut_off_confirmed,
'#weight' => 2,
];
$element['cut_off_confirmed'] = [
'#title' => $this->t('Confirmed by'),
'#description' => $this->t('This booking window will be locked if the item is confirmed by this date and paid in full by the above date.'),
'#weight' => 3,
] + $date_base;
if (isset($items[$delta]->date_confirmed)) {
$element['cut_off_confirmed']['#default_value'] = $this->createDefaultValue($items[$delta]->date_confirmed, $element['cut_off_confirmed']['#date_timezone']);
}
return $element;
}
/**
* Processes the element to set up the correct source for the ID.
*
* @param array $element
* The form element to process.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array $complete_form
* The complete form structure.
*
* @return array
* The processed element.
*/
public static function processElement(array &$element, FormStateInterface $form_state, array &$complete_form) {
$element['id']['#machine_name']['source'] = $element['#array_parents'];
$element['id']['#machine_name']['source'][] = 'label';
return $element;
}
/**
* Pre render the element to set up the correct states.
*
* @param array $element
* The form element to process.
*/
public static function preRenderWrapConfirmed(array $element) {
$checkbox_parents = $element['#parents'];
$checkbox_parents[] = 'use_confirmed';
$checkbox_name = array_shift($checkbox_parents);
if ($checkbox_parents) {
$checkbox_name .= '[' . implode('][', $checkbox_parents) . ']';
}
$element['cut_off_confirmed'] = [
'#type' => 'container',
'#attributes' => [
'class' => 'js-form-wrapper',
],
'#weight' => $element['cut_off_confirmed']['#weight'],
'#states' => [
'visible' => [':input[name="' . $checkbox_name . '"]' => ['checked' => TRUE]],
],
'element' => $element['cut_off_confirmed'],
];
return $element;
}
/**
* Form validation handler for widget elements.
*
* @param array $element
* The form element.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*/
public static function validateElement(array $element, FormStateInterface $form_state) {
if (!empty($element['use_confirmed']['#value'])) {
if (empty($element['cut_off_confirmed']['#value']['date'])) {
$form_state->setError(
$element['cut_off_confirmed'],
new TranslatableMarkup('%window %confirmed is required.', [
'%window' => $element['label']['#value'],
'%confirmed' => $element['cut_off_confirmed']['#title'],
])
);
}
elseif ($element['cut_off_confirmed']['#value']['date'] >= $element['cut_off']['#value']['date']) {
$form_state->setError(
$element['cut_off_confirmed'],
new TranslatableMarkup('%window %confirmed must be before %paid.', [
'%window' => $element['label']['#value'],
'%paid' => $element['cut_off']['#title'],
'%confirmed' => $element['cut_off_confirmed']['#title'],
])
);
}
}
}
/**
* {@inheritdoc}
*/
public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
// The widget form element type has transformed the value to a
// DrupalDateTime object at this point. We need to convert it back to the
// storage timezone and format.
switch ($this->getFieldSetting('datetime_type')) {
case DateTimeItem::DATETIME_TYPE_DATE:
$format = DateTimeItemInterface::DATE_STORAGE_FORMAT;
break;
default:
$format = DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
break;
}
foreach ($values as &$item) {
if (!empty($item['cut_off']) && $item['cut_off'] instanceof DrupalDateTime) {
$date = $item['cut_off'];
// Adjust the date for storage.
$date->setTimezone(new \DateTimezone(DateTimeItemInterface::STORAGE_TIMEZONE));
$item['cut_off'] = $date->format($format);
}
if (empty($item['use_confirmed'])) {
$item['cut_off_confirmed'] = $item['cut_off'];
}
elseif (!empty($item['cut_off_confirmed']) && $item['cut_off_confirmed'] instanceof DrupalDateTime) {
$date = $item['cut_off_confirmed'];
// Adjust the date for storage.
$date->setTimezone(new \DateTimezone(DateTimeItemInterface::STORAGE_TIMEZONE));
$item['cut_off_confirmed'] = $date->format($format);
}
}
return $values;
}
/**
* Creates a date object for use as a default value.
*
* This will take a default value, apply the proper timezone for display in
* a widget, and set the default time for date-only fields.
*
* @param \Drupal\Core\Datetime\DrupalDateTime $date
* The UTC default date.
* @param string $timezone
* The timezone to apply.
*
* @return \Drupal\Core\Datetime\DrupalDateTime
* A date object for use as a default value in a field widget.
*/
protected function createDefaultValue(DrupalDateTime $date, $timezone): DrupalDateTime {
// The date was created and verified during field_load(), so it is safe to
// use without further inspection.
if ($this->getFieldSetting('datetime_type') === DateTimeItem::DATETIME_TYPE_DATE) {
$date->setDefaultDateTime();
}
$date->setTimezone(new \DateTimeZone($timezone));
return $date;
}
}
