Move all files to 2017/

This commit is contained in:
Oliver Davies 2025-09-29 22:25:17 +01:00
parent ac7370f67f
commit 2875863330
15717 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,73 @@
# 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: label
label: 'Separator'
translation context: 'Date range separator'
field.formatter.settings.daterange_plain:
type: field.formatter.settings.datetime_plain
label: 'Date range plain display format settings'
mapping:
separator:
type: label
label: 'Separator'
translation context: 'Date range separator'
field.formatter.settings.daterange_custom:
type: field.formatter.settings.datetime_custom
label: 'Date range custom display format settings'
mapping:
separator:
type: label
label: 'Separator'
translation context: 'Date range 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'

View file

@ -0,0 +1,8 @@
name: 'Datetime Range'
type: module
description: 'Provides the ability to store end dates.'
package: Field types
version: VERSION
core: 8.x
dependencies:
- drupal:datetime

View file

@ -0,0 +1,168 @@
<?php
/**
* @file
* Field hooks to implement a datetime field that stores a start and end date.
*/
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\views\Views;
use Drupal\views\ViewEntityInterface;
/**
* 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>.', [':field' => \Drupal::url('help.page', ['name' => 'field']), ':field_ui' => (\Drupal::moduleHandler()->moduleExists('field_ui')) ? \Drupal::url('help.page', ['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.', [':field_ui' => (\Drupal::moduleHandler()->moduleExists('field_ui')) ? \Drupal::url('help.page', ['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.', [':date_format_list' => \Drupal::url('entity.date_format.collection')]) . '</dd>';
$output .= '</dl>';
return $output;
}
}
/**
* Implements hook_view_presave().
*
* When a view is saved using the old string or standard plugin format for
* Datetime Range filters or sorts, they will automatically be updated to
* Datetime filters or sorts. Old plugins usage must to be considered
* deprecated and must be converted before 9.0.0, when this updating layer will
* be removed.
*
* @deprecated in Drupal 8.5.x and will be removed before 9.0.0.
*
* @see https://www.drupal.org/node/2857691
*/
function datetime_range_view_presave(ViewEntityInterface $view) {
$config_factory = \Drupal::configFactory();
$displays = $view->get('display');
$changed = FALSE;
foreach ($displays as $display_name => &$display) {
// Update datetime_range filters.
if (isset($display['display_options']['filters'])) {
foreach ($display['display_options']['filters'] as $field_name => &$filter) {
if ($filter['plugin_id'] === 'string') {
$table_data = Views::viewsData()->get($filter['table']);
if (!$table_data) {
continue;
}
// Get field config.
$filter_views_data = $table_data[$filter['field']]['filter'];
if (!isset($filter_views_data['entity_type']) || !isset($filter_views_data['field_name'])) {
continue;
}
$field_storage_name = 'field.storage.' . $filter_views_data['entity_type'] . '.' . $filter_views_data['field_name'];
$field_configuration = $config_factory->get($field_storage_name);
if ($field_configuration->get('type') === 'daterange') {
// Set entity_type if missing.
if (!isset($filter['entity_type'])) {
$filter['entity_type'] = $filter_views_data['entity_type'];
}
// Set datetime plugin_id.
$filter['plugin_id'] = 'datetime';
// Create datetime value array.
$datetime_value = [
'min' => '',
'max' => '',
'value' => $filter['value'],
'type' => 'date',
];
// Map string operator/value to numeric equivalent.
switch ($filter['operator']) {
case '=':
case 'empty':
case 'not empty':
$operator = $filter['operator'];
break;
case '!=':
case 'not':
$operator = '!=';
break;
case 'starts':
$operator = 'regular_expression';
$datetime_value['value'] = '^' . preg_quote($datetime_value['value']);
break;
case 'ends':
$operator = 'regular_expression';
$datetime_value['value'] = preg_quote($datetime_value['value']) . '$';
break;
default:
$operator = 'regular_expression';
// Add .* to prevent blank regexes.
if (empty($datetime_value['value'])) {
$datetime_value['value'] = '.*';
}
else {
$datetime_value['value'] = preg_quote($datetime_value['value']);
}
}
// Set value and operator.
$filter['value'] = $datetime_value;
$filter['operator'] = $operator;
$changed = TRUE;
@trigger_error('Use of string filters for datetime_range fields is deprecated. Use the datetime filters instead. See https://www.drupal.org/node/2857691', E_USER_DEPRECATED);
}
}
}
}
// Update datetime_range sort handlers.
if (isset($display['display_options']['sorts'])) {
foreach ($display['display_options']['sorts'] as $field_name => &$sort) {
if ($sort['plugin_id'] === 'standard') {
$table_data = Views::viewsData()->get($sort['table']);
if (!$table_data) {
continue;
}
// Get field config.
$sort_views_data = $table_data[$sort['field']]['sort'];
if (!isset($sort_views_data['entity_type']) || !isset($sort_views_data['field_name'])) {
continue;
}
$field_storage_name = 'field.storage.' . $sort_views_data['entity_type'] . '.' . $sort_views_data['field_name'];
$field_configuration = $config_factory->get($field_storage_name);
if ($field_configuration->get('type') === 'daterange') {
// Set entity_type if missing.
if (!isset($sort['entity_type'])) {
$sort['entity_type'] = $sort_views_data['entity_type'];
}
// Set datetime plugin_id.
$sort['plugin_id'] = 'datetime';
$changed = TRUE;
@trigger_error('Use of standard sort handlers for datetime_range fields is deprecated. Use the datetime sort handlers instead. See https://www.drupal.org/node/2857691', E_USER_DEPRECATED);
}
}
}
}
}
if ($changed) {
$view->set('display', $displays);
}
}

