TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
<?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\Reports\Controller;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Backend\Attribute\AsController;
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
|
||||
use TYPO3\CMS\Backend\Template\Components\ComponentFactory;
|
||||
use TYPO3\CMS\Backend\Template\Enum\ModuleLayout;
|
||||
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Reports\Service\ContentStatisticsService;
|
||||
|
||||
/**
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
#[AsController]
|
||||
final readonly class ContentStatisticsController
|
||||
{
|
||||
public function __construct(
|
||||
private ModuleTemplateFactory $moduleTemplateFactory,
|
||||
private ContentStatisticsService $contentStatisticsService,
|
||||
private UriBuilder $uriBuilder,
|
||||
private ComponentFactory $componentFactory,
|
||||
) {}
|
||||
|
||||
public function handleRequest(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$view = $this->moduleTemplateFactory->create($request);
|
||||
$view->setLayout(ModuleLayout::NORMAL);
|
||||
$view->setTitle(
|
||||
$languageService->sL('reports.messages:mlang_tabs_tab'),
|
||||
$languageService->sL('reports.messages:contentStatistics.title')
|
||||
);
|
||||
$view->makeDocHeaderModuleMenu();
|
||||
|
||||
$buttonBar = $view->getDocHeaderComponent()->getButtonBar();
|
||||
|
||||
$cType = $request->getQueryParams()['ctype'] ?? '';
|
||||
if ($this->contentStatisticsService->isValidCtype($cType)) {
|
||||
$backButton = $this->componentFactory->createBackButton((string)$this->uriBuilder->buildUriFromRoute('system_reports_contentstatistics'));
|
||||
$buttonBar->addButton($backButton);
|
||||
|
||||
$shortcutButton = $this->componentFactory->createShortcutButton()
|
||||
->setRouteIdentifier('system_reports_contentstatistics')
|
||||
->setArguments(['ctype' => $cType])
|
||||
->setDisplayName($languageService->sL('reports.messages:contentStatistics.title'));
|
||||
$buttonBar->addButton($shortcutButton, ButtonBar::BUTTON_POSITION_RIGHT);
|
||||
|
||||
return $view->assignMultiple(
|
||||
$this->contentStatisticsService->collectStatisticForCtype($cType, (int)($request->getQueryParams()['page'] ?? 1)),
|
||||
)->renderResponse('ContentStatisticsDetail');
|
||||
}
|
||||
|
||||
$shortcutButton = $this->componentFactory->createShortcutButton()
|
||||
->setRouteIdentifier('system_reports_contentstatistics')
|
||||
->setDisplayName($languageService->sL('reports.messages:contentStatistics.title'));
|
||||
$buttonBar->addButton($shortcutButton, ButtonBar::BUTTON_POSITION_RIGHT);
|
||||
|
||||
return $view->assignMultiple([
|
||||
'data' => $this->contentStatisticsService->collectStatistic(),
|
||||
])->renderResponse('ContentStatistics');
|
||||
}
|
||||
|
||||
private function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?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\Reports\Controller;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Backend\Attribute\AsController;
|
||||
use TYPO3\CMS\Backend\Template\Enum\ModuleLayout;
|
||||
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Reports\Service\RecordStatisticsService;
|
||||
|
||||
/**
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
#[AsController]
|
||||
final readonly class RecordStatisticsController
|
||||
{
|
||||
public function __construct(
|
||||
private ModuleTemplateFactory $moduleTemplateFactory,
|
||||
private RecordStatisticsService $recordStatisticsService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Main action - displays database record statistics
|
||||
*/
|
||||
public function handleRequest(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$view = $this->moduleTemplateFactory->create($request);
|
||||
$view->setLayout(ModuleLayout::NORMAL);
|
||||
$view->setTitle(
|
||||
$languageService->translate('title', 'reports.modules.overview'),
|
||||
$languageService->translate('title', 'reports.modules.statistics')
|
||||
);
|
||||
$view->makeDocHeaderModuleMenu();
|
||||
$view->getDocHeaderComponent()->setShortcutContext(
|
||||
'system_reports_statistics',
|
||||
$languageService->translate('title', 'reports.modules.statistics')
|
||||
);
|
||||
|
||||
return $view->assignMultiple([
|
||||
'pages' => $this->recordStatisticsService->collectPageStatistics(),
|
||||
'doktypes' => $this->recordStatisticsService->collectDoktypeStatistics(),
|
||||
'tables' => $this->recordStatisticsService->collectTableStatistics(),
|
||||
])->renderResponse('RecordStatistics');
|
||||
}
|
||||
|
||||
private function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?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\Reports\Controller;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Backend\Attribute\AsController;
|
||||
use TYPO3\CMS\Backend\Template\Enum\ModuleLayout;
|
||||
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
||||
use TYPO3\CMS\Reports\Service\StatusService;
|
||||
|
||||
/**
|
||||
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
#[AsController]
|
||||
final readonly class StatusReportController
|
||||
{
|
||||
public function __construct(
|
||||
private ModuleTemplateFactory $moduleTemplateFactory,
|
||||
private StatusService $statusService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Main action - displays the system status report
|
||||
*/
|
||||
public function handleRequest(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$statusCollection = $this->statusService->getSystemStatus($request);
|
||||
$this->statusService->collectAndStoreSystemStatus($request);
|
||||
|
||||
// Apply sorting to collection and the providers
|
||||
$statusCollection = $this->statusService->sortStatusProviders($statusCollection);
|
||||
foreach ($statusCollection as &$statuses) {
|
||||
$statuses = $this->statusService->sortStatuses($statuses);
|
||||
}
|
||||
unset($statuses);
|
||||
|
||||
$languageService = $this->getLanguageService();
|
||||
$view = $this->moduleTemplateFactory->create($request);
|
||||
$view->setLayout(ModuleLayout::NORMAL);
|
||||
$view->setTitle(
|
||||
$languageService->translate('title', 'reports.modules.overview'),
|
||||
$languageService->translate('title', 'reports.modules.status')
|
||||
);
|
||||
$view->makeDocHeaderModuleMenu();
|
||||
$view->getDocHeaderComponent()->setShortcutContext(
|
||||
'system_reports_status',
|
||||
$languageService->translate('title', 'reports.modules.status')
|
||||
);
|
||||
|
||||
return $view->assignMultiple([
|
||||
'statusCollection' => $statusCollection,
|
||||
'severityIconMapping' => [
|
||||
ContextualFeedbackSeverity::NOTICE->value => 'actions-info',
|
||||
ContextualFeedbackSeverity::INFO->value => 'actions-info',
|
||||
ContextualFeedbackSeverity::OK->value => 'actions-check',
|
||||
ContextualFeedbackSeverity::WARNING->value => 'actions-exclamation',
|
||||
ContextualFeedbackSeverity::ERROR->value => 'actions-exclamation',
|
||||
],
|
||||
])->renderResponse('StatusReport');
|
||||
}
|
||||
|
||||
private function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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\Reports;
|
||||
|
||||
/**
|
||||
* Interface for classes which provide a status report entry.
|
||||
*/
|
||||
interface ExtendedStatusProviderInterface
|
||||
{
|
||||
/**
|
||||
* Returns the detailed status of an extension or (sub)system
|
||||
*
|
||||
* @return \TYPO3\CMS\Reports\Status[]
|
||||
*/
|
||||
public function getDetailedStatus();
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
<?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\Reports\Integrity;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
|
||||
use TYPO3\CMS\Core\Schema\TcaSchema;
|
||||
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* This class holds functions used by the TYPO3 backend for the RecordStatistics Service
|
||||
* of the database.
|
||||
*
|
||||
* @internal not part of TYPO3 Core API
|
||||
*/
|
||||
#[Autoconfigure(public: true)]
|
||||
class DatabaseIntegrityCheck
|
||||
{
|
||||
/**
|
||||
* @var array Will hold id/rec pairs from genTree()
|
||||
*/
|
||||
protected array $pageIdArray = [];
|
||||
|
||||
/**
|
||||
* @var array Will hold id/rec pairs from genTree() that are not default language
|
||||
*/
|
||||
protected array $pageTranslatedPageIDArray = [];
|
||||
|
||||
protected array $recStats = [
|
||||
'all_valid' => [],
|
||||
'published_versions' => [],
|
||||
'deleted' => [],
|
||||
];
|
||||
|
||||
protected array $lRecords = [];
|
||||
|
||||
public function __construct(
|
||||
protected readonly TcaSchemaFactory $tcaSchemaFactory,
|
||||
protected readonly ConnectionPool $connectionPool,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Generates a list of Page-uid's that corresponds to the tables in the tree.
|
||||
* This list should ideally include all records in the pages-table.
|
||||
*
|
||||
* @param int $theID a pid (page-record id) from which to start making the tree
|
||||
* @param bool $versions Internal variable, don't set from outside!
|
||||
*/
|
||||
public function genTree(int $theID, bool $versions = false): void
|
||||
{
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('pages');
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$queryBuilder->select('uid', 'title', 'doktype', 'deleted', 'hidden', 'sys_language_uid')
|
||||
->from('pages')
|
||||
->orderBy('sorting');
|
||||
if ($versions) {
|
||||
$queryBuilder->addSelect('t3ver_wsid');
|
||||
$queryBuilder->where(
|
||||
$queryBuilder->expr()->eq('t3ver_oid', $queryBuilder->createNamedParameter($theID, Connection::PARAM_INT))
|
||||
);
|
||||
} else {
|
||||
$queryBuilder->where(
|
||||
$queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($theID, Connection::PARAM_INT))
|
||||
);
|
||||
}
|
||||
$result = $queryBuilder->executeQuery();
|
||||
// Traverse the records selected
|
||||
while ($row = $result->fetchAssociative()) {
|
||||
$newID = $row['uid'];
|
||||
// Register various data for this item:
|
||||
if ($row['language_tag'] === 0) {
|
||||
$this->pageIdArray[$newID] = $row;
|
||||
} else {
|
||||
$this->pageTranslatedPageIDArray[$newID] = $row;
|
||||
}
|
||||
$this->recStats['all_valid']['pages'][$newID] = $newID;
|
||||
if ($row['deleted']) {
|
||||
$this->recStats['deleted']['pages'][$newID] = $newID;
|
||||
}
|
||||
|
||||
if (!isset($this->recStats['hidden'])) {
|
||||
$this->recStats['hidden'] = 0;
|
||||
}
|
||||
|
||||
if ($row['hidden']) {
|
||||
$this->recStats['hidden']++;
|
||||
}
|
||||
|
||||
$this->recStats['doktype'][$row['doktype']] ??= 0;
|
||||
$this->recStats['doktype'][$row['doktype']]++;
|
||||
// Add sub pages:
|
||||
$this->genTree($newID);
|
||||
// If versions are included in the tree, add those now:
|
||||
$this->genTree($newID, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills $this->lRecords with the records from all tc-tables that are not attached to a PID in the pid-list.
|
||||
*
|
||||
* @param array $pageIds list of pid's (page-record uid's). This list is probably made by genTree()
|
||||
*/
|
||||
public function lostRecords(array $pageIds): array
|
||||
{
|
||||
/** @var TcaSchema $schema */
|
||||
foreach ($this->tcaSchemaFactory->all() as $table => $schema) {
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable($table);
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$queryResult = $queryBuilder
|
||||
->select('*')
|
||||
->from($table)
|
||||
->where(
|
||||
$queryBuilder->expr()->notIn(
|
||||
'pid',
|
||||
$queryBuilder->createNamedParameter($pageIds, Connection::PARAM_INT_ARRAY)
|
||||
)
|
||||
)
|
||||
->executeQuery();
|
||||
while ($row = $queryResult->fetchAssociative()) {
|
||||
$recordId = (int)$row['uid'];
|
||||
$this->lRecords[$table][$recordId] = [
|
||||
'uid' => $recordId,
|
||||
'pid' => $row['pid'],
|
||||
'title' => strip_tags(BackendUtility::getRecordTitle($table, $row)),
|
||||
];
|
||||
}
|
||||
}
|
||||
return $this->lRecords;
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts records from $GLOBALS['TCA']-tables that ARE attached to an existing page.
|
||||
*
|
||||
* @param array $pageIds list of pid's (page-record uid's). This list is probably made by genTree()
|
||||
* @return array an array with the number of records from all $GLOBALS['TCA']-tables that are attached to a PID in the pid-list.
|
||||
*/
|
||||
public function countRecords(array $pageIds): array
|
||||
{
|
||||
$list = [];
|
||||
$list_n = [];
|
||||
/** @var TcaSchema $schema */
|
||||
foreach ($this->tcaSchemaFactory->all() as $table => $schema) {
|
||||
$pageIdsForTable = $pageIds;
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable($table);
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$count = $queryBuilder->count('uid')
|
||||
->from($table)
|
||||
->where(
|
||||
$queryBuilder->expr()->in(
|
||||
'pid',
|
||||
$queryBuilder->createNamedParameter($pageIds, Connection::PARAM_INT_ARRAY)
|
||||
)
|
||||
)
|
||||
->executeQuery()
|
||||
->fetchOne();
|
||||
if ($count) {
|
||||
$list[$table] = $count;
|
||||
}
|
||||
|
||||
// same query excluding all deleted records
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable($table);
|
||||
$queryBuilder->getRestrictions()
|
||||
->removeAll()
|
||||
->add(GeneralUtility::makeInstance(DeletedRestriction::class));
|
||||
$count = $queryBuilder->count('uid')
|
||||
->from($table)
|
||||
->where(
|
||||
$queryBuilder->expr()->in(
|
||||
'pid',
|
||||
$queryBuilder->createNamedParameter($pageIdsForTable, Connection::PARAM_INT_ARRAY)
|
||||
)
|
||||
)
|
||||
->executeQuery()
|
||||
->fetchOne();
|
||||
if ($count) {
|
||||
$list_n[$table] = $count;
|
||||
}
|
||||
}
|
||||
return ['all' => $list, 'non_deleted' => $list_n];
|
||||
}
|
||||
|
||||
public function getPageIdArray(): array
|
||||
{
|
||||
return $this->pageIdArray;
|
||||
}
|
||||
|
||||
public function getPageTranslatedPageIDArray(): array
|
||||
{
|
||||
return $this->pageTranslatedPageIDArray;
|
||||
}
|
||||
|
||||
public function getRecStats(): array
|
||||
{
|
||||
return $this->recStats;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?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\Reports\Registry;
|
||||
|
||||
use TYPO3\CMS\Reports\StatusProviderInterface;
|
||||
|
||||
/**
|
||||
* Registry for status providers. The registry receives all services, tagged with "reports.status".
|
||||
* The tagging of status providers is automatically done based on the implemented StatusProviderInterface.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class StatusRegistry
|
||||
{
|
||||
/**
|
||||
* @var StatusProviderInterface[]
|
||||
*/
|
||||
private array $providers = [];
|
||||
|
||||
/**
|
||||
* @param iterable<StatusProviderInterface> $providers
|
||||
*/
|
||||
public function __construct(iterable $providers)
|
||||
{
|
||||
foreach ($providers as $item) {
|
||||
$this->providers[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered status providers
|
||||
*
|
||||
* @return StatusProviderInterface[]
|
||||
*/
|
||||
public function getProviders(): array
|
||||
{
|
||||
return $this->providers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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\Reports\Report\Status;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MariaDBPlatform as DoctrineMariaDBPlatform;
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform as DoctrineMySQLPlatform;
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Cache\Backend\MemcachedBackend;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Registry;
|
||||
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
use TYPO3\CMS\Reports\Status;
|
||||
use TYPO3\CMS\Reports\Status as ReportStatus;
|
||||
use TYPO3\CMS\Reports\StatusProviderInterface;
|
||||
|
||||
/**
|
||||
* Performs some checks about the install tool protection status
|
||||
*/
|
||||
readonly class ConfigurationStatus implements StatusProviderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private UriBuilder $uriBuilder,
|
||||
private Registry $registry,
|
||||
private ConnectionPool $connectionPool,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Determines the Install Tool's status, mainly concerning its protection.
|
||||
*
|
||||
* @return Status[]
|
||||
*/
|
||||
public function getStatus(): array
|
||||
{
|
||||
$statuses = [
|
||||
'emptyReferenceIndex' => $this->getReferenceIndexStatus(),
|
||||
];
|
||||
if ($this->isMemcachedUsed()) {
|
||||
$statuses['memcachedConnection'] = $this->getMemcachedConnectionStatus();
|
||||
}
|
||||
if (!Environment::isWindows()) {
|
||||
$statuses['createdFilesWorldWritable'] = $this->getCreatedFilesWorldWritableStatus();
|
||||
$statuses['createdDirectoriesWorldWritable'] = $this->getCreatedDirectoriesWorldWritableStatus();
|
||||
}
|
||||
if ($this->isMysqlUsed()) {
|
||||
$statuses['mysqlDatabaseUsesUtf8'] = $this->getMysqlDatabaseUtf8Status();
|
||||
}
|
||||
return $statuses;
|
||||
}
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return 'configuration';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if sys_refindex is empty.
|
||||
*
|
||||
* @return \TYPO3\CMS\Reports\Status An object representing whether the reference index is empty or not
|
||||
*/
|
||||
protected function getReferenceIndexStatus(): ReportStatus
|
||||
{
|
||||
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_ok');
|
||||
$message = '';
|
||||
$severity = ContextualFeedbackSeverity::OK;
|
||||
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('sys_refindex');
|
||||
$count = $queryBuilder
|
||||
->count('*')
|
||||
->from('sys_refindex')
|
||||
->executeQuery()
|
||||
->fetchOne();
|
||||
|
||||
$lastRefIndexUpdate = $this->registry->get('core', 'sys_refindex_lastUpdate');
|
||||
|
||||
if (!$count && $lastRefIndexUpdate) {
|
||||
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_empty');
|
||||
$severity = ContextualFeedbackSeverity::WARNING;
|
||||
$url = (string)$this->uriBuilder->buildUriFromRoute('system_maintenance');
|
||||
$message = sprintf($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.backend_reference_index'), '<a href="' . htmlspecialchars($url) . '">', '</a>', BackendUtility::datetime($lastRefIndexUpdate));
|
||||
}
|
||||
return new ReportStatus($this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_referenceIndex'), $value, $message, $severity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether memcached is configured, if that's the case we assume it's also used.
|
||||
*
|
||||
* @return bool TRUE if memcached is used, FALSE otherwise.
|
||||
*/
|
||||
protected function isMemcachedUsed(): bool
|
||||
{
|
||||
$memcachedUsed = false;
|
||||
$memcachedServers = $this->getConfiguredMemcachedServers();
|
||||
if (!empty($memcachedServers)) {
|
||||
$memcachedUsed = true;
|
||||
}
|
||||
return $memcachedUsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configured memcached server connections.
|
||||
*
|
||||
* @return array An array of configured memcached server connections.
|
||||
*/
|
||||
protected function getConfiguredMemcachedServers(): array
|
||||
{
|
||||
$configurations = $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations'] ?? [];
|
||||
$memcachedServers = [];
|
||||
foreach ($configurations as $table => $conf) {
|
||||
if (is_array($conf)) {
|
||||
foreach ($conf as $value) {
|
||||
if ($value === MemcachedBackend::class && is_array($configurations[$table]['options']['servers'])) {
|
||||
$memcachedServers = $configurations[$table]['options']['servers'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $memcachedServers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether TYPO3 can connect to the configured memcached servers.
|
||||
*
|
||||
* @return \TYPO3\CMS\Reports\Status An object representing whether TYPO3 can connect to the configured memcached servers
|
||||
*/
|
||||
protected function getMemcachedConnectionStatus(): ReportStatus
|
||||
{
|
||||
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_ok');
|
||||
$message = '';
|
||||
$severity = ContextualFeedbackSeverity::OK;
|
||||
$failedConnections = [];
|
||||
$defaultMemcachedPort = ini_get('memcache.default_port');
|
||||
$defaultMemcachedPort = MathUtility::canBeInterpretedAsInteger($defaultMemcachedPort) ? (int)$defaultMemcachedPort : 11211;
|
||||
$memcachedServers = $this->getConfiguredMemcachedServers();
|
||||
if (function_exists('memcache_connect') && $memcachedServers !== []) {
|
||||
foreach ($memcachedServers as $testServer) {
|
||||
$configuredServer = $testServer;
|
||||
if (str_starts_with($testServer, 'unix://')) {
|
||||
$host = $testServer;
|
||||
$port = 0;
|
||||
} else {
|
||||
if (str_starts_with($testServer, 'tcp://')) {
|
||||
$testServer = substr($testServer, 6);
|
||||
}
|
||||
if (str_contains($testServer, ':')) {
|
||||
[$host, $port] = explode(':', $testServer, 2);
|
||||
$port = (int)$port;
|
||||
} else {
|
||||
$host = $testServer;
|
||||
$port = $defaultMemcachedPort;
|
||||
}
|
||||
}
|
||||
$memcachedConnection = @memcache_connect($host, $port);
|
||||
if ($memcachedConnection != null) {
|
||||
@memcache_close($memcachedConnection);
|
||||
} else {
|
||||
$failedConnections[] = $configuredServer;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($failedConnections)) {
|
||||
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_connectionFailed');
|
||||
$severity = ContextualFeedbackSeverity::WARNING;
|
||||
$message = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.memcache_not_usable') . '<br /><br /><ul><li>' . implode('</li><li>', $failedConnections) . '</li></ul>';
|
||||
}
|
||||
return new ReportStatus($this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_memcachedConfiguration'), $value, $message, $severity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning, if fileCreateMask has write bit for 'others' set.
|
||||
*
|
||||
* @return \TYPO3\CMS\Reports\Status The writable status for 'others'
|
||||
*/
|
||||
protected function getCreatedFilesWorldWritableStatus(): ReportStatus
|
||||
{
|
||||
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_ok');
|
||||
$message = '';
|
||||
$severity = ContextualFeedbackSeverity::OK;
|
||||
if ((int)$GLOBALS['TYPO3_CONF_VARS']['SYS']['fileCreateMask'] % 10 & 2) {
|
||||
$value = $GLOBALS['TYPO3_CONF_VARS']['SYS']['fileCreateMask'];
|
||||
$severity = ContextualFeedbackSeverity::WARNING;
|
||||
$message = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_CreatedFilePermissions.writable');
|
||||
}
|
||||
return new ReportStatus($this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_CreatedFilePermissions'), $value, $message, $severity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning, if folderCreateMask has write bit for 'others' set.
|
||||
*
|
||||
* @return \TYPO3\CMS\Reports\Status The writable status for 'others'
|
||||
*/
|
||||
protected function getCreatedDirectoriesWorldWritableStatus(): ReportStatus
|
||||
{
|
||||
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_ok');
|
||||
$message = '';
|
||||
$severity = ContextualFeedbackSeverity::OK;
|
||||
if ((int)$GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'] % 10 & 2) {
|
||||
$value = $GLOBALS['TYPO3_CONF_VARS']['SYS']['folderCreateMask'];
|
||||
$severity = ContextualFeedbackSeverity::WARNING;
|
||||
$message = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_CreatedDirectoryPermissions.writable');
|
||||
}
|
||||
return new ReportStatus($this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_CreatedDirectoryPermissions'), $value, $message, $severity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the default connection is a MySQL compatible database instance.
|
||||
*/
|
||||
protected function isMysqlUsed(): bool
|
||||
{
|
||||
$platform = $this->connectionPool
|
||||
->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME)
|
||||
->getDatabasePlatform();
|
||||
|
||||
return $platform instanceof DoctrineMariaDBPlatform || $platform instanceof DoctrineMySQLPlatform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the character set of the default database and reports an error if it is not utf-8.
|
||||
*/
|
||||
protected function getMysqlDatabaseUtf8Status(): ReportStatus
|
||||
{
|
||||
$collationConstraint = null;
|
||||
$charset = '';
|
||||
$connection = $this->connectionPool
|
||||
->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
|
||||
$connectionParams = $connection->getParams();
|
||||
$queryBuilder = $connection->createQueryBuilder();
|
||||
$defaultDatabaseCharset = (string)$queryBuilder->select('DEFAULT_CHARACTER_SET_NAME')
|
||||
->from('information_schema.SCHEMATA')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'SCHEMA_NAME',
|
||||
$queryBuilder->createNamedParameter($connection->getDatabase())
|
||||
)
|
||||
)
|
||||
->setMaxResults(1)
|
||||
->executeQuery()
|
||||
->fetchOne();
|
||||
|
||||
$severity = ContextualFeedbackSeverity::OK;
|
||||
$statusValue = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_ok');
|
||||
// also allow utf8mb3 and utf8mb4
|
||||
if (!str_starts_with($defaultDatabaseCharset, 'utf8')) {
|
||||
// If the default character set is e.g. latin1, BUT all tables in the system are UTF-8,
|
||||
// we assume that TYPO3 has the correct charset for adding tables, and everything is fine
|
||||
$queryBuilder = $connection->createQueryBuilder();
|
||||
$nonUtf8TableCollationsFound = $queryBuilder->select('table_collation')
|
||||
->from('information_schema.tables')
|
||||
->where(
|
||||
$queryBuilder->expr()->and(
|
||||
$queryBuilder->expr()->eq('table_schema', $queryBuilder->quote((string)$connection->getDatabase())),
|
||||
$queryBuilder->expr()->notLike('table_collation', $queryBuilder->quote('utf8%'))
|
||||
)
|
||||
)
|
||||
->setMaxResults(1)
|
||||
->executeQuery();
|
||||
|
||||
if ($nonUtf8TableCollationsFound->rowCount() > 0) {
|
||||
$message = sprintf($this->getLanguageService()
|
||||
->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_MysqlDatabaseCharacterSet_Unsupported'), $defaultDatabaseCharset);
|
||||
$severity = ContextualFeedbackSeverity::ERROR;
|
||||
$statusValue = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_wrongValue');
|
||||
} else {
|
||||
$message = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_MysqlDatabaseCharacterSet_Info');
|
||||
$severity = ContextualFeedbackSeverity::INFO;
|
||||
$statusValue = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_info');
|
||||
}
|
||||
} elseif (isset($connectionParams['defaultTableOptions'])) {
|
||||
$message = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_MysqlDatabaseCharacterSet_Ok');
|
||||
|
||||
$tableOptions = $connectionParams['defaultTableOptions'];
|
||||
if (isset($tableOptions['collation'])) {
|
||||
$collationConstraint = $queryBuilder->expr()->neq('table_collation', $queryBuilder->quote($tableOptions['collation']));
|
||||
$charset = $tableOptions['collation'];
|
||||
} elseif (isset($tableOptions['charset'])) {
|
||||
$collationConstraint = $queryBuilder->expr()->notLike('table_collation', $queryBuilder->quote($tableOptions['charset'] . '%'));
|
||||
$charset = $tableOptions['charset'];
|
||||
}
|
||||
|
||||
if (isset($collationConstraint)) {
|
||||
$queryBuilder = $connection->createQueryBuilder();
|
||||
$wrongCollationTablesFound = $queryBuilder->select('table_collation')
|
||||
->from('information_schema.tables')
|
||||
->where(
|
||||
$queryBuilder->expr()->and(
|
||||
$queryBuilder->expr()->eq('table_schema', $queryBuilder->quote($connection->getDatabase())),
|
||||
$collationConstraint
|
||||
)
|
||||
)
|
||||
->setMaxResults(1)
|
||||
->executeQuery();
|
||||
|
||||
if ($wrongCollationTablesFound->rowCount() > 0) {
|
||||
$message = sprintf($this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_MysqlDatabaseCharacterSet_MixedCollations'), $charset);
|
||||
$severity = ContextualFeedbackSeverity::ERROR;
|
||||
$statusValue = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_checkFailed');
|
||||
} else {
|
||||
if (isset($tableOptions['collation'])) {
|
||||
$collationConstraint = $queryBuilder->expr()->neq('collation_name', $queryBuilder->quote($tableOptions['collation']));
|
||||
} elseif (isset($tableOptions['charset'])) {
|
||||
$collationConstraint = $queryBuilder->expr()->notLike('collation_name', $queryBuilder->quote($tableOptions['charset'] . '%'));
|
||||
}
|
||||
|
||||
$queryBuilder = $connection->createQueryBuilder();
|
||||
$wrongCollationColumnsFound = $queryBuilder->select('collation_name')
|
||||
->from('information_schema.columns')
|
||||
->where(
|
||||
$queryBuilder->expr()->and(
|
||||
$queryBuilder->expr()->eq('table_schema', $queryBuilder->quote($connection->getDatabase())),
|
||||
$collationConstraint
|
||||
)
|
||||
)
|
||||
->setMaxResults(1)
|
||||
->executeQuery();
|
||||
|
||||
if ($wrongCollationColumnsFound->rowCount() > 0) {
|
||||
$message = sprintf($this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_MysqlDatabaseCharacterSet_MixedCollations'), $charset);
|
||||
$severity = ContextualFeedbackSeverity::ERROR;
|
||||
$statusValue = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_checkFailed');
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$message = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_MysqlDatabaseCharacterSet_Ok');
|
||||
}
|
||||
|
||||
return new ReportStatus(
|
||||
$this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_MysqlDatabaseCharacterSet'),
|
||||
$statusValue,
|
||||
$message,
|
||||
$severity
|
||||
);
|
||||
}
|
||||
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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\Reports\Report\Status;
|
||||
|
||||
use TYPO3\CMS\Core\Database\Connection;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Resource\File;
|
||||
use TYPO3\CMS\Core\Resource\FolderInterface;
|
||||
use TYPO3\CMS\Core\Resource\ResourceStorage;
|
||||
use TYPO3\CMS\Core\Resource\Service\ResourceConsistencyService;
|
||||
use TYPO3\CMS\Core\Resource\StorageRepository;
|
||||
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
||||
use TYPO3\CMS\Core\Validation\ResultException;
|
||||
use TYPO3\CMS\Core\Validation\ResultRenderingTrait;
|
||||
use TYPO3\CMS\Reports\Status;
|
||||
use TYPO3\CMS\Reports\Status as ReportStatus;
|
||||
use TYPO3\CMS\Reports\StatusProviderInterface;
|
||||
|
||||
/**
|
||||
* Performs several checks about the FAL status
|
||||
*/
|
||||
readonly class FalStatus implements StatusProviderInterface
|
||||
{
|
||||
use ResultRenderingTrait;
|
||||
|
||||
public function __construct(
|
||||
private ResourceConsistencyService $resourceConsistencyService,
|
||||
private ConnectionPool $connectionPool,
|
||||
private StorageRepository $storageRepository,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Determines the status of the FAL index.
|
||||
*
|
||||
* @return Status[] List of statuses
|
||||
*/
|
||||
public function getStatus(): array
|
||||
{
|
||||
return [
|
||||
'MissingFiles' => $this->getMissingFilesStatus(),
|
||||
'ConsistencyCheck' => $this->getConsistencyCheckStatus(),
|
||||
];
|
||||
}
|
||||
|
||||
public function getDetailedStatus(): array
|
||||
{
|
||||
return [
|
||||
'ConsistencyCheck' => $this->getConsistencyCheckStatus(),
|
||||
];
|
||||
}
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return 'fal';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are files marked as missed.
|
||||
*
|
||||
* @return \TYPO3\CMS\Reports\Status An object representing whether there are files marked as missed or not
|
||||
*/
|
||||
protected function getMissingFilesStatus()
|
||||
{
|
||||
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_none');
|
||||
$count = 0;
|
||||
$maxFilesToShow = 100;
|
||||
$message = '';
|
||||
$severity = ContextualFeedbackSeverity::OK;
|
||||
|
||||
$storages = $this->getBrowsableStorages();
|
||||
if (!empty($storages)) {
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('sys_file');
|
||||
$count = $queryBuilder
|
||||
->count('*')
|
||||
->from('sys_file')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'missing',
|
||||
$queryBuilder->createNamedParameter(1, Connection::PARAM_INT)
|
||||
),
|
||||
$queryBuilder->expr()->in(
|
||||
'storage',
|
||||
$queryBuilder->createNamedParameter(array_keys($storages), Connection::PARAM_INT_ARRAY)
|
||||
)
|
||||
)
|
||||
->executeQuery()
|
||||
->fetchOne();
|
||||
}
|
||||
|
||||
if ($count) {
|
||||
$value = sprintf($this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_missingFilesCount'), $count);
|
||||
$severity = ContextualFeedbackSeverity::WARNING;
|
||||
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('sys_file');
|
||||
$files = $queryBuilder
|
||||
->select('identifier', 'storage')
|
||||
->from('sys_file')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq(
|
||||
'missing',
|
||||
$queryBuilder->createNamedParameter(1, Connection::PARAM_INT)
|
||||
),
|
||||
$queryBuilder->expr()->in(
|
||||
'storage',
|
||||
$queryBuilder->createNamedParameter(array_keys($storages), Connection::PARAM_INT_ARRAY)
|
||||
)
|
||||
)
|
||||
->setMaxResults($maxFilesToShow)
|
||||
->executeQuery()
|
||||
->fetchAllAssociative();
|
||||
|
||||
$message = '<p>' . $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_missingFilesMessage') . '</p>';
|
||||
foreach ($files as $file) {
|
||||
$message .= $storages[$file['storage']]->getName() . ' ' . $file['identifier'] . '<br />';
|
||||
}
|
||||
|
||||
if ($count > $maxFilesToShow) {
|
||||
$message .= '...<br />';
|
||||
}
|
||||
}
|
||||
|
||||
return new ReportStatus($this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_missingFiles'), $value, $message, $severity);
|
||||
}
|
||||
|
||||
protected function getConsistencyCheckStatus(): ReportStatus
|
||||
{
|
||||
// @todo for performance reasons, consider using this only in CLI context as `ExtendedStatusProviderInterface`
|
||||
|
||||
$storages = $this->getBrowsableStorages();
|
||||
$inconsistenciesMessage = '';
|
||||
foreach ($storages as $storage) {
|
||||
$inconsistencies = $this->checkFolderConsistency($storage->getRootLevelFolder());
|
||||
if ($inconsistencies !== []) {
|
||||
$inconsistenciesMessage .= sprintf(
|
||||
'<h5>%s</h5>%s',
|
||||
htmlspecialchars(sprintf(
|
||||
'Storage "%s" (id:%d)',
|
||||
$storage->getName(),
|
||||
$storage->getUid()
|
||||
)),
|
||||
$this->wrapInHtmlUnorderedList($inconsistencies)
|
||||
);
|
||||
}
|
||||
}
|
||||
if ($inconsistenciesMessage === '') {
|
||||
return new ReportStatus(
|
||||
'Consistency check',
|
||||
'No inconsistencies found in these storages',
|
||||
// make sure we have a list of strings (0… index) with `array_values` to ensure correct rendering
|
||||
$this->wrapInHtmlUnorderedList(array_values(array_map(
|
||||
static fn(ResourceStorage $storage): string => sprintf(
|
||||
'%s (id: %d)',
|
||||
$storage->getName(),
|
||||
$storage->getUid()
|
||||
),
|
||||
$storages,
|
||||
))),
|
||||
ContextualFeedbackSeverity::OK,
|
||||
);
|
||||
}
|
||||
return new ReportStatus(
|
||||
'Consistency Status',
|
||||
'Inconsistent files have been found',
|
||||
$inconsistenciesMessage,
|
||||
ContextualFeedbackSeverity::ERROR,
|
||||
);
|
||||
}
|
||||
|
||||
private function checkFolderConsistency(FolderInterface $folder): array
|
||||
{
|
||||
$inconsistencies = [];
|
||||
foreach ($folder->getFiles() as $file) {
|
||||
if (!$file instanceof File) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
$this->resourceConsistencyService->validate($file->getStorage(), $file);
|
||||
} catch (ResultException $exception) {
|
||||
$inconsistencies[$file->getCombinedIdentifier()] = $this->compileResultMessages(
|
||||
$exception->messages,
|
||||
$this->getLanguageService()
|
||||
);
|
||||
}
|
||||
}
|
||||
foreach ($folder->getSubfolders() as $subFolder) {
|
||||
$inconsistencies = [...$inconsistencies, ...$this->checkFolderConsistency($subFolder)];
|
||||
}
|
||||
return $inconsistencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string>|array<string, list<string>> $items
|
||||
*/
|
||||
protected function wrapInHtmlUnorderedList(array $items): string
|
||||
{
|
||||
if (array_is_list($items)) {
|
||||
return sprintf(
|
||||
'<ul>%s</ul>',
|
||||
implode('', array_map(
|
||||
static fn(string $item): string => '<li>' . htmlspecialchars($item) . '</li>',
|
||||
$items
|
||||
))
|
||||
);
|
||||
}
|
||||
return sprintf(
|
||||
'<ul>%s</ul>',
|
||||
implode('', array_map(
|
||||
fn(string $key, array $values): string => sprintf(
|
||||
'<li>%s%s</li>',
|
||||
htmlspecialchars($key),
|
||||
$this->wrapInHtmlUnorderedList($values)
|
||||
),
|
||||
array_keys($items),
|
||||
array_values($items)
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter available storages that are actually browsable
|
||||
*
|
||||
* @return array<int,ResourceStorage>
|
||||
*/
|
||||
protected function getBrowsableStorages(): array
|
||||
{
|
||||
$storages = [];
|
||||
foreach ($this->storageRepository->findAll() as $storageObject) {
|
||||
if ($storageObject->isBrowsable()) {
|
||||
$storages[$storageObject->getUid()] = $storageObject;
|
||||
}
|
||||
}
|
||||
|
||||
return $storages;
|
||||
}
|
||||
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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\Reports\Report\Status;
|
||||
|
||||
use TYPO3\CMS\Core\Imaging\GraphicalFunctions;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Reports\Status;
|
||||
use TYPO3\CMS\Reports\StatusProviderInterface;
|
||||
|
||||
/**
|
||||
* Performs several checks about processing images
|
||||
*/
|
||||
class ImageProcessingStatus implements StatusProviderInterface
|
||||
{
|
||||
/**
|
||||
* Determines the status of the FAL index.
|
||||
*
|
||||
* @return Status[] List of statuses
|
||||
*/
|
||||
public function getStatus(): array
|
||||
{
|
||||
return [
|
||||
'webp' => $this->getMissingFilesStatus('webp'),
|
||||
'avif' => $this->getMissingFilesStatus('avif'),
|
||||
];
|
||||
}
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return 'imageprocessing';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if webp | avif support is activated, but webp | avif is not enabled in ImageMagick / GraphicsMagick
|
||||
*/
|
||||
protected function getMissingFilesStatus(string $fileFormat = 'webp'): Status
|
||||
{
|
||||
switch ($fileFormat) {
|
||||
case 'avif':
|
||||
$messageNotConfigured = 'LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_imageprocessing_avif_not_configured';
|
||||
$messageAvailable = 'LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_imageprocessing_avif_available';
|
||||
$messageNotAvailable = 'LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_imageprocessing_avif_not_available';
|
||||
$messageAvailableAndConfigured = 'LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_imageprocessing_avif_available_and_configured';
|
||||
$messageTitle = 'LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_imageprocessing_avif';
|
||||
break;
|
||||
|
||||
case 'webp':
|
||||
default:
|
||||
$messageNotConfigured = 'LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_imageprocessing_webp_not_configured';
|
||||
$messageAvailable = 'LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_imageprocessing_webp_available';
|
||||
$messageNotAvailable = 'LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_imageprocessing_webp_not_available';
|
||||
$messageAvailableAndConfigured = 'LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_imageprocessing_webp_available_and_configured';
|
||||
$messageTitle = 'LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_imageprocessing_webp';
|
||||
break;
|
||||
}
|
||||
|
||||
$imageProcessing = GeneralUtility::makeInstance(GraphicalFunctions::class);
|
||||
if (!$imageProcessing->isProcessingEnabled()) {
|
||||
$severity = ContextualFeedbackSeverity::INFO;
|
||||
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_disabled');
|
||||
$message = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_imageprocessing_disabled');
|
||||
// ImageMagick / GraphicsMagick is not enabled, all good
|
||||
} elseif (!in_array($fileFormat, $imageProcessing->getImageFileExt(), true)) {
|
||||
// webp | avif is not enabled in TYPO3's Configuration
|
||||
$severity = ContextualFeedbackSeverity::INFO;
|
||||
$message = $this->getLanguageService()->sL($messageNotConfigured);
|
||||
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_disabled');
|
||||
// But ImageMagick can do it, maybe it could be activated
|
||||
if ($imageProcessing->isConvertSupportAvailableForFormat(strtoupper($fileFormat))) {
|
||||
$message = $this->getLanguageService()->sL($messageAvailable);
|
||||
}
|
||||
} elseif (!$imageProcessing->isConvertSupportAvailableForFormat(strtoupper($fileFormat))) {
|
||||
// webp | avif is configured to be available, but ImageMagick/GraphicsMagick does not support this.
|
||||
$severity = ContextualFeedbackSeverity::WARNING;
|
||||
$message = $this->getLanguageService()->sL($messageNotAvailable);
|
||||
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_enabled');
|
||||
} else {
|
||||
// webp | avif is configured to be available, and ImageMagick/GraphicsMagick supports this.
|
||||
$severity = ContextualFeedbackSeverity::OK;
|
||||
$message = $this->getLanguageService()->sL($messageAvailableAndConfigured);
|
||||
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_enabled');
|
||||
}
|
||||
|
||||
return new Status(
|
||||
$this->getLanguageService()->sL($messageTitle),
|
||||
$value,
|
||||
$message,
|
||||
$severity
|
||||
);
|
||||
}
|
||||
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
<?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\Reports\Report\Status;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Middleware\VerifyHostHeader;
|
||||
use TYPO3\CMS\Core\Resource\Security\FileNameValidator;
|
||||
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Reports\RequestAwareStatusProviderInterface;
|
||||
use TYPO3\CMS\Reports\Status as ReportStatus;
|
||||
|
||||
/**
|
||||
* Performs several checks about the system's health
|
||||
*/
|
||||
class SecurityStatus implements RequestAwareStatusProviderInterface
|
||||
{
|
||||
/**
|
||||
* Determines the security of this TYPO3 installation
|
||||
*
|
||||
* @param ServerRequestInterface|null $request
|
||||
* @return ReportStatus[] List of statuses
|
||||
*/
|
||||
public function getStatus(?ServerRequestInterface $request = null): array
|
||||
{
|
||||
$statuses = [
|
||||
'trustedHostsPattern' => $this->getTrustedHostsPatternStatus(),
|
||||
'fileDenyPattern' => $this->getFileDenyPatternStatus(),
|
||||
'htaccessUpload' => $this->getHtaccessUploadStatus(),
|
||||
'exceptionHandler' => $this->getExceptionHandlerStatus(),
|
||||
'exportedFiles' => $this->getExportedFilesStatus(),
|
||||
];
|
||||
|
||||
if ($request !== null) {
|
||||
$statuses['encryptedConnectionStatus'] = $this->getEncryptedConnectionStatus($request);
|
||||
$lockSslStatus = $this->getLockSslStatus($request);
|
||||
if ($lockSslStatus) {
|
||||
$statuses['getLockSslStatus'] = $lockSslStatus;
|
||||
}
|
||||
}
|
||||
|
||||
return $statuses;
|
||||
}
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return 'security';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current connection is encrypted (HTTPS)
|
||||
*/
|
||||
protected function getEncryptedConnectionStatus(ServerRequestInterface $request): ReportStatus
|
||||
{
|
||||
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_ok');
|
||||
$message = '';
|
||||
$severity = ContextualFeedbackSeverity::OK;
|
||||
|
||||
$normalizedParams = $request->getAttribute('normalizedParams');
|
||||
|
||||
if (!$normalizedParams->isHttps()) {
|
||||
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_insecure');
|
||||
$severity = ContextualFeedbackSeverity::WARNING;
|
||||
$message = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_encryptedConnectionStatus_insecure');
|
||||
}
|
||||
|
||||
return new ReportStatus($this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_encryptedConnectionStatus'), $value, $message, $severity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ReportStatus
|
||||
*/
|
||||
protected function getLockSslStatus(ServerRequestInterface $request): ?ReportStatus
|
||||
{
|
||||
$normalizedParams = $request->getAttribute('normalizedParams');
|
||||
|
||||
if ($normalizedParams->isHttps()) {
|
||||
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_ok');
|
||||
$message = '';
|
||||
$severity = ContextualFeedbackSeverity::OK;
|
||||
|
||||
if (!$GLOBALS['TYPO3_CONF_VARS']['BE']['lockSSL']) {
|
||||
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_insecure');
|
||||
$message = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_lockSslStatus_insecure');
|
||||
$severity = ContextualFeedbackSeverity::WARNING;
|
||||
}
|
||||
|
||||
return new ReportStatus($this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_lockSslStatus'), $value, $message, $severity);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the trusted hosts pattern check is disabled.
|
||||
*
|
||||
* @return ReportStatus An object representing whether the check is disabled
|
||||
*/
|
||||
protected function getTrustedHostsPatternStatus(): ReportStatus
|
||||
{
|
||||
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_ok');
|
||||
$message = '';
|
||||
$severity = ContextualFeedbackSeverity::OK;
|
||||
|
||||
if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] === VerifyHostHeader::ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL) {
|
||||
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_insecure');
|
||||
$severity = ContextualFeedbackSeverity::ERROR;
|
||||
$message = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.install_trustedhosts');
|
||||
}
|
||||
|
||||
return new ReportStatus($this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_trustedHostsPattern'), $value, $message, $severity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if fileDenyPattern was changed which is dangerous on Apache
|
||||
*
|
||||
* @return ReportStatus An object representing whether the file deny pattern has changed
|
||||
*/
|
||||
protected function getFileDenyPatternStatus(): ReportStatus
|
||||
{
|
||||
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_ok');
|
||||
$message = '';
|
||||
$severity = ContextualFeedbackSeverity::OK;
|
||||
|
||||
$fileAccessCheck = GeneralUtility::makeInstance(FileNameValidator::class);
|
||||
if ($fileAccessCheck->missingImportantPatterns()) {
|
||||
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_insecure');
|
||||
$severity = ContextualFeedbackSeverity::ERROR;
|
||||
$message = sprintf(
|
||||
$this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.file_deny_pattern_partsNotPresent'),
|
||||
'<br /><pre>' . htmlspecialchars($fileAccessCheck::DEFAULT_FILE_DENY_PATTERN) . '</pre><br />'
|
||||
);
|
||||
}
|
||||
|
||||
return new ReportStatus($this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_fileDenyPattern'), $value, $message, $severity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if fileDenyPattern allows to upload .htaccess files which is
|
||||
* dangerous on Apache.
|
||||
*
|
||||
* @return ReportStatus An object representing whether it's possible to upload .htaccess files
|
||||
*/
|
||||
protected function getHtaccessUploadStatus(): ReportStatus
|
||||
{
|
||||
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_ok');
|
||||
$message = '';
|
||||
$severity = ContextualFeedbackSeverity::OK;
|
||||
|
||||
$fileNameAccess = GeneralUtility::makeInstance(FileNameValidator::class);
|
||||
if ($fileNameAccess->customFileDenyPatternConfigured()
|
||||
&& $fileNameAccess->isValid('.htaccess')) {
|
||||
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_insecure');
|
||||
$severity = ContextualFeedbackSeverity::ERROR;
|
||||
$message = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.file_deny_htaccess');
|
||||
}
|
||||
|
||||
return new ReportStatus($this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_htaccessUploadProtection'), $value, $message, $severity);
|
||||
}
|
||||
|
||||
protected function getExceptionHandlerStatus(): ReportStatus
|
||||
{
|
||||
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_ok');
|
||||
$message = '';
|
||||
$severity = ContextualFeedbackSeverity::OK;
|
||||
if (
|
||||
str_contains($GLOBALS['TYPO3_CONF_VARS']['SYS']['productionExceptionHandler'], 'Debug')
|
||||
|| (Environment::getContext()->isProduction() && (int)$GLOBALS['TYPO3_CONF_VARS']['SYS']['displayErrors'] === 1)
|
||||
) {
|
||||
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_insecure');
|
||||
$severity = ContextualFeedbackSeverity::ERROR;
|
||||
$message = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_exceptionHandler_errorMessage');
|
||||
} elseif ((int)$GLOBALS['TYPO3_CONF_VARS']['SYS']['displayErrors'] === 1) {
|
||||
$severity = ContextualFeedbackSeverity::WARNING;
|
||||
$message = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_exceptionHandler_warningMessage');
|
||||
}
|
||||
return new ReportStatus($this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_exceptionHandler'), $value, $message, $severity);
|
||||
}
|
||||
|
||||
protected function getExportedFilesStatus(): ReportStatus
|
||||
{
|
||||
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_ok');
|
||||
$message = '';
|
||||
$severity = ContextualFeedbackSeverity::OK;
|
||||
|
||||
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file');
|
||||
$exportedFiles = $queryBuilder
|
||||
->select('storage', 'identifier')
|
||||
->from('sys_file')
|
||||
->where(
|
||||
$queryBuilder->expr()->like(
|
||||
'identifier',
|
||||
$queryBuilder->createNamedParameter('%/_temp_/importexport/%')
|
||||
),
|
||||
$queryBuilder->expr()->or(
|
||||
$queryBuilder->expr()->like(
|
||||
'identifier',
|
||||
$queryBuilder->createNamedParameter('%.xml')
|
||||
),
|
||||
$queryBuilder->expr()->like(
|
||||
'identifier',
|
||||
$queryBuilder->createNamedParameter('%.t3d')
|
||||
)
|
||||
),
|
||||
)
|
||||
->executeQuery()
|
||||
->fetchAllAssociative();
|
||||
|
||||
if (count($exportedFiles) > 0) {
|
||||
$files = [];
|
||||
foreach ($exportedFiles as $exportedFile) {
|
||||
$files[] = '<li>' . htmlspecialchars($exportedFile['storage'] . ':' . $exportedFile['identifier']) . '</li>';
|
||||
}
|
||||
|
||||
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_insecure');
|
||||
$severity = ContextualFeedbackSeverity::WARNING;
|
||||
$message = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_exportedFiles_warningMessage');
|
||||
$message .= '<ul>' . implode(PHP_EOL, $files) . '</ul>';
|
||||
$message .= $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_exportedFiles_warningRecommendation');
|
||||
}
|
||||
|
||||
return new ReportStatus($this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_exportedFiles'), $value, $message, $severity);
|
||||
}
|
||||
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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\Reports\Report\Status;
|
||||
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Reports\Status;
|
||||
use TYPO3\CMS\Reports\Status as ReportStatus;
|
||||
use TYPO3\CMS\Reports\StatusProviderInterface;
|
||||
|
||||
/**
|
||||
* Performs several checks about the system's health
|
||||
*/
|
||||
class SystemStatus implements StatusProviderInterface
|
||||
{
|
||||
/**
|
||||
* Determines the Install Tool's status, mainly concerning its protection.
|
||||
*
|
||||
* @return Status[] List of statuses
|
||||
*/
|
||||
public function getStatus(): array
|
||||
{
|
||||
$statuses = [
|
||||
'PhpModules' => $this->getMissingPhpModulesOfExtensions(),
|
||||
];
|
||||
return $statuses;
|
||||
}
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return 'system';
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports whether extensions need additional PHP modules different from standard core requirements
|
||||
*
|
||||
* @return \TYPO3\CMS\Reports\Status A status of missing PHP modules
|
||||
*/
|
||||
protected function getMissingPhpModulesOfExtensions()
|
||||
{
|
||||
$modules = [];
|
||||
if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install/mod/class.tx_install.php']['requiredPhpModules'] ?? null)) {
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install/mod/class.tx_install.php']['requiredPhpModules'] as $className) {
|
||||
$hookObject = GeneralUtility::makeInstance($className);
|
||||
$modules = $hookObject->setRequiredPhpModules($modules, $this);
|
||||
}
|
||||
}
|
||||
$missingPhpModules = [];
|
||||
foreach ($modules as $module) {
|
||||
if (is_array($module)) {
|
||||
$detectedSubmodules = false;
|
||||
foreach ($module as $submodule) {
|
||||
if (extension_loaded($submodule)) {
|
||||
$detectedSubmodules = true;
|
||||
}
|
||||
}
|
||||
if ($detectedSubmodules === false) {
|
||||
$missingPhpModules[] = sprintf($this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_phpModulesGroup'), '(' . implode(', ', $module) . ')');
|
||||
}
|
||||
} elseif (!extension_loaded($module)) {
|
||||
$missingPhpModules[] = $module;
|
||||
}
|
||||
}
|
||||
if (!empty($missingPhpModules)) {
|
||||
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_phpModulesMissing');
|
||||
$message = sprintf($this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_phpModulesList'), implode(', ', $missingPhpModules));
|
||||
$message .= ' ' . $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_phpModulesInfo');
|
||||
$severity = ContextualFeedbackSeverity::ERROR;
|
||||
} else {
|
||||
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_phpModulesPresent');
|
||||
$message = '';
|
||||
$severity = ContextualFeedbackSeverity::OK;
|
||||
}
|
||||
return new ReportStatus($this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_phpModules'), $value, $message, $severity);
|
||||
}
|
||||
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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\Reports\Report\Status;
|
||||
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
||||
use TYPO3\CMS\Reports\Status;
|
||||
use TYPO3\CMS\Reports\Status as ReportStatus;
|
||||
use TYPO3\CMS\Reports\StatusProviderInterface;
|
||||
|
||||
/**
|
||||
* Performs basic checks about the TYPO3 install
|
||||
*/
|
||||
class Typo3Status implements StatusProviderInterface
|
||||
{
|
||||
/**
|
||||
* Returns the status for this report
|
||||
*
|
||||
* @return Status[] List of statuses
|
||||
*/
|
||||
public function getStatus(): array
|
||||
{
|
||||
$statuses = [
|
||||
'registeredXclass' => $this->getRegisteredXclassStatus(),
|
||||
];
|
||||
return $statuses;
|
||||
}
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return 'typo3';
|
||||
}
|
||||
|
||||
/**
|
||||
* List any Xclasses registered in the system
|
||||
*
|
||||
* @return \TYPO3\CMS\Reports\Status
|
||||
*/
|
||||
protected function getRegisteredXclassStatus()
|
||||
{
|
||||
$message = '';
|
||||
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_none');
|
||||
$severity = ContextualFeedbackSeverity::OK;
|
||||
|
||||
$xclassFoundArray = [];
|
||||
if (array_key_exists('Objects', $GLOBALS['TYPO3_CONF_VARS']['SYS'])) {
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'] as $originalClass => $override) {
|
||||
if (array_key_exists('className', $override)) {
|
||||
$xclassFoundArray[$originalClass] = $override['className'];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($xclassFoundArray)) {
|
||||
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_xclassUsageFound');
|
||||
$message = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_xclassUsageFound_message') . '<br />';
|
||||
$message .= '<ol>';
|
||||
foreach ($xclassFoundArray as $originalClass => $xClassName) {
|
||||
$messageDetail = sprintf(
|
||||
(string)($this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_xclassUsageFound_message_detail')),
|
||||
'<code>' . htmlspecialchars((string)$originalClass) . '</code>',
|
||||
'<code>' . htmlspecialchars((string)$xClassName) . '</code>'
|
||||
);
|
||||
$message .= '<li>' . $messageDetail . '</li>';
|
||||
}
|
||||
$message .= '</ol>';
|
||||
$severity = ContextualFeedbackSeverity::NOTICE;
|
||||
}
|
||||
|
||||
return new ReportStatus(
|
||||
$this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_xclassUsage'),
|
||||
$value,
|
||||
$message,
|
||||
$severity
|
||||
);
|
||||
}
|
||||
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?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\Reports\Report;
|
||||
|
||||
use TYPO3\CMS\Backend\Controller\Event\ModifyGenericBackendMessagesEvent;
|
||||
use TYPO3\CMS\Core\Attribute\AsEventListener;
|
||||
use TYPO3\CMS\Core\Context\Context;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Registry;
|
||||
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
||||
|
||||
/**
|
||||
* Adds a warning message about problems in the current installation to the About module
|
||||
*
|
||||
* @internal This is a concrete Event Listener implementation and not part of the TYPO3 Core API.
|
||||
*/
|
||||
final class WarningsForAboutModule
|
||||
{
|
||||
private string $reportsModuleName = 'system_reports_status';
|
||||
|
||||
public function __construct(
|
||||
private readonly Registry $registry,
|
||||
private readonly Context $context
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Tries to get the highest severity of the system's status first, if
|
||||
* something is found it is assumed that the status update task is set up
|
||||
* properly or the status report has been checked manually. We then add
|
||||
* a system warning message.
|
||||
*/
|
||||
#[AsEventListener('typo3-reports/warnings')]
|
||||
public function __invoke(ModifyGenericBackendMessagesEvent $event): void
|
||||
{
|
||||
if (!$this->context->getAspect('backend.user')->isAdmin()) {
|
||||
return;
|
||||
}
|
||||
// Get the highest severity
|
||||
$highestSeverity = $this->registry->get('tx_reports', 'status.highestSeverity');
|
||||
if ($highestSeverity === null || $highestSeverity <= ContextualFeedbackSeverity::OK->value) {
|
||||
return;
|
||||
}
|
||||
// Display a message that there's something wrong and that
|
||||
// the administrator should take a look at the detailed status report
|
||||
$event->addMessage(new FlashMessage(sprintf(
|
||||
$this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_problemNotification'),
|
||||
'<a href="#" data-dispatch-action="TYPO3.ModuleMenu.showModule" '
|
||||
. 'data-dispatch-args-list="' . $this->reportsModuleName . '">',
|
||||
'</a>'
|
||||
)));
|
||||
}
|
||||
|
||||
private function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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\Reports;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
/**
|
||||
* Interface for classes which provide a status report entry using information from the current request
|
||||
*/
|
||||
interface RequestAwareStatusProviderInterface extends StatusProviderInterface
|
||||
{
|
||||
/**
|
||||
* Returns the status of an extension or (sub)system
|
||||
*
|
||||
* @param ServerRequestInterface|null $request the currently handled request
|
||||
* @return Status[]
|
||||
*/
|
||||
public function getStatus(?ServerRequestInterface $request = null): array;
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
<?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\Reports\Service;
|
||||
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
|
||||
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
|
||||
use TYPO3\CMS\Core\Pagination\QueryBuilderPaginator;
|
||||
use TYPO3\CMS\Core\Pagination\SimplePagination;
|
||||
use TYPO3\CMS\Core\Schema\Exception\InvalidSchemaTypeException;
|
||||
use TYPO3\CMS\Core\Schema\Exception\UndefinedSchemaException;
|
||||
use TYPO3\CMS\Core\Schema\SchemaLabelResolver;
|
||||
use TYPO3\CMS\Core\Schema\Struct\SelectItem;
|
||||
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Service for collecting content element statistics
|
||||
*
|
||||
* @internal This is not part of the public API and may change at any time
|
||||
*/
|
||||
final readonly class ContentStatisticsService
|
||||
{
|
||||
public function __construct(
|
||||
private TcaSchemaFactory $tcaSchemaFactory,
|
||||
private ConnectionPool $connectionPool,
|
||||
private SchemaLabelResolver $schemaLabelResolver,
|
||||
) {}
|
||||
|
||||
public function collectStatistic(): array
|
||||
{
|
||||
$defaultFields = ['colPos', 'CType', 'starttime', 'endtime', 'editlock', 'sys_language_uid', 'l18n_parent', 'fe_group', 'rowDescription', 'hidden'];
|
||||
$countInformation = $this->getCountInformation();
|
||||
|
||||
$schema = $this->tcaSchemaFactory->get('tt_content');
|
||||
try {
|
||||
$typeField = $schema->getSubSchemaTypeInformation()->getFieldName();
|
||||
} catch (InvalidSchemaTypeException) {
|
||||
return ['error' => true];
|
||||
}
|
||||
$fieldConfig = $schema->hasField($typeField) ? $schema->getField($typeField)->getConfiguration() : [];
|
||||
$itemGroups = $fieldConfig['itemGroups'] ?? [];
|
||||
$groupedWizardItems = [];
|
||||
foreach (array_keys($itemGroups) as $groupIdentifier) {
|
||||
$groupedWizardItems['exists'][$groupIdentifier]['header'] = $itemGroups[$groupIdentifier];
|
||||
$groupedWizardItems['unused'][$groupIdentifier]['header'] = $itemGroups[$groupIdentifier];
|
||||
}
|
||||
|
||||
foreach ($fieldConfig['items'] ?? [] as $item) {
|
||||
$selectItem = SelectItem::fromTcaItemArray($item);
|
||||
if ($selectItem->isDivider()) {
|
||||
continue;
|
||||
}
|
||||
$recordType = $selectItem->getValue();
|
||||
$groupIdentifier = $selectItem->getGroup();
|
||||
if (!isset($countInformation[$recordType])
|
||||
|| ($countInformation[$recordType]['visible'] === 0 && $countInformation[$recordType]['hidden'] === 0)
|
||||
) {
|
||||
$usageIdentifier = 'unused';
|
||||
} else {
|
||||
$usageIdentifier = 'exists';
|
||||
}
|
||||
|
||||
$groupedWizardItems[$usageIdentifier][$groupIdentifier]['elements'] ??= [];
|
||||
|
||||
// In case this group is not defined in itemGroups, use the group identifier as label.
|
||||
$groupedWizardItems[$usageIdentifier][$groupIdentifier]['header'] ??= $groupIdentifier;
|
||||
$itemDescription = $selectItem->getDescription();
|
||||
$wizardEntry = [
|
||||
'iconIdentifier' => $selectItem->getIcon(),
|
||||
'iconOverlay' => $selectItem->getIconOverlay(),
|
||||
'title' => $selectItem->getLabel(),
|
||||
'description' => $itemDescription['description'] ?? ($itemDescription ?? ''),
|
||||
];
|
||||
$groupedWizardItems[$usageIdentifier][$groupIdentifier]['elements'][$recordType] = $wizardEntry;
|
||||
$groupedWizardItems[$usageIdentifier][$groupIdentifier]['elements'][$recordType]['count'] = $countInformation[$recordType] ?? [];
|
||||
try {
|
||||
$subschema = $schema->getSubSchema($recordType);
|
||||
$groupedWizardItems[$usageIdentifier][$groupIdentifier]['elements'][$recordType]['fields']
|
||||
= $subschema->getFields(fn($field) => !in_array($field->getName(), $defaultFields, true));
|
||||
} catch (UndefinedSchemaException) {
|
||||
}
|
||||
}
|
||||
return $groupedWizardItems;
|
||||
}
|
||||
|
||||
public function collectStatisticForCtype(string $cType, int $currentPage): array
|
||||
{
|
||||
$paginator = new QueryBuilderPaginator($this->buildQueryBuilderForCtype($cType), $currentPage, 10);
|
||||
$pagination = new SimplePagination($paginator);
|
||||
|
||||
$rows = $paginator->getPaginatedItems();
|
||||
foreach ($rows as &$row) {
|
||||
$row['path'] = BackendUtility::getRecordPath($row['pid'], '', 0);
|
||||
$row['tstamp_formatted'] = BackendUtility::datetime($row['tstamp']);
|
||||
}
|
||||
|
||||
return [
|
||||
'ctype' => $cType,
|
||||
'label' => $this->schemaLabelResolver->getLabelForFieldValue('tt_content', 'CType', $cType),
|
||||
'count' => $this->getCountInformation($cType)[$cType] ?? [],
|
||||
'rows' => $rows,
|
||||
'paginator' => $paginator,
|
||||
'pagination' => $pagination,
|
||||
];
|
||||
}
|
||||
|
||||
public function isValidCtype(string $cType): bool
|
||||
{
|
||||
if ($cType === '') {
|
||||
return false;
|
||||
}
|
||||
return $this->tcaSchemaFactory->get('tt_content')->hasSubSchema($cType);
|
||||
}
|
||||
|
||||
private function buildQueryBuilderForCtype(string $cType): QueryBuilder
|
||||
{
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('tt_content');
|
||||
$queryBuilder->getRestrictions()
|
||||
->removeAll()
|
||||
->add(GeneralUtility::makeInstance(DeletedRestriction::class));
|
||||
return $queryBuilder
|
||||
->select('uid', 'pid', 'header', 'tstamp', 'crdate', 'hidden', 'fe_group')
|
||||
->from('tt_content')
|
||||
->where(
|
||||
$queryBuilder->expr()->eq('CType', $queryBuilder->createNamedParameter($cType)),
|
||||
)
|
||||
->orderBy('uid', 'desc');
|
||||
}
|
||||
|
||||
private function getCountInformation(string $cType = ''): array
|
||||
{
|
||||
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('tt_content');
|
||||
$queryBuilder->getRestrictions()->removeAll();
|
||||
$queryBuilder = $queryBuilder
|
||||
->select('CType', 'deleted', 'hidden')
|
||||
->addSelectLiteral('COUNT(*) as count')
|
||||
->from('tt_content')
|
||||
->groupBy('CType')
|
||||
->addGroupBy('hidden')
|
||||
->addGroupBy('deleted');
|
||||
|
||||
if ($cType !== '') {
|
||||
$queryBuilder = $queryBuilder->where($queryBuilder->expr()->eq('CType', $queryBuilder->createNamedParameter($cType)));
|
||||
}
|
||||
|
||||
$counts = [];
|
||||
foreach ($queryBuilder->executeQuery()->fetchAllAssociative() as $row) {
|
||||
$cType = $row['CType'];
|
||||
$deleted = (int)$row['deleted'];
|
||||
$hidden = (int)$row['hidden'];
|
||||
$count = (int)$row['count'];
|
||||
if (!isset($counts[$cType])) {
|
||||
$counts[$cType] = ['deleted' => 0, 'hidden' => 0, 'visible' => 0];
|
||||
}
|
||||
|
||||
$key = match (true) {
|
||||
$deleted === 1 => 'deleted',
|
||||
$hidden === 1 => 'hidden',
|
||||
default => 'visible',
|
||||
};
|
||||
|
||||
$counts[$cType][$key] += $count;
|
||||
}
|
||||
|
||||
return $counts;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
<?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\Reports\Service;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
|
||||
use TYPO3\CMS\Core\DataHandling\PageDoktypeRegistry;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Imaging\IconSize;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Schema\Capability\TcaSchemaCapability;
|
||||
use TYPO3\CMS\Core\Schema\TcaSchema;
|
||||
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Reports\Integrity\DatabaseIntegrityCheck;
|
||||
|
||||
/**
|
||||
* Service for collecting database record statistics
|
||||
*
|
||||
* @internal This is not part of the public API and may change at any time
|
||||
*/
|
||||
#[Autoconfigure(public: true)]
|
||||
final readonly class RecordStatisticsService
|
||||
{
|
||||
public function __construct(
|
||||
private IconFactory $iconFactory,
|
||||
private TcaSchemaFactory $tcaSchemaFactory,
|
||||
private PageDoktypeRegistry $pageDoktypeRegistry,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Collects page statistics including total, translated, hidden, and deleted pages
|
||||
*
|
||||
* @return array<string, array{icon: string, count: int}>
|
||||
*/
|
||||
public function collectPageStatistics(): array
|
||||
{
|
||||
$databaseIntegrityCheck = GeneralUtility::makeInstance(DatabaseIntegrityCheck::class);
|
||||
$databaseIntegrityCheck->genTree(0);
|
||||
|
||||
return [
|
||||
'total_pages' => [
|
||||
'icon' => $this->iconFactory->getIconForRecord('pages', [], IconSize::SMALL)->render(),
|
||||
'count' => count($databaseIntegrityCheck->getPageIdArray()),
|
||||
],
|
||||
'translated_pages' => [
|
||||
'icon' => $this->iconFactory->getIconForRecord('pages', [], IconSize::SMALL)->render(),
|
||||
'count' => count($databaseIntegrityCheck->getPageTranslatedPageIDArray()),
|
||||
],
|
||||
'hidden_pages' => [
|
||||
'icon' => $this->iconFactory->getIconForRecord('pages', ['hidden' => 1], IconSize::SMALL)->render(),
|
||||
'count' => $databaseIntegrityCheck->getRecStats()['hidden'] ?? 0,
|
||||
],
|
||||
'deleted_pages' => [
|
||||
'icon' => $this->iconFactory->getIconForRecord('pages', ['deleted' => 1], IconSize::SMALL)->render(),
|
||||
'count' => isset($databaseIntegrityCheck->getRecStats()['deleted']['pages']) ? count($databaseIntegrityCheck->getRecStats()['deleted']['pages']) : 0,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects statistics for different page doktypes
|
||||
*
|
||||
* @return array<int, array{icon: string, title: string, count: int}>
|
||||
*/
|
||||
public function collectDoktypeStatistics(): array
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$databaseIntegrityCheck = GeneralUtility::makeInstance(DatabaseIntegrityCheck::class);
|
||||
$databaseIntegrityCheck->genTree(0);
|
||||
|
||||
$doktypes = [];
|
||||
foreach ($this->pageDoktypeRegistry->getAllDoktypes() as $doktype) {
|
||||
if ($doktype->isDivider()) {
|
||||
continue;
|
||||
}
|
||||
$doktypes[] = [
|
||||
'icon' => $this->iconFactory->getIconForRecord('pages', ['doktype' => $doktype->getValue()], IconSize::SMALL)->render(),
|
||||
'title' => $languageService->sL($doktype->getLabel()) . ' (' . $doktype->getValue() . ')',
|
||||
'count' => (int)($databaseIntegrityCheck->getRecStats()['doktype'][$doktype->getValue()] ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
return $doktypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects statistics for all TCA tables including lost records
|
||||
*
|
||||
* @return array<string, array{icon: string, title: string, count: int|string, lostRecords: string}>
|
||||
*/
|
||||
public function collectTableStatistics(): array
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$databaseIntegrityCheck = GeneralUtility::makeInstance(DatabaseIntegrityCheck::class);
|
||||
$databaseIntegrityCheck->genTree(0);
|
||||
|
||||
// Tables and lost records
|
||||
$pageIds = array_merge([0], array_keys($databaseIntegrityCheck->getPageIdArray()));
|
||||
$lostRecords = $databaseIntegrityCheck->lostRecords($pageIds);
|
||||
|
||||
$tableStatistic = [];
|
||||
$countArr = $databaseIntegrityCheck->countRecords($pageIds);
|
||||
$lostPageIds = isset($lostRecords['pages']) ? array_keys($lostRecords['pages']) : [];
|
||||
|
||||
/** @var TcaSchema $schema */
|
||||
foreach ($this->tcaSchemaFactory->all() as $table => $schema) {
|
||||
if ($schema->hasCapability(TcaSchemaCapability::HideInUi)) {
|
||||
continue;
|
||||
}
|
||||
$lostRecordCount = isset($lostRecords[$table]) ? count($lostRecords[$table]) : 0;
|
||||
$recordCount = 0;
|
||||
if ($countArr['all'][$table] ?? false) {
|
||||
$recordCount = (int)($countArr['non_deleted'][$table] ?? 0) . '/' . $lostRecordCount;
|
||||
}
|
||||
$lostRecordList = [];
|
||||
foreach ($lostRecords[$table] ?? [] as $data) {
|
||||
if (!in_array((int)$data['pid'], $lostPageIds, true)) {
|
||||
$lostRecordList[]
|
||||
= '<div class="record">'
|
||||
. $this->iconFactory->getIcon('status-dialog-error', IconSize::SMALL)->render()
|
||||
. 'uid:' . $data['uid'] . ', pid:' . $data['pid'] . ', ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs(strip_tags($data['title']), 20))
|
||||
. '</div>';
|
||||
} else {
|
||||
$lostRecordList[]
|
||||
= '<div class="record-noicon">'
|
||||
. 'uid:' . $data['uid'] . ', pid:' . $data['pid'] . ', ' . htmlspecialchars(GeneralUtility::fixed_lgd_cs(strip_tags($data['title']), 20))
|
||||
. '</div>';
|
||||
}
|
||||
}
|
||||
$tableStatistic[$table] = [
|
||||
'icon' => $this->iconFactory->getIconForRecord($table, [], IconSize::SMALL)->render(),
|
||||
'title' => $schema->getTitle($languageService->sL(...)),
|
||||
'count' => $recordCount,
|
||||
'lostRecords' => implode(LF, $lostRecordList),
|
||||
];
|
||||
}
|
||||
ksort($tableStatistic);
|
||||
|
||||
return $tableStatistic;
|
||||
}
|
||||
|
||||
private function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
<?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\Reports\Service;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Registry;
|
||||
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
||||
use TYPO3\CMS\Reports\ExtendedStatusProviderInterface;
|
||||
use TYPO3\CMS\Reports\Registry\StatusRegistry;
|
||||
use TYPO3\CMS\Reports\RequestAwareStatusProviderInterface;
|
||||
use TYPO3\CMS\Reports\Status;
|
||||
|
||||
/**
|
||||
* Service for collecting and processing system status information
|
||||
*
|
||||
* @internal This is not part of the public API and may change at any time
|
||||
*/
|
||||
#[Autoconfigure(public: true)]
|
||||
final readonly class StatusService
|
||||
{
|
||||
public function __construct(
|
||||
private StatusRegistry $statusRegistry,
|
||||
private Registry $registry,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Runs through all status providers and returns all statuses collected.
|
||||
*
|
||||
* @param ServerRequestInterface|null $request
|
||||
* @return Status[][]
|
||||
*/
|
||||
public function getSystemStatus(?ServerRequestInterface $request = null): array
|
||||
{
|
||||
$status = [];
|
||||
foreach ($this->statusRegistry->getProviders() as $statusProvider) {
|
||||
$statusProviderId = $statusProvider->getLabel();
|
||||
$status[$statusProviderId] ??= [];
|
||||
if ($statusProvider instanceof RequestAwareStatusProviderInterface) {
|
||||
$statuses = $statusProvider->getStatus($request);
|
||||
} else {
|
||||
$statuses = $statusProvider->getStatus();
|
||||
}
|
||||
$status[$statusProviderId] = array_merge($status[$statusProviderId], $statuses);
|
||||
}
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs through all status providers and returns all statuses collected, which are detailed.
|
||||
*
|
||||
* @return Status[][]
|
||||
*/
|
||||
public function getDetailedSystemStatus(): array
|
||||
{
|
||||
$status = [];
|
||||
foreach ($this->statusRegistry->getProviders() as $statusProvider) {
|
||||
$statusProviderId = $statusProvider->getLabel();
|
||||
if ($statusProvider instanceof ExtendedStatusProviderInterface) {
|
||||
$statuses = $statusProvider->getDetailedStatus();
|
||||
$status[$statusProviderId] = array_merge($status[$statusProviderId] ?? [], $statuses);
|
||||
}
|
||||
}
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the highest severity from the given statuses.
|
||||
*
|
||||
* @param array<string, array<string, Status>> $statusCollection An array of Status objects.
|
||||
* @return int The highest severity found from the statuses.
|
||||
*/
|
||||
public function getHighestSeverity(array $statusCollection): int
|
||||
{
|
||||
$highestSeverity = ContextualFeedbackSeverity::NOTICE;
|
||||
foreach ($statusCollection as $providerStatuses) {
|
||||
foreach ($providerStatuses as $status) {
|
||||
if ($status->getSeverity()->value > $highestSeverity->value) {
|
||||
$highestSeverity = $status->getSeverity();
|
||||
}
|
||||
// Reached the highest severity level, no need to go on
|
||||
if ($highestSeverity === ContextualFeedbackSeverity::ERROR) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $highestSeverity->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects system status and stores the highest severity in the registry.
|
||||
* This is useful for displaying warnings at login or in the backend.
|
||||
*
|
||||
* @param ServerRequestInterface|null $request
|
||||
*/
|
||||
public function collectAndStoreSystemStatus(?ServerRequestInterface $request = null): void
|
||||
{
|
||||
$status = $this->getSystemStatus($request);
|
||||
$this->registry->set('tx_reports', 'status.highestSeverity', $this->getHighestSeverity($status));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the status providers (alphabetically and puts primary status providers at the beginning)
|
||||
*
|
||||
* @param array<string, array<string, Status>> $statusCollection A collection of statuses (with providers)
|
||||
* @return array<string, array<string, Status>> The collection of statuses sorted by provider
|
||||
*/
|
||||
public function sortStatusProviders(array $statusCollection): array
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
|
||||
// Extract the primary status collections, i.e. the status groups
|
||||
// that must appear on top of the status report
|
||||
// Change their keys to localized collection titles
|
||||
$primaryStatuses = [
|
||||
$languageService->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_typo3') => $statusCollection['typo3'] ?? [],
|
||||
$languageService->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_system') => $statusCollection['system'] ?? [],
|
||||
$languageService->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_security') => $statusCollection['security'] ?? [],
|
||||
$languageService->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_configuration') => $statusCollection['configuration'] ?? [],
|
||||
];
|
||||
unset($statusCollection['typo3'], $statusCollection['system'], $statusCollection['security'], $statusCollection['configuration']);
|
||||
|
||||
// Assemble list of secondary status collections with left-over collections
|
||||
// Change their keys using localized labels if available
|
||||
$secondaryStatuses = [];
|
||||
foreach ($statusCollection as $statusProviderId => $collection) {
|
||||
if (str_starts_with($statusProviderId, 'LLL:')) {
|
||||
// Label provided by extension
|
||||
$label = $languageService->sL($statusProviderId);
|
||||
} else {
|
||||
// Generic label
|
||||
// @todo phase this out
|
||||
$label = $languageService->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_' . $statusProviderId);
|
||||
}
|
||||
$providerLabel = empty($label) ? $statusProviderId : $label;
|
||||
$secondaryStatuses[$providerLabel] = $collection;
|
||||
}
|
||||
// Sort the secondary status collections alphabetically
|
||||
ksort($secondaryStatuses);
|
||||
return array_merge($primaryStatuses, $secondaryStatuses);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the statuses by severity
|
||||
*
|
||||
* @param array<string, Status> $statusCollection A collection of statuses per provider
|
||||
* @return list<Status> The collection of statuses sorted by severity
|
||||
*/
|
||||
public function sortStatuses(array $statusCollection): array
|
||||
{
|
||||
$statuses = [];
|
||||
$sortTitle = [];
|
||||
$header = null;
|
||||
foreach ($statusCollection as $status) {
|
||||
if ($status->getTitle() === 'TYPO3') {
|
||||
$header = $status;
|
||||
continue;
|
||||
}
|
||||
$statuses[] = $status;
|
||||
$sortTitle[] = $status->getSeverity();
|
||||
}
|
||||
array_multisort($sortTitle, SORT_DESC, $statuses);
|
||||
// Making sure that the core version information is always on the top
|
||||
if (is_object($header)) {
|
||||
array_unshift($statuses, $header);
|
||||
}
|
||||
return $statuses;
|
||||
}
|
||||
|
||||
private function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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\Reports;
|
||||
|
||||
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
||||
|
||||
/**
|
||||
* A class representing a certain status
|
||||
*/
|
||||
class Status
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $title;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $message;
|
||||
|
||||
protected ContextualFeedbackSeverity $severity;
|
||||
|
||||
/**
|
||||
* Construct a status
|
||||
*
|
||||
* All values must be given as constructor arguments.
|
||||
* All strings should be localized.
|
||||
*
|
||||
* @param string $title Status title, eg. "Deprecation log"
|
||||
* @param string $value Status value, eg. "Disabled"
|
||||
* @param string $message Optional message further describing the title/value combination
|
||||
* Example:, eg "The deprecation log is important and does foo, to disable it do bar"
|
||||
* @param ContextualFeedbackSeverity $severity A severity level
|
||||
*/
|
||||
public function __construct($title, $value, $message = '', ContextualFeedbackSeverity $severity = ContextualFeedbackSeverity::OK)
|
||||
{
|
||||
$this->title = (string)$title;
|
||||
$this->value = (string)$value;
|
||||
$this->message = (string)$message;
|
||||
$this->severity = $severity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the status' title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the status' value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the status' message (if any)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMessage()
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
public function getSeverity(): ContextualFeedbackSeverity
|
||||
{
|
||||
return $this->severity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a string representation of a status.
|
||||
*
|
||||
* @return string String representation of this status.
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
// Max length 80 characters
|
||||
$stringRepresentation = str_pad('[' . $this->severity->name . ']', 7) . str_pad($this->title, 40) . ' - ' . substr($this->value, 0, 30);
|
||||
return $stringRepresentation;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* 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\Reports;
|
||||
|
||||
/**
|
||||
* Interface for classes which provide a status report entry.
|
||||
*/
|
||||
interface StatusProviderInterface
|
||||
{
|
||||
/**
|
||||
* Returns the status of an extension or (sub)system
|
||||
*
|
||||
* @return Status[]
|
||||
*/
|
||||
public function getStatus(): array;
|
||||
|
||||
/**
|
||||
* Return label of this status
|
||||
*/
|
||||
public function getLabel(): string;
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
<?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\Reports\Task;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Symfony\Component\Mime\Address;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Mail\MailerInterface;
|
||||
use TYPO3\CMS\Core\Mail\TemplatedEmailFactory;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageService;
|
||||
use TYPO3\CMS\Core\Registry;
|
||||
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Reports\Service\StatusService;
|
||||
use TYPO3\CMS\Reports\Status;
|
||||
use TYPO3\CMS\Scheduler\Task\AbstractTask;
|
||||
|
||||
/**
|
||||
* A task that should be run regularly to determine the system's status.
|
||||
* @internal This class is a specific scheduler task implementation and is not considered part of the Public TYPO3 API.
|
||||
*/
|
||||
class SystemStatusUpdateTask extends AbstractTask
|
||||
{
|
||||
/**
|
||||
* Email addresses to send email notification to in case we find problems with
|
||||
* the system.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $notificationEmail = '';
|
||||
|
||||
/**
|
||||
* Checkbox for to send all types of notification, not only problems
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $notificationAll = false;
|
||||
|
||||
/**
|
||||
* Executes the System Status Update task, determining the highest severity of
|
||||
* status reports and saving that to the registry to be displayed at login
|
||||
* if necessary.
|
||||
*
|
||||
* @see \TYPO3\CMS\Scheduler\Task\AbstractTask::execute()
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$statusService = GeneralUtility::makeInstance(StatusService::class);
|
||||
$systemStatus = $statusService->getDetailedSystemStatus();
|
||||
$highestSeverity = $statusService->getHighestSeverity($systemStatus);
|
||||
$registry = GeneralUtility::makeInstance(Registry::class);
|
||||
$registry->set('tx_reports', 'status.highestSeverity', $highestSeverity);
|
||||
if (($highestSeverity > ContextualFeedbackSeverity::OK->value) || $this->notificationAll) {
|
||||
$this->sendNotificationEmail($systemStatus);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a notification email, reporting system issues.
|
||||
*
|
||||
* @param Status[][] $systemStatus Array of statuses
|
||||
*/
|
||||
protected function sendNotificationEmail(array $systemStatus): void
|
||||
{
|
||||
$systemIssues = [];
|
||||
foreach ($systemStatus as $statusProvider) {
|
||||
foreach ($statusProvider as $status) {
|
||||
if ($this->notificationAll || ($status->getSeverity()->value > ContextualFeedbackSeverity::OK->value)) {
|
||||
$systemIssues[] = (string)$status . CRLF . $status->getMessage() . CRLF . CRLF;
|
||||
}
|
||||
}
|
||||
}
|
||||
$notificationEmails = GeneralUtility::trimExplode(LF, $this->notificationEmail, true);
|
||||
$sendEmailsTo = [];
|
||||
foreach ($notificationEmails as $notificationEmail) {
|
||||
$sendEmailsTo[] = new Address($notificationEmail);
|
||||
}
|
||||
$subject = sprintf($this->getLanguageService()->sL('reports.reports:status_updateTask_email_subject'), $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename']);
|
||||
$message = sprintf($this->getLanguageService()->sL('reports.reports:' . ($this->notificationAll ? 'status_allNotification' : 'status_problemNotification')), '', '');
|
||||
if (Environment::isCli()) {
|
||||
$message .= CRLF . CRLF;
|
||||
$message .= $this->getLanguageService()->sL('reports.reports:status_problem_notification_cli_disclaimer');
|
||||
}
|
||||
$message .= CRLF . CRLF;
|
||||
$message .= $this->getLanguageService()->sL('reports.reports:status_updateTask_email_site') . ': ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'];
|
||||
$message .= CRLF . CRLF;
|
||||
$message .= $this->getLanguageService()->sL('reports.reports:status_updateTask_email_issues') . ': ' . CRLF;
|
||||
$message .= implode(CRLF, $systemIssues);
|
||||
$message .= CRLF . CRLF;
|
||||
|
||||
$request = ($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface ? $GLOBALS['TYPO3_REQUEST'] : null;
|
||||
// @todo DI should be used to inject the MailerInterface in v15.0
|
||||
$email = GeneralUtility::makeInstance(TemplatedEmailFactory::class)->createWithOverrides(
|
||||
[20 => 'EXT:reports/Resources/Private/Templates/Email/'],
|
||||
[],
|
||||
[],
|
||||
$request,
|
||||
);
|
||||
$email
|
||||
->to(...$sendEmailsTo)
|
||||
->format('plain')
|
||||
->subject($subject)
|
||||
->setTemplate('Report')
|
||||
->assign('message', $message);
|
||||
|
||||
// @todo DI should be used to inject the MailerInterface in v15.0
|
||||
GeneralUtility::makeInstance(MailerInterface::class)->send($email);
|
||||
}
|
||||
|
||||
public function getAdditionalInformation()
|
||||
{
|
||||
return sprintf($this->getLanguageService()->sL('reports.reports:status_updateAdditionalInformation'), preg_replace('#\s+#', ', ', trim($this->notificationEmail)));
|
||||
}
|
||||
|
||||
public function getTaskParameters(): array
|
||||
{
|
||||
return [
|
||||
'tx_reports_notification_email' => $this->notificationEmail,
|
||||
'tx_reports_notification_all' => $this->notificationAll,
|
||||
];
|
||||
}
|
||||
|
||||
public function setTaskParameters(array $parameters): void
|
||||
{
|
||||
$this->notificationEmail = $parameters['notificationEmail'] ?? $parameters['tx_reports_notification_email'] ?? '';
|
||||
$this->notificationAll = (bool)($parameters['notificationAll'] ?? $parameters['tx_reports_notification_all'] ?? false);
|
||||
}
|
||||
|
||||
public function validateTaskParameters(array $parameters): bool
|
||||
{
|
||||
$validInput = true;
|
||||
$notificationEmails = GeneralUtility::trimExplode(LF, $parameters['tx_reports_notification_email'] ?? '', true);
|
||||
foreach ($notificationEmails as $notificationEmail) {
|
||||
if (!GeneralUtility::validEmail($notificationEmail)) {
|
||||
$validInput = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$validInput || empty($parameters['tx_reports_notification_email'] ?? '')) {
|
||||
GeneralUtility::makeInstance(FlashMessageService::class)->getMessageQueueByIdentifier()->addMessage(
|
||||
new FlashMessage($this->getLanguageService()->sL('reports.reports:status_updateTaskField_notificationEmails_invalid'), '', ContextualFeedbackSeverity::ERROR)
|
||||
);
|
||||
$validInput = false;
|
||||
}
|
||||
return $validInput;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user