TYPO3CMS  8
 All Classes Namespaces Files Functions Variables Pages
TreelistCacheUpdateHooks.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Frontend\Hooks;
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 
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 
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 
143  public function moveRecord_firstElementPostProcess($table, $recordId, $destinationPid, array $movedRecord, array $updatedFields, DataHandler $dataHandler)
144  {
145  if ($table === 'pages' && $this->requiresUpdate($updatedFields)) {
146  $affectedPageUid = $recordId;
147  $affectedPageOldPid = $movedRecord['pid'];
148  $affectedPageNewPid = $updatedFields['pid'];
149  $clearCacheActions = $this->determineClearCacheActions('update', $updatedFields);
150  // Clear treelist entries for old parent page
151  $this->processClearCacheActions($affectedPageUid, $affectedPageOldPid, $updatedFields, $clearCacheActions);
152  // Clear treelist entries for new parent page
153  $this->processClearCacheActions($affectedPageUid, $affectedPageNewPid, $updatedFields, $clearCacheActions);
154  }
155  }
156 
170  public function moveRecord_afterAnotherElementPostProcess($table, $recordId, $destinationPid, $originalDestinationPid, array $movedRecord, array $updatedFields, DataHandler $dataHandler)
171  {
172  if ($table === 'pages' && $this->requiresUpdate($updatedFields)) {
173  $affectedPageUid = $recordId;
174  $affectedPageOldPid = $movedRecord['pid'];
175  $affectedPageNewPid = $updatedFields['pid'];
176  $clearCacheActions = $this->determineClearCacheActions('update', $updatedFields);
177  // Clear treelist entries for old parent page
178  $this->processClearCacheActions($affectedPageUid, $affectedPageOldPid, $updatedFields, $clearCacheActions);
179  // Clear treelist entries for new parent page
180  $this->processClearCacheActions($affectedPageUid, $affectedPageNewPid, $updatedFields, $clearCacheActions);
181  }
182  }
183 
190  protected function requiresUpdate(array $updatedFields)
191  {
192  $requiresUpdate = false;
193  $updatedFieldNames = array_keys($updatedFields);
194  foreach ($updatedFieldNames as $updatedFieldName) {
195  if (in_array($updatedFieldName, $this->updateRequiringFields, true)) {
196  $requiresUpdate = true;
197  break;
198  }
199  }
200  return $requiresUpdate;
201  }
202 
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 = rand(1, 1000);
233  if ($randomNumber === 500) {
234  $this->removeExpiredCacheEntries();
235  }
236  }
237 
245  protected function clearCacheForAllParents($affectedParentPage)
246  {
247  $rootLine = BackendUtility::BEgetRootLine($affectedParentPage);
248  $rootLineIds = [];
249  foreach ($rootLine as $page) {
250  if ($page['uid'] != 0) {
251  $rootLineIds[] = $page['uid'];
252  }
253  }
254  if (!empty($rootLineIds)) {
255  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
256  ->getQueryBuilderForTable('cache_treelist');
257  $queryBuilder
258  ->delete('cache_treelist')
259  ->where(
260  $queryBuilder->expr()->in(
261  'pid',
262  $queryBuilder->createNamedParameter($rootLineIds, Connection::PARAM_INT_ARRAY)
263  )
264  )
265  ->execute();
266  }
267  }
268 
276  protected function clearCacheWhereUidInTreelist($affectedPage)
277  {
278  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
279  ->getQueryBuilderForTable('cache_treelist');
280  $queryBuilder
281  ->delete('cache_treelist')
282  ->where(
283  $queryBuilder->expr()->inSet('treelist', (int)$affectedPage)
284  )
285  ->execute();
286  }
287 
296  protected function setCacheExpiration($affectedPage, $expirationTime)
297  {
298  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
299  ->getQueryBuilderForTable('cache_treelist');
300  $queryBuilder
301  ->update('cache_treelist')
302  ->where(
303  $queryBuilder->expr()->inSet('treelist', (int)$affectedPage)
304  )
305  ->set('expires', $expirationTime)
306  ->execute();
307  }
308 
314  protected function removeExpiredCacheEntries()
315  {
316  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
317  ->getQueryBuilderForTable('cache_treelist');
318  $queryBuilder
319  ->delete('cache_treelist')
320  ->where(
321  $queryBuilder->expr()->lte(
322  'expires',
323  $queryBuilder->createNamedParameter($GLOBALS['EXEC_TIME'], \PDO::PARAM_INT)
324  )
325  )
326  ->execute();
327  }
328 
337  protected function determineClearCacheActions($status, $updatedFields)
338  {
339  $actions = [];
340  if ($status === 'new') {
341  // New page
342  $actions['allParents'] = true;
343  } elseif ($status === 'update') {
344  $updatedFieldNames = array_keys($updatedFields);
345  foreach ($updatedFieldNames as $updatedFieldName) {
346  switch ($updatedFieldName) {
347  case 'pid':
348 
349  case $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['disabled']:
350 
351  case $GLOBALS['TCA']['pages']['ctrl']['delete']:
352 
353  case $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['starttime']:
354 
355  case $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['fe_group']:
356 
357  case 'extendToSubpages':
358 
359  case 't3ver_wsid':
360 
361  case 'php_tree_stop':
362  // php_tree_stop
363  $actions['allParents'] = true;
364  $actions['uidInTreelist'] = true;
365  break;
366  case $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['endtime']:
367  // end time set/unset
368  // When setting an end time the cache entry needs an
369  // expiration time. When unsetting the end time the
370  // page must become listed in the treelist again.
371  if ($updatedFields['endtime'] > 0) {
372  $actions['setExpiration'] = true;
373  } else {
374  $actions['uidInTreelist'] = true;
375  }
376  break;
377  default:
378  if (in_array($updatedFieldName, $this->updateRequiringFields, true)) {
379  $actions['uidInTreelist'] = true;
380  }
381  }
382  }
383  }
384  return $actions;
385  }
386 }
moveRecord_firstElementPostProcess($table, $recordId, $destinationPid, array $movedRecord, array $updatedFields, DataHandler $dataHandler)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
static BEgetRootLine($uid, $clause= '', $workspaceOL=false)
static getRecord($table, $uid, $fields= '*', $where= '', $useDeleteClause=true)
processCmdmap_postProcess($command, $table, $recordId, $commandValue, DataHandler $dataHandler)
moveRecord_afterAnotherElementPostProcess($table, $recordId, $destinationPid, $originalDestinationPid, array $movedRecord, array $updatedFields, DataHandler $dataHandler)
if(TYPO3_MODE=== 'BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static makeInstance($className,...$constructorArguments)
processClearCacheActions($affectedPage, $affectedParentPage, $updatedFields, array $actions)
processDatamap_afterDatabaseOperations($status, $table, $recordId, array $updatedFields, DataHandler $dataHandler)