TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Hooks;
|
||||
|
||||
use TYPO3\CMS\Core\Crypto\Random;
|
||||
use TYPO3\CMS\Core\DataHandling\DataHandler;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\MathUtility;
|
||||
|
||||
/**
|
||||
* DataHandler hook to ensure that a be_user always has a username + password set if newly-created.
|
||||
*
|
||||
* @internal This class is a hook implementation and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class BackendUserPasswordCheck
|
||||
{
|
||||
protected Random $random;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->random = GeneralUtility::makeInstance(Random::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $incomingFieldArray
|
||||
* @param string $table
|
||||
* @param string $id
|
||||
*/
|
||||
public function processDatamap_preProcessFieldArray(&$incomingFieldArray, $table, $id, DataHandler $dataHandler)
|
||||
{
|
||||
// Not within be_users
|
||||
if ($table !== 'be_users') {
|
||||
return;
|
||||
}
|
||||
// Existing record, nothing to change
|
||||
if (MathUtility::canBeInterpretedAsInteger($id)) {
|
||||
return;
|
||||
}
|
||||
if ($dataHandler->isImporting) {
|
||||
return;
|
||||
}
|
||||
if (!isset($incomingFieldArray['password']) || (string)$incomingFieldArray['password'] === '') {
|
||||
$incomingFieldArray['password'] = $this->random->generateRandomPassword([
|
||||
'lowerCaseCharacters' => true,
|
||||
'upperCaseCharacters' => true,
|
||||
'digitCharacters' => true,
|
||||
'specialCharacters' => true,
|
||||
]);
|
||||
}
|
||||
if (!isset($incomingFieldArray['username']) || (string)$incomingFieldArray['username'] === '') {
|
||||
$incomingFieldArray['username'] = 'autogenerated-' . md5($id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Hooks;
|
||||
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Configuration\Exception\SiteConfigurationWriteException;
|
||||
use TYPO3\CMS\Core\Configuration\SiteWriter;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\DataHandling\DataHandler;
|
||||
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
|
||||
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
|
||||
use TYPO3\CMS\Core\Http\NormalizedParams;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageService;
|
||||
use TYPO3\CMS\Core\Site\SiteFinder;
|
||||
use TYPO3\CMS\Core\SysLog\Action\Site as SiteAction;
|
||||
use TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification;
|
||||
use TYPO3\CMS\Core\SysLog\Type;
|
||||
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Hook for creating a basic site configuration for new pages on root level.
|
||||
*
|
||||
* @internal This class is a hook implementation and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
class CreateSiteConfiguration
|
||||
{
|
||||
/**
|
||||
* @var int[]
|
||||
*/
|
||||
protected $allowedPageTypes = [
|
||||
PageRepository::DOKTYPE_DEFAULT,
|
||||
PageRepository::DOKTYPE_LINK,
|
||||
PageRepository::DOKTYPE_SHORTCUT,
|
||||
];
|
||||
|
||||
public function processDatamap_afterDatabaseOperations(string $status, string $table, $id, array $fieldValues, DataHandler $dataHandler): void
|
||||
{
|
||||
/**
|
||||
* Take action only on
|
||||
* - new records
|
||||
* - pages table
|
||||
* - live workspace
|
||||
* - resolved uids
|
||||
* - pages on root level OR with is_siteroot set
|
||||
* - pages in default language
|
||||
* - non-versioned records
|
||||
* - allowed doktypes
|
||||
* - not bulk importing things via CLI
|
||||
*/
|
||||
if ($status !== 'new'
|
||||
|| $table !== 'pages'
|
||||
|| $dataHandler->BE_USER->workspace > 0
|
||||
|| !isset($dataHandler->substNEWwithIDs[$id])
|
||||
|| (int)($fieldValues['l10n_parent'] ?? 0) !== 0
|
||||
|| ((int)$fieldValues['pid'] !== 0 && !($fieldValues['is_siteroot'] ?? false))
|
||||
|| (isset($fieldValues['t3ver_oid']) && (int)$fieldValues['t3ver_oid'] > 0)
|
||||
|| !in_array((int)$fieldValues['doktype'], $this->allowedPageTypes, true)
|
||||
|| $dataHandler->isImporting
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$uid = (int)$dataHandler->substNEWwithIDs[$id];
|
||||
$this->generateSiteConfigurationForRootPage($uid, $dataHandler->BE_USER);
|
||||
}
|
||||
|
||||
protected function generateSiteConfigurationForRootPage(int $pageId, BackendUserAuthentication $backendUser): void
|
||||
{
|
||||
$entryPoint = 'autogenerated-' . $pageId;
|
||||
$siteIdentifier = $entryPoint . '-' . md5((string)$pageId);
|
||||
|
||||
if (!$this->siteExistsByRootPageId($pageId)) {
|
||||
$siteWriter = GeneralUtility::makeInstance(SiteWriter::class);
|
||||
$normalizedParams = $this->getNormalizedParams();
|
||||
$basePrefix = Environment::isCli() ? $normalizedParams->getSitePath() : $normalizedParams->getSiteUrl();
|
||||
try {
|
||||
$siteWriter->createNewBasicSite(
|
||||
$siteIdentifier,
|
||||
$pageId,
|
||||
$basePrefix . $entryPoint
|
||||
);
|
||||
$backendUser->writelog(Type::SITE, SiteAction::CREATE, SystemLogErrorClassification::MESSAGE, null, 'Site configuration \'%s\' was automatically created for new root page (%s).', [$siteIdentifier, $pageId], 'site');
|
||||
$this->updateSlugForPage($pageId);
|
||||
} catch (SiteConfigurationWriteException $e) {
|
||||
$flashMessage = new FlashMessage($e->getMessage(), '', ContextualFeedbackSeverity::WARNING, true);
|
||||
$flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
|
||||
$defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
|
||||
$defaultFlashMessageQueue->enqueue($flashMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function getNormalizedParams(): NormalizedParams
|
||||
{
|
||||
$normalizedParams = null;
|
||||
$serverParams = Environment::isCli() ? ['HTTP_HOST' => 'localhost'] : $_SERVER;
|
||||
if (isset($GLOBALS['TYPO3_REQUEST'])) {
|
||||
$normalizedParams = $GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams');
|
||||
$serverParams = $GLOBALS['TYPO3_REQUEST']->getServerParams();
|
||||
}
|
||||
|
||||
if (!$normalizedParams instanceof NormalizedParams) {
|
||||
$normalizedParams = NormalizedParams::createFromServerParams($serverParams);
|
||||
}
|
||||
|
||||
return $normalizedParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the slug of the given pageId by spinning up a new DataHandler instance.
|
||||
*/
|
||||
protected function updateSlugForPage(int $pageId): void
|
||||
{
|
||||
$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
|
||||
$dataMap = [
|
||||
'pages' => [
|
||||
$pageId => [
|
||||
'slug' => '',
|
||||
],
|
||||
],
|
||||
];
|
||||
$dataHandler->start($dataMap, []);
|
||||
$dataHandler->process_datamap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a site exists by its root page. Sets up a new SiteFinder instance
|
||||
*
|
||||
* @param int $rootPageId the page ID (default language)
|
||||
*/
|
||||
protected function siteExistsByRootPageId(int $rootPageId): bool
|
||||
{
|
||||
try {
|
||||
GeneralUtility::makeInstance(SiteFinder::class)->getSiteByRootPageId($rootPageId);
|
||||
} catch (SiteNotFoundException $e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Hooks;
|
||||
|
||||
use TYPO3\CMS\Core\DataHandling\DataHandler;
|
||||
use TYPO3\CMS\Core\Session\SessionManager;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
class DestroySessionHook
|
||||
{
|
||||
/**
|
||||
* If a fe_users' or be_users' password is updated, clear all sessions.
|
||||
*/
|
||||
public function processDatamap_postProcessFieldArray(string $status, string $table, string|int $id, array $fieldArray, DataHandler $dataHandler): void
|
||||
{
|
||||
if ($table !== 'be_users' && $table !== 'fe_users') {
|
||||
return;
|
||||
}
|
||||
if ($status !== 'update') {
|
||||
return;
|
||||
}
|
||||
if (!isset($fieldArray['password']) || (string)$fieldArray['password'] === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$sessionManager = GeneralUtility::makeInstance(SessionManager::class);
|
||||
if ($table === 'be_users') {
|
||||
// Destroy BE user sessions for backend user
|
||||
$backend = $sessionManager->getSessionBackend('BE');
|
||||
$sessionManager->invalidateAllSessionsByUserId($backend, (int)$id, $GLOBALS['BE_USER']);
|
||||
}
|
||||
if ($table === 'fe_users') {
|
||||
// Destroy any FE user sessions for the given user
|
||||
$backend = $sessionManager->getSessionBackend('FE');
|
||||
$sessionManager->invalidateAllSessionsByUserId($backend, (int)$id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Hooks;
|
||||
|
||||
use TYPO3\CMS\Core\DataHandling\DataHandler;
|
||||
|
||||
/**
|
||||
* Guard to only allow modifications of pages.TSconfig for admin users.
|
||||
*/
|
||||
class PagesTsConfigGuard
|
||||
{
|
||||
public function processDatamap_preProcessFieldArray(
|
||||
array &$incomingFieldArray,
|
||||
string $table,
|
||||
string $id,
|
||||
DataHandler $dataHandler
|
||||
): void {
|
||||
if ($table === 'pages' && !$dataHandler->BE_USER->isAdmin()) {
|
||||
unset($incomingFieldArray['TSconfig']);
|
||||
unset($incomingFieldArray['tsconfig_includes']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Hooks;
|
||||
|
||||
use TYPO3\CMS\Core\DataHandling\DataHandler;
|
||||
use TYPO3\CMS\Core\SysLog\Action\Database as SystemLogDatabaseAction;
|
||||
use TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification;
|
||||
|
||||
/**
|
||||
* DataHandler hook to ensure that only system maintainers can change details of system maintainers.
|
||||
*
|
||||
* @internal This class is a hook implementation and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
final class SystemMaintainerAllowanceCheck
|
||||
{
|
||||
public function processDatamap_postProcessFieldArray(string $status, string $table, int|string $id, array &$fieldArray, DataHandler $dataHandler): void
|
||||
{
|
||||
if ($table !== 'be_users' || $status !== 'update' || empty($fieldArray)) {
|
||||
return;
|
||||
}
|
||||
// Do not allow a non system maintainer admin to change details of system maintainers.
|
||||
$systemMaintainers = array_map(intval(...), $GLOBALS['TYPO3_CONF_VARS']['SYS']['systemMaintainers'] ?? []);
|
||||
// False if current user is not in system maintainer list or if switch to user mode is active
|
||||
$isCurrentUserSystemMaintainer = $dataHandler->BE_USER->isSystemMaintainer();
|
||||
$isTargetUserInSystemMaintainerList = in_array((int)$id, $systemMaintainers, true);
|
||||
if (!$isCurrentUserSystemMaintainer && $isTargetUserInSystemMaintainerList) {
|
||||
$fieldArray = [];
|
||||
$dataHandler->log(
|
||||
$table,
|
||||
(int)$id,
|
||||
SystemLogDatabaseAction::UPDATE,
|
||||
null,
|
||||
SystemLogErrorClassification::SECURITY_NOTICE,
|
||||
'Only system maintainers can change details of other system maintainers. The values have not been updated.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Hooks;
|
||||
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
|
||||
|
||||
/**
|
||||
* Various display conditions to check for e.g. installed extensions or configuration settings used.
|
||||
*
|
||||
* @internal This class is a hook implementation and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
readonly class TcaDisplayConditions
|
||||
{
|
||||
/**
|
||||
* Check if an extension is loaded.
|
||||
*/
|
||||
public function isExtensionInstalled(array $parameters): bool
|
||||
{
|
||||
$extension = $parameters['conditionParameters'][0] ?? '';
|
||||
if (!empty($extension)) {
|
||||
return ExtensionManagementUtility::isLoaded($extension);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current record is the current backend user
|
||||
*
|
||||
* IMPORTANT: This only works for the be_users table.
|
||||
*
|
||||
* @param array $parameters
|
||||
*/
|
||||
public function isRecordCurrentUser(array $parameters): bool
|
||||
{
|
||||
$backendUser = $this->getBackendUser();
|
||||
$isCurrentUser = (int)($parameters['record']['uid'] ?? 0) === (int)$backendUser->getUserId();
|
||||
return strtolower($parameters['conditionParameters'][0] ?? 'true') !== 'true' ? !$isCurrentUser : $isCurrentUser;
|
||||
}
|
||||
|
||||
protected function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,466 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Hooks;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
|
||||
use TYPO3\CMS\Backend\Module\ModuleProvider;
|
||||
use TYPO3\CMS\Core\Configuration\FlexForm\Exception\InvalidIdentifierException;
|
||||
use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
|
||||
use TYPO3\CMS\Core\DataHandling\PageDoktypeRegistry;
|
||||
use TYPO3\CMS\Core\DataHandling\TableColumnType;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Imaging\IconRegistry;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Schema\Capability\RootLevelCapability;
|
||||
use TYPO3\CMS\Core\Schema\Capability\TcaSchemaCapability;
|
||||
use TYPO3\CMS\Core\Schema\Field\CategoryFieldType;
|
||||
use TYPO3\CMS\Core\Schema\TcaSchema;
|
||||
use TYPO3\CMS\Core\Schema\TcaSchemaFactory;
|
||||
|
||||
/**
|
||||
* Various items processor functions, mainly used for special select fields in `be_users` and `be_groups`
|
||||
*
|
||||
* @internal This class is a hook implementation and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
#[Autoconfigure(public: true)]
|
||||
readonly class TcaItemsProcessorFunctions
|
||||
{
|
||||
public function __construct(
|
||||
private IconFactory $iconFactory,
|
||||
private IconRegistry $iconRegistry,
|
||||
private ModuleProvider $moduleProvider,
|
||||
private FlexFormTools $flexFormTools,
|
||||
private TcaSchemaFactory $tcaSchemaFactory,
|
||||
private PageDoktypeRegistry $pageDoktypeRegistry,
|
||||
) {}
|
||||
|
||||
public function populateAvailableTables(array &$fieldDefinition): void
|
||||
{
|
||||
/** @var TcaSchema $schema */
|
||||
foreach ($this->tcaSchemaFactory->all() as $tableName => $schema) {
|
||||
// Hide "admin only" tables
|
||||
if ($schema->hasCapability(TcaSchemaCapability::AccessAdminOnly)) {
|
||||
continue;
|
||||
}
|
||||
$icon = $this->iconFactory->mapRecordTypeToIconIdentifier($tableName, [], $this->tcaSchemaFactory->get($tableName));
|
||||
$fieldDefinition['items'][] = ['label' => $schema->getTitle(), 'value' => $tableName, 'icon' => $icon];
|
||||
}
|
||||
}
|
||||
|
||||
public function populateAvailablePageTypes(array &$fieldDefinition): void
|
||||
{
|
||||
foreach ($this->pageDoktypeRegistry->getAllDoktypes() as $pageType) {
|
||||
if (!$pageType->getValue()) {
|
||||
continue;
|
||||
}
|
||||
$icon = $this->iconFactory->mapRecordTypeToIconIdentifier('pages', ['doktype' => $pageType->getValue()], $this->tcaSchemaFactory->get('pages'));
|
||||
$fieldDefinition['items'][] = ['label' => $pageType->getLabel(), 'value' => $pageType->getValue(), 'icon' => $icon];
|
||||
}
|
||||
}
|
||||
|
||||
public function populateAvailableUserModules(array &$fieldDefinition): void
|
||||
{
|
||||
$modules = $this->moduleProvider->getUserModules();
|
||||
if ($modules === []) {
|
||||
return;
|
||||
}
|
||||
$languageService = $this->getLanguageService();
|
||||
foreach ($modules as $identifier => $module) {
|
||||
// Item configuration
|
||||
$label = $languageService->sL($module->getTitle());
|
||||
$parentModule = $module->getParentModule();
|
||||
while ($parentModule) {
|
||||
$label = $languageService->sL($parentModule->getTitle()) . ' > ' . $label;
|
||||
$parentModule = $parentModule->getParentModule();
|
||||
}
|
||||
$help = null;
|
||||
if ($module->getDescription()) {
|
||||
$help = [
|
||||
'title' => $languageService->sL($module->getShortDescription()),
|
||||
'description' => $languageService->sL($module->getDescription()),
|
||||
];
|
||||
}
|
||||
$fieldDefinition['items'][] = [
|
||||
'label' => $label,
|
||||
'value' => $identifier,
|
||||
'icon' => $module->getIconIdentifier(),
|
||||
'description' => $help,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
public function populateExcludeFields(array &$fieldDefinition): void
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
foreach ($this->getGroupedExcludeFields() as $excludeFieldGroup) {
|
||||
$table = $excludeFieldGroup['table'] ?? '';
|
||||
$origin = $excludeFieldGroup['origin'] ?? '';
|
||||
$schema = $this->tcaSchemaFactory->get($table);
|
||||
// If the field comes from a FlexForm, the syntax is more complex
|
||||
if ($origin === 'flexForm') {
|
||||
// The field comes from a plugins FlexForm
|
||||
// Add header if not yet set for plugin section
|
||||
$sectionHeader = $excludeFieldGroup['sectionHeader'] ?? '';
|
||||
if (!isset($fieldDefinition['items'][$sectionHeader])) {
|
||||
// there is no icon handling for plugins - we take the icon from the table
|
||||
$icon = $this->iconFactory->mapRecordTypeToIconIdentifier($table, [], $this->tcaSchemaFactory->get($table));
|
||||
$fieldDefinition['items'][$sectionHeader] = ['label' => $sectionHeader, 'value' => '--div--', 'icon' => $icon];
|
||||
}
|
||||
} elseif (!isset($fieldDefinition['items'][$table])) {
|
||||
// Add header if not yet set for table
|
||||
$icon = $this->iconFactory->mapRecordTypeToIconIdentifier($table, [], $this->tcaSchemaFactory->get($table));
|
||||
$fieldDefinition['items'][$table] = ['label' => $schema->getTitle(), 'value' => '--div--', 'icon' => $icon];
|
||||
}
|
||||
$fullField = $excludeFieldGroup['fullField'] ?? '';
|
||||
$fieldName = $excludeFieldGroup['fieldName'] ?? '';
|
||||
$label = $origin === 'flexForm'
|
||||
? ($excludeFieldGroup['fieldLabel'] ?? '')
|
||||
: $languageService->sL($schema->getField($fieldName)->getLabel());
|
||||
// Item configuration:
|
||||
$fieldDefinition['items'][] = [
|
||||
'label' => rtrim($label, ':') . ' (' . $fieldName . ')',
|
||||
'value' => $table . ':' . $fullField,
|
||||
'icon' => 'empty-empty',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
public function populateExplicitAuthValues(array &$fieldDefinition): void
|
||||
{
|
||||
// Traverse grouped field values:
|
||||
foreach ($this->getGroupedExplicitAuthFieldValues() as $groupKey => $tableFields) {
|
||||
if (empty($tableFields['items']) || !is_array($tableFields['items'])) {
|
||||
continue;
|
||||
}
|
||||
// Add header:
|
||||
$fieldDefinition['items'][] = [
|
||||
'label' => $tableFields['tableFieldLabel'] ?? '',
|
||||
'value' => '--div--',
|
||||
];
|
||||
// Traverse options for this field:
|
||||
foreach ($tableFields['items'] as $itemValue => $itemContent) {
|
||||
$fieldDefinition['items'][] = [
|
||||
'label' => $itemContent,
|
||||
'value' => $groupKey . ':' . preg_replace('/[:|,]/', '', (string)$itemValue),
|
||||
'icon' => 'status-status-permission-granted',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function populateCustomPermissionOptions(array &$fieldDefinition): void
|
||||
{
|
||||
$customOptions = $GLOBALS['TYPO3_CONF_VARS']['BE']['customPermOptions'] ?? [];
|
||||
if (!is_array($customOptions) || $customOptions === []) {
|
||||
return;
|
||||
}
|
||||
$languageService = $this->getLanguageService();
|
||||
foreach ($customOptions as $customOptionsKey => $customOptionsValue) {
|
||||
if (empty($customOptionsValue['items']) || !is_array($customOptionsValue['items'])) {
|
||||
continue;
|
||||
}
|
||||
// Add header:
|
||||
$fieldDefinition['items'][] = [
|
||||
'label' => $languageService->sL($customOptionsValue['header'] ?? ''),
|
||||
'value' => '--div--',
|
||||
];
|
||||
// Traverse items:
|
||||
foreach ($customOptionsValue['items'] as $itemKey => $itemConfig) {
|
||||
$icon = 'empty-empty';
|
||||
$helpText = '';
|
||||
if (!empty($itemConfig[1]) && $this->iconRegistry->isRegistered($itemConfig[1])) {
|
||||
// Use icon identifier when registered
|
||||
$icon = $itemConfig[1];
|
||||
}
|
||||
if (!empty($itemConfig[2])) {
|
||||
$helpText = $languageService->sL($itemConfig[2]);
|
||||
}
|
||||
$fieldDefinition['items'][] = [
|
||||
'label' => $languageService->sL($itemConfig[0] ?? ''),
|
||||
'value' => $customOptionsKey . ':' . preg_replace('/[:|,]/', '', (string)$itemKey),
|
||||
'icon' => $icon,
|
||||
'description' => $helpText,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates a list of category fields (with the defined relationships) for the given table
|
||||
*/
|
||||
public function populateAvailableCategoryFields(array &$fieldDefinition): void
|
||||
{
|
||||
$table = (string)($fieldDefinition['config']['itemsProcConfig']['table'] ?? '');
|
||||
if ($table === '') {
|
||||
throw new \UnexpectedValueException('No table to search for category fields given.', 1627565458);
|
||||
}
|
||||
|
||||
if (!$this->tcaSchemaFactory->has($table)) {
|
||||
throw new \RuntimeException('Given table ' . $table . ' does not define any valid schema to search for category fields.', 1627565459);
|
||||
}
|
||||
|
||||
// Only category fields with the "manyToMany" relationship are allowed by default.
|
||||
// This can however be changed using the "allowedRelationships" itemsProcConfig.
|
||||
$allowedRelationships = $fieldDefinition['config']['itemsProcConfig']['allowedRelationships'] ?? false;
|
||||
if (!is_array($allowedRelationships) || $allowedRelationships === []) {
|
||||
$allowedRelationships = ['manyToMany'];
|
||||
}
|
||||
|
||||
$schema = $this->tcaSchemaFactory->get($table);
|
||||
|
||||
// Loop on all table columns to find category fields
|
||||
foreach ($schema->getFields() as $fieldName => $fieldConfig) {
|
||||
/** @var CategoryFieldType $fieldConfig */
|
||||
if (!$fieldConfig->isType(TableColumnType::CATEGORY)) {
|
||||
continue;
|
||||
}
|
||||
if (!in_array($fieldConfig->getConfiguration()['relationship'] ?? '', $allowedRelationships, true)) {
|
||||
continue;
|
||||
}
|
||||
$fieldDefinition['items'][] = [
|
||||
'label' => $this->getLanguageService()->sL($fieldConfig->getLabel()),
|
||||
'value' => $fieldName,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with the exclude fields as defined in TCA and FlexForms
|
||||
* Used for listing the exclude fields in be_groups forms.
|
||||
*
|
||||
* @return array Array of arrays with excludeFields (fieldName, table:fieldName) from TCA
|
||||
* and FlexForms (fieldName, table:extKey;sheetName;fieldName)
|
||||
*/
|
||||
protected function getGroupedExcludeFields(): array
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$excludeFieldGroups = [];
|
||||
|
||||
// Fetch translations for table names
|
||||
$tableToTranslation = [];
|
||||
// All TCA keys
|
||||
foreach ($this->tcaSchemaFactory->all() as $table => $schema) {
|
||||
$tableToTranslation[$table] = $schema->getTitle($languageService->sL(...)) ?: $table;
|
||||
}
|
||||
// Sort by translations
|
||||
asort($tableToTranslation);
|
||||
foreach ($tableToTranslation as $table => $translatedTable) {
|
||||
$excludeFieldGroup = [];
|
||||
$schema = $this->tcaSchemaFactory->get($table);
|
||||
|
||||
// All field names configured and not restricted to admins
|
||||
$rootLevelCapability = $schema->getCapability(TcaSchemaCapability::RestrictionRootLevel);
|
||||
// Skip this table if it’s rootlevel-only and the rootlevel restriction applies
|
||||
// (unless ignoreRootLevelRestriction is enabled).
|
||||
|
||||
if (!$rootLevelCapability->shallIgnoreRootLevelRestriction() && $rootLevelCapability->getRootLevelType() === RootLevelCapability::TYPE_ONLY_ON_ROOTLEVEL) {
|
||||
continue;
|
||||
}
|
||||
if ($schema->hasCapability(TcaSchemaCapability::AccessAdminOnly)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($schema->getFields() as $fieldName => $fieldDefinition) {
|
||||
// Only show fields that can be excluded for editors, or are hidden for non-admins
|
||||
if ($fieldDefinition->supportsAccessControl() && $fieldDefinition->getDisplayConditions() !== 'HIDE_FOR_NON_ADMINS') {
|
||||
// Get human-readable names of fields
|
||||
$translatedField = $languageService->sL($fieldDefinition->getLabel());
|
||||
// Add entry, key 'labels' needed for sorting
|
||||
$excludeFieldGroup[] = [
|
||||
'labels' => $translatedTable . ':' . $translatedField,
|
||||
'sectionHeader' => $translatedTable,
|
||||
'table' => $table,
|
||||
'tableField' => $fieldName,
|
||||
'fieldName' => $fieldName,
|
||||
'fullField' => $fieldName,
|
||||
'fieldLabel' => $translatedField,
|
||||
'origin' => 'tca',
|
||||
];
|
||||
}
|
||||
}
|
||||
// All FlexForm fields
|
||||
$flexFormArray = $this->getRegisteredFlexForms((string)$table);
|
||||
foreach ($flexFormArray as $tableField => $flexForms) {
|
||||
$flexFieldLabel = '';
|
||||
// Get all sheets
|
||||
foreach ($flexForms as $extIdent => $extConf) {
|
||||
if ($schema->hasSubSchema((string)$extIdent)) {
|
||||
$fieldDefinition = $schema->getSubSchema((string)$extIdent)->getField($tableField);
|
||||
} else {
|
||||
$fieldDefinition = $schema->getField($tableField);
|
||||
}
|
||||
if ($fieldDefinition->getLabel() !== '') {
|
||||
$flexFieldLabel = $languageService->sL($fieldDefinition->getLabel());
|
||||
}
|
||||
if (empty($extConf['sheets']) || !is_array($extConf['sheets'])) {
|
||||
continue;
|
||||
}
|
||||
// Get all fields in sheet
|
||||
foreach ($extConf['sheets'] as $sheetName => $sheet) {
|
||||
if (empty($sheet['ROOT']['el']) || !is_array($sheet['ROOT']['el'])) {
|
||||
continue;
|
||||
}
|
||||
foreach ($sheet['ROOT']['el'] as $pluginFieldName => $field) {
|
||||
// Use only fields that have exclude flag set
|
||||
if (empty($field['exclude'])) {
|
||||
continue;
|
||||
}
|
||||
$fieldLabel = !empty($field['label']) ? $languageService->sL($field['label']) : $pluginFieldName;
|
||||
$excludeFieldGroup[] = [
|
||||
'labels' => trim($translatedTable . ' ' . $flexFieldLabel . ' ' . $extIdent, ': ') . ':' . $fieldLabel,
|
||||
'sectionHeader' => trim($translatedTable . ' ' . $flexFieldLabel . ' ' . $extIdent, ':'),
|
||||
'table' => $table,
|
||||
'tableField' => $tableField,
|
||||
'extIdent' => $extIdent,
|
||||
'fieldName' => $pluginFieldName,
|
||||
'fullField' => $tableField . ';' . $extIdent . ';' . $sheetName . ';' . $pluginFieldName,
|
||||
'fieldLabel' => $fieldLabel,
|
||||
'origin' => 'flexForm',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Sort fields by the translated value
|
||||
if (!empty($excludeFieldGroup)) {
|
||||
usort($excludeFieldGroup, static function (array $array1, array $array2) {
|
||||
$array1 = reset($array1);
|
||||
$array2 = reset($array2);
|
||||
if (is_string($array1) && is_string($array2)) {
|
||||
return strcasecmp($array1, $array2);
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
$excludeFieldGroups = array_merge($excludeFieldGroups, $excludeFieldGroup);
|
||||
}
|
||||
}
|
||||
|
||||
return $excludeFieldGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns FlexForm data structures it finds. Used in select "special" for be_groups
|
||||
* to set "exclude" flags for single flex form fields.
|
||||
*
|
||||
* This only finds flex forms registered in 'ds' config sections - default and record type specific.
|
||||
* This does not resolve other sophisticated flex form data structure references.
|
||||
*
|
||||
* @todo: This approach is limited and doesn't find everything. It works for casual tt_content plugins, though:
|
||||
* @todo: The data structure identifier determination depends on data row, but we don't have all rows at hand here.
|
||||
* @todo: The code thus "guesses" some standard data structure identifier scenarios and tries to resolve those.
|
||||
* @todo: This guessing can not be solved in a good way. A general registry of "all" possible data structures is
|
||||
* @todo: probably not wanted, since that wouldn't work for truly dynamic DS calculations. Probably the only
|
||||
* @todo: thing we could do here is a hook to allow extensions declaring specific data structures to
|
||||
* @todo: allow backend admins to set exclude flags for certain fields in those cases.
|
||||
*
|
||||
* @param string $table Table to handle
|
||||
* @return array Data structures
|
||||
*/
|
||||
protected function getRegisteredFlexForms(string $table): array
|
||||
{
|
||||
if (!$this->tcaSchemaFactory->has($table)) {
|
||||
return [];
|
||||
}
|
||||
$schema = $this->tcaSchemaFactory->get($table);
|
||||
$flexForms = [];
|
||||
// Get all flex fields and add the default data structure
|
||||
foreach ($schema->getFields() as $field => $fieldDefinition) {
|
||||
if ($fieldDefinition->getType() !== TableColumnType::FLEX->value) {
|
||||
continue;
|
||||
}
|
||||
$flexForms[$field] = [];
|
||||
// Default data structure
|
||||
try {
|
||||
$flexForms[$field]['default'] = $this->flexFormTools->parseDataStructureByIdentifier(json_encode([
|
||||
'type' => 'tca',
|
||||
'tableName' => $table,
|
||||
'fieldName' => $field,
|
||||
'dataStructureKey' => 'default',
|
||||
]), $schema);
|
||||
} catch (InvalidIdentifierException $e) {
|
||||
// Skip default on error
|
||||
}
|
||||
}
|
||||
|
||||
// If flex fields exist and the table supports sub schemata, add specific data strcuturs for those sub schemas
|
||||
if ($flexForms !== [] && $schema->supportsSubSchema()) {
|
||||
foreach ($schema->getSubSchemata() as $recordType => $subSchema) {
|
||||
foreach (array_keys($flexForms) as $fieldName) {
|
||||
if ($subSchema->hasField($fieldName)) {
|
||||
try {
|
||||
$flexForms[$fieldName][$recordType] = $this->flexFormTools->parseDataStructureByIdentifier(json_encode([
|
||||
'type' => 'tca',
|
||||
'tableName' => $table,
|
||||
'fieldName' => $fieldName,
|
||||
'dataStructureKey' => $recordType,
|
||||
]), $schema);
|
||||
} catch (InvalidIdentifierException $e) {
|
||||
// Skip record type specific config on error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $flexForms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with explicit allow fields.
|
||||
* Used for listing these field/value pairs in be_groups forms
|
||||
*
|
||||
* @return array Array with information from all of $GLOBALS['TCA']
|
||||
*/
|
||||
protected function getGroupedExplicitAuthFieldValues(): array
|
||||
{
|
||||
$languageService = $this->getLanguageService();
|
||||
$allowOptions = [];
|
||||
foreach ($this->tcaSchemaFactory->all() as $table => $schema) {
|
||||
// All field names configured:
|
||||
foreach ($schema->getFields() as $field => $fieldDefinition) {
|
||||
$fieldConfig = $fieldDefinition->getConfiguration();
|
||||
if (($fieldConfig['type'] ?? '') !== 'select'
|
||||
|| ($fieldConfig['authMode'] ?? false) !== 'explicitAllow'
|
||||
|| empty($fieldConfig['items'])
|
||||
|| !is_array($fieldConfig['items'])
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
// Get Human Readable names of fields and table:
|
||||
$allowOptions[$table . ':' . $field]['tableFieldLabel']
|
||||
= $schema->getTitle($languageService->sL(...)) . ': '
|
||||
. $languageService->sL($fieldDefinition->getLabel());
|
||||
|
||||
foreach ($fieldConfig['items'] as $item) {
|
||||
$itemIdentifier = (string)($item['value'] ?? '');
|
||||
// Values '' and '--div--' are not controlled by this setting.
|
||||
if ($itemIdentifier === '' || $itemIdentifier === '--div--') {
|
||||
continue;
|
||||
}
|
||||
$allowOptions[$table . ':' . $field]['items'][$itemIdentifier] = $languageService->sL($item['label'] ?? '');
|
||||
}
|
||||
}
|
||||
}
|
||||
return $allowOptions;
|
||||
}
|
||||
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Hooks;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
|
||||
use TYPO3\CMS\Core\DataHandling\DataHandler;
|
||||
use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
|
||||
use TYPO3\CMS\Core\Resource\Index\Indexer;
|
||||
use TYPO3\CMS\Core\Resource\ResourceFactory;
|
||||
use TYPO3\CMS\Core\SysLog\Action\File as SystemLogFileAction;
|
||||
use TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Hook for updating the file index entry after a new sys_file_metadata record is created, e.g. manually via FormEngine
|
||||
*
|
||||
* @internal This class is a hook implementation and is not part of the TYPO3 Core API.
|
||||
*/
|
||||
#[Autoconfigure(public: true)]
|
||||
readonly class UpdateFileIndexEntry
|
||||
{
|
||||
public function __construct(
|
||||
private ResourceFactory $resourceFactory,
|
||||
) {}
|
||||
|
||||
public function processDatamap_afterDatabaseOperations(string $status, string $table, $id, array $fieldValues, DataHandler $dataHandler): void
|
||||
{
|
||||
/**
|
||||
* Take action only on
|
||||
* - new records
|
||||
* - sys_file_metadata table
|
||||
* - live workspace
|
||||
* - resolved uids
|
||||
* - resolved file uid
|
||||
* - record on root level
|
||||
* - record in default language
|
||||
* - non-versioned records
|
||||
* - not bulk importing things via CLI
|
||||
*/
|
||||
if ($status !== 'new'
|
||||
|| $table !== 'sys_file_metadata'
|
||||
|| $dataHandler->BE_USER->workspace > 0
|
||||
|| !isset($dataHandler->substNEWwithIDs[$id])
|
||||
|| !($fieldValues['file'] ?? false)
|
||||
|| (int)$fieldValues['l10n_parent'] !== 0
|
||||
|| (int)$fieldValues['pid'] !== 0
|
||||
|| (isset($fieldValues['t3ver_oid']) && (int)$fieldValues['t3ver_oid'] > 0)
|
||||
|| $dataHandler->isImporting
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$uid = (int)$dataHandler->substNEWwithIDs[$id];
|
||||
|
||||
try {
|
||||
$fileObject = $this->resourceFactory->getFileObject((int)$fieldValues['file']);
|
||||
GeneralUtility::makeInstance(Indexer::class, $fileObject->getStorage())->updateIndexEntry($fileObject);
|
||||
} catch (FileDoesNotExistException $e) {
|
||||
$dataHandler->log(
|
||||
'sys_file_metadata',
|
||||
$uid,
|
||||
SystemLogFileAction::EDIT,
|
||||
null,
|
||||
SystemLogErrorClassification::SYSTEM_ERROR,
|
||||
'The referenced file "{fileUid}" was not found.',
|
||||
null,
|
||||
['fileUid' => $fieldValues['file']]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user