PageRenderTime 49ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/Nette/Latte/Macros/UIMacros.php

https://github.com/stekycz/nette
PHP | 524 lines | 345 code | 98 blank | 81 comment | 68 complexity | ce1fbd44fc95c9d2eb9d794566885c6d MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. * This file is part of the Nette Framework (http://nette.org)
  4. *
  5. * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
  6. *
  7. * For the full copyright and license information, please view
  8. * the file license.txt that was distributed with this source code.
  9. */
  10. namespace Nette\Latte\Macros;
  11. use Nette,
  12. Nette\Latte,
  13. Nette\Latte\MacroNode,
  14. Nette\Latte\PhpWriter,
  15. Nette\Latte\CompileException,
  16. Nette\Utils\Strings;
  17. /**
  18. * Macros for Nette\Application\UI.
  19. *
  20. * - {link destination ...} control link
  21. * - {plink destination ...} presenter link
  22. * - {snippet ?} ... {/snippet ?} control snippet
  23. * - {contentType ...} HTTP Content-Type header
  24. * - {status ...} HTTP status
  25. *
  26. * @author David Grudl
  27. */
  28. class UIMacros extends MacroSet
  29. {
  30. /** @var array */
  31. private $namedBlocks = array();
  32. /** @var bool */
  33. private $extends;
  34. public static function install(Latte\Compiler $compiler)
  35. {
  36. $me = new static($compiler);
  37. $me->addMacro('include', array($me, 'macroInclude'));
  38. $me->addMacro('includeblock', array($me, 'macroIncludeBlock'));
  39. $me->addMacro('extends', array($me, 'macroExtends'));
  40. $me->addMacro('layout', array($me, 'macroExtends'));
  41. $me->addMacro('block', array($me, 'macroBlock'), array($me, 'macroBlockEnd'));
  42. $me->addMacro('#', array($me, 'macroBlock'), array($me, 'macroBlockEnd'));
  43. $me->addMacro('define', array($me, 'macroBlock'), array($me, 'macroBlockEnd'));
  44. $me->addMacro('snippet', array($me, 'macroBlock'), array($me, 'macroBlockEnd'));
  45. $me->addMacro('ifset', array($me, 'macroIfset'), 'endif');
  46. $me->addMacro('widget', array($me, 'macroControl')); // deprecated - use control
  47. $me->addMacro('control', array($me, 'macroControl'));
  48. $me->addMacro('href', NULL, NULL, function(MacroNode $node, PhpWriter $writer) use ($me) {
  49. return ' ?> href="<?php ' . $me->macroLink($node, $writer) . ' ?>"<?php ';
  50. });
  51. $me->addMacro('plink', array($me, 'macroLink'));
  52. $me->addMacro('link', array($me, 'macroLink'));
  53. $me->addMacro('ifCurrent', array($me, 'macroIfCurrent'), 'endif'); // deprecated; use n:class="$presenter->linkCurrent ? ..."
  54. $me->addMacro('contentType', array($me, 'macroContentType'));
  55. $me->addMacro('status', array($me, 'macroStatus'));
  56. }
  57. /**
  58. * Initializes before template parsing.
  59. * @return void
  60. */
  61. public function initialize()
  62. {
  63. $this->namedBlocks = array();
  64. $this->extends = NULL;
  65. }
  66. /**
  67. * Finishes template parsing.
  68. * @return array(prolog, epilog)
  69. */
  70. public function finalize()
  71. {
  72. // try close last block
  73. $last = $this->getCompiler()->getMacroNode();
  74. if ($last && ($last->name === 'block' || $last->name === '#')) {
  75. $this->getCompiler()->writeMacro('/' . $last->name);
  76. }
  77. $epilog = $prolog = array();
  78. if ($this->namedBlocks) {
  79. foreach ($this->namedBlocks as $name => $code) {
  80. $func = '_lb' . substr(md5($this->getCompiler()->getTemplateId() . $name), 0, 10) . '_' . preg_replace('#[^a-z0-9_]#i', '_', $name);
  81. $snippet = $name[0] === '_';
  82. $prolog[] = "//\n// block $name\n//\n"
  83. . "if (!function_exists(\$_l->blocks[" . var_export($name, TRUE) . "][] = '$func')) { "
  84. . "function $func(\$_l, \$_args) { "
  85. . (PHP_VERSION_ID > 50208 ? 'extract($_args)' : 'foreach ($_args as $__k => $__v) $$__k = $__v') // PHP bug #46873
  86. . ($snippet ? '; $_control->validateControl(' . var_export(substr($name, 1), TRUE) . ')' : '')
  87. . "\n?>$code<?php\n}}";
  88. }
  89. $prolog[] = "//\n// end of blocks\n//";
  90. }
  91. if ($this->namedBlocks || $this->extends) {
  92. $prolog[] = "// template extending and snippets support";
  93. $prolog[] = '$_l->extends = '
  94. . ($this->extends ? $this->extends : 'empty($template->_extended) && isset($_control) && $_control instanceof Nette\Application\UI\Presenter ? $_control->findLayoutTemplateFile() : NULL')
  95. . '; $template->_extended = $_extended = TRUE;';
  96. $prolog[] = '
  97. if ($_l->extends) {
  98. ' . ($this->namedBlocks ? 'ob_start();' : 'return Nette\Latte\Macros\CoreMacros::includeTemplate($_l->extends, get_defined_vars(), $template)->render();') . '
  99. } elseif (!empty($_control->snippetMode)) {
  100. return Nette\Latte\Macros\UIMacros::renderSnippets($_control, $_l, get_defined_vars());
  101. }';
  102. } else {
  103. $prolog[] = '
  104. // snippets support
  105. if (!empty($_control->snippetMode)) {
  106. return Nette\Latte\Macros\UIMacros::renderSnippets($_control, $_l, get_defined_vars());
  107. }';
  108. }
  109. return array(implode("\n\n", $prolog), implode("\n", $epilog));
  110. }
  111. /********************* macros ****************d*g**/
  112. /**
  113. * {include #block}
  114. */
  115. public function macroInclude(MacroNode $node, PhpWriter $writer)
  116. {
  117. $destination = $node->tokenizer->fetchWord(); // destination [,] [params]
  118. if (substr($destination, 0, 1) !== '#') {
  119. return FALSE;
  120. }
  121. $destination = ltrim($destination, '#');
  122. $parent = $destination === 'parent';
  123. if ($destination === 'parent' || $destination === 'this') {
  124. for ($item = $node->parentNode; $item && $item->name !== 'block' && !isset($item->data->name); $item = $item->parentNode);
  125. if (!$item) {
  126. throw new CompileException("Cannot include $destination block outside of any block.");
  127. }
  128. $destination = $item->data->name;
  129. }
  130. $name = Strings::contains($destination, '$') ? $destination : var_export($destination, TRUE);
  131. if (isset($this->namedBlocks[$destination]) && !$parent) {
  132. $cmd = "call_user_func(reset(\$_l->blocks[$name]), \$_l, %node.array? + get_defined_vars())";
  133. } else {
  134. $cmd = 'Nette\Latte\Macros\UIMacros::callBlock' . ($parent ? 'Parent' : '') . "(\$_l, $name, %node.array? + " . ($parent ? 'get_defined_vars' : '$template->getParameters') . '())';
  135. }
  136. if ($node->modifiers) {
  137. return $writer->write("ob_start(); $cmd; echo %modify(ob_get_clean())");
  138. } else {
  139. return $writer->write($cmd);
  140. }
  141. }
  142. /**
  143. * {includeblock "file"}
  144. */
  145. public function macroIncludeBlock(MacroNode $node, PhpWriter $writer)
  146. {
  147. return $writer->write('Nette\Latte\Macros\CoreMacros::includeTemplate(%node.word, %node.array? + get_defined_vars(), $_l->templates[%var])->render()',
  148. $this->getCompiler()->getTemplateId());
  149. }
  150. /**
  151. * {extends auto | none | $var | "file"}
  152. */
  153. public function macroExtends(MacroNode $node, PhpWriter $writer)
  154. {
  155. if (!$node->args) {
  156. throw new CompileException("Missing destination in {extends}");
  157. }
  158. if (!empty($node->parentNode)) {
  159. throw new CompileException("{extends} must be placed outside any macro.");
  160. }
  161. if ($this->extends !== NULL) {
  162. throw new CompileException("Multiple {extends} declarations are not allowed.");
  163. }
  164. if ($node->args === 'none') {
  165. $this->extends = 'FALSE';
  166. } elseif ($node->args === 'auto') {
  167. $this->extends = '$_presenter->findLayoutTemplateFile()';
  168. } else {
  169. $this->extends = $writer->write('%node.word%node.args');
  170. }
  171. return;
  172. }
  173. /**
  174. * {block [[#]name]}
  175. * {snippet [name [,]] [tag]}
  176. * {define [#]name}
  177. */
  178. public function macroBlock(MacroNode $node, PhpWriter $writer)
  179. {
  180. $name = $node->tokenizer->fetchWord();
  181. if ($node->name === 'block' && $name === FALSE) { // anonymous block
  182. return $node->modifiers === '' ? '' : 'ob_start()';
  183. }
  184. $node->data->name = $name = ltrim($name, '#');
  185. if ($name == NULL) {
  186. if ($node->name !== 'snippet') {
  187. throw new CompileException("Missing block name.");
  188. }
  189. } elseif (Strings::contains($name, '$')) { // dynamic block/snippet
  190. if ($node->name === 'snippet') {
  191. for ($parent = $node->parentNode; $parent && $parent->name !== 'snippet'; $parent = $parent->parentNode);
  192. if (!$parent) {
  193. throw new CompileException("Dynamic snippets are allowed only inside static snippet.");
  194. }
  195. $parent->data->dynamic = TRUE;
  196. $node->data->leave = TRUE;
  197. $node->closingCode = "<?php \$_dynSnippets[\$_dynSnippetId] = ob_get_flush() ?>";
  198. if ($node->prefix) {
  199. $node->attrCode = $writer->write("<?php echo ' id=\"' . (\$_dynSnippetId = \$_control->getSnippetId({$writer->formatWord($name)})) . '\"' ?>");
  200. return $writer->write('ob_start()');
  201. }
  202. $tag = trim($node->tokenizer->fetchWord(), '<>');
  203. $tag = $tag ? $tag : 'div';
  204. $node->closingCode .= "\n</$tag>";
  205. return $writer->write("?>\n<$tag id=\"<?php echo \$_dynSnippetId = \$_control->getSnippetId({$writer->formatWord($name)}) ?>\"><?php ob_start()");
  206. } else {
  207. $node->data->leave = TRUE;
  208. $fname = $writer->formatWord($name);
  209. $node->closingCode = "<?php }} " . ($node->name === 'define' ? '' : "call_user_func(reset(\$_l->blocks[$fname]), \$_l, get_defined_vars())") . " ?>";
  210. $func = '_lb' . substr(md5($this->getCompiler()->getTemplateId() . $name), 0, 10) . '_' . preg_replace('#[^a-z0-9_]#i', '_', $name);
  211. return "\n\n//\n// block $name\n//\n"
  212. . "if (!function_exists(\$_l->blocks[$fname]['{$this->getCompiler()->getTemplateId()}'] = '$func')) { "
  213. . "function $func(\$_l, \$_args) { "
  214. . (PHP_VERSION_ID > 50208 ? 'extract($_args)' : 'foreach ($_args as $__k => $__v) $$__k = $__v'); // PHP bug #46873
  215. }
  216. }
  217. // static block/snippet
  218. if ($node->name === 'snippet') {
  219. $node->data->name = $name = '_' . $name;
  220. }
  221. if (isset($this->namedBlocks[$name])) {
  222. throw new CompileException("Cannot redeclare static block '$name'");
  223. }
  224. $prolog = $this->namedBlocks ? '' : "if (\$_l->extends) { ob_end_clean(); return Nette\\Latte\\Macros\\CoreMacros::includeTemplate(\$_l->extends, get_defined_vars(), \$template)->render(); }\n";
  225. $top = empty($node->parentNode);
  226. $this->namedBlocks[$name] = TRUE;
  227. $include = 'call_user_func(reset($_l->blocks[%var]), $_l, ' . ($node->name === 'snippet' ? '$template->getParameters()' : 'get_defined_vars()') . ')';
  228. if ($node->modifiers) {
  229. $include = "ob_start(); $include; echo %modify(ob_get_clean())";
  230. }
  231. if ($node->name === 'snippet') {
  232. if ($node->prefix) {
  233. $node->attrCode = $writer->write('<?php echo \' id="\' . $_control->getSnippetId(%var) . \'"\' ?>', (string) substr($name, 1));
  234. return $writer->write($prolog . $include, $name);
  235. }
  236. $tag = trim($node->tokenizer->fetchWord(), '<>');
  237. $tag = $tag ? $tag : 'div';
  238. return $writer->write("$prolog ?>\n<$tag id=\"<?php echo \$_control->getSnippetId(%var) ?>\"><?php $include ?>\n</$tag><?php ",
  239. (string) substr($name, 1), $name
  240. );
  241. } elseif ($node->name === 'define') {
  242. return $prolog;
  243. } else {
  244. return $writer->write($prolog . $include, $name);
  245. }
  246. }
  247. /**
  248. * {/block}
  249. * {/snippet}
  250. * {/define}
  251. */
  252. public function macroBlockEnd(MacroNode $node, PhpWriter $writer)
  253. {
  254. if (isset($node->data->name)) { // block, snippet, define
  255. if ($node->name === 'snippet' && $node->prefix === MacroNode::PREFIX_NONE // n:snippet -> n:inner-snippet
  256. && preg_match('#^.*? n:\w+>\n?#s', $node->content, $m1) && preg_match('#[ \t]*<[^<]+\z#s', $node->content, $m2))
  257. {
  258. $node->openingCode = $m1[0] . $node->openingCode;
  259. $node->content = substr($node->content, strlen($m1[0]), -strlen($m2[0]));
  260. $node->closingCode .= $m2[0];
  261. }
  262. if (empty($node->data->leave)) {
  263. if (!empty($node->data->dynamic)) {
  264. $node->content .= '<?php if (isset($_dynSnippets)) return $_dynSnippets; ?>';
  265. }
  266. $this->namedBlocks[$node->data->name] = $tmp = rtrim(ltrim($node->content, "\n"), " \t");
  267. $node->content = substr_replace($node->content, $node->openingCode . "\n", strspn($node->content, "\n"), strlen($tmp));
  268. $node->openingCode = "<?php ?>";
  269. }
  270. } elseif ($node->modifiers) { // anonymous block with modifier
  271. return $writer->write('echo %modify(ob_get_clean())');
  272. }
  273. }
  274. /**
  275. * {ifset #block}
  276. */
  277. public function macroIfset(MacroNode $node, PhpWriter $writer)
  278. {
  279. if (!Strings::contains($node->args, '#')) {
  280. return FALSE;
  281. }
  282. $list = array();
  283. while (($name = $node->tokenizer->fetchWord()) !== FALSE) {
  284. $list[] = $name[0] === '#' ? '$_l->blocks["' . substr($name, 1) . '"]' : $name;
  285. }
  286. return 'if (isset(' . implode(', ', $list) . ')):';
  287. }
  288. /**
  289. * {control name[:method] [params]}
  290. */
  291. public function macroControl(MacroNode $node, PhpWriter $writer)
  292. {
  293. if ($node->name === 'widget') {
  294. trigger_error('Macro {widget} is deprecated; use {control} instead.', E_USER_DEPRECATED);
  295. }
  296. $pair = $node->tokenizer->fetchWord();
  297. if ($pair === FALSE) {
  298. throw new CompileException("Missing control name in {control}");
  299. }
  300. $pair = explode(':', $pair, 2);
  301. $name = $writer->formatWord($pair[0]);
  302. $method = isset($pair[1]) ? ucfirst($pair[1]) : '';
  303. $method = Strings::match($method, '#^\w*\z#') ? "render$method" : "{\"render$method\"}";
  304. $param = $writer->formatArray();
  305. if (!Strings::contains($node->args, '=>')) {
  306. $param = substr($param, 6, -1); // removes array()
  307. }
  308. return ($name[0] === '$' ? "if (is_object($name)) \$_ctrl = $name; else " : '')
  309. . '$_ctrl = $_control->getComponent(' . $name . '); '
  310. . 'if ($_ctrl instanceof Nette\Application\UI\IRenderable) $_ctrl->validateControl(); '
  311. . ($node->modifiers === '' ? "\$_ctrl->$method($param)" : $writer->write("ob_start(); \$_ctrl->$method($param); echo %modify(ob_get_clean())"));
  312. }
  313. /**
  314. * {link destination [,] [params]}
  315. * {plink destination [,] [params]}
  316. * n:href="destination [,] [params]"
  317. */
  318. public function macroLink(MacroNode $node, PhpWriter $writer)
  319. {
  320. return $writer->write('echo %escape(%modify(' . ($node->name === 'plink' ? '$_presenter' : '$_control') . '->link(%node.word, %node.array?)))');
  321. }
  322. /**
  323. * {ifCurrent destination [,] [params]}
  324. */
  325. public function macroIfCurrent(MacroNode $node, PhpWriter $writer)
  326. {
  327. return $writer->write(($node->args ? 'try { $_presenter->link(%node.word, %node.array?); } catch (Nette\Application\UI\InvalidLinkException $e) {}' : '')
  328. . '; if ($_presenter->getLastCreatedRequestFlag("current")):');
  329. }
  330. /**
  331. * {contentType ...}
  332. */
  333. public function macroContentType(MacroNode $node, PhpWriter $writer)
  334. {
  335. if (Strings::contains($node->args, 'xhtml')) {
  336. $this->getCompiler()->setContentType(Latte\Compiler::CONTENT_XHTML);
  337. } elseif (Strings::contains($node->args, 'html')) {
  338. $this->getCompiler()->setContentType(Latte\Compiler::CONTENT_HTML);
  339. } elseif (Strings::contains($node->args, 'xml')) {
  340. $this->getCompiler()->setContentType(Latte\Compiler::CONTENT_XML);
  341. } elseif (Strings::contains($node->args, 'javascript')) {
  342. $this->getCompiler()->setContentType(Latte\Compiler::CONTENT_JS);
  343. } elseif (Strings::contains($node->args, 'css')) {
  344. $this->getCompiler()->setContentType(Latte\Compiler::CONTENT_CSS);
  345. } elseif (Strings::contains($node->args, 'calendar')) {
  346. $this->getCompiler()->setContentType(Latte\Compiler::CONTENT_ICAL);
  347. } else {
  348. $this->getCompiler()->setContentType(Latte\Compiler::CONTENT_TEXT);
  349. }
  350. // temporary solution
  351. if (Strings::contains($node->args, '/')) {
  352. return $writer->write('$netteHttpResponse->setHeader("Content-Type", %var)', $node->args);
  353. }
  354. }
  355. /**
  356. * {status ...}
  357. */
  358. public function macroStatus(MacroNode $node, PhpWriter $writer)
  359. {
  360. return $writer->write((substr($node->args, -1) === '?' ? 'if (!$netteHttpResponse->isSent()) ' : '') .
  361. '$netteHttpResponse->setCode(%var)', (int) $node->args
  362. );
  363. }
  364. /********************* run-time writers ****************d*g**/
  365. /**
  366. * Calls block.
  367. * @return void
  368. */
  369. public static function callBlock(\stdClass $context, $name, array $params)
  370. {
  371. if (empty($context->blocks[$name])) {
  372. throw new Nette\InvalidStateException("Cannot include undefined block '$name'.");
  373. }
  374. $block = reset($context->blocks[$name]);
  375. $block($context, $params);
  376. }
  377. /**
  378. * Calls parent block.
  379. * @return void
  380. */
  381. public static function callBlockParent(\stdClass $context, $name, array $params)
  382. {
  383. if (empty($context->blocks[$name]) || ($block = next($context->blocks[$name])) === FALSE) {
  384. throw new Nette\InvalidStateException("Cannot include undefined parent block '$name'.");
  385. }
  386. $block($context, $params);
  387. }
  388. public static function renderSnippets(Nette\Application\UI\Control $control, \stdClass $local, array $params)
  389. {
  390. $control->snippetMode = FALSE;
  391. $payload = $control->getPresenter()->getPayload();
  392. if (isset($local->blocks)) {
  393. foreach ($local->blocks as $name => $function) {
  394. if ($name[0] !== '_' || !$control->isControlInvalid(substr($name, 1))) {
  395. continue;
  396. }
  397. ob_start();
  398. $function = reset($function);
  399. $snippets = $function($local, $params);
  400. $payload->snippets[$id = $control->getSnippetId(substr($name, 1))] = ob_get_clean();
  401. if ($snippets) {
  402. $payload->snippets += $snippets;
  403. unset($payload->snippets[$id]);
  404. }
  405. }
  406. }
  407. $control->snippetMode = TRUE;
  408. if ($control instanceof Nette\Application\UI\IRenderable) {
  409. $queue = array($control);
  410. do {
  411. foreach (array_shift($queue)->getComponents() as $child) {
  412. if ($child instanceof Nette\Application\UI\IRenderable) {
  413. if ($child->isControlInvalid()) {
  414. $child->snippetMode = TRUE;
  415. $child->render();
  416. $child->snippetMode = FALSE;
  417. }
  418. } elseif ($child instanceof Nette\ComponentModel\IContainer) {
  419. $queue[] = $child;
  420. }
  421. }
  422. } while ($queue);
  423. }
  424. }
  425. }