TYPO3 v15 dev-main snapshot ()

This commit is contained in:
2026-08-10 22:31:24 +02:00
commit aad9daaefd
1506 changed files with 94005 additions and 0 deletions
@@ -0,0 +1,127 @@
<?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\Form\Hooks;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
use TYPO3\CMS\Core\DataHandling\DataHandler;
use TYPO3\CMS\Core\SysLog\Action\Database as SystemLogDatabaseAction;
use TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification;
use TYPO3\CMS\Core\Utility\MathUtility;
use TYPO3\CMS\Form\Storage\Security\FormDefinitionPersistenceCommand;
use TYPO3\CMS\Form\Storage\Security\FormDefinitionPersistenceGuard;
/**
* Denies direct DataHandler write access to the form_definition table unless
* FormDefinitionPersistenceGuard has granted a matching invocation. This
* ensures form definitions can only be persisted through DatabaseStorageAdapter,
* which applies the form persistence manager's validation and permission checks.
*
* Registered as processDatamapClass (create/update) and processCmdmapClass
* (delete) so each path carries command, identifier, and field-level checks.
*
* @internal
*/
#[Autoconfigure(public: true)]
final readonly class FormDefinitionDataHandlerHook
{
public function __construct(
private FormDefinitionPersistenceGuard $guard,
) {}
/**
* Verifies create/update operations against the pending invocation grant.
*
* The command is derived from $id: a NEW-prefixed string means create,
* an integer means update. The full incoming field array is matched against
* the stored field names and HMAC. Setting $incomingFieldArray to null
* cancels the record in DataHandler.
*
* @param array<string, mixed>|null $incomingFieldArray
* @param-out array<string, mixed>|null $incomingFieldArray
*/
public function processDatamap_preProcessFieldArray(
?array &$incomingFieldArray,
string $table,
string|int $id,
DataHandler $dataHandler,
): void {
if ($table !== 'form_definition') {
return;
}
$isUpdate = MathUtility::canBeInterpretedAsInteger($id);
$command = $isUpdate
? FormDefinitionPersistenceCommand::Update
: FormDefinitionPersistenceCommand::Create;
$recordIdentifier = $isUpdate ? (int)$id : (string)$id;
if (!$this->guard->isInvocationAllowed($command, $recordIdentifier, $incomingFieldArray)) {
$incomingFieldArray = null;
$dataHandler->log(
$table,
$isUpdate ? (int)$id : 0,
$isUpdate ? SystemLogDatabaseAction::UPDATE : SystemLogDatabaseAction::INSERT,
null,
SystemLogErrorClassification::USER_ERROR,
'Persisting form definition "%s" via DataHandler is denied',
null,
[$id],
);
return;
}
$this->guard->consumeInvocation($command, $recordIdentifier, $incomingFieldArray);
}
/**
* Blocks unauthorised delete commands on form_definition records.
*
* Sets $commandIsProcessed to true (preventing DataHandler's built-in
* delete) when no COMMAND_FORM_DELETE grant is pending. This mirrors the
* FilePersistenceSlot pattern for FAL-based form definitions.
*/
public function processCmdmap(
string $command,
string $table,
int|string $id,
mixed $value,
bool &$commandIsProcessed,
DataHandler $dataHandler,
): void {
if ($table !== 'form_definition' || $command !== 'delete') {
return;
}
if (!$this->guard->isInvocationAllowed(FormDefinitionPersistenceCommand::Delete, (int)$id)) {
$commandIsProcessed = true;
$dataHandler->log(
$table,
(int)$id,
SystemLogDatabaseAction::DELETE,
null,
SystemLogErrorClassification::USER_ERROR,
'Deleting form definition "%s" via DataHandler is denied',
null,
[$id],
);
return;
}
$this->guard->consumeInvocation(FormDefinitionPersistenceCommand::Delete, (int)$id);
}
}
+89
View File
@@ -0,0 +1,89 @@
<?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\Form\Hooks;
use TYPO3\CMS\Filelist\ContextMenu\ItemProviders\FileProvider;
use TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManagerInterface;
/**
* Purges previously added form files from items for context menus.
* @internal
*/
class FormFileProvider extends FileProvider
{
/**
* @var array
*/
protected $itemsConfiguration = [];
/**
* Lowest priority, thus gets executed last.
*/
public function getPriority(): int
{
return 0;
}
public function canHandle(): bool
{
return parent::canHandle()
&& str_ends_with($this->identifier, FormPersistenceManagerInterface::FORM_DEFINITION_FILE_EXTENSION);
}
public function addItems(array $items): array
{
$this->initialize();
return $this->purgeItems($items);
}
/**
* Purges items that are not allowed for according command.
* According canBeEdited, canBeRenamed, ... commands will always return
* false in order to remove those form file items.
*
* Using the canRender() approach avoid adding hardcoded index name
* lookup. Thus, it's streamlined with the rest of the provides, but
* actually purges items instead of adding them.
*
* @param array $items
*/
protected function purgeItems(array $items): array
{
foreach ($items as $name => $item) {
$type = $item['type'];
if ($type === 'submenu' && !empty($item['childItems'])) {
$item['childItems'] = $this->purgeItems($item['childItems']);
} elseif (!$this->canRender($name, $type)) {
unset($items[$name]);
}
}
return $items;
}
protected function canBeEdited(): bool
{
return false;
}
protected function canBeRenamed(): bool
{
return false;
}
}
+39
View File
@@ -0,0 +1,39 @@
<?php
/*
* 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\Form\Hooks;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Form\Slot\FilePersistenceSlot;
/**
* @internal
* @deprecated: Remove in v16 along with the FileFormsToDatabaseUpgradeWizard
*/
class ImportExportHook
{
public function beforeAddSysFileRecordOnImport(array $params): void
{
$fileRecord = $params['fileRecord'];
$temporaryFile = $params['temporaryFile'];
$filePersistenceSlot = GeneralUtility::makeInstance(FilePersistenceSlot::class);
$filePersistenceSlot->allowInvocation(
FilePersistenceSlot::COMMAND_FILE_ADD,
implode(':', [$fileRecord['storage'], $fileRecord['identifier']]),
$filePersistenceSlot->getContentSignature(file_get_contents($temporaryFile))
);
}
}