annotate core/lib/Drupal/Core/Template/TwigExtension.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
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 }