diff --git a/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/Toolbar/ShortcutMenu.ts b/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/Toolbar/ShortcutMenu.ts index 289b33554416e449e846856b17e2434cf6c0a2f7..cab8f00a6b1fa11ae69af4f4a648967d4bbd6c4f 100644 --- a/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/Toolbar/ShortcutMenu.ts +++ b/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/Toolbar/ShortcutMenu.ts @@ -49,20 +49,18 @@ class ShortcutMenu { * makes a call to the backend class to create a new shortcut, * when finished it reloads the menu * - * @param {String} moduleName - * @param {String} url + * @param {String} routeIdentifier + * @param {String} routeArguments + * @param {String} displayName * @param {String} confirmationText - * @param {String} motherModule * @param {Object} shortcutButton - * @param {String} displayName */ public createShortcut( - moduleName: string, - url: string, + routeIdentifier: string, + routeArguments: string, + displayName: string, confirmationText: string, - motherModule: string, shortcutButton: JQuery, - displayName: string, ): void { if (typeof confirmationText !== 'undefined') { Modal.confirm(TYPO3.lang['bookmark.create'], confirmationText).on('confirm.button.ok', (e: JQueryEventObject): void => { @@ -74,9 +72,8 @@ class ShortcutMenu { }); (new AjaxRequest(TYPO3.settings.ajaxUrls.shortcut_create)).post({ - module: moduleName, - url: url, - motherModName: motherModule, + routeIdentifier: routeIdentifier, + arguments: routeArguments, displayName: displayName, }).then((): void => { this.refreshMenu(); diff --git a/typo3/sysext/backend/Classes/Backend/Shortcut/ShortcutRepository.php b/typo3/sysext/backend/Classes/Backend/Shortcut/ShortcutRepository.php index 01aa444c6422f938ffadd24b1ea10356f006b1d5..30d46a147c2532637ecbb8105ebb2f4b1ae3bb55 100644 --- a/typo3/sysext/backend/Classes/Backend/Shortcut/ShortcutRepository.php +++ b/typo3/sysext/backend/Classes/Backend/Shortcut/ShortcutRepository.php @@ -17,8 +17,8 @@ declare(strict_types=1); namespace TYPO3\CMS\Backend\Backend\Shortcut; +use Symfony\Component\Routing\Route; use TYPO3\CMS\Backend\Module\ModuleLoader; -use TYPO3\CMS\Backend\Routing\Exception\ResourceNotFoundException; use TYPO3\CMS\Backend\Routing\Router; use TYPO3\CMS\Backend\Routing\UriBuilder; use TYPO3\CMS\Backend\Utility\BackendUtility; @@ -32,7 +32,6 @@ use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException; use TYPO3\CMS\Core\Resource\ResourceFactory; use TYPO3\CMS\Core\Type\Bitmask\Permission; use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3\CMS\Core\Utility\MathUtility; /** * Repository for backend shortcuts @@ -46,33 +45,23 @@ class ShortcutRepository */ protected const SUPERGLOBAL_GROUP = -100; - /** - * @var array - */ - protected $shortcuts; + protected const TABLE_NAME = 'sys_be_shortcuts'; - /** - * @var array - */ - protected $shortcutGroups; + protected array $shortcuts; - /** - * @var IconFactory - */ - protected $iconFactory; + protected array $shortcutGroups; - /** - * @var ModuleLoader - */ - protected $moduleLoader; + protected ConnectionPool $connectionPool; - /** - * Constructor - */ - public function __construct() + protected IconFactory $iconFactory; + + protected ModuleLoader $moduleLoader; + + public function __construct(ConnectionPool $connectionPool, IconFactory $iconFactory, ModuleLoader $moduleLoader) { - $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class); - $this->moduleLoader = GeneralUtility::makeInstance(ModuleLoader::class); + $this->connectionPool = $connectionPool; + $this->iconFactory = $iconFactory; + $this->moduleLoader = $moduleLoader; $this->moduleLoader->load($GLOBALS['TBE_MODULES']); $this->shortcutGroups = $this->initShortcutGroups(); @@ -154,26 +143,24 @@ class ShortcutRepository /** * Returns if there already is a shortcut entry for a given TYPO3 URL * - * @param string $url + * @param string $routeIdentifier + * @param string $arguments * @return bool */ - public function shortcutExists(string $url): bool + public function shortcutExists(string $routeIdentifier, string $arguments): bool { - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) - ->getQueryBuilderForTable('sys_be_shortcuts'); + $queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::TABLE_NAME); $queryBuilder->getRestrictions()->removeAll(); $uid = $queryBuilder->select('uid') - ->from('sys_be_shortcuts') + ->from(self::TABLE_NAME) ->where( $queryBuilder->expr()->eq( 'userid', $queryBuilder->createNamedParameter($this->getBackendUser()->user['uid'], \PDO::PARAM_INT) ), - $queryBuilder->expr()->eq( - 'url', - $queryBuilder->createNamedParameter($url, \PDO::PARAM_STR) - ) + $queryBuilder->expr()->eq('route', $queryBuilder->createNamedParameter($routeIdentifier)), + $queryBuilder->expr()->eq('arguments', $queryBuilder->createNamedParameter($arguments)) ) ->execute() ->fetchColumn(); @@ -184,58 +171,49 @@ class ShortcutRepository /** * Add a shortcut * - * @param string $url URL of the new shortcut - * @param string $module module identifier of the new shortcut - * @param string $parentModule parent module identifier of the new shortcut + * @param string $routeIdentifier route identifier of the new shortcut + * @param string $arguments arguments of the new shortcut * @param string $title title of the new shortcut * @return bool * @throws \RuntimeException if the given URL is invalid */ - public function addShortcut(string $url, string $module, string $parentModule = '', string $title = ''): bool + public function addShortcut(string $routeIdentifier, string $arguments = '', string $title = ''): bool { - // @todo $parentModule can not longer be set using public API. - - if (empty($url) || empty($module)) { + // Do not add shortcuts for routes which do not exist + if (!$this->routeExists($routeIdentifier)) { return false; } - $queryParts = parse_url($url); - $queryParameters = []; - parse_str($queryParts['query'] ?? '', $queryParameters); - - if (!empty($queryParameters['scheme'])) { - throw new \RuntimeException('Shortcut URLs must be relative', 1518785877); - } - $languageService = $this->getLanguageService(); - $titlePrefix = ''; - $type = 'other'; - $table = ''; - $recordId = 0; - $pageId = 0; - - if (is_array($queryParameters['edit'])) { - $table = (string)key($queryParameters['edit']); - $recordId = (int)key($queryParameters['edit'][$table]); - $pageId = (int)BackendUtility::getRecord($table, $recordId)['pid']; - $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; - } - } // 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'])) { + $table = (string)key($queryParameters['edit']); + $recordId = (int)key($queryParameters['edit'][$table]); + $pageId = (int)BackendUtility::getRecord($table, $recordId)['pid']; + $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; + } + } // Check if given id is a combined identifier if (!empty($queryParameters['id']) && preg_match('/^[\d]+:/', $queryParameters['id'])) { try { @@ -250,7 +228,7 @@ class ShortcutRepository } } else { // Lookup the title of this page and use it as default description - $pageId = $pageId ?: $recordId ?: $this->extractPageIdFromShortcutUrl($url); + $pageId = $pageId ?: $recordId ?: (int)($queryParameters['id'] ?? 0); $page = $pageId ? BackendUtility::getRecord('pages', $pageId) : null; if (!empty($page)) { @@ -282,21 +260,19 @@ class ShortcutRepository // 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($module); - + $moduleLabels = $this->moduleLoader->getLabelsForModule($this->getModuleNameFromRouteIdentifier($routeIdentifier)); if (!empty($moduleLabels['shortdescription'])) { $title = $this->getLanguageService()->sL($moduleLabels['shortdescription']); } } - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) - ->getQueryBuilderForTable('sys_be_shortcuts'); + $queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::TABLE_NAME); $affectedRows = $queryBuilder - ->insert('sys_be_shortcuts') + ->insert(self::TABLE_NAME) ->values([ 'userid' => $this->getBackendUser()->user['uid'], - 'module_name' => $module . '|' . $parentModule, - 'url' => $url, + 'route' => $routeIdentifier, + 'arguments' => $arguments, 'description' => $title ?: 'Shortcut', 'sorting' => $GLOBALS['EXEC_TIME'], ]) @@ -316,9 +292,8 @@ class ShortcutRepository public function updateShortcut(int $id, string $title, int $groupId): bool { $backendUser = $this->getBackendUser(); - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) - ->getQueryBuilderForTable('sys_be_shortcuts'); - $queryBuilder->update('sys_be_shortcuts') + $queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::TABLE_NAME); + $queryBuilder->update(self::TABLE_NAME) ->where( $queryBuilder->expr()->eq( 'uid', @@ -358,10 +333,9 @@ class ShortcutRepository $shortcut = $this->getShortcutById($id); $success = false; - if ($shortcut['raw']['userid'] == $this->getBackendUser()->user['uid']) { - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) - ->getQueryBuilderForTable('sys_be_shortcuts'); - $affectedRows = $queryBuilder->delete('sys_be_shortcuts') + if ((int)$shortcut['raw']['userid'] === (int)$this->getBackendUser()->user['uid']) { + $queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::TABLE_NAME); + $affectedRows = $queryBuilder->delete(self::TABLE_NAME) ->where( $queryBuilder->expr()->eq( 'uid', @@ -459,13 +433,12 @@ class ShortcutRepository protected function initShortcuts(): array { $backendUser = $this->getBackendUser(); - // Traverse shortcuts $lastGroup = 0; $shortcuts = []; - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) - ->getQueryBuilderForTable('sys_be_shortcuts'); + + $queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::TABLE_NAME); $result = $queryBuilder->select('*') - ->from('sys_be_shortcuts') + ->from(self::TABLE_NAME) ->where( $queryBuilder->expr()->andX( $queryBuilder->expr()->eq( @@ -493,21 +466,16 @@ class ShortcutRepository while ($row = $result->fetch()) { $shortcut = ['raw' => $row]; + $routeIdentifier = $row['route'] ?? ''; + $arguments = json_decode($row['arguments'] ?? '', true) ?? []; - [$row['module_name'], $row['M_module_name']] = explode('|', $row['module_name']); - - $queryParts = parse_url($row['url']); - // Explode GET vars recursively - $queryParameters = []; - parse_str($queryParts['query'] ?? '', $queryParameters); + if ($routeIdentifier === 'record_edit' && is_array($arguments['edit'])) { + $shortcut['table'] = key($arguments['edit']); + $shortcut['recordid'] = key($arguments['edit'][$shortcut['table']]); - if ($row['module_name'] === 'xMOD_alt_doc.php' && is_array($queryParameters['edit'])) { - $shortcut['table'] = key($queryParameters['edit']); - $shortcut['recordid'] = key($queryParameters['edit'][$shortcut['table']]); - - if ($queryParameters['edit'][$shortcut['table']][$shortcut['recordid']] === 'edit') { + if ($arguments['edit'][$shortcut['table']][$shortcut['recordid']] === 'edit') { $shortcut['type'] = 'edit'; - } elseif ($queryParameters['edit'][$shortcut['table']][$shortcut['recordid']] === 'new') { + } elseif ($arguments['edit'][$shortcut['table']][$shortcut['recordid']] === 'new') { $shortcut['type'] = 'new'; } @@ -518,56 +486,57 @@ class ShortcutRepository $shortcut['type'] = 'other'; } - // Check for module access - $moduleName = $row['M_module_name'] ?: $row['module_name']; + $moduleName = $this->getModuleNameFromRouteIdentifier($routeIdentifier); + + // Skip shortcut if module name can not be resolved + if ($moduleName === '') { + continue; + } // Check if the user has access to this module // @todo Hack for EditDocumentController / FormEngine, see issues #91368 and #91210 - if (!is_array($this->moduleLoader->checkMod($moduleName)) && $moduleName !== 'xMOD_alt_doc.php') { + if ($routeIdentifier !== 'record_edit' && !is_array($this->moduleLoader->checkMod($moduleName))) { continue; } - - $pageId = $this->extractPageIdFromShortcutUrl($row['url']); - - if (!$backendUser->isAdmin()) { - if (MathUtility::canBeInterpretedAsInteger($pageId)) { - // Check for webmount access - if ($backendUser->isInWebMount($pageId) === null) { - continue; - } - // Check for record access - $pageRow = BackendUtility::getRecord('pages', $pageId); - - if ($pageRow === null) { - continue; - } - - if (!$backendUser->doesUserHaveAccess($pageRow, Permission::PAGE_SHOW)) { - continue; - } + if (($pageId = ((int)($arguments['id'] ?? 0))) > 0 && !$backendUser->isAdmin()) { + // Check for webmount access + if ($backendUser->isInWebMount($pageId) === null) { + continue; + } + // Check for record access + $pageRow = BackendUtility::getRecord('pages', $pageId); + if ($pageRow === null || !$backendUser->doesUserHaveAccess($pageRow, Permission::PAGE_SHOW)) { + continue; } } - $moduleParts = explode('_', $moduleName); $shortcutGroup = (int)$row['sc_group']; - if ($shortcutGroup && $lastGroup !== $shortcutGroup && $shortcutGroup !== self::SUPERGLOBAL_GROUP) { $shortcut['groupLabel'] = $this->getShortcutGroupLabel($shortcutGroup); } - $lastGroup = $shortcutGroup; - if ($row['description']) { - $shortcut['label'] = $row['description']; - } else { - $shortcut['label'] = GeneralUtility::fixed_lgd_cs(rawurldecode($queryParts['query']), 150); + $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); + } } - $shortcut['group'] = $shortcutGroup; - $shortcut['icon'] = $this->getShortcutIcon($row, $shortcut); - $shortcut['iconTitle'] = $this->getShortcutIconTitle($shortcut['label'], $row['module_name'], $row['M_module_name']); - $shortcut['action'] = 'jump(' . GeneralUtility::quoteJSvalue($this->getTokenUrl($row['url'])) . ',' . GeneralUtility::quoteJSvalue($moduleName) . ',' . GeneralUtility::quoteJSvalue($moduleParts[0]) . ', ' . (int)$pageId . ');'; + $shortcutUrl = (string)GeneralUtility::makeInstance(UriBuilder::class)->buildUriFromRoute($routeIdentifier, $arguments); + $shortcut['group'] = $shortcutGroup; + $shortcut['icon'] = $this->getShortcutIcon($routeIdentifier, $moduleName, $shortcut); + $shortcut['label'] = $description; + $shortcut['action'] = 'jump(' + . GeneralUtility::quoteJSvalue($shortcutUrl) + . ',' . GeneralUtility::quoteJSvalue($moduleName) + . ',' . GeneralUtility::quoteJSvalue($moduleName) + . ', ' . $pageId . ');'; $shortcuts[] = $shortcut; } @@ -606,15 +575,16 @@ class ShortcutRepository /** * Gets the icon for the shortcut * - * @param array $row + * @param string $routeIdentifier + * @param string $moduleName * @param array $shortcut * @return string Shortcut icon as img tag */ - protected function getShortcutIcon(array $row, array $shortcut): string + protected function getShortcutIcon(string $routeIdentifier, string $moduleName, array $shortcut): string { $selectFields = []; - switch ($row['module_name']) { - case 'xMOD_alt_doc.php': + switch ($routeIdentifier) { + case 'record_edit': $table = $shortcut['table']; $recordid = $shortcut['recordid']; $icon = ''; @@ -644,8 +614,7 @@ class ShortcutRepository $selectFields[] = 't3ver_oid'; } - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) - ->getQueryBuilderForTable($table); + $queryBuilder = $this->connectionPool->getQueryBuilderForTable($table); $queryBuilder->select(...array_unique(array_values($selectFields))) ->from($table) ->where( @@ -670,12 +639,11 @@ class ShortcutRepository break; default: $iconIdentifier = ''; - $moduleName = $row['module_name']; if (strpos($moduleName, '_') !== false) { [$mainModule, $subModule] = explode('_', $moduleName, 2); $iconIdentifier = $this->moduleLoader->modules[$mainModule]['sub'][$subModule]['iconIdentifier']; - } elseif (!empty($moduleName)) { + } elseif ($moduleName !== '') { $iconIdentifier = $this->moduleLoader->modules[$moduleName]['iconIdentifier']; } @@ -690,95 +658,52 @@ class ShortcutRepository } /** - * Returns title for the shortcut icon + * Get the module name from the resolved route or by static mapping for some special cases. * - * @param string $shortcutLabel Shortcut label - * @param string $moduleName Backend module name (key) - * @param string $parentModuleName Parent module label - * @return string Title for the shortcut icon + * @param string $routeIdentifier + * @return string */ - protected function getShortcutIconTitle(string $shortcutLabel, string $moduleName, string $parentModuleName = ''): string + protected function getModuleNameFromRouteIdentifier(string $routeIdentifier): string { - $languageService = $this->getLanguageService(); - - if (strpos($moduleName, 'xMOD_') === 0) { - $title = substr($moduleName, 5); - } else { - [$mainModule, $subModule] = explode('_', $moduleName); - $mainModuleLabels = $this->moduleLoader->getLabelsForModule($mainModule); - $title = $languageService->sL($mainModuleLabels['title']); - - if (!empty($subModule)) { - $subModuleLabels = $this->moduleLoader->getLabelsForModule($moduleName); - $title .= '>' . $languageService->sL($subModuleLabels['title']); - } - } - - if ($parentModuleName) { - $title .= ' (' . $parentModuleName . ')'; + if ($this->isSpecialRoute($routeIdentifier)) { + return $routeIdentifier; } - $title .= ': ' . $shortcutLabel; - - return $title; + $route = $this->getRoute($routeIdentifier); + return $route !== null ? (string)($route->getOption('moduleName') ?? '') : ''; } /** - * Return the ID of the page in the URL if found. + * Get the route for a given route identifier * - * @param string $url The URL of the current shortcut link - * @return int If a page ID was found, it is returned. Otherwise: 0 + * @param string $routeIdentifier + * @return Route|null */ - protected function extractPageIdFromShortcutUrl(string $url): int + protected function getRoute(string $routeIdentifier): ?Route { - return (int)preg_replace('/.*[\\?&]id=([^&]+).*/', '$1', $url); + return GeneralUtility::makeInstance(Router::class)->getRoutes()[$routeIdentifier] ?? null; } /** - * Adds the correct token, if the url is an index.php script - * @todo: this needs love + * Check if a route for the given identifier exists * - * @param string $url - * @return string + * @param string $routeIdentifier + * @return bool */ - protected function getTokenUrl(string $url): string + protected function routeExists(string $routeIdentifier): bool { - $parsedUrl = parse_url($url); - $parameters = []; - parse_str($parsedUrl['query'] ?? '', $parameters); - - $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); - // parse the returnUrl and replace the module token of it - if (!empty($parameters['returnUrl'])) { - $parsedReturnUrl = parse_url($parameters['returnUrl']); - $returnUrlParameters = []; - parse_str($parsedReturnUrl['query'] ?? '', $returnUrlParameters); - - if (strpos($parsedReturnUrl['path'] ?? '', 'index.php') !== false && !empty($returnUrlParameters['route'])) { - $module = $returnUrlParameters['route']; - $parameters['returnUrl'] = (string)$uriBuilder->buildUriFromRoutePath($module, $returnUrlParameters); - $url = $parsedUrl['path'] . '?' . http_build_query($parameters, '', '&', PHP_QUERY_RFC3986); - } - } - - if (strpos($parsedUrl['path'], 'index.php') !== false && isset($parameters['route'])) { - $routePath = $parameters['route']; - /** @var \TYPO3\CMS\Backend\Routing\Router $router */ - $router = GeneralUtility::makeInstance(Router::class); - - try { - $route = $router->match($routePath); + return $this->getRoute($routeIdentifier) !== null; + } - if ($route) { - $routeIdentifier = $route->getOption('_identifier'); - unset($parameters['route']); - $url = (string)$uriBuilder->buildUriFromRoute($routeIdentifier, $parameters); - } - } catch (ResourceNotFoundException $e) { - $url = ''; - } - } - return $url; + /** + * Check if given route identifier is a special "no module" route + * + * @param string $routeIdentifier + * @return bool + */ + protected function isSpecialRoute(string $routeIdentifier): bool + { + return in_array($routeIdentifier, ['record_edit', 'file_edit', 'wizard_rte'], true); } /** diff --git a/typo3/sysext/backend/Classes/Controller/EditDocumentController.php b/typo3/sysext/backend/Classes/Controller/EditDocumentController.php index 45a8787d08be0ba10e35686d0cd8ea8317436928..c0b4ae3cfa892e0e4e12995110f3a5a4050c6e82 100644 --- a/typo3/sysext/backend/Classes/Controller/EditDocumentController.php +++ b/typo3/sysext/backend/Classes/Controller/EditDocumentController.php @@ -1788,7 +1788,6 @@ class EditDocumentController if ($this->returnUrl !== $this->getCloseUrl()) { $queryParams = $request->getQueryParams(); $potentialArguments = [ - 'returnUrl', 'edit', 'defVals', 'overrideVals', @@ -1806,7 +1805,7 @@ class EditDocumentController } $shortCutButton = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()->makeShortcutButton(); $shortCutButton - ->setModuleName('xMOD_alt_doc.php') + ->setRouteIdentifier('record_edit') ->setDisplayName($this->getShortcutTitle($request)) ->setArguments($arguments); $buttonBar->addButton($shortCutButton, $position, $group); diff --git a/typo3/sysext/backend/Classes/Controller/HelpController.php b/typo3/sysext/backend/Classes/Controller/HelpController.php index c0e41b648a391b3e0c327ca7ac413644dcc7159b..c516e5d97ae7e4e5f58017f4225e82ef331572e5 100644 --- a/typo3/sysext/backend/Classes/Controller/HelpController.php +++ b/typo3/sysext/backend/Classes/Controller/HelpController.php @@ -174,10 +174,9 @@ class HelpController $action = $request->getQueryParams()['action'] ?? $request->getParsedBody()['action'] ?? 'index'; $shortcutButton = $buttonBar->makeShortcutButton() - ->setModuleName('help_cshmanual') + ->setRouteIdentifier('help_cshmanual') ->setDisplayName($this->getShortcutTitle($request)) ->setArguments([ - 'route' => $request->getQueryParams()['route'], 'action' => $action, 'table' => $request->getQueryParams()['table'] ?? '', 'field' => $request->getQueryParams()['field'] ?? '' diff --git a/typo3/sysext/backend/Classes/Controller/PageLayoutController.php b/typo3/sysext/backend/Classes/Controller/PageLayoutController.php index ba560d93af8886e697a632b7fc238b4e4297ede6..f33c01e361d32cdba9d3e57edbbbb1187a4b6e7d 100644 --- a/typo3/sysext/backend/Classes/Controller/PageLayoutController.php +++ b/typo3/sysext/backend/Classes/Controller/PageLayoutController.php @@ -764,10 +764,9 @@ class PageLayoutController } // Shortcut $shortcutButton = $this->buttonBar->makeShortcutButton() - ->setModuleName($this->moduleName) + ->setRouteIdentifier($this->moduleName) ->setDisplayName($this->getShortcutTitle()) ->setArguments([ - 'route' => $request->getQueryParams()['route'], 'id' => (int)$this->id, 'SET' => [ 'tt_content_showHidden' => (bool)$this->MOD_SETTINGS['tt_content_showHidden'], diff --git a/typo3/sysext/backend/Classes/Controller/ShortcutController.php b/typo3/sysext/backend/Classes/Controller/ShortcutController.php index eb6ec83576967f0ff4624e3d2d1fc4d5bdac1b63..093c87dbe564d33f24689a9659668185f6eabd17 100644 --- a/typo3/sysext/backend/Classes/Controller/ShortcutController.php +++ b/typo3/sysext/backend/Classes/Controller/ShortcutController.php @@ -83,16 +83,16 @@ class ShortcutController $result = 'success'; $parsedBody = $request->getParsedBody(); $queryParams = $request->getQueryParams(); - $url = rawurldecode($parsedBody['url'] ?? $queryParams['url'] ?? ''); + $routeIdentifier = $parsedBody['routeIdentifier'] ?? $queryParams['routeIdentifier'] ?? ''; + $arguments = $parsedBody['arguments'] ?? $queryParams['arguments'] ?? ''; - if ($this->shortcutRepository->shortcutExists($url)) { + if ($routeIdentifier === '') { + $result = 'missingRoute'; + } elseif ($this->shortcutRepository->shortcutExists($routeIdentifier, $arguments)) { $result = 'alreadyExists'; } else { - $moduleName = $parsedBody['module'] ?? ''; - $parentModuleName = $parsedBody['motherModName'] ?? ''; - $shortcutName = $parsedBody['displayName'] ?? ''; - $success = $this->shortcutRepository->addShortcut($url, $moduleName, $parentModuleName, $shortcutName); - + $shortcutName = $parsedBody['displayName'] ?? $queryParams['arguments'] ?? ''; + $success = $this->shortcutRepository->addShortcut($routeIdentifier, $arguments, $shortcutName); if (!$success) { $result = 'failed'; } diff --git a/typo3/sysext/backend/Classes/Controller/SiteConfigurationController.php b/typo3/sysext/backend/Classes/Controller/SiteConfigurationController.php index 2aec52beab1752d40770a91c41da115a0e558c88..45e9cff49ac8be42f75672d5a129c85f87eb3cd7 100644 --- a/typo3/sysext/backend/Classes/Controller/SiteConfigurationController.php +++ b/typo3/sysext/backend/Classes/Controller/SiteConfigurationController.php @@ -640,7 +640,7 @@ class SiteConfigurationController ->setIcon($iconFactory->getIcon('actions-refresh', Icon::SIZE_SMALL)); $buttonBar->addButton($reloadButton, ButtonBar::BUTTON_POSITION_RIGHT); $shortcutButton = $buttonBar->makeShortcutButton() - ->setModuleName('site_configuration') + ->setRouteIdentifier('site_configuration') ->setDisplayName($this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_siteconfiguration_module.xlf:mlang_labels_tablabel')) ->setArguments([ 'route' => $route diff --git a/typo3/sysext/backend/Classes/Template/Components/Buttons/Action/ShortcutButton.php b/typo3/sysext/backend/Classes/Template/Components/Buttons/Action/ShortcutButton.php index 3d4753bdb8c3d51d5d54d4c8d971c21be4bbde2a..9cb2197640761f7ceb2f7135f7e6b8592d1bb1a7 100644 --- a/typo3/sysext/backend/Classes/Template/Components/Buttons/Action/ShortcutButton.php +++ b/typo3/sysext/backend/Classes/Template/Components/Buttons/Action/ShortcutButton.php @@ -16,6 +16,7 @@ namespace TYPO3\CMS\Backend\Template\Components\Buttons\Action; use TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository; +use TYPO3\CMS\Backend\Routing\Router; use TYPO3\CMS\Backend\Template\Components\ButtonBar; use TYPO3\CMS\Backend\Template\Components\Buttons\ButtonInterface; use TYPO3\CMS\Backend\Template\Components\Buttons\PositionInterface; @@ -25,7 +26,6 @@ use TYPO3\CMS\Core\Imaging\Icon; use TYPO3\CMS\Core\Imaging\IconFactory; use TYPO3\CMS\Core\Localization\LanguageService; use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3\CMS\Core\Utility\HttpUtility; /** * ShortcutButton @@ -36,17 +36,25 @@ use TYPO3\CMS\Core\Utility\HttpUtility; * EXAMPLE USAGE TO ADD A SHORTCUT BUTTON: * * $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar(); + * $pageId = (int)($request->getQueryParams()['id'] ?? 0); * $myButton = $buttonBar->makeShortcutButton() + * ->setRouteIdentifier('web_view') + * ->setDisplayName('View page ' . $pageId) * ->setArguments([ - * 'route' => $request->getQueryParams()['route'] - * ]) - * ->setModuleName('my_info'); + * 'id' => $pageId + * ]); * $buttonBar->addButton($myButton); */ class ShortcutButton implements ButtonInterface, PositionInterface { + /** + * @var string The route identifier of the shortcut + */ + protected string $routeIdentifier = ''; + /** * @var string + * @deprecated since v11, will be removed in v12 */ protected $moduleName = ''; @@ -72,13 +80,37 @@ class ShortcutButton implements ButtonInterface, PositionInterface */ protected $getVariables = []; + /** + * Gets the route identifier for the shortcut. + * + * @return string + */ + public function getRouteIdentifier(): string + { + return $this->routeIdentifier; + } + + /** + * Sets the route identifier for the shortcut. + * + * @param string $routeIdentifier + * @return ShortcutButton + */ + public function setRouteIdentifier(string $routeIdentifier): self + { + $this->routeIdentifier = $routeIdentifier; + 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; } @@ -87,9 +119,11 @@ class ShortcutButton implements ButtonInterface, PositionInterface * * @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; } @@ -213,7 +247,7 @@ class ShortcutButton implements ButtonInterface, PositionInterface */ public function isValid() { - return !empty($this->moduleName); + return $this->moduleName !== '' || $this->routeIdentifier !== '' || (string)($this->arguments['route'] ?? '') !== ''; } /** @@ -237,7 +271,7 @@ class ShortcutButton implements ButtonInterface, PositionInterface 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->arguments)) { + 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 @@ -261,36 +295,124 @@ class ShortcutButton implements ButtonInterface, PositionInterface protected function createShortcutMarkup(): string { - $moduleName = $this->moduleName; - $storeUrl = HttpUtility::buildQueryString($this->arguments, '&'); + $routeIdentifier = $this->routeIdentifier; + $arguments = $this->arguments; + $iconFactory = GeneralUtility::makeInstance(IconFactory::class); - // Find out if this shortcut exists already. Note this is a hack based on the fact - // that sys_be_shortcuts stores the entire request string and not just needed params as array. - $pathInfo = parse_url(GeneralUtility::getIndpEnv('REQUEST_URI')); - $shortcutUrl = $pathInfo['path'] . '?' . $storeUrl; - $shortcutRepository = GeneralUtility::makeInstance(ShortcutRepository::class); - $shortcutExist = $shortcutRepository->shortcutExists($shortcutUrl); + if (strpos($routeIdentifier, '/') !== false) { + trigger_error('Automatic fallback for the route path is deprecated and will be removed in v12.', E_USER_DEPRECATED); + $routeIdentifier = $this->getRouteIdentifierByRoutePath($routeIdentifier); + } - $iconFactory = GeneralUtility::makeInstance(IconFactory::class); - if ($shortcutExist) { - $shortcutMarkup = '<a class="active btn btn-default btn-sm" title="">' + 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); + } + + 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 + unset($arguments['returnUrl']); + + // Encode arguments to be stored in the database + $arguments = json_encode($arguments); + + if (GeneralUtility::makeInstance(ShortcutRepository::class)->shortcutExists($routeIdentifier, $arguments)) { + return '<a class="active btn btn-default btn-sm" title="">' . $iconFactory->getIcon('actions-system-shortcut-active', Icon::SIZE_SMALL)->render() . '</a>'; - } else { - $languageService = $this->getLanguageService(); - $confirmationText = $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.makeBookmark'); - $onClick = 'top.TYPO3.ShortcutMenu.createShortcut(' - . GeneralUtility::quoteJSvalue(rawurlencode($moduleName)) - . ', ' . GeneralUtility::quoteJSvalue(rawurlencode($shortcutUrl)) - . ', ' . GeneralUtility::quoteJSvalue($confirmationText) - . ', \'\'' - . ', this' - . ', ' . GeneralUtility::quoteJSvalue($this->displayName) . ');return false;'; - $shortcutMarkup = '<a href="#" class="btn btn-default btn-sm" onclick="' . htmlspecialchars($onClick) . '" title="' - . htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.makeBookmark')) . '">' - . $iconFactory->getIcon('actions-system-shortcut-new', Icon::SIZE_SMALL)->render() . '</a>'; } - return $shortcutMarkup; + + $confirmationText = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.makeBookmark'); + $onClick = 'top.TYPO3.ShortcutMenu.createShortcut(' + . GeneralUtility::quoteJSvalue($routeIdentifier) + . ', ' . GeneralUtility::quoteJSvalue($arguments) + . ', ' . GeneralUtility::quoteJSvalue($this->displayName) + . ', ' . GeneralUtility::quoteJSvalue($confirmationText) + . ', this);return false;'; + + return '<a href="#" class="btn btn-default btn-sm" onclick="' . htmlspecialchars($onClick) . '" title="' . htmlspecialchars($confirmationText) . '">' + . $iconFactory->getIcon('actions-system-shortcut-new', Icon::SIZE_SMALL)->render() + . '</a>'; + } + + /** + * 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 ''; + } + + protected function routeExists(string $routeIdentifier): bool + { + return (bool)($this->getRoutes()[$routeIdentifier] ?? false); + } + + protected function getRoutes(): iterable + { + return GeneralUtility::makeInstance(Router::class)->getRoutes(); } protected function getBackendUser(): BackendUserAuthentication diff --git a/typo3/sysext/backend/Classes/Template/ModuleTemplate.php b/typo3/sysext/backend/Classes/Template/ModuleTemplate.php index fc19116df4b39d9f94c660e13c4b5b3861fe78aa..0aa03794a2ae0381b3a554e307ff193a37b769fd 100644 --- a/typo3/sysext/backend/Classes/Template/ModuleTemplate.php +++ b/typo3/sysext/backend/Classes/Template/ModuleTemplate.php @@ -16,6 +16,7 @@ namespace TYPO3\CMS\Backend\Template; use TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository; +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; @@ -540,32 +541,28 @@ class ModuleTemplate if (GeneralUtility::_GET('route') !== null) { $storeUrl = '&route=' . $moduleName . $storeUrl; } - if ((int)$motherModName === 1) { - $motherModule = 'top.currentModuleLoaded'; - } elseif (is_string($motherModName) && $motherModName !== '') { - $motherModule = GeneralUtility::quoteJSvalue($motherModName); - } else { - $motherModule = '\'\''; - } - $confirmationText = GeneralUtility::quoteJSvalue( - $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.makeBookmark') - ); $shortcutUrl = $pathInfo['path'] . '?' . $storeUrl; - $shortcutRepository = GeneralUtility::makeInstance(ShortcutRepository::class); - $shortcutExist = $shortcutRepository->shortcutExists($shortcutUrl); - if ($shortcutExist) { + // 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>'; } - $url = GeneralUtility::quoteJSvalue(rawurlencode($shortcutUrl)); - $onClick = 'top.TYPO3.ShortcutMenu.createShortcut(' . GeneralUtility::quoteJSvalue(rawurlencode($modName)) . - ', ' . $url . ', ' . $confirmationText . ', ' . $motherModule . ', this, ' . GeneralUtility::quoteJSvalue($displayName) . ');return false;'; + $confirmationText = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.makeBookmark'); + $onClick = 'top.TYPO3.ShortcutMenu.createShortcut(' + . GeneralUtility::quoteJSvalue($routeIdentifier) + . ', ' . GeneralUtility::quoteJSvalue($arguments) + . ', ' . GeneralUtility::quoteJSvalue($displayName) + . ', ' . GeneralUtility::quoteJSvalue($confirmationText) + . ', this);return false;'; return '<a href="#" class="' . htmlspecialchars($classes) . '" onclick="' . htmlspecialchars($onClick) . '" title="' . - htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.makeBookmark')) . '">' . + htmlspecialchars($confirmationText) . '">' . $this->iconFactory->getIcon('actions-system-shortcut-new', Icon::SIZE_SMALL)->render() . '</a>'; } @@ -592,6 +589,41 @@ class ModuleTemplate 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) * diff --git a/typo3/sysext/backend/Classes/ViewHelpers/ModuleLayout/Button/ShortcutButtonViewHelper.php b/typo3/sysext/backend/Classes/ViewHelpers/ModuleLayout/Button/ShortcutButtonViewHelper.php index e342d05f4245c53941b112a3a4605dcd44aa3187..d5431f529e73ec20b488865f10be7b97fc133ace 100644 --- a/typo3/sysext/backend/Classes/ViewHelpers/ModuleLayout/Button/ShortcutButtonViewHelper.php +++ b/typo3/sysext/backend/Classes/ViewHelpers/ModuleLayout/Button/ShortcutButtonViewHelper.php @@ -17,6 +17,7 @@ declare(strict_types=1); namespace TYPO3\CMS\Backend\ViewHelpers\ModuleLayout\Button; +use TYPO3\CMS\Backend\Routing\Router; use TYPO3\CMS\Backend\Template\Components\ButtonBar; use TYPO3\CMS\Backend\Template\Components\Buttons\ButtonInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -37,7 +38,7 @@ use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; * Default:: * * <be:moduleLayout> - * <be:moduleLayout.button.shortcutButton displayName="Shortcut label" arguments="{route: '{route}'"/> + * <be:moduleLayout.button.shortcutButton displayName="Shortcut label" arguments="{parameter: '{someValue}'}"/> * </be:moduleLayout> */ class ShortcutButtonViewHelper extends AbstractButtonViewHelper @@ -50,7 +51,8 @@ class ShortcutButtonViewHelper extends AbstractButtonViewHelper public function initializeArguments(): void { parent::initializeArguments(); - $this->registerArgument('displayName', 'string', 'Name for the shortcut'); + // This will be required in v12. Deprecation for empty argument logged by ModuleTemplate->makeShortcutIcon() + $this->registerArgument('displayName', 'string', 'Name for the shortcut', false, ''); $this->registerArgument('arguments', 'array', 'List of relevant GET variables as key/values list to store', false, []); // @deprecated since v11, will be removed in v12. Use 'arguments' instead. Deprecation logged by ModuleTemplate->makeShortcutIcon() $this->registerArgument('getVars', 'array', 'List of additional GET variables to store. The current id, module and all module arguments will always be stored', false, []); @@ -62,9 +64,11 @@ class ShortcutButtonViewHelper extends AbstractButtonViewHelper $moduleName = $currentRequest->getPluginName(); $displayName = $arguments['displayName']; - $shortcutButton = $buttonBar->makeShortcutButton() + // Initialize the shortcut button + $shortcutButton = $buttonBar + ->makeShortcutButton() ->setDisplayName($displayName) - ->setModuleName($moduleName); + ->setRouteIdentifier(self::getRouteIdentifierForModuleName($moduleName)); if (!empty($arguments['arguments'])) { $shortcutButton->setArguments($arguments['arguments']); @@ -81,4 +85,21 @@ class ShortcutButtonViewHelper extends AbstractButtonViewHelper return $shortcutButton; } + + /** + * Tries to fetch the route identifier for a given module name + * + * @param string $moduleName + * @return string + */ + protected static function getRouteIdentifierForModuleName(string $moduleName): string + { + foreach (GeneralUtility::makeInstance(Router::class)->getRoutes() as $identifier => $route) { + if ($route->hasOption('moduleName') && $route->getOption('moduleName') === $moduleName) { + return $identifier; + } + } + + return ''; + } } diff --git a/typo3/sysext/backend/Configuration/Services.yaml b/typo3/sysext/backend/Configuration/Services.yaml index 4b04fb44760f4044d5a9201ab2336e0d6c89f2b1..29fc73a5a67f6bc9401d1133c9b36e69c390e7d5 100644 --- a/typo3/sysext/backend/Configuration/Services.yaml +++ b/typo3/sysext/backend/Configuration/Services.yaml @@ -38,6 +38,9 @@ services: TYPO3\CMS\Backend\History\RecordHistoryRollback: public: true + TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository: + public: true + TYPO3\CMS\Backend\Controller\AboutController: tags: ['backend.controller'] diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/Toolbar/ShortcutMenu.js b/typo3/sysext/backend/Resources/Public/JavaScript/Toolbar/ShortcutMenu.js index 57f414b7211df9a78f8d8b618841f9b45945473b..0ca34a5707f199424a406b3e63b3444d21f3bb91 100644 --- a/typo3/sysext/backend/Resources/Public/JavaScript/Toolbar/ShortcutMenu.js +++ b/typo3/sysext/backend/Resources/Public/JavaScript/Toolbar/ShortcutMenu.js @@ -10,4 +10,4 @@ * * The TYPO3 project - inspiring people to share! */ -var __importDefault=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};define(["require","exports","jquery","TYPO3/CMS/Core/Ajax/AjaxRequest","../Icons","../Modal","../Notification","../Viewport"],(function(t,e,o,r,c,a,s,l){"use strict";var n;o=__importDefault(o),function(t){t.containerSelector="#typo3-cms-backend-backend-toolbaritems-shortcuttoolbaritem",t.toolbarIconSelector=".dropdown-toggle span.icon",t.toolbarMenuSelector=".dropdown-menu",t.shortcutItemSelector=".t3js-topbar-shortcut",t.shortcutDeleteSelector=".t3js-shortcut-delete",t.shortcutEditSelector=".t3js-shortcut-edit",t.shortcutFormTitleSelector='input[name="shortcut-title"]',t.shortcutFormGroupSelector='select[name="shortcut-group"]',t.shortcutFormSaveSelector=".shortcut-form-save",t.shortcutFormCancelSelector=".shortcut-form-cancel",t.shortcutFormSelector=".shortcut-form"}(n||(n={}));let u=new class{constructor(){this.initializeEvents=()=>{o.default(n.containerSelector).on("click",n.shortcutDeleteSelector,t=>{t.preventDefault(),t.stopImmediatePropagation(),this.deleteShortcut(o.default(t.currentTarget).closest(n.shortcutItemSelector))}).on("click",n.shortcutFormGroupSelector,t=>{t.preventDefault(),t.stopImmediatePropagation()}).on("click",n.shortcutEditSelector,t=>{t.preventDefault(),t.stopImmediatePropagation(),this.editShortcut(o.default(t.currentTarget).closest(n.shortcutItemSelector))}).on("click",n.shortcutFormSaveSelector,t=>{t.preventDefault(),t.stopImmediatePropagation(),this.saveShortcutForm(o.default(t.currentTarget).closest(n.shortcutFormSelector))}).on("submit",n.shortcutFormSelector,t=>{t.preventDefault(),t.stopImmediatePropagation(),this.saveShortcutForm(o.default(t.currentTarget).closest(n.shortcutFormSelector))}).on("click",n.shortcutFormCancelSelector,t=>{t.preventDefault(),t.stopImmediatePropagation(),this.refreshMenu()})},l.Topbar.Toolbar.registerEvent(this.initializeEvents)}createShortcut(t,e,s,l,u,i){void 0!==s&&a.confirm(TYPO3.lang["bookmark.create"],s).on("confirm.button.ok",a=>{const s=o.default(n.toolbarIconSelector,n.containerSelector),h=s.clone();c.getIcon("spinner-circle-light",c.sizes.small).then(t=>{s.replaceWith(t)}),new r(TYPO3.settings.ajaxUrls.shortcut_create).post({module:t,url:e,motherModName:l,displayName:i}).then(()=>{this.refreshMenu(),o.default(n.toolbarIconSelector,n.containerSelector).replaceWith(h),"object"==typeof u&&(c.getIcon("actions-system-shortcut-active",c.sizes.small).then(t=>{o.default(u).html(t)}),o.default(u).addClass("active"),o.default(u).attr("title",null),o.default(u).attr("onclick",null))}),o.default(a.currentTarget).trigger("modal-dismiss")}).on("confirm.button.cancel",t=>{o.default(t.currentTarget).trigger("modal-dismiss")})}deleteShortcut(t){a.confirm(TYPO3.lang["bookmark.delete"],TYPO3.lang["bookmark.confirmDelete"]).on("confirm.button.ok",e=>{new r(TYPO3.settings.ajaxUrls.shortcut_remove).post({shortcutId:t.data("shortcutid")}).then(()=>{this.refreshMenu()}),o.default(e.currentTarget).trigger("modal-dismiss")}).on("confirm.button.cancel",t=>{o.default(t.currentTarget).trigger("modal-dismiss")})}editShortcut(t){new r(TYPO3.settings.ajaxUrls.shortcut_editform).withQueryArguments({shortcutId:t.data("shortcutid"),shortcutGroup:t.data("shortcutgroup")}).get({cache:"no-cache"}).then(async t=>{o.default(n.containerSelector).find(n.toolbarMenuSelector).html(await t.resolve())})}saveShortcutForm(t){new r(TYPO3.settings.ajaxUrls.shortcut_saveform).post({shortcutId:t.data("shortcutid"),shortcutTitle:t.find(n.shortcutFormTitleSelector).val(),shortcutGroup:t.find(n.shortcutFormGroupSelector).val()}).then(()=>{s.success(TYPO3.lang["bookmark.savedTitle"],TYPO3.lang["bookmark.savedMessage"]),this.refreshMenu()})}refreshMenu(){new r(TYPO3.settings.ajaxUrls.shortcut_list).get({cache:"no-cache"}).then(async t=>{o.default(n.toolbarMenuSelector,n.containerSelector).html(await t.resolve())})}};return TYPO3.ShortcutMenu=u,u})); \ No newline at end of file +var __importDefault=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};define(["require","exports","jquery","TYPO3/CMS/Core/Ajax/AjaxRequest","../Icons","../Modal","../Notification","../Viewport"],(function(t,e,o,r,c,a,s,l){"use strict";var n;o=__importDefault(o),function(t){t.containerSelector="#typo3-cms-backend-backend-toolbaritems-shortcuttoolbaritem",t.toolbarIconSelector=".dropdown-toggle span.icon",t.toolbarMenuSelector=".dropdown-menu",t.shortcutItemSelector=".t3js-topbar-shortcut",t.shortcutDeleteSelector=".t3js-shortcut-delete",t.shortcutEditSelector=".t3js-shortcut-edit",t.shortcutFormTitleSelector='input[name="shortcut-title"]',t.shortcutFormGroupSelector='select[name="shortcut-group"]',t.shortcutFormSaveSelector=".shortcut-form-save",t.shortcutFormCancelSelector=".shortcut-form-cancel",t.shortcutFormSelector=".shortcut-form"}(n||(n={}));let u=new class{constructor(){this.initializeEvents=()=>{o.default(n.containerSelector).on("click",n.shortcutDeleteSelector,t=>{t.preventDefault(),t.stopImmediatePropagation(),this.deleteShortcut(o.default(t.currentTarget).closest(n.shortcutItemSelector))}).on("click",n.shortcutFormGroupSelector,t=>{t.preventDefault(),t.stopImmediatePropagation()}).on("click",n.shortcutEditSelector,t=>{t.preventDefault(),t.stopImmediatePropagation(),this.editShortcut(o.default(t.currentTarget).closest(n.shortcutItemSelector))}).on("click",n.shortcutFormSaveSelector,t=>{t.preventDefault(),t.stopImmediatePropagation(),this.saveShortcutForm(o.default(t.currentTarget).closest(n.shortcutFormSelector))}).on("submit",n.shortcutFormSelector,t=>{t.preventDefault(),t.stopImmediatePropagation(),this.saveShortcutForm(o.default(t.currentTarget).closest(n.shortcutFormSelector))}).on("click",n.shortcutFormCancelSelector,t=>{t.preventDefault(),t.stopImmediatePropagation(),this.refreshMenu()})},l.Topbar.Toolbar.registerEvent(this.initializeEvents)}createShortcut(t,e,s,l,u){void 0!==l&&a.confirm(TYPO3.lang["bookmark.create"],l).on("confirm.button.ok",a=>{const l=o.default(n.toolbarIconSelector,n.containerSelector),i=l.clone();c.getIcon("spinner-circle-light",c.sizes.small).then(t=>{l.replaceWith(t)}),new r(TYPO3.settings.ajaxUrls.shortcut_create).post({routeIdentifier:t,arguments:e,displayName:s}).then(()=>{this.refreshMenu(),o.default(n.toolbarIconSelector,n.containerSelector).replaceWith(i),"object"==typeof u&&(c.getIcon("actions-system-shortcut-active",c.sizes.small).then(t=>{o.default(u).html(t)}),o.default(u).addClass("active"),o.default(u).attr("title",null),o.default(u).attr("onclick",null))}),o.default(a.currentTarget).trigger("modal-dismiss")}).on("confirm.button.cancel",t=>{o.default(t.currentTarget).trigger("modal-dismiss")})}deleteShortcut(t){a.confirm(TYPO3.lang["bookmark.delete"],TYPO3.lang["bookmark.confirmDelete"]).on("confirm.button.ok",e=>{new r(TYPO3.settings.ajaxUrls.shortcut_remove).post({shortcutId:t.data("shortcutid")}).then(()=>{this.refreshMenu()}),o.default(e.currentTarget).trigger("modal-dismiss")}).on("confirm.button.cancel",t=>{o.default(t.currentTarget).trigger("modal-dismiss")})}editShortcut(t){new r(TYPO3.settings.ajaxUrls.shortcut_editform).withQueryArguments({shortcutId:t.data("shortcutid"),shortcutGroup:t.data("shortcutgroup")}).get({cache:"no-cache"}).then(async t=>{o.default(n.containerSelector).find(n.toolbarMenuSelector).html(await t.resolve())})}saveShortcutForm(t){new r(TYPO3.settings.ajaxUrls.shortcut_saveform).post({shortcutId:t.data("shortcutid"),shortcutTitle:t.find(n.shortcutFormTitleSelector).val(),shortcutGroup:t.find(n.shortcutFormGroupSelector).val()}).then(()=>{s.success(TYPO3.lang["bookmark.savedTitle"],TYPO3.lang["bookmark.savedMessage"]),this.refreshMenu()})}refreshMenu(){new r(TYPO3.settings.ajaxUrls.shortcut_list).get({cache:"no-cache"}).then(async t=>{o.default(n.toolbarMenuSelector,n.containerSelector).html(await t.resolve())})}};return TYPO3.ShortcutMenu=u,u})); \ No newline at end of file diff --git a/typo3/sysext/backend/Tests/Functional/Backend/Fixtures/ShortcutsAddedResult.csv b/typo3/sysext/backend/Tests/Functional/Backend/Fixtures/ShortcutsAddedResult.csv new file mode 100644 index 0000000000000000000000000000000000000000..bc7aed9a77ae77efce5714cf2bd84e582e5a1df3 --- /dev/null +++ b/typo3/sysext/backend/Tests/Functional/Backend/Fixtures/ShortcutsAddedResult.csv @@ -0,0 +1,11 @@ +"sys_be_shortcuts",,,,,, +,"uid","userid","description","sc_group","route","arguments" +,1,1,"Recordlist",1,"web_list","{""id"":123,""GET"":{""clipBoard"":1}}" +,2,1,"Edit Content",1,"record_edit","{""edit"":{""tt_content"":{""113"":""edit""}}}" +,3,1,"Page Layout - Group 2",2,"web_layout","{""id"":""123"",""SET"":{""tt_content_showHidden"":""1"",""function"":""1"",""language"":""0""}}" +,4,1,"Invalid route identifier",1,"web_proc","{""id"":""123""}" +,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","[]" diff --git a/typo3/sysext/backend/Tests/Functional/Backend/Fixtures/ShortcutsBase.csv b/typo3/sysext/backend/Tests/Functional/Backend/Fixtures/ShortcutsBase.csv new file mode 100644 index 0000000000000000000000000000000000000000..ffe0d2cf721ef2e37adc9988c58b786cdb832da6 --- /dev/null +++ b/typo3/sysext/backend/Tests/Functional/Backend/Fixtures/ShortcutsBase.csv @@ -0,0 +1,8 @@ +"sys_be_shortcuts",,,,,, +,"uid","userid","description","sc_group","route","arguments" +,1,1,"Recordlist",1,"web_list","{""id"":123,""GET"":{""clipBoard"":1}}" +,2,1,"Edit Content",1,"record_edit","{""edit"":{""tt_content"":{""113"":""edit""}}}" +,3,1,"Page Layout - Group 2",2,"web_layout","{""id"":""123"",""SET"":{""tt_content_showHidden"":""1"",""function"":""1"",""language"":""0""}}" +,4,1,"Invalid route identifier",1,"web_proc","{""id"":""123""}" +,5,2,"Wrong user",1,"web_layout","{""id"":""123""}" +,6,1,,1,"web_layout","{""id"":""123""}" diff --git a/typo3/sysext/backend/Tests/Functional/Backend/Shortcut/ShortcutRepositoryTest.php b/typo3/sysext/backend/Tests/Functional/Backend/Shortcut/ShortcutRepositoryTest.php new file mode 100644 index 0000000000000000000000000000000000000000..639acf01e42eef1cd804f95ab574bd40a8e08c59 --- /dev/null +++ b/typo3/sysext/backend/Tests/Functional/Backend/Shortcut/ShortcutRepositoryTest.php @@ -0,0 +1,181 @@ +<?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\Backend\Tests\Functional\Backend\Shortcut; + +use TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository; +use TYPO3\CMS\Backend\Module\ModuleLoader; +use TYPO3\CMS\Core\Core\Bootstrap; +use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Imaging\IconFactory; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; + +class ShortcutRepositoryTest extends FunctionalTestCase +{ + protected ShortcutRepository $subject; + + protected function setUp(): void + { + parent::setUp(); + $this->importCSVDataSet(__DIR__ . '/../Fixtures/ShortcutsBase.csv'); + + $this->setUpBackendUserFromFixture(1); + Bootstrap::initializeLanguageObject(); + + $this->subject = new ShortcutRepository( + GeneralUtility::makeInstance(ConnectionPool::class), + GeneralUtility::makeInstance(IconFactory::class), + GeneralUtility::makeInstance(ModuleLoader::class) + ); + } + + /** + * @dataProvider shortcutExistsTestDataProvider + * @test + * + * @param string $routeIdentifier + * @param array $arguments + * @param int $userid + * @param bool $exists + */ + public function shortcutExistsTest(string $routeIdentifier, array $arguments, int $userid, bool $exists): void + { + $GLOBALS['BE_USER']->user['uid'] = $userid; + self::assertEquals($exists, $this->subject->shortcutExists($routeIdentifier, json_encode($arguments))); + } + + public function shortcutExistsTestDataProvider(): \Generator + { + yield 'Shortcut exists' => [ + 'web_list', + ['id' => 123, 'GET' => ['clipBoard' => 1]], + 1, + true + ]; + yield 'Not this user' => [ + 'web_list', + ['id' => 123, 'GET' => ['clipBoard' => 1]], + 2, + false + ]; + yield 'Wrong route identifer' => [ + 'web_layout', + ['id' => 123, 'GET' => ['clipBoard' => 1]], + 1, + false + ]; + yield 'Wrong arguments' => [ + 'web_list', + ['id' => 321, 'GET' => ['clipBoard' => 1]], + 1, + false + ]; + } + + /** + * @test + */ + public function addShortcutTest(): void + { + foreach ($this->getShortcutsToAdd() as $shortcut) { + $this->subject->addShortcut( + $shortcut['routeIdentifier'], + json_encode($shortcut['arguments']), + $shortcut['title'] + ); + } + + $this->assertCSVDataSet('typo3/sysext/backend/Tests/Functional/Backend/Fixtures/ShortcutsAddedResult.csv'); + } + + public function getShortcutsToAdd(): array + { + return [ + 'Basic shortcut with all information' => [ + 'routeIdentifier' => 'web_list', + 'arguments' => ['id' => 111, 'GET' => ['clipBoard' => 1]], + 'title' => 'Recordlist of id 111' + ], + // @todo Below is deprecated functionality which only provides backwards compatibility for v11. Remove in v12! + 'FormEngine without title' => [ + 'routeIdentifier' => 'record_edit', + 'arguments' => ['edit' => ['pages' => [112 => 'edit']]], + 'title' => '' + ], + // @todo Below is deprecated functionality which only provides backwards compatibility for v11. Remove in v12! + 'Page Layout without title' => [ + 'routeIdentifier' => 'web_layout', + 'arguments' => [], + 'title' => '' + ] + ]; + } + + /** + * This effectively also tests ShortcutRepository::initShortcuts() + * + * @test + */ + public function getShortcutsByGroupTest(): void + { + $expected = [ + 1 => [ + 'table' => null, + 'recordid' => null, + 'groupLabel' => 'Pages', + 'type' => 'other', + 'icon' => 'data-identifier="module-web_list"', + 'label' => 'Recordlist', + 'action' => 'id=123\u0026GET%5BclipBoard%5D=1\',\'web_list\',\'web_list\', 123);' + ], + 2 => [ + 'table' => 'tt_content', + 'recordid' => 113, + 'groupLabel' => null, + 'type' => 'edit', + 'label' => 'Edit Content', + 'icon' => 'data-identifier="mimetypes-x-content-text"', + 'action' => '\u0026edit%5Btt_content%5D%5B113%5D=edit\',\'record_edit\',\'record_edit\', 0);' + ], + 6 => [ + 'table' => null, + 'recordid' => null, + 'groupLabel' => null, + 'type' => 'other', + 'label' => 'Page content', + 'icon' => 'data-identifier="module-web_layout"', + 'action' => '\u0026id=123\',\'web_layout\',\'web_layout\', 123);' + ] + ]; + + $shortcuts = $this->subject->getShortcutsByGroup(1); + self::assertCount(3, $shortcuts); + + foreach ($shortcuts as $shortcut) { + $id = (int)$shortcut['raw']['uid']; + self::assertEquals(1, $shortcut['group']); + self::assertEquals($expected[$id]['table'], $shortcut['table'] ?? null); + self::assertEquals($expected[$id]['recordid'], $shortcut['recordid'] ?? null); + self::assertEquals($expected[$id]['groupLabel'], $shortcut['groupLabel'] ?? null); + self::assertEquals($expected[$id]['type'], $shortcut['type']); + self::assertEquals($expected[$id]['label'], $shortcut['label']); + self::assertStringContainsString($expected[$id]['icon'], $shortcut['icon']); + self::assertStringContainsString($expected[$id]['action'], $shortcut['action']); + } + } +} diff --git a/typo3/sysext/backend/Tests/Functional/Controller/ShortcutControllerTest.php b/typo3/sysext/backend/Tests/Functional/Controller/ShortcutControllerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e1af847e0983724c4125adc2409d3faa5ec03c31 --- /dev/null +++ b/typo3/sysext/backend/Tests/Functional/Controller/ShortcutControllerTest.php @@ -0,0 +1,105 @@ +<?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\Backend\Tests\Functional\Controller; + +use TYPO3\CMS\Backend\Controller\ShortcutController; +use TYPO3\CMS\Core\Core\Bootstrap; +use TYPO3\CMS\Core\Http\NormalizedParams; +use TYPO3\CMS\Core\Http\ServerRequest; +use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; + +class ShortcutControllerTest extends FunctionalTestCase +{ + protected ShortcutController $subject; + protected ServerRequest $request; + + protected function setUp(): void + { + parent::setUp(); + + $this->importDataSet(__DIR__ . '/../Fixtures/sys_be_shortcuts.xml'); + + $this->setUpBackendUserFromFixture(1); + Bootstrap::initializeLanguageObject(); + + $this->subject = new ShortcutController(); + $this->request = (new ServerRequest())->withAttribute('normalizedParams', new NormalizedParams([], [], '', '')); + } + + /** + * @dataProvider addShortcutTestDataProvide + * @test + * + * @param array $parsedBody + * @param array $queryParams + * @param string $expectedResponseBody + */ + public function addShortcutTest(array $parsedBody, array $queryParams, string $expectedResponseBody): void + { + $request = $this->request->withParsedBody($parsedBody)->withQueryParams($queryParams); + self::assertEquals($expectedResponseBody, $this->subject->addAction($request)->getBody()); + } + + public function addShortcutTestDataProvide(): \Generator + { + yield 'No route defined' => [ + [], + [], + 'missingRoute' + ]; + yield 'Existing data as parsed body' => [ + [ + 'routeIdentifier' => 'web_layout', + 'arguments' => '{"id":"123"}' + ], + [], + 'alreadyExists' + ]; + yield 'Existing data as query parameters' => [ + [], + [ + 'routeIdentifier' => 'web_layout', + 'arguments' => '{"id":"123"}' + ], + 'alreadyExists' + ]; + yield 'Invalid route identifier' => [ + [], + [ + 'routeIdentifier' => 'invalid_route_identifier', + ], + 'failed' + ]; + yield 'New data as parsed body' => [ + [ + 'routeIdentifier' => 'web_list', + 'arguments' => '{"id":"123","GET":{"clipBoard":"1"}}' + ], + [], + 'success' + ]; + yield 'New data as query parameters' => [ + [], + [ + 'routeIdentifier' => 'web_list', + 'arguments' => '{"id":"321","GET":{"clipBoard":"1"}}' + ], + 'success' + ]; + } +} diff --git a/typo3/sysext/backend/Tests/Functional/Fixtures/sys_be_shortcuts.xml b/typo3/sysext/backend/Tests/Functional/Fixtures/sys_be_shortcuts.xml new file mode 100644 index 0000000000000000000000000000000000000000..6eabf0bea49d96bc2f5cc973019b9ea4a40e6912 --- /dev/null +++ b/typo3/sysext/backend/Tests/Functional/Fixtures/sys_be_shortcuts.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<dataset> + <sys_be_shortcuts> + <uid>1</uid> + <userid>1</userid> + <route>web_layout</route> + <arguments>{"id":"123"}</arguments> + </sys_be_shortcuts> +</dataset> diff --git a/typo3/sysext/backend/Tests/Functional/Template/Components/Buttons/Action/ShortcutButtonTest.php b/typo3/sysext/backend/Tests/Functional/Template/Components/Buttons/Action/ShortcutButtonTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9671eb798f4d2fc29384127b4fdb1b15f08d9950 --- /dev/null +++ b/typo3/sysext/backend/Tests/Functional/Template/Components/Buttons/Action/ShortcutButtonTest.php @@ -0,0 +1,122 @@ +<?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\Backend\Tests\Functional\Template\Components\Buttons\Action; + +use TYPO3\CMS\Backend\Template\Components\Buttons\Action\ShortcutButton; +use TYPO3\CMS\Core\Core\Bootstrap; +use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; + +class ShortcutButtonTest extends FunctionalTestCase +{ + private const FIXTURES_PATH_PATTERN = __DIR__ . '/../../../Fixtures/%s.html'; + + protected function setUp(): void + { + parent::setUp(); + + $this->setUpBackendUserFromFixture(1); + Bootstrap::initializeLanguageObject(); + } + + /** + * @test + */ + public function isButtonValid(): void + { + self::assertFalse((new ShortcutButton())->isValid()); + self::assertTrue((new ShortcutButton())->setRouteIdentifier('web_list')->isValid()); + // @todo Remove below in v12 + self::assertTrue((new ShortcutButton())->setArguments(['route' => 'web_list'])->isValid()); + self::assertTrue((new ShortcutButton())->setModuleName('web_list')->isValid()); + } + + /** + * @dataProvider rendersCorrectMarkupDataProvider + * @test + * + * @param ShortcutButton $button + * @param string $expectedMarkupFile + */ + public function rendersCorrectMarkup(ShortcutButton $button, string $expectedMarkupFile): void + { + self::assertEquals( + preg_replace('/\s+/', '', file_get_contents(sprintf(self::FIXTURES_PATH_PATTERN, $expectedMarkupFile))), + preg_replace('/\s+/', '', $button->render()) + ); + } + + public function rendersCorrectMarkupDataProvider(): \Generator + { + yield 'Recordlist' => [ + (new ShortcutButton())->setRouteIdentifier('web_list')->setDisplayName('Recordlist'), + 'RecordList' + ]; + // @todo Below is deprecated functionality which only provides backwards compatibility for v11. Remove in v12! + yield 'Recordlist as route path' => [ + (new ShortcutButton())->setRouteIdentifier('/module/web/list')->setDisplayName('Recordlist'), + 'RecordList' + ]; + yield 'Recordlist - single table view' => [ + (new ShortcutButton()) + ->setRouteIdentifier('web_list') + ->setDisplayName('Recordlist - single table view') + ->setArguments([ + 'id' => 123, + 'table' => 'some_table', + 'GET' => [ + 'clipBoard' => 1 + ] + ]), + 'RecordListSingleTable' + ]; + yield 'With special route identifier' => [ + (new ShortcutButton())->setRouteIdentifier('record_edit')->setDisplayName('Edit record'), + 'SpecialRouteIdentifier' + ]; + yield 'With special route identifier and arguments' => [ + (new ShortcutButton()) + ->setRouteIdentifier('record_edit') + ->setDisplayName('Edit record') + ->setArguments([ + 'id' => 123, + 'edit' => [ + 'pages' => [ + 123 => 'edit', + ], + 'overrideVals' => [ + 'pages' => [ + 'sys_language_uid' => 1 + ] + ] + ], + 'returnUrl' => 'some/url' + ]), + 'SpecialRouteIdentifierWithArguments' + ]; + // @todo Below is deprecated functionality which only provides backwards compatibility for v11. Remove in v12! + yield 'With special route path' => [ + (new ShortcutButton())->setRouteIdentifier('/record/edit')->setDisplayName('Edit record'), + 'SpecialRouteIdentifier' + ]; + // @todo Below is deprecated functionality which only provides backwards compatibility for v11. Remove in v12! + yield 'With special route path as Argument' => [ + (new ShortcutButton())->setArguments(['route' => '/record/edit'])->setDisplayName('Edit record'), + 'SpecialRouteIdentifier' + ]; + } +} diff --git a/typo3/sysext/backend/Tests/Functional/Template/Fixtures/RecordList.html b/typo3/sysext/backend/Tests/Functional/Template/Fixtures/RecordList.html new file mode 100644 index 0000000000000000000000000000000000000000..ef705a2044f98ce1e6fb626e2de5a47219692619 --- /dev/null +++ b/typo3/sysext/backend/Tests/Functional/Template/Fixtures/RecordList.html @@ -0,0 +1,10 @@ +<a href="#" + class="btn btn-default btn-sm" + onclick="top.TYPO3.ShortcutMenu.createShortcut('web_list', '[]', 'Recordlist', 'Create\u0020a\u0020bookmark\u0020to\u0020this\u0020page', this);return false;" + title="Create a bookmark to this page"> + <span class="t3js-icon icon icon-size-small icon-state-default icon-actions-system-shortcut-new" data-identifier="actions-system-shortcut-new"> + <span class="icon-markup"> + <svg class="icon-color" role="img"><use xlink:href="typo3/sysext/core/Resources/Public/Icons/T3Icons/sprites/actions.svg#actions-star" /></svg> + </span> + </span> +</a> diff --git a/typo3/sysext/backend/Tests/Functional/Template/Fixtures/RecordListSingleTable.html b/typo3/sysext/backend/Tests/Functional/Template/Fixtures/RecordListSingleTable.html new file mode 100644 index 0000000000000000000000000000000000000000..74931a73eed6dd3a36cf051f0825685bec18dfd6 --- /dev/null +++ b/typo3/sysext/backend/Tests/Functional/Template/Fixtures/RecordListSingleTable.html @@ -0,0 +1,10 @@ +<a href="#" + class="btn btn-default btn-sm" + onclick="top.TYPO3.ShortcutMenu.createShortcut('web_list', '{\u0022id\u0022:123,\u0022table\u0022:\u0022some_table\u0022,\u0022GET\u0022:{\u0022clipBoard\u0022:1}}', 'Recordlist\u0020-\u0020single\u0020table\u0020view', 'Create\u0020a\u0020bookmark\u0020to\u0020this\u0020page', this);return false;" + title="Create a bookmark to this page"> + <span class="t3js-icon icon icon-size-small icon-state-default icon-actions-system-shortcut-new" data-identifier="actions-system-shortcut-new"> + <span class="icon-markup"> + <svg class="icon-color" role="img"><use xlink:href="typo3/sysext/core/Resources/Public/Icons/T3Icons/sprites/actions.svg#actions-star" /></svg> + </span> + </span> +</a> diff --git a/typo3/sysext/backend/Tests/Functional/Template/Fixtures/SpecialRouteIdentifier.html b/typo3/sysext/backend/Tests/Functional/Template/Fixtures/SpecialRouteIdentifier.html new file mode 100644 index 0000000000000000000000000000000000000000..ade7e7a577440326b3f8b0c9aeea21c6d426ec29 --- /dev/null +++ b/typo3/sysext/backend/Tests/Functional/Template/Fixtures/SpecialRouteIdentifier.html @@ -0,0 +1,10 @@ +<a href="#" + class="btn btn-default btn-sm" + onclick="top.TYPO3.ShortcutMenu.createShortcut('record_edit', '[]', 'Edit\u0020record', 'Create\u0020a\u0020bookmark\u0020to\u0020this\u0020page', this);return false;" + title="Create a bookmark to this page"> + <span class="t3js-icon icon icon-size-small icon-state-default icon-actions-system-shortcut-new" data-identifier="actions-system-shortcut-new"> + <span class="icon-markup"> + <svg class="icon-color" role="img"><use xlink:href="typo3/sysext/core/Resources/Public/Icons/T3Icons/sprites/actions.svg#actions-star" /></svg> + </span> + </span> +</a> diff --git a/typo3/sysext/backend/Tests/Functional/Template/Fixtures/SpecialRouteIdentifierWithArguments.html b/typo3/sysext/backend/Tests/Functional/Template/Fixtures/SpecialRouteIdentifierWithArguments.html new file mode 100644 index 0000000000000000000000000000000000000000..631533788f8cfed748a717d040780a90aa579983 --- /dev/null +++ b/typo3/sysext/backend/Tests/Functional/Template/Fixtures/SpecialRouteIdentifierWithArguments.html @@ -0,0 +1,10 @@ +<a href="#" + class="btn btn-default btn-sm" + onclick="top.TYPO3.ShortcutMenu.createShortcut('record_edit', '{\u0022id\u0022:123,\u0022edit\u0022:{\u0022pages\u0022:{\u0022123\u0022:\u0022edit\u0022},\u0022overrideVals\u0022:{\u0022pages\u0022:{\u0022sys_language_uid\u0022:1}}}}', 'Edit\u0020record', 'Create\u0020a\u0020bookmark\u0020to\u0020this\u0020page', this);return false;" + title="Create a bookmark to this page"> + <span class="t3js-icon icon icon-size-small icon-state-default icon-actions-system-shortcut-new" data-identifier="actions-system-shortcut-new"> + <span class="icon-markup"> + <svg class="icon-color" role="img"><use xlink:href="typo3/sysext/core/Resources/Public/Icons/T3Icons/sprites/actions.svg#actions-star" /></svg> + </span> + </span> +</a> diff --git a/typo3/sysext/beuser/Classes/Controller/PermissionController.php b/typo3/sysext/beuser/Classes/Controller/PermissionController.php index 48ea870d4276d1fe61985a87ecb6a64c1066360f..4bfdc371427e22d0d668617feb30e84527979688 100644 --- a/typo3/sysext/beuser/Classes/Controller/PermissionController.php +++ b/typo3/sysext/beuser/Classes/Controller/PermissionController.php @@ -142,7 +142,6 @@ class PermissionController extends ActionController /** @var ButtonBar $buttonBar */ $buttonBar = $this->view->getModuleTemplate()->getDocHeaderComponent()->getButtonBar(); $currentRequest = $this->request; - $moduleName = $currentRequest->getPluginName(); $lang = $this->getLanguageService(); if ($currentRequest->getControllerActionName() === 'edit') { @@ -174,7 +173,7 @@ class PermissionController extends ActionController } $shortcutButton = $buttonBar->makeShortcutButton() - ->setModuleName($moduleName) + ->setRouteIdentifier('system_BeuserTxPermission') ->setDisplayName($this->getShortcutTitle()) ->setArguments([ 'route' => GeneralUtility::_GP('route'), diff --git a/typo3/sysext/beuser/Resources/Private/Layouts/Default.html b/typo3/sysext/beuser/Resources/Private/Layouts/Default.html index e95d0c2ce25f6cf01194cd33cc6eab7fed4afc1c..19ae9915619c440c43cff2a3ffb0f9ac1ffcb3e9 100644 --- a/typo3/sysext/beuser/Resources/Private/Layouts/Default.html +++ b/typo3/sysext/beuser/Resources/Private/Layouts/Default.html @@ -24,7 +24,7 @@ <f:render section="Buttons" /> <be:moduleLayout.button.shortcutButton displayName="{f:translate(id: shortcutLabel)}" - arguments="{route: '{route}', tx_beuser_system_beusertxbeuser: '{action: \'{action}\', controller: \'{controller}\'}' }" + arguments="{tx_beuser_system_beusertxbeuser: '{action: \'{action}\', controller: \'{controller}\'}' }" /> <div id="beuser-main-content"> diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-93093-ReworkShortcutPHPAPI.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-93093-ReworkShortcutPHPAPI.rst new file mode 100644 index 0000000000000000000000000000000000000000..161c759c93b8521a61e2b7ec027250b6687ae086 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/master/Breaking-93093-ReworkShortcutPHPAPI.rst @@ -0,0 +1,131 @@ +.. include:: ../../Includes.txt + +.. _changelog-Breaking-93093-ReworkShortcutPHPAPI: + +========================================== +Breaking: #93093 - Rework Shortcut PHP API +========================================== + +See :issue:`93093` + +Description +=========== + +The Shortcut PHP API previously has the full URL of the shortcut target +stored in the :sql:`sys_be_shortcuts` table. It however turned out that +this is not working well, error-prone and laborious. For example, all +created shortcuts are automatically invalid as soon as the corresponding +module changed its route path. Furthermore the :sql:`url` column included +the token which was actually never used but regenerated on every link +generation, e.g. when reloading the backend. Since even the initial +`returnUrl` was stored in the database, a shortcut which linked to +FormEngine has returned to this initial url forever. Even after years +when the initial target may no longer available. + +All these characteristics opposes the introduction of speaking urls for +the TYPO3 backend. Therefore, the internal handling and registration of +the Shortcut PHP API was reworked. + +A shortcut record does now not longer store the full url of the shortcut +target but instead only the modules route identifier and the necessary +arguments (parameters) for the URL. + +The columns :sql:`module_name` and :sql:`url` of the :php:`sys_be_shortcuts` +table have been replaced with: + +* :sql:`route` - Contains the route identifier of the module to link to +* :sql:`arguments` - Contains all necessary arguments (parameters) for the +link as JSON encoded string + +The :sql:`arguments` column does therefore not longer store any of the +following parameters: + +* `route` +* `token` +* `returnUrl` + +Shortcuts are usually created through the JavaScript function +:js:`TYPO3.ShortcutMenu.createShortcut()` which performs an AJAX call to +:php:`ShortcutController->addAction()`. The parameter signature of the +JavaScript function has therefore been changed and the :php:`addAction()` +method does now feature an additional result string `missingRoute`, in case +no `routeIdentifier` was provided in the AJAX call. + +The parameter signature changed as followed: + +.. code-block:: javascript + + // Old signature: + public createShortcut( + moduleName: string, + url: string, + confirmationText: string, + motherModule: string, + shortcutButton: JQuery, + displayName: string, + ) + + // New signature: + public createShortcut( + routeIdentifier: string, + routeArguments: string, + displayName: string, + confirmationText: string, + shortcutButton: JQuery, + ) + +The :php:`TYPO3\CMS\Backend\Template\Components\Buttons\Action\ShortcutButton` +API for generating such creation links now provides a new public method +:php:`setRouteIdentifier()` which replaces the deprecated +:php:`setModuleName()` method. See +:ref:`changelog-Deprecation-93093-DeprecateMethodNameInShortcutPHPAPI` for +all deprecations done during the rework. + + +Impact +====== + +Directly calling :js:`TYPO3.ShortcutMenu.createShortcut()` with the old +parameter signature will result in a JavaScript error. + +Already created shortcuts won't be available prior to running the provided +upgrade wizard. + +The columns :sql:`module_name` and :sql:`url` have been removed. Directly +querying these columns will raise a doctrine dbal exception. + + +Affected Installations +====================== + +Installations with already created shortcuts. + +Installations with custom extensions directly calling +:js:`TYPO3.ShortcutMenu.createShortcut()` with the old parameter signatur. + +Installations with custom extensions, directly using the columns +:sql:`module_name` and :sql:`url` or relying on them being filled. + +Installations with custom extensions using deprecated functionality of +the Shortcut PHP API. + + +Migration +========= + +Update the database schema (only "Add fields to tables") and run the +`shortcutRecordsMigration` upgrade wizard either in the install tool or on +CLI with +:shell:`./typo3/sysext/core/bin/typo3 upgrade:run shortcutRecordsMigration`. +Remove the unused :sql:`module_name` and :sql:`url` columns only after running +the wizard. + +Change any call to :js:`TYPO3.ShortcutMenu.createShortcut()` to use the new +parameter signature. + +Migrate custom extension code to use :sql:`route` and :sql:`arguments` instead +of :sql:`module_name` and :sql:`url`. + +Migrate any call to deprecated functionality of the Shortcut PHP API. + +.. index:: Backend, PHP-API, PartiallyScanned, ext:backend diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-92132-DeprecatedShortcutPHPAPI.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-92132-DeprecatedShortcutPHPAPI.rst index 0d2555f7810c249bbe58ef0beac90ae9812a5ed7..0e86d36b6524d1b6f7ee4e01a9e50df1d193ad21 100644 --- a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-92132-DeprecatedShortcutPHPAPI.rst +++ b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-92132-DeprecatedShortcutPHPAPI.rst @@ -18,6 +18,11 @@ Some methods related to :php:`ext:backend` shortcut / bookmark handling have bee * :php:`TYPO3\CMS\Backend\Template\Components\Buttons\Action\ShortcutButton->setGetVariables()` * :php:`TYPO3\CMS\Backend\Template\Components\Buttons\Action\ShortcutButton->setSetVariables()` +See also: + +- :ref:`changelog-Deprecation-93060-ShortcutTitleMustBeSetByControllers` +- :ref:`changelog-Deprecation-93093-DeprecateMethodNameInShortcutPHPAPI` + Impact ====== @@ -40,13 +45,14 @@ introduced. This method expects the full set of arguments and values to create a .. code-block:: php - $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar(); - $shortCutButton = $buttonBar->makeShortcutButton() - ->setModuleName('web_view') - ->setArguments([ - 'route' => $request->getQueryParams['route'], - 'id' => (int)($request->getQueryParams()['id'] ?? 0), - ]); - $buttonBar->addButton($shortCutButton, ButtonBar::BUTTON_POSITION_RIGHT); + $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar(); + $pageId = (int)($request->getQueryParams()['id'] ?? 0); + $shortCutButton = $buttonBar->makeShortcutButton() + ->setRouteIdentifier('web_view') + ->setDisplayName('View page ' . $pageId) + ->setArguments([ + 'id' => $pageId, + ]); + $buttonBar->addButton($shortCutButton, ButtonBar::BUTTON_POSITION_RIGHT); .. index:: Backend, PHP-API, FullyScanned, ext:backend diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-93060-ShortcutTitleMustBeSetByControllers.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-93060-ShortcutTitleMustBeSetByControllers.rst index 6455bfb42ef905a74cbf75f5fdf337cf68425f4f..8d2545490c73ae919735e16f7f28f7dd296053ee 100644 --- a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-93060-ShortcutTitleMustBeSetByControllers.rst +++ b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-93060-ShortcutTitleMustBeSetByControllers.rst @@ -1,5 +1,7 @@ .. include:: ../../Includes.txt +.. _changelog-Deprecation-93060-ShortcutTitleMustBeSetByControllers: + =============================================================== Deprecation: #93060 - Shortcut title must be set by controllers =============================================================== diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-93093-DeprecateMethodNameInShortcutPHPAPI.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-93093-DeprecateMethodNameInShortcutPHPAPI.rst new file mode 100644 index 0000000000000000000000000000000000000000..a235b2b27c34d088b477e26c6f470ee0fa3d1ab6 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-93093-DeprecateMethodNameInShortcutPHPAPI.rst @@ -0,0 +1,67 @@ +.. include:: ../../Includes.txt + +.. _changelog-Deprecation-93093-DeprecateMethodNameInShortcutPHPAPI: + +============================================================== +Deprecation: #93093 - Deprecate MethodName in Shortcut PHP API +============================================================== + +See :issue:`93093` + +Description +=========== + +Since :issue:`92723` the TYPO3 backend uses symfony routing for resolving +internal endpoints, e.g. modules. This will allow speaking urls and also +deep-linking in the future. To achieve this, the shortcut PHP API had to +be reworked to be fully compatible with the new routing. +See :ref:`changelog-Breaking-93093-ReworkShortcutPHPAPI` for more information +regarding the rework. + +In the course of the rework, following methods within :php:`ShortcutButton` +have been deprecated: + +* :php:`TYPO3\CMS\Backend\Template\Components\Buttons\Action\ShortcutButton->setModuleName()` +* :php:`TYPO3\CMS\Backend\Template\Components\Buttons\Action\ShortcutButton->getModuleName()` + +Impact +====== + +Using those methods directly or indirectly will trigger deprecation log +warnings. + + +Affected Installations +====================== + +Installations with custom extensions, adding a shortcut button in the module +header of their backend modules using the mentioned methods. The extension +scanner will find all PHP usages as weak match. + + +Migration +========= + +Use the new methods :php:`ShortcutButton->setRouteIdentifier()` and +:php:`ShortcutButton->getRouteIdentifier()` as replacement. Please note +that these methods require the route identifier of the backend module +which may differ from the module name. To find out the route identifier, +the "Backend Routes" section within the configuration module can be used. + +Before: + +.. code-block:: php + + $shortCutButton = $buttonBar + ->makeShortcutButton() + ->setModuleName('web_list'); + +After: + +.. code-block:: php + + $shortCutButton = $buttonBar + ->makeShortcutButton() + ->setRouteIdentifier('web_list'); + +.. index:: Backend, PHP-API, FullyScanned, ext:backend diff --git a/typo3/sysext/core/ext_tables.sql b/typo3/sysext/core/ext_tables.sql index c5cb7ec53667a6704e4677081caca8a6b9e65994..5d95f477154f57bde665fe8ee8b33ff5aeb9a5b4 100644 --- a/typo3/sysext/core/ext_tables.sql +++ b/typo3/sysext/core/ext_tables.sql @@ -130,8 +130,8 @@ CREATE TABLE sys_registry ( CREATE TABLE sys_be_shortcuts ( uid int(11) unsigned NOT NULL auto_increment, userid int(11) unsigned DEFAULT '0' NOT NULL, - module_name varchar(255) DEFAULT '' NOT NULL, - url text, + route varchar(255) DEFAULT '' NOT NULL, + arguments text, description varchar(255) DEFAULT '' NOT NULL, sorting int(11) DEFAULT '0' NOT NULL, sc_group tinyint(4) DEFAULT '0' NOT NULL, diff --git a/typo3/sysext/filelist/Classes/Controller/FileListController.php b/typo3/sysext/filelist/Classes/Controller/FileListController.php index 5610bbbc48b213d06ecfed5a53acd62e4b633508..487ebd94276f79ee5ae0af4707ad30f78393dbb6 100644 --- a/typo3/sysext/filelist/Classes/Controller/FileListController.php +++ b/typo3/sysext/filelist/Classes/Controller/FileListController.php @@ -663,7 +663,7 @@ class FileListController extends ActionController implements LoggerAwareInterfac // Shortcut $shortCutButton = $buttonBar->makeShortcutButton() - ->setModuleName('file_FilelistList') + ->setRouteIdentifier('file_FilelistList') ->setDisplayName($this->getShortcutTitle()) ->setArguments([ 'route' => GeneralUtility::_GP('route'), diff --git a/typo3/sysext/form/Classes/Controller/FormManagerController.php b/typo3/sysext/form/Classes/Controller/FormManagerController.php index f393676604044578bf80a3d063fbf1ce8ad65804..c78817c3c0d56fccbf9bd9b180838582c0b0d452 100644 --- a/typo3/sysext/form/Classes/Controller/FormManagerController.php +++ b/typo3/sysext/form/Classes/Controller/FormManagerController.php @@ -458,8 +458,6 @@ class FormManagerController extends AbstractBackendController { /** @var ButtonBar $buttonBar */ $buttonBar = $this->view->getModuleTemplate()->getDocHeaderComponent()->getButtonBar(); - $currentRequest = $this->request; - $moduleName = $currentRequest->getPluginName(); // Create new $addFormButton = $buttonBar->makeLinkButton() @@ -478,7 +476,7 @@ class FormManagerController extends AbstractBackendController // Shortcut $shortcutButton = $buttonBar->makeShortcutButton() - ->setModuleName($moduleName) + ->setRouteIdentifier('web_FormFormbuilder') ->setDisplayName($this->getLanguageService()->sL('LLL:EXT:form/Resources/Private/Language/Database.xlf:module.shortcut_name')) ->setArguments([ 'route' => GeneralUtility::_GP('route') diff --git a/typo3/sysext/info/Classes/Controller/InfoModuleController.php b/typo3/sysext/info/Classes/Controller/InfoModuleController.php index 6c5729a86344c7adbb8a5e69d4b5a50ea6f9b9be..5ae8246d49d934cdce17f9f0e838d3327f139830 100644 --- a/typo3/sysext/info/Classes/Controller/InfoModuleController.php +++ b/typo3/sysext/info/Classes/Controller/InfoModuleController.php @@ -286,7 +286,7 @@ class InfoModuleController } } $shortCutButton = $buttonBar->makeShortcutButton() - ->setModuleName($this->moduleName) + ->setRouteIdentifier($this->moduleName) ->setDisplayName($this->MOD_MENU['function'][$this->MOD_SETTINGS['function']]) ->setArguments($shortcutArguments); $buttonBar->addButton($shortCutButton, ButtonBar::BUTTON_POSITION_RIGHT); diff --git a/typo3/sysext/install/Classes/Updates/ShortcutRecordsMigration.php b/typo3/sysext/install/Classes/Updates/ShortcutRecordsMigration.php new file mode 100644 index 0000000000000000000000000000000000000000..2f4bde1068e4f6a4b52409bd99fe07d369aa0f01 --- /dev/null +++ b/typo3/sysext/install/Classes/Updates/ShortcutRecordsMigration.php @@ -0,0 +1,191 @@ +<?php + +declare(strict_types=1); + +namespace TYPO3\CMS\Install\Updates; + +/* + * 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\Backend\Routing\Router; +use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Database\Query\QueryBuilder; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +/** + * @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API. + */ +class ShortcutRecordsMigration implements UpgradeWizardInterface +{ + private const TABLE_NAME = 'sys_be_shortcuts'; + + protected ?iterable $routes = null; + + public function getIdentifier(): string + { + return 'shortcutRecordsMigration'; + } + + public function getTitle(): string + { + return 'Migrate shortcut records to new format.'; + } + + public function getDescription(): string + { + return 'To support speaking urls in the backend, some fields need to be changed in sys_be_shortcuts.'; + } + + public function getPrerequisites(): array + { + return [ + DatabaseUpdatedPrerequisite::class + ]; + } + + public function updateNecessary(): bool + { + return $this->columnsExistInTable() && $this->hasRecordsToUpdate(); + } + + public function executeUpdate(): bool + { + $this->routes = GeneralUtility::makeInstance(Router::class)->getRoutes(); + $connection = $this->getConnectionPool()->getConnectionForTable(self::TABLE_NAME); + + foreach ($this->getRecordsToUpdate() as $record) { + [$moduleName] = explode('|', (string)$record['module_name'], 2); + + if (!is_string($moduleName) || $moduleName === '') { + continue; + } + + if (($routeIdentifier = $this->getRouteIdentifierForModuleName($moduleName)) === '') { + continue; + } + + // Parse the url and reveal the arguments (query parameters) + $parsedUrl = parse_url((string)$record['url']) ?: []; + $arguments = []; + parse_str($parsedUrl['query'] ?? '', $arguments); + + // Unset not longer needed arguments + unset($arguments['route'], $arguments['returnUrl']); + + try { + $encodedArguments = json_encode($arguments, JSON_THROW_ON_ERROR) ?: ''; + } catch (\JsonException $e) { + // Skip the row if arguments can not be encoded + continue; + } + + // Update the record - Note: The "old" values won't be unset + $connection->update( + self::TABLE_NAME, + ['route' => $routeIdentifier, 'arguments' => $encodedArguments], + ['uid' => (int)$record['uid']] + ); + } + + return true; + } + + protected function columnsExistInTable(): bool + { + $schemaManager = $this->getConnectionPool()->getConnectionForTable(self::TABLE_NAME)->getSchemaManager(); + + if ($schemaManager === null) { + return false; + } + + $tableColumns = $schemaManager->listTableColumns(self::TABLE_NAME); + + foreach (['module_name', 'url', 'route', 'arguments'] as $column) { + if (!isset($tableColumns[$column])) { + return false; + } + } + + return true; + } + + protected function hasRecordsToUpdate(): bool + { + return (bool)$this->getPreparedQueryBuilder()->count('uid')->execute()->fetchColumn(); + } + + protected function getRecordsToUpdate(): array + { + return $this->getPreparedQueryBuilder()->select(...['uid', 'module_name', 'url'])->execute()->fetchAll(); + } + + protected function getPreparedQueryBuilder(): QueryBuilder + { + $queryBuilder = $this->getConnectionPool()->getQueryBuilderForTable(self::TABLE_NAME); + $queryBuilder->getRestrictions()->removeAll(); + $queryBuilder + ->from(self::TABLE_NAME) + ->where( + $queryBuilder->expr()->neq('module_name', $queryBuilder->createNamedParameter('')), + $queryBuilder->expr()->andX( + $queryBuilder->expr()->neq('url', $queryBuilder->createNamedParameter('')), + $queryBuilder->expr()->isNotNull('url') + ), + $queryBuilder->expr()->eq('route', $queryBuilder->createNamedParameter('')), + $queryBuilder->expr()->orX( + $queryBuilder->expr()->eq('arguments', $queryBuilder->createNamedParameter('')), + $queryBuilder->expr()->isNull('arguments') + ) + ); + + return $queryBuilder; + } + + protected function getRouteIdentifierForModuleName(string $moduleName): string + { + // Check static special cases first + switch ($moduleName) { + case 'xMOD_alt_doc.php': + return 'record_edit'; + case 'file_edit': + case 'wizard_rte': + return $moduleName; + } + + // Get identifier from module configuration + $routeIdentifier = $GLOBALS['TBE_MODULES']['_configuration'][$moduleName]['id'] ?? $moduleName; + + // Check if a route with the identifier exist + if (isset($this->routes[$routeIdentifier]) + && $this->routes[$routeIdentifier]->hasOption('moduleName') + && $this->routes[$routeIdentifier]->getOption('moduleName') === $moduleName + ) { + return $routeIdentifier; + } + + // If the defined route identifier can't be fetched, try from the other side + // by iterating over the routes to match a route by the defined module name + foreach ($this->routes as $identifier => $route) { + if ($route->hasOption('moduleName') && $route->getOption('moduleName') === $moduleName) { + return $routeIdentifier; + } + } + + return ''; + } + + protected function getConnectionPool(): ConnectionPool + { + return GeneralUtility::makeInstance(ConnectionPool::class); + } +} diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php index ddc6e54c2ab27fe2d400d7759c887a35ee48f19d..74d07e3516a51790542ab1fe511e65947a3d709a 100644 --- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php +++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php @@ -4690,4 +4690,18 @@ return [ 'Breaking-93080-RelationHandlerInternalsProtected.rst', ], ], + 'TYPO3\CMS\Backend\Template\Components\Buttons\Action\ShortcutButton->getModuleName' => [ + 'numberOfMandatoryArguments' => 0, + 'maximumNumberOfArguments' => 0, + 'restFiles' => [ + 'Deprecation-93093-DeprecateMethodNameInShortcutPHPAPI.rst' + ], + ], + 'TYPO3\CMS\Backend\Template\Components\Buttons\Action\ShortcutButton->setModuleName' => [ + 'numberOfMandatoryArguments' => 1, + 'maximumNumberOfArguments' => 1, + 'restFiles' => [ + 'Deprecation-93093-DeprecateMethodNameInShortcutPHPAPI.rst' + ], + ], ]; diff --git a/typo3/sysext/install/Tests/Functional/Updates/Fixtures/ShortcutsBase.csv b/typo3/sysext/install/Tests/Functional/Updates/Fixtures/ShortcutsBase.csv new file mode 100644 index 0000000000000000000000000000000000000000..44a06457f4c61d6e2cbb5a9adda2898b5adecce7 --- /dev/null +++ b/typo3/sysext/install/Tests/Functional/Updates/Fixtures/ShortcutsBase.csv @@ -0,0 +1,24 @@ +"sys_be_shortcuts",,,,,,,,, +,"uid","userid","module_name","url","description","sorting","sc_group","route","arguments" +,1,1,"system_config|","/typo3/index.php?&route=%2Fmodule%2Fsystem%2Fconfig&tree=siteConfiguration","Site Configuration",0,0,, +,2,1,"web_FormFormbuilder|","/typo3/index.php?&route=%2Fmodule%2Fweb%2FFormFormbuilder","Form manager",0,0,, +,3,1,"system_BeuserTxPermission|","/typo3/index.php?&route=%2Fmodule%2Fsystem%2FBeuserTxPermission&id=123","Permissions",0,0,, +,4,1,"file_FilelistList|","/typo3/index.php?&route=%2Fmodule%2Ffile%2FFilelistList&id=1%3A%2F","Filelist root",0,0,, +,5,1,"web_layout|","/typo3/index.php?&route=%2Fmodule%2Fweb%2Flayout&id=123&SET%5Btt_content_showHidden%5D=1&SET%5Bfunction%5D=1&SET%5Blanguage%5D=0","Web layout",0,0,, +,6,1,"xMOD_alt_doc.php|","/typo3/index.php?&route=%2Frecord%2Fedit&returnUrl=%2Ftypo3%2Findex.php%3Froute%3D%252Fmodule%252Fweb%252Flayout%26token%3D123456%26id%3D123%26%23element-tt_content-3&edit%5Btt_content%5D%5B3%5D=edit","Edit Page Content",0,0,, +,7,1,"xMOD_alt_doc.php|","/typo3/index.php?&route=%2Frecord%2Fedit&returnUrl=%2Ftypo3%2Findex.php%3Froute%3D%252Fmodule%252Fweb%252Flist%26token%3D123456%26id%3D0%26table%3D&edit%5Bbe_groups%5D%5B3%5D=edit","Edit Backend usergroup on root level",0,0,, +,8,1,"xMOD_alt_doc.php|","/typo3/index.php?&route=%2Frecord%2Fedit&returnUrl=%2Ftypo3%2Findex.php%3Froute%3D%252Fmodule%252Fweb%252Flist%26token%3D123456%26id%3D0%26table%3D&edit%5Bbe_groups%5D%5B0%5D=new","Create new Backend usergroup",0,0,, +,9,1,"xMOD_alt_doc.php|","/typo3/index.php?&route=%2Frecord%2Fedit&returnUrl=%2Ftypo3%2Findex.php%3Froute%3D%252Fmodule%252Fsystem%252FBeuserTxBeuser%26token%3D123456&edit%5Bbe_users%5D%5B1%5D=edit","Edit Backend user on root level",0,0,, +,10,1,"xMOD_alt_doc.php|","/typo3/index.php?&route=%2Frecord%2Fedit&returnUrl=%2Ftypo3%2Findex.php%3Froute%3D%252Fmodule%252Fweb%252Flist%26token%3D123456%26id%3D123%26table%3D&edit%5Bpages%5D%5B416%5D=edit&overrideVals%5Bpages%5D%5Bsys_language_uid%5D=1","Edit Page with overrides",0,0,, +,11,1,"web_info|","/typo3/index.php?&route=%2Fmodule%2Fweb%2Finfo&id=123&SET%5Bfunction%5D=TYPO3%5CCMS%5CInfo%5CController%5CTranslationStatusController&SET%5Bdepth%5D=3","Localization Overview",0,0,, +,12,1,"system_dbint|","/typo3/index.php?&route=%2Fmodule%2Fsystem%2Fdbint&SET%5Bfunction%5D=search&SET%5Bsearch%5D=raw&SET%5Bsearch_query_makeQuery%5D=all","Db int full search",0,0,, +,13,1,"system_BeuserTxBeuser|","/typo3/index.php?&route=%2Fmodule%2Fsystem%2FBeuserTxBeuser&tx_beuser_system_beusertxbeuser%5Baction%5D=index&tx_beuser_system_beusertxbeuser%5Bcontroller%5D=BackendUser","Backend users",0,0,, +,14,1,"web_list|","/typo3/index.php?&route=%2Fmodule%2Fweb%2Flist&id=123&GET%5BclipBoard%5D=1","Recordlist",0,0,, +,15,1,"web_list|","/typo3/index.php?&route=%2Fmodule%2Fweb%2Flist&id=123&table=tx_styleguide_inline_mnsymmetric&GET%5BclipBoard%5D=1","Single table view",0,0,, +,16,1,,"/typo3/index.php?&route=%2Fmodule%2Fweb%2Flist&id=123&table=tx_styleguide_inline_mnsymmetric&GET%5BclipBoard%5D=1","Invalid no module name",0,0,, +,17,1,"web_list|",,"Invalid no url",0,0,, +,18,1,"web_list|","/typo3/index.php?&route=%2Fmodule%2Fweb%2Flist&id=123&table=tx_styleguide_inline_mnsymmetric&GET%5BclipBoard%5D=1","Invalid new field route not empty",0,0,"route_identifier", +,19,1,"web_list|","/typo3/index.php?&route=%2Fmodule%2Fweb%2Flist&id=123&table=tx_styleguide_inline_mnsymmetric&GET%5BclipBoard%5D=1","Invalid new field arguments not empty",0,0,,"[]" +,20,1,,,"New record",0,0,"web_list","{""id"":123,""GET"":{""clipBoard"":true}}" +,21,1,,,"New record with empty arguments",0,0,"record_edit","[]" +,22,1,,,"New record with empty route",0,0,,"{""id"":123,""GET"":{""clipBoard"":true}}" diff --git a/typo3/sysext/install/Tests/Functional/Updates/Fixtures/ShortcutsMigratedToRoutes.csv b/typo3/sysext/install/Tests/Functional/Updates/Fixtures/ShortcutsMigratedToRoutes.csv new file mode 100644 index 0000000000000000000000000000000000000000..e73e43a6f7060d33c20e0cc69d43425b053b4b8e --- /dev/null +++ b/typo3/sysext/install/Tests/Functional/Updates/Fixtures/ShortcutsMigratedToRoutes.csv @@ -0,0 +1,24 @@ +"sys_be_shortcuts",,,,,,,,, +,"uid","userid","module_name","url","description","sorting","sc_group","route","arguments" +,1,1,"system_config|","/typo3/index.php?&route=%2Fmodule%2Fsystem%2Fconfig&tree=siteConfiguration","Site Configuration",0,0,"system_config","{""tree"":""siteConfiguration""}" +,2,1,"web_FormFormbuilder|","/typo3/index.php?&route=%2Fmodule%2Fweb%2FFormFormbuilder","Form manager",0,0,"web_FormFormbuilder","[]" +,3,1,"system_BeuserTxPermission|","/typo3/index.php?&route=%2Fmodule%2Fsystem%2FBeuserTxPermission&id=123","Permissions",0,0,"system_BeuserTxPermission","{""id"":""123""}" +,4,1,"file_FilelistList|","/typo3/index.php?&route=%2Fmodule%2Ffile%2FFilelistList&id=1%3A%2F","Filelist root",0,0,"file_FilelistList","{""id"":""1:\/""}" +,5,1,"web_layout|","/typo3/index.php?&route=%2Fmodule%2Fweb%2Flayout&id=123&SET%5Btt_content_showHidden%5D=1&SET%5Bfunction%5D=1&SET%5Blanguage%5D=0","Web layout",0,0,"web_layout","{""id"":""123"",""SET"":{""tt_content_showHidden"":""1"",""function"":""1"",""language"":""0""}}" +,6,1,"xMOD_alt_doc.php|","/typo3/index.php?&route=%2Frecord%2Fedit&returnUrl=%2Ftypo3%2Findex.php%3Froute%3D%252Fmodule%252Fweb%252Flayout%26token%3D123456%26id%3D123%26%23element-tt_content-3&edit%5Btt_content%5D%5B3%5D=edit","Edit Page Content",0,0,"record_edit","{""edit"":{""tt_content"":{""3"":""edit""}}}" +,7,1,"xMOD_alt_doc.php|","/typo3/index.php?&route=%2Frecord%2Fedit&returnUrl=%2Ftypo3%2Findex.php%3Froute%3D%252Fmodule%252Fweb%252Flist%26token%3D123456%26id%3D0%26table%3D&edit%5Bbe_groups%5D%5B3%5D=edit","Edit Backend usergroup on root level",0,0,"record_edit","{""edit"":{""be_groups"":{""3"":""edit""}}}" +,8,1,"xMOD_alt_doc.php|","/typo3/index.php?&route=%2Frecord%2Fedit&returnUrl=%2Ftypo3%2Findex.php%3Froute%3D%252Fmodule%252Fweb%252Flist%26token%3D123456%26id%3D0%26table%3D&edit%5Bbe_groups%5D%5B0%5D=new","Create new Backend usergroup",0,0,"record_edit","{""edit"":{""be_groups"":[""new""]}}" +,9,1,"xMOD_alt_doc.php|","/typo3/index.php?&route=%2Frecord%2Fedit&returnUrl=%2Ftypo3%2Findex.php%3Froute%3D%252Fmodule%252Fsystem%252FBeuserTxBeuser%26token%3D123456&edit%5Bbe_users%5D%5B1%5D=edit","Edit Backend user on root level",0,0,"record_edit","{""edit"":{""be_users"":{""1"":""edit""}}}" +,10,1,"xMOD_alt_doc.php|","/typo3/index.php?&route=%2Frecord%2Fedit&returnUrl=%2Ftypo3%2Findex.php%3Froute%3D%252Fmodule%252Fweb%252Flist%26token%3D123456%26id%3D123%26table%3D&edit%5Bpages%5D%5B416%5D=edit&overrideVals%5Bpages%5D%5Bsys_language_uid%5D=1","Edit Page with overrides",0,0,"record_edit","{""edit"":{""pages"":{""416"":""edit""}},""overrideVals"":{""pages"":{""sys_language_uid"":""1""}}}" +,11,1,"web_info|","/typo3/index.php?&route=%2Fmodule%2Fweb%2Finfo&id=123&SET%5Bfunction%5D=TYPO3%5CCMS%5CInfo%5CController%5CTranslationStatusController&SET%5Bdepth%5D=3","Localization Overview",0,0,"web_info","{""id"":""123"",""SET"":{""function"":""TYPO3\\CMS\\Info\\Controller\\TranslationStatusController"",""depth"":""3""}}" +,12,1,"system_dbint|","/typo3/index.php?&route=%2Fmodule%2Fsystem%2Fdbint&SET%5Bfunction%5D=search&SET%5Bsearch%5D=raw&SET%5Bsearch_query_makeQuery%5D=all","Db int full search",0,0,"system_dbint","{""SET"":{""function"":""search"",""search"":""raw"",""search_query_makeQuery"":""all""}}" +,13,1,"system_BeuserTxBeuser|","/typo3/index.php?&route=%2Fmodule%2Fsystem%2FBeuserTxBeuser&tx_beuser_system_beusertxbeuser%5Baction%5D=index&tx_beuser_system_beusertxbeuser%5Bcontroller%5D=BackendUser","Backend users",0,0,"system_BeuserTxBeuser","{""tx_beuser_system_beusertxbeuser"":{""action"":""index"",""controller"":""BackendUser""}}" +,14,1,"web_list|","/typo3/index.php?&route=%2Fmodule%2Fweb%2Flist&id=123&GET%5BclipBoard%5D=1","Recordlist",0,0,"web_list","{""id"":""123"",""GET"":{""clipBoard"":""1""}}" +,15,1,"web_list|","/typo3/index.php?&route=%2Fmodule%2Fweb%2Flist&id=123&table=tx_styleguide_inline_mnsymmetric&GET%5BclipBoard%5D=1","Single table view",0,0,"web_list","{""id"":""123"",""table"":""tx_styleguide_inline_mnsymmetric"",""GET"":{""clipBoard"":""1""}}" +,16,1,"","/typo3/index.php?&route=%2Fmodule%2Fweb%2Flist&id=123&table=tx_styleguide_inline_mnsymmetric&GET%5BclipBoard%5D=1","Invalid no module name",0,0,, +,17,1,"web_list|",,"Invalid no url",0,0,, +,18,1,"web_list|","/typo3/index.php?&route=%2Fmodule%2Fweb%2Flist&id=123&table=tx_styleguide_inline_mnsymmetric&GET%5BclipBoard%5D=1","Invalid new field route not empty",0,0,"route_identifier", +,19,1,"web_list|","/typo3/index.php?&route=%2Fmodule%2Fweb%2Flist&id=123&table=tx_styleguide_inline_mnsymmetric&GET%5BclipBoard%5D=1","Invalid new field arguments not empty",0,0,,"[]" +,20,1,,,"New record",0,0,"web_list","{""id"":123,""GET"":{""clipBoard"":true}}" +,21,1,,,"New record with empty arguments",0,0,"record_edit","[]" +,22,1,,,"New record with empty route",0,0,,"{""id"":123,""GET"":{""clipBoard"":true}}" diff --git a/typo3/sysext/install/Tests/Functional/Updates/ShortcutRecordsMigrationTest.php b/typo3/sysext/install/Tests/Functional/Updates/ShortcutRecordsMigrationTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0d5d31af8a1814787bc78c86abe3de4e47c0cb37 --- /dev/null +++ b/typo3/sysext/install/Tests/Functional/Updates/ShortcutRecordsMigrationTest.php @@ -0,0 +1,75 @@ +<?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\Install\Tests\Functional\Updates; + +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Types\StringType; +use Doctrine\DBAL\Types\TextType; +use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Database\Schema\TableDiff; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Install\Updates\ShortcutRecordsMigration; +use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; + +class ShortcutRecordsMigrationTest extends FunctionalTestCase +{ + private const TABLE_NAME = 'sys_be_shortcuts'; + + /** + * Require additional core extensions so the routes + * of the modules in the fixture are available. + * + * @var string[] + */ + protected $coreExtensionsToLoad = ['beuser', 'filelist', 'form', 'info', 'lowlevel']; + + protected string $baseDataSet = 'typo3/sysext/install/Tests/Functional/Updates/Fixtures/ShortcutsBase.csv'; + protected string $resultDataSet = 'typo3/sysext/install/Tests/Functional/Updates/Fixtures/ShortcutsMigratedToRoutes.csv'; + + /** + * @test + */ + public function shortcutRecordsUpdated(): void + { + $subject = new ShortcutRecordsMigration(); + + $schemaManager = GeneralUtility::makeInstance(ConnectionPool::class) + ->getConnectionForTable(self::TABLE_NAME) + ->getSchemaManager(); + + $schemaManager->alterTable( + new TableDiff( + self::TABLE_NAME, + [ + new Column('module_name', new StringType(), ['length' => 255, 'default' => '']), + new Column('url', new TextType(), ['notnull' => false]) + ] + ) + ); + + $this->importCSVDataSet(GeneralUtility::getFileAbsFileName($this->baseDataSet)); + self::assertTrue($subject->updateNecessary()); + $subject->executeUpdate(); + self::assertFalse($subject->updateNecessary()); + $this->assertCSVDataSet(GeneralUtility::getFileAbsFileName($this->resultDataSet)); + + // Just ensure that running the upgrade again does not change anything + $subject->executeUpdate(); + $this->assertCSVDataSet(GeneralUtility::getFileAbsFileName($this->resultDataSet)); + } +} diff --git a/typo3/sysext/install/ext_localconf.php b/typo3/sysext/install/ext_localconf.php index d04eefeaaea397040a1be572129327042bb05f0f..19b5e1a175ad26342517b85e1956ef73809b110c 100644 --- a/typo3/sysext/install/ext_localconf.php +++ b/typo3/sysext/install/ext_localconf.php @@ -11,6 +11,8 @@ $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['taskcenterEx = \TYPO3\CMS\Install\Updates\TaskcenterExtractionUpdate::class; $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['sysActionExtension'] = \TYPO3\CMS\Install\Updates\SysActionExtractionUpdate::class; +$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['shortcutRecordsMigration'] + = \TYPO3\CMS\Install\Updates\ShortcutRecordsMigration::class; $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/install']['update']['databaseRowsUpdateWizard'] = \TYPO3\CMS\Install\Updates\DatabaseRowsUpdateWizard::class; diff --git a/typo3/sysext/lowlevel/Classes/Controller/ConfigurationController.php b/typo3/sysext/lowlevel/Classes/Controller/ConfigurationController.php index f9e3767b0840c3976205239ef71e60bcf9a39545..5a6caf117b9bfe3bc719ce5469ec147b398b4814 100644 --- a/typo3/sysext/lowlevel/Classes/Controller/ConfigurationController.php +++ b/typo3/sysext/lowlevel/Classes/Controller/ConfigurationController.php @@ -121,7 +121,8 @@ class ConfigurationController // Shortcut in doc header $shortcutButton = $moduleTemplate->getDocHeaderComponent()->getButtonBar()->makeShortcutButton(); - $shortcutButton->setModuleName('system_config') + $shortcutButton + ->setRouteIdentifier('system_config') ->setDisplayName($configurationProvider->getLabel()) ->setArguments([ 'route' => $request->getQueryParams()['route'], diff --git a/typo3/sysext/lowlevel/Classes/Controller/DatabaseIntegrityController.php b/typo3/sysext/lowlevel/Classes/Controller/DatabaseIntegrityController.php index e7928a7827f04bd673b2f7f1ad9d362b4013b9ef..175f16b110df898301b59385175f1cd42d8ee37f 100644 --- a/typo3/sysext/lowlevel/Classes/Controller/DatabaseIntegrityController.php +++ b/typo3/sysext/lowlevel/Classes/Controller/DatabaseIntegrityController.php @@ -140,7 +140,7 @@ class DatabaseIntegrityController $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar(); // Shortcut $shortCutButton = $buttonBar->makeShortcutButton() - ->setModuleName($this->moduleName) + ->setRouteIdentifier($this->moduleName) ->setDisplayName($this->MOD_MENU['function'][$this->MOD_SETTINGS['function']]) ->setArguments([ 'route' => $request->getQueryParams()['route'], diff --git a/typo3/sysext/recordlist/Classes/RecordList/DatabaseRecordList.php b/typo3/sysext/recordlist/Classes/RecordList/DatabaseRecordList.php index b734e056d595639c8da3b04407fbc85c1e3f6137..edb8686c5ccf028a7bd0313fb54f9bb5fde62682 100644 --- a/typo3/sysext/recordlist/Classes/RecordList/DatabaseRecordList.php +++ b/typo3/sysext/recordlist/Classes/RecordList/DatabaseRecordList.php @@ -601,7 +601,7 @@ class DatabaseRecordList $buttonBar->addButton($reloadButton, ButtonBar::BUTTON_POSITION_RIGHT); // Shortcut - $shortCutButton = $buttonBar->makeShortcutButton()->setModuleName('web_list'); + $shortCutButton = $buttonBar->makeShortcutButton()->setRouteIdentifier('web_list'); $queryParams = $request->getQueryParams(); $arguments = [ 'route' => $queryParams['route'], diff --git a/typo3/sysext/recycler/Classes/Controller/RecyclerModuleController.php b/typo3/sysext/recycler/Classes/Controller/RecyclerModuleController.php index 088d96ea4e164bbe8cc7412bc66b2dceef54e24f..a96818d00e5371d9d24efd5f5c047edf4f3a90e2 100644 --- a/typo3/sysext/recycler/Classes/Controller/RecyclerModuleController.php +++ b/typo3/sysext/recycler/Classes/Controller/RecyclerModuleController.php @@ -157,7 +157,7 @@ class RecyclerModuleController $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar(); $shortcutButton = $buttonBar->makeShortcutButton() - ->setModuleName('web_RecyclerRecycler') + ->setRouteIdentifier('web_RecyclerRecycler') ->setDisplayName($this->getShortcutTitle()) ->setArguments([ 'route' => $route, diff --git a/typo3/sysext/redirects/Classes/Controller/ManagementController.php b/typo3/sysext/redirects/Classes/Controller/ManagementController.php index 63772e557519f7f6427ea5a37074aa94773cf66d..9b24cf15a6094d16c15cdb88b882b8e7d3acb128 100644 --- a/typo3/sysext/redirects/Classes/Controller/ManagementController.php +++ b/typo3/sysext/redirects/Classes/Controller/ManagementController.php @@ -178,7 +178,7 @@ class ManagementController // Shortcut $shortcutButton = $buttonBar->makeShortcutButton() - ->setModuleName('site_redirects') + ->setRouteIdentifier('site_redirects') ->setDisplayName($this->getLanguageService()->sL('LLL:EXT:redirects/Resources/Private/Language/locallang_module_redirect.xlf:mlang_labels_tablabel')) ->setArguments([ 'route' => $this->request->getQueryParams()['route'], diff --git a/typo3/sysext/reports/Classes/Controller/ReportController.php b/typo3/sysext/reports/Classes/Controller/ReportController.php index bc1549cc4c2ecbd87b4eab038c446e6df90a524a..cab8e4fbe3ff658215e01723e3f35244bce2bafa 100644 --- a/typo3/sysext/reports/Classes/Controller/ReportController.php +++ b/typo3/sysext/reports/Classes/Controller/ReportController.php @@ -113,7 +113,7 @@ class ReportController $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar(); $shortcutButton = $buttonBar->makeShortcutButton() - ->setModuleName('system_reports') + ->setRouteIdentifier('system_reports') ->setDisplayName($this->shortcutName) ->setArguments([ 'route' => $request->getQueryParams()['route'], diff --git a/typo3/sysext/scheduler/Classes/Controller/SchedulerModuleController.php b/typo3/sysext/scheduler/Classes/Controller/SchedulerModuleController.php index 41133aaa2e4723df5a8dd12bd78e375a0f577526..c496ac9dfa51b9a2f155020d43b8f198622be387 100644 --- a/typo3/sysext/scheduler/Classes/Controller/SchedulerModuleController.php +++ b/typo3/sysext/scheduler/Classes/Controller/SchedulerModuleController.php @@ -1383,7 +1383,7 @@ class SchedulerModuleController $shortcutArguments['tx_scheduler']['uid'] = $queryParams['tx_scheduler']['uid']; } $shortcutButton = $buttonBar->makeShortcutButton() - ->setModuleName('system_txschedulerM1') + ->setRouteIdentifier('system_txschedulerM1') ->setDisplayName($this->MOD_MENU['function'][$this->MOD_SETTINGS['function']]) ->setArguments($shortcutArguments); $buttonBar->addButton($shortcutButton); diff --git a/typo3/sysext/setup/Classes/Controller/SetupModuleController.php b/typo3/sysext/setup/Classes/Controller/SetupModuleController.php index 6752f38347f38588508c51ff016f1708bba9953c..5d31394ddef1c13cdde45b5565bcd1cb91e3e2bf 100644 --- a/typo3/sysext/setup/Classes/Controller/SetupModuleController.php +++ b/typo3/sysext/setup/Classes/Controller/SetupModuleController.php @@ -430,7 +430,7 @@ class SetupModuleController $buttonBar->addButton($saveButton); $shortcutButton = $buttonBar->makeShortcutButton() - ->setModuleName($this->moduleName) + ->setRouteIdentifier($this->moduleName) ->setDisplayName($this->getLanguageService()->sL('LLL:EXT:setup/Resources/Private/Language/locallang_mod.xlf:mlang_labels_tablabel')) ->setArguments([ 'route' => $route, diff --git a/typo3/sysext/tstemplate/Classes/Controller/TypoScriptTemplateModuleController.php b/typo3/sysext/tstemplate/Classes/Controller/TypoScriptTemplateModuleController.php index f4a197478732fce0812fff393a94c675e02ee835..b6405e12fa538e3ef91af97b28b57aadfd57ec1d 100644 --- a/typo3/sysext/tstemplate/Classes/Controller/TypoScriptTemplateModuleController.php +++ b/typo3/sysext/tstemplate/Classes/Controller/TypoScriptTemplateModuleController.php @@ -373,7 +373,7 @@ class TypoScriptTemplateModuleController } // Shortcut $shortcutButton = $buttonBar->makeShortcutButton() - ->setModuleName('web_ts') + ->setRouteIdentifier('web_ts') ->setDisplayName($this->getShortcutTitle()) ->setArguments([ 'route' => $this->request->getQueryParams()['route'], diff --git a/typo3/sysext/viewpage/Classes/Controller/ViewModuleController.php b/typo3/sysext/viewpage/Classes/Controller/ViewModuleController.php index 74f3666b7189dc5c775c738f8969c732acab08ce..bc1d619a3ac9b2d01817040591dbe2754b2e64ef 100644 --- a/typo3/sysext/viewpage/Classes/Controller/ViewModuleController.php +++ b/typo3/sysext/viewpage/Classes/Controller/ViewModuleController.php @@ -136,7 +136,7 @@ class ViewModuleController // Shortcut $shortcutButton = $buttonBar->makeShortcutButton() - ->setModuleName('web_ViewpageView') + ->setRouteIdentifier('web_ViewpageView') ->setDisplayName($this->getShortcutTitle($pageId)) ->setArguments([ 'route' => $route, diff --git a/typo3/sysext/workspaces/Classes/Controller/ReviewController.php b/typo3/sysext/workspaces/Classes/Controller/ReviewController.php index 6cb11e4556e9bef3d542326c9376868fb44e1bea..2c5451ed5bbe8299182abd73c16c3c3483849a33 100644 --- a/typo3/sysext/workspaces/Classes/Controller/ReviewController.php +++ b/typo3/sysext/workspaces/Classes/Controller/ReviewController.php @@ -186,7 +186,7 @@ class ReviewController $buttonBar->addButton($showButton); } $shortcutButton = $buttonBar->makeShortcutButton() - ->setModuleName('web_WorkspacesWorkspaces') + ->setRouteIdentifier('web_WorkspacesWorkspaces') ->setDisplayName(sprintf('%s: %s [%d]', $activeWorkspaceTitle, $pageTitle, $this->pageId)) ->setArguments([ 'route' => (string)GeneralUtility::_GP('route'),