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