Skip to content
Snippets Groups Projects
Commit b1656002 authored by Jess's avatar Jess
Browse files

Issue #2161337 by mpdonadio, darrick, swentel, effulgentsia, YesCT, xjm,...

Issue #2161337 by mpdonadio, darrick, swentel, effulgentsia, YesCT, xjm, Berdir, tim.plunkett, pguillard, SKAUGHT, sylus, jonathanjfshaw, FluxSauce, DuneBL, jhedstrom, dawehner, generalredneck, pjonckiere, borisson_, Gábor Hojtsy, webchick, miwayha: Add a Date Range field type with support for end date
parent b4d89a9e
2 merge requests!7452Issue #1797438. HTML5 validation is preventing form submit and not fully...,!789Issue #3210310: Adjust Database API to remove deprecated Drupal 9 code in Drupal 10
Showing
with 2605 additions and 0 deletions
......@@ -324,6 +324,10 @@ DateTime module
- Jonathan Hedstrom 'jhedstrom' https://www.drupal.org/u/jhedstrom
- Matthew Donadio 'mpdonadio' https://www.drupal.org/u/mpdonadio
DateTime range module
- Jonathan Hedstrom 'jhedstrom' https://www.drupal.org/u/jhedstrom
- Matthew Donadio 'mpdonadio' https://www.drupal.org/u/mpdonadio
Dynamic Page Cache module
- Fabian Franz 'Fabianx' https://www.drupal.org/u/fabianx
- Wim Leers 'Wim Leers' https://www.drupal.org/u/wim-leers
......
......@@ -88,6 +88,7 @@
"drupal/core-utility": "self.version",
"drupal/core-uuid": "self.version",
"drupal/datetime": "self.version",
"drupal/datetime_range": "self.version",
"drupal/dblog": "self.version",
"drupal/dynamic_page_cache": "self.version",
"drupal/editor": "self.version",
......
# Schema for the configuration files of the Datetime Range module.
# Daterange field type.
field.storage_settings.daterange:
type: field.storage_settings.datetime
label: 'Date range settings'
field.field_settings.daterange:
type: field.field_settings.datetime
label: 'Date range settings'
field.value.daterange:
type: mapping
label: 'Default value'
mapping:
default_date_type:
type: string
label: 'Default start date type'
default_date:
type: string
label: 'Default start date value'
default_end_date_type:
type: string
label: 'Default end date type'
default_end_date:
type: string
label: 'Default end date value'
field.formatter.settings.daterange_default:
type: field.formatter.settings.datetime_default
label: 'Date range default display format settings'
mapping:
separator:
type: string
label: 'Separator'
field.formatter.settings.daterange_plain:
type: field.formatter.settings.datetime_plain
label: 'Date range plain display format settings'
mapping:
separator:
type: string
label: 'Separator'
field.formatter.settings.daterange_custom:
type: field.formatter.settings.datetime_custom
label: 'Date range custom display format settings'
mapping:
separator:
type: string
label: 'Separator'
field.widget.settings.daterange_datelist:
type: mapping
label: 'Date range select list display format settings'
mapping:
increment:
type: integer
label: 'Time increments'
date_order:
type: string
label: 'Date part order'
time_type:
type: string
label: 'Time type'
field.widget.settings.daterange_default:
type: mapping
label: 'Date range default display format settings'
name: 'Datetime Range'
type: module
description: 'Provides the ability to store end dates.'
package: Core (Experimental)
version: VERSION
core: 8.x
dependencies:
- datetime
<?php
/**
* @file
* Field hooks to implement a datetime field that stores a start and end date.
*/
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Implements hook_help().
*/
function datetime_range_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.datetime_range':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Datetime Range module provides a Date field that stores start dates and times, as well as end dates and times. See the <a href=":field">Field module help</a> and the <a href=":field_ui">Field UI module help</a> pages for general information on fields and how to create and manage them. For more information, see the <a href=":datetime_do">online documentation for the Datetime Range module</a>.', array(':field' => \Drupal::url('help.page', array('name' => 'field')), ':field_ui' => (\Drupal::moduleHandler()->moduleExists('field_ui')) ? \Drupal::url('help.page', array('name' => 'field_ui')) : '#', ':datetime_do' => 'https://www.drupal.org/documentation/modules/datetime_range')) . '</p>';
$output .= '<h3>' . t('Uses') . '</h3>';
$output .= '<dl>';
$output .= '<dt>' . t('Managing and displaying date fields') . '</dt>';
$output .= '<dd>' . t('The <em>settings</em> and the <em>display</em> of the Date field can be configured separately. See the <a href=":field_ui">Field UI help</a> for more information on how to manage fields and their display.', array(':field_ui' => (\Drupal::moduleHandler()->moduleExists('field_ui')) ? \Drupal::url('help.page', array('name' => 'field_ui')) : '#')) . '</dd>';
$output .= '<dt>' . t('Displaying dates') . '</dt>';
$output .= '<dd>' . t('Dates can be displayed using the <em>Plain</em> or the <em>Default</em> formatter. The <em>Plain</em> formatter displays the date in the <a href="http://en.wikipedia.org/wiki/ISO_8601">ISO 8601</a> format. If you choose the <em>Default</em> formatter, you can choose a format from a predefined list that can be managed on the <a href=":date_format_list">Date and time formats</a> page.', array(':date_format_list' => \Drupal::url('entity.date_format.collection'))) . '</dd>';
$output .= '</dl>';
return $output;
}
}
<?php
namespace Drupal\datetime_range;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
/**
* Provides friendly methods for datetime range.
*/
trait DateTimeRangeTrait {
/**
* Creates a render array from a date object.
*
* @param \Drupal\Core\Datetime\DrupalDateTime $date
* A date object.
*
* @return array
* A render array.
*/
protected function buildDate(DrupalDateTime $date) {
if ($this->getFieldSetting('datetime_type') == DateTimeItem::DATETIME_TYPE_DATE) {
// A date without time will pick up the current time, use the default.
datetime_date_default_time($date);
}
$this->setTimeZone($date);
$build = [
'#plain_text' => $this->formatDate($date),
'#cache' => [
'contexts' => [
'timezone',
],
],
];
return $build;
}
/**
* Creates a render array from a date object with ISO date attribute.
*
* @param \Drupal\Core\Datetime\DrupalDateTime $date
* A date object.
*
* @return array
* A render array.
*/
protected function buildDateWithIsoAttribute(DrupalDateTime $date) {
if ($this->getFieldSetting('datetime_type') == DateTimeItem::DATETIME_TYPE_DATE) {
// A date without time will pick up the current time, use the default.
datetime_date_default_time($date);
}
// Create the ISO date in Universal Time.
$iso_date = $date->format("Y-m-d\TH:i:s") . 'Z';
$this->setTimeZone($date);
$build = [
'#theme' => 'time',
'#text' => $this->formatDate($date),
'#html' => FALSE,
'#attributes' => [
'datetime' => $iso_date,
],
'#cache' => [
'contexts' => [
'timezone',
],
],
];
return $build;
}
}
<?php
namespace Drupal\datetime_range\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
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 = @Translation("Custom"),
* field_types = {
* "daterange"
* }
* )
*/
class DateRangeCustomFormatter extends DateTimeCustomFormatter {
use DateTimeRangeTrait;
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
'separator' => '-',
] + 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->format('U') !== $end_date->format('U')) {
$elements[$delta] = [
'start_date' => $this->buildDate($start_date),
'separator' => ['#plain_text' => ' ' . $separator . ' '],
'end_date' => $this->buildDate($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['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'),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = parent::settingsSummary();
if ($separator = $this->getSetting('separator')) {
$summary[] = $this->t('Separator: %separator', ['%separator' => $separator]);
}
return $summary;
}
}
<?php
namespace Drupal\datetime_range\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
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 = @Translation("Default"),
* field_types = {
* "daterange"
* }
* )
*/
class DateRangeDefaultFormatter extends DateTimeDefaultFormatter {
use DateTimeRangeTrait;
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
'separator' => '-',
] + 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->format('U') !== $end_date->format('U')) {
$elements[$delta] = [
'start_date' => $this->buildDateWithIsoAttribute($start_date),
'separator' => ['#plain_text' => ' ' . $separator . ' '],
'end_date' => $this->buildDateWithIsoAttribute($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;
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$form = parent::settingsForm($form, $form_state);
$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'),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = parent::settingsSummary();
if ($separator = $this->getSetting('separator')) {
$summary[] = $this->t('Separator: %separator', ['%separator' => $separator]);
}
return $summary;
}
}
<?php
namespace Drupal\datetime_range\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
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 = @Translation("Plain"),
* field_types = {
* "daterange"
* }
* )
*/
class DateRangePlainFormatter extends DateTimePlainFormatter {
use DateTimeRangeTrait;
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
'separator' => '-',
] + 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->format('U') !== $end_date->format('U')) {
$elements[$delta] = [
'start_date' => $this->buildDate($start_date),
'separator' => ['#plain_text' => ' ' . $separator . ' '],
'end_date' => $this->buildDate($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['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'),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = parent::settingsSummary();
if ($separator = $this->getSetting('separator')) {
$summary[] = $this->t('Separator: %separator', ['%separator' => $separator]);
}
return $summary;
}
}
<?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;
/**
* 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' => isset($default_value[0]['default_end_date_type']) ? $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 ? DATETIME_DATE_STORAGE_FORMAT : DATETIME_DATETIME_STORAGE_FORMAT;
$default_values = [[]];
if (!empty($default_value[0]['default_date_type'])) {
$start_date = new DrupalDateTime($default_value[0]['default_date'], DATETIME_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'], DATETIME_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;
}
}
<?php
namespace Drupal\datetime_range\Plugin\Field\FieldType;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\datetime\DateTimeComputed;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
/**
* Plugin implementation of the 'daterange' field type.
*
* @FieldType(
* id = "daterange",
* label = @Translation("Date range"),
* description = @Translation("Create and store date ranges."),
* default_widget = "daterange_default",
* default_formatter = "daterange_default",
* list_class = "\Drupal\datetime_range\Plugin\Field\FieldType\DateRangeFieldItemList"
* )
*/
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 = REQUEST_TIME - mt_rand(0, 86400 * 365) - 86400;
$end = $start + 86400;
if ($type == static::DATETIME_TYPE_DATETIME) {
$values['value'] = gmdate(DATETIME_DATETIME_STORAGE_FORMAT, $start);
$values['end_value'] = gmdate(DATETIME_DATETIME_STORAGE_FORMAT, $end);
}
else {
$values['value'] = gmdate(DATETIME_DATE_STORAGE_FORMAT, $start);
$values['end_value'] = gmdate(DATETIME_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);
}
}
<?php
namespace Drupal\datetime_range\Plugin\Field\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem;
/**
* Plugin implementation of the 'daterange_datelist' widget.
*
* @FieldWidget(
* id = "daterange_datelist",
* label = @Translation("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('5 minute'),
10 => $this->t('10 minute'),
15 => $this->t('15 minute'),
30 => $this->t('30 minute'),
],
];
}
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;
}
}
<?php
namespace Drupal\datetime_range\Plugin\Field\FieldWidget;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
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 = @Translation("Date and time range"),
* field_types = {
* "daterange"
* }
* )
*/
class DateRangeDefaultWidget extends DateRangeWidgetBase implements ContainerFactoryPluginInterface {
/**
* 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;
}
}
<?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\DateTimeItem;
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);
$element['#element_validate'][] = [$this, 'validateStartEnd'];
$element['value']['#title'] = $this->t('Start');
$element['end_value'] = [
'#title' => $this->t('End'),
] + $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.
foreach ($values as &$item) {
if (!empty($item['value']) && $item['value'] instanceof DrupalDateTime) {
/** @var \Drupal\Core\Datetime\DrupalDateTime $start_date */
$start_date = $item['value'];
switch ($this->getFieldSetting('datetime_type')) {
case DateRangeItem::DATETIME_TYPE_DATE:
// If this is a date-only field, set it to the default time so the
// timezone conversion can be reversed.
datetime_date_default_time($start_date);
$format = DATETIME_DATE_STORAGE_FORMAT;
break;
case 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(timezone_open(drupal_get_user_timezone()));
$start_date->setTime(0, 0, 0);
$format = DATETIME_DATETIME_STORAGE_FORMAT;
break;
default:
$format = DATETIME_DATETIME_STORAGE_FORMAT;
break;
}
// Adjust the date for storage.
$start_date->setTimezone(new \DateTimezone(DATETIME_STORAGE_TIMEZONE));
$item['value'] = $start_date->format($format);
}
if (!empty($item['end_value']) && $item['end_value'] instanceof DrupalDateTime) {
/** @var \Drupal\Core\Datetime\DrupalDateTime $end_date */
$end_date = $item['end_value'];
switch ($this->getFieldSetting('datetime_type')) {
case DateRangeItem::DATETIME_TYPE_DATE:
// If this is a date-only field, set it to the default time so the
// timezone conversion can be reversed.
datetime_date_default_time($end_date);
$format = DATETIME_DATE_STORAGE_FORMAT;
break;
case DateRangeItem::DATETIME_TYPE_ALLDAY:
// All day fields end at midnight on the end 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(timezone_open(drupal_get_user_timezone()));
$end_date->setTime(23, 59, 59);
$format = DATETIME_DATETIME_STORAGE_FORMAT;
break;
default:
$format = DATETIME_DATETIME_STORAGE_FORMAT;
break;
}
// Adjust the date for storage.
$end_date->setTimezone(new \DateTimezone(DATETIME_STORAGE_TIMEZONE));
$item['end_value'] = $end_date->format($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->format('U') !== $end_date->format('U')) {
$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']]));
}
}
}
}
/**
* 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($date, $timezone) {
// 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) {
// A date without time will pick up the current time, use the default
// time.
datetime_date_default_time($date);
}
$date->setTimezone(new \DateTimeZone($timezone));
return $date;
}
}
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment