settings['dateFormat'])) { $this->settings['dateFormat'] = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] ?: 'd-m-Y'; } if (!isset($this->settings['timeFormat'])) { $this->settings['timeFormat'] = $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm']; } // Static format needed for date picker (flatpickr), see BackendController::generateJavascript() and #91606 $this->settings['dateTimeFormat'] = 'H:i d-m-Y'; $constraintConfiguration = $this->arguments->getArgument('constraint')->getPropertyMappingConfiguration(); $constraintConfiguration->allowAllProperties(); $constraintConfiguration->forProperty('manualDateStart')->setTypeConverterOption(DateTimeConverter::class, DateTimeConverter::CONFIGURATION_DATE_FORMAT, DateTimeFormat::ISO8601_LOCALTIME); $constraintConfiguration->forProperty('manualDateStop')->setTypeConverterOption(DateTimeConverter::class, DateTimeConverter::CONFIGURATION_DATE_FORMAT, DateTimeFormat::ISO8601_LOCALTIME); } /** * Show general information and the installed modules */ public function listAction(?Constraint $constraint = null, string $operation = ''): ResponseInterface { if ($operation === 'reset-filters') { $constraint = new Constraint(); } elseif ($constraint === null) { $constraint = $this->getConstraintFromBeUserData(); } $access = true; $pageId = $constraint->getPageId(); $permsClause = $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW); if ($pageId === 0 || (BackendUtility::readPageAccess($pageId, $permsClause) ?: []) === []) { if (!$this->getBackendUser()->isAdmin()) { // User does not have access to selected site $access = false; } if ($pageId === 0) { // In case no page is selected, set depth to 0 to display only "global" logs $constraint->setDepth(0); } } $this->persistConstraintInBeUserData($constraint); $this->resetConstraintsOnMemoryExhaustionError(); $this->setStartAndEndTimeFromTimeSelector($constraint); $showWorkspaceSelector = $this->forceWorkspaceSelectionIfInWorkspace($constraint); $viewVariables = [ 'access' => $access, 'settings' => $this->settings, 'pageId' => $pageId, 'constraint' => $constraint, 'userGroups' => $this->createUserAndGroupListForSelectOptions(), 'selectableNumberOfLogEntries' => $this->createSelectableNumberOfLogEntriesOptions(), 'workspaces' => $this->createWorkspaceListForSelectOptions(), 'pageDepths' => $this->createPageDepthOptions(), 'channels' => $this->logEntryRepository->getUsedChannels(), 'channel' => $constraint->getChannel(), 'levels' => $this->logEntryRepository->getUsedLevels(), 'level' => $constraint->getLevel(), 'showWorkspaceSelector' => $showWorkspaceSelector, ]; if ($access) { // Only fetch log entries if user has access $logEntries = $this->logEntryRepository->findByConstraint($constraint); $groupedLogEntries = $this->groupLogEntriesDay($logEntries); $viewVariables['groupedLogEntries'] = $groupedLogEntries; } $view = $this->moduleTemplateFactory->create($this->request); $view->getDocHeaderComponent()->setShortcutContext( 'system_log', $this->getLanguageService()->translate('title', 'belog.module') ); return $view->setFlashMessageQueue($this->getFlashMessageQueue()) ->setTitle(LocalizationUtility::translate('title', 'belog.module')) ->assignMultiple($viewVariables) ->renderResponse('BackendLog/List'); } public function initializeDeleteMessageAction(): void { $this->assertAllowedHttpMethod($this->request, 'POST'); } /** * Delete all log entries that share the same message with the log entry given * in $errorUid */ public function deleteMessageAction(int $errorUid): ResponseInterface { $logEntry = $this->logEntryRepository->findByUid($errorUid); if (!$logEntry) { $this->addFlashMessage(LocalizationUtility::translate('actions.delete.noRowFound', 'belog') ?? '', '', ContextualFeedbackSeverity::WARNING); return $this->redirect('list'); } $numberOfDeletedRows = $this->logEntryRepository->deleteByMessageDetails($logEntry); $this->addFlashMessage(sprintf(LocalizationUtility::translate('actions.delete.message', 'belog') ?? '', $numberOfDeletedRows)); BackendUtility::setUpdateSignal('updateSystemInformationMenu'); return $this->redirect('list'); } /** * Get module states (the constraint object) from user data */ protected function getConstraintFromBeUserData(): Constraint { $serializedConstraint = $this->request->getAttribute('moduleData')->get('constraint'); $constraint = null; if (is_string($serializedConstraint) && !empty($serializedConstraint)) { $constraint = @unserialize($serializedConstraint, ['allowed_classes' => [Constraint::class, \DateTime::class]]); } return $constraint ?: GeneralUtility::makeInstance(Constraint::class); } /** * Save current constraint object in be user settings (uC) */ protected function persistConstraintInBeUserData(Constraint $constraint): void { $moduleData = $this->request->getAttribute('moduleData'); $moduleData->set('constraint', serialize($constraint)); $this->getBackendUser()->pushModuleData($moduleData->getModuleIdentifier(), $moduleData->toArray()); } /** * In case the script execution fails, because the user requested too many results * (memory exhaustion in php), reset the constraints in be user settings, so * the belog can be accessed again in the next call. */ protected function resetConstraintsOnMemoryExhaustionError(): void { $reservedMemory = new \SplFixedArray(187500); // 3M register_shutdown_function(function () use (&$reservedMemory): void { $reservedMemory = null; // free the reserved memory $error = error_get_last(); if (str_contains($error['message'] ?? '', 'Allowed memory size of')) { $constraint = GeneralUtility::makeInstance(Constraint::class); $this->persistConstraintInBeUserData($constraint); } }); } /** * Create a sorted array for day from the query result of the sys log repository. * * pid is always -1 to render a flat list. * '12345' is a sub array to split entries by day, number is first second of day * * [pid][dayTimestamp][items] * * @param array $logEntries */ protected function groupLogEntriesDay(array $logEntries): array { $targetStructure = []; foreach ($logEntries as $entry) { $pid = -1; // Create array if it is not defined yet if (!is_array($targetStructure[$pid] ?? false)) { $targetStructure[-1] = []; } // Get day timestamp of log entry and create sub array if needed $timestampDay = strtotime($entry->getTstamp()->format('Y-m-d')); if (!is_array($targetStructure[$pid][$timestampDay] ?? false)) { $targetStructure[$pid][$timestampDay] = []; } // Add row $targetStructure[$pid][$timestampDay][] = $entry; } ksort($targetStructure); return $targetStructure; } /** * Create options for the user / group drop down. * This is not moved to a repository by intention to not mix up this 'meta' data * with real repository work. */ protected function createUserAndGroupListForSelectOptions(): array { $items = []; foreach (BackendUtility::getGroupNames() as $group) { $items['groups']['gr-' . $group['uid']] = BackendUtility::getRecordTitle('be_groups', $group); } foreach (BackendUtility::getUserNames() as $user) { $items['users']['us-' . $user['uid']] = BackendUtility::getRecordTitle('be_users', $user); } return $items; } /** * Options for the "max" drop down */ protected function createSelectableNumberOfLogEntriesOptions(): array { return [ 50 => 50, 100 => 100, 200 => 200, 500 => 500, 1000 => 1000, 1000000 => LocalizationUtility::translate('any', 'Belog'), ]; } /** * Create options for the workspace selector * * @return array Key is uid of workspace, value its label */ protected function createWorkspaceListForSelectOptions(): array { if (!ExtensionManagementUtility::isLoaded('workspaces')) { return []; } $workspaceArray = []; // Two meta entries: 'all' and 'live' $workspaceArray[-99] = LocalizationUtility::translate('any', 'Belog'); $workspaceArray[0] = LocalizationUtility::translate('live', 'Belog'); $resultSet = $this->connectionPool->getQueryBuilderForTable('sys_workspace') ->select('uid', 'title') ->from('sys_workspace') ->executeQuery(); while ($row = $resultSet->fetchAssociative()) { $workspaceArray[$row['uid']] = $row['uid'] . ': ' . $row['title']; } return $workspaceArray; } /** * If the user is in a workspace different than LIVE, * we force to show only log entries from the selected workspace, * and the workspace selector is not shown. */ protected function forceWorkspaceSelectionIfInWorkspace(Constraint $constraint): bool { if (!ExtensionManagementUtility::isLoaded('workspaces')) { return false; } if ($this->getBackendUser()->workspace !== 0) { $constraint->setWorkspaceUid($this->getBackendUser()->workspace); return false; } return true; } /** * Create options for the 'depth of page levels' selector. * * @return array Key is depth identifier (1 = One level), value the localized select option label */ protected function createPageDepthOptions(): array { return [ 0 => LocalizationUtility::translate('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_0'), 1 => LocalizationUtility::translate('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_1'), 2 => LocalizationUtility::translate('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_2'), 3 => LocalizationUtility::translate('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_3'), 4 => LocalizationUtility::translate('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_4'), 999 => LocalizationUtility::translate('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_infi'), ]; } /** * Calculate the start- and end timestamp */ protected function setStartAndEndTimeFromTimeSelector(Constraint $constraint): void { $startTime = $constraint->getManualDateStart() ? $constraint->getManualDateStart()->getTimestamp() : 0; $endTime = $constraint->getManualDateStop() ? $constraint->getManualDateStop()->getTimestamp() : 0; if ($endTime <= $startTime) { $endTime = $GLOBALS['EXEC_TIME']; } $constraint->setStartTimestamp($startTime); $constraint->setEndTimestamp($endTime); } protected function getBackendUser(): BackendUserAuthentication { return $GLOBALS['BE_USER']; } protected function getLanguageService(): LanguageService { return $GLOBALS['LANG']; } }