TYPO3 CMS  TYPO3_7-6
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 
63  public function freeze()
64  {
65  if ($this->frozen === true) {
66  throw new \RuntimeException(sprintf('The cache "%s" is already frozen.', $this->cacheIdentifier), 1323353176);
67  }
68  $cacheEntryFileExtensionLength = strlen($this->cacheEntryFileExtension);
69  for ($directoryIterator = new \DirectoryIterator($this->cacheDirectory); $directoryIterator->valid(); $directoryIterator->next()) {
70  if ($directoryIterator->isDot()) {
71  continue;
72  }
73  if ($cacheEntryFileExtensionLength > 0) {
74  $entryIdentifier = substr($directoryIterator->getFilename(), 0, -$cacheEntryFileExtensionLength);
75  } else {
76  $entryIdentifier = $directoryIterator->getFilename();
77  }
78  $this->cacheEntryIdentifiers[$entryIdentifier] = true;
79  file_put_contents($this->cacheDirectory . $entryIdentifier . $this->cacheEntryFileExtension, $this->get($entryIdentifier));
80  }
81  if ($this->useIgBinary === true) {
82  file_put_contents($this->cacheDirectory . 'FrozenCache.data', igbinary_serialize($this->cacheEntryIdentifiers));
83  } else {
84  file_put_contents($this->cacheDirectory . 'FrozenCache.data', serialize($this->cacheEntryIdentifiers));
85  }
86  $this->frozen = true;
87  }
88 
94  public function isFrozen()
95  {
96  return $this->frozen;
97  }
98 
113  public function setCache(\TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cache)
114  {
115  parent::setCache($cache);
116  if (file_exists($this->cacheDirectory . 'FrozenCache.data')) {
117  $this->frozen = true;
118  if ($this->useIgBinary === true) {
119  $this->cacheEntryIdentifiers = igbinary_unserialize(file_get_contents($this->cacheDirectory . 'FrozenCache.data'));
120  } else {
121  $this->cacheEntryIdentifiers = unserialize(file_get_contents($this->cacheDirectory . 'FrozenCache.data'));
122  }
123  }
124  }
125 
140  public function set($entryIdentifier, $data, array $tags = [], $lifetime = null)
141  {
142  if (!is_string($data)) {
143  throw new \TYPO3\CMS\Core\Cache\Exception\InvalidDataException('The specified data is of type "' . gettype($data) . '" but a string is expected.', 1204481674);
144  }
145  if ($entryIdentifier !== basename($entryIdentifier)) {
146  throw new \InvalidArgumentException('The specified entry identifier must not contain a path segment.', 1282073032);
147  }
148  if ($entryIdentifier === '') {
149  throw new \InvalidArgumentException('The specified entry identifier must not be empty.', 1298114280);
150  }
151  if ($this->frozen === true) {
152  throw new \RuntimeException(sprintf('Cannot add or modify cache entry because the backend of cache "%s" is frozen.', $this->cacheIdentifier), 1323344192);
153  }
154  $this->remove($entryIdentifier);
155  $temporaryCacheEntryPathAndFilename = $this->cacheDirectory . StringUtility::getUniqueId() . '.temp';
156  $lifetime = $lifetime === null ? $this->defaultLifetime : $lifetime;
157  $expiryTime = $lifetime === 0 ? 0 : $GLOBALS['EXEC_TIME'] + $lifetime;
158  $metaData = str_pad($expiryTime, self::EXPIRYTIME_LENGTH) . implode(' ', $tags) . str_pad(strlen($data), self::DATASIZE_DIGITS);
159  $result = file_put_contents($temporaryCacheEntryPathAndFilename, $data . $metaData);
160  \TYPO3\CMS\Core\Utility\GeneralUtility::fixPermissions($temporaryCacheEntryPathAndFilename);
161  if ($result === false) {
162  throw new \TYPO3\CMS\Core\Cache\Exception('The temporary cache file "' . $temporaryCacheEntryPathAndFilename . '" could not be written.', 1204026251);
163  }
164  $i = 0;
165  $cacheEntryPathAndFilename = $this->cacheDirectory . $entryIdentifier . $this->cacheEntryFileExtension;
166  while (($result = rename($temporaryCacheEntryPathAndFilename, $cacheEntryPathAndFilename)) === false && $i < 5) {
167  $i++;
168  }
169  if ($result === false) {
170  throw new \TYPO3\CMS\Core\Cache\Exception('The cache file "' . $cacheEntryPathAndFilename . '" could not be written.', 1222361632);
171  }
172  if ($this->cacheEntryFileExtension === '.php') {
173  GeneralUtility::makeInstance(OpcodeCacheService::class)->clearAllActive($cacheEntryPathAndFilename);
174  }
175  }
176 
185  public function get($entryIdentifier)
186  {
187  if ($this->frozen === true) {
188  return isset($this->cacheEntryIdentifiers[$entryIdentifier]) ? file_get_contents($this->cacheDirectory . $entryIdentifier . $this->cacheEntryFileExtension) : false;
189  }
190  if ($entryIdentifier !== basename($entryIdentifier)) {
191  throw new \InvalidArgumentException('The specified entry identifier must not contain a path segment.', 1282073033);
192  }
193  $pathAndFilename = $this->cacheDirectory . $entryIdentifier . $this->cacheEntryFileExtension;
194  if ($this->isCacheFileExpired($pathAndFilename)) {
195  return false;
196  }
197  $dataSize = (int)file_get_contents($pathAndFilename, null, null, (filesize($pathAndFilename) - self::DATASIZE_DIGITS), self::DATASIZE_DIGITS);
198  return file_get_contents($pathAndFilename, null, null, 0, $dataSize);
199  }
200 
209  public function has($entryIdentifier)
210  {
211  if ($this->frozen === true) {
212  return isset($this->cacheEntryIdentifiers[$entryIdentifier]);
213  }
214  if ($entryIdentifier !== basename($entryIdentifier)) {
215  throw new \InvalidArgumentException('The specified entry identifier must not contain a path segment.', 1282073034);
216  }
217  return !$this->isCacheFileExpired(($this->cacheDirectory . $entryIdentifier . $this->cacheEntryFileExtension));
218  }
219 
230  public function remove($entryIdentifier)
231  {
232  if ($entryIdentifier !== basename($entryIdentifier)) {
233  throw new \InvalidArgumentException('The specified entry identifier must not contain a path segment.', 1282073035);
234  }
235  if ($entryIdentifier === '') {
236  throw new \InvalidArgumentException('The specified entry identifier must not be empty.', 1298114279);
237  }
238  if ($this->frozen === true) {
239  throw new \RuntimeException(sprintf('Cannot remove cache entry because the backend of cache "%s" is frozen.', $this->cacheIdentifier), 1323344193);
240  }
241  $pathAndFilename = $this->cacheDirectory . $entryIdentifier . $this->cacheEntryFileExtension;
242  if (file_exists($pathAndFilename) === false) {
243  return false;
244  }
245  if (@unlink($pathAndFilename) === false) {
246  return false;
247  }
248  return true;
249  }
250 
259  public function findIdentifiersByTag($searchedTag)
260  {
261  $entryIdentifiers = [];
262  $now = $GLOBALS['EXEC_TIME'];
263  $cacheEntryFileExtensionLength = strlen($this->cacheEntryFileExtension);
264  for ($directoryIterator = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\DirectoryIterator::class, $this->cacheDirectory); $directoryIterator->valid(); $directoryIterator->next()) {
265  if ($directoryIterator->isDot()) {
266  continue;
267  }
268  $cacheEntryPathAndFilename = $directoryIterator->getPathname();
269  $index = (int)file_get_contents($cacheEntryPathAndFilename, null, null, (filesize($cacheEntryPathAndFilename) - self::DATASIZE_DIGITS), self::DATASIZE_DIGITS);
270  $metaData = file_get_contents($cacheEntryPathAndFilename, null, null, $index);
271  $expiryTime = (int)substr($metaData, 0, self::EXPIRYTIME_LENGTH);
272  if ($expiryTime !== 0 && $expiryTime < $now) {
273  continue;
274  }
275  if (in_array($searchedTag, explode(' ', substr($metaData, self::EXPIRYTIME_LENGTH, -self::DATASIZE_DIGITS)))) {
276  if ($cacheEntryFileExtensionLength > 0) {
277  $entryIdentifiers[] = substr($directoryIterator->getFilename(), 0, -$cacheEntryFileExtensionLength);
278  } else {
279  $entryIdentifiers[] = $directoryIterator->getFilename();
280  }
281  }
282  }
283  return $entryIdentifiers;
284  }
285 
292  public function flush()
293  {
294  parent::flush();
295  if ($this->frozen === true) {
296  $this->frozen = false;
297  }
298  }
299 
307  public function flushByTag($tag)
308  {
309  $identifiers = $this->findIdentifiersByTag($tag);
310  if (empty($identifiers)) {
311  return;
312  }
313  foreach ($identifiers as $entryIdentifier) {
314  $this->remove($entryIdentifier);
315  }
316  }
317 
326  protected function isCacheFileExpired($cacheEntryPathAndFilename)
327  {
328  if (file_exists($cacheEntryPathAndFilename) === false) {
329  return true;
330  }
331  $index = (int)file_get_contents($cacheEntryPathAndFilename, null, null, (filesize($cacheEntryPathAndFilename) - self::DATASIZE_DIGITS), self::DATASIZE_DIGITS);
332  $expiryTime = (int)file_get_contents($cacheEntryPathAndFilename, null, null, $index, self::EXPIRYTIME_LENGTH);
333  return $expiryTime !== 0 && $expiryTime < $GLOBALS['EXEC_TIME'];
334  }
335 
342  public function collectGarbage()
343  {
344  if ($this->frozen === true) {
345  return;
346  }
347  for ($directoryIterator = new \DirectoryIterator($this->cacheDirectory); $directoryIterator->valid(); $directoryIterator->next()) {
348  if ($directoryIterator->isDot()) {
349  continue;
350  }
351  if ($this->isCacheFileExpired($directoryIterator->getPathname())) {
352  $cacheEntryFileExtensionLength = strlen($this->cacheEntryFileExtension);
353  if ($cacheEntryFileExtensionLength > 0) {
354  $this->remove(substr($directoryIterator->getFilename(), 0, -$cacheEntryFileExtensionLength));
355  } else {
356  $this->remove($directoryIterator->getFilename());
357  }
358  }
359  }
360  }
361 
370  protected function findCacheFilesByIdentifier($entryIdentifier)
371  {
372  $pattern = $this->cacheDirectory . $entryIdentifier;
373  $filesFound = glob($pattern);
374  if ($filesFound === false || empty($filesFound)) {
375  return false;
376  }
377  return $filesFound;
378  }
379 
388  public function requireOnce($entryIdentifier)
389  {
390  if ($this->frozen === true) {
391  if (isset($this->cacheEntryIdentifiers[$entryIdentifier])) {
392  return require_once $this->cacheDirectory . $entryIdentifier . $this->cacheEntryFileExtension;
393  } else {
394  return false;
395  }
396  } else {
397  $pathAndFilename = $this->cacheDirectory . $entryIdentifier . $this->cacheEntryFileExtension;
398  if ($entryIdentifier !== basename($entryIdentifier)) {
399  throw new \InvalidArgumentException('The specified entry identifier must not contain a path segment.', 1282073036);
400  }
401  $pathAndFilename = $this->cacheDirectory . $entryIdentifier . $this->cacheEntryFileExtension;
402  return $this->isCacheFileExpired($pathAndFilename) ? false : require_once $pathAndFilename;
403  }
404  }
405 }
isCacheFileExpired($cacheEntryPathAndFilename)
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']