1 | <?php |
---|
2 | |
---|
3 | /** |
---|
4 | * represents a Dwoo template contained in a string |
---|
5 | * |
---|
6 | * This software is provided 'as-is', without any express or implied warranty. |
---|
7 | * In no event will the authors be held liable for any damages arising from the use of this software. |
---|
8 | * |
---|
9 | * @author Jordi Boggiano <j.boggiano@seld.be> |
---|
10 | * @copyright Copyright (c) 2008, Jordi Boggiano |
---|
11 | * @license http://dwoo.org/LICENSE Modified BSD License |
---|
12 | * @link http://dwoo.org/ |
---|
13 | * @version 1.1.0 |
---|
14 | * @date 2009-07-18 |
---|
15 | * @package Dwoo |
---|
16 | */ |
---|
17 | class Dwoo_Template_String implements Dwoo_ITemplate |
---|
18 | { |
---|
19 | /** |
---|
20 | * template name |
---|
21 | * |
---|
22 | * @var string |
---|
23 | */ |
---|
24 | protected $name; |
---|
25 | |
---|
26 | /** |
---|
27 | * template compilation id |
---|
28 | * |
---|
29 | * @var string |
---|
30 | */ |
---|
31 | protected $compileId; |
---|
32 | |
---|
33 | /** |
---|
34 | * template cache id, if not provided in the constructor, it is set to |
---|
35 | * the md4 hash of the request_uri. it is however highly recommended to |
---|
36 | * provide one that will fit your needs. |
---|
37 | * |
---|
38 | * in all cases, the compilation id is prepended to the cache id to separate |
---|
39 | * templates with similar cache ids from one another |
---|
40 | * |
---|
41 | * @var string |
---|
42 | */ |
---|
43 | protected $cacheId; |
---|
44 | |
---|
45 | /** |
---|
46 | * validity duration of the generated cache file (in seconds) |
---|
47 | * |
---|
48 | * set to -1 for infinite cache, 0 to disable and null to inherit the Dwoo instance's cache time |
---|
49 | * |
---|
50 | * @var int |
---|
51 | */ |
---|
52 | protected $cacheTime; |
---|
53 | |
---|
54 | /** |
---|
55 | * boolean flag that defines whether the compilation should be enforced (once) or |
---|
56 | * not use this if you have issues with the compiled templates not being updated |
---|
57 | * but if you do need this it's most likely that you should file a bug report |
---|
58 | * |
---|
59 | * @var bool |
---|
60 | */ |
---|
61 | protected $compilationEnforced; |
---|
62 | |
---|
63 | /** |
---|
64 | * caches the results of the file checks to save some time when the same |
---|
65 | * templates is rendered several times |
---|
66 | * |
---|
67 | * @var array |
---|
68 | */ |
---|
69 | protected static $cache = array('cached'=>array(), 'compiled'=>array()); |
---|
70 | |
---|
71 | /** |
---|
72 | * holds the compiler that built this template |
---|
73 | * |
---|
74 | * @var Dwoo_ICompiler |
---|
75 | */ |
---|
76 | protected $compiler; |
---|
77 | |
---|
78 | /** |
---|
79 | * chmod value for all files written (cached or compiled ones) |
---|
80 | * |
---|
81 | * set to null if you don't want any chmod operation to happen |
---|
82 | * |
---|
83 | * @var int |
---|
84 | */ |
---|
85 | protected $chmod = 0777; |
---|
86 | |
---|
87 | /** |
---|
88 | * creates a template from a string |
---|
89 | * |
---|
90 | * @param string $templateString the template to use |
---|
91 | * @param int $cacheTime duration of the cache validity for this template, |
---|
92 | * if null it defaults to the Dwoo instance that will |
---|
93 | * render this template, set to -1 for infinite cache or 0 to disable |
---|
94 | * @param string $cacheId the unique cache identifier of this page or anything else that |
---|
95 | * makes this template's content unique, if null it defaults |
---|
96 | * to the current url |
---|
97 | * @param string $compileId the unique compiled identifier, which is used to distinguish this |
---|
98 | * template from others, if null it defaults to the md4 hash of the template |
---|
99 | */ |
---|
100 | public function __construct($templateString, $cacheTime = null, $cacheId = null, $compileId = null) |
---|
101 | { |
---|
102 | $this->template = $templateString; |
---|
103 | if (function_exists('hash')) { |
---|
104 | $this->name = hash('md4', $templateString); |
---|
105 | } else { |
---|
106 | $this->name = md5($templateString); |
---|
107 | } |
---|
108 | $this->cacheTime = $cacheTime; |
---|
109 | |
---|
110 | if ($compileId !== null) { |
---|
111 | $this->compileId = str_replace('../', '__', strtr($compileId, '\\%?=!:;'.PATH_SEPARATOR, '/-------')); |
---|
112 | } |
---|
113 | |
---|
114 | if ($cacheId !== null) { |
---|
115 | $this->cacheId = str_replace('../', '__', strtr($cacheId, '\\%?=!:;'.PATH_SEPARATOR, '/-------')); |
---|
116 | } |
---|
117 | } |
---|
118 | |
---|
119 | /** |
---|
120 | * returns the cache duration for this template |
---|
121 | * |
---|
122 | * defaults to null if it was not provided |
---|
123 | * |
---|
124 | * @return int|null |
---|
125 | */ |
---|
126 | public function getCacheTime() |
---|
127 | { |
---|
128 | return $this->cacheTime; |
---|
129 | } |
---|
130 | |
---|
131 | /** |
---|
132 | * sets the cache duration for this template |
---|
133 | * |
---|
134 | * can be used to set it after the object is created if you did not provide |
---|
135 | * it in the constructor |
---|
136 | * |
---|
137 | * @param int $seconds duration of the cache validity for this template, if |
---|
138 | * null it defaults to the Dwoo instance's cache time. 0 = disable and |
---|
139 | * -1 = infinite cache |
---|
140 | */ |
---|
141 | public function setCacheTime($seconds = null) |
---|
142 | { |
---|
143 | $this->cacheTime = $seconds; |
---|
144 | } |
---|
145 | |
---|
146 | /** |
---|
147 | * returns the chmod value for all files written (cached or compiled ones) |
---|
148 | * |
---|
149 | * defaults to 0777 |
---|
150 | * |
---|
151 | * @return int|null |
---|
152 | */ |
---|
153 | public function getChmod() |
---|
154 | { |
---|
155 | return $this->chmod; |
---|
156 | } |
---|
157 | |
---|
158 | /** |
---|
159 | * set the chmod value for all files written (cached or compiled ones) |
---|
160 | * |
---|
161 | * set to null if you don't want to do any chmod() operation |
---|
162 | * |
---|
163 | * @param int $mask new bitmask to use for all files |
---|
164 | */ |
---|
165 | public function setChmod($mask = null) |
---|
166 | { |
---|
167 | $this->chmod = $mask; |
---|
168 | } |
---|
169 | |
---|
170 | /** |
---|
171 | * returns the template name |
---|
172 | * |
---|
173 | * @return string |
---|
174 | */ |
---|
175 | public function getName() |
---|
176 | { |
---|
177 | return $this->name; |
---|
178 | } |
---|
179 | |
---|
180 | /** |
---|
181 | * returns the resource name for this template class |
---|
182 | * |
---|
183 | * @return string |
---|
184 | */ |
---|
185 | public function getResourceName() |
---|
186 | { |
---|
187 | return 'string'; |
---|
188 | } |
---|
189 | |
---|
190 | /** |
---|
191 | * returns the resource identifier for this template, false here as strings don't have identifiers |
---|
192 | * |
---|
193 | * @return false |
---|
194 | */ |
---|
195 | public function getResourceIdentifier() |
---|
196 | { |
---|
197 | return false; |
---|
198 | } |
---|
199 | |
---|
200 | /** |
---|
201 | * returns the template source of this template |
---|
202 | * |
---|
203 | * @return string |
---|
204 | */ |
---|
205 | public function getSource() |
---|
206 | { |
---|
207 | return $this->template; |
---|
208 | } |
---|
209 | |
---|
210 | /** |
---|
211 | * returns an unique value identifying the current version of this template, |
---|
212 | * in this case it's the md4 hash of the content |
---|
213 | * |
---|
214 | * @return string |
---|
215 | */ |
---|
216 | public function getUid() |
---|
217 | { |
---|
218 | return $this->name; |
---|
219 | } |
---|
220 | |
---|
221 | /** |
---|
222 | * returns the compiler used by this template, if it was just compiled, or null |
---|
223 | * |
---|
224 | * @return Dwoo_ICompiler |
---|
225 | */ |
---|
226 | public function getCompiler() |
---|
227 | { |
---|
228 | return $this->compiler; |
---|
229 | } |
---|
230 | |
---|
231 | /** |
---|
232 | * marks this template as compile-forced, which means it will be recompiled even if it |
---|
233 | * was already saved and wasn't modified since the last compilation. do not use this in production, |
---|
234 | * it's only meant to be used in development (and the development of dwoo particularly) |
---|
235 | */ |
---|
236 | public function forceCompilation() |
---|
237 | { |
---|
238 | $this->compilationEnforced = true; |
---|
239 | } |
---|
240 | |
---|
241 | /** |
---|
242 | * returns the cached template output file name, true if it's cache-able but not cached |
---|
243 | * or false if it's not cached |
---|
244 | * |
---|
245 | * @param Dwoo_Core $dwoo the dwoo instance that requests it |
---|
246 | * @return string|bool |
---|
247 | */ |
---|
248 | public function getCachedTemplate(Dwoo_Core $dwoo) |
---|
249 | { |
---|
250 | if ($this->cacheTime !== null) { |
---|
251 | $cacheLength = $this->cacheTime; |
---|
252 | } else { |
---|
253 | $cacheLength = $dwoo->getCacheTime(); |
---|
254 | } |
---|
255 | |
---|
256 | // file is not cacheable |
---|
257 | if ($cacheLength == 0) { |
---|
258 | return false; |
---|
259 | } |
---|
260 | |
---|
261 | $cachedFile = $this->getCacheFilename($dwoo); |
---|
262 | |
---|
263 | if (isset(self::$cache['cached'][$this->cacheId]) === true && file_exists($cachedFile)) { |
---|
264 | // already checked, return cache file |
---|
265 | return $cachedFile; |
---|
266 | } elseif ($this->compilationEnforced !== true && file_exists($cachedFile) && ($cacheLength === -1 || filemtime($cachedFile) > ($_SERVER['REQUEST_TIME'] - $cacheLength)) && $this->isValidCompiledFile($this->getCompiledFilename($dwoo))) { |
---|
267 | // cache is still valid and can be loaded |
---|
268 | self::$cache['cached'][$this->cacheId] = true; |
---|
269 | return $cachedFile; |
---|
270 | } else { |
---|
271 | // file is cacheable |
---|
272 | return true; |
---|
273 | } |
---|
274 | } |
---|
275 | |
---|
276 | /** |
---|
277 | * caches the provided output into the cache file |
---|
278 | * |
---|
279 | * @param Dwoo_Core $dwoo the dwoo instance that requests it |
---|
280 | * @param string $output the template output |
---|
281 | * @return mixed full path of the cached file or false upon failure |
---|
282 | */ |
---|
283 | public function cache(Dwoo_Core $dwoo, $output) |
---|
284 | { |
---|
285 | $cacheDir = $dwoo->getCacheDir(); |
---|
286 | $cachedFile = $this->getCacheFilename($dwoo); |
---|
287 | |
---|
288 | // the code below is courtesy of Rasmus Schultz, |
---|
289 | // thanks for his help on avoiding concurency issues |
---|
290 | $temp = tempnam($cacheDir, 'temp'); |
---|
291 | if (!($file = @fopen($temp, 'wb'))) { |
---|
292 | $temp = $cacheDir . uniqid('temp'); |
---|
293 | if (!($file = @fopen($temp, 'wb'))) { |
---|
294 | trigger_error('Error writing temporary file \''.$temp.'\'', E_USER_WARNING); |
---|
295 | return false; |
---|
296 | } |
---|
297 | } |
---|
298 | |
---|
299 | fwrite($file, $output); |
---|
300 | fclose($file); |
---|
301 | |
---|
302 | $this->makeDirectory(dirname($cachedFile), $cacheDir); |
---|
303 | if (!@rename($temp, $cachedFile)) { |
---|
304 | @unlink($cachedFile); |
---|
305 | @rename($temp, $cachedFile); |
---|
306 | } |
---|
307 | |
---|
308 | if ($this->chmod !== null) { |
---|
309 | chmod($cachedFile, $this->chmod); |
---|
310 | } |
---|
311 | |
---|
312 | self::$cache['cached'][$this->cacheId] = true; |
---|
313 | |
---|
314 | return $cachedFile; |
---|
315 | } |
---|
316 | |
---|
317 | /** |
---|
318 | * clears the cached template if it's older than the given time |
---|
319 | * |
---|
320 | * @param Dwoo_Core $dwoo the dwoo instance that was used to cache that template |
---|
321 | * @param int $olderThan minimum time (in seconds) required for the cache to be cleared |
---|
322 | * @return bool true if the cache was not present or if it was deleted, false if it remains there |
---|
323 | */ |
---|
324 | public function clearCache(Dwoo_Core $dwoo, $olderThan = -1) |
---|
325 | { |
---|
326 | $cachedFile = $this->getCacheFilename($dwoo); |
---|
327 | |
---|
328 | return !file_exists($cachedFile) || (filectime($cachedFile) < (time() - $olderThan) && unlink($cachedFile)); |
---|
329 | } |
---|
330 | |
---|
331 | /** |
---|
332 | * returns the compiled template file name |
---|
333 | * |
---|
334 | * @param Dwoo_Core $dwoo the dwoo instance that requests it |
---|
335 | * @param Dwoo_ICompiler $compiler the compiler that must be used |
---|
336 | * @return string |
---|
337 | */ |
---|
338 | public function getCompiledTemplate(Dwoo_Core $dwoo, Dwoo_ICompiler $compiler = null) |
---|
339 | { |
---|
340 | $compiledFile = $this->getCompiledFilename($dwoo); |
---|
341 | |
---|
342 | if ($this->compilationEnforced !== true && isset(self::$cache['compiled'][$this->compileId]) === true) { |
---|
343 | // already checked, return compiled file |
---|
344 | } elseif ($this->compilationEnforced !== true && $this->isValidCompiledFile($compiledFile)) { |
---|
345 | // template is compiled |
---|
346 | self::$cache['compiled'][$this->compileId] = true; |
---|
347 | } else { |
---|
348 | // compiles the template |
---|
349 | $this->compilationEnforced = false; |
---|
350 | |
---|
351 | if ($compiler === null) { |
---|
352 | $compiler = $dwoo->getDefaultCompilerFactory($this->getResourceName()); |
---|
353 | |
---|
354 | if ($compiler === null || $compiler === array('Dwoo_Compiler', 'compilerFactory')) { |
---|
355 | if (class_exists('Dwoo_Compiler', false) === false) { |
---|
356 | include DWOO_DIRECTORY . 'Dwoo/Compiler.php'; |
---|
357 | } |
---|
358 | $compiler = Dwoo_Compiler::compilerFactory(); |
---|
359 | } else { |
---|
360 | $compiler = call_user_func($compiler); |
---|
361 | } |
---|
362 | } |
---|
363 | |
---|
364 | $this->compiler = $compiler; |
---|
365 | |
---|
366 | $compiler->setCustomPlugins($dwoo->getCustomPlugins()); |
---|
367 | $compiler->setSecurityPolicy($dwoo->getSecurityPolicy()); |
---|
368 | $this->makeDirectory(dirname($compiledFile), $dwoo->getCompileDir()); |
---|
369 | file_put_contents($compiledFile, $compiler->compile($dwoo, $this)); |
---|
370 | if ($this->chmod !== null) { |
---|
371 | chmod($compiledFile, $this->chmod); |
---|
372 | } |
---|
373 | |
---|
374 | self::$cache['compiled'][$this->compileId] = true; |
---|
375 | } |
---|
376 | |
---|
377 | return $compiledFile; |
---|
378 | } |
---|
379 | |
---|
380 | /** |
---|
381 | * Checks if compiled file is valid (it exists) |
---|
382 | * |
---|
383 | * @param string file |
---|
384 | * @return boolean True cache file existance |
---|
385 | */ |
---|
386 | protected function isValidCompiledFile($file) { |
---|
387 | return file_exists($file); |
---|
388 | } |
---|
389 | |
---|
390 | /** |
---|
391 | * returns a new template string object with the resource id being the template source code |
---|
392 | * |
---|
393 | * @param Dwoo_Core $dwoo the dwoo instance requiring it |
---|
394 | * @param mixed $resourceId the filename (relative to this template's dir) of the template to include |
---|
395 | * @param int $cacheTime duration of the cache validity for this template, |
---|
396 | * if null it defaults to the Dwoo instance that will |
---|
397 | * render this template |
---|
398 | * @param string $cacheId the unique cache identifier of this page or anything else that |
---|
399 | * makes this template's content unique, if null it defaults |
---|
400 | * to the current url |
---|
401 | * @param string $compileId the unique compiled identifier, which is used to distinguish this |
---|
402 | * template from others, if null it defaults to the filename+bits of the path |
---|
403 | * @param Dwoo_ITemplate $parentTemplate the template that is requesting a new template object (through |
---|
404 | * an include, extends or any other plugin) |
---|
405 | * @return Dwoo_Template_String |
---|
406 | */ |
---|
407 | public static function templateFactory(Dwoo_Core $dwoo, $resourceId, $cacheTime = null, $cacheId = null, $compileId = null, Dwoo_ITemplate $parentTemplate = null) |
---|
408 | { |
---|
409 | return new self($resourceId, $cacheTime, $cacheId, $compileId); |
---|
410 | } |
---|
411 | |
---|
412 | /** |
---|
413 | * returns the full compiled file name and assigns a default value to it if |
---|
414 | * required |
---|
415 | * |
---|
416 | * @param Dwoo_Core $dwoo the dwoo instance that requests the file name |
---|
417 | * @return string the full path to the compiled file |
---|
418 | */ |
---|
419 | protected function getCompiledFilename(Dwoo_Core $dwoo) |
---|
420 | { |
---|
421 | // no compile id was provided, set default |
---|
422 | if ($this->compileId===null) { |
---|
423 | $this->compileId = $this->name; |
---|
424 | } |
---|
425 | return $dwoo->getCompileDir() . $this->compileId.'.d'.Dwoo_Core::RELEASE_TAG.'.php'; |
---|
426 | } |
---|
427 | |
---|
428 | /** |
---|
429 | * returns the full cached file name and assigns a default value to it if |
---|
430 | * required |
---|
431 | * |
---|
432 | * @param Dwoo_Core $dwoo the dwoo instance that requests the file name |
---|
433 | * @return string the full path to the cached file |
---|
434 | */ |
---|
435 | protected function getCacheFilename(Dwoo_Core $dwoo) |
---|
436 | { |
---|
437 | // no cache id provided, use request_uri as default |
---|
438 | if ($this->cacheId === null) { |
---|
439 | if (isset($_SERVER['REQUEST_URI']) === true) { |
---|
440 | $cacheId = $_SERVER['REQUEST_URI']; |
---|
441 | } elseif (isset($_SERVER['SCRIPT_FILENAME']) && isset($_SERVER['argv'])) { |
---|
442 | $cacheId = $_SERVER['SCRIPT_FILENAME'].'-'.implode('-', $_SERVER['argv']); |
---|
443 | } else { |
---|
444 | $cacheId = ''; |
---|
445 | } |
---|
446 | // force compiled id generation |
---|
447 | $this->getCompiledFilename($dwoo); |
---|
448 | |
---|
449 | $this->cacheId = str_replace('../', '__', $this->compileId . strtr($cacheId, '\\%?=!:;'.PATH_SEPARATOR, '/-------')); |
---|
450 | } |
---|
451 | return $dwoo->getCacheDir() . $this->cacheId.'.html'; |
---|
452 | } |
---|
453 | |
---|
454 | /** |
---|
455 | * returns some php code that will check if this template has been modified or not |
---|
456 | * |
---|
457 | * if the function returns null, the template will be instanciated and then the Uid checked |
---|
458 | * |
---|
459 | * @return string |
---|
460 | */ |
---|
461 | public function getIsModifiedCode() |
---|
462 | { |
---|
463 | return null; |
---|
464 | } |
---|
465 | |
---|
466 | /** |
---|
467 | * ensures the given path exists |
---|
468 | * |
---|
469 | * @param string $path any path |
---|
470 | * @param string $baseDir the base directory where the directory is created |
---|
471 | * ($path must still contain the full path, $baseDir |
---|
472 | * is only used for unix permissions) |
---|
473 | */ |
---|
474 | protected function makeDirectory($path, $baseDir = null) |
---|
475 | { |
---|
476 | if (is_dir($path) === true) { |
---|
477 | return; |
---|
478 | } |
---|
479 | |
---|
480 | if ($this->chmod === null) { |
---|
481 | $chmod = 0777; |
---|
482 | } else { |
---|
483 | $chmod = $this->chmod; |
---|
484 | } |
---|
485 | mkdir($path, $chmod, true); |
---|
486 | |
---|
487 | // enforce the correct mode for all directories created |
---|
488 | if (strpos(PHP_OS, 'WIN') !== 0 && $baseDir !== null) { |
---|
489 | $path = strtr(str_replace($baseDir, '', $path), '\\', '/'); |
---|
490 | $folders = explode('/', trim($path, '/')); |
---|
491 | foreach ($folders as $folder) { |
---|
492 | $baseDir .= $folder . DIRECTORY_SEPARATOR; |
---|
493 | chmod($baseDir, $chmod); |
---|
494 | } |
---|
495 | } |
---|
496 | } |
---|
497 | } |
---|