first commit

This commit is contained in:
2024-07-15 12:33:27 +02:00
commit ce50ae282b
22084 changed files with 2623791 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
name: 'Datetime Range'
type: module
description: 'Provides the ability to store end dates.'
package: Field types
# version: VERSION
dependencies:
- drupal:datetime
# Information added by Drupal.org packaging script on 2024-07-04
version: '10.3.1'
project: 'drupal'
datestamp: 1720094222

View File

@@ -0,0 +1,67 @@
<?php
/**
* @file
* Field hooks to implement a datetime field that stores a start and end date.
*/
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Url;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\datetime_range\DateTimeRangeConstantsInterface;
use Drupal\datetime_range\Plugin\Field\FieldFormatter\DateRangeCustomFormatter;
use Drupal\datetime_range\Plugin\Field\FieldFormatter\DateRangeDefaultFormatter;
use Drupal\datetime_range\Plugin\Field\FieldFormatter\DateRangePlainFormatter;
/**
* Implements hook_help().
*/
function datetime_range_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.datetime_range':
$output = '';
$output .= '<h2>' . t('About') . '</h2>';
$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' => Url::fromRoute('help.page', ['name' => 'field'])->toString(), ':field_ui' => (\Drupal::moduleHandler()->moduleExists('field_ui')) ? Url::fromRoute('help.page', ['name' => 'field_ui'])->toString() : '#', ':datetime_do' => 'https://www.drupal.org/documentation/modules/datetime_range']) . '</p>';
$output .= '<h2>' . t('Uses') . '</h2>';
$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')) ? Url::fromRoute('help.page', ['name' => 'field_ui'])->toString() : '#']) . '</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' => Url::fromRoute('entity.date_format.collection')->toString()]) . '</dd>';
$output .= '</dl>';
return $output;
}
}
/**
* Implements hook_ENTITY_TYPE_presave() for entity_view_display entities.
*
* @todo Remove this when datetime_range_post_update_from_to_configuration is removed.
*/
function datetime_range_entity_view_display_presave(EntityViewDisplayInterface $entity_view_display): void {
/** @var \Drupal\Core\Field\FormatterPluginManager $field_formatter_manager */
$field_formatter_manager = \Drupal::service('plugin.manager.field.formatter');
foreach ($entity_view_display->getComponents() as $name => $component) {
if (empty($component['type'])) {
continue;
}
$plugin_definition = $field_formatter_manager->getDefinition($component['type'], FALSE);
$daterange_formatter_classes = [
DateRangeCustomFormatter::class,
DateRangeDefaultFormatter::class,
DateRangePlainFormatter::class,
];
if (!in_array($plugin_definition['class'], $daterange_formatter_classes, FALSE)) {
continue;
}
if (!isset($component['settings']['from_to'])) {
// Existing daterange formatters don't have 'from_to'.
$component['settings']['from_to'] = DateTimeRangeConstantsInterface::BOTH;
$entity_view_display->setComponent($name, $component);
}
}
}

View File

