1<?php
2namespace TYPO3\CMS\Backend\Form\Element;
3
4/*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
18use TYPO3\CMS\Backend\Utility\BackendUtility;
19use TYPO3\CMS\Core\Imaging\Icon;
20use TYPO3\CMS\Core\Utility\GeneralUtility;
21use TYPO3\CMS\Core\Utility\StringUtility;
22
23/**
24 * Creates a widget with check box elements.
25 *
26 * This is rendered for config type=select, renderType=selectCheckBox
27 */
28class SelectCheckBoxElement extends AbstractFormElement
29{
30    /**
31     * Default field information enabled for this element.
32     *
33     * @var array
34     */
35    protected $defaultFieldInformation = [
36        'tcaDescription' => [
37            'renderType' => 'tcaDescription',
38        ],
39    ];
40
41    /**
42     * Default field wizards enabled for this element.
43     *
44     * @var array
45     */
46    protected $defaultFieldWizard = [
47        'localizationStateSelector' => [
48            'renderType' => 'localizationStateSelector',
49        ],
50        'otherLanguageContent' => [
51            'renderType' => 'otherLanguageContent',
52            'after' => [
53                'localizationStateSelector'
54            ],
55        ],
56        'defaultLanguageDifferences' => [
57            'renderType' => 'defaultLanguageDifferences',
58            'after' => [
59                'otherLanguageContent',
60            ],
61        ],
62    ];
63
64    /**
65     * Render check boxes
66     *
67     * @return array As defined in initializeResultArray() of AbstractNode
68     */
69    public function render()
70    {
71        $resultArray = $this->initializeResultArray();
72
73        $html = [];
74        // Field configuration from TCA:
75        $parameterArray = $this->data['parameterArray'];
76        $config = $parameterArray['fieldConf']['config'];
77        $disabled = !empty($config['readOnly']);
78
79        $selItems = $config['items'];
80        if (!empty($selItems)) {
81            // Get values in an array (and make unique, which is fine because there can be no duplicates anyway)
82            // In case e.g. "l10n_display" is set to "defaultAsReadonly" only one value (as string) could be handed in
83            if (is_array($parameterArray['itemFormElValue'])) {
84                $itemArray = $parameterArray['itemFormElValue'];
85            } else {
86                $itemArray = [(string)$parameterArray['itemFormElValue']];
87            }
88            $itemArray = array_flip($itemArray);
89
90            // Traverse the Array of selector box items:
91            $groups = [];
92            $currentGroup = 0;
93            $c = 0;
94            $sOnChange = '';
95            if (!$disabled) {
96                $sOnChange = implode('', $parameterArray['fieldChangeFunc']);
97                // Used to accumulate the JS needed to restore the original selection.
98                foreach ($selItems as $p) {
99                    // Non-selectable element:
100                    if ($p[1] === '--div--') {
101                        $selIcon = '';
102                        if (isset($p[2]) && $p[2] !== 'empty-empty') {
103                            $selIcon = FormEngineUtility::getIconHtml($p[2]);
104                        }
105                        $currentGroup++;
106                        $groups[$currentGroup]['header'] = [
107                            'icon' => $selIcon,
108                            'title' => $p[0]
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                        $hasHelp = false;
118                        $help = '';
119                        $helpArray = [];
120                        if (!empty($p[3])) {
121                            $hasHelp = true;
122                            if (is_array($p[3])) {
123                                $helpArray = $p[3];
124                            } else {
125                                $helpArray['description'] = $p[3];
126                            }
127                        }
128                        if ($hasHelp) {
129                            $help = BackendUtility::wrapInHelp('', '', '', $helpArray);
130                        }
131
132                        // Selected or not by default:
133                        $checked = 0;
134                        if (isset($itemArray[$p[1]])) {
135                            $checked = 1;
136                            unset($itemArray[$p[1]]);
137                        }
138
139                        // Build item array
140                        $groups[$currentGroup]['items'][] = [
141                            'id' => StringUtility::getUniqueId('select_checkbox_row_'),
142                            'name' => $parameterArray['itemFormElName'] . '[' . $c . ']',
143                            'value' => $p[1],
144                            'checked' => $checked,
145                            'disabled' => false,
146                            'class' => '',
147                            'icon' => FormEngineUtility::getIconHtml(!empty($p[2]) ? $p[2] : 'empty-empty'),
148                            'title' => $p[0],
149                            'help' => $help
150                        ];
151                        $c++;
152                    }
153                }
154            }
155
156            $fieldInformationResult = $this->renderFieldInformation();
157            $fieldInformationHtml = $fieldInformationResult['html'];
158            $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
159
160            $fieldWizardResult = $this->renderFieldWizard();
161            $fieldWizardHtml = $fieldWizardResult['html'];
162            $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
163
164            $html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
165            $html[] = $fieldInformationHtml;
166            $html[] =   '<div class="form-wizards-wrap">';
167            $html[] =       '<div class="form-wizards-element">';
168
169            // Add an empty hidden field which will send a blank value if all items are unselected.
170            $html[] = '<input type="hidden" class="select-checkbox" name="' . htmlspecialchars($parameterArray['itemFormElName']) . '" value="">';
171
172            // Building the checkboxes
173            foreach ($groups as $groupKey => $group) {
174                $groupId = htmlspecialchars($parameterArray['itemFormElID']) . '-group-' . $groupKey;
175                $html[] = '<div class="panel panel-default">';
176                if (is_array($group['header'])) {
177                    $html[] = '<div class="panel-heading">';
178                    $html[] = '<a data-toggle="collapse" href="#' . $groupId . '" aria-expanded="false" aria-controls="' . $groupId . '">';
179                    $html[] = $group['header']['icon'];
180                    $html[] = htmlspecialchars($group['header']['title']);
181                    $html[] = '</a>';
182                    $html[] = '</div>';
183                }
184                if (is_array($group['items']) && !empty($group['items'])) {
185                    $tableRows = [];
186                    $resetGroup = [];
187
188                    // Render rows
189                    foreach ($group['items'] as $item) {
190                        $tableRows[] = '<tr class="' . $item['class'] . '">';
191                        $tableRows[] =    '<td class="col-checkbox">';
192                        $tableRows[] =        '<input type="checkbox" class="t3js-checkbox" '
193                                            . 'id="' . $item['id'] . '" '
194                                            . 'name="' . htmlspecialchars($item['name']) . '" '
195                                            . 'value="' . htmlspecialchars($item['value']) . '" '
196                                            . 'onclick="' . htmlspecialchars($sOnChange) . '" '
197                                            . ($item['checked'] ? 'checked=checked ' : '')
198                                            . ($item['disabled'] ? 'disabled=disabled ' : '') . '>';
199                        $tableRows[] =    '</td>';
200                        $tableRows[] =    '<td class="col-icon">';
201                        $tableRows[] =        '<label class="label-block" for="' . $item['id'] . '">' . $item['icon'] . '</label>';
202                        $tableRows[] =    '</td>';
203                        $tableRows[] =    '<td class="col-title">';
204                        $tableRows[] =        '<label class="label-block nowrap-disabled" for="' . $item['id'] . '">' . htmlspecialchars($this->appendValueToLabelInDebugMode($item['title'], $item['value']), ENT_COMPAT, 'UTF-8', false) . '</label>';
205                        $tableRows[] =    '</td>';
206                        $tableRows[] =    '<td class="text-right">' . $item['help'] . '</td>';
207                        $tableRows[] = '</tr>';
208                        $resetGroup[] = 'document.editform[' . GeneralUtility::quoteJSvalue($item['name']) . '].checked=' . $item['checked'] . ';';
209                    }
210
211                    // Build reset group button
212                    $resetGroupBtn = '';
213                    if (!empty($resetGroup)) {
214                        $resetGroup[] = 'TYPO3.FormEngine.updateCheckboxState(this);';
215                        $title = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.revertSelection'));
216                        $resetGroupBtn = '<a href="#" '
217                            . 'class="btn btn-default btn-sm" '
218                            . 'onclick="' . implode('', $resetGroup) . ' return false;" '
219                            . 'title="' . $title . '">'
220                            . $this->iconFactory->getIcon('actions-edit-undo', Icon::SIZE_SMALL)->render() . ' '
221                            . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.revertSelection') . '</a>';
222                    }
223
224                    if (is_array($group['header'])) {
225                        $html[] = '<div id="' . $groupId . '" class="panel-collapse collapse" role="tabpanel">';
226                    }
227                    $checkboxId = uniqid($groupId);
228                    $title = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.toggleall'));
229                    $html[] =    '<div class="table-responsive">';
230                    $html[] =        '<table class="table table-transparent table-hover">';
231                    $html[] =            '<thead>';
232                    $html[] =                '<tr>';
233                    $html[] =                    '<th class="col-checkbox">';
234                    $html[] =                       '<input type="checkbox" id="' . $checkboxId . '" class="t3js-toggle-checkboxes" data-trigger="hover" data-placement="right" data-title="' . $title . '" data-toggle="tooltip" />';
235                    $html[] =                    '</th>';
236                    $html[] =                    '<th class="col-title" colspan="2"><label for="' . $checkboxId . '">' . $title . '</label></th>';
237                    $html[] =                    '<th class="text-right">' . $resetGroupBtn . '</th>';
238                    $html[] =                '</tr>';
239                    $html[] =            '</thead>';
240                    $html[] =            '<tbody>' . implode(LF, $tableRows) . '</tbody>';
241                    $html[] =        '</table>';
242                    $html[] =    '</div>';
243                    if (is_array($group['header'])) {
244                        $html[] = '</div>';
245                    }
246                }
247                $html[] = '</div>';
248            }
249
250            $html[] =       '</div>';
251            if (!$disabled && !empty($fieldWizardHtml)) {
252                $html[] =   '<div class="form-wizards-items-bottom">';
253                $html[] =       $fieldWizardHtml;
254                $html[] =   '</div>';
255            }
256            $html[] =   '</div>';
257            $html[] = '</div>';
258        }
259
260        $resultArray['html'] = implode(LF, $html);
261        $resultArray['requireJsModules'][] = 'TYPO3/CMS/Backend/Tooltip';
262        return $resultArray;
263    }
264}
265