Skip to content

Commit

Permalink
[TASK] Simplify Templating Bootstrap in BE Controllers
Browse files Browse the repository at this point in the history
This patch introduces a new EXT:fluid view class
"BackendTemplateView" to be used as main view for
backend-related non-Extbase views.

This class is the base of a new non-Extbase and
non-request dependent backend view. The class is for
now marked @internal and experimental since we'll
probably add a factory to configure backend template
overrides for any backend view later-on.

A few ViewHelpers are changed to work without
accessing the request if enough VH arguments are provided.

This is the first patch in a series of patches that will
switch from StandaloneView usages in backend
controllers to this new BackendTemplateView.

Basic strategy:
* $view->getRequest()->setControllerExtensionName('SysNote')
  is removed. This is Extbase-specific and not needed nor
  wanted for common non-Extbase controllers.
* Instantiate the View (for now with makeInstance, will be
  replaced with a factory later-on)
* Set the needed paths via ->setTemplateRootPaths() etc.
  For these, we *always* use the main extension's entry
  templating paths, for instance
  'EXT:sys_note/Resources/Private/Templates' or
  'EXT:sys_note/Resources/Private/Partials'.
  We do *not* use sub directories here to clear up path
  logic.
* ->assign() / ->assignMultiple() whatever is needed.
* ->render('SubDirectory/TemplateName') the actual
  action / template, no '.html' suffix.

As a demo, EXT:sys_note is adapted accordingly which hands
over arguments to the above mentioned VH's in a way so
these don't access the request object anymore. The sys_note
code gets a couple of additional changes so the hooks can
prepare request dependent arguments and set them as
template variables (here: returnUrl).

This patch triggers a hidden gem: Since ViewHelpers no
longer receive an Extbase request, they also don't trigger
Extbase magic anymore. The casual victim here is
f:translate, which has already been prepared to not trigger
Extbase's frontend TypoScript parsing if there is no
Extbase request. This often improves backend view performance
by 25% or more, depending on the amount of frontend
TypoScript to parse.

Further patches will adapt other core backend routes and will
relate to this patch for reference.

Change-Id: I4fec3ad690452a00e731c9f6928273048397dd89
Resolves: #96513
Related: #96473
Releases: main
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/72966
Reviewed-by: Benni Mack <benni@typo3.org>
Reviewed-by: Oliver Bartsch <bo@cedev.de>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: core-ci <typo3@b13.com>
Tested-by: Benni Mack <benni@typo3.org>
Tested-by: Oliver Bartsch <bo@cedev.de>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
  • Loading branch information
lolli42 committed Jan 12, 2022
1 parent 3c76b9e commit 37ac299
Show file tree
Hide file tree
Showing 14 changed files with 170 additions and 119 deletions.
Expand Up @@ -614,7 +614,7 @@ protected function main(ServerRequestInterface $request): void
// Page title
$content .= '<h1 class="' . ($this->isPageEditable($this->current_sys_language) ? 't3js-title-inlineedit' : '') . '">' . htmlspecialchars($this->getLocalizedPageTitle()) . '</h1>';
// All other listings
$content .= $this->renderContent();
$content .= $this->renderContent($request);
$content .= '</form>';
// Setting up the buttons for the docheader
$this->makeButtons($request);
Expand Down Expand Up @@ -664,7 +664,7 @@ function(Paste) {
*
* @return string
*/
protected function renderContent(): string
protected function renderContent(ServerRequestInterface $request): string
{
$this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
$this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Tooltip');
Expand Down Expand Up @@ -727,14 +727,14 @@ protected function renderContent(): string
$content = '';
// Additional header content
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/db_layout.php']['drawHeaderHook'] ?? [] as $hook) {
$params = [];
$params = ['request' => $request];
$content .= GeneralUtility::callUserFunction($hook, $params, $this);
}
$content .= $tableOutput;

// Additional footer content
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/db_layout.php']['drawFooterHook'] ?? [] as $hook) {
$params = [];
$params = ['request' => $request];
$content .= GeneralUtility::callUserFunction($hook, $params, $this);
}
return $content;
Expand Down
Expand Up @@ -17,6 +17,7 @@

namespace TYPO3\CMS\Backend\ViewHelpers\Link;