@@ -0,0 +1,59 @@
<?php
/**
* @file
* Post-update functions for Datetime Range module.
*/
use Drupal\Core\Config\Entity\ConfigEntityUpdater;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\datetime_range\Plugin\Field\FieldFormatter\DateRangeCustomFormatter;
use Drupal\datetime_range\Plugin\Field\FieldFormatter\DateRangeDefaultFormatter;
use Drupal\datetime_range\Plugin\Field\FieldFormatter\DateRangePlainFormatter;
/**
* Implements hook_removed_post_updates().
*/
function datetime_range_removed_post_updates() {
return [
'datetime_range_post_update_translatable_separator' => '9.0.0',
'datetime_range_post_update_views_string_plugin_id' => '9.0.0',
];
}
/**
* Adds 'from_to' in flagged entity view date range formatter.
*
* @see \datetime_range_entity_view_display_presave
*/
function datetime_range_post_update_from_to_configuration(?array &$sandbox = NULL): void {
/** @var \Drupal\Core\Field\FormatterPluginManager $field_formatter_manager */
$field_formatter_manager = \Drupal::service('plugin.manager.field.formatter');
$config_entity_updater = \Drupal::classResolver(ConfigEntityUpdater::class);
$callback = function (EntityViewDisplayInterface $entity_view_display) use ($field_formatter_manager) {
foreach (array_values($entity_view_display->getComponents()) as $component) {
if (empty($component['type'])) {
continue;
}
$plugin_definition = $field_formatter_manager->getDefinition($component['type'], FALSE);
$daterange_formatter_classes = [
DateRangeCustomFormatter::class,
DateRangeDefaultFormatter::class,
DateRangePlainFormatter::class,
];
if (!in_array($plugin_definition['class'], $daterange_formatter_classes, FALSE)) {
continue;
}
if (!isset($component['settings']['from_to'])) {
return TRUE;
}
}
return FALSE;
};
$config_entity_updater->update($sandbox, 'entity_view_display', $callback);
}

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,120 @@
<?php
/**
* @file
* Provides database changes for testing the daterange formatter upgrade path.
*
* @see \Drupal\Tests\datetime_range\Functional\DateRangeFormatterSettingsUpdateTest
*/
use Drupal\Core\Database\Database;
use Drupal\field\Entity\FieldStorageConfig;
$connection = Database::getConnection();
// Add all datetime_range_removed_post_updates() as existing updates.
require_once __DIR__ . '/../../../../datetime_range/datetime_range.post_update.php';
$existing_updates = $connection->select('key_value')
->fields('key_value', ['value'])
->condition('collection', 'post_update')
->condition('name', 'existing_updates')
->execute()
->fetchField();
$existing_updates = unserialize($existing_updates);
$existing_updates = array_merge(
$existing_updates,
array_keys(datetime_range_removed_post_updates())
);
$connection->update('key_value')
->fields(['value' => serialize($existing_updates)])
->condition('collection', 'post_update')
->condition('name', 'existing_updates')
->execute();
// Add a new timestamp field 'field_datetime_range'.
$connection->insert('config')
->fields(['collection', 'name', 'data'])->values([
'collection' => '',
'name' => 'field.storage.node.field_datetime_range',
'data' => $field_storage = 'a:16:{s:4:"uuid";s:36:"a01264e6-2821-4b94-bc79-ba2b346795bb";s:8:"langcode";s:2:"en";s:6:"status";b:1;s:12:"dependencies";a:1:{s:6:"module";a:2:{i:0;s:14:"datetime_range";i:1;s:4:"node";}}s:2:"id";s:25:"node.field_datetime_range";s:10:"field_name";s:20:"field_datetime_range";s:11:"entity_type";s:4:"node";s:4:"type";s:9:"daterange";s:8:"settings";a:1:{s:13:"datetime_type";s:8:"datetime";}s:6:"module";s:14:"datetime_range";s:6:"locked";b:0;s:11:"cardinality";i:1;s:12:"translatable";b:1;s:7:"indexes";a:0:{}s:22:"persist_with_no_fields";b:0;s:14:"custom_storage";b:0;}',
])->values([
'collection' => '',
'name' => 'field.field.node.page.field_datetime_range',
'data' => 'a:16:{s:4:"uuid";s:36:"678b9e68-cff5-4b2e-9111-43e5d9d6c826";s:8:"langcode";s:2:"en";s:6:"status";b:1;s:12:"dependencies";a:2:{s:6:"config";a:2:{i:0;s:39:"field.storage.node.field_datetime_range";i:1;s:14:"node.type.page";}s:6:"module";a:1:{i:0;s:14:"datetime_range";}}s:2:"id";s:30:"node.page.field_datetime_range";s:10:"field_name";s:20:"field_datetime_range";s:11:"entity_type";s:4:"node";s:6:"bundle";s:4:"page";s:5:"label";s:14:"datetime range";s:11:"description";s:0:"";s:8:"required";b:0;s:12:"translatable";b:0;s:13:"default_value";a:0:{}s:22:"default_value_callback";s:0:"";s:8:"settings";a:0:{}s:10:"field_type";s:9:"daterange";}',
])->execute();
$connection->insert('key_value')
->fields(['collection', 'name', 'value'])
->values([
'collection' => 'config.entity.key_store.field_config',
'name' => 'uuid:678b9e68-cff5-4b2e-9111-43e5d9d6c826',
'value' => 'a:1:{i:0;s:42:"field.field.node.page.field_datetime_range";}',
])
->values([
'collection' => 'config.entity.key_store.field_storage_config',
'name' => 'uuid:a01264e6-2821-4b94-bc79-ba2b346795bb',
'value' => 'a:1:{i:0;s:39:"field.storage.node.field_datetime_range";}',
])
->values([
'collection' => 'entity.storage_schema.sql',
'name' => 'node.field_schema_data.field_datetime_range',
'value' => 'a:2:{s:26:"node__field_datetime_range";a:4:{s:11:"description";s:49:"Data storage for node field field_datetime_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:26:"field_datetime_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:30:"field_datetime_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:26:"field_datetime_range_value";a:1:{i:0;s:26:"field_datetime_range_value";}s:30:"field_datetime_range_end_value";a:1:{i:0;s:30:"field_datetime_range_end_value";}}}s:35:"node_revision__field_datetime_range";a:4:{s:11:"description";s:61:"Revision archive storage for node field field_datetime_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:26:"field_datetime_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:30:"field_datetime_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:26:"field_datetime_range_value";a:1:{i:0;s:26:"field_datetime_range_value";}s:30:"field_datetime_range_end_value";a:1:{i:0;s:30:"field_datetime_range_end_value";}}}}',
])
->execute();
$data = $connection->select('key_value')
->fields('key_value', ['value'])
->condition('collection', 'entity.definitions.installed')
->condition('name', 'node.field_storage_definitions')
->execute()
->fetchField();
$data = unserialize($data);
$data['field_datetime_range'] = new FieldStorageConfig(unserialize($field_storage));
$connection->update('key_value')
->fields(['value' => serialize($data)])
->condition('collection', 'entity.definitions.installed')
->condition('name', 'node.field_storage_definitions')
->execute();
$data = $connection->select('config')
->fields('config', ['data'])
->condition('collection', '')
->condition('name', 'core.entity_view_display.node.page.default')
->execute()
->fetchField();
$data = unserialize($data);
$data['content']['field_datetime_range'] = [
'type' => 'daterange_default',
'label' => 'above',
'settings' => [
'timezone_override' => '',
'format_type' => 'medium',
'separator' => '-',
],
'third_party_settings' => [],
'weight' => 102,
'region' => 'content',
];
$connection->update('config')
->fields([
'data' => serialize($data),
])
->condition('collection', '')
->condition('name', 'core.entity_view_display.node.page.default')
->execute();
$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();

