diff --git a/core/modules/views/src/Plugin/views/PluginBase.php b/core/modules/views/src/Plugin/views/PluginBase.php index e56dcae..ef4c141 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,24 +353,31 @@ 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)); - $twig_tokens[$token] = $replacement; + $token = trim(str_replace(array('{{', '}}'), '', $token)); + } + // Check for arrays in Twig tokens. Internally these are passed a + // dot-delimited strings, but need to be turned into associative arrays + // for parsing. + if (strpos($token, '.') !== FALSE) { + $parts = explode('.', $token); + $top = array_shift($parts); + $token_array = array(array_pop($parts) => $replacement); + foreach(array_reverse($parts) as $key) { + $token_array = array($key => $token_array); + } + $twig_tokens[$top] = $token_array; } else { - $other_tokens[$token] = $replacement; + $twig_tokens[$token] = $replacement; } } - // 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 @@ -393,7 +396,7 @@ function ($children, $elements) { return (string) $this->getRenderer()->render($build); } else { - return $text; + Xss::filterAdmin($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..137e9c5 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 && 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 && 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 || 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 && 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..9d0aab3 100644 --- a/core/modules/views/src/Plugin/views/area/TokenizeAreaPluginBase.php +++ b/core/modules/views/src/Plugin/views/area/TokenizeAreaPluginBase.php @@ -57,9 +57,9 @@ public function tokenForm(&$form, FormStateInterface $form_state) { } $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)) { diff --git a/core/modules/views/src/Plugin/views/argument/ArgumentPluginBase.php b/core/modules/views/src/Plugin/views/argument/ArgumentPluginBase.php index a1f1c5e..28b8a87 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,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. Use "argument.1" for the first argument, "argument.2" for the second, etc.'), '#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 f9ae57a..c7a85ff 100644 --- a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php +++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php @@ -1727,9 +1727,9 @@ 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. diff --git a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php index ee16994..b03ce4a 100644 --- a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php +++ b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php @@ -332,7 +332,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, @@ -869,8 +869,8 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) { $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')]); @@ -1514,8 +1514,8 @@ public function getRenderTokens($item) { $tokens = $this->view->build_info['substitutions']; } $count = 0; - foreach ($this->displayHandler->getHandlers('argument') as $arg => $handler) { - $token = '%' . ++$count; + foreach ($this->displayHandler->getHandlers('argument') as $arg => $handler) { + $token = "arguments.$arg"; if (!isset($tokens[$token])) { $tokens[$token] = ''; } @@ -1523,7 +1523,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. @@ -1620,6 +1621,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; + + // @TODO: mikeker: https://www.drupal.org/node/2492839 $tokens['%' . $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 3bb472f..29b6d3c 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/ViewExecutable.php b/core/modules/views/src/ViewExecutable.php index ddab68c..763ae3e 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'])) {