diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-89894-SeparateSystemExtensionsFrom3rd-partyExtensionsVisually.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-89894-SeparateSystemExtensionsFrom3rd-partyExtensionsVisually.rst new file mode 100644 index 0000000000000000000000000000000000000000..c605b4bdb465d20a53ffdd1eb50a676ef7000640 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/master/Feature-89894-SeparateSystemExtensionsFrom3rd-partyExtensionsVisually.rst @@ -0,0 +1,22 @@ +.. include:: ../../Includes.txt + +=============================================================================== +Feature: #89894 - Separate system extensions from 3rd-party extensions visually +=============================================================================== + +See :issue:`89894` + +Description +=========== + +The Extension Manager in TYPO3 allows backend users to list, activate, deactivate, configure, and possibly add/remove extensions from the system. When using the Extension Manager, backend users work with either core extensions (system extensions) or 3rd-party extensions, depending on their task. + +The extension list shown in the Extension Manager can now be filtered by certain extension types (system and 3rd-party extensions). + + +Impact +====== + +A limited list of extensions, either system or 3rd-party extensions, makes it easier for backend users to find the extension they intend to work with and/or to get a quick overview which extensions are currently installed (e.g. 3rd-party extensions). This improves the usability of the backend for integrators/administrators. + +.. index:: Backend, ext:extensionmanager diff --git a/typo3/sysext/extensionmanager/Classes/Controller/ListController.php b/typo3/sysext/extensionmanager/Classes/Controller/ListController.php index 93bf495fdf8f61934495cea64dbf8d527d4bef09..3afd7b2ed9eabd3d92a27768355a2cf98e8ef828 100644 --- a/typo3/sysext/extensionmanager/Classes/Controller/ListController.php +++ b/typo3/sysext/extensionmanager/Classes/Controller/ListController.php @@ -15,9 +15,12 @@ namespace TYPO3\CMS\Extensionmanager\Controller; */ use TYPO3\CMS\Backend\Template\Components\ButtonBar; +use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Backend\View\BackendTemplateView; +use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; use TYPO3\CMS\Core\Core\Environment; +use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Imaging\Icon; use TYPO3\CMS\Core\Messaging\FlashMessage; use TYPO3\CMS\Core\Page\PageRenderer; @@ -58,6 +61,11 @@ class ListController extends AbstractModuleController */ protected $dependencyUtility; + /** + * @var string + */ + protected $backendUserFilter = ''; + /** * @param ExtensionRepository $extensionRepository */ @@ -142,13 +150,21 @@ class ListController extends AbstractModuleController */ public function indexAction() { + if ($this->request->hasArgument('filter') && is_string($this->request->getArgument('filter'))) { + $this->backendUserFilter = $this->request->getArgument('filter'); + $this->saveBackendUserFilter(); + } else { + $this->backendUserFilter = $this->getBackendUserFilter(); + } + $this->addComposerModeNotification(); - $availableAndInstalledExtensions = $this->listUtility->getAvailableAndInstalledExtensionsWithAdditionalInformation(); + $availableAndInstalledExtensions = $this->listUtility->getAvailableAndInstalledExtensionsWithAdditionalInformation($this->backendUserFilter); ksort($availableAndInstalledExtensions); $this->view->assignMultiple( [ 'extensions' => $availableAndInstalledExtensions, 'isComposerMode' => Environment::isComposerMode(), + 'backendUserFilter' => $this->backendUserFilter ?: 'All' ] ); $this->handleTriggerArguments(); @@ -293,4 +309,41 @@ class ListController extends AbstractModuleController ->setIcon($icon); $buttonBar->addButton($button, ButtonBar::BUTTON_POSITION_LEFT); } + + protected function getBackendUserFilter(): string + { + $backendUser = $this->getBackendUserAuthentication(); + + if (!$backendUser instanceof BackendUserAuthentication) { + return ''; + } + + return $backendUser->uc['BackendComponents']['States']['ExtensionManager']['filter'] ?? ''; + } + + protected function saveBackendUserFilter(): void + { + $backendUser = $this->getBackendUserAuthentication(); + + if (!$backendUser instanceof BackendUserAuthentication) { + return; + } + + $backendUserId = (int)$backendUser->user['uid']; + $backendUserRecord = BackendUtility::getRecord('be_users', $backendUserId); + + if (is_array($backendUserRecord) && isset($backendUserRecord['uc'])) { + $uc = unserialize($backendUserRecord['uc'], ['allowed_classes' => [\stdClass::class]]); + if (is_array($uc)) { + $uc['BackendComponents']['States']['ExtensionManager']['filter'] = $this->backendUserFilter; + $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('be_users'); + $connection->update('be_users', ['uc' => serialize($uc)], ['uid' => $backendUserId]); + } + } + } + + protected function getBackendUserAuthentication(): ?BackendUserAuthentication + { + return $GLOBALS['BE_USER']; + } } diff --git a/typo3/sysext/extensionmanager/Classes/Utility/ListUtility.php b/typo3/sysext/extensionmanager/Classes/Utility/ListUtility.php index 25bb403047687f6036c9adb395ecd57e45f58fa2..6fcc3605dd3e151824ab109ec664e197a36b6527 100644 --- a/typo3/sysext/extensionmanager/Classes/Utility/ListUtility.php +++ b/typo3/sysext/extensionmanager/Classes/Utility/ListUtility.php @@ -107,21 +107,24 @@ class ListUtility implements \TYPO3\CMS\Core\SingletonInterface /** * Returns the list of available, but not necessarily loaded extensions * + * @param string * @return array[] All extensions with info */ - public function getAvailableExtensions() + public function getAvailableExtensions(string $filter = ''): array { if ($this->availableExtensions === null) { $this->availableExtensions = []; $this->eventDispatcher->dispatch(new PackagesMayHaveChangedEvent()); foreach ($this->packageManager->getAvailablePackages() as $package) { - $this->availableExtensions[$package->getPackageKey()] = [ - 'packagePath' => $package->getPackagePath(), - 'siteRelPath' => str_replace(Environment::getPublicPath() . '/', '', $package->getPackagePath()), - 'type' => $this->getInstallTypeForPackage($package), - 'key' => $package->getPackageKey(), - 'icon' => PathUtility::getAbsoluteWebPath($package->getPackagePath() . ExtensionManagementUtility::getExtensionIcon($package->getPackagePath())), - ]; + $installationType = $this->getInstallTypeForPackage($package); + if ($filter === '' || $filter === $installationType) { + $this->availableExtensions[$package->getPackageKey()] = [ + 'siteRelPath' => str_replace(Environment::getPublicPath() . '/', '', $package->getPackagePath()), + 'type' => $installationType, + 'key' => $package->getPackageKey(), + 'icon' => PathUtility::getAbsoluteWebPath($package->getPackagePath() . ExtensionManagementUtility::getExtensionIcon($package->getPackagePath())), + ]; + } } } @@ -259,11 +262,12 @@ class ListUtility implements \TYPO3\CMS\Core\SingletonInterface * Gets all available and installed extension with additional information * from em_conf and TER (if available) * + * @param string * @return array */ - public function getAvailableAndInstalledExtensionsWithAdditionalInformation() + public function getAvailableAndInstalledExtensionsWithAdditionalInformation(string $filter = ''): array { - $availableExtensions = $this->getAvailableExtensions(); + $availableExtensions = $this->getAvailableExtensions($filter); $availableAndInstalledExtensions = $this->getAvailableAndInstalledExtensions($availableExtensions); return $this->enrichExtensionsWithEmConfAndTerInformation($availableAndInstalledExtensions); } diff --git a/typo3/sysext/extensionmanager/Resources/Private/Language/locallang.xlf b/typo3/sysext/extensionmanager/Resources/Private/Language/locallang.xlf index ff936c439db4db87da8c391705d1a44e55faf8da..4e53cb49e56fdba6da3814a8a685bd3db59bf28b 100644 --- a/typo3/sysext/extensionmanager/Resources/Private/Language/locallang.xlf +++ b/typo3/sysext/extensionmanager/Resources/Private/Language/locallang.xlf @@ -150,6 +150,15 @@ <trans-unit id="extensionList.search" resname="extensionList.search"> <source>Search</source> </trans-unit> + <trans-unit id="extensionList.filter.showAll" resname="extensionList.filter.showAll"> + <source>All</source> + </trans-unit> + <trans-unit id="extensionList.filter.showSystemExtensions" resname="extensionList.filter.showSystemExtensions"> + <source>System</source> + </trans-unit> + <trans-unit id="extensionList.filter.showLocalExtensions" resname="extensionList.filter.showLocalExtensions"> + <source>Local</source> + </trans-unit> <trans-unit id="searchTemplate.submitButton" resname="searchTemplate.submitButton"> <source>Go</source> </trans-unit> diff --git a/typo3/sysext/extensionmanager/Resources/Private/Templates/List/Index.html b/typo3/sysext/extensionmanager/Resources/Private/Templates/List/Index.html index caeb52fd054b35e603c113a20bd6f5c0cfad761d..ee09c4286e8577ed0de584e6780723d9b16b54fb 100644 --- a/typo3/sysext/extensionmanager/Resources/Private/Templates/List/Index.html +++ b/typo3/sysext/extensionmanager/Resources/Private/Templates/List/Index.html @@ -7,10 +7,44 @@ <f:section name="content"> <f:render partial="List/UploadForm" /> - <form class="form-inline"> - <div class="form-group"> - <f:form.textfield name="Tx_Extensionmanager_extensionkey" placeholder="{f:translate(key:'extensionList.search')}" id="Tx_Extensionmanager_extensionkey" value="{search}" class="form-control" /> + <div class="row"> + <div class="col-sm-6"> + <div class="form-group"> + <f:form.textfield name="Tx_Extensionmanager_extensionkey" placeholder="{f:translate(key:'extensionList.search')}" id="Tx_Extensionmanager_extensionkey" value="{search}" class="form-control" /> + </div> + </div> + <div class="col-sm-6"> + <div class="btn-group pull-right"> + <f:link.action + action="index" + controller="List" + title="{f:translate(key:'extensionList.filter.showAll')}" + arguments="{filter:''}" + class="btn btn-default {f:if(condition: '{backendUserFilter} == All', then: 'active')}" + style="width: 80px;"> + <f:translate key="extensionList.filter.showAll" /> + </f:link.action> + <f:link.action + action="index" + controller="List" + arguments="{filter:'System'}" + title="{f:translate(key:'extensionList.filter.showSystemExtensions')}" + class="btn btn-default {f:if(condition: '{backendUserFilter} == System', then: 'active')}" + style="width: 80px;"> + <f:translate key="extensionList.filter.showSystemExtensions" /> + </f:link.action> + <f:link.action + action="index" + controller="List" + arguments="{filter:'Local'}" + title="{f:translate(key:'extensionList.filter.showLocalExtensions')}" + class="btn btn-default {f:if(condition: '{backendUserFilter} == Local', then: 'active')}" + style="width: 80px;"> + <f:translate key="extensionList.filter.showLocalExtensions" /> + </f:link.action> + </div> + </div> </div> </form> <br>