TYPO3 v15 dev-main snapshot ()

This commit is contained in:
2026-08-10 22:31:34 +02:00
commit 8a3bef5a34
61 changed files with 4603 additions and 0 deletions
@@ -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'];
}
}
+254
View File
@@ -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'];
}
}
+247
View File
@@ -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'];
}
}
+95
View File
@@ -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'];
}
}
+94
View File
@@ -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'];
}
}