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