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