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_num_args()) {
119: throw $e;
120: }
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: public function compile()
131: {
132: if (!$this->filters) {
133: $this->onPrepareFilters($this);
134: }
135:
136: $code = $this->getSource();
137: foreach ($this->filters as $filter) {
138: $code = self::extractPhp($code, $blocks);
139: $code = $filter($code);
140: $code = strtr($code, $blocks);
141: }
142:
143: return Helpers::optimizePhp($code);
144: }
145:
146:
147:
148:
149:
150: 151: 152: 153: 154:
155: public function registerFilter($callback)
156: {
157: $callback = new Nette\Callback($callback);
158: if (in_array($callback, $this->filters, TRUE)) {
159: throw new Nette\InvalidStateException("Filter '$callback' was registered twice.");
160: }
161: $this->filters[] = $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)] = new Nette\Callback($callback);
185: return $this;
186: }
187:
188:
189: 190: 191: 192: 193:
194: public function registerHelperLoader($callback)
195: {
196: $this->helperLoaders[] = new Nette\Callback($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 = $loader($lname);
233: if ($helper) {
234: $this->registerHelper($lname, $helper);
235: return $this->helpers[$lname]->invokeArgs($args);
236: }
237: }
238: return parent::__call($name, $args);
239: }
240:
241: return $this->helpers[$lname]->invokeArgs($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: function setParams(array $params)
299: {
300: trigger_error(__METHOD__ . '() is deprecated; use setParameters() instead.', E_USER_WARNING);
301: return $this->setParameters($params);
302: }
303:
304:
305:
306: function getParams()
307: {
308: trigger_error(__METHOD__ . '() is deprecated; use getParameters() instead.', E_USER_WARNING);
309: return $this->getParameters();
310: }
311:
312:
313: 314: 315: 316:
317: public function __set($name, $value)
318: {
319: $this->params[$name] = $value;
320: }
321:
322:
323: 324: 325: 326:
327: public function &__get($name)
328: {
329: if (!array_key_exists($name, $this->params)) {
330: trigger_error("The variable '$name' does not exist in template.", E_USER_NOTICE);
331: }
332:
333: return $this->params[$name];
334: }
335:
336:
337: 338: 339: 340:
341: public function __isset($name)
342: {
343: return isset($this->params[$name]);
344: }
345:
346:
347: 348: 349: 350: 351:
352: public function __unset($name)
353: {
354: unset($this->params[$name]);
355: }
356:
357:
358:
359:
360:
361: 362: 363: 364:
365: public function setCacheStorage(Caching\IStorage $storage)
366: {
367: $this->cacheStorage = $storage;
368: return $this;
369: }
370:
371:
372: 373: 374:
375: public function getCacheStorage()
376: {
377: if ($this->cacheStorage === NULL) {
378: return new Caching\Storages\DevNullStorage;
379: }
380: return $this->cacheStorage;
381: }
382:
383:
384:
385:
386:
387: 388: 389: 390: 391: 392:
393: private static function ($source, & $blocks)
394: {
395: $res = '';
396: $blocks = array();
397: $tokens = token_get_all($source);
398: foreach ($tokens as $n => $token) {
399: if (is_array($token)) {
400: if ($token[0] === T_INLINE_HTML) {
401: $res .= $token[1];
402: continue;
403:
404: } elseif ($token[0] === T_CLOSE_TAG) {
405: if ($php !== $res) {
406: $res .= str_repeat("\n", substr_count($php, "\n"));
407: }
408: $res .= $token[1];
409: continue;
410:
411: } elseif ($token[0] === T_OPEN_TAG && $token[1] === '<?' && isset($tokens[$n+1][1]) && $tokens[$n+1][1] === 'xml') {
412: $php = & $res;
413: $token[1] = '<<?php ?>?';
414:
415: } elseif ($token[0] === T_OPEN_TAG || $token[0] === T_OPEN_TAG_WITH_ECHO) {
416: $res .= $id = "<?php \x01@php:p" . count($blocks) . "@\x02";
417: $php = & $blocks[$id];
418: }
419: $php .= $token[1];
420:
421: } else {
422: $php .= $token;
423: }
424: }
425: return $res;
426: }
427:
428: }
429: