PageRenderTime 26ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/web/vendor/latte/latte/src/Latte/Macros/BlockMacros.php

https://gitlab.com/adam.kvita/MI-VMM-SIFT
PHP | 407 lines | 305 code | 53 blank | 49 comment | 64 complexity | dcfba1c3793ba8e3226bad19525caa62 MD5 | raw file
  1. <?php
  2. /**
  3. * This file is part of the Latte (https://latte.nette.org)
  4. * Copyright (c) 2008 David Grudl (https://davidgrudl.com)
  5. */
  6. namespace Latte\Macros;
  7. use Latte;
  8. use Latte\CompileException;
  9. use Latte\Helpers;
  10. use Latte\MacroNode;
  11. use Latte\PhpWriter;
  12. use Latte\Runtime\SnippetDriver;
  13. /**
  14. * Block macros.
  15. */
  16. class BlockMacros extends MacroSet
  17. {
  18. /** @var array */
  19. private $namedBlocks = [];
  20. /** @var array */
  21. private $blockTypes = [];
  22. /** @var bool */
  23. private $extends;
  24. /** @var string[] */
  25. private $imports;
  26. public static function install(Latte\Compiler $compiler)
  27. {
  28. $me = new static($compiler);
  29. $me->addMacro('include', [$me, 'macroInclude']);
  30. $me->addMacro('includeblock', [$me, 'macroIncludeBlock']); // deprecated
  31. $me->addMacro('import', [$me, 'macroImport'], NULL, NULL, self::ALLOWED_IN_HEAD);
  32. $me->addMacro('extends', [$me, 'macroExtends'], NULL, NULL, self::ALLOWED_IN_HEAD);
  33. $me->addMacro('layout', [$me, 'macroExtends'], NULL, NULL, self::ALLOWED_IN_HEAD);
  34. $me->addMacro('snippet', [$me, 'macroBlock'], [$me, 'macroBlockEnd']);
  35. $me->addMacro('block', [$me, 'macroBlock'], [$me, 'macroBlockEnd'], NULL, self::AUTO_CLOSE);
  36. $me->addMacro('define', [$me, 'macroBlock'], [$me, 'macroBlockEnd']);
  37. $me->addMacro('snippetArea', [$me, 'macroBlock'], [$me, 'macroBlockEnd']);
  38. $me->addMacro('ifset', [$me, 'macroIfset'], '}');
  39. $me->addMacro('elseifset', [$me, 'macroIfset']);
  40. }
  41. /**
  42. * Initializes before template parsing.
  43. * @return void
  44. */
  45. public function initialize()
  46. {
  47. $this->namedBlocks = [];
  48. $this->blockTypes = [];
  49. $this->extends = NULL;
  50. $this->imports = [];
  51. }
  52. /**
  53. * Finishes template parsing.
  54. */
  55. public function finalize()
  56. {
  57. $compiler = $this->getCompiler();
  58. $functions = [];
  59. foreach ($this->namedBlocks as $name => $code) {
  60. $compiler->addMethod(
  61. $functions[$name] = $this->generateMethodName($name),
  62. '?>' . $compiler->expandTokens($code) . '<?php',
  63. '$_args'
  64. );
  65. }
  66. if ($this->namedBlocks) {
  67. $compiler->addProperty('blocks', $functions);
  68. $compiler->addProperty('blockTypes', $this->blockTypes);
  69. }
  70. return [
  71. ($this->extends === NULL ? '' : '$this->parentName = ' . $this->extends . ';') . implode($this->imports)
  72. ];
  73. }
  74. /********************* macros ****************d*g**/
  75. /**
  76. * {include block}
  77. */
  78. public function macroInclude(MacroNode $node, PhpWriter $writer)
  79. {
  80. $node->replaced = FALSE;
  81. $destination = $node->tokenizer->fetchWord(); // destination [,] [params]
  82. if (!preg_match('~#|[\w-]+\z~A', $destination)) {
  83. return FALSE;
  84. }
  85. $destination = ltrim($destination, '#');
  86. $parent = $destination === 'parent';
  87. if ($destination === 'parent' || $destination === 'this') {
  88. for ($item = $node->parentNode; $item && $item->name !== 'block' && !isset($item->data->name); $item = $item->parentNode);
  89. if (!$item) {
  90. throw new CompileException("Cannot include $destination block outside of any block.");
  91. }
  92. $destination = $item->data->name;
  93. }
  94. $noEscape = Helpers::removeFilter($node->modifiers, 'noescape');
  95. if (!$noEscape && Helpers::removeFilter($node->modifiers, 'escape')) {
  96. trigger_error('Macro ' . $node->getNotation() . ' provides auto-escaping, remove |escape.');
  97. }
  98. if ($node->modifiers && !$noEscape) {
  99. $node->modifiers .= '|escape';
  100. }
  101. return $writer->write(
  102. '$this->renderBlock' . ($parent ? 'Parent' : '') . '('
  103. . (strpos($destination, '$') === FALSE ? var_export($destination, TRUE) : $destination)
  104. . ', %node.array? + '
  105. . (isset($this->namedBlocks[$destination]) || $parent ? 'get_defined_vars()' : '$this->params')
  106. . ($node->modifiers
  107. ? ', function ($s, $type) { $_fi = new LR\FilterInfo($type); return %modifyContent($s); }'
  108. : ($noEscape || $parent ? '' : ', ' . var_export(implode($node->context), TRUE)))
  109. . ');'
  110. );
  111. }
  112. /**
  113. * {includeblock "file"}
  114. * @deprecated
  115. */
  116. public function macroIncludeBlock(MacroNode $node, PhpWriter $writer)
  117. {
  118. trigger_error('Macro {includeblock} is deprecated, use similar macro {import}.', E_USER_DEPRECATED);
  119. $node->replaced = FALSE;
  120. if ($node->modifiers) {
  121. throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
  122. }
  123. return $writer->write(
  124. 'ob_start(function () {}); $this->createTemplate(%node.word, %node.array? + get_defined_vars(), "includeblock")->renderToContentType(%var); echo rtrim(ob_get_clean());',
  125. implode($node->context)
  126. );
  127. }
  128. /**
  129. * {import "file"}
  130. */
  131. public function macroImport(MacroNode $node, PhpWriter $writer)
  132. {
  133. if ($node->modifiers) {
  134. throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
  135. }
  136. $destination = $node->tokenizer->fetchWord();
  137. $this->checkExtraArgs($node);
  138. $code = $writer->write('$this->createTemplate(%word, $this->params, "import")->render();', $destination);
  139. if ($this->getCompiler()->isInHead()) {
  140. $this->imports[] = $code;
  141. } else {
  142. return $code;
  143. }
  144. }
  145. /**
  146. * {extends none | $var | "file"}
  147. */
  148. public function macroExtends(MacroNode $node, PhpWriter $writer)
  149. {
  150. $notation = $node->getNotation();
  151. if ($node->modifiers) {
  152. throw new CompileException("Modifiers are not allowed in $notation");
  153. } elseif (!$node->args) {
  154. throw new CompileException("Missing destination in $notation");
  155. } elseif ($node->parentNode) {
  156. throw new CompileException("$notation must be placed outside any macro.");
  157. } elseif ($this->extends !== NULL) {
  158. throw new CompileException("Multiple $notation declarations are not allowed.");
  159. } elseif ($node->args === 'none') {
  160. $this->extends = 'FALSE';
  161. } else {
  162. $this->extends = $writer->write('%node.word%node.args');
  163. }
  164. if (!$this->getCompiler()->isInHead()) {
  165. trigger_error("$notation must be placed in template head.", E_USER_WARNING);
  166. }
  167. }
  168. /**
  169. * {block [name]}
  170. * {snippet [name [,]] [tag]}
  171. * {snippetArea [name]}
  172. * {define name}
  173. */
  174. public function macroBlock(MacroNode $node, PhpWriter $writer)
  175. {
  176. $name = $node->tokenizer->fetchWord();
  177. if ($node->name === 'block' && $name === FALSE) { // anonymous block
  178. return $node->modifiers === '' ? '' : 'ob_start(function () {})';
  179. }
  180. $node->data->name = $name = ltrim($name, '#');
  181. if ($name == NULL) {
  182. if ($node->name === 'define') {
  183. throw new CompileException('Missing block name.');
  184. }
  185. } elseif (strpos($name, '$') !== FALSE) { // dynamic block/snippet
  186. if ($node->name === 'snippet') {
  187. for ($parent = $node->parentNode; $parent && !($parent->name === 'snippet' || $parent->name === 'snippetArea'); $parent = $parent->parentNode);
  188. if (!$parent) {
  189. throw new CompileException('Dynamic snippets are allowed only inside static snippet/snippetArea.');
  190. }
  191. $parent->data->dynamic = TRUE;
  192. $node->data->leave = TRUE;
  193. $node->closingCode = "<?php \$this->global->snippetDriver->leave(); ?>";
  194. $enterCode = '$this->global->snippetDriver->enter(' . $writer->formatWord($name) . ', "' . SnippetDriver::TYPE_DYNAMIC . '");';
  195. if ($node->prefix) {
  196. $node->attrCode = $writer->write("<?php echo ' id=\"' . htmlSpecialChars(\$this->global->snippetDriver->getHtmlId({$writer->formatWord($name)})) . '\"' ?>");
  197. return $writer->write($enterCode);
  198. }
  199. $tag = trim($node->tokenizer->fetchWord(), '<>');
  200. if ($tag) {
  201. trigger_error('HTML tag specified in {snippet} is deprecated, use n:snippet.', E_USER_DEPRECATED);
  202. }
  203. $tag = $tag ? $tag : 'div';
  204. $node->closingCode .= "\n</$tag>";
  205. $this->checkExtraArgs($node);
  206. return $writer->write("?>\n<$tag id=\"<?php echo htmlSpecialChars(\$this->global->snippetDriver->getHtmlId({$writer->formatWord($name)})) ?>\"><?php " . $enterCode);
  207. } else {
  208. $node->data->leave = TRUE;
  209. $node->data->func = $this->generateMethodName($name);
  210. $fname = $writer->formatWord($name);
  211. $node->closingCode = '<?php ' . ($node->name === 'define' ? '' : "\$this->renderBlock($fname, get_defined_vars());") . ' ?>';
  212. $blockType = var_export(implode($node->context), TRUE);
  213. $this->checkExtraArgs($node);
  214. return "\$this->checkBlockContentType($blockType, $fname);"
  215. . "\$this->blockQueue[$fname][] = [\$this, '{$node->data->func}'];";
  216. }
  217. }
  218. // static snippet/snippetArea
  219. if ($node->name === 'snippet' || $node->name === 'snippetArea') {
  220. if ($node->prefix && isset($node->htmlNode->attrs['id'])) {
  221. throw new CompileException('Cannot combine HTML attribute id with n:snippet.');
  222. }
  223. $node->data->name = $name = '_' . $name;
  224. }
  225. if (isset($this->namedBlocks[$name])) {
  226. throw new CompileException("Cannot redeclare static {$node->name} '$name'");
  227. }
  228. $extendsCheck = $this->namedBlocks ? '' : 'if ($this->getParentName()) return get_defined_vars();';
  229. $this->namedBlocks[$name] = TRUE;
  230. if (Helpers::removeFilter($node->modifiers, 'escape')) {
  231. trigger_error('Macro ' . $node->getNotation() . ' provides auto-escaping, remove |escape.');
  232. }
  233. if (Helpers::startsWith($node->context[1], Latte\Compiler::CONTEXT_HTML_ATTRIBUTE)) {
  234. $node->context[1] = '';
  235. $node->modifiers .= '|escape';
  236. } elseif ($node->modifiers) {
  237. $node->modifiers .= '|escape';
  238. }
  239. $this->blockTypes[$name] = implode($node->context);
  240. $include = '$this->renderBlock(%var, ' . (($node->name === 'snippet' || $node->name === 'snippetArea') ? '$this->params' : 'get_defined_vars()')
  241. . ($node->modifiers ? ', function ($s, $type) { $_fi = new LR\FilterInfo($type); return %modifyContent($s); }' : '') . ')';
  242. if ($node->name === 'snippet') {
  243. if ($node->prefix) {
  244. if (isset($node->htmlNode->macroAttrs['foreach'])) {
  245. trigger_error('Combination of n:snippet with n:foreach is invalid, use n:inner-foreach.', E_USER_WARNING);
  246. }
  247. $node->attrCode = $writer->write('<?php echo \' id="\' . htmlSpecialChars($this->global->snippetDriver->getHtmlId(%var)) . \'"\' ?>', (string) substr($name, 1));
  248. return $writer->write($include, $name);
  249. }
  250. $tag = trim($node->tokenizer->fetchWord(), '<>');
  251. if ($tag) {
  252. trigger_error('HTML tag specified in {snippet} is deprecated, use n:snippet.', E_USER_DEPRECATED);
  253. }
  254. $tag = $tag ? $tag : 'div';
  255. $this->checkExtraArgs($node);
  256. return $writer->write("?>\n<$tag id=\"<?php echo htmlSpecialChars(\$this->global->snippetDriver->getHtmlId(%var)) ?>\"><?php $include ?>\n</$tag><?php ",
  257. (string) substr($name, 1), $name
  258. );
  259. } elseif ($node->name === 'define') {
  260. $tokens = $node->tokenizer;
  261. $args = [];
  262. while ($tokens->isNext()) {
  263. $args[] = $tokens->expectNextValue($tokens::T_VARIABLE);
  264. if ($tokens->isNext()) {
  265. $tokens->expectNextValue(',');
  266. }
  267. }
  268. if ($args) {
  269. $node->data->args = 'list(' . implode(', ', $args) . ') = $_args + [' . str_repeat('NULL, ', count($args)) . '];';
  270. }
  271. return $extendsCheck;
  272. } else { // block, snippetArea
  273. $this->checkExtraArgs($node);
  274. return $writer->write($extendsCheck . $include, $name);
  275. }
  276. }
  277. /**
  278. * {/block}
  279. * {/snippet}
  280. * {/snippetArea}
  281. * {/define}
  282. */
  283. public function macroBlockEnd(MacroNode $node, PhpWriter $writer)
  284. {
  285. if (isset($node->data->name)) { // block, snippet, define
  286. if ($asInner = $node->name === 'snippet' && $node->prefix === MacroNode::PREFIX_NONE) {
  287. $node->content = $node->innerContent;
  288. }
  289. if (($node->name === 'snippet' || $node->name === 'snippetArea') && strpos($node->data->name, '$') === FALSE) {
  290. $type = $node->name === 'snippet' ? SnippetDriver::TYPE_STATIC : SnippetDriver::TYPE_AREA;
  291. $node->content = '<?php $this->global->snippetDriver->enter('
  292. . $writer->formatWord(substr($node->data->name, 1))
  293. . ', "' . $type . '"); ?>'
  294. . preg_replace('#(?<=\n)[ \t]+\z#', '', $node->content) . '<?php $this->global->snippetDriver->leave(); ?>';
  295. }
  296. if (empty($node->data->leave)) {
  297. if (preg_match('#\$|n:#', $node->content)) {
  298. $node->content = '<?php ' . (isset($node->data->args) ? 'extract($this->params); ' . $node->data->args : 'extract($_args);') . ' ?>'
  299. . $node->content;
  300. }
  301. $this->namedBlocks[$node->data->name] = $tmp = preg_replace('#^\n+|(?<=\n)[ \t]+\z#', '', $node->content);
  302. $node->content = substr_replace($node->content, $node->openingCode . "\n", strspn($node->content, "\n"), strlen($tmp));
  303. $node->openingCode = '<?php ?>';
  304. } elseif (isset($node->data->func)) {
  305. $node->content = rtrim($node->content, " \t");
  306. $this->getCompiler()->addMethod(
  307. $node->data->func,
  308. $this->getCompiler()->expandTokens("extract(\$_args);\n?>$node->content<?php"),
  309. '$_args'
  310. );
  311. $node->content = '';
  312. }
  313. if ($asInner) { // n:snippet -> n:inner-snippet
  314. $node->innerContent = $node->openingCode . $node->content . $node->closingCode;
  315. $node->closingCode = $node->openingCode = '<?php ?>';
  316. }
  317. return ' '; // consume next new line
  318. } elseif ($node->modifiers) { // anonymous block with modifier
  319. $node->modifiers .= '|escape';
  320. return $writer->write('$_fi = new LR\FilterInfo(%var); echo %modifyContent(ob_get_clean());', $node->context[0]);
  321. }
  322. }
  323. /**
  324. * {ifset block}
  325. * {elseifset block}
  326. */
  327. public function macroIfset(MacroNode $node, PhpWriter $writer)
  328. {
  329. if ($node->modifiers) {
  330. throw new CompileException('Modifiers are not allowed in ' . $node->getNotation());
  331. }
  332. if (!preg_match('~#|[\w-]+\z~A', $node->args)) {
  333. return FALSE;
  334. }
  335. $list = [];
  336. while (($name = $node->tokenizer->fetchWord()) !== FALSE) {
  337. $list[] = preg_match('~#|[\w-]+\z~A', $name)
  338. ? '$this->blockQueue["' . ltrim($name, '#') . '"]'
  339. : $writer->formatArgs(new Latte\MacroTokens($name));
  340. }
  341. return ($node->name === 'elseifset' ? '} else' : '')
  342. . 'if (isset(' . implode(', ', $list) . ')) {';
  343. }
  344. private function generateMethodName($blockName)
  345. {
  346. $clean = trim(preg_replace('#\W+#', '_', $blockName), '_');
  347. $name = 'block' . ucfirst($clean);
  348. $methods = array_keys($this->getCompiler()->getMethods());
  349. if (!$clean || in_array(strtolower($name), array_map('strtolower', $methods))) {
  350. $name .= '_' . substr(md5($blockName), 0, 5);
  351. }
  352. return $name;
  353. }
  354. }