Fixed bug #10776: Clearing cache_treelist after inserting page on root level does...
[Packages/TYPO3.CMS.git] / typo3 / sysext / cms / tslib / hooks / class.tx_cms_treelistcacheupdate.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2008-2009 Ingo Renner (ingo@typo3.org)
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
18 *
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27
28
29 /**
30 * Class that hooks into TCEmain and listens for updates to pages to update the
31 * treelist cache
32 *
33 * @author Ingo Renner <ingo@typo3.org>
34 * @package TYPO3
35 * @subpackage tslib
36 */
37 class tx_cms_treelistCacheUpdate {
38
39 // should not be manipulated from others except through the
40 // configuration provided @see __construct()
41 private $updateRequiringFields = array(
42 'pid',
43 'php_tree_stop',
44 'extendToSubpages'
45 );
46
47 /**
48 * constructor, adds update requiring fields to the default ones
49 *
50 */
51 public function __construct() {
52
53 // as enableFields can be set dynamically we add them here
54 $pagesEnableFields = $GLOBALS['TCA']['pages']['ctrl']['enablecolumns'];
55 foreach ($pagesEnableFields as $pagesEnableField) {
56 $this->updateRequiringFields[] = $pagesEnableField;
57 }
58 $this->updateRequiringFields[] = $GLOBALS['TCA']['pages']['ctrl']['delete'];
59
60 // extension can add fields to the pages table that require an
61 // update of the treelist cache, too; so we also add those
62 // example: $TYPO3_CONF_VARS['BE']['additionalTreelistUpdateFields'] .= ',my_field';
63 if (!empty($GLOBALS['TYPO3_CONF_VARS']['BE']['additionalTreelistUpdateFields'])) {
64 $additionalTreelistUpdateFields = t3lib_div::trimExplode(
65 ',',
66 $GLOBALS['TYPO3_CONF_VARS']['BE']['additionalTreelistUpdateFields'],
67 true
68 );
69
70 $this->updateRequiringFields += $additionalTreelistUpdateFields;
71 }
72
73 }
74
75 /**
76 * waits for TCEmain commands and looks for changed pages, if found further
77 * changes take place to determine whether the cache needs to be updated
78 *
79 * @param string TCEmain operation status, either 'new' or 'update'
80 * @param string the DB table the operation was carried out on
81 * @param mixed the record's uid for update records, a string to look the record's uid up after it has been created
82 * @param array array of changed fiels and their new values
83 * @param t3lib_TCEmain TCEmain parent object
84 */
85 public function processDatamap_afterDatabaseOperations($status, $table, $recordId, array $updatedFields, t3lib_TCEmain $tceMain) {
86
87 if ($table == 'pages' && $this->requiresUpdate($updatedFields)) {
88 $affectedPagePid = 0;
89 $affectedPageUid = 0;
90
91 if ($status == 'new') {
92 // detect new pages
93
94 // resolve the uid
95 $affectedPageUid = $tceMain->substNEWwithIDs[$recordId];
96 $affectedPagePid = $updatedFields['pid'];
97 } elseif ($status == 'update') {
98 // detect updated pages
99
100 $affectedPageUid = $recordId;
101
102 /*
103 when updating a page the pid is not directly available so we
104 need to retrieve it ourselves.
105 */
106 $fullPageRecord = t3lib_BEfunc::getRecord($table, $recordId);
107 $affectedPagePid = $fullPageRecord['pid'];
108 }
109
110 $clearCacheActions = $this->determineClearCacheActions(
111 $status,
112 $updatedFields
113 );
114
115 $this->processClearCacheActions(
116 $affectedPageUid,
117 $affectedPagePid,
118 $updatedFields,
119 $clearCacheActions
120 );
121 }
122 }
123
124 /**
125 * waits for TCEmain commands and looks for deleted pages, if found further
126 * changes take place to determine whether the cache needs to be updated
127 *
128 * @param string the TCE command
129 * @param string the record's table
130 * @param integer the record's uid
131 * @param array the commands value, typically an array with more detailed command information
132 * @param t3lib_TCEmain the TCEmain parent object
133 */
134 public function processCmdmap_postProcess($command, $table, $recordId, $commandValue, t3lib_TCEmain $tceMain) {
135
136 if ($table == 'pages' && $command == 'delete') {
137
138 $deletedRecord = t3lib_BEfunc::getRecord(
139 $table,
140 $recordId,
141 '*',
142 '',
143 false
144 );
145
146 $affectedPageUid = $deletedRecord['uid'];
147 $affectedPagePid = $deletedRecord['pid'];
148 // faking the updated fields
149 $updatedFields = array('deleted' => 1);
150
151 $clearCacheActions = $this->determineClearCacheActions(
152 'update',
153 $updatedFields
154 );
155
156 $this->processClearCacheActions(
157 $affectedPageUid,
158 $affectedPagePid,
159 $updatedFields,
160 $clearCacheActions
161 );
162 }
163 }
164
165 /**
166 * waits for TCEmain commands and looks for moved pages, if found further
167 * changes take place to determine whether the cache needs to be updated
168 *
169 * @param string table name of the moved record
170 * @param integer the record's uid
171 * @param integer the record's destination page id
172 * @param array the record that moved
173 * @param array array of changed fields
174 * @param t3lib_TCEmain TCEmain parent object
175 */
176 public function moveRecord_firstElementPostProcess($table, $recordId, $destinationPid, array $movedRecord, array $updatedFields, t3lib_TCEmain $tceMain) {
177
178 if ($table == 'pages' && $this->requiresUpdate($updatedFields)) {
179
180 $affectedPageUid = $recordId;
181 $affectedPageOldPid = $movedRecord['pid'];
182 $affectedPageNewPid = $updatedFields['pid'];
183
184 $clearCacheActions = $this->determineClearCacheActions(
185 'update',
186 $updatedFields
187 );
188
189 // clear treelist entries for old parent page
190 $this->processClearCacheActions(
191 $affectedPageUid,
192 $affectedPageOldPid,
193 $updatedFields,
194 $clearCacheActions
195 );
196 // clear treelist entries for new parent page
197 $this->processClearCacheActions(
198 $affectedPageUid,
199 $affectedPageNewPid,
200 $updatedFields,
201 $clearCacheActions
202 );
203 }
204 }
205
206 /**
207 * waits for TCEmain commands and looks for moved pages, if found further
208 * changes take place to determine whether the cache needs to be updated
209 *
210 * @param string table name of the moved record
211 * @param integer the record's uid
212 * @param integer the record's destination page id
213 * @param integer (negative) page id th page has been moved after
214 * @param array the record that moved
215 * @param array array of changed fields
216 * @param t3lib_TCEmain TCEmain parent object
217 */
218 public function moveRecord_afterAnotherElementPostProcess($table, $recordId, $destinationPid, $originalDestinationPid, array $movedRecord, array $updatedFields, t3lib_TCEmain $tceMain) {
219
220 if ($table == 'pages' && $this->requiresUpdate($updatedFields)) {
221
222 $affectedPageUid = $recordId;
223 $affectedPageOldPid = $movedRecord['pid'];
224 $affectedPageNewPid = $updatedFields['pid'];
225
226 $clearCacheActions = $this->determineClearCacheActions(
227 'update',
228 $updatedFields
229 );
230
231 // clear treelist entries for old parent page
232 $this->processClearCacheActions(
233 $affectedPageUid,
234 $affectedPageOldPid,
235 $updatedFields,
236 $clearCacheActions
237 );
238 // clear treelist entries for new parent page
239 $this->processClearCacheActions(
240 $affectedPageUid,
241 $affectedPageNewPid,
242 $updatedFields,
243 $clearCacheActions
244 );
245 }
246 }
247
248 /**
249 * checks whether the change requires an update of the treelist cache
250 *
251 * @param array array of changed fields
252 * @return boolean true if the treelist cache needs to be updated, false if no update to the cache is required
253 */
254 protected function requiresUpdate(array $updatedFields) {
255 $requiresUpdate = false;
256
257 $updatedFieldNames = array_keys($updatedFields);
258 foreach ($updatedFieldNames as $updatedFieldName) {
259 if (in_array($updatedFieldName, $this->updateRequiringFields)) {
260 $requiresUpdate = true;
261 break;
262 }
263 }
264
265 return $requiresUpdate;
266 }
267
268 /**
269 * calls the cache maintainance functions according to the determined actions
270 *
271 * @param integer uid of the affected page
272 * @param integer parent uid of the affected page
273 * @param array array of updated fields and their new values
274 * @param array array of actions to carry out
275 */
276 protected function processClearCacheActions($affectedPage, $affectedParentPage, $updatedFields, array $actions) {
277 $actionNames = array_keys($actions);
278 foreach ($actionNames as $actionName) {
279 switch ($actionName) {
280 case 'allParents':
281 $this->clearCacheForAllParents($affectedParentPage);
282 break;
283 case 'setExpiration':
284 // only used when setting an end time for a page
285 $expirationTime = $updatedFields['endtime'];
286 $this->setCacheExpiration($affectedPage, $expirationTime);
287 break;
288 case 'uidInTreelist':
289 $this->clearCacheWhereUidInTreelist($affectedPage);
290 break;
291 }
292 }
293
294 // from time to time clean the cache from expired entries
295 // (theoretically every 1000 calls)
296 $randomNumber = rand(1, 1000);
297 if ($randomNumber == 500) {
298 $this->removeExpiredCacheEntries();
299 }
300 }
301
302 /**
303 * clears the treelist cache for all parents of a changed page.
304 * gets called after creating a new page and after moving a page
305 *
306 * @param integer parent page id of the changed page, the page to start clearing from
307 */
308 protected function clearCacheForAllParents($affectedParentPage) {
309 $rootline = t3lib_BEfunc::BEgetRootLine($affectedParentPage);
310
311 $rootlineIds = array();
312 foreach ($rootline as $page) {
313 if($page['uid'] != 0) {
314 $rootlineIds[] = $page['uid'];
315 }
316 }
317
318 if (!empty($rootlineIds)) {
319 $rootlineIdsImploded = implode(',', $rootlineIds);
320
321 $GLOBALS['TYPO3_DB']->exec_DELETEquery(
322 'cache_treelist',
323 'pid IN(' . $rootlineIdsImploded . ')'
324 );
325 }
326 }
327
328 /**
329 * clears the treelist cache for all pages where the affected page is found
330 * in the treelist
331 *
332 * @param integer Id of the changed page
333 */
334 protected function clearCacheWhereUidInTreelist($affectedPage) {
335 $GLOBALS['TYPO3_DB']->exec_DELETEquery(
336 'cache_treelist',
337 $GLOBALS['TYPO3_DB']->listQuery(
338 'treelist',
339 $affectedPage,
340 'cache_treelist'
341 )
342 );
343 }
344
345 /**
346 * sets an expiration time for all cache entries having the changed page in
347 * the treelist.
348 *
349 * @param integer uid of the changed page
350 */
351 protected function setCacheExpiration($affectedPage, $expirationTime) {
352
353 $GLOBALS['TYPO3_DB']->exec_UPDATEquery(
354 'cache_treelist',
355 $GLOBALS['TYPO3_DB']->listQuery(
356 'treelist',
357 $affectedPage,
358 'cache_treelist'
359 ),
360 array(
361 'expires' => $expirationTime
362 )
363 );
364 }
365
366 /**
367 * removes all expired treelist cache entries
368 *
369 */
370 protected function removeExpiredCacheEntries() {
371 $GLOBALS['TYPO3_DB']->exec_DELETEquery(
372 'cache_treelist',
373 'expires <= ' . time()
374 );
375 }
376
377 /**
378 * determines what happened to the page record, this is necessary to clear
379 * as less cache entries as needed later
380 *
381 * @param string TCEmain operation status, either 'new' or 'update'
382 * @param array array of updated fields
383 * @return string list of actions that happened to the page record
384 */
385 protected function determineClearCacheActions($status, $updatedFields) {
386 $actions = array();
387
388 if ($status == 'new') {
389 // new page
390 $actions['allParents'] = true;
391 } elseif ($status == 'update') {
392 $updatedFieldNames = array_keys($updatedFields);
393
394 foreach ($updatedFieldNames as $updatedFieldName) {
395 switch ($updatedFieldName) {
396 case 'pid':
397 // page moved
398 $actions['allParents'] = true;
399 $actions['uidInTreelist'] = true;
400 break;
401 case $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['disabled']:
402 case $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['fe_group']:
403 case $GLOBALS['TCA']['pages']['ctrl']['delete']:
404 case 'extendToSubpages':
405 case 'php_tree_stop':
406 // page hidden / unhidden / deleted / extendToSubpages set
407 // php_tree_stop and/or FE groups set
408 $actions['uidInTreelist'] = true;
409 break;
410 case $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['starttime']:
411 /*
412 start time set/unset
413 Doesn't matter whether it was set or unset, in both
414 cases the cache needs to be cleared. When setting a
415 start time the page must be removed from the
416 treelist. When unsetting the start time it must
417 become listed in the tree list again.
418 */
419 $actions['uidInTreelist'] = true;
420 break;
421 case $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['endtime']:
422 /*
423 end time set/unset
424 When setting an end time the cache entry needs an
425 expiration time. When unsetting the end time the
426 page must become listed in the treelist again.
427 */
428 if($updatedFields['endtime'] > 0) {
429 $actions['setExpiration'] = true;
430 } else {
431 $actions['uidInTreelist'] = true;
432 }
433 break;
434 default:
435 if (in_array($updatedFieldName, $this->updateRequiringFields)) {
436 $actions['uidInTreelist'] = true;
437 }
438 }
439 }
440 }
441
442 return $actions;
443 }
444
445 }
446
447
448 if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['tslib/hooks/class.tx_cms_treelistcacheupdate.php']) {
449 include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['tslib/hooks/class.tx_cms_treelistcacheupdate.php']);
450 }
451
452 ?>