diff --git a/core/modules/views/src/Plugin/views/area/TokenizeAreaPluginBase.php b/core/modules/views/src/Plugin/views/area/TokenizeAreaPluginBase.php index bf6fbf6..4da1200 100644 --- a/core/modules/views/src/Plugin/views/area/TokenizeAreaPluginBase.php +++ b/core/modules/views/src/Plugin/views/area/TokenizeAreaPluginBase.php @@ -106,7 +106,7 @@ public function tokenForm(&$form, FormStateInterface $form_state) { */ public function tokenizeValue($value) { if ($this->options['tokenize']) { - $value = $this->view->style_plugin->tokenizeValue($value, 0); + $value = $this->view->getStyle()->tokenizeValue($value, 0); } // As we add the globalTokenForm() we also should replace the token here. return $this->globalTokenReplace($value); diff --git a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php index 4459ac2..fab7664 100644 --- a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php +++ b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php @@ -337,7 +337,7 @@ public function elementClasses($row_index = NULL) { * tokens so they will all be available. */ public function tokenizeValue($value, $row_index = NULL) { - if (strpos($value, '[') !== FALSE || strpos($value, '!') !== FALSE || strpos($value, '%') !== FALSE) { + if ($this->view->getStyle()->hasRowToken($value) || $this->view->getStyle()->hasArgToken($value)) { $fake_item = array( 'alter_text' => TRUE, 'text' => $value, diff --git a/core/modules/views/src/Plugin/views/style/StylePluginBase.php b/core/modules/views/src/Plugin/views/style/StylePluginBase.php index aa2d8e8..8191fca 100644 --- a/core/modules/views/src/Plugin/views/style/StylePluginBase.php +++ b/core/modules/views/src/Plugin/views/style/StylePluginBase.php @@ -9,10 +9,12 @@ use Drupal\Component\Utility\Html; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Utility\Token; use Drupal\views\Plugin\views\PluginBase; use Drupal\views\Plugin\views\display\DisplayPluginBase; use Drupal\views\Plugin\views\wizard\WizardInterface; use Drupal\views\ViewExecutable; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * @defgroup views_style_plugins Views style plugins @@ -48,6 +50,13 @@ protected $usesOptions = TRUE; /** + * The token utility. + * + * @var \Drupal\Core\Utility\Token + */ + protected $tokenUtility; + + /** * Store all available tokens row rows. */ protected $rowTokens = array(); @@ -113,6 +122,36 @@ protected $defaultFieldLabels = FALSE; /** + * Constructs a StylePluginBase object. + * + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin_id for the plugin instance. + * @param mixed $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\Utility\Token $token + * The Token utility. + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, Token $token_utility) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + $this->tokenUtility = $token_utility; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('token') + ); + } + + + /** * Overrides \Drupal\views\Plugin\views\PluginBase::init(). * * The style options might come externally as the style can be sourced from at @@ -184,20 +223,6 @@ function usesFields() { } /** - * Return TRUE if this style uses tokens. - * - * Used to ensure we don't fetch tokens when not needed for performance. - */ - public function usesTokens() { - if ($this->usesRowClass()) { - $class = $this->options['row_class']; - if (strpos($class, '[') !== FALSE || strpos($class, '!') !== FALSE || strpos($class, '%') !== FALSE) { - return TRUE; - } - } - } - - /** * Return TRUE if this style enables field labels by default. * * @return bool @@ -228,7 +253,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 ($this->hasArgToken($value) || $this->hasRowToken($value)) { // 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'])) { @@ -244,6 +269,80 @@ public function tokenizeValue($value, $row_index) { } /** + * Indicates whether the value has a potential Views argument token. + * + * @param string $value + * The string to check + * + * @return bool + * TRUE if the string has a potential Views argument token. + */ + public function hasArgToken($value) { + // If the string definitely does not contain a token, return FALSE + // immediately for performance. + if ((strpos($value, '!') === FALSE) && (strpos($value, '%') === FALSE)) { + return FALSE; + } + // Otherwise, scan for valid token patterns. + return preg_match('/[!%]\d+/', $value); + } + + /** + * Indicates whether the value has a potential Views row-level token. + * + * @param string $value + * The string to check + * + * @return bool + * TRUE if the string has a potential row token. + */ + public function hasRowToken($value) { + // If the string definitely does not contain a token, return FALSE + // immediately for performance. + if (strpos($value, '[') === FALSE) { + return FALSE; + } + // Otherwise, scan for valid token patterns. + // Match any non-empty string between [ and ] that does not contain [ or ]. + return preg_match('/\[[^\[\]]+\]/', $value); + } + + /** + * Indicates whether the value has a global token. + * + * @param string $value + * The string to check + * + * @return bool + * TRUE if the string has a global token. + * + * @see \Drupal\Core\Utility\Token + */ + public function hasGlobalToken($value) { + // If the string definitely does not contain a token, return FALSE + // immediately for performance. + if (strpos($value, '[') === FALSE) { + return FALSE; + } + // Otherwise, scan for valid token patterns. + $tokens = $this->tokenUtility->scan($value); + return (!empty($tokens)); + } + + /** + * Indicates whether the value has a potential Views token or global token. + * + * @param string $value + * The string to check + * + * @return bool + * TRUE if the string has any token pattern. + */ + public function hasToken($value) { + return ($this->hasArgToken($value) || $this->hasRowToken($value) || $this->hasGlobalToken($value)); + } + + /** * Should the output of the style plugin be rendered even if it's a empty view. */ public function evenEmpty() { diff --git a/core/modules/views/tests/src/Unit/Plugin/style/StylePluginBaseTest.php b/core/modules/views/tests/src/Unit/Plugin/style/StylePluginBaseTest.php new file mode 100644 index 0000000..56aab5d --- /dev/null +++ b/core/modules/views/tests/src/Unit/Plugin/style/StylePluginBaseTest.php @@ -0,0 +1,193 @@ +cache = $this->getMock('\Drupal\Core\Cache\CacheBackendInterface'); + $this->languageManager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface'); + $this->moduleHandler = $this->getMock('\Drupal\Core\Extension\ModuleHandlerInterface'); + $this->tokenUtility = new Token($this->moduleHandler, $this->cache, $this->languageManager); + $this->stylePlugin = new TestStylePlugin(array(), 'default', array(), $this->tokenUtility); + } + + /** + * @covers ::hasArgToken + * @dataProvider providerHasArgToken + */ + public function testHasArgToken($value, $return) { + $this->assertEquals($this->stylePlugin->hasArgToken($value), $return); + } + + /** + * Data provider for testHasArgToken(). + * + * @return array + */ + public function providerHasArgToken() { + return array( + array('%', FALSE), + array('!', FALSE), + array('!', FALSE), + array('% 1', FALSE), + array('! 1', FALSE), + array('!1', TRUE), + array('%1', TRUE), + array(' !12345 ', TRUE), + array(' %54321 ', TRUE), + array('%elephant', FALSE), + array('!elephant', FALSE), + array('[other_token]', FALSE), + ); + } + + /** + * @covers ::hasRowToken + * @dataProvider providerHasRowToken + */ + public function testHasRowToken($value, $return) { + $this->assertEquals($this->stylePlugin->hasRowToken($value), $return); + } + + /** + * Data provider for testHasRowToken(). + * + * @return array + */ + public function providerHasRowToken() { + return array( + array('%1', FALSE), + array('!1', FALSE), + array('[', FALSE), + array(']', FALSE), + array('[]', FALSE), + array('[[]', FALSE), + array('[elephant]', TRUE), + array(' [elephant_row_token_1] ', TRUE), + ); + } + + /** + * @covers ::hasGlobalToken + * @dataProvider providerHasGlobalToken + * + * @see \Drupal\system\Tests\System\TokenScanTest + */ + public function testHasGlobalToken($value, $return) { + $this->assertEquals($this->stylePlugin->hasGlobalToken($value), $return); + } + + /** + * Data provider for testHasGlobalToken(). + * + * @return array + * + * @see \Drupal\system\Tests\System\TokenScanTest + */ + public function providerHasGlobalToken() { + return array( + array('%1', FALSE), + array('!1', FALSE), + array('[', FALSE), + array(']', FALSE), + array('[]', FALSE), + array('[[]', FALSE), + array('[elephant]', FALSE), + // We don't need to test too many valid patterns because the token tests + // cover that already. + array(' [some:token] ', TRUE) + ); + } + + /** + * @covers ::hasToken + * @dataProvider providerHasToken + */ + public function testHasToken($value, $return) { + $this->assertEquals($this->stylePlugin->hasToken($value), $return); + } + + /** + * Data provider for testHasToken(). + * + * @return array + * + * @see \Drupal\system\Tests\System\TokenScanTest + */ + public function providerHasToken() { + return array( + array('%', FALSE), + array('!', FALSE), + array('!', FALSE), + array('% 1', FALSE), + array('! 1', FALSE), + array('!1', TRUE), + array('%1', TRUE), + array(' !12345 ', TRUE), + array(' %54321 ', TRUE), + array('%elephant', FALSE), + array('!elephant', FALSE), + array('[', FALSE), + array(']', FALSE), + array('[]', FALSE), + array('[[]', FALSE), + array('[elephant]', TRUE), + array(' [elephant_row_token_1] ', TRUE), + // We don't need to test too many valid patterns for global tokens + // because the token tests cover that already. + array(' [some:token] ', TRUE) + ); + } + +} + +class TestStylePlugin extends StylePluginBase {}