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

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