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

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. private $useYield;
  39. public function __construct(Environment $env)
  40. {
  41. $this->env = $env;
  42. $this->useYield = $env->useYield();
  43. $this->extensions = $env->getExtensions();
  44. }
  45. /**
  46. * Returns the template name.
  47. */
  48. abstract public function getTemplateName(): string;
  49. /**
  50. * Returns debug information about the template.
  51. *
  52. * @return array<int, int> Debug information
  53. */
  54. abstract public function getDebugInfo(): array;
  55. /**
  56. * Returns information about the original template source code.
  57. */
  58. abstract public function getSourceContext(): Source;
  59. /**
  60. * Returns the parent template.
  61. *
  62. * This method is for internal use only and should never be called
  63. * directly.
  64. *
  65. * @return self|TemplateWrapper|false The parent template or false if there is no parent
  66. */
  67. public function getParent(array $context)
  68. {
  69. if (null !== $this->parent) {
  70. return $this->parent;
  71. }
  72. try {
  73. if (!$parent = $this->doGetParent($context)) {
  74. return false;
  75. }
  76. if ($parent instanceof self || $parent instanceof TemplateWrapper) {
  77. return $this->parents[$parent->getSourceContext()->getName()] = $parent;
  78. }
  79. if (!isset($this->parents[$parent])) {
  80. $this->parents[$parent] = $this->loadTemplate($parent);
  81. }
  82. } catch (LoaderError $e) {
  83. $e->setSourceContext(null);
  84. $e->guess();
  85. throw $e;
  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 = [])
  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)
  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 = [])
  143. {
  144. if (!$this->useYield) {
  145. if ($this->env->isDebug()) {
  146. ob_start();
  147. } else {
  148. ob_start(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)
  173. {
  174. if (!$this->useYield) {
  175. $level = ob_get_level();
  176. if ($this->env->isDebug()) {
  177. ob_start();
  178. } else {
  179. ob_start(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 = [])
  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 An array of block names
  232. */
  233. public function getBlockNames(array $context, array $blocks = [])
  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. * @return self|TemplateWrapper
  245. */
  246. protected function loadTemplate($template, $templateName = null, $line = null, $index = null)
  247. {
  248. try {
  249. if (\is_array($template)) {
  250. return $this->env->resolveTemplate($template);
  251. }
  252. if ($template instanceof TemplateWrapper) {
  253. return $template;
  254. }
  255. if ($template instanceof self) {
  256. trigger_deprecation('twig/twig', '3.9', 'Passing a "%s" instance to "%s" is deprecated.', self::class, __METHOD__);
  257. return $template;
  258. }
  259. if ($template === $this->getTemplateName()) {
  260. $class = static::class;
  261. if (false !== $pos = strrpos($class, '___', -1)) {
  262. $class = substr($class, 0, $pos);
  263. }
  264. } else {
  265. $class = $this->env->getTemplateClass($template);
  266. }
  267. return $this->env->loadTemplate($class, $template, $index);
  268. } catch (Error $e) {
  269. if (!$e->getSourceContext()) {
  270. $e->setSourceContext($templateName ? new Source('', $templateName) : $this->getSourceContext());
  271. }
  272. if ($e->getTemplateLine() > 0) {
  273. throw $e;
  274. }
  275. if (!$line) {
  276. $e->guess();
  277. } else {
  278. $e->setTemplateLine($line);
  279. }
  280. throw $e;
  281. }
  282. }
  283. /**
  284. * @internal
  285. *
  286. * @return self
  287. */
  288. public function unwrap()
  289. {
  290. return $this;
  291. }
  292. /**
  293. * Returns all blocks.
  294. *
  295. * This method is for internal use only and should never be called
  296. * directly.
  297. *
  298. * @return array An array of blocks
  299. */
  300. public function getBlocks()
  301. {
  302. return $this->blocks;
  303. }
  304. public function display(array $context, array $blocks = []): void
  305. {
  306. foreach ($this->yield($context, $blocks) as $data) {
  307. echo $data;
  308. }
  309. }
  310. public function render(array $context): string
  311. {
  312. if (!$this->useYield) {
  313. $level = ob_get_level();
  314. if ($this->env->isDebug()) {
  315. ob_start();
  316. } else {
  317. ob_start(function () { return ''; });
  318. }
  319. try {
  320. $this->display($context);
  321. } catch (\Throwable $e) {
  322. while (ob_get_level() > $level) {
  323. ob_end_clean();
  324. }
  325. throw $e;
  326. }
  327. return ob_get_clean();
  328. }
  329. $content = '';
  330. foreach ($this->yield($context) as $data) {
  331. $content .= $data;
  332. }
  333. return $content;
  334. }
  335. /**
  336. * @return iterable<string>
  337. */
  338. public function yield(array $context, array $blocks = []): iterable
  339. {
  340. $context = $this->env->mergeGlobals($context);
  341. $blocks = array_merge($this->blocks, $blocks);
  342. try {
  343. yield from $this->doDisplay($context, $blocks);
  344. } catch (Error $e) {
  345. if (!$e->getSourceContext()) {
  346. $e->setSourceContext($this->getSourceContext());
  347. }
  348. // this is mostly useful for \Twig\Error\LoaderError exceptions
  349. // see \Twig\Error\LoaderError
  350. if (-1 === $e->getTemplateLine()) {
  351. $e->guess();
  352. }
  353. throw $e;
  354. } catch (\Throwable $e) {
  355. $e = new RuntimeError(\sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $this->getSourceContext(), $e);
  356. $e->guess();
  357. throw $e;
  358. }
  359. }
  360. /**
  361. * @return iterable<string>
  362. */
  363. public function yieldBlock($name, array $context, array $blocks = [], $useBlocks = true, ?self $templateContext = null)
  364. {
  365. if ($useBlocks && isset($blocks[$name])) {
  366. $template = $blocks[$name][0];
  367. $block = $blocks[$name][1];
  368. } elseif (isset($this->blocks[$name])) {
  369. $template = $this->blocks[$name][0];
  370. $block = $this->blocks[$name][1];
  371. } else {
  372. $template = null;
  373. $block = null;
  374. }
  375. // avoid RCEs when sandbox is enabled
  376. if (null !== $template && !$template instanceof self) {
  377. throw new \LogicException('A block must be a method on a \Twig\Template instance.');
  378. }
  379. if (null !== $template) {
  380. try {
  381. yield from $template->$block($context, $blocks);
  382. } catch (Error $e) {
  383. if (!$e->getSourceContext()) {
  384. $e->setSourceContext($template->getSourceContext());
  385. }
  386. // this is mostly useful for \Twig\Error\LoaderError exceptions
  387. // see \Twig\Error\LoaderError
  388. if (-1 === $e->getTemplateLine()) {
  389. $e->guess();
  390. }
  391. throw $e;
  392. } catch (\Throwable $e) {
  393. $e = new RuntimeError(\sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $template->getSourceContext(), $e);
  394. $e->guess();
  395. throw $e;
  396. }
  397. } elseif ($parent = $this->getParent($context)) {
  398. yield from $parent->unwrap()->yieldBlock($name, $context, array_merge($this->blocks, $blocks), false, $templateContext ?? $this);
  399. } elseif (isset($blocks[$name])) {
  400. 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());
  401. } else {
  402. throw new RuntimeError(\sprintf('Block "%s" on template "%s" does not exist.', $name, $this->getTemplateName()), -1, ($templateContext ?? $this)->getSourceContext());
  403. }
  404. }
  405. /**
  406. * Yields a parent block.
  407. *
  408. * This method is for internal use only and should never be called
  409. * directly.
  410. *
  411. * @param string $name The block name to display from the parent
  412. * @param array $context The context
  413. * @param array $blocks The current set of blocks
  414. *
  415. * @return iterable<string>
  416. */
  417. public function yieldParentBlock($name, array $context, array $blocks = [])
  418. {
  419. if (isset($this->traits[$name])) {
  420. yield from $this->traits[$name][0]->yieldBlock($name, $context, $blocks, false);
  421. } elseif ($parent = $this->getParent($context)) {
  422. yield from $parent->unwrap()->yieldBlock($name, $context, $blocks, false);
  423. } else {
  424. throw new RuntimeError(\sprintf('The template has no parent and no traits defining the "%s" block.', $name), -1, $this->getSourceContext());
  425. }
  426. }
  427. /**
  428. * Auto-generated method to display the template with the given context.
  429. *
  430. * @param array $context An array of parameters to pass to the template
  431. * @param array $blocks An array of blocks to pass to the template
  432. */
  433. abstract protected function doDisplay(array $context, array $blocks = []);
  434. }