vendor/twig/twig/src/Template.php line 332

  1. <?php
  2. /*
  3.  * This file is part of Twig.
  4.  *
  5.  * (c) Fabien Potencier
  6.  * (c) Armin Ronacher
  7.  *
  8.  * For the full copyright and license information, please view the LICENSE
  9.  * file that was distributed with this source code.
  10.  */
  11. namespace Twig;
  12. use Twig\Error\Error;
  13. use Twig\Error\LoaderError;
  14. use Twig\Error\RuntimeError;
  15. /**
  16.  * Default base class for compiled templates.
  17.  *
  18.  * This class is an implementation detail of how template compilation currently
  19.  * works, which might change. It should never be used directly. Use $twig->load()
  20.  * instead, which returns an instance of \Twig\TemplateWrapper.
  21.  *
  22.  * @author Fabien Potencier <fabien@symfony.com>
  23.  *
  24.  * @internal
  25.  */
  26. abstract class Template
  27. {
  28.     public const ANY_CALL 'any';
  29.     public const ARRAY_CALL 'array';
  30.     public const METHOD_CALL 'method';
  31.     protected $parent;
  32.     protected $parents = [];
  33.     protected $env;
  34.     protected $blocks = [];
  35.     protected $traits = [];
  36.     protected $extensions = [];
  37.     protected $sandbox;
  38.     private $useYield;
  39.     public function __construct(Environment $env)
  40.     {
  41.         $this->env $env;
  42.         $this->useYield $env->useYield();
  43.         $this->extensions $env->getExtensions();
  44.     }
  45.     /**
  46.      * Returns the template name.
  47.      *
  48.      * @return string The template name
  49.      */
  50.     abstract public function getTemplateName();
  51.     /**
  52.      * Returns debug information about the template.
  53.      *
  54.      * @return array Debug information
  55.      */
  56.     abstract public function getDebugInfo();
  57.     /**
  58.      * Returns information about the original template source code.
  59.      *
  60.      * @return Source
  61.      */
  62.     abstract public function getSourceContext();
  63.     /**
  64.      * Returns the parent template.
  65.      *
  66.      * This method is for internal use only and should never be called
  67.      * directly.
  68.      *
  69.      * @return self|TemplateWrapper|false The parent template or false if there is no parent
  70.      */
  71.     public function getParent(array $context)
  72.     {
  73.         if (null !== $this->parent) {
  74.             return $this->parent;
  75.         }
  76.         try {
  77.             if (!$parent $this->doGetParent($context)) {
  78.                 return false;
  79.             }
  80.             if ($parent instanceof self || $parent instanceof TemplateWrapper) {
  81.                 return $this->parents[$parent->getSourceContext()->getName()] = $parent;
  82.             }
  83.             if (!isset($this->parents[$parent])) {
  84.                 $this->parents[$parent] = $this->loadTemplate($parent);
  85.             }
  86.         } catch (LoaderError $e) {
  87.             $e->setSourceContext(null);
  88.             $e->guess();
  89.             throw $e;
  90.         }
  91.         return $this->parents[$parent];
  92.     }
  93.     protected function doGetParent(array $context)
  94.     {
  95.         return false;
  96.     }
  97.     public function isTraitable()
  98.     {
  99.         return true;
  100.     }
  101.     /**
  102.      * Displays a parent block.
  103.      *
  104.      * This method is for internal use only and should never be called
  105.      * directly.
  106.      *
  107.      * @param string $name    The block name to display from the parent
  108.      * @param array  $context The context
  109.      * @param array  $blocks  The current set of blocks
  110.      */
  111.     public function displayParentBlock($name, array $context, array $blocks = [])
  112.     {
  113.         foreach ($this->yieldParentBlock($name$context$blocks) as $data) {
  114.             echo $data;
  115.         }
  116.     }
  117.     /**
  118.      * Displays a block.
  119.      *
  120.      * This method is for internal use only and should never be called
  121.      * directly.
  122.      *
  123.      * @param string $name      The block name to display
  124.      * @param array  $context   The context
  125.      * @param array  $blocks    The current set of blocks
  126.      * @param bool   $useBlocks Whether to use the current set of blocks
  127.      */
  128.     public function displayBlock($name, array $context, array $blocks = [], $useBlocks true, ?self $templateContext null)
  129.     {
  130.         foreach ($this->yieldBlock($name$context$blocks$useBlocks$templateContext) as $data) {
  131.             echo $data;
  132.         }
  133.     }
  134.     /**
  135.      * Renders a parent block.
  136.      *
  137.      * This method is for internal use only and should never be called
  138.      * directly.
  139.      *
  140.      * @param string $name    The block name to render from the parent
  141.      * @param array  $context The context
  142.      * @param array  $blocks  The current set of blocks
  143.      *
  144.      * @return string The rendered block
  145.      */
  146.     public function renderParentBlock($name, array $context, array $blocks = [])
  147.     {
  148.         $content '';
  149.         foreach ($this->yieldParentBlock($name$context$blocks) as $data) {
  150.             $content .= $data;
  151.         }
  152.         return $content;
  153.     }
  154.     /**
  155.      * Renders a block.
  156.      *
  157.      * This method is for internal use only and should never be called
  158.      * directly.
  159.      *
  160.      * @param string $name      The block name to render
  161.      * @param array  $context   The context
  162.      * @param array  $blocks    The current set of blocks
  163.      * @param bool   $useBlocks Whether to use the current set of blocks
  164.      *
  165.      * @return string The rendered block
  166.      */
  167.     public function renderBlock($name, array $context, array $blocks = [], $useBlocks true)
  168.     {
  169.         $content '';
  170.         foreach ($this->yieldBlock($name$context$blocks$useBlocks) as $data) {
  171.             $content .= $data;
  172.         }
  173.         return $content;
  174.     }
  175.     /**
  176.      * Returns whether a block exists or not in the current context of the template.
  177.      *
  178.      * This method checks blocks defined in the current template
  179.      * or defined in "used" traits or defined in parent templates.
  180.      *
  181.      * @param string $name    The block name
  182.      * @param array  $context The context
  183.      * @param array  $blocks  The current set of blocks
  184.      *
  185.      * @return bool true if the block exists, false otherwise
  186.      */
  187.     public function hasBlock($name, array $context, array $blocks = [])
  188.     {
  189.         if (isset($blocks[$name])) {
  190.             return $blocks[$name][0] instanceof self;
  191.         }
  192.         if (isset($this->blocks[$name])) {
  193.             return true;
  194.         }
  195.         if ($parent $this->getParent($context)) {
  196.             return $parent->hasBlock($name$context);
  197.         }
  198.         return false;
  199.     }
  200.     /**
  201.      * Returns all block names in the current context of the template.
  202.      *
  203.      * This method checks blocks defined in the current template
  204.      * or defined in "used" traits or defined in parent templates.
  205.      *
  206.      * @param array $context The context
  207.      * @param array $blocks  The current set of blocks
  208.      *
  209.      * @return array An array of block names
  210.      */
  211.     public function getBlockNames(array $context, array $blocks = [])
  212.     {
  213.         $names array_merge(array_keys($blocks), array_keys($this->blocks));
  214.         if ($parent $this->getParent($context)) {
  215.             $names array_merge($names$parent->getBlockNames($context));
  216.         }
  217.         return array_unique($names);
  218.     }
  219.     /**
  220.      * @param string|TemplateWrapper|array<string|TemplateWrapper> $template
  221.      *
  222.      * @return self|TemplateWrapper
  223.      */
  224.     protected function loadTemplate($template$templateName null$line null$index null)
  225.     {
  226.         try {
  227.             if (\is_array($template)) {
  228.                 return $this->env->resolveTemplate($template);
  229.             }
  230.             if ($template instanceof TemplateWrapper) {
  231.                 return $template;
  232.             }
  233.             if ($template instanceof self) {
  234.                 trigger_deprecation('twig/twig''3.9''Passing a "%s" instance to "%s" is deprecated.'self::class, __METHOD__);
  235.                 return $template;
  236.             }
  237.             if ($template === $this->getTemplateName()) {
  238.                 $class = static::class;
  239.                 if (false !== $pos strrpos($class'___', -1)) {
  240.                     $class substr($class0$pos);
  241.                 }
  242.             } else {
  243.                 $class $this->env->getTemplateClass($template);
  244.             }
  245.             return $this->env->loadTemplate($class$template$index);
  246.         } catch (Error $e) {
  247.             if (!$e->getSourceContext()) {
  248.                 $e->setSourceContext($templateName ? new Source(''$templateName) : $this->getSourceContext());
  249.             }
  250.             if ($e->getTemplateLine() > 0) {
  251.                 throw $e;
  252.             }
  253.             if (!$line) {
  254.                 $e->guess();
  255.             } else {
  256.                 $e->setTemplateLine($line);
  257.             }
  258.             throw $e;
  259.         }
  260.     }
  261.     /**
  262.      * @internal
  263.      *
  264.      * @return self
  265.      */
  266.     public function unwrap()
  267.     {
  268.         return $this;
  269.     }
  270.     /**
  271.      * Returns all blocks.
  272.      *
  273.      * This method is for internal use only and should never be called
  274.      * directly.
  275.      *
  276.      * @return array An array of blocks
  277.      */
  278.     public function getBlocks()
  279.     {
  280.         return $this->blocks;
  281.     }
  282.     public function display(array $context, array $blocks = []): void
  283.     {
  284.         foreach ($this->yield($context$blocks) as $data) {
  285.             echo $data;
  286.         }
  287.     }
  288.     public function render(array $context): string
  289.     {
  290.         $content '';
  291.         foreach ($this->yield($context) as $data) {
  292.             $content .= $data;
  293.         }
  294.         return $content;
  295.     }
  296.     /**
  297.      * @return iterable<string>
  298.      */
  299.     public function yield(array $context, array $blocks = []): iterable
  300.     {
  301.         $context $this->env->mergeGlobals($context);
  302.         $blocks array_merge($this->blocks$blocks);
  303.         try {
  304.             if ($this->useYield) {
  305.                 yield from $this->doDisplay($context$blocks);
  306.                 return;
  307.             }
  308.             $level ob_get_level();
  309.             ob_start();
  310.             foreach ($this->doDisplay($context$blocks) as $data) {
  311.                 if (ob_get_length()) {
  312.                     $data ob_get_clean().$data;
  313.                     ob_start();
  314.                 }
  315.                 yield $data;
  316.             }
  317.             if (ob_get_length()) {
  318.                 yield ob_get_clean();
  319.             }
  320.         } catch (Error $e) {
  321.             if (!$e->getSourceContext()) {
  322.                 $e->setSourceContext($this->getSourceContext());
  323.             }
  324.             // this is mostly useful for \Twig\Error\LoaderError exceptions
  325.             // see \Twig\Error\LoaderError
  326.             if (-=== $e->getTemplateLine()) {
  327.                 $e->guess();
  328.             }
  329.             throw $e;
  330.         } catch (\Throwable $e) {
  331.             $e = new RuntimeError(\sprintf('An exception has been thrown during the rendering of a template ("%s").'$e->getMessage()), -1$this->getSourceContext(), $e);
  332.             $e->guess();
  333.             throw $e;
  334.         } finally {
  335.             if (!$this->useYield) {
  336.                 while (ob_get_level() > $level) {
  337.                     ob_end_clean();
  338.                 }
  339.             }
  340.         }
  341.     }
  342.     /**
  343.      * @return iterable<string>
  344.      */
  345.     public function yieldBlock($name, array $context, array $blocks = [], $useBlocks true, ?self $templateContext null)
  346.     {
  347.         if ($useBlocks && isset($blocks[$name])) {
  348.             $template $blocks[$name][0];
  349.             $block $blocks[$name][1];
  350.         } elseif (isset($this->blocks[$name])) {
  351.             $template $this->blocks[$name][0];
  352.             $block $this->blocks[$name][1];
  353.         } else {
  354.             $template null;
  355.             $block null;
  356.         }
  357.         // avoid RCEs when sandbox is enabled
  358.         if (null !== $template && !$template instanceof self) {
  359.             throw new \LogicException('A block must be a method on a \Twig\Template instance.');
  360.         }
  361.         if (null !== $template) {
  362.             try {
  363.                 if ($this->useYield) {
  364.                     yield from $template->$block($context$blocks);
  365.                     return;
  366.                 }
  367.                 $level ob_get_level();
  368.                 ob_start();
  369.                 foreach ($template->$block($context$blocks) as $data) {
  370.                     if (ob_get_length()) {
  371.                         $data ob_get_clean().$data;
  372.                         ob_start();
  373.                     }
  374.                     yield $data;
  375.                 }
  376.                 if (ob_get_length()) {
  377.                     yield ob_get_clean();
  378.                 }
  379.             } catch (Error $e) {
  380.                 if (!$e->getSourceContext()) {
  381.                     $e->setSourceContext($template->getSourceContext());
  382.                 }
  383.                 // this is mostly useful for \Twig\Error\LoaderError exceptions
  384.                 // see \Twig\Error\LoaderError
  385.                 if (-=== $e->getTemplateLine()) {
  386.                     $e->guess();
  387.                 }
  388.                 throw $e;
  389.             } catch (\Throwable $e) {
  390.                 $e = new RuntimeError(\sprintf('An exception has been thrown during the rendering of a template ("%s").'$e->getMessage()), -1$template->getSourceContext(), $e);
  391.                 $e->guess();
  392.                 throw $e;
  393.             } finally {
  394.                 if (!$this->useYield) {
  395.                     while (ob_get_level() > $level) {
  396.                         ob_end_clean();
  397.                     }
  398.                 }
  399.             }
  400.         } elseif ($parent $this->getParent($context)) {
  401.             yield from $parent->unwrap()->yieldBlock($name$contextarray_merge($this->blocks$blocks), false$templateContext ?? $this);
  402.         } elseif (isset($blocks[$name])) {
  403.             throw new RuntimeError(\sprintf('Block "%s" should not call parent() in "%s" as the block does not exist in the parent template "%s".'$name$blocks[$name][0]->getTemplateName(), $this->getTemplateName()), -1$blocks[$name][0]->getSourceContext());
  404.         } else {
  405.             throw new RuntimeError(\sprintf('Block "%s" on template "%s" does not exist.'$name$this->getTemplateName()), -1, ($templateContext ?? $this)->getSourceContext());
  406.         }
  407.     }
  408.     /**
  409.      * Yields a parent block.
  410.      *
  411.      * This method is for internal use only and should never be called
  412.      * directly.
  413.      *
  414.      * @param string $name    The block name to display from the parent
  415.      * @param array  $context The context
  416.      * @param array  $blocks  The current set of blocks
  417.      *
  418.      * @return iterable<string>
  419.      */
  420.     public function yieldParentBlock($name, array $context, array $blocks = [])
  421.     {
  422.         if (isset($this->traits[$name])) {
  423.             yield from $this->traits[$name][0]->yieldBlock($name$context$blocksfalse);
  424.         } elseif ($parent $this->getParent($context)) {
  425.             yield from $parent->unwrap()->yieldBlock($name$context$blocksfalse);
  426.         } else {
  427.             throw new RuntimeError(\sprintf('The template has no parent and no traits defining the "%s" block.'$name), -1$this->getSourceContext());
  428.         }
  429.     }
  430.     /**
  431.      * Auto-generated method to display the template with the given context.
  432.      *
  433.      * @param array $context An array of parameters to pass to the template
  434.      * @param array $blocks  An array of blocks to pass to the template
  435.      */
  436.     abstract protected function doDisplay(array $context, array $blocks = []);
  437. }