diff --git a/core/modules/datetime/datetime.views.inc b/core/modules/datetime/datetime.views.inc index d3b0d18..46d6665 100644 --- a/core/modules/datetime/datetime.views.inc +++ b/core/modules/datetime/datetime.views.inc @@ -11,18 +11,38 @@ * Implements hook_field_views_data(). */ function datetime_field_views_data(FieldStorageConfigInterface $field_storage) { + return datetime_type_field_views_data($field_storage, [], $field_storage->getMainPropertyName()); +} + +/** + * Helper for datetime based fields. + * + * Override the default Views data for a datetime based fields, + * adding datetime views plugins. + * + * @param FieldStorageConfigInterface $field_storage + * The field storage config entity. + * @param array $data + * Field view data or views_field_default_views_data($field_storage) if empty. + * @param string $column_name + * The schema column name with the datetime value. + * + * @return array + * The array of field views data with the datetime plugin. + */ +function datetime_type_field_views_data(FieldStorageConfigInterface $field_storage, $data, $column_name) { // @todo This code only covers configurable fields, handle base table fields // in https://www.drupal.org/node/2489476. - $data = views_field_default_views_data($field_storage); + $data = (empty($data)) ? views_field_default_views_data($field_storage) : $data; foreach ($data as $table_name => $table_data) { // Set the 'datetime' filter type. - $data[$table_name][$field_storage->getName() . '_value']['filter']['id'] = 'datetime'; + $data[$table_name][$field_storage->getName() . '_' . $column_name]['filter']['id'] = 'datetime'; // Set the 'datetime' argument type. - $data[$table_name][$field_storage->getName() . '_value']['argument']['id'] = 'datetime'; + $data[$table_name][$field_storage->getName() . '_' . $column_name]['argument']['id'] = 'datetime'; // Create year, month, and day arguments. - $group = $data[$table_name][$field_storage->getName() . '_value']['group']; + $group = $data[$table_name][$field_storage->getName() . '_' . $column_name]['group']; $arguments = [ // Argument type => help text. 'year' => t('Date in the form of YYYY.'), @@ -33,11 +53,16 @@ function datetime_field_views_data(FieldStorageConfigInterface $field_storage) { 'full_date' => t('Date in the form of CCYYMMDD.'), ]; foreach ($arguments as $argument_type => $help_text) { - $data[$table_name][$field_storage->getName() . '_value_' . $argument_type] = [ - 'title' => $field_storage->getLabel() . ' (' . $argument_type . ')', + $column_name_text = $column_name !== $field_storage->getMainPropertyName() ? ':' . $column_name : ''; + $data[$table_name][$field_storage->getName() . '_' . $column_name . '_' . $argument_type] = [ + 'title' => t('@label@column (@argument)', [ + '@label' => $field_storage->getLabel(), + '@column' => $column_name_text, + '@argument' => $argument_type, + ]), 'help' => $help_text, 'argument' => [ - 'field' => $field_storage->getName() . '_value', + 'field' => $field_storage->getName() . '_' . $column_name, 'id' => 'datetime_' . $argument_type, ], 'group' => $group, @@ -45,7 +70,7 @@ function datetime_field_views_data(FieldStorageConfigInterface $field_storage) { } // Set the 'datetime' sort handler. - $data[$table_name][$field_storage->getName() . '_value']['sort']['id'] = 'datetime'; + $data[$table_name][$field_storage->getName() . '_' . $column_name]['sort']['id'] = 'datetime'; } return $data; diff --git a/core/modules/datetime/tests/src/Kernel/Views/DateTimeHandlerTestBase.php b/core/modules/datetime/tests/src/Kernel/Views/DateTimeHandlerTestBase.php index f0c9c78..e102c2f 100644 --- a/core/modules/datetime/tests/src/Kernel/Views/DateTimeHandlerTestBase.php +++ b/core/modules/datetime/tests/src/Kernel/Views/DateTimeHandlerTestBase.php @@ -29,6 +29,13 @@ protected static $field_name = 'field_date'; /** + * Type of the field. + * + * @var string + */ + protected static $field_type = 'datetime'; + + /** * Nodes to test. * * @var \Drupal\node\NodeInterface[] @@ -53,7 +60,7 @@ protected function setUp($import_test_views = TRUE) { $fieldStorage = FieldStorageConfig::create([ 'field_name' => static::$field_name, 'entity_type' => 'node', - 'type' => 'datetime', + 'type' => static::$field_type, 'settings' => ['datetime_type' => DateTimeItem::DATETIME_TYPE_DATETIME], ]); $fieldStorage->save(); diff --git a/core/modules/datetime_range/datetime_range.module b/core/modules/datetime_range/datetime_range.module index b2b87da..426cbbd 100644 --- a/core/modules/datetime_range/datetime_range.module +++ b/core/modules/datetime_range/datetime_range.module @@ -6,6 +6,8 @@ */ use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\views\Views; +use Drupal\views\ViewEntityInterface; /** * Implements hook_help(). @@ -26,3 +28,126 @@ function datetime_range_help($route_name, RouteMatchInterface $route_match) { return $output; } } + +/** + * Implements hook_view_presave(). + * + * Provides BC layer for modules using datetime_range filter/sort plugins. + */ +function datetime_range_view_presave(ViewEntityInterface $view) { + $config_factory = \Drupal::configFactory(); + $displays = $view->get('display'); + $changed = FALSE; + + foreach ($displays as $display_name => &$display) { + + // Update datetime_range filters. + if (isset($display['display_options']['filters'])) { + foreach ($display['display_options']['filters'] as $field_name => &$filter) { + if ($filter['plugin_id'] == 'string') { + + // Get field config. + $filter_views_data = Views::viewsData()->get($filter['table'])[$filter['field']]['filter']; + if (!isset($filter_views_data['entity_type']) || !isset($filter_views_data['field_name'])) { + continue; + } + $field_storage_name = 'field.storage.' . $filter_views_data['entity_type'] . '.' . $filter_views_data['field_name']; + $field_configuration = $config_factory->get($field_storage_name); + + if ($field_configuration->get('type') == 'daterange') { + + // Set entity_type and field_name if missing. + if (!isset($filter['entity_type'])) { + $filter['entity_type'] = $filter_views_data['entity_type']; + } + + // Set datetime plugin_id. + $filter['plugin_id'] = 'datetime'; + + // Create datetime value array. + $datetime_value = [ + 'min' => '', + 'max' => '', + 'value' => $filter['value'], + 'type' => 'date', + ]; + + // Map string operator/value to numeric equivalent. + switch ($filter['operator']) { + case '=': + $operator = '='; + break; + + case '!=': + case 'not': + $operator = '!='; + break; + + case 'starts': + $operator = 'regular_expression'; + $datetime_value['value'] = '^' . $datetime_value['value']; + break; + + case 'ends': + $operator = 'regular_expression'; + $datetime_value['value'] = $datetime_value['value'] . '$'; + break; + + case 'empty': + $operator = 'empty'; + break; + + case 'not empty': + $operator = 'not empty'; + break; + + default: + $operator = 'regular_expression'; + // Add .* to prevent blank regexes. + if (empty($datetime_value['value'])) { + $datetime_value['value'] = '.*'; + } + } + + // Set value and operator. + $filter['value'] = $datetime_value; + $filter['operator'] = $operator; + $changed = TRUE; + } + } + } + } + + // Update datetime_range sort handlers. + if (isset($display['display_options']['sorts'])) { + foreach ($display['display_options']['sorts'] as $field_name => &$sort) { + if ($sort['plugin_id'] == 'standard') { + + // Get field config. + $sort_views_data = Views::viewsData()->get($sort['table'])[$sort['field']]['sort']; + if (!isset($sort_views_data['entity_type']) || !isset($sort_views_data['field_name'])) { + continue; + } + $field_storage_name = 'field.storage.' . $sort_views_data['entity_type'] . '.' . $sort_views_data['field_name']; + $field_configuration = $config_factory->get($field_storage_name); + + if ($field_configuration->get('type') == 'daterange') { + + // Set entity_type and field_name if missing. + if (!isset($sort['entity_type'])) { + $sort['entity_type'] = $sort_views_data['entity_type']; + } + + // Set datetime plugin_id. + $sort['plugin_id'] = 'datetime'; + $changed = TRUE; + } + } + } + } + } + + if ($changed) { + $view->set('display', $displays); + } +} diff --git a/core/modules/datetime_range/datetime_range.post_update.php b/core/modules/datetime_range/datetime_range.post_update.php index b5f3f5d..8cd82c8 100644 --- a/core/modules/datetime_range/datetime_range.post_update.php +++ b/core/modules/datetime_range/datetime_range.post_update.php @@ -5,9 +5,88 @@ * Post-update functions for Datetime Range module. */ +use Drupal\views\Views; + /** * Clear caches to ensure schema changes are read. */ function datetime_range_post_update_translatable_separator() { // Empty post-update hook to cause a cache rebuild. } + +/** + * Update existing views using datetime_range fields. + */ +function datetime_range_post_update_views_string_plugin_id() { + + /* @var \Drupal\views\Entity\View[] $views */ + $views = \Drupal::entityTypeManager()->getStorage('view')->loadMultiple(); + $config_factory = \Drupal::configFactory(); + $message = NULL; + $ids = []; + + foreach ($views as $view) { + $displays = $view->get('display'); + $needs_bc_layer_update = FALSE; + + foreach ($displays as $display_name => $display) { + + // Check if datetime_range filters need updates. + if (!$needs_bc_layer_update && isset($display['display_options']['filters'])) { + foreach ($display['display_options']['filters'] as $field_name => $filter) { + if ($filter['plugin_id'] == 'string') { + + // Get field config. + $filter_views_data = Views::viewsData()->get($filter['table'])[$filter['field']]['filter']; + if (!isset($filter_views_data['entity_type']) || !isset($filter_views_data['field_name'])) { + continue; + } + $field_storage_name = 'field.storage.' . $filter_views_data['entity_type'] . '.' . $filter_views_data['field_name']; + $field_configuration = $config_factory->get($field_storage_name); + + if ($field_configuration->get('type') == 'daterange') { + // Trigger the BC layer control. + $needs_bc_layer_update = TRUE; + continue 2; + } + } + } + } + + // Check if datetime_range sort handlers need updates. + if (!$needs_bc_layer_update && isset($display['display_options']['sorts'])) { + foreach ($display['display_options']['sorts'] as $field_name => $sort) { + if ($sort['plugin_id'] == 'standard') { + + // Get field config. + $sort_views_data = Views::viewsData()->get($sort['table'])[$sort['field']]['sort']; + if (!isset($sort_views_data['entity_type']) || !isset($sort_views_data['field_name'])) { + continue; + } + $field_storage_name = 'field.storage.' . $sort_views_data['entity_type'] . '.' . $sort_views_data['field_name']; + $field_configuration = $config_factory->get($field_storage_name); + + if ($field_configuration->get('type') == 'daterange') { + // Trigger the BC layer control. + $needs_bc_layer_update = TRUE; + continue 2; + } + } + } + } + } + + // If current view needs BC layer updates save it and the hook view_presave + // will do the rest. + if ($needs_bc_layer_update) { + $view->save(); + $ids[] = $view->id(); + } + } + + if (!empty($ids)) { + $message = \Drupal::translation()->translate('Updated datetime_range filter/sort plugins for views: @ids', ['@ids' => implode(', ', array_unique($ids))]); + } + + return $message; +} diff --git a/core/modules/datetime_range/datetime_range.views.inc b/core/modules/datetime_range/datetime_range.views.inc new file mode 100644 index 0000000..0823a84 --- /dev/null +++ b/core/modules/datetime_range/datetime_range.views.inc @@ -0,0 +1,21 @@ +loadInclude('datetime', 'inc', 'datetime.views'); + + // Get datetime field data for value and end_value. + $data = datetime_type_field_views_data($field_storage, [], 'value'); + $data = datetime_type_field_views_data($field_storage, $data, 'end_value'); + + return $data; +} diff --git a/core/modules/datetime_range/src/Tests/Update/DatetimeRangeViewUpdateTest.php b/core/modules/datetime_range/src/Tests/Update/DatetimeRangeViewUpdateTest.php new file mode 100644 index 0000000..d81ed751 --- /dev/null +++ b/core/modules/datetime_range/src/Tests/Update/DatetimeRangeViewUpdateTest.php @@ -0,0 +1,74 @@ +databaseDumpFiles = [ + __DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz', + __DIR__ . '/../../../tests/fixtures/update/datetime_range-filter-values.php', + ]; + } + + /** + * Tests that datetime_range filter values are updated properly. + */ + public function testViewsPostUpdateDateRangeFilterValues() { + + // Load our pre-update test view. + $view = View::load('test_datetime_range_filter_values'); + $data = $view->toArray(); + + // Check pre-update filter values. + $filter1 = $data['display']['default']['display_options']['filters']['field_range_value']; + $this->assertIdentical('string', $filter1['plugin_id']); + + // Check pre-update filter with operator going to be mapped. + $filter2 = $data['display']['default']['display_options']['filters']['field_range_end_value']; + $this->assertIdentical('string', $filter2['plugin_id']); + $this->assertIdentical('', $filter2['value']); + $this->assertIdentical('contains', $filter2['operator']); + + // Check pre-update sort values. + $sort = $data['display']['default']['display_options']['sorts']['field_range_value']; + $this->assertIdentical('standard', $sort['plugin_id']); + + $this->runUpdates(); + + // Reload and initialize our test view. + $view = View::load('test_datetime_range_filter_values'); + $data = $view->toArray(); + + // Check filter values. + $filter1 = $data['display']['default']['display_options']['filters']['field_range_value']; + $this->assertIdentical('datetime', $filter1['plugin_id']); + $this->assertIdentical('2017', $filter1['value']['value']); + $this->assertIdentical('=', $filter1['operator']); + + // Check string to datetime operator/value mapping. + $filter2 = $data['display']['default']['display_options']['filters']['field_range_end_value']; + $this->assertIdentical('datetime', $filter2['plugin_id']); + $this->assertIdentical('.*', $filter2['value']['value']); + $this->assertIdentical('regular_expression', $filter2['operator']); + + // Check sort values. + $sort = $data['display']['default']['display_options']['sorts']['field_range_value']; + $this->assertIdentical('datetime', $sort['plugin_id']); + } + +} diff --git a/core/modules/datetime_range/tests/fixtures/update/datetime_range-filter-values.php b/core/modules/datetime_range/tests/fixtures/update/datetime_range-filter-values.php new file mode 100644 index 0000000..952e67a --- /dev/null +++ b/core/modules/datetime_range/tests/fixtures/update/datetime_range-filter-values.php @@ -0,0 +1,332 @@ +select('config') + ->fields('config', ['data']) + ->condition('collection', '') + ->condition('name', 'core.entity_form_display.node.page.default') + ->execute() + ->fetchField(); + +$data = unserialize($data); +$data['dependencies']['config'][] = 'field.field.' . $field_datetime_range['id']; +$data['dependencies']['module'][] = 'datetime_range'; +$data['content'][$field_datetime_range['field_name']] = array( + "weight"=> 27, + "settings" => array(), + "third_party_settings" => array(), + "type" => "daterange_default", + "region" => "content" +); +$connection->update('config') + ->fields([ + 'data' => serialize($data), + ]) + ->condition('collection', '') + ->condition('name', 'core.entity_form_display.node.page.default') + ->execute(); + +// Update core.entity_view_display.node.page.default +$data = $connection->select('config') + ->fields('config', ['data']) + ->condition('collection', '') + ->condition('name', 'core.entity_view_display.node.page.default') + ->execute() + ->fetchField(); + +$data = unserialize($data); +$data['dependencies']['config'][] = 'field.field.' . $field_datetime_range['id']; +$data['dependencies']['module'][] = 'datetime_range'; +$data['content'][$field_datetime_range['field_name']] = array( + "weight"=> 102, + "label"=> "above", + "settings" => array("separator"=> "-", "format_type" => "medium", "timezone_override" => ""), + "third_party_settings" => array(), + "type" => "daterange_default", + "region" => "content" +); +$connection->update('config') + ->fields([ + 'data' => serialize($data), + ]) + ->condition('collection', '') + ->condition('name', 'core.entity_view_display.node.page.default') + ->execute(); + +$connection->insert('config') +->fields(array( + 'collection', + 'name', + 'data', +)) +->values(array( + 'collection' => '', + 'name' => 'field.field.' . $field_datetime_range['id'], + 'data' => serialize($field_datetime_range), +)) +->values(array( + 'collection' => '', + 'name' => 'field.storage.' . $field_storage_datetime_range['id'], + 'data' => serialize($field_storage_datetime_range), +)) +->values(array( + 'collection' => '', + 'name' => 'views.view.' . $views_datetime_range['id'], + 'data' => serialize($views_datetime_range), +)) +->execute(); + +// Update core.extension. +$extensions = $connection->select('config') + ->fields('config', ['data']) + ->condition('collection', '') + ->condition('name', 'core.extension') + ->execute() + ->fetchField(); +$extensions = unserialize($extensions); +$extensions['module']['datetime_range'] = 0; +$connection->update('config') + ->fields([ + 'data' => serialize($extensions), + ]) + ->condition('collection', '') + ->condition('name', 'core.extension') + ->execute(); + +$connection->insert('key_value') +->fields(array( + 'collection', + 'name', + 'value', +)) +->values(array( + 'collection' => 'config.entity.key_store.field_config', + 'name' => 'uuid:87dc4221-8d56-4112-8a7f-7a855ac35d08', + 'value' => 'a:1:{i:0;s:33:"field.field.' . $field_datetime_range['id'] . '";}', +)) +->values(array( + 'collection' => 'config.entity.key_store.field_storage_config', + 'name' => 'uuid:2190ad8c-39dd-4eb1-b189-1bfc0c244a40', + 'value' => 'a:1:{i:0;s:30:"field.storage.' . $field_storage_datetime_range['id'] . '";}', +)) +->values(array( + 'collection' => 'config.entity.key_store.view', + 'name' => 'uuid:d20760b6-7cc4-4844-ae04-96da7225a46f', + 'value' => 'a:1:{i:0;s:44:"views.view.' . $views_datetime_range['id'] . '";}', +)) +->values(array( + 'collection' => 'entity.storage_schema.sql', + 'name' => 'node.field_schema_data.field_range', + 'value' => 'a:2:{s:17:"node__field_range";a:4:{s:11:"description";s:40:"Data storage for node field field_range.";s:6:"fields";a:8:{s:6:"bundle";a:5:{s:4:"type";s:13:"varchar_ascii";s:6:"length";i:128;s:8:"not null";b:1;s:7:"default";s:0:"";s:11:"description";s:88:"The field instance bundle to which this row belongs, used when deleting a field instance";}s:7:"deleted";a:5:{s:4:"type";s:3:"int";s:4:"size";s:4:"tiny";s:8:"not null";b:1;s:7:"default";i:0;s:11:"description";s:60:"A boolean indicating whether this data item has been deleted";}s:9:"entity_id";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:38:"The entity id this data is attached to";}s:11:"revision_id";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:47:"The entity revision id this data is attached to";}s:8:"langcode";a:5:{s:4:"type";s:13:"varchar_ascii";s:6:"length";i:32;s:8:"not null";b:1;s:7:"default";s:0:"";s:11:"description";s:37:"The language code for this data item.";}s:5:"delta";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:67:"The sequence number for this data item, used for multi-value fields";}s:17:"field_range_value";a:4:{s:11:"description";s:21:"The start date value.";s:4:"type";s:7:"varchar";s:6:"length";i:20;s:8:"not null";b:1;}s:21:"field_range_end_value";a:4:{s:11:"description";s:19:"The end date value.";s:4:"type";s:7:"varchar";s:6:"length";i:20;s:8:"not null";b:1;}}s:11:"primary key";a:4:{i:0;s:9:"entity_id";i:1;s:7:"deleted";i:2;s:5:"delta";i:3;s:8:"langcode";}s:7:"indexes";a:4:{s:6:"bundle";a:1:{i:0;s:6:"bundle";}s:11:"revision_id";a:1:{i:0;s:11:"revision_id";}s:17:"field_range_value";a:1:{i:0;s:17:"field_range_value";}s:21:"field_range_end_value";a:1:{i:0;s:21:"field_range_end_value";}}}s:26:"node_revision__field_range";a:4:{s:11:"description";s:52:"Revision archive storage for node field field_range.";s:6:"fields";a:8:{s:6:"bundle";a:5:{s:4:"type";s:13:"varchar_ascii";s:6:"length";i:128;s:8:"not null";b:1;s:7:"default";s:0:"";s:11:"description";s:88:"The field instance bundle to which this row belongs, used when deleting a field instance";}s:7:"deleted";a:5:{s:4:"type";s:3:"int";s:4:"size";s:4:"tiny";s:8:"not null";b:1;s:7:"default";i:0;s:11:"description";s:60:"A boolean indicating whether this data item has been deleted";}s:9:"entity_id";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:38:"The entity id this data is attached to";}s:11:"revision_id";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:47:"The entity revision id this data is attached to";}s:8:"langcode";a:5:{s:4:"type";s:13:"varchar_ascii";s:6:"length";i:32;s:8:"not null";b:1;s:7:"default";s:0:"";s:11:"description";s:37:"The language code for this data item.";}s:5:"delta";a:4:{s:4:"type";s:3:"int";s:8:"unsigned";b:1;s:8:"not null";b:1;s:11:"description";s:67:"The sequence number for this data item, used for multi-value fields";}s:17:"field_range_value";a:4:{s:11:"description";s:21:"The start date value.";s:4:"type";s:7:"varchar";s:6:"length";i:20;s:8:"not null";b:1;}s:21:"field_range_end_value";a:4:{s:11:"description";s:19:"The end date value.";s:4:"type";s:7:"varchar";s:6:"length";i:20;s:8:"not null";b:1;}}s:11:"primary key";a:5:{i:0;s:9:"entity_id";i:1;s:11:"revision_id";i:2;s:7:"deleted";i:3;s:5:"delta";i:4;s:8:"langcode";}s:7:"indexes";a:4:{s:6:"bundle";a:1:{i:0;s:6:"bundle";}s:11:"revision_id";a:1:{i:0;s:11:"revision_id";}s:17:"field_range_value";a:1:{i:0;s:17:"field_range_value";}s:21:"field_range_end_value";a:1:{i:0;s:21:"field_range_end_value";}}}}', +)) +->values(array( + 'collection' => 'system.schema', + 'name' => 'datetime_range', + 'value' => 'i:8000;', +)) +->execute(); + +// Update entity.definitions.bundle_field_map +$value = $connection->select('key_value') + ->fields('key_value', ['value']) + ->condition('collection', 'entity.definitions.bundle_field_map') + ->condition('name', 'node') + ->execute() + ->fetchField(); + +$value = unserialize($value); +$value["field_range"] = array("type" => "daterange", "bundles" => array("page" => "page")); + +$connection->update('key_value') + ->fields([ + 'value' => serialize($value), + ]) + ->condition('collection', 'entity.definitions.bundle_field_map') + ->condition('name', 'node') + ->execute(); + +// Update system.module.files +$files = $connection->select('key_value') + ->fields('key_value', ['value']) + ->condition('collection', 'state') + ->condition('name', 'system.module.files') + ->execute() + ->fetchField(); + +$files = unserialize($files); +$files["datetime_range"] = "core/modules/datetime_range/datetime_range.info.yml"; + +$connection->update('key_value') + ->fields([ + 'value' => serialize($files), + ]) + ->condition('collection', 'state') + ->condition('name', 'system.module.files') + ->execute(); + +$connection->schema()->createTable('node__field_range', array( + 'fields' => array( + 'bundle' => array( + 'type' => 'varchar_ascii', + 'not null' => TRUE, + 'length' => '128', + 'default' => '', + ), + 'deleted' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'tiny', + 'default' => '0', + ), + 'entity_id' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'revision_id' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'langcode' => array( + 'type' => 'varchar_ascii', + 'not null' => TRUE, + 'length' => '32', + 'default' => '', + ), + 'delta' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'field_range_value' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '20', + ), + 'field_range_end_value' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '20', + ), + ), + 'primary key' => array( + 'entity_id', + 'deleted', + 'delta', + 'langcode', + ), + 'indexes' => array( + 'bundle' => array( + 'bundle', + ), + 'revision_id' => array( + 'revision_id', + ), + 'field_range_value' => array( + 'field_range_value', + ), + 'field_range_end_value' => array( + 'field_range_end_value', + ), + ), + 'mysql_character_set' => 'utf8mb4', +)); + +$connection->schema()->createTable('node_revision__field_range', array( + 'fields' => array( + 'bundle' => array( + 'type' => 'varchar_ascii', + 'not null' => TRUE, + 'length' => '128', + 'default' => '', + ), + 'deleted' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'tiny', + 'default' => '0', + ), + 'entity_id' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'revision_id' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'langcode' => array( + 'type' => 'varchar_ascii', + 'not null' => TRUE, + 'length' => '32', + 'default' => '', + ), + 'delta' => array( + 'type' => 'int', + 'not null' => TRUE, + 'size' => 'normal', + 'unsigned' => TRUE, + ), + 'field_range_value' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '20', + ), + 'field_range_end_value' => array( + 'type' => 'varchar', + 'not null' => TRUE, + 'length' => '20', + ), + ), + 'primary key' => array( + 'entity_id', + 'revision_id', + 'deleted', + 'delta', + 'langcode', + ), + 'indexes' => array( + 'bundle' => array( + 'bundle', + ), + 'revision_id' => array( + 'revision_id', + ), + 'field_range_value' => array( + 'field_range_value', + ), + 'field_range_end_value' => array( + 'field_range_end_value', + ), + ), + 'mysql_character_set' => 'utf8mb4', +)); + diff --git a/core/modules/datetime_range/tests/fixtures/update/field.field.node.page.field_range.yml b/core/modules/datetime_range/tests/fixtures/update/field.field.node.page.field_range.yml new file mode 100644 index 0000000..1b98429 --- /dev/null +++ b/core/modules/datetime_range/tests/fixtures/update/field.field.node.page.field_range.yml @@ -0,0 +1,21 @@ +uuid: 87dc4221-8d56-4112-8a7f-7a855ac35d08 +langcode: en +status: true +dependencies: + config: + - field.storage.node.field_range + - node.type.page + module: + - datetime_range +id: node.page.field_range +field_name: field_range +entity_type: node +bundle: page +label: range +description: '' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: { } +field_type: daterange diff --git a/core/modules/datetime_range/tests/fixtures/update/field.storage.node.field_range.yml b/core/modules/datetime_range/tests/fixtures/update/field.storage.node.field_range.yml new file mode 100644 index 0000000..26d610e --- /dev/null +++ b/core/modules/datetime_range/tests/fixtures/update/field.storage.node.field_range.yml @@ -0,0 +1,20 @@ +uuid: 2190ad8c-39dd-4eb1-b189-1bfc0c244a40 +langcode: en +status: true +dependencies: + module: + - datetime_range + - node +id: node.field_range +field_name: field_range +entity_type: node +type: daterange +settings: + datetime_type: datetime +module: datetime_range +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/core/modules/datetime_range/tests/fixtures/update/views.view.test_datetime_range_filter_values.yml b/core/modules/datetime_range/tests/fixtures/update/views.view.test_datetime_range_filter_values.yml new file mode 100644 index 0000000..9afc264 --- /dev/null +++ b/core/modules/datetime_range/tests/fixtures/update/views.view.test_datetime_range_filter_values.yml @@ -0,0 +1,231 @@ +uuid: d20760b6-7cc4-4844-ae04-96da7225a46f +langcode: en +status: true +dependencies: + module: + - node + - user +id: test_datetime_range_filter_values +label: test_datetime_range_filter_values +module: views +description: '' +tag: '' +base_table: node_field_data +base_field: nid +core: 8.x +display: + default: + display_plugin: default + id: default + display_title: Master + position: 0 + display_options: + access: + type: perm + options: + perm: 'access content' + cache: + type: tag + options: { } + query: + type: views_query + options: + disable_sql_rewrite: false + distinct: false + replica: false + query_comment: '' + query_tags: { } + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + pager: + type: mini + options: + items_per_page: 10 + offset: 0 + id: 0 + total_pages: null + expose: + items_per_page: false + items_per_page_label: 'Items per page' + items_per_page_options: '5, 10, 25, 50' + items_per_page_options_all: false + items_per_page_options_all_label: '- All -' + offset: false + offset_label: Offset + tags: + previous: ‹‹ + next: ›› + style: + type: default + options: + grouping: { } + row_class: '' + default_row_class: true + uses_fields: false + row: + type: fields + options: + inline: { } + separator: '' + hide_empty: false + default_field_elements: true + fields: + title: + id: title + table: node_field_data + field: title + entity_type: node + entity_field: title + label: '' + alter: + alter_text: false + make_link: false + absolute: false + trim: false + word_boundary: false + ellipsis: false + strip_tags: false + html: false + hide_empty: false + empty_zero: false + settings: + link_to_entity: true + plugin_id: field + relationship: none + group_type: group + admin_label: '' + exclude: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_alter_empty: true + click_sort_column: value + type: string + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + filters: + field_range_value: + id: field_range_value + table: node__field_range + field: field_range_value + relationship: none + group_type: group + admin_label: '' + operator: '=' + value: '2017' + group: 1 + exposed: false + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: '' + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: string + field_range_end_value: + id: field_range_end_value + table: node__field_range + field: field_range_end_value + relationship: none + group_type: group + admin_label: '' + operator: contains + value: '' + group: 1 + exposed: false + expose: + operator_id: '' + label: '' + description: '' + use_operator: false + operator: '' + identifier: '' + required: false + remember: false + multiple: false + remember_roles: + authenticated: authenticated + is_grouped: false + group_info: + label: '' + description: '' + identifier: '' + optional: true + widget: select + multiple: false + remember: false + default_group: All + default_group_multiple: { } + group_items: { } + plugin_id: string + sorts: + field_range_value: + id: field_range_value + table: node__field_range + field: field_range_value + relationship: none + group_type: group + admin_label: '' + order: ASC + exposed: false + expose: + label: '' + plugin_id: standard + header: { } + footer: { } + empty: { } + relationships: { } + arguments: { } + display_extenders: { } + filter_groups: + operator: AND + groups: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - url.query_args + - 'user.node_grants:view' + - user.permissions + tags: { } diff --git a/core/modules/datetime_range/tests/src/Kernel/Views/DateRangeHandlerTestBase.php b/core/modules/datetime_range/tests/src/Kernel/Views/DateRangeHandlerTestBase.php new file mode 100644 index 0000000..2e9ec0c --- /dev/null +++ b/core/modules/datetime_range/tests/src/Kernel/Views/DateRangeHandlerTestBase.php @@ -0,0 +1,24 @@ +getRequestTime(); + + // Change field storage to date-only. + $storage = FieldStorageConfig::load('node.' . static::$field_name); + $storage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATE); + $storage->save(); + + $dates = [ + // Tomorrow. + \Drupal::service('date.formatter')->format(static::$date + 86400, 'custom', DATETIME_DATE_STORAGE_FORMAT, DATETIME_STORAGE_TIMEZONE), + // Today. + \Drupal::service('date.formatter')->format(static::$date, 'custom', DATETIME_DATE_STORAGE_FORMAT, DATETIME_STORAGE_TIMEZONE), + // Yesterday. + \Drupal::service('date.formatter')->format(static::$date - 86400, 'custom', DATETIME_DATE_STORAGE_FORMAT, DATETIME_STORAGE_TIMEZONE), + ]; + + // Node 0: Yesterday - Today. + $node = Node::create([ + 'title' => $this->randomMachineName(8), + 'type' => 'page', + 'field_date' => [ + 'value' => $dates[2], + 'end_value' => $dates[1], + ], + ]); + $node->save(); + $this->nodes[] = $node; + + // Node 1: Today - Today. + $node = Node::create([ + 'title' => $this->randomMachineName(8), + 'type' => 'page', + 'field_date' => [ + 'value' => $dates[1], + 'end_value' => $dates[1], + ], + ]); + $node->save(); + $this->nodes[] = $node; + + // Node 2: Today - Tomorrow. + $node = Node::create([ + 'title' => $this->randomMachineName(8), + 'type' => 'page', + 'field_date' => [ + 'value' => $dates[1], + 'end_value' => $dates[0], + ], + ]); + $node->save(); + $this->nodes[] = $node; + + // Add end date filter to the test_filter_datetime view. + /** @var \Drupal\views\Entity\View $view */ + $view = \Drupal::entityTypeManager()->getStorage('view')->load('test_filter_datetime'); + $field_end = static::$field_name . '_end_value'; + $display = $view->getDisplay('default'); + $filter_end_date = $display['display_options']['filters'][static::$field_name . '_value']; + $filter_end_date['id'] = $field_end; + $filter_end_date['field'] = $field_end; + + $view->getDisplay('default')['display_options']['filters'][$field_end] = $filter_end_date; + $view->save(); + } + + /** + * Test offsets with date-only fields. + */ + public function testDateOffsets() { + $view = Views::getView('test_filter_datetime'); + $field_start = static::$field_name . '_value'; + $field_end = static::$field_name . '_end_value'; + + // Test simple operations. + $view->initHandlers(); + + // Search nodes with: + // - start date greater than or equal to 'yesterday'. + // - end date lower than or equal to 'today'. + // Expected results: nodes 0 and 1. + $view->filter[$field_start]->operator = '>='; + $view->filter[$field_start]->value['type'] = 'offset'; + $view->filter[$field_start]->value['value'] = '-1 day'; + $view->filter[$field_end]->operator = '<='; + $view->filter[$field_end]->value['type'] = 'offset'; + $view->filter[$field_end]->value['value'] = 'now'; + $view->setDisplay('default'); + $this->executeView($view); + $expected_result = [ + ['nid' => $this->nodes[0]->id()], + ['nid' => $this->nodes[1]->id()], + ]; + $this->assertIdenticalResultset($view, $expected_result, $this->map); + $view->destroy(); + + // Search nodes with: + // - start date greater than or equal to 'yesterday'. + // - end date greater than 'today'. + // Expected results: node 2. + $view->initHandlers(); + $view->filter[$field_start]->operator = '>='; + $view->filter[$field_start]->value['type'] = 'offset'; + $view->filter[$field_start]->value['value'] = '-1 day'; + $view->filter[$field_end]->operator = '>'; + $view->filter[$field_end]->value['type'] = 'offset'; + $view->filter[$field_end]->value['value'] = 'now'; + $view->setDisplay('default'); + $this->executeView($view); + $expected_result = [ + ['nid' => $this->nodes[2]->id()], + ]; + $this->assertIdenticalResultset($view, $expected_result, $this->map); + } + +}