Completed
Push — 3 ( bc9e38...39c73e )
by Robbie
05:15 queued 10s
created

Zend_Cache_Backend_File::_clean()   F

Complexity

Conditions 33
Paths 709

Size

Total Lines 92

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 33
nc 709
nop 3
dl 0
loc 92
rs 0.4041
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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