1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10: 11:
12:
13:
14:
15: 16: 17: 18: 19:
20: abstract class NTemplate extends NObject implements ITemplate
21: {
22:
23: public $warnOnUndefined = TRUE;
24:
25:
26: public $onPrepareFilters = array();
27:
28:
29: private $params = array();
30:
31:
32: private $filters = array();
33:
34:
35: private $helpers = array();
36:
37:
38: private $helperLoaders = array();
39:
40:
41:
42: 43: 44: 45: 46:
47: public function registerFilter($callback)
48: {
49: $callback = callback($callback);
50: if (in_array($callback, $this->filters)) {
51: throw new InvalidStateException("Filter '$callback' was registered twice.");
52: }
53: $this->filters[] = $callback;
54: }
55:
56:
57:
58: 59: 60: 61:
62: final public function getFilters()
63: {
64: return $this->filters;
65: }
66:
67:
68:
69:
70:
71:
72:
73: 74: 75: 76: 77:
78: public function render()
79: {
80: throw new NotImplementedException;
81: }
82:
83:
84:
85: 86: 87: 88: 89:
90: public function save($file)
91: {
92: if (file_put_contents($file, $this->__toString(TRUE)) === FALSE) {
93: throw new IOException("Unable to save file '$file'.");
94: }
95: }
96:
97:
98:
99: 100: 101: 102: 103:
104: public function __toString()
105: {
106: ob_start();
107: try {
108: $this->render();
109: return ob_get_clean();
110:
111: } catch (Exception $e) {
112: ob_end_clean();
113: if (func_num_args() && func_get_arg(0)) {
114: throw $e;
115: } else {
116: NDebug::toStringException($e);
117: }
118: }
119: }
120:
121:
122:
123: 124: 125: 126: 127:
128: public function compile($content)
129: {
130: if (!$this->filters) {
131: $this->onPrepareFilters($this);
132: }
133:
134: foreach ($this->filters as $filter) {
135: $content = self::extractPhp($content, $blocks);
136: $content = $filter->invoke($content);
137: $content = strtr($content, $blocks); 138: }
139:
140: return self::optimizePhp($content);
141: }
142:
143:
144:
145:
146:
147:
148:
149: 150: 151: 152: 153: 154:
155: public function registerHelper($name, $callback)
156: {
157: $this->helpers[strtolower($name)] = callback($callback);
158: }
159:
160:
161:
162: 163: 164: 165: 166:
167: public function registerHelperLoader($callback)
168: {
169: $this->helperLoaders[] = callback($callback);
170: }
171:
172:
173:
174: 175: 176: 177:
178: final public function getHelpers()
179: {
180: return $this->helpers;
181: }
182:
183:
184:
185: 186: 187: 188: 189: 190:
191: public function __call($name, $args)
192: {
193: $lname = strtolower($name);
194: if (!isset($this->helpers[$lname])) {
195: foreach ($this->helperLoaders as $loader) {
196: $helper = $loader->invoke($lname);
197: if ($helper) {
198: $this->registerHelper($lname, $helper);
199: return $this->helpers[$lname]->invokeArgs($args);
200: }
201: }
202: return parent::__call($name, $args);
203: }
204:
205: return $this->helpers[$lname]->invokeArgs($args);
206: }
207:
208:
209:
210: 211: 212: 213: 214:
215: public function setTranslator(ITranslator $translator = NULL)
216: {
217: $this->registerHelper('translate', $translator === NULL ? NULL : array($translator, 'translate'));
218: return $this;
219: }
220:
221:
222:
223:
224:
225:
226:
227: 228: 229: 230: 231: 232:
233: public function add($name, $value)
234: {
235: if (array_key_exists($name, $this->params)) {
236: throw new InvalidStateException("The variable '$name' already exists.");
237: }
238:
239: $this->params[$name] = $value;
240: }
241:
242:
243:
244: 245: 246: 247: 248:
249: public function setParams(array $params)
250: {
251: $this->params = $params;
252: return $this;
253: }
254:
255:
256:
257: 258: 259: 260:
261: public function getParams()
262: {
263: return $this->params;
264: }
265:
266:
267:
268: 269: 270: 271: 272: 273:
274: public function __set($name, $value)
275: {
276: $this->params[$name] = $value;
277: }
278:
279:
280:
281: 282: 283: 284: 285:
286: public function &__get($name)
287: {
288: if ($this->warnOnUndefined && !array_key_exists($name, $this->params)) {
289: trigger_error("The variable '$name' does not exist in template.", E_USER_NOTICE);
290: }
291:
292: return $this->params[$name];
293: }
294:
295:
296:
297: 298: 299: 300: 301:
302: public function __isset($name)
303: {
304: return isset($this->params[$name]);
305: }
306:
307:
308:
309: 310: 311: 312: 313:
314: public function __unset($name)
315: {
316: unset($this->params[$name]);
317: }
318:
319:
320:
321:
322:
323:
324:
325: 326: 327: 328: 329: 330:
331: private static function extractPhp($source, & $blocks)
332: {
333: $res = '';
334: $blocks = array();
335: $tokens = token_get_all($source);
336: foreach ($tokens as $n => $token) {
337: if (is_array($token)) {
338: if ($token[0] === T_INLINE_HTML) {
339: $res .= $token[1];
340: continue;
341:
342: } elseif ($token[0] === T_OPEN_TAG && $token[1] === '<?' && isset($tokens[$n+1][1]) && $tokens[$n+1][1] === 'xml') {
343: $php = & $res;
344: $token[1] = '<<?php ?>?';
345:
346: } elseif ($token[0] === T_OPEN_TAG || $token[0] === T_OPEN_TAG_WITH_ECHO) {
347: $res .= $id = "\x01@php:p" . count($blocks) . "@\x02";
348: $php = & $blocks[$id];
349: }
350: $php .= $token[1];
351:
352: } else {
353: $php .= $token;
354: }
355: }
356: return $res;
357: }
358:
359:
360:
361: 362: 363: 364: 365:
366: public static function optimizePhp($source)
367: {
368: $res = $php = '';
369: $lastChar = ';';
370: $tokens = new ArrayIterator(token_get_all($source));
371: foreach ($tokens as $key => $token) {
372: if (is_array($token)) {
373: if ($token[0] === T_INLINE_HTML) {
374: $lastChar = '';
375: $res .= $token[1];
376:
377: } elseif ($token[0] === T_CLOSE_TAG) {
378: $next = isset($tokens[$key + 1]) ? $tokens[$key + 1] : NULL;
379: if (substr($res, -1) !== '<' && preg_match('#^<\?php\s*$#', $php)) {
380: $php = ''; 381:
382: } elseif (is_array($next) && $next[0] === T_OPEN_TAG) { 383: if (!strspn($lastChar, ';{}:/')) {
384: $php .= $lastChar = ';';
385: }
386: if (substr($next[1], -1) === "\n") {
387: $php .= "\n";
388: }
389: $tokens->next();
390:
391: } elseif ($next) {
392: $res .= preg_replace('#;?(\s)*$#', '$1', $php) . $token[1]; 393: $php = '';
394:
395: } else { 396: if (!strspn($lastChar, '};')) {
397: $php .= ';';
398: }
399: }
400:
401: } elseif ($token[0] === T_ELSE || $token[0] === T_ELSEIF) {
402: if ($tokens[$key + 1] === ':' && $lastChar === '}') {
403: $php .= ';'; 404: }
405: $lastChar = '';
406: $php .= $token[1];
407:
408: } else {
409: if (!in_array($token[0], array(T_WHITESPACE, T_COMMENT, T_DOC_COMMENT, T_OPEN_TAG))) {
410: $lastChar = '';
411: }
412: $php .= $token[1];
413: }
414: } else {
415: $php .= $lastChar = $token;
416: }
417: }
418: return $res . $php;
419: }
420:
421: }
422: