PageRenderTime 49ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/core/lib/Drupal/Core/Template/TwigExtension.php

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