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