[BUGFIX] Correct record title escaping
[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\Backend\Form\InlineStackProcessor;
18 use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility;
19 use TYPO3\CMS\Backend\Utility\BackendUtility;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21 use TYPO3\CMS\Core\Utility\StringUtility;
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 * Render single element
33 *
34 * @return array As defined in initializeResultArray() of AbstractNode
35 */
36 public function render()
37 {
38 $table = $this->data['tableName'];
39 $field = $this->data['fieldName'];
40 $row = $this->data['databaseRow'];
41 $parameterArray = $this->data['parameterArray'];
42 $config = $parameterArray['fieldConf']['config'];
43
44 $selectItems = $parameterArray['fieldConf']['config']['items'];
45
46 // Check against inline uniqueness
47 /** @var InlineStackProcessor $inlineStackProcessor */
48 $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
49 $inlineStackProcessor->initializeByGivenStructure($this->data['inlineStructure']);
50 $uniqueIds = null;
51 if ($this->data['isInlineChild'] && $this->data['inlineParentUid']) {
52 $inlineObjectName = $inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
53 $inlineFormName = $inlineStackProcessor->getCurrentStructureFormPrefix();
54 if ($this->data['inlineParentConfig']['foreign_table'] === $table
55 && $this->data['inlineParentConfig']['foreign_unique'] === $field
56 ) {
57 $uniqueIds = $this->data['inlineData']['unique'][$inlineObjectName . '-' . $table]['used'];
58 $parameterArray['fieldChangeFunc']['inlineUnique'] = 'inline.updateUnique(this,'
59 . GeneralUtility::quoteJSvalue($inlineObjectName . '-' . $table) . ','
60 . GeneralUtility::quoteJSvalue($inlineFormName) . ','
61 . GeneralUtility::quoteJSvalue($row['uid']) . ');';
62 }
63 // hide uid of parent record for symmetric relations
64 if ($this->data['inlineParentConfig']['foreign_table'] === $table
65 && (
66 $this->data['inlineParentConfig']['foreign_field'] === $field
67 || $this->data['inlineParentConfig']['symmetric_field'] === $field
68 )
69 ) {
70 $uniqueIds[] = $this->data['inlineParentUid'];
71 }
72 }
73
74 // Initialization:
75 $selectId = StringUtility::getUniqueId('tceforms-select-');
76 $selectedIcon = '';
77 $size = (int)$config['size'];
78
79 // Style set on <select/>
80 $options = '';
81 $disabled = false;
82 if (!empty($config['readOnly'])) {
83 $disabled = true;
84 }
85
86 // Prepare groups
87 $selectItemCounter = 0;
88 $selectItemGroupCount = 0;
89 $selectItemGroups = array();
90 $selectIcons = array();
91 $selectedValue = '';
92 $hasIcons = false;
93
94 if (!empty($parameterArray['itemFormElValue'])) {
95 $selectedValue = (string)$parameterArray['itemFormElValue'][0];
96 }
97
98 foreach ($selectItems as $item) {
99 if ($item[1] === '--div--') {
100 // IS OPTGROUP
101 if ($selectItemCounter !== 0) {
102 $selectItemGroupCount++;
103 }
104 $selectItemGroups[$selectItemGroupCount]['header'] = array(
105 'title' => $item[0],
106 );
107 } else {
108 // IS ITEM
109 $icon = !empty($item[2]) ? FormEngineUtility::getIconHtml($item[2], $title, $title) : '';
110 $selected = $selectedValue === (string)$item[1];
111
112 if ($selected) {
113 $selectedIcon = $icon;
114 }
115
116 $selectItemGroups[$selectItemGroupCount]['items'][] = array(
117 'title' => $item[0],
118 'value' => $item[1],
119 'icon' => $icon,
120 'selected' => $selected,
121 'index' => $selectItemCounter
122 );
123
124 // ICON
125 if ($icon) {
126 $selectIcons[] = array(
127 'title' => $item[0],
128 'icon' => $icon,
129 'index' => $selectItemCounter,
130 );
131 }
132
133 $selectItemCounter++;
134 }
135 }
136
137 // Fallback icon
138 // @todo: assign a special icon for non matching values?
139 if (!$selectedIcon && $selectItemGroups[0]['items'][0]['icon']) {
140 $selectedIcon = $selectItemGroups[0]['items'][0]['icon'];
141 }
142
143 // Process groups
144 foreach ($selectItemGroups as $selectItemGroup) {
145 // suppress groups without items
146 if (empty($selectItemGroup['items'])) {
147 continue;
148 }
149
150 $optionGroup = is_array($selectItemGroup['header']);
151 $options .= ($optionGroup ? '<optgroup label="' . htmlspecialchars($selectItemGroup['header']['title'], ENT_COMPAT, 'UTF-8', false) . '">' : '');
152
153 if (is_array($selectItemGroup['items'])) {
154 foreach ($selectItemGroup['items'] as $item) {
155 $options .= '<option value="' . htmlspecialchars($item['value']) . '" data-icon="' .
156 htmlspecialchars($item['icon']) . '"'
157 . ($item['selected'] ? ' selected="selected"' : '') . '>' . htmlspecialchars($item['title'], ENT_COMPAT, 'UTF-8', false) . '</option>';
158 }
159 $hasIcons = !empty($item['icon']);
160 }
161
162 $options .= ($optionGroup ? '</optgroup>' : '');
163 }
164
165 // Build the element
166 $html = ['<div class="form-control-wrap">'];
167
168 if ($hasIcons) {
169 $html[] = '<div class="input-group">';
170 $html[] = '<span class="input-group-addon input-group-icon">';
171 $html[] = $selectedIcon;
172 $html[] = '</span>';
173 }
174
175 $html[] = '<select'
176 . ' id="' . $selectId . '"'
177 . ' name="' . htmlspecialchars($parameterArray['itemFormElName']) . '"'
178 . $this->getValidationDataAsDataAttribute($config)
179 . ' class="form-control form-control-adapt"'
180 . ($size ? ' size="' . $size . '"' : '')
181 . ($disabled ? ' disabled="disabled"' : '')
182 . '>';
183 $html[] = $options;
184 $html[] = '</select>';
185
186 if ($hasIcons) {
187 $html[] = '</div>';
188 }
189
190 $html[] = '</div>';
191
192 // Create icon table:
193 if (!empty($selectIcons) && !empty($config['showIconTable'])) {
194 $selectIconColumns = (int)$config['selicon_cols'];
195
196 if (!$selectIconColumns) {
197 $selectIconColumns = count($selectIcons);
198 }
199
200 $selectIconColumns = ($selectIconColumns > 12 ? 12 : $selectIconColumns);
201 $selectIconRows = ceil(count($selectIcons) / $selectIconColumns);
202 $selectIcons = array_pad($selectIcons, $selectIconRows * $selectIconColumns, '');
203
204 $html[] = '<div class="t3js-forms-select-single-icons table-icons table-fit table-fit-inline-block">';
205 $html[] = '<table class="table table-condensed table-white table-center">';
206 $html[] = '<tbody>';
207 $html[] = '<tr>';
208
209 foreach ($selectIcons as $i => $selectIcon) {
210 if ($i % $selectIconColumns === 0 && $i !== 0) {
211 $html[] = '</tr>';
212 $html[] = '<tr>';
213 }
214
215 $html[] = '<td>';
216
217 if (is_array($selectIcon)) {
218 $html[] = '<a href="#" title="' . htmlspecialchars($selectIcon['title'], ENT_COMPAT, 'UTF-8', false) . '" data-select-index="' . htmlspecialchars($selectIcon['index']) . '">';
219 $html[] = $selectIcon['icon'];
220 $html[] = '</a>';
221 }
222
223 $html[] = '</td>';
224 }
225
226 $html[] = '</tr>';
227 $html[] = '</tbody>';
228 $html[] = '</table>';
229 $html[] = '</div>';
230 }
231
232 $html = implode(LF, $html);
233
234 // Wizards:
235 if (!$disabled) {
236 $html = $this->renderWizards(
237 array($html),
238 $config['wizards'],
239 $table,
240 $row,
241 $field,
242 $parameterArray,
243 $parameterArray['itemFormElName'],
244 BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras'])
245 );
246 }
247
248 $resultArray = $this->initializeResultArray();
249 $resultArray['html'] = $html;
250 $resultArray['requireJsModules'][] = ['TYPO3/CMS/Backend/FormEngine/Element/SelectSingleElement' => implode(LF, [
251 'function(SelectSingleElement) {',
252 'SelectSingleElement.initialize(',
253 GeneralUtility::quoteJSvalue('#' . $selectId) . ',',
254 '{',
255 'onChange: function() {',
256 implode('', $parameterArray['fieldChangeFunc']),
257 '},',
258 'onFocus: function() {',
259 $parameterArray['onFocus'],
260 '},',
261 '}',
262 ');',
263 '}',
264 ])];
265
266 return $resultArray;
267 }
268 }