Commit 98267055 authored by Christian Kuhn's avatar Christian Kuhn
Browse files

[TASK] Deprecate ext:backend BackendTemplateView

The BackendTemplateView is a - usually Extbase backend
module related - Fluid view that adds a default backend
ModuleTemplate to render backend module content.

That implementation has a couple of drawbacks:
* Using BackendTemplateView hides a dependency to the
  request object by triggering a fallback in ModuleTemplate
  accessing $GLOBALS['TYPO3_REQUEST'].
* Using $this->view->getModuleTemplate() is pretty ugly,
  controllers should work with the ModuleTemplateFactory
  instead and retrieve an instance of ModuleTemplate using
  create($request).
* The level of indirection by having ModuleTemplate within
  the view class magically created makes controller actions
  harder to follow and understand.

With extbase Request nowadays implementing ServerRequestInterface
we can get rid of this indirection: Controllers should get an
instance of ModuleTemplateFactory injected, receive the
ModuleTemplate using ->create($request), and add further
title and doc header related details using that instance.

The patch changes ext:indexed_search, ext:extensionmanager and
ext:form to do that, and deprecates BackendTemplateView.

Change-Id: I613a560c8fc3c35343e31f397479f0c008a4a314
Resolves: #95164
Related: #92513
Related: #94428
Related: #94209
Related: #93885
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/70973

Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Tested-by: Jochen's avatarJochen <rothjochen@gmail.com>
Tested-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Reviewed-by: Jochen's avatarJochen <rothjochen@gmail.com>
Reviewed-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
parent fc7c77d8
......@@ -240,6 +240,7 @@ class ModuleTemplate
$this->pageRenderer = $pageRenderer;
$this->iconFactory = $iconFactory;
$this->flashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
// @todo: Make $request argument non-optional in v12.
$this->request = $request ?? $GLOBALS['TYPO3_REQUEST'];
$currentRoute = $this->request->getAttribute('route');
......
......@@ -23,6 +23,8 @@ use TYPO3\CMS\Fluid\View\TemplateView;
/**
* Decorates the main template view. Should be used as view if you want to use
* Fluid templates in a backend module in order to have a consistent backend.
*
* @deprecated since v11, will be removed with v12.
*/
class BackendTemplateView implements ViewInterface
{
......
.. include:: ../../Includes.txt
=====================================================
Deprecation: #95164 - ext:backend BackendTemplateView
=====================================================
See :issue:`95164`
Description
===========
To simplify and align the view part of Extbase based backend module controller code with
non-extbase based controllers, class :php:`TYPO3\CMS\Backend\View\BackendTemplateView`
has been marked as deprecated and will be removed in TYPO3 v12.
This follows the general core strategy to have document header related code
using the :php:`ModuleTemplate` class structure within controllers directly, while
Extbase views render only the main body part.
Impact
======
Extensions should switch away from using :php:`BackendTemplateView`. By hiding an
instance of :php:`ModuleTemplate` class, :php:`BackendTemplateView` basically added
a no longer needed level of indirection to code that should be located directly
within controller actions.
Together with the TYPO3 v11 requirement within Extbase controller actions to return
responses directly, combined with Extbase Request object now implementing the PSR-7
ServerRequestInterface, and with the deprecation of other doc header related Fluid
View helpers, Extbase controller action become much more obvious codewise.
Affected Installations
======================
Instances with extensions using class :php:`BackendTemplateView` are affected.
Candidates are typically Extbase based extensions that deliver backend modules.
The extension scanner will find usages as strong match.
Migration
=========
A transition away from :php:`BackendTemplateView` should be usually pretty straight:
Instead of retrieving a :php:`ModuleTemplate` instance from the view, the
:php:`ModuleTemplateFactory` should be injected and an instance retrieved using
:php:`create()`.
A typical scenario before:
.. code-block:: php
class MyController extends ActionController
{
protected $defaultViewObjectName = BackendTemplateView::class;
public function myAction(): ResponseInterface
{
$this->view->assign('someVar', 'someContent');
$moduleTemplate = $this->view->getModuleTemplate();
// Adding title, menus, buttons, etc. using $moduleTemplate ...
return $this->htmlResponse();
}
}
Dropping :php:`BackendTemplateView` leads to code similar to this:
.. code-block:: php
class MyController extends ActionController
{
protected ModuleTemplateFactory $moduleTemplateFactory;
public function __construct(
ModuleTemplateFactory $moduleTemplateFactory,
) {
$this->moduleTemplateFactory = $moduleTemplateFactory;
}
public function myAction(): ResponseInterface
{
$this->view->assign('someVar', 'someContent');
$moduleTemplate = $this->moduleTemplateFactory->create($this->request);
// Adding title, menus, buttons, etc. using $moduleTemplate ...
$moduleTemplate->setContent($this->view->render());
return $this->htmlResponse($moduleTemplate->renderContent());
}
}
.. index:: Backend, Fluid, PHP-API, FullyScanned, ext:backend
......@@ -147,7 +147,7 @@ class UpgradeCest extends AbstractCest
// Scan all available extensions
$I->click('Scan all');
$I->waitForElement('.panel-success', 20, ModalDialog::$openedModalSelector);
$I->waitForElement('.t3js-extensionscan-finished', 20, ModalDialog::$openedModalSelector);
// Wait for all flash messages to disappear
$I->waitForText('Marked not affected files', 10, self::$alertContainerSelector);
......
......@@ -15,7 +15,9 @@
namespace TYPO3\CMS\Extensionmanager\Controller;
use TYPO3\CMS\Backend\View\BackendTemplateView;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Backend\Template\ModuleTemplate;
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
use TYPO3\CMS\Core\Core\Environment;
/**
......@@ -24,19 +26,12 @@ use TYPO3\CMS\Core\Core\Environment;
*/
class AbstractModuleController extends AbstractController
{
/**
* BackendTemplateContainer
*
* @var BackendTemplateView
*/
protected $view;
protected ModuleTemplateFactory $moduleTemplateFactory;
/**
* Backend Template Container
*
* @var string
*/
protected $defaultViewObjectName = BackendTemplateView::class;
public function injectModuleTemplateFactory(ModuleTemplateFactory $moduleTemplateFactory)
{
$this->moduleTemplateFactory = $moduleTemplateFactory;
}
/**
* Resolve view and initialize the general view-variables extensionName,
......@@ -58,7 +53,7 @@ class AbstractModuleController extends AbstractController
/**
* Generates the action menu
*/
protected function generateMenu()
protected function initializeModuleTemplate(ServerRequestInterface $request): ModuleTemplate
{
$menuItems = [
'installedExtensions' => [
......@@ -89,17 +84,18 @@ class AbstractModuleController extends AbstractController
$menuItems['showAllVersions'] = [
'controller' => 'List',
'action' => 'showAllVersions',
'label' => $this->translate('showAllVersions') . ' ' . $this->request->getArgument('extensionKey')
'label' => $this->translate('showAllVersions') . ' ' . $request->getArgument('extensionKey')
];
}
}
$menu = $this->view->getModuleTemplate()->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
$moduleTemplate = $this->moduleTemplateFactory->create($request);
$menu = $moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
$menu->setIdentifier('ExtensionManagerModuleMenu');
foreach ($menuItems as $menuItemConfig) {
if ($this->request->getControllerName() === $menuItemConfig['controller']) {
$isActive = $this->request->getControllerActionName() === $menuItemConfig['action'] ? true : false;
if ($request->getControllerName() === $menuItemConfig['controller']) {
$isActive = $request->getControllerActionName() === $menuItemConfig['action'] ? true : false;
} else {
$isActive = false;
}
......@@ -109,14 +105,16 @@ class AbstractModuleController extends AbstractController
->setActive($isActive);
$menu->addMenuItem($menuItem);
if ($isActive) {
$this->view->getModuleTemplate()->setTitle(
$moduleTemplate->setTitle(
$this->translate('LLL:EXT:extensionmanager/Resources/Private/Language/locallang_mod.xlf:mlang_tabs_tab'),
$menuItemConfig['label']
);
}
}
$this->view->getModuleTemplate()->getDocHeaderComponent()->getMenuRegistry()->addMenu($menu);
$this->view->getModuleTemplate()->setFlashMessageQueue($this->getFlashMessageQueue());
$moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($menu);
$moduleTemplate->setFlashMessageQueue($this->getFlashMessageQueue());
return $moduleTemplate;
}
}
......@@ -38,29 +38,14 @@ use TYPO3\CMS\Extensionmanager\Utility\InstallUtility;
*/
class ActionController extends AbstractController
{
/**
* @var InstallUtility
*/
protected $installUtility;
protected InstallUtility $installUtility;
protected ExtensionManagementService $managementService;
/**
* @var ExtensionManagementService
*/
protected $managementService;
/**
* @param InstallUtility $installUtility
*/
public function injectInstallUtility(InstallUtility $installUtility)
{
public function __construct(
InstallUtility $installUtility,
ExtensionManagementService $managementService
) {
$this->installUtility = $installUtility;
}
/**
* @param ExtensionManagementService $managementService
*/
public function injectManagementService(ExtensionManagementService $managementService)
{
$this->managementService = $managementService;
}
......
......@@ -17,11 +17,10 @@ namespace TYPO3\CMS\Extensionmanager\Controller;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
use TYPO3\CMS\Backend\View\BackendTemplateView;
use TYPO3\CMS\Backend\Template\ModuleTemplate;
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Package\PackageManager;
use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
use TYPO3\CMS\Extensionmanager\Domain\Model\Extension;
/**
......@@ -39,22 +38,6 @@ class DistributionController extends AbstractModuleController
$this->pageRenderer = $pageRenderer;
}
/**
* Set up the doc header properly here
*
* @param ViewInterface $view
*/
protected function initializeView(ViewInterface $view)
{
if ($view instanceof BackendTemplateView) {
/** @var BackendTemplateView $view */
parent::initializeView($view);
$this->generateMenu();
$this->registerDocHeaderButtons();
$this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Extensionmanager/DistributionImage');
}
}
/**
* Shows information about the distribution
*
......@@ -70,26 +53,30 @@ class DistributionController extends AbstractModuleController
$this->view->assign('distributionActive', $active);
$this->view->assign('extension', $extension);
return $this->htmlResponse();
$this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Extensionmanager/DistributionImage');
$moduleTemplate = $this->initializeModuleTemplate($this->request);
$moduleTemplate = $this->registerDocHeaderButtons($moduleTemplate);
$moduleTemplate->setContent($this->view->render());
return $this->htmlResponse($moduleTemplate->renderContent());
}
/**
* Registers the Icons into the docheader
*
* @throws \InvalidArgumentException
*/
protected function registerDocHeaderButtons()
protected function registerDocHeaderButtons(ModuleTemplate $moduleTemplate): ModuleTemplate
{
/** @var ButtonBar $buttonBar */
$buttonBar = $this->view->getModuleTemplate()->getDocHeaderComponent()->getButtonBar();
$buttonBar = $moduleTemplate->getDocHeaderComponent()->getButtonBar();
$uri = $this->uriBuilder->reset()->uriFor('distributions', [], 'List');
$title = $this->translate('extConfTemplate.backToList');
$icon = $this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-view-go-back', Icon::SIZE_SMALL);
$icon = $moduleTemplate->getIconFactory()->getIcon('actions-view-go-back', Icon::SIZE_SMALL);
$button = $buttonBar->makeLinkButton()
->setHref($uri)
->setTitle($title)
->setIcon($icon);
$buttonBar->addButton($button, ButtonBar::BUTTON_POSITION_LEFT);
return $moduleTemplate;
}
}
......@@ -36,15 +36,8 @@ use TYPO3\CMS\Fluid\View\TemplateView;
*/
class DownloadController extends AbstractController
{
/**
* @var ExtensionRepository
*/
protected $extensionRepository;
/**
* @var ExtensionManagementService
*/
protected $managementService;
protected ExtensionRepository $extensionRepository;
protected ExtensionManagementService $managementService;
/**
* @var string
......@@ -56,30 +49,14 @@ class DownloadController extends AbstractController
*/
protected $view;
/**
* @param ExtensionRepository $extensionRepository
*/
public function injectExtensionRepository(ExtensionRepository $extensionRepository)
{
public function __construct(
ExtensionRepository $extensionRepository,
ExtensionManagementService $managementService
) {
$this->extensionRepository = $extensionRepository;
}
/**
* @param ExtensionManagementService $managementService
*/
public function injectManagementService(ExtensionManagementService $managementService)
{
$this->managementService = $managementService;
}
/**
* Defines which view object should be used for the installFromTer action
*/
protected function initializeInstallFromTerAction()
{
$this->defaultViewObjectName = TemplateView::class;
}
/**
* Check extension dependencies
*
......@@ -162,6 +139,15 @@ class DownloadController extends AbstractController
return $this->jsonResponse();
}
/**
* Defines which view object should be used for the installFromTer action
*/
protected function initializeInstallFromTerAction()
{
// @todo: Switch to JsonView
$this->defaultViewObjectName = TemplateView::class;
}
/**
* Install an extension from TER action
*
......
......@@ -26,7 +26,6 @@ use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\MathUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
use TYPO3\CMS\Extensionmanager\Service\ComposerManifestProposalGenerator;
use TYPO3\CMS\Extensionmanager\Utility\ListUtility;
......@@ -37,30 +36,10 @@ use TYPO3\CMS\Extensionmanager\Utility\ListUtility;
*/
class ExtensionComposerStatusController extends AbstractModuleController
{
/**
* @var ComposerDeficitDetector
*/
protected $composerDeficitDetector;
/**
* @var ComposerManifestProposalGenerator
*/
protected $composerManifestProposalGenerator;
/**
* @var PageRenderer
*/
protected $pageRenderer;
/**
* @var ListUtility
*/
protected $listUtility;
/**
* @var string
*/
protected $returnUrl = '';
protected ComposerDeficitDetector $composerDeficitDetector;
protected ComposerManifestProposalGenerator $composerManifestProposalGenerator;
protected PageRenderer $pageRenderer;
protected ListUtility $listUtility;
public function __construct(
ComposerDeficitDetector $composerDeficitDetector,
......@@ -74,27 +53,10 @@ class ExtensionComposerStatusController extends AbstractModuleController
$this->listUtility = $listUtility;
}
protected function initializeAction(): void
{
parent::initializeAction();
if ($this->request->hasArgument('returnUrl')) {
$this->returnUrl = GeneralUtility::sanitizeLocalUrl(
(string)$this->request->getArgument('returnUrl')
);
}
}
protected function initializeView(ViewInterface $view): void
{
parent::initializeView($view);
$this->registerDocHeaderButtons();
}
public function listAction(): ResponseInterface
{
$extensions = [];
$basePackagePath = Environment::getExtensionsPath() . '/';
$detailLinkReturnUrl = $this->uriBuilder->reset()->uriFor('list', array_filter(['returnUrl' => $this->returnUrl]));
foreach ($this->composerDeficitDetector->getExtensionsWithComposerDeficit() as $extensionKey => $deficit) {
$extensionPath = $basePackagePath . $extensionKey . '/';
$extensions[$extensionKey] = [
......@@ -103,15 +65,16 @@ class ExtensionComposerStatusController extends AbstractModuleController
'icon' => $this->getExtensionIcon($extensionPath),
'detailLink' => $this->uriBuilder->reset()->uriFor('detail', [
'extensionKey' => $extensionKey,
'returnUrl' => $detailLinkReturnUrl
'returnUrl' => $this->uriBuilder->reset()->uriFor('list'),
])
];
}
ksort($extensions);
$this->view->assign('extensions', $this->listUtility->enrichExtensionsWithEmConfInformation($extensions));
$this->generateMenu();
return $this->htmlResponse();
$moduleTemplate = $this->initializeModuleTemplate($this->request);
$moduleTemplate->setContent($this->view->render());
return $this->htmlResponse($moduleTemplate->renderContent());
}
public function detailAction(string $extensionKey): ResponseInterface
......@@ -130,7 +93,19 @@ class ExtensionComposerStatusController extends AbstractModuleController
$this->view->assign('composerManifestMarkup', $this->getComposerManifestMarkup($extensionKey));
}
return $this->htmlResponse();
$moduleTemplate = $this->initializeModuleTemplate($this->request);
$buttonBar = $moduleTemplate->getDocHeaderComponent()->getButtonBar();
$returnUrl = GeneralUtility::sanitizeLocalUrl((string)$this->request->getArgument('returnUrl'));
$buttonBar->addButton(
$buttonBar
->makeLinkButton()
->setHref($returnUrl)
->setClasses('typo3-goBack')
->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.goBack'))
->setIcon($moduleTemplate->getIconFactory()->getIcon('actions-view-go-back', Icon::SIZE_SMALL))
);
$moduleTemplate->setContent($this->view->render());
return $this->htmlResponse($moduleTemplate->renderContent());
}
protected function getComposerManifestMarkup(string $extensionKey): string
......@@ -167,21 +142,6 @@ class ExtensionComposerStatusController extends AbstractModuleController
. '</textarea>';
}
protected function registerDocHeaderButtons(): void
{
$buttonBar = $this->view->getModuleTemplate()->getDocHeaderComponent()->getButtonBar();
if ($this->returnUrl !== '') {
$buttonBar->addButton(
$buttonBar
->makeLinkButton()
->setHref($this->returnUrl)
->setClasses('typo3-goBack')
->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.goBack'))
->setIcon($this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-view-go-back', Icon::SIZE_SMALL))
);
}
}
protected function getExtensionIcon(string $extensionPath): string
{
$icon = ExtensionManagementUtility::getExtensionIcon($extensionPath);
......
......@@ -17,7 +17,7 @@ namespace TYPO3\CMS\Extensionmanager\Controller;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
use TYPO3\CMS\Backend\View\BackendTemplateView;
use TYPO3\CMS\Backend\Template\ModuleTemplate;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
use TYPO3\CMS\Core\Core\Environment;
......@@ -28,7 +28,6 @@ use TYPO3\CMS\Core\Pagination\ArrayPaginator;
use TYPO3\CMS\Core\Pagination\SimplePagination;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
use TYPO3\CMS\Extbase\Pagination\QueryResultPaginator;
use TYPO3\CMS\Extbase\Persistence\QueryResultInterface;
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
......@@ -45,55 +44,20 @@ use TYPO3\CMS\Extensionmanager\Utility\ListUtility;
*/
class ListController extends AbstractModuleController
{
/**
* @var ExtensionRepository
*/
protected $extensionRepository;
/**
* @var ListUtility
*/
protected $listUtility;
/**
* @var PageRenderer
*/
protected $pageRenderer;
/**
* @var DependencyUtility
*/
protected $dependencyUtility;
/**
* @param ExtensionRepository $extensionRepository
*/
public function injectExtensionRepository(ExtensionRepository $extensionRepository)
{
protected PageRenderer $pageRenderer;
protected ExtensionRepository $extensionRepository;
protected ListUtility $listUtility;
protected DependencyUtility $dependencyUtility;
public function __construct(
PageRenderer $pageRenderer,
ExtensionRepository $extensionRepository,
ListUtility $listUtility,
DependencyUtility $dependencyUtility
) {
$this->pageRenderer = $pageRenderer;
$this->extensionRepository = $extensionRepository;
}
/**
* @param ListUtility $listUtility
*/