first commit

This commit is contained in:
salam committed 2026-10-19 08:18:39 +02:00
commit 1218dd1517
12716 files changed
+989233

No files matched your search

@@ -0,0 +1,421 @@
<?php
namespace TYPO3\CMS\Backend\Module;
/*
* 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\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* This document provides a class that loads the modules for the TYPO3 interface.
*
* Load Backend Interface modules
*
* Typically instantiated like this:
* $this->loadModules = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Module\ModuleLoader::class);
* $this->loadModules->load($TBE_MODULES);
* @internal
*/
class ModuleLoader
{
/**
* After the init() function this array will contain the structure of available modules for the backend user.
*
* @var array
*/
public $modules = [];
/**
* This array will hold the elements that should go into the select-list of modules for groups...
*
* @var array
*/
public $modListGroup = [];
/**
* This array will hold the elements that should go into the select-list of modules for users...
*
* @var array
*/
public $modListUser = [];
/**
* The backend user for use internally
*
* @var \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
*/
public $BE_USER;
/**
* If set TRUE, workspace "permissions" will be observed so non-allowed modules will not be included in the array of modules.
*
* @var bool
*/
public $observeWorkspaces = false;
/**
* Contains the registered navigation components
*
* @var array
*/
protected $navigationComponents = [];
/**
* Labels for the modules
* @var array
*/
protected $moduleLabels = [];
/**
* Init.
* The outcome of the load() function will be a $this->modules array populated with the backend module structure available to the BE_USER
* Further the global var $LANG will have labels and images for the modules loaded in an internal array.
*
* @param array $modulesArray Should be the global var $TBE_MODULES, $BE_USER can optionally be set to an alternative Backend user object than the global var $BE_USER (which is the currently logged in user)
* @param BackendUserAuthentication $beUser Optional backend user object to use. If not set, the global BE_USER object is used.
*/
public function load($modulesArray, BackendUserAuthentication $beUser = null)
{
// Setting the backend user for use internally
$this->BE_USER = $beUser ?: $GLOBALS['BE_USER'];
// Unset the array for calling backend modules based on external backend module dispatchers in typo3/index.php
unset($modulesArray['_configuration']);
$this->navigationComponents = $modulesArray['_navigationComponents'];
unset($modulesArray['_navigationComponents']);
$mainModules = $this->parseModulesArray($modulesArray);
// Traverses the module setup and creates the internal array $this->modules
foreach ($mainModules as $mainModuleName => $subModules) {
$mainModuleConfiguration = $this->checkMod($mainModuleName);
// If $mainModuleConfiguration is not set (FALSE) there is no access to the module !(?)
if (is_array($mainModuleConfiguration)) {
$this->modules[$mainModuleName] = $mainModuleConfiguration;
// Load the submodules
if (is_array($subModules)) {
foreach ($subModules as $subModuleName) {
$subModuleConfiguration = $this->checkMod($mainModuleName . '_' . $subModuleName);
if (is_array($subModuleConfiguration)) {
$this->modules[$mainModuleName]['sub'][$subModuleName] = $subModuleConfiguration;
}
}
}
} elseif ($mainModuleConfiguration !== false) {
// Although the configuration was not found, still check if there are submodules
// This must be done in order to fill out the select-lists for modules correctly!!
if (is_array($subModules)) {
foreach ($subModules as $subModuleName) {
$this->checkMod($mainModuleName . '_' . $subModuleName);
}
}
}
}
}
/**
* Here we check for the module.
*
* Return values:
* 'notFound': If the module was not found in the path (no "conf.php" file)
* FALSE: If no access to the module (access check failed)
* array(): Configuration array, in case a valid module where access IS granted exists.
*
* @param string $name Module name
* @return string|bool|array See description of function
*/
public function checkMod($name)
{
// Check for own way of configuring module
if (is_array($GLOBALS['TBE_MODULES']['_configuration'][$name]['configureModuleFunction'] ?? false)) {
trigger_error('Registering a module using "configureModuleFunction" will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
$obj = $GLOBALS['TBE_MODULES']['_configuration'][$name]['configureModuleFunction'];
if (is_callable($obj)) {
$MCONF = call_user_func($obj, $name);
if ($this->checkModAccess($name, $MCONF) !== true) {
return false;
}
$this->addLabelsForModule($name, $MCONF['labels']);
return $MCONF;
}
}
// merge configuration and labels into one array
$setupInformation = $this->getModuleSetupInformation($name);
// clean up the configuration part
if (empty($setupInformation['configuration'])) {
return 'notFound';
}
$finalModuleConfiguration = $setupInformation['configuration'];
if (!empty($finalModuleConfiguration['shy'])
|| !$this->checkModAccess($name, $setupInformation['configuration'])
|| !$this->checkModWorkspace($name, $setupInformation['configuration'])
) {
return false;
}
$finalModuleConfiguration['name'] = $name;
// Language processing. This will add module labels and image reference to the internal ->moduleLabels array of the LANG object.
$this->addLabelsForModule($name, $finalModuleConfiguration['labels'] ?? $setupInformation['labels']);
/** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
$uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
if (isset($setupInformation['configuration']['routeTarget'])) {
$finalModuleConfiguration['script'] = (string)$uriBuilder->buildUriFromRoute($name);
} else {
$finalModuleConfiguration['script'] = (string)$uriBuilder->buildUriFromRoute('dummy');
}
if (!empty($setupInformation['configuration']['navigationFrameModule'])) {
$finalModuleConfiguration['navFrameScript'] = (string)$uriBuilder->buildUriFromRoute(
$setupInformation['configuration']['navigationFrameModule'],
!empty($setupInformation['configuration']['navigationFrameModuleParameters'])
? $setupInformation['configuration']['navigationFrameModuleParameters']
: []
);
}
// Check if this is a submodule
$mainModule = '';
if (strpos($name, '_') !== false) {
list($mainModule, ) = explode('_', $name, 2);
}
// check if there is a navigation component (like the pagetree)
if (is_array($this->navigationComponents[$name] ?? false)) {
$finalModuleConfiguration['navigationComponentId'] = $this->navigationComponents[$name]['componentId'];
} elseif ($mainModule
&& is_array($this->navigationComponents[$mainModule] ?? false)
&& $setupInformation['configuration']['inheritNavigationComponentFromMainModule'] !== false) {
// navigation component can be overridden by the main module component
$finalModuleConfiguration['navigationComponentId'] = $this->navigationComponents[$mainModule]['componentId'];
}
return $finalModuleConfiguration;
}
/**
* fetches the conf.php file of a certain module, and also merges that with
* some additional configuration
*
* @param string $moduleName the combined name of the module, can be "web", "web_info", or "tools_log"
* @return array an array with subarrays, named "configuration" (aka $MCONF), "labels" (previously known as $MLANG) and the stripped path
*/
protected function getModuleSetupInformation($moduleName)
{
$moduleSetupInformation = [
'configuration' => [],
'labels' => []
];
$moduleConfiguration = !empty($GLOBALS['TBE_MODULES']['_configuration'][$moduleName])
? $GLOBALS['TBE_MODULES']['_configuration'][$moduleName]
: null;
if ($moduleConfiguration !== null) {
// Overlay setup with additional labels
if (!empty($moduleConfiguration['labels']) && is_array($moduleConfiguration['labels'])) {
if (empty($moduleSetupInformation['labels']['default']) || !is_array($moduleSetupInformation['labels']['default'])) {
$moduleSetupInformation['labels']['default'] = $moduleConfiguration['labels'];
} else {
$moduleSetupInformation['labels']['default'] = array_replace_recursive($moduleSetupInformation['labels']['default'], $moduleConfiguration['labels']);
}
unset($moduleConfiguration['labels']);
}
// Overlay setup with additional configuration
if (is_array($moduleConfiguration)) {
$moduleSetupInformation['configuration'] = array_replace_recursive($moduleSetupInformation['configuration'], $moduleConfiguration);
}
}
// Add some default configuration
if (!isset($moduleSetupInformation['configuration']['inheritNavigationComponentFromMainModule'])) {
$moduleSetupInformation['configuration']['inheritNavigationComponentFromMainModule'] = true;
}
return $moduleSetupInformation;
}
/**
* Returns TRUE if the internal BE_USER has access to the module $name with $MCONF (based on security level set for that module)
*
* @param string $name Module name
* @param array $MCONF MCONF array (module configuration array) from the modules conf.php file (contains settings about what access level the module has)
* @return bool TRUE if access is granted for $this->BE_USER
*/
public function checkModAccess($name, $MCONF)
{
if (empty($MCONF['access'])) {
return true;
}
$access = strtolower($MCONF['access']);
// Check if this module is only allowed by system maintainers (= admins who are in the list of system maintainers)
if (strpos($MCONF['access'], BackendUserAuthentication::ROLE_SYSTEMMAINTAINER) !== false) {
return $this->BE_USER->isSystemMaintainer();
}
// Checking if admin-access is required
// If admin-permissions is required then return TRUE if user is admin
if (strpos($access, 'admin') !== false && $this->BE_USER->isAdmin()) {
return true;
}
// This will add modules to the select-lists of user and groups
if (strpos($access, 'user') !== false) {
$this->modListUser[] = $name;
}
if (strpos($access, 'group') !== false) {
$this->modListGroup[] = $name;
}
// This checks if a user is permitted to access the module
if ($this->BE_USER->isAdmin() || $this->BE_USER->check('modules', $name)) {
return true;
}
return false;
}
/**
* Check if a module is allowed inside the current workspace for be user
* Processing happens only if $this->observeWorkspaces is TRUE
*
* @param string $name Module name (unused)
* @param array $MCONF MCONF array (module configuration array) from the modules conf.php file (contains settings about workspace restrictions)
* @return bool TRUE if access is granted for $this->BE_USER
*/
public function checkModWorkspace($name, $MCONF)
{
if (!$this->observeWorkspaces) {
return true;
}
$status = true;
if (!empty($MCONF['workspaces'])) {
$status = $this->BE_USER->workspace === 0 && GeneralUtility::inList($MCONF['workspaces'], 'online')
|| $this->BE_USER->workspace === -1 && GeneralUtility::inList($MCONF['workspaces'], 'offline')
|| $this->BE_USER->workspace > 0 && GeneralUtility::inList($MCONF['workspaces'], 'custom');
} elseif ($this->BE_USER->workspace === -99) {
$status = false;
}
return $status;
}
/**
* Parses the moduleArray ($TBE_MODULES) into an internally useful structure.
* Returns an array where the keys are names of the module and the values may be TRUE (only module) or an array (of submodules)
*
* @param array $arr ModuleArray ($TBE_MODULES)
* @return array Output structure with available modules
*/
public function parseModulesArray($arr)
{
$theMods = [];
if (is_array($arr)) {
foreach ($arr as $mod => $subs) {
// Clean module name to alphanum
$mod = $this->cleanName($mod);
if ($mod) {
if ($subs) {
$subsArr = GeneralUtility::trimExplode(',', $subs);
foreach ($subsArr as $subMod) {
$subMod = $this->cleanName($subMod);
if ($subMod) {
$theMods[$mod][] = $subMod;
}
}
} else {
$theMods[$mod] = 1;
}
}
}
}
return $theMods;
}
/**
* The $str is cleaned so that it contains alphanumerical characters only.
* Module names must only consist of these characters
*
* @param string $str String to clean up
* @return string
*/
public function cleanName($str)
{
return preg_replace('/[^a-z0-9]/i', '', $str);
}
/**
* Registers labels for a module in a unified way.
*
* Legacy info: This was previously named
* - labels->tablabel (now called "shortdescription")
* - labels->tabdescr (now called "description")
* - tabs->tab (now called "title")
*
* The LLL information is stored, not the actual translated string.
*
* @param string $moduleName the name of the module
* @param string|array $labels the information about the three labels
*/
public function addLabelsForModule($moduleName, $labels)
{
// If LOCAL_LANG references are used for labels of the module:
if (is_string($labels)) {
// Extbase-based modules
$this->moduleLabels[$moduleName] = [
'shortdescription' => $labels . ':mlang_labels_tablabel',
'description' => $labels . ':mlang_labels_tabdescr',
'title' => $labels . ':mlang_tabs_tab',
];
} elseif (isset($labels['title'])) {
// New way, where all labels can be LLL references
$this->moduleLabels[$moduleName] = $labels;
} elseif (isset($labels['ll_ref'])) {
// Classic, non-extbase module labels
$this->addLabelsForModule($moduleName, $labels['ll_ref']);
} else {
// Very old obsolete approach, don't use anymore, use one of the ways above.
if (is_object($this->getLanguageService())) {
$language = $this->getLanguageService()->lang;
} else {
$language = 'default';
}
if (isset($labels[$language]['ll_ref'])) {
$this->addLabelsForModule($moduleName, $labels[$language]['ll_ref']);
} elseif (isset($labels['default']['ll_ref'])) {
$this->addLabelsForModule($moduleName, $labels['default']['ll_ref']);
} else {
$this->moduleLabels[$moduleName] = [
'shortdescription' => $labels[$language]['labels']['tablabel'] ?? $labels['default']['labels']['tablabel'],
'description' => $labels[$language]['labels']['tabdescr'] ?? $labels['default']['labels']['tabdescr'],
'title' => $labels[$language]['tabs']['tab'] ?? $labels['default']['tabs']['tab'],
];
}
}
}
/**
* Returns the labels for the given module
*
* @param string $moduleName
* @return array
*/
public function getLabelsForModule($moduleName)
{
return $this->moduleLabels[$moduleName] ?? [];
}
/**
* @return LanguageService
*/
protected function getLanguageService()
{
return $GLOBALS['LANG'];
}
}