2024-06-20 14:10:42 +00:00
< ? php
/*
* This file is part of Twig .
*
* ( c ) Fabien Potencier
* ( c ) Armin Ronacher
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
namespace Twig ;
use Twig\Error\Error ;
use Twig\Error\RuntimeError ;
/**
* Default base class for compiled templates .
*
* This class is an implementation detail of how template compilation currently
* works , which might change . It should never be used directly . Use $twig -> load ()
* instead , which returns an instance of \Twig\TemplateWrapper .
*
* @ author Fabien Potencier < fabien @ symfony . com >
*
* @ internal
*/
abstract class Template
{
public const ANY_CALL = 'any' ;
public const ARRAY_CALL = 'array' ;
public const METHOD_CALL = 'method' ;
protected $parent ;
protected $parents = [];
protected $blocks = [];
protected $traits = [];
2025-01-13 09:56:01 +00:00
protected $traitAliases = [];
2024-06-20 14:10:42 +00:00
protected $extensions = [];
protected $sandbox ;
private $useYield ;
2025-01-13 09:56:01 +00:00
public function __construct (
protected Environment $env ,
) {
2024-06-20 14:10:42 +00:00
$this -> useYield = $env -> useYield ();
$this -> extensions = $env -> getExtensions ();
}
/**
* Returns the template name .
*/
2025-01-13 09:56:01 +00:00
abstract public function getTemplateName () : string ;
2024-06-20 14:10:42 +00:00
/**
* Returns debug information about the template .
*
2025-01-13 09:56:01 +00:00
* @ return array < int , int > Debug information
2024-06-20 14:10:42 +00:00
*/
2025-01-13 09:56:01 +00:00
abstract public function getDebugInfo () : array ;
2024-06-20 14:10:42 +00:00
/**
* Returns information about the original template source code .
*/
2025-01-13 09:56:01 +00:00
abstract public function getSourceContext () : Source ;
2024-06-20 14:10:42 +00:00
/**
* Returns the parent template .
*
* This method is for internal use only and should never be called
* directly .
*
* @ return self | TemplateWrapper | false The parent template or false if there is no parent
*/
2025-01-13 09:56:01 +00:00
public function getParent ( array $context ) : self | TemplateWrapper | false
2024-06-20 14:10:42 +00:00
{
if ( null !== $this -> parent ) {
return $this -> parent ;
}
2025-01-13 09:56:01 +00:00
if ( ! $parent = $this -> doGetParent ( $context )) {
return false ;
}
2024-06-20 14:10:42 +00:00
2025-01-13 09:56:01 +00:00
if ( $parent instanceof self || $parent instanceof TemplateWrapper ) {
return $this -> parents [ $parent -> getSourceContext () -> getName ()] = $parent ;
}
2024-06-20 14:10:42 +00:00
2025-01-13 09:56:01 +00:00
if ( ! isset ( $this -> parents [ $parent ])) {
$this -> parents [ $parent ] = $this -> loadTemplate ( $parent );
2024-06-20 14:10:42 +00:00
}
return $this -> parents [ $parent ];
}
2025-01-13 09:56:01 +00:00
protected function doGetParent ( array $context ) : bool | string | self | TemplateWrapper
2024-06-20 14:10:42 +00:00
{
return false ;
}
2025-01-13 09:56:01 +00:00
public function isTraitable () : bool
2024-06-20 14:10:42 +00:00
{
return true ;
}
/**
* Displays a parent block .
*
* This method is for internal use only and should never be called
* directly .
*
* @ param string $name The block name to display from the parent
* @ param array $context The context
* @ param array $blocks The current set of blocks
*/
2025-01-13 09:56:01 +00:00
public function displayParentBlock ( $name , array $context , array $blocks = []) : void
2024-06-20 14:10:42 +00:00
{
foreach ( $this -> yieldParentBlock ( $name , $context , $blocks ) as $data ) {
echo $data ;
}
}
/**
* Displays a block .
*
* This method is for internal use only and should never be called
* directly .
*
* @ param string $name The block name to display
* @ param array $context The context
* @ param array $blocks The current set of blocks
* @ param bool $useBlocks Whether to use the current set of blocks
*/
2025-01-13 09:56:01 +00:00
public function displayBlock ( $name , array $context , array $blocks = [], $useBlocks = true , ? self $templateContext = null ) : void
2024-06-20 14:10:42 +00:00
{
foreach ( $this -> yieldBlock ( $name , $context , $blocks , $useBlocks , $templateContext ) as $data ) {
echo $data ;
}
}
/**
* Renders a parent block .
*
* This method is for internal use only and should never be called
* directly .
*
* @ param string $name The block name to render from the parent
* @ param array $context The context
* @ param array $blocks The current set of blocks
*
* @ return string The rendered block
*/
2025-01-13 09:56:01 +00:00
public function renderParentBlock ( $name , array $context , array $blocks = []) : string
2024-06-20 14:10:42 +00:00
{
2025-01-13 09:56:01 +00:00
if ( ! $this -> useYield ) {
if ( $this -> env -> isDebug ()) {
ob_start ();
} else {
ob_start ( function () { return '' ; });
}
$this -> displayParentBlock ( $name , $context , $blocks );
return ob_get_clean ();
}
2024-06-20 14:10:42 +00:00
$content = '' ;
foreach ( $this -> yieldParentBlock ( $name , $context , $blocks ) as $data ) {
$content .= $data ;
}
return $content ;
}
/**
* Renders a block .
*
* This method is for internal use only and should never be called
* directly .
*
* @ param string $name The block name to render
* @ param array $context The context
* @ param array $blocks The current set of blocks
* @ param bool $useBlocks Whether to use the current set of blocks
*
* @ return string The rendered block
*/
2025-01-13 09:56:01 +00:00
public function renderBlock ( $name , array $context , array $blocks = [], $useBlocks = true ) : string
2024-06-20 14:10:42 +00:00
{
2025-01-13 09:56:01 +00:00
if ( ! $this -> useYield ) {
$level = ob_get_level ();
if ( $this -> env -> isDebug ()) {
ob_start ();
} else {
ob_start ( function () { return '' ; });
}
try {
$this -> displayBlock ( $name , $context , $blocks , $useBlocks );
} catch ( \Throwable $e ) {
while ( ob_get_level () > $level ) {
ob_end_clean ();
}
throw $e ;
}
return ob_get_clean ();
}
2024-06-20 14:10:42 +00:00
$content = '' ;
foreach ( $this -> yieldBlock ( $name , $context , $blocks , $useBlocks ) as $data ) {
$content .= $data ;
}
return $content ;
}
/**
* Returns whether a block exists or not in the current context of the template .
*
* This method checks blocks defined in the current template
* or defined in " used " traits or defined in parent templates .
*
* @ param string $name The block name
* @ param array $context The context
* @ param array $blocks The current set of blocks
*
* @ return bool true if the block exists , false otherwise
*/
2025-01-13 09:56:01 +00:00
public function hasBlock ( $name , array $context , array $blocks = []) : bool
2024-06-20 14:10:42 +00:00
{
if ( isset ( $blocks [ $name ])) {
return $blocks [ $name ][ 0 ] instanceof self ;
}
if ( isset ( $this -> blocks [ $name ])) {
return true ;
}
if ( $parent = $this -> getParent ( $context )) {
return $parent -> hasBlock ( $name , $context );
}
return false ;
}
/**
* Returns all block names in the current context of the template .
*
* This method checks blocks defined in the current template
* or defined in " used " traits or defined in parent templates .
*
* @ param array $context The context
* @ param array $blocks The current set of blocks
*
2025-01-13 09:56:01 +00:00
* @ return array < string > An array of block names
2024-06-20 14:10:42 +00:00
*/
2025-01-13 09:56:01 +00:00
public function getBlockNames ( array $context , array $blocks = []) : array
2024-06-20 14:10:42 +00:00
{
$names = array_merge ( array_keys ( $blocks ), array_keys ( $this -> blocks ));
if ( $parent = $this -> getParent ( $context )) {
$names = array_merge ( $names , $parent -> getBlockNames ( $context ));
}
return array_unique ( $names );
}
/**
* @ param string | TemplateWrapper | array < string | TemplateWrapper > $template
*/
2025-01-13 09:56:01 +00:00
protected function loadTemplate ( $template , $templateName = null , $line = null , $index = null ) : self | TemplateWrapper
2024-06-20 14:10:42 +00:00
{
try {
if ( \is_array ( $template )) {
return $this -> env -> resolveTemplate ( $template );
}
if ( $template instanceof TemplateWrapper ) {
return $template ;
}
if ( $template instanceof self ) {
trigger_deprecation ( 'twig/twig' , '3.9' , 'Passing a "%s" instance to "%s" is deprecated.' , self :: class , __METHOD__ );
return $template ;
}
if ( $template === $this -> getTemplateName ()) {
$class = static :: class ;
if ( false !== $pos = strrpos ( $class , '___' , - 1 )) {
$class = substr ( $class , 0 , $pos );
}
} else {
$class = $this -> env -> getTemplateClass ( $template );
}
return $this -> env -> loadTemplate ( $class , $template , $index );
} catch ( Error $e ) {
if ( ! $e -> getSourceContext ()) {
$e -> setSourceContext ( $templateName ? new Source ( '' , $templateName ) : $this -> getSourceContext ());
}
if ( $e -> getTemplateLine () > 0 ) {
throw $e ;
}
if ( ! $line ) {
$e -> guess ();
} else {
$e -> setTemplateLine ( $line );
}
throw $e ;
}
}
/**
* @ internal
2025-01-13 09:56:01 +00:00
* @ return $this
2024-06-20 14:10:42 +00:00
*/
2025-01-13 09:56:01 +00:00
public function unwrap () : self
2024-06-20 14:10:42 +00:00
{
return $this ;
}
/**
* Returns all blocks .
*
* This method is for internal use only and should never be called
* directly .
*
* @ return array An array of blocks
*/
2025-01-13 09:56:01 +00:00
public function getBlocks () : array
2024-06-20 14:10:42 +00:00
{
return $this -> blocks ;
}
public function display ( array $context , array $blocks = []) : void
{
foreach ( $this -> yield ( $context , $blocks ) as $data ) {
echo $data ;
}
}
public function render ( array $context ) : string
{
2025-01-13 09:56:01 +00:00
if ( ! $this -> useYield ) {
$level = ob_get_level ();
if ( $this -> env -> isDebug ()) {
ob_start ();
} else {
ob_start ( function () { return '' ; });
}
try {
$this -> display ( $context );
} catch ( \Throwable $e ) {
while ( ob_get_level () > $level ) {
ob_end_clean ();
}
throw $e ;
}
return ob_get_clean ();
}
2024-06-20 14:10:42 +00:00
$content = '' ;
foreach ( $this -> yield ( $context ) as $data ) {
$content .= $data ;
}
return $content ;
}
/**
2025-01-13 09:56:01 +00:00
* @ return iterable < scalar | \Stringable | null >
2024-06-20 14:10:42 +00:00
*/
public function yield ( array $context , array $blocks = []) : iterable
{
2025-01-13 09:56:01 +00:00
$context += $this -> env -> getGlobals ();
2024-06-20 14:10:42 +00:00
$blocks = array_merge ( $this -> blocks , $blocks );
try {
2025-01-13 09:56:01 +00:00
yield from $this -> doDisplay ( $context , $blocks );
2024-06-20 14:10:42 +00:00
} catch ( Error $e ) {
if ( ! $e -> getSourceContext ()) {
$e -> setSourceContext ( $this -> getSourceContext ());
}
// this is mostly useful for \Twig\Error\LoaderError exceptions
// see \Twig\Error\LoaderError
if ( - 1 === $e -> getTemplateLine ()) {
$e -> guess ();
}
throw $e ;
} catch ( \Throwable $e ) {
2024-09-05 17:51:48 +00:00
$e = new RuntimeError ( \sprintf ( 'An exception has been thrown during the rendering of a template ("%s").' , $e -> getMessage ()), - 1 , $this -> getSourceContext (), $e );
2024-06-20 14:10:42 +00:00
$e -> guess ();
throw $e ;
}
}
/**
2025-01-13 09:56:01 +00:00
* @ return iterable < scalar | \Stringable | null >
2024-06-20 14:10:42 +00:00
*/
2025-01-13 09:56:01 +00:00
public function yieldBlock ( $name , array $context , array $blocks = [], $useBlocks = true , ? self $templateContext = null ) : iterable
2024-06-20 14:10:42 +00:00
{
if ( $useBlocks && isset ( $blocks [ $name ])) {
$template = $blocks [ $name ][ 0 ];
$block = $blocks [ $name ][ 1 ];
} elseif ( isset ( $this -> blocks [ $name ])) {
$template = $this -> blocks [ $name ][ 0 ];
$block = $this -> blocks [ $name ][ 1 ];
} else {
$template = null ;
$block = null ;
}
// avoid RCEs when sandbox is enabled
if ( null !== $template && ! $template instanceof self ) {
throw new \LogicException ( 'A block must be a method on a \Twig\Template instance.' );
}
if ( null !== $template ) {
try {
2025-01-13 09:56:01 +00:00
yield from $template -> $block ( $context , $blocks );
2024-06-20 14:10:42 +00:00
} catch ( Error $e ) {
if ( ! $e -> getSourceContext ()) {
$e -> setSourceContext ( $template -> getSourceContext ());
}
// this is mostly useful for \Twig\Error\LoaderError exceptions
// see \Twig\Error\LoaderError
if ( - 1 === $e -> getTemplateLine ()) {
$e -> guess ();
}
throw $e ;
} catch ( \Throwable $e ) {
2024-09-05 17:51:48 +00:00
$e = new RuntimeError ( \sprintf ( 'An exception has been thrown during the rendering of a template ("%s").' , $e -> getMessage ()), - 1 , $template -> getSourceContext (), $e );
2024-06-20 14:10:42 +00:00
$e -> guess ();
throw $e ;
}
} elseif ( $parent = $this -> getParent ( $context )) {
yield from $parent -> unwrap () -> yieldBlock ( $name , $context , array_merge ( $this -> blocks , $blocks ), false , $templateContext ? ? $this );
} elseif ( isset ( $blocks [ $name ])) {
2024-09-05 17:51:48 +00:00
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 ());
2024-06-20 14:10:42 +00:00
} else {
2024-09-05 17:51:48 +00:00
throw new RuntimeError ( \sprintf ( 'Block "%s" on template "%s" does not exist.' , $name , $this -> getTemplateName ()), - 1 , ( $templateContext ? ? $this ) -> getSourceContext ());
2024-06-20 14:10:42 +00:00
}
}
/**
* Yields a parent block .
*
* This method is for internal use only and should never be called
* directly .
*
* @ param string $name The block name to display from the parent
* @ param array $context The context
* @ param array $blocks The current set of blocks
*
2025-01-13 09:56:01 +00:00
* @ return iterable < scalar | \Stringable | null >
2024-06-20 14:10:42 +00:00
*/
2025-01-13 09:56:01 +00:00
public function yieldParentBlock ( $name , array $context , array $blocks = []) : iterable
2024-06-20 14:10:42 +00:00
{
if ( isset ( $this -> traits [ $name ])) {
2025-01-13 09:56:01 +00:00
yield from $this -> traits [ $name ][ 0 ] -> yieldBlock ( $this -> traitAliases [ $name ] ? ? $name , $context , $blocks , false );
2024-06-20 14:10:42 +00:00
} elseif ( $parent = $this -> getParent ( $context )) {
yield from $parent -> unwrap () -> yieldBlock ( $name , $context , $blocks , false );
} else {
2024-09-05 17:51:48 +00:00
throw new RuntimeError ( \sprintf ( 'The template has no parent and no traits defining the "%s" block.' , $name ), - 1 , $this -> getSourceContext ());
2024-06-20 14:10:42 +00:00
}
}
2025-01-13 09:56:01 +00:00
protected function hasMacro ( string $name , array $context ) : bool
{
if ( method_exists ( $this , $name )) {
return true ;
}
if ( ! $parent = $this -> getParent ( $context )) {
return false ;
}
return $parent -> hasMacro ( $name , $context );
}
protected function getTemplateForMacro ( string $name , array $context , int $line , Source $source ) : Template
{
if ( method_exists ( $this , $name )) {
return $this ;
}
$parent = $this ;
while ( $parent = $parent -> getParent ( $context )) {
if ( method_exists ( $parent , $name )) {
return $parent ;
}
}
throw new RuntimeError ( \sprintf ( 'Macro "%s" is not defined in template "%s".' , substr ( $name , \strlen ( 'macro_' )), $this -> getTemplateName ()), $line , $source );
}
2024-06-20 14:10:42 +00:00
/**
* Auto - generated method to display the template with the given context .
*
* @ param array $context An array of parameters to pass to the template
* @ param array $blocks An array of blocks to pass to the template
2025-01-13 09:56:01 +00:00
*
* @ return iterable < scalar | \Stringable | null >
2024-06-20 14:10:42 +00:00
*/
2025-01-13 09:56:01 +00:00
abstract protected function doDisplay ( array $context , array $blocks = []) : iterable ;
2024-06-20 14:10:42 +00:00
}