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