diff --git a/core/modules/datetime_range/config/schema/datetime_range.schema.yml b/core/modules/datetime_range/config/schema/datetime_range.schema.yml index f0f93259d34..c03b4b557a2 100644 --- a/core/modules/datetime_range/config/schema/datetime_range.schema.yml +++ b/core/modules/datetime_range/config/schema/datetime_range.schema.yml @@ -31,6 +31,14 @@ 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' @@ -40,6 +48,14 @@ 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' @@ -49,6 +65,14 @@ 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' diff --git a/core/modules/datetime_range/datetime_range.module b/core/modules/datetime_range/datetime_range.module index c0be2a2089d..6318fc9610b 100644 --- a/core/modules/datetime_range/datetime_range.module +++ b/core/modules/datetime_range/datetime_range.module @@ -5,8 +5,13 @@ * 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\DateTimeRangeDisplayOptions; +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(). @@ -27,3 +32,36 @@ function datetime_range_help($route_name, RouteMatchInterface $route_match) { 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'] = DateTimeRangeDisplayOptions::BOTH->value; + $entity_view_display->setComponent($name, $component); + } + } +} diff --git a/core/modules/datetime_range/datetime_range.post_update.php b/core/modules/datetime_range/datetime_range.post_update.php index 83df8ca9b1c..9dfa5156eb2 100644 --- a/core/modules/datetime_range/datetime_range.post_update.php +++ b/core/modules/datetime_range/datetime_range.post_update.php @@ -5,6 +5,12 @@ * 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(). */ @@ -14,3 +20,40 @@ function datetime_range_removed_post_updates() { '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); +} diff --git a/core/modules/datetime_range/src/DateTimeRangeDisplayOptions.php b/core/modules/datetime_range/src/DateTimeRangeDisplayOptions.php new file mode 100644 index 00000000000..4e2969534ef --- /dev/null +++ b/core/modules/datetime_range/src/DateTimeRangeDisplayOptions.php @@ -0,0 +1,17 @@ + DateTimeRangeDisplayOptions::BOTH->value, + 'separator' => '-', + ]; + } + /** * {@inheritdoc} */ @@ -24,11 +38,7 @@ trait DateTimeRangeTrait { $end_date = $item->end_date; if ($start_date->getTimestamp() !== $end_date->getTimestamp()) { - $elements[$delta] = [ - 'start_date' => $this->buildDateWithIsoAttribute($start_date), - 'separator' => ['#plain_text' => ' ' . $separator . ' '], - 'end_date' => $this->buildDateWithIsoAttribute($end_date), - ]; + $elements[$delta] = $this->renderStartEndWithIsoAttribute($start_date, $separator, $end_date); } else { $elements[$delta] = $this->buildDateWithIsoAttribute($start_date); @@ -46,4 +56,159 @@ trait DateTimeRangeTrait { 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::START_DATE->value => $this->t('Display start date only'), + DateTimeRangeDisplayOptions::END_DATE->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::START_DATE->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::END_DATE->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::START_DATE->value] = $this->buildDate($start_date); + } + if ($this->startDateIsDisplayed() && $this->endDateIsDisplayed()) { + $element['separator'] = ['#plain_text' => ' ' . $separator . ' ']; + } + if ($this->endDateIsDisplayed()) { + $element[DateTimeRangeDisplayOptions::END_DATE->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::START_DATE->value] = $this->buildDateWithIsoAttribute($start_date); + } + if ($this->startDateIsDisplayed() && $this->endDateIsDisplayed()) { + $element['separator'] = ['#plain_text' => ' ' . $separator . ' ']; + } + if ($this->endDateIsDisplayed()) { + $element[DateTimeRangeDisplayOptions::END_DATE->value] = $this->buildDateWithIsoAttribute($end_date); + } + return $element; + } + } diff --git a/core/modules/datetime_range/src/Plugin/Field/FieldFormatter/DateRangeCustomFormatter.php b/core/modules/datetime_range/src/Plugin/Field/FieldFormatter/DateRangeCustomFormatter.php index 78aa8aafe21..5d66d45b7e3 100644 --- a/core/modules/datetime_range/src/Plugin/Field/FieldFormatter/DateRangeCustomFormatter.php +++ b/core/modules/datetime_range/src/Plugin/Field/FieldFormatter/DateRangeCustomFormatter.php @@ -29,9 +29,7 @@ class DateRangeCustomFormatter extends DateTimeCustomFormatter { * {@inheritdoc} */ public static function defaultSettings() { - return [ - 'separator' => '-', - ] + parent::defaultSettings(); + return static::dateTimeRangeDefaultSettings() + parent::defaultSettings(); } /** @@ -52,11 +50,7 @@ class DateRangeCustomFormatter extends DateTimeCustomFormatter { $end_date = $item->end_date; if ($start_date->getTimestamp() !== $end_date->getTimestamp()) { - $elements[$delta] = [ - 'start_date' => $this->buildDate($start_date), - 'separator' => ['#plain_text' => ' ' . $separator . ' '], - 'end_date' => $this->buildDate($end_date), - ]; + $elements[$delta] = $this->renderStartEnd($start_date, $separator, $end_date); } else { $elements[$delta] = $this->buildDate($start_date); @@ -72,14 +66,7 @@ class DateRangeCustomFormatter extends DateTimeCustomFormatter { */ public function settingsForm(array $form, FormStateInterface $form_state) { $form = parent::settingsForm($form, $form_state); - - $form['separator'] = [ - '#type' => 'textfield', - '#title' => $this->t('Date separator'), - '#description' => $this->t('The string to separate the start and end dates'), - '#default_value' => $this->getSetting('separator'), - ]; - + $form = $this->dateTimeRangeSettingsForm($form); return $form; } @@ -87,13 +74,7 @@ class DateRangeCustomFormatter extends DateTimeCustomFormatter { * {@inheritdoc} */ public function settingsSummary() { - $summary = parent::settingsSummary(); - - if ($separator = $this->getSetting('separator')) { - $summary[] = $this->t('Separator: %separator', ['%separator' => $separator]); - } - - return $summary; + return array_merge(parent::settingsSummary(), $this->dateTimeRangeSettingsSummary()); } } diff --git a/core/modules/datetime_range/src/Plugin/Field/FieldFormatter/DateRangeDefaultFormatter.php b/core/modules/datetime_range/src/Plugin/Field/FieldFormatter/DateRangeDefaultFormatter.php index b93851bbd01..96fc430e509 100644 --- a/core/modules/datetime_range/src/Plugin/Field/FieldFormatter/DateRangeDefaultFormatter.php +++ b/core/modules/datetime_range/src/Plugin/Field/FieldFormatter/DateRangeDefaultFormatter.php @@ -29,9 +29,7 @@ class DateRangeDefaultFormatter extends DateTimeDefaultFormatter { * {@inheritdoc} */ public static function defaultSettings() { - return [ - 'separator' => '-', - ] + parent::defaultSettings(); + return static::dateTimeRangeDefaultSettings() + parent::defaultSettings(); } /** @@ -39,13 +37,7 @@ class DateRangeDefaultFormatter extends DateTimeDefaultFormatter { */ public function settingsForm(array $form, FormStateInterface $form_state) { $form = parent::settingsForm($form, $form_state); - - $form['separator'] = [ - '#type' => 'textfield', - '#title' => $this->t('Date separator'), - '#description' => $this->t('The string to separate the start and end dates'), - '#default_value' => $this->getSetting('separator'), - ]; + $form = $this->dateTimeRangeSettingsForm($form); return $form; } @@ -54,13 +46,7 @@ class DateRangeDefaultFormatter extends DateTimeDefaultFormatter { * {@inheritdoc} */ public function settingsSummary() { - $summary = parent::settingsSummary(); - - if ($separator = $this->getSetting('separator')) { - $summary[] = $this->t('Separator: %separator', ['%separator' => $separator]); - } - - return $summary; + return array_merge(parent::settingsSummary(), $this->dateTimeRangeSettingsSummary()); } } diff --git a/core/modules/datetime_range/src/Plugin/Field/FieldFormatter/DateRangePlainFormatter.php b/core/modules/datetime_range/src/Plugin/Field/FieldFormatter/DateRangePlainFormatter.php index 0f74fba6440..c7c8b9441bf 100644 --- a/core/modules/datetime_range/src/Plugin/Field/FieldFormatter/DateRangePlainFormatter.php +++ b/core/modules/datetime_range/src/Plugin/Field/FieldFormatter/DateRangePlainFormatter.php @@ -29,9 +29,7 @@ class DateRangePlainFormatter extends DateTimePlainFormatter { * {@inheritdoc} */ public static function defaultSettings() { - return [ - 'separator' => '-', - ] + parent::defaultSettings(); + return static::dateTimeRangeDefaultSettings() + parent::defaultSettings(); } /** @@ -49,11 +47,7 @@ class DateRangePlainFormatter extends DateTimePlainFormatter { $end_date = $item->end_date; if ($start_date->getTimestamp() !== $end_date->getTimestamp()) { - $elements[$delta] = [ - 'start_date' => $this->buildDate($start_date), - 'separator' => ['#plain_text' => ' ' . $separator . ' '], - 'end_date' => $this->buildDate($end_date), - ]; + $elements[$delta] = $this->renderStartEnd($start_date, $separator, $end_date); } else { $elements[$delta] = $this->buildDate($start_date); @@ -76,14 +70,7 @@ class DateRangePlainFormatter extends DateTimePlainFormatter { */ public function settingsForm(array $form, FormStateInterface $form_state) { $form = parent::settingsForm($form, $form_state); - - $form['separator'] = [ - '#type' => 'textfield', - '#title' => $this->t('Date separator'), - '#description' => $this->t('The string to separate the start and end dates'), - '#default_value' => $this->getSetting('separator'), - ]; - + $form = $this->dateTimeRangeSettingsForm($form); return $form; } @@ -91,13 +78,7 @@ class DateRangePlainFormatter extends DateTimePlainFormatter { * {@inheritdoc} */ public function settingsSummary() { - $summary = parent::settingsSummary(); - - if ($separator = $this->getSetting('separator')) { - $summary[] = $this->t('Separator: %separator', ['%separator' => $separator]); - } - - return $summary; + return array_merge(parent::settingsSummary(), $this->dateTimeRangeSettingsSummary()); } } diff --git a/core/modules/datetime_range/tests/fixtures/update/drupal.daterange-formatter-settings-2827055.php b/core/modules/datetime_range/tests/fixtures/update/drupal.daterange-formatter-settings-2827055.php new file mode 100644 index 00000000000..f8552a5018b --- /dev/null +++ b/core/modules/datetime_range/tests/fixtures/update/drupal.daterange-formatter-settings-2827055.php @@ -0,0 +1,120 @@ +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(); diff --git a/core/modules/datetime_range/tests/src/Functional/DateRangeFieldTest.php b/core/modules/datetime_range/tests/src/Functional/DateRangeFieldTest.php index efe74f344aa..b9e00934678 100644 --- a/core/modules/datetime_range/tests/src/Functional/DateRangeFieldTest.php +++ b/core/modules/datetime_range/tests/src/Functional/DateRangeFieldTest.php @@ -7,6 +7,7 @@ namespace Drupal\Tests\datetime_range\Functional; use Drupal\Core\Datetime\DrupalDateTime; use Drupal\Core\Datetime\Entity\DateFormat; use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface; +use Drupal\datetime_range\DateTimeRangeDisplayOptions; use Drupal\Tests\datetime\Functional\DateTestBase; use Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem; use Drupal\entity_test\Entity\EntityTest; @@ -39,7 +40,7 @@ class DateRangeFieldTest extends DateTestBase { * * @var array */ - protected $defaultSettings = ['timezone_override' => '', 'separator' => '-']; + protected $defaultSettings = ['timezone_override' => '', 'separator' => '-', 'from_to' => DateTimeRangeDisplayOptions::BOTH->value]; /** * {@inheritdoc} @@ -1398,4 +1399,170 @@ class DateRangeFieldTest extends DateTestBase { $this->assertSession()->elementsCount('xpath', "//*[@name='field_storage[subform][settings][datetime_type]' and contains(@disabled, 'disabled')]", 1); } + /** + * Tests displaying dates with the 'from_to' setting. + * + * @dataProvider fromToSettingDataProvider + */ + public function testFromToSetting(array $expected, $datetime_type, $field_formatter_type, array $display_settings = []): void { + $field_name = $this->fieldStorage->getName(); + + // Create a test content type. + $this->drupalCreateContentType(['type' => 'date_content']); + + // Ensure the field to a datetime field. + $this->fieldStorage->setSetting('datetime_type', $datetime_type); + $this->fieldStorage->save(); + + // Build up dates in the UTC timezone. + $value = '2012-12-31 00:00:00'; + $start_date = new DrupalDateTime($value, 'UTC'); + $end_value = '2013-06-06 00:00:00'; + $end_date = new DrupalDateTime($end_value, 'UTC'); + + // Submit a valid date and ensure it is accepted. + $date_format = DateFormat::load('html_date')->getPattern(); + + $edit = [ + "{$field_name}[0][value][date]" => $start_date->format($date_format), + "{$field_name}[0][end_value][date]" => $end_date->format($date_format), + ]; + + // Supply time as well when field is a datetime field. + if ($datetime_type === DateRangeItem::DATETIME_TYPE_DATETIME) { + $time_format = DateFormat::load('html_time')->getPattern(); + $edit["{$field_name}[0][value][time]"] = $start_date->format($time_format); + $edit["{$field_name}[0][end_value][time]"] = $end_date->format($time_format); + } + + $this->drupalGet('entity_test/add'); + $this->submitForm($edit, t('Save')); + preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match); + $id = $match[1]; + $this->assertSession()->pageTextContains(t('entity_test @id has been created.', ['@id' => $id])); + + // Now set display options. + $this->displayOptions = [ + 'type' => $field_formatter_type, + 'label' => 'hidden', + 'settings' => $display_settings + [ + 'format_type' => 'short', + 'separator' => 'THESEPARATOR', + ] + $this->defaultSettings, + ]; + + \Drupal::service('entity_display.repository')->getViewDisplay( + $this->field->getTargetEntityTypeId(), + $this->field->getTargetBundle(), + 'full') + ->setComponent($field_name, $this->displayOptions) + ->save(); + + $output = $this->renderTestEntity($id); + foreach ($expected as $content => $is_expected) { + if ($is_expected) { + $this->assertStringContainsString($content, $output); + } + else { + $this->assertStringNotContainsString($content, $output); + } + } + } + + /** + * The data provider for testing the 'from_to' setting. + * + * @return array + * An array of date settings to test the behavior of the 'from_to' setting. + */ + public function fromToSettingDataProvider(): array { + $datetime_types = [ + DateRangeItem::DATETIME_TYPE_DATE => [ + 'daterange_default' => [ + DateTimeRangeDisplayOptions::START_DATE->value => '12/31/2012', + DateTimeRangeDisplayOptions::END_DATE->value => '06/06/2013', + ], + 'daterange_plain' => [ + DateTimeRangeDisplayOptions::START_DATE->value => '2012-12-31', + DateTimeRangeDisplayOptions::END_DATE->value => '2013-06-06', + ], + 'daterange_custom' => [ + DateTimeRangeDisplayOptions::START_DATE->value => '2012-12-31', + DateTimeRangeDisplayOptions::END_DATE->value => '2013-06-06', + ], + ], + DateRangeItem::DATETIME_TYPE_DATETIME => [ + 'daterange_default' => [ + DateTimeRangeDisplayOptions::START_DATE->value => '12/31/2012 - 00:00', + DateTimeRangeDisplayOptions::END_DATE->value => '06/06/2013 - 00:00', + ], + 'daterange_plain' => [ + DateTimeRangeDisplayOptions::START_DATE->value => '2012-12-31T00:00:00', + DateTimeRangeDisplayOptions::END_DATE->value => '2013-06-06T00:00:00', + ], + 'daterange_custom' => [ + DateTimeRangeDisplayOptions::START_DATE->value => '2012-12-31T00:00:00', + DateTimeRangeDisplayOptions::END_DATE->value => '2013-06-06T00:00:00', + ], + ], + DateRangeItem::DATETIME_TYPE_ALLDAY => [ + 'daterange_default' => [ + DateTimeRangeDisplayOptions::START_DATE->value => '12/31/2012', + DateTimeRangeDisplayOptions::END_DATE->value => '06/06/2013', + ], + 'daterange_plain' => [ + DateTimeRangeDisplayOptions::START_DATE->value => '2012-12-31', + DateTimeRangeDisplayOptions::END_DATE->value => '2013-06-06', + ], + 'daterange_custom' => [ + DateTimeRangeDisplayOptions::START_DATE->value => '2012-12-31', + DateTimeRangeDisplayOptions::END_DATE->value => '2013-06-06', + ], + ], + ]; + + $return = []; + $separator = ' THESEPARATOR '; + foreach ($datetime_types as $datetime_type => $field_formatters) { + foreach ($field_formatters as $field_formatter_type => $dates) { + // Both start and end date. + $return[$datetime_type . '-' . $field_formatter_type . '-both'] = [ + 'expected' => [ + $dates[DateTimeRangeDisplayOptions::START_DATE->value] => TRUE, + $separator => TRUE, + $dates[DateTimeRangeDisplayOptions::END_DATE->value] => TRUE, + ], + 'datetime_type' => $datetime_type, + 'field_formatter_type' => $field_formatter_type, + ]; + + // Only start date. + $return[$datetime_type . '-' . $field_formatter_type . '-start_date'] = [ + 'expected' => [ + $dates[DateTimeRangeDisplayOptions::START_DATE->value] => TRUE, + $separator => FALSE, + $dates[DateTimeRangeDisplayOptions::END_DATE->value] => FALSE, + ], + 'datetime_type' => $datetime_type, + 'field_formatter_type' => $field_formatter_type, + ['from_to' => DateTimeRangeDisplayOptions::START_DATE->value], + ]; + + // Only end date. + $return[$datetime_type . '-' . $field_formatter_type . '-end_date'] = [ + 'expected' => [ + $dates[DateTimeRangeDisplayOptions::START_DATE->value] => FALSE, + $separator => FALSE, + $dates[DateTimeRangeDisplayOptions::END_DATE->value] => TRUE, + ], + 'datetime_type' => $datetime_type, + 'field_formatter_type' => $field_formatter_type, + ['from_to' => DateTimeRangeDisplayOptions::END_DATE->value], + ]; + } + } + + return $return; + } + } diff --git a/core/modules/datetime_range/tests/src/Functional/DateRangeFormatterSettingsUpdateTest.php b/core/modules/datetime_range/tests/src/Functional/DateRangeFormatterSettingsUpdateTest.php new file mode 100644 index 00000000000..841eb93bc28 --- /dev/null +++ b/core/modules/datetime_range/tests/src/Functional/DateRangeFormatterSettingsUpdateTest.php @@ -0,0 +1,57 @@ +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); + } + +} diff --git a/core/modules/datetime_range/tests/src/FunctionalJavascript/DateRangeFieldTest.php b/core/modules/datetime_range/tests/src/FunctionalJavascript/DateRangeFieldTest.php new file mode 100644 index 00000000000..272bfa3ff9a --- /dev/null +++ b/core/modules/datetime_range/tests/src/FunctionalJavascript/DateRangeFieldTest.php @@ -0,0 +1,97 @@ +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' => 'THESEPARATOR', + ], + ]) + ->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::START_DATE->value); + $this->assertFalse($separator->isVisible()); + $page->selectFieldOption($from_to_locator, DateTimeRangeDisplayOptions::END_DATE->value); + $this->assertFalse($separator->isVisible()); + } + +}