View file

@ -0,0 +1,92 @@
<?php
/**
* @file
* Post-update functions for Datetime Range module.
*/
use Drupal\views\Views;
/**
* Clear caches to ensure schema changes are read.
*/
function datetime_range_post_update_translatable_separator() {
// Empty post-update hook to cause a cache rebuild.
}
/**
* Update existing views using datetime_range fields.
*/
function datetime_range_post_update_views_string_plugin_id() {
/* @var \Drupal\views\Entity\View[] $views */
$views = \Drupal::entityTypeManager()->getStorage('view')->loadMultiple();
$config_factory = \Drupal::configFactory();
$message = NULL;
$ids = [];
foreach ($views as $view) {
$displays = $view->get('display');
$needs_bc_layer_update = FALSE;
foreach ($displays as $display_name => $display) {
// Check if datetime_range filters need updates.
if (!$needs_bc_layer_update && isset($display['display_options']['filters'])) {
foreach ($display['display_options']['filters'] as $field_name => $filter) {
if ($filter['plugin_id'] == 'string') {
// Get field config.
$filter_views_data = Views::viewsData()->get($filter['table'])[$filter['field']]['filter'];
if (!isset($filter_views_data['entity_type']) || !isset($filter_views_data['field_name'])) {
continue;
}
$field_storage_name = 'field.storage.' . $filter_views_data['entity_type'] . '.' . $filter_views_data['field_name'];
$field_configuration = $config_factory->get($field_storage_name);
if ($field_configuration->get('type') == 'daterange') {
// Trigger the BC layer control.
$needs_bc_layer_update = TRUE;
continue 2;
}
}
}
}
// Check if datetime_range sort handlers need updates.
if (!$needs_bc_layer_update && isset($display['display_options']['sorts'])) {
foreach ($display['display_options']['sorts'] as $field_name => $sort) {
if ($sort['plugin_id'] == 'standard') {
// Get field config.
$sort_views_data = Views::viewsData()->get($sort['table'])[$sort['field']]['sort'];
if (!isset($sort_views_data['entity_type']) || !isset($sort_views_data['field_name'])) {
continue;
}
$field_storage_name = 'field.storage.' . $sort_views_data['entity_type'] . '.' . $sort_views_data['field_name'];
$field_configuration = $config_factory->get($field_storage_name);
if ($field_configuration->get('type') == 'daterange') {
// Trigger the BC layer control.
$needs_bc_layer_update = TRUE;
continue 2;
}
}
}
}
}
// If current view needs BC layer updates save it and the hook view_presave
// will do the rest.
if ($needs_bc_layer_update) {
$view->save();
$ids[] = $view->id();
}
}
if (!empty($ids)) {
$message = \Drupal::translation()->translate('Updated datetime_range filter/sort plugins for views: @ids', ['@ids' => implode(', ', array_unique($ids))]);
}
return $message;
}

View file

@ -0,0 +1,24 @@
<?php
/**
* @file
* Provides views data for the datetime_range module.
*/
use Drupal\field\FieldStorageConfigInterface;
/**
* Implements hook_field_views_data().
*/
function datetime_range_field_views_data(FieldStorageConfigInterface $field_storage) {
// Include datetime.views.inc file in order for helper function
// datetime_type_field_views_data_helper() to be available.
\Drupal::moduleHandler()->loadInclude('datetime', 'inc', 'datetime.views');
// Get datetime field data for value and end_value.
$data = datetime_type_field_views_data_helper($field_storage, [], 'value');
$data = datetime_type_field_views_data_helper($field_storage, $data, 'end_value');
return $data;
}

View file

