Issue #2525910 by dawehner, effulgentsia, Berdir, lauriii, larowlan, timmillwood, Wim Leers, chx, arlinsandbulte, Fabianx, Gábor Hojtsy, Dave Reid, alexpott, catch: Ensure token replacements have cacheability + attachments metadata and that it is bubbled in any case
parent
4f60dd5357
commit
1a0cdcd32c
|
@ -1185,7 +1185,7 @@ services:
|
|||
- { name: service_collector, tag: breadcrumb_builder, call: addBuilder }
|
||||
token:
|
||||
class: Drupal\Core\Utility\Token
|
||||
arguments: ['@module_handler', '@cache.discovery', '@language_manager', '@cache_tags.invalidator']
|
||||
arguments: ['@module_handler', '@cache.discovery', '@language_manager', '@cache_tags.invalidator', '@renderer']
|
||||
batch.storage:
|
||||
class: Drupal\Core\Batch\BatchStorage
|
||||
arguments: ['@database', '@session', '@csrf_token']
|
||||
|
|
|
@ -132,6 +132,35 @@ class CacheableMetadata implements CacheableDependencyInterface {
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a dependency on an object: merges its cacheability metadata.
|
||||
*
|
||||
* @param \Drupal\Core\Cache\CacheableDependencyInterface|mixed $other_object
|
||||
* The dependency. If the object implements CacheableDependencyInterface,
|
||||
* then its cacheability metadata will be used. Otherwise, the passed in
|
||||
* object must be assumed to be uncacheable, so max-age 0 is set.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addCacheableDependency($other_object) {
|
||||
if ($other_object instanceof CacheableDependencyInterface) {
|
||||
$this->addCacheTags($other_object->getCacheTags());
|
||||
$this->addCacheContexts($other_object->getCacheContexts());
|
||||
if ($this->maxAge === Cache::PERMANENT) {
|
||||
$this->maxAge = $other_object->getCacheMaxAge();
|
||||
}
|
||||
elseif (($max_age = $other_object->getCacheMaxAge()) && $max_age !== Cache::PERMANENT) {
|
||||
$this->maxAge = Cache::mergeMaxAges($this->maxAge, $max_age);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Not a cacheable dependency, this can not be cached.
|
||||
$this->maxAge = 0;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the values of another CacheableMetadata object with this one.
|
||||
*
|
||||
|
|
|
@ -94,6 +94,19 @@ class BubbleableMetadata extends CacheableMetadata implements AttachmentsInterfa
|
|||
return $meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addCacheableDependency($other_object) {
|
||||
parent::addCacheableDependency($other_object);
|
||||
|
||||
if ($other_object instanceof AttachmentsInterface) {
|
||||
$this->addAttachments($other_object->getAttachments());
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges two attachments arrays (which live under the '#attached' key).
|
||||
*
|
||||
|
|
|
@ -8,11 +8,15 @@
|
|||
namespace Drupal\Core\Utility;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\Core\Render\AttachmentsInterface;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
|
||||
/**
|
||||
* Drupal placeholder/token replacement system.
|
||||
|
@ -104,6 +108,13 @@ class Token {
|
|||
*/
|
||||
protected $cacheTagsInvalidator;
|
||||
|
||||
/**
|
||||
* The renderer.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* Constructs a new class instance.
|
||||
*
|
||||
|
@ -115,12 +126,15 @@ class Token {
|
|||
* The language manager.
|
||||
* @param \Drupal\Core\Cache\CacheTagsInvalidatorInterface $cache_tags_invalidator
|
||||
* The cache tags invalidator.
|
||||
* @param \Drupal\Core\Render\RendererInterface $renderer
|
||||
* The renderer.
|
||||
*/
|
||||
public function __construct(ModuleHandlerInterface $module_handler, CacheBackendInterface $cache, LanguageManagerInterface $language_manager, CacheTagsInvalidatorInterface $cache_tags_invalidator) {
|
||||
public function __construct(ModuleHandlerInterface $module_handler, CacheBackendInterface $cache, LanguageManagerInterface $language_manager, CacheTagsInvalidatorInterface $cache_tags_invalidator, RendererInterface $renderer) {
|
||||
$this->cache = $cache;
|
||||
$this->languageManager = $language_manager;
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->cacheTagsInvalidator = $cache_tags_invalidator;
|
||||
$this->renderer = $renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -152,19 +166,39 @@ class Token {
|
|||
* \Drupal\Component\Utility\Xss::filter(),
|
||||
* \Drupal\Component\Utility\SafeMarkup::checkPlain() or other appropriate
|
||||
* scrubbing functions before displaying data to users.
|
||||
* @param \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata|null
|
||||
* (optional) An object to which static::generate() and the hooks and
|
||||
* functions that it invokes will add their required bubbleable metadata.
|
||||
*
|
||||
* To ensure that the metadata associated with the token replacements gets
|
||||
* attached to the same render array that contains the token-replaced text,
|
||||
* callers of this method are encouraged to pass in a BubbleableMetadata
|
||||
* object and apply it to the corresponding render array. For example:
|
||||
* @code
|
||||
* $bubbleable_metadata = new BubbleableMetadata();
|
||||
* $build['#markup'] = $token_service->replace('Tokens: [node:nid] [current-user:uid]', ['node' => $node], [], $bubbleable_metadata);
|
||||
* $bubbleable_metadata->applyTo($build);
|
||||
* @endcode
|
||||
*
|
||||
* When the caller does not pass in a BubbleableMetadata object, this
|
||||
* method creates a local one, and applies the collected metadata to the
|
||||
* Renderer's currently active render context.
|
||||
*
|
||||
* @return string
|
||||
* Text with tokens replaced.
|
||||
*/
|
||||
public function replace($text, array $data = array(), array $options = array()) {
|
||||
public function replace($text, array $data = array(), array $options = array(), BubbleableMetadata $bubbleable_metadata = NULL) {
|
||||
$text_tokens = $this->scan($text);
|
||||
if (empty($text_tokens)) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
$bubbleable_metadata_is_passed_in = (bool) $bubbleable_metadata;
|
||||
$bubbleable_metadata = $bubbleable_metadata ?: new BubbleableMetadata();
|
||||
|
||||
$replacements = array();
|
||||
foreach ($text_tokens as $type => $tokens) {
|
||||
$replacements += $this->generate($type, $tokens, $data, $options);
|
||||
$replacements += $this->generate($type, $tokens, $data, $options, $bubbleable_metadata);
|
||||
if (!empty($options['clear'])) {
|
||||
$replacements += array_fill_keys($tokens, '');
|
||||
}
|
||||
|
@ -173,12 +207,20 @@ class Token {
|
|||
// Optionally alter the list of replacement values.
|
||||
if (!empty($options['callback'])) {
|
||||
$function = $options['callback'];
|
||||
$function($replacements, $data, $options);
|
||||
$function($replacements, $data, $options, $bubbleable_metadata);
|
||||
}
|
||||
|
||||
$tokens = array_keys($replacements);
|
||||
$values = array_values($replacements);
|
||||
|
||||
// If a local $bubbleable_metadata object was created, apply the metadata
|
||||
// it collected to the renderer's currently active render context.
|
||||
if (!$bubbleable_metadata_is_passed_in && $this->renderer->hasRenderContext()) {
|
||||
$build = [];
|
||||
$bubbleable_metadata->applyTo($build);
|
||||
$this->renderer->render($build);
|
||||
}
|
||||
|
||||
return str_replace($tokens, $values, $text);
|
||||
}
|
||||
|
||||
|
@ -226,14 +268,14 @@ class Token {
|
|||
* An array of tokens to be replaced, keyed by the literal text of the token
|
||||
* as it appeared in the source text.
|
||||
* @param array $data
|
||||
* (optional) An array of keyed objects. For simple replacement scenarios
|
||||
* 'node', 'user', and others are common keys, with an accompanying node or
|
||||
* user object being the value. Some token types, like 'site', do not require
|
||||
* An array of keyed objects. For simple replacement scenarios: 'node',
|
||||
* 'user', and others are common keys, with an accompanying node or user
|
||||
* object being the value. Some token types, like 'site', do not require
|
||||
* any explicit information from $data and can be replaced even if it is
|
||||
* empty.
|
||||
* @param array $options
|
||||
* (optional) A keyed array of settings and flags to control the token
|
||||
* replacement process. Supported options are:
|
||||
* A keyed array of settings and flags to control the token replacement
|
||||
* process. Supported options are:
|
||||
* - langcode: A language code to be used when generating locale-sensitive
|
||||
* tokens.
|
||||
* - callback: A callback function that will be used to post-process the
|
||||
|
@ -245,6 +287,9 @@ class Token {
|
|||
* responsibility for running \Drupal\Component\Utility\Xss::filter(),
|
||||
* \Drupal\Component\Utility\SafeMarkup::checkPlain() or other appropriate
|
||||
* scrubbing functions before displaying data to users.
|
||||
* @param \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata
|
||||
* The bubbleable metadata. This is passed to the token replacement
|
||||
* implementations so that they can attach their metadata.
|
||||
*
|
||||
* @return array
|
||||
* An associative array of replacement values, keyed by the original 'raw'
|
||||
|
@ -254,9 +299,16 @@ class Token {
|
|||
* @see hook_tokens()
|
||||
* @see hook_tokens_alter()
|
||||
*/
|
||||
public function generate($type, array $tokens, array $data = array(), array $options = array()) {
|
||||
public function generate($type, array $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
|
||||
$options += array('sanitize' => TRUE);
|
||||
$replacements = $this->moduleHandler->invokeAll('tokens', array($type, $tokens, $data, $options));
|
||||
|
||||
foreach ($data as $object) {
|
||||
if ($object instanceof CacheableDependencyInterface || $object instanceof AttachmentsInterface) {
|
||||
$bubbleable_metadata->addCacheableDependency($object);
|
||||
}
|
||||
}
|
||||
|
||||
$replacements = $this->moduleHandler->invokeAll('tokens', [$type, $tokens, $data, $options, $bubbleable_metadata]);
|
||||
|
||||
// Allow other modules to alter the replacements.
|
||||
$context = array(
|
||||
|
@ -265,7 +317,7 @@ class Token {
|
|||
'data' => $data,
|
||||
'options' => $options,
|
||||
);
|
||||
$this->moduleHandler->alter('tokens', $replacements, $context);
|
||||
$this->moduleHandler->alter('tokens', $replacements, $context, $bubbleable_metadata);
|
||||
|
||||
return $replacements;
|
||||
}
|
||||
|
|
|
@ -34,22 +34,43 @@ use Drupal\user\Entity\User;
|
|||
* An array of tokens to be replaced. The keys are the machine-readable token
|
||||
* names, and the values are the raw [type:token] strings that appeared in the
|
||||
* original text.
|
||||
* @param $data
|
||||
* (optional) An associative array of data objects to be used when generating
|
||||
* replacement values, as supplied in the $data parameter to
|
||||
* \Drupal\Core\Utility\Token::replace().
|
||||
* @param $options
|
||||
* (optional) An associative array of options for token replacement; see
|
||||
* @param array $data
|
||||
* An associative array of data objects to be used when generating replacement
|
||||
* values, as supplied in the $data parameter to
|
||||
* \Drupal\Core\Utility\Token::replace().
|
||||
* @param array $options
|
||||
* An associative array of options for token replacement; see
|
||||
* \Drupal\Core\Utility\Token::replace() for possible values.
|
||||
* @param \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata
|
||||
* The bubbleable metadata. Prior to invoking this hook,
|
||||
* \Drupal\Core\Utility\Token::generate() collects metadata for all of the
|
||||
* data objects in $data. For any data sources not in $data, but that are
|
||||
* used by the token replacement logic, such as global configuration (e.g.,
|
||||
* 'system.site') and related objects (e.g., $node->getOwner()),
|
||||
* implementations of this hook must add the corresponding metadata.
|
||||
* For example:
|
||||
* @code
|
||||
* $bubbleable_metadata->addCacheableDependency(\Drupal::config('system.site'));
|
||||
* $bubbleable_metadata->addCacheableDependency($node->getOwner());
|
||||
* @endcode
|
||||
*
|
||||
* @return
|
||||
* Additionally, implementations of this hook, must forward
|
||||
* $bubbleable_metadata to the chained tokens that they invoke.
|
||||
* For example:
|
||||
* @code
|
||||
* if ($created_tokens = $token_service->findWithPrefix($tokens, 'created')) {
|
||||
* $replacements = $token_service->generate('date', $created_tokens, array('date' => $node->getCreatedTime()), $options, $bubbleable_metadata);
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @return array
|
||||
* An associative array of replacement values, keyed by the raw [type:token]
|
||||
* strings from the original text.
|
||||
*
|
||||
* @see hook_token_info()
|
||||
* @see hook_tokens_alter()
|
||||
*/
|
||||
function hook_tokens($type, $tokens, array $data = array(), array $options = array()) {
|
||||
function hook_tokens($type, $tokens, array $data, array $options, \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata) {
|
||||
$token_service = \Drupal::token();
|
||||
|
||||
$url_options = array('absolute' => TRUE);
|
||||
|
@ -87,6 +108,7 @@ function hook_tokens($type, $tokens, array $data = array(), array $options = arr
|
|||
case 'author':
|
||||
$account = $node->getOwner() ? $node->getOwner() : User::load(0);
|
||||
$replacements[$original] = $sanitize ? SafeMarkup::checkPlain($account->label()) : $account->label();
|
||||
$bubbleable_metadata->addCacheableDependency($account);
|
||||
break;
|
||||
|
||||
case 'created':
|
||||
|
@ -96,11 +118,11 @@ function hook_tokens($type, $tokens, array $data = array(), array $options = arr
|
|||
}
|
||||
|
||||
if ($author_tokens = $token_service->findWithPrefix($tokens, 'author')) {
|
||||
$replacements = $token_service->generate('user', $author_tokens, array('user' => $node->getOwner()), $options);
|
||||
$replacements = $token_service->generate('user', $author_tokens, array('user' => $node->getOwner()), $options, $bubbleable_metadata);
|
||||
}
|
||||
|
||||
if ($created_tokens = $token_service->findWithPrefix($tokens, 'created')) {
|
||||
$replacements = $token_service->generate('date', $created_tokens, array('date' => $node->getCreatedTime()), $options);
|
||||
$replacements = $token_service->generate('date', $created_tokens, array('date' => $node->getCreatedTime()), $options, $bubbleable_metadata);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,10 +142,14 @@ function hook_tokens($type, $tokens, array $data = array(), array $options = arr
|
|||
* - 'tokens'
|
||||
* - 'data'
|
||||
* - 'options'
|
||||
* @param \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata
|
||||
* The bubbleable metadata. In case you alter an existing token based upon
|
||||
* a data source that isn't in $context['data'], you must add that
|
||||
* dependency to $bubbleable_metadata.
|
||||
*
|
||||
* @see hook_tokens()
|
||||
*/
|
||||
function hook_tokens_alter(array &$replacements, array $context) {
|
||||
function hook_tokens_alter(array &$replacements, array $context, \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata) {
|
||||
$options = $context['options'];
|
||||
|
||||
if (isset($options['langcode'])) {
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Utility\Xss;
|
||||
use Drupal\Core\Datetime\Entity\DateFormat;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
|
||||
/**
|
||||
* Implements hook_token_info().
|
||||
|
@ -105,7 +107,7 @@ function comment_token_info() {
|
|||
/**
|
||||
* Implements hook_tokens().
|
||||
*/
|
||||
function comment_tokens($type, $tokens, array $data = array(), array $options = array()) {
|
||||
function comment_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
|
||||
$token_service = \Drupal::token();
|
||||
|
||||
$url_options = array('absolute' => TRUE);
|
||||
|
@ -138,6 +140,11 @@ function comment_tokens($type, $tokens, array $data = array(), array $options =
|
|||
|
||||
case 'mail':
|
||||
$mail = $comment->getAuthorEmail();
|
||||
// Add the user cacheability metadata in case the author of the comment
|
||||
// is not the anonymous user.
|
||||
if ($comment->getOwnerId()) {
|
||||
$bubbleable_metadata->addCacheableDependency($comment->getOwner());
|
||||
}
|
||||
$replacements[$original] = $sanitize ? SafeMarkup::checkPlain($mail) : $mail;
|
||||
break;
|
||||
|
||||
|
@ -170,26 +177,37 @@ function comment_tokens($type, $tokens, array $data = array(), array $options =
|
|||
|
||||
case 'author':
|
||||
$name = $comment->getAuthorName();
|
||||
// Add the user cacheability metadata in case the author of the comment
|
||||
// is not the anonymous user.
|
||||
if ($comment->getOwnerId()) {
|
||||
$bubbleable_metadata->addCacheableDependency($comment->getOwner());
|
||||
}
|
||||
$replacements[$original] = $sanitize ? Xss::filter($name) : $name;
|
||||
break;
|
||||
|
||||
case 'parent':
|
||||
if ($comment->hasParentComment()) {
|
||||
$parent = $comment->getParentComment();
|
||||
$bubbleable_metadata->addCacheableDependency($parent);
|
||||
$replacements[$original] = $sanitize ? Xss::filter($parent->getSubject()) : $parent->getSubject();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'created':
|
||||
$date_format = DateFormat::load('medium');
|
||||
$bubbleable_metadata->addCacheableDependency($date_format);
|
||||
$replacements[$original] = format_date($comment->getCreatedTime(), 'medium', '', NULL, $langcode);
|
||||
break;
|
||||
|
||||
case 'changed':
|
||||
$date_format = DateFormat::load('medium');
|
||||
$bubbleable_metadata->addCacheableDependency($date_format);
|
||||
$replacements[$original] = format_date($comment->getChangedTime(), 'medium', '', NULL, $langcode);
|
||||
break;
|
||||
|
||||
case 'entity':
|
||||
$entity = $comment->getCommentedEntity();
|
||||
$bubbleable_metadata->addCacheableDependency($entity);
|
||||
$title = $entity->label();
|
||||
$replacements[$original] = $sanitize ? Xss::filter($title) : $title;
|
||||
break;
|
||||
|
@ -199,23 +217,23 @@ function comment_tokens($type, $tokens, array $data = array(), array $options =
|
|||
// Chained token relationships.
|
||||
if ($entity_tokens = $token_service->findwithPrefix($tokens, 'entity')) {
|
||||
$entity = $comment->getCommentedEntity();
|
||||
$replacements += $token_service->generate($comment->getCommentedEntityTypeId(), $entity_tokens, array($comment->getCommentedEntityTypeId() => $entity), $options);
|
||||
$replacements += $token_service->generate($comment->getCommentedEntityTypeId(), $entity_tokens, array($comment->getCommentedEntityTypeId() => $entity), $options, $bubbleable_metadata);
|
||||
}
|
||||
|
||||
if ($date_tokens = $token_service->findwithPrefix($tokens, 'created')) {
|
||||
$replacements += $token_service->generate('date', $date_tokens, array('date' => $comment->getCreatedTime()), $options);
|
||||
$replacements += $token_service->generate('date', $date_tokens, array('date' => $comment->getCreatedTime()), $options, $bubbleable_metadata);
|
||||
}
|
||||
|
||||
if ($date_tokens = $token_service->findwithPrefix($tokens, 'changed')) {
|
||||
$replacements += $token_service->generate('date', $date_tokens, array('date' => $comment->getChangedTime()), $options);
|
||||
$replacements += $token_service->generate('date', $date_tokens, array('date' => $comment->getChangedTime()), $options, $bubbleable_metadata);
|
||||
}
|
||||
|
||||
if (($parent_tokens = $token_service->findwithPrefix($tokens, 'parent')) && $parent = $comment->getParentComment()) {
|
||||
$replacements += $token_service->generate('comment', $parent_tokens, array('comment' => $parent), $options);
|
||||
$replacements += $token_service->generate('comment', $parent_tokens, array('comment' => $parent), $options, $bubbleable_metadata);
|
||||
}
|
||||
|
||||
if (($author_tokens = $token_service->findwithPrefix($tokens, 'author')) && $account = $comment->getOwner()) {
|
||||
$replacements += $token_service->generate('user', $author_tokens, array('user' => $account), $options);
|
||||
$replacements += $token_service->generate('user', $author_tokens, array('user' => $account), $options, $bubbleable_metadata);
|
||||
}
|
||||
}
|
||||
elseif ($type == 'entity' & !empty($data['entity'])) {
|
||||
|
|
|
@ -10,6 +10,7 @@ namespace Drupal\comment\Tests;
|
|||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Utility\Xss;
|
||||
use Drupal\comment\Entity\Comment;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\node\Entity\Node;
|
||||
|
||||
/**
|
||||
|
@ -60,6 +61,7 @@ class CommentTokenReplaceTest extends CommentTestBase {
|
|||
$tests['[comment:langcode]'] = SafeMarkup::checkPlain($comment->language()->getId());
|
||||
$tests['[comment:url]'] = $comment->url('canonical', $url_options + array('fragment' => 'comment-' . $comment->id()));
|
||||
$tests['[comment:edit-url]'] = $comment->url('edit-form', $url_options);
|
||||
$tests['[comment:created]'] = \Drupal::service('date.formatter')->format($comment->getCreatedTime(), 'medium', array('langcode' => $language_interface->getId()));
|
||||
$tests['[comment:created:since]'] = \Drupal::service('date.formatter')->formatTimeDiffSince($comment->getCreatedTime(), array('langcode' => $language_interface->getId()));
|
||||
$tests['[comment:changed:since]'] = \Drupal::service('date.formatter')->formatTimeDiffSince($comment->getChangedTimeAcrossTranslations(), array('langcode' => $language_interface->getId()));
|
||||
$tests['[comment:parent:cid]'] = $comment->hasParentComment() ? $comment->getParentComment()->id() : NULL;
|
||||
|
@ -71,12 +73,48 @@ class CommentTokenReplaceTest extends CommentTestBase {
|
|||
$tests['[comment:author:uid]'] = $comment->getOwnerId();
|
||||
$tests['[comment:author:name]'] = SafeMarkup::checkPlain($this->adminUser->getUsername());
|
||||
|
||||
$base_bubbleable_metadata = BubbleableMetadata::createFromObject($comment);
|
||||
$metadata_tests = [];
|
||||
$metadata_tests['[comment:cid]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[comment:hostname]'] = $base_bubbleable_metadata;
|
||||
$bubbleable_metadata = clone $base_bubbleable_metadata;
|
||||
$bubbleable_metadata->addCacheableDependency($this->adminUser);
|
||||
$metadata_tests['[comment:author]'] = $bubbleable_metadata;
|
||||
$bubbleable_metadata = clone $base_bubbleable_metadata;
|
||||
$bubbleable_metadata->addCacheableDependency($this->adminUser);
|
||||
$metadata_tests['[comment:mail]'] = $bubbleable_metadata;
|
||||
$metadata_tests['[comment:homepage]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[comment:title]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[comment:body]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[comment:langcode]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[comment:url]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[comment:edit-url]'] = $base_bubbleable_metadata;
|
||||
$bubbleable_metadata = clone $base_bubbleable_metadata;
|
||||
$metadata_tests['[comment:created]'] = $bubbleable_metadata->addCacheTags(['rendered']);
|
||||
$bubbleable_metadata = clone $base_bubbleable_metadata;
|
||||
$metadata_tests['[comment:created:since]'] = $bubbleable_metadata->setCacheMaxAge(0);
|
||||
$bubbleable_metadata = clone $base_bubbleable_metadata;
|
||||
$metadata_tests['[comment:changed:since]'] = $bubbleable_metadata->setCacheMaxAge(0);
|
||||
$bubbleable_metadata = clone $base_bubbleable_metadata;
|
||||
$metadata_tests['[comment:parent:cid]'] = $bubbleable_metadata->addCacheTags(['comment:1']);
|
||||
$metadata_tests['[comment:parent:title]'] = $bubbleable_metadata;
|
||||
$bubbleable_metadata = clone $base_bubbleable_metadata;
|
||||
$metadata_tests['[comment:entity]'] = $bubbleable_metadata->addCacheTags(['node:2']);
|
||||
// Test node specific tokens.
|
||||
$metadata_tests['[comment:entity:nid]'] = $bubbleable_metadata;
|
||||
$metadata_tests['[comment:entity:title]'] = $bubbleable_metadata;
|
||||
$bubbleable_metadata = clone $base_bubbleable_metadata;
|
||||
$metadata_tests['[comment:author:uid]'] = $bubbleable_metadata->addCacheTags(['user:2']);
|
||||
$metadata_tests['[comment:author:name]'] = $bubbleable_metadata;
|
||||
|
||||
// Test to make sure that we generated something for each token.
|
||||
$this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
|
||||
|
||||
foreach ($tests as $input => $expected) {
|
||||
$output = $token_service->replace($input, array('comment' => $comment), array('langcode' => $language_interface->getId()));
|
||||
$bubbleable_metadata = new BubbleableMetadata();
|
||||
$output = $token_service->replace($input, array('comment' => $comment), array('langcode' => $language_interface->getId()), $bubbleable_metadata);
|
||||
$this->assertEqual($output, $expected, format_string('Sanitized comment token %token replaced.', array('%token' => $input)));
|
||||
$this->assertEqual($bubbleable_metadata, $metadata_tests[$input]);
|
||||
}
|
||||
|
||||
// Generate and test unsanitized tokens.
|
||||
|
|
|
@ -6,8 +6,10 @@
|
|||
*/
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Datetime\Entity\DateFormat;
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Url;
|
||||
|
@ -938,7 +940,7 @@ function file_file_predelete(File $file) {
|
|||
/**
|
||||
* Implements hook_tokens().
|
||||
*/
|
||||
function file_tokens($type, $tokens, array $data = array(), array $options = array()) {
|
||||
function file_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
|
||||
$token_service = \Drupal::token();
|
||||
|
||||
$url_options = array('absolute' => TRUE);
|
||||
|
@ -987,30 +989,36 @@ function file_tokens($type, $tokens, array $data = array(), array $options = arr
|
|||
|
||||
// These tokens are default variations on the chained tokens handled below.
|
||||
case 'created':
|
||||
$date_format = DateFormat::load('medium');
|
||||
$bubbleable_metadata->addCacheableDependency($date_format);
|
||||
$replacements[$original] = format_date($file->getCreatedTime(), 'medium', '', NULL, $langcode);
|
||||
break;
|
||||
|
||||
case 'changed':
|
||||
$date_format = DateFormat::load('medium');
|
||||
$bubbleable_metadata = $bubbleable_metadata->addCacheableDependency($date_format);
|
||||
$replacements[$original] = format_date($file->getChangedTime(), 'medium', '', NULL, $langcode);
|
||||
break;
|
||||
|
||||
case 'owner':
|
||||
$name = $file->getOwner()->label();
|
||||
$owner = $file->getOwner();
|
||||
$bubbleable_metadata->addCacheableDependency($owner);
|
||||
$name = $owner->label();
|
||||
$replacements[$original] = $sanitize ? SafeMarkup::checkPlain($name) : $name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($date_tokens = $token_service->findWithPrefix($tokens, 'created')) {
|
||||
$replacements += $token_service->generate('date', $date_tokens, array('date' => $file->getCreatedTime()), $options);
|
||||
$replacements += $token_service->generate('date', $date_tokens, array('date' => $file->getCreatedTime()), $options, $bubbleable_metadata);
|
||||
}
|
||||
|
||||
if ($date_tokens = $token_service->findWithPrefix($tokens, 'changed')) {
|
||||
$replacements += $token_service->generate('date', $date_tokens, array('date' => $file->getChangedTime()), $options);
|
||||
$replacements += $token_service->generate('date', $date_tokens, array('date' => $file->getChangedTime()), $options, $bubbleable_metadata);
|
||||
}
|
||||
|
||||
if (($owner_tokens = $token_service->findWithPrefix($tokens, 'owner')) && $file->getOwner()) {
|
||||
$replacements += $token_service->generate('user', $owner_tokens, array('user' => $file->getOwner()), $options);
|
||||
$replacements += $token_service->generate('user', $owner_tokens, array('user' => $file->getOwner()), $options, $bubbleable_metadata);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
namespace Drupal\file\Tests;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\file\Entity\File;
|
||||
|
||||
/**
|
||||
|
@ -58,12 +59,31 @@ class FileTokenReplaceTest extends FileFieldTestBase {
|
|||
$tests['[file:owner]'] = SafeMarkup::checkPlain(user_format_name($this->adminUser));
|
||||
$tests['[file:owner:uid]'] = $file->getOwnerId();
|
||||
|
||||
$base_bubbleable_metadata = BubbleableMetadata::createFromObject($file);
|
||||
$metadata_tests = [];
|
||||
$metadata_tests['[file:fid]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[file:name]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[file:path]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[file:mime]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[file:size]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[file:url]'] = $base_bubbleable_metadata;
|
||||
$bubbleable_metadata = clone $base_bubbleable_metadata;
|
||||
$metadata_tests['[file:created]'] = $bubbleable_metadata->addCacheTags(['rendered']);
|
||||
$metadata_tests['[file:created:short]'] = $bubbleable_metadata;
|
||||
$metadata_tests['[file:changed]'] = $bubbleable_metadata;
|
||||
$metadata_tests['[file:changed:short]'] = $bubbleable_metadata;
|
||||
$bubbleable_metadata = clone $base_bubbleable_metadata;
|
||||
$metadata_tests['[file:owner]'] = $bubbleable_metadata->addCacheTags(['user:2']);
|
||||
$metadata_tests['[file:owner:uid]'] = $bubbleable_metadata;
|
||||
|
||||
// Test to make sure that we generated something for each token.
|
||||
$this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
|
||||
|
||||
foreach ($tests as $input => $expected) {
|
||||
$output = $token_service->replace($input, array('file' => $file), array('langcode' => $language_interface->getId()));
|
||||
$bubbleable_metadata = new BubbleableMetadata();
|
||||
$output = $token_service->replace($input, array('file' => $file), array('langcode' => $language_interface->getId()), $bubbleable_metadata);
|
||||
$this->assertEqual($output, $expected, format_string('Sanitized file token %token replaced.', array('%token' => $input)));
|
||||
$this->assertEqual($bubbleable_metadata, $metadata_tests[$input]);
|
||||
}
|
||||
|
||||
// Generate and test unsanitized tokens.
|
||||
|
|
|
@ -6,7 +6,9 @@
|
|||
*/
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Datetime\Entity\DateFormat;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\user\Entity\User;
|
||||
|
||||
/**
|
||||
|
@ -83,7 +85,7 @@ function node_token_info() {
|
|||
/**
|
||||
* Implements hook_tokens().
|
||||
*/
|
||||
function node_tokens($type, $tokens, array $data = array(), array $options = array()) {
|
||||
function node_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
|
||||
$token_service = \Drupal::token();
|
||||
|
||||
$url_options = array('absolute' => TRUE);
|
||||
|
@ -176,29 +178,34 @@ function node_tokens($type, $tokens, array $data = array(), array $options = arr
|
|||
// Default values for the chained tokens handled below.
|
||||
case 'author':
|
||||
$account = $node->getOwner() ? $node->getOwner() : User::load(0);
|
||||
$bubbleable_metadata->addCacheableDependency($account);
|
||||
$replacements[$original] = $sanitize ? SafeMarkup::checkPlain($account->label()) : $account->label();
|
||||
break;
|
||||
|
||||
case 'created':
|
||||
$date_format = DateFormat::load('medium');
|
||||
$bubbleable_metadata->addCacheableDependency($date_format);
|
||||
$replacements[$original] = format_date($node->getCreatedTime(), 'medium', '', NULL, $langcode);
|
||||
break;
|
||||
|
||||
case 'changed':
|
||||
$date_format = DateFormat::load('medium');
|
||||
$bubbleable_metadata->addCacheableDependency($date_format);
|
||||
$replacements[$original] = format_date($node->getChangedTime(), 'medium', '', NULL, $langcode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($author_tokens = $token_service->findWithPrefix($tokens, 'author')) {
|
||||
$replacements += $token_service->generate('user', $author_tokens, array('user' => $node->getOwner()), $options);
|
||||
$replacements += $token_service->generate('user', $author_tokens, array('user' => $node->getOwner()), $options, $bubbleable_metadata);
|
||||
}
|
||||
|
||||
if ($created_tokens = $token_service->findWithPrefix($tokens, 'created')) {
|
||||
$replacements += $token_service->generate('date', $created_tokens, array('date' => $node->getCreatedTime()), $options);
|
||||
$replacements += $token_service->generate('date', $created_tokens, array('date' => $node->getCreatedTime()), $options, $bubbleable_metadata);
|
||||
}
|
||||
|
||||
if ($changed_tokens = $token_service->findWithPrefix($tokens, 'changed')) {
|
||||
$replacements += $token_service->generate('date', $changed_tokens, array('date' => $node->getChangedTime()), $options);
|
||||
$replacements += $token_service->generate('date', $changed_tokens, array('date' => $node->getChangedTime()), $options, $bubbleable_metadata);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
namespace Drupal\node\Tests;
|
||||
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\system\Tests\System\TokenReplaceUnitTestBase;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
|
||||
|
@ -76,12 +77,35 @@ class NodeTokenReplaceTest extends TokenReplaceUnitTestBase {
|
|||
$tests['[node:created:since]'] = \Drupal::service('date.formatter')->formatTimeDiffSince($node->getCreatedTime(), array('langcode' => $this->interfaceLanguage->getId()));
|
||||
$tests['[node:changed:since]'] = \Drupal::service('date.formatter')->formatTimeDiffSince($node->getChangedTime(), array('langcode' => $this->interfaceLanguage->getId()));
|
||||
|
||||
$base_bubbleable_metadata = BubbleableMetadata::createFromObject($node);
|
||||
|
||||
$metadata_tests = [];
|
||||
$metadata_tests['[node:nid]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[node:vid]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[node:type]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[node:type-name]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[node:title]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[node:body]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[node:summary]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[node:langcode]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[node:url]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[node:edit-url]'] = $base_bubbleable_metadata;
|
||||
$bubbleable_metadata = clone $base_bubbleable_metadata;
|
||||
$metadata_tests['[node:author]'] = $bubbleable_metadata->addCacheTags(['user:1']);
|
||||
$metadata_tests['[node:author:uid]'] = $bubbleable_metadata;
|
||||
$metadata_tests['[node:author:name]'] = $bubbleable_metadata;
|
||||
$bubbleable_metadata = clone $base_bubbleable_metadata;
|
||||
$metadata_tests['[node:created:since]'] = $bubbleable_metadata->setCacheMaxAge(0);
|
||||
$metadata_tests['[node:changed:since]'] = $bubbleable_metadata;
|
||||
|
||||
// Test to make sure that we generated something for each token.
|
||||
$this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
|
||||
|
||||
foreach ($tests as $input => $expected) {
|
||||
$output = $this->tokenService->replace($input, array('node' => $node), array('langcode' => $this->interfaceLanguage->getId()));
|
||||
$bubbleable_metadata = new BubbleableMetadata();
|
||||
$output = $this->tokenService->replace($input, array('node' => $node), array('langcode' => $this->interfaceLanguage->getId()), $bubbleable_metadata);
|
||||
$this->assertEqual($output, $expected, format_string('Sanitized node token %token replaced.', array('%token' => $input)));
|
||||
$this->assertEqual($bubbleable_metadata, $metadata_tests[$input]);
|
||||
}
|
||||
|
||||
// Generate and test unsanitized tokens.
|
||||
|
|
|
@ -260,14 +260,17 @@ class FrontPageTest extends ViewTestBase {
|
|||
'config:views.view.frontpage',
|
||||
'node_list',
|
||||
];
|
||||
|
||||
$render_cache_tags = Cache::mergeTags($empty_node_listing_cache_tags, $cache_context_tags);
|
||||
$render_cache_tags = Cache::mergeTags($render_cache_tags, ['config:system.site']);
|
||||
$this->assertViewsCacheTags(
|
||||
$view,
|
||||
$empty_node_listing_cache_tags,
|
||||
$do_assert_views_caches,
|
||||
Cache::mergeTags($empty_node_listing_cache_tags, $cache_context_tags)
|
||||
$render_cache_tags
|
||||
);
|
||||
$expected_tags = Cache::mergeTags($empty_node_listing_cache_tags, $cache_context_tags);
|
||||
$expected_tags = Cache::mergeTags($expected_tags, ['rendered', 'config:user.role.anonymous']);
|
||||
$expected_tags = Cache::mergeTags($expected_tags, ['rendered', 'config:user.role.anonymous', 'config:system.site']);
|
||||
$this->assertPageCacheContextsAndTags(
|
||||
Url::fromRoute('view.frontpage.page_1'),
|
||||
$cache_contexts,
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* Builds placeholder replacement tokens for node visitor statistics.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
|
||||
/**
|
||||
* Implements hook_token_info().
|
||||
*/
|
||||
|
@ -31,7 +33,7 @@ function statistics_token_info() {
|
|||
/**
|
||||
* Implements hook_tokens().
|
||||
*/
|
||||
function statistics_tokens($type, $tokens, array $data = array(), array $options = array()) {
|
||||
function statistics_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
|
||||
$token_service = \Drupal::token();
|
||||
|
||||
$replacements = array();
|
||||
|
@ -56,7 +58,7 @@ function statistics_tokens($type, $tokens, array $data = array(), array $options
|
|||
|
||||
if ($created_tokens = $token_service->findWithPrefix($tokens, 'last-view')) {
|
||||
$statistics = statistics_get($node->id());
|
||||
$replacements += $token_service->generate('date', $created_tokens, array('date' => $statistics['timestamp']), $options);
|
||||
$replacements += $token_service->generate('date', $created_tokens, array('date' => $statistics['timestamp']), $options, $bubbleable_metadata);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ namespace Drupal\system\Tests\System;
|
|||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Utility\Xss;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
|
||||
/**
|
||||
* Generates text using placeholders for dummy content to check token
|
||||
|
@ -111,12 +112,25 @@ class TokenReplaceUnitTest extends TokenReplaceUnitTestBase {
|
|||
$tests['[site:url-brief]'] = preg_replace(array('!^https?://!', '!/$!'), '', \Drupal::url('<front>', [], $url_options));
|
||||
$tests['[site:login-url]'] = \Drupal::url('user.page', [], $url_options);
|
||||
|
||||
$base_bubbleable_metadata = new BubbleableMetadata();
|
||||
|
||||
$metadata_tests = [];
|
||||
$metadata_tests['[site:name]'] = BubbleableMetadata::createFromObject(\Drupal::config('system.site'));
|
||||
$metadata_tests['[site:slogan]'] = BubbleableMetadata::createFromObject(\Drupal::config('system.site'));
|
||||
$metadata_tests['[site:mail]'] = BubbleableMetadata::createFromObject(\Drupal::config('system.site'));
|
||||
$bubbleable_metadata = clone $base_bubbleable_metadata;
|
||||
$metadata_tests['[site:url]'] = $bubbleable_metadata->addCacheContexts(['url.site']);
|
||||
$metadata_tests['[site:url-brief]'] = $bubbleable_metadata;
|
||||
$metadata_tests['[site:login-url]'] = $bubbleable_metadata;
|
||||
|
||||
// Test to make sure that we generated something for each token.
|
||||
$this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
|
||||
|
||||
foreach ($tests as $input => $expected) {
|
||||
$output = $this->tokenService->replace($input, array(), array('langcode' => $this->interfaceLanguage->getId()));
|
||||
$bubbleable_metadata = new BubbleableMetadata();
|
||||
$output = $this->tokenService->replace($input, array(), array('langcode' => $this->interfaceLanguage->getId()), $bubbleable_metadata);
|
||||
$this->assertEqual($output, $expected, format_string('Sanitized system site information token %token replaced.', array('%token' => $input)));
|
||||
$this->assertEqual($bubbleable_metadata, $metadata_tests[$input]);
|
||||
}
|
||||
|
||||
// Generate and test unsanitized tokens.
|
||||
|
@ -124,7 +138,7 @@ class TokenReplaceUnitTest extends TokenReplaceUnitTestBase {
|
|||
$tests['[site:slogan]'] = $config->get('slogan');
|
||||
|
||||
foreach ($tests as $input => $expected) {
|
||||
$output = $this->tokenService->replace($input, array(), array('langcode' => $this->interfaceLanguage->getId(), 'sanitize' => FALSE));
|
||||
$output = $this->tokenService->replace($input, array(), array('langcode' => $this->interfaceLanguage->getId(), 'sanitize' => FALSE), $bubbleable_metadata);
|
||||
$this->assertEqual($output, $expected, format_string('Unsanitized system site information token %token replaced.', array('%token' => $input)));
|
||||
}
|
||||
|
||||
|
@ -133,10 +147,10 @@ class TokenReplaceUnitTest extends TokenReplaceUnitTestBase {
|
|||
// flag is being passed properly through the call stack and being handled
|
||||
// correctly by a 'known' token, [site:slogan].
|
||||
$raw_tokens = array('slogan' => '[site:slogan]');
|
||||
$generated = $this->tokenService->generate('site', $raw_tokens);
|
||||
$generated = $this->tokenService->generate('site', $raw_tokens, [], [], $bubbleable_metadata);
|
||||
$this->assertEqual($generated['[site:slogan]'], $safe_slogan, 'Token sanitized.');
|
||||
|
||||
$generated = $this->tokenService->generate('site', $raw_tokens, array(), array('sanitize' => FALSE));
|
||||
$generated = $this->tokenService->generate('site', $raw_tokens, array(), array('sanitize' => FALSE), $bubbleable_metadata);
|
||||
$this->assertEqual($generated['[site:slogan]'], $slogan, 'Unsanitized token generated properly.');
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\system\Tests\System\TokenReplaceWebTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\system\Tests\System;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
|
||||
|
||||
/**
|
||||
* Tests the token system integration.
|
||||
*
|
||||
* @group system
|
||||
*/
|
||||
class TokenReplaceWebTest extends WebTestBase {
|
||||
|
||||
use AssertPageCacheContextsAndTagsTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['token_test', 'filter', 'node'];
|
||||
|
||||
/**
|
||||
* Tests a token replacement on an actual website.
|
||||
*/
|
||||
public function testTokens() {
|
||||
$node = $this->drupalCreateNode();
|
||||
$account = $this->drupalCreateUser();
|
||||
$this->drupalLogin($account);
|
||||
|
||||
$this->drupalGet('token-test/' . $node->id());
|
||||
$this->assertText("Tokens: {$node->id()} {$account->id()}");
|
||||
$this->assertCacheTags(['node:1', 'rendered', 'user:2']);
|
||||
$this->assertCacheContexts(['languages:language_interface', 'theme', 'user']);
|
||||
|
||||
$this->drupalGet('token-test-without-bubleable-metadata/' . $node->id());
|
||||
$this->assertText("Tokens: {$node->id()} {$account->id()}");
|
||||
$this->assertCacheTags(['node:1', 'rendered', 'user:2']);
|
||||
$this->assertCacheContexts(['languages:language_interface', 'theme', 'user']);
|
||||
}
|
||||
|
||||
}
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Utility\Xss;
|
||||
use Drupal\Core\Datetime\Entity\DateFormat;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
|
||||
/**
|
||||
* Implements hook_token_info().
|
||||
|
@ -87,7 +89,7 @@ function system_token_info() {
|
|||
/**
|
||||
* Implements hook_tokens().
|
||||
*/
|
||||
function system_tokens($type, $tokens, array $data = array(), array $options = array()) {
|
||||
function system_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
|
||||
$token_service = \Drupal::token();
|
||||
|
||||
$url_options = array('absolute' => TRUE);
|
||||
|
@ -106,29 +108,44 @@ function system_tokens($type, $tokens, array $data = array(), array $options = a
|
|||
foreach ($tokens as $name => $original) {
|
||||
switch ($name) {
|
||||
case 'name':
|
||||
$site_name = \Drupal::config('system.site')->get('name');
|
||||
$config = \Drupal::config('system.site');
|
||||
$bubbleable_metadata->addCacheableDependency($config);
|
||||
$site_name = $config->get('name');
|
||||
$replacements[$original] = $sanitize ? SafeMarkup::checkPlain($site_name) : $site_name;
|
||||
break;
|
||||
|
||||
case 'slogan':
|
||||
$slogan = \Drupal::config('system.site')->get('slogan');
|
||||
$config = \Drupal::config('system.site');
|
||||
$bubbleable_metadata->addCacheableDependency($config);
|
||||
$slogan = $config->get('slogan');
|
||||
$replacements[$original] = $sanitize ? Xss::filterAdmin($slogan) : $slogan;
|
||||
break;
|
||||
|
||||
case 'mail':
|
||||
$replacements[$original] = \Drupal::config('system.site')->get('mail');
|
||||
$config = \Drupal::config('system.site');
|
||||
$bubbleable_metadata->addCacheableDependency($config);
|
||||
$replacements[$original] = $config->get('mail');
|
||||
break;
|
||||
|
||||
case 'url':
|
||||
$replacements[$original] = \Drupal::url('<front>', array(), $url_options);
|
||||
/** @var \Drupal\Core\GeneratedUrl $result */
|
||||
$result = \Drupal::url('<front>', array(), $url_options, TRUE);
|
||||
$bubbleable_metadata->addCacheableDependency($result);
|
||||
$replacements[$original] = $result->getGeneratedUrl();
|
||||
break;
|
||||
|
||||
case 'url-brief':
|
||||
$replacements[$original] = preg_replace(array('!^https?://!', '!/$!'), '', \Drupal::url('<front>', array(), $url_options));
|
||||
/** @var \Drupal\Core\GeneratedUrl $result */
|
||||
$result = \Drupal::url('<front>', array(), $url_options, TRUE);
|
||||
$bubbleable_metadata->addCacheableDependency($result);
|
||||
$replacements[$original] = preg_replace(array('!^https?://!', '!/$!'), '', $result->getGeneratedUrl());
|
||||
break;
|
||||
|
||||
case 'login-url':
|
||||
$replacements[$original] = \Drupal::url('user.page', [], $url_options);
|
||||
/** @var \Drupal\Core\GeneratedUrl $result */
|
||||
$result = \Drupal::url('user.page', [], $url_options, TRUE);
|
||||
$bubbleable_metadata->addCacheableDependency($result);
|
||||
$replacements[$original] = $result->getGeneratedUrl();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -137,6 +154,9 @@ function system_tokens($type, $tokens, array $data = array(), array $options = a
|
|||
elseif ($type == 'date') {
|
||||
if (empty($data['date'])) {
|
||||
$date = REQUEST_TIME;
|
||||
// We depend on the current request time, so the tokens are not cacheable
|
||||
// at all.
|
||||
$bubbleable_metadata->setCacheMaxAge(0);
|
||||
}
|
||||
else {
|
||||
$date = $data['date'];
|
||||
|
@ -145,19 +165,26 @@ function system_tokens($type, $tokens, array $data = array(), array $options = a
|
|||
foreach ($tokens as $name => $original) {
|
||||
switch ($name) {
|
||||
case 'short':
|
||||
$date_format = DateFormat::load('short');
|
||||
$bubbleable_metadata->addCacheableDependency($date_format);
|
||||
$replacements[$original] = format_date($date, 'short', '', NULL, $langcode);
|
||||
break;
|
||||
|
||||
case 'medium':
|
||||
$date_format = DateFormat::load('medium');
|
||||
$bubbleable_metadata->addCacheableDependency($date_format);
|
||||
$replacements[$original] = format_date($date, 'medium', '', NULL, $langcode);
|
||||
break;
|
||||
|
||||
case 'long':
|
||||
$date_format = DateFormat::load('long');
|
||||
$bubbleable_metadata->addCacheableDependency($date_format);
|
||||
$replacements[$original] = format_date($date, 'long', '', NULL, $langcode);
|
||||
break;
|
||||
|
||||
case 'since':
|
||||
$replacements[$original] = \Drupal::service('date.formatter')->formatTimeDiffSince($date, array('langcode' => $langcode));
|
||||
$bubbleable_metadata->setCacheMaxAge(0);
|
||||
break;
|
||||
|
||||
case 'raw':
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\token_test\Controller\TestController.
|
||||
*/
|
||||
|
||||
namespace Drupal\token_test\Controller;
|
||||
|
||||
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\Core\Utility\Token;
|
||||
use Drupal\node\NodeInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a test controller for token replacement.
|
||||
*/
|
||||
class TestController extends ControllerBase {
|
||||
|
||||
/**
|
||||
* The token replacement system.
|
||||
*
|
||||
* @var \Drupal\Core\Utility\Token
|
||||
*/
|
||||
protected $token;
|
||||
|
||||
/**
|
||||
* Constructs a new TestController instance.
|
||||
*
|
||||
* @param \Drupal\Core\Utility\Token $token
|
||||
* The token replacement system.
|
||||
*/
|
||||
public function __construct(Token $token) {
|
||||
$this->token = $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static($container->get('token'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a token replacement with a node as well as the current user.
|
||||
*
|
||||
* This controller passes an explicit bubbleable metadata object to
|
||||
* $this->token->replace(), and applies the collected metadata to the render
|
||||
* array being built.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* The node.
|
||||
*
|
||||
* @return array
|
||||
* The render array.
|
||||
*/
|
||||
public function tokenReplace(NodeInterface $node) {
|
||||
$bubbleable_metadata = new BubbleableMetadata();
|
||||
$build['#markup'] = $this->token->replace('Tokens: [node:nid] [current-user:uid]', ['node' => $node], [], $bubbleable_metadata);
|
||||
$bubbleable_metadata->applyTo($build);
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a token replacement with a node as well as the current user.
|
||||
*
|
||||
* This controller is for testing the token service's fallback behavior of
|
||||
* applying collected metadata to the currently active render context when an
|
||||
* explicit bubbleable metadata object isn't passed in.
|
||||
*
|
||||
* @param \Drupal\node\NodeInterface $node
|
||||
* The node.
|
||||
*
|
||||
* @return array
|
||||
* The render array.
|
||||
*/
|
||||
public function tokenReplaceWithoutPassedBubbleableMetadata(NodeInterface $node) {
|
||||
$build['#markup'] = $this->token->replace('Tokens: [node:nid] [current-user:uid]', ['node' => $node], []);
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
name: Token test
|
||||
type: module
|
||||
core: 8.x
|
||||
package: Testing
|
||||
version: VERSION
|
||||
dependencies:
|
||||
- user
|
||||
- node
|
|
@ -0,0 +1,12 @@
|
|||
token_test.test:
|
||||
path: token-test/{node}
|
||||
defaults:
|
||||
_controller: Drupal\token_test\Controller\TestController::tokenReplace
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
token_test.test_without_bubbleable_metadata:
|
||||
path: token-test-without-bubleable-metadata/{node}
|
||||
defaults:
|
||||
_controller: Drupal\token_test\Controller\TestController::tokenReplaceWithoutPassedBubbleableMetadata
|
||||
requirements:
|
||||
_access: 'TRUE'
|
|
@ -10,6 +10,7 @@ namespace Drupal\taxonomy\Tests;
|
|||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Utility\Xss;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
|
||||
/**
|
||||
* Generates text using placeholders for dummy content to check taxonomy token
|
||||
|
@ -91,10 +92,26 @@ class TokenReplaceTest extends TaxonomyTestBase {
|
|||
$tests['[term:node-count]'] = 0;
|
||||
$tests['[term:parent:name]'] = '[term:parent:name]';
|
||||
$tests['[term:vocabulary:name]'] = SafeMarkup::checkPlain($this->vocabulary->label());
|
||||
$tests['[term:vocabulary]'] = SafeMarkup::checkPlain($this->vocabulary->label());
|
||||
|
||||
$base_bubbleable_metadata = BubbleableMetadata::createFromObject($term1);
|
||||
|
||||
$metadata_tests = array();
|
||||
$metadata_tests['[term:tid]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[term:name]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[term:description]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[term:url]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[term:node-count]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[term:parent:name]'] = $base_bubbleable_metadata;
|
||||
$bubbleable_metadata = clone $base_bubbleable_metadata;
|
||||
$metadata_tests['[term:vocabulary:name]'] = $bubbleable_metadata->addCacheTags($this->vocabulary->getCacheTags());
|
||||
$metadata_tests['[term:vocabulary]'] = $bubbleable_metadata->addCacheTags($this->vocabulary->getCacheTags());
|
||||
|
||||
foreach ($tests as $input => $expected) {
|
||||
$output = $token_service->replace($input, array('term' => $term1), array('langcode' => $language_interface->getId()));
|
||||
$bubbleable_metadata = new BubbleableMetadata();
|
||||
$output = $token_service->replace($input, array('term' => $term1), array('langcode' => $language_interface->getId()), $bubbleable_metadata);
|
||||
$this->assertEqual($output, $expected, format_string('Sanitized taxonomy term token %token replaced.', array('%token' => $input)));
|
||||
$this->assertEqual($bubbleable_metadata, $metadata_tests[$input]);
|
||||
}
|
||||
|
||||
// Generate and test sanitized tokens for term2.
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Utility\Xss;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\taxonomy\Entity\Vocabulary;
|
||||
|
||||
/**
|
||||
|
@ -92,7 +93,7 @@ function taxonomy_token_info() {
|
|||
/**
|
||||
* Implements hook_tokens().
|
||||
*/
|
||||
function taxonomy_tokens($type, $tokens, array $data = array(), array $options = array()) {
|
||||
function taxonomy_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
|
||||
$token_service = \Drupal::token();
|
||||
|
||||
$replacements = array();
|
||||
|
@ -129,12 +130,14 @@ function taxonomy_tokens($type, $tokens, array $data = array(), array $options =
|
|||
|
||||
case 'vocabulary':
|
||||
$vocabulary = Vocabulary::load($term->bundle());
|
||||
$bubbleable_metadata->addCacheableDependency($vocabulary);
|
||||
$replacements[$original] = SafeMarkup::checkPlain($vocabulary->label());
|
||||
break;
|
||||
|
||||
case 'parent':
|
||||
if ($parents = $taxonomy_storage->loadParents($term->id())) {
|
||||
$parent = array_pop($parents);
|
||||
$bubbleable_metadata->addCacheableDependency($parent);
|
||||
$replacements[$original] = SafeMarkup::checkPlain($parent->getName());
|
||||
}
|
||||
break;
|
||||
|
@ -143,12 +146,12 @@ function taxonomy_tokens($type, $tokens, array $data = array(), array $options =
|
|||
|
||||
if ($vocabulary_tokens = $token_service->findWithPrefix($tokens, 'vocabulary')) {
|
||||
$vocabulary = Vocabulary::load($term->bundle());
|
||||
$replacements += $token_service->generate('vocabulary', $vocabulary_tokens, array('vocabulary' => $vocabulary), $options);
|
||||
$replacements += $token_service->generate('vocabulary', $vocabulary_tokens, array('vocabulary' => $vocabulary), $options, $bubbleable_metadata);
|
||||
}
|
||||
|
||||
if (($vocabulary_tokens = $token_service->findWithPrefix($tokens, 'parent')) && $parents = $taxonomy_storage->loadParents($term->id())) {
|
||||
$parent = array_pop($parents);
|
||||
$replacements += $token_service->generate('term', $vocabulary_tokens, array('term' => $parent), $options);
|
||||
$replacements += $token_service->generate('term', $vocabulary_tokens, array('term' => $parent), $options, $bubbleable_metadata);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
namespace Drupal\user\Tests;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\user\Entity\User;
|
||||
|
@ -66,15 +67,52 @@ class UserTokenReplaceTest extends WebTestBase {
|
|||
$tests['[user:created:short]'] = format_date($account->getCreatedTime(), 'short', '', NULL, $language_interface->getId());
|
||||
$tests['[current-user:name]'] = SafeMarkup::checkPlain(user_format_name($global_account));
|
||||
|
||||
$base_bubbleable_metadata = BubbleableMetadata::createFromObject($account);
|
||||
$metadata_tests = [];
|
||||
$metadata_tests['[user:uid]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[user:name]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[user:mail]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[user:url]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[user:edit-url]'] = $base_bubbleable_metadata;
|
||||
$bubbleable_metadata = clone $base_bubbleable_metadata;
|
||||
$metadata_tests['[user:last-login]'] = $bubbleable_metadata->addCacheTags(['rendered']);
|
||||
$metadata_tests['[user:last-login:short]'] = $bubbleable_metadata;
|
||||
$metadata_tests['[user:created]'] = $bubbleable_metadata;
|
||||
$metadata_tests['[user:created:short]'] = $bubbleable_metadata;
|
||||
$metadata_tests['[current-user:name]'] = $base_bubbleable_metadata->merge(BubbleableMetadata::createFromObject($global_account)->addCacheContexts(['user']));
|
||||
|
||||
// Test to make sure that we generated something for each token.
|
||||
$this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
|
||||
|
||||
foreach ($tests as $input => $expected) {
|
||||
$output = $token_service->replace($input, array('user' => $account), array('langcode' => $language_interface->getId()));
|
||||
$bubbleable_metadata = new BubbleableMetadata();
|
||||
$output = $token_service->replace($input, array('user' => $account), array('langcode' => $language_interface->getId()), $bubbleable_metadata);
|
||||
$this->assertEqual($output, $expected, format_string('Sanitized user token %token replaced.', array('%token' => $input)));
|
||||
$this->assertEqual($bubbleable_metadata, $metadata_tests[$input]);
|
||||
}
|
||||
|
||||
// Generate tokens for the anonymous user.
|
||||
$anonymous_user = User::load(0);
|
||||
$tests = [];
|
||||
$tests['[user:uid]'] = t('not yet assigned');
|
||||
$tests['[user:name]'] = SafeMarkup::checkPlain(user_format_name($anonymous_user));
|
||||
|
||||
$base_bubbleable_metadata = BubbleableMetadata::createFromObject($anonymous_user);
|
||||
$metadata_tests = [];
|
||||
$metadata_tests['[user:uid]'] = $base_bubbleable_metadata;
|
||||
$bubbleable_metadata = clone $base_bubbleable_metadata;
|
||||
$bubbleable_metadata->addCacheableDependency(\Drupal::config('user.settings'));
|
||||
$metadata_tests['[user:name]'] = $bubbleable_metadata;
|
||||
|
||||
foreach ($tests as $input => $expected) {
|
||||
$bubbleable_metadata = new BubbleableMetadata();
|
||||
$output = $token_service->replace($input, array('user' => $anonymous_user), array('langcode' => $language_interface->getId()), $bubbleable_metadata);
|
||||
$this->assertEqual($output, $expected, format_string('Sanitized user token %token replaced.', array('%token' => $input)));
|
||||
$this->assertEqual($bubbleable_metadata, $metadata_tests[$input]);
|
||||
}
|
||||
|
||||
// Generate and test unsanitized tokens.
|
||||
$tests = [];
|
||||
$tests['[user:name]'] = user_format_name($account);
|
||||
$tests['[user:mail]'] = $account->getEmail();
|
||||
$tests['[current-user:name]'] = user_format_name($global_account);
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
*/
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Datetime\Entity\DateFormat;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\user\Entity\User;
|
||||
|
||||
/**
|
||||
|
@ -64,7 +66,7 @@ function user_token_info() {
|
|||
/**
|
||||
* Implements hook_tokens().
|
||||
*/
|
||||
function user_tokens($type, $tokens, array $data = array(), array $options = array()) {
|
||||
function user_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
|
||||
|
||||
$token_service = \Drupal::token();
|
||||
$url_options = array('absolute' => TRUE);
|
||||
|
@ -80,6 +82,7 @@ function user_tokens($type, $tokens, array $data = array(), array $options = arr
|
|||
$replacements = array();
|
||||
|
||||
if ($type == 'user' && !empty($data['user'])) {
|
||||
/** @var \Drupal\user\UserInterface $account */
|
||||
$account = $data['user'];
|
||||
foreach ($tokens as $name => $original) {
|
||||
switch ($name) {
|
||||
|
@ -91,6 +94,9 @@ function user_tokens($type, $tokens, array $data = array(), array $options = arr
|
|||
|
||||
case 'name':
|
||||
$name = user_format_name($account);
|
||||
if ($account->isAnonymous()) {
|
||||
$bubbleable_metadata->addCacheableDependency(\Drupal::config('user.settings'));
|
||||
}
|
||||
$replacements[$original] = $sanitize ? SafeMarkup::checkPlain($name) : $name;
|
||||
break;
|
||||
|
||||
|
@ -108,10 +114,14 @@ function user_tokens($type, $tokens, array $data = array(), array $options = arr
|
|||
|
||||
// These tokens are default variations on the chained tokens handled below.
|
||||
case 'last-login':
|
||||
$date_format = DateFormat::load('medium');
|
||||
$bubbleable_metadata->addCacheableDependency($date_format);
|
||||
$replacements[$original] = $account->getLastLoginTime() ? format_date($account->getLastLoginTime(), 'medium', '', NULL, $langcode) : t('never');
|
||||
break;
|
||||
|
||||
case 'created':
|
||||
$date_format = DateFormat::load('medium');
|
||||
$bubbleable_metadata->addCacheableDependency($date_format);
|
||||
// In the case of user_presave the created date may not yet be set.
|
||||
$replacements[$original] = $account->getCreatedTime() ? format_date($account->getCreatedTime(), 'medium', '', NULL, $langcode) : t('not yet created');
|
||||
break;
|
||||
|
@ -119,17 +129,18 @@ function user_tokens($type, $tokens, array $data = array(), array $options = arr
|
|||
}
|
||||
|
||||
if ($login_tokens = $token_service->findWithPrefix($tokens, 'last-login')) {
|
||||
$replacements += $token_service->generate('date', $login_tokens, array('date' => $account->getLastLoginTime()), $options);
|
||||
$replacements += $token_service->generate('date', $login_tokens, array('date' => $account->getLastLoginTime()), $options, $bubbleable_metadata);
|
||||
}
|
||||
|
||||
if ($registered_tokens = $token_service->findWithPrefix($tokens, 'created')) {
|
||||
$replacements += $token_service->generate('date', $registered_tokens, array('date' => $account->getCreatedTime()), $options);
|
||||
$replacements += $token_service->generate('date', $registered_tokens, array('date' => $account->getCreatedTime()), $options, $bubbleable_metadata);
|
||||
}
|
||||
}
|
||||
|
||||
if ($type == 'current-user') {
|
||||
$account = User::load(\Drupal::currentUser()->id());
|
||||
$replacements += $token_service->generate('user', $tokens, array('user' => $account), $options);
|
||||
$bubbleable_metadata->addCacheContexts(['user']);
|
||||
$replacements += $token_service->generate('user', $tokens, array('user' => $account), $options, $bubbleable_metadata);
|
||||
}
|
||||
|
||||
return $replacements;
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
namespace Drupal\views\Tests;
|
||||
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\views\Views;
|
||||
|
||||
/**
|
||||
|
@ -54,9 +55,25 @@ class TokenReplaceTest extends ViewUnitTestBase {
|
|||
'[view:page-count]' => '1',
|
||||
);
|
||||
|
||||
$base_bubbleable_metadata = BubbleableMetadata::createFromObject($view->storage);
|
||||
$metadata_tests = [];
|
||||
$metadata_tests['[view:label]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[view:description]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[view:id]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[view:title]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[view:url]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[view:total-rows]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[view:base-table]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[view:base-field]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[view:items-per-page]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[view:current-page]'] = $base_bubbleable_metadata;
|
||||
$metadata_tests['[view:page-count]'] = $base_bubbleable_metadata;
|
||||
|
||||
foreach ($expected as $token => $expected_output) {
|
||||
$output = $token_handler->replace($token, array('view' => $view));
|
||||
$bubbleable_metadata = new BubbleableMetadata();
|
||||
$output = $token_handler->replace($token, array('view' => $view), [], $bubbleable_metadata);
|
||||
$this->assertIdentical($output, $expected_output, format_string('Token %token replaced correctly.', array('%token' => $token)));
|
||||
$this->assertEqual($bubbleable_metadata, $metadata_tests[$token]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
|
||||
/**
|
||||
* Implements hook_token_info().
|
||||
|
@ -68,9 +69,7 @@ function views_token_info() {
|
|||
/**
|
||||
* Implements hook_tokens().
|
||||
*/
|
||||
function views_tokens($type, $tokens, array $data = array(), array $options = array()) {
|
||||
$token_service = \Drupal::token();
|
||||
|
||||
function views_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
|
||||
$url_options = array('absolute' => TRUE);
|
||||
if (isset($options['language'])) {
|
||||
$url_options['language'] = $options['language'];
|
||||
|
@ -83,6 +82,8 @@ function views_tokens($type, $tokens, array $data = array(), array $options = ar
|
|||
/** @var \Drupal\views\ViewExecutable $view */
|
||||
$view = $data['view'];
|
||||
|
||||
$bubbleable_metadata->addCacheableDependency($view->storage);
|
||||
|
||||
foreach ($tokens as $name => $original) {
|
||||
switch ($name) {
|
||||
case 'label':
|
||||
|
@ -104,7 +105,8 @@ function views_tokens($type, $tokens, array $data = array(), array $options = ar
|
|||
|
||||
case 'url':
|
||||
if ($url = $view->getUrl()) {
|
||||
$replacements[$original] = $url->setOptions($url_options)->toString();
|
||||
$replacements[$original] = $url->setOptions($url_options)
|
||||
->toString();
|
||||
}
|
||||
break;
|
||||
case 'base-table':
|
||||
|
@ -129,13 +131,6 @@ function views_tokens($type, $tokens, array $data = array(), array $options = ar
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// [view:url:*] nested tokens. This only works if Token module is installed.
|
||||
if ($url_tokens = $token_service->findWithPrefix($tokens, 'url')) {
|
||||
if ($path = $view->getUrl()) {
|
||||
$replacements += $token_service->generate('url', $url_tokens, array('path' => $url->getInternalPath()), $options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $replacements;
|
||||
|
|
|
@ -42,6 +42,28 @@ class CacheableMetadataTest extends UnitTestCase {
|
|||
$this->assertEquals($expected, $a->merge($b));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::addCacheableDependency
|
||||
* @dataProvider providerTestMerge
|
||||
*
|
||||
* This only tests at a high level, because it reuses existing logic. Detailed
|
||||
* tests exist for the existing logic:
|
||||
*
|
||||
* @see \Drupal\Tests\Core\Cache\CacheTest::testMergeTags()
|
||||
* @see \Drupal\Tests\Core\Cache\CacheTest::testMergeMaxAges()
|
||||
* @see \Drupal\Tests\Core\Cache\CacheContextsTest
|
||||
*/
|
||||
public function testAddCacheableDependency(CacheableMetadata $a, CacheableMetadata $b, CacheableMetadata $expected) {
|
||||
$cache_contexts_manager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$container = new ContainerBuilder();
|
||||
$container->set('cache_contexts_manager', $cache_contexts_manager);
|
||||
\Drupal::setContainer($container);
|
||||
|
||||
$this->assertEquals($expected, $a->addCacheableDependency($b));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides test data for testMerge().
|
||||
*
|
||||
|
|
|
@ -648,4 +648,52 @@ class BubbleableMetadataTest extends UnitTestCase {
|
|||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @covers ::addCacheableDependency
|
||||
* @dataProvider providerTestMerge
|
||||
*
|
||||
* This only tests at a high level, because it reuses existing logic. Detailed
|
||||
* tests exist for the existing logic:
|
||||
*
|
||||
* @see \Drupal\Tests\Core\Cache\CacheTest::testMergeTags()
|
||||
* @see \Drupal\Tests\Core\Cache\CacheTest::testMergeMaxAges()
|
||||
* @see \Drupal\Tests\Core\Cache\CacheContextsTest
|
||||
*/
|
||||
public function testAddCacheableDependency(BubbleableMetadata $a, $b, BubbleableMetadata $expected) {
|
||||
$cache_contexts_manager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$container = new ContainerBuilder();
|
||||
$container->set('cache_contexts_manager', $cache_contexts_manager);
|
||||
\Drupal::setContainer($container);
|
||||
|
||||
$this->assertEquals($expected, $a->addCacheableDependency($b));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides test data for testMerge().
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function providerTestAddCachableDependency() {
|
||||
return [
|
||||
// Merge in a cacheable metadata.
|
||||
'merge-cacheable-metadata' => [
|
||||
(new BubbleableMetadata())->setCacheContexts(['foo'])->setCacheTags(['foo'])->setCacheMaxAge(20),
|
||||
(new CacheableMetadata())->setCacheContexts(['bar'])->setCacheTags(['bar'])->setCacheMaxAge(60),
|
||||
(new BubbleableMetadata())->setCacheContexts(['foo', 'bar'])->setCacheTags(['foo', 'bar'])->setCacheMaxAge(20)
|
||||
],
|
||||
'merge-bubbleable-metadata' => [
|
||||
(new BubbleableMetadata())->setCacheContexts(['foo'])->setCacheTags(['foo'])->setCacheMaxAge(20)->setAttachments(['foo' => []]),
|
||||
(new BubbleableMetadata())->setCacheContexts(['bar'])->setCacheTags(['bar'])->setCacheMaxAge(60)->setAttachments(['bar' => []]),
|
||||
(new BubbleableMetadata())->setCacheContexts(['foo', 'bar'])->setCacheTags(['foo', 'bar'])->setCacheMaxAge(20)->setAttachments(['foo' => [], 'bar' => []])
|
||||
],
|
||||
'merge-attachments-metadata' => [
|
||||
(new BubbleableMetadata())->setAttachments(['foo' => []]),
|
||||
(new BubbleableMetadata())->setAttachments(['baro' => []]),
|
||||
(new BubbleableMetadata())->setAttachments(['foo' => [], 'bar' => []])
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,10 @@
|
|||
|
||||
namespace Drupal\Tests\Core\Utility;
|
||||
|
||||
use Drupal\Core\Cache\Context\CacheContextsManager;
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\Core\Utility\Token;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
|
@ -59,6 +62,20 @@ class TokenTest extends UnitTestCase {
|
|||
*/
|
||||
protected $cacheTagsInvalidator;
|
||||
|
||||
/**
|
||||
* The cache contexts manager.
|
||||
*
|
||||
* @var \Drupal\Core\Cache\Context\CacheContextsManager
|
||||
*/
|
||||
protected $cacheContextManager;
|
||||
|
||||
/**
|
||||
* The renderer.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RendererInterface|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@ -73,7 +90,17 @@ class TokenTest extends UnitTestCase {
|
|||
|
||||
$this->cacheTagsInvalidator = $this->getMock('\Drupal\Core\Cache\CacheTagsInvalidatorInterface');
|
||||
|
||||
$this->token = new Token($this->moduleHandler, $this->cache, $this->languageManager, $this->cacheTagsInvalidator);
|
||||
$this->renderer = $this->getMock('Drupal\Core\Render\RendererInterface');
|
||||
|
||||
$this->token = new Token($this->moduleHandler, $this->cache, $this->languageManager, $this->cacheTagsInvalidator, $this->renderer);
|
||||
|
||||
$container = new ContainerBuilder();
|
||||
$this->cacheContextManager = new CacheContextsManager($container, [
|
||||
'current_user',
|
||||
'custom_context'
|
||||
]);
|
||||
$container->set('cache_contexts_manager', $this->cacheContextManager);
|
||||
\Drupal::setContainer($container);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -122,6 +149,108 @@ class TokenTest extends UnitTestCase {
|
|||
$this->token->getInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::replace
|
||||
*/
|
||||
public function testReplaceWithBubbleableMetadataObject() {
|
||||
$this->moduleHandler->expects($this->any())
|
||||
->method('invokeAll')
|
||||
->willReturn(['[node:title]' => 'hello world']);
|
||||
|
||||
$bubbleable_metadata = new BubbleableMetadata();
|
||||
$bubbleable_metadata->setCacheContexts(['current_user']);
|
||||
$bubbleable_metadata->setCacheMaxAge(12);
|
||||
|
||||
$node = $this->prophesize('Drupal\node\NodeInterface');
|
||||
$node->getCacheTags()->willReturn(['node:1']);
|
||||
$node->getCacheContexts()->willReturn(['custom_context']);
|
||||
$node->getCacheMaxAge()->willReturn(10);
|
||||
$node = $node->reveal();
|
||||
|
||||
$result = $this->token->replace('[node:title]', ['node' => $node], [], $bubbleable_metadata);
|
||||
$this->assertEquals('hello world', $result);
|
||||
|
||||
$this->assertEquals(['node:1'], $bubbleable_metadata->getCacheTags());
|
||||
$this->assertEquals([
|
||||
'current_user',
|
||||
'custom_context'
|
||||
], $bubbleable_metadata->getCacheContexts());
|
||||
$this->assertEquals(10, $bubbleable_metadata->getCacheMaxAge());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::replace
|
||||
*/
|
||||
public function testReplaceWithHookTokensWithBubbleableMetadata() {
|
||||
$this->moduleHandler->expects($this->any())
|
||||
->method('invokeAll')
|
||||
->willReturnCallback(function ($hook_name, $args) {
|
||||
$cacheable_metadata = $args[4];
|
||||
$cacheable_metadata->addCacheContexts(['custom_context']);
|
||||
$cacheable_metadata->addCacheTags(['node:1']);
|
||||
$cacheable_metadata->setCacheMaxAge(10);
|
||||
|
||||
return ['[node:title]' => 'hello world'];
|
||||
});
|
||||
|
||||
$node = $this->prophesize('Drupal\node\NodeInterface');
|
||||
$node->getCacheContexts()->willReturn([]);
|
||||
$node->getCacheTags()->willReturn([]);
|
||||
$node->getCacheMaxAge()->willReturn(14);
|
||||
$node = $node->reveal();
|
||||
|
||||
$bubbleable_metadata = new BubbleableMetadata();
|
||||
$bubbleable_metadata->setCacheContexts(['current_user']);
|
||||
$bubbleable_metadata->setCacheMaxAge(12);
|
||||
|
||||
$result = $this->token->replace('[node:title]', ['node' => $node], [], $bubbleable_metadata);
|
||||
$this->assertEquals('hello world', $result);
|
||||
$this->assertEquals(['node:1'], $bubbleable_metadata->getCacheTags());
|
||||
$this->assertEquals([
|
||||
'current_user',
|
||||
'custom_context'
|
||||
], $bubbleable_metadata->getCacheContexts());
|
||||
$this->assertEquals(10, $bubbleable_metadata->getCacheMaxAge());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::replace
|
||||
* @covers ::replace
|
||||
*/
|
||||
public function testReplaceWithHookTokensAlterWithBubbleableMetadata() {
|
||||
$this->moduleHandler->expects($this->any())
|
||||
->method('invokeAll')
|
||||
->willReturn([]);
|
||||
|
||||
$this->moduleHandler->expects($this->any())
|
||||
->method('alter')
|
||||
->willReturnCallback(function ($hook_name, array &$replacements, array $context, BubbleableMetadata $bubbleable_metadata) {
|
||||
$replacements['[node:title]'] = 'hello world';
|
||||
$bubbleable_metadata->addCacheContexts(['custom_context']);
|
||||
$bubbleable_metadata->addCacheTags(['node:1']);
|
||||
$bubbleable_metadata->setCacheMaxAge(10);
|
||||
});
|
||||
|
||||
$node = $this->prophesize('Drupal\node\NodeInterface');
|
||||
$node->getCacheContexts()->willReturn([]);
|
||||
$node->getCacheTags()->willReturn([]);
|
||||
$node->getCacheMaxAge()->willReturn(14);
|
||||
$node = $node->reveal();
|
||||
|
||||
$bubbleable_metadata = new BubbleableMetadata();
|
||||
$bubbleable_metadata->setCacheContexts(['current_user']);
|
||||
$bubbleable_metadata->setCacheMaxAge(12);
|
||||
|
||||
$result = $this->token->replace('[node:title]', ['node' => $node], [], $bubbleable_metadata);
|
||||
$this->assertEquals('hello world', $result);
|
||||
$this->assertEquals(['node:1'], $bubbleable_metadata->getCacheTags());
|
||||
$this->assertEquals([
|
||||
'current_user',
|
||||
'custom_context'
|
||||
], $bubbleable_metadata->getCacheContexts());
|
||||
$this->assertEquals(10, $bubbleable_metadata->getCacheMaxAge());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::resetInfo
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue