clean install

This commit is contained in:
2024-12-10 15:08:16 +01:00
commit e14eb2d8fd
31193 changed files with 3555714 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
<?php
namespace Drupal\datetime_range;
/**
* Declares constants used in the datetime range module.
*
* @todo Convert this to an enum in 11.0.
* @see https://www.drupal.org/project/drupal/issues/3425141.
*/
interface DateTimeRangeConstantsInterface {
/**
* Values for the 'from_to' formatter setting.
*/
const BOTH = 'both';
const START_DATE = 'start_date';
const END_DATE = 'end_date';
}

View File

@@ -0,0 +1,214 @@
<?php
namespace Drupal\datetime_range;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Field\FieldItemListInterface;
/**
* Provides friendly methods for datetime range.
*/
trait DateTimeRangeTrait {
/**
* Get the default settings for a date and time range display.
*
* @return array
* An array containing default settings.
*/
protected static function dateTimeRangeDefaultSettings(): array {
return [
'from_to' => DateTimeRangeConstantsInterface::BOTH,
'separator' => '-',
];
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = [];
$separator = $this->getSetting('separator');
foreach ($items as $delta => $item) {
if (!empty($item->start_date) && !empty($item->end_date)) {
/** @var \Drupal\Core\Datetime\DrupalDateTime $start_date */
$start_date = $item->start_date;
/** @var \Drupal\Core\Datetime\DrupalDateTime $end_date */
$end_date = $item->end_date;
if ($start_date->getTimestamp() !== $end_date->getTimestamp()) {
$elements[$delta] = $this->renderStartEndWithIsoAttribute($start_date, $separator, $end_date);
}
else {
$elements[$delta] = $this->buildDateWithIsoAttribute($start_date);
if (!empty($item->_attributes)) {
$elements[$delta]['#attributes'] += $item->_attributes;
// Unset field item attributes since they have been included in the
// formatter output and should not be rendered in the field template.
unset($item->_attributes);
}
}
}
}
return $elements;
}
/**
* Configuration form for date time range.
*
* @param array $form
* The form array.
*
* @return array
* Modified form array.
*/
protected function dateTimeRangeSettingsForm(array $form): array {
$form['from_to'] = [
'#type' => 'select',
'#title' => $this->t('Display'),
'#options' => $this->getFromToOptions(),
'#default_value' => $this->getSetting('from_to'),
];
$field_name = $this->fieldDefinition->getName();
$form['separator'] = [
'#type' => 'textfield',
'#title' => $this->t('Date separator'),
'#description' => $this->t('The string to separate the start and end dates'),
'#default_value' => $this->getSetting('separator'),
'#states' => [
'visible' => [
'select[name="fields[' . $field_name . '][settings_edit_form][settings][from_to]"]' => ['value' => DateTimeRangeConstantsInterface::BOTH],
],
],
];
return $form;
}
/**
* Gets the date time range settings summary.
*
* @return array
* An array of summary messages.
*/
protected function dateTimeRangeSettingsSummary(): array {
$summary = [];
if ($from_to = $this->getSetting('from_to')) {
$from_to_options = $this->getFromToOptions();
if (isset($from_to_options[$from_to])) {
$summary[] = $from_to_options[$from_to];
}
}
if (($separator = $this->getSetting('separator')) && $this->getSetting('from_to') === DateTimeRangeConstantsInterface::BOTH) {
$summary[] = $this->t('Separator: %separator', ['%separator' => $separator]);
}
return $summary;
}
/**
* Returns a list of possible values for the 'from_to' setting.
*
* @return array
* A list of 'from_to' options.
*/
protected function getFromToOptions(): array {
return [
DateTimeRangeConstantsInterface::BOTH => $this->t('Display both start and end dates'),
DateTimeRangeConstantsInterface::START_DATE => $this->t('Display start date only'),
DateTimeRangeConstantsInterface::END_DATE => $this->t('Display end date only'),
];
}
/**
* Gets whether the start date should be displayed.
*
* @return bool
* True if the start date should be displayed. False otherwise.
*/
protected function startDateIsDisplayed(): bool {
switch ($this->getSetting('from_to')) {
case DateTimeRangeConstantsInterface::BOTH:
case DateTimeRangeConstantsInterface::START_DATE:
return TRUE;
}
return FALSE;
}
/**
* Gets whether the end date should be displayed.
*
* @return bool
* True if the end date should be displayed. False otherwise.
*/
protected function endDateIsDisplayed(): bool {
switch ($this->getSetting('from_to')) {
case DateTimeRangeConstantsInterface::BOTH:
case DateTimeRangeConstantsInterface::END_DATE:
return TRUE;
}
return FALSE;
}
/**
* Creates a render array given start/end dates.
*
* @param \Drupal\Core\Datetime\DrupalDateTime $start_date
* The start date to be rendered.
* @param string $separator
* The separator string.
* @param \Drupal\Core\Datetime\DrupalDateTime $end_date
* The end date to be rendered.
*
* @return array
* A renderable array for a single date time range.
*/
protected function renderStartEnd(DrupalDateTime $start_date, string $separator, DrupalDateTime $end_date): array {
$element = [];
if ($this->startDateIsDisplayed()) {
$element[DateTimeRangeConstantsInterface::START_DATE] = $this->buildDate($start_date);
}
if ($this->startDateIsDisplayed() && $this->endDateIsDisplayed()) {
$element['separator'] = ['#plain_text' => ' ' . $separator . ' '];
}
if ($this->endDateIsDisplayed()) {
$element[DateTimeRangeConstantsInterface::END_DATE] = $this->buildDate($end_date);
}
return $element;
}
/**
* Creates a render array with ISO attributes given start/end dates.
*
* @param \Drupal\Core\Datetime\DrupalDateTime $start_date
* The start date to be rendered.
* @param string $separator
* The separator string.
* @param \Drupal\Core\Datetime\DrupalDateTime $end_date
* The end date to be rendered.
*
* @return array
* A renderable array for a single date time range.
*/
protected function renderStartEndWithIsoAttribute(DrupalDateTime $start_date, string $separator, DrupalDateTime $end_date): array {
$element = [];
if ($this->startDateIsDisplayed()) {
$element[DateTimeRangeConstantsInterface::START_DATE] = $this->buildDateWithIsoAttribute($start_date);
}
if ($this->startDateIsDisplayed() && $this->endDateIsDisplayed()) {
$element['separator'] = ['#plain_text' => ' ' . $separator . ' '];
}
if ($this->endDateIsDisplayed()) {
$element[DateTimeRangeConstantsInterface::END_DATE] = $this->buildDateWithIsoAttribute($end_date);
}
return $element;
}
}

