‪TYPO3CMS  9.5
TreelistCacheUpdateHooks.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 
22 
29 {
36  private ‪$updateRequiringFields = [
37  'pid',
38  'php_tree_stop',
39  'extendToSubpages'
40  ];
41 
45  public function ‪__construct()
46  {
47  // As enableFields can be set dynamically we add them here
48  $pagesEnableFields = ‪$GLOBALS['TCA']['pages']['ctrl']['enablecolumns'];
49  foreach ($pagesEnableFields as $pagesEnableField) {
50  $this->updateRequiringFields[] = $pagesEnableField;
51  }
52  $this->updateRequiringFields[] = ‪$GLOBALS['TCA']['pages']['ctrl']['delete'];
53  // Extension can add fields to the pages table that require an
54  // update of the treelist cache, too; so we also add those
55  // example: $TYPO3_CONF_VARS['BE']['additionalTreelistUpdateFields'] .= ',my_field';
56  if (!empty(‪$GLOBALS['TYPO3_CONF_VARS']['BE']['additionalTreelistUpdateFields'])) {
57  $additionalTreelistUpdateFields = GeneralUtility::trimExplode(',', ‪$GLOBALS['TYPO3_CONF_VARS']['BE']['additionalTreelistUpdateFields'], true);
58  $this->updateRequiringFields = array_merge($this->updateRequiringFields, $additionalTreelistUpdateFields);
59  }
60  }
61 
72  public function ‪processDatamap_afterDatabaseOperations($status, $table, $recordId, array $updatedFields, ‪DataHandler $dataHandler)
73  {
74  if ($table === 'pages' && $this->‪requiresUpdate($updatedFields)) {
75  $affectedPagePid = 0;
76  $affectedPageUid = 0;
77  if ($status === 'new') {
78  // Detect new pages
79  // Resolve the uid
80  $affectedPageUid = $dataHandler->substNEWwithIDs[$recordId];
81  $affectedPagePid = $updatedFields['pid'];
82  } elseif ($status === 'update') {
83  // Detect updated pages
84  $affectedPageUid = $recordId;
85  // When updating a page the pid is not directly available so we
86  // need to retrieve it ourselves.
87  $fullPageRecord = ‪BackendUtility::getRecord($table, $recordId);
88  $affectedPagePid = $fullPageRecord['pid'];
89  }
90  $clearCacheActions = $this->‪determineClearCacheActions($status, $updatedFields);
91  $this->‪processClearCacheActions($affectedPageUid, $affectedPagePid, $updatedFields, $clearCacheActions);
92  }
93  }
94 
105  public function ‪processCmdmap_postProcess($command, $table, $recordId, $commandValue, ‪DataHandler $dataHandler)
106  {
107  $action = (is_array($commandValue) && isset($commandValue['action'])) ? (string)$commandValue['action'] : '';
108  if ($table === 'pages' && ($command === 'delete' || ($command === 'version' && $action === 'swap'))) {
109  $affectedRecord = ‪BackendUtility::getRecord($table, $recordId, '*', '', false);
110  $affectedPageUid = $affectedRecord['uid'];
111  $affectedPagePid = $affectedRecord['pid'];
112 
113  // Faking the updated fields
114  $updatedFields = [];
115  if ($command === 'delete') {
116  $updatedFields['deleted'] = 1;
117  } else {
118  // page was published to live (swapped)
119  $updatedFields['t3ver_wsid'] = 0;
120  }
121  $clearCacheActions = $this->‪determineClearCacheActions(
122  'update',
123  $updatedFields
124  );
125 
126  $this->‪processClearCacheActions($affectedPageUid, $affectedPagePid, $updatedFields, $clearCacheActions);
127  }
128  }
129 
141  public function ‪moveRecord_firstElementPostProcess($table, $recordId, $destinationPid, array $movedRecord, array $updatedFields, ‪DataHandler $dataHandler)
142  {
143  if ($table === 'pages' && $this->‪requiresUpdate($updatedFields)) {
144  $affectedPageUid = $recordId;
145  $affectedPageOldPid = $movedRecord['pid'];
146  $affectedPageNewPid = $updatedFields['pid'];
147  $clearCacheActions = $this->‪determineClearCacheActions('update', $updatedFields);
148  // Clear treelist entries for old parent page
149  $this->‪processClearCacheActions($affectedPageUid, $affectedPageOldPid, $updatedFields, $clearCacheActions);
150  // Clear treelist entries for new parent page
151  $this->‪processClearCacheActions($affectedPageUid, $affectedPageNewPid, $updatedFields, $clearCacheActions);
152  }
153  }
154 
167  public function ‪moveRecord_afterAnotherElementPostProcess($table, $recordId, $destinationPid, $originalDestinationPid, array $movedRecord, array $updatedFields, ‪DataHandler $dataHandler)
168  {
169  if ($table === 'pages' && $this->‪requiresUpdate($updatedFields)) {
170  $affectedPageUid = $recordId;
171  $affectedPageOldPid = $movedRecord['pid'];
172  $affectedPageNewPid = $updatedFields['pid'];
173  $clearCacheActions = $this->‪determineClearCacheActions('update', $updatedFields);
174  // Clear treelist entries for old parent page
175  $this->‪processClearCacheActions($affectedPageUid, $affectedPageOldPid, $updatedFields, $clearCacheActions);
176  // Clear treelist entries for new parent page
177  $this->‪processClearCacheActions($affectedPageUid, $affectedPageNewPid, $updatedFields, $clearCacheActions);
178  }
179  }
180 
187  protected function ‪requiresUpdate(array $updatedFields)
188  {
189  $requiresUpdate = false;
190  $updatedFieldNames = array_keys($updatedFields);
191  foreach ($updatedFieldNames as $updatedFieldName) {
192  if (in_array($updatedFieldName, $this->updateRequiringFields, true)) {
193  $requiresUpdate = true;
194  break;
195  }
196  }
197  return $requiresUpdate;
198  }
199 
208  protected function ‪processClearCacheActions($affectedPage, $affectedParentPage, $updatedFields, array $actions)
209  {
210  $actionNames = array_keys($actions);
211  foreach ($actionNames as $actionName) {
212  switch ($actionName) {
213  case 'allParents':
214  $this->‪clearCacheForAllParents($affectedParentPage);
215  break;
216  case 'setExpiration':
217  // Only used when setting an end time for a page
218  $expirationTime = $updatedFields['endtime'];
219  $this->‪setCacheExpiration($affectedPage, $expirationTime);
220  break;
221  case 'uidInTreelist':
222  $this->‪clearCacheWhereUidInTreelist($affectedPage);
223  break;
224  }
225  }
226  // From time to time clean the cache from expired entries
227  // (theoretically every 1000 calls)
228  $randomNumber = rand(1, 1000);
229  if ($randomNumber === 500) {
231  }
232  }
233 
240  protected function ‪clearCacheForAllParents($affectedParentPage)
241  {
242  $rootLine = ‪BackendUtility::BEgetRootLine($affectedParentPage);
243  $rootLineIds = [];
244  foreach ($rootLine as $page) {
245  if ($page['uid'] != 0) {
246  $rootLineIds[] = $page['uid'];
247  }
248  }
249  if (!empty($rootLineIds)) {
250  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
251  ->getQueryBuilderForTable('cache_treelist');
252  $queryBuilder
253  ->delete('cache_treelist')
254  ->where(
255  $queryBuilder->expr()->in(
256  'pid',
257  $queryBuilder->createNamedParameter($rootLineIds, Connection::PARAM_INT_ARRAY)
258  )
259  )
260  ->execute();
261  }
262  }
263 
270  protected function ‪clearCacheWhereUidInTreelist($affectedPage)
271  {
272  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
273  ->getQueryBuilderForTable('cache_treelist');
274  $queryBuilder
275  ->delete('cache_treelist')
276  ->where(
277  $queryBuilder->expr()->inSet('treelist', $queryBuilder->quote($affectedPage))
278  )
279  ->execute();
280  }
281 
289  protected function ‪setCacheExpiration($affectedPage, $expirationTime)
290  {
291  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
292  ->getQueryBuilderForTable('cache_treelist');
293  $queryBuilder
294  ->update('cache_treelist')
295  ->where(
296  $queryBuilder->expr()->inSet('treelist', $queryBuilder->quote($affectedPage))
297  )
298  ->set('expires', $expirationTime)
299  ->execute();
300  }
301 
305  protected function ‪removeExpiredCacheEntries()
306  {
307  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
308  ->getQueryBuilderForTable('cache_treelist');
309  $queryBuilder
310  ->delete('cache_treelist')
311  ->where(
312  $queryBuilder->expr()->lte(
313  'expires',
314  $queryBuilder->createNamedParameter(‪$GLOBALS['EXEC_TIME'], \PDO::PARAM_INT)
315  )
316  )
317  ->execute();
318  }
319 
328  protected function ‪determineClearCacheActions($status, $updatedFields): array
329  {
330  $actions = [];
331  if ($status === 'new') {
332  // New page
333  $actions['allParents'] = true;
334  } elseif ($status === 'update') {
335  $updatedFieldNames = array_keys($updatedFields);
336  foreach ($updatedFieldNames as $updatedFieldName) {
337  switch ($updatedFieldName) {
338  case 'pid':
339 
340  case ‪$GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['disabled']:
341 
342  case ‪$GLOBALS['TCA']['pages']['ctrl']['delete']:
343 
344  case ‪$GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['starttime']:
345 
346  case ‪$GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['fe_group']:
347 
348  case 'extendToSubpages':
349 
350  case 't3ver_wsid':
351 
352  case 'php_tree_stop':
353  // php_tree_stop
354  $actions['allParents'] = true;
355  $actions['uidInTreelist'] = true;
356  break;
357  case ‪$GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['endtime']:
358  // end time set/unset
359  // When setting an end time the cache entry needs an
360  // expiration time. When unsetting the end time the
361  // page must become listed in the treelist again.
362  if ($updatedFields['endtime'] > 0) {
363  $actions['setExpiration'] = true;
364  } else {
365  $actions['uidInTreelist'] = true;
366  }
367  break;
368  default:
369  if (in_array($updatedFieldName, $this->updateRequiringFields, true)) {
370  $actions['uidInTreelist'] = true;
371  }
372  }
373  }
374  }
375  return $actions;
376  }
377 }
‪TYPO3\CMS\Core\DataHandling\DataHandler
Definition: DataHandler.php:81
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks
Definition: TreelistCacheUpdateHooks.php:29
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\__construct
‪__construct()
Definition: TreelistCacheUpdateHooks.php:44
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\clearCacheForAllParents
‪clearCacheForAllParents($affectedParentPage)
Definition: TreelistCacheUpdateHooks.php:239
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\determineClearCacheActions
‪array determineClearCacheActions($status, $updatedFields)
Definition: TreelistCacheUpdateHooks.php:327
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\requiresUpdate
‪bool requiresUpdate(array $updatedFields)
Definition: TreelistCacheUpdateHooks.php:186
‪TYPO3\CMS\Backend\Utility\BackendUtility\BEgetRootLine
‪static array BEgetRootLine($uid, $clause='', $workspaceOL=false, array $additionalFields=[])
Definition: BackendUtility.php:374
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\moveRecord_afterAnotherElementPostProcess
‪moveRecord_afterAnotherElementPostProcess($table, $recordId, $destinationPid, $originalDestinationPid, array $movedRecord, array $updatedFields, DataHandler $dataHandler)
Definition: TreelistCacheUpdateHooks.php:166
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\clearCacheWhereUidInTreelist
‪clearCacheWhereUidInTreelist($affectedPage)
Definition: TreelistCacheUpdateHooks.php:269
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\moveRecord_firstElementPostProcess
‪moveRecord_firstElementPostProcess($table, $recordId, $destinationPid, array $movedRecord, array $updatedFields, DataHandler $dataHandler)
Definition: TreelistCacheUpdateHooks.php:140
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\processDatamap_afterDatabaseOperations
‪processDatamap_afterDatabaseOperations($status, $table, $recordId, array $updatedFields, DataHandler $dataHandler)
Definition: TreelistCacheUpdateHooks.php:71
‪TYPO3\CMS\Frontend\Hooks
Definition: FrontendHooks.php:4
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\$updateRequiringFields
‪array $updateRequiringFields
Definition: TreelistCacheUpdateHooks.php:35
‪TYPO3\CMS\Backend\Utility\BackendUtility
Definition: BackendUtility.php:72
‪TYPO3\CMS\Backend\Utility\BackendUtility\getRecord
‪static array null getRecord($table, $uid, $fields=' *', $where='', $useDeleteClause=true)
Definition: BackendUtility.php:130
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\processCmdmap_postProcess
‪processCmdmap_postProcess($command, $table, $recordId, $commandValue, DataHandler $dataHandler)
Definition: TreelistCacheUpdateHooks.php:104
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:31
‪$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:207
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\setCacheExpiration
‪setCacheExpiration($affectedPage, $expirationTime)
Definition: TreelistCacheUpdateHooks.php:288
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:44
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:45
‪TYPO3\CMS\Frontend\Hooks\TreelistCacheUpdateHooks\removeExpiredCacheEntries
‪removeExpiredCacheEntries()
Definition: TreelistCacheUpdateHooks.php:304