f248c06e3fdf9bd3391e64ce58e33b36e7891d93
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / Element / SelectSingleElement.php
1 <?php
2 namespace 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
17 use TYPO3\CMS\Core\Utility\GeneralUtility;
18 use TYPO3\CMS\Core\Utility\MathUtility;
19 use TYPO3\CMS\Backend\Utility\BackendUtility;
20 use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
21 use TYPO3\CMS\Backend\Form\InlineStackProcessor;
22
23 /**
24 * Creates a widget where only one item can be selected.
25 * This is either a select drop-down if no size config is given or set to 1, or a select box.
26 *
27 * This is rendered for type=select, maxitems=1
28 */
29 class SelectSingleElement extends AbstractFormElement {
30
31 /**
32 * @var array Result array given returned by render() - This property is a helper until class is properly refactored
33 */
34 protected $resultArray = array();
35
36 /**
37 * Render single element
38 *
39 * @return array As defined in initializeResultArray() of AbstractNode
40 */
41 public function render() {
42 $table = $this->globalOptions['table'];
43 $field = $this->globalOptions['fieldName'];
44 $row = $this->globalOptions['databaseRow'];
45 $parameterArray = $this->globalOptions['parameterArray'];
46 $config = $parameterArray['fieldConf']['config'];
47
48 $disabled = '';
49 if ($this->isGlobalReadonly() || $config['readOnly']) {
50 $disabled = ' disabled="disabled"';
51 }
52
53 $this->resultArray = $this->initializeResultArray();
54
55 // "Extra" configuration; Returns configuration for the field based on settings found in the "types" fieldlist.
56 $specConf = BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras']);
57 $selItems = FormEngineUtility::getSelectItems($table, $field, $row, $parameterArray);
58
59 // Creating the label for the "No Matching Value" entry.
60 $noMatchingLabel = isset($parameterArray['fieldTSConfig']['noMatchingValue_label'])
61 ? $this->getLanguageService()->sL($parameterArray['fieldTSConfig']['noMatchingValue_label'])
62 : '[ ' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.noMatchingValue') . ' ]';
63
64 $html = $this->getSingleField_typeSelect_single($table, $field, $row, $parameterArray, $config, $selItems, $noMatchingLabel);
65
66 // Wizards:
67 if (!$disabled) {
68 $altItem = '<input type="hidden" name="' . $parameterArray['itemFormElName'] . '" value="' . htmlspecialchars($parameterArray['itemFormElValue']) . '" />';
69 $html = $this->renderWizards(array($html, $altItem), $config['wizards'], $table, $row, $field, $parameterArray, $parameterArray['itemFormElName'], $specConf);
70 }
71 $this->resultArray['html'] = $html;
72 return $this->resultArray;
73 }
74
75 /**
76 * Creates a single-selector box
77 *
78 * @param string $table See getSingleField_typeSelect()
79 * @param string $field See getSingleField_typeSelect()
80 * @param array $row See getSingleField_typeSelect()
81 * @param array $parameterArray See getSingleField_typeSelect()
82 * @param array $config (Redundant) content of $PA['fieldConf']['config'] (for convenience)
83 * @param array $selectItems Items available for selection
84 * @param string $noMatchingLabel Label for no-matching-value
85 * @return string The HTML code for the item
86 */
87 protected function getSingleField_typeSelect_single($table, $field, $row, $parameterArray, $config, $selectItems, $noMatchingLabel) {
88 // Check against inline uniqueness
89 /** @var InlineStackProcessor $inlineStackProcessor */
90 $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
91 $inlineStackProcessor->initializeByGivenStructure($this->globalOptions['inlineStructure']);
92 $inlineParent = $inlineStackProcessor->getStructureLevel(-1);
93 $uniqueIds = NULL;
94 if (is_array($inlineParent) && $inlineParent['uid']) {
95 $inlineObjectName = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->globalOptions['inlineFirstPid']);
96 $inlineFormName = $inlineStackProcessor->getCurrentStructureFormPrefix();
97 if ($inlineParent['config']['foreign_table'] == $table && $inlineParent['config']['foreign_unique'] == $field) {
98 $uniqueIds = $this->globalOptions['inlineData']['unique'][$inlineObjectName . '-' . $table]['used'];
99 $parameterArray['fieldChangeFunc']['inlineUnique'] = 'inline.updateUnique(this,'
100 . GeneralUtility::quoteJSvalue($inlineObjectName . '-' . $table) . ','
101 . GeneralUtility::quoteJSvalue($inlineFormName) . ','
102 . GeneralUtility::quoteJSvalue($row['uid']) . ');';
103 }
104 // hide uid of parent record for symmetric relations
105 if (
106 $inlineParent['config']['foreign_table'] == $table
107 && ($inlineParent['config']['foreign_field'] == $field || $inlineParent['config']['symmetric_field'] == $field)
108 ) {
109 $uniqueIds[] = $inlineParent['uid'];
110 }
111 }
112
113 // Initialization:
114 $selectId = str_replace('.', '', uniqid('tceforms-select-', TRUE));
115 $selectedIndex = 0;
116 $selectedIcon = '';
117 $noMatchingValue = 1;
118 $onlySelectedIconShown = 0;
119 $size = (int)$config['size'];
120
121 // Style set on <select/>
122 $out = '';
123 $options = '';
124 $disabled = FALSE;
125 if ($this->isGlobalReadonly() || $config['readOnly']) {
126 $disabled = TRUE;
127 $onlySelectedIconShown = 1;
128 }
129
130 // Icon configuration:
131 if ($config['suppress_icons'] === 'IF_VALUE_FALSE') {
132 $suppressIcons = !$parameterArray['itemFormElValue'] ? 1 : 0;
133 } elseif ($config['suppress_icons'] === 'ONLY_SELECTED') {
134 $suppressIcons = 0;
135 $onlySelectedIconShown = 1;
136 } elseif ($config['suppress_icons']) {
137 $suppressIcons = 1;
138 } else {
139 $suppressIcons = 0;
140 }
141
142 // Prepare groups
143 $selectItemCounter = 0;
144 $selectItemGroupCount = 0;
145 $selectItemGroups = array();
146 $selectIcons = array();
147 foreach ($selectItems as $item) {
148 if ($item[1] === '--div--') {
149 // IS OPTGROUP
150 if ($selectItemCounter !== 0) {
151 $selectItemGroupCount++;
152 }
153 $selectItemGroups[$selectItemGroupCount]['header'] = array(
154 'title' => $item[0],
155 'icon' => (!empty($item[2]) ? FormEngineUtility::getIconHtml($item[2]) : ''),
156 );
157 } else {
158 // IS ITEM
159 $title = htmlspecialchars($item['0'], ENT_COMPAT, 'UTF-8', FALSE);
160 $icon = !empty($item[2]) ? FormEngineUtility::getIconHtml($item[2], $title, $title) : '';
161 $selected = ((string)$parameterArray['itemFormElValue'] === (string)$item[1] ? 1 : 0);
162 if ($selected) {
163 $selectedIndex = $selectItemCounter;
164 $selectedIcon = $icon;
165 $noMatchingValue = 0;
166 }
167 $selectItemGroups[$selectItemGroupCount]['items'][] = array(
168 'title' => $title,
169 'value' => $item[1],
170 'icon' => $icon,
171 'selected' => $selected,
172 'index' => $selectItemCounter
173 );
174 // ICON
175 if ($icon && !$suppressIcons && (!$onlySelectedIconShown || $selected)) {
176 $onClick = 'document.editform[' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName']) . '].selectedIndex=' . $selectItemCounter . ';';
177 if ($config['iconsInOptionTags']) {
178 $onClick .= 'document.getElementById(' . GeneralUtility::quoteJSvalue($selectId . '_icon') . ').innerHTML = '
179 . 'document.editform[' . GeneralUtility::quoteJSvalue($parameterArray['itemFormElName']) . ']'
180 . '.options[' . $selectItemCounter . '].getAttribute(\'data-icon\'); ';
181 }
182 $onClick .= implode('', $parameterArray['fieldChangeFunc']);
183 $onClick .= 'this.blur();return false;';
184 $selectIcons[] = array(
185 'title' => $title,
186 'icon' => $icon,
187 'index' => $selectItemCounter,
188 'onClick' => $onClick
189 );
190 }
191 $selectItemCounter++;
192 }
193
194 }
195
196 // No-matching-value:
197 if ($parameterArray['itemFormElValue'] && $noMatchingValue && !$parameterArray['fieldTSConfig']['disableNoMatchingValueElement'] && !$config['disableNoMatchingValueElement']) {
198 $noMatchingLabel = @sprintf($noMatchingLabel, $parameterArray['itemFormElValue']);
199 $options = '<option value="' . htmlspecialchars($parameterArray['itemFormElValue']) . '" selected="selected">' . htmlspecialchars($noMatchingLabel) . '</option>';
200 } elseif (!$selectedIcon && $selectItemGroups[0]['items'][0]['icon']) {
201 $selectedIcon = $selectItemGroups[0]['items'][0]['icon'];
202 }
203
204 // Process groups
205 foreach ($selectItemGroups as $selectItemGroup) {
206 // suppress groups without items
207 if (empty($selectItemGroup['items'])) {
208 continue;
209 }
210
211 $optionGroup = is_array($selectItemGroup['header']);
212 $options .= ($optionGroup ? '<optgroup label="' . htmlspecialchars($selectItemGroup['header']['title'], ENT_COMPAT, 'UTF-8', FALSE) . '">' : '');
213 if (is_array($selectItemGroup['items'])) {
214 foreach ($selectItemGroup['items'] as $item) {
215 $options .= '<option value="' . htmlspecialchars($item['value']) . '" data-icon="' .
216 htmlspecialchars($item['icon']) . '"'
217 . ($item['selected'] ? ' selected="selected"' : '') . '>' . $item['title'] . '</option>';
218 }
219 }
220 $options .= ($optionGroup ? '</optgroup>' : '');
221 }
222
223 // Create item form fields:
224 $sOnChange = 'if (this.options[this.selectedIndex].value==\'--div--\') {this.selectedIndex=' . $selectedIndex . ';} ';
225 if ($config['iconsInOptionTags']) {
226 $sOnChange .= 'document.getElementById(' . GeneralUtility::quoteJSvalue($selectId . '_icon') . ').innerHTML = this.options[this.selectedIndex].getAttribute(\'data-icon\'); ';
227 }
228 $sOnChange .= implode('', $parameterArray['fieldChangeFunc']);
229
230 // Add icons in option tags
231 $prepend = '';
232 $append = '';
233 if ($config['iconsInOptionTags']) {
234 $prepend = '<div class="input-group"><div id="' . $selectId . '_icon" class="input-group-addon input-group-icon t3js-formengine-select-prepend">' . $selectedIcon . '</div>';
235 $append = '</div>';
236 }
237
238 // Build the element
239 $out .= '
240 <div class="form-control-wrap">
241 ' . $prepend . '
242 <select'
243 . ' id="' . $selectId . '"'
244 . ' name="' . htmlspecialchars($parameterArray['itemFormElName']) . '"'
245 . $this->getValidationDataAsDataAttribute($config)
246 . ' class="form-control form-control-adapt"'
247 . ($size ? ' size="' . $size . '"' : '')
248 . ' onchange="' . htmlspecialchars($sOnChange) . '"'
249 . $parameterArray['onFocus']
250 . ($disabled ? ' disabled="disabled"' : '')
251 . '>
252 ' . $options . '
253 </select>
254 ' . $append . '
255 </div>';
256
257 // Create icon table:
258 if (count($selectIcons) && !$config['noIconsBelowSelect']) {
259 $selectIconColumns = (int)$config['selicon_cols'];
260 if (!$selectIconColumns) {
261 $selectIconColumns = count($selectIcons);
262 }
263 $selectIconColumns = ($selectIconColumns > 12 ? 12 : $selectIconColumns);
264 $selectIconRows = ceil(count($selectIcons) / $selectIconColumns);
265 $selectIcons = array_pad($selectIcons, $selectIconRows * $selectIconColumns, '');
266 $out .= '<div class="table-fit table-fit-inline-block"><table class="table table-condensed table-white table-center"><tbody><tr>';
267 for ($selectIconCount = 0; $selectIconCount < count($selectIcons); $selectIconCount++) {
268 if ($selectIconCount % $selectIconColumns === 0 && $selectIconCount !== 0) {
269 $out .= '</tr><tr>';
270 }
271 $out .= '<td>';
272 if (is_array($selectIcons[$selectIconCount])) {
273 $out .= (!$onlySelectedIconShown ? '<a href="#" title="' . $selectIcons[$selectIconCount]['title'] . '" onClick="' . htmlspecialchars($selectIcons[$selectIconCount]['onClick']) . '">' : '');
274 $out .= $selectIcons[$selectIconCount]['icon'];
275 $out .= (!$onlySelectedIconShown ? '</a>' : '');
276 }
277 $out .= '</td>';
278 }
279 $out .= '</tr></tbody></table></div>';
280 }
281
282 return $out;
283 }
284
285 }