PageRenderTime 63ms CodeModel.GetById 31ms RepoModel.GetById 1ms app.codeStats 0ms

/phpBB/phpbb/cache/driver/file.php

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