[BUGFIX] backend formfields: Make boolean fields UI working in Edge/IE
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / Element / InputTextElement.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\Core\Utility\StringUtility;
20 use TYPO3\CMS\Lang\LanguageService;
21
22 /**
23 * General type=input element.
24 *
25 * This one kicks in if no specific renderType like "inputDateTime"
26 * or "inputColorPicker" is set.
27 */
28 class InputTextElement extends AbstractFormElement
29 {
30 /**
31 * Default field wizards enabled for this element.
32 *
33 * @var array
34 */
35 protected $defaultFieldWizard = [
36 'localizationStateSelector' => [
37 'renderType' => 'localizationStateSelector',
38 ],
39 'otherLanguageContent' => [
40 'renderType' => 'otherLanguageContent',
41 'after' => [
42 'localizationStateSelector'
43 ],
44 ],
45 'defaultLanguageDifferences' => [
46 'renderType' => 'defaultLanguageDifferences',
47 'after' => [
48 'otherLanguageContent',
49 ],
50 ],
51 ];
52
53 /**
54 * This will render a single-line input form field, possibly with various control/validation features
55 *
56 * @return array As defined in initializeResultArray() of AbstractNode
57 */
58 public function render()
59 {
60 $languageService = $this->getLanguageService();
61
62 $table = $this->data['tableName'];
63 $fieldName = $this->data['fieldName'];
64 $row = $this->data['databaseRow'];
65 $parameterArray = $this->data['parameterArray'];
66 $resultArray = $this->initializeResultArray();
67
68 $itemValue = $parameterArray['itemFormElValue'];
69 $config = $parameterArray['fieldConf']['config'];
70 $evalList = GeneralUtility::trimExplode(',', $config['eval'], true);
71 $size = MathUtility::forceIntegerInRange($config['size'] ?? $this->defaultInputWidth, $this->minimumInputWidth, $this->maxInputWidth);
72 $width = (int)$this->formMaxWidth($size);
73 $nullControlNameEscaped = htmlspecialchars('control[active][' . $table . '][' . $row['uid'] . '][' . $fieldName . ']');
74
75 if ($config['readOnly']) {
76 // Early return for read only fields
77 if (in_array('password', $evalList, true)) {
78 $itemValue = $itemValue ? '*********' : '';
79 }
80 $html = [];
81 $html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
82 $html[] = '<div class="form-wizards-wrap">';
83 $html[] = '<div class="form-wizards-element">';
84 $html[] = '<div class="form-control-wrap" style="max-width: ' . $width . 'px">';
85 $html[] = '<input class="form-control" value="' . htmlspecialchars($itemValue) . '" type="text" disabled>';
86 $html[] = '</div>';
87 $html[] = '</div>';
88 $html[] = '</div>';
89 $html[] = '</div>';
90 $resultArray['html'] = implode(LF, $html);
91 return $resultArray;
92 }
93
94 // @todo: The whole eval handling is a mess and needs refactoring
95 foreach ($evalList as $func) {
96 // @todo: This is ugly: The code should find out on it's own whether a eval definition is a
97 // @todo: keyword like "date", or a class reference. The global registration could be dropped then
98 // Pair hook to the one in \TYPO3\CMS\Core\DataHandling\DataHandler::checkValue_input_Eval()
99 if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tce']['formevals'][$func])) {
100 if (class_exists($func)) {
101 $evalObj = GeneralUtility::makeInstance($func);
102 if (method_exists($evalObj, 'deevaluateFieldValue')) {
103 $_params = [
104 'value' => $itemValue
105 ];
106 $itemValue = $evalObj->deevaluateFieldValue($_params);
107 }
108 if (method_exists($evalObj, 'returnFieldJS')) {
109 $resultArray['additionalJavaScriptPost'][] = 'TBE_EDITOR.customEvalFunctions[' . GeneralUtility::quoteJSvalue($func) . ']'
110 . ' = function(value) {' . $evalObj->returnFieldJS() . '};';
111 }
112 }
113 }
114 }
115
116 $attributes = [
117 'value' => '',
118 'id' => StringUtility::getUniqueId('formengine-input-'),
119 'class' => implode(' ', [
120 'form-control',
121 't3js-clearable',
122 'hasDefaultValue',
123 ]),
124 'data-formengine-validation-rules' => $this->getValidationDataAsJsonString($config),
125 'data-formengine-input-params' => json_encode([
126 'field' => $parameterArray['itemFormElName'],
127 'evalList' => implode(',', $evalList),
128 'is_in' => trim($config['is_in'])
129 ]),
130 'data-formengine-input-name' => $parameterArray['itemFormElName'],
131 ];
132
133 $maxLength = $config['max'] ?? 0;
134 if ((int)$maxLength > 0) {
135 $attributes['maxlength'] = (int)$maxLength;
136 }
137 if (!empty($config['placeholder'])) {
138 $attributes['placeholder'] = trim($config['placeholder']);
139 }
140 if (isset($config['autocomplete'])) {
141 $attributes['autocomplete'] = empty($config['autocomplete']) ? 'new-' . $fieldName : 'on';
142 }
143
144 $valuePickerHtml = [];
145 if (isset($config['valuePicker']['items']) && is_array($config['valuePicker']['items'])) {
146 $mode = $config['valuePicker']['mode'] ?? '';
147 $itemName = $parameterArray['itemFormElName'];
148 $fieldChangeFunc = $parameterArray['fieldChangeFunc'];
149 if ($mode === 'append') {
150 $assignValue = 'document.querySelectorAll(' . GeneralUtility::quoteJSvalue('[data-formengine-input-name="' . $itemName . '"]') . ')[0]'
151 . '.value+=\'\'+this.options[this.selectedIndex].value';
152 } elseif ($mode === 'prepend') {
153 $assignValue = 'document.querySelectorAll(' . GeneralUtility::quoteJSvalue('[data-formengine-input-name="' . $itemName . '"]') . ')[0]'
154 . '.value=\'\'+this.options[this.selectedIndex].value+document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value';
155 } else {
156 $assignValue = 'document.querySelectorAll(' . GeneralUtility::quoteJSvalue('[data-formengine-input-name="' . $itemName . '"]') . ')[0]'
157 . '.value=this.options[this.selectedIndex].value';
158 }
159 $valuePickerHtml[] = '<select';
160 $valuePickerHtml[] = ' class="form-control tceforms-select tceforms-wizardselect"';
161 $valuePickerHtml[] = ' onchange="' . htmlspecialchars($assignValue . ';this.blur();this.selectedIndex=0;' . implode('', $fieldChangeFunc)) . '"';
162 $valuePickerHtml[] = '>';
163 $valuePickerHtml[] = '<option></option>';
164 foreach ($config['valuePicker']['items'] as $item) {
165 $valuePickerHtml[] = '<option value="' . htmlspecialchars($item[1]) . '">' . htmlspecialchars($languageService->sL($item[0])) . '</option>';
166 }
167 $valuePickerHtml[] = '</select>';
168 }
169
170 $valueSliderHtml = [];
171 if (isset($config['slider']) && is_array($config['slider'])) {
172 $resultArray['requireJsModules'][] = 'TYPO3/CMS/Backend/ValueSlider';
173 $min = $config['range']['lower'] ?? 0;
174 $max = $config['range']['upper'] ?? 10000;
175 $step = $config['slider']['step'] ?? 1;
176 $width = $config['slider']['width'] ?? 400;
177 $valueType = 'null';
178 if (in_array('int', $evalList, true)) {
179 $valueType = 'int';
180 $itemValue = (int)$itemValue;
181 } elseif (in_array('double2', $evalList, true)) {
182 $valueType = 'double';
183 $itemValue = (double)$itemValue;
184 }
185 $callbackParams = [ $table, $row['uid'], $fieldName, $parameterArray['itemFormElName'] ];
186 $id = 'slider-' . md5($parameterArray['itemFormElName']);
187 $valueSliderHtml[] = '<div';
188 $valueSliderHtml[] = ' id="' . $id . '"';
189 $valueSliderHtml[] = ' data-slider-id="' . $id . '"';
190 $valueSliderHtml[] = ' data-slider-min="' . (int)$min . '"';
191 $valueSliderHtml[] = ' data-slider-max="' . (int)$max . '"';
192 $valueSliderHtml[] = ' data-slider-step="' . htmlspecialchars($step) . '"';
193 $valueSliderHtml[] = ' data-slider-value="' . htmlspecialchars($itemValue) . '"';
194 $valueSliderHtml[] = ' data-slider-value-type="' . htmlspecialchars($valueType) . '"';
195 $valueSliderHtml[] = ' data-slider-item-name="' . htmlspecialchars($parameterArray['itemFormElName']) . '"';
196 $valueSliderHtml[] = ' data-slider-callback-params="' . htmlspecialchars(json_encode($callbackParams)) . '"';
197 $valueSliderHtml[] = ' style="width: ' . $width . 'px;"';
198 $valueSliderHtml[] = '>';
199 $valueSliderHtml[] = '</div>';
200 }
201
202 $legacyWizards = $this->renderWizards();
203 $legacyFieldControlHtml = implode(LF, $legacyWizards['fieldControl']);
204 $legacyFieldWizardHtml = implode(LF, $legacyWizards['fieldWizard']);
205
206 $fieldInformationResult = $this->renderFieldInformation();
207 $fieldInformationHtml = $fieldInformationResult['html'];
208 $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
209
210 $fieldControlResult = $this->renderFieldControl();
211 $fieldControlHtml = $legacyFieldControlHtml . $fieldControlResult['html'];
212 $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldControlResult, false);
213
214 $fieldWizardResult = $this->renderFieldWizard();
215 $fieldWizardHtml = $legacyFieldWizardHtml . $fieldWizardResult['html'];
216 $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
217
218 $mainFieldHtml = [];
219 $mainFieldHtml[] = '<div class="form-control-wrap" style="max-width: ' . $width . 'px">';
220 $mainFieldHtml[] = '<div class="form-wizards-wrap">';
221 $mainFieldHtml[] = '<div class="form-wizards-element">';
222 $mainFieldHtml[] = '<input type="text"' . GeneralUtility::implodeAttributes($attributes, true) . ' />';
223 $mainFieldHtml[] = '<input type="hidden" name="' . $parameterArray['itemFormElName'] . '" value="' . htmlspecialchars($itemValue) . '" />';
224 $mainFieldHtml[] = '</div>';
225 $mainFieldHtml[] = '<div class="form-wizards-items-aside">';
226 $mainFieldHtml[] = '<div class="btn-group">';
227 $mainFieldHtml[] = implode(LF, $valuePickerHtml);
228 $mainFieldHtml[] = implode(LF, $valueSliderHtml);
229 $mainFieldHtml[] = $fieldControlHtml;
230 $mainFieldHtml[] = '</div>';
231 $mainFieldHtml[] = '</div>';
232 $mainFieldHtml[] = '<div class="form-wizards-items-bottom">';
233 $mainFieldHtml[] = $fieldWizardHtml;
234 $mainFieldHtml[] = '</div>';
235 $mainFieldHtml[] = '</div>';
236 $mainFieldHtml[] = '</div>';
237 $mainFieldHtml = implode(LF, $mainFieldHtml);
238
239 $fullElement = $mainFieldHtml;
240 if ($this->hasNullCheckboxButNoPlaceholder()) {
241 $checked = $itemValue !== null ? ' checked="checked"' : '';
242 $fullElement = [];
243 $fullElement[] = '<div class="t3-form-field-disable"></div>';
244 $fullElement[] = '<div class="checkbox t3-form-field-eval-null-checkbox">';
245 $fullElement[] = '<label for="' . $nullControlNameEscaped . '">';
246 $fullElement[] = '<input type="hidden" name="' . $nullControlNameEscaped . '" value="0" />';
247 $fullElement[] = '<input type="checkbox" name="' . $nullControlNameEscaped . '" id="' . $nullControlNameEscaped . '" value="1"' . $checked . ' />';
248 $fullElement[] = $languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.nullCheckbox');
249 $fullElement[] = '</label>';
250 $fullElement[] = '</div>';
251 $fullElement[] = $mainFieldHtml;
252 $fullElement = implode(LF, $fullElement);
253 } elseif ($this->hasNullCheckboxWithPlaceholder()) {
254 $checked = $itemValue !== null ? ' checked="checked"' : '';
255 $placeholder = $shortenedPlaceholder = $config['placeholder'] ?? '';
256 $disabled = '';
257 $fallbackValue = 0;
258 if (strlen($placeholder) > 0) {
259 $shortenedPlaceholder = GeneralUtility::fixed_lgd_cs($placeholder, 20);
260 if ($placeholder !== $shortenedPlaceholder) {
261 $overrideLabel = sprintf(
262 $languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.placeholder.override'),
263 '<span title="' . htmlspecialchars($placeholder) . '">' . htmlspecialchars($shortenedPlaceholder) . '</span>'
264 );
265 } else {
266 $overrideLabel = sprintf(
267 $languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.placeholder.override'),
268 htmlspecialchars($placeholder)
269 );
270 }
271 } else {
272 $fallbackValue = 1;
273 $checked = ' checked="checked"';
274 $disabled = ' disabled="disabled"';
275 $overrideLabel = $languageService->sL(
276 'LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.placeholder.override_not_available'
277 );
278 }
279 $fullElement = [];
280 $fullElement[] = '<div class="checkbox t3js-form-field-eval-null-placeholder-checkbox">';
281 $fullElement[] = '<label for="' . $nullControlNameEscaped . '">';
282 $fullElement[] = '<input type="hidden" name="' . $nullControlNameEscaped . '" value="' . $fallbackValue . '" />';
283 $fullElement[] = '<input type="checkbox" name="' . $nullControlNameEscaped . '" id="' . $nullControlNameEscaped . '" value="1"' . $checked . $disabled . ' />';
284 $fullElement[] = $overrideLabel;
285 $fullElement[] = '</label>';
286 $fullElement[] = '</div>';
287 $fullElement[] = '<div class="t3js-formengine-placeholder-placeholder">';
288 $fullElement[] = '<div class="form-control-wrap" style="max-width:' . $width . 'px">';
289 $fullElement[] = '<input type="text" class="form-control" disabled="disabled" value="' . $shortenedPlaceholder . '" />';
290 $fullElement[] = '</div>';
291 $fullElement[] = '</div>';
292 $fullElement[] = '<div class="t3js-formengine-placeholder-formfield">';
293 $fullElement[] = $mainFieldHtml;
294 $fullElement[] = '</div>';
295 $fullElement = implode(LF, $fullElement);
296 }
297
298 $resultArray['html'] = '<div class="formengine-field-item t3js-formengine-field-item">' . $fieldInformationHtml . $fullElement . '</div>';
299 return $resultArray;
300 }
301
302 /**
303 * @return LanguageService
304 */
305 protected function getLanguageService()
306 {
307 return $GLOBALS['LANG'];
308 }
309 }