Chris@0: renderer = $renderer; Chris@0: $this->urlGenerator = $url_generator; Chris@0: $this->themeManager = $theme_manager; Chris@0: $this->dateFormatter = $date_formatter; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets the URL generator. Chris@0: * Chris@0: * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator Chris@0: * The URL generator. Chris@0: * Chris@0: * @return $this Chris@0: * Chris@0: * @deprecated in Drupal 8.0.x-dev, will be removed before Drupal 9.0.0. Chris@0: */ Chris@0: public function setGenerators(UrlGeneratorInterface $url_generator) { Chris@0: return $this->setUrlGenerator($url_generator); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets the URL generator. Chris@0: * Chris@0: * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator Chris@0: * The URL generator. Chris@0: * Chris@0: * @return $this Chris@0: * Chris@0: * @deprecated in Drupal 8.3.x-dev, will be removed before Drupal 9.0.0. Chris@0: */ Chris@0: public function setUrlGenerator(UrlGeneratorInterface $url_generator) { Chris@0: $this->urlGenerator = $url_generator; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets the theme manager. Chris@0: * Chris@0: * @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager Chris@0: * The theme manager. Chris@0: * Chris@0: * @return $this Chris@0: * Chris@0: * @deprecated in Drupal 8.3.x-dev, will be removed before Drupal 9.0.0. Chris@0: */ Chris@0: public function setThemeManager(ThemeManagerInterface $theme_manager) { Chris@0: $this->themeManager = $theme_manager; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets the date formatter. Chris@0: * Chris@0: * @param \Drupal\Core\Datetime\DateFormatter $date_formatter Chris@0: * The date formatter. Chris@0: * Chris@0: * @return $this Chris@0: * Chris@0: * @deprecated in Drupal 8.3.x-dev, will be removed before Drupal 9.0.0. Chris@0: */ Chris@0: public function setDateFormatter(DateFormatterInterface $date_formatter) { Chris@0: $this->dateFormatter = $date_formatter; Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getFunctions() { Chris@0: return [ Chris@0: // This function will receive a renderable array, if an array is detected. Chris@0: new \Twig_SimpleFunction('render_var', [$this, 'renderVar']), Chris@0: // The url and path function are defined in close parallel to those found Chris@0: // in \Symfony\Bridge\Twig\Extension\RoutingExtension Chris@0: new \Twig_SimpleFunction('url', [$this, 'getUrl'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]), Chris@0: new \Twig_SimpleFunction('path', [$this, 'getPath'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]), Chris@0: new \Twig_SimpleFunction('link', [$this, 'getLink']), Chris@0: new \Twig_SimpleFunction('file_url', function ($uri) { Chris@0: return file_url_transform_relative(file_create_url($uri)); Chris@0: }), Chris@0: new \Twig_SimpleFunction('attach_library', [$this, 'attachLibrary']), Chris@0: new \Twig_SimpleFunction('active_theme_path', [$this, 'getActiveThemePath']), Chris@0: new \Twig_SimpleFunction('active_theme', [$this, 'getActiveTheme']), Chris@0: new \Twig_SimpleFunction('create_attribute', [$this, 'createAttribute']), Chris@0: ]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getFilters() { Chris@0: return [ Chris@0: // Translation filters. Chris@0: new \Twig_SimpleFilter('t', 't', ['is_safe' => ['html']]), Chris@0: new \Twig_SimpleFilter('trans', 't', ['is_safe' => ['html']]), Chris@0: // The "raw" filter is not detectable when parsing "trans" tags. To detect Chris@0: // which prefix must be used for translation (@, !, %), we must clone the Chris@0: // "raw" filter and give it identifiable names. These filters should only Chris@0: // be used in "trans" tags. Chris@0: // @see TwigNodeTrans::compileString() Chris@0: new \Twig_SimpleFilter('placeholder', [$this, 'escapePlaceholder'], ['is_safe' => ['html'], 'needs_environment' => TRUE]), Chris@0: Chris@0: // Replace twig's escape filter with our own. Chris@0: new \Twig_SimpleFilter('drupal_escape', [$this, 'escapeFilter'], ['needs_environment' => TRUE, 'is_safe_callback' => 'twig_escape_filter_is_safe']), Chris@0: Chris@0: // Implements safe joining. Chris@0: // @todo Make that the default for |join? Upstream issue: Chris@0: // https://github.com/fabpot/Twig/issues/1420 Chris@0: new \Twig_SimpleFilter('safe_join', [$this, 'safeJoin'], ['needs_environment' => TRUE, 'is_safe' => ['html']]), Chris@0: Chris@0: // Array filters. Chris@18: new \Twig_SimpleFilter('without', [$this, 'withoutFilter']), Chris@0: Chris@0: // CSS class and ID filters. Chris@0: new \Twig_SimpleFilter('clean_class', '\Drupal\Component\Utility\Html::getClass'), Chris@0: new \Twig_SimpleFilter('clean_id', '\Drupal\Component\Utility\Html::getId'), Chris@0: // This filter will render a renderable array to use the string results. Chris@0: new \Twig_SimpleFilter('render', [$this, 'renderVar']), Chris@0: new \Twig_SimpleFilter('format_date', [$this->dateFormatter, 'format']), Chris@0: ]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getNodeVisitors() { Chris@0: // The node visitor is needed to wrap all variables with Chris@0: // render_var -> TwigExtension->renderVar() function. Chris@0: return [ Chris@0: new TwigNodeVisitor(), Chris@0: ]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getTokenParsers() { Chris@0: return [ Chris@0: new TwigTransTokenParser(), Chris@0: ]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getName() { Chris@0: return 'drupal_core'; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Generates a URL path given a route name and parameters. Chris@0: * Chris@0: * @param $name Chris@0: * The name of the route. Chris@0: * @param array $parameters Chris@0: * An associative array of route parameters names and values. Chris@0: * @param array $options Chris@0: * (optional) An associative array of additional options. The 'absolute' Chris@0: * option is forced to be FALSE. Chris@0: * Chris@0: * @return string Chris@0: * The generated URL path (relative URL) for the given route. Chris@0: * Chris@0: * @see \Drupal\Core\Routing\UrlGeneratorInterface::generateFromRoute() Chris@0: */ Chris@0: public function getPath($name, $parameters = [], $options = []) { Chris@18: 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: Chris@0: $options['absolute'] = FALSE; Chris@0: return $this->urlGenerator->generateFromRoute($name, $parameters, $options); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Generates an absolute URL given a route name and parameters. Chris@0: * Chris@0: * @param $name Chris@0: * The name of the route. Chris@0: * @param array $parameters Chris@0: * An associative array of route parameter names and values. Chris@0: * @param array $options Chris@0: * (optional) An associative array of additional options. The 'absolute' Chris@0: * option is forced to be TRUE. Chris@0: * Chris@0: * @return string Chris@0: * The generated absolute URL for the given route. Chris@0: * Chris@0: * @todo Add an option for scheme-relative URLs. Chris@0: */ Chris@0: public function getUrl($name, $parameters = [], $options = []) { Chris@18: 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: Chris@0: // Generate URL. Chris@0: $options['absolute'] = TRUE; Chris@0: $generated_url = $this->urlGenerator->generateFromRoute($name, $parameters, $options, TRUE); Chris@0: Chris@0: // Return as render array, so we can bubble the bubbleable metadata. Chris@0: $build = ['#markup' => $generated_url->getGeneratedUrl()]; Chris@0: $generated_url->applyTo($build); Chris@0: return $build; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets a rendered link from a url object. Chris@0: * Chris@0: * @param string $text Chris@0: * The link text for the anchor tag as a translated string. Chris@0: * @param \Drupal\Core\Url|string $url Chris@0: * The URL object or string used for the link. Chris@0: * @param array|\Drupal\Core\Template\Attribute $attributes Chris@0: * An optional array or Attribute object of link attributes. Chris@0: * Chris@0: * @return array Chris@0: * A render array representing a link to the given URL. Chris@0: */ Chris@0: public function getLink($text, $url, $attributes = []) { Chris@18: assert(is_string($url) || $url instanceof Url, '$url must be a string or object of type \Drupal\Core\Url'); Chris@18: assert(is_array($attributes) || $attributes instanceof Attribute, '$attributes, if set, must be an array or object of type \Drupal\Core\Template\Attribute'); Chris@18: Chris@0: if (!$url instanceof Url) { Chris@0: $url = Url::fromUri($url); Chris@0: } Chris@0: // The twig extension should not modify the original URL object, this Chris@0: // ensures consistent rendering. Chris@0: // @see https://www.drupal.org/node/2842399 Chris@0: $url = clone $url; Chris@0: if ($attributes) { Chris@0: if ($attributes instanceof Attribute) { Chris@0: $attributes = $attributes->toArray(); Chris@0: } Chris@0: $url->mergeOptions(['attributes' => $attributes]); Chris@0: } Chris@0: // The text has been processed by twig already, convert it to a safe object Chris@0: // for the render system. Chris@0: if ($text instanceof \Twig_Markup) { Chris@0: $text = Markup::create($text); Chris@0: } Chris@0: $build = [ Chris@0: '#type' => 'link', Chris@0: '#title' => $text, Chris@0: '#url' => $url, Chris@0: ]; Chris@0: return $build; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the name of the active theme. Chris@0: * Chris@0: * @return string Chris@0: * The name of the active theme. Chris@0: */ Chris@0: public function getActiveTheme() { Chris@0: return $this->themeManager->getActiveTheme()->getName(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the path of the active theme. Chris@0: * Chris@0: * @return string Chris@0: * The path to the active theme. Chris@0: */ Chris@0: public function getActiveThemePath() { Chris@0: return $this->themeManager->getActiveTheme()->getPath(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Determines at compile time whether the generated URL will be safe. Chris@0: * Chris@0: * Saves the unneeded automatic escaping for performance reasons. Chris@0: * Chris@0: * The URL generation process percent encodes non-alphanumeric characters. Chris@0: * Thus, the only character within a URL that must be escaped in HTML is the Chris@0: * ampersand ("&") which separates query params. Thus we cannot mark Chris@0: * the generated URL as always safe, but only when we are sure there won't be Chris@0: * multiple query params. This is the case when there are none or only one Chris@0: * constant parameter given. For instance, we know beforehand this will not Chris@0: * need to be escaped: Chris@0: * - path('route') Chris@0: * - path('route', {'param': 'value'}) Chris@0: * But the following may need to be escaped: Chris@0: * - path('route', var) Chris@0: * - path('route', {'param': ['val1', 'val2'] }) // a sub-array Chris@0: * - path('route', {'param1': 'value1', 'param2': 'value2'}) Chris@0: * If param1 and param2 reference placeholders in the route, it would not Chris@0: * need to be escaped, but we don't know that in advance. Chris@0: * Chris@0: * @param \Twig_Node $args_node Chris@0: * The arguments of the path/url functions. Chris@0: * Chris@0: * @return array Chris@0: * An array with the contexts the URL is safe Chris@0: */ Chris@0: public function isUrlGenerationSafe(\Twig_Node $args_node) { Chris@0: // Support named arguments. Chris@0: $parameter_node = $args_node->hasNode('parameters') ? $args_node->getNode('parameters') : ($args_node->hasNode(1) ? $args_node->getNode(1) : NULL); Chris@0: Chris@0: if (!isset($parameter_node) || $parameter_node instanceof \Twig_Node_Expression_Array && count($parameter_node) <= 2 && Chris@0: (!$parameter_node->hasNode(1) || $parameter_node->getNode(1) instanceof \Twig_Node_Expression_Constant)) { Chris@0: return ['html']; Chris@0: } Chris@0: Chris@0: return []; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Attaches an asset library to the template, and hence to the response. Chris@0: * Chris@0: * Allows Twig templates to attach asset libraries using Chris@0: * @code Chris@0: * {{ attach_library('extension/library_name') }} Chris@0: * @endcode Chris@0: * Chris@0: * @param string $library Chris@0: * An asset library. Chris@0: */ Chris@0: public function attachLibrary($library) { Chris@18: assert(is_string($library), 'Argument must be a string.'); Chris@18: Chris@0: // Use Renderer::render() on a temporary render array to get additional Chris@0: // bubbleable metadata on the render stack. Chris@0: $template_attached = ['#attached' => ['library' => [$library]]]; Chris@0: $this->renderer->render($template_attached); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Provides a placeholder wrapper around ::escapeFilter. Chris@0: * Chris@0: * @param \Twig_Environment $env Chris@0: * A Twig_Environment instance. Chris@0: * @param mixed $string Chris@0: * The value to be escaped. Chris@0: * Chris@0: * @return string|null Chris@0: * The escaped, rendered output, or NULL if there is no valid output. Chris@0: */ Chris@18: public function escapePlaceholder(\Twig_Environment $env, $string) { Chris@18: $return = $this->escapeFilter($env, $string); Chris@18: Chris@18: return $return ? '' . $return . '' : NULL; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Overrides twig_escape_filter(). Chris@0: * Chris@0: * Replacement function for Twig's escape filter. Chris@0: * Chris@0: * Note: This function should be kept in sync with Chris@0: * theme_render_and_autoescape(). Chris@0: * Chris@0: * @param \Twig_Environment $env Chris@0: * A Twig_Environment instance. Chris@0: * @param mixed $arg Chris@0: * The value to be escaped. Chris@0: * @param string $strategy Chris@0: * The escaping strategy. Defaults to 'html'. Chris@0: * @param string $charset Chris@0: * The charset. Chris@0: * @param bool $autoescape Chris@0: * Whether the function is called by the auto-escaping feature (TRUE) or by Chris@0: * the developer (FALSE). Chris@0: * Chris@0: * @return string|null Chris@0: * The escaped, rendered output, or NULL if there is no valid output. Chris@0: * Chris@0: * @throws \Exception Chris@0: * When $arg is passed as an object which does not implement __toString(), Chris@0: * RenderableInterface or toString(). Chris@0: * Chris@0: * @todo Refactor this to keep it in sync with theme_render_and_autoescape() Chris@0: * in https://www.drupal.org/node/2575065 Chris@0: */ Chris@0: public function escapeFilter(\Twig_Environment $env, $arg, $strategy = 'html', $charset = NULL, $autoescape = FALSE) { Chris@0: // Check for a numeric zero int or float. Chris@0: if ($arg === 0 || $arg === 0.0) { Chris@0: return 0; Chris@0: } Chris@0: Chris@0: // Return early for NULL and empty arrays. Chris@0: if ($arg == NULL) { Chris@0: return NULL; Chris@0: } Chris@0: Chris@0: $this->bubbleArgMetadata($arg); Chris@0: Chris@0: // Keep Twig_Markup objects intact to support autoescaping. Chris@0: if ($autoescape && ($arg instanceof \Twig_Markup || $arg instanceof MarkupInterface)) { Chris@0: return $arg; Chris@0: } Chris@0: Chris@0: $return = NULL; Chris@0: Chris@0: if (is_scalar($arg)) { Chris@0: $return = (string) $arg; Chris@0: } Chris@0: elseif (is_object($arg)) { Chris@0: if ($arg instanceof RenderableInterface) { Chris@0: $arg = $arg->toRenderable(); Chris@0: } Chris@0: elseif (method_exists($arg, '__toString')) { Chris@0: $return = (string) $arg; Chris@0: } Chris@0: // You can't throw exceptions in the magic PHP __toString() methods, see Chris@0: // http://php.net/manual/language.oop5.magic.php#object.tostring so Chris@0: // we also support a toString method. Chris@0: elseif (method_exists($arg, 'toString')) { Chris@0: $return = $arg->toString(); Chris@0: } Chris@0: else { Chris@0: throw new \Exception('Object of type ' . get_class($arg) . ' cannot be printed.'); Chris@0: } Chris@0: } Chris@0: Chris@0: // We have a string or an object converted to a string: Autoescape it! Chris@0: if (isset($return)) { Chris@0: if ($autoescape && $return instanceof MarkupInterface) { Chris@0: return $return; Chris@0: } Chris@0: // Drupal only supports the HTML escaping strategy, so provide a Chris@0: // fallback for other strategies. Chris@0: if ($strategy == 'html') { Chris@0: return Html::escape($return); Chris@0: } Chris@0: return twig_escape_filter($env, $return, $strategy, $charset, $autoescape); Chris@0: } Chris@0: Chris@0: // This is a normal render array, which is safe by definition, with Chris@0: // special simple cases already handled. Chris@0: Chris@0: // Early return if this element was pre-rendered (no need to re-render). Chris@0: if (isset($arg['#printed']) && $arg['#printed'] == TRUE && isset($arg['#markup']) && strlen($arg['#markup']) > 0) { Chris@0: return $arg['#markup']; Chris@0: } Chris@0: $arg['#printed'] = FALSE; Chris@0: return $this->renderer->render($arg); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Bubbles Twig template argument's cacheability & attachment metadata. Chris@0: * Chris@0: * For example: a generated link or generated URL object is passed as a Twig Chris@0: * template argument, and its bubbleable metadata must be bubbled. Chris@0: * Chris@0: * @see \Drupal\Core\GeneratedLink Chris@0: * @see \Drupal\Core\GeneratedUrl Chris@0: * Chris@0: * @param mixed $arg Chris@0: * A Twig template argument that is about to be printed. Chris@0: * Chris@0: * @see \Drupal\Core\Theme\ThemeManager::render() Chris@0: * @see \Drupal\Core\Render\RendererInterface::render() Chris@0: */ Chris@0: protected function bubbleArgMetadata($arg) { Chris@0: // If it's a renderable, then it'll be up to the generated render array it Chris@0: // returns to contain the necessary cacheability & attachment metadata. If Chris@0: // it doesn't implement CacheableDependencyInterface or AttachmentsInterface Chris@0: // then there is nothing to do here. Chris@0: if ($arg instanceof RenderableInterface || !($arg instanceof CacheableDependencyInterface || $arg instanceof AttachmentsInterface)) { Chris@0: return; Chris@0: } Chris@0: Chris@0: $arg_bubbleable = []; Chris@0: BubbleableMetadata::createFromObject($arg) Chris@0: ->applyTo($arg_bubbleable); Chris@0: Chris@0: $this->renderer->render($arg_bubbleable); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Wrapper around render() for twig printed output. Chris@0: * Chris@0: * If an object is passed which does not implement __toString(), Chris@0: * RenderableInterface or toString() then an exception is thrown; Chris@0: * Other objects are casted to string. However in the case that the Chris@0: * object is an instance of a Twig_Markup object it is returned directly Chris@0: * to support auto escaping. Chris@0: * Chris@0: * If an array is passed it is rendered via render() and scalar values are Chris@0: * returned directly. Chris@0: * Chris@0: * @param mixed $arg Chris@0: * String, Object or Render Array. Chris@0: * Chris@0: * @throws \Exception Chris@0: * When $arg is passed as an object which does not implement __toString(), Chris@0: * RenderableInterface or toString(). Chris@0: * Chris@0: * @return mixed Chris@0: * The rendered output or an Twig_Markup object. Chris@0: * Chris@0: * @see render Chris@0: * @see TwigNodeVisitor Chris@0: */ Chris@0: public function renderVar($arg) { Chris@0: // Check for a numeric zero int or float. Chris@0: if ($arg === 0 || $arg === 0.0) { Chris@0: return 0; Chris@0: } Chris@0: Chris@0: // Return early for NULL and empty arrays. Chris@0: if ($arg == NULL) { Chris@0: return NULL; Chris@0: } Chris@0: Chris@0: // Optimize for scalars as it is likely they come from the escape filter. Chris@0: if (is_scalar($arg)) { Chris@0: return $arg; Chris@0: } Chris@0: Chris@0: if (is_object($arg)) { Chris@0: $this->bubbleArgMetadata($arg); Chris@0: if ($arg instanceof RenderableInterface) { Chris@0: $arg = $arg->toRenderable(); Chris@0: } Chris@0: elseif (method_exists($arg, '__toString')) { Chris@0: return (string) $arg; Chris@0: } Chris@0: // You can't throw exceptions in the magic PHP __toString() methods, see Chris@0: // http://php.net/manual/language.oop5.magic.php#object.tostring so Chris@0: // we also support a toString method. Chris@0: elseif (method_exists($arg, 'toString')) { Chris@0: return $arg->toString(); Chris@0: } Chris@0: else { Chris@0: throw new \Exception('Object of type ' . get_class($arg) . ' cannot be printed.'); Chris@0: } Chris@0: } Chris@0: Chris@0: // This is a render array, with special simple cases already handled. Chris@0: // Early return if this element was pre-rendered (no need to re-render). Chris@0: if (isset($arg['#printed']) && $arg['#printed'] == TRUE && isset($arg['#markup']) && strlen($arg['#markup']) > 0) { Chris@0: return $arg['#markup']; Chris@0: } Chris@0: $arg['#printed'] = FALSE; Chris@0: return $this->renderer->render($arg); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Joins several strings together safely. Chris@0: * Chris@0: * @param \Twig_Environment $env Chris@0: * A Twig_Environment instance. Chris@0: * @param mixed[]|\Traversable|null $value Chris@0: * The pieces to join. Chris@0: * @param string $glue Chris@0: * The delimiter with which to join the string. Defaults to an empty string. Chris@0: * This value is expected to be safe for output and user provided data Chris@0: * should never be used as a glue. Chris@0: * Chris@0: * @return string Chris@0: * The strings joined together. Chris@0: */ Chris@0: public function safeJoin(\Twig_Environment $env, $value, $glue = '') { Chris@0: if ($value instanceof \Traversable) { Chris@0: $value = iterator_to_array($value, FALSE); Chris@0: } Chris@0: Chris@0: return implode($glue, array_map(function ($item) use ($env) { Chris@0: // If $item is not marked safe then it will be escaped. Chris@0: return $this->escapeFilter($env, $item, 'html', NULL, TRUE); Chris@0: }, (array) $value)); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Creates an Attribute object. Chris@0: * Chris@0: * @param array $attributes Chris@0: * (optional) An associative array of key-value pairs to be converted to Chris@0: * HTML attributes. Chris@0: * Chris@0: * @return \Drupal\Core\Template\Attribute Chris@0: * An attributes object that has the given attributes. Chris@0: */ Chris@0: public function createAttribute(array $attributes = []) { Chris@0: return new Attribute($attributes); Chris@0: } Chris@0: Chris@18: /** Chris@18: * Removes child elements from a copy of the original array. Chris@18: * Chris@18: * Creates a copy of the renderable array and removes child elements by key Chris@18: * specified through filter's arguments. The copy can be printed without these Chris@18: * elements. The original renderable array is still available and can be used Chris@18: * to print child elements in their entirety in the twig template. Chris@18: * Chris@18: * @param array|object $element Chris@18: * The parent renderable array to exclude the child items. Chris@18: * @param string[] ... Chris@18: * The string keys of $element to prevent printing. Chris@18: * Chris@18: * @return array Chris@18: * The filtered renderable array. Chris@18: */ Chris@18: public function withoutFilter($element) { Chris@18: if ($element instanceof \ArrayAccess) { Chris@18: $filtered_element = clone $element; Chris@18: } Chris@18: else { Chris@18: $filtered_element = $element; Chris@18: } Chris@18: $args = func_get_args(); Chris@18: unset($args[0]); Chris@18: foreach ($args as $arg) { Chris@18: if (isset($filtered_element[$arg])) { Chris@18: unset($filtered_element[$arg]); Chris@18: } Chris@18: } Chris@18: return $filtered_element; Chris@18: } Chris@18: Chris@0: }