Commit 6b1bbee7 authored by Georg Ringer's avatar Georg Ringer Committed by Benni Mack
Browse files

[!!!][FEATURE] Automatically register reports and status via service configuration

Reports and Status of the reports module are now automatically tagged
and registered, based on the implemented `ReportInterface` and
`StatusProviderInterface`.

The previous registration via
`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['reports']` has been removed.

Additionally, to be able to use autoconfiguration, the interfaces have
been extended by additional methods.

Resolves: #97320
Releases: main
Change-Id: I5f640471bad8125267d1537bab3ab6b5a02baf7e
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/74201

Tested-by: Nikita Hovratov's avatarNikita Hovratov <nikita.h@live.de>
Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Stefan Bürk's avatarStefan Bürk <stefan@buerk.tech>
Tested-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Reviewed-by: Nikita Hovratov's avatarNikita Hovratov <nikita.h@live.de>
Reviewed-by: Stefan Bürk's avatarStefan Bürk <stefan@buerk.tech>
Reviewed-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
parent bbc8676b
.. include:: /Includes.rst.txt
=======================================================================
Breaking: #97320 - Register Report and Status via Service Configuration
=======================================================================
See :issue:`97320`
Description
===========
The `reports` and `status` in EXT:reports are now registered via service
configuration, see the :doc:`feature changelog <Feature-97320-NewRegistrationForReportsAndStatus>`.
Therefore the registration via
:php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['reports']`
has been removed.
Additionally, to be able to use autoconfiguration, the following interfaces have been extended:
- :php:`TYPO3\CMS\Reports\ReportInterface`: :php:`getIdentifier`, :php:`getIconIdentifier`, :php:`getTitle`, :php:`getDescription`
- :php:`TYPO3\CMS\Reports\StatusProviderInterface`: :php:`getLabel`
Impact
======
Registration of custom `reports` via :php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['reports']`
are not evaluated anymore.
Registration of custom `status` via :php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['reports']['tx_reports']['status']['providers']`
are not evaluated anymore.
:php:`ReportInterface` and :php:`StatusProviderInterface`: are extended by the mentioned methods. If the required methods are not implemented it will lead to fatal errors.
Affected Installations
======================
All TYPO3 installations using the old registration.
All TYPO3 installations with custom `reports`, not implementing :php:`public function getIdentifier()`,
:php:`public function getIconIdentifier()`, :php:`public function getTitle()`, :php:`public function getDescription()`
All TYPO3 installations with custom `status`, not implementing
:php:`public function getLabel()`
Migration
=========
By implementing the required methods of the interfaces, the custom reports are fully backwards compatible.
If TYPO3 v12+ is the only supported version, the configuration :php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['reports']` from the :file:`ext_localconf.php` file can be removed as well.
Report
------
If :yaml:`autoconfigure` is not enabled in your :file:`Configuration/Services.(yaml|php)`,
add the tag :yaml:`reports.report` manually to your `reports` service.
.. code-block:: yaml
Vendor\Extension\Report\MyReport:
tags:
- name: reports.report
The old registration can be removed, if support for TYPO3 v11 or lower is not
necessary.
.. code-block:: php
// Before in ext_localconf.php
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['reports']['extension']['general'] = [
'title' => 'LLL:EXT:extension/Resources/Private/Language/locallang.xlf:title',
'description' => 'LLL:EXT:extension/Resources/Private/Language/locallang.xlf:description',
'icon' => 'EXT:extension/Resources/Public/Icons/Extension.svg',
'report' => \Vendor\Extension\Report::class
];
Additionally, make sure to implement all methods of :php:`TYPO3\CMS\Reports\ReportInterface`.
.. code-block:: php
// Changes for the report
class Report implements ReportInterface
{
public function getReport(): string
{
return 'Full report';
}
public function getIdentifier(): string
{
return 'general';
}
public function getTitle(): string
{
return 'LLL:EXT:extension/Resources/Private/Language/locallang.xlf:title';
}
public function getDescription(): string
{
return 'LLL:EXT:extension/Resources/Private/Language/locallang.xlf:description';
}
public function getIconIdentifier(): string
{
return 'module-reports';
}
}
Refer to the :doc:`Icon API <Feature-94692-RegisteringIconsViaServiceContainer>`
on how to register the icon.
Status
------
If :yaml:`autoconfigure` is not enabled in your :file:`Configuration/Services.(yaml|php)`,
add the tag :yaml:`reports.status` manually to your `status` service.
.. code-block:: yaml
Vendor\Extension\Status\MyStatus:
tags:
- name: reports.report
The old registration can be removed, if support for TYPO3 v11 or lower is not
necessary.
.. code-block:: php
// Before in ext_localconf.php
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['reports']['tx_reports']['status']['providers']['label'] = [
\Vendor\Extension\Status::class,
];
Additionally, make sure to implement all methods of :php:`TYPO3\CMS\Reports\StatusProviderInterface`.
.. code-block:: php
// Changes for the Status
class Status implements StatusProviderInterface
{
public function getStatus(): array
{
return [];
}
public function getLabel(): string
{
return 'label';
}
}
.. index:: Backend, LocalConfiguration, PHP-API, FullyScanned, ext:reports
.. include:: /Includes.rst.txt
=========================================================
Feature: #97320 - New registration for reports and status
=========================================================
See :issue:`97320`
Description
===========
The system extension `reports` provides the possibility to render various reports.
The most prominent and (only one) provided by the TYPO3 core is the one called `Status`.
The Status Report itself is extendable and shows status like a system environment check
and status of the used extensions.
Reports
-------
As all `reports` have to implement the :php:`ReportInterface` this fact is now
used to automatically register the `report`, based on the interface,
if :yaml:`autoconfigure` is enabled in :file:`Services.yaml`. Alternatively,
one can manually tag a custom `report` with the
:yaml:`reports.report` tag (See section "Migration" in the
:doc:`breaking changelog <Breaking-97320-RegisterReportAndStatusViaServiceConfiguration>`).
Due to the autoconfiguration, the following methods have to be implemented:
- :php:`getIdentifier`
- :php:`getIconIdentifier`
- :php:`getTitle`
- :php:`getDescription`
Status
------
As all `status` have to implement the :php:`StatusProviderInterface` this fact is now
used to automatically register the `status`, based on the interface,
if :yaml:`autoconfigure` is enabled in :file:`Services.yaml`. Alternatively,
one can manually tag a custom `report` with the
:yaml:`reports.status` tag (See section "Migration" in the
:doc:`breaking changelog <./Breaking-97320-RegisterReportAndStatusViaServiceConfiguration>`).
Due to the autoconfiguration, the label has to be provided by the
class directly, using the now required :php:`getLabel()` method.
Impact
======
`reports` and `status` are now automatically registered through the service
configuration, based on the implemented interface.
.. index:: Backend, PHP-API, ext:reports
......@@ -108,6 +108,11 @@ class ExtensionComposerStatus implements RequestAwareStatusProviderInterface
return $status;
}
public function getLabel(): string
{
return 'Composer Manifest';
}
protected function getLanguageService(): LanguageService
{
return $GLOBALS['LANG'];
......
......@@ -77,7 +77,7 @@ class ExtensionStatus implements StatusProviderInterface
*
* @return Status[] List of statuses
*/
public function getStatus()
public function getStatus(): array
{
$status = [];
$status['mainRepositoryStatus'] = $this->getMainRepositoryStatus();
......@@ -91,6 +91,11 @@ class ExtensionStatus implements StatusProviderInterface
return $status;
}
public function getLabel(): string
{
return 'Extension Manager';
}
/**
* Check main repository status: existence, has extensions, last update younger than 7 days
*
......
......@@ -54,6 +54,12 @@ services:
TYPO3\CMS\Extensionmanager\Report\ExtensionComposerStatus:
public: true
tags:
- name: reports.status
TYPO3\CMS\Extensionmanager\Report\ExtensionStatus:
tags:
- name: reports.status
TYPO3\CMS\Extensionmanager\Domain\Repository\BulkExtensionRepositoryWriter:
public: true
......
......@@ -4,8 +4,6 @@ declare(strict_types=1);
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extensionmanager\Report\ExtensionComposerStatus;
use TYPO3\CMS\Extensionmanager\Report\ExtensionStatus;
use TYPO3\CMS\Extensionmanager\Task\UpdateExtensionListTask;
defined('TYPO3') or die();
......@@ -21,11 +19,3 @@ if (!(bool)GeneralUtility::makeInstance(
'additionalFields' => '',
];
}
// Register extension status report system
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['reports']['tx_reports']['status']['providers']['Extension Manager'][] =
ExtensionStatus::class;
// Register extension composer status report
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['reports']['tx_reports']['status']['providers']['Composer Manifest'][] =
ExtensionComposerStatus::class;
......@@ -15,6 +15,7 @@
namespace TYPO3\CMS\Install\Report;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Messaging\FlashMessage;
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
......@@ -37,11 +38,19 @@ class EnvironmentStatusReport implements StatusProviderInterface, ExtendedStatus
*
* @return Status[]
*/
public function getStatus()
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
*
......@@ -49,6 +58,9 @@ class EnvironmentStatusReport implements StatusProviderInterface, ExtendedStatus
*/
public function getDetailedStatus()
{
if (Environment::isCli()) {
return [];
}
return $this->getStatusInternal(true);
}
......
......@@ -47,7 +47,7 @@ class InstallStatusReport implements StatusProviderInterface
*
* @return Status[]
*/
public function getStatus()
public function getStatus(): array
{
return [
'FileSystem' => $this->getFileSystemStatus(),
......@@ -56,6 +56,11 @@ class InstallStatusReport implements StatusProviderInterface
];
}
public function getLabel(): string
{
return 'typo3';
}
/**
* Checks for several directories being writable.
*
......
......@@ -34,7 +34,7 @@ class SecurityStatusReport implements StatusProviderInterface
*
* @return Status[]
*/
public function getStatus()
public function getStatus(): array
{
$this->executeAdminCommand();
return [
......@@ -43,6 +43,11 @@ class SecurityStatusReport implements StatusProviderInterface
];
}
public function getLabel(): string
{
return 'security';
}
/**
* Checks for the existence of the ENABLE_INSTALL_TOOL file.
*
......
......@@ -713,4 +713,10 @@ return [
'Feature-97450-PSR-14EventForModifyingVersionDifferences.rst',
],
],
'$GLOBALS[\'TYPO3_CONF_VARS\'][\'SC_OPTIONS\'][\'reports\']' => [
'restFiles' => [
'Breaking-97320-RegisterReportAndStatusViaServiceConfiguration.rst',
'Feature-97320-NewRegistrationForReportsAndStatus.rst',
],
],
];
......@@ -17,3 +17,15 @@ services:
TYPO3\CMS\Install\Service\Typo3tempFileService:
public: true
TYPO3\CMS\Install\Report\EnvironmentStatusReport:
tags:
- name: reports.status
TYPO3\CMS\Install\Report\InstallStatusReport:
tags:
- name: reports.status
TYPO3\CMS\Install\Report\SecurityStatusReport:
tags:
- name: reports.status
......@@ -2,10 +2,6 @@
declare(strict_types=1);
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Install\Report\EnvironmentStatusReport;
use TYPO3\CMS\Install\Report\InstallStatusReport;
use TYPO3\CMS\Install\Report\SecurityStatusReport;
use TYPO3\CMS\Install\Updates\BackendGroupsExplicitAllowDenyMigration;
use TYPO3\CMS\Install\Updates\BackendUserLanguageMigration;
use TYPO3\CMS\Install\Updates\CollectionsExtractionUpdate;
......@@ -32,12 +28,3 @@ $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['sysLogChanne
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['feLoginModeExtension'] = FeLoginModeExtractionUpdate::class;
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['sysLogSerialization'] = SysLogSerializationUpdate::class;
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['backendGroupsExplicitAllowDenyMigration'] = BackendGroupsExplicitAllowDenyMigration::class;
// Register report module additions
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['reports']['tx_reports']['status']['providers']['typo3'][] = InstallStatusReport::class;
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['reports']['tx_reports']['status']['providers']['security'][] = SecurityStatusReport::class;
// Only add the environment status report if not in CLI mode
if (!Environment::isCli()) {
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['reports']['tx_reports']['status']['providers']['system'][] = EnvironmentStatusReport::class;
}
......@@ -38,7 +38,7 @@ class RedirectStatus implements StatusProviderInterface, RequestAwareStatusProvi
/**
* Determines the status of the FAL index.
*
* @return array List of statuses
* @return Status[]
*/
public function getStatus(ServerRequestInterface $request = null): array
{
......@@ -47,6 +47,11 @@ class RedirectStatus implements StatusProviderInterface, RequestAwareStatusProvi
];
}
public function getLabel(): string
{
return 'LLL:EXT:redirects/Resources/Private/Language/locallang_reports.xlf:statusProvider';
}
protected function getConflictingRedirects(ServerRequestInterface $request): Status
{
$value = $this->getLanguageService()->sL('LLL:EXT:redirects/Resources/Private/Language/locallang_reports.xlf:status.conflictingRedirects.none');
......
......@@ -3,14 +3,12 @@
declare(strict_types=1);
use TYPO3\CMS\Backend\Form\FormDataProvider\TcaInputPlaceholders;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Redirects\Evaluation\SourceHost;
use TYPO3\CMS\Redirects\FormDataProvider\ValuePickerItemDataProvider;
use TYPO3\CMS\Redirects\Hooks\BackendControllerHook;
use TYPO3\CMS\Redirects\Hooks\DataHandlerCacheFlushingHook;
use TYPO3\CMS\Redirects\Hooks\DataHandlerSlugUpdateHook;
use TYPO3\CMS\Redirects\Hooks\DispatchNotificationHook;
use TYPO3\CMS\Redirects\Report\Status\RedirectStatus;
defined('TYPO3') or die();
......@@ -34,7 +32,3 @@ $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/backend.php']['constructPostPro
// Register update signal to send delayed notifications
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_befunc.php']['updateSignalHook']['redirects:slugChanged'] = DispatchNotificationHook::class . '->dispatchNotification';
if (ExtensionManagementUtility::isLoaded('reports')) {
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['reports']['tx_reports']['status']['providers']['LLL:EXT:redirects/Resources/Private/Language/locallang_reports.xlf:statusProvider'][] = RedirectStatus::class;
}
......@@ -25,9 +25,7 @@ use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Imaging\IconRegistry;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
use TYPO3\CMS\Reports\ReportInterface;
use TYPO3\CMS\Reports\Registry\ReportRegistry;
use TYPO3\CMS\Reports\RequestAwareReportInterface;
/**
......@@ -37,18 +35,12 @@ use TYPO3\CMS\Reports\RequestAwareReportInterface;
*/
class ReportController
{
protected UriBuilder $uriBuilder;
protected ModuleTemplateFactory $moduleTemplateFactory;
protected IconRegistry $iconRegistry;
public function __construct(
UriBuilder $uriBuilder,
ModuleTemplateFactory $moduleTemplateFactory,
IconRegistry $iconRegistry
protected readonly UriBuilder $uriBuilder,
protected readonly ModuleTemplateFactory $moduleTemplateFactory,
protected readonly IconRegistry $iconRegistry,
protected readonly ReportRegistry $reportRegistry
) {
$this->uriBuilder = $uriBuilder;
$this->moduleTemplateFactory = $moduleTemplateFactory;
$this->iconRegistry = $iconRegistry;
}
/**
......@@ -56,12 +48,12 @@ class ReportController
*/
public function handleRequest(ServerRequestInterface $request): ResponseInterface
{
$validRegisteredReports = $this->getValidReportCombinations();
$allReports = $this->reportRegistry->getReports();
$queryParams = $request->getQueryParams();
$backendUserUc = $this->getBackendUser()->uc['reports']['selection'] ?? [];
// This can be 'index' for "overview", or 'detail' to render one specific report specified by 'extension' and 'report'
// This can be 'index' for "overview", or 'detail' to render one specific report specified by 'report'
$mainView = $queryParams['action'] ?? $backendUserUc['action'] ?? 'detail';
if (count($validRegisteredReports) === 0 || $mainView === 'index') {
if ($mainView === 'index' || count($allReports) === 0) {
// Render overview directly if there are no reports at all to have some info box about that,
// or if that view has been requested explicitly.
$this->updateBackendUserUc('index');
......@@ -69,22 +61,19 @@ class ReportController
}
// For fallbacks if backendUser->uc() pointer is invalid or called first time.
$firstReport = $validRegisteredReports[0];
$extension = $request->getQueryParams()['extension'] ?? $backendUserUc['extension'] ?? $firstReport['extension'];
$report = $request->getQueryParams()['report'] ?? $backendUserUc['report'] ?? $firstReport['report'];
if (!in_array(['extension' => $extension, 'report' => $report], $validRegisteredReports)) {
$firstReportIdentifier = array_keys($allReports)[0];
$reportIdentifier = $request->getQueryParams()['report'] ?? $backendUserUc['report'] ?? $firstReportIdentifier;
if (!$this->reportRegistry->hasReport($reportIdentifier)) {
// If a selected report has been removed meanwhile (e.g. extension deleted), fall back to first one.
$extension = $firstReport['extension'];
$report = $firstReport['report'];
$reportIdentifier = $firstReportIdentifier;
}
if (($backendUserUc['action'] ?? '') !== 'detail'
|| ($backendUserUc['extension'] ?? '') !== $extension
|| ($backendUserUc['report'] ?? '') !== $report
|| ($backendUserUc['report'] ?? '') !== $reportIdentifier
) {
// Update uc if view changed to render same view on next call.
$this->updateBackendUserUc('detail', $extension, $report);
$this->updateBackendUserUc('detail', $reportIdentifier);
}
return $this->detailAction($request, $extension, $report);
return $this->detailAction($request, $reportIdentifier);
}
/**
......@@ -93,20 +82,10 @@ class ReportController
protected function indexAction(ServerRequestInterface $request): ResponseInterface
{
$languageService = $this->getLanguageService();
$registeredReports = $this->getRegisteredReportsArray();
foreach ($registeredReports as $extension => $reportModules) {
foreach ($reportModules as $module => $configuration) {
$icon = $configuration['icon'] ?? 'EXT:reports/Resources/Public/Icons/Extension.png';
$isRegisteredIcon = $registeredReports[$extension][$module]['isIconIdentifier'] = $this->iconRegistry->isRegistered($icon);
if (!$isRegisteredIcon) {
// @todo: Deprecate icons from non extension resources
$registeredReports[$extension][$module]['icon'] = PathUtility::isExtensionPath($icon) ? PathUtility::getPublicResourceWebPath($icon) : PathUtility::getAbsoluteWebPath($icon);
}
}
}
$view = $this->moduleTemplateFactory->create($request);
$view->assignMultiple([
'reports' => $registeredReports,
'reports' => $this->reportRegistry->getReports(),
]);
$view->setTitle(
$languageService->sL('LLL:EXT:reports/Resources/Private/Language/locallang.xlf:mlang_tabs_tab'),
......@@ -124,42 +103,36 @@ class ReportController
/**
* Render a single report.
*/
protected function detailAction(ServerRequestInterface $request, string $extension, string $report): ResponseInterface
protected function detailAction(ServerRequestInterface $request, string $report): ResponseInterface
{
$languageService = $this->getLanguageService();
$registeredReports = $this->getRegisteredReportsArray();
$reportClass = $registeredReports[$extension][$report]['report'];
$reportInstance = GeneralUtility::makeInstance($reportClass);
if ($reportInstance instanceof RequestAwareReportInterface) {
$content = $reportInstance->getReport($request);
} else {
$content = $reportInstance->getReport();
}
$reportInstance = $this->reportRegistry->getReport($report);
$content = $reportInstance instanceof RequestAwareReportInterface ? $reportInstance->getReport($request) : $reportInstance->getReport();
$view = $this->moduleTemplateFactory->create($request);
$view->assignMultiple([
'content' => $content,
'report' => $registeredReports[$extension][$report],
'report' => $reportInstance,
]);
$view->setTitle(
$languageService->sL('LLL:EXT:reports/Resources/Private/Language/locallang.xlf:mlang_tabs_tab'),