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); } }