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