diff --git a/core/modules/file/config/optional/views.view.files.yml b/core/modules/file/config/optional/views.view.files.yml index 8a9cdf0..5d83195 100644 --- a/core/modules/file/config/optional/views.view.files.yml +++ b/core/modules/file/config/optional/views.view.files.yml @@ -1019,7 +1019,7 @@ display: title_enable: false title: All title_enable: true - title: 'File usage information for %1' + title: 'File usage information for {{ arguments.fid }}' default_argument_type: fixed default_argument_options: argument: '' diff --git a/core/modules/node/config/optional/views.view.archive.yml b/core/modules/node/config/optional/views.view.archive.yml index 56ae50e..c30650b 100644 --- a/core/modules/node/config/optional/views.view.archive.yml +++ b/core/modules/node/config/optional/views.view.archive.yml @@ -87,7 +87,7 @@ display: exception: title_enable: true title_enable: true - title: '%1' + title: '{{ arguments.created_year_month }}' default_argument_type: fixed summary: sort_order: desc @@ -186,7 +186,7 @@ display: exception: title_enable: true title_enable: true - title: '%1' + title: '{{ arguments.created_year_month }}' default_argument_type: fixed summary: format: default_summary diff --git a/core/modules/taxonomy/config/optional/views.view.taxonomy_term.yml b/core/modules/taxonomy/config/optional/views.view.taxonomy_term.yml index e1086d1..7a32583 100644 --- a/core/modules/taxonomy/config/optional/views.view.taxonomy_term.yml +++ b/core/modules/taxonomy/config/optional/views.view.taxonomy_term.yml @@ -105,7 +105,7 @@ display: title_enable: false title: All title_enable: true - title: '%1' + title: '{{ arguments.tid }}' default_argument_type: fixed default_argument_options: argument: '' @@ -231,7 +231,7 @@ display: admin_label: '' empty: true tokenize: true - target: '!1' + target: '{{ raw_arguments.tid }}' view_mode: full bypass_access: false plugin_id: entity diff --git a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_roles_rid.yml b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_roles_rid.yml index 71c0a4d..65f2fe3 100644 --- a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_roles_rid.yml +++ b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_roles_rid.yml @@ -169,7 +169,7 @@ display: title_enable: false title: All title_enable: true - title: '%1' + title: '{{ arguments.roles_target_id }}' default_argument_type: fixed default_argument_options: argument: '' diff --git a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_uid_argument.yml b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_uid_argument.yml index fefcc42..4ff201c 100644 --- a/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_uid_argument.yml +++ b/core/modules/user/tests/modules/user_test_views/test_views/views.view.test_user_uid_argument.yml @@ -28,7 +28,7 @@ display: table: users_field_data field: uid title_enable: true - title: '%1' + title: '{{ arguments.uid }}' plugin_id: user_uid entity_type: user entity_field: uid diff --git a/core/modules/views/src/Plugin/views/PluginBase.php b/core/modules/views/src/Plugin/views/PluginBase.php index 49b7f1d..81ac493 100644 --- a/core/modules/views/src/Plugin/views/PluginBase.php +++ b/core/modules/views/src/Plugin/views/PluginBase.php @@ -337,10 +337,6 @@ public function globalTokenReplace($string = '', array $options = array()) { * Replaces Views' tokens in a given string. The resulting string will be * sanitized with Xss::filterAdmin. * - * This used to be a simple strtr() scattered throughout the code. Some Views - * tokens, such as arguments (e.g.: %1 or !1), still use the old format so we - * handle those as well as the new Twig-based tokens (e.g.: {{ field_name }}) - * * @param $text * Unsanitized string with possible tokens. * @param $tokens @@ -357,30 +353,39 @@ protected function viewsTokenReplace($text, $tokens) { return Xss::filterAdmin($text); } - // Separate Twig tokens from other tokens (e.g.: contextual filter tokens in - // the form of %1). $twig_tokens = array(); - $other_tokens = array(); foreach ($tokens as $token => $replacement) { + // Twig wants a token replacement array stripped of curly-brackets. + // Some Views tokens come with curly-braces, others do not. + //@todo: https://www.drupal.org/node/2544392 if (strpos($token, '{{') !== FALSE) { // Twig wants a token replacement array stripped of curly-brackets. - $token = trim(str_replace(array('{', '}'), '', $token)); + $token = trim(str_replace(['{{', '}}'], '', $token)); + } + // Check for arrays in Twig tokens. Internally these are passed as + // dot-delimited strings, but need to be turned into associative arrays + // for parsing. + if (strpos($token, '.') === FALSE) { // We need to validate tokens are valid Twig variables. Twig uses the // same variable naming rules as PHP. // @see http://php.net/manual/en/language.variables.basics.php assert('preg_match(\'/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/\', $token) === 1', 'Tokens need to be valid Twig variables.'); - $twig_tokens[$token] = $replacement; } else { - $other_tokens[$token] = $replacement; + $parts = explode('.', $token); + $top = array_shift($parts); + assert('preg_match(\'/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/\', $top) === 1', 'Tokens need to be valid Twig variables.'); + $token_array = array(array_pop($parts) => $replacement); + foreach(array_reverse($parts) as $key) { + assert('preg_match(\'/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/\', $key) === 1', 'Tokens need to be valid Twig variables.'); + $token_array = array($key => $token_array); + } + $twig_tokens[$top] = $token_array; } } - // Non-Twig tokens are a straight string replacement, Twig tokens get run - // through an inline template for rendering and replacement. - $text = strtr($text, $other_tokens); if ($twig_tokens) { // Use the unfiltered text for the Twig template, then filter the output. // Otherwise, Xss::filterAdmin could remove valid Twig syntax before the @@ -398,9 +403,6 @@ function ($children, $elements) { return (string) $this->getRenderer()->render($build); } - else { - return $text; - } } /** diff --git a/core/modules/views/src/Plugin/views/area/Entity.php b/core/modules/views/src/Plugin/views/area/Entity.php index df5e25e..3234f88 100644 --- a/core/modules/views/src/Plugin/views/area/Entity.php +++ b/core/modules/views/src/Plugin/views/area/Entity.php @@ -113,7 +113,7 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) { // display the entity ID to the admin form user. // @todo Use a method to check for tokens in // https://www.drupal.org/node/2396607. - if (strpos($this->options['target'], '{{') === FALSE && strpos($this->options['target'], '!') === FALSE && strpos($this->options['target'], '%') === FALSE && strpos($this->options['target'], '[') === FALSE) { + if (strpos($this->options['target'], '{{') === FALSE) { // @todo If the entity does not exist, this will will show the config // target identifier. Decide if this is the correct behavior in // https://www.drupal.org/node/2415391. @@ -146,7 +146,7 @@ public function submitOptionsForm(&$form, FormStateInterface $form_state) { // @todo Use a method to check for tokens in // https://www.drupal.org/node/2396607. $options = $form_state->getValue('options'); - if (strpos($options['target'], '{{') === FALSE && strpos($options['target'], '!') === FALSE && strpos($options['target'], '%') === FALSE && strpos($options['target'], '[') === FALSE) { + if (strpos($options['target'], '{{') === FALSE) { if ($entity = $this->entityManager->getStorage($this->entityType)->load($options['target'])) { $options['target'] = $entity->getConfigTarget(); } @@ -161,7 +161,7 @@ public function render($empty = FALSE) { if (!$empty || !empty($this->options['empty'])) { // @todo Use a method to check for tokens in // https://www.drupal.org/node/2396607. - if (strpos($this->options['target'], '{{') !== FALSE || strpos($this->options['target'], '!') !== FALSE || strpos($this->options['target'], '%') !== FALSE || strpos($this->options['target'], '[') !== FALSE) { + if (strpos($this->options['target'], '{{') !== FALSE) { $target_id = $this->tokenizeValue($this->options['target']); if ($entity = $this->entityManager->getStorage($this->entityType)->load($target_id)) { $target_entity = $entity; @@ -190,7 +190,7 @@ public function calculateDependencies() { // Ensure that we don't add dependencies for placeholders. // @todo Use a method to check for tokens in // https://www.drupal.org/node/2396607. - if (strpos($this->options['target'], '{{') === FALSE && strpos($this->options['target'], '!') === FALSE && strpos($this->options['target'], '%') === FALSE && strpos($this->options['target'], '[') === FALSE) { + if (strpos($this->options['target'], '{{') === FALSE) { if ($entity = $this->entityManager->loadEntityByConfigTarget($this->entityType, $this->options['target'])) { $dependencies[$this->entityManager->getDefinition($this->entityType)->getConfigDependencyKey()][] = $entity->getConfigDependencyName(); } diff --git a/core/modules/views/src/Plugin/views/area/TokenizeAreaPluginBase.php b/core/modules/views/src/Plugin/views/area/TokenizeAreaPluginBase.php index 82bb51d..18a9bd5 100644 --- a/core/modules/views/src/Plugin/views/area/TokenizeAreaPluginBase.php +++ b/core/modules/views/src/Plugin/views/area/TokenizeAreaPluginBase.php @@ -53,13 +53,12 @@ public function tokenForm(&$form, FormStateInterface $form_state) { // Get a list of the available fields and arguments for token replacement. $options = array(); foreach ($this->view->display_handler->getHandlers('field') as $field => $handler) { - $options[t('Fields')]["[$field]"] = $handler->adminLabel(); + $options[t('Fields')]["{{ $field }}"] = $handler->adminLabel(); } - $count = 0; // This lets us prepare the key as we want it printed. - foreach ($this->view->display_handler->getHandlers('argument') as $handler) { - $options[t('Arguments')]['%' . ++$count] = $this->t('@argument title', array('@argument' => $handler->adminLabel())); - $options[t('Arguments')]['!' . $count] = $this->t('@argument input', array('@argument' => $handler->adminLabel())); + foreach ($this->view->display_handler->getHandlers('argument') as $arg => $handler) { + $options[t('Arguments')]["{{ arguments.$arg }}"] = $this->t('@argument title', array('@argument' => $handler->adminLabel())); + $options[t('Arguments')]["{{ raw_arguments.$arg }}"] = $this->t('@argument input', array('@argument' => $handler->adminLabel())); } if (!empty($options)) { @@ -75,7 +74,7 @@ public function tokenForm(&$form, FormStateInterface $form_state) { ), ); $form['tokens']['help'] = array( - '#markup' => '

' . $this->t('The following tokens are available. If you would like to have the characters \'[\' and \']\' use the HTML entity codes \'%5B\' or \'%5D\' or they will get replaced with empty space.') . '

