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('#^\S{5,100}\z#', $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: }
199: $file = md5($name);
200: if (preg_match('#\b\w.{10,50}$#', $name, $m)) {
201: $file = trim(preg_replace('#\W+#', '-', $m[0]), '-') . '-' . $file;
202: }
203: return $this->tempDirectory . '/' . $file . '.php';
204: }
205:
206:
207: 208: 209: 210: 211: 212:
213: public function addFilter($name, $callback)
214: {
215: if ($name == NULL) {
216: array_unshift($this->filters[NULL], $callback);
217: } else {
218: $this->filters[strtolower($name)] = $callback;
219: }
220: return $this;
221: }
222:
223:
224: 225: 226: 227:
228: public function getFilters()
229: {
230: return $this->filters;
231: }
232:
233:
234: 235: 236: 237:
238: public function addMacro($name, IMacro $macro)
239: {
240: $this->getCompiler()->addMacro($name, $macro);
241: return $this;
242: }
243:
244:
245: 246: 247:
248: public function setContentType($type)
249: {
250: $this->contentType = $type;
251: return $this;
252: }
253:
254:
255: 256: 257: 258:
259: public function setTempDirectory($path)
260: {
261: $this->tempDirectory = $path;
262: return $this;
263: }
264:
265:
266: 267: 268: 269:
270: public function setAutoRefresh($on = TRUE)
271: {
272: $this->autoRefresh = (bool) $on;
273: return $this;
274: }
275:
276:
277: 278: 279:
280: public function getParser()
281: {
282: if (!$this->parser) {
283: $this->parser = new Parser;
284: }
285: return $this->parser;
286: }
287:
288:
289: 290: 291:
292: public function getCompiler()
293: {
294: if (!$this->compiler) {
295: $this->compiler = new Compiler;
296: Macros\CoreMacros::install($this->compiler);
297: Macros\BlockMacros::install($this->compiler);
298: }
299: return $this->compiler;
300: }
301:
302:
303: 304: 305:
306: public function setLoader(ILoader $loader)
307: {
308: $this->loader = $loader;
309: return $this;
310: }
311:
312:
313: 314: 315:
316: public function getLoader()
317: {
318: if (!$this->loader) {
319: $this->loader = new Loaders\FileLoader;
320: }
321: return $this->loader;
322: }
323:
324: }
325: