[TASK] Extract request processing from ShortcutToolbarItem 31/56331/24
authorMathias Brodala <mbrodala@pagemachine.de>
Sat, 17 Mar 2018 17:16:47 +0000 (18:16 +0100)
committerMathias Brodala <mbrodala@pagemachine.de>
Wed, 18 Jul 2018 07:49:25 +0000 (09:49 +0200)
Change-Id: I9f396da2084880b3dd813c8d7f0777c36ac989d9
Resolves: #84414
Releases: master
Reviewed-on: https://review.typo3.org/56331
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Robert van Kammen <rvkammen@hotmail.com>
Tested-by: Robert van Kammen <rvkammen@hotmail.com>
Reviewed-by: Jörg Bösche <typo3@joergboesche.de>
Reviewed-by: Mathias Brodala <mbrodala@pagemachine.de>
Tested-by: Mathias Brodala <mbrodala@pagemachine.de>
typo3/sysext/backend/Classes/Backend/Shortcut/ShortcutRepository.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Backend/ToolbarItems/ShortcutToolbarItem.php
typo3/sysext/backend/Classes/Controller/ShortcutController.php [new file with mode: 0644]
typo3/sysext/backend/Classes/Template/DocumentTemplate.php
typo3/sysext/backend/Classes/Template/ModuleTemplate.php
typo3/sysext/backend/Classes/Utility/BackendUtility.php
typo3/sysext/backend/Configuration/Backend/AjaxRoutes.php
typo3/sysext/core/Documentation/Changelog/master/Deprecation-84414-BackendUtilityshortcutExists.rst [new file with mode: 0644]
typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php

