TYPO3 API  SVNRelease
class.t3lib_cache_backend_filebackend.php
Go to the documentation of this file.
00001 <?php
00002 /***************************************************************
00003  *  Copyright notice
00004  *
00005  *  (c) 2009-2011 Ingo Renner <ingo@typo3.org>
00006  *  All rights reserved
00007  *
00008  *  This script is part of the TYPO3 project. The TYPO3 project is
00009  *  free software; you can redistribute it and/or modify
00010  *  it under the terms of the GNU General Public License as published by
00011  *  the Free Software Foundation; either version 2 of the License, or
00012  *  (at your option) any later version.
00013  *
00014  *  The GNU General Public License can be found at
00015  *  http://www.gnu.org/copyleft/gpl.html.
00016  *
00017  *  This script is distributed in the hope that it will be useful,
00018  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00019  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00020  *  GNU General Public License for more details.
00021  *
00022  *  This copyright notice MUST APPEAR in all copies of the script!
00023  ***************************************************************/
00024 
00025 
00026 /**
00027  * A caching backend which stores cache entries in files
00028  *
00029  * This file is a backport from FLOW3
00030  *
00031  * @package TYPO3
00032  * @subpackage t3lib_cache
00033  * @api
00034  * @version $Id: class.t3lib_cache_backend_filebackend.php 10121 2011-01-18 20:15:30Z ohader $
00035  */
00036 class t3lib_cache_backend_FileBackend extends t3lib_cache_backend_AbstractBackend implements t3lib_cache_backend_PhpCapableBackend {
00037 
00038     const SEPARATOR = '^';
00039 
00040     const EXPIRYTIME_FORMAT = 'YmdHis';
00041     const EXPIRYTIME_LENGTH = 14;
00042 
00043     const DATASIZE_DIGITS = 10;
00044 
00045     /**
00046      * @var string Directory where the files are stored
00047      */
00048     protected $cacheDirectory = '';
00049 
00050     /**
00051      * @var string Absolute path to root, usually document root of website
00052      */
00053     protected $root = '/';
00054 
00055     /**
00056      * Maximum allowed file path length in the current environment.
00057      *
00058      * @var integer
00059      */
00060     protected $maximumPathLength = null;
00061 
00062     /**
00063      * Constructs this backend
00064      *
00065      * @param array $options Configuration options - depends on the actual backend
00066      */
00067     public function __construct(array $options = array()) {
00068         parent::__construct($options);
00069 
00070         if (is_null($this->maximumPathLength)) {
00071             $this->maximumPathLength = t3lib_div::getMaximumPathLength();
00072         }
00073     }
00074 
00075     /**
00076      * Sets a reference to the cache frontend which uses this backend and
00077      * initializes the default cache directory
00078      *
00079      * @void
00080      * @author Robert Lemke <robert@typo3.org>
00081      */
00082     public function setCache(t3lib_cache_frontend_Frontend $cache) {
00083         parent::setCache($cache);
00084 
00085         if (empty($this->cacheDirectory)) {
00086             $cacheDirectory = 'typo3temp/cache/';
00087             try {
00088                 $this->setCacheDirectory($cacheDirectory);
00089             } catch (t3lib_cache_Exception $exception) {
00090             }
00091         }
00092     }
00093 
00094     /**
00095      * Sets the directory where the cache files are stored. By default it is
00096      * assumed that the directory is below the TYPO3_DOCUMENT_ROOT. However, an
00097      * absolute path can be selected, too.
00098      *
00099      * @param string The directory. If a relative path is given, it's assumed it's in TYPO3_DOCUMENT_ROOT. If an absolute path is given it is taken as is.
00100      * @return void
00101      * @throws t3lib_cache_Exception if the directory does not exist, is not writable or could not be created.
00102      * @author Robert Lemke <robert@typo3.org>
00103      * @author Ingo Renner <ingo@typo3.org>
00104      */
00105     public function setCacheDirectory($cacheDirectory) {
00106         $documentRoot = PATH_site;
00107 
00108         if (($open_basedir = ini_get('open_basedir'))) {
00109             if (TYPO3_OS === 'WIN') {
00110                 $delimiter = ';';
00111                 $cacheDirectory = str_replace('\\', '/', $cacheDirectory);
00112                 if (!(preg_match('/[A-Z]:/', substr($cacheDirectory, 0, 2)))) {
00113                     $cacheDirectory = PATH_site . $cacheDirectory;
00114                 }
00115             } else {
00116                 $delimiter = ':';
00117                 if ($cacheDirectory[0] != '/') {
00118                         // relative path to cache directory.
00119                     $cacheDirectory = PATH_site . $cacheDirectory;
00120                 }
00121             }
00122 
00123             $basedirs = explode($delimiter, $open_basedir);
00124             $cacheDirectoryInBaseDir = FALSE;
00125             foreach ($basedirs as $basedir) {
00126                 if (TYPO3_OS === 'WIN') {
00127                     $basedir = str_replace('\\', '/', $basedir);
00128                 }
00129                 if ($basedir[strlen($basedir) - 1] !== '/') {
00130                     $basedir .= '/';
00131                 }
00132                 if (t3lib_div::isFirstPartOfStr($cacheDirectory, $basedir)) {
00133                     $documentRoot = $basedir;
00134                     $cacheDirectory = str_replace($basedir, '', $cacheDirectory);
00135                     $cacheDirectoryInBaseDir = TRUE;
00136                     break;
00137                 }
00138             }
00139             if (!$cacheDirectoryInBaseDir) {
00140                 throw new t3lib_cache_Exception(
00141                     'Open_basedir restriction in effect. The directory "' . $cacheDirectory . '" is not in an allowed path.'
00142                 );
00143             }
00144         } else {
00145             if ($cacheDirectory[0] == '/') {
00146                     // Absolute path to cache directory.
00147                 $documentRoot = '/';
00148             }
00149             if (TYPO3_OS === 'WIN') {
00150                 if (substr($cacheDirectory, 0,  strlen($documentRoot)) === $documentRoot) {
00151                     $documentRoot = '';
00152                 }
00153             }
00154         }
00155 
00156             // After this point all paths have '/' as directory seperator
00157         if ($cacheDirectory[strlen($cacheDirectory) - 1] !== '/') {
00158             $cacheDirectory .= '/';
00159         }
00160         $cacheDirectory .= $this->cacheIdentifier;
00161         if ($cacheDirectory[strlen($cacheDirectory) - 1] !== '/') {
00162             $cacheDirectory .= '/';
00163         }
00164 
00165         if (!is_writable($documentRoot . $cacheDirectory)) {
00166             t3lib_div::mkdir_deep(
00167                 $documentRoot,
00168                 $cacheDirectory
00169             );
00170         }
00171         if (!is_dir($documentRoot . $cacheDirectory)) {
00172             throw new t3lib_cache_Exception(
00173                 'The directory "' . $documentRoot . $cacheDirectory . '" does not exist.',
00174                 1203965199
00175             );
00176         }
00177         if (!is_writable($documentRoot . $cacheDirectory)) {
00178             throw new t3lib_cache_Exception(
00179                 'The directory "' . $documentRoot . $cacheDirectory . '" is not writable.',
00180                 1203965200
00181             );
00182         }
00183 
00184         $this->root = $documentRoot;
00185         $this->cacheDirectory = $cacheDirectory;
00186     }
00187 
00188     /**
00189      * Returns the directory where the cache files are stored
00190      *
00191      * @return string Full path of the cache directory
00192      * @author Robert Lemke <robert@typo3.org>
00193      * @api
00194      */
00195     public function getCacheDirectory() {
00196         return $this->root . $this->cacheDirectory;
00197     }
00198 
00199     /**
00200      * Saves data in a cache file.
00201      *
00202      * @param string $entryIdentifier An identifier for this specific cache entry
00203      * @param string $data The data to be stored
00204      * @param array $tags Tags to associate with this cache entry
00205      * @param integer $lifetime Lifetime of this cache entry in seconds. If NULL is specified, the default lifetime is used. "0" means unlimited lifetime.
00206      * @return void
00207      * @throws t3lib_cache_Exception if the directory does not exist or is not writable or exceeds the maximum allowed path length, or if no cache frontend has been set.
00208      * @throws t3lib_cache_exception_InvalidData if the data to bes stored is not a string.
00209      * @author Robert Lemke <robert@typo3.org>
00210      * @api
00211      */
00212     public function set($entryIdentifier, $data, array $tags = array(), $lifetime = NULL) {
00213         if (!$this->cache instanceof t3lib_cache_frontend_Frontend) {
00214             throw new t3lib_cache_Exception(
00215                 'No cache frontend has been set yet via setCache().',
00216                 1204111375
00217             );
00218         }
00219 
00220         if (!is_string($data)) {
00221             throw new t3lib_cache_Exception_InvalidData(
00222                 'The specified data is of type "' . gettype($data) . '" but a string is expected.',
00223                 1204481674
00224             );
00225         }
00226 
00227         if ($entryIdentifier !== basename($entryIdentifier)) {
00228             throw new InvalidArgumentException(
00229                 'The specified entry identifier must not contain a path segment.',
00230                 1282073032
00231             );
00232         }
00233 
00234         $this->remove($entryIdentifier);
00235 
00236         $temporaryCacheEntryPathAndFilename = $this->root . $this->cacheDirectory . uniqid() . '.temp';
00237         if (strlen($temporaryCacheEntryPathAndFilename) > $this->maximumPathLength) {
00238             throw new t3lib_cache_Exception(
00239                 'The length of the temporary cache file path "' . $temporaryCacheEntryPathAndFilename .
00240                 '" is ' . strlen($temporaryCacheEntryPathAndFilename) . ' characters long and exceeds the maximum path length of ' .
00241                 $this->maximumPathLength . '. Please consider setting the temporaryDirectoryBase option to a shorter path. ',
00242                 1248710426
00243             );
00244         }
00245 
00246         $expiryTime = ($lifetime === NULL) ? 0 : ($GLOBALS['EXEC_TIME'] + $lifetime);
00247         $metaData = str_pad($expiryTime, self::EXPIRYTIME_LENGTH) . implode(' ', $tags) . str_pad(strlen($data), self::DATASIZE_DIGITS);
00248         $result = file_put_contents($temporaryCacheEntryPathAndFilename, $data . $metaData);
00249 
00250         if ($result === FALSE) {
00251             throw new t3lib_cache_exception(
00252                 'The temporary cache file "' . $temporaryCacheEntryPathAndFilename . '" could not be written.',
00253                 1204026251
00254             );
00255         }
00256 
00257         $i = 0;
00258         $cacheEntryPathAndFilename = $this->root . $this->cacheDirectory . $entryIdentifier;
00259             // @TODO: Figure out why the heck this is done and maybe find a smarter solution, report to FLOW3
00260         while (!rename($temporaryCacheEntryPathAndFilename, $cacheEntryPathAndFilename) && $i < 5) {
00261             $i++;
00262         }
00263 
00264             // @FIXME: At least the result of rename() should be handled here, report to FLOW3
00265         if ($result === FALSE) {
00266             throw new t3lib_cache_exception(
00267                 'The cache file "' . $cacheEntryPathAndFilename . '" could not be written.',
00268                 1222361632
00269             );
00270         }
00271     }
00272 
00273     /**
00274      * Loads data from a cache file.
00275      *
00276      * @param string $entryIdentifier An identifier which describes the cache entry to load
00277      * @return mixed The cache entry's content as a string or FALSE if the cache entry could not be loaded
00278      * @author Robert Lemke <robert@typo3.org>
00279      * @author Karsten Dambekalns <karsten@typo3.org>
00280      * @api
00281      */
00282     public function get($entryIdentifier) {
00283         if ($entryIdentifier !== basename($entryIdentifier)) {
00284             throw new InvalidArgumentException(
00285                 'The specified entry identifier must not contain a path segment.',
00286                 1282073033
00287             );
00288         }
00289 
00290         $pathAndFilename = $this->root . $this->cacheDirectory . $entryIdentifier;
00291         if ($this->isCacheFileExpired($pathAndFilename)) {
00292             return FALSE;
00293         }
00294         $dataSize = (integer) file_get_contents($pathAndFilename, NULL, NULL, filesize($pathAndFilename) - self::DATASIZE_DIGITS, self::DATASIZE_DIGITS);
00295         return file_get_contents($pathAndFilename, NULL, NULL, 0, $dataSize);
00296     }
00297 
00298     /**
00299      * Checks if a cache entry with the specified identifier exists.
00300      *
00301      * @param string $entryIdentifier Specifies the cache entry to remove
00302      * @return boolean TRUE if (at least) an entry could be removed or FALSE if no entry was found
00303      * @author Robert Lemke <robert@typo3.org>
00304      * @api
00305      */
00306     public function has($entryIdentifier) {
00307         if ($entryIdentifier !== basename($entryIdentifier)) {
00308             throw new InvalidArgumentException(
00309                 'The specified entry identifier must not contain a path segment.',
00310                 1282073034
00311             );
00312         }
00313 
00314         return !$this->isCacheFileExpired($this->root . $this->cacheDirectory . $entryIdentifier);
00315     }
00316 
00317     /**
00318      * Removes all cache entries matching the specified identifier.
00319      * Usually this only affects one entry.
00320      *
00321      * @param string $entryIdentifier Specifies the cache entry to remove
00322      * @return boolean TRUE if (at least) an entry could be removed or FALSE if no entry was found
00323      * @author Robert Lemke <robert@typo3.org>
00324      * @api
00325      */
00326     public function remove($entryIdentifier) {
00327         if ($entryIdentifier !== basename($entryIdentifier)) {
00328             throw new InvalidArgumentException(
00329                 'The specified entry identifier must not contain a path segment.',
00330                 1282073035
00331             );
00332         }
00333 
00334         $pathAndFilename = $this->root . $this->cacheDirectory . $entryIdentifier;
00335         if (!file_exists($pathAndFilename)) {
00336             return FALSE;
00337         }
00338         if (unlink($pathAndFilename) === FALSE) {
00339             return FALSE;
00340         }
00341         return TRUE;
00342     }
00343 
00344     /**
00345      * Finds and returns all cache entry identifiers which are tagged by the
00346      * specified tag.
00347      *
00348      * @param string $searchedTag The tag to search for
00349      * @return array An array with identifiers of all matching entries. An empty array if no entries matched
00350      * @author Robert Lemke <robert@typo3.org>
00351      * @author Karsten Dambekalns <karsten@typo3.org>
00352      * @api
00353      */
00354     public function findIdentifiersByTag($searchedTag) {
00355         $entryIdentifiers = array();
00356         $now = $GLOBALS['EXEC_TIME'];
00357         for ($directoryIterator = t3lib_div::makeInstance('DirectoryIterator', $this->root . $this->cacheDirectory); $directoryIterator->valid(); $directoryIterator->next()) {
00358             if ($directoryIterator->isDot()) {
00359                 continue;
00360             }
00361             $cacheEntryPathAndFilename = $directoryIterator->getPathname();
00362             $index = (integer) file_get_contents($cacheEntryPathAndFilename, NULL, NULL, filesize($cacheEntryPathAndFilename) - self::DATASIZE_DIGITS, self::DATASIZE_DIGITS);
00363             $metaData = file_get_contents($cacheEntryPathAndFilename, NULL, NULL, $index);
00364 
00365             $expiryTime = (integer) substr($metaData, 0, self::EXPIRYTIME_LENGTH);
00366             if ($expiryTime !== 0 && $expiryTime < $now) {
00367                 continue;
00368             }
00369             if (in_array($searchedTag, explode(' ', substr($metaData, self::EXPIRYTIME_LENGTH, -self::DATASIZE_DIGITS)))) {
00370                 $entryIdentifiers[] = $directoryIterator->getFilename();
00371             }
00372         }
00373         return $entryIdentifiers;
00374     }
00375 
00376     /**
00377      * Finds and returns all cache entry identifiers which are tagged by the
00378      * specified tags.
00379      *
00380      * @param array $searchedTags Array of tags to search for
00381      * @return array An array with identifiers of all matching entries. An empty array if no entries matched
00382      * @author Ingo Renner <ingo@typo3.org>
00383      * @author Christian Kuhn <lolli@schwarzbu.ch>
00384      * @api
00385      */
00386     public function findIdentifiersByTags(array $searchedTags) {
00387         $entryIdentifiers = array();
00388         for ($directoryIterator = t3lib_div::makeInstance('DirectoryIterator', $this->root . $this->cacheDirectory); $directoryIterator->valid(); $directoryIterator->next()) {
00389             if ($directoryIterator->isDot()) {
00390                 continue;
00391             }
00392             $cacheEntryPathAndFilename = $directoryIterator->getPathname();
00393             $index = (integer) file_get_contents($cacheEntryPathAndFilename, NULL, NULL, filesize($cacheEntryPathAndFilename) - self::DATASIZE_DIGITS, self::DATASIZE_DIGITS);
00394             $metaData = file_get_contents($cacheEntryPathAndFilename, NULL, NULL, $index);
00395 
00396             $expiryTime = (integer) substr($metaData, 0, self::EXPIRYTIME_LENGTH);
00397             if ($expiryTime !== 0 && $expiryTime < $GLOBALS['EXEC_TIME']) {
00398                 continue;
00399             }
00400             if (in_array($searchedTags, explode(' ', substr($metaData, self::EXPIRYTIME_LENGTH, -self::DATASIZE_DIGITS)))) {
00401                 $entryIdentifiers[] = $directoryIterator->getFilename();
00402             }
00403         }
00404         return $entryIdentifiers;
00405     }
00406 
00407     /**
00408      * Removes all cache entries of this cache.
00409      *
00410      * @return void
00411      * @author Robert Lemke <robert@typo3.org>
00412      * @author Christian Kuhn <lolli@schwarzbu.ch>
00413      * @api
00414      */
00415     public function flush() {
00416         t3lib_div::rmdir($this->root . $this->cacheDirectory, TRUE);
00417     }
00418 
00419     /**
00420      * Removes all cache entries of this cache which are tagged by the specified tag.
00421      *
00422      * @param string $tag The tag the entries must have
00423      * @return void
00424      * @author Ingo Renner <ingo@typo3.org>
00425      * @api
00426      */
00427     public function flushByTag($tag) {
00428         $identifiers = $this->findIdentifiersByTag($tag);
00429         if (count($identifiers) === 0) {
00430             return;
00431         }
00432 
00433         foreach ($identifiers as $entryIdentifier) {
00434             $this->remove($entryIdentifier);
00435         }
00436     }
00437 
00438     /**
00439      * Removes all cache entries of this cache which are tagged by the specified tag.
00440      *
00441      * @param array $tags The tags the entries must have
00442      * @return void
00443      * @author Ingo Renner <ingo@typo3.org>
00444      * @api
00445      */
00446     public function flushByTags(array $tags) {
00447         foreach ($tags as $tag) {
00448             $this->flushByTag($tag);
00449         }
00450     }
00451 
00452     /**
00453      * Checks if the given cache entry files are still valid or if their
00454      * lifetime has exceeded.
00455      *
00456      * @param string $cacheEntryPathAndFilename
00457      * @return boolean
00458      * @author Robert Lemke <robert@typo3.org>
00459      */
00460     protected function isCacheFileExpired($cacheEntryPathAndFilename) {
00461         if (!file_exists($cacheEntryPathAndFilename)) {
00462             return TRUE;
00463         }
00464         $index = (integer) file_get_contents($cacheEntryPathAndFilename, NULL, NULL, filesize($cacheEntryPathAndFilename) - self::DATASIZE_DIGITS, self::DATASIZE_DIGITS);
00465         $expiryTime = file_get_contents($cacheEntryPathAndFilename, NULL, NULL, $index, self::EXPIRYTIME_LENGTH);
00466         return ($expiryTime != 0 && $expiryTime < $GLOBALS['EXEC_TIME']);
00467     }
00468 
00469     /**
00470      * Does garbage collection
00471      *
00472      * @return void
00473      * @author Karsten Dambekalns <karsten@typo3.org>
00474      * @api
00475      */
00476     public function collectGarbage() {
00477         if (!$this->cache instanceof t3lib_cache_frontend_Frontend) {
00478             throw new t3lib_cache_Exception(
00479                 'Yet no cache frontend has been set via setCache().',
00480                 1222686150
00481             );
00482         }
00483 
00484         $pattern = $this->root . $this->cacheDirectory . '*';
00485         $filesFound = glob($pattern);
00486 
00487         if (is_array($filesFound)) {
00488             foreach ($filesFound as $cacheFilename) {
00489                 if ($this->isCacheFileExpired($cacheFilename)) {
00490                     $this->remove(basename($cacheFilename));
00491                 }
00492             }
00493         }
00494     }
00495 
00496     /**
00497      * Tries to find the cache entry for the specified identifier.
00498      * Usually only one cache entry should be found - if more than one exist, this
00499      * is due to some error or crash.
00500      *
00501      * @param string $entryIdentifier The cache entry identifier
00502      * @return mixed The file names (including path) as an array if one or more entries could be found, otherwise FALSE
00503      * @author Robert Lemke <robert@typo3.org>
00504      * @throws t3lib_cache_Exception if no frontend has been set
00505      * @internal
00506      */
00507     protected function findCacheFilesByIdentifier($entryIdentifier) {
00508         if (!$this->cache instanceof t3lib_cache_frontend_Frontend) {
00509             throw new t3lib_cache_Exception(
00510                 'Yet no cache frontend has been set via setCache().',
00511                 1204111376
00512             );
00513         }
00514 
00515         $pattern = $this->root . $this->cacheDirectory . $entryIdentifier;
00516         $filesFound = glob($pattern);
00517         if ($filesFound === FALSE || count($filesFound) === 0) {
00518             return FALSE;
00519         }
00520 
00521         return $filesFound;
00522     }
00523 
00524     /**
00525      * Loads PHP code from the cache and require_onces it right away.
00526      *
00527      * @param string $entryIdentifier An identifier which describes the cache entry to load
00528      * @return mixed Potential return value from the include operation
00529      * @api
00530      */
00531     public function requireOnce($entryIdentifier) {
00532         if ($entryIdentifier !== basename($entryIdentifier)) {
00533             throw new InvalidArgumentException(
00534                 'The specified entry identifier must not contain a path segment.',
00535                 1282073036
00536             );
00537         }
00538 
00539         $pathAndFilename = $this->root . $this->cacheDirectory . $entryIdentifier;
00540         return ($this->isCacheFileExpired($pathAndFilename)) ? FALSE : require_once($pathAndFilename);
00541     }
00542 }
00543 
00544 
00545 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/cache/backend/class.t3lib_cache_backend_filebackend.php'])) {
00546     include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/cache/backend/class.t3lib_cache_backend_filebackend.php']);
00547 }
00548 
00549 ?>