@ -0,0 +1,49 @@
<?php
namespace Drupal\datetime_range;
use Drupal\Core\Field\FieldItemListInterface;
/**
* Provides friendly methods for datetime range.
*/
trait DateTimeRangeTrait {
/**
* {@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] = [
'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;
}
}

View file

@ -0,0 +1,99 @@
<?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) {
// @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] = [
'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;
}
}

View file

@ -0,0 +1,66 @@
<?php
namespace Drupal\datetime_range\Plugin\Field\FieldFormatter;
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 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;
}
}

View file

@ -0,0 +1,103 @@
<?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->getTimestamp() !== $end_date->getTimestamp()) {
$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);
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;
}
}

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' => 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 ? 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,133 @@
<?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;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
/**
* 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(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,157 @@
<?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;
}
}

View file

@ -0,0 +1,101 @@
<?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;
}
}

View file

@ -0,0 +1,139 @@
<?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.
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:
$format = DateTimeItemInterface::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 = DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
break;
default:
$format = DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
break;
}
// Adjust the date for storage.
$start_date->setTimezone(new \DateTimezone(DateTimeItemInterface::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:
$format = DateTimeItemInterface::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 = DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
break;
default:
$format = DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
break;
}
// Adjust the date for storage.
$end_date->setTimezone(new \DateTimezone(DateTimeItemInterface::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->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']]));
}
}
}
}
}

View file

@ -0,0 +1,332 @@
<?php
// @codingStandardsIgnoreFile
/**
* @file
* Contains database additions to drupal-8.bare.standard.php.gz for testing the
* upgrade path of https://www.drupal.org/node/2786577.
*/
use Drupal\Core\Database\Database;
use Drupal\Core\Serialization\Yaml;
$connection = Database::getConnection();
// Configuration for an datetime_range field storage.
$field_storage_datetime_range = Yaml::decode(file_get_contents(__DIR__ . '/field.storage.node.field_range.yml'));
// Configuration for a datetime_range field on 'page' node bundle.
$field_datetime_range = Yaml::decode(file_get_contents(__DIR__ . '/field.field.node.page.field_range.yml'));
// Configuration for a View using datetime_range plugins.
$views_datetime_range = Yaml::decode(file_get_contents(__DIR__ . '/views.view.test_datetime_range_filter_values.yml'));
// Update core.entity_form_display.node.page.default
$data = $connection->select('config')
->fields('config', ['data'])
->condition('collection', '')
->condition('name', 'core.entity_form_display.node.page.default')
->execute()
->fetchField();
$data = unserialize($data);
$data['dependencies']['config'][] = 'field.field.' . $field_datetime_range['id'];
$data['dependencies']['module'][] = 'datetime_range';
$data['content'][$field_datetime_range['field_name']] = array(
"weight"=> 27,
"settings" => array(),
"third_party_settings" => array(),
"type" => "daterange_default",
"region" => "content"
);
$connection->update('config')
->fields([
'data' => serialize($data),
])
->condition('collection', '')
->condition('name', 'core.entity_form_display.node.page.default')
->execute();
// Update core.entity_view_display.node.page.default
$data = $connection->select('config')
->fields('config', ['data'])
->condition('collection', '')
->condition('name', 'core.entity_view_display.node.page.default')
->execute()
->fetchField();
$data = unserialize($data);
$data['dependencies']['config'][] = 'field.field.' . $field_datetime_range['id'];
$data['dependencies']['module'][] = 'datetime_range';
$data['content'][$field_datetime_range['field_name']] = array(
"weight"=> 102,
"label"=> "above",
"settings" => array("separator"=> "-", "format_type" => "medium", "timezone_override" => ""),
"third_party_settings" => array(),
"type" => "daterange_default",
"region" => "content"
);
$connection->update('config')
->fields([
'data' => serialize($data),
])
->condition('collection', '')
->condition('name', 'core.entity_view_display.node.page.default')
->execute();
$connection->insert('config')
->fields(array(
'collection',
'name',
'data',
))
->values(array(
'collection' => '',
'name' => 'field.field.' . $field_datetime_range['id'],
'data' => serialize($field_datetime_range),
))
->values(array(
'collection' => '',
'name' => 'field.storage.' . $field_storage_datetime_range['id'],
'data' => serialize($field_storage_datetime_range),
))
->values(array(
'collection' => '',
'name' => 'views.view.' . $views_datetime_range['id'],
'data' => serialize($views_datetime_range),
))
->execute();
// Update core.extension.
$extensions = $connection->select('config')
->fields('config', ['data'])
->condition('collection', '')
->condition('name', 'core.extension')
->execute()
->fetchField();
$extensions = unserialize($extensions);
$extensions['module']['datetime_range'] = 0;
$connection->update('config')
->fields([
'data' => serialize($extensions),
])
->condition('collection', '')
->condition('name', 'core.extension')
->execute();
$connection->insert('key_value')
->fields(array(
'collection',
'name',
'value',
))
->values(array(
'collection' => 'config.entity.key_store.field_config',
'name' => 'uuid:87dc4221-8d56-4112-8a7f-7a855ac35d08',
'value' => 'a:1:{i:0;s:33:"field.field.' . $field_datetime_range['id'] . '";}',
))
->values(array(
'collection' => 'config.entity.key_store.field_storage_config',
'name' => 'uuid:2190ad8c-39dd-4eb1-b189-1bfc0c244a40',
'value' => 'a:1:{i:0;s:30:"field.storage.' . $field_storage_datetime_range['id'] . '";}',
))
->values(array(
'collection' => 'config.entity.key_store.view',
'name' => 'uuid:d20760b6-7cc4-4844-ae04-96da7225a46f',
'value' => 'a:1:{i:0;s:44:"views.view.' . $views_datetime_range['id'] . '";}',
))
->values(array(
'collection' => 'entity.storage_schema.sql',
'name' => 'node.field_schema_data.field_range',
'value' => 'a:2:{s:17:"node__field_range";a:4:{s:11:"description";s:40:"Data storage for node field field_range.";s:6:"fields";a:8:{s:6:"bundle";a:5:{s:4:"type";s:13:"varchar_ascii";s:6:"length";i:128;s:8:"not null";b:1;s:7:"default";s:0:"";s:11:"description";s:88:"The field instance bundle to which this row belongs, used when deleting a field instance";}s:7:"deleted";a:5:{s:4:"type";s:3:"int";s:4:"size";s:4:"tiny";s:8:"not null";b:1;s:7:"default";i:0;s:11:"description";s:60:"A boolean indicating whether this data item has been deleted";}s:9:"entity_id";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:38:"The entity id this data is attached to";}s:11:"revision_id";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:47:"The entity revision id this data is attached to";}s:8:"langcode";a:5:{s:4:"type";s:13:"varchar_ascii";s:6:"length";i:32;s:8:"not null";b:1;s:7:"default";s:0:"";s:11:"description";s:37:"The language code for this data item.";}s:5:"delta";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:67:"The sequence number for this data item, used for multi-value fields";}s:17:"field_range_value";a:4:{s:11:"description";s:21:"The start date value.";s:4:"type";s:7:"varchar";s:6:"length";i:20;s:8:"not null";b:1;}s:21:"field_range_end_value";a:4:{s:11:"description";s:19:"The end date value.";s:4:"type";s:7:"varchar";s:6:"length";i:20;s:8:"not null";b:1;}}s:11:"primary key";a:4:{i:0;s:9:"entity_id";i:1;s:7:"deleted";i:2;s:5:"delta";i:3;s:8:"langcode";}s:7:"indexes";a:4:{s:6:"bundle";a:1:{i:0;s:6:"bundle";}s:11:"revision_id";a:1:{i:0;s:11:"revision_id";}s:17:"field_range_value";a:1:{i:0;s:17:"field_range_value";}s:21:"field_range_end_value";a:1:{i:0;s:21:"field_range_end_value";}}}s:26:"node_revision__field_range";a:4:{s:11:"description";s:52:"Revision archive storage for node field field_range.";s:6:"fields";a:8:{s:6:"bundle";a:5:{s:4:"type";s:13:"varchar_ascii";s:6:"length";i:128;s:8:"not null";b:1;s:7:"default";s:0:"";s:11:"description";s:88:"The field instance bundle to which this row belongs, used when deleting a field instance";}s:7:"deleted";a:5:{s:4:"type";s:3:"int";s:4:"size";s:4:"tiny";s:8:"not null";b:1;s:7:"default";i:0;s:11:"description";s:60:"A boolean indicating whether this data item has been deleted";}s:9:"entity_id";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:38:"The entity id this data is attached to";}s:11:"revision_id";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:47:"The entity revision id this data is attached to";}s:8:"langcode";a:5:{s:4:"type";s:13:"varchar_ascii";s:6:"length";i:32;s:8:"not null";b:1;s:7:"default";s:0:"";s:11:"description";s:37:"The language code for this data item.";}s:5:"delta";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:67:"The sequence number for this data item, used for multi-value fields";}s:17:"field_range_value";a:4:{s:11:"description";s:21:"The start date value.";s:4:"type";s:7:"varchar";s:6:"length";i:20;s:8:"not null";b:1;}s:21:"field_range_end_value";a:4:{s:11:"description";s:19:"The end date value.";s:4:"type";s:7:"varchar";s:6:"length";i:20;s:8:"not null";b:1;}}s:11:"primary key";a:5:{i:0;s:9:"entity_id";i:1;s:11:"revision_id";i:2;s:7:"deleted";i:3;s:5:"delta";i:4;s:8:"langcode";}s:7:"indexes";a:4:{s:6:"bundle";a:1:{i:0;s:6:"bundle";}s:11:"revision_id";a:1:{i:0;s:11:"revision_id";}s:17:"field_range_value";a:1:{i:0;s:17:"field_range_value";}s:21:"field_range_end_value";a:1:{i:0;s:21:"field_range_end_value";}}}}',
))
->values(array(
'collection' => 'system.schema',
'name' => 'datetime_range',
'value' => 'i:8000;',
))
->execute();
// Update entity.definitions.bundle_field_map
$value = $connection->select('key_value')
->fields('key_value', ['value'])
->condition('collection', 'entity.definitions.bundle_field_map')
->condition('name', 'node')
->execute()
->fetchField();
$value = unserialize($value);
$value["field_range"] = array("type" => "daterange", "bundles" => array("page" => "page"));
$connection->update('key_value')
->fields([
'value' => serialize($value),
])
->condition('collection', 'entity.definitions.bundle_field_map')
->condition('name', 'node')
->execute();
// Update system.module.files
$files = $connection->select('key_value')
->fields('key_value', ['value'])
->condition('collection', 'state')
->condition('name', 'system.module.files')
->execute()
->fetchField();
$files = unserialize($files);
$files["datetime_range"] = "core/modules/datetime_range/datetime_range.info.yml";
$connection->update('key_value')
->fields([
'value' => serialize($files),
])
->condition('collection', 'state')
->condition('name', 'system.module.files')
->execute();
$connection->schema()->createTable('node__field_range', array(
'fields' => array(
'bundle' => array(
'type' => 'varchar_ascii',
'not null' => TRUE,
'length' => '128',
'default' => '',
),
'deleted' => array(
'type' => 'int',
'not null' => TRUE,
'size' => 'tiny',
'default' => '0',
),
'entity_id' => array(
'type' => 'int',
'not null' => TRUE,
'size' => 'normal',
'unsigned' => TRUE,
),
'revision_id' => array(
'type' => 'int',
'not null' => TRUE,
'size' => 'normal',
'unsigned' => TRUE,
),
'langcode' => array(
'type' => 'varchar_ascii',
'not null' => TRUE,
'length' => '32',
'default' => '',
),
'delta' => array(
'type' => 'int',
'not null' => TRUE,
'size' => 'normal',
'unsigned' => TRUE,
),
'field_range_value' => array(
'type' => 'varchar',
'not null' => TRUE,
'length' => '20',
),
'field_range_end_value' => array(
'type' => 'varchar',
'not null' => TRUE,
'length' => '20',
),
),
'primary key' => array(
'entity_id',
'deleted',
'delta',
'langcode',
),
'indexes' => array(
'bundle' => array(
'bundle',
),
'revision_id' => array(
'revision_id',
),
'field_range_value' => array(
'field_range_value',
),
'field_range_end_value' => array(
'field_range_end_value',
),
),
'mysql_character_set' => 'utf8mb4',
));
$connection->schema()->createTable('node_revision__field_range', array(
'fields' => array(
'bundle' => array(
'type' => 'varchar_ascii',
'not null' => TRUE,
'length' => '128',
'default' => '',
),
'deleted' => array(
'type' => 'int',
'not null' => TRUE,
'size' => 'tiny',
'default' => '0',
),
'entity_id' => array(
'type' => 'int',
'not null' => TRUE,
'size' => 'normal',
'unsigned' => TRUE,
),
'revision_id' => array(
'type' => 'int',
'not null' => TRUE,
'size' => 'normal',
'unsigned' => TRUE,
),
'langcode' => array(
'type' => 'varchar_ascii',
'not null' => TRUE,
'length' => '32',
'default' => '',
),
'delta' => array(
'type' => 'int',
'not null' => TRUE,
'size' => 'normal',
'unsigned' => TRUE,
),
'field_range_value' => array(
'type' => 'varchar',
'not null' => TRUE,
'length' => '20',
),
'field_range_end_value' => array(
'type' => 'varchar',
'not null' => TRUE,
'length' => '20',
),
),
'primary key' => array(
'entity_id',
'revision_id',
'deleted',
'delta',
'langcode',
),
'indexes' => array(
'bundle' => array(
'bundle',
),
'revision_id' => array(
'revision_id',
),
'field_range_value' => array(
'field_range_value',
),
'field_range_end_value' => array(
'field_range_end_value',
),
),
'mysql_character_set' => 'utf8mb4',
));

View file

@ -0,0 +1,21 @@
uuid: 87dc4221-8d56-4112-8a7f-7a855ac35d08
langcode: en
status: true
dependencies:
config:
- field.storage.node.field_range
- node.type.page
module:
- datetime_range
id: node.page.field_range
field_name: field_range
entity_type: node
bundle: page
label: range
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings: { }
field_type: daterange

View file

@ -0,0 +1,20 @@
uuid: 2190ad8c-39dd-4eb1-b189-1bfc0c244a40
langcode: en
status: true
dependencies:
module:
- datetime_range
- node
id: node.field_range
field_name: field_range
entity_type: node
type: daterange
settings:
datetime_type: datetime
module: datetime_range
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false

View file

@ -0,0 +1,231 @@
uuid: d20760b6-7cc4-4844-ae04-96da7225a46f
langcode: en
status: true
dependencies:
module:
- node
- user
id: test_datetime_range_filter_values
label: test_datetime_range_filter_values
module: views
description: ''
tag: ''
base_table: node_field_data
base_field: nid
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: perm
options:
perm: 'access content'
cache:
type: tag
options: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: ''
query_tags: { }
exposed_form:
type: basic
options:
submit_button: Apply
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
pager:
type: mini
options:
items_per_page: 10
offset: 0
id: 0
total_pages: null
expose:
items_per_page: false
items_per_page_label: 'Items per page'
items_per_page_options: '5, 10, 25, 50'
items_per_page_options_all: false
items_per_page_options_all_label: '- All -'
offset: false
offset_label: Offset
tags:
previous:
next:
style:
type: default
options:
grouping: { }
row_class: ''
default_row_class: true
uses_fields: false
row:
type: fields
options:
inline: { }
separator: ''
hide_empty: false
default_field_elements: true
fields:
title:
id: title
table: node_field_data
field: title
entity_type: node
entity_field: title
label: ''
alter:
alter_text: false
make_link: false
absolute: false
trim: false
word_boundary: false
ellipsis: false
strip_tags: false
html: false
hide_empty: false
empty_zero: false
settings:
link_to_entity: true
plugin_id: field
relationship: none
group_type: group
admin_label: ''
exclude: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_alter_empty: true
click_sort_column: value
type: string
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
filters:
field_range_value:
id: field_range_value
table: node__field_range
field: field_range_value
relationship: none
group_type: group
admin_label: ''
operator: '='
value: '2017'
group: 1
exposed: false
expose:
operator_id: ''
label: ''
description: ''
use_operator: false
operator: ''
identifier: ''
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
is_grouped: false
group_info:
label: ''
description: ''
identifier: ''
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items: { }
plugin_id: string
field_range_end_value:
id: field_range_end_value
table: node__field_range
field: field_range_end_value
relationship: none
group_type: group
admin_label: ''
operator: contains
value: ''
group: 1
exposed: false
expose:
operator_id: ''
label: ''
description: ''
use_operator: false
operator: ''
identifier: ''
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
is_grouped: false
group_info:
label: ''
description: ''
identifier: ''
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items: { }
plugin_id: string
sorts:
field_range_value:
id: field_range_value
table: node__field_range
field: field_range_value
relationship: none
group_type: group
admin_label: ''
order: ASC
exposed: false
expose:
label: ''
plugin_id: standard
header: { }
footer: { }
empty: { }
relationships: { }
arguments: { }
display_extenders: { }
filter_groups:
operator: AND
groups: { }
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url.query_args
- 'user.node_grants:view'
- user.permissions
tags: { }

View file

@ -0,0 +1,8 @@
name: 'Datetime range test'
type: module
description: 'Provides a testing module for datetime_range.'
package: Testing
version: VERSION
core: 8.x
dependencies:
- drupal:taxonomy

View file

@ -0,0 +1,17 @@
<?php
/**
* @file
* Contains datetime_range_test.module
*/
/**
* Implements hook_entity_type_alter().
*/
function datetime_range_test_entity_type_alter(array &$entity_types) {
// Inhibit views data for the 'taxonomy_term' entity type in order to cover
// the case when an entity type provides no views data.
// @see https://www.drupal.org/project/drupal/issues/2995578
// @see \Drupal\Tests\datetime_range\Kernel\Views\EntityTypeWithoutViewsDataTest
$entity_types['taxonomy_term']->setHandlerClass('views_data', NULL);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,75 @@
<?php
namespace Drupal\Tests\datetime_range\Functional\Update;
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
use Drupal\views\Entity\View;
/**
* Test update of views with datetime_range filters.
*
* @see https://www.drupal.org/node/2786577
* @see datetime_range_post_update_views_string_plugin_id()
*
* @group Update
* @group legacy
*/
class DatetimeRangeViewUpdateTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
__DIR__ . '/../../../../tests/fixtures/update/datetime_range-filter-values.php',
];
}
/**
* Tests that datetime_range filter values are updated properly.
*/
public function testViewsPostUpdateDateRangeFilterValues() {
// Load our pre-update test view.
$view = View::load('test_datetime_range_filter_values');
$data = $view->toArray();
// Check pre-update filter values.
$filter1 = $data['display']['default']['display_options']['filters']['field_range_value'];
$this->assertSame('string', $filter1['plugin_id']);
// Check pre-update filter with operator going to be mapped.
$filter2 = $data['display']['default']['display_options']['filters']['field_range_end_value'];
$this->assertSame('string', $filter2['plugin_id']);
$this->assertSame('', $filter2['value']);
$this->assertSame('contains', $filter2['operator']);
// Check pre-update sort values.
$sort = $data['display']['default']['display_options']['sorts']['field_range_value'];
$this->assertSame('standard', $sort['plugin_id']);
$this->runUpdates();
// Reload and initialize our test view.
$view = View::load('test_datetime_range_filter_values');
$data = $view->toArray();
// Check filter values.
$filter1 = $data['display']['default']['display_options']['filters']['field_range_value'];
$this->assertSame('datetime', $filter1['plugin_id']);
$this->assertSame('2017', $filter1['value']['value']);
$this->assertSame('=', $filter1['operator']);
// Check string to datetime operator/value mapping.
$filter2 = $data['display']['default']['display_options']['filters']['field_range_end_value'];
$this->assertSame('datetime', $filter2['plugin_id']);
$this->assertSame('.*', $filter2['value']['value']);
$this->assertSame('regular_expression', $filter2['operator']);
// Check sort values.
$sort = $data['display']['default']['display_options']['sorts']['field_range_value'];
$this->assertSame('datetime', $sort['plugin_id']);
}
}

