Commit 9aec8275 authored by alexpott's avatar alexpott

Issue #2492839 by joelpittet, mikeker, dawehner, lauriii, plach, Fabianx,...

Issue #2492839 by joelpittet, mikeker, dawehner, lauriii, plach, Fabianx, olli: Views replacement token bc layer allows for Twig template injection via arguments
parent b29148a7
......@@ -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: ''
......
......@@ -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
......
......@@ -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
......
......@@ -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: ''
......
......@@ -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
......
......@@ -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,34 +353,44 @@ 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
// template is parsed.
$build = array(
'#type' => 'inline_template',
'#template' => $text,
......@@ -396,10 +402,14 @@ function ($children, $elements) {
],
);
return (string) $this->getRenderer()->render($build);
// Currently you cannot attach assets to tokens with
// Renderer::renderPlain(). This may be unnecessarily limiting. Consider
// using Renderer::executeInRenderContext() instead.
// @todo: https://www.drupal.org/node/2566621
return (string) $this->getRenderer()->renderPlain($build);
}
else {
return $text;
return Xss::filterAdmin($text);
}
}
......
......@@ -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();
}
......
......@@ -57,13 +57,12 @@ public function tokenForm(&$form, FormStateInterface $form_state) {
$optgroup_arguments = (string) t('Arguments');
$optgroup_fields = (string) t('Fields');
foreach ($this->view->display_handler->getHandlers('field') as $field => $handler) {
$options[$optgroup_fields]["[$field]"] = $handler->adminLabel();
$options[$optgroup_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[$optgroup_arguments]['%' . ++$count] = $this->t('@argument title', array('@argument' => $handler->adminLabel()));
$options[$optgroup_arguments]['!' . $count] = $this->t('@argument input', array('@argument' => $handler->adminLabel()));
foreach ($this->view->display_handler->getHandlers('argument') as $arg => $handler) {
$options[$optgroup_arguments]["{{ arguments.$arg }}"] = $this->t('@argument title', array('@argument' => $handler->adminLabel()));
$options[$optgroup_arguments]["{{ raw_arguments.$arg }}"] = $this->t('@argument input', array('@argument' => $handler->adminLabel()));
}
if (!empty($options)) {
......@@ -79,7 +78,7 @@ public function tokenForm(&$form, FormStateInterface $form_state) {
),
);
$form['tokens']['help'] = array(
'#markup' => '<p>' . $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.') . '</p>',
'#markup' => '<p>' . $this->t('The following tokens are available. You may use Twig syntax in this field.') . '</p>',
);
foreach (array_keys($options) as $type) {
if (!empty($options[$type])) {
......
......@@ -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. You may use Twig syntax in this field as well as the "arguments" and "raw_arguments" arrays.'),
'#states' => array(
'visible' => array(
':input[name="options[exception][title_enable]"]' => array('checked' => TRUE),
......@@ -249,7 +249,7 @@ 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.'),
'#states' => array(
'visible' => array(
':input[name="options[title_enable]"]' => array('checked' => TRUE),
......@@ -258,6 +258,23 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
'#fieldset' => 'argument_present',
);
$output = $this->getTokenHelp();
$form['token_help'] = [
'#type' => 'details',
'#title' => $this->t('Replacement patterns'),
'#value' => $output,
'#states' => [
'visible' => [
[
':input[name="options[title_enable]"]' => ['checked' => TRUE],
],
[
':input[name="options[exception][title_enable]"]' => ['checked' => TRUE],
],
],
],
];
$form['specify_validation'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Specify validation criteria'),
......@@ -348,6 +365,45 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
);
}
/**
* Provide token help information for the argument.
*
* @return array
* A render array.
*/
protected function getTokenHelp() {
$output = [];
foreach ($this->view->display_handler->getHandlers('argument') as $arg => $handler) {
/** @var \Drupal\views\Plugin\views\argument\ArgumentPluginBase $handler */
$options[(string) t('Arguments')]["{{ arguments.$arg }}"] = $this->t('@argument title', array('@argument' => $handler->adminLabel()));
$options[(string) t('Arguments')]["{{ raw_arguments.$arg }}"] = $this->t('@argument input', array('@argument' => $handler->adminLabel()));
}
// We have some options, so make a list.
if (!empty($options)) {
$output[] = [
'#markup' => '<p>' . $this->t("The following replacement tokens are available for this argument.") . '</p>',
];
foreach (array_keys($options) as $type) {
if (!empty($options[$type])) {
$items = array();
foreach ($options[$type] as $key => $value) {
$items[] = $key . ' == ' . $value;
}
$item_list = array(
'#theme' => 'item_list',
'#items' => $items,
);
$output[] = $item_list;
}
}
}
return $output;
}
public function validateOptionsForm(&$form, FormStateInterface $form_state) {
$option_values = &$form_state->getValue('options');
if (empty($option_values)) {
......
......@@ -1725,21 +1725,27 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
);
$options = array();
$count = 0; // This lets us prepare the key as we want it printed.
// We cast the optgroup label to string as array keys must not be
// objects and t() may return a TranslationWrapper once issue #2557113
// lands.
$optgroup_arguments = (string) t('Arguments');
foreach ($this->view->display_handler->getHandlers('argument') as $handler) {
$options[$optgroup_arguments]['%' . ++$count] = $this->t('@argument title', array('@argument' => $handler->adminLabel()));
$options[$optgroup_arguments]['!' . $count] = $this->t('@argument input', array('@argument' => $handler->adminLabel()));
foreach ($this->view->display_handler->getHandlers('argument') as $arg => $handler) {
$options[$optgroup_arguments]["{{ arguments.$arg }}"] = $this->t('@argument title', array('@argument' => $handler->adminLabel()));
$options[$optgroup_arguments]["{{ raw_arguments.$arg }}"] = $this->t('@argument input', array('@argument' => $handler->adminLabel()));
}
// Default text.
// We have some options, so make a list.
$output = '';
$description = [];
$description[] = [
'#markup' => $this->t('A Drupal path or external URL the more link will point to. Note that this will override the link display setting above.'),
];
if (!empty($options)) {
$output = $this->t('<p>The following tokens are available for this link.</p>');
$description[] = [
'#prefix' => '<p>',
'#markup' => $this->t('The following tokens are available for this link. You may use Twig syntax in this field.'),
'#suffix' => '</p>',
];
foreach (array_keys($options) as $type) {
if (!empty($options[$type])) {
$items = array();
......@@ -1749,9 +1755,8 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
$item_list = array(
'#theme' => 'item_list',
'#items' => $items,
'#list_type' => $type,
);
$output .= drupal_render($item_list);
$description[] = $item_list;
}
}
}
......@@ -1760,7 +1765,7 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
'#type' => 'textfield',
'#title' => $this->t('Custom URL'),
'#default_value' => $this->getOption('link_url'),
'#description' => $this->t('A Drupal path or external URL the more link will point to. Note that this will override the link display setting above.') . $output,
'#description' => $description,
'#states' => array(
'visible' => array(
':input[name="link_display"]' => array('value' => 'custom_url'),
......
......@@ -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,
......@@ -872,10 +872,9 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
// Add the field to the list of options.
$options[$optgroup_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[$optgroup_arguments]['%' . ++$count] = $this->t('@argument title', array('@argument' => $handler->adminLabel()));
$options[$optgroup_arguments]['!' . $count] = $this->t('@argument input', array('@argument' => $handler->adminLabel()));
$options[$optgroup_arguments]["{{ arguments.$arg }}"] = $this->t('@argument title', array('@argument' => $handler->adminLabel()));
$options[$optgroup_arguments]["{{ raw_arguments.$arg }}"] = $this->t('@argument input', array('@argument' => $handler->adminLabel()));
}
$this->documentSelfTokens($options[$optgroup_fields]);
......@@ -900,7 +899,6 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
$item_list = array(
'#theme' => 'item_list',
'#items' => $items,
'#list_type' => $type,
);
$output[] = $item_list;
}
......@@ -1561,7 +1559,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] = '';
}
......@@ -1569,7 +1567,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.
......@@ -1665,8 +1664,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));
}
}
......
......@@ -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'])) {
......
......@@ -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.
......@@ -173,6 +173,44 @@ public function testRewrite() {
$this->assertSubString($output, $random_text);
}
/**
* 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'];
// Test the old style tokens.
$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->assertFalse(strpos((string) $output, 'views_test_data_test_pre_render_function executed') !== FALSE, 'Ensure that the pre_render function was not executed');
$this->assertEqual('%1 !1', (string) $output, "Ensure that old style placeholders aren't replaced");
// This time use new style tokens but ensure that we still don't allow
// arbitrary code execution.
$name_field_0->options['alter']['alter_text'] = TRUE;
$name_field_0->options['alter']['text'] = '{{ arguments.null }} {{ raw_arguments.null }}';
$row = $view->result[0];
$output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field_0, $row) {
return $name_field_0->advancedRender($row);
});
$this->assertFalse(strpos((string) $output, 'views_test_data_test_pre_render_function executed') !== FALSE, 'Ensure that the pre_render function was not executed');
$this->assertEqual('{{ { &quot;#pre_render&quot;: [&quot;views_test_data_test_pre_render_function&quot;]} }} {{ { &quot;#pre_render&quot;: [&quot;views_test_data_test_pre_render_function&quot;]} }}', (string) $output, 'Ensure that new style placeholders are replaced');
}
/**
* Tests the field tokens, row level and field level.
*/
......
......@@ -43,6 +43,16 @@ public function testViewsTokenReplace() {
$this->assertIdentical($result, 'en means English');
}
/**
* Tests viewsTokenReplace without any twig tokens.
*/
public function testViewsTokenReplaceWithTwigTokens() {
$text = 'Just some text';
$tokens = [];
$result = $this->testPluginBase->viewsTokenReplace($text, $tokens);
$this->assertIdentical($result, 'Just some text');
}
}
/**
......
<?php
/**
* @file
* Contains \Drupal\views\Tests\Update\ArgumentPlaceholderUpdatePathTest.
*/
namespace Drupal\views\Tests\Update;
use Drupal\system\Tests\Update\UpdatePathTestBase;
use Drupal\views\Entity\View;
/**
* Tests the argument placeholder update path.
*
* @see views_update_8002()
*
* @group views
*/
class ArgumentPlaceholderUpdatePathTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
__DIR__ . '/../../../tests/fixtures/update/argument-placeholder.php'
];
}
/**
* Ensures that %1 and !1 are converted to twig tokens in existing views.
*/
public function testArgumentPlaceholderUpdate() {
$this->runUpdates();
$view = View::load('test_token_view');
$data = $view->toArray();
$this->assertEqual('{{ arguments.nid }}-test-class-{{ raw_arguments.nid }}', $data['display']['default']['display_options']['style']['options']['col_class_custom']);
$this->assertEqual('{{ arguments.nid }}-test-class-{{ raw_arguments.nid }}', $data['display']['default']['display_options']['style']['options']['row_class_custom']);
$this->assertEqual('{{ arguments.nid }}-description-{{ raw_arguments.nid }}', $data['display']['feed_1']['display_options']['style']['options']['description']);
$this->assertEqual('{{ arguments.nid }}-custom-text-{{ raw_arguments.nid }}', $data['display']['default']['display_options']['fields']['title']['alter']['text']);
$this->assertEqual('test_token_view {{ arguments.nid }} {{ raw_arguments.nid }}', $data['display']['default']['display_options']['title']);
$this->assertEqual('{{ arguments.nid }}-custom-{{ raw_arguments.nid }}', $data['display']['default']['display_options']['header']['area_text_custom']['content']);
$this->assertEqual('{{ arguments.nid }}-text-{{ raw_arguments.nid }}', $data['display']['default']['display_options']['footer']['area']['content']['value']);
$this->assertEqual("Displaying @start - @end of @total\n\n{{ arguments.nid }}-result-{{ raw_arguments.nid }}", $data['display']['default']['display_options']['empty']['result']['content']);
$this->assertEqual('{{ arguments.nid }}-title-{{ raw_arguments.nid }}', $data['display']['default']['display_options']['empty']['title']['title']);
$this->assertEqual('{{ arguments.nid }}-entity-{{ raw_arguments.nid }}', $data['display']['default']['display_options']['empty']['entity_node']['target']);
$this->assertEqual('{{ arguments.nid }} title {{ raw_arguments.nid }}', $data['display']['default']['display_options']['arguments']['nid']['title']);
$this->assertEqual('{{ arguments.nid }} exception-title {{ raw_arguments.nid }}', $data['display']['default']['display_options']['arguments']['nid']['exception']['title']);
$this->assertEqual('{{ arguments.nid }}-more-text-{{ raw_arguments.nid }}', $data['display']['default']['display_options']['use_more_text']);
$this->assertEqual('{{ arguments.nid }}-custom-url-{{ raw_arguments.nid }}', $data['display']['default']['display_options']['link_url']);
}
}
......@@ -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'])) {
......
<?php
$connection = Drupal\Core\Database\Database::getConnection();
$connection->insert('config')
->fields(array(
'collection' => '',
'name' => 'views.view.test_token_view',
'data' => serialize(\Drupal\Component\Serialization\Yaml::decode(file_get_contents('core/modules/views/tests/modules/views_test_config/test_views/views.view.test_token_view.yml'))),
))
->execute();
......@@ -174,7 +174,7 @@ display:
title_enable: false
title: All
title_enable: true
title: '%1'
title: '{{ arguments.tid }}'
default_argument_type: fixed
default_argument_options:
argument: ''
......
......@@ -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:
......
langcode: en
status: true
dependencies: { }
id: test_field_argument_tokens
label: null
module: views
description: ''
tag: ''
base_table: views_test_data
base_field: id
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: 'null'
style:
type: default
row:
type: fields
display_plugin: default