PageRenderTime 73ms CodeModel.GetById 48ms app.highlight 18ms RepoModel.GetById 1ms app.codeStats 0ms

/framework/vendor/zend/Zend/Cache/Backend/File.php

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