/library/Zend/Cache/Backend/File.php
PHP | 1007 lines | 657 code | 36 blank | 314 comment | 59 complexity | 51f153ed71f20cc3131532c47bbb563f MD5 | raw file
Possible License(s): AGPL-1.0
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 24594 2012-01-05 21:27:01Z matthew $
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-2012 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 *
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}