View File

@@ -0,0 +1,12 @@
name: 'Datetime range test'
type: module
description: 'Provides a testing module for datetime_range.'
package: Testing
# version: VERSION
dependencies:
- drupal:taxonomy
# Information added by Drupal.org packaging script on 2024-07-04
version: '10.3.1'
project: 'drupal'
datestamp: 1720094222

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,57 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\datetime_range\Functional;
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
/**
* Tests the update path for daterange formatter settings.
*
* @group datetime
*/
class DateRangeFormatterSettingsUpdateTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'node',
'datetime',
'datetime_range',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles(): void {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-9.4.0.bare.standard.php.gz',
__DIR__ . '/../../fixtures/update/drupal.daterange-formatter-settings-2827055.php',
];
}
/**
* Tests update path for the 'from_to' formatter setting.
*
* @covers \datetime_range_post_update_from_to_configuration
*/
public function testPostUpdateDateRangeFormatter(): void {
$config_factory = \Drupal::configFactory();
// Check that 'from_to' is missing before update.
$settings = $config_factory->get('core.entity_view_display.node.page.default')->get('content.field_datetime_range.settings');
$this->assertArrayNotHasKey('from_to', $settings);
$this->runUpdates();
$settings = $config_factory->get('core.entity_view_display.node.page.default')->get('content.field_datetime_range.settings');
$this->assertArrayHasKey('from_to', $settings);
}
}

View File

