Skip to content
Snippets Groups Projects
Verified Commit 246d721a authored by Lee Rowlands's avatar Lee Rowlands
Browse files

Issue #2831233 by mradcliffe, philltran, vsujeetkumar, selwynpolit,...

Issue #2831233 by mradcliffe, philltran, vsujeetkumar, selwynpolit, s.messaris, cpierce42, acbramley, Lendude, Nitin shrivastava, jibran, mohit_aghera, mike.roman, Kumar Kundan, adityasingh, _utsavsharma, jedihe, Munavijayalakshmi, KapilV, efpapado, anushrikumari, kalyansamanta, ameymudras, interX, nishantghetiya, gambry, alexpott, larowlan, antojose, catch, Nicolas Bouteille, nicoloye, Kristen Pol, bygeoffthompson: Field tokens for "historical data" fields (revisions) contain a hyphen, breaking twig templates and throwing an assertion error
parent e1f56ada
No related branches found
No related tags found
No related merge requests found
......@@ -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);
});
}
}
<?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();
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
......@@ -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
......
<?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']);
}
}
......@@ -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.
*/
......
......@@ -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);
});
}
......@@ -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',
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment