[TASK] Remove superfluous parenthesis in sysext core
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Cache / Backend / SimpleFileBackend.php
1 <?php
2 namespace TYPO3\CMS\Core\Cache\Backend;
3
4 /* *
5 * This script belongs to the FLOW3 framework. *
6 * *
7 * It is free software; you can redistribute it and/or modify it under *
8 * the terms of the GNU Lesser General Public License, either version 3 *
9 * of the License, or (at your option) any later version. *
10 * *
11 * The TYPO3 project - inspiring people to share! *
12 * */
13 /**
14 * A caching backend which stores cache entries in files, but does not support or
15 * care about expiry times and tags.
16 *
17 * @package TYPO3
18 * @subpackage t3lib_cache
19 * @api
20 */
21 class SimpleFileBackend extends \TYPO3\CMS\Core\Cache\Backend\AbstractBackend implements \TYPO3\CMS\Core\Cache\Backend\PhpCapableBackendInterface {
22
23 const SEPARATOR = '^';
24 const EXPIRYTIME_FORMAT = 'YmdHis';
25 const EXPIRYTIME_LENGTH = 14;
26 const DATASIZE_DIGITS = 10;
27 /**
28 * Directory where the files are stored
29 *
30 * @var string
31 */
32 protected $cacheDirectory = '';
33
34 /**
35 * TYPO3 v4 note: This variable is only available in v5
36 * Temporary path to cache directory before setCache() was called. It is
37 * set by setCacheDirectory() and used in setCache() method which calls
38 * the directory creation if needed. The variable is not used afterwards,
39 * the final cache directory path is stored in $this->cacheDirectory then.
40 *
41 * @var string Temporary path to cache directory
42 */
43 protected $temporaryCacheDirectory = '';
44
45 /**
46 * A file extension to use for each cache entry.
47 *
48 * @var string
49 */
50 protected $cacheEntryFileExtension = '';
51
52 /**
53 * @var array
54 */
55 protected $cacheEntryIdentifiers = array();
56
57 /**
58 * @var boolean
59 */
60 protected $frozen = FALSE;
61
62 /**
63 * If the extension "igbinary" is installed, use it for increased performance.
64 * Caching the result of extension_loaded() here is faster than calling extension_loaded() multiple times.
65 *
66 * @var boolean
67 */
68 protected $useIgBinary = FALSE;
69
70 /**
71 * Initializes this cache frontend
72 *
73 * @return void
74 */
75 public function initializeObject() {
76 $this->useIgBinary = extension_loaded('igbinary');
77 }
78
79 /**
80 * Sets a reference to the cache frontend which uses this backend and
81 * initializes the default cache directory.
82 *
83 * TYPO3 v4 note: This method is different between TYPO3 v4 and FLOW3
84 * because the Environment class to get the path to a temporary directory
85 * does not exist in v4.
86 *
87 * @param \TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cache The cache frontend
88 * @throws \TYPO3\CMS\Core\Cache\Exception
89 * @return void
90 */
91 public function setCache(\TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cache) {
92 parent::setCache($cache);
93 if (empty($this->temporaryCacheDirectory)) {
94 // If no cache directory was given with cacheDirectory
95 // configuration option, set it to a path below typo3temp/
96 $temporaryCacheDirectory = PATH_site . 'typo3temp/';
97 } else {
98 $temporaryCacheDirectory = $this->temporaryCacheDirectory;
99 }
100 $codeOrData = $cache instanceof \TYPO3\CMS\Core\Cache\Frontend\PhpFrontend ? 'Code' : 'Data';
101 $finalCacheDirectory = $temporaryCacheDirectory . 'Cache/' . $codeOrData . '/' . $this->cacheIdentifier . '/';
102 if (!is_dir($finalCacheDirectory)) {
103 $this->createFinalCacheDirectory($finalCacheDirectory);
104 }
105 unset($this->temporaryCacheDirectory);
106 $this->cacheDirectory = $finalCacheDirectory;
107 $this->cacheEntryFileExtension = $cache instanceof \TYPO3\CMS\Core\Cache\Frontend\PhpFrontend ? '.php' : '';
108 if (strlen($this->cacheDirectory) + 23 > \TYPO3\CMS\Core\Utility\GeneralUtility::getMaximumPathLength()) {
109 throw new \TYPO3\CMS\Core\Cache\Exception('The length of the temporary cache file path "' . $this->cacheDirectory . '" exceeds the ' . 'maximum path length of ' . (\TYPO3\CMS\Core\Utility\GeneralUtility::getMaximumPathLength() - 23) . '. Please consider ' . 'setting the temporaryDirectoryBase option to a shorter path.', 1248710426);
110 }
111 }
112
113 /**
114 * Sets the directory where the cache files are stored. By default it is
115 * assumed that the directory is below the TYPO3_DOCUMENT_ROOT. However, an
116 * absolute path can be selected, too.
117 *
118 * This method does not exist in FLOW3 anymore, but it is needed in
119 * TYPO3 v4 to enable a cache path outside of document root. The final
120 * cache path is checked and created in createFinalCachDirectory(),
121 * called by setCache() method, which is done _after_ the cacheDirectory
122 * option was handled.
123 *
124 * @param string $cacheDirectory The cache base directory. If a relative path
125 * @return void
126 * @throws \TYPO3\CMS\Core\Cache\Exception if the directory is not within allowed
127 */
128 public function setCacheDirectory($cacheDirectory) {
129 // Skip handling if directory is a stream ressource
130 // This is used by unit tests with vfs:// directoryies
131 if (strpos($cacheDirectory, '://')) {
132 $this->temporaryCacheDirectory = $cacheDirectory;
133 return;
134 }
135 $documentRoot = PATH_site;
136 if ($open_basedir = ini_get('open_basedir')) {
137 if (TYPO3_OS === 'WIN') {
138 $delimiter = ';';
139 $cacheDirectory = str_replace('\\', '/', $cacheDirectory);
140 if (!preg_match('/[A-Z]:/', substr($cacheDirectory, 0, 2))) {
141 $cacheDirectory = PATH_site . $cacheDirectory;
142 }
143 } else {
144 $delimiter = ':';
145 if ($cacheDirectory[0] != '/') {
146 // relative path to cache directory.
147 $cacheDirectory = PATH_site . $cacheDirectory;
148 }
149 }
150 $basedirs = explode($delimiter, $open_basedir);
151 $cacheDirectoryInBaseDir = FALSE;
152 foreach ($basedirs as $basedir) {
153 if (TYPO3_OS === 'WIN') {
154 $basedir = str_replace('\\', '/', $basedir);
155 }
156 if ($basedir[strlen($basedir) - 1] !== '/') {
157 $basedir .= '/';
158 }
159 if (\TYPO3\CMS\Core\Utility\GeneralUtility::isFirstPartOfStr($cacheDirectory, $basedir)) {
160 $documentRoot = $basedir;
161 $cacheDirectory = str_replace($basedir, '', $cacheDirectory);
162 $cacheDirectoryInBaseDir = TRUE;
163 break;
164 }
165 }
166 if (!$cacheDirectoryInBaseDir) {
167 throw new \TYPO3\CMS\Core\Cache\Exception('Open_basedir restriction in effect. The directory "' . $cacheDirectory . '" is not in an allowed path.');
168 }
169 } else {
170 if ($cacheDirectory[0] == '/') {
171 // Absolute path to cache directory.
172 $documentRoot = '/';
173 }
174 if (TYPO3_OS === 'WIN') {
175 if (substr($cacheDirectory, 0, strlen($documentRoot)) === $documentRoot) {
176 $documentRoot = '';
177 }
178 }
179 }
180 // After this point all paths have '/' as directory seperator
181 if ($cacheDirectory[strlen($cacheDirectory) - 1] !== '/') {
182 $cacheDirectory .= '/';
183 }
184 $this->temporaryCacheDirectory = $documentRoot . $cacheDirectory . $this->cacheIdentifier . '/';
185 }
186
187 /**
188 * Create the final cache directory if it does not exist. This method
189 * exists in TYPO3 v4 only.
190 *
191 * @param string $finalCacheDirectory Absolute path to final cache directory
192 * @return void
193 * @throws \TYPO3\CMS\Core\Cache\Exception If directory is not writable after creation
194 */
195 protected function createFinalCacheDirectory($finalCacheDirectory) {
196 try {
197 \TYPO3\CMS\Core\Utility\GeneralUtility::mkdir_deep($finalCacheDirectory);
198 } catch (\RuntimeException $e) {
199 throw new \TYPO3\CMS\Core\Cache\Exception('The directory "' . $finalCacheDirectory . '" can not be created.', 1303669848, $e);
200 }
201 if (!is_writable($finalCacheDirectory)) {
202 throw new \TYPO3\CMS\Core\Cache\Exception('The directory "' . $finalCacheDirectory . '" is not writable.', 1203965200);
203 }
204 }
205
206 /**
207 * Returns the directory where the cache files are stored
208 *
209 * @return string Full path of the cache directory
210 * @api
211 */
212 public function getCacheDirectory() {
213 return $this->cacheDirectory;
214 }
215
216 /**
217 * Saves data in a cache file.
218 *
219 * @param string $entryIdentifier An identifier for this specific cache entry
220 * @param string $data The data to be stored
221 * @param array $tags Tags to associate with this cache entry
222 * @param integer $lifetime Lifetime of this cache entry in seconds. If NULL is specified, the default lifetime is used. "0" means unlimited lifetime.
223 * @return void
224 * @throws \TYPO3\CMS\Core\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.
225 * @throws \TYPO3\CMS\Core\Cache\Exception\InvalidDataException if the data to bes stored is not a string.
226 * @throws \InvalidArgumentException
227 * @api
228 */
229 public function set($entryIdentifier, $data, array $tags = array(), $lifetime = NULL) {
230 if (!is_string($data)) {
231 throw new \t3lib_cache_Exception_InvalidData('The specified data is of type "' . gettype($data) . '" but a string is expected.', 1334756734);
232 }
233 if ($entryIdentifier !== basename($entryIdentifier)) {
234 throw new \InvalidArgumentException('The specified entry identifier must not contain a path segment.', 1334756735);
235 }
236 if ($entryIdentifier === '') {
237 throw new \InvalidArgumentException('The specified entry identifier must not be empty.', 1334756736);
238 }
239 $temporaryCacheEntryPathAndFilename = $this->cacheDirectory . uniqid() . '.temp';
240 $result = file_put_contents($temporaryCacheEntryPathAndFilename, $data);
241 \TYPO3\CMS\Core\Utility\GeneralUtility::fixPermissions($temporaryCacheEntryPathAndFilename);
242 if ($result === FALSE) {
243 throw new \TYPO3\CMS\Core\Cache\Exception('The temporary cache file "' . $temporaryCacheEntryPathAndFilename . '" could not be written.', 1334756737);
244 }
245 $cacheEntryPathAndFilename = $this->cacheDirectory . $entryIdentifier . $this->cacheEntryFileExtension;
246 rename($temporaryCacheEntryPathAndFilename, $cacheEntryPathAndFilename);
247 }
248
249 /**
250 * Loads data from a cache file.
251 *
252 * @param string $entryIdentifier An identifier which describes the cache entry to load
253 * @return mixed The cache entry's content as a string or FALSE if the cache entry could not be loaded
254 * @throws \InvalidArgumentException If identifier is invalid
255 * @api
256 */
257 public function get($entryIdentifier) {
258 if ($entryIdentifier !== basename($entryIdentifier)) {
259 throw new \InvalidArgumentException('The specified entry identifier must not contain a path segment.', 1334756877);
260 }
261 $pathAndFilename = $this->cacheDirectory . $entryIdentifier . $this->cacheEntryFileExtension;
262 if (!file_exists($pathAndFilename)) {
263 return FALSE;
264 }
265 return file_get_contents($pathAndFilename);
266 }
267
268 /**
269 * Checks if a cache entry with the specified identifier exists.
270 *
271 * @param string $entryIdentifier
272 * @return boolean TRUE if such an entry exists, FALSE if not
273 * @throws \InvalidArgumentException
274 * @api
275 */
276 public function has($entryIdentifier) {
277 if ($entryIdentifier !== basename($entryIdentifier)) {
278 throw new \InvalidArgumentException('The specified entry identifier must not contain a path segment.', 1334756878);
279 }
280 return file_exists($this->cacheDirectory . $entryIdentifier . $this->cacheEntryFileExtension);
281 }
282
283 /**
284 * Removes all cache entries matching the specified identifier.
285 * Usually this only affects one entry.
286 *
287 * @param string $entryIdentifier Specifies the cache entry to remove
288 * @return boolean TRUE if (at least) an entry could be removed or FALSE if no entry was found
289 * @throws \InvalidArgumentException
290 * @api
291 */
292 public function remove($entryIdentifier) {
293 if ($entryIdentifier !== basename($entryIdentifier)) {
294 throw new \InvalidArgumentException('The specified entry identifier must not contain a path segment.', 1334756960);
295 }
296 if ($entryIdentifier === '') {
297 throw new \InvalidArgumentException('The specified entry identifier must not be empty.', 1334756961);
298 }
299 try {
300 unlink($this->cacheDirectory . $entryIdentifier . $this->cacheEntryFileExtension);
301 } catch (\Exception $e) {
302 return FALSE;
303 }
304 return TRUE;
305 }
306
307 /**
308 * Removes all cache entries of this cache.
309 *
310 * @return void
311 * @api
312 */
313 public function flush() {
314 \TYPO3\CMS\Core\Utility\GeneralUtility::rmdir($this->cacheDirectory, TRUE);
315 $this->createFinalCacheDirectory($this->cacheDirectory);
316 }
317
318 /**
319 * Checks if the given cache entry files are still valid or if their
320 * lifetime has exceeded.
321 *
322 * @param string $cacheEntryPathAndFilename
323 * @return boolean
324 * @api
325 */
326 protected function isCacheFileExpired($cacheEntryPathAndFilename) {
327 return file_exists($cacheEntryPathAndFilename) === FALSE;
328 }
329
330 /**
331 * Not necessary
332 *
333 * @return void
334 * @api
335 */
336 public function collectGarbage() {
337
338 }
339
340 /**
341 * Tries to find the cache entry for the specified identifier.
342 *
343 * @param string $entryIdentifier The cache entry identifier
344 * @return mixed The file names (including path) as an array if one or more entries could be found, otherwise FALSE
345 */
346 protected function findCacheFilesByIdentifier($entryIdentifier) {
347 $pathAndFilename = $this->cacheDirectory . $entryIdentifier . $this->cacheEntryFileExtension;
348 return file_exists($pathAndFilename) ? array($pathAndFilename) : FALSE;
349 }
350
351 /**
352 * Loads PHP code from the cache and require_onces it right away.
353 *
354 * @param string $entryIdentifier An identifier which describes the cache entry to load
355 * @return mixed Potential return value from the include operation
356 * @throws \InvalidArgumentException
357 * @api
358 */
359 public function requireOnce($entryIdentifier) {
360 $pathAndFilename = $this->cacheDirectory . $entryIdentifier . $this->cacheEntryFileExtension;
361 if ($entryIdentifier !== basename($entryIdentifier)) {
362 throw new \InvalidArgumentException('The specified entry identifier must not contain a path segment.', 1282073036);
363 }
364 return file_exists($pathAndFilename) ? require_once $pathAndFilename : FALSE;
365 }
366
367 }
368
369
370 ?>