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

[!!!][TASK] Remove Shortcut API functionality

The Shortcut API, handling the "shortcuts"
in the backend was reworked in v11. This
included adaptations to the new backend
routing next to code cleanup.

Therefore, this patch now removes old API
methods, a ViewHelper and a couple of
legacy fallbacks.

Resolves: #96154
Related: #92132
Related: #93060
Related: #93093
Releases: main
Change-Id: Ie1f75497c40656966e3eda129d8e7582b32f591b
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/72394


Tested-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
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 5d4032a4
......@@ -30,8 +30,6 @@ use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException;
use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException;
use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Resource\StorageRepository;
use TYPO3\CMS\Core\Type\Bitmask\Permission;
use TYPO3\CMS\Core\Utility\GeneralUtility;
......@@ -187,90 +185,6 @@ class ShortcutRepository
return false;
}
$languageService = $this->getLanguageService();
// Only apply "magic" if title is not set
// @todo This is deprecated and can be removed in v12
if ($title === '') {
$queryParameters = json_decode($arguments, true);
$titlePrefix = '';
$type = 'other';
$table = '';
$recordId = 0;
$pageId = 0;
if ($queryParameters && is_array($queryParameters['edit'] ?? null)) {
$table = (string)key($queryParameters['edit']);
$recordId = (int)key($queryParameters['edit'][$table]);
$pageId = (int)(BackendUtility::getRecord($table, $recordId)['pid'] ?? 0);
$languageFile = 'LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf';
$action = $queryParameters['edit'][$table][$recordId];
switch ($action) {
case 'edit':
$type = 'edit';
$titlePrefix = $languageService->sL($languageFile . ':shortcut_edit');
break;
case 'new':
$type = 'new';
$titlePrefix = $languageService->sL($languageFile . ':shortcut_create');
break;
}
}
$moduleName = $this->getModuleNameFromRouteIdentifier($routeIdentifier);
$id = (string)($queryParameters['id'] ?? '');
if ($moduleName === 'file_FilelistList' && $id !== '') {
try {
$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
$resource = $resourceFactory->getObjectFromCombinedIdentifier($queryParameters['id']);
$title = trim(sprintf(
'%s (%s)',
$titlePrefix,
$resource->getName()
));
} catch (ResourceDoesNotExistException $e) {
}
} else {
// Lookup the title of this page and use it as default description
$pageId = $pageId ?: $recordId ?: (int)$id;
$page = $pageId ? BackendUtility::getRecord('pages', $pageId) : null;
if (!empty($page)) {
// Set the name to the title of the page
if ($type === 'other') {
$title = sprintf(
'%s (%s)',
$title,
$page['title']
);
} else {
$title = sprintf(
'%s %s (%s)',
$titlePrefix,
$languageService->sL($GLOBALS['TCA'][$table]['ctrl']['title']),
$page['title']
);
}
} elseif (!empty($table)) {
$title = trim(sprintf(
'%s %s',
$titlePrefix,
$languageService->sL($GLOBALS['TCA'][$table]['ctrl']['title'])
));
}
}
}
// In case title is still empty try to set the modules short description label
// @todo This is deprecated and can be removed in v12
if ($title === '') {
$moduleLabels = $this->moduleLoader->getLabelsForModule($this->getModuleNameFromRouteIdentifier($routeIdentifier));
if (!empty($moduleLabels['shortdescription'])) {
$title = $this->getLanguageService()->sL($moduleLabels['shortdescription']);
}
}
$queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::TABLE_NAME);
$affectedRows = $queryBuilder
->insert(self::TABLE_NAME)
......@@ -278,7 +192,7 @@ class ShortcutRepository
'userid' => $this->getBackendUser()->user['uid'],
'route' => $routeIdentifier,
'arguments' => $arguments,
'description' => $title ?: 'Shortcut',
'description' => $title ?: 'Shortcut', // Fall back to "Shortcut", see: initShortcuts()
'sorting' => $GLOBALS['EXEC_TIME'],
])
->execute();
......@@ -562,23 +476,10 @@ class ShortcutRepository
}
$lastGroup = $shortcutGroup;
$description = $row['description'] ?? '';
// Empty description should usually never happen since not defining such, is deprecated and a
// fallback is in place, at least for v11. Only manual inserts could lead to an empty description.
// @todo Can be removed in v12 since setting a display name is mandatory then
if ($description === '') {
$moduleLabel = (string)($this->moduleLoader->getLabelsForModule($moduleName)['shortdescription'] ?? '');
if ($moduleLabel !== '') {
$description = $this->getLanguageService()->sL($moduleLabel);
}
}
$shortcutUrl = (string)GeneralUtility::makeInstance(UriBuilder::class)->buildUriFromRoute($routeIdentifier, $arguments);
$shortcut['group'] = $shortcutGroup;
$shortcut['icon'] = $this->getShortcutIcon($routeIdentifier, $moduleName, $shortcut);
$shortcut['label'] = $description;
$shortcut['href'] = $shortcutUrl;
$shortcut['label'] = ($row['description'] ?? false) ?: 'Shortcut'; // Fall back to "Shortcut", see: addShortcut()
$shortcut['href'] = (string)GeneralUtility::makeInstance(UriBuilder::class)->buildUriFromRoute($routeIdentifier, $arguments);
$shortcut['route'] = $routeIdentifier;
$shortcut['module'] = $moduleName;
$shortcut['pageId'] = $pageId;
......
......@@ -91,7 +91,7 @@ class ShortcutController
} elseif ($this->shortcutRepository->shortcutExists($routeIdentifier, $arguments)) {
$result = 'alreadyExists';
} else {
$shortcutName = $parsedBody['displayName'] ?? $queryParams['arguments'] ?? '';
$shortcutName = $parsedBody['displayName'] ?? $queryParams['displayName'] ?? '';
$success = $this->shortcutRepository->addShortcut($routeIdentifier, $arguments, $shortcutName);
if (!$success) {
$result = 'failed';
......
......@@ -21,7 +21,6 @@ use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
use TYPO3\CMS\Backend\Template\Components\Buttons\ButtonInterface;
use TYPO3\CMS\Backend\Template\Components\Buttons\PositionInterface;
use TYPO3\CMS\Backend\Template\ModuleTemplate;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Imaging\IconFactory;
......@@ -54,12 +53,6 @@ class ShortcutButton implements ButtonInterface, PositionInterface
*/
protected string $routeIdentifier = '';
/**
* @var string
* @deprecated since v11, will be removed in v12
*/
protected $moduleName = '';
/**
* @var string
*/
......@@ -70,18 +63,6 @@ class ShortcutButton implements ButtonInterface, PositionInterface
*/
protected $arguments = [];
/**
* @var array
* @deprecated since v11, will be removed in v12
*/
protected $setVariables = [];
/**
* @var array
* @deprecated since v11, will be removed in v12
*/
protected $getVariables = [];
/**
* @var bool
*/
......@@ -109,32 +90,6 @@ class ShortcutButton implements ButtonInterface, PositionInterface
return $this;
}
/**
* Gets the name of the module.
*
* @return string
* @deprecated since v11, will be removed in v12
*/
public function getModuleName()
{
trigger_error('Method getModuleName() is deprecated and will be removed in v12. Use getRouteIdentifier() instead.', E_USER_DEPRECATED);
return $this->moduleName;
}
/**
* Sets the name of the module.
*
* @param string $moduleName
* @return ShortcutButton
* @deprecated since v11, will be removed in v12
*/
public function setModuleName($moduleName)
{
trigger_error('Method setModuleName() is deprecated and will be removed in v12. Use setRouteIdentifier() instead.', E_USER_DEPRECATED);
$this->moduleName = $moduleName;
return $this;
}
/**
* Gets the display name of the module.
*
......@@ -167,56 +122,6 @@ class ShortcutButton implements ButtonInterface, PositionInterface
return $this;
}
/**
* Gets the SET variables.
*
* @return array
* @deprecated since v11, will be removed in v12
*/
public function getSetVariables()
{
trigger_error('Method getSetVariables() is deprecated and will be removed in v12. Please use ShortcutButton->setArguments() instead.', E_USER_DEPRECATED);
return $this->setVariables;
}
/**
* Sets the SET variables.
*
* @param array $setVariables
* @return ShortcutButton
* @deprecated since v11, will be removed in v12. Deprecation logged by ModuleTemplate->makeShortcutIcon()
*/
public function setSetVariables(array $setVariables)
{
$this->setVariables = $setVariables;
return $this;
}
/**
* Gets the GET variables.
*
* @return array
* @deprecated since v11, will be removed in v12
*/
public function getGetVariables()
{
trigger_error('Method getGetVariables() is deprecated and will be removed in v12. Please use ShortcutButton->setArguments() instead.', E_USER_DEPRECATED);
return $this->getVariables;
}
/**
* Sets the GET variables.
*
* @param array $getVariables
* @return ShortcutButton
* @deprecated since v11, will be removed in v12. Deprecation logged by ModuleTemplate->makeShortcutIcon()
*/
public function setGetVariables(array $getVariables)
{
$this->getVariables = $getVariables;
return $this;
}
/**
* Defines whether the shortcut button should be extended to also
* allow copying the current URL to the operating systems' clipboard.
......@@ -267,7 +172,7 @@ class ShortcutButton implements ButtonInterface, PositionInterface
*/
public function isValid()
{
return $this->moduleName !== '' || $this->routeIdentifier !== '' || (string)($this->arguments['route'] ?? '') !== '';
return $this->displayName !== '' && $this->routeExists($this->routeIdentifier);
}
/**
......@@ -287,62 +192,21 @@ class ShortcutButton implements ButtonInterface, PositionInterface
*/
public function render()
{
if ($this->getBackendUser()->mayMakeShortcut()) {
if ($this->displayName === '') {
trigger_error('Creating a shortcut button without a display name is deprecated and fallbacks will be removed in v12. Please use ShortcutButton->setDisplayName() to set a display name.', E_USER_DEPRECATED);
}
if (!empty($this->routeIdentifier) || !empty($this->arguments)) {
$shortcutMarkup = $this->createShortcutMarkup();
} else {
// @deprecated since v11, the else branch will be removed in v12. Deprecation thrown by makeShortcutIcon() below
if (empty($this->getVariables)) {
$this->getVariables = ['id', 'route'];
}
$moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
$shortcutMarkup = $moduleTemplate->makeShortcutIcon(
implode(',', $this->getVariables),
implode(',', $this->setVariables),
$this->moduleName,
'',
$this->displayName
);
}
} else {
$shortcutMarkup = '';
if (!$this->getBackendUser()->mayMakeShortcut()) {
// Early return in case the current user is not allowed to create shortcuts.
// Note: This is not checked in isValid(), since it only concerns the current
// user and does not mean, the button is not configured properly.
return '';
}
return $shortcutMarkup;
}
protected function createShortcutMarkup(): string
{
$routeIdentifier = $this->routeIdentifier;
$arguments = $this->arguments;
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
if (str_contains($routeIdentifier, '/')) {
trigger_error('Automatic fallback for the route path is deprecated and will be removed in v12.', E_USER_DEPRECATED);
$routeIdentifier = $this->getRouteIdentifierByRoutePath($routeIdentifier);
}
if ($routeIdentifier === '' && $this->moduleName !== '') {
trigger_error('Using ShortcutButton::$moduleNname is deprecated and will be removed in v12. Use ShortcutButton::$routeIdentifier instead.', E_USER_DEPRECATED);
$routeIdentifier = $this->getRouteIdentifierByModuleName($this->moduleName);
}
// The route parameter is not needed, since this is already provided with the $routeIdentifier
unset($arguments['route']);
if (isset($arguments['route'])) {
trigger_error('Using route as an argument is deprecated and will be removed in v12. Set the route identifier with ShortcutButton::setRouteIdentifier() instead.', E_USER_DEPRECATED);
if ($routeIdentifier === '' && is_string($arguments['route'])) {
$routeIdentifier = $this->getRouteIdentifierByRoutePath($arguments['route']);
}
unset($arguments['route']);
}
// No route found so no shortcut button will be rendered
if ($routeIdentifier === '' || !$this->routeExists($routeIdentifier)) {
return '';
}
// returnUrl will not longer be stored in the database
// returnUrl should not be stored in the database
unset($arguments['returnUrl']);
// Encode arguments to be stored in the database
......@@ -410,64 +274,6 @@ class ShortcutButton implements ButtonInterface, PositionInterface
'</ul>';
}
/**
* Map a given route path to its route identifier
*
* @param string $routePath
* @return string
* @deprecated Only for backwards compatibility. Can be removed in v12.
*/
protected function getRouteIdentifierByRoutePath(string $routePath): string
{
foreach ($this->getRoutes() as $identifier => $route) {
if ($route->getPath() === $routePath
&& (
$route->hasOption('moduleName')
|| in_array($identifier, ['record_edit', 'file_edit', 'wizard_rte'], true)
)
) {
return $identifier;
}
}
return '';
}
/**
* Map a given module name to its route identifier by respecting some special cases
*
* @param string $moduleName
* @return string
* @deprecated Only for backwards compatibility. Can be removed in v12.
*/
protected function getRouteIdentifierByModuleName(string $moduleName): string
{
$identifier = '';
// Special case module names
switch ($moduleName) {
case 'xMOD_alt_doc.php':
$identifier = 'record_edit';
break;
case 'file_edit':
case 'wizard_rte':
$identifier = $moduleName;
break;
}
if ($identifier !== '') {
return $identifier;
}
foreach ($this->getRoutes() as $identifier => $route) {
if ($route->hasOption('moduleName') && $route->getOption('moduleName') === $moduleName) {
return $identifier;
}
}
return '';
}
/**
* Returns HTML attributes for client-side `ActionDispatcher` of the "add shortcut" button.
*
......
......@@ -16,14 +16,11 @@
namespace TYPO3\CMS\Backend\Template;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository;
use TYPO3\CMS\Backend\Routing\Route;
use TYPO3\CMS\Backend\Routing\Router;
use TYPO3\CMS\Backend\Template\Components\DocHeaderComponent;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
use TYPO3\CMS\Core\Imaging\Icon;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Messaging\AbstractMessage;
......@@ -33,7 +30,6 @@ use TYPO3\CMS\Core\Messaging\FlashMessageService;
use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\HttpUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
use TYPO3\CMS\Fluid\View\StandaloneView;
use TYPO3Fluid\Fluid\View\ViewInterface;
......@@ -534,137 +530,6 @@ class ModuleTemplate
* Do not use these methods within own extensions if possible or
* be prepared to change this later again.
*******************************************/
/**
* Returns a linked shortcut-icon which will call the shortcut frame and set a
* shortcut there back to the calling page/module
*
* @param string $gvList Is the list of GET variables to store (if any)
* @param string $setList Is the list of SET[] variables to store
* (if any) - SET[] variables a stored in $GLOBALS["SOBE"]->MOD_SETTINGS
* for backend modules
* @param string $modName Module name string
* @param string|int $motherModName Is used to enter the "parent module
* name" if the module is a submodule under eg. Web>* or File>*. You
* can also set this value to 1 in which case the currentLoadedModule
* is sent to the shortcut script (so - not a fixed value!) - that is used
* in file_edit and wizard_rte modules where those are really running as
* a part of another module.
* @param string $displayName When given this name is used instead of the
* module name.
* @param string $classes Additional CSS classes for the link around the icon
*
* @return string HTML content
* @todo Make this thing return a button object
* @internal
* @deprecated since v11, will be removed in v12
*/
public function makeShortcutIcon($gvList, $setList, $modName, $motherModName = '', $displayName = '', $classes = 'btn btn-default btn-sm')
{
trigger_error('Method makeShortcutIcon() is deprecated and will be removed in v12. Please use ShortcutButton->setArguments() instead.', E_USER_DEPRECATED);
$gvList = 'route,id,' . $gvList;
$storeUrl = $this->makeShortcutUrl($gvList, $setList);
$pathInfo = parse_url($GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri());
// Fallback for alt_mod. We still pass in the old xMOD... stuff,
// but TBE_MODULES only knows about "record_edit".
// We still need to pass the xMOD name to createShortcut below,
// since this is used for icons.
$moduleName = $modName === 'xMOD_alt_doc.php' ? 'record_edit' : $modName;
// Add the module identifier automatically if typo3/index.php is used:
// @todo: routing
if (GeneralUtility::_GET('route') !== null) {
$storeUrl = '&route=' . $moduleName . $storeUrl;
}
$shortcutUrl = $pathInfo['path'] . '?' . $storeUrl;
// We simply let the above functionality as it is for maximum backwards compatibility and now
// just process the generated $shortcutUrl to match the new format (routeIdentifier & arguments)
[$routeIdentifier, $arguments] = $this->getCreateShortcutProperties($shortcutUrl);
if (GeneralUtility::makeInstance(ShortcutRepository::class)->shortcutExists($routeIdentifier, $arguments)) {
return '<a class="active ' . htmlspecialchars($classes) . '" title="">' .
$this->iconFactory->getIcon('actions-system-shortcut-active', Icon::SIZE_SMALL)->render() . '</a>';
}
$confirmationText = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.makeBookmark');
$attrs = [
'href' => '#',
'class' => $classes,
'title' => $confirmationText,
'data-dispatch-action' => 'TYPO3.ShortcutMenu.createShortcut',
'data-dispatch-args' => GeneralUtility::jsonEncodeForHtmlAttribute([
$routeIdentifier,
$arguments,
$displayName,
$confirmationText,
'{$target}',
], false),
];
return sprintf(
'<a %s>%s</a>',
GeneralUtility::implodeAttributes($attrs, true),
$this->iconFactory->getIcon('actions-system-shortcut-new', Icon::SIZE_SMALL)->render()
);
}
/**
* MAKE url for storing
* Internal func
*
* @param string $gvList Is the list of GET variables to store (if any)
* @param string $setList Is the list of SET[] variables to store (if any)
* - SET[] variables a stored in $GLOBALS["SOBE"]->MOD_SETTINGS for backend
* modules
*
* @return string GET-parameters for the shortcut-url only(!). String starts with '&'
* @internal
* @deprecated since v11, will be removed in v12. Deprecation logged by parent method makeShortcutIcon()
*/
public function makeShortcutUrl($gvList, $setList)
{
$getParams = GeneralUtility::_GET();
$storeArray = array_merge(
GeneralUtility::compileSelectedGetVarsFromArray($gvList, $getParams),
['SET' => GeneralUtility::compileSelectedGetVarsFromArray($setList, (array)($GLOBALS['SOBE']->MOD_SETTINGS ?? []))]
);
return HttpUtility::buildQueryString($storeArray, '&');
}
/**
* Process the generated shortcut url and return properties needed for the
* shortcut registration with route identifier and JSON encoded arguments.
*
* @param string $shortcutUrl
*
* @return array
* @deprecated Only for backwards compatibility. Can be removed in v12
*/
protected function getCreateShortcutProperties(string $shortcutUrl): array
{
$routeIdentifier = '';
$arguments = [];
parse_str(parse_url($shortcutUrl)['query'] ?? '', $arguments);
$routePath = (string)($arguments['route'] ?? '');
if ($routePath !== '') {
foreach (GeneralUtility::makeInstance(Router::class)->getRoutes() as $identifier => $route) {
if ($route->getPath() === $routePath
&& (
$route->hasOption('moduleName')
|| in_array($identifier, ['record_edit', 'file_edit', 'wizard_rte'], true)
)
) {
$routeIdentifier = $identifier;
}
}
}
unset($arguments['route'], $arguments['returnUrl']);
return [$routeIdentifier, json_encode($arguments)];
}
/**
* Retrieves configured favicon for backend (with fallback)
......
<
......@@ -7,5 +7,4 @@
,5,2,"Wrong user",1,"web_layout","{""id"":""123""}"
,6,1,,1,"web_layout","{""id"":""123""}"
,7,1,"Recordlist of id 111",0,"web_list","{""id"":111,""GET"":{""clipBoard"":1}}"
,8,1,"Edit Page",0,"record_edit","{""edit"":{""pages"":{""112"":""edit""}}}"
,9,1,"Page content",0,"web_layout","[]"
,8,1,"Shortcut",0,"record_edit","{""edit"":{""pages"":{""112"":""edit""}}}"