@@ -0,0 +1,171 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\datetime_range\Functional\EntityResource\EntityTest;
use Drupal\Core\Url;
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\entity_test\Functional\Rest\EntityTestResourceTestBase;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
use GuzzleHttp\RequestOptions;
/**
* Tests the 'daterange' field's normalization.
*
* @group datetime_range
*/
class EntityTestDateRangeTest extends EntityTestResourceTestBase {
use AnonResourceTestTrait;
/**
* The ISO date string to use throughout the test.
*
* @var string
*/
protected static $dateString = '2017-03-01T20:02:00';
/**
* Datetime Range test field name.
*
* @var string
*/
protected static $fieldName = 'field_daterange';
/**
* {@inheritdoc}
*/
protected static $modules = ['datetime_range', 'entity_test'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Add datetime_range field.
FieldStorageConfig::create([
'field_name' => static::$fieldName,
'type' => 'daterange',
'entity_type' => static::$entityTypeId,
'settings' => ['datetime_type' => DateRangeItem::DATETIME_TYPE_ALLDAY],
])->save();
FieldConfig::create([
'field_name' => static::$fieldName,
'entity_type' => static::$entityTypeId,
'bundle' => $this->entity->bundle(),
])->save();
// Reload entity so that it has the new field.
$this->entity = $this->entityStorage->load($this->entity->id());
$this->entity->set(static::$fieldName, [
'value' => static::$dateString,
'end_value' => static::$dateString,
]);
$this->entity->save();
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
$entity_test = EntityTest::create([
'name' => 'Llama',
'type' => static::$entityTypeId,
]);
$entity_test->setOwnerId(0);
$entity_test->save();
return $entity_test;
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
return parent::getExpectedNormalizedEntity() + [
static::$fieldName => [
[
'value' => '2017-03-02T07:02:00+11:00',
'end_value' => '2017-03-02T07:02:00+11:00',
],
],
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
return parent::getNormalizedPostEntity() + [
static::$fieldName => [
[
'value' => '2017-03-01T20:02:00+00:00',
'end_value' => '2017-03-01T20:02:00+00:00',
],
],
];
}
/**
* {@inheritdoc}
*/
protected function assertNormalizationEdgeCases($method, Url $url, array $request_options): void {
parent::assertNormalizationEdgeCases($method, $url, $request_options);
if ($this->entity->getEntityType()->hasKey('bundle')) {
$fieldName = static::$fieldName;
// DX: 422 when 'value' data type is incorrect.
$normalization = $this->getNormalizedPostEntity();
$normalization[static::$fieldName][0]['value'] = [
'2017', '03', '01', '21', '53', '00',
];
$request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
$response = $this->request($method, $url, $request_options);
$message = "Unprocessable Entity: validation failed.\n{$fieldName}.0.value: This value should be of the correct primitive type.\n";
$this->assertResourceErrorResponse(422, $message, $response);
// DX: 422 when 'end_value' is not specified.
$normalization = $this->getNormalizedPostEntity();
unset($normalization[static::$fieldName][0]['end_value']);
$request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
$response = $this->request($method, $url, $request_options);
$message = "Unprocessable Entity: validation failed.\n{$fieldName}.0.end_value: This value should not be null.\n";
$this->assertResourceErrorResponse(422, $message, $response);
// DX: 422 when 'end_value' data type is incorrect.
$normalization = $this->getNormalizedPostEntity();
$normalization[static::$fieldName][0]['end_value'] = [
'2017', '03', '01', '21', '53', '00',
];
$request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
$response = $this->request($method, $url, $request_options);
$message = "Unprocessable Entity: validation failed.\n{$fieldName}.0.end_value: This value should be of the correct primitive type.\n";
$this->assertResourceErrorResponse(422, $message, $response);
// DX: 422 when end date value is invalid.
$normalization = $this->getNormalizedPostEntity();
$value = '2017-13-55T20:02:00+00:00';
$normalization[static::$fieldName][0]['end_value'] = $value;
$request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
$response = $this->request($method, $url, $request_options);
$message = "The specified date \"$value\" is not in an accepted format: \"Y-m-d\\TH:i:sP\" (RFC 3339), \"Y-m-d\\TH:i:sO\" (ISO 8601).";
$this->assertResourceErrorResponse(422, $message, $response);
// @todo Expand in https://www.drupal.org/project/drupal/issues/2847041.
}
}
}

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\datetime_range\Functional;
use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
/**
* Generic module test for datetime_range.
*
* @group datetime_range
*/
class GenericTest extends GenericModuleTestBase {}

View File

@@ -0,0 +1,97 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\datetime_range\FunctionalJavascript;
use Drupal\datetime_range\DateTimeRangeConstantsInterface;
use Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
/**
* Tests Daterange field.
*
* @group datetime
*/
class DateRangeFieldTest extends WebDriverTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['node', 'entity_test', 'field_ui', 'datetime', 'datetime_range'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalLogin($this->drupalCreateUser([
'view test entity',
'administer entity_test content',
'administer content types',
'administer node fields',
'administer node display',
'bypass node access',
'administer entity_test fields',
]));
}
/**
* Tests the conditional visibility of the 'Date separator' field.
*/
public function testFromToSeparatorState(): void {
$field_name = $this->randomMachineName();
$this->drupalCreateContentType(['type' => 'date_content']);
$field_storage = FieldStorageConfig::create([
'field_name' => $field_name,
'entity_type' => 'node',
'type' => 'daterange',
'settings' => ['datetime_type' => DateRangeItem::DATETIME_TYPE_DATE],
]);
$field_storage->save();
$field = FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'date_content',
]);
$field->save();
\Drupal::service('entity_display.repository')->getViewDisplay('node', 'date_content')
->setComponent($field_name, [
'type' => 'daterange_default',
'label' => 'hidden',
'settings' => [
'format_type' => 'short',
'separator' => 'THE_SEPARATOR',
],
])
->save();
$this->drupalGet("admin/structure/types/manage/date_content/display");
$page = $this->getSession()->getPage();
$page->pressButton("{$field_name}_settings_edit");
$this->assertSession()->waitForElement('css', '.ajax-new-content');
$from_to_locator = 'fields[' . $field_name . '][settings_edit_form][settings][from_to]';
$separator = $page->findField('Date separator');
// Assert that date separator field is visible if 'from_to' is set to
// BOTH.
$this->assertSession()->fieldValueEquals($from_to_locator, DateTimeRangeConstantsInterface::BOTH);
$this->assertTrue($separator->isVisible());
// Assert that the date separator is not visible if 'from_to' is set to
// START_DATE or END_DATE.
$page->selectFieldOption($from_to_locator, DateTimeRangeConstantsInterface::START_DATE);
$this->assertFalse($separator->isVisible());
$page->selectFieldOption($from_to_locator, DateTimeRangeConstantsInterface::END_DATE);
$this->assertFalse($separator->isVisible());
}
}

