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,296 @@
<?php
namespace TYPO3\CMS\Backend\Domain\Model\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!
*/
/**
* Model for menu entries
*/
class BackendModule
{
/**
* @var string
*/
protected $title = '';
/**
* @var string
*/
protected $name = '';
/**
* @var string
*/
protected $icon = '';
/**
* @var string
*/
protected $link = '';
/**
* @var string
*/
protected $onClick = '';
/**
* @var string
*/
protected $description = '';
/**
* @var string
*/
protected $navigationComponentId = '';
/**
* @var string
*/
protected $navigationFrameScript = '';
/**
* @var string
*/
protected $navigationFrameScriptParameters = '';
/**
* @var \SplObjectStorage
*/
protected $children;
/**
* @var bool
*/
protected $collapsed = false;
/**
* construct
*/
public function __construct()
{
$this->children = new \SplObjectStorage();
}
/**
* Set children
*
* @param \SplObjectStorage $children
*/
public function setChildren($children)
{
$this->children = $children;
}
/**
* Get children
*
* @return \SplObjectStorage
*/
public function getChildren()
{
return $this->children;
}
/**
* Add Child
*
* @param \TYPO3\CMS\Backend\Domain\Model\Module\BackendModule $child
*/
public function addChild(\TYPO3\CMS\Backend\Domain\Model\Module\BackendModule $child)
{
$this->children->attach($child);
}
/**
* Set icon
*
* @param string $icon
*/
public function setIcon($icon)
{
$this->icon = $icon;
}
/**
* Get icon
*
* @return string
*/
public function getIcon()
{
return $this->icon;
}
/**
* Set name
*
* @param string $name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* Get name
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Set title
*
* @param string $title
*/
public function setTitle($title)
{
$this->title = $title;
}
/**
* Get Title
*
* @return string
*/
public function getTitle()
{
return $this->title;
}
/**
* Set Link
*
* @param string $link
*/
public function setLink($link)
{
$this->link = $link;
}
/**
* Get Link
*
* @return string
*/
public function getLink()
{
return $this->link;
}
/**
* Set Description
*
* @param string $description
*/
public function setDescription($description)
{
$this->description = $description;
}
/**
* Get Description
*
* @return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Set Navigation Component Id
*
* @param string $navigationComponentId
*/
public function setNavigationComponentId($navigationComponentId)
{
$this->navigationComponentId = $navigationComponentId;
}
/**
* Get Navigation Component Id
*
* @return string
*/
public function getNavigationComponentId()
{
return $this->navigationComponentId;
}
/**
* @param string $navigationFrameScript
*/
public function setNavigationFrameScript($navigationFrameScript)
{
$this->navigationFrameScript = $navigationFrameScript;
}
/**
* @return string
*/
public function getNavigationFrameScript()
{
return $this->navigationFrameScript;
}
/**
* @param string $navigationFrameScriptParameters
*/
public function setNavigationFrameScriptParameters($navigationFrameScriptParameters)
{
$this->navigationFrameScriptParameters = $navigationFrameScriptParameters;
}
/**
* @return string
*/
public function getNavigationFrameScriptParameters()
{
return $this->navigationFrameScriptParameters;
}
/**
* Set onClick
*
* @param string $onClick
*/
public function setOnClick($onClick)
{
$this->onClick = $onClick;
}
/**
* Get onClick
*
* @return string
*/
public function getOnClick()
{
return $this->onClick;
}
public function setCollapsed(bool $collapsed): void
{
$this->collapsed = $collapsed;
}
public function getCollapsed(): bool
{
return $this->collapsed;
}
}
@@ -0,0 +1,264 @@
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Backend\Domain\Repository\Localization;
/*
* 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 Doctrine\DBAL\Driver\Statement;
use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Repository for record localizations
*
* @internal
*/
class LocalizationRepository
{
/**
* @var TranslationConfigurationProvider
*/
protected $translationConfigurationProvider;
public function __construct(TranslationConfigurationProvider $translationConfigurationProvider = null)
{
$this->translationConfigurationProvider = $translationConfigurationProvider ?? GeneralUtility::makeInstance(TranslationConfigurationProvider::class);
}
/**
* Fetch the language from which the records in a certain language were initially localized
*
* @param int $pageId
* @param int $localizedLanguage
* @return array
*/
public function fetchOriginLanguage(int $pageId, int $localizedLanguage): array
{
$queryBuilder = $this->getQueryBuilderWithWorkspaceRestriction('tt_content');
$queryBuilder->select('tt_content_orig.sys_language_uid')
->from('tt_content')
->join(
'tt_content',
'tt_content',
'tt_content_orig',
$queryBuilder->expr()->eq(
'tt_content.l10n_source',
$queryBuilder->quoteIdentifier('tt_content_orig.uid')
)
)
->where(
$queryBuilder->expr()->eq(
'tt_content.pid',
$queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT)
),
$queryBuilder->expr()->eq(
'tt_content.sys_language_uid',
$queryBuilder->createNamedParameter($localizedLanguage, \PDO::PARAM_INT)
),
$queryBuilder->expr()->neq(
'tt_content_orig.sys_language_uid',
$queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
)
)
->groupBy('tt_content_orig.sys_language_uid');
$this->getAllowedLanguageConstraintsForBackendUser($pageId, $queryBuilder, $this->getBackendUser(), 'tt_content_orig');
return $queryBuilder->execute()->fetch() ?: [];
}
/**
* Returns number of localized records in given page and language
* Records which were added to the language directly (not through translation) are not counted.
*
* @param int $pageId
* @param int $languageId
* @return int
*/
public function getLocalizedRecordCount(int $pageId, int $languageId): int
{
$queryBuilder = $this->getQueryBuilderWithWorkspaceRestriction('tt_content');
$rowCount = $queryBuilder->count('uid')
->from('tt_content')
->where(
$queryBuilder->expr()->eq(
'sys_language_uid',
$queryBuilder->createNamedParameter($languageId, \PDO::PARAM_INT)
),
$queryBuilder->expr()->eq(
'pid',
$queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT)
),
$queryBuilder->expr()->neq(
'l10n_source',
$queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
)
)
->execute()
->fetchColumn(0);
return (int)$rowCount;
}
/**
* Fetch all available languages
*
* @param int $pageId
* @param int $languageId
* @return array
*/
public function fetchAvailableLanguages(int $pageId, int $languageId): array
{
$queryBuilder = $this->getQueryBuilderWithWorkspaceRestriction('tt_content');
$queryBuilder->select('sys_language_uid')
->from('tt_content')
->where(
$queryBuilder->expr()->eq(
'pid',
$queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT)
),
$queryBuilder->expr()->neq(
'sys_language_uid',
$queryBuilder->createNamedParameter($languageId, \PDO::PARAM_INT)
)
)
->groupBy('sys_language_uid');
$this->getAllowedLanguageConstraintsForBackendUser($pageId, $queryBuilder, $this->getBackendUser());
$languages = $queryBuilder->execute()->fetchAll();
return $languages ?: [];
}
/**
* Builds additional query constraints to exclude hidden languages and
* limit a backend user to its allowed languages (unless the user is an admin)
*
* @param int $pageId
* @param QueryBuilder $queryBuilder
* @param BackendUserAuthentication $backendUser
* @param string $alias
*/
protected function getAllowedLanguageConstraintsForBackendUser(int $pageId, QueryBuilder $queryBuilder, BackendUserAuthentication $backendUser, string $alias = ''): void
{
if ($backendUser->isAdmin()) {
return;
}
// This always includes default language
$allowedLanguages = $this->translationConfigurationProvider->getSystemLanguages($pageId);
$queryBuilder->andWhere(
$queryBuilder->expr()->in(
($alias === '' ? '' : ($alias . '.')) . 'sys_language_uid',
$queryBuilder->createNamedParameter(array_keys($allowedLanguages), Connection::PARAM_INT_ARRAY)
)
);
}
/**
* Get records for copy process
*
* @param int $pageId
* @param int $destLanguageId
* @param int $languageId
* @param string $fields
* @return Statement
*/
public function getRecordsToCopyDatabaseResult(int $pageId, int $destLanguageId, int $languageId, string $fields = '*'): Statement
{
$originalUids = [];
// Get original uid of existing elements triggered language
$queryBuilder = $this->getQueryBuilderWithWorkspaceRestriction('tt_content');
$originalUidsStatement = $queryBuilder
->select('l10n_source')
->from('tt_content')
->where(
$queryBuilder->expr()->eq(
'sys_language_uid',
$queryBuilder->createNamedParameter($destLanguageId, \PDO::PARAM_INT)
),
$queryBuilder->expr()->eq(
'pid',
$queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT)
)
)
->execute();
while ($origUid = $originalUidsStatement->fetchColumn(0)) {
$originalUids[] = (int)$origUid;
}
$queryBuilder->select(...GeneralUtility::trimExplode(',', $fields, true))
->from('tt_content')
->where(
$queryBuilder->expr()->eq(
'sys_language_uid',
$queryBuilder->createNamedParameter($languageId, \PDO::PARAM_INT)
),
$queryBuilder->expr()->eq(
'pid',
$queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT)
)
)
->orderBy('sorting');
if (!empty($originalUids)) {
$queryBuilder
->andWhere(
$queryBuilder->expr()->notIn(
'uid',
$queryBuilder->createNamedParameter($originalUids, Connection::PARAM_INT_ARRAY)
)
);
}
return $queryBuilder->execute();
}
/**
* Returns the current BE user.
*
* @return BackendUserAuthentication
*/
protected function getBackendUser(): BackendUserAuthentication
{
return $GLOBALS['BE_USER'];
}
/**
* Get a QueryBuilder for the given table with preconfigured restrictions
* to not retrieve workspace placeholders or deleted records.
*
* @param string $tableName
* @return QueryBuilder
*/
protected function getQueryBuilderWithWorkspaceRestriction(string $tableName): QueryBuilder
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($tableName);
$queryBuilder->getRestrictions()
->removeAll()
->add(GeneralUtility::makeInstance(DeletedRestriction::class))
->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
return $queryBuilder;
}
}
@@ -0,0 +1,373 @@
<?php
namespace TYPO3\CMS\Backend\Domain\Repository\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\Module\ModuleLoader;
use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Imaging\IconRegistry;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Repository for backend module menu
* compiles all data from $GLOBALS[TBE_MODULES]
*/
class BackendModuleRepository implements \TYPO3\CMS\Core\SingletonInterface
{
/**
* @var \TYPO3\CMS\Backend\Module\ModuleStorage
*/
protected $moduleStorage;
/**
* Constructs the module menu and gets the Singleton instance of the menu
*/
public function __construct()
{
$this->moduleStorage = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Module\ModuleStorage::class);
$rawData = $this->getRawModuleMenuData();
$this->convertRawModuleDataToModuleMenuObject($rawData);
$this->createMenuEntriesForTbeModulesExt();
}
/**
* loads all module information in the module storage
*
* @param array $excludeGroupNames
* @return \SplObjectStorage
*/
public function loadAllowedModules(array $excludeGroupNames = [])
{
if (empty($excludeGroupNames)) {
return $this->moduleStorage->getEntries();
}
$modules = new \SplObjectStorage();
foreach ($this->moduleStorage->getEntries() as $moduleGroup) {
if (!in_array($moduleGroup->getName(), $excludeGroupNames, true)) {
if ($moduleGroup->getChildren()->count() > 0) {
$modules->attach($moduleGroup);
}
}
}
return $modules;
}
/**
* @param string $groupName
* @return \SplObjectStorage|false
**/
public function findByGroupName($groupName = '')
{
foreach ($this->moduleStorage->getEntries() as $moduleGroup) {
if ($moduleGroup->getName() === $groupName) {
return $moduleGroup;
}
}
return false;
}
/**
* Finds a module menu entry by name
*
* @param string $name
* @return \TYPO3\CMS\Backend\Domain\Model\Module\BackendModule|bool
*/
public function findByModuleName($name)
{
$entries = $this->moduleStorage->getEntries();
$entry = $this->findByModuleNameInGivenEntries($name, $entries);
return $entry;
}
/**
* Finds a module menu entry by name in a given storage
*
* @param string $name
* @param \SplObjectStorage $entries
* @return \TYPO3\CMS\Backend\Domain\Model\Module\BackendModule|bool
*/
public function findByModuleNameInGivenEntries($name, \SplObjectStorage $entries)
{
foreach ($entries as $entry) {
if ($entry->getName() === $name) {
return $entry;
}
$children = $entry->getChildren();
if (!empty($children)) {
$childRecord = $this->findByModuleNameInGivenEntries($name, $children);
if ($childRecord !== false) {
return $childRecord;
}
}
}
return false;
}
/**
* Creates the module menu object structure from the raw data array
*
* @param array $rawModuleData
*/
protected function convertRawModuleDataToModuleMenuObject(array $rawModuleData)
{
foreach ($rawModuleData as $module) {
$entry = $this->createEntryFromRawData($module);
if (isset($module['subitems']) && !empty($module['subitems'])) {
foreach ($module['subitems'] as $subitem) {
$subEntry = $this->createEntryFromRawData($subitem);
$entry->addChild($subEntry);
}
}
$this->moduleStorage->attachEntry($entry);
}
}
/**
* Creates a menu entry object from an array
*
* @param array $module
* @return \TYPO3\CMS\Backend\Domain\Model\Module\BackendModule
*/
protected function createEntryFromRawData(array $module)
{
/** @var \TYPO3\CMS\Backend\Domain\Model\Module\BackendModule $entry */
$entry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Domain\Model\Module\BackendModule::class);
if (!empty($module['name']) && is_string($module['name'])) {
$entry->setName($module['name']);
}
if (!empty($module['title']) && is_string($module['title'])) {
$entry->setTitle($this->getLanguageService()->sL($module['title']));
}
if (!empty($module['onclick']) && is_string($module['onclick'])) {
$entry->setOnClick($module['onclick']);
}
if (!empty($module['link']) && is_string($module['link'])) {
$entry->setLink($module['link']);
} elseif (empty($module['link']) && !empty($module['path']) && is_string($module['path'])) {
$entry->setLink($module['path']);
}
if (!empty($module['description']) && is_string($module['description'])) {
$entry->setDescription($this->getLanguageService()->sL($module['description']));
}
if (!empty($module['icon'])) {
$entry->setIcon($module['icon']);
}
if (!empty($module['navigationComponentId']) && is_string($module['navigationComponentId'])) {
$entry->setNavigationComponentId($module['navigationComponentId']);
}
if (!empty($module['navigationFrameScript']) && is_string($module['navigationFrameScript'])) {
$entry->setNavigationFrameScript($module['navigationFrameScript']);
} elseif (!empty($module['parentNavigationFrameScript']) && is_string($module['parentNavigationFrameScript'])) {
$entry->setNavigationFrameScript($module['parentNavigationFrameScript']);
}
if (!empty($module['navigationFrameScriptParam']) && is_string($module['navigationFrameScriptParam'])) {
$entry->setNavigationFrameScriptParameters($module['navigationFrameScriptParam']);
}
$moduleMenuState = json_decode($this->getBackendUser()->uc['modulemenu'] ?? '{}', true);
$entry->setCollapsed(isset($moduleMenuState[$module['name']]));
return $entry;
}
/**
* Creates the "third level" menu entries (submodules for the info module for
* example) from the TBE_MODULES_EXT array
*/
protected function createMenuEntriesForTbeModulesExt()
{
foreach ($GLOBALS['TBE_MODULES_EXT'] ?? [] as $mainModule => $tbeModuleExt) {
list($main) = explode('_', $mainModule);
$mainEntry = $this->findByModuleName($main);
if ($mainEntry === false) {
continue;
}
$subEntries = $mainEntry->getChildren();
if (empty($subEntries)) {
continue;
}
$matchingSubEntry = $this->findByModuleName($mainModule);
if ($matchingSubEntry !== false) {
if (isset($tbeModuleExt['MOD_MENU']) && isset($tbeModuleExt['MOD_MENU']['function'])) {
foreach ($tbeModuleExt['MOD_MENU']['function'] as $subModule) {
$entry = $this->createEntryFromRawData($subModule);
$matchingSubEntry->addChild($entry);
}
}
}
}
}
/**
* loads the module menu from the moduleloader based on $GLOBALS['TBE_MODULES']
* and compiles an array with all the data needed for menu etc.
*
* @return array
*/
public function getRawModuleMenuData()
{
// Loads the backend modules available for the logged in user.
/** @var ModuleLoader $moduleLoader */
$moduleLoader = GeneralUtility::makeInstance(ModuleLoader::class);
$moduleLoader->observeWorkspaces = true;
$moduleLoader->load($GLOBALS['TBE_MODULES']);
$loadedModules = $moduleLoader->modules;
$modules = [];
// Unset modules that are meant to be hidden from the menu.
$loadedModules = $this->removeHiddenModules($loadedModules);
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
$dummyScript = (string)$uriBuilder->buildUriFromRoute('dummy');
foreach ($loadedModules as $moduleName => $moduleData) {
$moduleLink = '';
if (!is_array($moduleData['sub'])) {
$moduleLink = $moduleData['script'];
}
$moduleLink = GeneralUtility::resolveBackPath($moduleLink);
$moduleLabels = $moduleLoader->getLabelsForModule($moduleName);
$moduleKey = 'modmenu_' . $moduleName;
$modules[$moduleKey] = [
'name' => $moduleName,
'title' => $moduleLabels['title'],
'onclick' => 'top.goToModule(' . GeneralUtility::quoteJSvalue($moduleName) . ');',
'icon' => $this->getModuleIcon($moduleKey, $moduleData),
'link' => $moduleLink,
'description' => $moduleLabels['shortdescription']
];
if (!is_array($moduleData['sub']) && $moduleData['script'] !== $dummyScript) {
// Work around for modules with own main entry, but being self the only submodule
$modules[$moduleKey]['subitems'][$moduleKey] = [
'name' => $moduleName,
'title' => $moduleLabels['title'],
'onclick' => 'top.goToModule(' . GeneralUtility::quoteJSvalue($moduleName) . ');',
'icon' => $this->getModuleIcon($moduleKey, $moduleData),
'link' => $moduleLink,
'originalLink' => $moduleLink,
'description' => $moduleLabels['shortdescription'],
'navigationFrameScript' => null,
'navigationFrameScriptParam' => null,
'navigationComponentId' => null
];
} elseif (is_array($moduleData['sub'])) {
foreach ($moduleData['sub'] as $submoduleName => $submoduleData) {
if (isset($submoduleData['script'])) {
$submoduleLink = GeneralUtility::resolveBackPath($submoduleData['script']);
} else {
$submoduleLink = (string)$uriBuilder->buildUriFromRoute($submoduleData['name']);
}
$submoduleKey = $moduleName . '_' . $submoduleName;
$submoduleLabels = $moduleLoader->getLabelsForModule($submoduleKey);
$submoduleDescription = $submoduleLabels['shortdescription'];
$originalLink = $submoduleLink;
$navigationFrameScript = $submoduleData['navFrameScript'];
$modules[$moduleKey]['subitems'][$submoduleKey] = [
'name' => $moduleName . '_' . $submoduleName,
'title' => $submoduleLabels['title'],
'onclick' => 'top.goToModule(' . GeneralUtility::quoteJSvalue($moduleName . '_' . $submoduleName) . ');',
'icon' => $this->getModuleIcon($moduleKey, $submoduleData),
'link' => $submoduleLink,
'originalLink' => $originalLink,
'description' => $submoduleDescription,
'navigationFrameScript' => $navigationFrameScript,
'navigationFrameScriptParam' => $submoduleData['navFrameScriptParam'],
'navigationComponentId' => $submoduleData['navigationComponentId']
];
// if the main module has a navframe script, inherit to the submodule,
// but only if it is not disabled explicitly (option is set to FALSE)
if ($moduleData['navFrameScript'] && $submoduleData['inheritNavigationComponentFromMainModule'] !== false) {
$modules[$moduleKey]['subitems'][$submoduleKey]['parentNavigationFrameScript'] = $moduleData['navFrameScript'];
}
}
}
}
return $modules;
}
/**
* Reads User configuration from options.hideModules and removes
* modules accordingly.
*
* @param array $loadedModules
* @return array
*/
protected function removeHiddenModules($loadedModules)
{
$userTsConfig = $this->getBackendUser()->getTSConfig();
// Hide modules if set in userTS.
$hiddenMainModules = GeneralUtility::trimExplode(',', $userTsConfig['options.']['hideModules'] ?? '', true);
foreach ($hiddenMainModules as $hiddenMainModule) {
unset($loadedModules[$hiddenMainModule]);
}
// Hide sub-modules if set in userTS.
$hiddenModules = $userTsConfig['options.']['hideModules.'] ?? [];
if (is_array($hiddenModules)) {
foreach ($hiddenModules as $mainModuleName => $subModules) {
$hiddenSubModules = GeneralUtility::trimExplode(',', $subModules, true);
foreach ($hiddenSubModules as $hiddenSubModule) {
unset($loadedModules[$mainModuleName]['sub'][$hiddenSubModule]);
}
}
}
return $loadedModules;
}
/**
* gets the module icon
*
* @param string $moduleKey Module key
* @param array $moduleData the compiled data associated with it
* @return string Icon data, either sprite or <img> tag
*/
protected function getModuleIcon($moduleKey, $moduleData)
{
$iconIdentifier = !empty($moduleData['iconIdentifier'])
? $moduleData['iconIdentifier']
: 'module-icon-' . $moduleKey;
$iconRegistry = GeneralUtility::makeInstance(IconRegistry::class);
if ($iconRegistry->isRegistered($iconIdentifier)) {
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
return $iconFactory->getIcon($iconIdentifier)->render();
}
return '';
}
/**
* Return language service instance
*
* @return LanguageService
*/
protected function getLanguageService()
{
return $GLOBALS['LANG'];
}
/**
* @return BackendUserAuthentication
*/
protected function getBackendUser(): BackendUserAuthentication
{
return $GLOBALS['BE_USER'];
}
}
@@ -0,0 +1,434 @@
<?php
namespace TYPO3\CMS\Backend\Domain\Repository;
/*
* 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\Controller\HelpController;
use TYPO3\CMS\Backend\Module\ModuleLoader;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Type\File\ImageInfo;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
/**
* Table manual repository for csh manual handling
* @internal This class is a specific Backend repository implementation and is not considered part of the Public TYPO3 API.
*/
class TableManualRepository
{
/**
* Get the manual of the given table
*
* @param string $table
* @return array the manual for a TCA table, see getItem() for details
*/
public function getTableManual($table)
{
$parts = [];
// Load descriptions for table $table
$this->getLanguageService()->loadSingleTableDescription($table);
if (is_array($GLOBALS['TCA_DESCR'][$table]['columns']) && $this->checkAccess('tables_select', $table)) {
// Reserved for header of table
$parts[0] = '';
// Traverse table columns as listed in TCA_DESCR
foreach ($GLOBALS['TCA_DESCR'][$table]['columns'] as $field => $_) {
if (!$this->isExcludableField($table, $field) || $this->checkAccess('non_exclude_fields', $table . ':' . $field)) {
if (!$field) {
// Header
$parts[0] = $this->getItem($table, '', true);
} else {
// Field
$parts[] = $this->getItem($table, $field, true);
}
}
}
if (!$parts[0]) {
unset($parts[0]);
}
}
return $parts;
}
/**
* Get a single manual
*
* @param string $table table name
* @param string $field field name
* @return array
*/
public function getSingleManual($table, $field)
{
$this->getLanguageService()->loadSingleTableDescription($table);
return $this->getItem($table, $field);
}
/**
* Get TOC sections
*
* @param int $mode e.g. HelpController::TOC_ONLY
* @return array
*/
public function getSections($mode)
{
// Initialize
$cshKeys = array_flip(array_keys($GLOBALS['TCA_DESCR']));
$tcaKeys = array_keys($GLOBALS['TCA']);
$outputSections = [];
$tocArray = [];
// TYPO3 Core Features
$lang = $this->getLanguageService();
$lang->loadSingleTableDescription('xMOD_csh_corebe');
$this->renderTableOfContentItem($mode, 'xMOD_csh_corebe', 'core', $outputSections, $tocArray, $cshKeys);
// Backend Modules
$loadModules = GeneralUtility::makeInstance(ModuleLoader::class);
$loadModules->load($GLOBALS['TBE_MODULES']);
foreach ($loadModules->modules as $mainMod => $info) {
$cshKey = '_MOD_' . $mainMod;
if ($cshKeys[$cshKey]) {
$lang->loadSingleTableDescription($cshKey);
$this->renderTableOfContentItem($mode, $cshKey, 'modules', $outputSections, $tocArray, $cshKeys);
}
if (is_array($info['sub'])) {
foreach ($info['sub'] as $subMod => $subInfo) {
$cshKey = '_MOD_' . $mainMod . '_' . $subMod;
if ($cshKeys[$cshKey]) {
$lang->loadSingleTableDescription($cshKey);
$this->renderTableOfContentItem($mode, $cshKey, 'modules', $outputSections, $tocArray, $cshKeys);
}
}
}
}
// Database Tables
foreach ($tcaKeys as $table) {
// Load descriptions for table $table
$lang->loadSingleTableDescription($table);
if (is_array($GLOBALS['TCA_DESCR'][$table]['columns']) && $this->checkAccess('tables_select', $table)) {
$this->renderTableOfContentItem($mode, $table, 'tables', $outputSections, $tocArray, $cshKeys);
}
}
foreach ($cshKeys as $cshKey => $value) {
// Extensions
if (GeneralUtility::isFirstPartOfStr($cshKey, 'xEXT_') && !isset($GLOBALS['TCA'][$cshKey])) {
$lang->loadSingleTableDescription($cshKey);
$this->renderTableOfContentItem($mode, $cshKey, 'extensions', $outputSections, $tocArray, $cshKeys);
}
// Other
if (!GeneralUtility::isFirstPartOfStr($cshKey, '_MOD_') && !isset($GLOBALS['TCA'][$cshKey])) {
$lang->loadSingleTableDescription($cshKey);
$this->renderTableOfContentItem($mode, $cshKey, 'other', $outputSections, $tocArray, $cshKeys);
}
}
if ($mode === HelpController::TOC_ONLY) {
return $tocArray;
}
return [
'toc' => $tocArray,
'content' => $outputSections
];
}
/**
* Creates a TOC list element and renders corresponding HELP content if "renderALL" mode is set.
*
* @param int $mode Mode
* @param string $table CSH key / Table name
* @param string $tocCat TOC category keyword: "core", "modules", "tables", "other
* @param array $outputSections Array for accumulation of rendered HELP Content (in "renderALL" mode). Passed by reference!
* @param array $tocArray TOC array; Here TOC index elements are created. Passed by reference!
* @param array $CSHkeys CSH keys array. Every item rendered will be unset in this array so finally we can see what CSH keys are not processed yet. Passed by reference!
*/
protected function renderTableOfContentItem($mode, $table, $tocCat, &$outputSections, &$tocArray, &$CSHkeys)
{
$tocArray[$tocCat][$table] = $this->getTableFieldLabel($table);
if (!$mode) {
// Render full manual right here!
$outputSections[$table]['content'] = $this->getTableManual($table);
if (!$outputSections[$table]) {
unset($outputSections[$table]);
}
}
// Unset CSH key
unset($CSHkeys[$table]);
}
/**
* Returns composite label for table/field
*
* @param string $key CSH key / table name
* @param string $field Sub key / field name
* @param string $mergeToken Token to merge the two strings with
* @return string Labels joined with merge token
* @see getTableFieldNames()
*/
protected function getTableFieldLabel($key, $field = '', $mergeToken = ': ')
{
// Get table / field parts
list($tableName, $fieldName) = $this->getTableFieldNames($key, $field);
// Create label
return $this->getLanguageService()->sL($tableName) . ($field ? $mergeToken . rtrim(trim($this->getLanguageService()->sL($fieldName)), ':') : '');
}
/**
* Returns labels for a given field in a given structure
*
* @param string $key CSH key / table name
* @param string $field Sub key / field name
* @return array Table and field labels in a numeric array
*/
protected function getTableFieldNames($key, $field)
{
$this->getLanguageService()->loadSingleTableDescription($key);
// Define the label for the key
if (!empty($GLOBALS['TCA_DESCR'][$key]['columns']['']['alttitle'])) {
// If there's an alternative title, use it
$keyName = $GLOBALS['TCA_DESCR'][$key]['columns']['']['alttitle'];
} elseif (isset($GLOBALS['TCA'][$key])) {
// Otherwise, if it's a table, use its title
$keyName = $GLOBALS['TCA'][$key]['ctrl']['title'];
} else {
// If no title was found, make sure to remove any "_MOD_"
$keyName = preg_replace('/^_MOD_/', '', $key);
}
// Define the label for the field
$fieldName = $field;
if (!empty($GLOBALS['TCA_DESCR'][$key]['columns'][$field]['alttitle'])) {
// If there's an alternative title, use it
$fieldName = $GLOBALS['TCA_DESCR'][$key]['columns'][$field]['alttitle'];
} elseif (!empty($GLOBALS['TCA'][$key]['columns'][$field])) {
// Otherwise, if it's a table, use its title
$fieldName = $GLOBALS['TCA'][$key]['columns'][$field]['label'];
}
return [$keyName, $fieldName];
}
/**
* Gets a single $table/$field information piece
* If $anchors is set, then seeAlso references to the same table will be page-anchors, not links.
*
* @param string $table CSH key / table name
* @param string $field Sub key / field name
* @param bool $anchors If anchors is to be shown.
* @return array with the information
*/
protected function getItem($table, $field, $anchors = false)
{
if (!empty($table)) {
$field = !empty($field) ? $field : '';
$setup = $GLOBALS['TCA_DESCR'][$table]['columns'][$field];
return [
'table' => $table,
'field' => $field,
'configuration' => $setup,
'headerLine' => $this->getTableFieldLabel($table, $field),
'content' => !empty($setup['description']) ? $setup['description'] : '',
'images' => !empty($setup['image']) ? $this->getImages($setup['image'], $setup['image_descr']) : [],
'seeAlso' => !empty($setup['seeAlso']) ? $this->getSeeAlsoLinks($setup['seeAlso'], $anchors ? $table : '') : '',
];
}
return [];
}
/**
* Get see-also links
*
* @param string $value See-also input codes
* @param string $anchorTable If $anchorTable is set to a tablename, then references to this table will be made as anchors, not URLs.
* @return array See-also links
*/
protected function getSeeAlsoLinks($value, $anchorTable = '')
{
// Split references by comma or linebreak
$items = preg_split('/[,' . LF . ']/', $value);
$lines = [];
foreach ($items as $itemValue) {
$itemValue = trim($itemValue);
if ($itemValue) {
$reference = GeneralUtility::trimExplode(':', $itemValue);
$referenceUrl = GeneralUtility::trimExplode('|', $itemValue);
if (strpos($referenceUrl[1], 'http') === 0) {
// URL reference
$lines[] = [
'url' => $referenceUrl[1],
'title' => $referenceUrl[0],
'target' => '_blank'
];
} elseif (strpos($referenceUrl[1], 'FILE:') === 0) {
// File reference
$fileName = GeneralUtility::getFileAbsFileName(substr($referenceUrl[1], 5));
if ($fileName && @is_file($fileName)) {
$fileName = '../' . PathUtility::stripPathSitePrefix($fileName);
$lines[] = [
'url' => $fileName,
'title' => $referenceUrl[0],
'target' => '_blank'
];
}
} else {
// Table reference
$table = !empty($reference[0]) ? $reference[0] : '';
$field = !empty($reference[1]) ? $reference[1] : '';
$accessAllowed = true;
// Check if table exists and current user can access it
if (!empty($table)) {
$accessAllowed = !$this->getTableSetup($table) || $this->checkAccess('tables_select', $table);
}
// Check if field exists and is excludable or user can access it
if ($accessAllowed && !empty($field)) {
$accessAllowed = !$this->isExcludableField($table, $field) || $this->checkAccess('non_exclude_fields', $table . ':' . $field);
}
// Check read access
if ($accessAllowed && isset($GLOBALS['TCA_DESCR'][$table])) {
// Make see-also link
$label = $this->getTableFieldLabel($table, $field, ' / ');
if ($anchorTable && $table === $anchorTable) {
$lines[] = [
'url' => '#' . rawurlencode(implode('.', $reference)),
'title' => $label,
];
} else {
$lines[] = [
'internal' => true,
'arguments' => [
'table' => $table,
'field' => $field,
'action' => 'detail',
],
'title' => $label
];
}
}
}
}
}
return $lines;
}
/**
* Check if given table / field is excludable
*
* @param string $table The table
* @param string $field The field
* @return bool TRUE if given field is excludable
*/
protected function isExcludableField($table, $field)
{
$fieldSetup = $this->getFieldSetup($table, $field);
if (!empty($fieldSetup)) {
return !empty($fieldSetup['exclude']);
}
return false;
}
/**
* Returns an array of images with description
*
* @param string $images Image file reference (list of)
* @param string $descriptions Description string (divided for each image by line break)
* @return array
*/
protected function getImages($images, $descriptions)
{
$imageData = [];
// Splitting
$imgArray = GeneralUtility::trimExplode(',', $images, true);
if (!empty($imgArray)) {
$descrArray = explode(LF, $descriptions, count($imgArray));
foreach ($imgArray as $k => $image) {
$descriptions = $descrArray[$k];
$absImagePath = GeneralUtility::getFileAbsFileName($image);
if ($absImagePath && @is_file($absImagePath)) {
$imgFile = PathUtility::stripPathSitePrefix($absImagePath);
$imageInfo = GeneralUtility::makeInstance(ImageInfo::class, $absImagePath);
if ($imageInfo->getWidth()) {
$imageData[] = [
'image' => $imgFile,
'description' => $descriptions
];
}
}
}
}
return $imageData;
}
/**
* Returns the setup for given table
*
* @param string $table The table
* @return array The table setup
*/
protected function getTableSetup($table)
{
if (!empty($table) && !empty($GLOBALS['TCA'][$table])) {
return $GLOBALS['TCA'][$table];
}
return [];
}
/**
* Returns the setup for given table / field
*
* @param string $table The table
* @param string $field The field
* @param bool $allowEmptyField Allow empty field
* @return array The field setup
*/
protected function getFieldSetup($table, $field, $allowEmptyField = false)
{
$tableSetup = $this->getTableSetup($table);
if (!empty($tableSetup) && (!empty($field) || $allowEmptyField) && !empty($tableSetup['columns'][$field])) {
return $tableSetup['columns'][$field];
}
return [];
}
/**
* Check if current backend user has access to given identifier
*
* @param string $type The type
* @param string $identifier The search string in access list
* @return bool TRUE if the user has access
*/
protected function checkAccess($type, $identifier)
{
if (!empty($type) && !empty($identifier)) {
return $this->getBackendUser()->check($type, $identifier);
}
return false;
}
/**
* Returns the current BE user.
*
* @return BackendUserAuthentication
*/
protected function getBackendUser(): BackendUserAuthentication
{
return $GLOBALS['BE_USER'];
}
/**
* Returns LanguageService
*
* @return \TYPO3\CMS\Core\Localization\LanguageService
*/
protected function getLanguageService()
{
return $GLOBALS['LANG'];
}
}