', + '#markup' => '

' . $this->t('The following tokens are available. You may use Twig syntax in this field.') . '

', ); foreach (array_keys($options) as $type) { if (!empty($options[$type])) { diff --git a/core/modules/views/src/Plugin/views/argument/ArgumentPluginBase.php b/core/modules/views/src/Plugin/views/argument/ArgumentPluginBase.php index a1f1c5e..96c8aea 100644 --- a/core/modules/views/src/Plugin/views/argument/ArgumentPluginBase.php +++ b/core/modules/views/src/Plugin/views/argument/ArgumentPluginBase.php @@ -211,7 +211,7 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) { '#title_display' => 'invisible', '#size' => 20, '#default_value' => $this->options['exception']['title'], - '#description' => $this->t('Override the view and other argument titles. Use "%1" for the first argument, "%2" for the second, etc.'), + '#description' => $this->t('Override the view and other argument titles. Use may use Twig syntax in this field. Use "argument.1" for the first argument, "argument.2" for the second, etc.'), '#states' => array( 'visible' => array( ':input[name="options[exception][title_enable]"]' => array('checked' => TRUE), @@ -249,7 +249,8 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) { '#title' => $this->t('Provide title'), '#title_display' => 'invisible', '#default_value' => $this->options['title'], - '#description' => $this->t('Override the view and other argument titles. Use "%1" for the first argument, "%2" for the second, etc.'), + '#description' => $this->t('Override the view and other argument titles. You may use Twig syntax in this field.'), + // @todo: Need to show available tokens. '#states' => array( 'visible' => array( ':input[name="options[title_enable]"]' => array('checked' => TRUE), diff --git a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php index a049afc..880136a 100644 --- a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php @@ -1725,17 +1725,16 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) { ); $options = array(); - $count = 0; // This lets us prepare the key as we want it printed. - foreach ($this->view->display_handler->getHandlers('argument') as $handler) { - $options[t('Arguments')]['%' . ++$count] = $this->t('@argument title', array('@argument' => $handler->adminLabel())); - $options[t('Arguments')]['!' . $count] = $this->t('@argument input', array('@argument' => $handler->adminLabel())); + foreach ($this->view->display_handler->getHandlers('argument') as $arg => $handler) { + $options[t('Arguments')]["{{ arguments.$arg }}"] = $this->t('@argument title', array('@argument' => $handler->adminLabel())); + $options[t('Arguments')]["{{ raw_arguments.$arg }}"] = $this->t('@argument input', array('@argument' => $handler->adminLabel())); } // Default text. // We have some options, so make a list. $output = ''; if (!empty($options)) { - $output = $this->t('

