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