Commit 1eeedc59 authored by Susanne Moog's avatar Susanne Moog Committed by Benni Mack
Browse files

[TASK] Decouple adminPanel from frontend

The admin panel has been extracted into an own extension. To enable
users to de-activate the admin panel completely, the hard coupling
between the extension and other parts of the core had to be resolved.

With this change, the initialization of both adminPanel and feedit
were moved into PSR-15 middlewares. Additionally all parameters
related to the adminPanel were removed from the FrontendBackend-
UserAuthentication.

As feedit is tigthly coupled with the adminPanel some changes had
to be made to its initialization, too.

The flow of the adminPanel initialization and rendering were
streamlined to allow modules to make use of the request object.

Due to these changes in the control flow of the application the
two existing tests were removed and new tests will be rewritten
once the API is declared as stable.

Releases: master
Resolves: #84641
Change-Id: I72beefde0d792d3f4295c45aa27204c817d2de7a
Reviewed-on: https://review.typo3.org/56558

Tested-by: default avatarTYPO3com <no-reply@typo3.com>
Reviewed-by: Andreas Fernandez's avatarAndreas Fernandez <a.fernandez@scripting-base.de>
Tested-by: Andreas Fernandez's avatarAndreas Fernandez <a.fernandez@scripting-base.de>
Reviewed-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Tested-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
parent 41a2a691
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Adminpanel\Controller;
/*
* 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!
*/
use TYPO3\CMS\Adminpanel\Modules\AdminPanelModuleInterface;
use TYPO3\CMS\Adminpanel\View\AdminPanelView;
use TYPO3\CMS\Backend\FrontendBackendUserAuthentication;
use TYPO3\CMS\Core\Cache\CacheManager;
use TYPO3\CMS\Core\Http\ServerRequest;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Service\DependencyOrderingService;
use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
/**
* Main controller for the admin panel
*
* @internal
*/
class MainController implements SingletonInterface
{
/**
* @var array<AdminPanelModuleInterface>
*/
protected $modules = [];
/**
* Initializes settings for the admin panel.
*
* @param \TYPO3\CMS\Core\Http\ServerRequest $request
*/
public function initialize(ServerRequest $request): void
{
$this->validateSortAndInitializeModules();
$this->saveConfiguration();
foreach ($this->modules as $module) {
if ($module->isEnabled()) {
$module->initializeModule($request);
}
}
}
/**
* Renders the panel - Is currently called via RenderHook in postProcessOutput
*
* @todo Still uses the legacy AdminpanelView and should be rewritten to fluid
*
* @return string
*/
public function render(): string
{
// handling via legacy functions
$adminPanelView = GeneralUtility::makeInstance(AdminPanelView::class);
$adminPanelView->setModules($this->modules);
return $adminPanelView->display();
}
/**
* Save admin panel configuration to backend user UC
*/
protected function saveConfiguration(): void
{
$input = GeneralUtility::_GP('TSFE_ADMIN_PANEL');
$beUser = $this->getBackendUser();
if (is_array($input)) {
// Setting
$beUser->uc['TSFE_adminConfig'] = array_merge(
!is_array($beUser->uc['TSFE_adminConfig']) ? [] : $beUser->uc['TSFE_adminConfig'],
$input
);
unset($beUser->uc['TSFE_adminConfig']['action']);
foreach ($this->modules as $module) {
if ($module->isEnabled() && $module->isOpen()) {
$module->onSubmit($input);
}
}
// Saving
$beUser->writeUC();
// Flush fluid template cache
$cacheManager = new CacheManager();
$cacheManager->setCacheConfigurations($GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']);
$cacheManager->getCache('fluid_template')->flush();
}
}
/**
* Validates, sorts and initiates the registered modules
*
* @throws \RuntimeException
*/
protected function validateSortAndInitializeModules(): void
{
$modules = $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules'] ?? [];
if (empty($modules)) {
return;
}
foreach ($modules as $identifier => $configuration) {
if (empty($configuration) || !is_array($configuration)) {
throw new \RuntimeException(
'Missing configuration for module "' . $identifier . '".',
1519490105
);
}
if (!is_string($configuration['module']) ||
empty($configuration['module']) ||
!class_exists($configuration['module']) ||
!is_subclass_of(
$configuration['module'],
AdminPanelModuleInterface::class
)
) {
throw new \RuntimeException(
'The module "' .
$identifier .
'" defines an invalid module class. Ensure the class exists and implements the "' .
AdminPanelModuleInterface::class .
'".',
1519490112
);
}
}
$orderedModules = GeneralUtility::makeInstance(DependencyOrderingService::class)->orderByDependencies(
$modules
);
foreach ($orderedModules as $module) {
$this->modules[] = GeneralUtility::makeInstance($module['module']);
}
}
/**
* Returns LanguageService
*
* @return LanguageService
*/
protected function getLanguageService(): LanguageService
{
return $GLOBALS['LANG'];
}
/**
* Returns the current BE user.
*
* @return FrontendBackendUserAuthentication
*/
protected function getBackendUser(): FrontendBackendUserAuthentication
{
return $GLOBALS['BE_USER'];
}
/**
* @return TypoScriptFrontendController
*/
protected function getTypoScriptFrontendController(): TypoScriptFrontendController
{
return $GLOBALS['TSFE'];
}
}
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Adminpanel\Hooks;
/*
* 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!
*/
use TYPO3\CMS\Adminpanel\Controller\MainController;
use TYPO3\CMS\Backend\FrontendBackendUserAuthentication;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
/**
* Hook used to postProcess output - renders the admin panel
*/
class RenderHook
{
/**
* PostProcess output hook to render the admin panel
* We use a hook this late in the project to make sure all data is collected and can be displayed
*
* As the main content is already rendered, we use a string replace on the content to append the adminPanel
* to the HTML body.
*
* @param array $params
* @param TypoScriptFrontendController $pObj
*/
public function renderAdminPanel(array $params, TypoScriptFrontendController $pObj)
{
if ($pObj->isBackendUserLoggedIn() &&
$GLOBALS['BE_USER'] instanceof FrontendBackendUserAuthentication &&
(
!$GLOBALS['BE_USER']->extAdminConfig['hide'] && $pObj->config['config']['admPanel']
)
) {
$mainController = GeneralUtility::makeInstance(MainController::class);
$pObj->content = str_ireplace(
'</body>',
$mainController->render() . '</body>',
$pObj->content
);
}
}
}
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Adminpanel\Middleware;
/*
* 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!
*/
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use TYPO3\CMS\Adminpanel\Controller\MainController;
use TYPO3\CMS\Adminpanel\View\AdminPanelView;
use TYPO3\CMS\Backend\FrontendBackendUserAuthentication;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* PSR-15 Middleware to initialize the admin panel
*/
class AdminPanelInitiator implements MiddlewareInterface
{
/**
* Initialize the adminPanel if
* - backend user is logged in
* - at least one adminpanel functionality is enabled
*
* @param ServerRequestInterface $request
* @param RequestHandlerInterface $handler
* @return ResponseInterface
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
if ($GLOBALS['BE_USER'] instanceof FrontendBackendUserAuthentication) {
// Initialize admin panel since simulation settings are required here
$beUser = $GLOBALS['BE_USER'];
// set legacy config
$beUser->extAdminConfig = $beUser->getTSConfigProp('admPanel');
$adminPanelConfiguration = $beUser->extAdminConfig;
if (isset($adminPanelConfiguration['enable.'])) {
foreach ($adminPanelConfiguration['enable.'] as $value) {
if ($value) {
$adminPanelController = GeneralUtility::makeInstance(
MainController::class
);
$adminPanelController->initialize($request);
// legacy handling
$beUser->adminPanel = GeneralUtility::makeInstance(AdminPanelView::class);
$beUser->extAdmEnabled = true;
break;
}
}
}
}
return $handler->handle($request);
}
}
<?php
declare(strict_types=1);
declare(strict_types = 1);
namespace TYPO3\CMS\Adminpanel\Modules;
......@@ -17,6 +17,7 @@ namespace TYPO3\CMS\Adminpanel\Modules;
*/
use TYPO3\CMS\Backend\FrontendBackendUserAuthentication;
use TYPO3\CMS\Core\Http\ServerRequest;
use TYPO3\CMS\Core\Localization\LanguageService;
/**
......@@ -31,6 +32,16 @@ abstract class AbstractModule implements AdminPanelModuleInterface
*/
protected $extResources = 'EXT:adminpanel/Resources/Private';
/**
* @var array
*/
protected $mainConfiguration;
public function __construct()
{
$this->mainConfiguration = $this->getBackendUser()->getTSConfigProp('admPanel');
}
/**
* @inheritdoc
*/
......@@ -42,7 +53,7 @@ abstract class AbstractModule implements AdminPanelModuleInterface
/**
* @inheritdoc
*/
public function initializeModule(): void
public function initializeModule(ServerRequest $request): void
{
}
......@@ -59,8 +70,8 @@ abstract class AbstractModule implements AdminPanelModuleInterface
{
$identifier = $this->getIdentifier();
$result = $this->isEnabledViaTsConfig();
if ($this->getBackendUser()->extAdminConfig['override.'][$identifier] ?? false) {
$result = (bool)$this->getBackendUser()->extAdminConfig['override.'][$identifier];
if ($this->mainConfiguration['override.'][$identifier] ?? false) {
$result = (bool)$this->mainConfiguration['override.'][$identifier];
}
return $result;
}
......@@ -116,7 +127,7 @@ abstract class AbstractModule implements AdminPanelModuleInterface
{
$labelStr = $this->getLanguageService()->getLL($key);
if ($convertWithHtmlspecialchars) {
$labelStr = htmlspecialchars($labelStr);
$labelStr = htmlspecialchars($labelStr, ENT_QUOTES | ENT_HTML5);
}
return $labelStr;
}
......@@ -143,8 +154,8 @@ abstract class AbstractModule implements AdminPanelModuleInterface
$beUser = $this->getBackendUser();
$identifier = $this->getIdentifier();
if ($option && isset($beUser->extAdminConfig['override.'][$identifier . '.'][$option])) {
$returnValue = $beUser->extAdminConfig['override.'][$identifier . '.'][$option];
if ($option && isset($this->mainConfiguration['override.'][$identifier . '.'][$option])) {
$returnValue = $this->mainConfiguration['override.'][$identifier . '.'][$option];
} else {
$returnValue = $beUser->uc['TSFE_adminConfig'][$identifier . '_' . $option] ?? '';
}
......@@ -171,9 +182,9 @@ abstract class AbstractModule implements AdminPanelModuleInterface
{
$result = false;
$identifier = $this->getIdentifier();
if (!empty($this->getBackendUser()->extAdminConfig['enable.']['all'])) {
if (!empty($this->mainConfiguration['enable.']['all'])) {
$result = true;
} elseif (!empty($this->getBackendUser()->extAdminConfig['enable.'][$identifier])) {
} elseif (!empty($this->mainConfiguration['enable.'][$identifier])) {
$result = true;
}
return $result;
......
<?php
declare(strict_types=1);
declare(strict_types = 1);
namespace TYPO3\CMS\Adminpanel\Modules;
......@@ -16,6 +16,8 @@ namespace TYPO3\CMS\Adminpanel\Modules;
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Core\Http\ServerRequest;
/**
* Interface for admin panel modules registered via EXTCONF
*
......@@ -23,6 +25,7 @@ namespace TYPO3\CMS\Adminpanel\Modules;
*/
interface AdminPanelModuleInterface
{
/**
* Additional JavaScript code for this module
* (you should only use vanilla JS here, as you cannot
......@@ -56,8 +59,10 @@ interface AdminPanelModuleInterface
/**
* Initialize the module - runs early in a TYPO3 request
*
* @param \TYPO3\CMS\Core\Http\ServerRequest $request
*/
public function initializeModule(): void;
public function initializeModule(ServerRequest $request): void;
/**
* Module is enabled
......
......@@ -16,6 +16,7 @@ namespace TYPO3\CMS\Adminpanel\Modules;
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Core\Http\ServerRequest;
use TYPO3\CMS\Core\Type\Bitmask\Permission;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Fluid\View\StandaloneView;
......@@ -65,7 +66,7 @@ class CacheModule extends AbstractModule
/**
* @inheritdoc
*/
public function initializeModule(): void
public function initializeModule(ServerRequest $request): void
{
if ($this->getConfigurationOption('noCache')) {
$this->getTypoScriptFrontendController()->set_no_cache('Admin Panel: No Caching', true);
......
<?php
declare(strict_types=1);
declare(strict_types = 1);
namespace TYPO3\CMS\Adminpanel\Modules;
......@@ -16,7 +16,9 @@ namespace TYPO3\CMS\Adminpanel\Modules;
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Adminpanel\Service\EditToolbarService;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Http\ServerRequest;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Fluid\View\StandaloneView;
......@@ -39,6 +41,7 @@ class EditModule extends AbstractModule
$view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($templateNameAndPath));
$view->setPartialRootPaths([$this->extResources . '/Partials']);
$editToolbarService = GeneralUtility::makeInstance(EditToolbarService::class);
$view->assignMultiple([
'feEdit' => ExtensionManagementUtility::isLoaded('feedit'),
'display' => [
......@@ -46,7 +49,7 @@ class EditModule extends AbstractModule
'fieldIcons' => $this->getConfigurationOption('displayFieldIcons'),
'displayIcons' => $this->getConfigurationOption('displayIcons'),
],
'toolbar' => $this->getBackendUser()->adminPanel->ext_makeToolBar(),
'toolbar' => $editToolbarService->createToolbar(),
'script' => [
'pageUid' => (int)$this->getTypoScriptFrontendController()->page['uid'],
'pageModule' => $this->getPageModule(),
......@@ -98,9 +101,11 @@ class EditModule extends AbstractModule
* Initialize the edit module
* Includes the frontend edit initialization
*
* @param ServerRequest $request
*
* @todo move into fe_edit (including the module)
*/
public function initializeModule(): void
public function initializeModule(ServerRequest $request): void
{
$extFeEditLoaded = ExtensionManagementUtility::isLoaded('feedit');
$typoScriptFrontend = $this->getTypoScriptFrontendController();
......
<?php
declare(strict_types=1);
declare(strict_types = 1);
namespace TYPO3\CMS\Adminpanel\Modules;
......@@ -17,6 +17,7 @@ namespace TYPO3\CMS\Adminpanel\Modules;
*/
use TYPO3\CMS\Adminpanel\Repositories\FrontendGroupsRepository;
use TYPO3\CMS\Core\Http\ServerRequest;
use TYPO3\CMS\Core\Type\Bitmask\Permission;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Fluid\View\StandaloneView;
......@@ -93,7 +94,10 @@ class PreviewModule extends AbstractModule
return $this->getLanguageService()->sL($locallangFileAndPath);
}
public function initializeModule(): void
/**
* @param ServerRequest $request
*/
public function initializeModule(ServerRequest $request): void
{
$this->initializeFrontendPreview();
if (GeneralUtility::_GP('ADMCMD_simUser')) {
......
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Adminpanel\Modules;
/*
......@@ -15,6 +16,7 @@ namespace TYPO3\CMS\Adminpanel\Modules;
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Core\Http\ServerRequest;
use TYPO3\CMS\Core\TimeTracker\TimeTracker;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Fluid\View\StandaloneView;
......@@ -74,7 +76,7 @@ class TsDebugModule extends AbstractModule
/**
* @inheritdoc
*/
public function initializeModule(): void
public function initializeModule(ServerRequest $request): void
{
$typoScriptFrontend = $this->getTypoScriptFrontendController();
$typoScriptFrontend->forceTemplateParsing = (bool)$this->getConfigurationOption('forceTemplateParsing');
......@@ -111,7 +113,6 @@ class TsDebugModule extends AbstractModule
/**
* Renders the TypoScript log as string
*
* @param $output
* @return string
*/
private function renderTypoScriptLog(): string
......
<?php
declare(strict_types=1);
declare(strict_types = 1);
namespace TYPO3\CMS\Adminpanel\Repositories;
......@@ -110,7 +110,7 @@ class FrontendGroupsRepository
/**
* Returns the current BE user.
*
* @return \TYPO3\CMS\Backend\FrontendBackendUserAuthentication
* @return FrontendBackendUserAuthentication
*/
protected function getBackendUser(): FrontendBackendUserAuthentication
{
......
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Adminpanel\Service;
use TYPO3\CMS\Backend\FrontendBackendUserAuthentication;
use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Type\Bitmask\Permission;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
/**
* Class for the Edit Toolbar
*
* @internal
*/
class EditToolbarService
{
/**
* Creates the tool bar links for the "edit" section of the Admin Panel.
*
* @return string A string containing images wrapped in <a>-tags linking them to proper functions.
*/
public function createToolbar(): string
{
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
$tsfe = $this->getTypoScriptFrontendController();
// If mod.newContentElementWizard.override is set, use that extension's create new content wizard instead:
$tsConfig = BackendUtility::getModTSconfig($tsfe->page['uid'], 'mod');