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