View File

@@ -0,0 +1,81 @@
<?php
namespace Drupal\datetime_range\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\Attribute\FieldFormatter;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\datetime\Plugin\Field\FieldFormatter\DateTimeCustomFormatter;
use Drupal\datetime_range\DateTimeRangeTrait;
/**
* Plugin implementation of the 'Custom' formatter for 'daterange' fields.
*
* This formatter renders the data range as plain text, with a fully
* configurable date format using the PHP date syntax and separator.
*/
#[FieldFormatter(
id: 'daterange_custom',
label: new TranslatableMarkup('Custom'),
field_types: [
'daterange',
],
)]
class DateRangeCustomFormatter extends DateTimeCustomFormatter {
use DateTimeRangeTrait;
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return static::dateTimeRangeDefaultSettings() + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
// @todo Evaluate removing this method in
// https://www.drupal.org/node/2793143 to determine if the behavior and
// markup in the base class implementation can be used instead.
$elements = [];
$separator = $this->getSetting('separator');
foreach ($items as $delta => $item) {
if (!empty($item->start_date) && !empty($item->end_date)) {
/** @var \Drupal\Core\Datetime\DrupalDateTime $start_date */
$start_date = $item->start_date;
/** @var \Drupal\Core\Datetime\DrupalDateTime $end_date */
$end_date = $item->end_date;
if ($start_date->getTimestamp() !== $end_date->getTimestamp()) {
$elements[$delta] = $this->renderStartEnd($start_date, $separator, $end_date);
}
else {
$elements[$delta] = $this->buildDate($start_date);
}
}
}
return $elements;
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$form = parent::settingsForm($form, $form_state);
$form = $this->dateTimeRangeSettingsForm($form);
return $form;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
return array_merge(parent::settingsSummary(), $this->dateTimeRangeSettingsSummary());
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace Drupal\datetime_range\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\Attribute\FieldFormatter;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\datetime\Plugin\Field\FieldFormatter\DateTimeDefaultFormatter;
use Drupal\datetime_range\DateTimeRangeTrait;
/**
* Plugin implementation of the 'Default' formatter for 'daterange' fields.
*
* This formatter renders the data range using <time> elements, with
* configurable date formats (from the list of configured formats) and a
* separator.
*/
#[FieldFormatter(
id: 'daterange_default',
label: new TranslatableMarkup('Default'),
field_types: [
'daterange',
],
)]
class DateRangeDefaultFormatter extends DateTimeDefaultFormatter {
use DateTimeRangeTrait;
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return static::dateTimeRangeDefaultSettings() + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$form = parent::settingsForm($form, $form_state);
$form = $this->dateTimeRangeSettingsForm($form);
return $form;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
return array_merge(parent::settingsSummary(), $this->dateTimeRangeSettingsSummary());
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace Drupal\datetime_range\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\Attribute\FieldFormatter;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\datetime\Plugin\Field\FieldFormatter\DateTimePlainFormatter;
use Drupal\datetime_range\DateTimeRangeTrait;
/**
* Plugin implementation of the 'Plain' formatter for 'daterange' fields.
*
* This formatter renders the data range as a plain text string, with a
* configurable separator using an ISO-like date format string.
*/
#[FieldFormatter(
id: 'daterange_plain',
label: new TranslatableMarkup('Plain'),
field_types: [
'daterange',
],
)]
class DateRangePlainFormatter extends DateTimePlainFormatter {
use DateTimeRangeTrait;
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return static::dateTimeRangeDefaultSettings() + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = [];
$separator = $this->getSetting('separator');
foreach ($items as $delta => $item) {
if (!empty($item->start_date) && !empty($item->end_date)) {
/** @var \Drupal\Core\Datetime\DrupalDateTime $start_date */
$start_date = $item->start_date;
/** @var \Drupal\Core\Datetime\DrupalDateTime $end_date */
$end_date = $item->end_date;
if ($start_date->getTimestamp() !== $end_date->getTimestamp()) {
$elements[$delta] = $this->renderStartEnd($start_date, $separator, $end_date);
}
else {
$elements[$delta] = $this->buildDate($start_date);
if (!empty($item->_attributes)) {
$elements[$delta]['#attributes'] += $item->_attributes;
// Unset field item attributes since they have been included in the
// formatter output and should not be rendered in the field template.
unset($item->_attributes);
}
}
}
}
return $elements;
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$form = parent::settingsForm($form, $form_state);
$form = $this->dateTimeRangeSettingsForm($form);
return $form;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
return array_merge(parent::settingsSummary(), $this->dateTimeRangeSettingsSummary());
}
}

