[ '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[] = '
'; $html[] = $fieldInformationHtml; $html[] = '
'; $html[] = '
'; if (!$readOnly) { // Add an empty hidden field which will send a blank value if all items are unselected. $html[] = ''; } // Building the checkboxes foreach ($groups as $groupKey => $group) { $groupId = htmlspecialchars($elementId . '-group-' . $groupKey); $groupCollapsibleId = $groupId . '-collapse'; $hasGroupHeader = is_array($group['header'] ?? false); $html[] = '
'; if ($hasGroupHeader) { $expanded = ($config['appearance']['expandAll'] ?? false); $html[] = ''; } 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[] = ''; $tableRows[] = ''; $tableRows[] = ''; $tableRows[] = ''; $tableRows[] = ''; $tableRows[] = ''; $tableRows[] = ''; $tableRows[] = ''; $tableRows[] = ''; $tableRows[] = '' . $item['help'] . ''; $tableRows[] = ''; } if ($hasGroupHeader) { $expandAll = ($config['appearance']['expandAll'] ?? false) ? 'show' : ''; $html[] = '
'; } $html[] = '
'; $html[] = ''; if (!$readOnly) { // Add table header with actions, in case the element is not readOnly $html[] = ''; $html[] = ''; $html[] = ''; $html[] = ''; $html[] = ''; $html[] = ''; // 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[] = '' . implode(LF, $tableRows) . ''; $html[] = '
' . $this->getRecordSelectionCheckActions() . '' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.th.name')) . '
'; $html[] = '
'; if ($hasGroupHeader) { $html[] = '
'; } } $html[] = '
'; } $html[] = '
'; if (!$readOnly && !empty($fieldWizardHtml)) { $html[] = '
'; $html[] = $fieldWizardHtml; $html[] = '
'; } $html[] = '
'; $html[] = '
'; $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 = '' . $text . ''; $wrappedText = ''; return $wrappedText; } protected function getRecordSelectionCheckActions(): string { $lang = $this->getLanguageService(); return ' '; } }