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
35 class t3lib_cache_backend_FileBackend
extends t3lib_cache_backend_AbstractBackend
{
37 const SEPARATOR
= '^';
39 const EXPIRYTIME_FORMAT
= 'YmdHis';
40 const EXPIRYTIME_LENGTH
= 14;
43 * @var string Directory where the files are stored
45 protected $cacheDirectory = '';
47 protected $root = '/';
50 * Maximum allowed file path length in the current environment.
51 * Will be set in initializeObject()
55 protected $maximumPathLength = null;
58 * Constructs this backend
60 * @param mixed Configuration options - depends on the actual backend
62 public function __construct(array $options = array()) {
63 parent
::__construct($options);
64 if (TYPO3_OS
=== 'WIN') {
68 if (empty($this->cacheDirectory
)) {
69 $cacheDirectory = 'typo3temp/cache/';
71 $this->setCacheDirectory($cacheDirectory);
72 } catch(t3lib_cache_Exception
$exception) {
77 if (is_null($this->maximumPathLength
)) {
78 $this->maximumPathLength
= t3lib_div
::getMaximumPathLength();
83 * Sets the directory where the cache files are stored. By default it is
84 * assumed that the directory is below the TYPO3_DOCUMENT_ROOT. However, an
85 * absolute path can be selected, too.
87 * @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.
89 * @throws t3lib_cache_Exception if the directory does not exist, is not writable or could not be created.
90 * @author Robert Lemke <robert@typo3.org>
91 * @author Ingo Renner <ingo@typo3.org>
93 public function setCacheDirectory($cacheDirectory) {
94 $documentRoot = PATH_site
;
96 // resetting if an absolute path is given
97 if ($cacheDirectory{0} == '/') {
101 if ($cacheDirectory{strlen($cacheDirectory) - 1} !== '/') {
102 $cacheDirectory .= '/';
105 if (!is_writable($documentRoot . $cacheDirectory)) {
106 t3lib_div
::mkdir_deep(
112 if (!is_dir($documentRoot . $cacheDirectory)) {
113 throw new t3lib_cache_Exception(
114 'The directory "' . $cacheDirectory . '" does not exist.',
119 if (!is_writable($documentRoot . $cacheDirectory)) {
120 throw new t3lib_cache_Exception(
121 'The directory "' . $cacheDirectory . '" is not writable.',
126 $tagsDirectory = $cacheDirectory . 'tags/';
128 if (!is_writable($tagsDirectory)) {
129 t3lib_div
::mkdir_deep(
135 $this->cacheDirectory
= $documentRoot . $cacheDirectory;
139 * Returns the directory where the cache files are stored
141 * @return string Full path of the cache directory
142 * @author Robert Lemke <robert@typo3.org>
144 public function getCacheDirectory() {
145 return $this->cacheDirectory
;
149 * Saves data in a cache file.
151 * @param string An identifier for this specific cache entry
152 * @param string The data to be stored
153 * @param array Tags to associate with this cache entry
154 * @param integer Lifetime of this cache entry in seconds. If NULL is specified, the default lifetime is used. "0" means unlimited lifetime.
156 * @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.
157 * @throws t3lib_cache_exception_InvalidData if the data to bes stored is not a string.
158 * @author Robert Lemke <robert@typo3.org>
160 public function set($entryIdentifier, $data, array $tags = array(), $lifetime = NULL) {
161 if (!$this->cache
instanceof t3lib_cache_frontend_Frontend
) {
162 throw new t3lib_cache_Exception(
163 'No cache frontend has been set yet via setCache().',
168 if (!is_string($data)) {
169 throw new t3lib_cache_Exception_InvalidData(
170 'The specified data is of type "' . gettype($data) . '" but a string is expected.',
175 $expirytime = $this->calculateExpiryTime($lifetime);
176 $cacheEntryPath = $this->renderCacheEntryPath($entryIdentifier);
178 if (!is_writable($cacheEntryPath)) {
180 t3lib_div
::mkdir_deep(
184 } catch(Exception
$exception) {
187 if (!is_writable($cacheEntryPath)) {
188 throw new t3lib_cache_Exception(
189 'The cache directory "' . $cacheEntryPath . '" could not be created.',
195 $this->remove($entryIdentifier);
197 $data = $expirytime->format(self
::EXPIRYTIME_FORMAT
) . $data;
198 $cacheEntryPathAndFilename = $cacheEntryPath . uniqid() . '.temp';
199 if (strlen($cacheEntryPathAndFilename) > $this->maximumPathLength
) {
200 throw new t3lib_cache_Exception(
201 'The length of the temporary cache file path "' . $cacheEntryPathAndFilename . '" is ' . strlen($cacheEntryPathAndFilename) . ' characters long and exceeds the maximum path length of ' . $this->maximumPathLength
. '. Please consider setting the temporaryDirectoryBase option to a shorter path. ',
206 $result = file_put_contents($cacheEntryPathAndFilename, $data);
207 if ($result === FALSE) {
208 throw new t3lib_cache_Exception(
209 'The temporary cache file "' . $cacheEntryPathAndFilename . '" could not be written.',
214 for ($i = 0; $i < 5; $i++
) {
215 $result = rename($cacheEntryPathAndFilename, $cacheEntryPath . $entryIdentifier);
216 if ($result === TRUE) {
221 if ($result === FALSE) {
222 throw new t3lib_cache_Exception(
223 'The cache file "' . $entryIdentifier . '" could not be written.',
228 foreach ($tags as $tag) {
229 $this->setTag($entryIdentifier, $tag);
234 * Creates a tag that is associated with the given cache identifier
236 * @param string $entryIdentifier An identifier for this specific cache entry
237 * @param string Tag to associate with this cache entry
239 * @throws t3lib_cache_Exception if the tag path is not writable or exceeds the maximum allowed path length
240 * @author Bastian Waidelich <bastian@typo3.org>
241 * @author Ingo Renner <ingo@typo3.org>
243 protected function setTag($entryIdentifier, $tag) {
244 $tagPath = $this->cacheDirectory
. 'tags/' . $tag . '/';
246 if (!is_writable($tagPath)) {
247 t3lib_div
::mkdir_deep($this->root
, $tagPath);
248 if (!is_writable($tagPath)) {
249 throw new t3lib_cache_Exception(
250 'The tag directory "' . $tagPath . '" could not be created.',
256 $tagPathAndFilename = $tagPath . $this->cache
->getIdentifier()
257 . self
::SEPARATOR
. $entryIdentifier;
258 if (strlen($tagPathAndFilename) > $this->maximumPathLength
) {
259 throw new t3lib_cache_Exception(
260 'The length of the tag path "' . $tagPathAndFilename . '" is ' . strlen($tagPathAndFilename) . ' characters long and exceeds the maximum path length of ' . $maximumPathLength . '. Please consider setting the temporaryDirectoryBase option to a shorter path. ',
264 touch($tagPathAndFilename);
268 * Loads data from a cache file.
270 * @param string An identifier which describes the cache entry to load
271 * @return mixed The cache entry's content as a string or FALSE if the cache entry could not be loaded
272 * @author Robert Lemke <robert@typo3.org>
273 * @author Karsten Dambekalns <karsten@typo3.org>
275 public function get($entryIdentifier) {
276 $pathAndFilename = $this->renderCacheEntryPath($entryIdentifier) . $entryIdentifier;
277 return ($this->isCacheFileExpired($pathAndFilename)) ?
FALSE : file_get_contents($pathAndFilename, NULL, NULL, self
::EXPIRYTIME_LENGTH
);
281 * Checks if a cache entry with the specified identifier exists.
283 * @param string $entryIdentifier
284 * @return boolean TRUE if such an entry exists, FALSE if not
285 * @author Robert Lemke <robert@typo3.org>
287 public function has($entryIdentifier) {
288 return !$this->isCacheFileExpired($this->renderCacheEntryPath($entryIdentifier) . $entryIdentifier);
292 * Removes all cache entries matching the specified identifier.
293 * Usually this only affects one entry.
295 * @param string Specifies the cache entry to remove
296 * @return boolean TRUE if (at least) an entry could be removed or FALSE if no entry was found
297 * @author Robert Lemke <robert@typo3.org>
299 public function remove($entryIdentifier) {
300 $pathAndFilename = $this->renderCacheEntryPath($entryIdentifier) . $entryIdentifier;
302 if (!file_exists($pathAndFilename)) {
306 if (unlink ($pathAndFilename) === FALSE) {
310 foreach($this->findTagFilesByEntry($entryIdentifier) as $pathAndFilename) {
311 if (!file_exists($pathAndFilename)) {
315 if (unlink ($pathAndFilename) === FALSE) {
324 * Finds and returns all cache entry identifiers which are tagged by the
327 * @param string The tag to search for
328 * @return array An array with identifiers of all matching entries. An empty array if no entries matched
329 * @author Robert Lemke <robert@typo3.org>
330 * @author Karsten Dambekalns <karsten@typo3.org>
332 public function findIdentifiersByTag($tag) {
333 if (!$this->cache
instanceof t3lib_cache_frontend_Frontend
) {
334 throw new t3lib_cache_Exception(
335 'Yet no cache frontend has been set via setCache().',
340 $path = $this->cacheDirectory
. 'tags/';
341 $pattern = $path . $tag . '/'
342 . $this->cache
->getIdentifier() . self
::SEPARATOR
. '*';
343 $filesFound = glob($pattern);
345 if ($filesFound === FALSE ||
count($filesFound) === 0) {
349 $cacheEntries = array();
350 foreach ($filesFound as $filename) {
351 list(,$entryIdentifier) = explode(self
::SEPARATOR
, basename($filename));
352 if ($this->has($entryIdentifier)) {
353 $cacheEntries[$entryIdentifier] = $entryIdentifier;
357 return array_values($cacheEntries);
361 * Finds and returns all cache entry identifiers which are tagged by the
364 * @param array Array of tags to search for
365 * @return array An array with identifiers of all matching entries. An empty array if no entries matched
366 * @author Ingo Renner <ingo@typo3.org>
368 public function findIdentifiersByTags(array $tags) {
369 $taggedEntries = array();
370 $foundEntries = array();
372 foreach ($tags as $tag) {
373 $taggedEntries[$tag] = $this->findIdentifiersByTag($tag);
376 $intersectedTaggedEntries = call_user_func_array('array_intersect', $taggedEntries);
378 foreach ($intersectedTaggedEntries as $entryIdentifier) {
379 if ($this->has($entryIdentifier)) {
380 $foundEntries[$entryIdentifier] = $entryIdentifier;
384 return $foundEntries;
388 * Removes all cache entries of this cache.
391 * @author Robert Lemke <robert@typo3.org>
393 public function flush() {
394 if (!$this->cache
instanceof t3lib_cache_frontend_Frontend
) {
395 throw new t3lib_cache_Exception(
396 'Yet no cache frontend has been set via setCache().',
401 $dataPath = $this->cacheDirectory
. 'data/' . $this->cache
->getIdentifier() . '/';
402 $tagsPath = $this->cacheDirectory
. 'tags/';
404 t3lib_div
::rmdir($dataPath, true);
405 t3lib_div
::rmdir($tagsPath, true);
409 * Removes all cache entries of this cache which are tagged by the specified tag.
411 * @param string The tag the entries must have
413 * @author Ingo Renner <ingo@typo3.org>
415 public function flushByTag($tag) {
416 $identifiers = $this->findIdentifiersByTag($tag);
417 if (count($identifiers) === 0) {
421 foreach ($identifiers as $entryIdentifier) {
422 $this->remove($entryIdentifier);
427 * Removes all cache entries of this cache which are tagged by the specified tag.
429 * @param array The tags the entries must have
431 * @author Ingo Renner <ingo@typo3.org>
433 public function flushByTags(array $tags) {
434 foreach ($tags as $tag) {
435 $this->flushByTag($tag);
440 * Checks if the given cache entry files are still valid or if their
441 * lifetime has exceeded.
443 * @param string $cacheFilename
445 * @author Robert Lemke <robert@typo3.org>
447 protected function isCacheFileExpired($cacheFilename) {
448 $timestamp = (file_exists($cacheFilename)) ?
file_get_contents($cacheFilename, NULL, NULL, 0, self
::EXPIRYTIME_LENGTH
) : 1;
449 return $timestamp < gmdate(self
::EXPIRYTIME_FORMAT
);
453 * Does garbage collection
456 * @author Karsten Dambekalns <karsten@typo3.org>
458 public function collectGarbage() {
459 if (!$this->cache
instanceof t3lib_cache_frontend_Frontend
) {
460 throw new t3lib_cache_Exception(
461 'Yet no cache frontend has been set via setCache().',
466 $pattern = $this->cacheDirectory
. 'data/' . $this->cache
->getIdentifier() . '/*/*/*';
467 $filesFound = glob($pattern);
469 foreach ($filesFound as $cacheFilename) {
470 if ($this->isCacheFileExpired($cacheFilename)) {
471 $this->remove(basename($cacheFilename));
477 * Renders the full path (excluding file name) leading to the given cache entry.
478 * Doesn't check if such a cache entry really exists.
480 * @param string $identifier Identifier for the cache entry
481 * @return string Absolute path leading to the directory containing the cache entry
482 * @author Robert Lemke <robert@typo3.org>
485 protected function renderCacheEntryPath($identifier) {
486 $identifierHash = sha1($identifier);
487 return $this->cacheDirectory
. 'data/' . $this->cache
->getIdentifier() . '/' . $identifierHash[0] . '/' . $identifierHash[1] . '/';
491 * Tries to find the cache entry for the specified identifier.
492 * Usually only one cache entry should be found - if more than one exist, this
493 * is due to some error or crash.
495 * @param string The cache entry identifier
496 * @return mixed The file names (including path) as an array if one or more entries could be found, otherwise FALSE
497 * @author Robert Lemke <robert@typo3.org>
498 * @throws t3lib_cache_Exception if no frontend has been set
501 protected function findCacheFilesByIdentifier($entryIdentifier) {
502 if (!$this->cache
instanceof t3lib_cache_frontend_Frontend
) {
503 throw new t3lib_cache_Exception(
504 'Yet no cache frontend has been set via setCache().',
509 $pattern = $this->renderCacheEntryPath($entryIdentifier) . $entryIdentifier;
510 $filesFound = glob($pattern);
511 if ($filesFound === FALSE ||
count($filesFound) === 0) {
520 * Tries to find the tag entries for the specified cache entry.
522 * @param string The cache entry identifier to find tag files for
523 * @return array The file names (including path)
524 * @author Robert Lemke <robert@typo3.org>
525 * @throws t3lib_cache_Exception if no frontend has been set
528 protected function findTagFilesByEntry($entryIdentifier) {
529 if (!$this->cache
instanceof t3lib_cache_frontend_Frontend
) {
530 throw new t3lib_cache_Exception(
531 'Yet no cache frontend has been set via setCache().',
536 $path = $this->cacheDirectory
. 'tags/';
537 $pattern = $path . '*/' . $this->cache
->getIdentifier() . self
::SEPARATOR
. $entryIdentifier;
538 return glob($pattern);
543 if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE
]['XCLASS']['t3lib/cache/backend/class.t3lib_cache_backend_filebackend.php']) {
544 include_once($TYPO3_CONF_VARS[TYPO3_MODE
]['XCLASS']['t3lib/cache/backend/class.t3lib_cache_backend_filebackend.php']);