Commit 42102cac authored by Oliver Bartsch's avatar Oliver Bartsch Committed by Benni Mack
Browse files

[TASK] Simplify RecyclerModuleController

While the RecyclerModuleController does only
feature one action - indexAction - the handleRequest()
method still contained corresponding logic to
dynamically forward the request to another action.

This is now simplified together with a couple of
further cleanups, e.g. removal of unnecessary
class properties / methods and adding strict
types.

Additionally, the new BackendTemplateView is
now used instead of StandaloneView.

Resolves: #96533
Related: #96513
Releases: main
Change-Id: I0d4a2f1aae47cdfce9bec92f2f768ee64bc64357
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/73015

Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Tested-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Reviewed-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
parent 8cc8103c
<?php
declare(strict_types=1);
/*
* This file is part of the TYPO3 CMS project.
*
......@@ -23,7 +25,6 @@ use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Http\HtmlResponse;
use TYPO3\CMS\Core\Http\NormalizedParams;
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Localization\LanguageService;
......@@ -31,50 +32,15 @@ use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Core\Type\Bitmask\Permission;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\MathUtility;
use TYPO3\CMS\Fluid\View\StandaloneView;
use TYPO3\CMS\Fluid\View\BackendTemplateView;
/**
* Backend Module for the 'recycler' extension.
*
* @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API.
*/
class RecyclerModuleController
{
/**
* @var array
*/
protected $pageRecord = [];
/**
* @var bool
*/
protected $isAccessibleForCurrentUser = false;
/**
* @var bool
*/
protected $allowDelete = false;
/**
* @var int
*/
protected $recordsPageLimit = 50;
/**
* @var int
*/
protected $id;
/**
* @var StandaloneView
*/
protected $view;
/**
* @var ModuleTemplate
*/
protected $moduleTemplate;
protected IconFactory $iconFactory;
protected PageRenderer $pageRenderer;
protected ModuleTemplateFactory $moduleTemplateFactory;
......@@ -89,177 +55,84 @@ class RecyclerModuleController
$this->moduleTemplateFactory = $moduleTemplateFactory;
}
/**
* Injects the request object for the current request, and renders correct action
*
* @param ServerRequestInterface $request the current request
* @return ResponseInterface the response with the content
*/
public function handleRequest(ServerRequestInterface $request): ResponseInterface
{
$this->id = (int)($request->getQueryParams()['id'] ?? $request->getParsedBody()['id'] ?? 0);
$backendUser = $this->getBackendUser();
$this->pageRecord = BackendUtility::readPageAccess($this->id, $backendUser->getPagePermsClause(Permission::PAGE_SHOW)) ?: [];
$this->isAccessibleForCurrentUser = ($this->id && $this->pageRecord !== []) || (!$this->id && $this->getBackendUser()->isAdmin());
$this->moduleTemplate = $this->moduleTemplateFactory->create($request);
// don't access in workspace
if ($backendUser->workspace !== 0) {
$this->isAccessibleForCurrentUser = false;
}
$id = (int)($request->getQueryParams()['id'] ?? $request->getParsedBody()['id'] ?? 0);
$pageRecord = BackendUtility::readPageAccess($id, $backendUser->getPagePermsClause(Permission::PAGE_SHOW)) ?: [];
$moduleTemplate = $this->moduleTemplateFactory->create($request);
// read configuration
if ($backendUser->isAdmin()) {
$this->allowDelete = true;
} else {
$this->allowDelete = (bool)($backendUser->getTSConfig()['mod.']['recycler.']['allowDelete'] ?? false);
}
$this->recordsPageLimit = MathUtility::forceIntegerInRange(
(int)($backendUser->getTSConfig()['mod.']['recycler.']['recordsPageLimit'] ?? 25),
1
);
$action = 'index';
$this->initializeView($action);
$result = $this->{$action . 'Action'}($request);
if ($result instanceof ResponseInterface) {
return $result;
}
$this->registerDocHeaderButtons();
$this->moduleTemplate->setContent($this->view->render());
return new HtmlResponse($this->moduleTemplate->renderContent());
}
/**
* @param string $templateName
*/
protected function initializeView(string $templateName)
{
$this->view = GeneralUtility::makeInstance(StandaloneView::class);
$this->view->setTemplate($templateName);
$this->view->setTemplateRootPaths(['EXT:recycler/Resources/Private/Templates/RecyclerModule']);
$this->view->setPartialRootPaths(['EXT:recycler/Resources/Private/Partials']);
$this->view->setLayoutRootPaths(['EXT:recycler/Resources/Private/Layouts']);
$this->view->getRequest()->setControllerExtensionName('Recycler');
}
/**
* Renders the content of the module.
*
* @param ServerRequestInterface $request
*/
public function indexAction(ServerRequestInterface $request)
{
$this->pageRenderer->addInlineSettingArray('Recycler', $this->getJavaScriptConfiguration($request->getAttribute('normalizedParams')));
$recordsPageLimit = MathUtility::forceIntegerInRange((int)($backendUser->getTSConfig()['mod.']['recycler.']['recordsPageLimit'] ?? 25), 1);
$allowDelete = $backendUser->isAdmin() || ($backendUser->getTSConfig()['mod.']['recycler.']['allowDelete'] ?? false);
$sessionData = $backendUser->uc['tx_recycler'] ?? [];
$this->pageRenderer->addInlineSettingArray('Recycler', [
'pagingSize' => $recordsPageLimit,
'startUid' => $id,
'deleteDisable' => !$allowDelete,
'depthSelection' => ($sessionData['depthSelection'] ?? false) ?: '0',
'tableSelection' => ($sessionData['tableSelection'] ?? false) ?: '',
]);
$this->pageRenderer->addInlineLanguageLabelFile('EXT:recycler/Resources/Private/Language/locallang.xlf');
$this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Recycler/Recycler');
$this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/MultiRecordSelection');
if ($this->isAccessibleForCurrentUser) {
$this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($this->pageRecord);
if ($backendUser->workspace !== 0
&& (($id && $pageRecord !== []) || (!$id && $backendUser->isAdmin()))
) {
$moduleTemplate->getDocHeaderComponent()->setMetaInformation($pageRecord);
}
$this->moduleTemplate->setTitle(
$moduleTemplate->setTitle(
$this->getLanguageService()->sL('LLL:EXT:recycler/Resources/Private/Language/locallang_mod.xlf:mlang_tabs_tab'),
$this->pageRecord['title'] ?? ''
$pageRecord['title'] ?? ''
);
$this->view->assign('allowDelete', $this->allowDelete);
$this->registerDocHeaderButtons($moduleTemplate, $id, $pageRecord);
$view = GeneralUtility::makeInstance(BackendTemplateView::class);
$view->setTemplateRootPaths(['EXT:recycler/Resources/Private/Templates']);
$view->setPartialRootPaths(['EXT:recycler/Resources/Private/Partials']);
$view->assign('allowDelete', $allowDelete);
$moduleTemplate->setContent($view->render('RecyclerModule'));
return new HtmlResponse($moduleTemplate->renderContent());
}
/**
* Registers the Icons into the docheader
*
* @throws \InvalidArgumentException
* Registers doc header buttons.
*/
protected function registerDocHeaderButtons()
protected function registerDocHeaderButtons(ModuleTemplate $moduleTemplate, int $id, array $pageRecord): void
{
$buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
$languageService = $this->getLanguageService();
$buttonBar = $moduleTemplate->getDocHeaderComponent()->getButtonBar();
$shortcutTitle = sprintf(
'%s: %s [%d]',
$languageService->sL('LLL:EXT:recycler/Resources/Private/Language/locallang_mod.xlf:mlang_tabs_tab'),
BackendUtility::getRecordTitle('pages', $pageRecord),
$id
);
$shortcutButton = $buttonBar->makeShortcutButton()
->setRouteIdentifier('web_RecyclerRecycler')
->setDisplayName($this->getShortcutTitle())
->setArguments(['id' => (int)$this->id]);
->setDisplayName($shortcutTitle)
->setArguments(['id' => $id]);
$buttonBar->addButton($shortcutButton);
$reloadButton = $buttonBar->makeLinkButton()
->setHref('#')
->setDataAttributes(['action' => 'reload'])
->setTitle($this->getLanguageService()->sL('LLL:EXT:recycler/Resources/Private/Language/locallang.xlf:button.reload'))
->setTitle($languageService->sL('LLL:EXT:recycler/Resources/Private/Language/locallang.xlf:button.reload'))
->setIcon($this->iconFactory->getIcon('actions-refresh', Icon::SIZE_SMALL));
$buttonBar->addButton($reloadButton, ButtonBar::BUTTON_POSITION_RIGHT);
}
/**
* Gets the JavaScript configuration.
*
* @param NormalizedParams $normalizedParams
* @return array The JavaScript configuration
*/
protected function getJavaScriptConfiguration(NormalizedParams $normalizedParams): array
{
return [
'pagingSize' => $this->recordsPageLimit,
'showDepthMenu' => true,
'startUid' => $this->id,
'isSSL' => $normalizedParams->isHttps(),
'deleteDisable' => !$this->allowDelete,
'depthSelection' => $this->getDataFromSession('depthSelection', '0'),
'tableSelection' => $this->getDataFromSession('tableSelection', ''),
'States' => $this->getBackendUser()->uc['moduleData']['web_recycler']['States'] ?? [],
];
}
/**
* Gets data from the session of the current backend user.
*
* @param string $identifier The identifier to be used to get the data
* @param string $default The default date to be used if nothing was found in the session
* @return string The accordant data in the session of the current backend user
*/
protected function getDataFromSession($identifier, $default = null)
{
$sessionData = &$this->getBackendUser()->uc['tx_recycler'];
if (isset($sessionData[$identifier]) && $sessionData[$identifier]) {
$data = $sessionData[$identifier];
} else {
$data = $default;
}
return $data;
}
/**
* Returns the shortcut title for the current page
*
* @return string
*/
protected function getShortcutTitle(): string
{
return sprintf(
'%s: %s [%d]',
$this->getLanguageService()->sL('LLL:EXT:recycler/Resources/Private/Language/locallang_mod.xlf:mlang_tabs_tab'),
BackendUtility::getRecordTitle('pages', $this->pageRecord),
$this->id
);
}
/**
* Returns the current BE user.
*
* @return BackendUserAuthentication
*/
protected function getBackendUser(): BackendUserAuthentication
{
return $GLOBALS['BE_USER'];
}
/**
* Returns an instance of LanguageService
*
* @return LanguageService
*/
protected function getLanguageService(): LanguageService
{
return $GLOBALS['LANG'];
......
<f:be.pageRenderer includeRequireJsModules="{0:'TYPO3/CMS/Recycler/Recycler'}" />
<h1><f:translate key="title" /></h1>
<f:render section="content" />
<f:layout name="Default" />
<html
xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers"
data-namespace-typo3-fluid="true"
>
<f:section name="content">
<div id="recycler-index" class="tx_recycler_recycler">
<form id="recycler-form">
<div class="row row-cols-auto align-items-end g-3 mb-4">
<div class="col">
<div class="input-group">
<input type="text" name="search-text" class="form-control" placeholder="{f:translate(key: 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.enterSearchString')}">
<button type="submit" class="btn btn-default disabled"><core:icon identifier="actions-search" /></button>
</div>
</div>
<div class="col">
<label for="depth" class="form-label"><f:translate key="LLL:EXT:recycler/Resources/Private/Language/locallang.xlf:filter.depth" /></label>
<select name="depth" id="depth" class="form-select">
<option value="0"><f:translate key="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_0" /></option>
<option value="1"><f:translate key="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_1" /></option>
<option value="2"><f:translate key="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_2" /></option>
<option value="3"><f:translate key="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_3" /></option>
<option value="4"><f:translate key="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_4" /></option>
<option value="999"><f:translate key="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_infi" /></option>
</select>
</div>
<div class="col">
<label for="pages" class="form-label"><f:translate key="LLL:EXT:recycler/Resources/Private/Language/locallang.xlf:filter.type" /></label>
<select name="pages" id="pages" class="form-select"></select>
<h1><f:translate key="LLL:EXT:recycler/Resources/Private/Language/locallang.xlf:title" /></h1>
<div id="recycler-index" class="tx_recycler_recycler">
<form id="recycler-form">
<div class="row row-cols-auto align-items-end g-3 mb-4">
<div class="col">
<div class="input-group">
<input type="text" name="search-text" class="form-control" placeholder="{f:translate(key: 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.enterSearchString')}">
<button type="submit" class="btn btn-default disabled"><core:icon identifier="actions-search" /></button>
</div>
</div>
</form>
<f:render section="multiRecordSelectionActions" arguments="{allowDelete: allowDelete}"/>
<div class="table-fit mb-0">
<table class="table table-hover" id="itemsInRecycler">
<thead>
<tr>
<th><f:render section="multiRecordSelectionCheckboxActions" /></th>
<th><f:translate key="table.header.record" /></th>
<th><f:translate key="table.header.tstamp" /></th>
<th><f:translate key="table.header.uid" /></th>
<th><f:translate key="table.header.pid" /></th>
<th></th>
</tr>
</thead>
<tbody data-multi-record-selection-row-selection="true">
</tbody>
</table>
<div class="col">
<label for="depth" class="form-label"><f:translate key="LLL:EXT:recycler/Resources/Private/Language/locallang.xlf:filter.depth" /></label>
<select name="depth" id="depth" class="form-select">
<option value="0"><f:translate key="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_0" /></option>
<option value="1"><f:translate key="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_1" /></option>
<option value="2"><f:translate key="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_2" /></option>
<option value="3"><f:translate key="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_3" /></option>
<option value="4"><f:translate key="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_4" /></option>
<option value="999"><f:translate key="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_infi" /></option>
</select>
</div>
<div class="col">
<label for="pages" class="form-label"><f:translate key="LLL:EXT:recycler/Resources/Private/Language/locallang.xlf:filter.type" /></label>
<select name="pages" id="pages" class="form-select"></select>
</div>
</div>
<f:render section="multiRecordSelectionActions" arguments="{allowDelete: allowDelete}"/>
<nav>
</nav>
</form>
<f:render section="multiRecordSelectionActions" arguments="{allowDelete: allowDelete}"/>
<div class="table-fit mb-0">
<table class="table table-hover" id="itemsInRecycler">
<thead>
<tr>
<th><f:render section="multiRecordSelectionCheckboxActions" /></th>
<th><f:translate key="LLL:EXT:recycler/Resources/Private/Language/locallang.xlf:table.header.record" /></th>
<th><f:translate key="LLL:EXT:recycler/Resources/Private/Language/locallang.xlf:table.header.tstamp" /></th>
<th><f:translate key="LLL:EXT:recycler/Resources/Private/Language/locallang.xlf:table.header.uid" /></th>
<th><f:translate key="LLL:EXT:recycler/Resources/Private/Language/locallang.xlf:table.header.pid" /></th>
<th></th>
</tr>
</thead>
<tbody data-multi-record-selection-row-selection="true">
</tbody>
</table>
</div>
</f:section>
<f:render section="multiRecordSelectionActions" arguments="{allowDelete: allowDelete}"/>
<nav>
</nav>
</div>
<f:section name="multiRecordSelectionCheckboxActions">
<div class="btn-group dropdown position-static">
......@@ -85,7 +89,7 @@
<button class="btn btn-default btn-sm" data-multi-record-selection-action="massundo" data-multi-record-selection-action-config='{"idField": "uid"}'>
<core:icon identifier="actions-edit-undo" />
<span class="text">
<f:translate key="button.undo" />
<f:translate key="LLL:EXT:recycler/Resources/Private/Language/locallang.xlf:button.undo" />
</span>
</button>
</div>
......@@ -94,7 +98,7 @@
<button class="btn btn-default btn-sm" data-multi-record-selection-action="massdelete" data-multi-record-selection-action-config='{"idField": "uid"}'>
<core:icon identifier="actions-edit-delete" />
<span class="text">
<f:translate key="button.delete" />
<f:translate key="LLL:EXT:recycler/Resources/Private/Language/locallang.xlf:button.delete" />
</span>
</button>
</div>
......@@ -102,3 +106,5 @@
</div>
</div>
</f:section>
</html>
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment