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