PageRenderTime 58ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/Zend/Cache/Backend/File.php

https://github.com/codeactual/hashmark
PHP | 1007 lines | 657 code | 36 blank | 314 comment | 59 complexity | 293d7764df3cd977e2ede67747b56434 MD5 | raw file
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Cache
  17. * @subpackage Zend_Cache_Backend
  18. * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://framework.zend.com/license/new-bsd New BSD License
  20. * @version $Id: File.php 24029 2011-05-09 22:04:02Z mabe $
  21. */
  22. /**
  23. * @see Zend_Cache_Backend_Interface
  24. */
  25. require_once 'Zend/Cache/Backend/ExtendedInterface.php';
  26. /**
  27. * @see Zend_Cache_Backend
  28. */
  29. require_once 'Zend/Cache/Backend.php';
  30. /**
  31. * @package Zend_Cache
  32. * @subpackage Zend_Cache_Backend
  33. * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com)
  34. * @license http://framework.zend.com/license/new-bsd New BSD License
  35. */
  36. class Zend_Cache_Backend_File extends Zend_Cache_Backend implements Zend_Cache_Backend_ExtendedInterface
  37. {
  38. /**
  39. * Available options
  40. *
  41. * =====> (string) cache_dir :
  42. * - Directory where to put the cache files
  43. *
  44. * =====> (boolean) file_locking :
  45. * - Enable / disable file_locking
  46. * - Can avoid cache corruption under bad circumstances but it doesn't work on multithread
  47. * webservers and on NFS filesystems for example
  48. *
  49. * =====> (boolean) read_control :
  50. * - Enable / disable read control
  51. * - If enabled, a control key is embeded in cache file and this key is compared with the one
  52. * calculated after the reading.
  53. *
  54. * =====> (string) read_control_type :
  55. * - Type of read control (only if read control is enabled). Available values are :
  56. * 'md5' for a md5 hash control (best but slowest)
  57. * 'crc32' for a crc32 hash control (lightly less safe but faster, better choice)
  58. * 'adler32' for an adler32 hash control (excellent choice too, faster than crc32)
  59. * 'strlen' for a length only test (fastest)
  60. *
  61. * =====> (int) hashed_directory_level :
  62. * - Hashed directory level
  63. * - Set the hashed directory structure level. 0 means "no hashed directory
  64. * structure", 1 means "one level of directory", 2 means "two levels"...
  65. * This option can speed up the cache only when you have many thousands of
  66. * cache file. Only specific benchs can help you to choose the perfect value
  67. * for you. Maybe, 1 or 2 is a good start.
  68. *
  69. * =====> (int) hashed_directory_umask :
  70. * - Umask for hashed directory structure
  71. *
  72. * =====> (string) file_name_prefix :
  73. * - prefix for cache files
  74. * - be really carefull with this option because a too generic value in a system cache dir
  75. * (like /tmp) can cause disasters when cleaning the cache
  76. *
  77. * =====> (int) cache_file_umask :
  78. * - Umask for cache files
  79. *
  80. * =====> (int) metatadatas_array_max_size :
  81. * - max size for the metadatas array (don't change this value unless you
  82. * know what you are doing)
  83. *
  84. * @var array available options
  85. */
  86. protected $_options = array(
  87. 'cache_dir' => null,
  88. 'file_locking' => true,
  89. 'read_control' => true,
  90. 'read_control_type' => 'crc32',
  91. 'hashed_directory_level' => 0,
  92. 'hashed_directory_umask' => 0700,
  93. 'file_name_prefix' => 'zend_cache',
  94. 'cache_file_umask' => 0600,
  95. 'metadatas_array_max_size' => 100
  96. );
  97. /**
  98. * Array of metadatas (each item is an associative array)
  99. *
  100. * @var array
  101. */
  102. protected $_metadatasArray = array();
  103. /**
  104. * Constructor
  105. *
  106. * @param array $options associative array of options
  107. * @throws Zend_Cache_Exception
  108. * @return void
  109. */
  110. public function __construct(array $options = array())
  111. {
  112. parent::__construct($options);
  113. if ($this->_options['cache_dir'] !== null) { // particular case for this option
  114. $this->setCacheDir($this->_options['cache_dir']);
  115. } else {
  116. $this->setCacheDir(self::getTmpDir() . DIRECTORY_SEPARATOR, false);
  117. }
  118. if (isset($this->_options['file_name_prefix'])) { // particular case for this option
  119. if (!preg_match('~^[a-zA-Z0-9_]+$~D', $this->_options['file_name_prefix'])) {
  120. Zend_Cache::throwException('Invalid file_name_prefix : must use only [a-zA-Z0-9_]');
  121. }
  122. }
  123. if ($this->_options['metadatas_array_max_size'] < 10) {
  124. Zend_Cache::throwException('Invalid metadatas_array_max_size, must be > 10');
  125. }
  126. if (isset($options['hashed_directory_umask']) && is_string($options['hashed_directory_umask'])) {
  127. // See #ZF-4422
  128. $this->_options['hashed_directory_umask'] = octdec($this->_options['hashed_directory_umask']);
  129. }
  130. if (isset($options['cache_file_umask']) && is_string($options['cache_file_umask'])) {
  131. // See #ZF-4422
  132. $this->_options['cache_file_umask'] = octdec($this->_options['cache_file_umask']);
  133. }
  134. }
  135. /**
  136. * Set the cache_dir (particular case of setOption() method)
  137. *
  138. * @param string $value
  139. * @param boolean $trailingSeparator If true, add a trailing separator is necessary
  140. * @throws Zend_Cache_Exception
  141. * @return void
  142. */
  143. public function setCacheDir($value, $trailingSeparator = true)
  144. {
  145. if (!is_dir($value)) {
  146. Zend_Cache::throwException('cache_dir must be a directory');
  147. }
  148. if (!is_writable($value)) {
  149. Zend_Cache::throwException('cache_dir is not writable');
  150. }
  151. if ($trailingSeparator) {
  152. // add a trailing DIRECTORY_SEPARATOR if necessary
  153. $value = rtrim(realpath($value), '\\/') . DIRECTORY_SEPARATOR;
  154. }
  155. $this->_options['cache_dir'] = $value;
  156. }
  157. /**
  158. * Test if a cache is available for the given id and (if yes) return it (false else)
  159. *
  160. * @param string $id cache id
  161. * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested
  162. * @return string|false cached datas
  163. */
  164. public function load($id, $doNotTestCacheValidity = false)
  165. {
  166. if (!($this->_test($id, $doNotTestCacheValidity))) {
  167. // The cache is not hit !
  168. return false;
  169. }
  170. $metadatas = $this->_getMetadatas($id);
  171. $file = $this->_file($id);
  172. $data = $this->_fileGetContents($file);
  173. if ($this->_options['read_control']) {
  174. $hashData = $this->_hash($data, $this->_options['read_control_type']);
  175. $hashControl = $metadatas['hash'];
  176. if ($hashData != $hashControl) {
  177. // Problem detected by the read control !
  178. $this->_log('Zend_Cache_Backend_File::load() / read_control : stored hash and computed hash do not match');
  179. $this->remove($id);
  180. return false;
  181. }
  182. }
  183. return $data;
  184. }
  185. /**
  186. * Test if a cache is available or not (for the given id)
  187. *
  188. * @param string $id cache id
  189. * @return mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record
  190. */
  191. public function test($id)
  192. {
  193. clearstatcache();
  194. return $this->_test($id, false);
  195. }
  196. /**
  197. * Save some string datas into a cache record
  198. *
  199. * Note : $data is always "string" (serialization is done by the
  200. * core not by the backend)
  201. *
  202. * @param string $data Datas to cache
  203. * @param string $id Cache id
  204. * @param array $tags Array of strings, the cache record will be tagged by each string entry
  205. * @param int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
  206. * @return boolean true if no problem
  207. */
  208. public function save($data, $id, $tags = array(), $specificLifetime = false)
  209. {
  210. clearstatcache();
  211. $file = $this->_file($id);
  212. $path = $this->_path($id);
  213. if ($this->_options['hashed_directory_level'] > 0) {
  214. if (!is_writable($path)) {
  215. // maybe, we just have to build the directory structure
  216. $this->_recursiveMkdirAndChmod($id);
  217. }
  218. if (!is_writable($path)) {
  219. return false;
  220. }
  221. }
  222. if ($this->_options['read_control']) {
  223. $hash = $this->_hash($data, $this->_options['read_control_type']);
  224. } else {
  225. $hash = '';
  226. }
  227. $metadatas = array(
  228. 'hash' => $hash,
  229. 'mtime' => time(),
  230. 'expire' => $this->_expireTime($this->getLifetime($specificLifetime)),
  231. 'tags' => $tags
  232. );
  233. $res = $this->_setMetadatas($id, $metadatas);
  234. if (!$res) {
  235. $this->_log('Zend_Cache_Backend_File::save() / error on saving metadata');
  236. return false;
  237. }
  238. $res = $this->_filePutContents($file, $data);
  239. return $res;
  240. }
  241. /**
  242. * Remove a cache record
  243. *
  244. * @param string $id cache id
  245. * @return boolean true if no problem
  246. */
  247. public function remove($id)
  248. {
  249. $file = $this->_file($id);
  250. $boolRemove = $this->_remove($file);
  251. $boolMetadata = $this->_delMetadatas($id);
  252. return $boolMetadata && $boolRemove;
  253. }
  254. /**
  255. * Clean some cache records
  256. *
  257. * Available modes are :
  258. *
  259. * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used)
  260. * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used)
  261. * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags
  262. * ($tags can be an array of strings or a single string)
  263. * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags}
  264. * ($tags can be an array of strings or a single string)
  265. * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags
  266. * ($tags can be an array of strings or a single string)
  267. *
  268. * @param string $mode clean mode
  269. * @param tags array $tags array of tags
  270. * @return boolean true if no problem
  271. */
  272. public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
  273. {
  274. // We use this protected method to hide the recursive stuff
  275. clearstatcache();
  276. return $this->_clean($this->_options['cache_dir'], $mode, $tags);
  277. }
  278. /**
  279. * Return an array of stored cache ids
  280. *
  281. * @return array array of stored cache ids (string)
  282. */
  283. public function getIds()
  284. {
  285. return $this->_get($this->_options['cache_dir'], 'ids', array());
  286. }
  287. /**
  288. * Return an array of stored tags
  289. *
  290. * @return array array of stored tags (string)
  291. */
  292. public function getTags()
  293. {
  294. return $this->_get($this->_options['cache_dir'], 'tags', array());
  295. }
  296. /**
  297. * Return an array of stored cache ids which match given tags
  298. *
  299. * In case of multiple tags, a logical AND is made between tags
  300. *
  301. * @param array $tags array of tags
  302. * @return array array of matching cache ids (string)
  303. */
  304. public function getIdsMatchingTags($tags = array())
  305. {
  306. return $this->_get($this->_options['cache_dir'], 'matching', $tags);
  307. }
  308. /**
  309. * Return an array of stored cache ids which don't match given tags
  310. *
  311. * In case of multiple tags, a logical OR is made between tags
  312. *
  313. * @param array $tags array of tags
  314. * @return array array of not matching cache ids (string)
  315. */
  316. public function getIdsNotMatchingTags($tags = array())
  317. {
  318. return $this->_get($this->_options['cache_dir'], 'notMatching', $tags);
  319. }
  320. /**
  321. * Return an array of stored cache ids which match any given tags
  322. *
  323. * In case of multiple tags, a logical AND is made between tags
  324. *
  325. * @param array $tags array of tags
  326. * @return array array of any matching cache ids (string)
  327. */
  328. public function getIdsMatchingAnyTags($tags = array())
  329. {
  330. return $this->_get($this->_options['cache_dir'], 'matchingAny', $tags);
  331. }
  332. /**
  333. * Return the filling percentage of the backend storage
  334. *
  335. * @throws Zend_Cache_Exception
  336. * @return int integer between 0 and 100
  337. */
  338. public function getFillingPercentage()
  339. {
  340. $free = disk_free_space($this->_options['cache_dir']);
  341. $total = disk_total_space($this->_options['cache_dir']);
  342. if ($total == 0) {
  343. Zend_Cache::throwException('can\'t get disk_total_space');
  344. } else {
  345. if ($free >= $total) {
  346. return 100;
  347. }
  348. return ((int) (100. * ($total - $free) / $total));
  349. }
  350. }
  351. /**
  352. * Return an array of metadatas for the given cache id
  353. *
  354. * The array must include these keys :
  355. * - expire : the expire timestamp
  356. * - tags : a string array of tags
  357. * - mtime : timestamp of last modification time
  358. *
  359. * @param string $id cache id
  360. * @return array array of metadatas (false if the cache id is not found)
  361. */
  362. public function getMetadatas($id)
  363. {
  364. $metadatas = $this->_getMetadatas($id);
  365. if (!$metadatas) {
  366. return false;
  367. }
  368. if (time() > $metadatas['expire']) {
  369. return false;
  370. }
  371. return array(
  372. 'expire' => $metadatas['expire'],
  373. 'tags' => $metadatas['tags'],
  374. 'mtime' => $metadatas['mtime']
  375. );
  376. }
  377. /**
  378. * Give (if possible) an extra lifetime to the given cache id
  379. *
  380. * @param string $id cache id
  381. * @param int $extraLifetime
  382. * @return boolean true if ok
  383. */
  384. public function touch($id, $extraLifetime)
  385. {
  386. $metadatas = $this->_getMetadatas($id);
  387. if (!$metadatas) {
  388. return false;
  389. }
  390. if (time() > $metadatas['expire']) {
  391. return false;
  392. }
  393. $newMetadatas = array(
  394. 'hash' => $metadatas['hash'],
  395. 'mtime' => time(),
  396. 'expire' => $metadatas['expire'] + $extraLifetime,
  397. 'tags' => $metadatas['tags']
  398. );
  399. $res = $this->_setMetadatas($id, $newMetadatas);
  400. if (!$res) {
  401. return false;
  402. }
  403. return true;
  404. }
  405. /**
  406. * Return an associative array of capabilities (booleans) of the backend
  407. *
  408. * The array must include these keys :
  409. * - automatic_cleaning (is automating cleaning necessary)
  410. * - tags (are tags supported)
  411. * - expired_read (is it possible to read expired cache records
  412. * (for doNotTestCacheValidity option for example))
  413. * - priority does the backend deal with priority when saving
  414. * - infinite_lifetime (is infinite lifetime can work with this backend)
  415. * - get_list (is it possible to get the list of cache ids and the complete list of tags)
  416. *
  417. * @return array associative of with capabilities
  418. */
  419. public function getCapabilities()
  420. {
  421. return array(
  422. 'automatic_cleaning' => true,
  423. 'tags' => true,
  424. 'expired_read' => true,
  425. 'priority' => false,
  426. 'infinite_lifetime' => true,
  427. 'get_list' => true
  428. );
  429. }
  430. /**
  431. * PUBLIC METHOD FOR UNIT TESTING ONLY !
  432. *
  433. * Force a cache record to expire
  434. *
  435. * @param string $id cache id
  436. */
  437. public function ___expire($id)
  438. {
  439. $metadatas = $this->_getMetadatas($id);
  440. if ($metadatas) {
  441. $metadatas['expire'] = 1;
  442. $this->_setMetadatas($id, $metadatas);
  443. }
  444. }
  445. /**
  446. * Get a metadatas record
  447. *
  448. * @param string $id Cache id
  449. * @return array|false Associative array of metadatas
  450. */
  451. protected function _getMetadatas($id)
  452. {
  453. if (isset($this->_metadatasArray[$id])) {
  454. return $this->_metadatasArray[$id];
  455. } else {
  456. $metadatas = $this->_loadMetadatas($id);
  457. if (!$metadatas) {
  458. return false;
  459. }
  460. $this->_setMetadatas($id, $metadatas, false);
  461. return $metadatas;
  462. }
  463. }
  464. /**
  465. * Set a metadatas record
  466. *
  467. * @param string $id Cache id
  468. * @param array $metadatas Associative array of metadatas
  469. * @param boolean $save optional pass false to disable saving to file
  470. * @return boolean True if no problem
  471. */
  472. protected function _setMetadatas($id, $metadatas, $save = true)
  473. {
  474. if (count($this->_metadatasArray) >= $this->_options['metadatas_array_max_size']) {
  475. $n = (int) ($this->_options['metadatas_array_max_size'] / 10);
  476. $this->_metadatasArray = array_slice($this->_metadatasArray, $n);
  477. }
  478. if ($save) {
  479. $result = $this->_saveMetadatas($id, $metadatas);
  480. if (!$result) {
  481. return false;
  482. }
  483. }
  484. $this->_metadatasArray[$id] = $metadatas;
  485. return true;
  486. }
  487. /**
  488. * Drop a metadata record
  489. *
  490. * @param string $id Cache id
  491. * @return boolean True if no problem
  492. */
  493. protected function _delMetadatas($id)
  494. {
  495. if (isset($this->_metadatasArray[$id])) {
  496. unset($this->_metadatasArray[$id]);
  497. }
  498. $file = $this->_metadatasFile($id);
  499. return $this->_remove($file);
  500. }
  501. /**
  502. * Clear the metadatas array
  503. *
  504. * @return void
  505. */
  506. protected function _cleanMetadatas()
  507. {
  508. $this->_metadatasArray = array();
  509. }
  510. /**
  511. * Load metadatas from disk
  512. *
  513. * @param string $id Cache id
  514. * @return array|false Metadatas associative array
  515. */
  516. protected function _loadMetadatas($id)
  517. {
  518. $file = $this->_metadatasFile($id);
  519. $result = $this->_fileGetContents($file);
  520. if (!$result) {
  521. return false;
  522. }
  523. $tmp = @unserialize($result);
  524. return $tmp;
  525. }
  526. /**
  527. * Save metadatas to disk
  528. *
  529. * @param string $id Cache id
  530. * @param array $metadatas Associative array
  531. * @return boolean True if no problem
  532. */
  533. protected function _saveMetadatas($id, $metadatas)
  534. {
  535. $file = $this->_metadatasFile($id);
  536. $result = $this->_filePutContents($file, serialize($metadatas));
  537. if (!$result) {
  538. return false;
  539. }
  540. return true;
  541. }
  542. /**
  543. * Make and return a file name (with path) for metadatas
  544. *
  545. * @param string $id Cache id
  546. * @return string Metadatas file name (with path)
  547. */
  548. protected function _metadatasFile($id)
  549. {
  550. $path = $this->_path($id);
  551. $fileName = $this->_idToFileName('internal-metadatas---' . $id);
  552. return $path . $fileName;
  553. }
  554. /**
  555. * Check if the given filename is a metadatas one
  556. *
  557. * @param string $fileName File name
  558. * @return boolean True if it's a metadatas one
  559. */
  560. protected function _isMetadatasFile($fileName)
  561. {
  562. $id = $this->_fileNameToId($fileName);
  563. if (substr($id, 0, 21) == 'internal-metadatas---') {
  564. return true;
  565. } else {
  566. return false;
  567. }
  568. }
  569. /**
  570. * Remove a file
  571. *
  572. * If we can't remove the file (because of locks or any problem), we will touch
  573. * the file to invalidate it
  574. *
  575. * @param string $file Complete file path
  576. * @return boolean True if ok
  577. */
  578. protected function _remove($file)
  579. {
  580. if (!is_file($file)) {
  581. return false;
  582. }
  583. if (!@unlink($file)) {
  584. # we can't remove the file (because of locks or any problem)
  585. $this->_log("Zend_Cache_Backend_File::_remove() : we can't remove $file");
  586. return false;
  587. }
  588. return true;
  589. }
  590. /**
  591. * Clean some cache records (protected method used for recursive stuff)
  592. *
  593. * Available modes are :
  594. * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used)
  595. * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used)
  596. * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags
  597. * ($tags can be an array of strings or a single string)
  598. * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags}
  599. * ($tags can be an array of strings or a single string)
  600. * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags
  601. * ($tags can be an array of strings or a single string)
  602. *
  603. * @param string $dir Directory to clean
  604. * @param string $mode Clean mode
  605. * @param array $tags Array of tags
  606. * @throws Zend_Cache_Exception
  607. * @return boolean True if no problem
  608. */
  609. protected function _clean($dir, $mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
  610. {
  611. if (!is_dir($dir)) {
  612. return false;
  613. }
  614. $result = true;
  615. $prefix = $this->_options['file_name_prefix'];
  616. $glob = @glob($dir . $prefix . '--*');
  617. if ($glob === false) {
  618. // On some systems it is impossible to distinguish between empty match and an error.
  619. return true;
  620. }
  621. foreach ($glob as $file) {
  622. if (is_file($file)) {
  623. $fileName = basename($file);
  624. if ($this->_isMetadatasFile($fileName)) {
  625. // in CLEANING_MODE_ALL, we drop anything, even remainings old metadatas files
  626. if ($mode != Zend_Cache::CLEANING_MODE_ALL) {
  627. continue;
  628. }
  629. }
  630. $id = $this->_fileNameToId($fileName);
  631. $metadatas = $this->_getMetadatas($id);
  632. if ($metadatas === FALSE) {
  633. $metadatas = array('expire' => 1, 'tags' => array());
  634. }
  635. switch ($mode) {
  636. case Zend_Cache::CLEANING_MODE_ALL:
  637. $res = $this->remove($id);
  638. if (!$res) {
  639. // in this case only, we accept a problem with the metadatas file drop
  640. $res = $this->_remove($file);
  641. }
  642. $result = $result && $res;
  643. break;
  644. case Zend_Cache::CLEANING_MODE_OLD:
  645. if (time() > $metadatas['expire']) {
  646. $result = $this->remove($id) && $result;
  647. }
  648. break;
  649. case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
  650. $matching = true;
  651. foreach ($tags as $tag) {
  652. if (!in_array($tag, $metadatas['tags'])) {
  653. $matching = false;
  654. break;
  655. }
  656. }
  657. if ($matching) {
  658. $result = $this->remove($id) && $result;
  659. }
  660. break;
  661. case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
  662. $matching = false;
  663. foreach ($tags as $tag) {
  664. if (in_array($tag, $metadatas['tags'])) {
  665. $matching = true;
  666. break;
  667. }
  668. }
  669. if (!$matching) {
  670. $result = $this->remove($id) && $result;
  671. }
  672. break;
  673. case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
  674. $matching = false;
  675. foreach ($tags as $tag) {
  676. if (in_array($tag, $metadatas['tags'])) {
  677. $matching = true;
  678. break;
  679. }
  680. }
  681. if ($matching) {
  682. $result = $this->remove($id) && $result;
  683. }
  684. break;
  685. default:
  686. Zend_Cache::throwException('Invalid mode for clean() method');
  687. break;
  688. }
  689. }
  690. if ((is_dir($file)) and ($this->_options['hashed_directory_level']>0)) {
  691. // Recursive call
  692. $result = $this->_clean($file . DIRECTORY_SEPARATOR, $mode, $tags) && $result;
  693. if ($mode == Zend_Cache::CLEANING_MODE_ALL) {
  694. // we try to drop the structure too
  695. @rmdir($file);
  696. }
  697. }
  698. }
  699. return $result;
  700. }
  701. protected function _get($dir, $mode, $tags = array())
  702. {
  703. if (!is_dir($dir)) {
  704. return false;
  705. }
  706. $result = array();
  707. $prefix = $this->_options['file_name_prefix'];
  708. $glob = @glob($dir . $prefix . '--*');
  709. if ($glob === false) {
  710. // On some systems it is impossible to distinguish between empty match and an error.
  711. return array();
  712. }
  713. foreach ($glob as $file) {
  714. if (is_file($file)) {
  715. $fileName = basename($file);
  716. $id = $this->_fileNameToId($fileName);
  717. $metadatas = $this->_getMetadatas($id);
  718. if ($metadatas === FALSE) {
  719. continue;
  720. }
  721. if (time() > $metadatas['expire']) {
  722. continue;
  723. }
  724. switch ($mode) {
  725. case 'ids':
  726. $result[] = $id;
  727. break;
  728. case 'tags':
  729. $result = array_unique(array_merge($result, $metadatas['tags']));
  730. break;
  731. case 'matching':
  732. $matching = true;
  733. foreach ($tags as $tag) {
  734. if (!in_array($tag, $metadatas['tags'])) {
  735. $matching = false;
  736. break;
  737. }
  738. }
  739. if ($matching) {
  740. $result[] = $id;
  741. }
  742. break;
  743. case 'notMatching':
  744. $matching = false;
  745. foreach ($tags as $tag) {
  746. if (in_array($tag, $metadatas['tags'])) {
  747. $matching = true;
  748. break;
  749. }
  750. }
  751. if (!$matching) {
  752. $result[] = $id;
  753. }
  754. break;
  755. case 'matchingAny':
  756. $matching = false;
  757. foreach ($tags as $tag) {
  758. if (in_array($tag, $metadatas['tags'])) {
  759. $matching = true;
  760. break;
  761. }
  762. }
  763. if ($matching) {
  764. $result[] = $id;
  765. }
  766. break;
  767. default:
  768. Zend_Cache::throwException('Invalid mode for _get() method');
  769. break;
  770. }
  771. }
  772. if ((is_dir($file)) and ($this->_options['hashed_directory_level']>0)) {
  773. // Recursive call
  774. $recursiveRs = $this->_get($file . DIRECTORY_SEPARATOR, $mode, $tags);
  775. if ($recursiveRs === false) {
  776. $this->_log('Zend_Cache_Backend_File::_get() / recursive call : can\'t list entries of "'.$file.'"');
  777. } else {
  778. $result = array_unique(array_merge($result, $recursiveRs));
  779. }
  780. }
  781. }
  782. return array_unique($result);
  783. }
  784. /**
  785. * Compute & return the expire time
  786. *
  787. * @return int expire time (unix timestamp)
  788. */
  789. protected function _expireTime($lifetime)
  790. {
  791. if ($lifetime === null) {
  792. return 9999999999;
  793. }
  794. return time() + $lifetime;
  795. }
  796. /**
  797. * Make a control key with the string containing datas
  798. *
  799. * @param string $data Data
  800. * @param string $controlType Type of control 'md5', 'crc32' or 'strlen'
  801. * @throws Zend_Cache_Exception
  802. * @return string Control key
  803. */
  804. protected function _hash($data, $controlType)
  805. {
  806. switch ($controlType) {
  807. case 'md5':
  808. return md5($data);
  809. case 'crc32':
  810. return crc32($data);
  811. case 'strlen':
  812. return strlen($data);
  813. case 'adler32':
  814. return hash('adler32', $data);
  815. default:
  816. Zend_Cache::throwException("Incorrect hash function : $controlType");
  817. }
  818. }
  819. /**
  820. * Transform a cache id into a file name and return it
  821. *
  822. * @param string $id Cache id
  823. * @return string File name
  824. */
  825. protected function _idToFileName($id)
  826. {
  827. $prefix = $this->_options['file_name_prefix'];
  828. $result = $prefix . '---' . $id;
  829. return $result;
  830. }
  831. /**
  832. * Make and return a file name (with path)
  833. *
  834. * @param string $id Cache id
  835. * @return string File name (with path)
  836. */
  837. protected function _file($id)
  838. {
  839. $path = $this->_path($id);
  840. $fileName = $this->_idToFileName($id);
  841. return $path . $fileName;
  842. }
  843. /**
  844. * Return the complete directory path of a filename (including hashedDirectoryStructure)
  845. *
  846. * @param string $id Cache id
  847. * @param boolean $parts if true, returns array of directory parts instead of single string
  848. * @return string Complete directory path
  849. */
  850. protected function _path($id, $parts = false)
  851. {
  852. $partsArray = array();
  853. $root = $this->_options['cache_dir'];
  854. $prefix = $this->_options['file_name_prefix'];
  855. if ($this->_options['hashed_directory_level']>0) {
  856. $hash = hash('adler32', $id);
  857. for ($i=0 ; $i < $this->_options['hashed_directory_level'] ; $i++) {
  858. $root = $root . $prefix . '--' . substr($hash, 0, $i + 1) . DIRECTORY_SEPARATOR;
  859. $partsArray[] = $root;
  860. }
  861. }
  862. if ($parts) {
  863. return $partsArray;
  864. } else {
  865. return $root;
  866. }
  867. }
  868. /**
  869. * Make the directory strucuture for the given id
  870. *
  871. * @param string $id cache id
  872. * @return boolean true
  873. */
  874. protected function _recursiveMkdirAndChmod($id)
  875. {
  876. if ($this->_options['hashed_directory_level'] <=0) {
  877. return true;
  878. }
  879. $partsArray = $this->_path($id, true);
  880. foreach ($partsArray as $part) {
  881. if (!is_dir($part)) {
  882. @mkdir($part, $this->_options['hashed_directory_umask']);
  883. @chmod($part, $this->_options['hashed_directory_umask']); // see #ZF-320 (this line is required in some configurations)
  884. }
  885. }
  886. return true;
  887. }
  888. /**
  889. * Test if the given cache id is available (and still valid as a cache record)
  890. *
  891. * @param string $id Cache id
  892. * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
  893. * @return boolean|mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record
  894. */
  895. protected function _test($id, $doNotTestCacheValidity)
  896. {
  897. $metadatas = $this->_getMetadatas($id);
  898. if (!$metadatas) {
  899. return false;
  900. }
  901. if ($doNotTestCacheValidity || (time() <= $metadatas['expire'])) {
  902. return $metadatas['mtime'];
  903. }
  904. return false;
  905. }
  906. /**
  907. * Return the file content of the given file
  908. *
  909. * @param string $file File complete path
  910. * @return string File content (or false if problem)
  911. */
  912. protected function _fileGetContents($file)
  913. {
  914. $result = false;
  915. if (!is_file($file)) {
  916. return false;
  917. }
  918. $f = @fopen($file, 'rb');
  919. if ($f) {
  920. if ($this->_options['file_locking']) @flock($f, LOCK_SH);
  921. $result = stream_get_contents($f);
  922. if ($this->_options['file_locking']) @flock($f, LOCK_UN);
  923. @fclose($f);
  924. }
  925. return $result;
  926. }
  927. /**
  928. * Put the given string into the given file
  929. *
  930. * @param string $file File complete path
  931. * @param string $string String to put in file
  932. * @return boolean true if no problem
  933. */
  934. protected function _filePutContents($file, $string)
  935. {
  936. $result = false;
  937. $f = @fopen($file, 'ab+');
  938. if ($f) {
  939. if ($this->_options['file_locking']) @flock($f, LOCK_EX);
  940. fseek($f, 0);
  941. ftruncate($f, 0);
  942. $tmp = @fwrite($f, $string);
  943. if (!($tmp === FALSE)) {
  944. $result = true;
  945. }
  946. @fclose($f);
  947. }
  948. @chmod($file, $this->_options['cache_file_umask']);
  949. return $result;
  950. }
  951. /**
  952. * Transform a file name into cache id and return it
  953. *
  954. * @param string $fileName File name
  955. * @return string Cache id
  956. */
  957. protected function _fileNameToId($fileName)
  958. {
  959. $prefix = $this->_options['file_name_prefix'];
  960. return preg_replace('~^' . $prefix . '---(.*)$~', '$1', $fileName);
  961. }
  962. }