View File

@@ -0,0 +1,132 @@
<?php
namespace Drupal\datetime_range\Plugin\Field\FieldType;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemList;
use Drupal\Core\Form\FormStateInterface;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeFieldItemList;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
/**
* Represents a configurable entity daterange field.
*/
class DateRangeFieldItemList extends DateTimeFieldItemList {
/**
* {@inheritdoc}
*/
public function defaultValuesForm(array &$form, FormStateInterface $form_state) {
if (empty($this->getFieldDefinition()->getDefaultValueCallback())) {
$default_value = $this->getFieldDefinition()->getDefaultValueLiteral();
$element = parent::defaultValuesForm($form, $form_state);
$element['default_date_type']['#title'] = $this->t('Default start date');
$element['default_date_type']['#description'] = $this->t('Set a default value for the start date.');
$element['default_end_date_type'] = [
'#type' => 'select',
'#title' => $this->t('Default end date'),
'#description' => $this->t('Set a default value for the end date.'),
'#default_value' => $default_value[0]['default_end_date_type'] ?? '',
'#options' => [
static::DEFAULT_VALUE_NOW => $this->t('Current date'),
static::DEFAULT_VALUE_CUSTOM => $this->t('Relative date'),
],
'#empty_value' => '',
];
$element['default_end_date'] = [
'#type' => 'textfield',
'#title' => $this->t('Relative default value'),
'#description' => $this->t("Describe a time by reference to the current day, like '+90 days' (90 days from the day the field is created) or '+1 Saturday' (the next Saturday). See <a href=\"http://php.net/manual/function.strtotime.php\">strtotime</a> for more details."),
'#default_value' => (isset($default_value[0]['default_end_date_type']) && $default_value[0]['default_end_date_type'] == static::DEFAULT_VALUE_CUSTOM) ? $default_value[0]['default_end_date'] : '',
'#states' => [
'visible' => [
':input[id="edit-default-value-input-default-end-date-type"]' => ['value' => static::DEFAULT_VALUE_CUSTOM],
],
],
];
return $element;
}
}
/**
* {@inheritdoc}
*/
public function defaultValuesFormValidate(array $element, array &$form, FormStateInterface $form_state) {
if ($form_state->getValue(['default_value_input', 'default_date_type']) == static::DEFAULT_VALUE_CUSTOM) {
$is_strtotime = @strtotime($form_state->getValue(['default_value_input', 'default_date']));
if (!$is_strtotime) {
$form_state->setErrorByName('default_value_input][default_date', $this->t('The relative start date value entered is invalid.'));
}
}
if ($form_state->getValue(['default_value_input', 'default_end_date_type']) == static::DEFAULT_VALUE_CUSTOM) {
$is_strtotime = @strtotime($form_state->getValue(['default_value_input', 'default_end_date']));
if (!$is_strtotime) {
$form_state->setErrorByName('default_value_input][default_end_date', $this->t('The relative end date value entered is invalid.'));
}
}
}
/**
* {@inheritdoc}
*/
public function defaultValuesFormSubmit(array $element, array &$form, FormStateInterface $form_state) {
if ($form_state->getValue(['default_value_input', 'default_date_type']) || $form_state->getValue(['default_value_input', 'default_end_date_type'])) {
if ($form_state->getValue(['default_value_input', 'default_date_type']) == static::DEFAULT_VALUE_NOW) {
$form_state->setValueForElement($element['default_date'], static::DEFAULT_VALUE_NOW);
}
if ($form_state->getValue(['default_value_input', 'default_end_date_type']) == static::DEFAULT_VALUE_NOW) {
$form_state->setValueForElement($element['default_end_date'], static::DEFAULT_VALUE_NOW);
}
return [$form_state->getValue('default_value_input')];
}
return [];
}
/**
* {@inheritdoc}
*/
public static function processDefaultValue($default_value, FieldableEntityInterface $entity, FieldDefinitionInterface $definition) {
// Explicitly call the base class so that we can get the default value
// types.
$default_value = FieldItemList::processDefaultValue($default_value, $entity, $definition);
// Allow either the start or end date to have a default, but not require
// defaults for both.
if (!empty($default_value[0]['default_date_type']) || !empty($default_value[0]['default_end_date_type'])) {
// A default value should be in the format and timezone used for date
// storage. All-day ranges are stored the same as date+time ranges. We
// only provide a default value for the first item, as do all fields.
// Otherwise, there is no way to clear out unwanted values on multiple
// value fields.
$storage_format = $definition->getSetting('datetime_type') == DateRangeItem::DATETIME_TYPE_DATE ? DateTimeItemInterface::DATE_STORAGE_FORMAT : DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
$default_values = [[]];
if (!empty($default_value[0]['default_date_type'])) {
$start_date = new DrupalDateTime($default_value[0]['default_date'], DateTimeItemInterface::STORAGE_TIMEZONE);
$start_value = $start_date->format($storage_format);
$default_values[0]['value'] = $start_value;
$default_values[0]['start_date'] = $start_date;
}
if (!empty($default_value[0]['default_end_date_type'])) {
$end_date = new DrupalDateTime($default_value[0]['default_end_date'], DateTimeItemInterface::STORAGE_TIMEZONE);
$end_value = $end_date->format($storage_format);
$default_values[0]['end_value'] = $end_value;
$default_values[0]['end_date'] = $end_date;
}
$default_value = $default_values;
}
return $default_value;
}
}

