vendor/contao/core-bundle/src/Resources/contao/library/Contao/TemplateInheritance.php line 316

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of Contao.
  4.  *
  5.  * (c) Leo Feyer
  6.  *
  7.  * @license LGPL-3.0-or-later
  8.  */
  9. namespace Contao;
  10. use Contao\CoreBundle\Monolog\ContaoContext;
  11. use Psr\Log\LogLevel;
  12. use Symfony\Component\DependencyInjection\ContainerInterface;
  13. /**
  14.  * Provides the template inheritance logic
  15.  *
  16.  * @author Leo Feyer <https://github.com/leofeyer>
  17.  */
  18. trait TemplateInheritance
  19. {
  20.     /**
  21.      * Template file
  22.      * @var string
  23.      */
  24.     protected $strTemplate;
  25.     /**
  26.      * Parent template
  27.      * @var string
  28.      */
  29.     protected $strParent;
  30.     /**
  31.      * Default template
  32.      * @var string
  33.      */
  34.     protected $strDefault;
  35.     /**
  36.      * Output format
  37.      * @var string
  38.      */
  39.     protected $strFormat 'html5';
  40.     /**
  41.      * Tag ending
  42.      * @var string
  43.      */
  44.     protected $strTagEnding '>';
  45.     /**
  46.      * Blocks
  47.      * @var array
  48.      */
  49.     protected $arrBlocks = array();
  50.     /**
  51.      * Block names
  52.      * @var array
  53.      */
  54.     protected $arrBlockNames = array();
  55.     /**
  56.      * Buffer level
  57.      * @var int
  58.      */
  59.     protected $intBufferLevel 0;
  60.     /**
  61.      * @var bool|null
  62.      */
  63.     protected $blnDebug;
  64.     /**
  65.      * Parse the template file and return it as string
  66.      *
  67.      * @return string The template markup
  68.      */
  69.     public function inherit()
  70.     {
  71.         if (null !== ($result $this->renderTwigSurrogateIfExists()))
  72.         {
  73.             return $result;
  74.         }
  75.         $strBuffer '';
  76.         // Start with the template itself
  77.         $this->strParent $this->strTemplate;
  78.         // Include the parent templates
  79.         while ($this->strParent !== null)
  80.         {
  81.             $strCurrent $this->strParent;
  82.             $strParent $this->strDefault ?: $this->getTemplatePath($this->strParent$this->strFormat);
  83.             // Reset the flags
  84.             $this->strParent null;
  85.             $this->strDefault null;
  86.             ob_start();
  87.             $this->intBufferLevel 1;
  88.             try
  89.             {
  90.                 if (file_exists($strParent))
  91.                 {
  92.                     include $strParent;
  93.                 }
  94.                 else
  95.                 {
  96.                     System::getContainer()
  97.                         ->get('monolog.logger.contao')
  98.                         ->log(
  99.                             LogLevel::ERROR,
  100.                             'Invalid template path: ' StringUtil::stripRootDir($strParent),
  101.                             array('contao' => new ContaoContext(__METHOD__ContaoContext::ERROR))
  102.                         )
  103.                     ;
  104.                 }
  105.                 // Capture the output of the root template
  106.                 if ($this->strParent === null)
  107.                 {
  108.                     $strBuffer ob_get_contents();
  109.                 }
  110.                 elseif ($this->strParent == $strCurrent)
  111.                 {
  112.                     $this->strDefault $this->getTemplatePath($this->strParent$this->strFormattrue);
  113.                 }
  114.             }
  115.             finally
  116.             {
  117.                 for ($i=0$i<$this->intBufferLevel$i++)
  118.                 {
  119.                     ob_end_clean();
  120.                 }
  121.             }
  122.         }
  123.         // Reset the internal arrays
  124.         $this->arrBlocks = array();
  125.         $blnDebug $this->blnDebug;
  126.         if ($blnDebug === null)
  127.         {
  128.             $blnDebug System::getContainer()->getParameter('kernel.debug');
  129.             // Backwards compatibility
  130.             if ($blnDebug !== (bool) ($GLOBALS['TL_CONFIG']['debugMode'] ?? false))
  131.             {
  132.                 trigger_deprecation('contao/core-bundle''4.12''Dynamically setting TL_CONFIG.debugMode has been deprecated. Use %s::setDebug() instead.'__CLASS__);
  133.                 $blnDebug = (bool) ($GLOBALS['TL_CONFIG']['debugMode'] ?? false);
  134.             }
  135.         }
  136.         // Add start and end markers in debug mode
  137.         if ($blnDebug)
  138.         {
  139.             $strRelPath StringUtil::stripRootDir($this->getTemplatePath($this->strTemplate$this->strFormat));
  140.             $strBuffer "\n<!-- TEMPLATE START: $strRelPath -->\n$strBuffer\n<!-- TEMPLATE END: $strRelPath -->\n";
  141.         }
  142.         return $strBuffer;
  143.     }
  144.     public function setDebug(bool $debug null): self
  145.     {
  146.         $this->blnDebug $debug;
  147.         return $this;
  148.     }
  149.     /**
  150.      * Extend another template
  151.      *
  152.      * @param string $name The template name
  153.      */
  154.     public function extend($name)
  155.     {
  156.         $this->strParent $name;
  157.     }
  158.     /**
  159.      * Insert the content of the parent block
  160.      */
  161.     public function parent()
  162.     {
  163.         echo '[[TL_PARENT]]';
  164.     }
  165.     /**
  166.      * Start a new block
  167.      *
  168.      * @param string $name The block name
  169.      *
  170.      * @throws \Exception If a child templates contains nested blocks
  171.      */
  172.     public function block($name)
  173.     {
  174.         $this->arrBlockNames[] = $name;
  175.         // Root template
  176.         if ($this->strParent === null)
  177.         {
  178.             // Register the block name
  179.             if (!isset($this->arrBlocks[$name]))
  180.             {
  181.                 $this->arrBlocks[$name] = '[[TL_PARENT]]';
  182.             }
  183.             // Combine the contents of the child blocks
  184.             elseif (\is_array($this->arrBlocks[$name]))
  185.             {
  186.                 $callback = static function ($current$parent)
  187.                 {
  188.                     return str_replace('[[TL_PARENT]]'$parent$current);
  189.                 };
  190.                 $this->arrBlocks[$name] = array_reduce($this->arrBlocks[$name], $callback'[[TL_PARENT]]');
  191.             }
  192.             // Handle nested blocks
  193.             if ($this->arrBlocks[$name] != '[[TL_PARENT]]')
  194.             {
  195.                 // Output everything before the first TL_PARENT tag
  196.                 if (strpos($this->arrBlocks[$name], '[[TL_PARENT]]') !== false)
  197.                 {
  198.                     list($content) = explode('[[TL_PARENT]]'$this->arrBlocks[$name], 2);
  199.                     echo $content;
  200.                 }
  201.                 // Output the current block and start a new output buffer to remove the following blocks
  202.                 else
  203.                 {
  204.                     echo $this->arrBlocks[$name];
  205.                     ob_start();
  206.                     ++$this->intBufferLevel;
  207.                 }
  208.             }
  209.         }
  210.         // Child template
  211.         else
  212.         {
  213.             // Clean the output buffer
  214.             ob_clean();
  215.             // Check for nested blocks
  216.             if (\count($this->arrBlockNames) > 1)
  217.             {
  218.                 throw new \Exception('Nested blocks are not allowed in child templates');
  219.             }
  220.         }
  221.     }
  222.     /**
  223.      * End a block
  224.      *
  225.      * @throws \Exception If there is no open block
  226.      */
  227.     public function endblock()
  228.     {
  229.         // Check for open blocks
  230.         if (empty($this->arrBlockNames))
  231.         {
  232.             throw new \Exception('You must start a block before you can end it');
  233.         }
  234.         // Get the block name
  235.         $name array_pop($this->arrBlockNames);
  236.         // Root template
  237.         if ($this->strParent === null)
  238.         {
  239.             // Handle nested blocks
  240.             if ($this->arrBlocks[$name] != '[[TL_PARENT]]')
  241.             {
  242.                 // Output everything after the first TL_PARENT tag
  243.                 if (strpos($this->arrBlocks[$name], '[[TL_PARENT]]') !== false)
  244.                 {
  245.                     list(, $content) = explode('[[TL_PARENT]]'$this->arrBlocks[$name], 2);
  246.                     echo $content;
  247.                 }
  248.                 // Remove the overwritten content
  249.                 else
  250.                 {
  251.                     ob_end_clean();
  252.                     --$this->intBufferLevel;
  253.                 }
  254.             }
  255.         }
  256.         // Child template
  257.         else
  258.         {
  259.             // Capture the block content
  260.             $this->arrBlocks[$name][] = ob_get_clean();
  261.             // Start a new output buffer
  262.             ob_start();
  263.         }
  264.     }
  265.     /**
  266.      * Insert a template
  267.      *
  268.      * @param string $name The template name
  269.      * @param array  $data An optional data array
  270.      */
  271.     public function insert($name, array $data=null)
  272.     {
  273.         /** @var Template $tpl */
  274.         if ($this instanceof Template)
  275.         {
  276.             $tpl = new static($name);
  277.         }
  278.         elseif (TL_MODE == 'BE')
  279.         {
  280.             $tpl = new BackendTemplate($name);
  281.         }
  282.         else
  283.         {
  284.             $tpl = new FrontendTemplate($name);
  285.         }
  286.         if ($data !== null)
  287.         {
  288.             $tpl->setData($data);
  289.         }
  290.         echo $tpl->parse();
  291.     }
  292.     /**
  293.      * Find a particular template file and return its path
  294.      *
  295.      * @param string  $strTemplate The name of the template
  296.      * @param string  $strFormat   The file extension
  297.      * @param boolean $blnDefault  If true, the default template path is returned
  298.      *
  299.      * @return string The path to the template file
  300.      */
  301.     protected function getTemplatePath($strTemplate$strFormat='html5'$blnDefault=false)
  302.     {
  303.         if ($blnDefault)
  304.         {
  305.             return TemplateLoader::getDefaultPath($strTemplate$strFormat);
  306.         }
  307.         return Controller::getTemplate($strTemplate);
  308.     }
  309.     /**
  310.      * Render a Twig template if one exists
  311.      */
  312.     protected function renderTwigSurrogateIfExists(): ?string
  313.     {
  314.         $container System::getContainer();
  315.         if (null === ($twig $container->get('twig'ContainerInterface::NULL_ON_INVALID_REFERENCE)))
  316.         {
  317.             return null;
  318.         }
  319.         $templateCandidate "@Contao/{$this->strTemplate}.html.twig";
  320.         if (!$twig->getLoader()->exists($templateCandidate))
  321.         {
  322.             return null;
  323.         }
  324.         $contextFactory $container->get('contao.twig.interop.context_factory');
  325.         $context $this instanceof Template ?
  326.             $contextFactory->fromContaoTemplate($this) :
  327.             $contextFactory->fromClass($this)
  328.         ;
  329.         return $twig->render($templateCandidate$context);
  330.     }
  331. }
  332. class_alias(TemplateInheritance::class, 'TemplateInheritance');