diff --git a/typo3/sysext/backend/Classes/Backend/Shortcut/ShortcutRepository.php b/typo3/sysext/backend/Classes/Backend/Shortcut/ShortcutRepository.php
new file mode 100644 (file)
index 0000000..4bfda5e
--- /dev/null
@@ -0,0 +1,785 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Backend\Backend\Shortcut;
+
+/*
+ * 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\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;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Database\Connection;
+use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Imaging\Icon;
+use TYPO3\CMS\Core\Imaging\IconFactory;
+use TYPO3\CMS\Core\Localization\LanguageService;
+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
+ */
+class ShortcutRepository
+{
+    /**
+     * @var int Number of super global (All) group
+     */
+    protected const SUPERGLOBAL_GROUP = -100;
+
+    /**
+     * @var array
+     */
+    protected $shortcuts;
+
+    /**
+     * @var array
+     */
+    protected $shortcutGroups;
+
+    /**
+     * @var IconFactory
+     */
+    protected $iconFactory;
+
+    /**
+     * @var ModuleLoader
+     */
+    protected $moduleLoader;
+
+    /**
+     * Constructor
+     */
+    public function __construct()
+    {
+        $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
+        $this->moduleLoader = GeneralUtility::makeInstance(ModuleLoader::class);
+        $this->moduleLoader->load($GLOBALS['TBE_MODULES']);
+
+        $this->shortcutGroups = $this->initShortcutGroups();
+        $this->shortcuts = $this->initShortcuts();
+    }
+
+    /**
+     * Gets a shortcut by its uid
+     *
+     * @param int $shortcutId Shortcut id to get the complete shortcut for
+     * @return mixed An array containing the shortcut's data on success or FALSE on failure
+     */
+    public function getShortcutById(int $shortcutId)
+    {
+        foreach ($this->shortcuts as $shortcut) {
+            if ($shortcut['raw']['uid'] === $shortcutId) {
+                return $shortcut;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Gets shortcuts for a specific group
+     *
+     * @param int $groupId Group Id
+     * @return array Array of shortcuts that matched the group
+     */
+    public function getShortcutsByGroup(int $groupId): array
+    {
+        $shortcuts = [];
+
+        foreach ($this->shortcuts as $shortcut) {
+            if ($shortcut['group'] === $groupId) {
+                $shortcuts[] = $shortcut;
+            }
+        }
+
+        return $shortcuts;
+    }
+
+    /**
+     * Get shortcut groups the current user has access to
+     *
+     * @return array
+     */
+    public function getShortcutGroups(): array
+    {
+        $shortcutGroups = $this->shortcutGroups;
+
+        if (!$this->getBackendUser()->isAdmin()) {
+            foreach ($shortcutGroups as $groupId => $groupName) {
+                if ((int)$groupId < 0) {
+                    unset($shortcutGroups[$groupId]);
+                }
+            }
+        }
+
+        return $shortcutGroups;
+    }
+
+    /**
+     * runs through the available shortcuts an collects their groups
+     *
+     * @return array Array of groups which have shortcuts
+     */
+    public function getGroupsFromShortcuts(): array
+    {
+        $groups = [];
+
+        foreach ($this->shortcuts as $shortcut) {
+            $groups[$shortcut['group']] = $this->shortcutGroups[$shortcut['group']];
+        }
+
+        return array_unique($groups);
+    }
+
+    /**
+     * Returns if there already is a shortcut entry for a given TYPO3 URL
+     *
+     * @param string $url
+     * @return bool
+     */
+    public function shortcutExists(string $url): bool
+    {
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable('sys_be_shortcuts');
+        $queryBuilder->getRestrictions()->removeAll();
+
+        $uid = $queryBuilder->select('uid')
+            ->from('sys_be_shortcuts')
+            ->where(
+                $queryBuilder->expr()->eq(
+                    'userid',
+                    $queryBuilder->createNamedParameter($this->getBackendUser()->user['uid'], \PDO::PARAM_INT)
+                ),
+                $queryBuilder->expr()->eq(
+                    'url',
+                    $queryBuilder->createNamedParameter($url, \PDO::PARAM_STR)
+                )
+            )
+            ->execute()
+            ->fetchColumn();
+
+        return (bool)$uid;
+    }
+
+    /**
+     * 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 $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
+    {
+        if (empty($url) || empty($module)) {
+            return false;
+        }
+
+        $queryParts = parse_url($url);
+        $queryParameters = GeneralUtility::explodeUrl2Array($queryParts['query'], true);
+
+        if (!empty($queryParameters['scheme'])) {
+            throw new \RuntimeException('Shortcut URLs must be relative', 1518785877);
+        }
+
+        $languageService = $this->getLanguageService();
+        $title = $title ?: 'Shortcut';
+        $titlePrefix = '';
+        $type = 'other';
+        $table = '';
+        $recordId = 0;
+        $pageId = 0;
+
+        if (is_array($queryParameters['edit'])) {
+            $table = 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 {
+                $resourceFactory = ResourceFactory::getInstance();
+                $resource = $resourceFactory->getObjectFromCombinedIdentifier($queryParameters['id']);
+                $title = trim(sprintf(
+                    '%s (%s)',
+                    $titlePrefix,
+                    $resource->getName()
+                ));
+            } catch (ResourceDoesNotExistException $e) {
+            }
+        } else {
+            // Lookup the title of this page and use it as default description
+            $pageId = $pageId ?: $recordId ?: $this->extractPageIdFromShortcutUrl($url);
+            $page = $pageId ? BackendUtility::getRecord('pages', $pageId) : null;
+
+            if (!empty($page)) {
+                // Set the name to the title of the page
+                if ($type === 'other') {
+                    $title = sprintf(
+                        '%s (%s)',
+                        $title,
+                        $page['title']
+                    );
+                } else {
+                    $title = sprintf(
+                        '%s %s (%s)',
+                        $titlePrefix,
+                        $languageService->sL($GLOBALS['TCA'][$table]['ctrl']['title']),
+                        $page['title']
+                    );
+                }
+            } elseif (!empty($table)) {
+                $title = trim(sprintf(
+                    '%s %s',
+                    $titlePrefix,
+                    $languageService->sL($GLOBALS['TCA'][$table]['ctrl']['title'])
+                ));
+            }
+        }
+
+        if ($title === 'Shortcut') {
+            $moduleLabels = $this->moduleLoader->getLabelsForModule($module);
+
+            if (!empty($moduleLabels['shortdescription'])) {
+                $title = $this->getLanguageService()->sL($moduleLabels['shortdescription']);
+            }
+        }
+
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable('sys_be_shortcuts');
+        $affectedRows = $queryBuilder
+            ->insert('sys_be_shortcuts')
+            ->values([
+                'userid' => $this->getBackendUser()->user['uid'],
+                'module_name' => $module . '|' . $parentModule,
+                'url' => $url,
+                'description' => $title,
+                'sorting' => $GLOBALS['EXEC_TIME'],
+            ])
+            ->execute();
+
+        return $affectedRows === 1;
+    }
+
+    /**
+     * Update a shortcut
+     *
+     * @param int $id identifier of a shortcut
+     * @param string $title new title of the shortcut
+     * @param int $groupId new group identifier of the shortcut
+     * @return bool
+     */
+    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')
+            ->where(
+                $queryBuilder->expr()->eq(
+                    'uid',
+                    $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
+                )
+            )
+            ->set('description', $title)
+            ->set('sc_group', $groupId);
+
+        if (!$backendUser->isAdmin()) {
+            // Users can only modify their own shortcuts
+            $queryBuilder->andWhere(
+                $queryBuilder->expr()->eq(
+                    'userid',
+                    $queryBuilder->createNamedParameter($backendUser->user['uid'], \PDO::PARAM_INT)
+                )
+            );
+
+            if ($groupId < 0) {
+                $queryBuilder->set('sc_group', 0);
+            }
+        }
+
+        $affectedRows = $queryBuilder->execute();
+
+        return $affectedRows === 1;
+    }
+
+    /**
+     * Remove a shortcut
+     *
+     * @param int $id identifier of a shortcut
+     * @return bool
+     */
+    public function removeShortcut(int $id): bool
+    {
+        $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')
+                ->where(
+                    $queryBuilder->expr()->eq(
+                        'uid',
+                        $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)
+                    )
+                )
+                ->execute();
+
+            if ($affectedRows === 1) {
+                $success = true;
+            }
+        }
+
+        return $success;
+    }
+
+    /**
+     * Gets the available shortcut groups from default groups, user TSConfig, and global groups
+     *
+     * @return array
+     */
+    protected function initShortcutGroups(): array
+    {
+        $languageService = $this->getLanguageService();
+        $backendUser = $this->getBackendUser();
+        // By default, 5 groups are set
+        $shortcutGroups = [
+            1 => '1',
+            2 => '1',
+            3 => '1',
+            4 => '1',
+            5 => '1',
+        ];
+
+        // Groups from TSConfig
+        $bookmarkGroups = $backendUser->getTSConfig()['options.']['bookmarkGroups.'] ?? [];
+
+        if (is_array($bookmarkGroups)) {
+            foreach ($bookmarkGroups as $groupId => $label) {
+                if (!empty($label)) {
+                    $shortcutGroups[$groupId] = (string)$label;
+                } elseif ($backendUser->isAdmin()) {
+                    unset($shortcutGroups[$groupId]);
+                }
+            }
+        }
+
+        // Generate global groups, all global groups have negative IDs.
+        if (!empty($shortcutGroups)) {
+            foreach ($shortcutGroups as $groupId => $groupLabel) {
+                $shortcutGroups[$groupId * -1] = $groupLabel;
+            }
+        }
+
+        // Group -100 is kind of superglobal and can't be changed.
+        $shortcutGroups[self::SUPERGLOBAL_GROUP] = '1';
+
+        // Add labels
+        $languageFile = 'LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf';
+
+        foreach ($shortcutGroups as $groupId => $groupLabel) {
+            $groupId = (int)$groupId;
+            $label = $groupLabel;
+
+            if ($groupLabel === '1') {
+                $label = $languageService->sL($languageFile . ':bookmark_group_' . abs($groupId));
+
+                if (empty($label)) {
+                    // Fallback label
+                    $label = $languageService->sL($languageFile . ':bookmark_group') . ' ' . abs($groupId);
+                }
+            }
+
+            if ($groupId < 0) {
+                // Global group
+                $label = $languageService->sL($languageFile . ':bookmark_global') . ': ' . (!empty($label) ? $label : abs($groupId));
+
+                if ($groupId === self::SUPERGLOBAL_GROUP) {
+                    $label = $languageService->sL($languageFile . ':bookmark_global') . ': ' . $languageService->sL($languageFile . ':bookmark_all');
+                }
+            }
+
+            $shortcutGroups[$groupId] = htmlspecialchars($label);
+        }
+
+        return $shortcutGroups;
+    }
+
+    /**
+     * Retrieves the shortcuts for the current user
+     *
+     * @return array Array of shortcuts
+     */
+    protected function initShortcuts(): array
+    {
+        $backendUser = $this->getBackendUser();
+        // Traverse shortcuts
+        $lastGroup = 0;
+        $shortcuts = [];
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable('sys_be_shortcuts');
+        $result = $queryBuilder->select('*')
+            ->from('sys_be_shortcuts')
+            ->where(
+                $queryBuilder->expr()->andX(
+                    $queryBuilder->expr()->eq(
+                        'userid',
+                        $queryBuilder->createNamedParameter($backendUser->user['uid'], \PDO::PARAM_INT)
+                    ),
+                    $queryBuilder->expr()->gte(
+                        'sc_group',
+                        $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
+                    )
+                )
+            )
+            ->orWhere(
+                $queryBuilder->expr()->in(
+                    'sc_group',
+                    $queryBuilder->createNamedParameter(
+                        array_keys($this->getGlobalShortcutGroups()),
+                        Connection::PARAM_INT_ARRAY
+                    )
+                )
+            )
+            ->orderBy('sc_group')
+            ->addOrderBy('sorting')
+            ->execute();
+
+        while ($row = $result->fetch()) {
+            $shortcut = ['raw' => $row];
+
+            list($row['module_name'], $row['M_module_name']) = explode('|', $row['module_name']);
+
+            $queryParts = parse_url($row['url']);
+            $queryParameters = GeneralUtility::explodeUrl2Array($queryParts['query'], 1);
+
+            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') {
+                    $shortcut['type'] = 'edit';
+                } elseif ($queryParameters['edit'][$shortcut['table']][$shortcut['recordid']] === 'new') {
+                    $shortcut['type'] = 'new';
+                }
+
+                if (substr((string)$shortcut['recordid'], -1) === ',') {
+                    $shortcut['recordid'] = substr((string)$shortcut['recordid'], 0, -1);
+                }
+            } else {
+                $shortcut['type'] = 'other';
+            }
+
+            // Check for module access
+            $moduleName = $row['M_module_name'] ?: $row['module_name'];
+
+            // Check if the user has access to this module
+            if (!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;
+                    }
+                }
+            }
+
+            $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);
+            }
+
+            $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 . ');';
+
+            $shortcuts[] = $shortcut;
+        }
+
+        return $shortcuts;
+    }
+
+    /**
+     * Gets a list of global groups, shortcuts in these groups are available to all users
+     *
+     * @return array Array of global groups
+     */
+    protected function getGlobalShortcutGroups(): array
+    {
+        $globalGroups = [];
+
+        foreach ($this->shortcutGroups as $groupId => $groupLabel) {
+            if ($groupId < 0) {
+                $globalGroups[$groupId] = $groupLabel;
+            }
+        }
+
+        return $globalGroups;
+    }
+
+    /**
+     * Gets the label for a shortcut group
+     *
+     * @param int $groupId A shortcut group id
+     * @return string The shortcut group label, can be an empty string if no group was found for the id
+     */
+    protected function getShortcutGroupLabel(int $groupId): string
+    {
+        return $this->shortcutGroups[$groupId] ?? '';
+    }
+
+    /**
+     * Gets the icon for the shortcut
+     *
+     * @param array $row
+     * @param array $shortcut
+     * @return string Shortcut icon as img tag
+     */
+    protected function getShortcutIcon(array $row, array $shortcut): string
+    {
+        switch ($row['module_name']) {
+            case 'xMOD_alt_doc.php':
+                $table = $shortcut['table'];
+                $recordid = $shortcut['recordid'];
+                $icon = '';
+
+                if ($shortcut['type'] === 'edit') {
+                    // Creating the list of fields to include in the SQL query:
+                    $selectFields[] = 'uid';
+                    $selectFields[] = 'pid';
+
+                    if ($table === 'pages') {
+                        $selectFields[] = 'module';
+                        $selectFields[] = 'extendToSubpages';
+                        $selectFields[] = 'doktype';
+                    }
+
+                    if (is_array($GLOBALS['TCA'][$table]['ctrl']['enablecolumns'])) {
+                        $selectFields = array_merge($selectFields, $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']);
+                    }
+
+                    if ($GLOBALS['TCA'][$table]['ctrl']['typeicon_column']) {
+                        $selectFields[] = $GLOBALS['TCA'][$table]['ctrl']['typeicon_column'];
+                    }
+
+                    if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
+                        $selectFields[] = 't3ver_state';
+                    }
+
+                    $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+                        ->getQueryBuilderForTable($table);
+                    $queryBuilder->select(...array_unique(array_values($selectFields)))
+                        ->from($table)
+                        ->where(
+                            $queryBuilder->expr()->in(
+                                'uid',
+                                $queryBuilder->createNamedParameter($recordid, \PDO::PARAM_INT)
+                            )
+                        );
+
+                    $row = $queryBuilder->execute()->fetch();
+
+                    $icon = $this->iconFactory->getIconForRecord($table, (array)$row, Icon::SIZE_SMALL)->render();
+                } elseif ($shortcut['type'] === 'new') {
+                    $icon = $this->iconFactory->getIconForRecord($table, [], Icon::SIZE_SMALL)->render();
+                }
+                break;
+            case 'file_edit':
+                $icon = $this->iconFactory->getIcon('mimetypes-text-html', Icon::SIZE_SMALL)->render();
+                break;
+            case 'wizard_rte':
+                $icon = $this->iconFactory->getIcon('mimetypes-word', Icon::SIZE_SMALL)->render();
+                break;
+            default:
+                $iconIdentifier = '';
+                $moduleName = $row['module_name'];
+
+                if (strpos($moduleName, '_') !== false) {
+                    list($mainModule, $subModule) = explode('_', $moduleName, 2);
+                    $iconIdentifier = $this->moduleLoader->modules[$mainModule]['sub'][$subModule]['iconIdentifier'];
+                } elseif (!empty($moduleName)) {
+                    $iconIdentifier = $this->moduleLoader->modules[$moduleName]['iconIdentifier'];
+                }
+
+                if (!$iconIdentifier) {
+                    $iconIdentifier = 'empty-empty';
+                }
+
+                $icon = $this->iconFactory->getIcon($iconIdentifier, Icon::SIZE_SMALL)->render();
+        }
+
+        return $icon;
+    }
+
+    /**
+     * Returns title for the shortcut icon
+     *
+     * @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
+     */
+    protected function getShortcutIconTitle(string $shortcutLabel, string $moduleName, string $parentModuleName = ''): string
+    {
+        $languageService = $this->getLanguageService();
+
+        if (strpos($moduleName, 'xMOD_') === 0) {
+            $title = substr($moduleName, 5);
+        } else {
+            list($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 . ')';
+        }
+
+        $title .= ': ' . $shortcutLabel;
+
+        return $title;
+    }
+
+    /**
+     * Return the ID of the page in the URL if found.
+     *
+     * @param string $url The URL of the current shortcut link
+     * @return int If a page ID was found, it is returned. Otherwise: 0
+     */
+    protected function extractPageIdFromShortcutUrl(string $url): int
+    {
+        return (int)preg_replace('/.*[\\?&]id=([^&]+).*/', '$1', $url);
+    }
+
+    /**
+     * Adds the correct token, if the url is an index.php script
+     * @todo: this needs love
+     *
+     * @param string $url
+     * @return string
+     */
+    protected function getTokenUrl(string $url): string
+    {
+        $parsedUrl = parse_url($url);
+        parse_str($parsedUrl['query'], $parameters);
+
+        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
+        // parse the returnUrl and replace the module token of it
+        if (isset($parameters['returnUrl'])) {
+            $parsedReturnUrl = parse_url($parameters['returnUrl']);
+            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 (isset($parameters['M']) && empty($parameters['route'])) {
+            $parameters['route'] = $parameters['M'];
+            unset($parameters['M']);
+        }
+
+        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);
+
+                if ($route) {
+                    $routeIdentifier = $route->getOption('_identifier');
+                    unset($parameters['route']);
+                    $url = (string)$uriBuilder->buildUriFromRoute($routeIdentifier, $parameters);
+                }
+            } catch (ResourceNotFoundException $e) {
+                $url = '';
+            }
+        }
+        return $url;
+    }
+
+    /**
+     * Returns the current BE user.
+     *
+     * @return BackendUserAuthentication
+     */
+    protected function getBackendUser(): BackendUserAuthentication
+    {
+        return $GLOBALS['BE_USER'];
+    }
+
+    /**
+     * @return LanguageService
+     */
+    protected function getLanguageService(): LanguageService
+    {
+        return $GLOBALS['LANG'];
+    }
+}
index f989794..87f0edb 100644 (file)
@@ -14,25 +14,10 @@ namespace TYPO3\CMS\Backend\Backend\ToolbarItems;
  * The TYPO3 project - inspiring people to share!
  */
 
-use Psr\Http\Message\ResponseInterface;
-use Psr\Http\Message\ServerRequestInterface;
-use TYPO3\CMS\Backend\Module\ModuleLoader;
-use TYPO3\CMS\Backend\Routing\UriBuilder;
+use TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository;
 use TYPO3\CMS\Backend\Toolbar\ToolbarItemInterface;
-use TYPO3\CMS\Backend\Utility\BackendUtility;
-use TYPO3\CMS\Core\Database\Connection;
-use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\QueryHelper;
-use TYPO3\CMS\Core\Http\HtmlResponse;
-use TYPO3\CMS\Core\Http\JsonResponse;
-use TYPO3\CMS\Core\Imaging\Icon;
-use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Page\PageRenderer;
-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;
 use TYPO3\CMS\Fluid\View\StandaloneView;
 
 /**
@@ -41,80 +26,27 @@ use TYPO3\CMS\Fluid\View\StandaloneView;
 class ShortcutToolbarItem implements ToolbarItemInterface
 {
     /**
-     * @var int Number of super global group
+     * @var ShortcutRepository
      */
-    const SUPERGLOBAL_GROUP = -100;
-
-    /**
-     * @var string
-     */
-    public $perms_clause;
-
-    /**
-     * @var array
-     */
-    public $fieldArray;
-
-    /**
-     * All available shortcuts
-     *
-     * @var array
-     */
-    protected $shortcuts;
-
-    /**
-     * @var array
-     */
-    protected $shortcutGroups;
-
-    /**
-     * Labels of all groups.
-     * If value is 1, the system will try to find a label in the locallang array.
-     *
-     * @var array
-     */
-    protected $groupLabels;
-
-    /**
-     * @var IconFactory
-     */
-    protected $iconFactory;
-
-    /**
-     * @var ModuleLoader
-     */
-    protected $moduleLoader;
+    protected $shortcutRepository;
 
     /**
      * Constructor
      */
     public function __construct()
     {
-        $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
-        $this->getLanguageService()->includeLLFile('EXT:core/Resources/Private/Language/locallang_misc.xlf');
-        // Needed to get the correct icons when reloading the menu after saving it
-        $this->moduleLoader = GeneralUtility::makeInstance(ModuleLoader::class);
-        $this->moduleLoader->load($GLOBALS['TBE_MODULES']);
-
-        // By default, 5 groups are set
-        $this->shortcutGroups = [
-            1 => '1',
-            2 => '1',
-            3 => '1',
-            4 => '1',
-            5 => '1'
-        ];
-        $this->shortcutGroups = $this->initShortcutGroups();
-        $this->shortcuts = $this->initShortcuts();
+        $this->shortcutRepository = GeneralUtility::makeInstance(ShortcutRepository::class);
 
-        $this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/Toolbar/ShortcutMenu');
+        $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
+        $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Toolbar/ShortcutMenu');
         $languageService = $this->getLanguageService();
-        $this->getPageRenderer()->addInlineLanguageLabelArray([
-            'bookmark.delete' => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:toolbarItems.bookmarksDelete'),
-            'bookmark.confirmDelete' => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:toolbarItems.confirmBookmarksDelete'),
-            'bookmark.create' => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:toolbarItems.createBookmark'),
-            'bookmark.savedTitle' => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:toolbarItems.bookmarkSavedTitle'),
-            'bookmark.savedMessage' => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:toolbarItems.bookmarkSavedMessage'),
+        $languageFile = 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf';
+        $pageRenderer->addInlineLanguageLabelArray([
+            'bookmark.delete' => $languageService->sL($languageFile . ':toolbarItems.bookmarksDelete'),
+            'bookmark.confirmDelete' => $languageService->sL($languageFile . ':toolbarItems.confirmBookmarksDelete'),
+            'bookmark.create' => $languageService->sL($languageFile . ':toolbarItems.createBookmark'),
+            'bookmark.savedTitle' => $languageService->sL($languageFile . ':toolbarItems.bookmarkSavedTitle'),
+            'bookmark.savedMessage' => $languageService->sL($languageFile . ':toolbarItems.bookmarkSavedMessage'),
         ]);
     }
 
@@ -141,6 +73,16 @@ class ShortcutToolbarItem implements ToolbarItemInterface
     }
 
     /**
+     * This item has a drop down
+     *
+     * @return bool
+     */
+    public function hasDropDown()
+    {
+        return true;
+    }
+
+    /**
      * Render drop down content
      *
      * @return string HTML
@@ -148,29 +90,21 @@ class ShortcutToolbarItem implements ToolbarItemInterface
     public function getDropDown()
     {
         $shortcutMenu = [];
-        $groups = $this->getGroupsFromShortcuts();
+        $groups = $this->shortcutRepository->getGroupsFromShortcuts();
         arsort($groups, SORT_NUMERIC);
+
         foreach ($groups as $groupId => $groupLabel) {
             $shortcutMenu[] = [
                 'id' => (int)$groupId,
                 'title' => $groupLabel,
-                'shortcuts' => $this->getShortcutsByGroup($groupId)
+                'shortcuts' => $this->shortcutRepository->getShortcutsByGroup($groupId),
             ];
         }
 
         $dropDownView = $this->getFluidTemplateObject('DropDown.html');
         $dropDownView->assign('shortcutMenu', $shortcutMenu);
-        return $dropDownView->render();
-    }
 
-    /**
-     * Renders the menu so that it can be returned as response to an AJAX call
-     *
-     * @return ResponseInterface
-     */
-    public function menuAction(): ResponseInterface
-    {
-        return new HtmlResponse($this->getDropDown());
+        return $dropDownView->render();
     }
 
     /**
@@ -184,710 +118,34 @@ class ShortcutToolbarItem implements ToolbarItemInterface
     }
 
     /**
-     * This item has a drop down
-     *
-     * @return bool
-     */
-    public function hasDropDown()
-    {
-        return true;
-    }
-
-    /**
-     * Retrieves the shortcuts for the current user
-     *
-     * @return array Array of shortcuts
-     */
-    protected function initShortcuts()
-    {
-        $backendUser = $this->getBackendUser();
-        // Traverse shortcuts
-        $lastGroup = 0;
-        $shortcuts = [];
-        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
-            ->getQueryBuilderForTable('sys_be_shortcuts');
-        $result = $queryBuilder->select('*')
-            ->from('sys_be_shortcuts')
-            ->where(
-                $queryBuilder->expr()->andX(
-                    $queryBuilder->expr()->eq(
-                        'userid',
-                        $queryBuilder->createNamedParameter($backendUser->user['uid'], \PDO::PARAM_INT)
-                    ),
-                    $queryBuilder->expr()->gte(
-                        'sc_group',
-                        $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
-                    )
-                )
-            )
-            ->orWhere(
-                $queryBuilder->expr()->in(
-                    'sc_group',
-                    $queryBuilder->createNamedParameter(
-                        array_keys($this->getGlobalShortcutGroups()),
-                        Connection::PARAM_INT_ARRAY
-                    )
-                )
-            )
-            ->orderBy('sc_group')
-            ->addOrderBy('sorting')
-            ->execute();
-
-        while ($row = $result->fetch()) {
-            $shortcut = ['raw' => $row];
-
-            list($row['module_name'], $row['M_module_name']) = explode('|', $row['module_name']);
-
-            $queryParts = parse_url($row['url']);
-            $queryParameters = GeneralUtility::explodeUrl2Array($queryParts['query'], 1);
-            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') {
-                    $shortcut['type'] = 'edit';
-                } elseif ($queryParameters['edit'][$shortcut['table']][$shortcut['recordid']] === 'new') {
-                    $shortcut['type'] = 'new';
-                }
-                if (substr($shortcut['recordid'], -1) === ',') {
-                    $shortcut['recordid'] = substr($shortcut['recordid'], 0, -1);
-                }
-            } else {
-                $shortcut['type'] = 'other';
-            }
-            // Check for module access
-            $moduleName = $row['M_module_name'] ?: $row['module_name'];
-
-            // Check if the user has access to this module
-            if (!is_array($this->moduleLoader->checkMod($moduleName))) {
-                continue;
-            }
-            $pageId = $this->getLinkedPageId($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, $perms = 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);
-            }
-            $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 . ');';
-
-            $shortcuts[] = $shortcut;
-        }
-        return $shortcuts;
-    }
-
-    /**
-     * Adds the correct token, if the url is an index.php script
-     * @todo: this needs love
-     *
-     * @param string $url
-     * @return string
-     */
-    protected function getTokenUrl($url)
-    {
-        $parsedUrl = parse_url($url);
-        parse_str($parsedUrl['query'], $parameters);
-
-        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
-        // parse the returnUrl and replace the module token of it
-        if (isset($parameters['returnUrl'])) {
-            $parsedReturnUrl = parse_url($parameters['returnUrl']);
-            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 (isset($parameters['M']) && empty($parameters['route'])) {
-            $parameters['route'] = $parameters['M'];
-            unset($parameters['M']);
-        }
-
-        if (strpos($parsedUrl['path'], 'index.php') !== false && isset($parameters['route'])) {
-            $routePath = $parameters['route'];
-            /** @var \TYPO3\CMS\Backend\Routing\Router $router */
-            $router = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\Router::class);
-            try {
-                $route = $router->match($routePath);
-                if ($route) {
-                    $routeIdentifier = $route->getOption('_identifier');
-                    unset($parameters['route']);
-                    $url = (string)$uriBuilder->buildUriFromRoute($routeIdentifier, $parameters);
-                }
-            } catch (\TYPO3\CMS\Backend\Routing\Exception\ResourceNotFoundException $e) {
-                $url = '';
-            }
-        }
-        return $url;
-    }
-
-    /**
-     * Gets shortcuts for a specific group
-     *
-     * @param int $groupId Group Id
-     * @return array Array of shortcuts that matched the group
-     */
-    protected function getShortcutsByGroup($groupId)
-    {
-        $shortcuts = [];
-        foreach ($this->shortcuts as $shortcut) {
-            if ($shortcut['group'] == $groupId) {
-                $shortcuts[] = $shortcut;
-            }
-        }
-        return $shortcuts;
-    }
-
-    /**
-     * Gets a shortcut by its uid
-     *
-     * @param int $shortcutId Shortcut id to get the complete shortcut for
-     * @return mixed An array containing the shortcut's data on success or FALSE on failure
-     */
-    protected function getShortcutById($shortcutId)
-    {
-        $returnShortcut = false;
-        foreach ($this->shortcuts as $shortcut) {
-            if ($shortcut['raw']['uid'] == (int)$shortcutId) {
-                $returnShortcut = $shortcut;
-                continue;
-            }
-        }
-        return $returnShortcut;
-    }
-
-    /**
-     * Gets the available shortcut groups from default groups, user TSConfig, and global groups
-     *
-     * @return array
-     */
-    protected function initShortcutGroups()
-    {
-        $languageService = $this->getLanguageService();
-        $backendUser = $this->getBackendUser();
-        // Groups from TSConfig
-        $bookmarkGroups = $backendUser->getTSConfig()['options.']['bookmarkGroups.'] ?? [];
-        if (is_array($bookmarkGroups) && !empty($bookmarkGroups)) {
-            foreach ($bookmarkGroups as $groupId => $label) {
-                if (!empty($label)) {
-                    $this->shortcutGroups[$groupId] = (string)$label;
-                } elseif ($backendUser->isAdmin()) {
-                    unset($this->shortcutGroups[$groupId]);
-                }
-            }
-        }
-        // Generate global groups, all global groups have negative IDs.
-        if (!empty($this->shortcutGroups)) {
-            $groups = $this->shortcutGroups;
-            foreach ($groups as $groupId => $groupLabel) {
-                $this->shortcutGroups[$groupId * -1] = $groupLabel;
-            }
-        }
-        // Group -100 is kind of superglobal and can't be changed.
-        $this->shortcutGroups[self::SUPERGLOBAL_GROUP] = 1;
-        // Add labels
-        foreach ($this->shortcutGroups as $groupId => $groupLabel) {
-            $groupId = (int)$groupId;
-            $label = $groupLabel;
-            if ($groupLabel == '1') {
-                $label = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:bookmark_group_' . abs($groupId)));
-                if (empty($label)) {
-                    // Fallback label
-                    $label = htmlspecialchars($languageService->getLL('bookmark_group')) . ' ' . abs($groupId);
-                }
-            }
-            if ($groupId < 0) {
-                // Global group
-                $label = htmlspecialchars($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf:bookmark_global')) . ': ' . (!empty($label) ? $label : abs($groupId));
-                if ($groupId === self::SUPERGLOBAL_GROUP) {
-                    $label = htmlspecialchars($languageService->getLL('bookmark_global')) . ': ' . htmlspecialchars($languageService->getLL('bookmark_all'));
-                }
-            }
-            $this->shortcutGroups[$groupId] = $label;
-        }
-
-        return $this->shortcutGroups;
-    }
-
-    /**
-     * Fetches the available shortcut groups, renders a form so it can be saved later on, called via AJAX
-     *
-     * @param ServerRequestInterface $request
-     * @return ResponseInterface the full HTML for the form
-     */
-    public function editFormAction(ServerRequestInterface $request): ResponseInterface
-    {
-        $parsedBody = $request->getParsedBody();
-        $queryParams = $request->getQueryParams();
-
-        $selectedShortcutId = (int)($parsedBody['shortcutId'] ?? $queryParams['shortcutId']);
-        $selectedShortcutGroupId = (int)($parsedBody['shortcutGroup'] ?? $queryParams['shortcutGroup']);
-        $selectedShortcut = $this->getShortcutById($selectedShortcutId);
-        $shortcutGroups = $this->shortcutGroups;
-        if (!$this->getBackendUser()->isAdmin()) {
-            foreach ($shortcutGroups as $groupId => $groupName) {
-                if ((int)$groupId < 0) {
-                    unset($shortcutGroups[$groupId]);
-                }
-            }
-        }
-
-        $editFormView = $this->getFluidTemplateObject('EditForm.html');
-        $editFormView->assign('selectedShortcutId', $selectedShortcutId);
-        $editFormView->assign('selectedShortcutGroupId', $selectedShortcutGroupId);
-        $editFormView->assign('selectedShortcut', $selectedShortcut);
-        $editFormView->assign('shortcutGroups', $shortcutGroups);
-
-        return new HtmlResponse($editFormView->render());
-    }
-
-    /**
-     * Deletes a shortcut through an AJAX call
-     *
-     * @param ServerRequestInterface $request
-     * @return ResponseInterface
-     */
-    public function removeShortcutAction(ServerRequestInterface $request): ResponseInterface
-    {
-        $parsedBody = $request->getParsedBody();
-        $queryParams = $request->getQueryParams();
-
-        $shortcutId = (int)($parsedBody['shortcutId'] ?? $queryParams['shortcutId']);
-        $fullShortcut = $this->getShortcutById($shortcutId);
-        $success = false;
-        if ($fullShortcut['raw']['userid'] == $this->getBackendUser()->user['uid']) {
-            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
-                ->getQueryBuilderForTable('sys_be_shortcuts');
-            $affectedRows = $queryBuilder->delete('sys_be_shortcuts')
-                ->where(
-                    $queryBuilder->expr()->eq(
-                        'uid',
-                        $queryBuilder->createNamedParameter($shortcutId, \PDO::PARAM_INT)
-                    )
-                )
-                ->execute();
-            if ($affectedRows === 1) {
-                $success = true;
-            }
-        }
-        return new JsonResponse(['success' => $success]);
-    }
-
-    /**
-     * Creates a shortcut through an AJAX call
-     *
-     * @param ServerRequestInterface $request
-     * @return ResponseInterface
-     * @throws \RuntimeException
-     */
-    public function createShortcutAction(ServerRequestInterface $request): ResponseInterface
-    {
-        $languageService = $this->getLanguageService();
-        $parsedBody = $request->getParsedBody();
-        $queryParams = $request->getQueryParams();
-
-        // Default name
-        $shortcutName = 'Shortcut';
-        $shortcutNamePrepend = '';
-        $url = $parsedBody['url'] ?? $queryParams['url'];
-
-        // Use given display name
-        if (!empty($parsedBody['displayName'])) {
-            $shortcutName = $parsedBody['displayName'];
-        }
-
-        // Determine shortcut type
-        $url = rawurldecode($url);
-        $queryParts = parse_url($url);
-        $queryParameters = GeneralUtility::explodeUrl2Array($queryParts['query'], true);
-
-        // Proceed only if no scheme is defined, as URL is expected to be relative
-        if (!empty($queryParameters['scheme'])) {
-            throw new \RuntimeException('relative url expected', 1518785877);
-        }
-
-        if (is_array($queryParameters['edit'])) {
-            $shortcut['table'] = key($queryParameters['edit']);
-            $shortcut['recordid'] = key($queryParameters['edit'][$shortcut['table']]);
-            $shortcut['pid'] = BackendUtility::getRecord($shortcut['table'], $shortcut['recordid'])['pid'];
-            if ($queryParameters['edit'][$shortcut['table']][$shortcut['recordid']] === 'edit') {
-                $shortcut['type'] = 'edit';
-                $shortcutNamePrepend = htmlspecialchars($languageService->getLL('shortcut_edit'));
-            } elseif ($queryParameters['edit'][$shortcut['table']][$shortcut['recordid']] === 'new') {
-                $shortcut['type'] = 'new';
-                $shortcutNamePrepend = htmlspecialchars($languageService->getLL('shortcut_create'));
-            }
-        } else {
-            $shortcut['type'] = 'other';
-            $shortcut['table'] = '';
-            $shortcut['recordid'] = 0;
-        }
-
-        // Check if given id is a combined identifier
-        if (!empty($queryParameters['id']) && preg_match('/^[0-9]+:/', $queryParameters['id'])) {
-            try {
-                $resourceFactory = ResourceFactory::getInstance();
-                $resource = $resourceFactory->getObjectFromCombinedIdentifier($queryParameters['id']);
-                $shortcutName = trim($shortcutNamePrepend . ' ' . $resource->getName());
-            } catch (ResourceDoesNotExistException $e) {
-            }
-        } else {
-            // Lookup the title of this page and use it as default description
-            $pageId = (int)($shortcut['pid'] ?: ($shortcut['recordid'] ?: $this->getLinkedPageId($url)));
-            $page = false;
-            if ($pageId) {
-                $page = BackendUtility::getRecord('pages', $pageId);
-            }
-            if (!empty($page)) {
-                // Set the name to the title of the page
-                if ($shortcut['type'] === 'other') {
-                    if (empty($shortcutName)) {
-                        $shortcutName = $page['title'];
-                    } else {
-                        $shortcutName .= ' (' . $page['title'] . ')';
-                    }
-                } else {
-                    $shortcutName = $shortcutNamePrepend . ' ' .
-                        $languageService->sL($GLOBALS['TCA'][$shortcut['table']]['ctrl']['title']) .
-                        ' (' . $page['title'] . ')';
-                }
-            } elseif ($shortcut['table'] !== '' && $shortcut['type'] !== 'other') {
-                $shortcutName = $shortcutNamePrepend . ' ' .
-                    $languageService->sL($GLOBALS['TCA'][$shortcut['table']]['ctrl']['title']);
-            }
-        }
-
-        $shortcutCreated = $this->tryAddingTheShortcut($parsedBody['module'], $url, $shortcutName);
-        return new HtmlResponse($shortcutCreated);
-    }
-
-    /**
-     * Try to adding a shortcut
-     *
-     * @param string $module
-     * @param string $url
-     * @param string $shortcutName
-     * @return string
-     */
-    protected function tryAddingTheShortcut(string $module, $url, $shortcutName): string
-    {
-        $shortcutCreated = 'failed';
-        if (!empty($module) && !empty($url)) {
-            $shortcutCreated = 'alreadyExists';
-
-            if (!BackendUtility::shortcutExists($url) && !is_array($module)) {
-                $shortcutCreated = $this->addShortcut($url, $shortcutName, $module);
-            }
-        }
-        return $shortcutCreated;
-    }
-
-    /**
-     * Add a shortcut now with some user stuffs
-     *
-     * @param string $url
-     * @param string $shortcutName
-     * @param string $module
-     *
-     * @return string
-     */
-    protected function addShortcut($url, $shortcutName, $module)
-    {
-        $moduleLabels = $this->moduleLoader->getLabelsForModule($module);
-        if ($shortcutName === 'Shortcut' && !empty($moduleLabels['shortdescription'])) {
-            $shortcutName = $this->getLanguageService()->sL($moduleLabels['shortdescription']);
-        }
-
-        $motherModule = GeneralUtility::_POST('motherModName');
-        $fieldValues = [
-            'userid' => $this->getBackendUser()->user['uid'],
-            'module_name' => $module . '|' . $motherModule,
-            'url' => $url,
-            'description' => $shortcutName,
-            'sorting' => $GLOBALS['EXEC_TIME']
-        ];
-
-        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
-            ->getQueryBuilderForTable('sys_be_shortcuts');
-        $affectedRows = $queryBuilder
-            ->insert('sys_be_shortcuts')
-            ->values($fieldValues)
-            ->execute();
-
-        if ($affectedRows === 1) {
-            return 'success';
-        }
-        return 'failed';
-    }
-
-    /**
-     * Exists already a shortcut entry for this TYPO3 url?
-     *
-     * @param string $url
-     *
-     * @return bool
-     */
-    protected function shortcutExists($url)
-    {
-        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
-            ->getQueryBuilderForTable('sys_be_shortcuts');
-        $uid = $queryBuilder->select('uid')
-            ->from('sys_be_shortcuts')
-            ->where(
-                $queryBuilder->expr()->eq(
-                    'userid',
-                    $queryBuilder->createNamedParameter($this->getBackendUser()->user['uid'], \PDO::PARAM_INT)
-                ),
-                $queryBuilder->expr()->eq('url', $queryBuilder->createNamedParameter($url, \PDO::PARAM_STR))
-            )
-            ->execute()
-            ->fetchColumn();
-
-        return (bool)$uid;
-    }
-
-    /**
-     * Gets called when a shortcut is changed, checks whether the user has
-     * permissions to do so and saves the changes if everything is ok
-     *
-     * @param ServerRequestInterface $request
-     * @return ResponseInterface
-     */
-    public function saveFormAction(ServerRequestInterface $request): ResponseInterface
-    {
-        $parsedBody = $request->getParsedBody();
-        $queryParams = $request->getQueryParams();
-
-        $backendUser = $this->getBackendUser();
-        $shortcutId = (int)($parsedBody['shortcutId'] ?? $queryParams['shortcutId']);
-        $shortcutName = strip_tags($parsedBody['shortcutTitle'] ?? $queryParams['shortcutTitle']);
-        $shortcutGroupId = (int)($parsedBody['shortcutGroup'] ?? $queryParams['shortcutGroup']);
-
-        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
-            ->getQueryBuilderForTable('sys_be_shortcuts');
-        $queryBuilder->update('sys_be_shortcuts')
-            ->where(
-                $queryBuilder->expr()->eq(
-                    'uid',
-                    $queryBuilder->createNamedParameter($shortcutId, \PDO::PARAM_INT)
-                )
-            )
-            ->set('description', $shortcutName)
-            ->set('sc_group', $shortcutGroupId);
-
-        if (!$backendUser->isAdmin()) {
-            // Users can only modify their own shortcuts
-            $queryBuilder->andWhere(
-                $queryBuilder->expr()->eq(
-                    'userid',
-                    $queryBuilder->createNamedParameter($backendUser->user['uid'], \PDO::PARAM_INT)
-                )
-            );
-
-            if ($shortcutGroupId < 0) {
-                $queryBuilder->set('sc_group', 0);
-            }
-        }
-
-        return new HtmlResponse($queryBuilder->execute() === 1 ? $shortcutName : 'failed');
-    }
-
-    /**
-     * Gets the label for a shortcut group
-     *
-     * @param int $groupId A shortcut group id
-     * @return string The shortcut group label, can be an empty string if no group was found for the id
-     */
-    protected function getShortcutGroupLabel($groupId)
-    {
-        return $this->shortcutGroups[$groupId] ?? '';
-    }
-
-    /**
-     * Gets a list of global groups, shortcuts in these groups are available to all users
-     *
-     * @return array Array of global groups
-     */
-    protected function getGlobalShortcutGroups()
-    {
-        $globalGroups = [];
-        foreach ($this->shortcutGroups as $groupId => $groupLabel) {
-            if ($groupId < 0) {
-                $globalGroups[$groupId] = $groupLabel;
-            }
-        }
-        return $globalGroups;
-    }
-
-    /**
-     * runs through the available shortcuts an collects their groups
+     * Position relative to others, live search should be very right
      *
-     * @return array Array of groups which have shortcuts
+     * @return int
      */
-    protected function getGroupsFromShortcuts()
+    public function getIndex()
     {
-        $groups = [];
-        foreach ($this->shortcuts as $shortcut) {
-            $groups[$shortcut['group']] = $this->shortcutGroups[$shortcut['group']];
-        }
-        return array_unique($groups);
+        return 20;
     }
 
     /**
-     * Gets the icon for the shortcut
+     * returns a new standalone view, shorthand function
      *
-     * @param array $row
-     * @param array $shortcut
-     * @return string Shortcut icon as img tag
+     * @param string $templateFilename
+     * @return StandaloneView
+     * @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidExtensionNameException
      * @throws \InvalidArgumentException
+     * @internal param string $templateFile
      */
-    protected function getShortcutIcon($row, $shortcut)
-    {
-        switch ($row['module_name']) {
-            case 'xMOD_alt_doc.php':
-                $table = $shortcut['table'];
-                $recordid = $shortcut['recordid'];
-                $icon = '';
-                if ($shortcut['type'] === 'edit') {
-                    // Creating the list of fields to include in the SQL query:
-                    $selectFields = $this->fieldArray;
-                    $selectFields[] = 'uid';
-                    $selectFields[] = 'pid';
-                    if ($table === 'pages') {
-                        $selectFields[] = 'module';
-                        $selectFields[] = 'extendToSubpages';
-                        $selectFields[] = 'doktype';
-                    }
-                    if (is_array($GLOBALS['TCA'][$table]['ctrl']['enablecolumns'])) {
-                        $selectFields = array_merge($selectFields, $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']);
-                    }
-                    if ($GLOBALS['TCA'][$table]['ctrl']['typeicon_column']) {
-                        $selectFields[] = $GLOBALS['TCA'][$table]['ctrl']['typeicon_column'];
-                    }
-                    if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
-                        $selectFields[] = 't3ver_state';
-                    }
-
-                    $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
-                        ->getQueryBuilderForTable($table);
-                    $queryBuilder->select(...array_unique(array_values($selectFields)))
-                        ->from($table)
-                        ->where(
-                            $queryBuilder->expr()->in(
-                                'uid',
-                                $queryBuilder->createNamedParameter($recordid, \PDO::PARAM_INT)
-                            )
-                        );
-
-                    if ($table === 'pages' && $this->perms_clause) {
-                        $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($this->perms_clause));
-                    }
-
-                    $row = $queryBuilder->execute()->fetch();
-
-                    $icon = $this->iconFactory->getIconForRecord($table, (array)$row, Icon::SIZE_SMALL)->render();
-                } elseif ($shortcut['type'] === 'new') {
-                    $icon = $this->iconFactory->getIconForRecord($table, [], Icon::SIZE_SMALL)->render();
-                }
-                break;
-            case 'file_edit':
-                $icon = $this->iconFactory->getIcon('mimetypes-text-html', Icon::SIZE_SMALL)->render();
-                break;
-            case 'wizard_rte':
-                $icon = $this->iconFactory->getIcon('mimetypes-word', Icon::SIZE_SMALL)->render();
-                break;
-            default:
-                $iconIdentifier = '';
-                $moduleName = $row['module_name'];
-                if (strpos($moduleName, '_') !== false) {
-                    list($mainModule, $subModule) = explode('_', $moduleName, 2);
-                    $iconIdentifier = $this->moduleLoader->modules[$mainModule]['sub'][$subModule]['iconIdentifier'];
-                } elseif (!empty($moduleName)) {
-                    $iconIdentifier = $this->moduleLoader->modules[$moduleName]['iconIdentifier'];
-                }
-                if (!$iconIdentifier) {
-                    $iconIdentifier = 'empty-empty';
-                }
-                $icon = $this->iconFactory->getIcon($iconIdentifier, Icon::SIZE_SMALL)->render();
-        }
-
-        return $icon;
-    }
-
-    /**
-     * Returns title for the shortcut icon
-     *
-     * @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
-     */
-    protected function getShortcutIconTitle($shortcutLabel, $moduleName, $parentModuleName = '')
-    {
-        $languageService = $this->getLanguageService();
-        if (substr($moduleName, 0, 5) === 'xMOD_') {
-            $title = substr($moduleName, 5);
-        } else {
-            list($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 . ')';
-        }
-        $title .= ': ' . $shortcutLabel;
-        return $title;
-    }
-
-    /**
-     * Return the ID of the page in the URL if found.
-     *
-     * @param string $url The URL of the current shortcut link
-     * @return string If a page ID was found, it is returned. Otherwise: 0
-     */
-    protected function getLinkedPageId($url)
+    protected function getFluidTemplateObject(string $templateFilename): StandaloneView
     {
-        return preg_replace('/.*[\\?&]id=([^&]+).*/', '$1', $url);
-    }
+        $view = GeneralUtility::makeInstance(StandaloneView::class);
+        $view->setLayoutRootPaths(['EXT:backend/Resources/Private/Layouts']);
+        $view->setPartialRootPaths(['EXT:backend/Resources/Private/Partials']);
+        $view->setTemplateRootPaths(['EXT:backend/Resources/Private/Templates/ShortcutToolbarItem']);
+        $view->setTemplate($templateFilename);
+        $view->getRequest()->setControllerExtensionName('Backend');
 
-    /**
-     * Position relative to others, live search should be very right
-     *
-     * @return int
-     */
-    public function getIndex()
-    {
-        return 20;
+        return $view;
     }
 
     /**
@@ -901,44 +159,10 @@ class ShortcutToolbarItem implements ToolbarItemInterface
     }
 
     /**
-     * Returns current PageRenderer
-     *
-     * @return PageRenderer
-     */
-    protected function getPageRenderer()
-    {
-        return GeneralUtility::makeInstance(PageRenderer::class);
-    }
-
-    /**
-     * Returns LanguageService
-     *
      * @return \TYPO3\CMS\Core\Localization\LanguageService
      */
     protected function getLanguageService()
     {
         return $GLOBALS['LANG'];
     }
-
-    /**
-     * returns a new standalone view, shorthand function
-     *
-     * @param string $templateFilename
-     * @return StandaloneView
-     * @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidExtensionNameException
-     * @throws \InvalidArgumentException
-     * @internal param string $templateFile
-     */
-    protected function getFluidTemplateObject(string $templateFilename): StandaloneView
-    {
-        $view = GeneralUtility::makeInstance(StandaloneView::class);
-        $view->setLayoutRootPaths(['EXT:backend/Resources/Private/Layouts']);
-        $view->setPartialRootPaths(['EXT:backend/Resources/Private/Partials']);
-        $view->setTemplateRootPaths(['EXT:backend/Resources/Private/Templates/ShortcutToolbarItem']);
-
-        $view->setTemplate($templateFilename);
-
-        $view->getRequest()->setControllerExtensionName('Backend');
-        return $view;
-    }
 }
