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