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