vendor/contao/core-bundle/src/Twig/Extension/ContaoExtension.php line 93

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4.  * This file is part of Contao.
  5.  *
  6.  * (c) Leo Feyer
  7.  *
  8.  * @license LGPL-3.0-or-later
  9.  */
  10. namespace Contao\CoreBundle\Twig\Extension;
  11. use Contao\BackendTemplateTrait;
  12. use Contao\CoreBundle\Twig\Inheritance\DynamicExtendsTokenParser;
  13. use Contao\CoreBundle\Twig\Inheritance\DynamicIncludeTokenParser;
  14. use Contao\CoreBundle\Twig\Inheritance\TemplateHierarchyInterface;
  15. use Contao\CoreBundle\Twig\Interop\ContaoEscaper;
  16. use Contao\CoreBundle\Twig\Interop\ContaoEscaperNodeVisitor;
  17. use Contao\CoreBundle\Twig\Interop\PhpTemplateProxyNodeVisitor;
  18. use Contao\CoreBundle\Twig\Runtime\FigureRendererRuntime;
  19. use Contao\CoreBundle\Twig\Runtime\InsertTagRuntime;
  20. use Contao\CoreBundle\Twig\Runtime\LegacyTemplateFunctionsRuntime;
  21. use Contao\CoreBundle\Twig\Runtime\PictureConfigurationRuntime;
  22. use Contao\CoreBundle\Twig\Runtime\SchemaOrgRuntime;
  23. use Contao\FrontendTemplateTrait;
  24. use Contao\Template;
  25. use Twig\Environment;
  26. use Twig\Extension\AbstractExtension;
  27. use Twig\Extension\CoreExtension;
  28. use Twig\Extension\EscaperExtension;
  29. use Twig\TwigFunction;
  30. use Webmozart\PathUtil\Path;
  31. /**
  32.  * @experimental
  33.  */
  34. final class ContaoExtension extends AbstractExtension
  35. {
  36.     /**
  37.      * @var Environment
  38.      */
  39.     private $environment;
  40.     /**
  41.      * @var TemplateHierarchyInterface
  42.      */
  43.     private $hierarchy;
  44.     /**
  45.      * @var array
  46.      */
  47.     private $contaoEscaperFilterRules = [];
  48.     public function __construct(Environment $environmentTemplateHierarchyInterface $hierarchy)
  49.     {
  50.         $this->environment $environment;
  51.         $this->hierarchy $hierarchy;
  52.         $contaoEscaper = new ContaoEscaper();
  53.         /** @var EscaperExtension $escaperExtension */
  54.         $escaperExtension $environment->getExtension(EscaperExtension::class);
  55.         $escaperExtension->setEscaper('contao_html', [$contaoEscaper'escapeHtml']);
  56.         $escaperExtension->setEscaper('contao_html_attr', [$contaoEscaper'escapeHtmlAttr']);
  57.         // Use our escaper on all templates in the `@Contao` and `@Contao_*` namespaces
  58.         $this->addContaoEscaperRule('%^@Contao(_[a-zA-Z0-9_-]*)?/%');
  59.     }
  60.     /**
  61.      * Adds a Contao escaper rule.
  62.      *
  63.      * If a template name matches any of the defined rules, it will be processed
  64.      * with the 'contao_html' escaper strategy. Make sure your rule will only
  65.      * match templates with input encoded contexts!
  66.      */
  67.     public function addContaoEscaperRule(string $regularExpression): void
  68.     {
  69.         if (\in_array($regularExpression$this->contaoEscaperFilterRulestrue)) {
  70.             return;
  71.         }
  72.         $this->contaoEscaperFilterRules[] = $regularExpression;
  73.     }
  74.     public function getNodeVisitors(): array
  75.     {
  76.         return [
  77.             // Enables the 'contao_twig' escaper for Contao templates with
  78.             // input encoding
  79.             new ContaoEscaperNodeVisitor(
  80.                 function () {
  81.                     return $this->contaoEscaperFilterRules;
  82.                 }
  83.             ),
  84.             // Allows rendering PHP templates with the legacy framework by
  85.             // installing proxy nodes
  86.             new PhpTemplateProxyNodeVisitor(self::class),
  87.         ];
  88.     }
  89.     public function getTokenParsers(): array
  90.     {
  91.         return [
  92.             // Overwrite the parsers for the 'extends' and 'include' tags to
  93.             // additionally support the Contao template hierarchy
  94.             new DynamicExtendsTokenParser($this->hierarchy),
  95.             new DynamicIncludeTokenParser($this->hierarchy),
  96.         ];
  97.     }
  98.     public function getFunctions(): array
  99.     {
  100.         $includeFunctionCallable $this->getTwigIncludeFunction()->getCallable();
  101.         return [
  102.             // Overwrite the 'include' function to additionally support the
  103.             // Contao template hierarchy
  104.             new TwigFunction(
  105.                 'include',
  106.                 function (Environment $env$context$template$variables = [], $withContext true$ignoreMissing false$sandboxed false /* we need named arguments here */) use ($includeFunctionCallable) {
  107.                     $args = \func_get_args();
  108.                     $args[2] = DynamicIncludeTokenParser::adjustTemplateName((string) $template$this->hierarchy);
  109.                     return $includeFunctionCallable(...$args);
  110.                 },
  111.                 ['needs_environment' => true'needs_context' => true'is_safe' => ['all']]
  112.             ),
  113.             new TwigFunction(
  114.                 'contao_figure',
  115.                 [FigureRendererRuntime::class, 'render'],
  116.                 ['is_safe' => ['html']]
  117.             ),
  118.             new TwigFunction(
  119.                 'picture_config',
  120.                 [PictureConfigurationRuntime::class, 'fromArray']
  121.             ),
  122.             new TwigFunction(
  123.                 'insert_tag',
  124.                 [InsertTagRuntime::class, 'replace'],
  125.                 ['is_safe' => ['html']]
  126.             ),
  127.             new TwigFunction(
  128.                 'add_schema_org',
  129.                 [SchemaOrgRuntime::class, 'add']
  130.             ),
  131.             new TwigFunction(
  132.                 'contao_sections',
  133.                 [LegacyTemplateFunctionsRuntime::class, 'renderLayoutSections'],
  134.                 ['needs_context' => true'is_safe' => ['html']]
  135.             ),
  136.             new TwigFunction(
  137.                 'contao_section',
  138.                 [LegacyTemplateFunctionsRuntime::class, 'renderLayoutSection'],
  139.                 ['needs_context' => true'is_safe' => ['html']]
  140.             ),
  141.             new TwigFunction(
  142.                 'render_contao_backend_template',
  143.                 [LegacyTemplateFunctionsRuntime::class, 'renderContaoBackendTemplate'],
  144.                 ['is_safe' => ['html']]
  145.             ),
  146.         ];
  147.     }
  148.     /**
  149.      * @see \Contao\CoreBundle\Twig\Interop\PhpTemplateProxyNode
  150.      * @see \Contao\CoreBundle\Twig\Interop\PhpTemplateProxyNodeVisitor
  151.      *
  152.      * @internal
  153.      */
  154.     public function renderLegacyTemplate(string $name, array $blocks, array $context): string
  155.     {
  156.         $template Path::getFilenameWithoutExtension($name);
  157.         $partialTemplate = new class($template) extends Template {
  158.             use FrontendTemplateTrait;
  159.             use BackendTemplateTrait;
  160.             public function setBlocks(array $blocks): void
  161.             {
  162.                 $this->arrBlocks array_map(
  163.                     static function ($block) {
  164.                         return \is_array($block) ? $block : [$block];
  165.                     },
  166.                     $blocks
  167.                 );
  168.             }
  169.             public function parse(): string
  170.             {
  171.                 return $this->inherit();
  172.             }
  173.             protected function renderTwigSurrogateIfExists(): ?string
  174.             {
  175.                 return null;
  176.             }
  177.         };
  178.         $partialTemplate->setData($context);
  179.         $partialTemplate->setBlocks($blocks);
  180.         return $partialTemplate->parse();
  181.     }
  182.     private function getTwigIncludeFunction(): TwigFunction
  183.     {
  184.         foreach ($this->environment->getExtension(CoreExtension::class)->getFunctions() as $function) {
  185.             if ('include' === $function->getName()) {
  186.                 return $function;
  187.             }
  188.         }
  189.         throw new \RuntimeException(sprintf('The %s class was expected to register the "include" Twig function but did not.'CoreExtension::class));
  190.     }
  191. }