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