View file

@ -0,0 +1,106 @@
<?php
namespace Drupal\Tests\datetime_range\Kernel;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\Tests\field\Kernel\FieldKernelTestBase;
/**
* Test datetime range field type via API.
*
* @group datetime
*/
class DateRangeItemTest extends FieldKernelTestBase {
/**
* A field storage to use in this test class.
*
* @var \Drupal\field\Entity\FieldStorageConfig
*/
protected $fieldStorage;
/**
* The field used in this test class.
*
* @var \Drupal\field\Entity\FieldConfig
*/
protected $field;
/**
* {@inheritdoc}
*/
public static $modules = [
'datetime',
'datetime_range',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Add a datetime range field.
$this->fieldStorage = FieldStorageConfig::create([
'field_name' => mb_strtolower($this->randomMachineName()),
'entity_type' => 'entity_test',
'type' => 'daterange',
'settings' => ['datetime_type' => DateRangeItem::DATETIME_TYPE_DATE],
]);
$this->fieldStorage->save();
$this->field = FieldConfig::create([
'field_storage' => $this->fieldStorage,
'bundle' => 'entity_test',
'required' => TRUE,
]);
$this->field->save();
$display_options = [
'type' => 'daterange_default',
'label' => 'hidden',
'settings' => [
'format_type' => 'fallback',
'separator' => 'UNTRANSLATED',
],
];
EntityViewDisplay::create([
'targetEntityType' => $this->field->getTargetEntityTypeId(),
'bundle' => $this->field->getTargetBundle(),
'mode' => 'default',
'status' => TRUE,
])->setComponent($this->fieldStorage->getName(), $display_options)
->save();
}
/**
* Tests the field configured for date-only.
*/
public function testDateOnly() {
$this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATE);
$field_name = $this->fieldStorage->getName();
// Create an entity.
$entity = EntityTest::create([
'name' => $this->randomString(),
$field_name => [
'value' => '2016-09-21',
'end_value' => '2016-09-21',
],
]);
// Dates are saved without a time value. When they are converted back into
// a \Drupal\datetime\DateTimeComputed object they should all have the same
// time.
$start_date = $entity->{$field_name}->start_date;
sleep(1);
$end_date = $entity->{$field_name}->end_date;
$this->assertEquals($start_date->getTimestamp(), $end_date->getTimestamp());
$this->assertEquals('12:00:00', $start_date->format('H:i:s'));
$this->assertEquals('12:00:00', $end_date->format('H:i:s'));
}
}

