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