TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,346 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Belog\Controller;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Belog\Domain\Model\Constraint;
|
||||
use TYPO3\CMS\Belog\Domain\Model\LogEntry;
|
||||
use TYPO3\CMS\Belog\Domain\Repository\LogEntryRepository;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Domain\DateTimeFormat;
|
||||
use TYPO3\CMS\Core\Http\AllowedMethodsTrait;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Type\Bitmask\Permission;
|
||||
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
||||
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
|
||||
use TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter;
|
||||
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
|
||||
|
||||
/**
|
||||
* Show log entries from sys_log
|
||||
*
|
||||
* @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class BackendLogController extends ActionController
|
||||
{
|
||||
use AllowedMethodsTrait;
|
||||
|
||||
public function __construct(
|
||||
protected readonly ModuleTemplateFactory $moduleTemplateFactory,
|
||||
protected readonly LogEntryRepository $logEntryRepository,
|
||||
protected readonly ConnectionPool $connectionPool,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Initialize list action
|
||||
*/
|
||||
public function initializeListAction(): void
|
||||
{
|
||||
if (!isset($this->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<LogEntry> $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'];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user