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