The following tokens are available for this link.

'); + $output = $this->t('

The following tokens are available for this link. You may use Twig syntax in this field.

'); foreach (array_keys($options) as $type) { if (!empty($options[$type])) { $items = array(); diff --git a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php index 152d4e9..0a86fc9 100644 --- a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php +++ b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php @@ -333,7 +333,7 @@ public function elementClasses($row_index = NULL) { * {@inheritdoc} */ public function tokenizeValue($value, $row_index = NULL) { - if (strpos($value, '{{') !== FALSE || strpos($value, '!') !== FALSE || strpos($value, '%') !== FALSE) { + if (strpos($value, '{{') !== FALSE) { $fake_item = array( 'alter_text' => TRUE, 'text' => $value, @@ -868,10 +868,9 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) { // Add the field to the list of options. $options[t('Fields')]["{{ {$this->options['id']} }}"] = substr(strrchr($this->adminLabel(), ":"), 2 ); - $count = 0; // This lets us prepare the key as we want it printed. foreach ($this->view->display_handler->getHandlers('argument') as $arg => $handler) { - $options[t('Arguments')]['%' . ++$count] = $this->t('@argument title', array('@argument' => $handler->adminLabel())); - $options[t('Arguments')]['!' . $count] = $this->t('@argument input', array('@argument' => $handler->adminLabel())); + $options[t('Arguments')]["{{ arguments.$arg }}"] = $this->t('@argument title', array('@argument' => $handler->adminLabel())); + $options[t('Arguments')]["{{ raw_arguments.$arg }}"] = $this->t('@argument input', array('@argument' => $handler->adminLabel())); } $this->documentSelfTokens($options[t('Fields')]); @@ -1557,7 +1556,7 @@ public function getRenderTokens($item) { } $count = 0; foreach ($this->displayHandler->getHandlers('argument') as $arg => $handler) { - $token = '%' . ++$count; + $token = "{{ arguments.$arg }}"; if (!isset($tokens[$token])) { $tokens[$token] = ''; } @@ -1565,7 +1564,8 @@ public function getRenderTokens($item) { // Use strip tags as there should never be HTML in the path. // However, we need to preserve special characters like " that // were removed by SafeMarkup::checkPlain(). - $tokens['!' . $count] = isset($this->view->args[$count - 1]) ? strip_tags(Html::decodeEntities($this->view->args[$count - 1])) : ''; + $tokens["{{ raw_arguments.$arg }}"] = isset($this->view->args[$count]) ? strip_tags(Html::decodeEntities($this->view->args[$count])) : ''; + $count++; } // Get flattened set of tokens for any array depth in query parameters. @@ -1661,8 +1661,8 @@ protected function getTokenValuesRecursive(array $array, array $parent_keys = ar } else { // Create a token key based on array element structure. - $token_string = !empty($parent_keys) ? implode('_', $parent_keys) . '_' . $param : $param; - $tokens['%' . $token_string] = strip_tags(Html::decodeEntities($val)); + $token_string = !empty($parent_keys) ? implode('.', $parent_keys) . '.' . $param : $param; + $tokens['{{ arguments.' . $token_string . ' }}'] = strip_tags(Html::decodeEntities($val)); } } diff --git a/core/modules/views/src/Plugin/views/style/StylePluginBase.php b/core/modules/views/src/Plugin/views/style/StylePluginBase.php index ccfd1ba..de927cc 100644 --- a/core/modules/views/src/Plugin/views/style/StylePluginBase.php +++ b/core/modules/views/src/Plugin/views/style/StylePluginBase.php @@ -194,7 +194,7 @@ function usesFields() { public function usesTokens() { if ($this->usesRowClass()) { $class = $this->options['row_class']; - if (strpos($class, '{{') !== FALSE || strpos($class, '!') !== FALSE || strpos($class, '%') !== FALSE) { + if (strpos($class, '{{') !== FALSE) { return TRUE; } } @@ -231,7 +231,7 @@ public function getRowClass($row_index) { * Take a value and apply token replacement logic to it. */ public function tokenizeValue($value, $row_index) { - if (strpos($value, '{{') !== FALSE || strpos($value, '!') !== FALSE || strpos($value, '%') !== FALSE) { + if (strpos($value, '{{') !== FALSE) { // Row tokens might be empty, for example for node row style. $tokens = isset($this->rowTokens[$row_index]) ? $this->rowTokens[$row_index] : array(); if (!empty($this->view->build_info['substitutions'])) { diff --git a/core/modules/views/src/Tests/Handler/FieldKernelTest.php b/core/modules/views/src/Tests/Handler/FieldKernelTest.php index 70940d0..1265d66 100644 --- a/core/modules/views/src/Tests/Handler/FieldKernelTest.php +++ b/core/modules/views/src/Tests/Handler/FieldKernelTest.php @@ -27,7 +27,7 @@ class FieldKernelTest extends ViewKernelTestBase { * * @var array */ - public static $testViews = array('test_view', 'test_field_tokens', 'test_field_output'); + public static $testViews = array('test_view', 'test_field_tokens', 'test_field_argument_tokens', 'test_field_output'); /** * Map column names. @@ -174,6 +174,30 @@ public function testRewrite() { } /** + * Tests the arguments tokens on field level. + */ + public function testArgumentTokens() { + /** @var \Drupal\Core\Render\RendererInterface $renderer */ + $renderer = \Drupal::service('renderer'); + + $view = Views::getView('test_field_argument_tokens'); + $this->executeView($view, ['{{ { "#pre_render": ["views_test_data_test_pre_render_function"]} }}']); + + $name_field_0 = $view->field['name']; + + $name_field_0->options['alter']['alter_text'] = TRUE; + $name_field_0->options['alter']['text'] = '%1 !1'; + + $row = $view->result[0]; + $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field_0, $row) { + return $name_field_0->advancedRender($row); + }); + + $this->assertNotEqual('Muh', (string) $output); + $this->assertEqual('', (string) $output, 'Ensure that the preprocess function is not executed.'); + } + + /** * Tests the field tokens, row level and field level. */ public function testFieldTokens() { diff --git a/core/modules/views/src/ViewExecutable.php b/core/modules/views/src/ViewExecutable.php index 2a8145c..3638c9c 100644 --- a/core/modules/views/src/ViewExecutable.php +++ b/core/modules/views/src/ViewExecutable.php @@ -1028,8 +1028,8 @@ protected function _buildArguments() { } // Add this argument's substitution - $substitutions['%' . ($position + 1)] = $arg_title; - $substitutions['!' . ($position + 1)] = strip_tags(Html::decodeEntities($arg)); + $substitutions["{{ arguments.$id }}"] = $arg_title; + $substitutions["{{ raw_arguments.$id }}"] = strip_tags(Html::decodeEntities($arg)); // Test to see if we should use this argument's title if (!empty($argument->options['title_enable']) && !empty($argument->options['title'])) { diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_area.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_area.yml index de5b615..336b043 100644 --- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_area.yml +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_entity_area.yml @@ -31,7 +31,7 @@ display: field: entity_entity_test id: entity_entity_test table: views - target: '!1' + target: '{{ raw_arguments.id }}' view_mode: full plugin_id: entity entity_block: diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_argument_tokens.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_argument_tokens.yml new file mode 100644 index 0000000..800beaa --- /dev/null +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_argument_tokens.yml @@ -0,0 +1,59 @@ +langcode: en +status: true +dependencies: { } +id: test_field_argument_tokens +label: null +module: views +description: '' +tag: '' +base_table: views_test_data +base_field: nid +core: '8' +display: + default: + display_options: + access: + type: none + cache: + type: tag + exposed_form: + type: basic + pager: + type: full + query: + type: views_query + fields: + name: + id: name + table: views_test_data + field: name + plugin_id: string + name_1: + id: name_1 + table: views_test_data + field: name + plugin_id: string + name_2: + id: name_2 + table: views_test_data + field: name + plugin_id: string + job: + id: job + table: views_test_data + field: job + plugin_id: string + arguments: + null: + id: null + table: views + field: null + plugin_id: ull + style: + type: default + row: + type: fields + display_plugin: default + display_title: Defaults + id: default + position: 0 diff --git a/core/modules/views/tests/modules/views_test_data/src/Plugin/views/field/FieldTest.php b/core/modules/views/tests/modules/views_test_data/src/Plugin/views/field/FieldTest.php index 0f8feef..5da095a 100644 --- a/core/modules/views/tests/modules/views_test_data/src/Plugin/views/field/FieldTest.php +++ b/core/modules/views/tests/modules/views_test_data/src/Plugin/views/field/FieldTest.php @@ -46,7 +46,7 @@ public function getTestValue() { * Overrides Drupal\views\Plugin\views\field\FieldPluginBase::addSelfTokens(). */ protected function addSelfTokens(&$tokens, $item) { - $tokens['[test__token]'] = $this->getTestValue(); + $tokens['{{ test_token }}'] = $this->getTestValue(); } /** diff --git a/core/modules/views/tests/modules/views_test_data/views_test_data.module b/core/modules/views/tests/modules/views_test_data/views_test_data.module index 1af309c..4a2c1ff 100644 --- a/core/modules/views/tests/modules/views_test_data/views_test_data.module +++ b/core/modules/views/tests/modules/views_test_data/views_test_data.module @@ -110,3 +110,8 @@ function template_preprocess_views_view_mapping_test(&$variables) { } } } + +function views_test_data_test_pre_render_function($element) { + $element['#markup'] = 'Muh'; + return $element; +} diff --git a/core/modules/views/tests/src/Unit/Plugin/area/EntityTest.php b/core/modules/views/tests/src/Unit/Plugin/area/EntityTest.php index bb89281..322ea02 100644 --- a/core/modules/views/tests/src/Unit/Plugin/area/EntityTest.php +++ b/core/modules/views/tests/src/Unit/Plugin/area/EntityTest.php @@ -130,10 +130,10 @@ protected function setupEntityManager() { */ public function providerTestTokens() { return [ - ['!1', 5], - ['%2', 6], + ['{{ arguments.test1 }}', 5], + ['{{ raw_arguments.test2 }}', 6], ['{{ test_render_token }}', 7], - ['[test:global_token]', 8], + ['{{ test:global_token }}', 8], ]; } diff --git a/core/modules/views/views.install b/core/modules/views/views.install index 5615747..1e1498f 100644 --- a/core/modules/views/views.install +++ b/core/modules/views/views.install @@ -117,5 +117,136 @@ function views_update_8001(&$sandbox) { } /** + * Updates %1 and !1 tokens to argument tokens. + */ +function views_update_8002() { + $config_factory = \Drupal::configFactory(); + foreach ($config_factory->listAll('views.view.') as $view_config_name) { + $view = $config_factory->getEditable($view_config_name); + $displays = $view->get('display'); + + $changed = FALSE; + + // Update all the field settings, which support tokens. + foreach ($displays as $display_name => &$display) { + if (!empty($display['display_options']['fields'])) { + foreach ($display['display_options']['fields'] as $field_name => &$field) { + $token_values = ['path', 'alt', 'link_class', 'rel', 'target', 'query', 'fragment', 'prefix', 'suffix', 'more_link_text', 'more_link_path', 'link_attributes']; + + foreach ($token_values as $token_name) { + if (isset($field['alter'][$token_name])) { + if (is_array($field['alter'][$token_name])) { + foreach (array_keys($field['alter'][$token_name]) as $key) { + $field['alter'][$token_name][$key] = _views_update_8002_token_update($field['alter'][$token_name][$key]); + } + } + else { + $field['alter'][$token_name] = _views_update_8002_token_update($field['alter'][$token_name]); + } + $changed = TRUE; + } + } + } + } + } + + // Update the area handlers with tokens. + foreach ($displays as $display_name => &$display) { + $area_types = ['header', 'footer', 'empty']; + foreach ($area_types as $area_type) { + if (!empty($display['display_options'][$area_type])) { + foreach ($display['display_options'][$area_type] as &$area) { + switch ($area['plugin_id']) { + case 'title': + $area['title'] = _views_update_8002_token_update($area['title']); + break; + case 'result': + $area['content'] = _views_update_8002_token_update($area['content']); + break; + case 'text': + $area['content']['value'] = _views_update_8002_token_update($area['content']['value']); + break; + case 'text_custom': + $area['content'] = _views_update_8002_token_update($area['content']); + break; + } + } + $changed = TRUE; + } + } + } + + // Update the argument title settings. + foreach ($displays as $display_name => &$display) { + if (!empty($display['display_options']['arguments'])) { + foreach ($display['display_options']['arguments'] as &$argument) { + if (isset($argument['exception']['title'])) { + $argument['exception']['title'] = _views_update_8002_token_update($argument['exception']['title']); + } + if (isset($argument['title'])) { + $argument['title'] = _views_update_8002_token_update($argument['title']); + } + } + } + } + + // Update the display title settings. + // Update the more link text and more link URL. + foreach ($displays as $display_name => &$display) { + if (!empty($display['display_options']['title'])) { + $display['display_options']['title'] = _views_update_8002_token_update($display['display_options']['title']); + } + if (!empty($display['display_options']['use_more_text'])) { + $display['display_options']['use_more_text'] = _views_update_8002_token_update($display['display_options']['use_more_text']); + } + if (!empty($display['display_options']['link_url'])) { + $display['display_options']['link_url'] = _views_update_8002_token_update($display['display_options']['link_url']); + } + } + + // Update custom classes for row class + grid classes. + // Update RSS description field. + foreach ($displays as $display_name => &$display) { + if (!empty($display['display_options']['style'])) { + if (!empty($display['display_options']['style']['row_class'])) { + $display['display_options']['style']['row_class'] = _views_update_8002_token_update($display['display_options']['style']['row_class']); + } + if (!empty($display['display_options']['style']['col_class_custom'])) { + $display['display_options']['style']['col_class_custom'] = _views_update_8002_token_update($display['display_options']['style']['col_class_custom']); + } + if (!empty($display['display_options']['style']['row_class_custom'])) { + $display['display_options']['style']['row_class_custom'] = _views_update_8002_token_update($display['display_options']['style']['row_class_custom']); + } + if (!empty($display['display_options']['style']['description'])) { + $display['display_options']['style']['description'] = _views_update_8002_token_update($display['display_options']['style']['description']); + } + } + } + + + if ($changed) { + $view->save(TRUE); + } + } +} + +/** + * Updates a views configuration string from using %/! to twig tokens. + * + * @param string $text + * Text in which to search for argument tokens and replace them with their + * twig representation. + * + * @return string + * The updated value. + */ +function _views_update_8002_token_update($text) { + preg_replace_callback('/%(\d)/', function ($arg1) { return "{{ arguments.{$arg1[1]} }}"; }, $text); + preg_replace_callback('/!(\d)/', function ($arg1) { return "{{ raw_arguments.{$arg1[1]} }}"; }, $text); + + return $text; +} + +/** * @} End of "addtogroup updates-8.0.0-beta". */ diff --git a/core/modules/views_ui/src/Tests/XssTest.php b/core/modules/views_ui/src/Tests/XssTest.php index a847434..33a7fa6 100644 --- a/core/modules/views_ui/src/Tests/XssTest.php +++ b/core/modules/views_ui/src/Tests/XssTest.php @@ -29,8 +29,8 @@ public function testViewsUi() { $this->assertEscaped('test', 'Field admin label is properly escaped.'); $this->drupalGet('admin/structure/views/nojs/handler/sa_contrib_2013_035/page_1/header/area'); - $this->assertRaw('[title] == <marquee>test</marquee>', 'Token label is properly escaped.'); - $this->assertRaw('[title_1] == <script>alert("XSS")</script>', 'Token label is properly escaped.'); + $this->assertRaw('{{ title }} == <marquee>test</marquee>', 'Token label is properly escaped.'); + $this->assertRaw('{{ title_1 }} == <script>alert("XSS")</script>', 'Token label is properly escaped.'); } }