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
{
38 const SEPARATOR
= '^';
40 const EXPIRYTIME_FORMAT
= 'YmdHis';
41 const EXPIRYTIME_LENGTH
= 14;
44 * @var string Directory where the files are stored
46 protected $cacheDirectory = '';
48 protected $root = '/';
51 * Maximum allowed file path length in the current environment.
52 * Will be set in initializeObject()
56 protected $maximumPathLength = null;
59 * Constructs this backend
61 * @param mixed Configuration options - depends on the actual backend
63 public function __construct(array $options = array()) {
64 parent
::__construct($options);
66 if (empty($this->cacheDirectory
)) {
67 $cacheDirectory = 'typo3temp/cache/';
69 $this->setCacheDirectory($cacheDirectory);
70 } catch(t3lib_cache_Exception
$exception) {
75 if (is_null($this->maximumPathLength
)) {
76 $this->maximumPathLength
= t3lib_div
::getMaximumPathLength();
81 * Sets the directory where the cache files are stored. By default it is
82 * assumed that the directory is below the TYPO3_DOCUMENT_ROOT. However, an
83 * absolute path can be selected, too.
85 * @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.
87 * @throws t3lib_cache_Exception if the directory does not exist, is not writable or could not be created.
88 * @author Robert Lemke <robert@typo3.org>
89 * @author Ingo Renner <ingo@typo3.org>
91 public function setCacheDirectory($cacheDirectory) {
92 $documentRoot = PATH_site
;
94 if (($open_basedir = ini_get('open_basedir'))) {
97 if (TYPO3_OS
=== 'WIN') {
99 $cacheDirectory = str_replace('\\', '/', $cacheDirectory);
100 if (!(preg_match('/[A-Z]:/', substr($cacheDirectory,0,2)))) {
101 $cacheDirectory = PATH_site
. $cacheDirectory;
105 if ($cacheDirectory[0] != '/') {
106 // relative path to cache directory.
107 $cacheDirectory = PATH_site
. $cacheDirectory;
110 $basedirs = explode($delimiter, $open_basedir);
112 $cacheDirectoryInBaseDir = FALSE;
114 foreach ($basedirs as $basedir) {
115 if (TYPO3_OS
=== 'WIN') {
116 $basedir = str_replace('\\', '/', $basedir);
118 if ($basedir[strlen($basedir) - 1] !== '/') {
121 if (t3lib_div
::isFirstPartOfStr($cacheDirectory, $basedir)) {
122 $documentRoot = $basedir;
123 $cacheDirectory = str_replace($basedir, '', $cacheDirectory);
124 $cacheDirectoryInBaseDir = TRUE;
129 if (!$cacheDirectoryInBaseDir) {
130 throw new t3lib_cache_Exception(
131 'Open_basedir restriction in effect. The directory "' . $cacheDirectory . '" is not in an allowed path.'
135 if ($cacheDirectory[0] == '/') {
136 // absolute path to cache directory.
140 if (TYPO3_OS
=== 'WIN') {
145 // after this point all paths have '/' as directory seperator
147 if ($cacheDirectory[strlen($cacheDirectory) - 1] !== '/') {
148 $cacheDirectory .= '/';
151 if (!is_writable($documentRoot . $cacheDirectory)) {
152 t3lib_div
::mkdir_deep(
158 if (!is_dir($documentRoot . $cacheDirectory)) {
159 throw new t3lib_cache_Exception(
160 'The directory "' . $documentRoot . $cacheDirectory . '" does not exist.',
165 if (!is_writable($documentRoot . $cacheDirectory)) {
166 throw new t3lib_cache_Exception(
167 'The directory "' . $documentRoot . $cacheDirectory . '" is not writable.',
172 $tagsDirectory = $cacheDirectory . 'tags/';
176 if (!is_writable($documentRoot . $tagsDirectory)) {
177 t3lib_div
::mkdir_deep(
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>
192 public function getCacheDirectory() {
193 return $this->root
. $this->cacheDirectory
;
197 * Saves data in a cache file.
199 * @param string An identifier for this specific cache entry
200 * @param string The data to be stored
201 * @param array Tags to associate with this cache entry
202 * @param integer Lifetime of this cache entry in seconds. If NULL is specified, the default lifetime is used. "0" means unlimited lifetime.
204 * @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.
205 * @throws t3lib_cache_exception_InvalidData if the data to bes stored is not a string.
206 * @author Robert Lemke <robert@typo3.org>
208 public function set($entryIdentifier, $data, array $tags = array(), $lifetime = NULL) {
209 if (!$this->cache
instanceof t3lib_cache_frontend_Frontend
) {
210 throw new t3lib_cache_Exception(
211 'No cache frontend has been set yet via setCache().',
216 if (!is_string($data)) {
217 throw new t3lib_cache_Exception_InvalidData(
218 'The specified data is of type "' . gettype($data) . '" but a string is expected.',
223 $expirytime = $this->calculateExpiryTime($lifetime);
224 $cacheEntryPath = $this->renderCacheEntryPath($entryIdentifier);
225 $absCacheEntryPath = $this->root
. $cacheEntryPath;
228 if (!is_writable($absCacheEntryPath)) {
230 t3lib_div
::mkdir_deep(
234 } catch(Exception
$exception) {
238 if (!is_writable($absCacheEntryPath)) {
239 throw new t3lib_cache_Exception(
240 'The cache directory "' . $absCacheEntryPath . '" could not be created.',
246 $this->remove($entryIdentifier);
248 $data = $expirytime->format(self
::EXPIRYTIME_FORMAT
) . $data;
249 $cacheEntryPathAndFilename = $absCacheEntryPath . uniqid() . '.temp';
250 if (strlen($cacheEntryPathAndFilename) > $this->maximumPathLength
) {
251 throw new t3lib_cache_Exception(
252 'The length of the temporary cache file path "' . $cacheEntryPathAndFilename .
253 '" is ' . strlen($cacheEntryPathAndFilename) . ' characters long and exceeds the maximum path length of ' .
254 $this->maximumPathLength
. '. Please consider setting the temporaryDirectoryBase option to a shorter path. ',
258 $result = file_put_contents($cacheEntryPathAndFilename, $data);
259 if ($result === FALSE) {
260 throw new t3lib_cache_Exception(
261 'The temporary cache file "' . $cacheEntryPathAndFilename . '" could not be written.',
266 for ($i = 0; $i < 5; $i++
) {
267 $result = rename($cacheEntryPathAndFilename, $absCacheEntryPath . $entryIdentifier);
268 if ($result === TRUE) {
273 if ($result === FALSE) {
274 throw new t3lib_cache_Exception(
275 'The cache file "' . $entryIdentifier . '" could not be written.',
280 foreach ($tags as $tag) {
281 $this->setTag($entryIdentifier, $tag);
286 * Creates a tag that is associated with the given cache identifier
288 * @param string $entryIdentifier An identifier for this specific cache entry
289 * @param string $tag Tag to associate with this cache entry
291 * @throws t3lib_cache_Exception if the tag path is not writable or exceeds the maximum allowed path length
292 * @author Bastian Waidelich <bastian@typo3.org>
293 * @author Ingo Renner <ingo@typo3.org>
295 protected function setTag($entryIdentifier, $tag) {
296 $tagPath = $this->cacheDirectory
. 'tags/' . $tag . '/';
297 $absTagPath = $this->root
. $tagPath;
298 if (!is_writable($absTagPath)) {
299 t3lib_div
::mkdir_deep($this->root
, $tagPath);
300 if (!is_writable($absTagPath)) {
301 throw new t3lib_cache_Exception(
302 'The tag directory "' . $absTagPath . '" could not be created.',
308 $tagPathAndFilename = $absTagPath . $this->cacheIdentifier
309 . self
::SEPARATOR
. $entryIdentifier;
310 if (strlen($tagPathAndFilename) > $this->maximumPathLength
) {
311 throw new t3lib_cache_Exception(
312 'The length of the tag path "' . $tagPathAndFilename . '" is ' . strlen($tagPathAndFilename) .
313 ' characters long and exceeds the maximum path length of ' . $this->maximumPathLength
.
314 '. Please consider setting the temporaryDirectoryBase option to a shorter path. ',
318 touch($tagPathAndFilename);
322 * Loads data from a cache file.
324 * @param string An identifier which describes the cache entry to load
325 * @return mixed The cache entry's content as a string or FALSE if the cache entry could not be loaded
326 * @author Robert Lemke <robert@typo3.org>
327 * @author Karsten Dambekalns <karsten@typo3.org>
329 public function get($entryIdentifier) {
330 $pathAndFilename = $this->root
. $this->renderCacheEntryPath($entryIdentifier) . $entryIdentifier;
331 return ($this->isCacheFileExpired($pathAndFilename)) ?
FALSE : file_get_contents($pathAndFilename, NULL, NULL, self
::EXPIRYTIME_LENGTH
);
335 * Checks if a cache entry with the specified identifier exists.
337 * @param string $entryIdentifier
338 * @return boolean TRUE if such an entry exists, FALSE if not
339 * @author Robert Lemke <robert@typo3.org>
341 public function has($entryIdentifier) {
342 return !$this->isCacheFileExpired($this->root
. $this->renderCacheEntryPath($entryIdentifier) . $entryIdentifier);
346 * Removes all cache entries matching the specified identifier.
347 * Usually this only affects one entry.
349 * @param string Specifies the cache entry to remove
350 * @return boolean TRUE if (at least) an entry could be removed or FALSE if no entry was found
351 * @author Robert Lemke <robert@typo3.org>
353 public function remove($entryIdentifier) {
354 $pathAndFilename = $this->root
. $this->renderCacheEntryPath($entryIdentifier) . $entryIdentifier;
356 if (!file_exists($pathAndFilename)) {
360 if (unlink($pathAndFilename) === FALSE) {
364 foreach($this->findTagFilesByEntry($entryIdentifier) as $pathAndFilename) {
365 if (!file_exists($pathAndFilename)) {
369 if (unlink($pathAndFilename) === FALSE) {
378 * Finds and returns all cache entry identifiers which are tagged by the
381 * @param string The tag to search for
382 * @return array An array with identifiers of all matching entries. An empty array if no entries matched
383 * @author Robert Lemke <robert@typo3.org>
384 * @author Karsten Dambekalns <karsten@typo3.org>
386 public function findIdentifiersByTag($tag) {
387 if (!$this->cache
instanceof t3lib_cache_frontend_Frontend
) {
388 throw new t3lib_cache_Exception(
389 'Yet no cache frontend has been set via setCache().',
394 $path = $this->root
. $this->cacheDirectory
. 'tags/';
395 $pattern = $path . $tag . '/' . $this->cacheIdentifier
. self
::SEPARATOR
. '*';
396 $filesFound = glob($pattern);
398 if ($filesFound === FALSE ||
count($filesFound) === 0) {
402 $cacheEntries = array();
403 foreach ($filesFound as $filename) {
404 list(,$entryIdentifier) = explode(self
::SEPARATOR
, basename($filename));
405 if ($this->has($entryIdentifier)) {
406 $cacheEntries[$entryIdentifier] = $entryIdentifier;
410 return array_values($cacheEntries);
414 * Finds and returns all cache entry identifiers which are tagged by the
417 * @param array Array of tags to search for
418 * @return array An array with identifiers of all matching entries. An empty array if no entries matched
419 * @author Ingo Renner <ingo@typo3.org>
421 public function findIdentifiersByTags(array $tags) {
422 $taggedEntries = array();
423 $foundEntries = array();
425 foreach ($tags as $tag) {
426 $taggedEntries[$tag] = $this->findIdentifiersByTag($tag);
429 $intersectedTaggedEntries = call_user_func_array('array_intersect', $taggedEntries);
431 foreach ($intersectedTaggedEntries as $entryIdentifier) {
432 if ($this->has($entryIdentifier)) {
433 $foundEntries[$entryIdentifier] = $entryIdentifier;
437 return $foundEntries;
441 * Removes all cache entries of this cache.
444 * @author Robert Lemke <robert@typo3.org>
446 public function flush() {
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 $dataPath = $this->root
. $this->cacheDirectory
. 'data/' . $this->cacheIdentifier
. '/';
455 $tagsPath = $this->root
. $this->cacheDirectory
. 'tags/';
457 t3lib_div
::rmdir($dataPath, true);
458 t3lib_div
::rmdir($tagsPath, true);
462 * Removes all cache entries of this cache which are tagged by the specified tag.
464 * @param string The tag the entries must have
466 * @author Ingo Renner <ingo@typo3.org>
468 public function flushByTag($tag) {
469 $identifiers = $this->findIdentifiersByTag($tag);
470 if (count($identifiers) === 0) {
474 foreach ($identifiers as $entryIdentifier) {
475 $this->remove($entryIdentifier);
480 * Removes all cache entries of this cache which are tagged by the specified tag.
482 * @param array The tags the entries must have
484 * @author Ingo Renner <ingo@typo3.org>
486 public function flushByTags(array $tags) {
487 foreach ($tags as $tag) {
488 $this->flushByTag($tag);
493 * Checks if the given cache entry files are still valid or if their
494 * lifetime has exceeded.
496 * @param string $cacheFilename
498 * @author Robert Lemke <robert@typo3.org>
500 protected function isCacheFileExpired($cacheFilename) {
501 $timestamp = (file_exists($cacheFilename)) ?
file_get_contents($cacheFilename, NULL, NULL, 0, self
::EXPIRYTIME_LENGTH
) : 1;
502 return $timestamp < gmdate(self
::EXPIRYTIME_FORMAT
);
506 * Does garbage collection
509 * @author Karsten Dambekalns <karsten@typo3.org>
511 public function collectGarbage() {
512 if (!$this->cache
instanceof t3lib_cache_frontend_Frontend
) {
513 throw new t3lib_cache_Exception(
514 'Yet no cache frontend has been set via setCache().',
519 $pattern = $this->root
. $this->cacheDirectory
. 'data/' . $this->cacheIdentifier
. '/*/*/*';
520 $filesFound = glob($pattern);
522 foreach ($filesFound as $cacheFilename) {
523 if ($this->isCacheFileExpired($cacheFilename)) {
524 $this->remove(basename($cacheFilename));
530 * Renders the full path (excluding file name) leading to the given cache entry.
531 * Doesn't check if such a cache entry really exists.
533 * @param string $identifier Identifier for the cache entry
534 * @return string Absolute path leading to the directory containing the cache entry
535 * @author Robert Lemke <robert@typo3.org>
538 protected function renderCacheEntryPath($identifier) {
539 $identifierHash = sha1($identifier);
540 return $this->cacheDirectory
. 'data/' . $this->cacheIdentifier
. '/' . $identifierHash[0] . '/' . $identifierHash[1] . '/';
544 * Tries to find the cache entry for the specified identifier.
545 * Usually only one cache entry should be found - if more than one exist, this
546 * is due to some error or crash.
548 * @param string $entryIdentifier The cache entry identifier
549 * @return mixed The file names (including path) as an array if one or more entries could be found, otherwise FALSE
550 * @author Robert Lemke <robert@typo3.org>
551 * @throws t3lib_cache_Exception if no frontend has been set
554 protected function findCacheFilesByIdentifier($entryIdentifier) {
555 if (!$this->cache
instanceof t3lib_cache_frontend_Frontend
) {
556 throw new t3lib_cache_Exception(
557 'Yet no cache frontend has been set via setCache().',
562 $pattern = $this->root
. $this->renderCacheEntryPath($entryIdentifier) . $entryIdentifier;
563 $filesFound = glob($pattern);
564 if ($filesFound === FALSE ||
count($filesFound) === 0) {
573 * Tries to find the tag entries for the specified cache entry.
575 * @param string $entryIdentifier The cache entry identifier to find tag files for
576 * @return array The file names (including path)
577 * @author Robert Lemke <robert@typo3.org>
578 * @throws t3lib_cache_Exception if no frontend has been set
581 protected function findTagFilesByEntry($entryIdentifier) {
582 if (!$this->cache
instanceof t3lib_cache_frontend_Frontend
) {
583 throw new t3lib_cache_Exception(
584 'Yet no cache frontend has been set via setCache().',
589 $path = $this->root
. $this->cacheDirectory
. 'tags/';
590 $pattern = $path . '*/' . $this->cacheIdentifier
. self
::SEPARATOR
. $entryIdentifier;
591 $tagFilesFound = glob($pattern);
592 return ($tagFilesFound ?
$tagFilesFound : array());
597 if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE
]['XCLASS']['t3lib/cache/backend/class.t3lib_cache_backend_filebackend.php']) {
598 include_once($TYPO3_CONF_VARS[TYPO3_MODE
]['XCLASS']['t3lib/cache/backend/class.t3lib_cache_backend_filebackend.php']);