TYPO3 v15 dev-main snapshot ()

This commit is contained in:
2026-08-10 22:31:00 +02:00
commit f9941541b7
1178 changed files with 135377 additions and 0 deletions
@@ -0,0 +1,258 @@
<?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\Backend\Form\Element;
use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Imaging\IconSize;
use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\StringUtility;
/**
* Renders table permission options for each available table.
*
* @internal Only used in be_groups for the "combined read & write" table permission list.
*/
final class TablePermissionElement extends AbstractFormElement
{
private const array Permissions = [
'none' => 'none',
'select' => 'select',
'modify' => 'modify',
];
public function __construct(
private readonly IconFactory $iconFactory,
) {}
public function render(): array
{
$resultArray = $this->initializeResultArray();
$parameterArray = $this->data['parameterArray'];
$config = $parameterArray['fieldConf']['config'];
$elementFieldName = $parameterArray['itemFormElName'];
$currentValue = ['modify' => [], 'select' => []];
if (is_array($parameterArray['itemFormElValue']['modify'] ?? false)
&& is_array($parameterArray['itemFormElValue']['select'] ?? false)
) {
$currentValue = $parameterArray['itemFormElValue'];
}
$readOnly = (bool)($config['readOnly'] ?? false);
$availableTables = $config['items'] ?? [];
if (empty($availableTables)) {
// Early return in case the field does not contain any items
return $resultArray;
}
$tablesConfiguration = [];
$lang = $this->getLanguageService();
$itemArrayModify = array_flip($currentValue['modify']);
$itemArraySelect = array_flip($currentValue['select']);
$elementId = StringUtility::getUniqueId('formengine-table-permission-');
foreach ($availableTables as $table) {
$permissions = [];
foreach (self::Permissions as $permission) {
$permissions[$permission] = [
'label' => $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:be_groups.tables_modify.permissions.' . $permission),
'attributes' => [
'type' => 'radio',
'class' => 'form-check-input t3js-table-permissions-item t3js-multi-record-selection-check',
'value' => $permission,
'name' => $elementId . '[' . $table['value'] . ']',
'id' => $elementId . '[' . $table['value'] . '][' . $permission . ']',
'data-table' => $table['value'],
],
];
}
if (isset($itemArrayModify[$table['value']])) {
$permissions[self::Permissions['modify']]['attributes']['checked'] = 'checked';
} elseif (isset($itemArraySelect[$table['value']])) {
$permissions[self::Permissions['select']]['attributes']['checked'] = 'checked';
} else {
$permissions[self::Permissions['none']]['attributes']['checked'] = 'checked';
}
if ($readOnly) {
foreach (self::Permissions as $permission) {
$permissions[$permission]['attributes']['disabled'] = 'disabled';
}
}
$tablesConfiguration[] = [
'permissions' => $permissions,
'label' => [
'id' => $elementId . '-' . $table['value'] . '-label',
'icon' => $this->getIconForTable(!empty($table['icon']) ? $table['icon'] : 'empty-empty'),
'title' => $lang->sL($table['label']),
'value' => $table['value'],
],
];
}
$modifyStateFieldName = htmlspecialchars($elementFieldName);
$selectStateFieldName = htmlspecialchars(str_replace($this->data['fieldName'], $config['selectFieldName'], $elementFieldName));
$fieldInformationResult = $this->renderFieldInformation();
$fieldInformationHtml = $fieldInformationResult['html'];
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
$html[] = '<typo3-formengine-element-tablepermission modifyStateFieldName="' . $modifyStateFieldName . '" selectStateFieldName="' . $selectStateFieldName . '">';
$html[] = '<div class="formengine-field-item t3js-formengine-field-item" data-formengine-validation-rules="' . htmlspecialchars($this->getValidationDataAsJsonString($config)) . '">';
$html[] = $fieldInformationHtml;
if (!$readOnly) {
$html[] = '<input type="hidden" name="' . $modifyStateFieldName . '" value="' . implode(',', $currentValue['modify']) . '">';
$html[] = '<input type="hidden" name="' . $selectStateFieldName . '" value="' . implode(',', $currentValue['select']) . '">';
}
$tableRows = [];
foreach ($tablesConfiguration as $tableConfiguration) {
$tableRows[] = '<tr role="radiogroup" aria-labelledby="' . $tableConfiguration['label']['id'] . '">';
foreach ($tableConfiguration['permissions'] as $key => $permission) {
$tableRows[] = '<td class="col-radiogroup">';
$tableRows[] = '<div class="form-check form-check-inline" data-multi-record-selection-identifier="' . $elementId . '-' . $key . '" data-multi-record-selection-element="true">';
$tableRows[] = '<input ' . GeneralUtility::implodeAttributes($permission['attributes'], true, true) . '>';
$tableRows[] = '<label class="form-check-label" for="' . $permission['attributes']['id'] . '">' . htmlspecialchars($permission['label']) . '</label>';
$tableRows[] = '</div>';
$tableRows[] = '</td>';
}
$tableRows[] = '<td class="col-title col-border-left">';
$tableRows[] = '<label class="label-block nowrap-disabled" id="' . $tableConfiguration['label']['id'] . '">';
$tableRows[] = '<span>' . $tableConfiguration['label']['icon'] . '</span>';
$tableRows[] = htmlspecialchars($this->appendValueToLabelInDebugMode($tableConfiguration['label']['title'], $tableConfiguration['label']['value']));
$tableRows[] = '</label>';
$tableRows[] = '</td>';
$tableRows[] = '</tr>';
}
$html[] = '<div id="' . $elementId . '">';
$html[] = '<div class="table-fit">';
$html[] = '<table class="table table-hover">';
$html[] = '<thead>';
$html[] = '<tr>';
foreach (self::Permissions as $permission) {
$html[] = '<th data-multi-record-selection-identifier="' . $elementId . '-' . $permission . '">';
$html[] = $this->getRecordSelectionCheckActions($permission === self::Permissions['none'] ? ['all'] : ['all', 'none', 'toggle'], $readOnly);
$html[] = '</th>';
}
$html[] = '<th class="col-title col-border-left">' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.th.name')) . '</th>';
$html[] = '</tr>';
$html[] = '</thead>';
$html[] = '<tbody>' . implode(LF, $tableRows) . '</tbody>';
$html[] = '</table>';
$html[] = '</div>';
$html[] = '</div>';
$html[] = '</div>';
if (!$readOnly) {
$resultArray['javaScriptModules'][] = JavaScriptModuleInstruction::create('@typo3/backend/form-engine/element/table-permission-element.js');
$resultArray['javaScriptModules'][] = JavaScriptModuleInstruction::create('@typo3/backend/multi-record-selection.js');
}
$html[] = '</typo3-formengine-element-tablepermission>';
$resultArray['html'] = $this->wrapWithFieldsetAndLegend(implode(LF, $html));
return $resultArray;
}
protected function wrapWithFieldsetAndLegend(string $innerHTML): string
{
$legend = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_tca.xlf:be_groups.tables_modify'));
if ($this->getBackendUser()->shallDisplayDebugInformation()) {
$legend .= ' <code>[' . ($this->data['parameterArray']['fieldConf']['config']['selectFieldName'] ?? '') . ', ' . $this->data['fieldName'] . ']</code>';
}
$html = [];
$html[] = '<fieldset>';
$html[] = '<legend class="form-label t3js-formengine-label">' . $legend . '</legend>';
$html[] = $innerHTML;
$html[] = '</fieldset>';
return implode(LF, $html);
}
private function getIconForTable(string $icon): string
{
return FormEngineUtility::getIconHtml($icon);
}
private function getRecordSelectionCheckActions(array $optionsToShow, bool $readOnly): string
{
$checkboxOptions = [
'all' => '
<li>
<button type="button" class="dropdown-item" disabled data-multi-record-selection-check-action="check-all" title="' . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.checkAll') . '">
<span class="dropdown-item-columns">
<span class="dropdown-item-column dropdown-item-column-icon" aria-hidden="true">
' . $this->iconFactory->getIcon('actions-selection-elements-all', IconSize::SMALL)->render() . '
</span>
<span class="dropdown-item-column dropdown-item-column-title">
' . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.checkAll') . '
</span>
</span>
</button>
</li>',
'none' => '
<li>
<button type="button" class="dropdown-item" disabled data-multi-record-selection-check-action="check-none" title="' . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.uncheckAll') . '">
<span class="dropdown-item-columns">
<span class="dropdown-item-column dropdown-item-column-icon" aria-hidden="true">
' . $this->iconFactory->getIcon('actions-selection-elements-none', IconSize::SMALL)->render() . '
</span>
<span class="dropdown-item-column dropdown-item-column-title">
' . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.uncheckAll') . '
</span>
</span>
</button>
</li>',
'toggle' => '
<li>
<button type="button" class="dropdown-item" data-multi-record-selection-check-action="toggle" title="' . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.toggleSelection') . '">
<span class="dropdown-item-columns">
<span class="dropdown-item-column dropdown-item-column-icon" aria-hidden="true">
' . $this->iconFactory->getIcon('actions-selection-elements-invert', IconSize::SMALL)->render() . '
</span>
<span class="dropdown-item-column dropdown-item-column-title">
' . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.toggleSelection') . '
</span>
</span>
</button>
</li>',
];
$checkboxOptions = array_filter($checkboxOptions, static fn($checkboxOption) => in_array($checkboxOption, $optionsToShow, true), ARRAY_FILTER_USE_KEY);
if ($checkboxOptions === []) {
return '';
}
return '
<div class="dropdown">
<button type="button" class="dropdown-toggle dropdown-toggle-link t3js-multi-record-selection-check-actions-toggle" data-bs-toggle="dropdown" data-bs-boundary="window" aria-expanded="false" ' . ($readOnly ? ' disabled="disabled"' : '') . '>
<core:icon identifier="actions-selection" size="small" />
' . $this->iconFactory->getIcon('actions-selection', IconSize::SMALL)->render() . '
</button>
<ul class="dropdown-menu t3js-multi-record-selection-check-actions">
' . implode(LF, $checkboxOptions) . '
</ul>
</div>';
}
}