From a9037fb1b7b3a3b2b8de6431c9b4f966f7c515e8 Mon Sep 17 00:00:00 2001 From: Andreas Kienast <a.fernandez@scripting-base.de> Date: Fri, 19 Jan 2024 10:15:49 +0100 Subject: [PATCH] [FEATURE] Provide workspaces in LiveSearch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When EXT:workspaces is installed, backend users with granted permissions may now search for workspaces in the LiveSearch and invoke certain, related actions. Resolves: #102869 Releases: main Change-Id: I6fd9a67708cdf70ef13f6395641c30fb51a7d4a8 Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/82516 Reviewed-by: Mathias Brodala <mbrodala@pagemachine.de> Reviewed-by: Andreas Kienast <a.fernandez@scripting-base.de> Tested-by: Jasmina Ließmann <minapokhalo+typo3@gmail.com> Reviewed-by: Andreas Nedbal <andy@pixelde.su> Tested-by: Andreas Nedbal <andy@pixelde.su> Tested-by: core-ci <typo3@b13.com> Tested-by: Guido Schmechel <guido.schmechel@brandung.de> Tested-by: Andreas Kienast <a.fernandez@scripting-base.de> --- ...ture-102869-ListWorkspacesInLiveSearch.rst | 26 ++++ .../Backend/LiveSearch/WorkspaceProvider.php | 142 ++++++++++++++++++ .../Resources/Private/Language/locallang.xlf | 3 + 3 files changed, 171 insertions(+) create mode 100644 typo3/sysext/core/Documentation/Changelog/13.2/Feature-102869-ListWorkspacesInLiveSearch.rst create mode 100644 typo3/sysext/workspaces/Classes/Backend/LiveSearch/WorkspaceProvider.php diff --git a/typo3/sysext/core/Documentation/Changelog/13.2/Feature-102869-ListWorkspacesInLiveSearch.rst b/typo3/sysext/core/Documentation/Changelog/13.2/Feature-102869-ListWorkspacesInLiveSearch.rst new file mode 100644 index 000000000000..4f7b848f59d1 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/13.2/Feature-102869-ListWorkspacesInLiveSearch.rst @@ -0,0 +1,26 @@ +.. include:: /Includes.rst.txt + +.. _feature-102869-1705661913: + +================================================ +Feature: #102869 - List workspaces in LiveSearch +================================================ + +See :issue:`102869` + +Description +=========== + +The backend LiveSearch is now able to list workspaces a backend user has access +to, offering the possibility to switch to a workspace quickly outside the +Workspaces module. + + +Impact +====== + +Backend users now have another, quickly accessible way to access a workspace. +With proper permissions, a backend user may also switch to the edit interface of +a workspace to configure its settings. + +.. index:: Backend, ext:workspaces diff --git a/typo3/sysext/workspaces/Classes/Backend/LiveSearch/WorkspaceProvider.php b/typo3/sysext/workspaces/Classes/Backend/LiveSearch/WorkspaceProvider.php new file mode 100644 index 000000000000..fdc3c594ad49 --- /dev/null +++ b/typo3/sysext/workspaces/Classes/Backend/LiveSearch/WorkspaceProvider.php @@ -0,0 +1,142 @@ +<?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\Workspaces\Backend\LiveSearch; + +use Symfony\Component\DependencyInjection\Attribute\Autowire; +use TYPO3\CMS\Backend\Routing\UriBuilder; +use TYPO3\CMS\Backend\Search\LiveSearch\ResultItem; +use TYPO3\CMS\Backend\Search\LiveSearch\ResultItemAction; +use TYPO3\CMS\Backend\Search\LiveSearch\SearchDemand\SearchDemand; +use TYPO3\CMS\Backend\Search\LiveSearch\SearchProviderInterface; +use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; +use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; +use TYPO3\CMS\Core\Imaging\IconFactory; +use TYPO3\CMS\Core\Imaging\IconSize; +use TYPO3\CMS\Core\Localization\LanguageService; +use TYPO3\CMS\Core\Localization\LanguageServiceFactory; +use TYPO3\CMS\Workspaces\Service\WorkspaceService; + +/** + * Search provider to query workspaces from database + * + * @internal + */ +final readonly class WorkspaceProvider implements SearchProviderInterface +{ + private LanguageService $languageService; + + public function __construct( + #[Autowire(service: 'cache.runtime')] + private FrontendInterface $runtimeCache, + private WorkspaceService $workspaceService, + private UriBuilder $uriBuilder, + private IconFactory $iconFactory, + private LanguageServiceFactory $languageServiceFactory, + ) { + $this->languageService = $this->languageServiceFactory->createFromUserPreferences($this->getBackendUser()); + } + + public function count(SearchDemand $searchDemand): int + { + return count($this->getFilteredWorkspaces($searchDemand)); + } + + public function find(SearchDemand $searchDemand): array + { + $icon = $this->iconFactory->getIcon('mimetypes-x-sys_workspace', IconSize::SMALL); + $typeLabel = $this->languageService->sL($GLOBALS['TCA']['sys_workspace']['ctrl']['title']); + $workspaces = $this->getFilteredWorkspaces($searchDemand); + $items = []; + + foreach ($workspaces as $workspaceId => $workspaceLabel) { + $actions = []; + $actions[] = (new ResultItemAction('open_workspace')) + ->setLabel($this->languageService->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:action.switchToWorkspace')) + ->setUrl((string)$this->uriBuilder->buildUriFromRoute('workspaces_admin', [ + 'workspace' => $workspaceId, + 'id' => $searchDemand->getPageId(), + ])) + ; + + if ($workspaceId > 0 && $this->getBackendUser()->isAdmin()) { + $editWorkspaceRecordUrl = (string)$this->uriBuilder->buildUriFromRoute('record_edit', [ + 'edit' => [ + 'sys_workspace' => [ + $workspaceId => 'edit', + ], + ], + 'returnUrl' => (string)$this->uriBuilder->buildUriFromRoute('workspaces_admin', ['id' => $searchDemand->getPageId()]), + ]); + + $actions[] = (new ResultItemAction('configure_workspace')) + ->setLabel($this->languageService->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:button.editWorkspaceSettings')) + ->setIcon($this->iconFactory->getIcon('actions-cog-alt', IconSize::SMALL)) + ->setUrl($editWorkspaceRecordUrl); + } + + $items[] = (new ResultItem(self::class)) + ->setItemTitle($workspaceLabel) + ->setTypeLabel($typeLabel) + ->setIcon($icon) + ->setActions(...$actions); + } + + return $items; + } + + public function getFilterLabel(): string + { + return $this->languageService->sL($GLOBALS['TCA']['sys_workspace']['ctrl']['title']); + } + + private function getAvailableWorkspaces(): array + { + $cacheId = 'available-workspaces-' . hash('xxh3', $this->getBackendUser()->name); + $availableWorkspaces = $this->runtimeCache->get($cacheId); + if ($availableWorkspaces === false) { + $availableWorkspaces = $this->workspaceService->getAvailableWorkspaces(); + $this->runtimeCache->set($cacheId, $availableWorkspaces); + } + + return $availableWorkspaces; + } + + private function getFilteredWorkspaces(SearchDemand $searchDemand): array + { + // @todo: This isn't nice. The interface should rather have an `canAccess()` method to check whether the current + // backend user is permitted to use a provider at all. + if (count($this->getAvailableWorkspaces()) <= 1) { + return []; + } + + $filteredWorkspaces = array_filter( + $this->getAvailableWorkspaces(), + static fn(string $workspaceName) => str_contains(mb_strtolower($workspaceName), mb_strtolower($searchDemand->getQuery())) + ); + + $firstResult = $searchDemand->getOffset(); + $remainingItems = $searchDemand->getLimit(); + + return array_slice($filteredWorkspaces, $firstResult, $remainingItems, true); + } + + private function getBackendUser(): BackendUserAuthentication + { + return $GLOBALS['BE_USER']; + } +} diff --git a/typo3/sysext/workspaces/Resources/Private/Language/locallang.xlf b/typo3/sysext/workspaces/Resources/Private/Language/locallang.xlf index 3e9364b4e075..8b664c456c7e 100644 --- a/typo3/sysext/workspaces/Resources/Private/Language/locallang.xlf +++ b/typo3/sysext/workspaces/Resources/Private/Language/locallang.xlf @@ -393,6 +393,9 @@ <trans-unit id="moduleMenu.dropdown.label" resname="moduleMenu.dropdown.label"> <source>Selected workspace</source> </trans-unit> + <trans-unit id="action.switchToWorkspace" resname="action.switchToWorkspace"> + <source>Switch to workspace</source> + </trans-unit> </body> </file> </xliff> -- GitLab