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: trigger_error("Exception in " . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
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 = new Nette\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)] = new Nette\Callback($callback);
203: return $this;
204: }
205:
206:
207:
208: 209: 210: 211: 212:
213: public function registerHelperLoader($callback)
214: {
215: $this->helperLoaders[] = new Nette\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: public function setTranslator(Nette\Localization\ITranslator $translator = NULL)
273: {
274: $this->registerHelper('translate', $translator === NULL ? NULL : array($translator, 'translate'));
275: return $this;
276: }
277:
278:
279:
280:
281:
282:
283:
284: 285: 286: 287: 288: 289:
290: public function add($name, $value)
291: {
292: if (array_key_exists($name, $this->params)) {
293: throw new Nette\InvalidStateException("The variable '$name' already exists.");
294: }
295:
296: $this->params[$name] = $value;
297: return $this;
298: }
299:
300:
301:
302: 303: 304: 305: 306:
307: public function setParameters(array $params)
308: {
309: $this->params = $params + $this->params;
310: return $this;
311: }
312:
313:
314:
315: 316: 317: 318:
319: public function getParameters()
320: {
321: $this->params['template'] = $this;
322: return $this->params;
323: }
324:
325:
326:
327:
328: function setParams(array $params)
329: {
330: trigger_error(__METHOD__ . '() is deprecated; use setParameters() instead.', E_USER_WARNING);
331: return $this->setParameters($params);
332: }
333:
334:
335:
336:
337: function getParams()
338: {
339: trigger_error(__METHOD__ . '() is deprecated; use getParameters() instead.', E_USER_WARNING);
340: return $this->getParameters();
341: }
342:
343:
344:
345: 346: 347: 348: 349: 350:
351: public function __set($name, $value)
352: {
353: $this->params[$name] = $value;
354: }
355:
356:
357:
358: 359: 360: 361: 362:
363: public function &__get($name)
364: {
365: if (!array_key_exists($name, $this->params)) {
366: trigger_error("The variable '$name' does not exist in template.", E_USER_NOTICE);
367: }
368:
369: return $this->params[$name];
370: }
371:
372:
373:
374: 375: 376: 377: 378:
379: public function __isset($name)
380: {
381: return isset($this->params[$name]);
382: }
383:
384:
385:
386: 387: 388: 389: 390:
391: public function __unset($name)
392: {
393: unset($this->params[$name]);
394: }
395:
396:
397:
398:
399:
400:
401:
402: 403: 404: 405:
406: public function setCacheStorage(Caching\IStorage $storage)
407: {
408: $this->cacheStorage = $storage;
409: return $this;
410: }
411:
412:
413:
414: 415: 416:
417: public function getCacheStorage()
418: {
419: if ($this->cacheStorage === NULL) {
420: return new Caching\Storages\DevNullStorage;
421: }
422: return $this->cacheStorage;
423: }
424:
425:
426:
427:
428:
429:
430:
431: 432: 433: 434: 435: 436:
437: private static function ($source, & $blocks)
438: {
439: $res = '';
440: $blocks = array();
441: $tokens = token_get_all($source);
442: foreach ($tokens as $n => $token) {
443: if (is_array($token)) {
444: if ($token[0] === T_INLINE_HTML) {
445: $res .= $token[1];
446: continue;
447:
448: } elseif ($token[0] === T_CLOSE_TAG) {
449: if ($php !== $res) {
450: $res .= str_repeat("\n", substr_count($php, "\n"));
451: }
452: $res .= $token[1];
453: continue;
454:
455: } elseif ($token[0] === T_OPEN_TAG && $token[1] === '<?' && isset($tokens[$n+1][1]) && $tokens[$n+1][1] === 'xml') {
456: $php = & $res;
457: $token[1] = '<<?php ?>?';
458:
459: } elseif ($token[0] === T_OPEN_TAG || $token[0] === T_OPEN_TAG_WITH_ECHO) {
460: $res .= $id = "<?php \x01@php:p" . count($blocks) . "@\x02";
461: $php = & $blocks[$id];
462: }
463: $php .= $token[1];
464:
465: } else {
466: $php .= $token;
467: }
468: }
469: return $res;
470: }
471:
472: }
473: