TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
<?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\Domain\Model;
|
||||
|
||||
use Psr\Log\LogLevel;
|
||||
|
||||
/**
|
||||
* Constraints for log entries
|
||||
* @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class Constraint
|
||||
{
|
||||
/**
|
||||
* Selected user/group; possible values are "gr-<uid>" for a group, "us-<uid>" for a user or -1 for "all users"
|
||||
*/
|
||||
protected string $userOrGroup = '0';
|
||||
|
||||
/**
|
||||
* Number of log rows to show
|
||||
*/
|
||||
protected int $number = 20;
|
||||
|
||||
/**
|
||||
* UID of selected workspace
|
||||
*/
|
||||
protected int $workspaceUid = -99;
|
||||
|
||||
/**
|
||||
* Selected channel
|
||||
*/
|
||||
protected string $channel = '';
|
||||
|
||||
/**
|
||||
* Selected level
|
||||
*/
|
||||
protected string $level = LogLevel::DEBUG;
|
||||
|
||||
/**
|
||||
* Calculated start timestamp
|
||||
*/
|
||||
protected int $startTimestamp = 0;
|
||||
|
||||
/**
|
||||
* Calculated end timestamp
|
||||
*/
|
||||
protected int $endTimestamp = 0;
|
||||
|
||||
/**
|
||||
* Manual date start
|
||||
*/
|
||||
protected ?\DateTime $manualDateStart = null;
|
||||
|
||||
/**
|
||||
* Manual date stop
|
||||
*/
|
||||
protected ?\DateTime $manualDateStop = null;
|
||||
|
||||
/**
|
||||
* Selected page ID in page context
|
||||
*/
|
||||
protected int $pageId = 0;
|
||||
|
||||
/**
|
||||
* Page level depth
|
||||
*/
|
||||
protected int $depth = 0;
|
||||
|
||||
public function setUserOrGroup(string $user): void
|
||||
{
|
||||
$this->userOrGroup = $user;
|
||||
}
|
||||
|
||||
public function getUserOrGroup(): string
|
||||
{
|
||||
return $this->userOrGroup;
|
||||
}
|
||||
|
||||
public function setNumber(int $number): void
|
||||
{
|
||||
$this->number = $number;
|
||||
}
|
||||
|
||||
public function getNumber(): int
|
||||
{
|
||||
return $this->number;
|
||||
}
|
||||
|
||||
public function setWorkspaceUid(int $workspace): void
|
||||
{
|
||||
$this->workspaceUid = $workspace;
|
||||
}
|
||||
|
||||
public function getWorkspaceUid(): int
|
||||
{
|
||||
return $this->workspaceUid;
|
||||
}
|
||||
|
||||
public function setChannel(string $channel): void
|
||||
{
|
||||
$this->channel = $channel;
|
||||
}
|
||||
|
||||
public function getChannel(): string
|
||||
{
|
||||
return $this->channel;
|
||||
}
|
||||
|
||||
public function setLevel(string $level): void
|
||||
{
|
||||
$this->level = $level;
|
||||
}
|
||||
|
||||
public function getLevel(): string
|
||||
{
|
||||
return $this->level;
|
||||
}
|
||||
|
||||
public function setStartTimestamp(int $timestamp): void
|
||||
{
|
||||
$this->startTimestamp = $timestamp;
|
||||
}
|
||||
|
||||
public function getStartTimestamp(): int
|
||||
{
|
||||
return $this->startTimestamp;
|
||||
}
|
||||
|
||||
public function setEndTimestamp(int $timestamp): void
|
||||
{
|
||||
$this->endTimestamp = $timestamp;
|
||||
}
|
||||
|
||||
public function getEndTimestamp(): int
|
||||
{
|
||||
return $this->endTimestamp;
|
||||
}
|
||||
|
||||
public function setPageId(?int $id): void
|
||||
{
|
||||
$this->pageId = $id ?? 0;
|
||||
}
|
||||
|
||||
public function getPageId(): int
|
||||
{
|
||||
return $this->pageId;
|
||||
}
|
||||
|
||||
public function setDepth(int $depth): void
|
||||
{
|
||||
$this->depth = $depth;
|
||||
}
|
||||
|
||||
public function getDepth(): int
|
||||
{
|
||||
return $this->depth;
|
||||
}
|
||||
|
||||
public function setManualDateStart(?\DateTime $manualDateStart = null): void
|
||||
{
|
||||
$this->manualDateStart = $manualDateStart;
|
||||
}
|
||||
|
||||
public function getManualDateStart(): ?\DateTime
|
||||
{
|
||||
return $this->manualDateStart;
|
||||
}
|
||||
|
||||
public function setManualDateStop(?\DateTime $manualDateStop = null): void
|
||||
{
|
||||
$this->manualDateStop = $manualDateStop;
|
||||
}
|
||||
|
||||
public function getManualDateStop(): ?\DateTime
|
||||
{
|
||||
return $this->manualDateStop;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
<?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\Domain\Model;
|
||||
|
||||
use TYPO3\CMS\Core\Log\LogDataTrait;
|
||||
|
||||
/**
|
||||
* A sys log entry
|
||||
* This model is 'complete': All current database properties are in there.
|
||||
*
|
||||
* @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class LogEntry
|
||||
{
|
||||
use LogDataTrait;
|
||||
|
||||
/**
|
||||
* @var int<0, max>
|
||||
*/
|
||||
protected int $uid = 0;
|
||||
|
||||
/**
|
||||
* This is not a relation to BeUser model, since the user does
|
||||
* not always exist, but we want the uid in then anyway.
|
||||
* This case is ugly in extbase, the best way we
|
||||
* have found now is to resolve the username (if it exists) in a
|
||||
* view helper and just use the uid of the be user here.
|
||||
*/
|
||||
protected int $backendUserUid = 0;
|
||||
|
||||
/**
|
||||
* Action ID of the action that happened, for example 3 was a file action
|
||||
*/
|
||||
protected int $action = 0;
|
||||
|
||||
/**
|
||||
* UID of the record the event happened to
|
||||
*/
|
||||
protected int $recordUid = 0;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
protected string $tableName = '';
|
||||
|
||||
/**
|
||||
* PID of the record the event happened to
|
||||
*/
|
||||
protected int $recordPid = 0;
|
||||
|
||||
/**
|
||||
* Error code
|
||||
*/
|
||||
protected int $error = 0;
|
||||
|
||||
/**
|
||||
* This is the log message itself, but possibly with %s substitutions.
|
||||
*/
|
||||
protected string $details = '';
|
||||
|
||||
/**
|
||||
* Timestamp when the log entry was written
|
||||
*/
|
||||
protected \DateTimeInterface $tstamp;
|
||||
|
||||
/**
|
||||
* Type code
|
||||
*/
|
||||
protected int $type = 0;
|
||||
|
||||
/**
|
||||
* Channel name.
|
||||
*/
|
||||
protected string $channel = '';
|
||||
|
||||
/**
|
||||
* Level.
|
||||
*/
|
||||
protected string $level = '';
|
||||
|
||||
/**
|
||||
* IP address of client
|
||||
*/
|
||||
protected string $ip = '';
|
||||
|
||||
/**
|
||||
* Serialized log data. This is a serialized array with substitutions for $this->details.
|
||||
*/
|
||||
protected string $logData = '';
|
||||
|
||||
/**
|
||||
* Event PID
|
||||
*/
|
||||
protected int $eventPid = 0;
|
||||
|
||||
/**
|
||||
* This is only the UID and not the full workspace object for the same reason as in $beUserUid.
|
||||
*/
|
||||
protected int $workspaceUid = 0;
|
||||
|
||||
public function getUid(): int
|
||||
{
|
||||
return $this->uid;
|
||||
}
|
||||
|
||||
public function getBackendUserUid(): int
|
||||
{
|
||||
return $this->backendUserUid;
|
||||
}
|
||||
|
||||
public function getAction(): int
|
||||
{
|
||||
return $this->action;
|
||||
}
|
||||
|
||||
public function getRecordUid(): int
|
||||
{
|
||||
return $this->recordUid;
|
||||
}
|
||||
|
||||
public function getTableName(): string
|
||||
{
|
||||
return $this->tableName;
|
||||
}
|
||||
|
||||
public function getRecordPid(): int
|
||||
{
|
||||
return $this->recordPid;
|
||||
}
|
||||
|
||||
public function setError(int $error): void
|
||||
{
|
||||
$this->error = $error;
|
||||
}
|
||||
|
||||
public function getError(): int
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
public function getErrorIconClass(): string
|
||||
{
|
||||
return match ($this->getError()) {
|
||||
1 => 'status-dialog-warning',
|
||||
2, 3 => 'status-dialog-error',
|
||||
default => 'empty-empty',
|
||||
};
|
||||
}
|
||||
|
||||
public function getDetails(): string
|
||||
{
|
||||
if ($this->type === 255) {
|
||||
return str_replace('###IP###', $this->ip, $this->details);
|
||||
}
|
||||
return $this->details;
|
||||
}
|
||||
|
||||
public function getTstamp(): \DateTimeInterface
|
||||
{
|
||||
return $this->tstamp;
|
||||
}
|
||||
|
||||
public function getType(): int
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function getChannel(): string
|
||||
{
|
||||
return $this->channel;
|
||||
}
|
||||
|
||||
public function getLevel(): string
|
||||
{
|
||||
return $this->level;
|
||||
}
|
||||
|
||||
public function getIp(): string
|
||||
{
|
||||
return $this->ip;
|
||||
}
|
||||
|
||||
public function setLogData(string $logData): void
|
||||
{
|
||||
$this->logData = $logData;
|
||||
}
|
||||
|
||||
public function getLogData(): array
|
||||
{
|
||||
if ($this->logData === '') {
|
||||
return [];
|
||||
}
|
||||
$logData = $this->unserializeLogData($this->logData);
|
||||
return $logData ?? [];
|
||||
}
|
||||
|
||||
public function getLogDataRaw(): string
|
||||
{
|
||||
return $this->logData;
|
||||
}
|
||||
|
||||
public function getEventPid(): int
|
||||
{
|
||||
return $this->eventPid;
|
||||
}
|
||||
|
||||
public function getWorkspaceUid(): int
|
||||
{
|
||||
return $this->workspaceUid;
|
||||
}
|
||||
|
||||
public static function createFromDatabaseRecord(array $row): self
|
||||
{
|
||||
$obj = new self();
|
||||
$obj->uid = $row['uid'] ?? $obj->uid;
|
||||
$obj->tstamp = new \DateTimeImmutable(date('Y-m-d\TH:i:s', $row['tstamp'] ?? 0));
|
||||
$obj->backendUserUid = $row['userid'] ?? $obj->backendUserUid;
|
||||
$obj->action = $row['action'] ?? $obj->action;
|
||||
$obj->recordUid = $row['recuid'] ?? $obj->recordUid;
|
||||
$obj->tableName = $row['tablename'] ?? $obj->tableName;
|
||||
$obj->recordPid = $row['recpid'] ?? $obj->recordPid;
|
||||
$obj->error = $row['error'] ?? $obj->error;
|
||||
$obj->type = $row['type'] ?? $obj->type;
|
||||
$obj->details = $row['details'] ?? $obj->details;
|
||||
$obj->ip = $row['IP'] ?? $obj->ip;
|
||||
$obj->logData = $row['log_data'] ?? $obj->logData;
|
||||
$obj->eventPid = $row['event_pid'] ?? $obj->eventPid;
|
||||
$obj->workspaceUid = $row['workspace'] ?? $obj->workspaceUid;
|
||||
$obj->channel = $row['channel'] ?? $obj->channel;
|
||||
$obj->level = $row['level'] ?? $obj->level;
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
<?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\Domain\Repository;
|
||||
|
||||
use Psr\Log\LogLevel;
|
||||
use TYPO3\CMS\Backend\Tree\Repository\PageTreeRepository;
|
||||
use TYPO3\CMS\Belog\Domain\Model\Constraint;
|
||||
use TYPO3\CMS\Belog\Domain\Model\LogEntry;
|
||||
use TYPO3\CMS\Core\Authentication\GroupResolver;
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
|
||||
use TYPO3\CMS\Core\Log\LogLevel as Typo3LogLevel;
|
||||
use TYPO3\CMS\Core\Type\Bitmask\Permission;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Sys log entry repository
|
||||
* @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
readonly class LogEntryRepository
|
||||
{
|
||||
public function __construct(
|
||||
private GroupResolver $groupResolver,
|
||||
private ConnectionPool $connectionPool,
|
||||
) {}
|
||||
|
||||
public function findByUid($uid): ?LogEntry
|
||||
{
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('sys_log');
|
||||
$row = $queryBuilder
|
||||
->select('*')
|
||||
->from('sys_log')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($uid, Connection::PARAM_INT))
|
||||
)
|
||||
->fetchAssociative();
|
||||
return $row ? LogEntry::createFromDatabaseRecord($row) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all log entries that match all given constraints.
|
||||
*
|
||||
* @return array<LogEntry>
|
||||
*/
|
||||
public function findByConstraint(Constraint $constraint): array
|
||||
{
|
||||
$query = $this->connectionPool->getQueryBuilderForTable('sys_log');
|
||||
$query->select('*')
|
||||
->from('sys_log')
|
||||
->orderBy('uid', 'DESC');
|
||||
$queryConstraints = $this->createQueryConstraints($query, $constraint);
|
||||
$stmt = $query
|
||||
->where(...$queryConstraints)
|
||||
->setMaxResults($constraint->getNumber())
|
||||
->executeQuery();
|
||||
$result = [];
|
||||
while ($row = $stmt->fetchAssociative()) {
|
||||
$result[] = LogEntry::createFromDatabaseRecord($row);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an array of query constraints from constraint object
|
||||
*/
|
||||
protected function createQueryConstraints(QueryBuilder $query, Constraint $constraint): array
|
||||
{
|
||||
// User / group handling
|
||||
$queryConstraints = $this->addUsersAndGroupsToQueryConstraints($constraint, $query);
|
||||
// Workspace
|
||||
if ($constraint->getWorkspaceUid() !== -99) {
|
||||
$queryConstraints[] = $query->expr()->eq('workspace', $query->createNamedParameter($constraint->getWorkspaceUid(), Connection::PARAM_INT));
|
||||
}
|
||||
// Channel
|
||||
if ($channel = $constraint->getChannel()) {
|
||||
$queryConstraints[] = $query->expr()->eq('channel', $query->createNamedParameter($channel));
|
||||
}
|
||||
// Level
|
||||
if ($level = $constraint->getLevel()) {
|
||||
$queryConstraints[] = $query->expr()->in('level', $query->createNamedParameter(Typo3LogLevel::atLeast($level), Connection::PARAM_STR_ARRAY));
|
||||
}
|
||||
// Start / endtime handling: The timestamp calculation was already done
|
||||
// in the controller, since we need those calculated values in the view as well.
|
||||
$queryConstraints[] = $query->expr()->gte('tstamp', $query->createNamedParameter($constraint->getStartTimestamp(), Connection::PARAM_INT));
|
||||
$queryConstraints[] = $query->expr()->lt('tstamp', $query->createNamedParameter($constraint->getEndTimestamp(), Connection::PARAM_INT));
|
||||
// Page and level constraint if in page context
|
||||
$constraint = $this->addPageTreeConstraintsToQuery($constraint, $query);
|
||||
if ($constraint) {
|
||||
$queryConstraints[] = $constraint;
|
||||
}
|
||||
return $queryConstraints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds constraints for the page(s) to the query; this could be one single page or a whole subtree beneath a given
|
||||
* page.
|
||||
*/
|
||||
protected function addPageTreeConstraintsToQuery(Constraint $constraint, QueryBuilder $query): ?string
|
||||
{
|
||||
$pageIds = [];
|
||||
// Check if we should get a whole tree of pages and not only a single page
|
||||
if ($constraint->getDepth() > 0) {
|
||||
$repository = GeneralUtility::makeInstance(PageTreeRepository::class);
|
||||
$repository->setAdditionalWhereClause($GLOBALS['BE_USER']->getPagePermsClause(Permission::PAGE_SHOW));
|
||||
$pages = $repository->getFlattenedPages([$constraint->getPageId()], $constraint->getDepth());
|
||||
foreach ($pages as $page) {
|
||||
$pageIds[] = (int)$page['uid'];
|
||||
}
|
||||
}
|
||||
if (!empty($constraint->getPageId())) {
|
||||
$pageIds[] = $constraint->getPageId();
|
||||
}
|
||||
if (!empty($pageIds)) {
|
||||
return $query->expr()->in('event_pid', $query->createNamedParameter($pageIds, Connection::PARAM_INT_ARRAY));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds users and groups to the query constraints.
|
||||
*/
|
||||
protected function addUsersAndGroupsToQueryConstraints(Constraint $constraint, QueryBuilder $query): array
|
||||
{
|
||||
$userOrGroup = $constraint->getUserOrGroup();
|
||||
if ($userOrGroup === '') {
|
||||
return [];
|
||||
}
|
||||
$queryConstraints = [];
|
||||
// Constraint for a group
|
||||
if (str_starts_with($userOrGroup, 'gr-')) {
|
||||
$groupId = (int)substr($userOrGroup, 3);
|
||||
$userIds = $this->groupResolver->findAllUsersInGroups([$groupId], 'be_groups', 'be_users');
|
||||
if (!empty($userIds)) {
|
||||
$userIds = array_column($userIds, 'uid');
|
||||
$userIds = array_map(intval(...), $userIds);
|
||||
$queryConstraints[] = $query->expr()->in('userid', $query->createNamedParameter($userIds, Connection::PARAM_INT_ARRAY));
|
||||
} else {
|
||||
// If there are no group members -> use -1 as constraint to not find anything
|
||||
$queryConstraints[] = $query->expr()->eq('userid', $query->createNamedParameter(-1, Connection::PARAM_INT));
|
||||
}
|
||||
} elseif (str_starts_with($userOrGroup, 'us-')) {
|
||||
$queryConstraints[] = $query->expr()->in('userid', $query->createNamedParameter((int)substr($userOrGroup, 3), Connection::PARAM_INT));
|
||||
} elseif ($userOrGroup === '-1') {
|
||||
$queryConstraints[] = $query->expr()->in('userid', $query->createNamedParameter((int)$GLOBALS['BE_USER']->user['uid'], Connection::PARAM_INT));
|
||||
}
|
||||
return $queryConstraints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all messages which have the same message details
|
||||
*/
|
||||
public function deleteByMessageDetails(LogEntry $logEntry): int
|
||||
{
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('sys_log');
|
||||
return $queryBuilder->delete('sys_log')
|
||||
->where($queryBuilder->expr()->eq('details', $queryBuilder->createNamedParameter($logEntry->getDetails())))
|
||||
->executeStatement();
|
||||
}
|
||||
|
||||
public function getUsedChannels(): array
|
||||
{
|
||||
$channels = $this->connectionPool->getQueryBuilderForTable('sys_log')
|
||||
->select('channel')
|
||||
->distinct()
|
||||
->from('sys_log')
|
||||
->orderBy('channel')
|
||||
->executeQuery()
|
||||
->fetchFirstColumn();
|
||||
return array_combine($channels, $channels);
|
||||
}
|
||||
|
||||
public function getUsedLevels(): array
|
||||
{
|
||||
static $allLevels = [
|
||||
LogLevel::EMERGENCY,
|
||||
LogLevel::ALERT,
|
||||
LogLevel::CRITICAL,
|
||||
LogLevel::ERROR,
|
||||
LogLevel::WARNING,
|
||||
LogLevel::NOTICE,
|
||||
LogLevel::INFO,
|
||||
LogLevel::DEBUG,
|
||||
];
|
||||
$levels = $this->connectionPool->getQueryBuilderForTable('sys_log')
|
||||
->select('level')
|
||||
->distinct()
|
||||
->from('sys_log')
|
||||
->executeQuery()
|
||||
->fetchFirstColumn();
|
||||
$levelsUsed = array_intersect($allLevels, $levels);
|
||||
return array_combine($levelsUsed, $levelsUsed);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user