PageRenderTime 28ms CodeModel.GetById 4ms RepoModel.GetById 0ms app.codeStats 0ms

/phpBB/includes/cache/driver/file.php

http://github.com/phpbb/phpbb3
PHP | 729 lines | 463 code | 125 blank | 141 comment | 83 complexity | 89be406ad831791146e87b0411ffc4fd MD5 | raw file
Possible License(s): AGPL-1.0
  1. <?php
  2. /**
  3. *
  4. * @package acm
  5. * @copyright (c) 2005, 2009 phpBB Group
  6. * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License v2
  7. *
  8. */
  9. /**
  10. * @ignore
  11. */
  12. if (!defined('IN_PHPBB'))
  13. {
  14. exit;
  15. }
  16. /**
  17. * ACM File Based Caching
  18. * @package acm
  19. */
  20. class phpbb_cache_driver_file extends phpbb_cache_driver_base
  21. {
  22. var $vars = array();
  23. var $var_expires = array();
  24. var $is_modified = false;
  25. var $sql_rowset = array();
  26. var $sql_row_pointer = array();
  27. var $cache_dir = '';
  28. /**
  29. * Set cache path
  30. */
  31. function __construct($cache_dir = null)
  32. {
  33. global $phpbb_root_path;
  34. $this->cache_dir = !is_null($cache_dir) ? $cache_dir : $phpbb_root_path . 'cache/';
  35. }
  36. /**
  37. * Load global cache
  38. */
  39. function load()
  40. {
  41. return $this->_read('data_global');
  42. }
  43. /**
  44. * Unload cache object
  45. */
  46. function unload()
  47. {
  48. $this->save();
  49. unset($this->vars);
  50. unset($this->var_expires);
  51. unset($this->sql_rowset);
  52. unset($this->sql_row_pointer);
  53. $this->vars = array();
  54. $this->var_expires = array();
  55. $this->sql_rowset = array();
  56. $this->sql_row_pointer = array();
  57. }
  58. /**
  59. * Save modified objects
  60. */
  61. function save()
  62. {
  63. if (!$this->is_modified)
  64. {
  65. return;
  66. }
  67. global $phpEx;
  68. if (!$this->_write('data_global'))
  69. {
  70. if (!function_exists('phpbb_is_writable'))
  71. {
  72. global $phpbb_root_path;
  73. include($phpbb_root_path . 'includes/functions.' . $phpEx);
  74. }
  75. // Now, this occurred how often? ... phew, just tell the user then...
  76. if (!phpbb_is_writable($this->cache_dir))
  77. {
  78. // We need to use die() here, because else we may encounter an infinite loop (the message handler calls $cache->unload())
  79. die('Fatal: ' . $this->cache_dir . ' is NOT writable.');
  80. exit;
  81. }
  82. die('Fatal: Not able to open ' . $this->cache_dir . 'data_global.' . $phpEx);
  83. exit;
  84. }
  85. $this->is_modified = false;
  86. }
  87. /**
  88. * Tidy cache
  89. */
  90. function tidy()
  91. {
  92. global $phpEx;
  93. $dir = @opendir($this->cache_dir);
  94. if (!$dir)
  95. {
  96. return;
  97. }
  98. $time = time();
  99. while (($entry = readdir($dir)) !== false)
  100. {
  101. if (!preg_match('/^(sql_|data_(?!global))/', $entry))
  102. {
  103. continue;
  104. }
  105. if (!($handle = @fopen($this->cache_dir . $entry, 'rb')))
  106. {
  107. continue;
  108. }
  109. // Skip the PHP header
  110. fgets($handle);
  111. // Skip expiration
  112. $expires = (int) fgets($handle);
  113. fclose($handle);
  114. if ($time >= $expires)
  115. {
  116. $this->remove_file($this->cache_dir . $entry);
  117. }
  118. }
  119. closedir($dir);
  120. if (file_exists($this->cache_dir . 'data_global.' . $phpEx))
  121. {
  122. if (!sizeof($this->vars))
  123. {
  124. $this->load();
  125. }
  126. foreach ($this->var_expires as $var_name => $expires)
  127. {
  128. if ($time >= $expires)
  129. {
  130. $this->destroy($var_name);
  131. }
  132. }
  133. }
  134. set_config('cache_last_gc', time(), true);
  135. }
  136. /**
  137. * Get saved cache object
  138. */
  139. function get($var_name)
  140. {
  141. if ($var_name[0] == '_')
  142. {
  143. global $phpEx;
  144. if (!$this->_exists($var_name))
  145. {
  146. return false;
  147. }
  148. return $this->_read('data' . $var_name);
  149. }
  150. else
  151. {
  152. return ($this->_exists($var_name)) ? $this->vars[$var_name] : false;
  153. }
  154. }
  155. /**
  156. * Put data into cache
  157. */
  158. function put($var_name, $var, $ttl = 31536000)
  159. {
  160. if ($var_name[0] == '_')
  161. {
  162. $this->_write('data' . $var_name, $var, time() + $ttl);
  163. }
  164. else
  165. {
  166. $this->vars[$var_name] = $var;
  167. $this->var_expires[$var_name] = time() + $ttl;
  168. $this->is_modified = true;
  169. }
  170. }
  171. /**
  172. * Purge cache data
  173. */
  174. function purge()
  175. {
  176. // Purge all phpbb cache files
  177. $dir = @opendir($this->cache_dir);
  178. if (!$dir)
  179. {
  180. return;
  181. }
  182. while (($entry = readdir($dir)) !== false)
  183. {
  184. if (strpos($entry, 'sql_') !== 0 && strpos($entry, 'data_') !== 0 && strpos($entry, 'ctpl_') !== 0 && strpos($entry, 'tpl_') !== 0)
  185. {
  186. continue;
  187. }
  188. $this->remove_file($this->cache_dir . $entry);
  189. }
  190. closedir($dir);
  191. unset($this->vars);
  192. unset($this->var_expires);
  193. unset($this->sql_rowset);
  194. unset($this->sql_row_pointer);
  195. $this->vars = array();
  196. $this->var_expires = array();
  197. $this->sql_rowset = array();
  198. $this->sql_row_pointer = array();
  199. $this->is_modified = false;
  200. }
  201. /**
  202. * Destroy cache data
  203. */
  204. function destroy($var_name, $table = '')
  205. {
  206. global $phpEx;
  207. if ($var_name == 'sql' && !empty($table))
  208. {
  209. if (!is_array($table))
  210. {
  211. $table = array($table);
  212. }
  213. $dir = @opendir($this->cache_dir);
  214. if (!$dir)
  215. {
  216. return;
  217. }
  218. while (($entry = readdir($dir)) !== false)
  219. {
  220. if (strpos($entry, 'sql_') !== 0)
  221. {
  222. continue;
  223. }
  224. if (!($handle = @fopen($this->cache_dir . $entry, 'rb')))
  225. {
  226. continue;
  227. }
  228. // Skip the PHP header
  229. fgets($handle);
  230. // Skip expiration
  231. fgets($handle);
  232. // Grab the query, remove the LF
  233. $query = substr(fgets($handle), 0, -1);
  234. fclose($handle);
  235. foreach ($table as $check_table)
  236. {
  237. // Better catch partial table names than no table names. ;)
  238. if (strpos($query, $check_table) !== false)
  239. {
  240. $this->remove_file($this->cache_dir . $entry);
  241. break;
  242. }
  243. }
  244. }
  245. closedir($dir);
  246. return;
  247. }
  248. if (!$this->_exists($var_name))
  249. {
  250. return;
  251. }
  252. if ($var_name[0] == '_')
  253. {
  254. $this->remove_file($this->cache_dir . 'data' . $var_name . ".$phpEx", true);
  255. }
  256. else if (isset($this->vars[$var_name]))
  257. {
  258. $this->is_modified = true;
  259. unset($this->vars[$var_name]);
  260. unset($this->var_expires[$var_name]);
  261. // We save here to let the following cache hits succeed
  262. $this->save();
  263. }
  264. }
  265. /**
  266. * Check if a given cache entry exist
  267. */
  268. function _exists($var_name)
  269. {
  270. if ($var_name[0] == '_')
  271. {
  272. global $phpEx;
  273. return file_exists($this->cache_dir . 'data' . $var_name . ".$phpEx");
  274. }
  275. else
  276. {
  277. if (!sizeof($this->vars))
  278. {
  279. $this->load();
  280. }
  281. if (!isset($this->var_expires[$var_name]))
  282. {
  283. return false;
  284. }
  285. return (time() > $this->var_expires[$var_name]) ? false : isset($this->vars[$var_name]);
  286. }
  287. }
  288. /**
  289. * Load cached sql query
  290. */
  291. function sql_load($query)
  292. {
  293. // Remove extra spaces and tabs
  294. $query = preg_replace('/[\n\r\s\t]+/', ' ', $query);
  295. if (($rowset = $this->_read('sql_' . md5($query))) === false)
  296. {
  297. return false;
  298. }
  299. $query_id = sizeof($this->sql_rowset);
  300. $this->sql_rowset[$query_id] = $rowset;
  301. $this->sql_row_pointer[$query_id] = 0;
  302. return $query_id;
  303. }
  304. /**
  305. * Save sql query
  306. */
  307. function sql_save($query, &$query_result, $ttl)
  308. {
  309. global $db;
  310. // Remove extra spaces and tabs
  311. $query = preg_replace('/[\n\r\s\t]+/', ' ', $query);
  312. $query_id = sizeof($this->sql_rowset);
  313. $this->sql_rowset[$query_id] = array();
  314. $this->sql_row_pointer[$query_id] = 0;
  315. while ($row = $db->sql_fetchrow($query_result))
  316. {
  317. $this->sql_rowset[$query_id][] = $row;
  318. }
  319. $db->sql_freeresult($query_result);
  320. if ($this->_write('sql_' . md5($query), $this->sql_rowset[$query_id], $ttl + time(), $query))
  321. {
  322. $query_result = $query_id;
  323. }
  324. }
  325. /**
  326. * Ceck if a given sql query exist in cache
  327. */
  328. function sql_exists($query_id)
  329. {
  330. return isset($this->sql_rowset[$query_id]);
  331. }
  332. /**
  333. * Fetch row from cache (database)
  334. */
  335. function sql_fetchrow($query_id)
  336. {
  337. if ($this->sql_row_pointer[$query_id] < sizeof($this->sql_rowset[$query_id]))
  338. {
  339. return $this->sql_rowset[$query_id][$this->sql_row_pointer[$query_id]++];
  340. }
  341. return false;
  342. }
  343. /**
  344. * Fetch a field from the current row of a cached database result (database)
  345. */
  346. function sql_fetchfield($query_id, $field)
  347. {
  348. if ($this->sql_row_pointer[$query_id] < sizeof($this->sql_rowset[$query_id]))
  349. {
  350. return (isset($this->sql_rowset[$query_id][$this->sql_row_pointer[$query_id]][$field])) ? $this->sql_rowset[$query_id][$this->sql_row_pointer[$query_id]++][$field] : false;
  351. }
  352. return false;
  353. }
  354. /**
  355. * Seek a specific row in an a cached database result (database)
  356. */
  357. function sql_rowseek($rownum, $query_id)
  358. {
  359. if ($rownum >= sizeof($this->sql_rowset[$query_id]))
  360. {
  361. return false;
  362. }
  363. $this->sql_row_pointer[$query_id] = $rownum;
  364. return true;
  365. }
  366. /**
  367. * Free memory used for a cached database result (database)
  368. */
  369. function sql_freeresult($query_id)
  370. {
  371. if (!isset($this->sql_rowset[$query_id]))
  372. {
  373. return false;
  374. }
  375. unset($this->sql_rowset[$query_id]);
  376. unset($this->sql_row_pointer[$query_id]);
  377. return true;
  378. }
  379. /**
  380. * Read cached data from a specified file
  381. *
  382. * @access private
  383. * @param string $filename Filename to write
  384. * @return mixed False if an error was encountered, otherwise the data type of the cached data
  385. */
  386. function _read($filename)
  387. {
  388. global $phpEx;
  389. $file = "{$this->cache_dir}$filename.$phpEx";
  390. $type = substr($filename, 0, strpos($filename, '_'));
  391. if (!file_exists($file))
  392. {
  393. return false;
  394. }
  395. if (!($handle = @fopen($file, 'rb')))
  396. {
  397. return false;
  398. }
  399. // Skip the PHP header
  400. fgets($handle);
  401. if ($filename == 'data_global')
  402. {
  403. $this->vars = $this->var_expires = array();
  404. $time = time();
  405. while (($expires = (int) fgets($handle)) && !feof($handle))
  406. {
  407. // Number of bytes of data
  408. $bytes = substr(fgets($handle), 0, -1);
  409. if (!is_numeric($bytes) || ($bytes = (int) $bytes) === 0)
  410. {
  411. // We cannot process the file without a valid number of bytes
  412. // so we discard it
  413. fclose($handle);
  414. $this->vars = $this->var_expires = array();
  415. $this->is_modified = false;
  416. $this->remove_file($file);
  417. return false;
  418. }
  419. if ($time >= $expires)
  420. {
  421. fseek($handle, $bytes, SEEK_CUR);
  422. continue;
  423. }
  424. $var_name = substr(fgets($handle), 0, -1);
  425. // Read the length of bytes that consists of data.
  426. $data = fread($handle, $bytes - strlen($var_name));
  427. $data = @unserialize($data);
  428. // Don't use the data if it was invalid
  429. if ($data !== false)
  430. {
  431. $this->vars[$var_name] = $data;
  432. $this->var_expires[$var_name] = $expires;
  433. }
  434. // Absorb the LF
  435. fgets($handle);
  436. }
  437. fclose($handle);
  438. $this->is_modified = false;
  439. return true;
  440. }
  441. else
  442. {
  443. $data = false;
  444. $line = 0;
  445. while (($buffer = fgets($handle)) && !feof($handle))
  446. {
  447. $buffer = substr($buffer, 0, -1); // Remove the LF
  448. // $buffer is only used to read integers
  449. // if it is non numeric we have an invalid
  450. // cache file, which we will now remove.
  451. if (!is_numeric($buffer))
  452. {
  453. break;
  454. }
  455. if ($line == 0)
  456. {
  457. $expires = (int) $buffer;
  458. if (time() >= $expires)
  459. {
  460. break;
  461. }
  462. if ($type == 'sql')
  463. {
  464. // Skip the query
  465. fgets($handle);
  466. }
  467. }
  468. else if ($line == 1)
  469. {
  470. $bytes = (int) $buffer;
  471. // Never should have 0 bytes
  472. if (!$bytes)
  473. {
  474. break;
  475. }
  476. // Grab the serialized data
  477. $data = fread($handle, $bytes);
  478. // Read 1 byte, to trigger EOF
  479. fread($handle, 1);
  480. if (!feof($handle))
  481. {
  482. // Somebody tampered with our data
  483. $data = false;
  484. }
  485. break;
  486. }
  487. else
  488. {
  489. // Something went wrong
  490. break;
  491. }
  492. $line++;
  493. }
  494. fclose($handle);
  495. // unserialize if we got some data
  496. $data = ($data !== false) ? @unserialize($data) : $data;
  497. if ($data === false)
  498. {
  499. $this->remove_file($file);
  500. return false;
  501. }
  502. return $data;
  503. }
  504. }
  505. /**
  506. * Write cache data to a specified file
  507. *
  508. * 'data_global' is a special case and the generated format is different for this file:
  509. * <code>
  510. * <?php exit; ?>
  511. * (expiration)
  512. * (length of var and serialised data)
  513. * (var)
  514. * (serialised data)
  515. * ... (repeat)
  516. * </code>
  517. *
  518. * The other files have a similar format:
  519. * <code>
  520. * <?php exit; ?>
  521. * (expiration)
  522. * (query) [SQL files only]
  523. * (length of serialised data)
  524. * (serialised data)
  525. * </code>
  526. *
  527. * @access private
  528. * @param string $filename Filename to write
  529. * @param mixed $data Data to store
  530. * @param int $expires Timestamp when the data expires
  531. * @param string $query Query when caching SQL queries
  532. * @return bool True if the file was successfully created, otherwise false
  533. */
  534. function _write($filename, $data = null, $expires = 0, $query = '')
  535. {
  536. global $phpEx;
  537. $file = "{$this->cache_dir}$filename.$phpEx";
  538. if ($handle = @fopen($file, 'wb'))
  539. {
  540. @flock($handle, LOCK_EX);
  541. // File header
  542. fwrite($handle, '<' . '?php exit; ?' . '>');
  543. if ($filename == 'data_global')
  544. {
  545. // Global data is a different format
  546. foreach ($this->vars as $var => $data)
  547. {
  548. if (strpos($var, "\r") !== false || strpos($var, "\n") !== false)
  549. {
  550. // CR/LF would cause fgets() to read the cache file incorrectly
  551. // do not cache test entries, they probably won't be read back
  552. // the cache keys should really be alphanumeric with a few symbols.
  553. continue;
  554. }
  555. $data = serialize($data);
  556. // Write out the expiration time
  557. fwrite($handle, "\n" . $this->var_expires[$var] . "\n");
  558. // Length of the remaining data for this var (ignoring two LF's)
  559. fwrite($handle, strlen($data . $var) . "\n");
  560. fwrite($handle, $var . "\n");
  561. fwrite($handle, $data);
  562. }
  563. }
  564. else
  565. {
  566. fwrite($handle, "\n" . $expires . "\n");
  567. if (strpos($filename, 'sql_') === 0)
  568. {
  569. fwrite($handle, $query . "\n");
  570. }
  571. $data = serialize($data);
  572. fwrite($handle, strlen($data) . "\n");
  573. fwrite($handle, $data);
  574. }
  575. @flock($handle, LOCK_UN);
  576. fclose($handle);
  577. if (!function_exists('phpbb_chmod'))
  578. {
  579. global $phpbb_root_path;
  580. include($phpbb_root_path . 'includes/functions.' . $phpEx);
  581. }
  582. phpbb_chmod($file, CHMOD_READ | CHMOD_WRITE);
  583. return true;
  584. }
  585. return false;
  586. }
  587. /**
  588. * Removes/unlinks file
  589. */
  590. function remove_file($filename, $check = false)
  591. {
  592. if (!function_exists('phpbb_is_writable'))
  593. {
  594. global $phpbb_root_path, $phpEx;
  595. include($phpbb_root_path . 'includes/functions.' . $phpEx);
  596. }
  597. if ($check && !phpbb_is_writable($this->cache_dir))
  598. {
  599. // E_USER_ERROR - not using language entry - intended.
  600. trigger_error('Unable to remove files within ' . $this->cache_dir . '. Please check directory permissions.', E_USER_ERROR);
  601. }
  602. return @unlink($filename);
  603. }
  604. }