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

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