View File

@@ -0,0 +1,139 @@
<?php
namespace Drupal\datetime_range\Plugin\Field\FieldType;
use Drupal\Core\Field\Attribute\FieldType;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\datetime\DateTimeComputed;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
/**
* Plugin implementation of the 'daterange' field type.
*/
#[FieldType(
id: "daterange",
label: new TranslatableMarkup("Date range"),
description: [
new TranslatableMarkup("Ideal for storing durations that consist of start and end dates (and times)"),
new TranslatableMarkup("Choose between setting both date and time, or date only, for each duration"),
new TranslatableMarkup("The system automatically validates that the end date (and time) is later than the start, and both fields are completed"),
],
category: "date_time",
default_widget: "daterange_default",
default_formatter: "daterange_default",
list_class: DateRangeFieldItemList::class,
)]
class DateRangeItem extends DateTimeItem {
/**
* Value for the 'datetime_type' setting: store a date and time.
*/
const DATETIME_TYPE_ALLDAY = 'allday';
/**
* {@inheritdoc}
*/
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
$properties['value'] = DataDefinition::create('datetime_iso8601')
->setLabel(t('Start date value'))
->setRequired(TRUE);
$properties['start_date'] = DataDefinition::create('any')
->setLabel(t('Computed start date'))
->setDescription(t('The computed start DateTime object.'))
->setComputed(TRUE)
->setClass(DateTimeComputed::class)
->setSetting('date source', 'value');
$properties['end_value'] = DataDefinition::create('datetime_iso8601')
->setLabel(t('End date value'))
->setRequired(TRUE);
$properties['end_date'] = DataDefinition::create('any')
->setLabel(t('Computed end date'))
->setDescription(t('The computed end DateTime object.'))
->setComputed(TRUE)
->setClass(DateTimeComputed::class)
->setSetting('date source', 'end_value');
return $properties;
}
/**
* {@inheritdoc}
*/
public static function schema(FieldStorageDefinitionInterface $field_definition) {
$schema = parent::schema($field_definition);
$schema['columns']['value']['description'] = 'The start date value.';
$schema['columns']['end_value'] = [
'description' => 'The end date value.',
] + $schema['columns']['value'];
$schema['indexes']['end_value'] = ['end_value'];
return $schema;
}
/**
* {@inheritdoc}
*/
public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
$element = parent::storageSettingsForm($form, $form_state, $has_data);
$element['datetime_type']['#options'][static::DATETIME_TYPE_ALLDAY] = $this->t('All Day');
return $element;
}
/**
* {@inheritdoc}
*/
public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
$type = $field_definition->getSetting('datetime_type');
// Just pick a date in the past year. No guidance is provided by this Field
// type.
$start = \Drupal::time()->getRequestTime() - mt_rand(0, 86400 * 365) - 86400;
$end = $start + 86400;
if ($type == static::DATETIME_TYPE_DATETIME) {
$values['value'] = gmdate(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, $start);
$values['end_value'] = gmdate(DateTimeItemInterface::DATETIME_STORAGE_FORMAT, $end);
}
else {
$values['value'] = gmdate(DateTimeItemInterface::DATE_STORAGE_FORMAT, $start);
$values['end_value'] = gmdate(DateTimeItemInterface::DATE_STORAGE_FORMAT, $end);
}
return $values;
}
/**
* {@inheritdoc}
*/
public function isEmpty() {
$start_value = $this->get('value')->getValue();
$end_value = $this->get('end_value')->getValue();
return ($start_value === NULL || $start_value === '') && ($end_value === NULL || $end_value === '');
}
/**
* {@inheritdoc}
*/
public function onChange($property_name, $notify = TRUE) {
// Enforce that the computed date is recalculated.
if ($property_name == 'value') {
$this->start_date = NULL;
}
elseif ($property_name == 'end_value') {
$this->end_date = NULL;
}
parent::onChange($property_name, $notify);
}
}

