PageRenderTime 72ms CodeModel.GetById 52ms app.highlight 15ms RepoModel.GetById 1ms app.codeStats 1ms

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

http://zoop.googlecode.com/
PHP | 558 lines | 436 code | 22 blank | 100 comment | 40 complexity | 25bca53df6807f260f72c154d5650011 MD5 | raw file
  1<?php
  2/**
  3 * Zend Framework
  4 *
  5 * LICENSE
  6 *
  7 * This source file is subject to the new BSD license that is bundled
  8 * with this package in the file LICENSE.txt.
  9 * It is also available through the world-wide-web at this URL:
 10 * http://framework.zend.com/license/new-bsd
 11 * If you did not receive a copy of the license and are unable to
 12 * obtain it through the world-wide-web, please send an email
 13 * to license@zend.com so we can send you a copy immediately.
 14 *
 15 * @category   Zend
 16 * @package    Zend_Cache
 17 * @subpackage Zend_Cache_Backend
 18 * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
 19 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 20 * @version    $Id: BlackHole.php 17867 2009-08-28 09:42:11Z yoshida@zend.co.jp $
 21 */
 22
 23/**
 24 * @see Zend_Cache_Backend_Interface
 25 */
 26require_once 'Zend/Cache/Backend/Interface.php';
 27
 28/**
 29 * @see Zend_Cache_Backend
 30 */
 31require_once 'Zend/Cache/Backend.php';
 32
 33/**
 34 * @package    Zend_Cache
 35 * @subpackage Zend_Cache_Backend
 36 * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
 37 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 38 */
 39class Zend_Cache_Backend_Static 
 40    extends Zend_Cache_Backend 
 41    implements Zend_Cache_Backend_Interface
 42{
 43    const INNER_CACHE_NAME = 'zend_cache_backend_static_tagcache';
 44
 45    /**
 46     * Static backend options
 47     * @var array
 48     */
 49    protected $_options = array(
 50        'public_dir'            => null,
 51        'sub_dir'               => 'html',
 52        'file_extension'        => '.html',
 53        'index_filename'        => 'index',
 54        'file_locking'          => true,
 55        'cache_file_umask'      => 0600,
 56        'cache_directory_umask' => 0700,
 57        'debug_header'          => false,
 58        'tag_cache'             => null,
 59        'disable_caching'       => false
 60    );
 61
 62    /**
 63     * Cache for handling tags
 64     * @var Zend_Cache_Core
 65     */
 66    protected $_tagCache = null;
 67
 68    /**
 69     * Tagged items
 70     * @var array
 71     */
 72    protected $_tagged = null;
 73
 74    /**
 75     * Interceptor child method to handle the case where an Inner
 76     * Cache object is being set since it's not supported by the
 77     * standard backend interface
 78     *
 79     * @param  string $name
 80     * @param  mixed $value
 81     * @return Zend_Cache_Backend_Static
 82     */
 83    public function setOption($name, $value)
 84    {
 85        if ($name == 'tag_cache') {
 86            $this->setInnerCache($value);
 87        } else {
 88            parent::setOption($name, $value);
 89        }
 90        return $this;
 91    }
 92
 93    /**
 94     * Retrieve any option via interception of the parent's statically held
 95     * options including the local option for a tag cache.
 96     *
 97     * @param  string $name
 98     * @return mixed
 99     */
100    public function getOption($name)
101    {
102        if ($name == 'tag_cache') {
103            return $this->getInnerCache();
104        } else {
105            if (in_array($name, $this->_options)) {
106                return $this->_options[$name];
107            }
108            if ($name == 'lifetime') {
109                return parent::getLifetime();
110            }
111            return null;
112        }
113    }
114
115    /**
116     * Test if a cache is available for the given id and (if yes) return it (false else)
117     *
118     * Note : return value is always "string" (unserialization is done by the core not by the backend)
119     *
120     * @param  string  $id                     Cache id
121     * @param  boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
122     * @return string|false cached datas
123     */
124    public function load($id, $doNotTestCacheValidity = false)
125    {
126        if (empty($id)) {
127            $id = $this->_detectId();
128        } else {
129            $id = $this->_decodeId($id);
130        }
131        if (!$this->_verifyPath($id)) {
132            Zend_Cache::throwException('Invalid cache id: does not match expected public_dir path');
133        }
134        if ($doNotTestCacheValidity) {
135            $this->_log("Zend_Cache_Backend_Static::load() : \$doNotTestCacheValidity=true is unsupported by the Static backend");
136        }
137
138        $fileName = basename($id);
139        if (empty($fileName)) {
140            $fileName = $this->_options['index_filename'];
141        }
142        $pathName = $this->_options['public_dir'] . dirname($id);
143        $file     = rtrim($pathName, '/') . '/' . $fileName . $this->_options['file_extension'];
144        if (file_exists($file)) {
145            $content = file_get_contents($file);
146            return $content;
147        }
148
149        return false;
150    }
151
152    /**
153     * Test if a cache is available or not (for the given id)
154     *
155     * @param  string $id cache id
156     * @return bool 
157     */
158    public function test($id)
159    {
160        $id = $this->_decodeId($id);
161        if (!$this->_verifyPath($id)) {
162            Zend_Cache::throwException('Invalid cache id: does not match expected public_dir path');
163        }
164
165        $fileName = basename($id);
166        if (empty($fileName)) {
167            $fileName = $this->_options['index_filename'];
168        }
169        if (is_null($this->_tagged) && $tagged = $this->getInnerCache()->load(self::INNER_CACHE_NAME)) {
170            $this->_tagged = $tagged;
171        } elseif (!$this->_tagged) {
172            return false;
173        }
174        $pathName = $this->_options['public_dir'] . dirname($id);
175        
176        // Switch extension if needed
177        if (isset($this->_tagged[$id])) {
178            $extension = $this->_tagged[$id]['extension'];
179        } else {
180            $extension = $this->_options['file_extension'];
181        }
182        $file     = $pathName . '/' . $fileName . $extension;
183        if (file_exists($file)) {
184            return true;
185        }
186        return false;
187    }
188
189    /**
190     * Save some string datas into a cache record
191     *
192     * Note : $data is always "string" (serialization is done by the
193     * core not by the backend)
194     *
195     * @param  string $data            Datas to cache
196     * @param  string $id              Cache id
197     * @param  array $tags             Array of strings, the cache record will be tagged by each string entry
198     * @param  int   $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
199     * @return boolean true if no problem
200     */
201    public function save($data, $id, $tags = array(), $specificLifetime = false)
202    {
203        if ($this->_options['disable_caching']) {
204            return true;
205        }
206        $extension = null;
207        if ($this->_isSerialized($data)) {
208            $data = unserialize($data);
209            $extension = '.' . ltrim($data[1], '.');
210            $data = $data[0];
211        }
212
213        clearstatcache();
214        if (is_null($id) || strlen($id) == 0) {
215            $id = $this->_detectId();
216        } else {
217            $id = $this->_decodeId($id);
218        }
219
220        $fileName = basename($id);
221        if (empty($fileName)) {
222            $fileName = $this->_options['index_filename'];
223        }
224
225        $pathName = realpath($this->_options['public_dir']) . dirname($id);
226        $this->_createDirectoriesFor($pathName);
227
228        if (is_null($id) || strlen($id) == 0) {
229            $dataUnserialized = unserialize($data);
230            $data = $dataUnserialized['data'];
231        }
232        $ext = $this->_options['file_extension'];
233        if ($extension) $ext = $extension;
234        $file = rtrim($pathName, '/') . '/' . $fileName . $ext;
235        if ($this->_options['file_locking']) {
236            $result = file_put_contents($file, $data, LOCK_EX);
237        } else {
238            $result = file_put_contents($file, $data);
239        }
240        @chmod($file, $this->_octdec($this->_options['cache_file_umask']));
241
242        if (is_null($this->_tagged) && $tagged = $this->getInnerCache()->load(self::INNER_CACHE_NAME)) {
243            $this->_tagged = $tagged;
244        } elseif (is_null($this->_tagged)) {
245            $this->_tagged = array();
246        }
247        if (!isset($this->_tagged[$id])) {
248            $this->_tagged[$id] = array();
249        }
250        if (!isset($this->_tagged[$id]['tags'])) {
251            $this->_tagged[$id]['tags'] = array();
252        }
253        $this->_tagged[$id]['tags'] = array_unique(array_merge($this->_tagged[$id]['tags'], $tags));
254        $this->_tagged[$id]['extension'] = $ext;
255        $this->getInnerCache()->save($this->_tagged, self::INNER_CACHE_NAME);
256        return (bool) $result;
257    }
258    
259    /**
260     * Recursively create the directories needed to write the static file
261     */
262    protected function _createDirectoriesFor($path)
263    {
264        $parts = explode('/', $path);
265        $directory = '';
266        foreach ($parts as $part) {
267            $directory = rtrim($directory, '/') . '/' . $part;
268            if (!is_dir($directory)) {
269                mkdir($directory, $this->_octdec($this->_options['cache_directory_umask']));
270            }
271        }
272    }
273    
274    /**
275     * Detect serialization of data (cannot predict since this is the only way
276     * to obey the interface yet pass in another parameter).
277     *
278     * In future, ZF 2.0, check if we can just avoid the interface restraints.
279     *
280     * This format is the only valid one possible for the class, so it's simple
281     * to just run a regular expression for the starting serialized format.
282     */
283    protected function _isSerialized($data)
284    {
285        return preg_match("/a:2:\{i:0;s:\d+:\"/", $data);
286    }
287
288    /**
289     * Remove a cache record
290     *
291     * @param  string $id Cache id
292     * @return boolean True if no problem
293     */
294    public function remove($id)
295    {
296        if (!$this->_verifyPath($id)) {
297            Zend_Cache::throwException('Invalid cache id: does not match expected public_dir path');
298        }
299        $fileName = basename($id);
300        if (is_null($this->_tagged) && $tagged = $this->getInnerCache()->load(self::INNER_CACHE_NAME)) {
301            $this->_tagged = $tagged;
302        } elseif (!$this->_tagged) {
303            return false;
304        }
305        if (isset($this->_tagged[$id])) {
306            $extension = $this->_tagged[$id]['extension'];
307        } else {
308            $extension = $this->_options['file_extension'];
309        }
310        if (empty($fileName)) {
311            $fileName = $this->_options['index_filename'];
312        }
313        $pathName = $this->_options['public_dir'] . dirname($id);
314        $file     = realpath($pathName) . '/' . $fileName . $extension;
315        if (!file_exists($file)) {
316            return false;
317        }
318        return unlink($file);
319    }
320
321    /**
322     * Remove a cache record recursively for the given directory matching a
323     * REQUEST_URI based relative path (deletes the actual file matching this
324     * in addition to the matching directory)
325     *
326     * @param  string $id Cache id
327     * @return boolean True if no problem
328     */
329    public function removeRecursively($id)
330    {
331        if (!$this->_verifyPath($id)) {
332            Zend_Cache::throwException('Invalid cache id: does not match expected public_dir path');
333        }
334        $fileName = basename($id);
335        if (empty($fileName)) {
336            $fileName = $this->_options['index_filename'];
337        }
338        $pathName  = $this->_options['public_dir'] . dirname($id);
339        $file      = $pathName . '/' . $fileName . $this->_options['file_extension'];
340        $directory = $pathName . '/' . $fileName;
341        if (file_exists($directory)) {
342            if (!is_writable($directory)) {
343                return false;
344            }
345            foreach (new DirectoryIterator($directory) as $file) {
346                if (true === $file->isFile()) {
347                    if (false === unlink($file->getPathName())) {
348                        return false;
349                    }
350                }
351            }
352            rmdir(dirname($path));
353        }
354        if (file_exists($file)) {
355            if (!is_writable($file)) {
356                return false;
357            }
358            return unlink($file);
359        }
360        return true;
361    }
362
363    /**
364     * Clean some cache records
365     *
366     * Available modes are :
367     * Zend_Cache::CLEANING_MODE_ALL (default)    => remove all cache entries ($tags is not used)
368     * Zend_Cache::CLEANING_MODE_OLD              => remove too old cache entries ($tags is not used)
369     * Zend_Cache::CLEANING_MODE_MATCHING_TAG     => remove cache entries matching all given tags
370     *                                               ($tags can be an array of strings or a single string)
371     * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags}
372     *                                               ($tags can be an array of strings or a single string)
373     * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags
374     *                                               ($tags can be an array of strings or a single string)
375     *
376     * @param  string $mode Clean mode
377     * @param  array  $tags Array of tags
378     * @return boolean true if no problem
379     */
380    public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
381    {
382        $result = false;
383        switch ($mode) {
384            case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
385            case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
386                if (empty($tags)) {
387                    throw new Zend_Exception('Cannot use tag matching modes as no tags were defined');
388                }
389                if (is_null($this->_tagged) && $tagged = $this->getInnerCache()->load(self::INNER_CACHE_NAME)) {
390                    $this->_tagged = $tagged;
391                } elseif (!$this->_tagged) {
392                    return true;
393                }
394                foreach ($tags as $tag) {
395                    $urls = array_keys($this->_tagged);
396                    foreach ($urls as $url) {
397                        if (isset($this->_tagged[$url]['tags']) && in_array($tag, $this->_tagged[$url]['tags'])) {
398                            $this->remove($url);
399                            unset($this->_tagged[$url]);
400                        }
401                    }
402                }
403                $this->getInnerCache()->save($this->_tagged, self::INNER_CACHE_NAME);
404                $result = true;
405                break;
406            case Zend_Cache::CLEANING_MODE_ALL:
407                if (is_null($this->_tagged)) {
408                    $tagged = $this->getInnerCache()->load(self::INNER_CACHE_NAME);
409                    $this->_tagged = $tagged;
410                }
411                if (is_null($this->_tagged) || empty($this->_tagged)) {
412                    return true;
413                }
414                $urls = array_keys($this->_tagged);
415                foreach ($urls as $url) {
416                    $this->remove($url);
417                    unset($this->_tagged[$url]);
418                }
419                $this->getInnerCache()->save($this->_tagged, self::INNER_CACHE_NAME);
420                $result = true;
421                break;
422            case Zend_Cache::CLEANING_MODE_OLD:
423                $this->_log("Zend_Cache_Backend_Static : Selected Cleaning Mode Currently Unsupported By This Backend");
424                break;
425            case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
426                if (empty($tags)) {
427                    throw new Zend_Exception('Cannot use tag matching modes as no tags were defined');
428                }
429                if (is_null($this->_tagged)) {
430                    $tagged = $this->getInnerCache()->load(self::INNER_CACHE_NAME);
431                    $this->_tagged = $tagged;
432                }
433                if (is_null($this->_tagged) || empty($this->_tagged)) {
434                    return true;
435                }
436                $urls = array_keys($this->_tagged);
437                foreach ($urls as $url) {
438                    $difference = array_diff($tags, $this->_tagged[$url]['tags']);
439                    if (count($tags) == count($difference)) {
440                        $this->remove($url);
441                        unset($this->_tagged[$url]);
442                    }
443                }
444                $this->getInnerCache()->save($this->_tagged, self::INNER_CACHE_NAME);
445                $result = true;
446                break;
447            default:
448                Zend_Cache::throwException('Invalid mode for clean() method');
449                break;
450        }
451        return $result;
452    }
453
454    /**
455     * Set an Inner Cache, used here primarily to store Tags associated
456     * with caches created by this backend. Note: If Tags are lost, the cache
457     * should be completely cleaned as the mapping of tags to caches will
458     * have been irrevocably lost.
459     *
460     * @param  Zend_Cache_Core
461     * @return void
462     */
463    public function setInnerCache(Zend_Cache_Core $cache)
464    {
465        $this->_tagCache = $cache;
466        $this->_options['tag_cache'] = $cache;
467    }
468
469    /**
470     * Get the Inner Cache if set
471     *
472     * @return Zend_Cache_Core
473     */
474    public function getInnerCache()
475    {
476        if (is_null($this->_tagCache)) {
477            Zend_Cache::throwException('An Inner Cache has not been set; use setInnerCache()');
478        }
479        return $this->_tagCache;
480    }
481
482    /**
483     * Verify path exists and is non-empty
484     * 
485     * @param  string $path 
486     * @return bool
487     */
488    protected function _verifyPath($path)
489    {
490        $path = realpath($path);
491        $base = realpath($this->_options['public_dir']);
492        return strncmp($path, $base, strlen($base)) !== 0;
493    }
494
495    /**
496     * Determine the page to save from the request
497     * 
498     * @return string
499     */
500    protected function _detectId()
501    {
502        return $_SERVER['REQUEST_URI'];
503    }
504
505    /**
506     * Validate a cache id or a tag (security, reliable filenames, reserved prefixes...)
507     *
508     * Throw an exception if a problem is found
509     *
510     * @param  string $string Cache id or tag
511     * @throws Zend_Cache_Exception
512     * @return void
513     * @deprecated Not usable until perhaps ZF 2.0
514     */
515    protected static function _validateIdOrTag($string)
516    {
517        if (!is_string($string)) {
518            Zend_Cache::throwException('Invalid id or tag : must be a string');
519        }
520
521        // Internal only checked in Frontend - not here!
522        if (substr($string, 0, 9) == 'internal-') {
523            return;
524        }
525
526        // Validation assumes no query string, fragments or scheme included - only the path
527        if (!preg_match(
528                '/^(?:\/(?:(?:%[[:xdigit:]]{2}|[A-Za-z0-9-_.!~*\'()\[\]:@&=+$,;])*)?)+$/', 
529                $string
530            )
531        ) {
532            Zend_Cache::throwException("Invalid id or tag '$string' : must be a valid URL path");
533        }
534    }
535    
536    /**
537     * Detect an octal string and return its octal value for file permission ops
538     * otherwise return the non-string (assumed octal or decimal int already)
539     *
540     * @param $val The potential octal in need of conversion
541     * @return int
542     */
543    protected function _octdec($val)
544    {
545        if (decoct(octdec($val)) == $val && is_string($val)) {
546            return octdec($val);
547        }
548        return $val;
549    }
550    
551    /**
552     * Decode a request URI from the provided ID
553     */
554    protected function _decodeId($id)
555    {
556        return pack('H*', $id);;
557    }
558}