Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
83.33% |
5 / 6 |
CRAP | |
78.26% |
18 / 23 |
FormattableMarkup | |
0.00% |
0 / 1 |
|
83.33% |
5 / 6 |
13.48 | |
78.26% |
18 / 23 |
__construct | |
100.00% |
1 / 1 |
1 | |
100.00% |
3 / 3 |
|||
__toString | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
count | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
jsonSerialize | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
placeholderFormat | |
0.00% |
0 / 1 |
7.10 | |
68.75% |
11 / 16 |
|||
placeholderEscape | |
100.00% |
1 / 1 |
2 | |
100.00% |
1 / 1 |
<?php | |
/** | |
* @file | |
* Contains \Drupal\Component\Render\FormattableMarkup. | |
*/ | |
namespace Drupal\Component\Render; | |
use Drupal\Component\Utility\Html; | |
use Drupal\Component\Utility\SafeMarkup; | |
use Drupal\Component\Utility\Unicode; | |
use Drupal\Component\Utility\UrlHelper; | |
/** | |
* Formats a string for HTML display by replacing variable placeholders. | |
* | |
* When cast to a string, this object replaces variable placeholders in the | |
* string with the arguments passed in during construction and escapes the | |
* values so they can be safely displayed as HTML. See the documentation of | |
* \Drupal\Component\Render\FormattableMarkup::placeholderFormat() for details | |
* on the supported placeholders and how to use them securely. Incorrect use of | |
* this class can result in security vulnerabilities. | |
* | |
* In most cases, you should use TranslatableMarkup or PluralTranslatableMarkup | |
* rather than this object, since they will translate the text (on | |
* non-English-only sites) in addition to formatting it. Variables concatenated | |
* without the insertion of language-specific words or punctuation are some | |
* examples where translation is not applicable and using this class directly | |
* directly is appropriate. | |
* | |
* This class is designed for formatting messages that are mostly text, not as | |
* an HTML template language. As such: | |
* - The passed in string should contain no (or minimal) HTML. | |
* - Variable placeholders should not be used within the "<" and ">" of an | |
* HTML tag, such as in HTML attribute values. This would be a security | |
* risk. Examples: | |
* @code | |
* // Insecure (placeholder within "<" and ">"): | |
* $this->placeholderFormat('<@variable>text</@variable>', ['@variable' => $variable]); | |
* // Insecure (placeholder within "<" and ">"): | |
* $this->placeholderFormat('<a @variable>link text</a>', ['@variable' => $variable]); | |
* // Insecure (placeholder within "<" and ">"): | |
* $this->placeholderFormat('<a title="@variable">link text</a>', ['@variable' => $variable]); | |
* @endcode | |
* Only the "href" attribute is supported via the special ":variable" | |
* placeholder, to allow simple links to be inserted: | |
* @code | |
* // Secure (usage of ":variable" placeholder for href attribute): | |
* $this->placeholderFormat('<a href=":variable">link text</a>', [':variable' , $variable]); | |
* // Secure (usage of ":variable" placeholder for href attribute): | |
* $this->placeholderFormat('<a href=":variable" title="static text">link text</a>', [':variable' => $variable]); | |
* // Insecure (the "@variable" placeholder does not filter dangerous | |
* // protocols): | |
* $this->placeholderFormat('<a href="@variable">link text</a>', ['@variable' => $variable]); | |
* // Insecure ("@variable" placeholder within "<" and ">"): | |
* $this->placeholderFormat('<a href=":url" title="@variable">link text</a>', [':url' => $url, '@variable' => $variable]); | |
* @endcode | |
* To build non-minimal HTML, use an HTML template language such as Twig, | |
* rather than this class. | |
* | |
* @ingroup sanitization | |
* | |
* @see \Drupal\Core\StringTranslation\TranslatableMarkup | |
* @see \Drupal\Core\StringTranslation\PluralTranslatableMarkup | |
* @see \Drupal\Component\Render\FormattableMarkup::placeholderFormat() | |
*/ | |
class FormattableMarkup implements MarkupInterface, \Countable { | |
/** | |
* The arguments to replace placeholders with. | |
* | |
* @var array | |
*/ | |
protected $arguments = []; | |
/** | |
* Constructs a new class instance. | |
* | |
* @param string $string | |
* A string containing placeholders. The string itself will not be escaped, | |
* any unsafe content must be in $args and inserted via placeholders. | |
* @param array $arguments | |
* An array with placeholder replacements, keyed by placeholder. See | |
* \Drupal\Component\Render\FormattableMarkup::placeholderFormat() for | |
* additional information about placeholders. | |
* | |
* @see \Drupal\Component\Render\FormattableMarkup::placeholderFormat() | |
*/ | |
public function __construct($string, array $arguments) { | |
$this->string = (string) $string; | |
$this->arguments = $arguments; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function __toString() { | |
return static::placeholderFormat($this->string, $this->arguments); | |
} | |
/** | |
* Returns the string length. | |
* | |
* @return int | |
* The length of the string. | |
*/ | |
public function count() { | |
return Unicode::strlen($this->string); | |
} | |
/** | |
* Returns a representation of the object for use in JSON serialization. | |
* | |
* @return string | |
* The safe string content. | |
*/ | |
public function jsonSerialize() { | |
return $this->__toString(); | |
} | |
/** | |
* Replaces placeholders in a string with values. | |
* | |
* @param string $string | |
* A string containing placeholders. The string itself is expected to be | |
* safe and correct HTML. Any unsafe content must be in $args and | |
* inserted via placeholders. | |
* @param array $args | |
* An associative array of replacements. Each array key should be the same | |
* as a placeholder in $string. The corresponding value should be a string | |
* or an object that implements | |
* \Drupal\Component\Render\MarkupInterface. The value replaces the | |
* placeholder in $string. Sanitization and formatting will be done before | |
* replacement. The type of sanitization and formatting depends on the first | |
* character of the key: | |
* - @variable: When the placeholder replacement value is: | |
* - A string, the replaced value in the returned string will be sanitized | |
* using \Drupal\Component\Utility\Html::escape(). | |
* - A MarkupInterface object, the replaced value in the returned string | |
* will not be sanitized. | |
* - A MarkupInterface object cast to a string, the replaced value in the | |
* returned string be forcibly sanitized using | |
* \Drupal\Component\Utility\Html::escape(). | |
* @code | |
* $this->placeholderFormat('This will force HTML-escaping of the replacement value: @text', ['@text' => (string) $safe_string_interface_object)); | |
* @endcode | |
* Use this placeholder as the default choice for anything displayed on | |
* the site, but not within HTML attributes, JavaScript, or CSS. Doing so | |
* is a security risk. | |
* - %variable: Use when the replacement value is to be wrapped in <em> | |
* tags. | |
* A call like: | |
* @code | |
* $string = "%output_text"; | |
* $arguments = ['output_text' => 'text output here.']; | |
* $this->placeholderFormat($string, $arguments); | |
* @endcode | |
* makes the following HTML code: | |
* @code | |
* <em class="placeholder">text output here.</em> | |
* @endcode | |
* As with @variable, do not use this within HTML attributes, JavaScript, | |
* or CSS. Doing so is a security risk. | |
* - :variable: Return value is escaped with | |
* \Drupal\Component\Utility\Html::escape() and filtered for dangerous | |
* protocols using UrlHelper::stripDangerousProtocols(). Use this when | |
* using the "href" attribute, ensuring the attribute value is always | |
* wrapped in quotes: | |
* @code | |
* // Secure (with quotes): | |
* $this->placeholderFormat('<a href=":url">@variable</a>', [':url' => $url, @variable => $variable]); | |
* // Insecure (without quotes): | |
* $this->placeholderFormat('<a href=:url>@variable</a>', [':url' => $url, @variable => $variable]); | |
* @endcode | |
* When ":variable" comes from arbitrary user input, the result is secure, | |
* but not guaranteed to be a valid URL (which means the resulting output | |
* could fail HTML validation). To guarantee a valid URL, use | |
* Url::fromUri($user_input)->toString() (which either throws an exception | |
* or returns a well-formed URL) before passing the result into a | |
* ":variable" placeholder. | |
* | |
* @return string | |
* A formatted HTML string with the placeholders replaced. | |
* | |
* @ingroup sanitization | |
* | |
* @see \Drupal\Core\StringTranslation\TranslatableMarkup | |
* @see \Drupal\Core\StringTranslation\PluralTranslatableMarkup | |
* @see \Drupal\Component\Utility\Html::escape() | |
* @see \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols() | |
* @see \Drupal\Core\Url::fromUri() | |
*/ | |
protected static function placeholderFormat($string, array $args) { | |
// Transform arguments before inserting them. | |
foreach ($args as $key => $value) { | |
switch ($key[0]) { | |
case '@': | |
// Escape if the value is not an object from a class that implements | |
// \Drupal\Component\Render\MarkupInterface, for example strings will | |
// be escaped. | |
// \Drupal\Component\Utility\SafeMarkup\SafeMarkup::isSafe() may | |
// return TRUE for content that is safe within HTML fragments, but not | |
// within other contexts, so this placeholder type must not be used | |
// within HTML attributes, JavaScript, or CSS. | |
$args[$key] = static::placeholderEscape($value); | |
break; | |
case ':': | |
// Strip URL protocols that can be XSS vectors. | |
$value = UrlHelper::stripDangerousProtocols($value); | |
// Escape unconditionally, without checking | |
// \Drupal\Component\Utility\SafeMarkup\SafeMarkup::isSafe(). This | |
// forces characters that are unsafe for use in an "href" HTML | |
// attribute to be encoded. If a caller wants to pass a value that is | |
// extracted from HTML and therefore is already HTML encoded, it must | |
// invoke | |
// \Drupal\Component\Render\OutputStrategyInterface::renderFromHtml() | |
// on it prior to passing it in as a placeholder value of this type. | |
// @todo Add some advice and stronger warnings. | |
// https://www.drupal.org/node/2569041. | |
$args[$key] = Html::escape($value); | |
break; | |
case '%': | |
// Similarly to @, escape non-safe values. Also, add wrapping markup | |
// in order to render as a placeholder. Not for use within attributes, | |
// per the warning above about | |
// \Drupal\Component\Utility\SafeMarkup\SafeMarkup::isSafe() and also | |
// due to the wrapping markup. | |
$args[$key] = '<em class="placeholder">' . static::placeholderEscape($value) . '</em>'; | |
break; | |
default: | |
// We do not trigger an error for placeholder that start with an | |
// alphabetic character. | |
if (!ctype_alpha($key[0])) { | |
// We trigger an error as we may want to introduce new placeholders | |
// in the future without breaking backward compatibility. | |
trigger_error('Invalid placeholder (' . $key . ') in string: ' . $string, E_USER_ERROR); | |
} | |
break; | |
} | |
} | |
return strtr($string, $args); | |
} | |
/** | |
* Escapes a placeholder replacement value if needed. | |
* | |
* @param string|\Drupal\Component\Render\MarkupInterface $value | |
* A placeholder replacement value. | |
* | |
* @return string | |
* The properly escaped replacement value. | |
*/ | |
protected static function placeholderEscape($value) { | |
return SafeMarkup::isSafe($value) ? (string) $value : Html::escape($value); | |
} | |
} |