‪TYPO3CMS  ‪main
CacheLifetimeCalculator.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Psr\EventDispatcher\EventDispatcherInterface;
29 
39 {
40  protected int ‪$defaultCacheTimeout = 86400;
41 
42  public function ‪__construct(
43  protected readonly ‪FrontendInterface $runtimeCache,
44  protected readonly EventDispatcherInterface $eventDispatcher,
45  protected readonly ‪ConnectionPool $connectionPool
46  ) {}
47 
51  public function ‪calculateLifetimeForPage(int $pageId, array $pageRecord, array $renderingInstructions, int $defaultCacheTimoutInSeconds, ‪Context $context): int
52  {
53  $cachedCacheLifetimeIdentifier = 'cacheLifeTimeForPage_' . $pageId;
54  $cachedCacheLifetime = $this->runtimeCache->get($cachedCacheLifetimeIdentifier);
55  if ($cachedCacheLifetime !== false) {
56  return $cachedCacheLifetime;
57  }
58  if ($pageRecord['cache_timeout'] ?? false) {
59  // Cache period was set for the page:
60  $cacheTimeout = (int)$pageRecord['cache_timeout'];
61  } else {
62  // Cache period was set via TypoScript "config.cache_period",
63  // otherwise it's the default of 24 hours
64  $cacheTimeout = $defaultCacheTimoutInSeconds ?: (int)($renderingInstructions['cache_period'] ?? $this->defaultCacheTimeout);
65  }
66  // A pages endtime limits the upper bound of the maxmium cache lifetime
67  $pageEndtime = (int)($pageRecord['endtime'] ?? 0);
68  if ($pageEndtime > 0) {
69  $cacheTimeout = min($cacheTimeout, $pageEndtime - ‪$GLOBALS['EXEC_TIME']);
70  }
71  if (!empty($renderingInstructions['cache_clearAtMidnight'])) {
72  $timeOutTime = ‪$GLOBALS['EXEC_TIME'] + $cacheTimeout;
73  $midnightTime = mktime(0, 0, 0, (int)date('m', $timeOutTime), (int)date('d', $timeOutTime), (int)date('Y', $timeOutTime));
74  // If the midnight time of the expire-day is greater than the current time,
75  // we may set the timeOutTime to the new midnighttime.
76  if ($midnightTime > ‪$GLOBALS['EXEC_TIME']) {
77  $cacheTimeout = $midnightTime - ‪$GLOBALS['EXEC_TIME'];
78  }
79  }
80 
81  // Calculate the timeout time for records on the page and adjust cache timeout if necessary
82  // Get the configuration
83  $tablesToConsider = $this->‪getCurrentPageCacheConfiguration($pageId, $renderingInstructions);
84 
85  // Get the time, rounded to the minute (do not pollute MySQL cache!)
86  // It is ok that we do not take seconds into account here because this
87  // value will be subtracted later. So we never get the time "before"
88  // the cache change.
89  $currentTimestamp = (int)‪$GLOBALS['ACCESS_TIME'];
90  $cacheTimeout = min($this->‪calculatePageCacheLifetime($tablesToConsider, $currentTimestamp), $cacheTimeout);
91 
93  $cacheTimeout,
94  $pageId,
95  $pageRecord,
96  $renderingInstructions,
97  $context
98  );
99  $event = $this->eventDispatcher->dispatch($event);
100  $cacheTimeout = $event->getCacheLifetime();
101  $this->runtimeCache->set($cachedCacheLifetimeIdentifier, $cacheTimeout);
102  return $cacheTimeout;
103  }
104 
110  protected function ‪calculatePageCacheLifetime(array $tablesToConsider, int $currentTimestamp): int
111  {
112  $result = PHP_INT_MAX;
113  // Find timeout by checking every table
114  foreach ($tablesToConsider as $tableDef) {
115  $result = min($result, $this->‪getFirstTimeValueForRecord($tableDef, $currentTimestamp));
116  }
117  // We return + 1 second just to ensure that cache is definitely regenerated
118  return $result === PHP_INT_MAX ? PHP_INT_MAX : $result - $currentTimestamp + 1;
119  }
120 
138  protected function ‪getCurrentPageCacheConfiguration(int $currentPageId, array $renderingInstructions): array
139  {
140  $result = ['tt_content:' . $currentPageId];
141  if (isset($renderingInstructions['cache.'][$currentPageId])) {
142  $result = array_merge($result, ‪GeneralUtility::trimExplode(',', str_replace(':current', ':' . $currentPageId, $renderingInstructions['cache.'][$currentPageId])));
143  }
144  if (isset($renderingInstructions['cache.']['all'])) {
145  $result = array_merge($result, ‪GeneralUtility::trimExplode(',', str_replace(':current', ':' . $currentPageId, $renderingInstructions['cache.']['all'])));
146  }
147  return array_unique($result);
148  }
149 
159  protected function ‪getFirstTimeValueForRecord(string $tableDef, int $currentTimestamp): int
160  {
161  $result = PHP_INT_MAX;
162  [$tableName, $pid] = ‪GeneralUtility::trimExplode(':', $tableDef);
163  if (empty($tableName) || empty($pid)) {
164  throw new \InvalidArgumentException('Unexpected value for parameter $tableDef. Expected <tablename>:<pid>, got \'' . htmlspecialchars($tableDef) . '\'.', 1307190365);
165  }
166 
167  $queryBuilder = $this->connectionPool->getQueryBuilderForTable($tableName);
168  $queryBuilder->getRestrictions()
169  ->removeByType(StartTimeRestriction::class)
170  ->removeByType(EndTimeRestriction::class);
171  $timeFields = [];
172  $timeConditions = $queryBuilder->expr()->or();
173  foreach (['starttime', 'endtime'] as $field) {
174  if (isset($GLOBALS['TCA'][$tableName]['ctrl']['enablecolumns'][$field])) {
175  $timeFields[$field] = $GLOBALS['TCA'][$tableName]['ctrl']['enablecolumns'][$field];
176  $queryBuilder->addSelectLiteral(
177  'MIN('
178  . 'CASE WHEN '
179  . $queryBuilder->expr()->lte(
180  $timeFields[$field],
181  $queryBuilder->createNamedParameter($currentTimestamp, Connection::PARAM_INT)
182  )
183  . ' THEN NULL ELSE ' . $queryBuilder->quoteIdentifier($timeFields[$field]) . ' END'
184  . ') AS ' . $queryBuilder->quoteIdentifier($timeFields[$field])
185  );
186  $timeConditions = $timeConditions->with(
187  $queryBuilder->expr()->gt(
188  $timeFields[$field],
189  $queryBuilder->createNamedParameter($currentTimestamp, Connection::PARAM_INT)
190  )
191  );
192  }
193  }
194 
195  // if starttime or endtime are defined, evaluate them
196  if (!empty($timeFields)) {
197  // find the timestamp, when the current page's content changes the next time
198  $row = $queryBuilder
199  ->from($tableName)
200  ->where(
201  $queryBuilder->expr()->eq(
202  'pid',
203  $queryBuilder->createNamedParameter($pid, ‪Connection::PARAM_INT)
204  ),
205  $timeConditions
206  )
207  ->executeQuery()
208  ->fetchAssociative();
209 
210  ‪if ($row) {
211  foreach ($timeFields as $timeField => $_) {
212  // if a MIN value is found, take it into account for the
213  // cache lifetime we have to filter out start/endtimes < $currentTimestamp,
214  // as the SQL query also returns rows with starttime < $currentTimestamp
215  // and endtime > $currentTimestamp (and using a starttime from the past
216  // would be wrong)
217  if ($row[$timeField] !== null && (int)$row[$timeField] > $currentTimestamp) {
218  $result = min($result, (int)$row[$timeField]);
219  }
220  }
221  }
222  }
223 
224  return $result;
225  }
226 }
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT
‪const PARAM_INT
Definition: Connection.php:52
‪TYPO3\CMS\Core\Database\Query\Restriction\EndTimeRestriction
Definition: EndTimeRestriction.php:27
‪TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction
Definition: StartTimeRestriction.php:27
‪TYPO3\CMS\Frontend\Cache
Definition: CacheInstruction.php:18
‪TYPO3\CMS\Core\Context\Context
Definition: Context.php:54
‪TYPO3\CMS\Frontend\Cache\CacheLifetimeCalculator\getCurrentPageCacheConfiguration
‪array getCurrentPageCacheConfiguration(int $currentPageId, array $renderingInstructions)
Definition: CacheLifetimeCalculator.php:138
‪TYPO3\CMS\Frontend\Cache\CacheLifetimeCalculator
Definition: CacheLifetimeCalculator.php:39
‪TYPO3\CMS\Frontend\Cache\CacheLifetimeCalculator\$defaultCacheTimeout
‪int $defaultCacheTimeout
Definition: CacheLifetimeCalculator.php:40
‪TYPO3\CMS\Frontend\Cache\CacheLifetimeCalculator\__construct
‪__construct(protected readonly FrontendInterface $runtimeCache, protected readonly EventDispatcherInterface $eventDispatcher, protected readonly ConnectionPool $connectionPool)
Definition: CacheLifetimeCalculator.php:42
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:41
‪TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
Definition: FrontendInterface.php:22
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪if
‪if(PHP_SAPI !=='cli')
Definition: checkNamespaceIntegrity.php:27
‪TYPO3\CMS\Frontend\Cache\CacheLifetimeCalculator\calculateLifetimeForPage
‪calculateLifetimeForPage(int $pageId, array $pageRecord, array $renderingInstructions, int $defaultCacheTimoutInSeconds, Context $context)
Definition: CacheLifetimeCalculator.php:51
‪TYPO3\CMS\Frontend\Cache\CacheLifetimeCalculator\getFirstTimeValueForRecord
‪int getFirstTimeValueForRecord(string $tableDef, int $currentTimestamp)
Definition: CacheLifetimeCalculator.php:159
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Frontend\Event\ModifyCacheLifetimeForPageEvent
Definition: ModifyCacheLifetimeForPageEvent.php:27
‪TYPO3\CMS\Frontend\Cache\CacheLifetimeCalculator\calculatePageCacheLifetime
‪int calculatePageCacheLifetime(array $tablesToConsider, int $currentTimestamp)
Definition: CacheLifetimeCalculator.php:110
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static list< string > trimExplode(string $delim, string $string, bool $removeEmptyValues=false, int $limit=0)
Definition: GeneralUtility.php:822