View file

@ -0,0 +1,130 @@
<?php
namespace Drupal\Tests\datetime_range\Kernel;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Language\Language;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\KernelTests\KernelTestBase;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Test to ensure the datetime range separator is translatable.
*
* @group datetime
*/
class SeparatorTranslationTest extends KernelTestBase {
/**
* A field storage to use in this test class.
*
* @var \Drupal\field\Entity\FieldStorageConfig
*/
protected $fieldStorage;
/**
* The field used in this test class.
*
* @var \Drupal\field\Entity\FieldConfig
*/
protected $field;
/**
* {@inheritdoc}
*/
public static $modules = [
'datetime',
'datetime_range',
'entity_test',
'field',
'language',
'system',
'user',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('entity_test');
$this->installEntitySchema('user');
$this->installConfig(['system']);
$this->installSchema('system', ['sequences', 'key_value']);
// Add a datetime range field.
$this->fieldStorage = FieldStorageConfig::create([
'field_name' => mb_strtolower($this->randomMachineName()),
'entity_type' => 'entity_test',
'type' => 'daterange',
'settings' => ['datetime_type' => DateTimeItem::DATETIME_TYPE_DATE],
]);
$this->fieldStorage->save();
$this->field = FieldConfig::create([
'field_storage' => $this->fieldStorage,
'bundle' => 'entity_test',
'required' => TRUE,
]);
$this->field->save();
$display_options = [
'type' => 'daterange_default',
'label' => 'hidden',
'settings' => [
'format_type' => 'fallback',
'separator' => 'UNTRANSLATED',
],
];
EntityViewDisplay::create([
'targetEntityType' => $this->field->getTargetEntityTypeId(),
'bundle' => $this->field->getTargetBundle(),
'mode' => 'default',
'status' => TRUE,
])->setComponent($this->fieldStorage->getName(), $display_options)
->save();
}
/**
* Tests the translation of the range separator.
*/
public function testSeparatorTranslation() {
// Create an entity.
$entity = EntityTest::create([
'name' => $this->randomString(),
$this->fieldStorage->getName() => [
'value' => '2016-09-20',
'end_value' => '2016-09-21',
],
]);
// Verify the untranslated separator.
$display = EntityViewDisplay::collectRenderDisplay($entity, 'default');
$build = $display->build($entity);
$output = $this->container->get('renderer')->renderRoot($build);
$this->verbose($output);
$this->assertContains('UNTRANSLATED', (string) $output);
// Translate the separator.
ConfigurableLanguage::createFromLangcode('nl')->save();
/** @var \Drupal\language\ConfigurableLanguageManagerInterface $language_manager */
$language_manager = $this->container->get('language_manager');
$language_manager->getLanguageConfigOverride('nl', 'core.entity_view_display.entity_test.entity_test.default')
->set('content.' . $this->fieldStorage->getName() . '.settings.separator', 'NL_TRANSLATED!')
->save();
$this->container->get('language.config_factory_override')
->setLanguage(new Language(['id' => 'nl']));
$this->container->get('cache_tags.invalidator')->invalidateTags($entity->getCacheTags());
$display = EntityViewDisplay::collectRenderDisplay($entity, 'default');
$build = $display->build($entity);
$output = $this->container->get('renderer')->renderRoot($build);
$this->verbose($output);
$this->assertContains('NL_TRANSLATED!', (string) $output);
}
}

