Commit de0bbdd9 authored by alexpott's avatar alexpott

Revert "Issue #2567257 by dawehner, stefan.r, effulgentsia, pwolanin, catch,...

Revert "Issue #2567257 by dawehner, stefan.r, effulgentsia, pwolanin, catch, Xano, mr.baileys, Wim Leers, k4v, Dave Reid, chx, googletorp, plach, lauriii, Berdir, webchick, alexpott, stefan.r: hook_tokens() $sanitize option incompatible with Html sanitisation requirements"

This reverts commit 542a1663.
parent 542a1663
...@@ -7,8 +7,6 @@ ...@@ -7,8 +7,6 @@
namespace Drupal\Core\Utility; namespace Drupal\Core\Utility;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableDependencyInterface; use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Cache\CacheBackendInterface;
...@@ -143,9 +141,7 @@ public function __construct(ModuleHandlerInterface $module_handler, CacheBackend ...@@ -143,9 +141,7 @@ public function __construct(ModuleHandlerInterface $module_handler, CacheBackend
* Replaces all tokens in a given string with appropriate values. * Replaces all tokens in a given string with appropriate values.
* *
* @param string $text * @param string $text
* An HTML string containing replaceable tokens. The caller is responsible * A string potentially containing replaceable tokens.
* for calling \Drupal\Component\Utility\Html::escape() in case the $text
* was plain text.
* @param array $data * @param array $data
* (optional) An array of keyed objects. For simple replacement scenarios * (optional) An array of keyed objects. For simple replacement scenarios
* 'node', 'user', and others are common keys, with an accompanying node or * 'node', 'user', and others are common keys, with an accompanying node or
...@@ -158,9 +154,18 @@ public function __construct(ModuleHandlerInterface $module_handler, CacheBackend ...@@ -158,9 +154,18 @@ public function __construct(ModuleHandlerInterface $module_handler, CacheBackend
* - langcode: A language code to be used when generating locale-sensitive * - langcode: A language code to be used when generating locale-sensitive
* tokens. * tokens.
* - callback: A callback function that will be used to post-process the * - callback: A callback function that will be used to post-process the
* array of token replacements after they are generated. * array of token replacements after they are generated. For example, a
* module using tokens in a text-only email might provide a callback to
* strip HTML entities from token values before they are inserted into the
* final text.
* - clear: A boolean flag indicating that tokens should be removed from the * - clear: A boolean flag indicating that tokens should be removed from the
* final text if no replacement value can be generated. * final text if no replacement value can be generated.
* - sanitize: A boolean flag indicating that tokens should be sanitized for
* display to a web browser. Defaults to TRUE. Developers who set this
* option to FALSE assume responsibility for running
* \Drupal\Component\Utility\Xss::filter(),
* \Drupal\Component\Utility\Html::escape() or other appropriate scrubbing
* functions before displaying data to users.
* @param \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata|null * @param \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata|null
* (optional) An object to which static::generate() and the hooks and * (optional) An object to which static::generate() and the hooks and
* functions that it invokes will add their required bubbleable metadata. * functions that it invokes will add their required bubbleable metadata.
...@@ -180,13 +185,7 @@ public function __construct(ModuleHandlerInterface $module_handler, CacheBackend ...@@ -180,13 +185,7 @@ public function __construct(ModuleHandlerInterface $module_handler, CacheBackend
* Renderer's currently active render context. * Renderer's currently active render context.
* *
* @return string * @return string
* The token result is the entered HTML text with tokens replaced. The * Text with tokens replaced.
* caller is responsible for choosing the right escaping / sanitization. If
* the result is intended to be used as plain text, the usage of
* PlainTextOutput::renderFromHtml() is recommended. If the result is just
* printed as part of a template relying on Twig autoescaping is possible,
* otherwise for example the result can be put into #markup, in which case
* it would be sanitized by Xss::filterAdmin().
*/ */
public function replace($text, array $data = array(), array $options = array(), BubbleableMetadata $bubbleable_metadata = NULL) { public function replace($text, array $data = array(), array $options = array(), BubbleableMetadata $bubbleable_metadata = NULL) {
$text_tokens = $this->scan($text); $text_tokens = $this->scan($text);
...@@ -205,11 +204,6 @@ public function replace($text, array $data = array(), array $options = array(), ...@@ -205,11 +204,6 @@ public function replace($text, array $data = array(), array $options = array(),
} }
} }
// Escape the tokens, unless they are explicitly markup.
foreach ($replacements as $token => $value) {
$replacements[$token] = SafeMarkup::isSafe($value) ? $value : Html::escape($value);
}
// Optionally alter the list of replacement values. // Optionally alter the list of replacement values.
if (!empty($options['callback'])) { if (!empty($options['callback'])) {
$function = $options['callback']; $function = $options['callback'];
...@@ -288,6 +282,11 @@ public function scan($text) { ...@@ -288,6 +282,11 @@ public function scan($text) {
* array of token replacements after they are generated. Can be used when * array of token replacements after they are generated. Can be used when
* modules require special formatting of token text, for example URL * modules require special formatting of token text, for example URL
* encoding or truncation to a specific length. * encoding or truncation to a specific length.
* - sanitize: A boolean flag indicating that tokens should be sanitized for
* display to a web browser. Developers who set this option to FALSE assume
* responsibility for running \Drupal\Component\Utility\Xss::filter(),
* \Drupal\Component\Utility\Html::escape() or other appropriate scrubbing
* functions before displaying data to users.
* @param \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata * @param \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata
* The bubbleable metadata. This is passed to the token replacement * The bubbleable metadata. This is passed to the token replacement
* implementations so that they can attach their metadata. * implementations so that they can attach their metadata.
...@@ -301,6 +300,8 @@ public function scan($text) { ...@@ -301,6 +300,8 @@ public function scan($text) {
* @see hook_tokens_alter() * @see hook_tokens_alter()
*/ */
public function generate($type, array $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) { public function generate($type, array $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
$options += array('sanitize' => TRUE);
foreach ($data as $object) { foreach ($data as $object) {
if ($object instanceof CacheableDependencyInterface || $object instanceof AttachmentsInterface) { if ($object instanceof CacheableDependencyInterface || $object instanceof AttachmentsInterface) {
$bubbleable_metadata->addCacheableDependency($object); $bubbleable_metadata->addCacheableDependency($object);
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
* Hooks related to the Token system. * Hooks related to the Token system.
*/ */
use Drupal\Component\Utility\Html;
use Drupal\user\Entity\User; use Drupal\user\Entity\User;
/** /**
...@@ -64,9 +65,7 @@ ...@@ -64,9 +65,7 @@
* *
* @return array * @return array
* An associative array of replacement values, keyed by the raw [type:token] * An associative array of replacement values, keyed by the raw [type:token]
* strings from the original text. The returned values must be either plain * strings from the original text.
* text strings, or an object implementing SafeStringInterface if they are
* HTML-formatted.
* *
* @see hook_token_info() * @see hook_token_info()
* @see hook_tokens_alter() * @see hook_tokens_alter()
...@@ -82,6 +81,8 @@ function hook_tokens($type, $tokens, array $data, array $options, \Drupal\Core\R ...@@ -82,6 +81,8 @@ function hook_tokens($type, $tokens, array $data, array $options, \Drupal\Core\R
else { else {
$langcode = NULL; $langcode = NULL;
} }
$sanitize = !empty($options['sanitize']);
$replacements = array(); $replacements = array();
if ($type == 'node' && !empty($data['node'])) { if ($type == 'node' && !empty($data['node'])) {
...@@ -96,7 +97,7 @@ function hook_tokens($type, $tokens, array $data, array $options, \Drupal\Core\R ...@@ -96,7 +97,7 @@ function hook_tokens($type, $tokens, array $data, array $options, \Drupal\Core\R
break; break;
case 'title': case 'title':
$replacements[$original] = $node->getTitle(); $replacements[$original] = $sanitize ? Html::escape($node->getTitle()) : $node->getTitle();
break; break;
case 'edit-url': case 'edit-url':
...@@ -106,7 +107,7 @@ function hook_tokens($type, $tokens, array $data, array $options, \Drupal\Core\R ...@@ -106,7 +107,7 @@ function hook_tokens($type, $tokens, array $data, array $options, \Drupal\Core\R
// Default values for the chained tokens handled below. // Default values for the chained tokens handled below.
case 'author': case 'author':
$account = $node->getOwner() ? $node->getOwner() : User::load(0); $account = $node->getOwner() ? $node->getOwner() : User::load(0);
$replacements[$original] = $account->label(); $replacements[$original] = $sanitize ? Html::escape($account->label()) : $account->label();
$bubbleable_metadata->addCacheableDependency($account); $bubbleable_metadata->addCacheableDependency($account);
break; break;
......
...@@ -7,7 +7,6 @@ ...@@ -7,7 +7,6 @@
namespace Drupal\action\Plugin\Action; namespace Drupal\action\Plugin\Action;
use Drupal\Component\Utility\PlainTextOutput;
use Drupal\Core\Access\AccessResult; use Drupal\Core\Access\AccessResult;
use Drupal\Core\Action\ConfigurableActionBase; use Drupal\Core\Action\ConfigurableActionBase;
use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Entity\EntityManagerInterface;
...@@ -128,7 +127,7 @@ public function execute($entity = NULL) { ...@@ -128,7 +127,7 @@ public function execute($entity = NULL) {
$this->configuration['node'] = $entity; $this->configuration['node'] = $entity;
} }
$recipient = PlainTextOutput::renderFromHtml($this->token->replace($this->configuration['recipient'], $this->configuration)); $recipient = $this->token->replace($this->configuration['recipient'], $this->configuration);
// If the recipient is a registered user with a language preference, use // If the recipient is a registered user with a language preference, use
// the recipient's preferred language. Otherwise, use the system default // the recipient's preferred language. Otherwise, use the system default
......
...@@ -7,11 +7,11 @@ ...@@ -7,11 +7,11 @@
namespace Drupal\action\Plugin\Action; namespace Drupal\action\Plugin\Action;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Access\AccessResult; use Drupal\Core\Access\AccessResult;
use Drupal\Core\Action\ConfigurableActionBase; use Drupal\Core\Action\ConfigurableActionBase;
use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Session\AccountInterface; use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Utility\Token; use Drupal\Core\Utility\Token;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
...@@ -32,33 +32,20 @@ class MessageAction extends ConfigurableActionBase implements ContainerFactoryPl ...@@ -32,33 +32,20 @@ class MessageAction extends ConfigurableActionBase implements ContainerFactoryPl
*/ */
protected $token; protected $token;
/**
* The renderer.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/** /**
* Constructs a MessageAction object. * Constructs a MessageAction object.
*
* @param \Drupal\Core\Utility\Token $token
* The token replacement service.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer.
*/ */
public function __construct(array $configuration, $plugin_id, $plugin_definition, Token $token, RendererInterface $renderer) { public function __construct(array $configuration, $plugin_id, $plugin_definition, Token $token) {
parent::__construct($configuration, $plugin_id, $plugin_definition); parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->token = $token; $this->token = $token;
$this->renderer = $renderer;
} }
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($configuration, $plugin_id, $plugin_definition, $container->get('token'), $container->get('renderer')); return new static($configuration, $plugin_id, $plugin_definition, $container->get('token'));
} }
/** /**
...@@ -68,12 +55,8 @@ public function execute($entity = NULL) { ...@@ -68,12 +55,8 @@ public function execute($entity = NULL) {
if (empty($this->configuration['node'])) { if (empty($this->configuration['node'])) {
$this->configuration['node'] = $entity; $this->configuration['node'] = $entity;
} }
$message = $this->token->replace($this->configuration['message'], $this->configuration); $message = $this->token->replace(Xss::filterAdmin($this->configuration['message']), $this->configuration);
$build = [ drupal_set_message($message);
'#markup' => $message,
];
drupal_set_message($this->renderer->renderPlain($build));
} }
/** /**
......
...@@ -5,7 +5,9 @@ ...@@ -5,7 +5,9 @@
* Builds placeholder replacement tokens for comment-related data. * Builds placeholder replacement tokens for comment-related data.
*/ */
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\UrlHelper; use Drupal\Component\Utility\UrlHelper;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Datetime\Entity\DateFormat; use Drupal\Core\Datetime\Entity\DateFormat;
use Drupal\Core\Render\BubbleableMetadata; use Drupal\Core\Render\BubbleableMetadata;
...@@ -117,6 +119,8 @@ function comment_tokens($type, $tokens, array $data, array $options, BubbleableM ...@@ -117,6 +119,8 @@ function comment_tokens($type, $tokens, array $data, array $options, BubbleableM
else { else {
$langcode = NULL; $langcode = NULL;
} }
$sanitize = !empty($options['sanitize']);
$replacements = array(); $replacements = array();
if ($type == 'comment' && !empty($data['comment'])) { if ($type == 'comment' && !empty($data['comment'])) {
...@@ -132,7 +136,7 @@ function comment_tokens($type, $tokens, array $data, array $options, BubbleableM ...@@ -132,7 +136,7 @@ function comment_tokens($type, $tokens, array $data, array $options, BubbleableM
// Poster identity information for comments. // Poster identity information for comments.
case 'hostname': case 'hostname':
$replacements[$original] = $comment->getHostname(); $replacements[$original] = $sanitize ? Html::escape($comment->getHostname()) : $comment->getHostname();
break; break;
case 'mail': case 'mail':
...@@ -142,25 +146,23 @@ function comment_tokens($type, $tokens, array $data, array $options, BubbleableM ...@@ -142,25 +146,23 @@ function comment_tokens($type, $tokens, array $data, array $options, BubbleableM
if ($comment->getOwnerId()) { if ($comment->getOwnerId()) {
$bubbleable_metadata->addCacheableDependency($comment->getOwner()); $bubbleable_metadata->addCacheableDependency($comment->getOwner());
} }
$replacements[$original] = $mail; $replacements[$original] = $sanitize ? Html::escape($mail) : $mail;
break; break;
case 'homepage': case 'homepage':
$replacements[$original] = UrlHelper::stripDangerousProtocols($comment->getHomepage()); $replacements[$original] = $sanitize ? UrlHelper::filterBadProtocol($comment->getHomepage()) : $comment->getHomepage();
break; break;
case 'title': case 'title':
$replacements[$original] = $comment->getSubject(); $replacements[$original] = $sanitize ? Html::escape($comment->getSubject()) : $comment->getSubject();
break; break;
case 'body': case 'body':
// "processed" returns a \Drupal\Component\Utility\SafeStringInterface $replacements[$original] = $sanitize ? $comment->comment_body->processed : $comment->comment_body->value;
// via check_markup().
$replacements[$original] = $comment->comment_body->processed;
break; break;
case 'langcode': case 'langcode':
$replacements[$original] = $comment->language()->getId(); $replacements[$original] = $sanitize ? Html::escape($comment->language()->getId()) : $comment->language()->getId();
break; break;
// Comment related URLs. // Comment related URLs.
...@@ -181,14 +183,14 @@ function comment_tokens($type, $tokens, array $data, array $options, BubbleableM ...@@ -181,14 +183,14 @@ function comment_tokens($type, $tokens, array $data, array $options, BubbleableM
if ($comment->getOwnerId()) { if ($comment->getOwnerId()) {
$bubbleable_metadata->addCacheableDependency($comment->getOwner()); $bubbleable_metadata->addCacheableDependency($comment->getOwner());
} }
$replacements[$original] = $name; $replacements[$original] = $sanitize ? Html::escape($name) : $name;
break; break;
case 'parent': case 'parent':
if ($comment->hasParentComment()) { if ($comment->hasParentComment()) {
$parent = $comment->getParentComment(); $parent = $comment->getParentComment();
$bubbleable_metadata->addCacheableDependency($parent); $bubbleable_metadata->addCacheableDependency($parent);
$replacements[$original] = $parent->getSubject(); $replacements[$original] = $sanitize ? Html::escape($parent->getSubject()) : $parent->getSubject();
} }
break; break;
...@@ -208,7 +210,7 @@ function comment_tokens($type, $tokens, array $data, array $options, BubbleableM ...@@ -208,7 +210,7 @@ function comment_tokens($type, $tokens, array $data, array $options, BubbleableM
$entity = $comment->getCommentedEntity(); $entity = $comment->getCommentedEntity();
$bubbleable_metadata->addCacheableDependency($entity); $bubbleable_metadata->addCacheableDependency($entity);
$title = $entity->label(); $title = $entity->label();
$replacements[$original] = $title; $replacements[$original] = $sanitize ? Html::escape($title) : $title;
break; break;
} }
} }
......
...@@ -7,7 +7,6 @@ ...@@ -7,7 +7,6 @@
namespace Drupal\comment\Tests; namespace Drupal\comment\Tests;
use Drupal\Component\Utility\FormattableString;
use Drupal\Component\Utility\Html; use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\UrlHelper; use Drupal\Component\Utility\UrlHelper;
use Drupal\Component\Utility\Xss; use Drupal\Component\Utility\Xss;
...@@ -33,16 +32,13 @@ function testCommentTokenReplacement() { ...@@ -33,16 +32,13 @@ function testCommentTokenReplacement() {
'language' => $language_interface, 'language' => $language_interface,
); );
// Change the title of the admin user.
$this->adminUser->name->value = 'This is a title with some special & > " stuff.';
$this->adminUser->save();
$this->drupalLogin($this->adminUser); $this->drupalLogin($this->adminUser);
// Set comment variables. // Set comment variables.
$this->setCommentSubject(TRUE); $this->setCommentSubject(TRUE);
// Create a node and a comment. // Create a node and a comment.
$node = $this->drupalCreateNode(['type' => 'article', 'title' => '<script>alert("123")</script>']); $node = $this->drupalCreateNode(array('type' => 'article'));
$parent_comment = $this->postComment($node, $this->randomMachineName(), $this->randomMachineName(), TRUE); $parent_comment = $this->postComment($node, $this->randomMachineName(), $this->randomMachineName(), TRUE);
// Post a reply to the comment. // Post a reply to the comment.
...@@ -54,29 +50,29 @@ function testCommentTokenReplacement() { ...@@ -54,29 +50,29 @@ function testCommentTokenReplacement() {
// Add HTML to ensure that sanitation of some fields tested directly. // Add HTML to ensure that sanitation of some fields tested directly.
$comment->setSubject('<blink>Blinking Comment</blink>'); $comment->setSubject('<blink>Blinking Comment</blink>');
// Generate and test tokens. // Generate and test sanitized tokens.
$tests = array(); $tests = array();
$tests['[comment:cid]'] = $comment->id(); $tests['[comment:cid]'] = $comment->id();
$tests['[comment:hostname]'] = $comment->getHostname(); $tests['[comment:hostname]'] = Html::escape($comment->getHostname());
$tests['[comment:author]'] = Html::escape($comment->getAuthorName()); $tests['[comment:author]'] = Html::escape($comment->getAuthorName());
$tests['[comment:mail]'] = $this->adminUser->getEmail(); $tests['[comment:mail]'] = Html::escape($this->adminUser->getEmail());
$tests['[comment:homepage]'] = UrlHelper::filterBadProtocol($comment->getHomepage()); $tests['[comment:homepage]'] = UrlHelper::filterBadProtocol($comment->getHomepage());
$tests['[comment:title]'] = Html::escape($comment->getSubject()); $tests['[comment:title]'] = Html::escape($comment->getSubject());
$tests['[comment:body]'] = $comment->comment_body->processed; $tests['[comment:body]'] = $comment->comment_body->processed;
$tests['[comment:langcode]'] = $comment->language()->getId(); $tests['[comment:langcode]'] = Html::escape($comment->language()->getId());
$tests['[comment:url]'] = $comment->url('canonical', $url_options + array('fragment' => 'comment-' . $comment->id())); $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: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]'] = \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: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:changed:since]'] = \Drupal::service('date.formatter')->formatTimeDiffSince($comment->getChangedTimeAcrossTranslations(), array('langcode' => $language_interface->getId()));
$tests['[comment:parent:cid]'] = $comment->hasParentComment() ? $comment->getParentComment()->id() : NULL; $tests['[comment:parent:cid]'] = $comment->hasParentComment() ? $comment->getParentComment()->id() : NULL;
$tests['[comment:parent:title]'] = $parent_comment->getSubject(); $tests['[comment:parent:title]'] = Html::escape($parent_comment->getSubject());
$tests['[comment:entity]'] = Html::escape($node->getTitle()); $tests['[comment:entity]'] = Html::escape($node->getTitle());
// Test node specific tokens. // Test node specific tokens.
$tests['[comment:entity:nid]'] = $comment->getCommentedEntityId(); $tests['[comment:entity:nid]'] = $comment->getCommentedEntityId();
$tests['[comment:entity:title]'] = Html::escape($node->getTitle()); $tests['[comment:entity:title]'] = Html::escape($node->getTitle());
$tests['[comment:author:uid]'] = $comment->getOwnerId(); $tests['[comment:author:uid]'] = $comment->getOwnerId();
$tests['[comment:author:name]'] = Html::escape($this->adminUser->getDisplayName()); $tests['[comment:author:name]'] = Html::escape($this->adminUser->getUsername());
$base_bubbleable_metadata = BubbleableMetadata::createFromObject($comment); $base_bubbleable_metadata = BubbleableMetadata::createFromObject($comment);
$metadata_tests = []; $metadata_tests = [];
...@@ -118,16 +114,35 @@ function testCommentTokenReplacement() { ...@@ -118,16 +114,35 @@ function testCommentTokenReplacement() {
foreach ($tests as $input => $expected) { foreach ($tests as $input => $expected) {
$bubbleable_metadata = new BubbleableMetadata(); $bubbleable_metadata = new BubbleableMetadata();
$output = $token_service->replace($input, array('comment' => $comment), array('langcode' => $language_interface->getId()), $bubbleable_metadata); $output = $token_service->replace($input, array('comment' => $comment), array('langcode' => $language_interface->getId()), $bubbleable_metadata);
$this->assertEqual($output, $expected, new FormattableString('Comment token %token replaced.', ['%token' => $input])); $this->assertEqual($output, $expected, format_string('Sanitized comment token %token replaced.', array('%token' => $input)));
$this->assertEqual($bubbleable_metadata, $metadata_tests[$input]); $this->assertEqual($bubbleable_metadata, $metadata_tests[$input]);
} }
// Generate and test unsanitized tokens.
$tests['[comment:hostname]'] = $comment->getHostname();
$tests['[comment:author]'] = $comment->getAuthorName();
$tests['[comment:mail]'] = $this->adminUser->getEmail();
$tests['[comment:homepage]'] = $comment->getHomepage();
$tests['[comment:title]'] = $comment->getSubject();
$tests['[comment:body]'] = $comment->comment_body->value;
$tests['[comment:langcode]'] = $comment->language()->getId();
$tests['[comment:parent:title]'] = $parent_comment->getSubject();
$tests['[comment:entity]'] = $node->getTitle();
$tests['[comment:author:name]'] = $this->adminUser->getUsername();
foreach ($tests as $input => $expected) {
$output = $token_service->replace($input, array('comment' => $comment), array('langcode' => $language_interface->getId(), 'sanitize' => FALSE));
$this->assertEqual($output, $expected, format_string('Unsanitized comment token %token replaced.', array('%token' => $input)));
}
// Test anonymous comment author. // Test anonymous comment author.
$author_name = 'This is a random & " > string'; $author_name = $this->randomString();
$comment->setOwnerId(0)->setAuthorName($author_name); $comment->setOwnerId(0)->setAuthorName($author_name);
$input = '[comment:author]'; $input = '[comment:author]';
$output = $token_service->replace($input, array('comment' => $comment), array('langcode' => $language_interface->getId())); $output = $token_service->replace($input, array('comment' => $comment), array('langcode' => $language_interface->getId()));
$this->assertEqual($output, Html::escape($author_name), format_string('Comment author token %token replaced.', array('%token' => $input))); $this->assertEqual($output, Html::escape($author_name), format_string('Sanitized comment author token %token replaced.', array('%token' => $input)));
$output = $token_service->replace($input, array('comment' => $comment), array('langcode' => $language_interface->getId(), 'sanitize' => FALSE));
$this->assertEqual($output, $author_name, format_string('Unsanitized comment author token %token replaced.', array('%token' => $input)));
// Load node so comment_count gets computed. // Load node so comment_count gets computed.
$node = Node::load($node->id()); $node = Node::load($node->id());
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
* Defines a "managed_file" Form API field and a "file" field for Field module. * Defines a "managed_file" Form API field and a "file" field for Field module.
*/ */
use Drupal\Component\Utility\Html;
use Drupal\Core\Datetime\Entity\DateFormat; use Drupal\Core\Datetime\Entity\DateFormat;