Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 namespace Drupal\Core\Template;
|
Chris@0
|
4
|
Chris@0
|
5 use Drupal\Component\Utility\Html;
|
Chris@0
|
6 use Drupal\Component\Render\MarkupInterface;
|
Chris@0
|
7 use Drupal\Core\Cache\CacheableDependencyInterface;
|
Chris@0
|
8 use Drupal\Core\Datetime\DateFormatterInterface;
|
Chris@0
|
9 use Drupal\Core\Render\AttachmentsInterface;
|
Chris@0
|
10 use Drupal\Core\Render\BubbleableMetadata;
|
Chris@0
|
11 use Drupal\Core\Render\Markup;
|
Chris@0
|
12 use Drupal\Core\Render\RenderableInterface;
|
Chris@0
|
13 use Drupal\Core\Render\RendererInterface;
|
Chris@0
|
14 use Drupal\Core\Routing\UrlGeneratorInterface;
|
Chris@0
|
15 use Drupal\Core\Theme\ThemeManagerInterface;
|
Chris@0
|
16 use Drupal\Core\Url;
|
Chris@0
|
17
|
Chris@0
|
18 /**
|
Chris@0
|
19 * A class providing Drupal Twig extensions.
|
Chris@0
|
20 *
|
Chris@0
|
21 * This provides a Twig extension that registers various Drupal-specific
|
Chris@0
|
22 * extensions to Twig, specifically Twig functions, filter, and node visitors.
|
Chris@0
|
23 *
|
Chris@0
|
24 * @see \Drupal\Core\CoreServiceProvider
|
Chris@0
|
25 */
|
Chris@0
|
26 class TwigExtension extends \Twig_Extension {
|
Chris@0
|
27
|
Chris@0
|
28 /**
|
Chris@0
|
29 * The URL generator.
|
Chris@0
|
30 *
|
Chris@0
|
31 * @var \Drupal\Core\Routing\UrlGeneratorInterface
|
Chris@0
|
32 */
|
Chris@0
|
33 protected $urlGenerator;
|
Chris@0
|
34
|
Chris@0
|
35 /**
|
Chris@0
|
36 * The renderer.
|
Chris@0
|
37 *
|
Chris@0
|
38 * @var \Drupal\Core\Render\RendererInterface
|
Chris@0
|
39 */
|
Chris@0
|
40 protected $renderer;
|
Chris@0
|
41
|
Chris@0
|
42 /**
|
Chris@0
|
43 * The theme manager.
|
Chris@0
|
44 *
|
Chris@0
|
45 * @var \Drupal\Core\Theme\ThemeManagerInterface
|
Chris@0
|
46 */
|
Chris@0
|
47 protected $themeManager;
|
Chris@0
|
48
|
Chris@0
|
49 /**
|
Chris@0
|
50 * The date formatter.
|
Chris@0
|
51 *
|
Chris@0
|
52 * @var \Drupal\Core\Datetime\DateFormatterInterface
|
Chris@0
|
53 */
|
Chris@0
|
54 protected $dateFormatter;
|
Chris@0
|
55
|
Chris@0
|
56 /**
|
Chris@0
|
57 * Constructs \Drupal\Core\Template\TwigExtension.
|
Chris@0
|
58 *
|
Chris@0
|
59 * @param \Drupal\Core\Render\RendererInterface $renderer
|
Chris@0
|
60 * The renderer.
|
Chris@0
|
61 * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
|
Chris@0
|
62 * The URL generator.
|
Chris@0
|
63 * @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager
|
Chris@0
|
64 * The theme manager.
|
Chris@0
|
65 * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
|
Chris@0
|
66 * The date formatter.
|
Chris@0
|
67 */
|
Chris@0
|
68 public function __construct(RendererInterface $renderer, UrlGeneratorInterface $url_generator, ThemeManagerInterface $theme_manager, DateFormatterInterface $date_formatter) {
|
Chris@0
|
69 $this->renderer = $renderer;
|
Chris@0
|
70 $this->urlGenerator = $url_generator;
|
Chris@0
|
71 $this->themeManager = $theme_manager;
|
Chris@0
|
72 $this->dateFormatter = $date_formatter;
|
Chris@0
|
73 }
|
Chris@0
|
74
|
Chris@0
|
75 /**
|
Chris@0
|
76 * Sets the URL generator.
|
Chris@0
|
77 *
|
Chris@0
|
78 * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
|
Chris@0
|
79 * The URL generator.
|
Chris@0
|
80 *
|
Chris@0
|
81 * @return $this
|
Chris@0
|
82 *
|
Chris@0
|
83 * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0.
|
Chris@0
|
84 */
|
Chris@0
|
85 public function setGenerators(UrlGeneratorInterface $url_generator) {
|
Chris@0
|
86 return $this->setUrlGenerator($url_generator);
|
Chris@0
|
87 }
|
Chris@0
|
88
|
Chris@0
|
89 /**
|
Chris@0
|
90 * Sets the URL generator.
|
Chris@0
|
91 *
|
Chris@0
|
92 * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
|
Chris@0
|
93 * The URL generator.
|
Chris@0
|
94 *
|
Chris@0
|
95 * @return $this
|
Chris@0
|
96 *
|
Chris@0
|
97 * @deprecated in Drupal 8.3.x-dev, will be removed before Drupal 9.0.0.
|
Chris@0
|
98 */
|
Chris@0
|
99 public function setUrlGenerator(UrlGeneratorInterface $url_generator) {
|
Chris@0
|
100 $this->urlGenerator = $url_generator;
|
Chris@0
|
101 return $this;
|
Chris@0
|
102 }
|
Chris@0
|
103
|
Chris@0
|
104 /**
|
Chris@0
|
105 * Sets the theme manager.
|
Chris@0
|
106 *
|
Chris@0
|
107 * @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager
|
Chris@0
|
108 * The theme manager.
|
Chris@0
|
109 *
|
Chris@0
|
110 * @return $this
|
Chris@0
|
111 *
|
Chris@0
|
112 * @deprecated in Drupal 8.3.x-dev, will be removed before Drupal 9.0.0.
|
Chris@0
|
113 */
|
Chris@0
|
114 public function setThemeManager(ThemeManagerInterface $theme_manager) {
|
Chris@0
|
115 $this->themeManager = $theme_manager;
|
Chris@0
|
116 return $this;
|
Chris@0
|
117 }
|
Chris@0
|
118
|
Chris@0
|
119 /**
|
Chris@0
|
120 * Sets the date formatter.
|
Chris@0
|
121 *
|
Chris@0
|
122 * @param \Drupal\Core\Datetime\DateFormatter $date_formatter
|
Chris@0
|
123 * The date formatter.
|
Chris@0
|
124 *
|
Chris@0
|
125 * @return $this
|
Chris@0
|
126 *
|
Chris@0
|
127 * @deprecated in Drupal 8.3.x-dev, will be removed before Drupal 9.0.0.
|
Chris@0
|
128 */
|
Chris@0
|
129 public function setDateFormatter(DateFormatterInterface $date_formatter) {
|
Chris@0
|
130 $this->dateFormatter = $date_formatter;
|
Chris@0
|
131 return $this;
|
Chris@0
|
132 }
|
Chris@0
|
133
|
Chris@0
|
134 /**
|
Chris@0
|
135 * {@inheritdoc}
|
Chris@0
|
136 */
|
Chris@0
|
137 public function getFunctions() {
|
Chris@0
|
138 return [
|
Chris@0
|
139 // This function will receive a renderable array, if an array is detected.
|
Chris@0
|
140 new \Twig_SimpleFunction('render_var', [$this, 'renderVar']),
|
Chris@0
|
141 // The url and path function are defined in close parallel to those found
|
Chris@0
|
142 // in \Symfony\Bridge\Twig\Extension\RoutingExtension
|
Chris@0
|
143 new \Twig_SimpleFunction('url', [$this, 'getUrl'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]),
|
Chris@0
|
144 new \Twig_SimpleFunction('path', [$this, 'getPath'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]),
|
Chris@0
|
145 new \Twig_SimpleFunction('link', [$this, 'getLink']),
|
Chris@0
|
146 new \Twig_SimpleFunction('file_url', function ($uri) {
|
Chris@0
|
147 return file_url_transform_relative(file_create_url($uri));
|
Chris@0
|
148 }),
|
Chris@0
|
149 new \Twig_SimpleFunction('attach_library', [$this, 'attachLibrary']),
|
Chris@0
|
150 new \Twig_SimpleFunction('active_theme_path', [$this, 'getActiveThemePath']),
|
Chris@0
|
151 new \Twig_SimpleFunction('active_theme', [$this, 'getActiveTheme']),
|
Chris@0
|
152 new \Twig_SimpleFunction('create_attribute', [$this, 'createAttribute']),
|
Chris@0
|
153 ];
|
Chris@0
|
154 }
|
Chris@0
|
155
|
Chris@0
|
156 /**
|
Chris@0
|
157 * {@inheritdoc}
|
Chris@0
|
158 */
|
Chris@0
|
159 public function getFilters() {
|
Chris@0
|
160 return [
|
Chris@0
|
161 // Translation filters.
|
Chris@0
|
162 new \Twig_SimpleFilter('t', 't', ['is_safe' => ['html']]),
|
Chris@0
|
163 new \Twig_SimpleFilter('trans', 't', ['is_safe' => ['html']]),
|
Chris@0
|
164 // The "raw" filter is not detectable when parsing "trans" tags. To detect
|
Chris@0
|
165 // which prefix must be used for translation (@, !, %), we must clone the
|
Chris@0
|
166 // "raw" filter and give it identifiable names. These filters should only
|
Chris@0
|
167 // be used in "trans" tags.
|
Chris@0
|
168 // @see TwigNodeTrans::compileString()
|
Chris@0
|
169 new \Twig_SimpleFilter('placeholder', [$this, 'escapePlaceholder'], ['is_safe' => ['html'], 'needs_environment' => TRUE]),
|
Chris@0
|
170
|
Chris@0
|
171 // Replace twig's escape filter with our own.
|
Chris@0
|
172 new \Twig_SimpleFilter('drupal_escape', [$this, 'escapeFilter'], ['needs_environment' => TRUE, 'is_safe_callback' => 'twig_escape_filter_is_safe']),
|
Chris@0
|
173
|
Chris@0
|
174 // Implements safe joining.
|
Chris@0
|
175 // @todo Make that the default for |join? Upstream issue:
|
Chris@0
|
176 // https://github.com/fabpot/Twig/issues/1420
|
Chris@0
|
177 new \Twig_SimpleFilter('safe_join', [$this, 'safeJoin'], ['needs_environment' => TRUE, 'is_safe' => ['html']]),
|
Chris@0
|
178
|
Chris@0
|
179 // Array filters.
|
Chris@18
|
180 new \Twig_SimpleFilter('without', [$this, 'withoutFilter']),
|
Chris@0
|
181
|
Chris@0
|
182 // CSS class and ID filters.
|
Chris@0
|
183 new \Twig_SimpleFilter('clean_class', '\Drupal\Component\Utility\Html::getClass'),
|
Chris@0
|
184 new \Twig_SimpleFilter('clean_id', '\Drupal\Component\Utility\Html::getId'),
|
Chris@0
|
185 // This filter will render a renderable array to use the string results.
|
Chris@0
|
186 new \Twig_SimpleFilter('render', [$this, 'renderVar']),
|
Chris@0
|
187 new \Twig_SimpleFilter('format_date', [$this->dateFormatter, 'format']),
|
Chris@0
|
188 ];
|
Chris@0
|
189 }
|
Chris@0
|
190
|
Chris@0
|
191 /**
|
Chris@0
|
192 * {@inheritdoc}
|
Chris@0
|
193 */
|
Chris@0
|
194 public function getNodeVisitors() {
|
Chris@0
|
195 // The node visitor is needed to wrap all variables with
|
Chris@0
|
196 // render_var -> TwigExtension->renderVar() function.
|
Chris@0
|
197 return [
|
Chris@0
|
198 new TwigNodeVisitor(),
|
Chris@0
|
199 ];
|
Chris@0
|
200 }
|
Chris@0
|
201
|
Chris@0
|
202 /**
|
Chris@0
|
203 * {@inheritdoc}
|
Chris@0
|
204 */
|
Chris@0
|
205 public function getTokenParsers() {
|
Chris@0
|
206 return [
|
Chris@0
|
207 new TwigTransTokenParser(),
|
Chris@0
|
208 ];
|
Chris@0
|
209 }
|
Chris@0
|
210
|
Chris@0
|
211 /**
|
Chris@0
|
212 * {@inheritdoc}
|
Chris@0
|
213 */
|
Chris@0
|
214 public function getName() {
|
Chris@0
|
215 return 'drupal_core';
|
Chris@0
|
216 }
|
Chris@0
|
217
|
Chris@0
|
218 /**
|
Chris@0
|
219 * Generates a URL path given a route name and parameters.
|
Chris@0
|
220 *
|
Chris@0
|
221 * @param $name
|
Chris@0
|
222 * The name of the route.
|
Chris@0
|
223 * @param array $parameters
|
Chris@0
|
224 * An associative array of route parameters names and values.
|
Chris@0
|
225 * @param array $options
|
Chris@0
|
226 * (optional) An associative array of additional options. The 'absolute'
|
Chris@0
|
227 * option is forced to be FALSE.
|
Chris@0
|
228 *
|
Chris@0
|
229 * @return string
|
Chris@0
|
230 * The generated URL path (relative URL) for the given route.
|
Chris@0
|
231 *
|
Chris@0
|
232 * @see \Drupal\Core\Routing\UrlGeneratorInterface::generateFromRoute()
|
Chris@0
|
233 */
|
Chris@0
|
234 public function getPath($name, $parameters = [], $options = []) {
|
Chris@18
|
235 assert($this->urlGenerator instanceof UrlGeneratorInterface, "The URL generator hasn't been set up. Any configuration YAML file with a service directive dealing with the Twig configuration can cause this, most likely found in a recently installed or changed module.");
|
Chris@18
|
236
|
Chris@0
|
237 $options['absolute'] = FALSE;
|
Chris@0
|
238 return $this->urlGenerator->generateFromRoute($name, $parameters, $options);
|
Chris@0
|
239 }
|
Chris@0
|
240
|
Chris@0
|
241 /**
|
Chris@0
|
242 * Generates an absolute URL given a route name and parameters.
|
Chris@0
|
243 *
|
Chris@0
|
244 * @param $name
|
Chris@0
|
245 * The name of the route.
|
Chris@0
|
246 * @param array $parameters
|
Chris@0
|
247 * An associative array of route parameter names and values.
|
Chris@0
|
248 * @param array $options
|
Chris@0
|
249 * (optional) An associative array of additional options. The 'absolute'
|
Chris@0
|
250 * option is forced to be TRUE.
|
Chris@0
|
251 *
|
Chris@0
|
252 * @return string
|
Chris@0
|
253 * The generated absolute URL for the given route.
|
Chris@0
|
254 *
|
Chris@0
|
255 * @todo Add an option for scheme-relative URLs.
|
Chris@0
|
256 */
|
Chris@0
|
257 public function getUrl($name, $parameters = [], $options = []) {
|
Chris@18
|
258 assert($this->urlGenerator instanceof UrlGeneratorInterface, "The URL generator hasn't been set up. Any configuration YAML file with a service directive dealing with the Twig configuration can cause this, most likely found in a recently installed or changed module.");
|
Chris@18
|
259
|
Chris@0
|
260 // Generate URL.
|
Chris@0
|
261 $options['absolute'] = TRUE;
|
Chris@0
|
262 $generated_url = $this->urlGenerator->generateFromRoute($name, $parameters, $options, TRUE);
|
Chris@0
|
263
|
Chris@0
|
264 // Return as render array, so we can bubble the bubbleable metadata.
|
Chris@0
|
265 $build = ['#markup' => $generated_url->getGeneratedUrl()];
|
Chris@0
|
266 $generated_url->applyTo($build);
|
Chris@0
|
267 return $build;
|
Chris@0
|
268 }
|
Chris@0
|
269
|
Chris@0
|
270 /**
|
Chris@0
|
271 * Gets a rendered link from a url object.
|
Chris@0
|
272 *
|
Chris@0
|
273 * @param string $text
|
Chris@0
|
274 * The link text for the anchor tag as a translated string.
|
Chris@0
|
275 * @param \Drupal\Core\Url|string $url
|
Chris@0
|
276 * The URL object or string used for the link.
|
Chris@0
|
277 * @param array|\Drupal\Core\Template\Attribute $attributes
|
Chris@0
|
278 * An optional array or Attribute object of link attributes.
|
Chris@0
|
279 *
|
Chris@0
|
280 * @return array
|
Chris@0
|
281 * A render array representing a link to the given URL.
|
Chris@0
|
282 */
|
Chris@0
|
283 public function getLink($text, $url, $attributes = []) {
|
Chris@18
|
284 assert(is_string($url) || $url instanceof Url, '$url must be a string or object of type \Drupal\Core\Url');
|
Chris@18
|
285 assert(is_array($attributes) || $attributes instanceof Attribute, '$attributes, if set, must be an array or object of type \Drupal\Core\Template\Attribute');
|
Chris@18
|
286
|
Chris@0
|
287 if (!$url instanceof Url) {
|
Chris@0
|
288 $url = Url::fromUri($url);
|
Chris@0
|
289 }
|
Chris@0
|
290 // The twig extension should not modify the original URL object, this
|
Chris@0
|
291 // ensures consistent rendering.
|
Chris@0
|
292 // @see https://www.drupal.org/node/2842399
|
Chris@0
|
293 $url = clone $url;
|
Chris@0
|
294 if ($attributes) {
|
Chris@0
|
295 if ($attributes instanceof Attribute) {
|
Chris@0
|
296 $attributes = $attributes->toArray();
|
Chris@0
|
297 }
|
Chris@0
|
298 $url->mergeOptions(['attributes' => $attributes]);
|
Chris@0
|
299 }
|
Chris@0
|
300 // The text has been processed by twig already, convert it to a safe object
|
Chris@0
|
301 // for the render system.
|
Chris@0
|
302 if ($text instanceof \Twig_Markup) {
|
Chris@0
|
303 $text = Markup::create($text);
|
Chris@0
|
304 }
|
Chris@0
|
305 $build = [
|
Chris@0
|
306 '#type' => 'link',
|
Chris@0
|
307 '#title' => $text,
|
Chris@0
|
308 '#url' => $url,
|
Chris@0
|
309 ];
|
Chris@0
|
310 return $build;
|
Chris@0
|
311 }
|
Chris@0
|
312
|
Chris@0
|
313 /**
|
Chris@0
|
314 * Gets the name of the active theme.
|
Chris@0
|
315 *
|
Chris@0
|
316 * @return string
|
Chris@0
|
317 * The name of the active theme.
|
Chris@0
|
318 */
|
Chris@0
|
319 public function getActiveTheme() {
|
Chris@0
|
320 return $this->themeManager->getActiveTheme()->getName();
|
Chris@0
|
321 }
|
Chris@0
|
322
|
Chris@0
|
323 /**
|
Chris@0
|
324 * Gets the path of the active theme.
|
Chris@0
|
325 *
|
Chris@0
|
326 * @return string
|
Chris@0
|
327 * The path to the active theme.
|
Chris@0
|
328 */
|
Chris@0
|
329 public function getActiveThemePath() {
|
Chris@0
|
330 return $this->themeManager->getActiveTheme()->getPath();
|
Chris@0
|
331 }
|
Chris@0
|
332
|
Chris@0
|
333 /**
|
Chris@0
|
334 * Determines at compile time whether the generated URL will be safe.
|
Chris@0
|
335 *
|
Chris@0
|
336 * Saves the unneeded automatic escaping for performance reasons.
|
Chris@0
|
337 *
|
Chris@0
|
338 * The URL generation process percent encodes non-alphanumeric characters.
|
Chris@0
|
339 * Thus, the only character within a URL that must be escaped in HTML is the
|
Chris@0
|
340 * ampersand ("&") which separates query params. Thus we cannot mark
|
Chris@0
|
341 * the generated URL as always safe, but only when we are sure there won't be
|
Chris@0
|
342 * multiple query params. This is the case when there are none or only one
|
Chris@0
|
343 * constant parameter given. For instance, we know beforehand this will not
|
Chris@0
|
344 * need to be escaped:
|
Chris@0
|
345 * - path('route')
|
Chris@0
|
346 * - path('route', {'param': 'value'})
|
Chris@0
|
347 * But the following may need to be escaped:
|
Chris@0
|
348 * - path('route', var)
|
Chris@0
|
349 * - path('route', {'param': ['val1', 'val2'] }) // a sub-array
|
Chris@0
|
350 * - path('route', {'param1': 'value1', 'param2': 'value2'})
|
Chris@0
|
351 * If param1 and param2 reference placeholders in the route, it would not
|
Chris@0
|
352 * need to be escaped, but we don't know that in advance.
|
Chris@0
|
353 *
|
Chris@0
|
354 * @param \Twig_Node $args_node
|
Chris@0
|
355 * The arguments of the path/url functions.
|
Chris@0
|
356 *
|
Chris@0
|
357 * @return array
|
Chris@0
|
358 * An array with the contexts the URL is safe
|
Chris@0
|
359 */
|
Chris@0
|
360 public function isUrlGenerationSafe(\Twig_Node $args_node) {
|
Chris@0
|
361 // Support named arguments.
|
Chris@0
|
362 $parameter_node = $args_node->hasNode('parameters') ? $args_node->getNode('parameters') : ($args_node->hasNode(1) ? $args_node->getNode(1) : NULL);
|
Chris@0
|
363
|
Chris@0
|
364 if (!isset($parameter_node) || $parameter_node instanceof \Twig_Node_Expression_Array && count($parameter_node) <= 2 &&
|
Chris@0
|
365 (!$parameter_node->hasNode(1) || $parameter_node->getNode(1) instanceof \Twig_Node_Expression_Constant)) {
|
Chris@0
|
366 return ['html'];
|
Chris@0
|
367 }
|
Chris@0
|
368
|
Chris@0
|
369 return [];
|
Chris@0
|
370 }
|
Chris@0
|
371
|
Chris@0
|
372 /**
|
Chris@0
|
373 * Attaches an asset library to the template, and hence to the response.
|
Chris@0
|
374 *
|
Chris@0
|
375 * Allows Twig templates to attach asset libraries using
|
Chris@0
|
376 * @code
|
Chris@0
|
377 * {{ attach_library('extension/library_name') }}
|
Chris@0
|
378 * @endcode
|
Chris@0
|
379 *
|
Chris@0
|
380 * @param string $library
|
Chris@0
|
381 * An asset library.
|
Chris@0
|
382 */
|
Chris@0
|
383 public function attachLibrary($library) {
|
Chris@18
|
384 assert(is_string($library), 'Argument must be a string.');
|
Chris@18
|
385
|
Chris@0
|
386 // Use Renderer::render() on a temporary render array to get additional
|
Chris@0
|
387 // bubbleable metadata on the render stack.
|
Chris@0
|
388 $template_attached = ['#attached' => ['library' => [$library]]];
|
Chris@0
|
389 $this->renderer->render($template_attached);
|
Chris@0
|
390 }
|
Chris@0
|
391
|
Chris@0
|
392 /**
|
Chris@0
|
393 * Provides a placeholder wrapper around ::escapeFilter.
|
Chris@0
|
394 *
|
Chris@0
|
395 * @param \Twig_Environment $env
|
Chris@0
|
396 * A Twig_Environment instance.
|
Chris@0
|
397 * @param mixed $string
|
Chris@0
|
398 * The value to be escaped.
|
Chris@0
|
399 *
|
Chris@0
|
400 * @return string|null
|
Chris@0
|
401 * The escaped, rendered output, or NULL if there is no valid output.
|
Chris@0
|
402 */
|
Chris@18
|
403 public function escapePlaceholder(\Twig_Environment $env, $string) {
|
Chris@18
|
404 $return = $this->escapeFilter($env, $string);
|
Chris@18
|
405
|
Chris@18
|
406 return $return ? '<em class="placeholder">' . $return . '</em>' : NULL;
|
Chris@0
|
407 }
|
Chris@0
|
408
|
Chris@0
|
409 /**
|
Chris@0
|
410 * Overrides twig_escape_filter().
|
Chris@0
|
411 *
|
Chris@0
|
412 * Replacement function for Twig's escape filter.
|
Chris@0
|
413 *
|
Chris@0
|
414 * Note: This function should be kept in sync with
|
Chris@0
|
415 * theme_render_and_autoescape().
|
Chris@0
|
416 *
|
Chris@0
|
417 * @param \Twig_Environment $env
|
Chris@0
|
418 * A Twig_Environment instance.
|
Chris@0
|
419 * @param mixed $arg
|
Chris@0
|
420 * The value to be escaped.
|
Chris@0
|
421 * @param string $strategy
|
Chris@0
|
422 * The escaping strategy. Defaults to 'html'.
|
Chris@0
|
423 * @param string $charset
|
Chris@0
|
424 * The charset.
|
Chris@0
|
425 * @param bool $autoescape
|
Chris@0
|
426 * Whether the function is called by the auto-escaping feature (TRUE) or by
|
Chris@0
|
427 * the developer (FALSE).
|
Chris@0
|
428 *
|
Chris@0
|
429 * @return string|null
|
Chris@0
|
430 * The escaped, rendered output, or NULL if there is no valid output.
|
Chris@0
|
431 *
|
Chris@0
|
432 * @throws \Exception
|
Chris@0
|
433 * When $arg is passed as an object which does not implement __toString(),
|
Chris@0
|
434 * RenderableInterface or toString().
|
Chris@0
|
435 *
|
Chris@0
|
436 * @todo Refactor this to keep it in sync with theme_render_and_autoescape()
|
Chris@0
|
437 * in https://www.drupal.org/node/2575065
|
Chris@0
|
438 */
|
Chris@0
|
439 public function escapeFilter(\Twig_Environment $env, $arg, $strategy = 'html', $charset = NULL, $autoescape = FALSE) {
|
Chris@0
|
440 // Check for a numeric zero int or float.
|
Chris@0
|
441 if ($arg === 0 || $arg === 0.0) {
|
Chris@0
|
442 return 0;
|
Chris@0
|
443 }
|
Chris@0
|
444
|
Chris@0
|
445 // Return early for NULL and empty arrays.
|
Chris@0
|
446 if ($arg == NULL) {
|
Chris@0
|
447 return NULL;
|
Chris@0
|
448 }
|
Chris@0
|
449
|
Chris@0
|
450 $this->bubbleArgMetadata($arg);
|
Chris@0
|
451
|
Chris@0
|
452 // Keep Twig_Markup objects intact to support autoescaping.
|
Chris@0
|
453 if ($autoescape && ($arg instanceof \Twig_Markup || $arg instanceof MarkupInterface)) {
|
Chris@0
|
454 return $arg;
|
Chris@0
|
455 }
|
Chris@0
|
456
|
Chris@0
|
457 $return = NULL;
|
Chris@0
|
458
|
Chris@0
|
459 if (is_scalar($arg)) {
|
Chris@0
|
460 $return = (string) $arg;
|
Chris@0
|
461 }
|
Chris@0
|
462 elseif (is_object($arg)) {
|
Chris@0
|
463 if ($arg instanceof RenderableInterface) {
|
Chris@0
|
464 $arg = $arg->toRenderable();
|
Chris@0
|
465 }
|
Chris@0
|
466 elseif (method_exists($arg, '__toString')) {
|
Chris@0
|
467 $return = (string) $arg;
|
Chris@0
|
468 }
|
Chris@0
|
469 // You can't throw exceptions in the magic PHP __toString() methods, see
|
Chris@0
|
470 // http://php.net/manual/language.oop5.magic.php#object.tostring so
|
Chris@0
|
471 // we also support a toString method.
|
Chris@0
|
472 elseif (method_exists($arg, 'toString')) {
|
Chris@0
|
473 $return = $arg->toString();
|
Chris@0
|
474 }
|
Chris@0
|
475 else {
|
Chris@0
|
476 throw new \Exception('Object of type ' . get_class($arg) . ' cannot be printed.');
|
Chris@0
|
477 }
|
Chris@0
|
478 }
|
Chris@0
|
479
|
Chris@0
|
480 // We have a string or an object converted to a string: Autoescape it!
|
Chris@0
|
481 if (isset($return)) {
|
Chris@0
|
482 if ($autoescape && $return instanceof MarkupInterface) {
|
Chris@0
|
483 return $return;
|
Chris@0
|
484 }
|
Chris@0
|
485 // Drupal only supports the HTML escaping strategy, so provide a
|
Chris@0
|
486 // fallback for other strategies.
|
Chris@0
|
487 if ($strategy == 'html') {
|
Chris@0
|
488 return Html::escape($return);
|
Chris@0
|
489 }
|
Chris@0
|
490 return twig_escape_filter($env, $return, $strategy, $charset, $autoescape);
|
Chris@0
|
491 }
|
Chris@0
|
492
|
Chris@0
|
493 // This is a normal render array, which is safe by definition, with
|
Chris@0
|
494 // special simple cases already handled.
|
Chris@0
|
495
|
Chris@0
|
496 // Early return if this element was pre-rendered (no need to re-render).
|
Chris@0
|
497 if (isset($arg['#printed']) && $arg['#printed'] == TRUE && isset($arg['#markup']) && strlen($arg['#markup']) > 0) {
|
Chris@0
|
498 return $arg['#markup'];
|
Chris@0
|
499 }
|
Chris@0
|
500 $arg['#printed'] = FALSE;
|
Chris@0
|
501 return $this->renderer->render($arg);
|
Chris@0
|
502 }
|
Chris@0
|
503
|
Chris@0
|
504 /**
|
Chris@0
|
505 * Bubbles Twig template argument's cacheability & attachment metadata.
|
Chris@0
|
506 *
|
Chris@0
|
507 * For example: a generated link or generated URL object is passed as a Twig
|
Chris@0
|
508 * template argument, and its bubbleable metadata must be bubbled.
|
Chris@0
|
509 *
|
Chris@0
|
510 * @see \Drupal\Core\GeneratedLink
|
Chris@0
|
511 * @see \Drupal\Core\GeneratedUrl
|
Chris@0
|
512 *
|
Chris@0
|
513 * @param mixed $arg
|
Chris@0
|
514 * A Twig template argument that is about to be printed.
|
Chris@0
|
515 *
|
Chris@0
|
516 * @see \Drupal\Core\Theme\ThemeManager::render()
|
Chris@0
|
517 * @see \Drupal\Core\Render\RendererInterface::render()
|
Chris@0
|
518 */
|
Chris@0
|
519 protected function bubbleArgMetadata($arg) {
|
Chris@0
|
520 // If it's a renderable, then it'll be up to the generated render array it
|
Chris@0
|
521 // returns to contain the necessary cacheability & attachment metadata. If
|
Chris@0
|
522 // it doesn't implement CacheableDependencyInterface or AttachmentsInterface
|
Chris@0
|
523 // then there is nothing to do here.
|
Chris@0
|
524 if ($arg instanceof RenderableInterface || !($arg instanceof CacheableDependencyInterface || $arg instanceof AttachmentsInterface)) {
|
Chris@0
|
525 return;
|
Chris@0
|
526 }
|
Chris@0
|
527
|
Chris@0
|
528 $arg_bubbleable = [];
|
Chris@0
|
529 BubbleableMetadata::createFromObject($arg)
|
Chris@0
|
530 ->applyTo($arg_bubbleable);
|
Chris@0
|
531
|
Chris@0
|
532 $this->renderer->render($arg_bubbleable);
|
Chris@0
|
533 }
|
Chris@0
|
534
|
Chris@0
|
535 /**
|
Chris@0
|
536 * Wrapper around render() for twig printed output.
|
Chris@0
|
537 *
|
Chris@0
|
538 * If an object is passed which does not implement __toString(),
|
Chris@0
|
539 * RenderableInterface or toString() then an exception is thrown;
|
Chris@0
|
540 * Other objects are casted to string. However in the case that the
|
Chris@0
|
541 * object is an instance of a Twig_Markup object it is returned directly
|
Chris@0
|
542 * to support auto escaping.
|
Chris@0
|
543 *
|
Chris@0
|
544 * If an array is passed it is rendered via render() and scalar values are
|
Chris@0
|
545 * returned directly.
|
Chris@0
|
546 *
|
Chris@0
|
547 * @param mixed $arg
|
Chris@0
|
548 * String, Object or Render Array.
|
Chris@0
|
549 *
|
Chris@0
|
550 * @throws \Exception
|
Chris@0
|
551 * When $arg is passed as an object which does not implement __toString(),
|
Chris@0
|
552 * RenderableInterface or toString().
|
Chris@0
|
553 *
|
Chris@0
|
554 * @return mixed
|
Chris@0
|
555 * The rendered output or an Twig_Markup object.
|
Chris@0
|
556 *
|
Chris@0
|
557 * @see render
|
Chris@0
|
558 * @see TwigNodeVisitor
|
Chris@0
|
559 */
|
Chris@0
|
560 public function renderVar($arg) {
|
Chris@0
|
561 // Check for a numeric zero int or float.
|
Chris@0
|
562 if ($arg === 0 || $arg === 0.0) {
|
Chris@0
|
563 return 0;
|
Chris@0
|
564 }
|
Chris@0
|
565
|
Chris@0
|
566 // Return early for NULL and empty arrays.
|
Chris@0
|
567 if ($arg == NULL) {
|
Chris@0
|
568 return NULL;
|
Chris@0
|
569 }
|
Chris@0
|
570
|
Chris@0
|
571 // Optimize for scalars as it is likely they come from the escape filter.
|
Chris@0
|
572 if (is_scalar($arg)) {
|
Chris@0
|
573 return $arg;
|
Chris@0
|
574 }
|
Chris@0
|
575
|
Chris@0
|
576 if (is_object($arg)) {
|
Chris@0
|
577 $this->bubbleArgMetadata($arg);
|
Chris@0
|
578 if ($arg instanceof RenderableInterface) {
|
Chris@0
|
579 $arg = $arg->toRenderable();
|
Chris@0
|
580 }
|
Chris@0
|
581 elseif (method_exists($arg, '__toString')) {
|
Chris@0
|
582 return (string) $arg;
|
Chris@0
|
583 }
|
Chris@0
|
584 // You can't throw exceptions in the magic PHP __toString() methods, see
|
Chris@0
|
585 // http://php.net/manual/language.oop5.magic.php#object.tostring so
|
Chris@0
|
586 // we also support a toString method.
|
Chris@0
|
587 elseif (method_exists($arg, 'toString')) {
|
Chris@0
|
588 return $arg->toString();
|
Chris@0
|
589 }
|
Chris@0
|
590 else {
|
Chris@0
|
591 throw new \Exception('Object of type ' . get_class($arg) . ' cannot be printed.');
|
Chris@0
|
592 }
|
Chris@0
|
593 }
|
Chris@0
|
594
|
Chris@0
|
595 // This is a render array, with special simple cases already handled.
|
Chris@0
|
596 // Early return if this element was pre-rendered (no need to re-render).
|
Chris@0
|
597 if (isset($arg['#printed']) && $arg['#printed'] == TRUE && isset($arg['#markup']) && strlen($arg['#markup']) > 0) {
|
Chris@0
|
598 return $arg['#markup'];
|
Chris@0
|
599 }
|
Chris@0
|
600 $arg['#printed'] = FALSE;
|
Chris@0
|
601 return $this->renderer->render($arg);
|
Chris@0
|
602 }
|
Chris@0
|
603
|
Chris@0
|
604 /**
|
Chris@0
|
605 * Joins several strings together safely.
|
Chris@0
|
606 *
|
Chris@0
|
607 * @param \Twig_Environment $env
|
Chris@0
|
608 * A Twig_Environment instance.
|
Chris@0
|
609 * @param mixed[]|\Traversable|null $value
|
Chris@0
|
610 * The pieces to join.
|
Chris@0
|
611 * @param string $glue
|
Chris@0
|
612 * The delimiter with which to join the string. Defaults to an empty string.
|
Chris@0
|
613 * This value is expected to be safe for output and user provided data
|
Chris@0
|
614 * should never be used as a glue.
|
Chris@0
|
615 *
|
Chris@0
|
616 * @return string
|
Chris@0
|
617 * The strings joined together.
|
Chris@0
|
618 */
|
Chris@0
|
619 public function safeJoin(\Twig_Environment $env, $value, $glue = '') {
|
Chris@0
|
620 if ($value instanceof \Traversable) {
|
Chris@0
|
621 $value = iterator_to_array($value, FALSE);
|
Chris@0
|
622 }
|
Chris@0
|
623
|
Chris@0
|
624 return implode($glue, array_map(function ($item) use ($env) {
|
Chris@0
|
625 // If $item is not marked safe then it will be escaped.
|
Chris@0
|
626 return $this->escapeFilter($env, $item, 'html', NULL, TRUE);
|
Chris@0
|
627 }, (array) $value));
|
Chris@0
|
628 }
|
Chris@0
|
629
|
Chris@0
|
630 /**
|
Chris@0
|
631 * Creates an Attribute object.
|
Chris@0
|
632 *
|
Chris@0
|
633 * @param array $attributes
|
Chris@0
|
634 * (optional) An associative array of key-value pairs to be converted to
|
Chris@0
|
635 * HTML attributes.
|
Chris@0
|
636 *
|
Chris@0
|
637 * @return \Drupal\Core\Template\Attribute
|
Chris@0
|
638 * An attributes object that has the given attributes.
|
Chris@0
|
639 */
|
Chris@0
|
640 public function createAttribute(array $attributes = []) {
|
Chris@0
|
641 return new Attribute($attributes);
|
Chris@0
|
642 }
|
Chris@0
|
643
|
Chris@18
|
644 /**
|
Chris@18
|
645 * Removes child elements from a copy of the original array.
|
Chris@18
|
646 *
|
Chris@18
|
647 * Creates a copy of the renderable array and removes child elements by key
|
Chris@18
|
648 * specified through filter's arguments. The copy can be printed without these
|
Chris@18
|
649 * elements. The original renderable array is still available and can be used
|
Chris@18
|
650 * to print child elements in their entirety in the twig template.
|
Chris@18
|
651 *
|
Chris@18
|
652 * @param array|object $element
|
Chris@18
|
653 * The parent renderable array to exclude the child items.
|
Chris@18
|
654 * @param string[] ...
|
Chris@18
|
655 * The string keys of $element to prevent printing.
|
Chris@18
|
656 *
|
Chris@18
|
657 * @return array
|
Chris@18
|
658 * The filtered renderable array.
|
Chris@18
|
659 */
|
Chris@18
|
660 public function withoutFilter($element) {
|
Chris@18
|
661 if ($element instanceof \ArrayAccess) {
|
Chris@18
|
662 $filtered_element = clone $element;
|
Chris@18
|
663 }
|
Chris@18
|
664 else {
|
Chris@18
|
665 $filtered_element = $element;
|
Chris@18
|
666 }
|
Chris@18
|
667 $args = func_get_args();
|
Chris@18
|
668 unset($args[0]);
|
Chris@18
|
669 foreach ($args as $arg) {
|
Chris@18
|
670 if (isset($filtered_element[$arg])) {
|
Chris@18
|
671 unset($filtered_element[$arg]);
|
Chris@18
|
672 }
|
Chris@18
|
673 }
|
Chris@18
|
674 return $filtered_element;
|
Chris@18
|
675 }
|
Chris@18
|
676
|
Chris@0
|
677 }
|