View File

@@ -0,0 +1,156 @@
<?php
namespace Drupal\datetime_range\Plugin\Field\FieldWidget;
use Drupal\Core\Field\Attribute\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem;
/**
* Plugin implementation of the 'daterange_datelist' widget.
*/
#[FieldWidget(
id: 'daterange_datelist',
label: new TranslatableMarkup('Select list'),
field_types: ['daterange'],
)]
class DateRangeDatelistWidget extends DateRangeWidgetBase {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
'increment' => '15',
'date_order' => 'YMD',
'time_type' => '24',
] + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$element = parent::formElement($items, $delta, $element, $form, $form_state);
$date_order = $this->getSetting('date_order');
if ($this->getFieldSetting('datetime_type') == DateRangeItem::DATETIME_TYPE_DATETIME) {
$time_type = $this->getSetting('time_type');
$increment = $this->getSetting('increment');
}
else {
$time_type = '';
$increment = '';
}
// Set up the date part order array.
switch ($date_order) {
default:
case 'YMD':
$date_part_order = ['year', 'month', 'day'];
break;
case 'MDY':
$date_part_order = ['month', 'day', 'year'];
break;
case 'DMY':
$date_part_order = ['day', 'month', 'year'];
break;
}
switch ($time_type) {
case '24':
$date_part_order = array_merge($date_part_order, ['hour', 'minute']);
break;
case '12':
$date_part_order = array_merge($date_part_order, ['hour', 'minute', 'ampm']);
break;
case 'none':
break;
}
$element['value'] = [
'#type' => 'datelist',
'#date_increment' => $increment,
'#date_part_order' => $date_part_order,
] + $element['value'];
$element['end_value'] = [
'#type' => 'datelist',
'#date_increment' => $increment,
'#date_part_order' => $date_part_order,
] + $element['end_value'];
return $element;
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$element = parent::settingsForm($form, $form_state);
$element['date_order'] = [
'#type' => 'select',
'#title' => $this->t('Date part order'),
'#default_value' => $this->getSetting('date_order'),
'#options' => ['MDY' => $this->t('Month/Day/Year'), 'DMY' => $this->t('Day/Month/Year'), 'YMD' => $this->t('Year/Month/Day')],
];
if ($this->getFieldSetting('datetime_type') == DateRangeItem::DATETIME_TYPE_DATETIME) {
$element['time_type'] = [
'#type' => 'select',
'#title' => $this->t('Time type'),
'#default_value' => $this->getSetting('time_type'),
'#options' => ['24' => $this->t('24 hour time'), '12' => $this->t('12 hour time')],
];
$element['increment'] = [
'#type' => 'select',
'#title' => $this->t('Time increments'),
'#default_value' => $this->getSetting('increment'),
'#options' => [
1 => $this->t('1 minute'),
5 => $this->t('@count minutes', ['@count' => 5]),
10 => $this->t('@count minutes', ['@count' => 10]),
15 => $this->t('@count minutes', ['@count' => 15]),
30 => $this->t('@count minutes', ['@count' => 30]),
],
];
}
else {
$element['time_type'] = [
'#type' => 'hidden',
'#value' => 'none',
];
$element['increment'] = [
'#type' => 'hidden',
'#value' => $this->getSetting('increment'),
];
}
return $element;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = [];
$summary[] = $this->t('Date part order: @order', ['@order' => $this->getSetting('date_order')]);
if ($this->getFieldSetting('datetime_type') == DateRangeItem::DATETIME_TYPE_DATETIME) {
$summary[] = $this->t('Time type: @time_type', ['@time_type' => $this->getSetting('time_type')]);
$summary[] = $this->t('Time increments: @increment', ['@increment' => $this->getSetting('increment')]);
}
return $summary;
}
}

