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

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