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

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\RuntimeError;
  14. /**
  15. * Default base class for compiled templates.
  16. *
  17. * This class is an implementation detail of how template compilation currently
  18. * works, which might change. It should never be used directly. Use $twig->load()
  19. * instead, which returns an instance of \Twig\TemplateWrapper.
  20. *
  21. * @author Fabien Potencier <fabien@symfony.com>
  22. *
  23. * @internal
  24. */
  25. abstract class Template
  26. {
  27. public const ANY_CALL = 'any';
  28. public const ARRAY_CALL = 'array';
  29. public const METHOD_CALL = 'method';
  30. protected $parent;
  31. protected $parents = [];
  32. protected $blocks = [];
  33. protected $traits = [];
  34. protected $traitAliases = [];
  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. // The compiled doGetParent() may evaluate user expressions (filters,
  72. // functions, method calls) when the parent name is dynamic. Make sure
  73. // the sandbox security check runs first so those expressions cannot
  74. // bypass the allow-list when getParent() is reached before the first
  75. // ensureSecurityChecked() call on this template (e.g. via
  76. // getTemplateForMacro() or yieldBlock() into a pre-warmed instance).
  77. $this->ensureSecurityChecked();
  78. if (!$parent = $this->doGetParent($context)) {
  79. return false;
  80. }
  81. if ($parent instanceof self || $parent instanceof TemplateWrapper) {
  82. return $this->parents[$parent->getSourceContext()->getName()] = $parent;
  83. }
  84. if (!isset($this->parents[$parent])) {
  85. $this->parents[$parent] = $this->load($parent, -1);
  86. }
  87. return $this->parents[$parent];
  88. }
  89. protected function doGetParent(array $context): bool|string|self|TemplateWrapper
  90. {
  91. return false;
  92. }
  93. public function isTraitable(): bool
  94. {
  95. return true;
  96. }
  97. /**
  98. * Displays a parent block.
  99. *
  100. * This method is for internal use only and should never be called
  101. * directly.
  102. *
  103. * @param string $name The block name to display from the parent
  104. * @param array $context The context
  105. * @param array $blocks The current set of blocks
  106. */
  107. public function displayParentBlock($name, array $context, array $blocks = []): void
  108. {
  109. foreach ($this->yieldParentBlock($name, $context, $blocks) as $data) {
  110. echo $data;
  111. }
  112. }
  113. /**
  114. * Displays a block.
  115. *
  116. * This method is for internal use only and should never be called
  117. * directly.
  118. *
  119. * @param string $name The block name to display
  120. * @param array $context The context
  121. * @param array $blocks The current set of blocks
  122. * @param bool $useBlocks Whether to use the current set of blocks
  123. */
  124. public function displayBlock($name, array $context, array $blocks = [], $useBlocks = true, ?self $templateContext = null): void
  125. {
  126. foreach ($this->yieldBlock($name, $context, $blocks, $useBlocks, $templateContext) as $data) {
  127. echo $data;
  128. }
  129. }
  130. /**
  131. * Renders a parent 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 render from the parent
  137. * @param array $context The context
  138. * @param array $blocks The current set of blocks
  139. *
  140. * @return string The rendered block
  141. */
  142. public function renderParentBlock($name, array $context, array $blocks = []): string
  143. {
  144. if (!$this->useYield) {
  145. if ($this->env->isDebug()) {
  146. ob_start();
  147. } else {
  148. ob_start(static function () { return ''; });
  149. }
  150. $this->displayParentBlock($name, $context, $blocks);
  151. return ob_get_clean();
  152. }
  153. $content = '';
  154. foreach ($this->yieldParentBlock($name, $context, $blocks) as $data) {
  155. $content .= $data;
  156. }
  157. return $content;
  158. }
  159. /**
  160. * Renders a block.
  161. *
  162. * This method is for internal use only and should never be called
  163. * directly.
  164. *
  165. * @param string $name The block name to render
  166. * @param array $context The context
  167. * @param array $blocks The current set of blocks
  168. * @param bool $useBlocks Whether to use the current set of blocks
  169. *
  170. * @return string The rendered block
  171. */
  172. public function renderBlock($name, array $context, array $blocks = [], $useBlocks = true): string
  173. {
  174. if (!$this->useYield) {
  175. $level = ob_get_level();
  176. if ($this->env->isDebug()) {
  177. ob_start();
  178. } else {
  179. ob_start(static function () { return ''; });
  180. }
  181. try {
  182. $this->displayBlock($name, $context, $blocks, $useBlocks);
  183. } catch (\Throwable $e) {
  184. while (ob_get_level() > $level) {
  185. ob_end_clean();
  186. }
  187. throw $e;
  188. }
  189. return ob_get_clean();
  190. }
  191. $content = '';
  192. foreach ($this->yieldBlock($name, $context, $blocks, $useBlocks) as $data) {
  193. $content .= $data;
  194. }
  195. return $content;
  196. }
  197. /**
  198. * Returns whether a block exists or not in the current context of the template.
  199. *
  200. * This method checks blocks defined in the current template
  201. * or defined in "used" traits or defined in parent templates.
  202. *
  203. * @param string $name The block name
  204. * @param array $context The context
  205. * @param array $blocks The current set of blocks
  206. *
  207. * @return bool true if the block exists, false otherwise
  208. */
  209. public function hasBlock($name, array $context, array $blocks = []): bool
  210. {
  211. if (isset($blocks[$name])) {
  212. return $blocks[$name][0] instanceof self;
  213. }
  214. if (isset($this->blocks[$name])) {
  215. return true;
  216. }
  217. if ($parent = $this->getParent($context)) {
  218. return $parent->hasBlock($name, $context);
  219. }
  220. return false;
  221. }
  222. /**
  223. * Returns all block names in the current context of the template.
  224. *
  225. * This method checks blocks defined in the current template
  226. * or defined in "used" traits or defined in parent templates.
  227. *
  228. * @param array $context The context
  229. * @param array $blocks The current set of blocks
  230. *
  231. * @return array<string> An array of block names
  232. */
  233. public function getBlockNames(array $context, array $blocks = []): array
  234. {
  235. $names = array_merge(array_keys($blocks), array_keys($this->blocks));
  236. if ($parent = $this->getParent($context)) {
  237. $names = array_merge($names, $parent->getBlockNames($context));
  238. }
  239. return array_unique($names);
  240. }
  241. /**
  242. * @param string|TemplateWrapper|array<string|TemplateWrapper> $template
  243. */
  244. protected function load(string|TemplateWrapper|array $template, int $line, ?int $index = null): self
  245. {
  246. try {
  247. if (\is_array($template)) {
  248. return $this->env->resolveTemplate($template)->unwrap();
  249. }
  250. if ($template instanceof TemplateWrapper) {
  251. return $template->unwrap();
  252. }
  253. if ($template === $this->getTemplateName()) {
  254. $class = static::class;
  255. if (false !== $pos = strrpos($class, '___', -1)) {
  256. $class = substr($class, 0, $pos);
  257. }
  258. } else {
  259. $class = $this->env->getTemplateClass($template);
  260. }
  261. return $this->env->loadTemplate($class, $template, $index);
  262. } catch (Error $e) {
  263. if (!$e->getSourceContext()) {
  264. $e->setSourceContext($this->getSourceContext());
  265. }
  266. if ($e->getTemplateLine() > 0) {
  267. throw $e;
  268. }
  269. if (-1 === $line) {
  270. $e->guess();
  271. } else {
  272. $e->setTemplateLine($line);
  273. }
  274. throw $e;
  275. }
  276. }
  277. /**
  278. * @param string|TemplateWrapper|array<string|TemplateWrapper> $template
  279. *
  280. * @deprecated since Twig 3.21 and will be removed in 4.0. Use Template::load() instead.
  281. */
  282. protected function loadTemplate($template, $templateName = null, ?int $line = null, ?int $index = null): self|TemplateWrapper
  283. {
  284. trigger_deprecation('twig/twig', '3.21', 'The "%s" method is deprecated.', __METHOD__);
  285. if (null === $line) {
  286. $line = -1;
  287. }
  288. if ($template instanceof self) {
  289. return $template;
  290. }
  291. return $this->load($template, $line, $index);
  292. }
  293. /**
  294. * @internal
  295. *
  296. * @return $this
  297. */
  298. public function unwrap(): self
  299. {
  300. return $this;
  301. }
  302. /**
  303. * Returns all blocks.
  304. *
  305. * This method is for internal use only and should never be called
  306. * directly.
  307. *
  308. * @return array An array of blocks
  309. */
  310. public function getBlocks(): array
  311. {
  312. return $this->blocks;
  313. }
  314. public function display(array $context, array $blocks = []): void
  315. {
  316. foreach ($this->yield($context, $blocks) as $data) {
  317. echo $data;
  318. }
  319. }
  320. public function render(array $context): string
  321. {
  322. if (!$this->useYield) {
  323. $level = ob_get_level();
  324. if ($this->env->isDebug()) {
  325. ob_start();
  326. } else {
  327. ob_start(static function () { return ''; });
  328. }
  329. try {
  330. $this->display($context);
  331. } catch (\Throwable $e) {
  332. while (ob_get_level() > $level) {
  333. ob_end_clean();
  334. }
  335. throw $e;
  336. }
  337. return ob_get_clean();
  338. }
  339. $content = '';
  340. foreach ($this->yield($context) as $data) {
  341. $content .= $data;
  342. }
  343. return $content;
  344. }
  345. /**
  346. * @return iterable<scalar|\Stringable|null>
  347. */
  348. public function yield(array $context, array $blocks = []): iterable
  349. {
  350. $context += $this->env->getGlobals();
  351. $blocks = array_merge($this->blocks, $blocks);
  352. try {
  353. $this->ensureSecurityChecked();
  354. yield from $this->doDisplay($context, $blocks);
  355. } catch (Error $e) {
  356. if (!$e->getSourceContext()) {
  357. $e->setSourceContext($this->getSourceContext());
  358. }
  359. // this is mostly useful for \Twig\Error\LoaderError exceptions
  360. // see \Twig\Error\LoaderError
  361. if (-1 === $e->getTemplateLine()) {
  362. $e->guess();
  363. }
  364. throw $e;
  365. } catch (\Throwable $e) {
  366. $e = new RuntimeError(\sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $this->getSourceContext(), $e);
  367. $e->guess();
  368. throw $e;
  369. }
  370. }
  371. /**
  372. * @return iterable<scalar|\Stringable|null>
  373. */
  374. public function yieldBlock($name, array $context, array $blocks = [], $useBlocks = true, ?self $templateContext = null): iterable
  375. {
  376. if ($useBlocks && isset($blocks[$name])) {
  377. $template = $blocks[$name][0];
  378. $block = $blocks[$name][1];
  379. } elseif (isset($this->blocks[$name])) {
  380. $template = $this->blocks[$name][0];
  381. $block = $this->blocks[$name][1];
  382. } else {
  383. $template = null;
  384. $block = null;
  385. }
  386. // avoid RCEs when sandbox is enabled
  387. if (null !== $template && !$template instanceof self) {
  388. throw new \LogicException('A block must be a method on a \Twig\Template instance.');
  389. }
  390. if (null !== $template) {
  391. try {
  392. $template->ensureSecurityChecked();
  393. yield from $template->$block($context, $blocks);
  394. } catch (Error $e) {
  395. if (!$e->getSourceContext()) {
  396. $e->setSourceContext($template->getSourceContext());
  397. }
  398. // this is mostly useful for \Twig\Error\LoaderError exceptions
  399. // see \Twig\Error\LoaderError
  400. if (-1 === $e->getTemplateLine()) {
  401. $e->guess();
  402. }
  403. throw $e;
  404. } catch (\Throwable $e) {
  405. $e = new RuntimeError(\sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $template->getSourceContext(), $e);
  406. $e->guess();
  407. throw $e;
  408. }
  409. } elseif ($parent = $this->getParent($context)) {
  410. yield from $parent->unwrap()->yieldBlock($name, $context, array_merge($this->blocks, $blocks), false, $templateContext ?? $this);
  411. } elseif (isset($blocks[$name])) {
  412. 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());
  413. } else {
  414. throw new RuntimeError(\sprintf('Block "%s" on template "%s" does not exist.', $name, $this->getTemplateName()), -1, ($templateContext ?? $this)->getSourceContext());
  415. }
  416. }
  417. /**
  418. * Yields a parent block.
  419. *
  420. * This method is for internal use only and should never be called
  421. * directly.
  422. *
  423. * @param string $name The block name to display from the parent
  424. * @param array $context The context
  425. * @param array $blocks The current set of blocks
  426. *
  427. * @return iterable<scalar|\Stringable|null>
  428. */
  429. public function yieldParentBlock($name, array $context, array $blocks = []): iterable
  430. {
  431. if (isset($this->traits[$name])) {
  432. yield from $this->traits[$name][0]->yieldBlock($this->traitAliases[$name] ?? $name, $context, $blocks, false);
  433. } elseif ($parent = $this->getParent($context)) {
  434. yield from $parent->unwrap()->yieldBlock($name, $context, $blocks, false);
  435. } else {
  436. throw new RuntimeError(\sprintf('The template has no parent and no traits defining the "%s" block.', $name), -1, $this->getSourceContext());
  437. }
  438. }
  439. protected function hasMacro(string $name, array $context): bool
  440. {
  441. if (method_exists($this, $name)) {
  442. return true;
  443. }
  444. if (!$parent = $this->getParent($context)) {
  445. return false;
  446. }
  447. return $parent->hasMacro($name, $context);
  448. }
  449. protected function getTemplateForMacro(string $name, array $context, int $line, Source $source): self
  450. {
  451. if (method_exists($this, $name)) {
  452. $this->ensureSecurityChecked();
  453. return $this;
  454. }
  455. $parent = $this;
  456. while ($parent = $parent->getParent($context)) {
  457. if (method_exists($parent, $name)) {
  458. $parent->ensureSecurityChecked();
  459. return $parent;
  460. }
  461. }
  462. throw new RuntimeError(\sprintf('Macro "%s" is not defined in template "%s".', substr($name, \strlen('macro_')), $this->getTemplateName()), $line, $source);
  463. }
  464. /**
  465. * Runs the sandbox security check against the current sandbox state.
  466. *
  467. * @internal
  468. */
  469. public function ensureSecurityChecked(): void
  470. {
  471. }
  472. /**
  473. * Auto-generated method to display the template with the given context.
  474. *
  475. * @param array $context An array of parameters to pass to the template
  476. * @param array $blocks An array of blocks to pass to the template
  477. *
  478. * @return iterable<scalar|\Stringable|null>
  479. */
  480. abstract protected function doDisplay(array $context, array $blocks = []): iterable;
  481. }