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

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