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 */ 17class 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 = 0755; 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} 498