63a5388c20ccc863e3c3d198dbf21b0c12ce677c
[Packages/TYPO3.CMS.git] / t3lib / cache / backend / class.t3lib_cache_backend_filebackend.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2009 Ingo Renner <ingo@typo3.org>
6 * All rights reserved
7 *
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.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 *
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.
21 *
22 * This copyright notice MUST APPEAR in all copies of the script!
23 ***************************************************************/
24
25
26 /**
27 * A caching backend which stores cache entries in files
28 *
29 * This file is a backport from FLOW3
30 *
31 * @package TYPO3
32 * @subpackage t3lib_cache
33 * @version $Id$
34 */
35 class t3lib_cache_backend_FileBackend extends t3lib_cache_backend_AbstractBackend {
36
37 const SEPARATOR = '-';
38
39 const FILENAME_EXPIRYTIME_FORMAT = 'YmdHis';
40 const FILENAME_EXPIRYTIME_GLOB = '??????????????';
41 const FILENAME_EXPIRYTIME_UNLIMITED = '99991231235959';
42
43 /**
44 * @var string Directory where the files are stored
45 */
46 protected $cacheDirectory = '';
47
48 /**
49 * Constructs this backend
50 *
51 * @param mixed Configuration options - depends on the actual backend
52 */
53 public function __construct(array $options = array()) {
54 parent::__construct($options);
55
56 if (empty($this->cacheDirectory)) {
57 $cacheDirectory = 'typo3temp/cache/';
58 try {
59 $this->setCacheDirectory($cacheDirectory);
60 } catch(t3lib_cache_Exception $exception) {
61
62 }
63 }
64 }
65
66 /**
67 * Sets the directory where the cache files are stored. By default it is
68 * assumed that the directory is below the TYPO3_DOCUMENT_ROOT. However, an
69 * absolute path can be selected, too.
70 *
71 * @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.
72 * @return void
73 * @throws t3lib_cache_Exception if the directory does not exist, is not writable or could not be created.
74 * @author Robert Lemke <robert@typo3.org>
75 * @author Ingo Renner <ingo@typo3.org>
76 */
77 public function setCacheDirectory($cacheDirectory) {
78 $documentRoot = t3lib_div::getIndpEnv('TYPO3_DOCUMENT_ROOT') . '/';
79
80 // resetting if an absolute path is given
81 if ($cacheDirectory{0} == '/') {
82 $documentRoot = '/';
83 }
84
85 if ($cacheDirectory{strlen($cacheDirectory) - 1} !== '/') {
86 $cacheDirectory .= '/';
87 }
88
89 if (!is_writable($documentRoot . $cacheDirectory)) {
90 t3lib_div::mkdir_deep(
91 $documentRoot,
92 $cacheDirectory
93 );
94 }
95
96 if (!is_dir($documentRoot . $cacheDirectory)) {
97 throw new t3lib_cache_Exception(
98 'The directory "' . $cacheDirectory . '" does not exist.',
99 1203965199
100 );
101 }
102
103 if (!is_writable($documentRoot . $cacheDirectory)) {
104 throw new t3lib_cache_Exception(
105 'The directory "' . $cacheDirectory . '" is not writable.',
106 1203965200
107 );
108 }
109
110 $tagsDirectory = $cacheDirectory . 'tags/';
111
112 if (!is_writable($tagsDirectory)) {
113 t3lib_div::mkdir_deep(
114 $documentRoot,
115 $tagsDirectory
116 );
117 }
118
119 $this->cacheDirectory = $documentRoot . $cacheDirectory;
120 }
121
122 /**
123 * Returns the directory where the cache files are stored
124 *
125 * @return string Full path of the cache directory
126 * @author Robert Lemke <robert@typo3.org>
127 */
128 public function getCacheDirectory() {
129 return $this->cacheDirectory;
130 }
131
132 /**
133 * Saves data in a cache file.
134 *
135 * @param string An identifier for this specific cache entry
136 * @param string The data to be stored
137 * @param array Tags to associate with this cache entry
138 * @param integer Lifetime of this cache entry in seconds. If NULL is specified, the default lifetime is used. "0" means unlimited lifetime.
139 * @return void
140 * @throws t3lib_cache_Exception if the directory does not exist or is not writable, or if no cache frontend has been set.
141 * @throws t3lib_cache_exception_InvalidData if the data to bes stored is not a string.
142 * @author Robert Lemke <robert@typo3.org>
143 */
144 public function set($entryIdentifier, $data, array $tags = array(), $lifetime = NULL) {
145 if (!$this->cache instanceof t3lib_cache_frontend_Frontend) {
146 throw new t3lib_cache_Exception(
147 'No cache frontend has been set yet via setCache().',
148 1204111375
149 );
150 }
151
152 if (!is_string($data)) {
153 throw new t3lib_cache_Exception_InvalidData(
154 'The specified data is of type "' . gettype($data) . '" but a string is expected.',
155 1204481674
156 );
157 }
158
159 if ($lifetime === 0 || ($lifetime === NULL && $this->defaultLifetime === 0)) {
160 $expiryTime = new DateTime('9999-12-31T23:59:59+0000', new DateTimeZone('UTC'));
161 } else {
162 if ($lifetime === NULL) {
163 $lifetime = $this->defaultLifetime;
164 }
165 $expiryTime = new DateTime('now +' . $lifetime . ' seconds', new DateTimeZone('UTC'));
166 }
167
168 $cacheEntryPath = $this->renderCacheEntryPath($entryIdentifier);
169 $filename = $this->renderCacheFilename($entryIdentifier, $expiryTime);
170
171 if (!is_writable($cacheEntryPath)) {
172 try {
173 t3lib_div::mkdir_deep(
174 '',
175 $cacheEntryPath
176 );
177 } catch(Exception $exception) {
178
179 }
180 if (!is_writable($cacheEntryPath)) {
181 throw new t3lib_cache_Exception(
182 'The cache directory "' . $cacheEntryPath . '" could not be created.',
183 1204026250
184 );
185 }
186 }
187
188 $this->remove($entryIdentifier);
189
190 $temporaryFilename = $filename . '.' . uniqid() . '.temp';
191 $result = file_put_contents($cacheEntryPath . $temporaryFilename, $data);
192 if ($result === FALSE) {
193 throw new t3lib_cache_Exception(
194 'The temporary cache file "' . $temporaryFilename . '" could not be written.',
195 1204026251
196 );
197 }
198
199 for ($i = 0; $i < 5; $i++) {
200 $result = rename(
201 $cacheEntryPath . $temporaryFilename,
202 $cacheEntryPath . $filename
203 );
204
205 if ($result === TRUE) {
206 break;
207 }
208 }
209
210 if ($result === FALSE) {
211 throw new t3lib_cache_Exception(
212 'The cache file "' . $filename . '" could not be written.',
213 1222361632
214 );
215 }
216
217 foreach ($tags as $tag) {
218 $tagPath = $this->cacheDirectory . 'tags/' . $tag . '/';
219
220 if (!is_writable($tagPath)) {
221 t3lib_div::mkdir_deep(
222 '',
223 $tagPath
224 );
225 }
226
227 touch($tagPath
228 . $this->cache->getIdentifier()
229 . self::SEPARATOR
230 . $entryIdentifier
231 );
232 }
233 }
234
235 /**
236 * Loads data from a cache file.
237 *
238 * @param string An identifier which describes the cache entry to load
239 * @return mixed The cache entry's content as a string or FALSE if the cache entry could not be loaded
240 * @author Robert Lemke <robert@typo3.org>
241 * @author Karsten Dambekalns <karsten@typo3.org>
242 */
243 public function get($entryIdentifier) {
244 $pathsAndFilenames = $this->findCacheFilesByIdentifier($entryIdentifier);
245
246 if ($pathsAndFilenames === FALSE) {
247 return FALSE;
248 }
249 $pathAndFilename = array_pop($pathsAndFilenames);
250
251 return ($this->isCacheFileExpired($pathAndFilename)) ? FALSE : file_get_contents($pathAndFilename);
252 }
253
254 /**
255 * Checks if a cache entry with the specified identifier exists.
256 *
257 * @param string $entryIdentifier
258 * @return boolean TRUE if such an entry exists, FALSE if not
259 * @author Robert Lemke <robert@typo3.org>
260 * @author Karsten Dambekalns <karsten@typo3.org>
261 */
262 public function has($entryIdentifier) {
263 $pathsAndFilenames = $this->findCacheFilesByIdentifier($entryIdentifier);
264
265 if ($pathsAndFilenames === FALSE) return FALSE;
266
267 return !$this->isCacheFileExpired(array_pop($pathsAndFilenames));
268 }
269
270 /**
271 * Removes all cache entries matching the specified identifier.
272 * Usually this only affects one entry.
273 *
274 * @param string Specifies the cache entry to remove
275 * @return boolean TRUE if (at least) an entry could be removed or FALSE if no entry was found
276 * @author Robert Lemke <robert@typo3.org>
277 */
278 public function remove($entryIdentifier) {
279 $pathsAndFilenames = $this->findCacheFilesByIdentifier($entryIdentifier);
280
281 if ($pathsAndFilenames === FALSE) {
282 return FALSE;
283 }
284
285 foreach ($pathsAndFilenames as $pathAndFilename) {
286 $result = unlink($pathAndFilename);
287 if ($result === FALSE) {
288 return FALSE;
289 }
290 }
291
292 $pathsAndFilenames = $this->findTagFilesByEntry($entryIdentifier);
293 if ($pathsAndFilenames === FALSE) {
294 return FALSE;
295 }
296
297 foreach ($pathsAndFilenames as $pathAndFilename) {
298 $result = unlink($pathAndFilename);
299 if ($result === FALSE) {
300 return FALSE;
301 }
302 }
303
304 return TRUE;
305 }
306
307 /**
308 * Finds and returns all cache entry identifiers which are tagged by the
309 * specified tag.
310 *
311 * @param string The tag to search for
312 * @return array An array with identifiers of all matching entries. An empty array if no entries matched
313 * @author Robert Lemke <robert@typo3.org>
314 * @author Karsten Dambekalns <karsten@typo3.org>
315 */
316 public function findIdentifiersByTag($tag) {
317 if (!$this->cache instanceof t3lib_cache_frontend_Frontend) {
318 throw new t3lib_cache_Exception(
319 'Yet no cache frontend has been set via setCache().',
320 1204111376
321 );
322 }
323
324 $path = $this->cacheDirectory . 'tags/';
325 $pattern = $path . $tag . '/'
326 . $this->cache->getIdentifier() . self::SEPARATOR . '*';
327 $filesFound = glob($pattern);
328
329 if ($filesFound === FALSE || count($filesFound) === 0) {
330 return array();
331 }
332
333 $cacheEntries = array();
334 foreach ($filesFound as $filename) {
335 list(,$entryIdentifier) = explode(self::SEPARATOR, basename($filename));
336 if ($this->has($entryIdentifier)) {
337 $cacheEntries[$entryIdentifier] = $entryIdentifier;
338 }
339 }
340
341 return array_values($cacheEntries);
342 }
343
344 /**
345 * Finds and returns all cache entry identifiers which are tagged by the
346 * specified tags.
347 *
348 * @param array Array of tags to search for
349 * @return array An array with identifiers of all matching entries. An empty array if no entries matched
350 * @author Ingo Renner <ingo@typo3.org>
351 */
352 public function findIdentifiersByTags(array $tags) {
353 $taggedEntries = array();
354 $foundEntries = array();
355
356 foreach ($tags as $tag) {
357 $taggedEntries[$tag] = $this->findIdentifiersByTag($tag);
358 }
359
360 $intersectedTaggedEntries = call_user_func_array('array_intersect', $taggedEntries);
361
362 foreach ($intersectedTaggedEntries as $entryIdentifier) {
363 if ($this->has($entryIdentifier)) {
364 $foundEntries[$entryIdentifier] = $entryIdentifier;
365 }
366 }
367
368 return $foundEntries;
369 }
370
371 /**
372 * Removes all cache entries of this cache.
373 *
374 * @return void
375 * @author Robert Lemke <robert@typo3.org>
376 */
377 public function flush() {
378 if (!$this->cache instanceof t3lib_cache_frontend_Frontend) {
379 throw new t3lib_cache_Exception(
380 'Yet no cache frontend has been set via setCache().',
381 1204111376
382 );
383 }
384
385 $dataPath = $this->cacheDirectory . 'data/' . $this->cache->getIdentifier() . '/';
386 $tagsPath = $this->cacheDirectory . 'tags/' . $this->cache->getIdentifier() . '/';
387
388 t3lib_div::rmdir($dataPath, true);
389 t3lib_div::rmdir($tagsPath, true);
390 }
391
392 /**
393 * Removes all cache entries of this cache which are tagged by the specified tag.
394 *
395 * @param string The tag the entries must have
396 * @return void
397 * @author Ingo Renner <ingo@typo3.org>
398 */
399 public function flushByTag($tag) {
400 $identifiers = $this->findIdentifiersByTag($tag);
401 if (count($identifiers) === 0) {
402 return;
403 }
404
405 foreach ($identifiers as $entryIdentifier) {
406 $this->remove($entryIdentifier);
407 }
408 }
409
410 /**
411 * Removes all cache entries of this cache which are tagged by the specified tag.
412 *
413 * @param array The tags the entries must have
414 * @return void
415 * @author Ingo Renner <ingo@typo3.org>
416 */
417 public function flushByTags(array $tags) {
418 foreach ($tags as $tag) {
419 $this->flushByTag($tag);
420 }
421 }
422
423 /**
424 * Checks if the given cache entry files are still valid or if their
425 * lifetime has exceeded.
426 *
427 * @param string $cacheFilename
428 * @return boolean
429 * @author Robert Lemke <robert@typo3.org>
430 */
431 protected function isCacheFileExpired($cacheFilename) {
432 list($timestamp) = explode(self::SEPARATOR, basename($cacheFilename), 2);
433 return $timestamp < gmdate('YmdHis');
434 }
435
436 /**
437 * Does garbage collection for the given entry or all entries.
438 *
439 * @return void
440 * @author Karsten Dambekalns <karsten@typo3.org>
441 */
442 public function collectGarbage() {
443 if (!$this->cache instanceof t3lib_cache_frontend_Frontend) {
444 throw new t3lib_cache_Exception(
445 'Yet no cache frontend has been set via setCache().',
446 1222686150
447 );
448 }
449
450 $pattern = $this->cacheDirectory . 'data/' . $this->cache->getIdentifier() . '/*/*/*';
451 $filesFound = glob($pattern);
452
453 foreach ($filesFound as $cacheFile) {
454 $splitFilename = explode(self::SEPARATOR, basename($cacheFile), 2);
455 if ($splitFilename[0] < gmdate('YmdHis')) {
456 $this->remove($splitFilename[1]);
457 }
458 }
459 }
460
461 /**
462 * Renders a file name for the specified cache entry
463 *
464 * @param string Identifier for the cache entry
465 * @param DateTime Date and time specifying the expiration of the entry. Must be a UTC time.
466 * @return string Filename of the cache data file
467 * @author Robert Lemke <robert@typo3.org>
468 */
469 protected function renderCacheFilename($identifier, DateTime $expiryTime) {
470 $filename = $expiryTime->format(self::FILENAME_EXPIRYTIME_FORMAT) . self::SEPARATOR . $identifier;
471
472 return $filename;
473 }
474
475 /**
476 * Renders the full path (excluding file name) leading to the given cache entry.
477 * Doesn't check if such a cache entry really exists.
478 *
479 * @param string $identifier Identifier for the cache entry
480 * @return string Absolute path leading to the directory containing the cache entry
481 * @author Robert Lemke <robert@typo3.org>
482 */
483 protected function renderCacheEntryPath($identifier) {
484 $identifierHash = sha1($identifier);
485 return $this->cacheDirectory . 'data/' . $this->cache->getIdentifier() . '/' . $identifierHash[0] . '/' . $identifierHash[1] . '/';
486 }
487
488 /**
489 * Tries to find the cache entry for the specified identifier.
490 * Usually only one cache entry should be found - if more than one exist, this
491 * is due to some error or crash.
492 *
493 * @param string The cache entry identifier
494 * @return mixed The file names (including path) as an array if one or more entries could be found, otherwise FALSE
495 * @author Robert Lemke <robert@typo3.org>
496 * @throws t3lib_cache_Exception if no frontend has been set
497 */
498 protected function findCacheFilesByIdentifier($entryIdentifier) {
499 if (!$this->cache instanceof t3lib_cache_frontend_Frontend) {
500 throw new t3lib_cache_Exception(
501 'Yet no cache frontend has been set via setCache().',
502 1204111376
503 );
504 }
505
506 $pattern = $this->renderCacheEntryPath($entryIdentifier) . self::FILENAME_EXPIRYTIME_GLOB . self::SEPARATOR . $entryIdentifier;
507 $filesFound = glob($pattern);
508
509 return $filesFound;
510 }
511
512
513 /**
514 * Tries to find the tag entries for the specified cache entry.
515 *
516 * @param string The cache entry identifier to find tag files for
517 * @return mixed The file names (including path) as an array if one or more entries could be found, otherwise FALSE
518 * @author Robert Lemke <robert@typo3.org>
519 * @throws t3lib_cache_Exception if no frontend has been set
520 */
521 protected function findTagFilesByEntry($entryIdentifier) {
522 if (!$this->cache instanceof t3lib_cache_frontend_Frontend) {
523 throw new t3lib_cache_Exception(
524 'Yet no cache frontend has been set via setCache().',
525 1204111376
526 );
527 }
528
529 $path = $this->cacheDirectory . 'tags/';
530 $pattern = $path . '*/' . $this->cache->getIdentifier() . self::SEPARATOR . $entryIdentifier;
531 $filesFound = glob($pattern);
532
533 if ($filesFound === FALSE || count($filesFound) === 0) {
534 return FALSE;
535 }
536
537 return $filesFound;
538 }
539 }
540
541
542 if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/cache/backend/class.t3lib_cache_backend_filebackend.php']) {
543 include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/cache/backend/class.t3lib_cache_backend_filebackend.php']);
544 }
545
546 ?>