1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Latte;
9:
10:
11: 12: 13: 14: 15:
16: class Engine extends Object
17: {
18:
19: const CONTENT_HTML = Compiler::CONTENT_HTML,
20: CONTENT_XHTML = Compiler::CONTENT_XHTML,
21: CONTENT_XML = Compiler::CONTENT_XML,
22: CONTENT_JS = Compiler::CONTENT_JS,
23: CONTENT_CSS = Compiler::CONTENT_CSS,
24: CONTENT_ICAL = Compiler::CONTENT_ICAL,
25: CONTENT_TEXT = Compiler::CONTENT_TEXT;
26:
27:
28: public $onCompile = array();
29:
30:
31: private $parser;
32:
33:
34: private $compiler;
35:
36:
37: private $loader;
38:
39:
40: private $contentType = self::CONTENT_HTML;
41:
42:
43: private $tempDirectory;
44:
45:
46: private $autoRefresh = TRUE;
47:
48:
49: private $filters = array(
50: NULL => array(),
51: 'bytes' => 'Latte\Runtime\Filters::bytes',
52: 'capitalize' => 'Latte\Runtime\Filters::capitalize',
53: 'datastream' => 'Latte\Runtime\Filters::dataStream',
54: 'date' => 'Latte\Runtime\Filters::date',
55: 'escapecss' => 'Latte\Runtime\Filters::escapeCss',
56: 'escapehtml' => 'Latte\Runtime\Filters::escapeHtml',
57: 'escapehtmlcomment' => 'Latte\Runtime\Filters::escapeHtmlComment',
58: 'escapeical' => 'Latte\Runtime\Filters::escapeICal',
59: 'escapejs' => 'Latte\Runtime\Filters::escapeJs',
60: 'escapeurl' => 'rawurlencode',
61: 'escapexml' => 'Latte\Runtime\Filters::escapeXML',
62: 'firstupper' => 'Latte\Runtime\Filters::firstUpper',
63: 'implode' => 'implode',
64: 'indent' => 'Latte\Runtime\Filters::indent',
65: 'lower' => 'Latte\Runtime\Filters::lower',
66: 'nl2br' => 'Latte\Runtime\Filters::nl2br',
67: 'number' => 'number_format',
68: 'repeat' => 'str_repeat',
69: 'replace' => 'Latte\Runtime\Filters::replace',
70: 'replacere' => 'Latte\Runtime\Filters::replaceRe',
71: 'safeurl' => 'Latte\Runtime\Filters::safeUrl',
72: 'strip' => 'Latte\Runtime\Filters::strip',
73: 'striptags' => 'strip_tags',
74: 'substr' => 'Latte\Runtime\Filters::substring',
75: 'trim' => 'Latte\Runtime\Filters::trim',
76: 'truncate' => 'Latte\Runtime\Filters::truncate',
77: 'upper' => 'Latte\Runtime\Filters::upper',
78: );
79:
80:
81: private $baseTemplateClass = 'Latte\Template';
82:
83:
84: 85: 86: 87:
88: public function render($name, array $params = array())
89: {
90: $template = new $this->baseTemplateClass($params, $this->filters, $this, $name);
91: $this->loadCacheFile($name, $template->getParameters());
92: }
93:
94:
95: 96: 97: 98:
99: public function renderToString($name, array $params = array())
100: {
101: ob_start();
102: try {
103: $this->render($name, $params);
104: } catch (\Exception $e) {
105: ob_end_clean();
106: throw $e;
107: }
108: return ob_get_clean();
109: }
110:
111:
112: 113: 114: 115:
116: public function compile($name)
117: {
118: foreach ($this->onCompile ?: array() as $cb) {
119: call_user_func(Helpers::checkCallback($cb), $this);
120: }
121: $this->onCompile = array();
122:
123: $source = $this->getLoader()->getContent($name);
124: try {
125: $tokens = $this->getParser()->setContentType($this->contentType)
126: ->parse($source);
127: $code = $this->getCompiler()->setContentType($this->contentType)
128: ->compile($tokens);
129:
130: if (!preg_match('#\n|\?#', $name)) {
131: $code = "<?php\n// source: $name\n?>" . $code;
132: }
133:
134: } catch (\Exception $e) {
135: $e = $e instanceof CompileException ? $e : new CompileException("Thrown exception '{$e->getMessage()}'", NULL, $e);
136: throw $e->setSource($source, $this->getCompiler()->getLine(), $name);
137: }
138: $code = Helpers::optimizePhp($code);
139: return $code;
140: }
141:
142:
143: 144: 145:
146: private function loadCacheFile($name, $params)
147: {
148: if (!$this->tempDirectory) {
149: return call_user_func(function() {
150: foreach (func_get_arg(1) as $__k => $__v) {
151: $$__k = $__v;
152: }
153: unset($__k, $__v);
154: eval('?>' . func_get_arg(0));
155: }, $this->compile($name), $params);
156: }
157:
158: $file = $this->getCacheFile($name);
159: $handle = fopen($file, 'c+');
160: if (!$handle) {
161: throw new \RuntimeException("Unable to open or create file '$file'.");
162: }
163: flock($handle, LOCK_SH);
164: $stat = fstat($handle);
165: if (!$stat['size'] || ($this->autoRefresh && $this->getLoader()->isExpired($name, $stat['mtime']))) {
166: ftruncate($handle, 0);
167: flock($handle, LOCK_EX);
168: $stat = fstat($handle);
169: if (!$stat['size']) {
170: $code = $this->compile($name);
171: if (fwrite($handle, $code, strlen($code)) !== strlen($code)) {
172: ftruncate($handle, 0);
173: throw new \RuntimeException("Unable to write file '$file'.");
174: }
175: }
176: flock($handle, LOCK_SH);
177: }
178:
179: call_user_func(function() {
180: foreach (func_get_arg(1) as $__k => $__v) {
181: $$__k = $__v;
182: }
183: unset($__k, $__v);
184: include func_get_arg(0);
185: }, $file, $params);
186: }
187:
188:
189: 190: 191:
192: public function getCacheFile($name)
193: {
194: if (!$this->tempDirectory) {
195: throw new \RuntimeException('Set path to temporary directory using setTempDirectory().');
196: } elseif (!is_dir($this->tempDirectory)) {
197: @mkdir($this->tempDirectory);
198: if (!is_dir($this->tempDirectory)) {
199: throw new \RuntimeException("Temporary directory cannot be created. Check access rights");
200: }
201: }
202: $file = md5($name);
203: if (preg_match('#\b\w.{10,50}$#', $name, $m)) {
204: $file = trim(preg_replace('#\W+#', '-', $m[0]), '-') . '-' . $file;
205: }
206: return $this->tempDirectory . '/' . $file . '.php';
207: }
208:
209:
210: 211: 212: 213: 214: 215:
216: public function addFilter($name, $callback)
217: {
218: if ($name == NULL) {
219: array_unshift($this->filters[NULL], $callback);
220: } else {
221: $this->filters[strtolower($name)] = $callback;
222: }
223: return $this;
224: }
225:
226:
227: 228: 229: 230:
231: public function getFilters()
232: {
233: return $this->filters;
234: }
235:
236:
237: 238: 239: 240: 241: 242:
243: public function invokeFilter($name, array $args)
244: {
245: $lname = strtolower($name);
246: if (!isset($this->filters[$lname])) {
247: $args2 = $args;
248: array_unshift($args2, $lname);
249: foreach ($this->filters[NULL] as $filter) {
250: $res = call_user_func_array(Helpers::checkCallback($filter), $args2);
251: if ($res !== NULL) {
252: return $res;
253: } elseif (isset($this->filters[$lname])) {
254: return call_user_func_array(Helpers::checkCallback($this->filters[$lname]), $args);
255: }
256: }
257: throw new \LogicException("Filter '$name' is not defined.");
258: }
259: return call_user_func_array(Helpers::checkCallback($this->filters[$lname]), $args);
260: }
261:
262:
263: 264: 265: 266:
267: public function addMacro($name, IMacro $macro)
268: {
269: $this->getCompiler()->addMacro($name, $macro);
270: return $this;
271: }
272:
273:
274: 275: 276:
277: public function setContentType($type)
278: {
279: $this->contentType = $type;
280: return $this;
281: }
282:
283:
284: 285: 286: 287:
288: public function setTempDirectory($path)
289: {
290: $this->tempDirectory = $path;
291: return $this;
292: }
293:
294:
295: 296: 297: 298:
299: public function setAutoRefresh($on = TRUE)
300: {
301: $this->autoRefresh = (bool) $on;
302: return $this;
303: }
304:
305:
306: 307: 308:
309: public function getParser()
310: {
311: if (!$this->parser) {
312: $this->parser = new Parser;
313: }
314: return $this->parser;
315: }
316:
317:
318: 319: 320:
321: public function getCompiler()
322: {
323: if (!$this->compiler) {
324: $this->compiler = new Compiler;
325: Macros\CoreMacros::install($this->compiler);
326: Macros\BlockMacros::install($this->compiler);
327: }
328: return $this->compiler;
329: }
330:
331:
332: 333: 334:
335: public function setLoader(ILoader $loader)
336: {
337: $this->loader = $loader;
338: return $this;
339: }
340:
341:
342: 343: 344:
345: public function getLoader()
346: {
347: if (!$this->loader) {
348: $this->loader = new Loaders\FileLoader;
349: }
350: return $this->loader;
351: }
352:
353: }
354: