]>
Commit | Line | Data |
---|---|---|
42c80841 NL |
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 | } |