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