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