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,228 @@
<?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\Backend\Template\DocumentTemplate;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Parent class for 'Extension Objects' in backend modules.
*
* Used for 'submodules' to other modules. Also called 'Function menu modules'
* in \TYPO3\CMS\Core\Utility\ExtensionManagementUtility. And now its even called
* 'Extension Objects'. Or 'Module functions'. Wish we had just one name. Or a
* name at all...(?) Thank God its not so advanced when it works...
*
* In other words this class is used for backend modules which is not true
* backend modules appearing in the menu but rather adds themselves as a new
* entry in the function menu which typically exists for a backend
* module (like Web>Info or Tools etc...)
* The magic that binds this together is stored in the global variable
* $TBE_MODULES_EXT where extensions wanting to connect a module based on
* this class to an existing backend module store configuration which consists
* of the classname, script-path and a label (title/name).
*
* For more information about this, please see the large example comment for the
* class \TYPO3\CMS\Backend\Module\BaseScriptClass. This will show the principle of a
* 'level-1' connection. The more advanced example - having two layers as it is done
* with the 'web_info' module - can be seen in the comment above.
*
* EXAMPLE: One level.
* This can be seen in the extension 'frontend' where the info module have a
* function added. In 'ext_tables.php' this is done by this function call:
*
* \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::insertModuleFunction(
* 'web_info',
* \TYPO3\CMS\Frontend\Controller\PageInformationController::class,
* NULL,
* 'LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:mod_tx_cms_webinfo_page'
* );
*
* For this two-level thing to work it also requires that the parent
* module (the real backend module) supports it.
* This is the case for the modules web_info since it has two
* times inclusion sections in their index.php scripts.
*
* @see \TYPO3\CMS\Backend\Module\BaseScriptClass
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
*/
abstract class AbstractFunctionModule
{
/**
* Contains a reference to the parent (calling) object (which is probably an instance of
* an extension class to \TYPO3\CMS\Backend\Module\BaseScriptClass
*
* @var BaseScriptClass
* @see init()
*/
public $pObj;
/**
* @var BaseScriptClass
*/
public $extObj;
/**
* Can be hardcoded to the name of a locallang.xlf file (from the same directory as the class file) to use/load
* and is included / added to $GLOBALS['LOCAL_LANG']
*
* @see init()
* @var string
*/
public $localLangFile = '';
/**
* Contains module configuration parts from TBE_MODULES_EXT if found
*
* @see handleExternalFunctionValue()
* @var array
*/
public $extClassConf;
/**
* If this value is set it points to a key in the TBE_MODULES_EXT array (not on the top level..) where another classname/filepath/title can be defined for sub-subfunctions.
* This is a little hard to explain, so see it in action; it used in the extension 'func_wizards' in order to provide yet a layer of interfacing with the backend module.
* The extension 'func_wizards' has this description: 'Adds the 'Wizards' item to the function menu in Web>Func. This is just a framework for wizard extensions.' - so as you can see it is designed to allow further connectivity - 'level 2'
*
* @see handleExternalFunctionValue(), \TYPO3\CMS\FuncWizards\Controller\WebFunctionWizardsBaseController
* @var string
*/
public $function_key = '';
/**
* @var PageRenderer
*/
protected $pageRenderer;
/**
* Constructor deprecates the class
*/
public function __construct()
{
trigger_error(
'Class AbstractFunctionModule is deprecated and will be removed in TYPO3 v10.0',
E_USER_DEPRECATED
);
}
/**
* Initialize the object
*
* @param \object $pObj A reference to the parent (calling) object
* @throws \RuntimeException
* @see \TYPO3\CMS\Backend\Module\BaseScriptClass::checkExtObj()
*/
public function init($pObj)
{
$this->pObj = $pObj;
// Local lang:
if (!empty($this->localLangFile)) {
$this->getLanguageService()->includeLLFile($this->localLangFile);
}
// Setting MOD_MENU items as we need them for logging:
$this->pObj->MOD_MENU = array_merge($this->pObj->MOD_MENU, $this->modMenu());
}
/**
* If $this->function_key is set (which means there are two levels of object connectivity) then
* $this->extClassConf is loaded with the TBE_MODULES_EXT configuration for that sub-sub-module
*
* @see $function_key, \TYPO3\CMS\FuncWizards\Controller\WebFunctionWizardsBaseController::init()
*/
public function handleExternalFunctionValue()
{
// Must clean first to make sure the correct key is set...
$this->pObj->MOD_SETTINGS = BackendUtility::getModuleData($this->pObj->MOD_MENU, GeneralUtility::_GP('SET'), $this->pObj->MCONF['name']);
if ($this->function_key) {
$this->extClassConf = $this->pObj->getExternalItemConfig($this->pObj->MCONF['name'], $this->function_key, $this->pObj->MOD_SETTINGS[$this->function_key]);
}
}
/**
* Same as \TYPO3\CMS\Backend\Module\BaseScriptClass::checkExtObj()
*
* @see \TYPO3\CMS\Backend\Module\BaseScriptClass::checkExtObj()
*/
public function checkExtObj()
{
if (is_array($this->extClassConf) && $this->extClassConf['name']) {
$this->extObj = GeneralUtility::makeInstance($this->extClassConf['name']);
$this->extObj->init($this->pObj, $this->extClassConf);
// Re-write:
$this->pObj->MOD_SETTINGS = BackendUtility::getModuleData($this->pObj->MOD_MENU, GeneralUtility::_GP('SET'), $this->pObj->MCONF['name']);
}
}
/**
* Calls the main function inside ANOTHER sub-submodule which might exist.
*/
public function extObjContent()
{
if (is_object($this->extObj)) {
return $this->extObj->main();
}
}
/**
* Dummy function - but is used to set up additional menu items for this submodule.
*
* @return array A MOD_MENU array which will be merged together with the one from the parent object
* @see init(), \TYPO3\CMS\Frontend\Controller\PageInformationController::modMenu()
*/
public function modMenu()
{
return [];
}
/**
* @return LanguageService
*/
protected function getLanguageService()
{
return $GLOBALS['LANG'];
}
/**
* @return BackendUserAuthentication
*/
protected function getBackendUserAuthentication()
{
return $GLOBALS['BE_USER'];
}
/**
* @return DocumentTemplate
*/
protected function getDocumentTemplate()
{
return $GLOBALS['TBE_TEMPLATE'];
}
/**
* @return PageRenderer
*/
protected function getPageRenderer()
{
if ($this->pageRenderer === null) {
$this->pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
}
return $this->pageRenderer;
}
}
@@ -0,0 +1,80 @@
<?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 Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Backend\Template\ModuleTemplate;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* A backend module. This class may be used by extension backend modules
* to implement own actions and controllers. It initializes the module
* template and comes with a simple dispatcher method.
*
* @internal Experimental for now
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
*/
class AbstractModule
{
/**
* ModuleTemplate object
*
* @var ModuleTemplate
*/
protected $moduleTemplate;
/**
* Constructor Method
*/
public function __construct()
{
trigger_error(
'Class AbstractModule is deprecated since TYPO3 v9 and will be removed with TYPO3 v10.0.'
. ' Instantiate ModuleTemplate yourself if needed and use routing registered in Routes.php.',
E_USER_DEPRECATED
);
$this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
}
/**
* PSR Request Object
*
* @var ServerRequestInterface
*/
protected $request;
/**
* Central Request Dispatcher
*
* @param ServerRequestInterface $request PSR7 Request Object
* @param ResponseInterface $response PSR7 Response Object
*
* @return ResponseInterface
*
* @throws \InvalidArgumentException In case an action is not callable
*/
public function processRequest(ServerRequestInterface $request, ResponseInterface $response)
{
$methodName = $request->getQueryParams()['action'] ?: 'index';
if (!is_callable([$this, $methodName])) {
throw new \InvalidArgumentException(
'The method "' . $methodName . '" is not callable within "' . static::class . '".',
1442736343
);
}
return $this->{$methodName}($request, $response);
}
}
@@ -0,0 +1,405 @@
<?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\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Messaging\FlashMessage;
use TYPO3\CMS\Core\Messaging\FlashMessageService;
use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Core\Type\Bitmask\Permission;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Parent class for 'ScriptClasses' in backend modules.
*
* EXAMPLE PROTOTYPE
*
* As for examples there are lots of them if you search for classes which extends \TYPO3\CMS\Backend\Module\BaseScriptClass
* However you can see a prototype example of how a module might use this class in an index.php file typically hosting a backend module.
*
* NOTICE: This example only outlines the basic structure of how this class is used.
* You should consult the documentation and other real-world examples for some actual things to do when building modules.
*
* TYPICAL SETUP OF A BACKEND MODULE:
*
* PrototypeController EXTENDS THE CLASS \TYPO3\CMS\Backend\Module\BaseScriptClass with a main() function:
*
* namespace Vendor\Prototype\Controller;
*
* class PrototypeController extends \TYPO3\CMS\Backend\Module\BaseScriptClass {
* public function __construct() {
* $this->getLanguageService()->includeLLFile('EXT:prototype/Resources/Private/Language/locallang.xlf');
* $this->getBackendUser()->modAccess($GLOBALS['MCONF']);
* }
* }
*
* MAIN FUNCTION - HERE YOU CREATE THE MODULE CONTENT IN $this->content
* public function main() {
* TYPICALLY THE INTERNAL VAR, $this->doc is instantiated like this:
* $this->doc = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Template\DocumentTemplate::class);
* ... AND OF COURSE A LOT OF OTHER THINGS GOES ON - LIKE PUTTING CONTENT INTO $this->content
* $this->content='';
* }
*
* MAKE INSTANCE OF THE SCRIPT CLASS AND CALL init()
* $GLOBALS['SOBE'] = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\Vendor\Prototype\Controller\PrototypeController::class);
* $GLOBALS['SOBE']->init();
*
*
* THEN WE WILL CHECK IF THERE IS A 'SUBMODULE' REGISTERED TO BE INITIALIZED AS WELL:
* $GLOBALS['SOBE']->checkExtObj();
*
* THEN WE CALL THE main() METHOD AND THIS SHOULD SPARK THE CREATION OF THE MODULE OUTPUT.
* $GLOBALS['SOBE']->main();
*
* @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
*/
class BaseScriptClass
{
/**
* Loaded with the global array $MCONF which holds some module configuration from the conf.php file of backend modules.
*
* @see init()
* @var array
*/
public $MCONF = [];
/**
* The integer value of the GET/POST var, 'id'. Used for submodules to the 'Web' module (page id)
*
* @see init()
* @var int
*/
public $id;
/**
* The value of GET/POST var, 'CMD'
*
* @see init()
* @var mixed
*/
public $CMD;
/**
* A WHERE clause for selection records from the pages table based on read-permissions of the current backend user.
*
* @see init()
* @var string
*/
public $perms_clause;
/**
* The module menu items array. Each key represents a key for which values can range between the items in the array of that key.
*
* @see init()
* @var array
*/
public $MOD_MENU = [
'function' => []
];
/**
* Current settings for the keys of the MOD_MENU array
*
* @see $MOD_MENU
* @var array
*/
public $MOD_SETTINGS = [];
/**
* Module TSconfig based on PAGE TSconfig / USER TSconfig
*
* @see menuConfig()
* @var array
*/
public $modTSconfig;
/**
* If type is 'ses' then the data is stored as session-lasting data. This means that it'll be wiped out the next time the user logs in.
* Can be set from extension classes of this class before the init() function is called.
*
* @see menuConfig(), \TYPO3\CMS\Backend\Utility\BackendUtility::getModuleData()
* @var string
*/
public $modMenu_type = '';
/**
* dontValidateList can be used to list variables that should not be checked if their value is found in the MOD_MENU array. Used for dynamically generated menus.
* Can be set from extension classes of this class before the init() function is called.
*
* @see menuConfig(), \TYPO3\CMS\Backend\Utility\BackendUtility::getModuleData()
* @var string
*/
public $modMenu_dontValidateList = '';
/**
* List of default values from $MOD_MENU to set in the output array (only if the values from MOD_MENU are not arrays)
* Can be set from extension classes of this class before the init() function is called.
*
* @see menuConfig(), \TYPO3\CMS\Backend\Utility\BackendUtility::getModuleData()
* @var string
*/
public $modMenu_setDefaultList = '';
/**
* Contains module configuration parts from TBE_MODULES_EXT if found
*
* @see handleExternalFunctionValue()
* @var array
*/
public $extClassConf;
/**
* Generally used for accumulating the output content of backend modules
*
* @var string
*/
public $content = '';
/**
* @var \TYPO3\CMS\Backend\Template\DocumentTemplate
*/
public $doc;
/**
* May contain an instance of a 'Function menu module' which connects to this backend module.
*
* @see checkExtObj()
* @var AbstractFunctionModule
*/
public $extObj;
/**
* @var PageRenderer
*/
protected $pageRenderer;
/**
* Constructor deprecates the class
*/
public function __construct()
{
trigger_error(
'Class BaseScriptClass is deprecated and will be removed in TYPO3 v10.0',
E_USER_DEPRECATED
);
}
/**
* Initializes the backend module by setting internal variables, initializing the menu.
*
* @see menuConfig()
*/
public function init()
{
// Name might be set from outside
if (!$this->MCONF['name']) {
$this->MCONF = $GLOBALS['MCONF'];
}
$this->id = (int)GeneralUtility::_GP('id');
$this->CMD = GeneralUtility::_GP('CMD');
$this->perms_clause = $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW);
$this->menuConfig();
$this->handleExternalFunctionValue();
}
/**
* Initializes the internal MOD_MENU array setting and unsetting items based on various conditions. It also merges in external menu items from the global array TBE_MODULES_EXT (see mergeExternalItems())
* Then MOD_SETTINGS array is cleaned up (see \TYPO3\CMS\Backend\Utility\BackendUtility::getModuleData()) so it contains only valid values. It's also updated with any SET[] values submitted.
* Also loads the modTSconfig internal variable.
*
* @see init(), $MOD_MENU, $MOD_SETTINGS, \TYPO3\CMS\Backend\Utility\BackendUtility::getModuleData(), mergeExternalItems()
*/
public function menuConfig()
{
// Page / user TSconfig settings and blinding of menu-items
$this->modTSconfig['properties'] = BackendUtility::getPagesTSconfig($this->id)['mod.'][$this->MCONF['name'] . '.'] ?? [];
$this->MOD_MENU['function'] = $this->mergeExternalItems($this->MCONF['name'], 'function', $this->MOD_MENU['function']);
$blindActions = $this->modTSconfig['properties']['menu.']['function.'] ?? [];
foreach ($blindActions as $key => $value) {
if (!$value && array_key_exists($key, $this->MOD_MENU['function'])) {
unset($this->MOD_MENU['function'][$key]);
}
}
$this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, GeneralUtility::_GP('SET'), $this->MCONF['name'], $this->modMenu_type, $this->modMenu_dontValidateList, $this->modMenu_setDefaultList);
}
/**
* Merges menu items from global array $TBE_MODULES_EXT
*
* @param string $modName Module name for which to find value
* @param string $menuKey Menu key, eg. 'function' for the function menu.
* @param array $menuArr The part of a MOD_MENU array to work on.
* @return array Modified array part.
* @internal
* @see \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::insertModuleFunction(), menuConfig()
*/
public function mergeExternalItems($modName, $menuKey, $menuArr)
{
$mergeArray = $GLOBALS['TBE_MODULES_EXT'][$modName]['MOD_MENU'][$menuKey];
if (is_array($mergeArray)) {
foreach ($mergeArray as $k => $v) {
if (((string)$v['ws'] === '' || $this->getBackendUser()->workspace === 0 && GeneralUtility::inList($v['ws'], 'online')) || $this->getBackendUser()->workspace === -1 && GeneralUtility::inList($v['ws'], 'offline') || $this->getBackendUser()->workspace > 0 && GeneralUtility::inList($v['ws'], 'custom')) {
$menuArr[$k] = $this->getLanguageService()->sL($v['title']);
}
}
}
return $menuArr;
}
/**
* Loads $this->extClassConf with the configuration for the CURRENT function of the menu.
*
* @param string $MM_key The key to MOD_MENU for which to fetch configuration. 'function' is default since it is first and foremost used to get information per "extension object" (I think that is what its called)
* @param string $MS_value The value-key to fetch from the config array. If NULL (default) MOD_SETTINGS[$MM_key] will be used. This is useful if you want to force another function than the one defined in MOD_SETTINGS[function]. Call this in init() function of your Script Class: handleExternalFunctionValue('function', $forcedSubModKey)
* @see getExternalItemConfig(), init()
*/
public function handleExternalFunctionValue($MM_key = 'function', $MS_value = null)
{
if ($MS_value === null) {
$MS_value = $this->MOD_SETTINGS[$MM_key];
}
$this->extClassConf = $this->getExternalItemConfig($this->MCONF['name'], $MM_key, $MS_value);
}
/**
* Returns configuration values from the global variable $TBE_MODULES_EXT for the module given.
* For example if the module is named "web_info" and the "function" key ($menuKey) of MOD_SETTINGS is "stat" ($value) then you will have the values of $TBE_MODULES_EXT['webinfo']['MOD_MENU']['function']['stat'] returned.
*
* @param string $modName Module name
* @param string $menuKey Menu key, eg. "function" for the function menu. See $this->MOD_MENU
* @param string $value Optionally the value-key to fetch from the array that would otherwise have been returned if this value was not set. Look source...
* @return mixed The value from the TBE_MODULES_EXT array.
* @see handleExternalFunctionValue()
*/
public function getExternalItemConfig($modName, $menuKey, $value = '')
{
if (isset($GLOBALS['TBE_MODULES_EXT'][$modName])) {
return (string)$value !== '' ? $GLOBALS['TBE_MODULES_EXT'][$modName]['MOD_MENU'][$menuKey][$value] : $GLOBALS['TBE_MODULES_EXT'][$modName]['MOD_MENU'][$menuKey];
}
return null;
}
/**
* Creates an instance of the class found in $this->extClassConf['name'] in $this->extObj if any (this should hold three keys, "name", "path" and "title" if a "Function menu module" tries to connect...)
* This value in extClassConf might be set by an extension (in an ext_tables/ext_localconf file) which thus "connects" to a module.
* The array $this->extClassConf is set in handleExternalFunctionValue() based on the value of MOD_SETTINGS[function]
* If an instance is created it is initiated with $this passed as value and $this->extClassConf as second argument. Further the $this->MOD_SETTING is cleaned up again after calling the init function.
*
* @see handleExternalFunctionValue(), \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::insertModuleFunction(), $extObj
*/
public function checkExtObj()
{
if (is_array($this->extClassConf) && $this->extClassConf['name']) {
$this->extObj = GeneralUtility::makeInstance($this->extClassConf['name']);
$this->extObj->init($this, $this->extClassConf);
// Re-write:
$this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, GeneralUtility::_GP('SET'), $this->MCONF['name'], $this->modMenu_type, $this->modMenu_dontValidateList, $this->modMenu_setDefaultList);
}
}
/**
* Calls the checkExtObj function in sub module if present.
*/
public function checkSubExtObj()
{
if (is_object($this->extObj)) {
$this->extObj->checkExtObj();
}
}
/**
* Calls the 'header' function inside the "Function menu module" if present.
* A header function might be needed to add JavaScript or other stuff in the head. This can't be done in the main function because the head is already written.
*/
public function extObjHeader()
{
if (is_callable([$this->extObj, 'head'])) {
$this->extObj->head();
}
}
/**
* Calls the 'main' function inside the "Function menu module" if present
*/
public function extObjContent()
{
if ($this->extObj === null) {
$flashMessage = GeneralUtility::makeInstance(
FlashMessage::class,
$this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang.xlf:no_modules_registered'),
$this->getLanguageService()->getLL('title'),
FlashMessage::ERROR
);
/** @var \TYPO3\CMS\Core\Messaging\FlashMessageService $flashMessageService */
$flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
/** @var \TYPO3\CMS\Core\Messaging\FlashMessageQueue $defaultFlashMessageQueue */
$defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
$defaultFlashMessageQueue->enqueue($flashMessage);
} else {
$this->extObj->pObj = $this;
if (is_callable([$this->extObj, 'main'])) {
$this->content .= $this->extObj->main();
}
}
}
/**
* Return the content of the 'main' function inside the "Function menu module" if present
*
* @return string
*/
public function getExtObjContent()
{
$savedContent = $this->content;
$this->content = '';
$this->extObjContent();
$newContent = $this->content;
$this->content = $savedContent;
return $newContent;
}
/**
* Returns the Language Service
* @return LanguageService
*/
protected function getLanguageService()
{
return $GLOBALS['LANG'];
}
/**
* Returns the Backend User
* @return BackendUserAuthentication
*/
protected function getBackendUser()
{
return $GLOBALS['BE_USER'];
}
/**
* @return PageRenderer
*/
protected function getPageRenderer()
{
if ($this->pageRenderer === null) {
$this->pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
}
return $this->pageRenderer;
}
}
@@ -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'];
}
}
@@ -0,0 +1,66 @@
<?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\Backend\Domain\Model\Module\BackendModule;
/**
* Model for the module storage
*/
class ModuleStorage implements \TYPO3\CMS\Core\SingletonInterface
{
/**
* @var \SplObjectStorage
*/
protected $entries;
/**
* construct
*/
public function __construct()
{
$this->entries = new \SplObjectStorage();
}
/**
* Set Entries
*
* @param \SplObjectStorage $entries
*/
public function setEntries($entries)
{
$this->entries = $entries;
}
/**
* Get Entries
*
* @return \SplObjectStorage
*/
public function getEntries()
{
return $this->entries;
}
/**
* Attach Entry
*
* @param BackendModule $entry
*/
public function attachEntry(BackendModule $entry)
{
$this->entries->attach($entry);
}
}