PageRenderTime 22ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/common/includes/class.cachehandler.php

https://code.google.com/
PHP | 473 lines | 294 code | 31 blank | 148 comment | 65 complexity | a6e5c382da08f3c0a230fd52f4ec5d33 MD5 | raw file
Possible License(s): AGPL-1.0, LGPL-2.1
  1. <?php
  2. /**
  3. * @package EDK
  4. */
  5. /**
  6. * Cache handling methods.
  7. *
  8. * Contains methods to handle the killboard's cache directory.
  9. * @package EDK
  10. */
  11. class CacheHandler
  12. {
  13. /** @var string Internal root path. */
  14. protected static $internalroot = KB_CACHEDIR;
  15. /** @var string External root path. */
  16. protected static $externalroot = KB_CACHEDIR;
  17. /** @var array Cache of generated paths to avoid recalculation. */
  18. protected static $paths = array();
  19. /** @var string Default location in cache to store files. */
  20. protected static $defaultLocation = "store";
  21. /** @var integer Depth of subdirectories. */
  22. protected static $depth = 2;
  23. /**
  24. * Add a file to the cache.
  25. *
  26. * @param string $filename String filename, starting from below cache dir.
  27. * @param string $data String containing the data to store in the cache.
  28. * @param string $location set a specific subdirectory of the cache to use.
  29. *
  30. * @return boolean true if successful, false if an error occurred.
  31. */
  32. public static function put($filename, $data, $location = null)
  33. {
  34. return file_put_contents(self::$internalroot."/"
  35. .self::getPath($filename, $location, true), $data);
  36. }
  37. /**
  38. * Get a file from the cache.
  39. *
  40. * @param string $filename Filename, starting from below cache dir.
  41. * @param string $location Set a specific subdirectory of the cache to use.
  42. *
  43. * @return boolean true if successful, false if an error occurred.
  44. */
  45. public static function get($filename, $location = null)
  46. {
  47. return @file_get_contents(self::$internalroot."/"
  48. .self::getPath($filename, $location, false));
  49. }
  50. /**
  51. * Remove a cached file
  52. *
  53. * @param string $filename Name of cached file.
  54. * @param string $location Optional location. Sets a specific subdirectory
  55. * of the cache to use.
  56. * @return boolean true on success, false on failure.
  57. */
  58. public static function remove($filename, $location = null)
  59. {
  60. if (!self::exists($filename, $location)) {
  61. return true;
  62. }
  63. $dir = self::getPath($filename, $location, false);
  64. $path = self::$internalroot.'/'.$dir;
  65. if (!unlink($path)) {
  66. return false;
  67. }
  68. // Remove the cache directory holding the file if it is empty.
  69. self::removeDir($dir);
  70. return true;
  71. }
  72. /**
  73. * Remove a cache directory if empty.
  74. *
  75. * @param string $dir String The directory to remove.
  76. * @param boolean $parents Remove empty parent directory if true.
  77. * @return boolean true on success, false on failure.
  78. */
  79. protected static function removeDir($dir, $parents = true)
  80. {
  81. if (substr($dir, -1) != '/') {
  82. $dir .= '/';
  83. }
  84. $dirfiles = @scandir(self::$internalroot.'/'.$dir);
  85. if ($dirfiles === false) {
  86. return false;
  87. }
  88. if (count($dirfiles) > 2) {
  89. // Remove empty subdirectories
  90. foreach ($dirfiles as $fname) {
  91. if (substr($fname, 0, 1) != "."
  92. && is_dir(self::$internalroot.'/'.$dir.$fname)) {
  93. self::removeDir($dir.$fname."/", false);
  94. }
  95. }
  96. // Is the directory empty now?
  97. $dirfiles = scandir(self::$internalroot.'/'.$dir);
  98. if (count($dirfiles) > 2) {
  99. return true;
  100. }
  101. }
  102. $dir = substr($dir, 0, -1);
  103. $pdir = substr($dir, 0, strrpos($dir, '/'));
  104. rmdir(self::$internalroot.'/'.$dir);
  105. if (!$parents) {
  106. return true;
  107. }
  108. if (empty($pdir)) {
  109. return true;
  110. } else {
  111. return self::removeDir($pdir);
  112. }
  113. }
  114. /**
  115. * Remove all files in a cache directory older than the given time.
  116. *
  117. * @param string $dir The directory to remove files from.
  118. * @param integer $hours The minimum age in hours of files to remove.
  119. * @param boolean $removeDir Set true to remove empty directories.
  120. *
  121. * @return integer|false The count of files removed or false on error.
  122. */
  123. public static function removeByAge($dir = null, $hours = 24, $removeDir = true)
  124. {
  125. if (is_null($dir)) {
  126. $dir = self::$defaultLocation;
  127. }
  128. if (!is_dir(self::$internalroot.'/'.$dir)) {
  129. return 0;
  130. }
  131. if (substr($dir, -1) != '/') {
  132. $dir .= '/';
  133. }
  134. $seconds = (int) $hours * 60 * 60;
  135. $del = 0;
  136. $files = @scandir(self::$internalroot.'/'.$dir);
  137. if (!$files) {
  138. return false;
  139. }
  140. foreach ($files as $num => $fname) {
  141. if (substr($fname, 0, 1) != ".") {
  142. if (is_dir(self::$internalroot.'/'.$dir.$fname)) {
  143. $del += self::removeByAge(
  144. $dir.$fname."/", $hours, $removeDir);
  145. } else if ((time() - filemtime(self::$internalroot.'/'
  146. .$dir.$fname)) > $seconds) {
  147. $del += (int)@unlink(self::$internalroot.'/'.$dir.$fname);
  148. }
  149. }
  150. }
  151. // Directories with files in are not deleted.
  152. if ($removeDir && substr_count($dir, '/') > 1) {
  153. @rmdir(self::$internalroot.'/'.$dir);
  154. }
  155. return $del;
  156. }
  157. /**
  158. * Remove files in a cache directory to reduce total size to that given.
  159. *
  160. * @param string $dir The directory to remove files from.
  161. * @param integer $maxsize The maximum size in Megabytes for the cache.
  162. * @param boolean $bysize Whether to remove largest files first or oldest.
  163. *
  164. * @return integer|false The count of files removed or false on error.
  165. */
  166. public static function removeBySize($dir, $maxsize = 0, $bysize = false)
  167. {
  168. if (!is_numeric($maxsize)) {
  169. if (substr($maxsize, -1) == 'k') {
  170. $maxsize = substr($maxsize, 0, strlen($maxsize) - 1);
  171. $maxsize = $maxsize * pow(2, 10);
  172. } else if (substr($maxsize, -1) == 'M') {
  173. $maxsize = substr($maxsize, 0, strlen($maxsize) - 1);
  174. $maxsize = $maxsize * pow(2, 20);
  175. } else if (substr($maxsize, -1) == 'G') {
  176. $maxsize = substr($maxsize, 0, strlen($maxsize) - 1);
  177. $maxsize = $maxsize * pow(2, 30);
  178. } else {
  179. return false;
  180. }
  181. } else {
  182. $maxsize = $maxsize * pow(2, 20);
  183. }
  184. $files = self::getFiles($dir);
  185. if ($bysize) {
  186. usort($files, array('CacheHandler', 'compareSize'));
  187. } else {
  188. usort($files, array('CacheHandler', 'compareAge'));
  189. }
  190. $cursize = 0;
  191. $delcount = 0;
  192. foreach ($files as $file) {
  193. $cursize += $file[2];
  194. }
  195. foreach ($files as $key => $file) {
  196. if ($cursize < $maxsize) {
  197. break;
  198. }
  199. if (unlink($file[1])) {
  200. $cursize -= $file[2];
  201. unset($files[$key]);
  202. $delcount++;
  203. }
  204. }
  205. self::removeDir($dir);
  206. return $delcount;
  207. }
  208. /**
  209. * Remove files in a cache directory to reduce total file count to that given.
  210. * Oldest files are removed first.
  211. *
  212. * @param string $dir The directory to remove files from.
  213. * @param integer $maxsize The maximum count of files in the cache.
  214. *
  215. * @return integer|false The count of files removed or false on error.
  216. */
  217. public static function removeByCount($dir, $maxsize = 0)
  218. {
  219. $maxsize = (int) $maxsize;
  220. $files = self::getFiles($dir);
  221. usort($files, array('CacheHandler', 'compareAge'));
  222. $cursize = 0;
  223. $delcount = 0;
  224. $cursize = count($files);
  225. foreach ($files as $key => $file) {
  226. if ($cursize < $maxsize) {
  227. break;
  228. }
  229. if (unlink($file[1])) {
  230. $cursize--;
  231. unset($files[$key]);
  232. $delcount++;
  233. }
  234. }
  235. self::removeDir($dir);
  236. return $delcount;
  237. }
  238. /**
  239. * Return an array of all files under the given dir.
  240. *
  241. * @param string $dir Root directory to search in.
  242. *
  243. * @return array (age, name, size)
  244. */
  245. private static function &getFiles($dir)
  246. {
  247. if (strpos($dir, '..')
  248. || !is_dir(self::$internalroot.'/'.$dir)) {
  249. return array();
  250. }
  251. if (substr($dir, -1) != '/') {
  252. $dir .= '/';
  253. }
  254. $del = 0;
  255. $files = scandir(self::$internalroot.'/'.$dir);
  256. if (!$files) return false;
  257. $result = array();
  258. foreach ($files as $num => $fname) {
  259. if (substr($fname, 0, 1) != ".") {
  260. if (!is_dir(self::$internalroot.'/'.$dir.$fname)) {
  261. if (is_writeable(self::$internalroot.'/'.$dir.$fname))
  262. $result[] = array(filemtime(self::$internalroot.'/'.$dir.$fname), self::$internalroot.'/'.$dir.$fname, filesize(self::$internalroot.'/'.$dir.$fname));
  263. } else {
  264. $subResult = self::getFiles($dir.$fname."/");
  265. $result = array_merge($result, $subResult);
  266. }
  267. }
  268. }
  269. return $result;
  270. }
  271. /**
  272. * Age comparison function for use with file array returned by getFiles.
  273. *
  274. * @param array $a (age, name, size)
  275. * @param array $b (age, name, size)
  276. * @return integer -1 if a is older, 0 for same age, +1 if b is older.
  277. */
  278. private static function compareAge($a, $b)
  279. {
  280. if ($a[0] == $b[0]) {
  281. return 0;
  282. }
  283. return ($a[0] < $b[0]) ? -1 : 1;
  284. }
  285. /**
  286. * Size comparison function for use with file array returned by getFiles.
  287. *
  288. * @param array $a (age, name, size)
  289. * @param array $b (age, name, size)
  290. * @return integer -1 if a is bigger, 0 for same age, +1 if b is bigger.
  291. */
  292. private static function compareSize($a, $b)
  293. {
  294. if ($a[2] == $b[2]) {
  295. return 0;
  296. }
  297. return ($a[2] > $b[2]) ? -1 : 1;
  298. }
  299. /**
  300. * Add subdirectories to the given depth and return the full cachefile path.
  301. * @param string $filename Name of cached file.
  302. * @param string $location Optional location. Sets a specific subdirectory
  303. * of the cache to use.
  304. * @param boolean $create Set true to create the path if it does not exist.
  305. * @return string The full cachefile path.
  306. */
  307. protected static function getPath($filename, $location = null, $create = false)
  308. {
  309. if (is_null($location)) {
  310. $location = self::$defaultLocation;
  311. }
  312. if (isset(self::$paths[$location.$filename])) {
  313. return self::$paths[$location.$filename];
  314. }
  315. $newfilename = str_replace(array("?", "[", "]", "=", "+", "<", ">",
  316. "|", ":", ";", "'", "*", ",", "/", "\\"), "", $filename);
  317. if ($newfilename == ".." || $newfilename == ".") {
  318. trigger_error("Invalid cache filename. Name given was: ".htmlentities($newfilename), E_USER_ERROR);
  319. die;
  320. }
  321. $newlocation = $location;
  322. if ($newlocation != self::$defaultLocation) {
  323. if (strpos($newlocation, "..") !== false
  324. || substr($newlocation, 0, 1) == '/'
  325. || substr($newlocation, 0, 1) == '\\') {
  326. trigger_error("Invalid cache path. Path given was: ".htmlentities($newlocation), E_USER_ERROR);
  327. die;
  328. } else {
  329. $newlocation = str_replace("\\", "/", $newlocation);
  330. $newlocation = str_replace(array("?", "[", "]", "=", "+", "<", ">", "|", ":", ";", "'", "*", ","), "", $newlocation);
  331. if ($newlocation == "") $newlocation = self::$defaultLocation;
  332. }
  333. }
  334. // Create subdirectories and left pad with 0s if length is too short.
  335. $depth = self::$depth;
  336. if (substr($newlocation, -1) != '/') {
  337. $newlocation .= '/';
  338. }
  339. if (strrpos($filename, ".") === false) {
  340. $length = 8; //2 * $depth + 2
  341. } else {
  342. $length = 8 + strlen($filename) - strrpos($filename, ".");
  343. }
  344. $newfilename = str_pad($newfilename, $length, "0", STR_PAD_LEFT);
  345. $nameslice = $newfilename;
  346. while ($depth > 0) {
  347. $newlocation .= substr($nameslice, 0, 2).'/';
  348. $nameslice = substr($nameslice, 2);
  349. $depth--;
  350. }
  351. if ($create) {
  352. if (!file_exists(self::$internalroot.'/'.$newlocation)) {
  353. // Race conditions can cause errors here but we don't care so ignore them.
  354. @mkdir(self::$internalroot.'/'.$newlocation, 0755, true);
  355. }
  356. self::$paths[$location.$filename] = $newlocation.$newfilename;
  357. }
  358. return $newlocation.$newfilename;
  359. }
  360. /**
  361. * Return true if the file is in the cache.
  362. * @param string $filename Name of cached file.
  363. * @param string $location Optional location. Sets a specific subdirectory
  364. * of the cache to use.
  365. * @return boolean true if the file exists, false otherwise.
  366. */
  367. public static function exists($filename, $location = null)
  368. {
  369. return file_exists(self::$internalroot.'/'
  370. .self::getPath($filename, $location, false));
  371. }
  372. /**
  373. * Return the age of the given cache file.
  374. * @param string $filename Name of cached file.
  375. * @param string $location Optional location. Sets a specific subdirectory
  376. * of the cache to use.
  377. * @return integer|boolean the age of the file in seconds or false on error.
  378. */
  379. public static function age($filename, $location = null)
  380. {
  381. $mtime = @filemtime(self::getPath($filename, $location, false));
  382. return $mtime ? time() - $mtime : false;
  383. }
  384. /**
  385. * Get the internally accessible address of the cached file.
  386. * @param string $filename Name of cached file.
  387. * @param string $location Optional location. Sets a specific subdirectory
  388. * of the cache to use.
  389. * @return string The path to the given file.
  390. */
  391. public static function getInternal($filename, $location = null)
  392. {
  393. return self::$internalroot.'/'
  394. .self::getPath($filename, $location, true);
  395. }
  396. /**
  397. * Get the externally accessible address of the cached file.
  398. * @param string $filename Name of cached file.
  399. * @param string $location Optional location. Sets a specific subdirectory
  400. * of the cache to use.
  401. * @return string The external path to the given file.
  402. */
  403. public static function getExternal($filename, $location = null)
  404. {
  405. return self::$externalroot.'/'
  406. .self::getPath($filename, $location, false);
  407. }
  408. /**
  409. * Change the default cache directory used to make external links.
  410. *
  411. * Note that the safety of the path returned is up to the caller to verify.
  412. *
  413. * @param string $dir The new root path
  414. * @return string The new root path as set.
  415. */
  416. public static function setExternalPath($dir)
  417. {
  418. self::$externalroot = $dir;
  419. return self::$externalroot;
  420. }
  421. /**
  422. * Change the default cache root directory
  423. *
  424. * @param string $dir valid path to a suitable cache directory.
  425. * @return boolean true if the new path was valid, false if not.
  426. */
  427. public static function setInternalPath($dir)
  428. {
  429. if (substr($dir, 0, 1) == '/'
  430. || strpos($dir, '..') !== false
  431. || !is_dir($dir)
  432. || !is_writeable($dir)) {
  433. return false;
  434. } else {
  435. self::$internalroot = $dir;
  436. return true;
  437. }
  438. }
  439. }