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