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