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