[BUGFIX] Avoid php warnings in Indexed Search Repository and Controlller
[Packages/TYPO3.CMS.git] / typo3 / sysext / belog / Classes / Controller / BackendLogController.php
1 <?php
2
3 /*
4 * This file is part of the TYPO3 CMS project.
5 *
6 * It is free software; you can redistribute it and/or modify it under
7 * the terms of the GNU General Public License, either version 2
8 * of the License, or any later version.
9 *
10 * For the full copyright and license information, please read the
11 * LICENSE.txt file that was distributed with this source code.
12 *
13 * The TYPO3 project - inspiring people to share!
14 */
15
16 namespace TYPO3\CMS\Belog\Controller;
17
18 use Psr\Http\Message\ResponseInterface;
19 use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
20 use TYPO3\CMS\Backend\Utility\BackendUtility;
21 use TYPO3\CMS\Belog\Domain\Model\Constraint;
22 use TYPO3\CMS\Belog\Domain\Model\LogEntry;
23 use TYPO3\CMS\Belog\Domain\Repository\LogEntryRepository;
24 use TYPO3\CMS\Belog\Domain\Repository\WorkspaceRepository;
25 use TYPO3\CMS\Core\Messaging\AbstractMessage;
26 use TYPO3\CMS\Core\Page\PageRenderer;
27 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
28 use TYPO3\CMS\Core\Utility\GeneralUtility;
29 use TYPO3\CMS\Extbase\Http\ForwardResponse;
30 use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
31 use TYPO3\CMS\Extbase\Persistence\QueryResultInterface;
32 use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
33
34 /**
35 * Show log entries from sys_log
36 *
37 * @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
38 */
39 class BackendLogController extends ActionController
40 {
41 protected ModuleTemplateFactory $moduleTemplateFactory;
42 protected LogEntryRepository $logEntryRepository;
43 protected WorkspaceRepository $workspaceRepository;
44
45 public function __construct(
46 ModuleTemplateFactory $moduleTemplateFactory,
47 LogEntryRepository $logEntryRepository,
48 WorkspaceRepository $workspaceRepository
49 ) {
50 $this->moduleTemplateFactory = $moduleTemplateFactory;
51 $this->logEntryRepository = $logEntryRepository;
52 $this->workspaceRepository = $workspaceRepository;
53 }
54
55 /**
56 * Initialize list action
57 */
58 public function initializeListAction()
59 {
60 if (!isset($this->settings['dateFormat'])) {
61 $this->settings['dateFormat'] = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] ?: 'd-m-Y';
62 }
63 if (!isset($this->settings['timeFormat'])) {
64 $this->settings['timeFormat'] = $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'];
65 }
66 // Static format needed for date picker (flatpickr), see BackendController::generateJavascript() and #91606
67 $this->settings['dateTimeFormat'] = ($GLOBALS['TYPO3_CONF_VARS']['SYS']['USdateFormat'] ? 'h:m m-d-Y' : 'h:m d-m-Y');
68 $constraintConfiguration = $this->arguments->getArgument('constraint')->getPropertyMappingConfiguration();
69 $constraintConfiguration->allowAllProperties();
70 $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
71 $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/GlobalEventHandler');
72 $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/DateTimePicker');
73 $pageRenderer->loadRequireJsModule('TYPO3/CMS/Belog/BackendLog');
74 }
75
76 /**
77 * Show general information and the installed modules
78 *
79 * @param Constraint|null $constraint
80 * @param int|null $pageId
81 * @param string $layout
82 * @param string $operation
83 * @return ResponseInterface
84 */
85 public function listAction(Constraint $constraint = null, int $pageId = null, string $layout = 'Default', string $operation = ''): ResponseInterface
86 {
87 if ($operation === 'reset-filters') {
88 $constraint = new Constraint();
89 } elseif ($constraint === null) {
90 $constraint = $this->getConstraintFromBeUserData();
91 }
92 $this->persistConstraintInBeUserData($constraint);
93 $constraint->setPageId($pageId);
94 $this->resetConstraintsOnMemoryExhaustionError();
95 $this->setStartAndEndTimeFromTimeSelector($constraint);
96 $this->forceWorkspaceSelectionIfInWorkspace($constraint);
97 $logEntries = $this->logEntryRepository->findByConstraint($constraint);
98 $groupedLogEntries = $this->groupLogEntriesDay($logEntries);
99 $this->view->assignMultiple([
100 'pageId' => $pageId,
101 'layout' => $layout,
102 'groupedLogEntries' => $groupedLogEntries,
103 'constraint' => $constraint,
104 'userGroups' => $this->createUserAndGroupListForSelectOptions(),
105 'workspaces' => $this->createWorkspaceListForSelectOptions(),
106 'pageDepths' => $this->createPageDepthOptions(),
107 'channels' => $this->logEntryRepository->getUsedChannels(),
108 'channel' => $constraint->getChannel(),
109 'levels' => $this->logEntryRepository->getUsedLevels(),
110 'level' => $constraint->getLevel(),
111 ]);
112
113 if ($layout === 'Default') {
114 $moduleTemplate = $this->moduleTemplateFactory->create($this->request);
115 $moduleTemplate->setTitle(LocalizationUtility::translate('LLL:EXT:belog/Resources/Private/Language/locallang_mod.xlf:mlang_tabs_tab'));
116 $moduleTemplate->setContent($this->view->render());
117 return $this->htmlResponse($moduleTemplate->renderContent());
118 }
119 return $this->htmlResponse();
120 }
121
122 /**
123 * Delete all log entries that share the same message with the log entry given
124 * in $errorUid
125 *
126 * @param int $errorUid
127 */
128 public function deleteMessageAction(int $errorUid): ResponseInterface
129 {
130 /** @var \TYPO3\CMS\Belog\Domain\Model\LogEntry $logEntry */
131 $logEntry = $this->logEntryRepository->findByUid($errorUid);
132 if (!$logEntry) {
133 $this->addFlashMessage(LocalizationUtility::translate('actions.delete.noRowFound', 'belog') ?? '', '', AbstractMessage::WARNING);
134 return new ForwardResponse('list');
135 }
136 $numberOfDeletedRows = $this->logEntryRepository->deleteByMessageDetails($logEntry);
137 $this->addFlashMessage(sprintf(LocalizationUtility::translate('actions.delete.message', 'belog') ?? '', $numberOfDeletedRows));
138 return new ForwardResponse('list');
139 }
140
141 /**
142 * Get module states (the constraint object) from user data
143 *
144 * @return Constraint
145 */
146 protected function getConstraintFromBeUserData()
147 {
148 $serializedConstraint = $GLOBALS['BE_USER']->getModuleData(static::class);
149 $constraint = null;
150 if (is_string($serializedConstraint) && !empty($serializedConstraint)) {
151 $constraint = @unserialize($serializedConstraint, ['allowed_classes' => [Constraint::class, \DateTime::class]]);
152 }
153 return $constraint ?: GeneralUtility::makeInstance(Constraint::class);
154 }
155
156 /**
157 * Save current constraint object in be user settings (uC)
158 *
159 * @param Constraint $constraint
160 */
161 protected function persistConstraintInBeUserData(Constraint $constraint)
162 {
163 $GLOBALS['BE_USER']->pushModuleData(static::class, serialize($constraint));
164 }
165
166 /**
167 * In case the script execution fails, because the user requested too many results
168 * (memory exhaustion in php), reset the constraints in be user settings, so
169 * the belog can be accessed again in the next call.
170 */
171 protected function resetConstraintsOnMemoryExhaustionError()
172 {
173 $reservedMemory = new \SplFixedArray(187500); // 3M
174 register_shutdown_function(function () use (&$reservedMemory): void {
175 $reservedMemory = null; // free the reserved memory
176 $error = error_get_last();
177 if (str_contains($error['message'] ?? '', 'Allowed memory size of')) {
178 $constraint = GeneralUtility::makeInstance(Constraint::class);
179 $this->persistConstraintInBeUserData($constraint);
180 }
181 });
182 }
183
184 /**
185 * Create a sorted array for day from the query result of the sys log repository.
186 *
187 * pid is always -1 to render a flat list.
188 * '12345' is a sub array to split entries by day, number is first second of day
189 *
190 * [pid][dayTimestamp][items]
191 *
192 * @param QueryResultInterface $logEntries
193 * @return array
194 */
195 protected function groupLogEntriesDay(QueryResultInterface $logEntries): array
196 {
197 $targetStructure = [];
198 /** @var LogEntry $entry */
199 foreach ($logEntries as $entry) {
200 $pid = -1;
201 // Create array if it is not defined yet
202 if (!is_array($targetStructure[$pid] ?? false)) {
203 $targetStructure[-1] = [];
204 }
205 // Get day timestamp of log entry and create sub array if needed
206 $timestampDay = strtotime(strftime('%d.%m.%Y', $entry->getTstamp()) ?: '');
207 if (!is_array($targetStructure[$pid][$timestampDay] ?? false)) {
208 $targetStructure[$pid][$timestampDay] = [];
209 }
210 // Add row
211 $targetStructure[$pid][$timestampDay][] = $entry;
212 }
213 ksort($targetStructure);
214 return $targetStructure;
215 }
216
217 /**
218 * Create options for the user / group drop down.
219 * This is not moved to a repository by intention to not mix up this 'meta' data
220 * with real repository work
221 *
222 * @return array Key is the option name, value its label
223 */
224 protected function createUserAndGroupListForSelectOptions()
225 {
226 $userGroupArray = [];
227 // Two meta entries: 'all' and 'self'
228 $userGroupArray[0] = LocalizationUtility::translate('allUsers', 'Belog');
229 $userGroupArray[-1] = LocalizationUtility::translate('self', 'Belog');
230 // List of groups, key is gr-'uid'
231 $groups = BackendUtility::getGroupNames();
232 foreach ($groups as $group) {
233 $userGroupArray['gr-' . $group['uid']] = LocalizationUtility::translate('group', 'Belog') . ' ' . $group['title'];
234 }
235 // List of users, key is us-'uid'
236 $users = BackendUtility::getUserNames();
237 foreach ($users as $user) {
238 $userGroupArray['us-' . $user['uid']] = LocalizationUtility::translate('user', 'Belog') . ' ' . $user['username'];
239 }
240 return $userGroupArray;
241 }
242
243 /**
244 * Create options for the workspace selector
245 *
246 * @return array Key is uid of workspace, value its label
247 */
248 protected function createWorkspaceListForSelectOptions()
249 {
250 if (!ExtensionManagementUtility::isLoaded('workspaces')) {
251 return [];
252 }
253 $workspaceArray = [];
254 // Two meta entries: 'all' and 'live'
255 $workspaceArray[-99] = LocalizationUtility::translate('any', 'Belog');
256 $workspaceArray[0] = LocalizationUtility::translate('live', 'Belog');
257 $workspaces = $this->workspaceRepository->findAll();
258 /** @var \TYPO3\CMS\Belog\Domain\Model\Workspace $workspace */
259 foreach ($workspaces as $workspace) {
260 $workspaceArray[$workspace->getUid()] = $workspace->getUid() . ': ' . $workspace->getTitle();
261 }
262 return $workspaceArray;
263 }
264
265 /**
266 * If the user is in a workspace different than LIVE,
267 * we force to show only log entries from the selected workspace,
268 * and the workspace selector is not shown.
269 *
270 * @param Constraint $constraint
271 */
272 protected function forceWorkspaceSelectionIfInWorkspace(Constraint $constraint)
273 {
274 if (!ExtensionManagementUtility::isLoaded('workspaces')) {
275 $this->view->assign('showWorkspaceSelector', false);
276 } elseif ($GLOBALS['BE_USER']->workspace !== 0) {
277 $constraint->setWorkspaceUid($GLOBALS['BE_USER']->workspace);
278 $this->view->assign('showWorkspaceSelector', false);
279 } else {
280 $this->view->assign('showWorkspaceSelector', true);
281 }
282 }
283
284 /**
285 * Create options for the 'depth of page levels' selector.
286 * This is shown if the module is displayed in page -> info
287 *
288 * @return array Key is depth identifier (1 = One level), value the localized select option label
289 */
290 protected function createPageDepthOptions()
291 {
292 $options = [
293 0 => LocalizationUtility::translate('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_0', 'lang'),
294 1 => LocalizationUtility::translate('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_1', 'lang'),
295 2 => LocalizationUtility::translate('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_2', 'lang'),
296 3 => LocalizationUtility::translate('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_3', 'lang'),
297 4 => LocalizationUtility::translate('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_4', 'lang'),
298 999 => LocalizationUtility::translate('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_infi', 'lang'),
299 ];
300 return $options;
301 }
302
303 /**
304 * Calculate the start- and end timestamp
305 *
306 * @param Constraint $constraint
307 */
308 protected function setStartAndEndTimeFromTimeSelector(Constraint $constraint)
309 {
310 $startTime = $constraint->getManualDateStart() ? $constraint->getManualDateStart()->getTimestamp() : 0;
311 $endTime = $constraint->getManualDateStop() ? $constraint->getManualDateStop()->getTimestamp() : 0;
312 if ($endTime <= $startTime) {
313 $endTime = $GLOBALS['EXEC_TIME'];
314 }
315 $constraint->setStartTimestamp($startTime);
316 $constraint->setEndTimestamp($endTime);
317 }
318 }