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