Mercurial > hg > isophonics-drupal-site
comparison core/lib/Drupal/Component/Render/FormattableMarkup.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children | 1fec387a4317 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4c8ae668cc8c |
---|---|
1 <?php | |
2 | |
3 namespace Drupal\Component\Render; | |
4 | |
5 use Drupal\Component\Utility\Html; | |
6 use Drupal\Component\Utility\Unicode; | |
7 use Drupal\Component\Utility\UrlHelper; | |
8 | |
9 /** | |
10 * Formats a string for HTML display by replacing variable placeholders. | |
11 * | |
12 * When cast to a string, this object replaces variable placeholders in the | |
13 * string with the arguments passed in during construction and escapes the | |
14 * values so they can be safely displayed as HTML. See the documentation of | |
15 * \Drupal\Component\Render\FormattableMarkup::placeholderFormat() for details | |
16 * on the supported placeholders and how to use them securely. Incorrect use of | |
17 * this class can result in security vulnerabilities. | |
18 * | |
19 * In most cases, you should use TranslatableMarkup or PluralTranslatableMarkup | |
20 * rather than this object, since they will translate the text (on | |
21 * non-English-only sites) in addition to formatting it. Variables concatenated | |
22 * without the insertion of language-specific words or punctuation are some | |
23 * examples where translation is not applicable and using this class directly | |
24 * directly is appropriate. | |
25 * | |
26 * This class is designed for formatting messages that are mostly text, not as | |
27 * an HTML template language. As such: | |
28 * - The passed in string should contain no (or minimal) HTML. | |
29 * - Variable placeholders should not be used within the "<" and ">" of an | |
30 * HTML tag, such as in HTML attribute values. This would be a security | |
31 * risk. Examples: | |
32 * @code | |
33 * // Insecure (placeholder within "<" and ">"): | |
34 * $this->placeholderFormat('<@variable>text</@variable>', ['@variable' => $variable]); | |
35 * // Insecure (placeholder within "<" and ">"): | |
36 * $this->placeholderFormat('<a @variable>link text</a>', ['@variable' => $variable]); | |
37 * // Insecure (placeholder within "<" and ">"): | |
38 * $this->placeholderFormat('<a title="@variable">link text</a>', ['@variable' => $variable]); | |
39 * @endcode | |
40 * Only the "href" attribute is supported via the special ":variable" | |
41 * placeholder, to allow simple links to be inserted: | |
42 * @code | |
43 * // Secure (usage of ":variable" placeholder for href attribute): | |
44 * $this->placeholderFormat('<a href=":variable">link text</a>', [':variable' , $variable]); | |
45 * // Secure (usage of ":variable" placeholder for href attribute): | |
46 * $this->placeholderFormat('<a href=":variable" title="static text">link text</a>', [':variable' => $variable]); | |
47 * // Insecure (the "@variable" placeholder does not filter dangerous | |
48 * // protocols): | |
49 * $this->placeholderFormat('<a href="@variable">link text</a>', ['@variable' => $variable]); | |
50 * // Insecure ("@variable" placeholder within "<" and ">"): | |
51 * $this->placeholderFormat('<a href=":url" title="@variable">link text</a>', [':url' => $url, '@variable' => $variable]); | |
52 * @endcode | |
53 * To build non-minimal HTML, use an HTML template language such as Twig, | |
54 * rather than this class. | |
55 * | |
56 * @ingroup sanitization | |
57 * | |
58 * @see \Drupal\Core\StringTranslation\TranslatableMarkup | |
59 * @see \Drupal\Core\StringTranslation\PluralTranslatableMarkup | |
60 * @see \Drupal\Component\Render\FormattableMarkup::placeholderFormat() | |
61 */ | |
62 class FormattableMarkup implements MarkupInterface, \Countable { | |
63 | |
64 /** | |
65 * The arguments to replace placeholders with. | |
66 * | |
67 * @var array | |
68 */ | |
69 protected $arguments = []; | |
70 | |
71 /** | |
72 * Constructs a new class instance. | |
73 * | |
74 * @param string $string | |
75 * A string containing placeholders. The string itself will not be escaped, | |
76 * any unsafe content must be in $args and inserted via placeholders. | |
77 * @param array $arguments | |
78 * An array with placeholder replacements, keyed by placeholder. See | |
79 * \Drupal\Component\Render\FormattableMarkup::placeholderFormat() for | |
80 * additional information about placeholders. | |
81 * | |
82 * @see \Drupal\Component\Render\FormattableMarkup::placeholderFormat() | |
83 */ | |
84 public function __construct($string, array $arguments) { | |
85 $this->string = (string) $string; | |
86 $this->arguments = $arguments; | |
87 } | |
88 | |
89 /** | |
90 * {@inheritdoc} | |
91 */ | |
92 public function __toString() { | |
93 return static::placeholderFormat($this->string, $this->arguments); | |
94 } | |
95 | |
96 /** | |
97 * Returns the string length. | |
98 * | |
99 * @return int | |
100 * The length of the string. | |
101 */ | |
102 public function count() { | |
103 return Unicode::strlen($this->string); | |
104 } | |
105 | |
106 /** | |
107 * Returns a representation of the object for use in JSON serialization. | |
108 * | |
109 * @return string | |
110 * The safe string content. | |
111 */ | |
112 public function jsonSerialize() { | |
113 return $this->__toString(); | |
114 } | |
115 | |
116 /** | |
117 * Replaces placeholders in a string with values. | |
118 * | |
119 * @param string $string | |
120 * A string containing placeholders. The string itself is expected to be | |
121 * safe and correct HTML. Any unsafe content must be in $args and | |
122 * inserted via placeholders. | |
123 * @param array $args | |
124 * An associative array of replacements. Each array key should be the same | |
125 * as a placeholder in $string. The corresponding value should be a string | |
126 * or an object that implements | |
127 * \Drupal\Component\Render\MarkupInterface. The value replaces the | |
128 * placeholder in $string. Sanitization and formatting will be done before | |
129 * replacement. The type of sanitization and formatting depends on the first | |
130 * character of the key: | |
131 * - @variable: When the placeholder replacement value is: | |
132 * - A string, the replaced value in the returned string will be sanitized | |
133 * using \Drupal\Component\Utility\Html::escape(). | |
134 * - A MarkupInterface object, the replaced value in the returned string | |
135 * will not be sanitized. | |
136 * - A MarkupInterface object cast to a string, the replaced value in the | |
137 * returned string be forcibly sanitized using | |
138 * \Drupal\Component\Utility\Html::escape(). | |
139 * @code | |
140 * $this->placeholderFormat('This will force HTML-escaping of the replacement value: @text', ['@text' => (string) $safe_string_interface_object)); | |
141 * @endcode | |
142 * Use this placeholder as the default choice for anything displayed on | |
143 * the site, but not within HTML attributes, JavaScript, or CSS. Doing so | |
144 * is a security risk. | |
145 * - %variable: Use when the replacement value is to be wrapped in <em> | |
146 * tags. | |
147 * A call like: | |
148 * @code | |
149 * $string = "%output_text"; | |
150 * $arguments = ['%output_text' => 'text output here.']; | |
151 * $this->placeholderFormat($string, $arguments); | |
152 * @endcode | |
153 * makes the following HTML code: | |
154 * @code | |
155 * <em class="placeholder">text output here.</em> | |
156 * @endcode | |
157 * As with @variable, do not use this within HTML attributes, JavaScript, | |
158 * or CSS. Doing so is a security risk. | |
159 * - :variable: Return value is escaped with | |
160 * \Drupal\Component\Utility\Html::escape() and filtered for dangerous | |
161 * protocols using UrlHelper::stripDangerousProtocols(). Use this when | |
162 * using the "href" attribute, ensuring the attribute value is always | |
163 * wrapped in quotes: | |
164 * @code | |
165 * // Secure (with quotes): | |
166 * $this->placeholderFormat('<a href=":url">@variable</a>', [':url' => $url, '@variable' => $variable]); | |
167 * // Insecure (without quotes): | |
168 * $this->placeholderFormat('<a href=:url>@variable</a>', [':url' => $url, '@variable' => $variable]); | |
169 * @endcode | |
170 * When ":variable" comes from arbitrary user input, the result is secure, | |
171 * but not guaranteed to be a valid URL (which means the resulting output | |
172 * could fail HTML validation). To guarantee a valid URL, use | |
173 * Url::fromUri($user_input)->toString() (which either throws an exception | |
174 * or returns a well-formed URL) before passing the result into a | |
175 * ":variable" placeholder. | |
176 * | |
177 * @return string | |
178 * A formatted HTML string with the placeholders replaced. | |
179 * | |
180 * @ingroup sanitization | |
181 * | |
182 * @see \Drupal\Core\StringTranslation\TranslatableMarkup | |
183 * @see \Drupal\Core\StringTranslation\PluralTranslatableMarkup | |
184 * @see \Drupal\Component\Utility\Html::escape() | |
185 * @see \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols() | |
186 * @see \Drupal\Core\Url::fromUri() | |
187 */ | |
188 protected static function placeholderFormat($string, array $args) { | |
189 // Transform arguments before inserting them. | |
190 foreach ($args as $key => $value) { | |
191 switch ($key[0]) { | |
192 case '@': | |
193 // Escape if the value is not an object from a class that implements | |
194 // \Drupal\Component\Render\MarkupInterface, for example strings will | |
195 // be escaped. | |
196 // Strings that are safe within HTML fragments, but not within other | |
197 // contexts, may still be an instance of | |
198 // \Drupal\Component\Render\MarkupInterface, so this placeholder type | |
199 // must not be used within HTML attributes, JavaScript, or CSS. | |
200 $args[$key] = static::placeholderEscape($value); | |
201 break; | |
202 | |
203 case ':': | |
204 // Strip URL protocols that can be XSS vectors. | |
205 $value = UrlHelper::stripDangerousProtocols($value); | |
206 // Escape unconditionally, without checking whether the value is an | |
207 // instance of \Drupal\Component\Render\MarkupInterface. This forces | |
208 // characters that are unsafe for use in an "href" HTML attribute to | |
209 // be encoded. If a caller wants to pass a value that is extracted | |
210 // from HTML and therefore is already HTML encoded, it must invoke | |
211 // \Drupal\Component\Render\OutputStrategyInterface::renderFromHtml() | |
212 // on it prior to passing it in as a placeholder value of this type. | |
213 // @todo Add some advice and stronger warnings. | |
214 // https://www.drupal.org/node/2569041. | |
215 $args[$key] = Html::escape($value); | |
216 break; | |
217 | |
218 case '%': | |
219 // Similarly to @, escape non-safe values. Also, add wrapping markup | |
220 // in order to render as a placeholder. Not for use within attributes, | |
221 // per the warning above about | |
222 // \Drupal\Component\Render\MarkupInterface and also due to the | |
223 // wrapping markup. | |
224 $args[$key] = '<em class="placeholder">' . static::placeholderEscape($value) . '</em>'; | |
225 break; | |
226 | |
227 default: | |
228 // We do not trigger an error for placeholder that start with an | |
229 // alphabetic character. | |
230 // @todo https://www.drupal.org/node/2807743 Change to an exception | |
231 // and always throw regardless of the first character. | |
232 if (!ctype_alpha($key[0])) { | |
233 // We trigger an error as we may want to introduce new placeholders | |
234 // in the future without breaking backward compatibility. | |
235 trigger_error('Invalid placeholder (' . $key . ') in string: ' . $string, E_USER_ERROR); | |
236 } | |
237 elseif (strpos($string, $key) !== FALSE) { | |
238 trigger_error('Invalid placeholder (' . $key . ') in string: ' . $string, E_USER_DEPRECATED); | |
239 } | |
240 // No replacement possible therefore we can discard the argument. | |
241 unset($args[$key]); | |
242 break; | |
243 } | |
244 } | |
245 | |
246 return strtr($string, $args); | |
247 } | |
248 | |
249 /** | |
250 * Escapes a placeholder replacement value if needed. | |
251 * | |
252 * @param string|\Drupal\Component\Render\MarkupInterface $value | |
253 * A placeholder replacement value. | |
254 * | |
255 * @return string | |
256 * The properly escaped replacement value. | |
257 */ | |
258 protected static function placeholderEscape($value) { | |
259 return $value instanceof MarkupInterface ? (string) $value : Html::escape($value); | |
260 } | |
261 | |
262 } |