View file

@ -0,0 +1,43 @@
<?php
namespace Drupal\Tests\datetime_range\Kernel\Views;
use Drupal\Core\Config\InstallStorage;
use Drupal\Core\Serialization\Yaml;
use Drupal\KernelTests\KernelTestBase;
use Drupal\views\Entity\View;
/**
* Tests datetime_range.module when an entity type provides no views data.
*
* @group datetime
*/
class EntityTypeWithoutViewsDataTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'datetime_range',
'datetime_range_test',
'node',
'system',
'taxonomy',
'text',
'user',
'views',
];
/**
* Tests the case when an entity type provides no views data.
*
* @see datetime_test_entity_type_alter()
* @see datetime_range_view_presave()
*/
public function testEntityTypeWithoutViewsData() {
$view_yaml = drupal_get_path('module', 'taxonomy') . '/' . InstallStorage::CONFIG_OPTIONAL_DIRECTORY . '/views.view.taxonomy_term.yml';
$values = Yaml::decode(file_get_contents($view_yaml));
$this->assertEquals(SAVED_NEW, View::create($values)->save());
}
}

View file

@ -0,0 +1,158 @@
<?php
namespace Drupal\Tests\datetime_range\Kernel\Views;
use Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\node\Entity\Node;
use Drupal\Tests\datetime\Kernel\Views\DateTimeHandlerTestBase;
use Drupal\views\Views;
/**
* Tests date-only fields.
*
* @group datetime
*/
class FilterDateTest extends DateTimeHandlerTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['datetime_test', 'node', 'datetime_range', 'field'];
/**
* Type of the field.
*
* @var string
*/
protected static $field_type = 'daterange';
/**
* {@inheritdoc}
*/
public static $testViews = ['test_filter_datetime'];
/**
* For offset tests, set to the current time.
*/
protected static $date;
/**
* {@inheritdoc}
*
* Create nodes with relative date range of:
* yesterday - today, today - today, and today - tomorrow.
*/
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
// Set to 'today'.
static::$date = $this->getUTCEquivalentOfUserNowAsTimestamp();
// Change field storage to date-only.
$storage = FieldStorageConfig::load('node.' . static::$field_name);
$storage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATE);
$storage->save();
// Retrieve tomorrow, today and yesterday dates.
$dates = $this->getRelativeDateValuesFromTimestamp(static::$date);
// Node 0: Yesterday - Today.
$node = Node::create([
'title' => $this->randomMachineName(8),
'type' => 'page',
'field_date' => [
'value' => $dates[2],
'end_value' => $dates[1],
],
]);
$node->save();
$this->nodes[] = $node;
// Node 1: Today - Today.
$node = Node::create([
'title' => $this->randomMachineName(8),
'type' => 'page',
'field_date' => [
'value' => $dates[1],
'end_value' => $dates[1],
],
]);
$node->save();
$this->nodes[] = $node;
// Node 2: Today - Tomorrow.
$node = Node::create([
'title' => $this->randomMachineName(8),
'type' => 'page',
'field_date' => [
'value' => $dates[1],
'end_value' => $dates[0],
],
]);
$node->save();
$this->nodes[] = $node;
// Add end date filter to the test_filter_datetime view.
/** @var \Drupal\views\Entity\View $view */
$view = \Drupal::entityTypeManager()->getStorage('view')->load('test_filter_datetime');
$field_end = static::$field_name . '_end_value';
$display = $view->getDisplay('default');
$filter_end_date = $display['display_options']['filters'][static::$field_name . '_value'];
$filter_end_date['id'] = $field_end;
$filter_end_date['field'] = $field_end;
$view->getDisplay('default')['display_options']['filters'][$field_end] = $filter_end_date;
$view->save();
}
/**
* Test offsets with date-only fields.
*/
public function testDateOffsets() {
$view = Views::getView('test_filter_datetime');
$field_start = static::$field_name . '_value';
$field_end = static::$field_name . '_end_value';
// Test simple operations.
$view->initHandlers();
// Search nodes with:
// - start date greater than or equal to 'yesterday'.
// - end date lower than or equal to 'today'.
// Expected results: nodes 0 and 1.
$view->filter[$field_start]->operator = '>=';
$view->filter[$field_start]->value['type'] = 'offset';
$view->filter[$field_start]->value['value'] = '-1 day';
$view->filter[$field_end]->operator = '<=';
$view->filter[$field_end]->value['type'] = 'offset';
$view->filter[$field_end]->value['value'] = 'now';
$view->setDisplay('default');
$this->executeView($view);
$expected_result = [
['nid' => $this->nodes[0]->id()],
['nid' => $this->nodes[1]->id()],
];
$this->assertIdenticalResultset($view, $expected_result, $this->map);
$view->destroy();
// Search nodes with:
// - start date greater than or equal to 'yesterday'.
// - end date greater than 'today'.
// Expected results: node 2.
$view->initHandlers();
$view->filter[$field_start]->operator = '>=';
$view->filter[$field_start]->value['type'] = 'offset';
$view->filter[$field_start]->value['value'] = '-1 day';
$view->filter[$field_end]->operator = '>';
$view->filter[$field_end]->value['type'] = 'offset';
$view->filter[$field_end]->value['value'] = 'now';
$view->setDisplay('default');
$this->executeView($view);
$expected_result = [
['nid' => $this->nodes[2]->id()],
];
$this->assertIdenticalResultset($view, $expected_result, $this->map);
}
}