File indexing completed on 2024-05-26 06:02:46

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_Cache
0017  * @subpackage Zend_Cache_Backend
0018  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0019  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0020  * @version    $Id$
0021  */
0022 
0023 /**
0024  * @see Zend_Cache_Backend_Interface
0025  */
0026 // require_once 'Zend/Cache/Backend/ExtendedInterface.php';
0027 
0028 /**
0029  * @see Zend_Cache_Backend
0030  */
0031 // require_once 'Zend/Cache/Backend.php';
0032 
0033 
0034 /**
0035  * @package    Zend_Cache
0036  * @subpackage Zend_Cache_Backend
0037  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0038  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0039  */
0040 class Zend_Cache_Backend_File extends Zend_Cache_Backend implements Zend_Cache_Backend_ExtendedInterface
0041 {
0042     /**
0043      * Available options
0044      *
0045      * =====> (string) cache_dir :
0046      * - Directory where to put the cache files
0047      *
0048      * =====> (boolean) file_locking :
0049      * - Enable / disable file_locking
0050      * - Can avoid cache corruption under bad circumstances but it doesn't work on multithread
0051      * webservers and on NFS filesystems for example
0052      *
0053      * =====> (boolean) read_control :
0054      * - Enable / disable read control
0055      * - If enabled, a control key is embeded in cache file and this key is compared with the one
0056      * calculated after the reading.
0057      *
0058      * =====> (string) read_control_type :
0059      * - Type of read control (only if read control is enabled). Available values are :
0060      *   'md5' for a md5 hash control (best but slowest)
0061      *   'crc32' for a crc32 hash control (lightly less safe but faster, better choice)
0062      *   'adler32' for an adler32 hash control (excellent choice too, faster than crc32)
0063      *   'strlen' for a length only test (fastest)
0064      *
0065      * =====> (int) hashed_directory_level :
0066      * - Hashed directory level
0067      * - Set the hashed directory structure level. 0 means "no hashed directory
0068      * structure", 1 means "one level of directory", 2 means "two levels"...
0069      * This option can speed up the cache only when you have many thousands of
0070      * cache file. Only specific benchs can help you to choose the perfect value
0071      * for you. Maybe, 1 or 2 is a good start.
0072      *
0073      * =====> (int) hashed_directory_umask :
0074      * - deprecated
0075      * - Permissions for hashed directory structure
0076      *
0077      * =====> (int) hashed_directory_perm :
0078      * - Permissions for hashed directory structure
0079      *
0080      * =====> (string) file_name_prefix :
0081      * - prefix for cache files
0082      * - be really carefull with this option because a too generic value in a system cache dir
0083      *   (like /tmp) can cause disasters when cleaning the cache
0084      *
0085      * =====> (int) cache_file_umask :
0086      * - deprecated
0087      * - Permissions for cache files
0088      *
0089      * =====> (int) cache_file_perm :
0090      * - Permissions for cache files
0091      *
0092      * =====> (int) metatadatas_array_max_size :
0093      * - max size for the metadatas array (don't change this value unless you
0094      *   know what you are doing)
0095      *
0096      * @var array available options
0097      */
0098     protected $_options = array(
0099         'cache_dir' => null,
0100         'file_locking' => true,
0101         'read_control' => true,
0102         'read_control_type' => 'crc32',
0103         'hashed_directory_level' => 0,
0104         'hashed_directory_perm' => 0700,
0105         'file_name_prefix' => 'zend_cache',
0106         'cache_file_perm' => 0600,
0107         'metadatas_array_max_size' => 100
0108     );
0109 
0110     /**
0111      * Array of metadatas (each item is an associative array)
0112      *
0113      * @var array
0114      */
0115     protected $_metadatasArray = array();
0116 
0117 
0118     /**
0119      * Constructor
0120      *
0121      * @param  array $options associative array of options
0122      * @throws Zend_Cache_Exception
0123      */
0124     public function __construct(array $options = array())
0125     {
0126         parent::__construct($options);
0127         if ($this->_options['cache_dir'] !== null) { // particular case for this option
0128             $this->setCacheDir($this->_options['cache_dir']);
0129         } else {
0130             $this->setCacheDir(self::getTmpDir() . DIRECTORY_SEPARATOR, false);
0131         }
0132         if (isset($this->_options['file_name_prefix'])) { // particular case for this option
0133             if (!preg_match('~^[a-zA-Z0-9_]+$~D', $this->_options['file_name_prefix'])) {
0134                 Zend_Cache::throwException('Invalid file_name_prefix : must use only [a-zA-Z0-9_]');
0135             }
0136         }
0137         if ($this->_options['metadatas_array_max_size'] < 10) {
0138             Zend_Cache::throwException('Invalid metadatas_array_max_size, must be > 10');
0139         }
0140 
0141         if (isset($options['hashed_directory_umask'])) {
0142             // See #ZF-12047
0143             trigger_error("'hashed_directory_umask' is deprecated -> please use 'hashed_directory_perm' instead", E_USER_NOTICE);
0144             if (!isset($options['hashed_directory_perm'])) {
0145                 $options['hashed_directory_perm'] = $options['hashed_directory_umask'];
0146             }
0147         }
0148         if (isset($options['hashed_directory_perm']) && is_string($options['hashed_directory_perm'])) {
0149             // See #ZF-4422
0150             $this->_options['hashed_directory_perm'] = octdec($this->_options['hashed_directory_perm']);
0151         }
0152 
0153         if (isset($options['cache_file_umask'])) {
0154             // See #ZF-12047
0155             trigger_error("'cache_file_umask' is deprecated -> please use 'cache_file_perm' instead", E_USER_NOTICE);
0156             if (!isset($options['cache_file_perm'])) {
0157                 $options['cache_file_perm'] = $options['cache_file_umask'];
0158             }
0159         }
0160         if (isset($options['cache_file_perm']) && is_string($options['cache_file_perm'])) {
0161             // See #ZF-4422
0162             $this->_options['cache_file_perm'] = octdec($this->_options['cache_file_perm']);
0163         }
0164     }
0165 
0166     /**
0167      * Set the cache_dir (particular case of setOption() method)
0168      *
0169      * @param  string  $value
0170      * @param  boolean $trailingSeparator If true, add a trailing separator is necessary
0171      * @throws Zend_Cache_Exception
0172      * @return void
0173      */
0174     public function setCacheDir($value, $trailingSeparator = true)
0175     {
0176         if (!is_dir($value)) {
0177             Zend_Cache::throwException(sprintf('cache_dir "%s" must be a directory', $value));
0178         }
0179         if (!is_writable($value)) {
0180             Zend_Cache::throwException(sprintf('cache_dir "%s" is not writable', $value));
0181         }
0182         if ($trailingSeparator) {
0183             // add a trailing DIRECTORY_SEPARATOR if necessary
0184             $value = rtrim(realpath($value), '\\/') . DIRECTORY_SEPARATOR;
0185         }
0186         $this->_options['cache_dir'] = $value;
0187     }
0188 
0189     /**
0190      * Test if a cache is available for the given id and (if yes) return it (false else)
0191      *
0192      * @param string $id cache id
0193      * @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested
0194      * @return string|false cached datas
0195      */
0196     public function load($id, $doNotTestCacheValidity = false)
0197     {
0198         if (!($this->_test($id, $doNotTestCacheValidity))) {
0199             // The cache is not hit !
0200             return false;
0201         }
0202         $metadatas = $this->_getMetadatas($id);
0203         $file = $this->_file($id);
0204         $data = $this->_fileGetContents($file);
0205         if ($this->_options['read_control']) {
0206             $hashData = $this->_hash($data, $this->_options['read_control_type']);
0207             $hashControl = $metadatas['hash'];
0208             if ($hashData != $hashControl) {
0209                 // Problem detected by the read control !
0210                 $this->_log('Zend_Cache_Backend_File::load() / read_control : stored hash and computed hash do not match');
0211                 $this->remove($id);
0212                 return false;
0213             }
0214         }
0215         return $data;
0216     }
0217 
0218     /**
0219      * Test if a cache is available or not (for the given id)
0220      *
0221      * @param string $id cache id
0222      * @return mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record
0223      */
0224     public function test($id)
0225     {
0226         clearstatcache();
0227         return $this->_test($id, false);
0228     }
0229 
0230     /**
0231      * Save some string datas into a cache record
0232      *
0233      * Note : $data is always "string" (serialization is done by the
0234      * core not by the backend)
0235      *
0236      * @param  string      $data             Datas to cache
0237      * @param  string      $id               Cache id
0238      * @param  array       $tags             Array of strings, the cache record will be tagged by each string entry
0239      * @param  boolean|int $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
0240      * @return boolean true if no problem
0241      */
0242     public function save($data, $id, $tags = array(), $specificLifetime = false)
0243     {
0244         clearstatcache();
0245         $file = $this->_file($id);
0246         $path = $this->_path($id);
0247         if ($this->_options['hashed_directory_level'] > 0) {
0248             if (!is_writable($path)) {
0249                 // maybe, we just have to build the directory structure
0250                 $this->_recursiveMkdirAndChmod($id);
0251             }
0252             if (!is_writable($path)) {
0253                 return false;
0254             }
0255         }
0256         if ($this->_options['read_control']) {
0257             $hash = $this->_hash($data, $this->_options['read_control_type']);
0258         } else {
0259             $hash = '';
0260         }
0261         $metadatas = array(
0262             'hash' => $hash,
0263             'mtime' => time(),
0264             'expire' => $this->_expireTime($this->getLifetime($specificLifetime)),
0265             'tags' => $tags
0266         );
0267         $res = $this->_setMetadatas($id, $metadatas);
0268         if (!$res) {
0269             $this->_log('Zend_Cache_Backend_File::save() / error on saving metadata');
0270             return false;
0271         }
0272         $res = $this->_filePutContents($file, $data);
0273         return $res;
0274     }
0275 
0276     /**
0277      * Remove a cache record
0278      *
0279      * @param  string $id cache id
0280      * @return boolean true if no problem
0281      */
0282     public function remove($id)
0283     {
0284         $file = $this->_file($id);
0285         $boolRemove   = $this->_remove($file);
0286         $boolMetadata = $this->_delMetadatas($id);
0287         return $boolMetadata && $boolRemove;
0288     }
0289 
0290     /**
0291      * Clean some cache records
0292      *
0293      * Available modes are :
0294      *
0295      * Zend_Cache::CLEANING_MODE_ALL (default)    => remove all cache entries ($tags is not used)
0296      * Zend_Cache::CLEANING_MODE_OLD              => remove too old cache entries ($tags is not used)
0297      * Zend_Cache::CLEANING_MODE_MATCHING_TAG     => remove cache entries matching all given tags
0298      *                                               ($tags can be an array of strings or a single string)
0299      * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags}
0300      *                                               ($tags can be an array of strings or a single string)
0301      * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags
0302      *                                               ($tags can be an array of strings or a single string)
0303      *
0304      * @param string $mode clean mode
0305      * @param array $tags array of tags
0306      * @return boolean true if no problem
0307      */
0308     public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
0309     {
0310         // We use this protected method to hide the recursive stuff
0311         clearstatcache();
0312         return $this->_clean($this->_options['cache_dir'], $mode, $tags);
0313     }
0314 
0315     /**
0316      * Return an array of stored cache ids
0317      *
0318      * @return array array of stored cache ids (string)
0319      */
0320     public function getIds()
0321     {
0322         return $this->_get($this->_options['cache_dir'], 'ids', array());
0323     }
0324 
0325     /**
0326      * Return an array of stored tags
0327      *
0328      * @return array array of stored tags (string)
0329      */
0330     public function getTags()
0331     {
0332         return $this->_get($this->_options['cache_dir'], 'tags', array());
0333     }
0334 
0335     /**
0336      * Return an array of stored cache ids which match given tags
0337      *
0338      * In case of multiple tags, a logical AND is made between tags
0339      *
0340      * @param array $tags array of tags
0341      * @return array array of matching cache ids (string)
0342      */
0343     public function getIdsMatchingTags($tags = array())
0344     {
0345         return $this->_get($this->_options['cache_dir'], 'matching', $tags);
0346     }
0347 
0348     /**
0349      * Return an array of stored cache ids which don't match given tags
0350      *
0351      * In case of multiple tags, a logical OR is made between tags
0352      *
0353      * @param array $tags array of tags
0354      * @return array array of not matching cache ids (string)
0355      */
0356     public function getIdsNotMatchingTags($tags = array())
0357     {
0358         return $this->_get($this->_options['cache_dir'], 'notMatching', $tags);
0359     }
0360 
0361     /**
0362      * Return an array of stored cache ids which match any given tags
0363      *
0364      * In case of multiple tags, a logical AND is made between tags
0365      *
0366      * @param array $tags array of tags
0367      * @return array array of any matching cache ids (string)
0368      */
0369     public function getIdsMatchingAnyTags($tags = array())
0370     {
0371         return $this->_get($this->_options['cache_dir'], 'matchingAny', $tags);
0372     }
0373 
0374     /**
0375      * Return the filling percentage of the backend storage
0376      *
0377      * @throws Zend_Cache_Exception
0378      * @return int integer between 0 and 100
0379      */
0380     public function getFillingPercentage()
0381     {
0382         $free = disk_free_space($this->_options['cache_dir']);
0383         $total = disk_total_space($this->_options['cache_dir']);
0384         if ($total == 0) {
0385             Zend_Cache::throwException('can\'t get disk_total_space');
0386         } else {
0387             if ($free >= $total) {
0388                 return 100;
0389             }
0390             return ((int) (100. * ($total - $free) / $total));
0391         }
0392     }
0393 
0394     /**
0395      * Return an array of metadatas for the given cache id
0396      *
0397      * The array must include these keys :
0398      * - expire : the expire timestamp
0399      * - tags : a string array of tags
0400      * - mtime : timestamp of last modification time
0401      *
0402      * @param string $id cache id
0403      * @return array array of metadatas (false if the cache id is not found)
0404      */
0405     public function getMetadatas($id)
0406     {
0407         $metadatas = $this->_getMetadatas($id);
0408         if (!$metadatas) {
0409             return false;
0410         }
0411         if (time() > $metadatas['expire']) {
0412             return false;
0413         }
0414         return array(
0415             'expire' => $metadatas['expire'],
0416             'tags' => $metadatas['tags'],
0417             'mtime' => $metadatas['mtime']
0418         );
0419     }
0420 
0421     /**
0422      * Give (if possible) an extra lifetime to the given cache id
0423      *
0424      * @param string $id cache id
0425      * @param int $extraLifetime
0426      * @return boolean true if ok
0427      */
0428     public function touch($id, $extraLifetime)
0429     {
0430         $metadatas = $this->_getMetadatas($id);
0431         if (!$metadatas) {
0432             return false;
0433         }
0434         if (time() > $metadatas['expire']) {
0435             return false;
0436         }
0437         $newMetadatas = array(
0438             'hash' => $metadatas['hash'],
0439             'mtime' => time(),
0440             'expire' => $metadatas['expire'] + $extraLifetime,
0441             'tags' => $metadatas['tags']
0442         );
0443         $res = $this->_setMetadatas($id, $newMetadatas);
0444         if (!$res) {
0445             return false;
0446         }
0447         return true;
0448     }
0449 
0450     /**
0451      * Return an associative array of capabilities (booleans) of the backend
0452      *
0453      * The array must include these keys :
0454      * - automatic_cleaning (is automating cleaning necessary)
0455      * - tags (are tags supported)
0456      * - expired_read (is it possible to read expired cache records
0457      *                 (for doNotTestCacheValidity option for example))
0458      * - priority does the backend deal with priority when saving
0459      * - infinite_lifetime (is infinite lifetime can work with this backend)
0460      * - get_list (is it possible to get the list of cache ids and the complete list of tags)
0461      *
0462      * @return array associative of with capabilities
0463      */
0464     public function getCapabilities()
0465     {
0466         return array(
0467             'automatic_cleaning' => true,
0468             'tags' => true,
0469             'expired_read' => true,
0470             'priority' => false,
0471             'infinite_lifetime' => true,
0472             'get_list' => true
0473         );
0474     }
0475 
0476     /**
0477      * PUBLIC METHOD FOR UNIT TESTING ONLY !
0478      *
0479      * Force a cache record to expire
0480      *
0481      * @param string $id cache id
0482      */
0483     public function ___expire($id)
0484     {
0485         $metadatas = $this->_getMetadatas($id);
0486         if ($metadatas) {
0487             $metadatas['expire'] = 1;
0488             $this->_setMetadatas($id, $metadatas);
0489         }
0490     }
0491 
0492     /**
0493      * Get a metadatas record
0494      *
0495      * @param  string $id  Cache id
0496      * @return array|false Associative array of metadatas
0497      */
0498     protected function _getMetadatas($id)
0499     {
0500         if (isset($this->_metadatasArray[$id])) {
0501             return $this->_metadatasArray[$id];
0502         } else {
0503             $metadatas = $this->_loadMetadatas($id);
0504             if (!$metadatas) {
0505                 return false;
0506             }
0507             $this->_setMetadatas($id, $metadatas, false);
0508             return $metadatas;
0509         }
0510     }
0511 
0512     /**
0513      * Set a metadatas record
0514      *
0515      * @param  string $id        Cache id
0516      * @param  array  $metadatas Associative array of metadatas
0517      * @param  boolean $save     optional pass false to disable saving to file
0518      * @return boolean True if no problem
0519      */
0520     protected function _setMetadatas($id, $metadatas, $save = true)
0521     {
0522         if (count($this->_metadatasArray) >= $this->_options['metadatas_array_max_size']) {
0523             $n = (int) ($this->_options['metadatas_array_max_size'] / 10);
0524             $this->_metadatasArray = array_slice($this->_metadatasArray, $n);
0525         }
0526         if ($save) {
0527             $result = $this->_saveMetadatas($id, $metadatas);
0528             if (!$result) {
0529                 return false;
0530             }
0531         }
0532         $this->_metadatasArray[$id] = $metadatas;
0533         return true;
0534     }
0535 
0536     /**
0537      * Drop a metadata record
0538      *
0539      * @param  string $id Cache id
0540      * @return boolean True if no problem
0541      */
0542     protected function _delMetadatas($id)
0543     {
0544         if (isset($this->_metadatasArray[$id])) {
0545             unset($this->_metadatasArray[$id]);
0546         }
0547         $file = $this->_metadatasFile($id);
0548         return $this->_remove($file);
0549     }
0550 
0551     /**
0552      * Clear the metadatas array
0553      *
0554      * @return void
0555      */
0556     protected function _cleanMetadatas()
0557     {
0558         $this->_metadatasArray = array();
0559     }
0560 
0561     /**
0562      * Load metadatas from disk
0563      *
0564      * @param  string $id Cache id
0565      * @return array|false Metadatas associative array
0566      */
0567     protected function _loadMetadatas($id)
0568     {
0569         $file = $this->_metadatasFile($id);
0570         $result = $this->_fileGetContents($file);
0571         if (!$result) {
0572             return false;
0573         }
0574         $tmp = @unserialize($result);
0575         return $tmp;
0576     }
0577 
0578     /**
0579      * Save metadatas to disk
0580      *
0581      * @param  string $id        Cache id
0582      * @param  array  $metadatas Associative array
0583      * @return boolean True if no problem
0584      */
0585     protected function _saveMetadatas($id, $metadatas)
0586     {
0587         $file = $this->_metadatasFile($id);
0588         $result = $this->_filePutContents($file, serialize($metadatas));
0589         if (!$result) {
0590             return false;
0591         }
0592         return true;
0593     }
0594 
0595     /**
0596      * Make and return a file name (with path) for metadatas
0597      *
0598      * @param  string $id Cache id
0599      * @return string Metadatas file name (with path)
0600      */
0601     protected function _metadatasFile($id)
0602     {
0603         $path = $this->_path($id);
0604         $fileName = $this->_idToFileName('internal-metadatas---' . $id);
0605         return $path . $fileName;
0606     }
0607 
0608     /**
0609      * Check if the given filename is a metadatas one
0610      *
0611      * @param  string $fileName File name
0612      * @return boolean True if it's a metadatas one
0613      */
0614     protected function _isMetadatasFile($fileName)
0615     {
0616         $id = $this->_fileNameToId($fileName);
0617         if (substr($id, 0, 21) == 'internal-metadatas---') {
0618             return true;
0619         } else {
0620             return false;
0621         }
0622     }
0623 
0624     /**
0625      * Remove a file
0626      *
0627      * If we can't remove the file (because of locks or any problem), we will touch
0628      * the file to invalidate it
0629      *
0630      * @param  string $file Complete file path
0631      * @return boolean True if ok
0632      */
0633     protected function _remove($file)
0634     {
0635         if (!is_file($file)) {
0636             return false;
0637         }
0638         if (!@unlink($file)) {
0639             # we can't remove the file (because of locks or any problem)
0640             $this->_log("Zend_Cache_Backend_File::_remove() : we can't remove $file");
0641             return false;
0642         }
0643         return true;
0644     }
0645 
0646     /**
0647      * Clean some cache records (protected method used for recursive stuff)
0648      *
0649      * Available modes are :
0650      * Zend_Cache::CLEANING_MODE_ALL (default)    => remove all cache entries ($tags is not used)
0651      * Zend_Cache::CLEANING_MODE_OLD              => remove too old cache entries ($tags is not used)
0652      * Zend_Cache::CLEANING_MODE_MATCHING_TAG     => remove cache entries matching all given tags
0653      *                                               ($tags can be an array of strings or a single string)
0654      * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags}
0655      *                                               ($tags can be an array of strings or a single string)
0656      * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags
0657      *                                               ($tags can be an array of strings or a single string)
0658      *
0659      * @param  string $dir  Directory to clean
0660      * @param  string $mode Clean mode
0661      * @param  array  $tags Array of tags
0662      * @throws Zend_Cache_Exception
0663      * @return boolean True if no problem
0664      */
0665     protected function _clean($dir, $mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
0666     {
0667         if (!is_dir($dir)) {
0668             return false;
0669         }
0670         $result = true;
0671         $prefix = $this->_options['file_name_prefix'];
0672         $glob = @glob($dir . $prefix . '--*');
0673         if ($glob === false) {
0674             // On some systems it is impossible to distinguish between empty match and an error.
0675             return true;
0676         }
0677         $metadataFiles = array();
0678         foreach ($glob as $file)  {
0679             if (is_file($file)) {
0680                 $fileName = basename($file);
0681                 if ($this->_isMetadatasFile($fileName)) {
0682                     // In CLEANING_MODE_ALL, we drop anything, even remainings old metadatas files.
0683                     // To do that, we need to save the list of the metadata files first.
0684                     if ($mode == Zend_Cache::CLEANING_MODE_ALL) {
0685                         $metadataFiles[] = $file;
0686                     }
0687                     continue;
0688                 }
0689                 $id = $this->_fileNameToId($fileName);
0690                 $metadatas = $this->_getMetadatas($id);
0691                 if ($metadatas === FALSE) {
0692                     $metadatas = array('expire' => 1, 'tags' => array());
0693                 }
0694                 switch ($mode) {
0695                     case Zend_Cache::CLEANING_MODE_ALL:
0696                         $result = $result && $this->remove($id);
0697                         break;
0698                     case Zend_Cache::CLEANING_MODE_OLD:
0699                         if (time() > $metadatas['expire']) {
0700                             $result = $this->remove($id) && $result;
0701                         }
0702                         break;
0703                     case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
0704                         $matching = true;
0705                         foreach ($tags as $tag) {
0706                             if (!in_array($tag, $metadatas['tags'])) {
0707                                 $matching = false;
0708                                 break;
0709                             }
0710                         }
0711                         if ($matching) {
0712                             $result = $this->remove($id) && $result;
0713                         }
0714                         break;
0715                     case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
0716                         $matching = false;
0717                         foreach ($tags as $tag) {
0718                             if (in_array($tag, $metadatas['tags'])) {
0719                                 $matching = true;
0720                                 break;
0721                             }
0722                         }
0723                         if (!$matching) {
0724                             $result = $this->remove($id) && $result;
0725                         }
0726                         break;
0727                     case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
0728                         $matching = false;
0729                         foreach ($tags as $tag) {
0730                             if (in_array($tag, $metadatas['tags'])) {
0731                                 $matching = true;
0732                                 break;
0733                             }
0734                         }
0735                         if ($matching) {
0736                             $result = $this->remove($id) && $result;
0737                         }
0738                         break;
0739                     default:
0740                         Zend_Cache::throwException('Invalid mode for clean() method');
0741                         break;
0742                 }
0743             }
0744             if ((is_dir($file)) and ($this->_options['hashed_directory_level']>0)) {
0745                 // Recursive call
0746                 $result = $this->_clean($file . DIRECTORY_SEPARATOR, $mode, $tags) && $result;
0747                 if ($mode == Zend_Cache::CLEANING_MODE_ALL) {
0748                     // we try to drop the structure too
0749                     @rmdir($file);
0750                 }
0751             }
0752         }
0753 
0754         // cycle through metadataFiles and delete orphaned ones
0755         foreach ($metadataFiles as $file) {
0756             if (file_exists($file)) {
0757                 $result = $this->_remove($file) && $result;
0758             }
0759         }
0760 
0761         return $result;
0762     }
0763 
0764     protected function _get($dir, $mode, $tags = array())
0765     {
0766         if (!is_dir($dir)) {
0767             return false;
0768         }
0769         $result = array();
0770         $prefix = $this->_options['file_name_prefix'];
0771         $glob = @glob($dir . $prefix . '--*');
0772         if ($glob === false) {
0773             // On some systems it is impossible to distinguish between empty match and an error.
0774             return array();
0775         }
0776         foreach ($glob as $file)  {
0777             if (is_file($file)) {
0778                 $fileName = basename($file);
0779                 $id = $this->_fileNameToId($fileName);
0780                 $metadatas = $this->_getMetadatas($id);
0781                 if ($metadatas === FALSE) {
0782                     continue;
0783                 }
0784                 if (time() > $metadatas['expire']) {
0785                     continue;
0786                 }
0787                 switch ($mode) {
0788                     case 'ids':
0789                         $result[] = $id;
0790                         break;
0791                     case 'tags':
0792                         $result = array_unique(array_merge($result, $metadatas['tags']));
0793                         break;
0794                     case 'matching':
0795                         $matching = true;
0796                         foreach ($tags as $tag) {
0797                             if (!in_array($tag, $metadatas['tags'])) {
0798                                 $matching = false;
0799                                 break;
0800                             }
0801                         }
0802                         if ($matching) {
0803                             $result[] = $id;
0804                         }
0805                         break;
0806                     case 'notMatching':
0807                         $matching = false;
0808                         foreach ($tags as $tag) {
0809                             if (in_array($tag, $metadatas['tags'])) {
0810                                 $matching = true;
0811                                 break;
0812                             }
0813                         }
0814                         if (!$matching) {
0815                             $result[] = $id;
0816                         }
0817                         break;
0818                     case 'matchingAny':
0819                         $matching = false;
0820                         foreach ($tags as $tag) {
0821                             if (in_array($tag, $metadatas['tags'])) {
0822                                 $matching = true;
0823                                 break;
0824                             }
0825                         }
0826                         if ($matching) {
0827                             $result[] = $id;
0828                         }
0829                         break;
0830                     default:
0831                         Zend_Cache::throwException('Invalid mode for _get() method');
0832                         break;
0833                 }
0834             }
0835             if ((is_dir($file)) and ($this->_options['hashed_directory_level']>0)) {
0836                 // Recursive call
0837                 $recursiveRs =  $this->_get($file . DIRECTORY_SEPARATOR, $mode, $tags);
0838                 if ($recursiveRs === false) {
0839                     $this->_log('Zend_Cache_Backend_File::_get() / recursive call : can\'t list entries of "'.$file.'"');
0840                 } else {
0841                     $result = array_unique(array_merge($result, $recursiveRs));
0842                 }
0843             }
0844         }
0845         return array_unique($result);
0846     }
0847 
0848     /**
0849      * Compute & return the expire time
0850      *
0851      * @param  int $lifetime
0852      * @return int expire time (unix timestamp)
0853      */
0854     protected function _expireTime($lifetime)
0855     {
0856         if ($lifetime === null) {
0857             return 9999999999;
0858         }
0859         return time() + $lifetime;
0860     }
0861 
0862     /**
0863      * Make a control key with the string containing datas
0864      *
0865      * @param  string $data        Data
0866      * @param  string $controlType Type of control 'md5', 'crc32' or 'strlen'
0867      * @throws Zend_Cache_Exception
0868      * @return string Control key
0869      */
0870     protected function _hash($data, $controlType)
0871     {
0872         switch ($controlType) {
0873         case 'md5':
0874             return md5($data);
0875         case 'crc32':
0876             return crc32($data);
0877         case 'strlen':
0878             return strlen($data);
0879         case 'adler32':
0880             return hash('adler32', $data);
0881         default:
0882             Zend_Cache::throwException("Incorrect hash function : $controlType");
0883         }
0884     }
0885 
0886     /**
0887      * Transform a cache id into a file name and return it
0888      *
0889      * @param  string $id Cache id
0890      * @return string File name
0891      */
0892     protected function _idToFileName($id)
0893     {
0894         $prefix = $this->_options['file_name_prefix'];
0895         $result = $prefix . '---' . $id;
0896         return $result;
0897     }
0898 
0899     /**
0900      * Make and return a file name (with path)
0901      *
0902      * @param  string $id Cache id
0903      * @return string File name (with path)
0904      */
0905     protected function _file($id)
0906     {
0907         $path = $this->_path($id);
0908         $fileName = $this->_idToFileName($id);
0909         return $path . $fileName;
0910     }
0911 
0912     /**
0913      * Return the complete directory path of a filename (including hashedDirectoryStructure)
0914      *
0915      * @param  string $id Cache id
0916      * @param  boolean $parts if true, returns array of directory parts instead of single string
0917      * @return string Complete directory path
0918      */
0919     protected function _path($id, $parts = false)
0920     {
0921         $partsArray = array();
0922         $root = $this->_options['cache_dir'];
0923         $prefix = $this->_options['file_name_prefix'];
0924         if ($this->_options['hashed_directory_level']>0) {
0925             $hash = hash('adler32', $id);
0926             for ($i=0 ; $i < $this->_options['hashed_directory_level'] ; $i++) {
0927                 $root = $root . $prefix . '--' . substr($hash, 0, $i + 1) . DIRECTORY_SEPARATOR;
0928                 $partsArray[] = $root;
0929             }
0930         }
0931         if ($parts) {
0932             return $partsArray;
0933         } else {
0934             return $root;
0935         }
0936     }
0937 
0938     /**
0939      * Make the directory strucuture for the given id
0940      *
0941      * @param string $id cache id
0942      * @return boolean true
0943      */
0944     protected function _recursiveMkdirAndChmod($id)
0945     {
0946         if ($this->_options['hashed_directory_level'] <=0) {
0947             return true;
0948         }
0949         $partsArray = $this->_path($id, true);
0950         foreach ($partsArray as $part) {
0951             if (!is_dir($part)) {
0952                 @mkdir($part, $this->_options['hashed_directory_perm']);
0953                 @chmod($part, $this->_options['hashed_directory_perm']); // see #ZF-320 (this line is required in some configurations)
0954             }
0955         }
0956         return true;
0957     }
0958 
0959     /**
0960      * Test if the given cache id is available (and still valid as a cache record)
0961      *
0962      * @param  string  $id                     Cache id
0963      * @param  boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
0964      * @return boolean|mixed false (a cache is not available) or "last modified" timestamp (int) of the available cache record
0965      */
0966     protected function _test($id, $doNotTestCacheValidity)
0967     {
0968         $metadatas = $this->_getMetadatas($id);
0969         if (!$metadatas) {
0970             return false;
0971         }
0972         if ($doNotTestCacheValidity || (time() <= $metadatas['expire'])) {
0973             return $metadatas['mtime'];
0974         }
0975         return false;
0976     }
0977 
0978     /**
0979      * Return the file content of the given file
0980      *
0981      * @param  string $file File complete path
0982      * @return string File content (or false if problem)
0983      */
0984     protected function _fileGetContents($file)
0985     {
0986         $result = false;
0987         if (!is_file($file)) {
0988             return false;
0989         }
0990         $f = @fopen($file, 'rb');
0991         if ($f) {
0992             if ($this->_options['file_locking']) @flock($f, LOCK_SH);
0993             $result = stream_get_contents($f);
0994             if ($this->_options['file_locking']) @flock($f, LOCK_UN);
0995             @fclose($f);
0996         }
0997         return $result;
0998     }
0999 
1000     /**
1001      * Put the given string into the given file
1002      *
1003      * @param  string $file   File complete path
1004      * @param  string $string String to put in file
1005      * @return boolean true if no problem
1006      */
1007     protected function _filePutContents($file, $string)
1008     {
1009         $result = false;
1010         $f = @fopen($file, 'ab+');
1011         if ($f) {
1012             if ($this->_options['file_locking']) @flock($f, LOCK_EX);
1013             fseek($f, 0);
1014             ftruncate($f, 0);
1015             $tmp = @fwrite($f, $string);
1016             if (!($tmp === FALSE)) {
1017                 $result = true;
1018             }
1019             @fclose($f);
1020         }
1021         @chmod($file, $this->_options['cache_file_perm']);
1022         return $result;
1023     }
1024 
1025     /**
1026      * Transform a file name into cache id and return it
1027      *
1028      * @param  string $fileName File name
1029      * @return string Cache id
1030      */
1031     protected function _fileNameToId($fileName)
1032     {
1033         $prefix = $this->_options['file_name_prefix'];
1034         return preg_replace('~^' . $prefix . '---(.*)$~', '$1', $fileName);
1035     }
1036 
1037 }