‪TYPO3CMS  10.4
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 
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 === 'swap'))) {
110  $affectedRecord = ‪BackendUtility::getRecord($table, $recordId, '*', '', false);
111  $affectedPageUid = $affectedRecord['uid'];
112  $affectedPagePid = $affectedRecord['pid'];
113 
114  // Faking the updated fields
115  $updatedFields = [];
116  if ($command === 'delete') {
117  $updatedFields['deleted'] = 1;
118  } else {
119  // page was published to live (swapped)
120  $updatedFields['t3ver_wsid'] = 0;
121  }
122  $clearCacheActions = $this->‪determineClearCacheActions(
123  'update',
124  $updatedFields
125  );
126 
127  $this->‪processClearCacheActions($affectedPageUid, $affectedPagePid, $updatedFields, $clearCacheActions);
128  }
129  }
130 
142  public function ‪moveRecord_firstElementPostProcess($table, $recordId, $destinationPid, array $movedRecord, array $updatedFields, ‪DataHandler $dataHandler)
143  {
144  if ($table === 'pages' && $this->‪requiresUpdate($updatedFields)) {
145  $affectedPageUid = $recordId;
146  $affectedPageOldPid = $movedRecord['pid'];
147  $affectedPageNewPid = $updatedFields['pid'];
148  $clearCacheActions = $this->‪determineClearCacheActions('update', $updatedFields);
149  // Clear treelist entries for old parent page
150  $this->‪processClearCacheActions($affectedPageUid, $affectedPageOldPid, $updatedFields, $clearCacheActions);
151  // Clear treelist entries for new parent page
152  $this->‪processClearCacheActions($affectedPageUid, $affectedPageNewPid, $updatedFields, $clearCacheActions);
153  }
154  }
155 
168  public function ‪moveRecord_afterAnotherElementPostProcess($table, $recordId, $destinationPid, $originalDestinationPid, array $movedRecord, array $updatedFields, ‪DataHandler $dataHandler)
169  {
170  if ($table === 'pages' && $this->‪requiresUpdate($updatedFields)) {
171  $affectedPageUid = $recordId;
172  $affectedPageOldPid = $movedRecord['pid'];
173  $affectedPageNewPid = $updatedFields['pid'];
174  $clearCacheActions = $this->‪determineClearCacheActions('update', $updatedFields);
175  // Clear treelist entries for old parent page
176  $this->‪processClearCacheActions($affectedPageUid, $affectedPageOldPid, $updatedFields, $clearCacheActions);
177  // Clear treelist entries for new parent page
178  $this->‪processClearCacheActions($affectedPageUid, $affectedPageNewPid, $updatedFields, $clearCacheActions);
179  }
180  }
181 
188  protected function ‪requiresUpdate(array $updatedFields)
189  {
190  $requiresUpdate = false;
191  $updatedFieldNames = array_keys($updatedFields);
192  foreach ($updatedFieldNames as $updatedFieldName) {
193  if (in_array($updatedFieldName, $this->updateRequiringFields, true)) {
194  $requiresUpdate = true;
195  break;
196  }
197  }
198  return $requiresUpdate;
199  }
200 
209  protected function ‪processClearCacheActions($affectedPage, $affectedParentPage, $updatedFields, array $actions)
210  {
211  $actionNames = array_keys($actions);
212  foreach ($actionNames as $actionName) {
213  switch ($actionName) {
214  case 'allParents':
215  $this->‪clearCacheForAllParents($affectedParentPage);
216  break;
217  case 'setExpiration':
218  // Only used when setting an end time for a page
219  $expirationTime = $updatedFields['endtime'];
220  $this->‪setCacheExpiration($affectedPage, $expirationTime);
221  break;
222  case 'uidInTreelist':
223  $this->‪clearCacheWhereUidInTreelist($affectedPage);
224  break;
225  }
226  }
227  // From time to time clean the cache from expired entries
228  // (theoretically every 1000 calls)
229  $randomNumber = random_int(1, 1000);
230  if ($randomNumber === 500) {
232  }
233  }
234 
241  protected function ‪clearCacheForAllParents($affectedParentPage)
242  {
243  $rootLine = ‪BackendUtility::BEgetRootLine($affectedParentPage);
244  $rootLineIds = [];
245  foreach ($rootLine as $page) {
246  if ($page['uid'] != 0) {
247  $rootLineIds[] = $page['uid'];
248  }
249  }
250  if (!empty($rootLineIds)) {
251  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
252  ->getQueryBuilderForTable('cache_treelist');
253  $queryBuilder
254  ->delete('cache_treelist')
255  ->where(
256  $queryBuilder->expr()->in(
257  'pid',
258  $queryBuilder->createNamedParameter($rootLineIds, Connection::PARAM_INT_ARRAY)
259  )
260  )
261  ->execute();
262  }
263  }
264 
271  protected function ‪clearCacheWhereUidInTreelist($affectedPage)
272  {
273  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
274  ->getQueryBuilderForTable('cache_treelist');
275  $queryBuilder
276  ->delete('cache_treelist')
277  ->where(
278  $queryBuilder->expr()->inSet('treelist', $queryBuilder->quote($affectedPage))
279  )
280  ->execute();
281  }
282 
290  protected function ‪setCacheExpiration($affectedPage, $expirationTime)
291  {
292  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
293  ->getQueryBuilderForTable('cache_treelist');
294  $queryBuilder
295  ->update('cache_treelist')
296  ->where(
297  $queryBuilder->expr()->inSet('treelist', $queryBuilder->quote($affectedPage))
298  )
299  ->set('expires', $expirationTime)
300  ->execute();
301  }
302 
306  protected function ‪removeExpiredCacheEntries()
307  {
308  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
309  ->getQueryBuilderForTable('cache_treelist');
310  $queryBuilder
311  ->delete('cache_treelist')
312  ->where(
313  $queryBuilder->expr()->lte(
314  'expires',
315  $queryBuilder->createNamedParameter(‪$GLOBALS['EXEC_TIME'], \PDO::PARAM_INT)
316  )
317  )
318  ->execute();
319  }
320 
329  protected function ‪determineClearCacheActions($status, $updatedFields): array
330  {
331  $actions = [];
332  if ($status === 'new') {
333  // New page
334  $actions['allParents'] = true;
335  } elseif ($status === 'update') {
336  $updatedFieldNames = array_keys($updatedFields);
337  foreach ($updatedFieldNames as $updatedFieldName) {
338  switch ($updatedFieldName) {
339  case 'pid':
340 
341  case ‪$GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['disabled']:
342 
343  case ‪$GLOBALS['TCA']['pages']['ctrl']['delete']:
344 
345  case ‪$GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['starttime']:
346 
347  case ‪$GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['fe_group']:
348 
349  case 'extendToSubpages':
350 
351  case 't3ver_wsid':
352 
353  case 'php_tree_stop':
354  // php_tree_stop
355  $actions['allParents'] = true;
356  $actions['uidInTreelist'] = true;
357  break;
358  case ‪$GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['endtime']:
359  // end time set/unset
360  // When setting an end time the cache entry needs an
361  // expiration time. When unsetting the end time the
362  // page must become listed in the treelist again.
363  if ($updatedFields['endtime'] > 0) {
364  $actions['setExpiration'] = true;
365  } else {
366  $actions['uidInTreelist'] = true;
367  }
368  break;
369  default:
370  if (in_array($updatedFieldName, $this->updateRequiringFields, true)) {
371  $actions['uidInTreelist'] = true;
372  }
373  }
374  }
375  }
376  return $actions;
377  }
378 }
‪TYPO3\CMS\Core\DataHandling\DataHandler
Definition: DataHandler.php:84
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks
Definition: TreelistCacheUpdateHooks.php:30
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\__construct
‪__construct()
Definition: TreelistCacheUpdateHooks.php:45
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\clearCacheForAllParents
‪clearCacheForAllParents($affectedParentPage)
Definition: TreelistCacheUpdateHooks.php:240
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\determineClearCacheActions
‪array determineClearCacheActions($status, $updatedFields)
Definition: TreelistCacheUpdateHooks.php:328
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\requiresUpdate
‪bool requiresUpdate(array $updatedFields)
Definition: TreelistCacheUpdateHooks.php:187
‪TYPO3\CMS\Backend\Utility\BackendUtility\BEgetRootLine
‪static array BEgetRootLine($uid, $clause='', $workspaceOL=false, array $additionalFields=[])
Definition: BackendUtility.php:343
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\moveRecord_afterAnotherElementPostProcess
‪moveRecord_afterAnotherElementPostProcess($table, $recordId, $destinationPid, $originalDestinationPid, array $movedRecord, array $updatedFields, DataHandler $dataHandler)
Definition: TreelistCacheUpdateHooks.php:167
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\clearCacheWhereUidInTreelist
‪clearCacheWhereUidInTreelist($affectedPage)
Definition: TreelistCacheUpdateHooks.php:270
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\moveRecord_firstElementPostProcess
‪moveRecord_firstElementPostProcess($table, $recordId, $destinationPid, array $movedRecord, array $updatedFields, DataHandler $dataHandler)
Definition: TreelistCacheUpdateHooks.php:141
‪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: MediaItemHooks.php:16
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\$updateRequiringFields
‪array $updateRequiringFields
Definition: TreelistCacheUpdateHooks.php:36
‪TYPO3\CMS\Backend\Utility\BackendUtility
Definition: BackendUtility.php:75
‪TYPO3\CMS\Backend\Utility\BackendUtility\getRecord
‪static array null getRecord($table, $uid, $fields=' *', $where='', $useDeleteClause=true)
Definition: BackendUtility.php:95
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static string[] trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:1059
‪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:36
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\processClearCacheActions
‪processClearCacheActions($affectedPage, $affectedParentPage, $updatedFields, array $actions)
Definition: TreelistCacheUpdateHooks.php:208
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\setCacheExpiration
‪setCacheExpiration($affectedPage, $expirationTime)
Definition: TreelistCacheUpdateHooks.php:289
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:46
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\removeExpiredCacheEntries
‪removeExpiredCacheEntries()
Definition: TreelistCacheUpdateHooks.php:305