1<?php 2/* 3 * e107 website system 4 * 5 * Copyright (C) 2008-2010 e107 Inc (e107.org) 6 * Released under the terms and conditions of the 7 * GNU General Public License (http://www.gnu.org/licenses/gpl.txt) 8 * 9 * Cache handler 10 * 11 * $URL$ 12 * $Id$ 13*/ 14 15if (!defined('e107_INIT')) { exit; } 16 17define('CACHE_PREFIX','<?php exit;'); 18 19/** 20 * Class to cache data as files, improving site speed and throughput. 21 * FIXME - pref independant cache handler, cache drivers 22 * 23 * @package e107 24 * @subpackage e107_handlers 25 * @version $Id$ 26 * @author e107 Inc 27 */ 28class ecache { 29 30 public $CachePageMD5; 31 public $CachenqMD5; 32 public $UserCacheActive; // Checkable flag - TRUE if user cache enabled 33 public $SystemCacheActive; // Checkable flag - TRUE if system cache enabled 34 35 const CACHE_PREFIX = '<?php exit;'; 36 37 function __construct() 38 { 39 //$this->UserCacheActive = e107::getPref('cachestatus'); 40 //$this->SystemCacheActive = e107::getPref('syscachestatus'); 41 } 42 43 /** 44 * Set the MD5 Hash 45 */ 46 public function setMD5($text, $hash=true) 47 { 48 if($text === null) 49 { 50 $this->CachePageMD5 = md5(e_BASE.e_LANGUAGE.THEME.USERCLASS_LIST.defset('e_QUERY').filemtime(THEME.'theme.php')); 51 return $this; 52 } 53 54 $this->CachePageMD5 = ($hash === true) ? md5($text) : $text; 55 return $this; 56 } 57 58 59 public function getMD5() 60 { 61 return $this->CachePageMD5; 62 } 63 64 /** 65 * @return string 66 * @param string $query 67 * @desc Internal class function that returns the filename of a cache file based on the query. 68 * @scope private 69 * If the tag begins 'menu_', e_QUERY is not included in the hash which creates the file name 70 */ 71 function cache_fname($CacheTag, $syscache = false) 72 { 73 if(strpos($CacheTag, "nomd5_") === 0) { 74 // Add 'nomd5' to indicate we are not calculating an md5 75 $CheckTag = '_nomd5'; 76 } 77 elseif (isset($this) && $this instanceof ecache) 78 { 79 if (defined("THEME")) 80 { 81 if (strpos($CacheTag, "nq_") === 0) 82 { 83 // We do not care about e_QUERY, so don't use it in the md5 calculation 84 if (!$this->CachenqMD5) 85 { 86 $this->CachenqMD5 = md5(e_BASE.(defined("ADMIN") && ADMIN == true ? "admin" : "").e_LANGUAGE.THEME.USERCLASS_LIST.filemtime(THEME.'theme.php')); 87 } 88 // Add 'nq' to indicate we are not using e_QUERY 89 $CheckTag = '_nq_'.$this->CachenqMD5; 90 91 } 92 else 93 { 94 // It's a page - need the query in the hash 95 if (!$this->CachePageMD5) 96 { 97 $this->CachePageMD5 = md5(e_BASE.e_LANGUAGE.THEME.USERCLASS_LIST.defset('e_QUERY').filemtime(THEME.'theme.php')); 98 } 99 $CheckTag = '_'.$this->CachePageMD5; 100 } 101 } 102 else 103 { 104 // Check if a custom CachePageMD5 is in use in e_module.php. 105 $CheckTag = ($this->CachePageMD5) ? "_".$this->CachePageMD5 : ""; 106 } 107 } 108 else 109 { 110 $CheckTag = ''; 111 } 112 $q = ($syscache ? "S_" : "C_").preg_replace("#\W#", "_", $CacheTag); 113 114 if($syscache === true) 115 { 116 $CheckTag = ''; // no MD5 on system cache. XXX To be Checked. 117 } 118 119 $fname = e_CACHE_CONTENT.$q.$CheckTag.'.cache.php'; 120 //echo "cache f_name = $fname <br />"; 121 return $fname; 122 } 123 124 /** 125 * Retrieve Cache data. 126 * @param $CacheTag 127 * @param bool|int $MaximumAge the time in minutes before the cache file 'expires' 128 * @param bool $ForcedCheck check even if cache pref is disabled. 129 * @param bool $syscache set to true when checking sys cache. 130 * @return string 131 * @desc Returns the data from the cache file associated with $query, else it returns false if there is no cache for $query. 132 * @scope public 133 */ 134 public function retrieve($CacheTag, $MaximumAge = false, $ForcedCheck = false, $syscache = false) 135 { 136 if(($ForcedCheck != false ) || ($syscache == false && $this->UserCacheActive) || ($syscache == true && $this->SystemCacheActive) && !e107::getParser()->checkHighlighting()) 137 { 138 $cache_file = (isset($this) && $this instanceof ecache ? $this->cache_fname($CacheTag, $syscache) : self::cache_fname($CacheTag, $syscache)); 139 140 if(file_exists($cache_file)) 141 { 142 if ($MaximumAge !== false && (filemtime($cache_file) + ($MaximumAge * 60)) < time()) { 143 unlink($cache_file); 144 return false; 145 } 146 else 147 { 148 $ret = file_get_contents($cache_file); 149 if (substr($ret,0,strlen(self::CACHE_PREFIX)) == self::CACHE_PREFIX) 150 { 151 $ret = substr($ret, strlen(self::CACHE_PREFIX)); 152 } 153 elseif(substr($ret,0,5) == '<?php') 154 { 155 $ret = substr($ret, 5); // Handle the history for now 156 } 157 return $ret; 158 } 159 } 160 else 161 { 162 // e107::getDebug()->log("Couldn't find cache file: ".json_encode($cache_file)); 163 return false; 164 } 165 } 166 return false; 167 } 168 169 /** 170 * @return string 171 * @param string $CacheTag 172 * @param int $MaximumAge the time in minutes before the cache file 'expires' 173 * @param boolean $force 174 * @desc Returns the data from the cache file associated with $query, else it returns false if there is no cache for $query. 175 * @scope public 176 */ 177 function retrieve_sys($CacheTag, $MaximumAge = false, $ForcedCheck = false) 178 { 179 if(isset($this) && $this instanceof ecache) 180 { 181 return $this->retrieve($CacheTag, $MaximumAge, $ForcedCheck, true); 182 } 183 else 184 { 185 return self::retrieve($CacheTag, $MaximumAge, $ForcedCheck, true); 186 } 187 } 188 189 190 /** 191 * 192 * @param string $CacheTag - name of tag for future retrieval - should NOT contain an MD5. 193 * @param string $Data - data to be cached 194 * @param boolean $ForceCache [optional] if TRUE, writes cache even when disabled in admin prefs. 195 * @param boolean $bRaw [optional] if TRUE, writes data exactly as provided instead of prefacing with php leadin 196 * @param boolean $syscache [optional] 197 * @return none 198 */ 199 public function set($CacheTag, $Data, $ForceCache = false, $bRaw=0, $syscache = false) 200 { 201 if(defined('E107_INSTALL') && E107_INSTALL === true) 202 { 203 return null; 204 } 205 206 if(($ForceCache != false ) || ($syscache == false && $this->UserCacheActive) || ($syscache == true && $this->SystemCacheActive) && !e107::getParser()->checkHighlighting()) 207 { 208 $cache_file = (isset($this) && $this instanceof ecache ? $this->cache_fname($CacheTag, $syscache) : self::cache_fname($CacheTag, $syscache)); 209 @file_put_contents($cache_file, ($bRaw? $Data : self::CACHE_PREFIX.$Data) ); 210 @chmod($cache_file, 0755); //Cache should not be world-writeable 211 @touch($cache_file); 212 } 213 } 214 215 /** 216 * @return void 217 * @param string $CacheTag - name of tag for future retrieval - should NOT contain an MD5 218 * @param string $Data - data to be cached 219 * @param bool $ForceCache (optional, default false) - if TRUE, writes cache even when disabled 220 * @param bool $bRaw (optional, default false) - if TRUE, writes data exactly as provided instead of prefacing with php leadin 221 * @desc Creates / overwrites the cache file for $query, $text is the data to store for $query. 222 * @scope public 223 */ 224 function set_sys($CacheTag, $Data, $ForceCache = false, $bRaw=0) 225 { 226 if(isset($this) && $this instanceof ecache) 227 { 228 return $this->set($CacheTag, $Data, $ForceCache, $bRaw, true); 229 } 230 else 231 { 232 self::set($CacheTag, $Data, $ForceCache, $bRaw, true); 233 } 234 } 235 236 237 /** 238 * Deletes cache files. If $query is set, deletes files named {$CacheTag}*.cache.php, if not it deletes all cache files - (*.cache.php) 239 * 240 * @param string $CacheTag 241 * @param boolean $syscache 242 * @param boolean $related clear also 'nq_' and 'nomd5_' entries 243 * @return bool 244 * 245 */ 246 public function clear($CacheTag = '', $syscache = false, $related = false) 247 { 248 249 $file = ($CacheTag) ? preg_replace("#\W#", "_", $CacheTag)."*.cache.php" : "*.cache.php"; 250 e107::getEvent()->triggerAdminEvent('cache_clear', "cachetag=$CacheTag&file=$file&syscache=$syscache"); 251 $ret = self::delete(e_CACHE_CONTENT, $file, $syscache); 252 253 if($CacheTag && $related) //TODO - too dirty - add it to the $file pattern above 254 { 255 self::delete(e_CACHE_CONTENT, 'nq_'.$file, $syscache); 256 self::delete(e_CACHE_CONTENT, 'nomd5_'.$file, $syscache); 257 //ecache::delete(e_CACHE_CONTENT, 'nq_'.$file, $syscache); 258 //ecache::delete(e_CACHE_CONTENT, 'nomd5_'.$file, $syscache); 259 } 260 return $ret; 261 } 262 263 /** 264 * @return bool 265 * @param string $CacheTag 266 * @desc Deletes cache files. If $query is set, deletes files named {$CacheTag}*.cache.php, if not it deletes all cache files - (*.cache.php) 267 */ 268 function clear_sys($CacheTag = '', $related = false) 269 { 270 271 272 if(isset($this) && $this instanceof ecache) 273 { 274 return $this->clear($CacheTag, true, $related); 275 } 276 else 277 { 278 self::clear($CacheTag, true, $related); 279 // ecache::clear($CacheTag, true, $related); 280 } 281 } 282 283 /** 284 * @return bool 285 * @param string $dir 286 * @param string $pattern 287 * @desc Internal class function to allow deletion of cache files using a pattern, default '*.*' 288 * @scope private 289 */ 290 function delete($dir, $pattern = "*.*", $syscache = false) { 291 $deleted = false; 292 $pattern = ($syscache ? "S_" : "C_").$pattern; 293 $pattern = str_replace(array("\*", "\?"), array(".*", "."), preg_quote($pattern)); 294 if (substr($dir, -1) != "/") { 295 $dir .= "/"; 296 } 297 if (is_dir($dir)) 298 { 299 $d = opendir($dir); 300 while ($file = readdir($d)) { 301 if (is_file($dir.$file) && preg_match("/^{$pattern}$/", $file)) { 302 if (unlink($dir.$file)) { 303 $deleted[] = $file; 304 } 305 } 306 } 307 closedir($d); 308 return true; 309 } else { 310 return false; 311 } 312 } 313 314 315 /** 316 * Clear Full Cache 317 * @param string $type: content | system| browser | db | image | js | css | library 318 * @example clearAll('db'); 319 */ 320 321 function clearAll($type,$mask = null) 322 { 323 $path = null; 324 325 if($type =='content') 326 { 327 $this->clear(); 328 return; 329 } 330 331 if($type === 'system') 332 { 333 $this->clear_sys(); 334 return; 335 } 336 337 if($type === 'browser') 338 { 339 e107::getConfig()->set('e_jslib_browser_cache', time())->save(false); 340 return; 341 } 342 343 if($type === 'db') 344 { 345 $path = e_CACHE_DB; 346 $mask = ($mask == null) ? '.*\.php' : $mask; 347 } 348 349 if($type === 'image') 350 { 351 $path = e_CACHE_IMAGE; 352 $mask = ($mask == null) ? '.*(\.cache\.bin|\.jpg|\.jpeg|\.png|\.gif)' : $mask; 353 } 354 355 if($type === 'js') 356 { 357 $path = e_WEB."cache/"; 358 $mask = ($mask == null) ? '.*\.js' : $mask; 359 } 360 361 if($type === 'css') 362 { 363 $path = e_WEB."cache/"; 364 $mask = ($mask == null) ? '.*\.css' : $mask; 365 } 366 367 if($type === 'library') 368 { 369 $path = e_CACHE_CONTENT; 370 $mask = ($mask == null) ? 'S_Library_.*\.cache\.php' : $mask; 371 } 372 373 if((null == $path) || (null == $mask)) 374 { 375 return; 376 } 377 378 $fl = e107::getFile(false); 379 $fl->mode = 'fname'; 380 $files = $fl->get_files($path, $mask); 381 382 if($files) 383 { 384 foreach ($files as $file) 385 { 386 unlink($path.$file); 387 } 388 } 389 } 390 391} 392