PageRenderTime 52ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/phpBB/includes/acm/acm_file.php

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