TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,343 @@
|
||||
<?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\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;
|
||||
|
||||
/**
|
||||
* Creates a widget with check box elements.
|
||||
*
|
||||
* This is rendered for config type=select, renderType=selectCheckBox
|
||||
*/
|
||||
class SelectCheckBoxElement extends AbstractFormElement
|
||||
{
|
||||
/**
|
||||
* Default field wizards enabled for this element.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultFieldWizard = [
|
||||
'localizationStateSelector' => [
|
||||
'renderType' => 'localizationStateSelector',
|
||||
],
|
||||
'otherLanguageContent' => [
|
||||
'renderType' => 'otherLanguageContent',
|
||||
'after' => [
|
||||
'localizationStateSelector',
|
||||
],
|
||||
],
|
||||
'defaultLanguageDifferences' => [
|
||||
'renderType' => 'defaultLanguageDifferences',
|
||||
'after' => [
|
||||
'otherLanguageContent',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private readonly IconFactory $iconFactory,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Render check boxes
|
||||
*
|
||||
* @return array As defined in initializeResultArray() of AbstractNode
|
||||
*/
|
||||
public function render(): array
|
||||
{
|
||||
$resultArray = $this->initializeResultArray();
|
||||
|
||||
// Field configuration from TCA:
|
||||
$parameterArray = $this->data['parameterArray'];
|
||||
$config = $parameterArray['fieldConf']['config'];
|
||||
$readOnly = (bool)($config['readOnly'] ?? false);
|
||||
|
||||
$selectItems = $config['items'] ?? [];
|
||||
if (empty($selectItems)) {
|
||||
// Early return in case the field does not contain any items
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
// Get item value as array and make unique, which is fine because there can be no duplicates anyway.
|
||||
$itemArray = array_flip($parameterArray['itemFormElValue']);
|
||||
|
||||
// Initialize variables and traverse the items
|
||||
$groups = [];
|
||||
$currentGroup = 0;
|
||||
$counter = 0;
|
||||
$elementId = StringUtility::getUniqueId('formengine-select-checkbox-');
|
||||
foreach ($selectItems as $item) {
|
||||
// Non-selectable element:
|
||||
if ($item['value'] === '--div--') {
|
||||
$selIcon = '';
|
||||
if (isset($item['icon']) && $item['icon'] !== 'empty-empty') {
|
||||
$selIcon = FormEngineUtility::getIconHtml($item['icon']);
|
||||
}
|
||||
$currentGroup++;
|
||||
$groups[$currentGroup]['header'] = [
|
||||
'icon' => $selIcon,
|
||||
'title' => $item['label'],
|
||||
];
|
||||
} else {
|
||||
// Check if some help text is available
|
||||
// Help text is expected to be an associative array
|
||||
// with two key, "title" and "description"
|
||||
// For the sake of backwards compatibility, we test if the help text
|
||||
// is a string and use it as a description (this could happen if items
|
||||
// are modified with an itemsProcFunc)
|
||||
$help = '';
|
||||
if (!empty($item['description'])) {
|
||||
if (is_array($item['description'])) {
|
||||
$helpArray = $item['description'];
|
||||
} else {
|
||||
$helpArray['description'] = $item['description'];
|
||||
}
|
||||
$help = $this->wrapInHelp($helpArray);
|
||||
}
|
||||
|
||||
// Check if current item is selected. If found, unset the key in the $itemArray.
|
||||
$checked = isset($itemArray[$item['value']]);
|
||||
if ($checked) {
|
||||
unset($itemArray[$item['value']]);
|
||||
}
|
||||
|
||||
// Build item array
|
||||
$groups[$currentGroup]['items'][] = [
|
||||
'id' => $elementId . '-item-' . $counter,
|
||||
'name' => $parameterArray['itemFormElName'] . '[' . $counter . ']',
|
||||
'value' => $item['value'],
|
||||
'checked' => $checked,
|
||||
'icon' => FormEngineUtility::getIconHtml(!empty($item['icon']) ? $item['icon'] : 'empty-empty'),
|
||||
'title' => $item['label'],
|
||||
'help' => $help,
|
||||
];
|
||||
$counter++;
|
||||
}
|
||||
}
|
||||
|
||||
$fieldInformationResult = $this->renderFieldInformation();
|
||||
$fieldInformationHtml = $fieldInformationResult['html'];
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
|
||||
|
||||
$fieldWizardResult = $this->renderFieldWizard();
|
||||
$fieldWizardHtml = $fieldWizardResult['html'];
|
||||
$resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
|
||||
|
||||
$html[] = '<div class="formengine-field-item t3js-formengine-field-item" data-formengine-validation-rules="' . htmlspecialchars($this->getValidationDataAsJsonString($config)) . '">';
|
||||
$html[] = $fieldInformationHtml;
|
||||
$html[] = '<div class="form-wizards-wrap">';
|
||||
$html[] = '<div class="form-wizards-item-element">';
|
||||
|
||||
if (!$readOnly) {
|
||||
// Add an empty hidden field which will send a blank value if all items are unselected.
|
||||
$html[] = '<input type="hidden" class="select-checkbox" name="' . htmlspecialchars($parameterArray['itemFormElName']) . '" value="">';
|
||||
}
|
||||
|
||||
// Building the checkboxes
|
||||
foreach ($groups as $groupKey => $group) {
|
||||
$groupId = htmlspecialchars($elementId . '-group-' . $groupKey);
|
||||
$groupCollapsibleId = $groupId . '-collapse';
|
||||
|
||||
$hasGroupHeader = is_array($group['header'] ?? false);
|
||||
|
||||
$html[] = '<div id="' . $groupId . '" class="panel panel-default" data-multi-record-selection-identifier="' . $groupId . '">';
|
||||
if ($hasGroupHeader) {
|
||||
$expanded = ($config['appearance']['expandAll'] ?? false);
|
||||
$html[] = '<div class="panel-heading" role="tab">';
|
||||
$html[] = '<div class="panel-heading-row">';
|
||||
$html[] = '<button type="button" class="panel-button' . (!$expanded ? ' collapsed' : '') . '" aria-expanded="' . ($expanded ? 'true' : 'false') . '"';
|
||||
$html[] = ' aria-controls="' . $groupCollapsibleId . '" data-bs-target="#' . $groupCollapsibleId . '" data-bs-toggle="collapse">';
|
||||
$html[] = '<div class="panel-icon">';
|
||||
$html[] = $group['header']['icon'];
|
||||
$html[] = '</div>';
|
||||
$html[] = '<div class="panel-title">';
|
||||
$html[] = htmlspecialchars($group['header']['title']);
|
||||
$html[] = '</div>';
|
||||
$html[] = '<span class="caret"></span>';
|
||||
$html[] = '</button>';
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
}
|
||||
if (is_array($group['items'] ?? null)) {
|
||||
$tableRows = [];
|
||||
|
||||
// Render rows
|
||||
foreach ($group['items'] as $item) {
|
||||
$inputElementAttrs = [
|
||||
'type' => 'checkbox',
|
||||
'class' => 'form-check-input t3js-multi-record-selection-check',
|
||||
'id' => $item['id'],
|
||||
'name' => $item['name'],
|
||||
'value' => $item['value'],
|
||||
];
|
||||
|
||||
if ($item['checked']) {
|
||||
$inputElementAttrs['checked'] = 'checked';
|
||||
}
|
||||
|
||||
if ($readOnly) {
|
||||
// Disable item if the element is readonly
|
||||
$inputElementAttrs['disabled'] = 'disabled';
|
||||
} else {
|
||||
// Add fieldChange attributes if element is not readOnly
|
||||
$inputElementAttrs = array_merge(
|
||||
$inputElementAttrs,
|
||||
$this->getOnFieldChangeAttrs('click', $parameterArray['fieldChangeFunc'] ?? [])
|
||||
);
|
||||
}
|
||||
|
||||
$tableRows[] = '<tr data-multi-record-selection-element="true">';
|
||||
$tableRows[] = '<td class="col-checkbox">';
|
||||
$tableRows[] = '<span class="form-check form-check-type-toggle">';
|
||||
$tableRows[] = '<input ' . GeneralUtility::implodeAttributes($inputElementAttrs, true, true) . '>';
|
||||
$tableRows[] = '</span>';
|
||||
$tableRows[] = '</td>';
|
||||
$tableRows[] = '<td class="col-title">';
|
||||
$tableRows[] = '<label class="label-block nowrap-disabled" for="' . $item['id'] . '">';
|
||||
$tableRows[] = '<span class="inline-icon">' . $item['icon'] . '</span>';
|
||||
$tableRows[] = htmlspecialchars($this->appendValueToLabelInDebugMode($item['title'], $item['value']), ENT_COMPAT, 'UTF-8', false);
|
||||
$tableRows[] = '</label>';
|
||||
$tableRows[] = '</td>';
|
||||
$tableRows[] = '<td class="text-end">' . $item['help'] . '</td>';
|
||||
$tableRows[] = '</tr>';
|
||||
}
|
||||
|
||||
if ($hasGroupHeader) {
|
||||
$expandAll = ($config['appearance']['expandAll'] ?? false) ? 'show' : '';
|
||||
$html[] = '<div id="' . $groupCollapsibleId . '" class="panel-collapse collapse ' . $expandAll . '" role="tabpanel">';
|
||||
}
|
||||
|
||||
$html[] = '<div class="table-fit">';
|
||||
$html[] = '<table class="table table-hover">';
|
||||
if (!$readOnly) {
|
||||
// Add table header with actions, in case the element is not readOnly
|
||||
$html[] = '<thead>';
|
||||
$html[] = '<tr>';
|
||||
$html[] = '<th class="col-checkbox">' . $this->getRecordSelectionCheckActions() . '</th>';
|
||||
$html[] = '<th class="col-title" colspan="2">' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.th.name')) . '</th>';
|
||||
$html[] = '</tr>';
|
||||
$html[] = '</thead>';
|
||||
|
||||
// Add JavaScript module. This is only needed, in case the element
|
||||
// is not readOnly, since otherwise no checkbox changes take place.
|
||||
$resultArray['javaScriptModules'][] = JavaScriptModuleInstruction::create('@typo3/backend/multi-record-selection.js');
|
||||
}
|
||||
$html[] = '<tbody>' . implode(LF, $tableRows) . '</tbody>';
|
||||
$html[] = '</table>';
|
||||
$html[] = '</div>';
|
||||
if ($hasGroupHeader) {
|
||||
$html[] = '</div>';
|
||||
}
|
||||
}
|
||||
$html[] = '</div>';
|
||||
}
|
||||
|
||||
$html[] = '</div>';
|
||||
if (!$readOnly && !empty($fieldWizardHtml)) {
|
||||
$html[] = '<div class="form-wizards-item-bottom">';
|
||||
$html[] = $fieldWizardHtml;
|
||||
$html[] = '</div>';
|
||||
}
|
||||
$html[] = '</div>';
|
||||
$html[] = '</div>';
|
||||
|
||||
$resultArray['html'] = $this->wrapWithFieldsetAndLegend(implode(LF, $html));
|
||||
return $resultArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that creates an icon with a help text. If a user clicks on
|
||||
* the icon, the help text will show up as tooltip
|
||||
*
|
||||
* @param array $overloadHelpText Array with text to overload help text
|
||||
* @return string the HTML code ready to render
|
||||
*/
|
||||
protected function wrapInHelp(array $overloadHelpText = []): string
|
||||
{
|
||||
// If there's a help text or some overload information, proceed with preparing an output
|
||||
if (empty($overloadHelpText)) {
|
||||
return '';
|
||||
}
|
||||
$text = $this->iconFactory->getIcon('actions-system-help-open', IconSize::SMALL)->render();
|
||||
$abbrClassAdd = ' help-teaser-icon';
|
||||
$text = '<abbr class="help-teaser' . $abbrClassAdd . '">' . $text . '</abbr>';
|
||||
$wrappedText = '<span class="help-link" data-bs-content="<p></p>"';
|
||||
// The overload array may provide a title and a description
|
||||
// If either one is defined, add them to the "data" attributes
|
||||
if (isset($overloadHelpText['title'])) {
|
||||
$wrappedText .= ' data-title="' . htmlspecialchars($overloadHelpText['title']) . '"';
|
||||
}
|
||||
if (isset($overloadHelpText['description'])) {
|
||||
$wrappedText .= ' data-description="' . htmlspecialchars($overloadHelpText['description']) . '"';
|
||||
}
|
||||
$wrappedText .= '>' . $text . '</span>';
|
||||
return $wrappedText;
|
||||
}
|
||||
|
||||
protected function getRecordSelectionCheckActions(): string
|
||||
{
|
||||
$lang = $this->getLanguageService();
|
||||
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" aria-label="' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.openSelectionOptions')) . '">
|
||||
' . $this->iconFactory->getIcon('actions-selection', IconSize::SMALL)->render() . '
|
||||
</button>
|
||||
<ul class="dropdown-menu t3js-multi-record-selection-check-actions">
|
||||
<li>
|
||||
<button type="button" class="dropdown-item" disabled data-multi-record-selection-check-action="check-all" title="' . htmlspecialchars($lang->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">
|
||||
' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.checkAll')) . '
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" class="dropdown-item" disabled data-multi-record-selection-check-action="check-none" title="' . htmlspecialchars($lang->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">
|
||||
' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.uncheckAll')) . '
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" class="dropdown-item" data-multi-record-selection-check-action="toggle" title="' . htmlspecialchars($lang->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">
|
||||
' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.toggleSelection')) . '
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user