use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException;
use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Core\Utility\GeneralUtility;
Expand Down Expand Up @@ -84,8 +85,12 @@ public function render(): string
if ($this->arguments['uid'] < 1) {
throw new \InvalidArgumentException('Uid must be a positive integer, ' . $this->arguments['uid'] . ' given.', 1526127158);
}
if (empty($this->arguments['returnUrl'])) {
$this->arguments['returnUrl'] = $this->renderingContext->getRequest()->getAttribute('normalizedParams')->getRequestUri();
$request = $this->renderingContext->getRequest();
if (empty($this->arguments['returnUrl'])
&& $request instanceof ServerRequestInterface
) {
// @todo: We may want to deprecate fetching returnUrl from request
$this->arguments['returnUrl'] = $request->getAttribute('normalizedParams')->getRequestUri();
}

$params = [
Expand Down
Expand Up @@ -17,6 +17,7 @@

namespace TYPO3\CMS\Backend\ViewHelpers;

use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Core\Utility\ArrayUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
Expand Down Expand Up @@ -64,8 +65,14 @@ public static function renderStatic(array $arguments, \Closure $renderChildrenCl
if ($arguments['query'] !== null) {
ArrayUtility::mergeRecursiveWithOverrule($parameters, GeneralUtility::explodeUrl2Array($arguments['query']));
}
if ($arguments['currentUrlParameterName'] !== null) {
$parameters[$arguments['currentUrlParameterName']] = $renderingContext->getRequest()->getAttribute('normalizedParams')->getRequestUri();
$request = $renderingContext->getRequest();
if (!empty($arguments['currentUrlParameterName'])
&& empty($arguments['arguments'][$arguments['currentUrlParameterName']])
&& $request instanceof ServerRequestInterface
) {
// If currentUrlParameterName is given and if that argument is not hand over yet, and if there is a request, fetch it from request
// @todo: We may want to deprecate fetching stuff from request and advise handing over a proper value as 'arguments' argument.
$parameters[$arguments['currentUrlParameterName']] = $request->getAttribute('normalizedParams')->getRequestUri();
}
return (string)$uriBuilder->buildUriFromRoute($arguments['route'], $parameters);
}
Expand Down
28 changes: 28 additions & 0 deletions typo3/sysext/fluid/Classes/View/BackendTemplateView.php
@@ -0,0 +1,28 @@
<?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\Fluid\View;

/**
* A view dedicated for backend non-extbase usage.
*
* @internal Do not use in extensions at the moment.
* This view will most likely receive a configuration system and a factory in further v12 progress.
*/
class BackendTemplateView extends AbstractTemplateView
{
}
Expand Up @@ -39,7 +39,7 @@
* includeCssFiles="{0: 'EXT:my_ext/Resources/Public/Css/Stylesheet.css'}"
* includeJsFiles="{0: 'EXT:my_ext/Resources/Public/JavaScript/Library1.js', 1: 'EXT:my_ext/Resources/Public/JavaScript/Library2.js'}"
* addJsInlineLabels="{'my_ext.label1': 'LLL:EXT:my_ext/Resources/Private/Language/locallang.xlf:label1'}"
* includesRequireJsModules="{0: 'EXT:my_ext/Resources/Public/JavaScript/RequireJsModule.js'}"
* includeRequireJsModules="{0: 'EXT:my_ext/Resources/Public/JavaScript/RequireJsModule.js'}"
* addInlineSettings="{'some.setting.key': 'some.setting.value'}"
* />
*
Expand Down
Expand Up @@ -17,6 +17,7 @@

namespace TYPO3\CMS\Fluid\ViewHelpers\Link;

use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Http\ApplicationType;
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;
Expand Down Expand Up @@ -81,7 +82,11 @@ public function render(): string
$attributes = [];
$linkText = htmlspecialchars($email);
$escapeSpecialCharacters = true;
if (ApplicationType::fromRequest($this->renderingContext->getRequest())->isFrontend()) {
$request = $this->renderingContext->getRequest();
if ($request instanceof ServerRequestInterface
&& ApplicationType::fromRequest($this->renderingContext->getRequest())->isFrontend()
) {
// If there is no request, backend is assumed.
/** @var TypoScriptFrontendController $frontend */
$frontend = $GLOBALS['TSFE'];
$frontend->cObj->typoLink($email, ['parameter' => $linkHref]);
Expand Down
Expand Up @@ -17,9 +17,7 @@

namespace TYPO3\CMS\Fluid\Tests\Functional\ViewHelpers\Link;

use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Http\ServerRequest;
use TYPO3\CMS\Core\Tests\Functional\SiteHandling\SiteBasedTestTrait;
use TYPO3\CMS\Fluid\View\StandaloneView;
use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
Expand All @@ -34,9 +32,8 @@ class EmailViewHelperTest extends FunctionalTestCase
*/
public function renderCreatesProperMarkupInBackend(): void
{
$GLOBALS['TYPO3_REQUEST'] = (new ServerRequest())
->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_BE);
$view = new StandaloneView();
$view->setRequest();
$view->setTemplateSource('<f:link.email email="foo@example.com">send mail</f:link.email>');
self::assertEquals('<a href="mailto:foo@example.com">send mail</a>', $view->render());
}
Expand All @@ -46,9 +43,8 @@ public function renderCreatesProperMarkupInBackend(): void
*/
public function renderCreatesProperMarkupInBackendWithEmptyChild(): void
{
$GLOBALS['TYPO3_REQUEST'] = (new ServerRequest())
->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_BE);
$view = new StandaloneView();
$view->setRequest();
$view->setTemplateSource('<f:link.email email="foo@example.com" />');
self::assertEquals('<a href="mailto:foo@example.com">foo@example.com</a>', $view->render());
}
Expand Down
34 changes: 15 additions & 19 deletions typo3/sysext/sys_note/Classes/Controller/NoteController.php
Expand Up @@ -21,7 +21,7 @@
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Type\Bitmask\Permission;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Fluid\View\StandaloneView;
use TYPO3\CMS\Fluid\View\BackendTemplateView;
use TYPO3\CMS\SysNote\Domain\Repository\SysNoteRepository;

/**
Expand All @@ -31,11 +31,7 @@
*/
class NoteController
{
/**
* @var SysNoteRepository
*/
protected $notesRepository;

protected SysNoteRepository $notesRepository;
protected array $pagePermissionCache = [];

public function __construct()
Expand All @@ -44,34 +40,34 @@ public function __construct()
}

/**
* Render notes by single PID or PID list
* Render notes by single PID
*
* @param string $pids Single PID or comma separated list of PIDs
* @param int $pid The page id notes should be rendered for
* @param int|null $position null for no restriction, integer for defined position
* @param string $returnUrl Url to return to when editing and closing a notes record again
* @return string
*/
public function listAction($pids, int $position = null): string
public function listAction(int $pid, int $position = null, string $returnUrl = ''): string
{
$backendUser = $this->getBackendUser();
if (empty($pids)
if ($pid <= 0
|| empty($backendUser->user[$backendUser->userid_column])
|| !$backendUser->check('tables_select', 'sys_note')
) {
return '';
}

$notes = $this->notesRepository->findByPidsAndAuthorId($pids, (int)$backendUser->user[$backendUser->userid_column], $position);
$notes = $this->notesRepository->findByPidsAndAuthorId($pid, (int)$backendUser->user[$backendUser->userid_column], $position);
if (!$notes) {
return '';
}
$view = GeneralUtility::makeInstance(StandaloneView::class);
$view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName(
'EXT:sys_note/Resources/Private/Templates/Note/List.html'
));
$view->setLayoutRootPaths(['EXT:sys_note/Resources/Private/Layouts']);
$view->getRequest()->setControllerExtensionName('SysNote');
$view->assign('notes', $this->enrichWithEditPermissions($notes));
return $view->render();
$view = GeneralUtility::makeInstance(BackendTemplateView::class);
$view->setTemplateRootPaths(['EXT:sys_note/Resources/Private/Templates']);
$view->assignMultiple([
'notes' => $this->enrichWithEditPermissions($notes),
'returnUrl' => $returnUrl,
]);
return $view->render('List');
}

protected function enrichWithEditPermissions(array $notes): array
Expand Down
15 changes: 9 additions & 6 deletions typo3/sysext/sys_note/Classes/Hook/InfoModuleHook.php
Expand Up @@ -17,24 +17,27 @@

namespace TYPO3\CMS\SysNote\Hook;

use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\SysNote\Controller\NoteController;

/**
* Hook for the info module
* Hook for the info module.
*
* @internal This is a specific hook implementation and is not considered part of the Public TYPO3 API.
*/
class InfoModuleHook
{
/**
* Add sys_notes as additional content to the footer of the info module
*
* @return string
*/
public function render()
public function render(array $params): string
{
/** @var ServerRequestInterface $request */
$request = $params['request'];
$controller = GeneralUtility::makeInstance(NoteController::class);
$id = (int)GeneralUtility::_GP('id');
return $controller->listAction($id);
$id = (int)($request->getQueryParams()['id'] ?? 0);
$returnUrl = $request->getAttribute('normalizedParams')->getRequestUri();
return $controller->listAction($id, null, $returnUrl);
}
}
29 changes: 15 additions & 14 deletions typo3/sysext/sys_note/Classes/Hook/PageHook.php
Expand Up @@ -17,41 +17,42 @@

namespace TYPO3\CMS\SysNote\Hook;

use TYPO3\CMS\Backend\Controller\PageLayoutController;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\SysNote\Controller\NoteController;
use TYPO3\CMS\SysNote\Domain\Repository\SysNoteRepository;

/**
* Hook for the page module
* Hook to render notes in the page module.
*
* @internal This is a specific hook implementation and is not considered part of the Public TYPO3 API.
*/
class PageHook
{

/**
* Add sys_notes as additional content to the header of the page module
*
* @param array $params
* @param \TYPO3\CMS\Backend\Controller\PageLayoutController $parentObject
* @return string
*/
public function renderInHeader(array $params, PageLayoutController $parentObject)
public function renderInHeader(array $params)
{
/** @var ServerRequestInterface $request */
$request = $params['request'];
$id = (int)($request->getQueryParams()['id'] ?? 0);
$returnUrl = $request->getAttribute('normalizedParams')->getRequestUri();
$controller = GeneralUtility::makeInstance(NoteController::class);
return $controller->listAction($parentObject->id, SysNoteRepository::SYS_NOTE_POSITION_TOP);
return $controller->listAction($id, SysNoteRepository::SYS_NOTE_POSITION_TOP, $returnUrl);
}

/**
* Add sys_notes as additional content to the footer of the page module
*
* @param array $params
* @param \TYPO3\CMS\Backend\Controller\PageLayoutController $parentObject
* @return string
*/
public function renderInFooter(array $params, PageLayoutController $parentObject)
public function renderInFooter(array $params): string
{
/** @var ServerRequestInterface $request */
$request = $params['request'];
$id = (int)($request->getQueryParams()['id'] ?? 0);
$returnUrl = $request->getAttribute('normalizedParams')->getRequestUri();
$controller = GeneralUtility::makeInstance(NoteController::class);
return $controller->listAction($parentObject->id, SysNoteRepository::SYS_NOTE_POSITION_BOTTOM);
return $controller->listAction($id, SysNoteRepository::SYS_NOTE_POSITION_BOTTOM, $returnUrl);
}
}
13 changes: 8 additions & 5 deletions typo3/sysext/sys_note/Classes/Provider/RecordListProvider.php
Expand Up @@ -22,12 +22,13 @@
use TYPO3\CMS\SysNote\Domain\Repository\SysNoteRepository;

/**
* Class RecordListProvider
* Render existing notes within list module.
*
* @internal
*/
class RecordListProvider
{
protected $noteController;
protected NoteController $noteController;

public function __construct(NoteController $noteController)
{
Expand All @@ -36,8 +37,10 @@ public function __construct(NoteController $noteController)

public function __invoke(RenderAdditionalContentToRecordListEvent $event): void
{
$id = (int)($event->getRequest()->getParsedBody()['id'] ?? $event->getRequest()->getQueryParams()['id'] ?? 0);
$event->addContentAbove($this->noteController->listAction($id, SysNoteRepository::SYS_NOTE_POSITION_TOP));
$event->addContentBelow($this->noteController->listAction($id, SysNoteRepository::SYS_NOTE_POSITION_BOTTOM));
$request = $event->getRequest();
$pid = (int)($event->getRequest()->getParsedBody()['id'] ?? $event->getRequest()->getQueryParams()['id'] ?? 0);
$returnUrl = $request->getAttribute('normalizedParams')->getRequestUri();
$event->addContentAbove($this->noteController->listAction($pid, SysNoteRepository::SYS_NOTE_POSITION_TOP, $returnUrl));
$event->addContentBelow($this->noteController->listAction($pid, SysNoteRepository::SYS_NOTE_POSITION_BOTTOM, $returnUrl));
}
}

This file was deleted.

0 comments on commit 37ac299

Please sign in to comment.