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

Open in your IDE?
  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. public function __construct(Environment $env)
  39. {
  40. $this->env = $env;
  41. $this->extensions = $env->getExtensions();
  42. }
  43. /**
  44. * Returns the template name.
  45. *
  46. * @return string The template name
  47. */
  48. abstract public function getTemplateName();
  49. /**
  50. * Returns debug information about the template.
  51. *
  52. * @return array Debug information
  53. */
  54. abstract public function getDebugInfo();
  55. /**
  56. * Returns information about the original template source code.
  57. *
  58. * @return Source
  59. */
  60. abstract public function getSourceContext();
  61. /**
  62. * Returns the parent template.
  63. *
  64. * This method is for internal use only and should never be called
  65. * directly.
  66. *
  67. * @return Template|TemplateWrapper|false The parent template or false if there is no parent
  68. */
  69. public function getParent(array $context)
  70. {
  71. if (null !== $this->parent) {
  72. return $this->parent;
  73. }
  74. try {
  75. $parent = $this->doGetParent($context);
  76. if (false === $parent) {
  77. return false;
  78. }
  79. if ($parent instanceof self || $parent instanceof TemplateWrapper) {
  80. return $this->parents[$parent->getSourceContext()->getName()] = $parent;
  81. }
  82. if (!isset($this->parents[$parent])) {
  83. $this->parents[$parent] = $this->loadTemplate($parent);
  84. }
  85. } catch (LoaderError $e) {
  86. $e->setSourceContext(null);
  87. $e->guess();
  88. throw $e;
  89. }
  90. return $this->parents[$parent];
  91. }
  92. protected function doGetParent(array $context)
  93. {
  94. return false;
  95. }
  96. public function isTraitable()
  97. {
  98. return true;
  99. }
  100. /**
  101. * Displays a parent block.
  102. *
  103. * This method is for internal use only and should never be called
  104. * directly.
  105. *
  106. * @param string $name The block name to display from the parent
  107. * @param array $context The context
  108. * @param array $blocks The current set of blocks
  109. */
  110. public function displayParentBlock($name, array $context, array $blocks = [])
  111. {
  112. if (isset($this->traits[$name])) {
  113. $this->traits[$name][0]->displayBlock($name, $context, $blocks, false);
  114. } elseif (false !== $parent = $this->getParent($context)) {
  115. $parent->displayBlock($name, $context, $blocks, false);
  116. } else {
  117. throw new RuntimeError(sprintf('The template has no parent and no traits defining the "%s" block.', $name), -1, $this->getSourceContext());
  118. }
  119. }
  120. /**
  121. * Displays a block.
  122. *
  123. * This method is for internal use only and should never be called
  124. * directly.
  125. *
  126. * @param string $name The block name to display
  127. * @param array $context The context
  128. * @param array $blocks The current set of blocks
  129. * @param bool $useBlocks Whether to use the current set of blocks
  130. */
  131. public function displayBlock($name, array $context, array $blocks = [], $useBlocks = true, self $templateContext = null)
  132. {
  133. if ($useBlocks && isset($blocks[$name])) {
  134. $template = $blocks[$name][0];
  135. $block = $blocks[$name][1];
  136. } elseif (isset($this->blocks[$name])) {
  137. $template = $this->blocks[$name][0];
  138. $block = $this->blocks[$name][1];
  139. } else {
  140. $template = null;
  141. $block = null;
  142. }
  143. // avoid RCEs when sandbox is enabled
  144. if (null !== $template && !$template instanceof self) {
  145. throw new \LogicException('A block must be a method on a \Twig\Template instance.');
  146. }
  147. if (null !== $template) {
  148. try {
  149. $template->$block($context, $blocks);
  150. } catch (Error $e) {
  151. if (!$e->getSourceContext()) {
  152. $e->setSourceContext($template->getSourceContext());
  153. }
  154. // this is mostly useful for \Twig\Error\LoaderError exceptions
  155. // see \Twig\Error\LoaderError
  156. if (-1 === $e->getTemplateLine()) {
  157. $e->guess();
  158. }
  159. throw $e;
  160. } catch (\Exception $e) {
  161. $e = new RuntimeError(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $template->getSourceContext(), $e);
  162. $e->guess();
  163. throw $e;
  164. }
  165. } elseif (false !== $parent = $this->getParent($context)) {
  166. $parent->displayBlock($name, $context, array_merge($this->blocks, $blocks), false, $templateContext ?? $this);
  167. } elseif (isset($blocks[$name])) {
  168. 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());
  169. } else {
  170. throw new RuntimeError(sprintf('Block "%s" on template "%s" does not exist.', $name, $this->getTemplateName()), -1, ($templateContext ?? $this)->getSourceContext());
  171. }
  172. }
  173. /**
  174. * Renders a parent block.
  175. *
  176. * This method is for internal use only and should never be called
  177. * directly.
  178. *
  179. * @param string $name The block name to render from the parent
  180. * @param array $context The context
  181. * @param array $blocks The current set of blocks
  182. *
  183. * @return string The rendered block
  184. */
  185. public function renderParentBlock($name, array $context, array $blocks = [])
  186. {
  187. if ($this->env->isDebug()) {
  188. ob_start();
  189. } else {
  190. ob_start(function () { return ''; });
  191. }
  192. $this->displayParentBlock($name, $context, $blocks);
  193. return ob_get_clean();
  194. }
  195. /**
  196. * Renders a block.
  197. *
  198. * This method is for internal use only and should never be called
  199. * directly.
  200. *
  201. * @param string $name The block name to render
  202. * @param array $context The context
  203. * @param array $blocks The current set of blocks
  204. * @param bool $useBlocks Whether to use the current set of blocks
  205. *
  206. * @return string The rendered block
  207. */
  208. public function renderBlock($name, array $context, array $blocks = [], $useBlocks = true)
  209. {
  210. if ($this->env->isDebug()) {
  211. ob_start();
  212. } else {
  213. ob_start(function () { return ''; });
  214. }
  215. $this->displayBlock($name, $context, $blocks, $useBlocks);
  216. return ob_get_clean();
  217. }
  218. /**
  219. * Returns whether a block exists or not in the current context of the template.
  220. *
  221. * This method checks blocks defined in the current template
  222. * or defined in "used" traits or defined in parent templates.
  223. *
  224. * @param string $name The block name
  225. * @param array $context The context
  226. * @param array $blocks The current set of blocks
  227. *
  228. * @return bool true if the block exists, false otherwise
  229. */
  230. public function hasBlock($name, array $context, array $blocks = [])
  231. {
  232. if (isset($blocks[$name])) {
  233. return $blocks[$name][0] instanceof self;
  234. }
  235. if (isset($this->blocks[$name])) {
  236. return true;
  237. }
  238. if (false !== $parent = $this->getParent($context)) {
  239. return $parent->hasBlock($name, $context);
  240. }
  241. return false;
  242. }
  243. /**
  244. * Returns all block names in the current context of the template.
  245. *
  246. * This method checks blocks defined in the current template
  247. * or defined in "used" traits or defined in parent templates.
  248. *
  249. * @param array $context The context
  250. * @param array $blocks The current set of blocks
  251. *
  252. * @return array An array of block names
  253. */
  254. public function getBlockNames(array $context, array $blocks = [])
  255. {
  256. $names = array_merge(array_keys($blocks), array_keys($this->blocks));
  257. if (false !== $parent = $this->getParent($context)) {
  258. $names = array_merge($names, $parent->getBlockNames($context));
  259. }
  260. return array_unique($names);
  261. }
  262. /**
  263. * @return Template|TemplateWrapper
  264. */
  265. protected function loadTemplate($template, $templateName = null, $line = null, $index = null)
  266. {
  267. try {
  268. if (\is_array($template)) {
  269. return $this->env->resolveTemplate($template);
  270. }
  271. if ($template instanceof self || $template instanceof TemplateWrapper) {
  272. return $template;
  273. }
  274. if ($template === $this->getTemplateName()) {
  275. $class = static::class;
  276. if (false !== $pos = strrpos($class, '___', -1)) {
  277. $class = substr($class, 0, $pos);
  278. }
  279. } else {
  280. $class = $this->env->getTemplateClass($template);
  281. }
  282. return $this->env->loadTemplate($class, $template, $index);
  283. } catch (Error $e) {
  284. if (!$e->getSourceContext()) {
  285. $e->setSourceContext($templateName ? new Source('', $templateName) : $this->getSourceContext());
  286. }
  287. if ($e->getTemplateLine() > 0) {
  288. throw $e;
  289. }
  290. if (!$line) {
  291. $e->guess();
  292. } else {
  293. $e->setTemplateLine($line);
  294. }
  295. throw $e;
  296. }
  297. }
  298. /**
  299. * @internal
  300. *
  301. * @return Template
  302. */
  303. public function unwrap()
  304. {
  305. return $this;
  306. }
  307. /**
  308. * Returns all blocks.
  309. *
  310. * This method is for internal use only and should never be called
  311. * directly.
  312. *
  313. * @return array An array of blocks
  314. */
  315. public function getBlocks()
  316. {
  317. return $this->blocks;
  318. }
  319. public function display(array $context, array $blocks = [])
  320. {
  321. $this->displayWithErrorHandling($this->env->mergeGlobals($context), array_merge($this->blocks, $blocks));
  322. }
  323. public function render(array $context)
  324. {
  325. $level = ob_get_level();
  326. if ($this->env->isDebug()) {
  327. ob_start();
  328. } else {
  329. ob_start(function () { return ''; });
  330. }
  331. try {
  332. $this->display($context);
  333. } catch (\Throwable $e) {
  334. while (ob_get_level() > $level) {
  335. ob_end_clean();
  336. }
  337. throw $e;
  338. }
  339. return ob_get_clean();
  340. }
  341. protected function displayWithErrorHandling(array $context, array $blocks = [])
  342. {
  343. try {
  344. $this->doDisplay($context, $blocks);
  345. } catch (Error $e) {
  346. if (!$e->getSourceContext()) {
  347. $e->setSourceContext($this->getSourceContext());
  348. }
  349. // this is mostly useful for \Twig\Error\LoaderError exceptions
  350. // see \Twig\Error\LoaderError
  351. if (-1 === $e->getTemplateLine()) {
  352. $e->guess();
  353. }
  354. throw $e;
  355. } catch (\Exception $e) {
  356. $e = new RuntimeError(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $this->getSourceContext(), $e);
  357. $e->guess();
  358. throw $e;
  359. }
  360. }
  361. /**
  362. * Auto-generated method to display the template with the given context.
  363. *
  364. * @param array $context An array of parameters to pass to the template
  365. * @param array $blocks An array of blocks to pass to the template
  366. */
  367. abstract protected function doDisplay(array $context, array $blocks = []);
  368. }