TYPO3 v15 dev-main snapshot ()

This commit is contained in:
2026-08-10 22:31:31 +02:00
commit 3e43c11539
407 changed files with 51272 additions and 0 deletions
+143
View File
@@ -0,0 +1,143 @@
<?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\Install\Report;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Install\SystemEnvironment\Check;
use TYPO3\CMS\Install\SystemEnvironment\DatabaseCheck;
use TYPO3\CMS\Install\SystemEnvironment\SetupCheck;
use TYPO3\CMS\Reports\ExtendedStatusProviderInterface;
use TYPO3\CMS\Reports\Status;
use TYPO3\CMS\Reports\StatusProviderInterface;
/**
* Provides an environment status report
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
*/
class EnvironmentStatusReport implements StatusProviderInterface, ExtendedStatusProviderInterface
{
/**
* Compile environment status report
*
* @return Status[]
*/
public function getStatus(): array
{
if (Environment::isCli()) {
return [];
}
return $this->getStatusInternal(false);
}
public function getLabel(): string
{
return 'system';
}
/**
* Returns the detailed status of an extension or (sub)system
*
* @return Status[]
*/
public function getDetailedStatus()
{
return $this->getStatusInternal(true);
}
/**
* @param bool $verbose
* @return Status[]
*/
protected function getStatusInternal($verbose)
{
$statusMessageQueue = new FlashMessageQueue('install');
foreach (GeneralUtility::makeInstance(Check::class)->getStatus() as $message) {
$statusMessageQueue->enqueue($message);
}
foreach (GeneralUtility::makeInstance(SetupCheck::class)->getStatus() as $message) {
$statusMessageQueue->enqueue($message);
}
foreach (GeneralUtility::makeInstance(DatabaseCheck::class)->getStatus() as $message) {
$statusMessageQueue->enqueue($message);
}
$reportStatusTypes = [
'error' => [],
'warning' => [],
'ok' => [],
'information' => [],
'notice' => [],
];
foreach ($statusMessageQueue->toArray() as $message) {
switch ($message->getSeverity()) {
case ContextualFeedbackSeverity::ERROR:
$reportStatusTypes['error'][] = $message;
break;
case ContextualFeedbackSeverity::WARNING:
$reportStatusTypes['warning'][] = $message;
break;
case ContextualFeedbackSeverity::OK:
$reportStatusTypes['ok'][] = $message;
break;
case ContextualFeedbackSeverity::INFO:
$reportStatusTypes['information'][] = $message;
break;
case ContextualFeedbackSeverity::NOTICE:
$reportStatusTypes['notice'][] = $message;
break;
}
}
$statusArray = [];
foreach ($reportStatusTypes as $type => $statusObjects) {
$value = count($statusObjects);
$message = '';
if ($verbose) {
foreach ($statusObjects as $statusObject) {
$message .= '### ' . $statusObject->getTitle() . ': ' . $statusObject->getSeverity()->name . CRLF;
}
}
if ($value > 0) {
$pathToXliff = 'LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf';
// Map information type to enums which is used in \TYPO3\CMS\Core\Type\ContextualFeedbackSeverity
if ($type === 'information') {
$type = 'info';
}
if (!$verbose) {
$message = $this->getLanguageService()->sL($pathToXliff . ':environment.status.message.' . $type);
}
$severity = constant('\TYPO3\CMS\Core\Type\ContextualFeedbackSeverity::' . strtoupper($type));
$statusArray[] = new Status(
$this->getLanguageService()->sL($pathToXliff . ':environment.status.title'),
sprintf($this->getLanguageService()->sL($pathToXliff . ':environment.status.value'), $value),
$message,
$severity
);
}
}
return $statusArray;
}
protected function getLanguageService(): ?LanguageService
{
return $GLOBALS['LANG'] ?? null;
}
}
+333
View File
@@ -0,0 +1,333 @@
<?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\Install\Report;
use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Information\Typo3Version;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Service\UpgradeWizardsService;
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Install\Service\CoreVersionService;
use TYPO3\CMS\Install\Service\Exception\RemoteFetchException;
use TYPO3\CMS\Reports\Status;
use TYPO3\CMS\Reports\StatusProviderInterface;
/**
* Provides an installation status report.
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
*/
final readonly class InstallStatusReport implements StatusProviderInterface
{
private const int WRAP_FLAT = 1;
private const int WRAP_NESTED = 2;
public function __construct(
private UpgradeWizardsService $upgradeWizardsService,
private UriBuilder $uriBuilder,
) {}
/**
* Compiles a collection of system status checks as a status report.
*
* @return Status[]
*/
public function getStatus(): array
{
return [
'FileSystem' => $this->getFileSystemStatus(),
'RemainingUpdates' => $this->getRemainingUpdatesStatus(),
'NewVersion' => $this->getNewVersionStatus(),
];
}
public function getLabel(): string
{
return 'typo3';
}
/**
* Checks for several directories being writable.
*
* @return Status Indicates status of the file system
*/
private function getFileSystemStatus(): Status
{
$languageService = $this->getLanguageService();
$value = $languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_writable');
$message = '';
$severity = ContextualFeedbackSeverity::OK;
// Requirement level
// -1 = not required, but if it exists may be writable or not
// 0 = not required, if it exists the dir should be writable
// 1 = required, doesn't have to be writable
// 2 = required, has to be writable
$varPath = Environment::getVarPath();
$sitePath = Environment::getPublicPath();
$rootPath = Environment::getProjectPath();
$checkWritable = [
$sitePath . '/typo3temp/' => 2,
$sitePath . '/typo3temp/assets/' => 2,
// only needed when GraphicalFunctions is used
$sitePath . '/typo3temp/assets/images/' => 0,
// used in PageGenerator (inlineStyle2Temp) and Backend + Language JS files
$sitePath . '/typo3temp/assets/css/' => 2,
$sitePath . '/typo3temp/assets/js/' => 2,
// fallback storage of FAL
$sitePath . '/typo3temp/assets/_processed_/' => 0,
$varPath => 2,
$varPath . '/transient/' => 2,
$varPath . '/lock/' => 2,
Environment::getLabelsPath() => 0,
$sitePath . '/' . $GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'] => -1,
$sitePath . '/' . $GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'] . '_temp_/' => 0,
];
// Check for writable extension folder files in non-composer mode only
if (!Environment::isComposerMode()) {
$checkWritable[Environment::getExtensionsPath()] = 0;
$checkWritable[$sitePath . '/typo3conf/'] = 2;
}
foreach ($checkWritable as $path => $requirementLevel) {
$relPath = substr($path, strlen($rootPath) + 1);
if (!@is_dir($path)) {
// If the directory is missing, try to create it
GeneralUtility::mkdir($path);
}
if (!@is_dir($path)) {
if ($requirementLevel > 0) {
// directory is required
$value = $languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_missingDirectory');
$message .= sprintf($languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_directoryDoesNotExistCouldNotCreate'), $relPath) . '<br />';
$severity = ContextualFeedbackSeverity::ERROR;
} else {
$message .= sprintf($languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_directoryDoesNotExist'), $relPath);
if ($requirementLevel == 0) {
$message .= ' ' . $languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_directoryShouldAlsoBeWritable');
}
$message .= '<br />';
if ($severity->value < ContextualFeedbackSeverity::WARNING->value) {
$value = $languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_nonExistingDirectory');
$severity = ContextualFeedbackSeverity::WARNING;
}
}
} else {
if (!is_writable($path)) {
switch ($requirementLevel) {
case 0:
$message .= sprintf(
$languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_directoryShouldBeWritable'),
$path
) . '<br />';
if ($severity->value < ContextualFeedbackSeverity::WARNING->value) {
$value = $languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_recommendedWritableDirectory');
$severity = ContextualFeedbackSeverity::WARNING;
}
break;
case 2:
$value = $languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_requiredWritableDirectory');
$message .= sprintf(
$languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_directoryMustBeWritable'),
$path
) . '<br />';
$severity = ContextualFeedbackSeverity::ERROR;
break;
default:
}
}
}
}
return new Status($languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_fileSystem'), $value, $message, $severity);
}
/**
* Returns all incomplete update wizards.
*
* Fetches all wizards that are not marked "done" in the registry and filters out
* the ones that should not be rendered (= no upgrade required).
*/
private function getIncompleteWizards(): array
{
$incompleteWizards = $this->upgradeWizardsService->getUpgradeWizardsList();
$incompleteWizards = array_filter(
$incompleteWizards,
static function ($wizard) {
return $wizard['shouldRenderWizard'];
}
);
return $incompleteWizards;
}
/**
* Checks if there are still updates to perform
*
* @return Status Represents whether the installation is completely updated yet
*/
private function getRemainingUpdatesStatus(): Status
{
$languageService = $this->getLanguageService();
$value = $languageService->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_updateComplete');
$message = '';
$severity = ContextualFeedbackSeverity::OK;
// check if there are update wizards left to perform
$incompleteWizards = $this->getIncompleteWizards();
if (count($incompleteWizards)) {
// At least one incomplete wizard was found
$value = $languageService->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_updateIncomplete');
$severity = ContextualFeedbackSeverity::WARNING;
$url = (string)$this->uriBuilder->buildUriFromRoute('system_upgrade');
$message = sprintf($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.install_update'), '<a href="' . htmlspecialchars($url) . '">', '</a>');
}
return new Status($languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_remainingUpdates'), $value, $message, $severity);
}
/**
* Checks if there is a new minor TYPO3 version to update to.
*
* @return Status Represents whether there is a new version available online
*/
private function getNewVersionStatus(): Status
{
$typoVersion = GeneralUtility::makeInstance(Typo3Version::class);
$languageService = $this->getLanguageService();
$coreVersionService = GeneralUtility::makeInstance(CoreVersionService::class);
// No updates for development versions
if (!$coreVersionService->isInstalledVersionAReleasedVersion()) {
return new Status('TYPO3', $typoVersion->getVersion(), $languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_isDevelopmentVersion'), ContextualFeedbackSeverity::NOTICE);
}
try {
$versionMaintenanceWindow = $coreVersionService->getMaintenanceWindow();
} catch (RemoteFetchException $remoteFetchException) {
return new Status(
'TYPO3',
$typoVersion->getVersion(),
$languageService->sL(
'LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_remoteFetchException'
),
ContextualFeedbackSeverity::NOTICE
);
}
if (!$versionMaintenanceWindow->isSupportedByCommunity() && !$versionMaintenanceWindow->isSupportedByElts()) {
// Version is not maintained
$message = $languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_versionOutdated');
$status = ContextualFeedbackSeverity::ERROR;
} else {
$message = '';
$status = ContextualFeedbackSeverity::OK;
// There is an update available
$availableReleases = [];
$latestRelease = $coreVersionService->getYoungestPatchRelease();
$isCurrentVersionElts = $coreVersionService->isCurrentInstalledVersionElts();
if ($coreVersionService->isPatchReleaseSuitableForUpdate($latestRelease)) {
$availableReleases[] = $latestRelease;
}
if (!$versionMaintenanceWindow->isSupportedByCommunity()) {
if ($latestRelease->isElts()) {
$latestCommunityDrivenRelease = $coreVersionService->getYoungestCommunityPatchRelease();
if ($coreVersionService->isPatchReleaseSuitableForUpdate($latestCommunityDrivenRelease)) {
$availableReleases[] = $latestCommunityDrivenRelease;
}
} elseif (!$isCurrentVersionElts) {
// Inform user about ELTS being available soon if:
// - regular support ran out
// - the current installed version is no ELTS
// - no ELTS update was released, yet
$message = sprintf(
$languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_elts_information'),
$typoVersion->getVersion(),
'<a href="https://typo3.com/elts" target="_blank" rel="noopener">https://typo3.com/elts</a>'
);
$status = ContextualFeedbackSeverity::WARNING;
}
}
if ($availableReleases !== []) {
$messages = [];
$status = ContextualFeedbackSeverity::WARNING;
foreach ($availableReleases as $availableRelease) {
$versionString = $availableRelease->getVersion();
if ($availableRelease->isElts()) {
$versionString .= ' ELTS';
}
if ($coreVersionService->isUpdateSecurityRelevant($availableRelease)) {
$status = ContextualFeedbackSeverity::ERROR;
$updateMessage = sprintf($languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_newVersionSecurityRelevant'), $versionString);
} else {
$updateMessage = sprintf($languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_newVersion'), $versionString);
}
if ($availableRelease->isElts()) {
if ($isCurrentVersionElts) {
$updateMessage .= ' ' . sprintf(
$languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_elts_download'),
'<a href="https://my.typo3.org" target="_blank" rel="noopener">my.typo3.org</a>'
);
} else {
$updateMessage .= ' ' . sprintf(
$languageService->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_elts_subscribe'),
$coreVersionService->getInstalledVersion(),
'<a href="https://typo3.com/elts" target="_blank" rel="noopener">https://typo3.com/elts</a>'
);
}
}
$messages[] = $updateMessage;
}
$message = $this->wrapList($messages, count($messages) > 1 ? self::WRAP_NESTED : self::WRAP_FLAT);
}
}
return new Status('TYPO3', $typoVersion->getVersion(), $message, $status);
}
private function wrapList(array $items, int $style): string
{
if ($style === self::WRAP_NESTED) {
return sprintf(
'<ul>%s</ul>',
implode('', $this->wrapItems($items, '<li>', '</li>'))
);
}
return sprintf(
'<p>%s</p>',
implode('', $this->wrapItems($items, '<br>', ''))
);
}
private function wrapItems(array $items, string $before, string $after): array
{
return array_map(
static function (string $item) use ($before, $after): string {
return $before . $item . $after;
},
array_filter($items)
);
}
private function getLanguageService(): LanguageService
{
return $GLOBALS['LANG'];
}
}
+118
View File
@@ -0,0 +1,118 @@
<?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\Install\Report;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Install\Service\EnableFileService;
use TYPO3\CMS\Install\SystemEnvironment\ServerResponse\ServerResponseCheck;
use TYPO3\CMS\Reports\RequestAwareStatusProviderInterface;
use TYPO3\CMS\Reports\Status;
/**
* Provides a status report of the security of the install tool
* @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
*/
final class SecurityStatusReport implements RequestAwareStatusProviderInterface
{
/**
* Compiles a collection of system status checks as a status report.
*
* @return Status[]
*/
public function getStatus(?ServerRequestInterface $request = null): array
{
if ($request !== null) {
$this->removeInstallToolEnableFilesIfRequested($request);
}
return [
'installToolProtection' => $this->getInstallToolProtectionStatus($request),
'serverResponseStatus' => GeneralUtility::makeInstance(ServerResponseCheck::class)->asStatus($request),
];
}
public function getLabel(): string
{
return 'security';
}
/**
* Checks for the existence of the ENABLE_INSTALL_TOOL file.
*
* @return Status An object representing whether ENABLE_INSTALL_TOOL exists
*/
private function getInstallToolProtectionStatus(?ServerRequestInterface $request): Status
{
$enableInstallToolFile = EnableFileService::getBestLocationForInstallToolEnableFile();
// @todo: Note $this->getLanguageService() is declared to allow null. Calling ->sL() may fatal?!
$value = $this->getLanguageService()->sL('LLL:EXT:reports/Resources/Private/Language/locallang_reports.xlf:status_disabled');
$message = '';
$severity = ContextualFeedbackSeverity::OK;
if (EnableFileService::installToolEnableFileExists()) {
if (EnableFileService::isInstallToolEnableFilePermanent()) {
$severity = ContextualFeedbackSeverity::WARNING;
$disableInstallToolUrl = $request?->getAttribute('normalizedParams')?->getRequestUrl() . '&adminCmd=remove_ENABLE_INSTALL_TOOL';
$value = $this->getLanguageService()->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_enabledPermanently');
$message = sprintf(
$this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.install_enabled'),
'<code style="white-space: nowrap;">' . $enableInstallToolFile . '</code>'
);
$message .= ' <a href="' . htmlspecialchars($disableInstallToolUrl) . '">'
. $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.install_enabled_cmd') . '</a>';
} else {
if (EnableFileService::installToolEnableFileLifetimeExpired()) {
EnableFileService::removeInstallToolEnableFile();
} else {
$severity = ContextualFeedbackSeverity::NOTICE;
$disableInstallToolUrl = $request?->getAttribute('normalizedParams')?->getRequestUrl() . '&adminCmd=remove_ENABLE_INSTALL_TOOL';
$value = $this->getLanguageService()->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_enabledTemporarily');
$message = sprintf(
$this->getLanguageService()->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_installEnabledTemporarily'),
'<code style="white-space: nowrap;">' . $enableInstallToolFile . '</code>',
floor((@filemtime($enableInstallToolFile) + EnableFileService::INSTALL_TOOL_ENABLE_FILE_LIFETIME - time()) / 60)
);
$message .= ' <a href="' . htmlspecialchars($disableInstallToolUrl) . '">'
. $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.install_enabled_cmd') . '</a>';
}
}
}
return new Status(
$this->getLanguageService()->sL('LLL:EXT:install/Resources/Private/Language/Report/locallang.xlf:status_installTool'),
$value,
$message,
$severity
);
}
private function removeInstallToolEnableFilesIfRequested(ServerRequestInterface $request): void
{
// @todo: This should of course be a POST-only call! No idea how, but it should be.
// Also, the EnableFileService is pretty ugly nowadays, since it can handle
// multiple file locations, but does not reflect this in its methods properly.
// Thankfully, EnableFileService is @internal, so all this could be cleaned up
// without being breaking ...
if (($request->getQueryParams()['adminCmd'] ?? '') === 'remove_ENABLE_INSTALL_TOOL') {
EnableFileService::removeInstallToolEnableFile();
}
}
private function getLanguageService(): ?LanguageService
{
return $GLOBALS['LANG'] ?? null;
}
}