View File

@@ -0,0 +1,99 @@
<?php
namespace Drupal\datetime_range\Plugin\Field\FieldWidget;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Field\Attribute\FieldWidget;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Plugin implementation of the 'daterange_default' widget.
*/
#[FieldWidget(
id: 'daterange_default',
label: new TranslatableMarkup('Date and time range'),
field_types: ['daterange'],
)]
class DateRangeDefaultWidget extends DateRangeWidgetBase {
/**
* The date format storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $dateStorage;
/**
* {@inheritdoc}
*/
public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, EntityStorageInterface $date_storage) {
parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
$this->dateStorage = $date_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$plugin_id,
$plugin_definition,
$configuration['field_definition'],
$configuration['settings'],
$configuration['third_party_settings'],
$container->get('entity_type.manager')->getStorage('date_format')
);
}
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$element = parent::formElement($items, $delta, $element, $form, $form_state);
// Identify the type of date and time elements to use.
switch ($this->getFieldSetting('datetime_type')) {
case DateRangeItem::DATETIME_TYPE_DATE:
case DateRangeItem::DATETIME_TYPE_ALLDAY:
$date_type = 'date';
$time_type = 'none';
$date_format = $this->dateStorage->load('html_date')->getPattern();
$time_format = '';
break;
default:
$date_type = 'date';
$time_type = 'time';
$date_format = $this->dateStorage->load('html_date')->getPattern();
$time_format = $this->dateStorage->load('html_time')->getPattern();
break;
}
$element['value'] += [
'#date_date_format' => $date_format,
'#date_date_element' => $date_type,
'#date_date_callbacks' => [],
'#date_time_format' => $time_format,
'#date_time_element' => $time_type,
'#date_time_callbacks' => [],
];
$element['end_value'] += [
'#date_date_format' => $date_format,
'#date_date_element' => $date_type,
'#date_date_callbacks' => [],
'#date_time_format' => $time_format,
'#date_time_element' => $time_type,
'#date_time_callbacks' => [],
];
return $element;
}
}

