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@0
|
180 new \Twig_SimpleFilter('without', 'twig_without'),
|
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@0
|
235 $options['absolute'] = FALSE;
|
Chris@0
|
236 return $this->urlGenerator->generateFromRoute($name, $parameters, $options);
|
Chris@0
|
237 }
|
Chris@0
|
238
|
Chris@0
|
239 /**
|
Chris@0
|
240 * Generates an absolute URL given a route name and parameters.
|
Chris@0
|
241 *
|
Chris@0
|
242 * @param $name
|
Chris@0
|
243 * The name of the route.
|
Chris@0
|
244 * @param array $parameters
|
Chris@0
|
245 * An associative array of route parameter names and values.
|
Chris@0
|
246 * @param array $options
|
Chris@0
|
247 * (optional) An associative array of additional options. The 'absolute'
|
Chris@0
|
248 * option is forced to be TRUE.
|
Chris@0
|
249 *
|
Chris@0
|
250 * @return string
|
Chris@0
|
251 * The generated absolute URL for the given route.
|
Chris@0
|
252 *
|
Chris@0
|
253 * @todo Add an option for scheme-relative URLs.
|
Chris@0
|
254 */
|
Chris@0
|
255 public function getUrl($name, $parameters = [], $options = []) {
|
Chris@0
|
256 // Generate URL.
|
Chris@0
|
257 $options['absolute'] = TRUE;
|
Chris@0
|
258 $generated_url = $this->urlGenerator->generateFromRoute($name, $parameters, $options, TRUE);
|
Chris@0
|
259
|
Chris@0
|
260 // Return as render array, so we can bubble the bubbleable metadata.
|
Chris@0
|
261 $build = ['#markup' => $generated_url->getGeneratedUrl()];
|
Chris@0
|
262 $generated_url->applyTo($build);
|
Chris@0
|
263 return $build;
|
Chris@0
|
264 }
|
Chris@0
|
265
|
Chris@0
|
266 /**
|
Chris@0
|
267 * Gets a rendered link from a url object.
|
Chris@0
|
268 *
|
Chris@0
|
269 * @param string $text
|
Chris@0
|
270 * The link text for the anchor tag as a translated string.
|
Chris@0
|
271 * @param \Drupal\Core\Url|string $url
|
Chris@0
|
272 * The URL object or string used for the link.
|
Chris@0
|
273 * @param array|\Drupal\Core\Template\Attribute $attributes
|
Chris@0
|
274 * An optional array or Attribute object of link attributes.
|
Chris@0
|
275 *
|
Chris@0
|
276 * @return array
|
Chris@0
|
277 * A render array representing a link to the given URL.
|
Chris@0
|
278 */
|
Chris@0
|
279 public function getLink($text, $url, $attributes = []) {
|
Chris@0
|
280 if (!$url instanceof Url) {
|
Chris@0
|
281 $url = Url::fromUri($url);
|
Chris@0
|
282 }
|
Chris@0
|
283 // The twig extension should not modify the original URL object, this
|
Chris@0
|
284 // ensures consistent rendering.
|
Chris@0
|
285 // @see https://www.drupal.org/node/2842399
|
Chris@0
|
286 $url = clone $url;
|
Chris@0
|
287 if ($attributes) {
|
Chris@0
|
288 if ($attributes instanceof Attribute) {
|
Chris@0
|
289 $attributes = $attributes->toArray();
|
Chris@0
|
290 }
|
Chris@0
|
291 $url->mergeOptions(['attributes' => $attributes]);
|
Chris@0
|
292 }
|
Chris@0
|
293 // The text has been processed by twig already, convert it to a safe object
|
Chris@0
|
294 // for the render system.
|
Chris@0
|
295 if ($text instanceof \Twig_Markup) {
|
Chris@0
|
296 $text = Markup::create($text);
|
Chris@0
|
297 }
|
Chris@0
|
298 $build = [
|
Chris@0
|
299 '#type' => 'link',
|
Chris@0
|
300 '#title' => $text,
|
Chris@0
|
301 '#url' => $url,
|
Chris@0
|
302 ];
|
Chris@0
|
303 return $build;
|
Chris@0
|
304 }
|
Chris@0
|
305
|
Chris@0
|
306 /**
|
Chris@0
|
307 * Gets the name of the active theme.
|
Chris@0
|
308 *
|
Chris@0
|
309 * @return string
|
Chris@0
|
310 * The name of the active theme.
|
Chris@0
|
311 */
|
Chris@0
|
312 public function getActiveTheme() {
|
Chris@0
|
313 return $this->themeManager->getActiveTheme()->getName();
|
Chris@0
|
314 }
|
Chris@0
|
315
|
Chris@0
|
316 /**
|
Chris@0
|
317 * Gets the path of the active theme.
|
Chris@0
|
318 *
|
Chris@0
|
319 * @return string
|
Chris@0
|
320 * The path to the active theme.
|
Chris@0
|
321 */
|
Chris@0
|
322 public function getActiveThemePath() {
|
Chris@0
|
323 return $this->themeManager->getActiveTheme()->getPath();
|
Chris@0
|
324 }
|
Chris@0
|
325
|
Chris@0
|
326 /**
|
Chris@0
|
327 * Determines at compile time whether the generated URL will be safe.
|
Chris@0
|
328 *
|
Chris@0
|
329 * Saves the unneeded automatic escaping for performance reasons.
|
Chris@0
|
330 *
|
Chris@0
|
331 * The URL generation process percent encodes non-alphanumeric characters.
|
Chris@0
|
332 * Thus, the only character within a URL that must be escaped in HTML is the
|
Chris@0
|
333 * ampersand ("&") which separates query params. Thus we cannot mark
|
Chris@0
|
334 * the generated URL as always safe, but only when we are sure there won't be
|
Chris@0
|
335 * multiple query params. This is the case when there are none or only one
|
Chris@0
|
336 * constant parameter given. For instance, we know beforehand this will not
|
Chris@0
|
337 * need to be escaped:
|
Chris@0
|
338 * - path('route')
|
Chris@0
|
339 * - path('route', {'param': 'value'})
|
Chris@0
|
340 * But the following may need to be escaped:
|
Chris@0
|
341 * - path('route', var)
|
Chris@0
|
342 * - path('route', {'param': ['val1', 'val2'] }) // a sub-array
|
Chris@0
|
343 * - path('route', {'param1': 'value1', 'param2': 'value2'})
|
Chris@0
|
344 * If param1 and param2 reference placeholders in the route, it would not
|
Chris@0
|
345 * need to be escaped, but we don't know that in advance.
|
Chris@0
|
346 *
|
Chris@0
|
347 * @param \Twig_Node $args_node
|
Chris@0
|
348 * The arguments of the path/url functions.
|
Chris@0
|
349 *
|
Chris@0
|
350 * @return array
|
Chris@0
|
351 * An array with the contexts the URL is safe
|
Chris@0
|
352 */
|
Chris@0
|
353 public function isUrlGenerationSafe(\Twig_Node $args_node) {
|
Chris@0
|
354 // Support named arguments.
|
Chris@0
|
355 $parameter_node = $args_node->hasNode('parameters') ? $args_node->getNode('parameters') : ($args_node->hasNode(1) ? $args_node->getNode(1) : NULL);
|
Chris@0
|
356
|
Chris@0
|
357 if (!isset($parameter_node) || $parameter_node instanceof \Twig_Node_Expression_Array && count($parameter_node) <= 2 &&
|
Chris@0
|
358 (!$parameter_node->hasNode(1) || $parameter_node->getNode(1) instanceof \Twig_Node_Expression_Constant)) {
|
Chris@0
|
359 return ['html'];
|
Chris@0
|
360 }
|
Chris@0
|
361
|
Chris@0
|
362 return [];
|
Chris@0
|
363 }
|
Chris@0
|
364
|
Chris@0
|
365 /**
|
Chris@0
|
366 * Attaches an asset library to the template, and hence to the response.
|
Chris@0
|
367 *
|
Chris@0
|
368 * Allows Twig templates to attach asset libraries using
|
Chris@0
|
369 * @code
|
Chris@0
|
370 * {{ attach_library('extension/library_name') }}
|
Chris@0
|
371 * @endcode
|
Chris@0
|
372 *
|
Chris@0
|
373 * @param string $library
|
Chris@0
|
374 * An asset library.
|
Chris@0
|
375 */
|
Chris@0
|
376 public function attachLibrary($library) {
|
Chris@0
|
377 // Use Renderer::render() on a temporary render array to get additional
|
Chris@0
|
378 // bubbleable metadata on the render stack.
|
Chris@0
|
379 $template_attached = ['#attached' => ['library' => [$library]]];
|
Chris@0
|
380 $this->renderer->render($template_attached);
|
Chris@0
|
381 }
|
Chris@0
|
382
|
Chris@0
|
383 /**
|
Chris@0
|
384 * Provides a placeholder wrapper around ::escapeFilter.
|
Chris@0
|
385 *
|
Chris@0
|
386 * @param \Twig_Environment $env
|
Chris@0
|
387 * A Twig_Environment instance.
|
Chris@0
|
388 * @param mixed $string
|
Chris@0
|
389 * The value to be escaped.
|
Chris@0
|
390 *
|
Chris@0
|
391 * @return string|null
|
Chris@0
|
392 * The escaped, rendered output, or NULL if there is no valid output.
|
Chris@0
|
393 */
|
Chris@0
|
394 public function escapePlaceholder($env, $string) {
|
Chris@0
|
395 return '<em class="placeholder">' . $this->escapeFilter($env, $string) . '</em>';
|
Chris@0
|
396 }
|
Chris@0
|
397
|
Chris@0
|
398 /**
|
Chris@0
|
399 * Overrides twig_escape_filter().
|
Chris@0
|
400 *
|
Chris@0
|
401 * Replacement function for Twig's escape filter.
|
Chris@0
|
402 *
|
Chris@0
|
403 * Note: This function should be kept in sync with
|
Chris@0
|
404 * theme_render_and_autoescape().
|
Chris@0
|
405 *
|
Chris@0
|
406 * @param \Twig_Environment $env
|
Chris@0
|
407 * A Twig_Environment instance.
|
Chris@0
|
408 * @param mixed $arg
|
Chris@0
|
409 * The value to be escaped.
|
Chris@0
|
410 * @param string $strategy
|
Chris@0
|
411 * The escaping strategy. Defaults to 'html'.
|
Chris@0
|
412 * @param string $charset
|
Chris@0
|
413 * The charset.
|
Chris@0
|
414 * @param bool $autoescape
|
Chris@0
|
415 * Whether the function is called by the auto-escaping feature (TRUE) or by
|
Chris@0
|
416 * the developer (FALSE).
|
Chris@0
|
417 *
|
Chris@0
|
418 * @return string|null
|
Chris@0
|
419 * The escaped, rendered output, or NULL if there is no valid output.
|
Chris@0
|
420 *
|
Chris@0
|
421 * @throws \Exception
|
Chris@0
|
422 * When $arg is passed as an object which does not implement __toString(),
|
Chris@0
|
423 * RenderableInterface or toString().
|
Chris@0
|
424 *
|
Chris@0
|
425 * @todo Refactor this to keep it in sync with theme_render_and_autoescape()
|
Chris@0
|
426 * in https://www.drupal.org/node/2575065
|
Chris@0
|
427 */
|
Chris@0
|
428 public function escapeFilter(\Twig_Environment $env, $arg, $strategy = 'html', $charset = NULL, $autoescape = FALSE) {
|
Chris@0
|
429 // Check for a numeric zero int or float.
|
Chris@0
|
430 if ($arg === 0 || $arg === 0.0) {
|
Chris@0
|
431 return 0;
|
Chris@0
|
432 }
|
Chris@0
|
433
|
Chris@0
|
434 // Return early for NULL and empty arrays.
|
Chris@0
|
435 if ($arg == NULL) {
|
Chris@0
|
436 return NULL;
|
Chris@0
|
437 }
|
Chris@0
|
438
|
Chris@0
|
439 $this->bubbleArgMetadata($arg);
|
Chris@0
|
440
|
Chris@0
|
441 // Keep Twig_Markup objects intact to support autoescaping.
|
Chris@0
|
442 if ($autoescape && ($arg instanceof \Twig_Markup || $arg instanceof MarkupInterface)) {
|
Chris@0
|
443 return $arg;
|
Chris@0
|
444 }
|
Chris@0
|
445
|
Chris@0
|
446 $return = NULL;
|
Chris@0
|
447
|
Chris@0
|
448 if (is_scalar($arg)) {
|
Chris@0
|
449 $return = (string) $arg;
|
Chris@0
|
450 }
|
Chris@0
|
451 elseif (is_object($arg)) {
|
Chris@0
|
452 if ($arg instanceof RenderableInterface) {
|
Chris@0
|
453 $arg = $arg->toRenderable();
|
Chris@0
|
454 }
|
Chris@0
|
455 elseif (method_exists($arg, '__toString')) {
|
Chris@0
|
456 $return = (string) $arg;
|
Chris@0
|
457 }
|
Chris@0
|
458 // You can't throw exceptions in the magic PHP __toString() methods, see
|
Chris@0
|
459 // http://php.net/manual/language.oop5.magic.php#object.tostring so
|
Chris@0
|
460 // we also support a toString method.
|
Chris@0
|
461 elseif (method_exists($arg, 'toString')) {
|
Chris@0
|
462 $return = $arg->toString();
|
Chris@0
|
463 }
|
Chris@0
|
464 else {
|
Chris@0
|
465 throw new \Exception('Object of type ' . get_class($arg) . ' cannot be printed.');
|
Chris@0
|
466 }
|
Chris@0
|
467 }
|
Chris@0
|
468
|
Chris@0
|
469 // We have a string or an object converted to a string: Autoescape it!
|
Chris@0
|
470 if (isset($return)) {
|
Chris@0
|
471 if ($autoescape && $return instanceof MarkupInterface) {
|
Chris@0
|
472 return $return;
|
Chris@0
|
473 }
|
Chris@0
|
474 // Drupal only supports the HTML escaping strategy, so provide a
|
Chris@0
|
475 // fallback for other strategies.
|
Chris@0
|
476 if ($strategy == 'html') {
|
Chris@0
|
477 return Html::escape($return);
|
Chris@0
|
478 }
|
Chris@0
|
479 return twig_escape_filter($env, $return, $strategy, $charset, $autoescape);
|
Chris@0
|
480 }
|
Chris@0
|
481
|
Chris@0
|
482 // This is a normal render array, which is safe by definition, with
|
Chris@0
|
483 // special simple cases already handled.
|
Chris@0
|
484
|
Chris@0
|
485 // Early return if this element was pre-rendered (no need to re-render).
|
Chris@0
|
486 if (isset($arg['#printed']) && $arg['#printed'] == TRUE && isset($arg['#markup']) && strlen($arg['#markup']) > 0) {
|
Chris@0
|
487 return $arg['#markup'];
|
Chris@0
|
488 }
|
Chris@0
|
489 $arg['#printed'] = FALSE;
|
Chris@0
|
490 return $this->renderer->render($arg);
|
Chris@0
|
491 }
|
Chris@0
|
492
|
Chris@0
|
493 /**
|
Chris@0
|
494 * Bubbles Twig template argument's cacheability & attachment metadata.
|
Chris@0
|
495 *
|
Chris@0
|
496 * For example: a generated link or generated URL object is passed as a Twig
|
Chris@0
|
497 * template argument, and its bubbleable metadata must be bubbled.
|
Chris@0
|
498 *
|
Chris@0
|
499 * @see \Drupal\Core\GeneratedLink
|
Chris@0
|
500 * @see \Drupal\Core\GeneratedUrl
|
Chris@0
|
501 *
|
Chris@0
|
502 * @param mixed $arg
|
Chris@0
|
503 * A Twig template argument that is about to be printed.
|
Chris@0
|
504 *
|
Chris@0
|
505 * @see \Drupal\Core\Theme\ThemeManager::render()
|
Chris@0
|
506 * @see \Drupal\Core\Render\RendererInterface::render()
|
Chris@0
|
507 */
|
Chris@0
|
508 protected function bubbleArgMetadata($arg) {
|
Chris@0
|
509 // If it's a renderable, then it'll be up to the generated render array it
|
Chris@0
|
510 // returns to contain the necessary cacheability & attachment metadata. If
|
Chris@0
|
511 // it doesn't implement CacheableDependencyInterface or AttachmentsInterface
|
Chris@0
|
512 // then there is nothing to do here.
|
Chris@0
|
513 if ($arg instanceof RenderableInterface || !($arg instanceof CacheableDependencyInterface || $arg instanceof AttachmentsInterface)) {
|
Chris@0
|
514 return;
|
Chris@0
|
515 }
|
Chris@0
|
516
|
Chris@0
|
517 $arg_bubbleable = [];
|
Chris@0
|
518 BubbleableMetadata::createFromObject($arg)
|
Chris@0
|
519 ->applyTo($arg_bubbleable);
|
Chris@0
|
520
|
Chris@0
|
521 $this->renderer->render($arg_bubbleable);
|
Chris@0
|
522 }
|
Chris@0
|
523
|
Chris@0
|
524 /**
|
Chris@0
|
525 * Wrapper around render() for twig printed output.
|
Chris@0
|
526 *
|
Chris@0
|
527 * If an object is passed which does not implement __toString(),
|
Chris@0
|
528 * RenderableInterface or toString() then an exception is thrown;
|
Chris@0
|
529 * Other objects are casted to string. However in the case that the
|
Chris@0
|
530 * object is an instance of a Twig_Markup object it is returned directly
|
Chris@0
|
531 * to support auto escaping.
|
Chris@0
|
532 *
|
Chris@0
|
533 * If an array is passed it is rendered via render() and scalar values are
|
Chris@0
|
534 * returned directly.
|
Chris@0
|
535 *
|
Chris@0
|
536 * @param mixed $arg
|
Chris@0
|
537 * String, Object or Render Array.
|
Chris@0
|
538 *
|
Chris@0
|
539 * @throws \Exception
|
Chris@0
|
540 * When $arg is passed as an object which does not implement __toString(),
|
Chris@0
|
541 * RenderableInterface or toString().
|
Chris@0
|
542 *
|
Chris@0
|
543 * @return mixed
|
Chris@0
|
544 * The rendered output or an Twig_Markup object.
|
Chris@0
|
545 *
|
Chris@0
|
546 * @see render
|
Chris@0
|
547 * @see TwigNodeVisitor
|
Chris@0
|
548 */
|
Chris@0
|
549 public function renderVar($arg) {
|
Chris@0
|
550 // Check for a numeric zero int or float.
|
Chris@0
|
551 if ($arg === 0 || $arg === 0.0) {
|
Chris@0
|
552 return 0;
|
Chris@0
|
553 }
|
Chris@0
|
554
|
Chris@0
|
555 // Return early for NULL and empty arrays.
|
Chris@0
|
556 if ($arg == NULL) {
|
Chris@0
|
557 return NULL;
|
Chris@0
|
558 }
|
Chris@0
|
559
|
Chris@0
|
560 // Optimize for scalars as it is likely they come from the escape filter.
|
Chris@0
|
561 if (is_scalar($arg)) {
|
Chris@0
|
562 return $arg;
|
Chris@0
|
563 }
|
Chris@0
|
564
|
Chris@0
|
565 if (is_object($arg)) {
|
Chris@0
|
566 $this->bubbleArgMetadata($arg);
|
Chris@0
|
567 if ($arg instanceof RenderableInterface) {
|
Chris@0
|
568 $arg = $arg->toRenderable();
|
Chris@0
|
569 }
|
Chris@0
|
570 elseif (method_exists($arg, '__toString')) {
|
Chris@0
|
571 return (string) $arg;
|
Chris@0
|
572 }
|
Chris@0
|
573 // You can't throw exceptions in the magic PHP __toString() methods, see
|
Chris@0
|
574 // http://php.net/manual/language.oop5.magic.php#object.tostring so
|
Chris@0
|
575 // we also support a toString method.
|
Chris@0
|
576 elseif (method_exists($arg, 'toString')) {
|
Chris@0
|
577 return $arg->toString();
|
Chris@0
|
578 }
|
Chris@0
|
579 else {
|
Chris@0
|
580 throw new \Exception('Object of type ' . get_class($arg) . ' cannot be printed.');
|
Chris@0
|
581 }
|
Chris@0
|
582 }
|
Chris@0
|
583
|
Chris@0
|
584 // This is a render array, with special simple cases already handled.
|
Chris@0
|
585 // Early return if this element was pre-rendered (no need to re-render).
|
Chris@0
|
586 if (isset($arg['#printed']) && $arg['#printed'] == TRUE && isset($arg['#markup']) && strlen($arg['#markup']) > 0) {
|
Chris@0
|
587 return $arg['#markup'];
|
Chris@0
|
588 }
|
Chris@0
|
589 $arg['#printed'] = FALSE;
|
Chris@0
|
590 return $this->renderer->render($arg);
|
Chris@0
|
591 }
|
Chris@0
|
592
|
Chris@0
|
593 /**
|
Chris@0
|
594 * Joins several strings together safely.
|
Chris@0
|
595 *
|
Chris@0
|
596 * @param \Twig_Environment $env
|
Chris@0
|
597 * A Twig_Environment instance.
|
Chris@0
|
598 * @param mixed[]|\Traversable|null $value
|
Chris@0
|
599 * The pieces to join.
|
Chris@0
|
600 * @param string $glue
|
Chris@0
|
601 * The delimiter with which to join the string. Defaults to an empty string.
|
Chris@0
|
602 * This value is expected to be safe for output and user provided data
|
Chris@0
|
603 * should never be used as a glue.
|
Chris@0
|
604 *
|
Chris@0
|
605 * @return string
|
Chris@0
|
606 * The strings joined together.
|
Chris@0
|
607 */
|
Chris@0
|
608 public function safeJoin(\Twig_Environment $env, $value, $glue = '') {
|
Chris@0
|
609 if ($value instanceof \Traversable) {
|
Chris@0
|
610 $value = iterator_to_array($value, FALSE);
|
Chris@0
|
611 }
|
Chris@0
|
612
|
Chris@0
|
613 return implode($glue, array_map(function ($item) use ($env) {
|
Chris@0
|
614 // If $item is not marked safe then it will be escaped.
|
Chris@0
|
615 return $this->escapeFilter($env, $item, 'html', NULL, TRUE);
|
Chris@0
|
616 }, (array) $value));
|
Chris@0
|
617 }
|
Chris@0
|
618
|
Chris@0
|
619 /**
|
Chris@0
|
620 * Creates an Attribute object.
|
Chris@0
|
621 *
|
Chris@0
|
622 * @param array $attributes
|
Chris@0
|
623 * (optional) An associative array of key-value pairs to be converted to
|
Chris@0
|
624 * HTML attributes.
|
Chris@0
|
625 *
|
Chris@0
|
626 * @return \Drupal\Core\Template\Attribute
|
Chris@0
|
627 * An attributes object that has the given attributes.
|
Chris@0
|
628 */
|
Chris@0
|
629 public function createAttribute(array $attributes = []) {
|
Chris@0
|
630 return new Attribute($attributes);
|
Chris@0
|
631 }
|
Chris@0
|
632
|
Chris@0
|
633 }
|