diff --git a/typo3/sysext/backend/Classes/Controller/ShortcutController.php b/typo3/sysext/backend/Classes/Controller/ShortcutController.php
new file mode 100644 (file)
index 0000000..93f4c5e
--- /dev/null
@@ -0,0 +1,198 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Backend\Controller;
+
+/*
+ * 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 Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository;
+use TYPO3\CMS\Backend\Backend\ToolbarItems\ShortcutToolbarItem;
+use TYPO3\CMS\Backend\Module\ModuleLoader;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Http\HtmlResponse;
+use TYPO3\CMS\Core\Http\JsonResponse;
+use TYPO3\CMS\Core\Localization\LanguageService;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Fluid\View\StandaloneView;
+
+/**
+ * Controller for shortcut processing
+ */
+class ShortcutController
+{
+    /**
+     * @var ShortcutToolbarItem
+     */
+    protected $shortcutToolbarItem;
+
+    /**
+     * @var ShortcutRepository
+     */
+    protected $shortcutRepository;
+
+    /**
+     * @var ModuleLoader
+     */
+    protected $moduleLoader;
+
+    /**
+     * Set up dependencies
+     */
+    public function __construct()
+    {
+        $this->shortcutToolbarItem = GeneralUtility::makeInstance(ShortcutToolbarItem::class);
+        $this->shortcutRepository = GeneralUtility::makeInstance(ShortcutRepository::class);
+        // Needed to get the correct icons when reloading the menu after saving it
+        $this->moduleLoader = GeneralUtility::makeInstance(ModuleLoader::class);
+        $this->moduleLoader->load($GLOBALS['TBE_MODULES']);
+    }
+
+    /**
+     * Renders the menu so that it can be returned as response to an AJAX call
+     *
+     * @return ResponseInterface
+     */
+    public function menuAction(): ResponseInterface
+    {
+        return new HtmlResponse($this->shortcutToolbarItem->getDropDown());
+    }
+
+    /**
+     * Creates a shortcut through an AJAX call
+     *
+     * @param ServerRequestInterface $request
+     * @return ResponseInterface
+     */
+    public function addAction(ServerRequestInterface $request): ResponseInterface
+    {
+        $result = 'success';
+        $parsedBody = $request->getParsedBody();
+        $queryParams = $request->getQueryParams();
+        $url = rawurldecode($parsedBody['url'] ?? $queryParams['url'] ?? '');
+
+        if ($this->shortcutRepository->shortcutExists($url)) {
+            $result = 'alreadyExists';
+        } else {
+            $moduleName = $parsedBody['module'] ?? '';
+            $parentModuleName = $parsedBody['motherModName'] ?? '';
+            $shortcutName = $parsedBody['displayName'] ?? '';
+            $success = $this->shortcutRepository->addShortcut($url, $moduleName, $parentModuleName, $shortcutName);
+
+            if (!$success) {
+                $result = 'failed';
+            }
+        }
+
+        return new HtmlResponse($result);
+    }
+
+    /**
+     * Fetches the available shortcut groups, renders a form so it can be saved later on, called via AJAX
+     *
+     * @param ServerRequestInterface $request
+     * @return ResponseInterface the full HTML for the form
+     */
+    public function showEditFormAction(ServerRequestInterface $request): ResponseInterface
+    {
+        $parsedBody = $request->getParsedBody();
+        $queryParams = $request->getQueryParams();
+
+        $selectedShortcutId = (int)($parsedBody['shortcutId'] ?? $queryParams['shortcutId']);
+        $selectedShortcutGroupId = (int)($parsedBody['shortcutGroup'] ?? $queryParams['shortcutGroup']);
+        $selectedShortcut = $this->shortcutRepository->getShortcutById($selectedShortcutId);
+        $shortcutGroups = $this->shortcutRepository->getShortcutGroups();
+
+        $editFormView = $this->getFluidTemplateObject('EditForm.html');
+        $editFormView->assign('selectedShortcutId', $selectedShortcutId);
+        $editFormView->assign('selectedShortcutGroupId', $selectedShortcutGroupId);
+        $editFormView->assign('selectedShortcut', $selectedShortcut);
+        $editFormView->assign('shortcutGroups', $shortcutGroups);
+
+        return new HtmlResponse($editFormView->render());
+    }
+
+    /**
+     * Gets called when a shortcut is changed, checks whether the user has
+     * permissions to do so and saves the changes if everything is ok
+     *
+     * @param ServerRequestInterface $request
+     * @return ResponseInterface
+     */
+    public function updateAction(ServerRequestInterface $request): ResponseInterface
+    {
+        $parsedBody = $request->getParsedBody();
+        $queryParams = $request->getQueryParams();
+        $shortcutId = (int)($parsedBody['shortcutId'] ?? $queryParams['shortcutId'] ?? 0);
+        $shortcutTitle = strip_tags($parsedBody['shortcutTitle'] ?? $queryParams['shortcutTitle'] ?? '');
+        $shortcutGroupId = (int)($parsedBody['shortcutGroup'] ?? $queryParams['shortcutGroup'] ?? 0);
+
+        $success = $this->shortcutRepository->updateShortcut($shortcutId, $shortcutTitle, $shortcutGroupId);
+
+        return new HtmlResponse($success ? $shortcutTitle : 'failed');
+    }
+
+    /**
+     * Deletes a shortcut through an AJAX call
+     *
+     * @param ServerRequestInterface $request
+     * @return ResponseInterface
+     */
+    public function removeAction(ServerRequestInterface $request): ResponseInterface
+    {
+        $parsedBody = $request->getParsedBody();
+        $queryParams = $request->getQueryParams();
+        $shortcutId = (int)($parsedBody['shortcutId'] ?? $queryParams['shortcutId'] ?? 0);
+        $success = $this->shortcutRepository->removeShortcut($shortcutId);
+
+        return new JsonResponse(['success' => $success]);
+    }
+
+    /**
+     * returns a new standalone view, shorthand function
+     *
+     * @param string $templateFilename
+     * @return StandaloneView
+     * @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidExtensionNameException
+     * @throws \InvalidArgumentException
+     * @internal param string $templateFile
+     */
+    protected function getFluidTemplateObject(string $templateFilename): StandaloneView
+    {
+        $view = GeneralUtility::makeInstance(StandaloneView::class);
+        $view->setLayoutRootPaths(['EXT:backend/Resources/Private/Layouts']);
+        $view->setPartialRootPaths(['EXT:backend/Resources/Private/Partials']);
+        $view->setTemplateRootPaths(['EXT:backend/Resources/Private/Templates/ShortcutToolbarItem']);
+        $view->setTemplate($templateFilename);
+        $view->getRequest()->setControllerExtensionName('Backend');
+
+        return $view;
+    }
+
+    /**
+     * @return BackendUserAuthentication
+     */
+    protected function getBackendUser(): BackendUserAuthentication
+    {
+        return $GLOBALS['BE_USER'];
+    }
+
+    /**
+     * @return LanguageService
+     */
+    protected function getLanguageService(): LanguageService
+    {
+        return $GLOBALS['LANG'];
+    }
+}
index 08cf56a..713a0b5 100644 (file)
@@ -16,6 +16,7 @@ namespace TYPO3\CMS\Backend\Template;
 
 use Psr\Log\LoggerAwareInterface;
 use Psr\Log\LoggerAwareTrait;
