diff --git a/core/modules/views/src/ViewsConfigUpdater.php b/core/modules/views/src/ViewsConfigUpdater.php index d9eaf2defcc27a2bbb74cfcac2f5a8d179df6adc..a140898f323df49af614d26e87380f94902a0ea7 100644 --- a/core/modules/views/src/ViewsConfigUpdater.php +++ b/core/modules/views/src/ViewsConfigUpdater.php @@ -142,6 +142,9 @@ public function updateAll(ViewEntityInterface $view) { if ($this->processTimestampFormatterTimeDiffUpdateHandler($handler, $handler_type)) { $changed = TRUE; } + if ($this->processRevisionFieldHyphenFix($view)) { + $changed = TRUE; + } return $changed; }); } @@ -328,4 +331,85 @@ protected function processTimestampFormatterTimeDiffUpdateHandler(array &$handle return FALSE; } + /** + * Replaces hyphen on historical data (revision) fields. + * + * This replaces hyphens with double underscores in twig assertions. + * + * @param \Drupal\views\ViewEntityInterface $view + * The view entity. + * + * @return bool + * Whether the handler was updated. + * + * @see https://www.drupal.org/project/drupal/issues/2831233 + */ + public function processRevisionFieldHyphenFix(ViewEntityInterface $view): bool { + // Regex to search only for token with machine name '-revision_id'. + $old_part = '/{{([^}]+)(-revision_id)/'; + $new_part = '{{$1__revision_id'; + $old_field = '-revision_id'; + $new_field = '__revision_id'; + /** @var \Drupal\views\ViewEntityInterface $view */ + $is_update = FALSE; + $displays = $view->get('display'); + foreach ($displays as &$display) { + if (isset($display['display_options']['fields'])) { + foreach ($display['display_options']['fields'] as $field_name => $field) { + if (!empty($field['alter']['text'])) { + // Fixes replacement token references in rewritten fields. + $alter_text = $field['alter']['text']; + if (preg_match($old_part, $alter_text) === 1) { + $is_update = TRUE; + $field['alter']['text'] = preg_replace($old_part, $new_part, $alter_text); + } + } + + if (!empty($field['alter']['path'])) { + // Fixes replacement token references in link paths. + $alter_path = $field['alter']['path']; + if (preg_match($old_part, $alter_path) === 1) { + $is_update = TRUE; + $field['alter']['path'] = preg_replace($old_part, $new_part, $alter_path); + } + } + + if (str_contains($field_name, $old_field)) { + // Replaces the field name and the view id. + $is_update = TRUE; + $field['id'] = str_replace($old_field, $new_field, $field['id']); + $field['field'] = str_replace($old_field, $new_field, $field['field']); + + // Replace key with save order. + $field_name_update = str_replace($old_field, $new_field, $field_name); + $fields = $display['display_options']['fields']; + $keys = array_keys($fields); + $keys[array_search($field_name, $keys)] = $field_name_update; + $display['display_options']['fields'] = array_combine($keys, $fields); + $display['display_options']['fields'][$field_name_update] = $field; + } + } + } + } + if ($is_update) { + $view->set('display', $displays); + } + return $is_update; + } + + /** + * Checks each display in a view to see if it needs the hyphen fix. + * + * @param \Drupal\views\ViewEntityInterface $view + * The view entity. + * + * @return bool + * TRUE if the view has any displays that needed to be updated. + */ + public function needsRevisionFieldHyphenFix(ViewEntityInterface $view): bool { + return $this->processDisplayHandlers($view, TRUE, function (&$handler, $handler_type) use ($view) { + return $this->processRevisionFieldHyphenFix($view); + }); + } + } diff --git a/core/modules/views/tests/fixtures/update/fix-revision-id-update.php b/core/modules/views/tests/fixtures/update/fix-revision-id-update.php new file mode 100644 index 0000000000000000000000000000000000000000..228ace239aec47e082a2f6d6ee3f9a0c817e96c1 --- /dev/null +++ b/core/modules/views/tests/fixtures/update/fix-revision-id-update.php @@ -0,0 +1,19 @@ +<?php + +/** + * @file + * Test fixture. + */ + +use Drupal\Core\Database\Database; +use Drupal\Core\Serialization\Yaml; + +$connection = Database::getConnection(); + +$connection->insert('config') + ->fields([ + 'collection' => '', + 'name' => 'views.view.test_fix_revision_id_update', + 'data' => serialize(Yaml::decode(file_get_contents('core/modules/views/tests/fixtures/update/views.view.test_fix_revision_id_update.yml'))), + ]) + ->execute(); diff --git a/core/modules/views/tests/fixtures/update/views.view.test_fix_revision_id_update.yml b/core/modules/views/tests/fixtures/update/views.view.test_fix_revision_id_update.yml new file mode 100644 index 0000000000000000000000000000000000000000..49191d8d3140e9e1c6d2b825d23d1ce75a82726f --- /dev/null +++ b/core/modules/views/tests/fixtures/update/views.view.test_fix_revision_id_update.yml @@ -0,0 +1,189 @@ +uuid: 9382cd5c-3853-436c-9c35-3b6203b6cc24 +langcode: und +status: true +dependencies: { } +id: test_fix_revision_id_update +module: views +description: '' +tag: '' +base_table: entity_test_rev_revision +base_field: revision_id +core: '8' +display: + default: + display_options: + access: + type: none + cache: + type: tag + fields: + id: + id: id + table: entity_test_rev_revision + field: id + plugin_id: field + entity_type: entity_test_rev + entity_field: id + revision_id: + id: revision_id + table: entity_test_rev_revision + field: revision_id + plugin_id: field + entity_type: entity_test_rev + entity_field: revision_id + field_test: + id: field_test + table: entity_test_rev__field_test + field: field_test + plugin_id: field + entity_type: entity_test_rev + entity_field: field_test + field_test-revision_id_1: + id: field_test-revision_id_1 + table: entity_test_rev_revision__field_test + field: field_test-revision_id + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: false + alter: + alter_text: true + text: 'Replace: {{ field_test-revision_id_1 }}' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: number_integer + settings: + thousand_separator: '' + prefix_suffix: true + 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 + plugin_id: field + field_test-revision_id_2: + id: field_test-revision_id_2 + table: entity_test_rev_revision__field_test + field: field_test-revision_id + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: false + alter: + alter_text: true + text: 'field_test-revision_id_2: {{ field_test-revision_id_2 }}' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: number_integer + settings: + thousand_separator: '' + prefix_suffix: true + 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 + plugin_id: field + name: + id: name + table: entity_test_rev_revision + field: name + plugin_id: field + entity_type: entity_test_rev + entity_field: name + sorts: + revision_id: + id: revision_id + table: entity_test_rev_revision + field: revision_id + entity_type: entity_test_rev + entity_field: revision_id + order: ASC + style: + type: html_list + display_plugin: default + display_title: Master + id: default + position: 0 diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_revision_test.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_revision_test.yml index 63c74410959db9cbafb889e96a7d73d7608c9e43..889b80efbe503dd1091794bdc510b9f7c0f62ad3 100644 --- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_revision_test.yml +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_field_revision_test.yml @@ -6,7 +6,7 @@ module: views description: '' tag: '' base_table: entity_test_rev_revision -base_field: id +base_field: revision_id display: default: display_options: @@ -36,6 +36,70 @@ display: plugin_id: field entity_type: entity_test_rev entity_field: field_test + field_test__revision_id_1: + id: field_test__revision_id_1 + table: entity_test_rev_revision__field_test + field: field_test__revision_id + relationship: none + group_type: group + admin_label: '' + label: '' + exclude: false + alter: + alter_text: true + text: 'Replace: {{ field_test__revision_id_1 }}' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: number_integer + settings: + thousand_separator: '' + prefix_suffix: true + 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 + plugin_id: field name: id: name table: entity_test_rev_revision diff --git a/core/modules/views/tests/src/Functional/Update/ViewsFixRevisionIdUpdateTest.php b/core/modules/views/tests/src/Functional/Update/ViewsFixRevisionIdUpdateTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a65f0d35b2dcf1b216282b97fd81daa4525a307e --- /dev/null +++ b/core/modules/views/tests/src/Functional/Update/ViewsFixRevisionIdUpdateTest.php @@ -0,0 +1,80 @@ +<?php + +namespace Drupal\Tests\views\Functional\Update; + +use Drupal\FunctionalTests\Update\UpdatePathTestBase; +use Drupal\views\Entity\View; + +/** + * Tests the upgrade path for revision ids in field aliases. + * + * @see views_post_update_fix_revision_id_part() + * + * @group Update + * @group legacy + */ +class ViewsFixRevisionIdUpdateTest extends UpdatePathTestBase { + + /** + * {@inheritdoc} + */ + protected static $modules = ['entity_test']; + + /** + * {@inheritdoc} + */ + protected function setDatabaseDumpFiles() { + $this->databaseDumpFiles = [ + __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-9.4.0.bare.standard.php.gz', + __DIR__ . '/../../../fixtures/update/fix-revision-id-update.php', + ]; + } + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + + $this->installModulesFromClassProperty($this->container); + } + + /** + * Tests the upgrade path for revision ids in field aliases. + */ + public function testViewsPostUpdateFixRevisionId() { + $view = View::load('test_fix_revision_id_update'); + $data = $view->toArray(); + $fields = $data['display']['default']['display_options']['fields']; + + $this->assertArrayHasKey('field_test-revision_id_1', $fields); + $this->assertEquals('field_test-revision_id_1', $fields['field_test-revision_id_1']['id']); + $this->assertEquals('field_test-revision_id', $fields['field_test-revision_id_1']['field']); + $this->assertEquals('Replace: {{ field_test-revision_id_1 }}', $fields['field_test-revision_id_1']['alter']['text']); + + $this->assertArrayHasKey('field_test-revision_id_2', $fields); + $this->assertEquals('field_test-revision_id_2', $fields['field_test-revision_id_2']['id']); + $this->assertEquals('field_test-revision_id', $fields['field_test-revision_id_2']['field']); + $this->assertEquals('field_test-revision_id_2: {{ field_test-revision_id_2 }}', $fields['field_test-revision_id_2']['alter']['text']); + + $this->runUpdates(); + + $view = View::load('test_fix_revision_id_update'); + $data = $view->toArray(); + $fields = $data['display']['default']['display_options']['fields']; + + $this->assertArrayNotHasKey('field_test-revision_id_1', $fields); + $this->assertArrayHasKey('field_test__revision_id_1', $fields); + $this->assertEquals('field_test__revision_id_1', $fields['field_test__revision_id_1']['id']); + $this->assertEquals('field_test__revision_id', $fields['field_test__revision_id_1']['field']); + $this->assertEquals('Replace: {{ field_test__revision_id_1 }}', $fields['field_test__revision_id_1']['alter']['text']); + + $this->assertArrayNotHasKey('field_test-revision_id_2', $fields); + $this->assertArrayHasKey('field_test__revision_id_2', $fields); + $this->assertEquals('field_test__revision_id_2', $fields['field_test__revision_id_2']['id']); + $this->assertEquals('field_test__revision_id', $fields['field_test__revision_id_2']['field']); + $this->assertEquals('field_test-revision_id_2: {{ field_test__revision_id_2 }}', $fields['field_test__revision_id_2']['alter']['text']); + + } + +} diff --git a/core/modules/views/tests/src/Kernel/Handler/FieldFieldTest.php b/core/modules/views/tests/src/Kernel/Handler/FieldFieldTest.php index a60fb8cb42335e24e7c97680e263044c15a4eb08..d893234e4db3e70781c627d90cde99791dc92c1e 100644 --- a/core/modules/views/tests/src/Kernel/Handler/FieldFieldTest.php +++ b/core/modules/views/tests/src/Kernel/Handler/FieldFieldTest.php @@ -490,6 +490,19 @@ public function testRevisionRender() { $this->assertEquals('next entity value', $executable->getStyle()->getField(3, 'name')); } + /** + * Tests the token replacement for revision fields. + */ + public function testRevisionTokenRender() { + $view = Views::getView('test_field_field_revision_test'); + $this->executeView($view); + + $this->assertEquals('Replace: 1', $view->getStyle()->getField(0, 'field_test__revision_id_1')); + $this->assertEquals('Replace: 2', $view->getStyle()->getField(1, 'field_test__revision_id_1')); + $this->assertEquals('Replace: 3', $view->getStyle()->getField(2, 'field_test__revision_id_1')); + $this->assertEquals('Replace: 4', $view->getStyle()->getField(3, 'field_test__revision_id_1')); + } + /** * Tests the result set of a complex revision view. */ diff --git a/core/modules/views/views.post_update.php b/core/modules/views/views.post_update.php index e26db5c15b0b09d995a0d289024ebf154e7a91bd..60e024b6e161332c53df74497b2d6ef357decb75 100644 --- a/core/modules/views/views.post_update.php +++ b/core/modules/views/views.post_update.php @@ -81,3 +81,16 @@ function views_post_update_timestamp_formatter(array &$sandbox = NULL): void { return $view_config_updater->needsTimestampFormatterTimeDiffUpdate($view); }); } + +/** + * Fix '-revision_id' replacement token syntax. + */ +function views_post_update_fix_revision_id_part(&$sandbox = NULL): void { + /** @var \Drupal\views\ViewsConfigUpdater $view_config_updater */ + $view_config_updater = \Drupal::classResolver(ViewsConfigUpdater::class); + $view_config_updater->setDeprecationsEnabled(FALSE); + \Drupal::classResolver(ConfigEntityUpdater::class) + ->update($sandbox, 'view', function (ViewEntityInterface $view) use ($view_config_updater) { + return $view_config_updater->needsRevisionFieldHyphenFix($view); + }); +} diff --git a/core/modules/views/views.views.inc b/core/modules/views/views.views.inc index 2f88006d1a0dd5981733b02920b64bfae2ecc99f..e24be15b906eb288835dbd66cbe777796b0b85ce 100644 --- a/core/modules/views/views.views.inc +++ b/core/modules/views/views.views.inc @@ -520,7 +520,7 @@ function views_field_default_views_data(FieldStorageConfigInterface $field_stora } else { $group = t('@group (historical data)', ['@group' => $group_name]); - $field_alias = $field_name . '-revision_id'; + $field_alias = $field_name . '__revision_id'; } $data[$table_alias][$field_alias] = [ @@ -578,7 +578,7 @@ function views_field_default_views_data(FieldStorageConfigInterface $field_stora 'field_name' => $field_name, 'entity_type' => $entity_type_id, // Provide a real field for group by. - 'real field' => $field_alias . '_' . $real_field, + 'real field' => $field_name . '_' . $real_field, 'additional fields' => $add_fields, // Default the element type to div, let the UI change it if necessary. 'element type' => 'div',