TYPO3 CMS  TYPO3_7-6
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 
20 
26 {
34  'pid',
35  'php_tree_stop',
36  'extendToSubpages'
37  ];
38 
42  public function __construct()
43  {
44  // As enableFields can be set dynamically we add them here
45  $pagesEnableFields = $GLOBALS['TCA']['pages']['ctrl']['enablecolumns'];
46  foreach ($pagesEnableFields as $pagesEnableField) {
47  $this->updateRequiringFields[] = $pagesEnableField;
48  }
49  $this->updateRequiringFields[] = $GLOBALS['TCA']['pages']['ctrl']['delete'];
50  // Extension can add fields to the pages table that require an
51  // update of the treelist cache, too; so we also add those
52  // example: $TYPO3_CONF_VARS['BE']['additionalTreelistUpdateFields'] .= ',my_field';
53  if (!empty($GLOBALS['TYPO3_CONF_VARS']['BE']['additionalTreelistUpdateFields'])) {
54  $additionalTreelistUpdateFields = GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['BE']['additionalTreelistUpdateFields'], true);
55  $this->updateRequiringFields = array_merge($this->updateRequiringFields, $additionalTreelistUpdateFields);
56  }
57  }
58 
70  public function processDatamap_afterDatabaseOperations($status, $table, $recordId, array $updatedFields, DataHandler $tceMain)
71  {
72  if ($table == 'pages' && $this->requiresUpdate($updatedFields)) {
73  $affectedPagePid = 0;
74  $affectedPageUid = 0;
75  if ($status == 'new') {
76  // Detect new pages
77  // Resolve the uid
78  $affectedPageUid = $tceMain->substNEWwithIDs[$recordId];
79  $affectedPagePid = $updatedFields['pid'];
80  } elseif ($status == 'update') {
81  // Detect updated pages
82  $affectedPageUid = $recordId;
83  // When updating a page the pid is not directly available so we
84  // need to retrieve it ourselves.
85  $fullPageRecord = BackendUtility::getRecord($table, $recordId);
86  $affectedPagePid = $fullPageRecord['pid'];
87  }
88  $clearCacheActions = $this->determineClearCacheActions($status, $updatedFields);
89  $this->processClearCacheActions($affectedPageUid, $affectedPagePid, $updatedFields, $clearCacheActions);
90  }
91  }
92 
104  public function processCmdmap_postProcess($command, $table, $recordId, $commandValue, DataHandler $tceMain)
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 
141  public function moveRecord_firstElementPostProcess($table, $recordId, $destinationPid, array $movedRecord, array $updatedFields, DataHandler $tceMain)
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 
168  public function moveRecord_afterAnotherElementPostProcess($table, $recordId, $destinationPid, $originalDestinationPid, array $movedRecord, array $updatedFields, DataHandler $tceMain)
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)) {
194  $requiresUpdate = true;
195  break;
196  }
197  }
198  return $requiresUpdate;
199  }
200 
210  protected function processClearCacheActions($affectedPage, $affectedParentPage, $updatedFields, array $actions)
211  {
212  $actionNames = array_keys($actions);
213  foreach ($actionNames as $actionName) {
214  switch ($actionName) {
215  case 'allParents':
216  $this->clearCacheForAllParents($affectedParentPage);
217  break;
218  case 'setExpiration':
219  // Only used when setting an end time for a page
220  $expirationTime = $updatedFields['endtime'];
221  $this->setCacheExpiration($affectedPage, $expirationTime);
222  break;
223  case 'uidInTreelist':
224  $this->clearCacheWhereUidInTreelist($affectedPage);
225  break;
226  }
227  }
228  // From time to time clean the cache from expired entries
229  // (theoretically every 1000 calls)
230  $randomNumber = rand(1, 1000);
231  if ($randomNumber == 500) {
232  $this->removeExpiredCacheEntries();
233  }
234  }
235 
243  protected function clearCacheForAllParents($affectedParentPage)
244  {
245  $rootLine = BackendUtility::BEgetRootLine($affectedParentPage);
246  $rootLineIds = [];
247  foreach ($rootLine as $page) {
248  if ($page['uid'] != 0) {
249  $rootLineIds[] = $page['uid'];
250  }
251  }
252  if (!empty($rootLineIds)) {
253  $rootLineIdsImploded = implode(',', $rootLineIds);
254  $this->getDatabaseConnection()->exec_DELETEquery('cache_treelist', 'pid IN(' . $rootLineIdsImploded . ')');
255  }
256  }
257 
265  protected function clearCacheWhereUidInTreelist($affectedPage)
266  {
267  $this->getDatabaseConnection()->exec_DELETEquery('cache_treelist', $this->getDatabaseConnection()->listQuery('treelist', $affectedPage, 'cache_treelist'));
268  }
269 
278  protected function setCacheExpiration($affectedPage, $expirationTime)
279  {
280  $this->getDatabaseConnection()->exec_UPDATEquery('cache_treelist', $this->getDatabaseConnection()->listQuery('treelist', $affectedPage, 'cache_treelist'), [
281  'expires' => $expirationTime
282  ]);
283  }
284 
290  protected function removeExpiredCacheEntries()
291  {
292  $this->getDatabaseConnection()->exec_DELETEquery('cache_treelist', 'expires <= ' . $GLOBALS['EXEC_TIME']);
293  }
294 
303  protected function determineClearCacheActions($status, $updatedFields)
304  {
305  $actions = [];
306  if ($status == 'new') {
307  // New page
308  $actions['allParents'] = true;
309  } elseif ($status == 'update') {
310  $updatedFieldNames = array_keys($updatedFields);
311  foreach ($updatedFieldNames as $updatedFieldName) {
312  switch ($updatedFieldName) {
313  case 'pid':
314 
315  case $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['disabled']:
316 
317  case $GLOBALS['TCA']['pages']['ctrl']['delete']:
318 
319  case $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['starttime']:
320 
321  case $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['fe_group']:
322 
323  case 'extendToSubpages':
324 
325  case 't3ver_wsid':
326 
327  case 'php_tree_stop':
328  // php_tree_stop
329  $actions['allParents'] = true;
330  $actions['uidInTreelist'] = true;
331  break;
332  case $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['endtime']:
333  // end time set/unset
334  // When setting an end time the cache entry needs an
335  // expiration time. When unsetting the end time the
336  // page must become listed in the treelist again.
337  if ($updatedFields['endtime'] > 0) {
338  $actions['setExpiration'] = true;
339  } else {
340  $actions['uidInTreelist'] = true;
341  }
342  break;
343  default:
344  if (in_array($updatedFieldName, $this->updateRequiringFields)) {
345  $actions['uidInTreelist'] = true;
346  }
347  }
348  }
349  }
350  return $actions;
351  }
352 
358  protected function getDatabaseConnection()
359  {
360  return $GLOBALS['TYPO3_DB'];
361  }
362 }
processDatamap_afterDatabaseOperations($status, $table, $recordId, array $updatedFields, DataHandler $tceMain)
moveRecord_afterAnotherElementPostProcess($table, $recordId, $destinationPid, $originalDestinationPid, array $movedRecord, array $updatedFields, DataHandler $tceMain)
static BEgetRootLine($uid, $clause='', $workspaceOL=false)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
moveRecord_firstElementPostProcess($table, $recordId, $destinationPid, array $movedRecord, array $updatedFields, DataHandler $tceMain)
processCmdmap_postProcess($command, $table, $recordId, $commandValue, DataHandler $tceMain)
processClearCacheActions($affectedPage, $affectedParentPage, $updatedFields, array $actions)
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']