+use TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository;
 use TYPO3\CMS\Backend\Routing\UriBuilder;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
@@ -358,7 +359,8 @@ function jumpToUrl(URL) {
         $confirmationText = GeneralUtility::quoteJSvalue($GLOBALS['LANG']->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.makeBookmark'));
 
         $shortcutUrl = $pathInfo['path'] . '?' . $storeUrl;
-        $shortcutExist = BackendUtility::shortcutExists($shortcutUrl);
+        $shortcutRepository = GeneralUtility::makeInstance(ShortcutRepository::class);
+        $shortcutExist = $shortcutRepository->shortcutExists($shortcutUrl);
 
         if ($shortcutExist) {
             return '<a class="active ' . htmlspecialchars($classes) . '" title="">' .
index 1ce4424..701a946 100644 (file)
@@ -14,6 +14,7 @@ namespace TYPO3\CMS\Backend\Template;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository;
 use TYPO3\CMS\Backend\Template\Components\DocHeaderComponent;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
@@ -544,7 +545,8 @@ class ModuleTemplate
         );
 
         $shortcutUrl = $pathInfo['path'] . '?' . $storeUrl;
-        $shortcutExist = BackendUtility::shortcutExists($shortcutUrl);
+        $shortcutRepository = GeneralUtility::makeInstance(ShortcutRepository::class);
+        $shortcutExist = $shortcutRepository->shortcutExists($shortcutUrl);
 
         if ($shortcutExist) {
             return '<a class="active ' . htmlspecialchars($classes) . '" title="">' .
index 36637f5..c953e31 100644 (file)
@@ -15,6 +15,7 @@ namespace TYPO3\CMS\Backend\Utility;
  */
 
 use Psr\Log\LoggerInterface;
+use TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository;
 use TYPO3\CMS\Backend\Routing\PageUriBuilder;
 use TYPO3\CMS\Backend\Routing\UriBuilder;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
@@ -4463,31 +4464,19 @@ class BackendUtility
      * Exists already a shortcut entry for this TYPO3 url?
      *
      * @param string $url
+     * @deprecated since TYPO3 v9, will be removed with TYPO3 v10.
      *
      * @return bool
      */
     public static function shortcutExists($url)
     {
-        $queryBuilder = static::getQueryBuilderForTable('sys_be_shortcuts');
-        $queryBuilder->getRestrictions()->removeAll();
-
-        $count = $queryBuilder
-            ->count('uid')
-            ->from('sys_be_shortcuts')
-            ->where(
-                $queryBuilder->expr()->eq(
-                    'userid',
-                    $queryBuilder->createNamedParameter(
-                        self::getBackendUserAuthentication()->user['uid'],
-                        \PDO::PARAM_INT
-                    )
-                ),
-                $queryBuilder->expr()->eq('url', $queryBuilder->createNamedParameter($url, \PDO::PARAM_STR))
-            )
-            ->execute()
-            ->fetchColumn(0);
+        trigger_error(
+            'Method BackendUtility::shortcutExists() has been marked as deprecated and will be removed in TYPO3 v10.0. Use an instance of ShortcutRepository instead.',
+            E_USER_DEPRECATED
+        );
 
-        return (bool)$count;
+        $shortcutRepository = GeneralUtility::makeInstance(ShortcutRepository::class);
+        return $shortcutRepository->shortcutExists($url);
     }
 
     /**
index 051cf19..d55fc23 100644 (file)
@@ -104,31 +104,31 @@ return [
     // Get shortcut edit form
     'shortcut_editform' => [
         'path' => '/shortcut/editform',
-        'target' => \TYPO3\CMS\Backend\Backend\ToolbarItems\ShortcutToolbarItem::class . '::editFormAction'
+        'target' => Controller\ShortcutController::class . '::showEditFormAction'
     ],
 
     // Save edited shortcut
     'shortcut_saveform' => [
         'path' => '/shortcut/saveform',
-        'target' => \TYPO3\CMS\Backend\Backend\ToolbarItems\ShortcutToolbarItem::class . '::saveFormAction'
+        'target' => Controller\ShortcutController::class . '::updateAction'
     ],
 
     // Render shortcut toolbar item
     'shortcut_list' => [
         'path' => '/shortcut/list',
-        'target' => \TYPO3\CMS\Backend\Backend\ToolbarItems\ShortcutToolbarItem::class . '::menuAction'
+        'target' => Controller\ShortcutController::class . '::menuAction'
     ],
 
     // Delete a shortcut
     'shortcut_remove' => [
         'path' => '/shortcut/remove',
-        'target' => \TYPO3\CMS\Backend\Backend\ToolbarItems\ShortcutToolbarItem::class . '::removeShortcutAction'
+        'target' => Controller\ShortcutController::class . '::removeAction'
     ],
 
     // Create a new shortcut
     'shortcut_create' => [
         'path' => '/shortcut/create',
-        'target' => \TYPO3\CMS\Backend\Backend\ToolbarItems\ShortcutToolbarItem::class . '::createShortcutAction'
+        'target' => Controller\ShortcutController::class . '::addAction'
     ],
 
     // Render systeminformtion toolbar item
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-84414-BackendUtilityshortcutExists.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-84414-BackendUtilityshortcutExists.rst
new file mode 100644 (file)
index 0000000..28814f1
--- /dev/null
@@ -0,0 +1,32 @@
+.. include:: ../../Includes.txt
+
+====================================================
+Deprecation: #84414 - BackendUtility::shortcutExists
+====================================================
+
+See :issue:`84414`
+
+Description
+===========
+
+The PHP method :php:`TYPO3\CMS\Backend\Utility\BackendUtility::shortcutExists` has been marked as deprecated and will be removed with TYPO3 v10.
+
+
+Impact
+======
+
+Installations accessing the method will trigger a PHP :php:`E_USER_DEPRECATED` error.
+
+
+Affected Installations
+======================
+
+Instances calling the method.
+
+
+Migration
+=========
+
+Use an instance of :php:`TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository` and call method :php:`shortcutExists` to get the same behavior.
+
+.. index:: Backend, PHP-API, FullyScanned, ext:backend
\ No newline at end of file
index 08b1ec3..701053c 100644 (file)
@@ -603,4 +603,11 @@ return [
             'Deprecation-85113-LegacyBackendModuleRoutingMethods.rst',
         ],
     ],
+    'TYPO3\CMS\Backend\Utility\BackendUtility::shortcutExists' => [
+        'numberOfMandatoryArguments' => 1,
+        'maximumNumberOfArguments' => 1,
+        'restFiles' => [
+            'Deprecation-84414-BackendUtilityshortcutExists.rst',
+        ],
+    ],
 ];