‪TYPO3CMS  ‪main
SelectCheckBoxElement.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
17 
20 use TYPO3\CMS\Core\Imaging\IconSize;
24 
31 {
37  protected ‪$defaultFieldInformation = [
38  'tcaDescription' => [
39  'renderType' => 'tcaDescription',
40  ],
41  ];
42 
48  protected ‪$defaultFieldWizard = [
49  'localizationStateSelector' => [
50  'renderType' => 'localizationStateSelector',
51  ],
52  'otherLanguageContent' => [
53  'renderType' => 'otherLanguageContent',
54  'after' => [
55  'localizationStateSelector',
56  ],
57  ],
58  'defaultLanguageDifferences' => [
59  'renderType' => 'defaultLanguageDifferences',
60  'after' => [
61  'otherLanguageContent',
62  ],
63  ],
64  ];
65 
66  public function ‪__construct(
67  private readonly ‪IconFactory $iconFactory,
68  ) {}
69 
75  public function ‪render(): array
76  {
77  $resultArray = $this->‪initializeResultArray();
78 
79  // Field configuration from TCA:
80  $parameterArray = $this->data['parameterArray'];
81  $config = $parameterArray['fieldConf']['config'];
82  $readOnly = (bool)($config['readOnly'] ?? false);
83 
84  $selectItems = $config['items'] ?? [];
85  if (empty($selectItems)) {
86  // Early return in case the field does not contain any items
87  return $resultArray;
88  }
89 
90  // Get item value as array and make unique, which is fine because there can be no duplicates anyway.
91  $itemArray = array_flip($parameterArray['itemFormElValue']);
92 
93  // Initialize variables and traverse the items
94  $groups = [];
95  $currentGroup = 0;
96  $counter = 0;
97  $elementId = ‪StringUtility::getUniqueId('formengine-select-checkbox-');
98  foreach ($selectItems as $item) {
99  // Non-selectable element:
100  if ($item['value'] === '--div--') {
101  $selIcon = '';
102  if (isset($item['icon']) && $item['icon'] !== 'empty-empty') {
103  $selIcon = ‪FormEngineUtility::getIconHtml($item['icon']);
104  }
105  $currentGroup++;
106  $groups[$currentGroup]['header'] = [
107  'icon' => $selIcon,
108  'title' => $item['label'],
109  ];
110  } else {
111  // Check if some help text is available
112  // Help text is expected to be an associative array
113  // with two key, "title" and "description"
114  // For the sake of backwards compatibility, we test if the help text
115  // is a string and use it as a description (this could happen if items
116  // are modified with an itemProcFunc)
117  $help = '';
118  if (!empty($item['description'])) {
119  if (is_array($item['description'])) {
120  $helpArray = $item['description'];
121  } else {
122  $helpArray['description'] = $item['description'];
123  }
124  $help = $this->‪wrapInHelp($helpArray);
125  }
126 
127  // Check if current item is selected. If found, unset the key in the $itemArray.
128  $checked = isset($itemArray[$item['value']]);
129  if ($checked) {
130  unset($itemArray[$item['value']]);
131  }
132 
133  // Build item array
134  $groups[$currentGroup]['items'][] = [
135  'id' => $elementId . '-item-' . $counter,
136  'name' => $parameterArray['itemFormElName'] . '[' . $counter . ']',
137  'value' => $item['value'],
138  'checked' => $checked,
139  'icon' => ‪FormEngineUtility::getIconHtml(!empty($item['icon']) ? $item['icon'] : 'empty-empty'),
140  'title' => $item['label'],
141  'help' => $help,
142  ];
143  $counter++;
144  }
145  }
146 
147  $fieldInformationResult = $this->‪renderFieldInformation();
148  $fieldInformationHtml = $fieldInformationResult['html'];
149  $resultArray = $this->‪mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
150 
151  $fieldWizardResult = $this->‪renderFieldWizard();
152  $fieldWizardHtml = $fieldWizardResult['html'];
153  $resultArray = $this->‪mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
154 
155  $html[] = '<div class="formengine-field-item t3js-formengine-field-item" data-formengine-validation-rules="' . htmlspecialchars($this->‪getValidationDataAsJsonString($config)) . '">';
156  $html[] = $fieldInformationHtml;
157  $html[] = '<div class="form-wizards-wrap">';
158  $html[] = '<div class="form-wizards-element">';
159 
160  if (!$readOnly) {
161  // Add an empty hidden field which will send a blank value if all items are unselected.
162  $html[] = '<input type="hidden" class="select-checkbox" name="' . htmlspecialchars($parameterArray['itemFormElName']) . '" value="">';
163  }
164 
165  // Building the checkboxes
166  foreach ($groups as $groupKey => $group) {
167  $groupId = htmlspecialchars($elementId . '-group-' . $groupKey);
168  $groupCollapsibleId = $groupId . '-collapse';
169  $toggleAllCheckboxId = $groupId . '-toggle-all';
170 
171  $hasGroupHeader = is_array($group['header'] ?? false);
172 
173  $html[] = '<div id="' . $groupId . '" class="panel panel-default">';
174  if ($hasGroupHeader) {
175  $html[] = '<div class="panel-heading">';
176  $html[] = '<a data-bs-toggle="collapse" href="#' . $groupCollapsibleId . '" aria-expanded="false" aria-controls="' . $groupCollapsibleId . '">';
177  $html[] = $group['header']['icon'];
178  $html[] = htmlspecialchars($group['header']['title']);
179  $html[] = '</a>';
180  $html[] = '</div>';
181  }
182  if (!empty($group['items']) && is_array($group['items'])) {
183  $tableRows = [];
184 
185  // Render rows
186  foreach ($group['items'] as $item) {
187  $inputElementAttrs = [
188  'type' => 'checkbox',
189  'class' => 't3js-checkbox',
190  'id' => $item['id'],
191  'name' => $item['name'],
192  'value' => $item['value'],
193  ];
194 
195  if ($item['checked']) {
196  $inputElementAttrs['checked'] = 'checked';
197  }
198 
199  if ($readOnly) {
200  // Disable item if the element is readonly
201  $inputElementAttrs['disabled'] = 'disabled';
202  } else {
203  // Add fieldChange attributes if element is not readOnly
204  $inputElementAttrs = array_merge(
205  $inputElementAttrs,
206  $this->getOnFieldChangeAttrs('click', $parameterArray['fieldChangeFunc'] ?? [])
207  );
208  }
209 
210  $tableRows[] = '<tr>';
211  $tableRows[] = '<td class="col-checkbox">';
212  $tableRows[] = '<input ' . GeneralUtility::implodeAttributes($inputElementAttrs, true, true) . '>';
213  $tableRows[] = '</td>';
214  $tableRows[] = '<td class="col-title">';
215  $tableRows[] = '<label class="label-block nowrap-disabled" for="' . $item['id'] . '">';
216  $tableRows[] = '<span class="inline-icon">' . $item['icon'] . '</span>';
217  $tableRows[] = htmlspecialchars($this->‪appendValueToLabelInDebugMode($item['title'], $item['value']), ENT_COMPAT, 'UTF-8', false);
218  $tableRows[] = '</label>';
219  $tableRows[] = '</td>';
220  $tableRows[] = '<td class="text-end">' . $item['help'] . '</td>';
221  $tableRows[] = '</tr>';
222  }
223 
224  if ($hasGroupHeader) {
225  $expandAll = ($config['appearance']['expandAll'] ?? false) ? 'show' : '';
226  $html[] = '<div id="' . $groupCollapsibleId . '" class="panel-collapse collapse ' . $expandAll . '" role="tabpanel">';
227  }
228 
229  $html[] = '<div class="table-fit">';
230  $html[] = '<table class="table table-transparent table-hover">';
231  if (!$readOnly) {
232  $title = htmlspecialchars($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.toggleall'));
233 
234  // Add table header with actions, in case the element is not readOnly
235  $html[] = '<thead>';
236  $html[] = '<tr>';
237  $html[] = '<th class="col-checkbox">';
238  $html[] = '<input type="checkbox" id="' . $toggleAllCheckboxId . '" class="t3js-toggle-checkboxes" title="' . $title . '" />';
239  $html[] = '</th>';
240  $html[] = '<th class="col-title"><label for="' . $toggleAllCheckboxId . '">' . $title . '</label></th>';
241  $html[] = '<th class="text-end">';
242  $html[] = '<button type="button" class="btn btn-default btn-sm t3js-revert-selection">';
243  $html[] = $this->iconFactory->getIcon('actions-edit-undo', IconSize::SMALL)->render() . ' ';
244  $html[] = htmlspecialchars($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.revertSelection'));
245  $html[] = '</button>';
246  $html[] = '</th>';
247  $html[] = '</tr>';
248  $html[] = '</thead>';
249 
250  // Add JavaScript module. This is only needed, in case the element
251  // is not readOnly, since otherwise no checkbox changes take place.
252  $resultArray['javaScriptModules'][] = ‪JavaScriptModuleInstruction::create(
253  '@typo3/backend/form-engine/element/select-check-box-element.js'
254  )->instance($toggleAllCheckboxId);
255  }
256  $html[] = '<tbody>' . implode(LF, $tableRows) . '</tbody>';
257  $html[] = '</table>';
258  $html[] = '</div>';
259  if ($hasGroupHeader) {
260  $html[] = '</div>';
261  }
262  }
263  $html[] = '</div>';
264  }
265 
266  $html[] = '</div>';
267  if (!$readOnly && !empty($fieldWizardHtml)) {
268  $html[] = '<div class="form-wizards-items-bottom">';
269  $html[] = $fieldWizardHtml;
270  $html[] = '</div>';
271  }
272  $html[] = '</div>';
273  $html[] = '</div>';
274 
275  $resultArray['html'] = $this->‪wrapWithFieldsetAndLegend(implode(LF, $html));
276  return $resultArray;
277  }
278 
286  protected function ‪wrapInHelp(array $overloadHelpText = []): string
287  {
288  // If there's a help text or some overload information, proceed with preparing an output
289  if (empty($overloadHelpText)) {
290  return '';
291  }
292  $text = $this->iconFactory->getIcon('actions-system-help-open', IconSize::SMALL)->render();
293  $abbrClassAdd = ' help-teaser-icon';
294  $text = '<abbr class="help-teaser' . $abbrClassAdd . '">' . $text . '</abbr>';
295  $wrappedText = '<span class="help-link" data-bs-content="<p></p>"';
296  // The overload array may provide a title and a description
297  // If either one is defined, add them to the "data" attributes
298  if (isset($overloadHelpText['title'])) {
299  $wrappedText .= ' data-title="' . htmlspecialchars($overloadHelpText['title']) . '"';
300  }
301  if (isset($overloadHelpText['description'])) {
302  $wrappedText .= ' data-description="' . htmlspecialchars($overloadHelpText['description']) . '"';
303  }
304  $wrappedText .= '>' . $text . '</span>';
305  return $wrappedText;
306  }
307 }
‪TYPO3\CMS\Backend\Form\Element\SelectCheckBoxElement
Definition: SelectCheckBoxElement.php:31
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement\renderFieldInformation
‪array renderFieldInformation()
Definition: AbstractFormElement.php:73
‪TYPO3\CMS\Backend\Form\AbstractNode\mergeChildReturnIntoExistingResult
‪array mergeChildReturnIntoExistingResult(array $existing, array $childReturn, bool $mergeHtml=true)
Definition: AbstractNode.php:104
‪TYPO3\CMS\Backend\Form\Element\SelectCheckBoxElement\render
‪array render()
Definition: SelectCheckBoxElement.php:73
‪TYPO3\CMS\Backend\Form\Element\SelectCheckBoxElement\$defaultFieldWizard
‪array $defaultFieldWizard
Definition: SelectCheckBoxElement.php:46
‪TYPO3\CMS\Core\Page\JavaScriptModuleInstruction\create
‪static create(string $name, string $exportName=null)
Definition: JavaScriptModuleInstruction.php:47
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement
Definition: AbstractFormElement.php:37
‪TYPO3\CMS\Core\Imaging\IconFactory
Definition: IconFactory.php:34
‪TYPO3\CMS\Core\Page\JavaScriptModuleInstruction
Definition: JavaScriptModuleInstruction.php:23
‪TYPO3\CMS\Backend\Form\Element
Definition: AbstractFormElement.php:16
‪TYPO3\CMS\Backend\Form\Utility\FormEngineUtility
Definition: FormEngineUtility.php:41
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement\wrapWithFieldsetAndLegend
‪wrapWithFieldsetAndLegend(string $innerHTML)
Definition: AbstractFormElement.php:133
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement\getLanguageService
‪getLanguageService()
Definition: AbstractFormElement.php:456
‪TYPO3\CMS\Backend\Form\Utility\FormEngineUtility\getIconHtml
‪static string getIconHtml($icon, $alt='', $title='')
Definition: FormEngineUtility.php:134
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement\appendValueToLabelInDebugMode
‪appendValueToLabelInDebugMode(string|int $label, string|int $value)
Definition: AbstractFormElement.php:447
‪TYPO3\CMS\Backend\Form\Element\SelectCheckBoxElement\__construct
‪__construct(private readonly IconFactory $iconFactory,)
Definition: SelectCheckBoxElement.php:64
‪TYPO3\CMS\Backend\Form\AbstractNode\getValidationDataAsJsonString
‪getValidationDataAsJsonString(array $config)
Definition: AbstractNode.php:133
‪TYPO3\CMS\Backend\Form\Element\SelectCheckBoxElement\wrapInHelp
‪string wrapInHelp(array $overloadHelpText=[])
Definition: SelectCheckBoxElement.php:284
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Utility\StringUtility
Definition: StringUtility.php:24
‪TYPO3\CMS\Backend\Form\Element\AbstractFormElement\renderFieldWizard
‪array renderFieldWizard()
Definition: AbstractFormElement.php:105
‪TYPO3\CMS\Backend\Form\Element\SelectCheckBoxElement\$defaultFieldInformation
‪array $defaultFieldInformation
Definition: SelectCheckBoxElement.php:36
‪TYPO3\CMS\Core\Utility\StringUtility\getUniqueId
‪static getUniqueId(string $prefix='')
Definition: StringUtility.php:57
‪TYPO3\CMS\Backend\Form\AbstractNode\initializeResultArray
‪initializeResultArray()
Definition: AbstractNode.php:77