PageRenderTime 56ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/src/frapi/library/Zend/Cache/Backend/File.php

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