File.php 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997
  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-2009 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://framework.zend.com/license/new-bsd New BSD License
  20. * @version $Id$
  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-2009 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('~^[\w]+$~', $this->_options['file_name_prefix'])) {
  120. Zend_Cache::throwException('Invalid file_name_prefix : must use only [a-zA-A0-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. return ($this->_delMetadatas($id) && $this->_remove($file));
  251. }
  252. /**
  253. * Clean some cache records
  254. *
  255. * Available modes are :
  256. * 'all' (default) => remove all cache entries ($tags is not used)
  257. * 'old' => remove too old cache entries ($tags is not used)
  258. * 'matchingTag' => remove cache entries matching all given tags
  259. * ($tags can be an array of strings or a single string)
  260. * 'notMatchingTag' => remove cache entries not matching one of the given tags
  261. * ($tags can be an array of strings or a single string)
  262. * 'matchingAnyTag' => remove cache entries matching any given tags
  263. * ($tags can be an array of strings or a single string)
  264. *
  265. * @param string $mode clean mode
  266. * @param tags array $tags array of tags
  267. * @return boolean true if no problem
  268. */
  269. public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
  270. {
  271. // We use this protected method to hide the recursive stuff
  272. clearstatcache();
  273. return $this->_clean($this->_options['cache_dir'], $mode, $tags);
  274. }
  275. /**
  276. * Return an array of stored cache ids
  277. *
  278. * @return array array of stored cache ids (string)
  279. */
  280. public function getIds()
  281. {
  282. return $this->_get($this->_options['cache_dir'], 'ids', array());
  283. }
  284. /**
  285. * Return an array of stored tags
  286. *
  287. * @return array array of stored tags (string)
  288. */
  289. public function getTags()
  290. {
  291. return $this->_get($this->_options['cache_dir'], 'tags', array());
  292. }
  293. /**
  294. * Return an array of stored cache ids which match given tags
  295. *
  296. * In case of multiple tags, a logical AND is made between tags
  297. *
  298. * @param array $tags array of tags
  299. * @return array array of matching cache ids (string)
  300. */
  301. public function getIdsMatchingTags($tags = array())
  302. {
  303. return $this->_get($this->_options['cache_dir'], 'matching', $tags);
  304. }
  305. /**
  306. * Return an array of stored cache ids which don't match given tags
  307. *
  308. * In case of multiple tags, a logical OR is made between tags
  309. *
  310. * @param array $tags array of tags
  311. * @return array array of not matching cache ids (string)
  312. */
  313. public function getIdsNotMatchingTags($tags = array())
  314. {
  315. return $this->_get($this->_options['cache_dir'], 'notMatching', $tags);
  316. }
  317. /**
  318. * Return an array of stored cache ids which match any given tags
  319. *
  320. * In case of multiple tags, a logical AND is made between tags
  321. *
  322. * @param array $tags array of tags
  323. * @return array array of any matching cache ids (string)
  324. */
  325. public function getIdsMatchingAnyTags($tags = array())
  326. {
  327. return $this->_get($this->_options['cache_dir'], 'matchingAny', $tags);
  328. }
  329. /**
  330. * Return the filling percentage of the backend storage
  331. *
  332. * @throws Zend_Cache_Exception
  333. * @return int integer between 0 and 100
  334. */
  335. public function getFillingPercentage()
  336. {
  337. $free = disk_free_space($this->_options['cache_dir']);
  338. $total = disk_total_space($this->_options['cache_dir']);
  339. if ($total == 0) {
  340. Zend_Cache::throwException('can\'t get disk_total_space');
  341. } else {
  342. if ($free >= $total) {
  343. return 100;
  344. }
  345. return ((int) (100. * ($total - $free) / $total));
  346. }
  347. }
  348. /**
  349. * Return an array of metadatas for the given cache id
  350. *
  351. * The array must include these keys :
  352. * - expire : the expire timestamp
  353. * - tags : a string array of tags
  354. * - mtime : timestamp of last modification time
  355. *
  356. * @param string $id cache id
  357. * @return array array of metadatas (false if the cache id is not found)
  358. */
  359. public function getMetadatas($id)
  360. {
  361. $metadatas = $this->_getMetadatas($id);
  362. if (!$metadatas) {
  363. return false;
  364. }
  365. if (time() > $metadatas['expire']) {
  366. return false;
  367. }
  368. return array(
  369. 'expire' => $metadatas['expire'],
  370. 'tags' => $metadatas['tags'],
  371. 'mtime' => $metadatas['mtime']
  372. );
  373. }
  374. /**
  375. * Give (if possible) an extra lifetime to the given cache id
  376. *
  377. * @param string $id cache id
  378. * @param int $extraLifetime
  379. * @return boolean true if ok
  380. */
  381. public function touch($id, $extraLifetime)
  382. {
  383. $metadatas = $this->_getMetadatas($id);
  384. if (!$metadatas) {
  385. return false;
  386. }
  387. if (time() > $metadatas['expire']) {
  388. return false;
  389. }
  390. $newMetadatas = array(
  391. 'hash' => $metadatas['hash'],
  392. 'mtime' => time(),
  393. 'expire' => $metadatas['expire'] + $extraLifetime,
  394. 'tags' => $metadatas['tags']
  395. );
  396. $res = $this->_setMetadatas($id, $newMetadatas);
  397. if (!$res) {
  398. return false;
  399. }
  400. return true;
  401. }
  402. /**
  403. * Return an associative array of capabilities (booleans) of the backend
  404. *
  405. * The array must include these keys :
  406. * - automatic_cleaning (is automating cleaning necessary)
  407. * - tags (are tags supported)
  408. * - expired_read (is it possible to read expired cache records
  409. * (for doNotTestCacheValidity option for example))
  410. * - priority does the backend deal with priority when saving
  411. * - infinite_lifetime (is infinite lifetime can work with this backend)
  412. * - get_list (is it possible to get the list of cache ids and the complete list of tags)
  413. *
  414. * @return array associative of with capabilities
  415. */
  416. public function getCapabilities()
  417. {
  418. return array(
  419. 'automatic_cleaning' => true,
  420. 'tags' => true,
  421. 'expired_read' => true,
  422. 'priority' => false,
  423. 'infinite_lifetime' => true,
  424. 'get_list' => true
  425. );
  426. }
  427. /**
  428. * PUBLIC METHOD FOR UNIT TESTING ONLY !
  429. *
  430. * Force a cache record to expire
  431. *
  432. * @param string $id cache id
  433. */
  434. public function ___expire($id)
  435. {
  436. $metadatas = $this->_getMetadatas($id);
  437. if ($metadatas) {
  438. $metadatas['expire'] = 1;
  439. $this->_setMetadatas($id, $metadatas);
  440. }
  441. }
  442. /**
  443. * Get a metadatas record
  444. *
  445. * @param string $id Cache id
  446. * @return array|false Associative array of metadatas
  447. */
  448. protected function _getMetadatas($id)
  449. {
  450. if (isset($this->_metadatasArray[$id])) {
  451. return $this->_metadatasArray[$id];
  452. } else {
  453. $metadatas = $this->_loadMetadatas($id);
  454. if (!$metadatas) {
  455. return false;
  456. }
  457. $this->_setMetadatas($id, $metadatas, false);
  458. return $metadatas;
  459. }
  460. }
  461. /**
  462. * Set a metadatas record
  463. *
  464. * @param string $id Cache id
  465. * @param array $metadatas Associative array of metadatas
  466. * @param boolean $save optional pass false to disable saving to file
  467. * @return boolean True if no problem
  468. */
  469. protected function _setMetadatas($id, $metadatas, $save = true)
  470. {
  471. if (count($this->_metadatasArray) >= $this->_options['metadatas_array_max_size']) {
  472. $n = (int) ($this->_options['metadatas_array_max_size'] / 10);
  473. $this->_metadatasArray = array_slice($this->_metadatasArray, $n);
  474. }
  475. if ($save) {
  476. $result = $this->_saveMetadatas($id, $metadatas);
  477. if (!$result) {
  478. return false;
  479. }
  480. }
  481. $this->_metadatasArray[$id] = $metadatas;
  482. return true;
  483. }
  484. /**
  485. * Drop a metadata record
  486. *
  487. * @param string $id Cache id
  488. * @return boolean True if no problem
  489. */
  490. protected function _delMetadatas($id)
  491. {
  492. if (isset($this->_metadatasArray[$id])) {
  493. unset($this->_metadatasArray[$id]);
  494. }
  495. $file = $this->_metadatasFile($id);
  496. return $this->_remove($file);
  497. }
  498. /**
  499. * Clear the metadatas array
  500. *
  501. * @return void
  502. */
  503. protected function _cleanMetadatas()
  504. {
  505. $this->_metadatasArray = array();
  506. }
  507. /**
  508. * Load metadatas from disk
  509. *
  510. * @param string $id Cache id
  511. * @return array|false Metadatas associative array
  512. */
  513. protected function _loadMetadatas($id)
  514. {
  515. $file = $this->_metadatasFile($id);
  516. $result = $this->_fileGetContents($file);
  517. if (!$result) {
  518. return false;
  519. }
  520. $tmp = @unserialize($result);
  521. return $tmp;
  522. }
  523. /**
  524. * Save metadatas to disk
  525. *
  526. * @param string $id Cache id
  527. * @param array $metadatas Associative array
  528. * @return boolean True if no problem
  529. */
  530. protected function _saveMetadatas($id, $metadatas)
  531. {
  532. $file = $this->_metadatasFile($id);
  533. $result = $this->_filePutContents($file, serialize($metadatas));
  534. if (!$result) {
  535. return false;
  536. }
  537. return true;
  538. }
  539. /**
  540. * Make and return a file name (with path) for metadatas
  541. *
  542. * @param string $id Cache id
  543. * @return string Metadatas file name (with path)
  544. */
  545. protected function _metadatasFile($id)
  546. {
  547. $path = $this->_path($id);
  548. $fileName = $this->_idToFileName('internal-metadatas---' . $id);
  549. return $path . $fileName;
  550. }
  551. /**
  552. * Check if the given filename is a metadatas one
  553. *
  554. * @param string $fileName File name
  555. * @return boolean True if it's a metadatas one
  556. */
  557. protected function _isMetadatasFile($fileName)
  558. {
  559. $id = $this->_fileNameToId($fileName);
  560. if (substr($id, 0, 21) == 'internal-metadatas---') {
  561. return true;
  562. } else {
  563. return false;
  564. }
  565. }
  566. /**
  567. * Remove a file
  568. *
  569. * If we can't remove the file (because of locks or any problem), we will touch
  570. * the file to invalidate it
  571. *
  572. * @param string $file Complete file path
  573. * @return boolean True if ok
  574. */
  575. protected function _remove($file)
  576. {
  577. if (!is_file($file)) {
  578. return false;
  579. }
  580. if (!@unlink($file)) {
  581. # we can't remove the file (because of locks or any problem)
  582. $this->_log("Zend_Cache_Backend_File::_remove() : we can't remove $file");
  583. return false;
  584. }
  585. return true;
  586. }
  587. /**
  588. * Clean some cache records (protected method used for recursive stuff)
  589. *
  590. * Available modes are :
  591. * Zend_Cache::CLEANING_MODE_ALL (default) => remove all cache entries ($tags is not used)
  592. * Zend_Cache::CLEANING_MODE_OLD => remove too old cache entries ($tags is not used)
  593. * Zend_Cache::CLEANING_MODE_MATCHING_TAG => remove cache entries matching all given tags
  594. * ($tags can be an array of strings or a single string)
  595. * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags}
  596. * ($tags can be an array of strings or a single string)
  597. * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags
  598. * ($tags can be an array of strings or a single string)
  599. *
  600. * @param string $dir Directory to clean
  601. * @param string $mode Clean mode
  602. * @param array $tags Array of tags
  603. * @throws Zend_Cache_Exception
  604. * @return boolean True if no problem
  605. */
  606. protected function _clean($dir, $mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
  607. {
  608. if (!is_dir($dir)) {
  609. return false;
  610. }
  611. $result = true;
  612. $prefix = $this->_options['file_name_prefix'];
  613. $glob = @glob($dir . $prefix . '--*');
  614. if ($glob === false) {
  615. return true;
  616. }
  617. foreach ($glob as $file) {
  618. if (is_file($file)) {
  619. $fileName = basename($file);
  620. if ($this->_isMetadatasFile($fileName)) {
  621. // in CLEANING_MODE_ALL, we drop anything, even remainings old metadatas files
  622. if ($mode != Zend_Cache::CLEANING_MODE_ALL) {
  623. continue;
  624. }
  625. }
  626. $id = $this->_fileNameToId($fileName);
  627. $metadatas = $this->_getMetadatas($id);
  628. if ($metadatas === FALSE) {
  629. $metadatas = array('expire' => 1, 'tags' => array());
  630. }
  631. switch ($mode) {
  632. case Zend_Cache::CLEANING_MODE_ALL:
  633. $res = $this->remove($id);
  634. if (!$res) {
  635. // in this case only, we accept a problem with the metadatas file drop
  636. $res = $this->_remove($file);
  637. }
  638. $result = $result && $res;
  639. break;
  640. case Zend_Cache::CLEANING_MODE_OLD:
  641. if (time() > $metadatas['expire']) {
  642. $result = ($result) && ($this->remove($id));
  643. }
  644. break;
  645. case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
  646. $matching = true;
  647. foreach ($tags as $tag) {
  648. if (!in_array($tag, $metadatas['tags'])) {
  649. $matching = false;
  650. break;
  651. }
  652. }
  653. if ($matching) {
  654. $result = ($result) && ($this->remove($id));
  655. }
  656. break;
  657. case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
  658. $matching = false;
  659. foreach ($tags as $tag) {
  660. if (in_array($tag, $metadatas['tags'])) {
  661. $matching = true;
  662. break;
  663. }
  664. }
  665. if (!$matching) {
  666. $result = ($result) && $this->remove($id);
  667. }
  668. break;
  669. case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
  670. $matching = false;
  671. foreach ($tags as $tag) {
  672. if (in_array($tag, $metadatas['tags'])) {
  673. $matching = true;
  674. break;
  675. }
  676. }
  677. if ($matching) {
  678. $result = ($result) && ($this->remove($id));
  679. }
  680. break;
  681. default:
  682. Zend_Cache::throwException('Invalid mode for clean() method');
  683. break;
  684. }
  685. }
  686. if ((is_dir($file)) and ($this->_options['hashed_directory_level']>0)) {
  687. // Recursive call
  688. $result = ($result) && ($this->_clean($file . DIRECTORY_SEPARATOR, $mode, $tags));
  689. if ($mode=='all') {
  690. // if mode=='all', we try to drop the structure too
  691. @rmdir($file);
  692. }
  693. }
  694. }
  695. return $result;
  696. }
  697. protected function _get($dir, $mode, $tags = array())
  698. {
  699. if (!is_dir($dir)) {
  700. return false;
  701. }
  702. $result = array();
  703. $prefix = $this->_options['file_name_prefix'];
  704. $glob = @glob($dir . $prefix . '--*');
  705. if ($glob === false) {
  706. return true;
  707. }
  708. foreach ($glob as $file) {
  709. if (is_file($file)) {
  710. $fileName = basename($file);
  711. $id = $this->_fileNameToId($fileName);
  712. $metadatas = $this->_getMetadatas($id);
  713. if ($metadatas === FALSE) {
  714. continue;
  715. }
  716. if (time() > $metadatas['expire']) {
  717. continue;
  718. }
  719. switch ($mode) {
  720. case 'ids':
  721. $result[] = $id;
  722. break;
  723. case 'tags':
  724. $result = array_unique(array_merge($result, $metadatas['tags']));
  725. break;
  726. case 'matching':
  727. $matching = true;
  728. foreach ($tags as $tag) {
  729. if (!in_array($tag, $metadatas['tags'])) {
  730. $matching = false;
  731. break;
  732. }
  733. }
  734. if ($matching) {
  735. $result[] = $id;
  736. }
  737. break;
  738. case 'notMatching':
  739. $matching = false;
  740. foreach ($tags as $tag) {
  741. if (in_array($tag, $metadatas['tags'])) {
  742. $matching = true;
  743. break;
  744. }
  745. }
  746. if (!$matching) {
  747. $result[] = $id;
  748. }
  749. break;
  750. case 'matchingAny':
  751. $matching = false;
  752. foreach ($tags as $tag) {
  753. if (in_array($tag, $metadatas['tags'])) {
  754. $matching = true;
  755. break;
  756. }
  757. }
  758. if ($matching) {
  759. $result[] = $id;
  760. }
  761. break;
  762. default:
  763. Zend_Cache::throwException('Invalid mode for _get() method');
  764. break;
  765. }
  766. }
  767. if ((is_dir($file)) and ($this->_options['hashed_directory_level']>0)) {
  768. // Recursive call
  769. $result = array_unique(array_merge($result, $this->_get($file . DIRECTORY_SEPARATOR, $mode, $tags)));
  770. }
  771. }
  772. return array_unique($result);
  773. }
  774. /**
  775. * Compute & return the expire time
  776. *
  777. * @return int expire time (unix timestamp)
  778. */
  779. protected function _expireTime($lifetime)
  780. {
  781. if ($lifetime === null) {
  782. return 9999999999;
  783. }
  784. return time() + $lifetime;
  785. }
  786. /**
  787. * Make a control key with the string containing datas
  788. *
  789. * @param string $data Data
  790. * @param string $controlType Type of control 'md5', 'crc32' or 'strlen'
  791. * @throws Zend_Cache_Exception
  792. * @return string Control key
  793. */
  794. protected function _hash($data, $controlType)
  795. {
  796. switch ($controlType) {
  797. case 'md5':
  798. return md5($data);
  799. case 'crc32':
  800. return crc32($data);
  801. case 'strlen':
  802. return strlen($data);
  803. case 'adler32':
  804. return hash('adler32', $data);
  805. default:
  806. Zend_Cache::throwException("Incorrect hash function : $controlType");
  807. }
  808. }
  809. /**
  810. * Transform a cache id into a file name and return it
  811. *
  812. * @param string $id Cache id
  813. * @return string File name
  814. */
  815. protected function _idToFileName($id)
  816. {
  817. $prefix = $this->_options['file_name_prefix'];
  818. $result = $prefix . '---' . $id;
  819. return $result;
  820. }
  821. /**
  822. * Make and return a file name (with path)
  823. *
  824. * @param string $id Cache id
  825. * @return string File name (with path)
  826. */
  827. protected function _file($id)
  828. {
  829. $path = $this->_path($id);
  830. $fileName = $this->_idToFileName($id);
  831. return $path . $fileName;
  832. }
  833. /**
  834. * Return the complete directory path of a filename (including hashedDirectoryStructure)
  835. *
  836. * @param string $id Cache id
  837. * @param boolean $parts if true, returns array of directory parts instead of single string
  838. * @return string Complete directory path
  839. */
  840. protected function _path($id, $parts = false)
  841. {
  842. $partsArray = array();
  843. $root = $this->_options['cache_dir'];
  844. $prefix = $this->_options['file_name_prefix'];
  845. if ($this->_options['hashed_directory_level']>0) {
  846. $hash = hash('adler32', $id);
  847. for ($i=0 ; $i < $this->_options['hashed_directory_level'] ; $i++) {
  848. $root = $root . $prefix . '--' . substr($hash, 0, $i + 1) . DIRECTORY_SEPARATOR;
  849. $partsArray[] = $root;
  850. }
  851. }
  852. if ($parts) {
  853. return $partsArray;
  854. } else {
  855. return $root;
  856. }
  857. }
  858. /**
  859. * Make the directory strucuture for the given id
  860. *
  861. * @param string $id cache id
  862. * @return boolean true
  863. */
  864. protected function _recursiveMkdirAndChmod($id)
  865. {
  866. if ($this->_options['hashed_directory_level'] <=0) {
  867. return true;
  868. }
  869. $partsArray = $this->_path($id, true);
  870. foreach ($partsArray as $part) {
  871. if (!is_dir($part)) {
  872. @mkdir($part, $this->_options['hashed_directory_umask']);
  873. @chmod($part, $this->_options['hashed_directory_umask']); // see #ZF-320 (this line is required in some configurations)
  874. }
  875. }
  876. return true;
  877. }
  878. /**
  879. * Test if the given cache id is available (and still valid as a cache record)
  880. *
  881. * @param string $id Cache id
  882. * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
  883. * @return boolean|mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record
  884. */
  885. protected function _test($id, $doNotTestCacheValidity)
  886. {
  887. $metadatas = $this->_getMetadatas($id);
  888. if (!$metadatas) {
  889. return false;
  890. }
  891. if ($doNotTestCacheValidity || (time() <= $metadatas['expire'])) {
  892. return $metadatas['mtime'];
  893. }
  894. return false;
  895. }
  896. /**
  897. * Return the file content of the given file
  898. *
  899. * @param string $file File complete path
  900. * @return string File content (or false if problem)
  901. */
  902. protected function _fileGetContents($file)
  903. {
  904. $result = false;
  905. if (!is_file($file)) {
  906. return false;
  907. }
  908. $f = @fopen($file, 'rb');
  909. if ($f) {
  910. if ($this->_options['file_locking']) @flock($f, LOCK_SH);
  911. $result = stream_get_contents($f);
  912. if ($this->_options['file_locking']) @flock($f, LOCK_UN);
  913. @fclose($f);
  914. }
  915. return $result;
  916. }
  917. /**
  918. * Put the given string into the given file
  919. *
  920. * @param string $file File complete path
  921. * @param string $string String to put in file
  922. * @return boolean true if no problem
  923. */
  924. protected function _filePutContents($file, $string)
  925. {
  926. $result = false;
  927. $f = @fopen($file, 'ab+');
  928. if ($f) {
  929. if ($this->_options['file_locking']) @flock($f, LOCK_EX);
  930. fseek($f, 0);
  931. ftruncate($f, 0);
  932. $tmp = @fwrite($f, $string);
  933. if (!($tmp === FALSE)) {
  934. $result = true;
  935. }
  936. @fclose($f);
  937. }
  938. @chmod($file, $this->_options['cache_file_umask']);
  939. return $result;
  940. }
  941. /**
  942. * Transform a file name into cache id and return it
  943. *
  944. * @param string $fileName File name
  945. * @return string Cache id
  946. */
  947. protected function _fileNameToId($fileName)
  948. {
  949. $prefix = $this->_options['file_name_prefix'];
  950. return preg_replace('~^' . $prefix . '---(.*)$~', '$1', $fileName);
  951. }
  952. }