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: 237:
238: public function __call($name, $args)
239: {
240: $lname = strtolower($name);
241: if (!isset($this->helpers[$lname])) {
242: foreach ($this->helperLoaders as $loader) {
243: $helper = $loader($lname);
244: if ($helper) {
245: $this->registerHelper($lname, $helper);
246: return $this->helpers[$lname]->invokeArgs($args);
247: }
248: }
249: return parent::__call($name, $args);
250: }
251:
252: return $this->helpers[$lname]->invokeArgs($args);
253: }
254:
255:
256:
257: 258: 259: 260: 261:
262: public function setTranslator(Nette\Localization\ITranslator $translator = NULL)
263: {
264: $this->registerHelper('translate', $translator === NULL ? NULL : array($translator, 'translate'));
265: return $this;
266: }
267:
268:
269:
270:
271:
272:
273:
274: 275: 276: 277: 278: 279:
280: public function add($name, $value)
281: {
282: if (array_key_exists($name, $this->params)) {
283: throw new Nette\InvalidStateException("The variable '$name' already exists.");
284: }
285:
286: $this->params[$name] = $value;
287: return $this;
288: }
289:
290:
291:
292: 293: 294: 295: 296:
297: public function setParameters(array $params)
298: {
299: $this->params = $params + $this->params;
300: return $this;
301: }
302:
303:
304:
305: 306: 307: 308:
309: public function getParameters()
310: {
311: $this->params['template'] = $this;
312: return $this->params;
313: }
314:
315:
316:
317:
318: function setParams(array $params)
319: {
320: trigger_error(__METHOD__ . '() is deprecated; use setParameters() instead.', E_USER_WARNING);
321: return $this->setParameters($params);
322: }
323:
324:
325:
326:
327: function getParams()
328: {
329: trigger_error(__METHOD__ . '() is deprecated; use getParameters() instead.', E_USER_WARNING);
330: return $this->getParameters();
331: }
332:
333:
334:
335: 336: 337: 338: 339: 340:
341: public function __set($name, $value)
342: {
343: $this->params[$name] = $value;
344: }
345:
346:
347:
348: 349: 350: 351: 352:
353: public function &__get($name)
354: {
355: if (!array_key_exists($name, $this->params)) {
356: trigger_error("The variable '$name' does not exist in template.", E_USER_NOTICE);
357: }
358:
359: return $this->params[$name];
360: }
361:
362:
363:
364: 365: 366: 367: 368:
369: public function __isset($name)
370: {
371: return isset($this->params[$name]);
372: }
373:
374:
375:
376: 377: 378: 379: 380:
381: public function __unset($name)
382: {
383: unset($this->params[$name]);
384: }
385:
386:
387:
388:
389:
390:
391:
392: 393: 394: 395: 396:
397: public function setCacheStorage(Caching\IStorage $storage)
398: {
399: $this->cacheStorage = $storage;
400: return $this;
401: }
402:
403:
404:
405: 406: 407:
408: public function getCacheStorage()
409: {
410: if ($this->cacheStorage === NULL) {
411: return new Caching\Storages\DevNullStorage;
412: }
413: return $this->cacheStorage;
414: }
415:
416:
417:
418:
419:
420:
421:
422: 423: 424: 425: 426: 427:
428: private static function extractPhp($source, & $blocks)
429: {
430: $res = '';
431: $blocks = array();
432: $tokens = token_get_all($source);
433: foreach ($tokens as $n => $token) {
434: if (is_array($token)) {
435: if ($token[0] === T_INLINE_HTML || $token[0] === T_CLOSE_TAG) {
436: $res .= $token[1];
437: continue;
438:
439: } elseif ($token[0] === T_OPEN_TAG && $token[1] === '<?' && isset($tokens[$n+1][1]) && $tokens[$n+1][1] === 'xml') {
440: $php = & $res;
441: $token[1] = '<<?php ?>?';
442:
443: } elseif ($token[0] === T_OPEN_TAG || $token[0] === T_OPEN_TAG_WITH_ECHO) {
444: $res .= $id = "<?php \x01@php:p" . count($blocks) . "@\x02";
445: $php = & $blocks[$id];
446: }
447: $php .= $token[1];
448:
449: } else {
450: $php .= $token;
451: }
452: }
453: return $res;
454: }
455:
456: }
457: