Initial Drupal 11 with DDEV setup
This commit is contained in:
@ -0,0 +1,94 @@
|
||||
# Schema for the configuration files of the Datetime Range module.
|
||||
|
||||
# Daterange field type.
|
||||
# This field type has no field instance settings, so no specific config schema type.
|
||||
# @see `type: field.field_settings.*`
|
||||
field.storage_settings.daterange:
|
||||
type: field.storage_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:
|
||||
from_to:
|
||||
type: string
|
||||
label: 'Display'
|
||||
constraints:
|
||||
Choice:
|
||||
- both
|
||||
- start_date
|
||||
- end_date
|
||||
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:
|
||||
from_to:
|
||||
type: string
|
||||
label: 'Display'
|
||||
constraints:
|
||||
Choice:
|
||||
- both
|
||||
- start_date
|
||||
- end_date
|
||||
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:
|
||||
from_to:
|
||||
type: string
|
||||
label: 'Display'
|
||||
constraints:
|
||||
Choice:
|
||||
- both
|
||||
- start_date
|
||||
- end_date
|
||||
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'
|
||||
7
web/core/modules/datetime_range/datetime_range.info.yml
Normal file
7
web/core/modules/datetime_range/datetime_range.info.yml
Normal file
@ -0,0 +1,7 @@
|
||||
name: 'Datetime Range'
|
||||
type: module
|
||||
description: 'Provides the ability to store end dates.'
|
||||
package: Field types
|
||||
version: VERSION
|
||||
dependencies:
|
||||
- drupal:datetime
|
||||
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Post-update functions for Datetime Range module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_removed_post_updates().
|
||||
*/
|
||||
function datetime_range_removed_post_updates(): array {
|
||||
return [
|
||||
'datetime_range_post_update_translatable_separator' => '9.0.0',
|
||||
'datetime_range_post_update_views_string_plugin_id' => '9.0.0',
|
||||
'datetime_range_post_update_from_to_configuration' => '11.0.0',
|
||||
];
|
||||
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
parameters:
|
||||
datetime_range.skip_procedural_hook_scan: true
|
||||
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\datetime_range;
|
||||
|
||||
/**
|
||||
* Declares constants used in the datetime range module.
|
||||
*
|
||||
* @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use
|
||||
* enum DateTimeRangeDisplayOptions instead.
|
||||
* @see https://www.drupal.org/node/3495241
|
||||
*/
|
||||
interface DateTimeRangeConstantsInterface {
|
||||
|
||||
/**
|
||||
* Values for the 'from_to' formatter setting.
|
||||
*/
|
||||
const BOTH = 'both';
|
||||
const START_DATE = 'start_date';
|
||||
const END_DATE = 'end_date';
|
||||
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\datetime_range;
|
||||
|
||||
/**
|
||||
* Declares constants used in the Datetime Range module.
|
||||
*/
|
||||
enum DateTimeRangeDisplayOptions: string {
|
||||
|
||||
// Values for the 'from_to' formatter setting.
|
||||
case Both = 'both';
|
||||
case StartDate = 'start_date';
|
||||
case EndDate = 'end_date';
|
||||
|
||||
}
|
||||
215
web/core/modules/datetime_range/src/DateTimeRangeTrait.php
Normal file
215
web/core/modules/datetime_range/src/DateTimeRangeTrait.php
Normal file
@ -0,0 +1,215 @@
|
||||
<?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' => DateTimeRangeDisplayOptions::Both->value,
|
||||
'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' => DateTimeRangeDisplayOptions::Both->value],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
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') === DateTimeRangeDisplayOptions::Both->value) {
|
||||
$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 [
|
||||
DateTimeRangeDisplayOptions::Both->value => $this->t('Display both start and end dates'),
|
||||
DateTimeRangeDisplayOptions::StartDate->value => $this->t('Display start date only'),
|
||||
DateTimeRangeDisplayOptions::EndDate->value => $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 DateTimeRangeDisplayOptions::Both->value:
|
||||
case DateTimeRangeDisplayOptions::StartDate->value:
|
||||
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 DateTimeRangeDisplayOptions::Both->value:
|
||||
case DateTimeRangeDisplayOptions::EndDate->value:
|
||||
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[DateTimeRangeDisplayOptions::StartDate->value] = $this->buildDate($start_date);
|
||||
}
|
||||
if ($this->startDateIsDisplayed() && $this->endDateIsDisplayed()) {
|
||||
$element['separator'] = ['#plain_text' => ' ' . $separator . ' '];
|
||||
}
|
||||
if ($this->endDateIsDisplayed()) {
|
||||
$element[DateTimeRangeDisplayOptions::EndDate->value] = $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[DateTimeRangeDisplayOptions::StartDate->value] = $this->buildDateWithIsoAttribute($start_date);
|
||||
}
|
||||
if ($this->startDateIsDisplayed() && $this->endDateIsDisplayed()) {
|
||||
$element['separator'] = ['#plain_text' => ' ' . $separator . ' '];
|
||||
}
|
||||
if ($this->endDateIsDisplayed()) {
|
||||
$element[DateTimeRangeDisplayOptions::EndDate->value] = $this->buildDateWithIsoAttribute($end_date);
|
||||
}
|
||||
return $element;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\datetime_range\Hook;
|
||||
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
|
||||
/**
|
||||
* Hook implementations for datetime_range.
|
||||
*/
|
||||
class DatetimeRangeHooks {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
#[Hook('help')]
|
||||
public function help($route_name, RouteMatchInterface $route_match): ?string {
|
||||
switch ($route_name) {
|
||||
case 'help.page.datetime_range':
|
||||
$output = '';
|
||||
$output .= '<h2>' . $this->t('About') . '</h2>';
|
||||
$output .= '<p>' . $this->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>' . $this->t('Uses') . '</h2>';
|
||||
$output .= '<dl>';
|
||||
$output .= '<dt>' . $this->t('Managing and displaying date fields') . '</dt>';
|
||||
$output .= '<dd>' . $this->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>' . $this->t('Displaying dates') . '</dt>';
|
||||
$output .= '<dd>' . $this->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;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\datetime_range\Hook;
|
||||
|
||||
use Drupal\datetime\DateTimeViewsHelper;
|
||||
use Drupal\field\FieldStorageConfigInterface;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
|
||||
/**
|
||||
* Hook implementations for datetime_range.
|
||||
*/
|
||||
class DatetimeRangeViewsHooks {
|
||||
|
||||
public function __construct(
|
||||
protected readonly DateTimeViewsHelper $dateTimeViewsHelper,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Implements hook_field_views_data().
|
||||
*/
|
||||
#[Hook('field_views_data')]
|
||||
public function fieldViewsData(FieldStorageConfigInterface $field_storage): array {
|
||||
// Get datetime field data for value and end_value.
|
||||
$data = $this->dateTimeViewsHelper->buildViewsData($field_storage, [], 'value');
|
||||
$data = $this->dateTimeViewsHelper->buildViewsData($field_storage, $data, 'end_value');
|
||||
return $data;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,86 @@
|
||||
<?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());
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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->set('start_date', NULL);
|
||||
}
|
||||
elseif ($property_name == 'end_value') {
|
||||
$this->set('end_date', NULL);
|
||||
}
|
||||
parent::onChange($property_name, $notify);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,131 @@
|
||||
<?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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render API callback: Validates that the start date <= the end date.
|
||||
*
|
||||
* This function is assigned as a #element_validate callback.
|
||||
*
|
||||
* @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']]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
name: 'Datetime range test'
|
||||
type: module
|
||||
description: 'Provides a testing module for datetime_range.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
dependencies:
|
||||
- drupal:taxonomy
|
||||
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\datetime_range_test\Hook;
|
||||
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
|
||||
/**
|
||||
* Hook implementations for datetime_range_test.
|
||||
*/
|
||||
class DatetimeRangeTestHooks {
|
||||
|
||||
/**
|
||||
* Implements hook_entity_type_alter().
|
||||
*/
|
||||
#[Hook('entity_type_alter')]
|
||||
public function entityTypeAlter(array &$entity_types) : void {
|
||||
// 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
@ -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.
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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 {}
|
||||
@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\datetime_range\FunctionalJavascript;
|
||||
|
||||
use Drupal\datetime_range\DateTimeRangeDisplayOptions;
|
||||
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, DateTimeRangeDisplayOptions::Both->value);
|
||||
$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, DateTimeRangeDisplayOptions::StartDate->value);
|
||||
$this->assertFalse($separator->isVisible());
|
||||
$page->selectFieldOption($from_to_locator, DateTimeRangeDisplayOptions::EndDate->value);
|
||||
$this->assertFalse($separator->isVisible());
|
||||
}
|
||||
|
||||
}
|
||||
@ -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'));
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
<?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',
|
||||
'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());
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user