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