TYPO3 CMS  TYPO3_8-7
FileBackend.php
Go to the documentation of this file.
1 <?php
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
20 
28 {
29  const SEPARATOR = '^';
30  const EXPIRYTIME_FORMAT = 'YmdHis';
31  const EXPIRYTIME_LENGTH = 14;
32  const DATASIZE_DIGITS = 10;
38  protected $cacheEntryFileExtension = '';
39 
43  protected $cacheEntryIdentifiers = [];
44 
48  protected $frozen = false;
49 
62  public function freeze()
63  {
64  if ($this->frozen === true) {
65  throw new \RuntimeException(sprintf('The cache "%s" is already frozen.', $this->cacheIdentifier), 1323353176);
66  }
67  $cacheEntryFileExtensionLength = strlen($this->cacheEntryFileExtension);
68  for ($directoryIterator = new \DirectoryIterator($this->cacheDirectory); $directoryIterator->valid(); $directoryIterator->next()) {
69  if ($directoryIterator->isDot()) {
70  continue;
71  }
72  if ($cacheEntryFileExtensionLength > 0) {
73  $entryIdentifier = substr($directoryIterator->getFilename(), 0, -$cacheEntryFileExtensionLength);
74  } else {
75  $entryIdentifier = $directoryIterator->getFilename();
76  }
77  $this->cacheEntryIdentifiers[$entryIdentifier] = true;
78  file_put_contents($this->cacheDirectory . $entryIdentifier . $this->cacheEntryFileExtension, $this->get($entryIdentifier));
79  }
80  file_put_contents($this->cacheDirectory . 'FrozenCache.data', serialize($this->cacheEntryIdentifiers));
81  $this->frozen = true;
82  }
83 
89  public function isFrozen()
90  {
91  return $this->frozen;
92  }
93 
107  public function setCache(\TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cache)
108  {
109  parent::setCache($cache);
110  if (file_exists($this->cacheDirectory . 'FrozenCache.data')) {
111  $this->frozen = true;
112  $this->cacheEntryIdentifiers = unserialize(file_get_contents($this->cacheDirectory . 'FrozenCache.data'));
113  }
114  }
115 
129  public function set($entryIdentifier, $data, array $tags = [], $lifetime = null)
130  {
131  if (!is_string($data)) {
132  throw new \TYPO3\CMS\Core\Cache\Exception\InvalidDataException('The specified data is of type "' . gettype($data) . '" but a string is expected.', 1204481674);
133  }
134  if ($entryIdentifier !== basename($entryIdentifier)) {
135  throw new \InvalidArgumentException('The specified entry identifier must not contain a path segment.', 1282073032);
136  }
137  if ($entryIdentifier === '') {
138  throw new \InvalidArgumentException('The specified entry identifier must not be empty.', 1298114280);
139  }
140  if ($this->frozen === true) {
141  throw new \RuntimeException(sprintf('Cannot add or modify cache entry because the backend of cache "%s" is frozen.', $this->cacheIdentifier), 1323344192);
142  }
143  $this->remove($entryIdentifier);
144  $temporaryCacheEntryPathAndFilename = $this->cacheDirectory . StringUtility::getUniqueId() . '.temp';
145  $lifetime = $lifetime === null ? $this->defaultLifetime : $lifetime;
146  $expiryTime = $lifetime === 0 ? 0 : $GLOBALS['EXEC_TIME'] + $lifetime;
147  $metaData = str_pad($expiryTime, self::EXPIRYTIME_LENGTH) . implode(' ', $tags) . str_pad(strlen($data), self::DATASIZE_DIGITS);
148  $result = file_put_contents($temporaryCacheEntryPathAndFilename, $data . $metaData);
149  \TYPO3\CMS\Core\Utility\GeneralUtility::fixPermissions($temporaryCacheEntryPathAndFilename);
150  if ($result === false) {
151  throw new \TYPO3\CMS\Core\Cache\Exception('The temporary cache file "' . $temporaryCacheEntryPathAndFilename . '" could not be written.', 1204026251);
152  }
153  $i = 0;
154  $cacheEntryPathAndFilename = $this->cacheDirectory . $entryIdentifier . $this->cacheEntryFileExtension;
155  while (($result = rename($temporaryCacheEntryPathAndFilename, $cacheEntryPathAndFilename)) === false && $i < 5) {
156  $i++;
157  }
158  if ($result === false) {
159  throw new \TYPO3\CMS\Core\Cache\Exception('The cache file "' . $cacheEntryPathAndFilename . '" could not be written.', 1222361632);
160  }
161  if ($this->cacheEntryFileExtension === '.php') {
162  GeneralUtility::makeInstance(OpcodeCacheService::class)->clearAllActive($cacheEntryPathAndFilename);
163  }
164  }
165 
174  public function get($entryIdentifier)
175  {
176  if ($this->frozen === true) {
177  return isset($this->cacheEntryIdentifiers[$entryIdentifier]) ? file_get_contents($this->cacheDirectory . $entryIdentifier . $this->cacheEntryFileExtension) : false;
178  }
179  if ($entryIdentifier !== basename($entryIdentifier)) {
180  throw new \InvalidArgumentException('The specified entry identifier must not contain a path segment.', 1282073033);
181  }
182  $pathAndFilename = $this->cacheDirectory . $entryIdentifier . $this->cacheEntryFileExtension;
183  if ($this->isCacheFileExpired($pathAndFilename)) {
184  return false;
185  }
186  $dataSize = (int)file_get_contents($pathAndFilename, null, null, (filesize($pathAndFilename) - self::DATASIZE_DIGITS), self::DATASIZE_DIGITS);
187  return file_get_contents($pathAndFilename, null, null, 0, $dataSize);
188  }
189 
198  public function has($entryIdentifier)
199  {
200  if ($this->frozen === true) {
201  return isset($this->cacheEntryIdentifiers[$entryIdentifier]);
202  }
203  if ($entryIdentifier !== basename($entryIdentifier)) {
204  throw new \InvalidArgumentException('The specified entry identifier must not contain a path segment.', 1282073034);
205  }
206  return !$this->isCacheFileExpired(($this->cacheDirectory . $entryIdentifier . $this->cacheEntryFileExtension));
207  }
208 
219  public function remove($entryIdentifier)
220  {
221  if ($entryIdentifier !== basename($entryIdentifier)) {
222  throw new \InvalidArgumentException('The specified entry identifier must not contain a path segment.', 1282073035);
223  }
224  if ($entryIdentifier === '') {
225  throw new \InvalidArgumentException('The specified entry identifier must not be empty.', 1298114279);
226  }
227  if ($this->frozen === true) {
228  throw new \RuntimeException(sprintf('Cannot remove cache entry because the backend of cache "%s" is frozen.', $this->cacheIdentifier), 1323344193);
229  }
230  $pathAndFilename = $this->cacheDirectory . $entryIdentifier . $this->cacheEntryFileExtension;
231  if (file_exists($pathAndFilename) === false) {
232  return false;
233  }
234  if (@unlink($pathAndFilename) === false) {
235  return false;
236  }
237  return true;
238  }
239 
248  public function findIdentifiersByTag($searchedTag)
249  {
250  $entryIdentifiers = [];
251  $now = $GLOBALS['EXEC_TIME'];
252  $cacheEntryFileExtensionLength = strlen($this->cacheEntryFileExtension);
253  for ($directoryIterator = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\DirectoryIterator::class, $this->cacheDirectory); $directoryIterator->valid(); $directoryIterator->next()) {
254  if ($directoryIterator->isDot()) {
255  continue;
256  }
257  $cacheEntryPathAndFilename = $directoryIterator->getPathname();
258  $index = (int)file_get_contents($cacheEntryPathAndFilename, null, null, (filesize($cacheEntryPathAndFilename) - self::DATASIZE_DIGITS), self::DATASIZE_DIGITS);
259  $metaData = file_get_contents($cacheEntryPathAndFilename, null, null, $index);
260  $expiryTime = (int)substr($metaData, 0, self::EXPIRYTIME_LENGTH);
261  if ($expiryTime !== 0 && $expiryTime < $now) {
262  continue;
263  }
264  if (in_array($searchedTag, explode(' ', substr($metaData, self::EXPIRYTIME_LENGTH, -self::DATASIZE_DIGITS)))) {
265  if ($cacheEntryFileExtensionLength > 0) {
266  $entryIdentifiers[] = substr($directoryIterator->getFilename(), 0, -$cacheEntryFileExtensionLength);
267  } else {
268  $entryIdentifiers[] = $directoryIterator->getFilename();
269  }
270  }
271  }
272  return $entryIdentifiers;
273  }
274 
280  public function flush()
281  {
282  parent::flush();
283  if ($this->frozen === true) {
284  $this->frozen = false;
285  }
286  }
287 
294  public function flushByTag($tag)
295  {
296  $identifiers = $this->findIdentifiersByTag($tag);
297  if (empty($identifiers)) {
298  return;
299  }
300  foreach ($identifiers as $entryIdentifier) {
301  $this->remove($entryIdentifier);
302  }
303  }
304 
313  protected function isCacheFileExpired($cacheEntryPathAndFilename)
314  {
315  if (file_exists($cacheEntryPathAndFilename) === false) {
316  return true;
317  }
318  $index = (int)file_get_contents($cacheEntryPathAndFilename, null, null, (filesize($cacheEntryPathAndFilename) - self::DATASIZE_DIGITS), self::DATASIZE_DIGITS);
319  $expiryTime = (int)file_get_contents($cacheEntryPathAndFilename, null, null, $index, self::EXPIRYTIME_LENGTH);
320  return $expiryTime !== 0 && $expiryTime < $GLOBALS['EXEC_TIME'];
321  }
322 
328  public function collectGarbage()
329  {
330  if ($this->frozen === true) {
331  return;
332  }
333  for ($directoryIterator = new \DirectoryIterator($this->cacheDirectory); $directoryIterator->valid(); $directoryIterator->next()) {
334  if ($directoryIterator->isDot()) {
335  continue;
336  }
337  if ($this->isCacheFileExpired($directoryIterator->getPathname())) {
338  $cacheEntryFileExtensionLength = strlen($this->cacheEntryFileExtension);
339  if ($cacheEntryFileExtensionLength > 0) {
340  $this->remove(substr($directoryIterator->getFilename(), 0, -$cacheEntryFileExtensionLength));
341  } else {
342  $this->remove($directoryIterator->getFilename());
343  }
344  }
345  }
346  }
347 
356  protected function findCacheFilesByIdentifier($entryIdentifier)
357  {
358  $pattern = $this->cacheDirectory . $entryIdentifier;
359  $filesFound = glob($pattern);
360  if ($filesFound === false || empty($filesFound)) {
361  return false;
362  }
363  return $filesFound;
364  }
365 
374  public function requireOnce($entryIdentifier)
375  {
376  if ($this->frozen === true) {
377  if (isset($this->cacheEntryIdentifiers[$entryIdentifier])) {
378  return require_once $this->cacheDirectory . $entryIdentifier . $this->cacheEntryFileExtension;
379  }
380  return false;
381  }
382  if ($entryIdentifier !== basename($entryIdentifier)) {
383  throw new \InvalidArgumentException('The specified entry identifier must not contain a path segment.', 1282073036);
384  }
385  $pathAndFilename = $this->cacheDirectory . $entryIdentifier . $this->cacheEntryFileExtension;
386  return $this->isCacheFileExpired($pathAndFilename) ? false : require_once $pathAndFilename;
387  }
388 }
isCacheFileExpired($cacheEntryPathAndFilename)
static makeInstance($className,... $constructorArguments)
setCache(\TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cache)
static fixPermissions($path, $recursive=false)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']