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