[TASK] Remove @package and @subpackage annotations
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Classes / Hooks / TreelistCacheUpdateHooks.php
1 <?php
2 namespace TYPO3\CMS\Frontend\Hooks;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2008-2011 Ingo Renner (ingo@typo3.org)
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 * A copy is found in the textfile GPL.txt and important notices to the license
19 * from the author is found in LICENSE.txt distributed with these scripts.
20 *
21 *
22 * This script is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * This copyright notice MUST APPEAR in all copies of the script!
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 */
35 class TreelistCacheUpdateHooks {
36
37 // Should not be manipulated from others except through the
38 // configuration provided @see __construct()
39 private $updateRequiringFields = array(
40 'pid',
41 'php_tree_stop',
42 'extendToSubpages'
43 );
44
45 /**
46 * Constructor, adds update requiring fields to the default ones
47 */
48 public function __construct() {
49 // As enableFields can be set dynamically we add them here
50 $pagesEnableFields = $GLOBALS['TCA']['pages']['ctrl']['enablecolumns'];
51 foreach ($pagesEnableFields as $pagesEnableField) {
52 $this->updateRequiringFields[] = $pagesEnableField;
53 }
54 $this->updateRequiringFields[] = $GLOBALS['TCA']['pages']['ctrl']['delete'];
55 // Extension can add fields to the pages table that require an
56 // update of the treelist cache, too; so we also add those
57 // example: $TYPO3_CONF_VARS['BE']['additionalTreelistUpdateFields'] .= ',my_field';
58 if (!empty($GLOBALS['TYPO3_CONF_VARS']['BE']['additionalTreelistUpdateFields'])) {
59 $additionalTreelistUpdateFields = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['BE']['additionalTreelistUpdateFields'], TRUE);
60 $this->updateRequiringFields += $additionalTreelistUpdateFields;
61 }
62 }
63
64 /**
65 * waits for TCEmain commands and looks for changed pages, if found further
66 * changes take place to determine whether the cache needs to be updated
67 *
68 * @param string $status TCEmain operation status, either 'new' or 'update'
69 * @param string $table The DB table the operation was carried out on
70 * @param mixed $recordId The record's uid for update records, a string to look the record's uid up after it has been created
71 * @param array $updatedFields Array of changed fiels and their new values
72 * @param \TYPO3\CMS\Core\DataHandling\DataHandler $tceMain TCEmain parent object
73 * @return void
74 */
75 public function processDatamap_afterDatabaseOperations($status, $table, $recordId, array $updatedFields, \TYPO3\CMS\Core\DataHandling\DataHandler $tceMain) {
76 if ($table == 'pages' && $this->requiresUpdate($updatedFields)) {
77 $affectedPagePid = 0;
78 $affectedPageUid = 0;
79 if ($status == 'new') {
80 // Detect new pages
81 // Resolve the uid
82 $affectedPageUid = $tceMain->substNEWwithIDs[$recordId];
83 $affectedPagePid = $updatedFields['pid'];
84 } elseif ($status == 'update') {
85 // Detect updated pages
86 $affectedPageUid = $recordId;
87 // When updating a page the pid is not directly available so we
88 // need to retrieve it ourselves.
89 $fullPageRecord = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord($table, $recordId);
90 $affectedPagePid = $fullPageRecord['pid'];
91 }
92 $clearCacheActions = $this->determineClearCacheActions($status, $updatedFields);
93 $this->processClearCacheActions($affectedPageUid, $affectedPagePid, $updatedFields, $clearCacheActions);
94 }
95 }
96
97 /**
98 * waits for TCEmain commands and looks for deleted pages, if found further
99 * changes take place to determine whether the cache needs to be updated
100 *
101 * @param string $command The TCE command
102 * @param string $table The record's table
103 * @param integer $recordId The record's uid
104 * @param array $commandValue The commands value, typically an array with more detailed command information
105 * @param \TYPO3\CMS\Core\DataHandling\DataHandler $tceMain The TCEmain parent object
106 * @return void
107 */
108 public function processCmdmap_postProcess($command, $table, $recordId, $commandValue, \TYPO3\CMS\Core\DataHandling\DataHandler $tceMain) {
109 if ($table == 'pages' && $command == 'delete') {
110 $deletedRecord = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord($table, $recordId, '*', '', FALSE);
111 $affectedPageUid = $deletedRecord['uid'];
112 $affectedPagePid = $deletedRecord['pid'];
113 // Faking the updated fields
114 $updatedFields = array('deleted' => 1);
115 $clearCacheActions = $this->determineClearCacheActions('update', $updatedFields);
116 $this->processClearCacheActions($affectedPageUid, $affectedPagePid, $updatedFields, $clearCacheActions);
117 }
118 }
119
120 /**
121 * waits for TCEmain commands and looks for moved pages, if found further
122 * changes take place to determine whether the cache needs to be updated
123 *
124 * @param string $table Table name of the moved record
125 * @param integer $recordId The record's uid
126 * @param integer $destinationPid The record's destination page id
127 * @param array $movedRecord The record that moved
128 * @param array $updatedFields Array of changed fields
129 * @param \TYPO3\CMS\Core\DataHandling\DataHandler $tceMain TCEmain parent object
130 * @return void
131 */
132 public function moveRecord_firstElementPostProcess($table, $recordId, $destinationPid, array $movedRecord, array $updatedFields, \TYPO3\CMS\Core\DataHandling\DataHandler $tceMain) {
133 if ($table == 'pages' && $this->requiresUpdate($updatedFields)) {
134 $affectedPageUid = $recordId;
135 $affectedPageOldPid = $movedRecord['pid'];
136 $affectedPageNewPid = $updatedFields['pid'];
137 $clearCacheActions = $this->determineClearCacheActions('update', $updatedFields);
138 // Clear treelist entries for old parent page
139 $this->processClearCacheActions($affectedPageUid, $affectedPageOldPid, $updatedFields, $clearCacheActions);
140 // Clear treelist entries for new parent page
141 $this->processClearCacheActions($affectedPageUid, $affectedPageNewPid, $updatedFields, $clearCacheActions);
142 }
143 }
144
145 /**
146 * Waits for TCEmain commands and looks for moved pages, if found further
147 * changes take place to determine whether the cache needs to be updated
148 *
149 * @param string $table Table name of the moved record
150 * @param integer $recordId The record's uid
151 * @param integer $destinationPid The record's destination page id
152 * @param integer $originalDestinationPid (negative) page id th page has been moved after
153 * @param array $movedRecord The record that moved
154 * @param array $updatedFields Array of changed fields
155 * @param \TYPO3\CMS\Core\DataHandling\DataHandler $tceMain TCEmain parent object
156 * @return void
157 */
158 public function moveRecord_afterAnotherElementPostProcess($table, $recordId, $destinationPid, $originalDestinationPid, array $movedRecord, array $updatedFields, \TYPO3\CMS\Core\DataHandling\DataHandler $tceMain) {
159 if ($table == 'pages' && $this->requiresUpdate($updatedFields)) {
160 $affectedPageUid = $recordId;
161 $affectedPageOldPid = $movedRecord['pid'];
162 $affectedPageNewPid = $updatedFields['pid'];
163 $clearCacheActions = $this->determineClearCacheActions('update', $updatedFields);
164 // Clear treelist entries for old parent page
165 $this->processClearCacheActions($affectedPageUid, $affectedPageOldPid, $updatedFields, $clearCacheActions);
166 // Clear treelist entries for new parent page
167 $this->processClearCacheActions($affectedPageUid, $affectedPageNewPid, $updatedFields, $clearCacheActions);
168 }
169 }
170
171 /**
172 * Checks whether the change requires an update of the treelist cache
173 *
174 * @param array $updatedFields Array of changed fields
175 * @return boolean TRUE if the treelist cache needs to be updated, FALSE if no update to the cache is required
176 */
177 protected function requiresUpdate(array $updatedFields) {
178 $requiresUpdate = FALSE;
179 $updatedFieldNames = array_keys($updatedFields);
180 foreach ($updatedFieldNames as $updatedFieldName) {
181 if (in_array($updatedFieldName, $this->updateRequiringFields)) {
182 $requiresUpdate = TRUE;
183 break;
184 }
185 }
186 return $requiresUpdate;
187 }
188
189 /**
190 * Calls the cache maintainance functions according to the determined actions
191 *
192 * @param integer $affectedPage uid of the affected page
193 * @param integer $affectedParentPage parent uid of the affected page
194 * @param array $updatedFields Array of updated fields and their new values
195 * @param array $actions Array of actions to carry out
196 * @return void
197 */
198 protected function processClearCacheActions($affectedPage, $affectedParentPage, $updatedFields, array $actions) {
199 $actionNames = array_keys($actions);
200 foreach ($actionNames as $actionName) {
201 switch ($actionName) {
202 case 'allParents':
203 $this->clearCacheForAllParents($affectedParentPage);
204 break;
205 case 'setExpiration':
206 // Only used when setting an end time for a page
207 $expirationTime = $updatedFields['endtime'];
208 $this->setCacheExpiration($affectedPage, $expirationTime);
209 break;
210 case 'uidInTreelist':
211 $this->clearCacheWhereUidInTreelist($affectedPage);
212 break;
213 }
214 }
215 // From time to time clean the cache from expired entries
216 // (theoretically every 1000 calls)
217 $randomNumber = rand(1, 1000);
218 if ($randomNumber == 500) {
219 $this->removeExpiredCacheEntries();
220 }
221 }
222
223 /**
224 * Clears the treelist cache for all parents of a changed page.
225 * gets called after creating a new page and after moving a page
226 *
227 * @param integer $affectedParentPage Parent page id of the changed page, the page to start clearing from
228 * @return void
229 */
230 protected function clearCacheForAllParents($affectedParentPage) {
231 $rootline = \TYPO3\CMS\Backend\Utility\BackendUtility::BEgetRootLine($affectedParentPage);
232 $rootlineIds = array();
233 foreach ($rootline as $page) {
234 if ($page['uid'] != 0) {
235 $rootlineIds[] = $page['uid'];
236 }
237 }
238 if (!empty($rootlineIds)) {
239 $rootlineIdsImploded = implode(',', $rootlineIds);
240 $GLOBALS['TYPO3_DB']->exec_DELETEquery('cache_treelist', 'pid IN(' . $rootlineIdsImploded . ')');
241 }
242 }
243
244 /**
245 * Clears the treelist cache for all pages where the affected page is found
246 * in the treelist
247 *
248 * @param integer $affectedPage ID of the changed page
249 * @return void
250 */
251 protected function clearCacheWhereUidInTreelist($affectedPage) {
252 $GLOBALS['TYPO3_DB']->exec_DELETEquery('cache_treelist', $GLOBALS['TYPO3_DB']->listQuery('treelist', $affectedPage, 'cache_treelist'));
253 }
254
255 /**
256 * Sets an expiration time for all cache entries having the changed page in
257 * the treelist.
258 *
259 * @param integer $affectedPage Uid of the changed page
260 * @param integer $expirationTime
261 * @return void
262 */
263 protected function setCacheExpiration($affectedPage, $expirationTime) {
264 $GLOBALS['TYPO3_DB']->exec_UPDATEquery('cache_treelist', $GLOBALS['TYPO3_DB']->listQuery('treelist', $affectedPage, 'cache_treelist'), array(
265 'expires' => $expirationTime
266 ));
267 }
268
269 /**
270 * Removes all expired treelist cache entries
271 *
272 * @return void
273 */
274 protected function removeExpiredCacheEntries() {
275 $GLOBALS['TYPO3_DB']->exec_DELETEquery('cache_treelist', 'expires <= ' . $GLOBALS['EXEC_TIME']);
276 }
277
278 /**
279 * Determines what happened to the page record, this is necessary to clear
280 * as less cache entries as needed later
281 *
282 * @param string $status TCEmain operation status, either 'new' or 'update'
283 * @param array $updatedFields Array of updated fields
284 * @return string List of actions that happened to the page record
285 */
286 protected function determineClearCacheActions($status, $updatedFields) {
287 $actions = array();
288 if ($status == 'new') {
289 // New page
290 $actions['allParents'] = TRUE;
291 } elseif ($status == 'update') {
292 $updatedFieldNames = array_keys($updatedFields);
293 foreach ($updatedFieldNames as $updatedFieldName) {
294 switch ($updatedFieldName) {
295 case 'pid':
296
297 case $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['disabled']:
298
299 case $GLOBALS['TCA']['pages']['ctrl']['delete']:
300
301 case $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['starttime']:
302
303 case $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['fe_group']:
304
305 case 'extendToSubpages':
306
307 case 'php_tree_stop':
308 // php_tree_stop
309 $actions['allParents'] = TRUE;
310 $actions['uidInTreelist'] = TRUE;
311 break;
312 case $GLOBALS['TCA']['pages']['ctrl']['enablecolumns']['endtime']:
313 // end time set/unset
314 // When setting an end time the cache entry needs an
315 // expiration time. When unsetting the end time the
316 // page must become listed in the treelist again.
317 if ($updatedFields['endtime'] > 0) {
318 $actions['setExpiration'] = TRUE;
319 } else {
320 $actions['uidInTreelist'] = TRUE;
321 }
322 break;
323 default:
324 if (in_array($updatedFieldName, $this->updateRequiringFields)) {
325 $actions['uidInTreelist'] = TRUE;
326 }
327 }
328 }
329 }
330 return $actions;
331 }
332
333 }
334
335
336 ?>