View File

@@ -0,0 +1,129 @@
<?php
namespace Drupal\datetime_range\Plugin\Field\FieldWidget;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
use Drupal\datetime\Plugin\Field\FieldWidget\DateTimeWidgetBase;
use Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem;
/**
* Base class for the 'daterange_*' widgets.
*/
class DateRangeWidgetBase extends DateTimeWidgetBase {
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
$element = parent::formElement($items, $delta, $element, $form, $form_state);
// Wrap all of the select elements with a fieldset.
$element['#theme_wrappers'][] = 'fieldset';
$element['#element_validate'][] = [$this, 'validateStartEnd'];
$element['value']['#title'] = $this->t('Start date');
$element['end_value'] = [
'#title' => $this->t('End date'),
] + $element['value'];
if ($items[$delta]->start_date) {
/** @var \Drupal\Core\Datetime\DrupalDateTime $start_date */
$start_date = $items[$delta]->start_date;
$element['value']['#default_value'] = $this->createDefaultValue($start_date, $element['value']['#date_timezone']);
}
if ($items[$delta]->end_date) {
/** @var \Drupal\Core\Datetime\DrupalDateTime $end_date */
$end_date = $items[$delta]->end_date;
$element['end_value']['#default_value'] = $this->createDefaultValue($end_date, $element['end_value']['#date_timezone']);
}
return $element;
}
/**
* {@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.
$datetime_type = $this->getFieldSetting('datetime_type');
if ($datetime_type === DateRangeItem::DATETIME_TYPE_DATE) {
$storage_format = DateTimeItemInterface::DATE_STORAGE_FORMAT;
}
else {
$storage_format = DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
}
$storage_timezone = new \DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE);
$user_timezone = new \DateTimeZone(date_default_timezone_get());
foreach ($values as &$item) {
if (!empty($item['value']) && $item['value'] instanceof DrupalDateTime) {
/** @var \Drupal\Core\Datetime\DrupalDateTime $start_date */
$start_date = $item['value'];
if ($datetime_type === DateRangeItem::DATETIME_TYPE_ALLDAY) {
// All day fields start at midnight on the starting date, but are
// stored like datetime fields, so we need to adjust the time.
// This function is called twice, so to prevent a double conversion
// we need to explicitly set the timezone.
$start_date->setTimeZone($user_timezone)->setTime(0, 0, 0);
}
// Adjust the date for storage.
$item['value'] = $start_date->setTimezone($storage_timezone)->format($storage_format);
}
if (!empty($item['end_value']) && $item['end_value'] instanceof DrupalDateTime) {
/** @var \Drupal\Core\Datetime\DrupalDateTime $end_date */
$end_date = $item['end_value'];
if ($datetime_type === DateRangeItem::DATETIME_TYPE_ALLDAY) {
// All day fields start at midnight on the starting date, but are
// stored like datetime fields, so we need to adjust the time.
// This function is called twice, so to prevent a double conversion
// we need to explicitly set the timezone.
$end_date->setTimeZone($user_timezone)->setTime(23, 59, 59);
}
// Adjust the date for storage.
$item['end_value'] = $end_date->setTimezone($storage_timezone)->format($storage_format);
}
}
return $values;
}
/**
* #element_validate callback to ensure that the start date <= the end date.
*
* @param array $element
* An associative array containing the properties and children of the
* generic form element.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
* @param array $complete_form
* The complete form structure.
*/
public function validateStartEnd(array &$element, FormStateInterface $form_state, array &$complete_form) {
$start_date = $element['value']['#value']['object'];
$end_date = $element['end_value']['#value']['object'];
if ($start_date instanceof DrupalDateTime && $end_date instanceof DrupalDateTime) {
if ($start_date->getTimestamp() !== $end_date->getTimestamp()) {
$interval = $start_date->diff($end_date);
if ($interval->invert === 1) {
$form_state->setError($element, $this->t('The @title end date cannot be before the start date', ['@title' => $element['#title']]));
}
}
}
}
}