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