TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,329 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Backend\Module;
|
||||
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* This is the central point to retrieve modules from the ModuleRegistry, while
|
||||
* performing the necessary access checks, which ModuleRegistry does not deal with.
|
||||
*/
|
||||
readonly class ModuleProvider
|
||||
{
|
||||
public function __construct(
|
||||
protected ModuleRegistry $moduleRegistry,
|
||||
protected ModuleAccessGateRegistry $gateRegistry,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Simple wrapper for the registry, which just checks if a
|
||||
* module is registered. Does NOT perform any access checks.
|
||||
*/
|
||||
public function isModuleRegistered(string $identifier): bool
|
||||
{
|
||||
return $this->moduleRegistry->hasModule($identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Module for the given identifier. In case a user is given, also access checks are performed.
|
||||
*/
|
||||
public function getModule(
|
||||
string $identifier,
|
||||
?BackendUserAuthentication $user = null,
|
||||
bool $respectWorkspaceRestrictions = true
|
||||
): ?ModuleInterface {
|
||||
if (($user === null && $this->moduleRegistry->hasModule($identifier))
|
||||
|| $this->accessGranted($identifier, $user, $respectWorkspaceRestrictions)
|
||||
) {
|
||||
$module = $this->moduleRegistry->getModule($identifier);
|
||||
if ($user !== null) {
|
||||
$this->filterInaccessibleSubModules($module, $user, $respectWorkspaceRestrictions);
|
||||
}
|
||||
return $module;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all modules either grouped by main modules or flat.
|
||||
* In case a user is given, also access checks are performed.
|
||||
*
|
||||
* @return ModuleInterface[]
|
||||
*/
|
||||
public function getModules(
|
||||
?BackendUserAuthentication $user = null,
|
||||
bool $respectWorkspaceRestrictions = true,
|
||||
bool $grouped = true
|
||||
): array {
|
||||
if (!$grouped) {
|
||||
return array_filter(
|
||||
$this->moduleRegistry->getModules(),
|
||||
fn(ModuleInterface $module): bool => $user === null || $this->accessGranted($module->getIdentifier(), $user, $respectWorkspaceRestrictions)
|
||||
);
|
||||
}
|
||||
|
||||
$availableModules = array_filter($this->moduleRegistry->getModules(), static fn(ModuleInterface $module): bool => !$module->hasParentModule());
|
||||
|
||||
foreach ($availableModules as $identifier => $module) {
|
||||
if ($user !== null && !$this->accessGranted($identifier, $user, $respectWorkspaceRestrictions)) {
|
||||
unset($availableModules[$identifier]);
|
||||
continue;
|
||||
}
|
||||
if ($user !== null) {
|
||||
$this->filterInaccessibleSubModules($module, $user, $respectWorkspaceRestrictions);
|
||||
}
|
||||
}
|
||||
|
||||
return $availableModules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the requested (main) module if exist and allowed, prepared
|
||||
* for menu generation or similar structured output (nested). Takes
|
||||
* TSConfig into account. Does not respect "appearance[renderInModuleMenu]".
|
||||
*/
|
||||
public function getModuleForMenu(
|
||||
string $identifier,
|
||||
BackendUserAuthentication $user,
|
||||
bool $respectWorkspaceRestrictions = true
|
||||
): ?MenuModule {
|
||||
$module = $this->getModule($identifier, $user, $respectWorkspaceRestrictions);
|
||||
if ($module === null) {
|
||||
return null;
|
||||
}
|
||||
// Before preparing the module for the menu, check if it is defined ad hidden in TSconfig
|
||||
$hideModules = GeneralUtility::trimExplode(',', $user->getTSConfig()['options.']['hideModules'] ?? '', true);
|
||||
if (in_array($identifier, $hideModules, true)) {
|
||||
return null;
|
||||
}
|
||||
$menuItem = new MenuModule(clone $module);
|
||||
if ($menuItem->isStandalone()) {
|
||||
return $menuItem;
|
||||
}
|
||||
$this->buildMenuModuleRecursively($menuItem, $module, $hideModules, $user, $respectWorkspaceRestrictions);
|
||||
if (!$menuItem->hasSubModules()) {
|
||||
// In case the main module does not have any submodules, unset it again
|
||||
return null;
|
||||
}
|
||||
return $menuItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all allowed modules for the current user, prepared
|
||||
* for module menu generation or similar structured output (nested).
|
||||
* Takes TSConfig and "appearance[renderInModuleMenu]" into account.
|
||||
*
|
||||
* @return MenuModule[]
|
||||
*/
|
||||
public function getModulesForModuleMenu(
|
||||
BackendUserAuthentication $user,
|
||||
bool $respectWorkspaceRestrictions = true
|
||||
): array {
|
||||
$moduleMenuItems = [];
|
||||
$moduleMenuState = $this->getModuleMenuState($user);
|
||||
|
||||
// Before preparing the modules for the menu, check if we need to hide some of them (defined in TSconfig)
|
||||
$hideModules = GeneralUtility::trimExplode(',', $user->getTSConfig()['options.']['hideModules'] ?? '', true);
|
||||
foreach ($this->getModules($user, $respectWorkspaceRestrictions) as $identifier => $module) {
|
||||
if (in_array($identifier, $hideModules, true)
|
||||
|| !($module->getAppearance()['renderInModuleMenu'] ?? true)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
// Only use main modules for this first level
|
||||
if ($module->hasParentModule()) {
|
||||
continue;
|
||||
}
|
||||
$menuItem = new MenuModule(clone $module, isset($moduleMenuState[$identifier]));
|
||||
$moduleMenuItems[$identifier] = $menuItem;
|
||||
if ($menuItem->isStandalone()) {
|
||||
continue;
|
||||
}
|
||||
$this->buildMenuModuleRecursively($menuItem, $module, $hideModules, $user, $respectWorkspaceRestrictions, true);
|
||||
if (!$menuItem->hasSubModules()) {
|
||||
// In case the main module does not have any submodules, unset it again
|
||||
unset($moduleMenuItems[$identifier]);
|
||||
}
|
||||
}
|
||||
return $moduleMenuItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check access of a module for a given user
|
||||
*/
|
||||
public function accessGranted(
|
||||
string $identifier,
|
||||
BackendUserAuthentication $user,
|
||||
bool $respectWorkspaceRestrictions = true
|
||||
): bool {
|
||||
if (!$this->moduleRegistry->hasModule($identifier)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$module = $this->moduleRegistry->getModule($identifier);
|
||||
|
||||
if ($respectWorkspaceRestrictions && ExtensionManagementUtility::isLoaded('workspaces')) {
|
||||
$workspaceAccess = $module->getWorkspaceAccess();
|
||||
if ($workspaceAccess === '' && $module->hasParentModule()) {
|
||||
// In case workspace access is not set explicitly use the information
|
||||
// from the parent module as this access restriction is inherited.
|
||||
$workspaceAccess = $module->getParentModule()->getWorkspaceAccess();
|
||||
}
|
||||
if ($workspaceAccess !== '' && $workspaceAccess !== '*') {
|
||||
if (($workspaceAccess === 'live' && $user->workspace !== 0)
|
||||
|| ($workspaceAccess === 'offline' && $user->workspace === 0)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
} elseif ($user->workspace === -99 && $workspaceAccess !== '*') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$moduleAccess = $module->getAccess();
|
||||
if ($moduleAccess === '') {
|
||||
// Early return since this module does not have any access permissions set
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->gateRegistry->has($moduleAccess)) {
|
||||
return $this->gateRegistry->get($moduleAccess)->decide($module, $user) === ModuleAccessResult::Granted;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first module, accessible for the given user.
|
||||
* This will only return submodules or standalone modules.
|
||||
*
|
||||
* @internal not part of TYPO3's API. Only for use in TYPO3 Core.
|
||||
*/
|
||||
public function getFirstAccessibleModule(BackendUserAuthentication $user): ?ModuleInterface
|
||||
{
|
||||
$modules = array_filter($this->moduleRegistry->getModules(), function (ModuleInterface $module) use ($user): bool {
|
||||
return $this->accessGranted($module->getIdentifier(), $user)
|
||||
&& ($module->isStandalone() || $module->hasParentModule());
|
||||
});
|
||||
|
||||
return reset($modules) ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all modules with access=user, to be selected in the user/group records
|
||||
*
|
||||
* @return ModuleInterface[]
|
||||
* @internal not part of TYPO3's API. Only for use in TYPO3 Core.
|
||||
*/
|
||||
public function getUserModules(): array
|
||||
{
|
||||
return array_filter($this->moduleRegistry->getModules(), static fn(ModuleInterface $module): bool => $module->getAccess() === 'user');
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively removes inaccessible submodules from a module at any depth
|
||||
*/
|
||||
protected function filterInaccessibleSubModules(
|
||||
ModuleInterface $module,
|
||||
BackendUserAuthentication $user,
|
||||
bool $respectWorkspaceRestrictions
|
||||
): void {
|
||||
if (!$module->hasSubModules()) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($module->getSubModules() as $subModuleIdentifier => $subModule) {
|
||||
if (!$this->accessGranted($subModuleIdentifier, $user, $respectWorkspaceRestrictions)) {
|
||||
$module->removeSubModule($subModuleIdentifier);
|
||||
} else {
|
||||
$this->filterInaccessibleSubModules($subModule, $user, $respectWorkspaceRestrictions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively builds menu module structure, checking access, TSConfig hideModules,
|
||||
* and optionally renderInModuleMenu appearance setting at all nesting levels
|
||||
*/
|
||||
protected function buildMenuModuleRecursively(
|
||||
MenuModule $menuItem,
|
||||
ModuleInterface $module,
|
||||
array $hideModules,
|
||||
BackendUserAuthentication $user,
|
||||
bool $respectWorkspaceRestrictions,
|
||||
bool $checkRenderInModuleMenu = false
|
||||
): void {
|
||||
$moduleMenuState = $this->getModuleMenuState($user);
|
||||
foreach ($module->getSubModules() as $subModuleIdentifier => $subModule) {
|
||||
if (in_array($subModuleIdentifier, $hideModules, true)
|
||||
|| ($checkRenderInModuleMenu && !($subModule->getAppearance()['renderInModuleMenu'] ?? true))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
// Skip submodules that depend on their own submodules if they don't have any accessible ones
|
||||
if (($subModule->getAppearance()['dependsOnSubmodules'] ?? false)
|
||||
&& !$this->hasAccessibleSubModules($subModule, $hideModules, $user, $respectWorkspaceRestrictions, $checkRenderInModuleMenu)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
$subMenuItem = new MenuModule(clone $subModule, isset($moduleMenuState[$subModuleIdentifier]));
|
||||
$menuItem->addSubModule($subMenuItem);
|
||||
// Recursively build deeper levels
|
||||
if ($subModule->hasSubModules()) {
|
||||
$this->buildMenuModuleRecursively($subMenuItem, $subModule, $hideModules, $user, $respectWorkspaceRestrictions, $checkRenderInModuleMenu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a module has at least one accessible submodule at any depth
|
||||
*/
|
||||
protected function hasAccessibleSubModules(
|
||||
ModuleInterface $module,
|
||||
array $hideModules,
|
||||
BackendUserAuthentication $user,
|
||||
bool $respectWorkspaceRestrictions,
|
||||
bool $checkRenderInModuleMenu = false
|
||||
): bool {
|
||||
foreach ($module->getSubModules() as $subModuleIdentifier => $subModule) {
|
||||
if (in_array($subModuleIdentifier, $hideModules, true)
|
||||
|| ($checkRenderInModuleMenu && !($subModule->getAppearance()['renderInModuleMenu'] ?? true))
|
||||
|| !$this->accessGranted($subModuleIdentifier, $user, $respectWorkspaceRestrictions)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
// If this submodule is accessible, return true
|
||||
if (!($subModule->getAppearance()['dependsOnSubmodules'] ?? false)) {
|
||||
return true;
|
||||
}
|
||||
// If it depends on submodules, check recursively
|
||||
if ($this->hasAccessibleSubModules($subModule, $hideModules, $user, $respectWorkspaceRestrictions, $checkRenderInModuleMenu)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getModuleMenuState(BackendUserAuthentication $user): array
|
||||
{
|
||||
return json_decode($user->uc['modulemenu'] ?? '{}', true);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user