2 /***************************************************************
5 * (c) 2009 Ingo Renner <ingo@typo3.org>
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
17 * This script is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * This copyright notice MUST APPEAR in all copies of the script!
23 ***************************************************************/
27 * A caching backend which stores cache entries in files
29 * This file is a backport from FLOW3
32 * @subpackage t3lib_cache
36 class t3lib_cache_backend_FileBackend
extends t3lib_cache_backend_AbstractBackend
implements t3lib_cache_backend_PhpCapableBackend
{
38 const SEPARATOR
= '^';
40 const EXPIRYTIME_FORMAT
= 'YmdHis';
41 const EXPIRYTIME_LENGTH
= 14;
43 const DATASIZE_DIGITS
= 10;
46 * @var string Directory where the files are stored
48 protected $cacheDirectory = '';
51 * @var string Absolute path to root, usually document root of website
53 protected $root = '/';
56 * Maximum allowed file path length in the current environment.
60 protected $maximumPathLength = null;
63 * Constructs this backend
65 * @param mixed Configuration options - depends on the actual backend
67 public function __construct(array $options = array()) {
68 parent
::__construct($options);
70 if (is_null($this->maximumPathLength
)) {
71 $this->maximumPathLength
= t3lib_div
::getMaximumPathLength();
76 * Sets a reference to the cache frontend which uses this backend and
77 * initializes the default cache directory
80 * @author Robert Lemke <robert@typo3.org>
82 public function setCache(t3lib_cache_frontend_frontend
$cache) {
83 parent
::setCache($cache);
85 if (empty($this->cacheDirectory
)) {
86 $cacheDirectory = 'typo3temp/cache/';
88 $this->setCacheDirectory($cacheDirectory);
89 } catch(t3lib_cache_Exception
$exception) {
95 * Sets the directory where the cache files are stored. By default it is
96 * assumed that the directory is below the TYPO3_DOCUMENT_ROOT. However, an
97 * absolute path can be selected, too.
99 * @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.
101 * @throws t3lib_cache_Exception if the directory does not exist, is not writable or could not be created.
102 * @author Robert Lemke <robert@typo3.org>
103 * @author Ingo Renner <ingo@typo3.org>
105 public function setCacheDirectory($cacheDirectory) {
106 $documentRoot = PATH_site
;
108 if (($open_basedir = ini_get('open_basedir'))) {
109 if (TYPO3_OS
=== 'WIN') {
111 $cacheDirectory = str_replace('\\', '/', $cacheDirectory);
112 if (!(preg_match('/[A-Z]:/', substr($cacheDirectory,0,2)))) {
113 $cacheDirectory = PATH_site
. $cacheDirectory;
117 if ($cacheDirectory[0] != '/') {
118 // relative path to cache directory.
119 $cacheDirectory = PATH_site
. $cacheDirectory;
123 $basedirs = explode($delimiter, $open_basedir);
124 $cacheDirectoryInBaseDir = FALSE;
125 foreach ($basedirs as $basedir) {
126 if (TYPO3_OS
=== 'WIN') {
127 $basedir = str_replace('\\', '/', $basedir);
129 if ($basedir[strlen($basedir) - 1] !== '/') {
132 if (t3lib_div
::isFirstPartOfStr($cacheDirectory, $basedir)) {
133 $documentRoot = $basedir;
134 $cacheDirectory = str_replace($basedir, '', $cacheDirectory);
135 $cacheDirectoryInBaseDir = TRUE;
139 if (!$cacheDirectoryInBaseDir) {
140 throw new t3lib_cache_Exception(
141 'Open_basedir restriction in effect. The directory "' . $cacheDirectory . '" is not in an allowed path.'
145 if ($cacheDirectory[0] == '/') {
146 // Absolute path to cache directory.
149 if (TYPO3_OS
=== 'WIN') {
154 // After this point all paths have '/' as directory seperator
155 if ($cacheDirectory[strlen($cacheDirectory) - 1] !== '/') {
156 $cacheDirectory .= '/';
158 $cacheDirectory .= $this->cacheIdentifier
;
159 if ($cacheDirectory[strlen($cacheDirectory) - 1] !== '/') {
160 $cacheDirectory .= '/';
163 if (!is_writable($documentRoot . $cacheDirectory)) {
164 t3lib_div
::mkdir_deep(
169 if (!is_dir($documentRoot . $cacheDirectory)) {
170 throw new t3lib_cache_Exception(
171 'The directory "' . $documentRoot . $cacheDirectory . '" does not exist.',
175 if (!is_writable($documentRoot . $cacheDirectory)) {
176 throw new t3lib_cache_Exception(
177 'The directory "' . $documentRoot . $cacheDirectory . '" is not writable.',
182 $this->root
= $documentRoot;
183 $this->cacheDirectory
= $cacheDirectory;
187 * Returns the directory where the cache files are stored
189 * @return string Full path of the cache directory
190 * @author Robert Lemke <robert@typo3.org>
193 public function getCacheDirectory() {
194 return $this->root
. $this->cacheDirectory
;
198 * Saves data in a cache file.
200 * @param string $entryIdentifier An identifier for this specific cache entry
201 * @param string $data The data to be stored
202 * @param array $tags Tags to associate with this cache entry
203 * @param integer $lifetime Lifetime of this cache entry in seconds. If NULL is specified, the default lifetime is used. "0" means unlimited lifetime.
205 * @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.
206 * @throws t3lib_cache_exception_InvalidData if the data to bes stored is not a string.
207 * @author Robert Lemke <robert@typo3.org>
210 public function set($entryIdentifier, $data, array $tags = array(), $lifetime = NULL) {
211 if (!$this->cache
instanceof t3lib_cache_frontend_Frontend
) {
212 throw new t3lib_cache_Exception(
213 'No cache frontend has been set yet via setCache().',
218 if (!is_string($data)) {
219 throw new t3lib_cache_Exception_InvalidData(
220 'The specified data is of type "' . gettype($data) . '" but a string is expected.',
225 $this->remove($entryIdentifier);
227 $temporaryCacheEntryPathAndFilename = $this->root
. $this->cacheDirectory
. uniqid() . '.temp';
228 if (strlen($temporaryCacheEntryPathAndFilename) > $this->maximumPathLength
) {
229 throw new t3lib_cache_Exception(
230 'The length of the temporary cache file path "' . $temporaryCacheEntryPathAndFilename .
231 '" is ' . strlen($temporaryCacheEntryPathAndFilename) . ' characters long and exceeds the maximum path length of ' .
232 $this->maximumPathLength
. '. Please consider setting the temporaryDirectoryBase option to a shorter path. ',
237 $expiryTime = ($lifetime === NULL) ?
0 : ($GLOBALS['EXEC_TIME'] +
$lifetime);
238 $metaData = str_pad($expiryTime, self
::EXPIRYTIME_LENGTH
) . implode(' ', $tags) . str_pad(strlen($data), self
::DATASIZE_DIGITS
);
239 $result = file_put_contents($temporaryCacheEntryPathAndFilename, $data . $metaData);
241 if ($result === FALSE) {
242 throw new t3lib_cache_exception(
243 'The temporary cache file "' . $temporaryCacheEntryPathAndFilename . '" could not be written.',
249 $cacheEntryPathAndFilename = $this->root
. $this->cacheDirectory
. $entryIdentifier;
250 // @TODO: Figure out why the heck this is done and maybe find a smarter solution, report to FLOW3
251 while (!rename($temporaryCacheEntryPathAndFilename, $cacheEntryPathAndFilename) && $i < 5) {
255 // @FIXME: At least the result of rename() should be handled here, report to FLOW3
256 if ($result === FALSE) {
257 throw new t3lib_cache_exception(
258 'The cache file "' . $cacheEntryPathAndFilename . '" could not be written.',
265 * Loads data from a cache file.
267 * @param string $entryIdentifier An identifier which describes the cache entry to load
268 * @return mixed The cache entry's content as a string or FALSE if the cache entry could not be loaded
269 * @author Robert Lemke <robert@typo3.org>
270 * @author Karsten Dambekalns <karsten@typo3.org>
273 public function get($entryIdentifier) {
274 $pathAndFilename = $this->root
. $this->cacheDirectory
. $entryIdentifier;
275 if ($this->isCacheFileExpired($pathAndFilename)) {
278 $dataSize = (integer)file_get_contents($pathAndFilename, NULL, NULL, filesize($pathAndFilename) - self
::DATASIZE_DIGITS
, self
::DATASIZE_DIGITS
);
279 return file_get_contents($pathAndFilename, NULL, NULL, 0, $dataSize);
283 * Checks if a cache entry with the specified identifier exists.
285 * @param string $entryIdentifier Specifies the cache entry to remove
286 * @return boolean TRUE if (at least) an entry could be removed or FALSE if no entry was found
287 * @author Robert Lemke <robert@typo3.org>
290 public function has($entryIdentifier) {
291 return !$this->isCacheFileExpired($this->root
. $this->cacheDirectory
. $entryIdentifier);
295 * Removes all cache entries matching the specified identifier.
296 * Usually this only affects one entry.
298 * @param string $entryIdentifier Specifies the cache entry to remove
299 * @return boolean TRUE if (at least) an entry could be removed or FALSE if no entry was found
300 * @author Robert Lemke <robert@typo3.org>
303 public function remove($entryIdentifier) {
304 $pathAndFilename = $this->root
. $this->cacheDirectory
. $entryIdentifier;
305 if (!file_exists($pathAndFilename)) {
308 if (unlink($pathAndFilename) === FALSE) {
315 * Finds and returns all cache entry identifiers which are tagged by the
318 * @param string $searchedTag The tag to search for
319 * @return array An array with identifiers of all matching entries. An empty array if no entries matched
320 * @author Robert Lemke <robert@typo3.org>
321 * @author Karsten Dambekalns <karsten@typo3.org>
324 public function findIdentifiersByTag($searchedTag) {
325 $entryIdentifiers = array();
326 $now = $GLOBALS['EXEC_TIME'];
327 for ($directoryIterator = t3lib_div
::makeInstance('DirectoryIterator', $this->root
. $this->cacheDirectory
); $directoryIterator->valid(); $directoryIterator->next()) {
328 if ($directoryIterator->isDot()) {
331 $cacheEntryPathAndFilename = $directoryIterator->getPathname();
332 $index = (integer)file_get_contents($cacheEntryPathAndFilename, NULL, NULL, filesize($cacheEntryPathAndFilename) - self
::DATASIZE_DIGITS
, self
::DATASIZE_DIGITS
);
333 $metaData = file_get_contents($cacheEntryPathAndFilename, NULL, NULL, $index);
335 $expiryTime = (integer)substr($metaData, 0, self
::EXPIRYTIME_LENGTH
);
336 if ($expiryTime !== 0 && $expiryTime < $now) {
339 if (in_array($searchedTag, explode(' ', substr($metaData, self
::EXPIRYTIME_LENGTH
, -self
::DATASIZE_DIGITS
)))) {
340 $entryIdentifiers[] = $directoryIterator->getFilename();
343 return $entryIdentifiers;
347 * Finds and returns all cache entry identifiers which are tagged by the
350 * @param array $searchedTags Array of tags to search for
351 * @return array An array with identifiers of all matching entries. An empty array if no entries matched
352 * @author Ingo Renner <ingo@typo3.org>
353 * @author Christian Kuhn <lolli@schwarzbu.ch>
356 public function findIdentifiersByTags(array $searchedTags) {
357 $entryIdentifiers = array();
358 for ($directoryIterator = t3lib_div
::makeInstance('DirectoryIterator', $this->root
. $this->cacheDirectory
); $directoryIterator->valid(); $directoryIterator->next()) {
359 if ($directoryIterator->isDot()) {
362 $cacheEntryPathAndFilename = $directoryIterator->getPathname();
363 $index = (integer)file_get_contents($cacheEntryPathAndFilename, NULL, NULL, filesize($cacheEntryPathAndFilename) - self
::DATASIZE_DIGITS
, self
::DATASIZE_DIGITS
);
364 $metaData = file_get_contents($cacheEntryPathAndFilename, NULL, NULL, $index);
366 $expiryTime = (integer)substr($metaData, 0, self
::EXPIRYTIME_LENGTH
);
367 if ($expiryTime !== 0 && $expiryTime < $GLOBALS['EXEC_TIME']) {
370 if (in_array($searchedTags, explode(' ', substr($metaData, self
::EXPIRYTIME_LENGTH
, -self
::DATASIZE_DIGITS
)))) {
371 $entryIdentifiers[] = $directoryIterator->getFilename();
374 return $entryIdentifiers;
378 * Removes all cache entries of this cache.
381 * @author Robert Lemke <robert@typo3.org>
382 * @author Christian Kuhn <lolli@schwarzbu.ch>
385 public function flush() {
386 t3lib_div
::rmdir($this->root
. $this->cacheDirectory
, TRUE);
390 * Removes all cache entries of this cache which are tagged by the specified tag.
392 * @param string $tag The tag the entries must have
394 * @author Ingo Renner <ingo@typo3.org>
397 public function flushByTag($tag) {
398 $identifiers = $this->findIdentifiersByTag($tag);
399 if (count($identifiers) === 0) {
403 foreach ($identifiers as $entryIdentifier) {
404 $this->remove($entryIdentifier);
409 * Removes all cache entries of this cache which are tagged by the specified tag.
411 * @param array $tags The tags the entries must have
413 * @author Ingo Renner <ingo@typo3.org>
416 public function flushByTags(array $tags) {
417 foreach ($tags as $tag) {
418 $this->flushByTag($tag);
423 * Checks if the given cache entry files are still valid or if their
424 * lifetime has exceeded.
426 * @param string $cacheEntryPathAndFilename
428 * @author Robert Lemke <robert@typo3.org>
430 protected function isCacheFileExpired($cacheEntryPathAndFilename) {
431 if (!file_exists($cacheEntryPathAndFilename)) {
434 $index = (integer)file_get_contents($cacheEntryPathAndFilename, NULL, NULL, filesize($cacheEntryPathAndFilename) - self
::DATASIZE_DIGITS
, self
::DATASIZE_DIGITS
);
435 $expiryTime = file_get_contents($cacheEntryPathAndFilename, NULL, NULL, $index, self
::EXPIRYTIME_LENGTH
);
436 return ($expiryTime != 0 && $expiryTime < $GLOBALS['EXEC_TIME']);
440 * Does garbage collection
443 * @author Karsten Dambekalns <karsten@typo3.org>
446 public function collectGarbage() {
447 if (!$this->cache
instanceof t3lib_cache_frontend_Frontend
) {
448 throw new t3lib_cache_Exception(
449 'Yet no cache frontend has been set via setCache().',
454 $pattern = $this->root
. $this->cacheDirectory
. '*';
455 $filesFound = glob($pattern);
457 if (is_array($filesFound)) {
458 foreach ($filesFound as $cacheFilename) {
459 if ($this->isCacheFileExpired($cacheFilename)) {
460 $this->remove(basename($cacheFilename));
467 * Tries to find the cache entry for the specified identifier.
468 * Usually only one cache entry should be found - if more than one exist, this
469 * is due to some error or crash.
471 * @param string $entryIdentifier The cache entry identifier
472 * @return mixed The file names (including path) as an array if one or more entries could be found, otherwise FALSE
473 * @author Robert Lemke <robert@typo3.org>
474 * @throws t3lib_cache_Exception if no frontend has been set
477 protected function findCacheFilesByIdentifier($entryIdentifier) {
478 if (!$this->cache
instanceof t3lib_cache_frontend_Frontend
) {
479 throw new t3lib_cache_Exception(
480 'Yet no cache frontend has been set via setCache().',
485 $pattern = $this->root
. $this->cacheDirectory
. $entryIdentifier;
486 $filesFound = glob($pattern);
487 if ($filesFound === FALSE ||
count($filesFound) === 0) {
495 * Loads PHP code from the cache and require_onces it right away.
497 * @param string $entryIdentifier An identifier which describes the cache entry to load
498 * @return mixed Potential return value from the include operation
501 public function requireOnce($entryIdentifier) {
502 $pathAndFilename = $this->root
. $this->cacheDirectory
. $entryIdentifier;
503 return ($this->isCacheFileExpired($pathAndFilename)) ?
FALSE : require_once($pathAndFilename);
508 if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE
]['XCLASS']['t3lib/cache/backend/class.t3lib_cache_backend_filebackend.php']) {
509 include_once($TYPO3_CONF_VARS[TYPO3_MODE
]['XCLASS']['t3lib/cache/backend/class.t3lib_cache_backend_filebackend.php']);