View File

@@ -0,0 +1,108 @@
<?php
declare(strict_types=1);
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}
*/
protected static $modules = [
'datetime',
'datetime_range',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Add a datetime range field.
$this->fieldStorage = FieldStorageConfig::create([
'field_name' => $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(): void {
$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,129 @@
<?php
declare(strict_types=1);
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}
*/
protected static $modules = [
'datetime',
'datetime_range',
'entity_test',
'field',
'language',
'system',
'user',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('entity_test');
$this->installEntitySchema('user');
$this->installConfig(['system']);
// Add a datetime range field.
$this->fieldStorage = FieldStorageConfig::create([
'field_name' => $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(): void {
// 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->assertStringContainsString('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->assertStringContainsString('NL_TRANSLATED!', (string) $output);
}
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
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()
*/
public function testEntityTypeWithoutViewsData(): void {
$view_yaml = $this->getModulePath('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,167 @@
<?php
declare(strict_types=1);
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}
*/
protected static $modules = [
'datetime_test',
'node',
'datetime_range',
'field',
];
/**
* Type of the field.
*
* @var string
*/
protected static $fieldType = 'daterange';
/**
* {@inheritdoc}
*/
public static $testViews = ['test_filter_datetime'];
/**
* For offset tests, set to the current time.
*
* @var int
*/
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): void {
parent::setUp($import_test_views);
// Set to 'today'.
static::$date = $this->getUTCEquivalentOfUserNowAsTimestamp();
// Change field storage to date-only.
$storage = FieldStorageConfig::load('node.' . static::$fieldName);
$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::$fieldName . '_end_value';
$display = $view->getDisplay('default');
$filter_end_date = $display['display_options']['filters'][static::$fieldName . '_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();
}
/**
* Tests offsets with date-only fields.
*/
public function testDateOffsets(): void {
$view = Views::getView('test_filter_datetime');
$field_start = static::$fieldName . '_value';
$field_end = static::$fieldName . '_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);
}
}