‪TYPO3CMS  ‪main
TreelistCacheUpdateHooks.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
17 
18 use TYPO3\CMS\Backend\Utility\BackendUtility;
23 
30 {
37  private ‪$updateRequiringFields = [
38  'pid',
39  'php_tree_stop',
40  'extendToSubpages',
41  ];
42 
46  public function ‪__construct()
47  {
48  // As enableFields can be set dynamically we add them here
49  $pagesEnableFields = ‪$GLOBALS['TCA']['pages']['ctrl']['enablecolumns'];
50  foreach ($pagesEnableFields as $pagesEnableField) {
51  $this->updateRequiringFields[] = $pagesEnableField;
52  }
53  $this->updateRequiringFields[] = ‪$GLOBALS['TCA']['pages']['ctrl']['delete'];
54  // Extension can add fields to the pages table that require an
55  // update of the treelist cache, too; so we also add those
56  // example: $TYPO3_CONF_VARS['BE']['additionalTreelistUpdateFields'] .= ',my_field';
57  if (!empty(‪$GLOBALS['TYPO3_CONF_VARS']['BE']['additionalTreelistUpdateFields'])) {
58  $additionalTreelistUpdateFields = ‪GeneralUtility::trimExplode(',', ‪$GLOBALS['TYPO3_CONF_VARS']['BE']['additionalTreelistUpdateFields'], true);
59  $this->updateRequiringFields = array_merge($this->updateRequiringFields, $additionalTreelistUpdateFields);
60  }
61  }
62 
73  public function ‪processDatamap_afterDatabaseOperations($status, $table, $recordId, array $updatedFields, ‪DataHandler $dataHandler)
74  {
75  if ($table === 'pages' && $this->‪requiresUpdate($updatedFields)) {
76  $affectedPagePid = 0;
77  $affectedPageUid = 0;
78  if ($status === 'new') {
79  // Detect new pages
80  // Resolve the uid
81  $affectedPageUid = $dataHandler->substNEWwithIDs[$recordId];
82  $affectedPagePid = $updatedFields['pid'];
83  } elseif ($status === 'update') {
84  // Detect updated pages
85  $affectedPageUid = $recordId;
86  // When updating a page the pid is not directly available so we
87  // need to retrieve it ourselves.
88  $fullPageRecord = BackendUtility::getRecord($table, $recordId);
89  $affectedPagePid = $fullPageRecord['pid'];
90  }
91  $clearCacheActions = $this->‪determineClearCacheActions($status, $updatedFields);
92  $this->‪processClearCacheActions($affectedPageUid, $affectedPagePid, $updatedFields, $clearCacheActions);
93  }
94  }
95 
106  public function ‪processCmdmap_postProcess($command, $table, $recordId, $commandValue, ‪DataHandler $dataHandler)
107  {
108  $action = (is_array($commandValue) && isset($commandValue['action'])) ? (string)$commandValue['action'] : '';
109  if ($table === 'pages' && ($command === 'delete' || ($command === 'version' && $action === 'publish'))) {
110  $affectedRecord = BackendUtility::getRecord($table, $recordId, '*', '', false);
111  if (!is_array($affectedRecord)) {
112  return;
113  }
114  $affectedPageUid = $affectedRecord['uid'];
115  $affectedPagePid = $affectedRecord['pid'];
116 
117  // Faking the updated fields
118  $updatedFields = [];
119  if ($command === 'delete') {
120  $updatedFields['deleted'] = 1;
121  } else {
122  // page was published to live
123  $updatedFields['t3ver_wsid'] = 0;
124  }
125  $clearCacheActions = $this->‪determineClearCacheActions(
126  'update',
127  $updatedFields
128  );
129 
130  $this->‪processClearCacheActions($affectedPageUid, $affectedPagePid, $updatedFields, $clearCacheActions);
131  }
132  }
133 
145  public function ‪moveRecord_firstElementPostProcess($table, $recordId, $destinationPid, array $movedRecord, array $updatedFields, ‪DataHandler $dataHandler)
146  {
147  if ($table === 'pages' && $this->‪requiresUpdate($updatedFields)) {
148  $affectedPageUid = $recordId;
149  $affectedPageOldPid = $movedRecord['pid'];
150  $affectedPageNewPid = $updatedFields['pid'];
151  $clearCacheActions = $this->‪determineClearCacheActions('update', $updatedFields);
152  // Clear treelist entries for old parent page
153  $this->‪processClearCacheActions($affectedPageUid, $affectedPageOldPid, $updatedFields, $clearCacheActions);
154  // Clear treelist entries for new parent page
155  $this->‪processClearCacheActions($affectedPageUid, $affectedPageNewPid, $updatedFields, $clearCacheActions);
156  }
157  }
158 
171  public function ‪moveRecord_afterAnotherElementPostProcess($table, $recordId, $destinationPid, $originalDestinationPid, array $movedRecord, array $updatedFields, ‪DataHandler $dataHandler)
172  {
173  if ($table === 'pages' && $this->‪requiresUpdate($updatedFields)) {
174  $affectedPageUid = $recordId;
175  $affectedPageOldPid = $movedRecord['pid'];
176  $affectedPageNewPid = $updatedFields['pid'];
177  $clearCacheActions = $this->‪determineClearCacheActions('update', $updatedFields);
178  // Clear treelist entries for old parent page
179  $this->‪processClearCacheActions($affectedPageUid, $affectedPageOldPid, $updatedFields, $clearCacheActions);
180  // Clear treelist entries for new parent page
181  $this->‪processClearCacheActions($affectedPageUid, $affectedPageNewPid, $updatedFields, $clearCacheActions);
182  }
183  }
184 
191  protected function ‪requiresUpdate(array $updatedFields)
192  {
193  $requiresUpdate = false;
194  $updatedFieldNames = array_keys($updatedFields);
195  foreach ($updatedFieldNames as $updatedFieldName) {
196  if (in_array($updatedFieldName, $this->updateRequiringFields, true)) {
197  $requiresUpdate = true;
198  break;
199  }
200  }
201  return $requiresUpdate;
202  }
203 
212  protected function ‪processClearCacheActions($affectedPage, $affectedParentPage, $updatedFields, array $actions)
213  {
214  $actionNames = array_keys($actions);
215  foreach ($actionNames as $actionName) {
216  switch ($actionName) {
217  case 'allParents':
218  $this->‪clearCacheForAllParents($affectedParentPage);
219  break;
220  case 'setExpiration':
221  // Only used when setting an end time for a page
222  $expirationTime = $updatedFields['endtime'];
223  $this->‪setCacheExpiration($affectedPage, $expirationTime);
224  break;
225  case 'uidInTreelist':
226  $this->‪clearCacheWhereUidInTreelist($affectedPage);
227  break;
228  }
229  }
230  // From time to time clean the cache from expired entries
231  // (theoretically every 1000 calls)
232  $randomNumber = random_int(1, 1000);
233  if ($randomNumber === 500) {
235  }
236  }
237 
244  protected function ‪clearCacheForAllParents($affectedParentPage)
245  {
246  $rootLine = BackendUtility::BEgetRootLine($affectedParentPage);
247  $rootLineIds = [];
248  foreach ($rootLine as $page) {
249  if ($page['uid'] != 0) {
250  $rootLineIds[] = $page['uid'];
251  }
252  }
253  if (!empty($rootLineIds)) {
254  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
255  ->getQueryBuilderForTable('cache_treelist');
256  $queryBuilder
257  ->delete('cache_treelist')
258  ->where(
259  $queryBuilder->expr()->in(
260  'pid',
261  $queryBuilder->createNamedParameter($rootLineIds, ‪Connection::PARAM_INT_ARRAY)
262  )
263  )
264  ->executeStatement();
265  }
266  }
267 
274  protected function ‪clearCacheWhereUidInTreelist($affectedPage)
275  {
276  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
277  ->getQueryBuilderForTable('cache_treelist');
278  $queryBuilder
279  ->delete('cache_treelist')
280  ->where(
281  $queryBuilder->expr()->inSet('treelist', $queryBuilder->quote((string)$affectedPage))
282  )
283  ->executeStatement();
284  }
285 
293  protected function ‪setCacheExpiration($affectedPage, $expirationTime)
294  {
295  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
296  ->getQueryBuilderForTable('cache_treelist');
297  $queryBuilder
298  ->update('cache_treelist')
299  ->where(
300  $queryBuilder->expr()->inSet('treelist', $queryBuilder->quote((string)$affectedPage))
301  )
302  ->set('expires', $expirationTime)
303  ->executeStatement();
304  }
305 
309  protected function ‪removeExpiredCacheEntries()
310  {
311  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
312  ->getQueryBuilderForTable('cache_treelist');
313  $queryBuilder
314  ->delete('cache_treelist')
315  ->where(
316  $queryBuilder->expr()->lte(
317  'expires',
318  $queryBuilder->createNamedParameter(‪$GLOBALS['EXEC_TIME'], ‪Connection::PARAM_INT)
319  )
320  )
321  ->executeStatement();
322  }
323 
332  protected function ‪determineClearCacheActions($status, $updatedFields): array
333  {
334  $actions = [];
335  if ($status === 'new') {
336  // New page
337  $actions['allParents'] = true;
338  } elseif ($status === 'update') {
339  $updatedFieldNames = array_keys($updatedFields);
340  foreach ($updatedFieldNames as $updatedFieldName) {
341  switch ($updatedFieldName) {
342  case 'pid':
343 
344  case ‪$GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['disabled']:
345 
346  case ‪$GLOBALS['TCA']['pages']['ctrl']['delete']:
347 
348  case ‪$GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['starttime']:
349 
350  case ‪$GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['fe_group']:
351 
352  case 'extendToSubpages':
353 
354  case 't3ver_wsid':
355 
356  case 'php_tree_stop':
357  // php_tree_stop
358  $actions['allParents'] = true;
359  $actions['uidInTreelist'] = true;
360  break;
361  case ‪$GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['endtime']:
362  // end time set/unset
363  // When setting an end time the cache entry needs an
364  // expiration time. When unsetting the end time the
365  // page must become listed in the treelist again.
366  if ($updatedFields['endtime'] > 0) {
367  $actions['setExpiration'] = true;
368  } else {
369  $actions['uidInTreelist'] = true;
370  }
371  break;
372  default:
373  if (in_array($updatedFieldName, $this->updateRequiringFields, true)) {
374  $actions['uidInTreelist'] = true;
375  }
376  }
377  }
378  }
379  return $actions;
380  }
381 }
‪TYPO3\CMS\Core\DataHandling\DataHandler
Definition: DataHandler.php:94
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks
Definition: TreelistCacheUpdateHooks.php:30
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT
‪const PARAM_INT
Definition: Connection.php:52
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\__construct
‪__construct()
Definition: TreelistCacheUpdateHooks.php:45
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\clearCacheForAllParents
‪clearCacheForAllParents($affectedParentPage)
Definition: TreelistCacheUpdateHooks.php:243
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\determineClearCacheActions
‪array determineClearCacheActions($status, $updatedFields)
Definition: TreelistCacheUpdateHooks.php:331
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\requiresUpdate
‪bool requiresUpdate(array $updatedFields)
Definition: TreelistCacheUpdateHooks.php:190
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\moveRecord_afterAnotherElementPostProcess
‪moveRecord_afterAnotherElementPostProcess($table, $recordId, $destinationPid, $originalDestinationPid, array $movedRecord, array $updatedFields, DataHandler $dataHandler)
Definition: TreelistCacheUpdateHooks.php:170
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\clearCacheWhereUidInTreelist
‪clearCacheWhereUidInTreelist($affectedPage)
Definition: TreelistCacheUpdateHooks.php:273
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\moveRecord_firstElementPostProcess
‪moveRecord_firstElementPostProcess($table, $recordId, $destinationPid, array $movedRecord, array $updatedFields, DataHandler $dataHandler)
Definition: TreelistCacheUpdateHooks.php:144
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\processDatamap_afterDatabaseOperations
‪processDatamap_afterDatabaseOperations($status, $table, $recordId, array $updatedFields, DataHandler $dataHandler)
Definition: TreelistCacheUpdateHooks.php:72
‪TYPO3\CMS\Frontend\Hooks
Definition: TreelistCacheUpdateHooks.php:16
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\$updateRequiringFields
‪array $updateRequiringFields
Definition: TreelistCacheUpdateHooks.php:36
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\processCmdmap_postProcess
‪processCmdmap_postProcess($command, $table, $recordId, $commandValue, DataHandler $dataHandler)
Definition: TreelistCacheUpdateHooks.php:105
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:41
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\processClearCacheActions
‪processClearCacheActions($affectedPage, $affectedParentPage, $updatedFields, array $actions)
Definition: TreelistCacheUpdateHooks.php:211
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\setCacheExpiration
‪setCacheExpiration($affectedPage, $expirationTime)
Definition: TreelistCacheUpdateHooks.php:292
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\removeExpiredCacheEntries
‪removeExpiredCacheEntries()
Definition: TreelistCacheUpdateHooks.php:308
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT_ARRAY
‪const PARAM_INT_ARRAY
Definition: Connection.php:72
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static list< string > trimExplode(string $delim, string $string, bool $removeEmptyValues=false, int $limit=0)
Definition: GeneralUtility.php:817