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