[BUGFIX] Unify return types for PDO drivers
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / Element / TextElement.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\Authentication\BackendUserAuthentication;
18 use TYPO3\CMS\Core\Localization\LanguageService;
19 use TYPO3\CMS\Core\Utility\GeneralUtility;
20 use TYPO3\CMS\Core\Utility\MathUtility;
21 use TYPO3\CMS\Core\Utility\StringUtility;
22
23 /**
24 * Generation of TCEform elements of the type "text"
25 */
26 class TextElement extends AbstractFormElement
27 {
28 /**
29 * Default field information enabled for this element.
30 *
31 * @var array
32 */
33 protected $defaultFieldInformation = [
34 'tcaDescription' => [
35 'renderType' => 'tcaDescription',
36 ],
37 ];
38
39 /**
40 * Default field wizards enabled for this element.
41 *
42 * @var array
43 */
44 protected $defaultFieldWizard = [
45 'localizationStateSelector' => [
46 'renderType' => 'localizationStateSelector',
47 ],
48 'otherLanguageContent' => [
49 'renderType' => 'otherLanguageContent',
50 'after' => [
51 'localizationStateSelector'
52 ],
53 ],
54 'defaultLanguageDifferences' => [
55 'renderType' => 'defaultLanguageDifferences',
56 'after' => [
57 'otherLanguageContent',
58 ],
59 ],
60 ];
61
62 /**
63 * The number of chars expected per row when the height of a text area field is
64 * automatically calculated based on the number of characters found in the field content.
65 *
66 * @var int
67 */
68 protected $charactersPerRow = 40;
69
70 /**
71 * This will render a <textarea>
72 *
73 * @return array As defined in initializeResultArray() of AbstractNode
74 */
75 public function render()
76 {
77 $languageService = $this->getLanguageService();
78 $backendUser = $this->getBackendUserAuthentication();
79
80 $table = $this->data['tableName'];
81 $fieldName = $this->data['fieldName'];
82 $row = $this->data['databaseRow'];
83 $parameterArray = $this->data['parameterArray'];
84 $resultArray = $this->initializeResultArray();
85
86 $itemValue = $parameterArray['itemFormElValue'];
87 $config = $parameterArray['fieldConf']['config'];
88 $evalList = GeneralUtility::trimExplode(',', $config['eval'], true);
89 $cols = MathUtility::forceIntegerInRange($config['cols'] ?: $this->defaultInputWidth, $this->minimumInputWidth, $this->maxInputWidth);
90 $width = $this->formMaxWidth($cols);
91 $nullControlNameEscaped = htmlspecialchars('control[active][' . $table . '][' . $row['uid'] . '][' . $fieldName . ']');
92
93 // Setting number of rows
94 $rows = MathUtility::forceIntegerInRange($config['rows'] ?: 5, 1, 20);
95 $originalRows = $rows;
96 $itemFormElementValueLength = strlen($itemValue);
97 if ($itemFormElementValueLength > $this->charactersPerRow * 2) {
98 $rows = MathUtility::forceIntegerInRange(
99 round($itemFormElementValueLength / $this->charactersPerRow),
100 count(explode(LF, $itemValue)),
101 20
102 );
103 if ($rows < $originalRows) {
104 $rows = $originalRows;
105 }
106 }
107
108 $fieldInformationResult = $this->renderFieldInformation();
109 $fieldInformationHtml = $fieldInformationResult['html'];
110 $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldInformationResult, false);
111
112 if ($config['readOnly']) {
113 $html = [];
114 $html[] = '<div class="formengine-field-item t3js-formengine-field-item">';
115 $html[] = $fieldInformationHtml;
116 $html[] = '<div class="form-wizards-wrap">';
117 $html[] = '<div class="form-wizards-element">';
118 $html[] = '<div class="form-control-wrap" style="max-width: ' . $width . 'px">';
119 $html[] = '<textarea class="form-control" rows="' . $rows . '" disabled>';
120 $html[] = htmlspecialchars($itemValue);
121 $html[] = '</textarea>';
122 $html[] = '</div>';
123 $html[] = '</div>';
124 $html[] = '</div>';
125 $html[] = '</div>';
126 $resultArray['html'] = implode(LF, $html);
127 return $resultArray;
128 }
129
130 // @todo: The whole eval handling is a mess and needs refactoring
131 foreach ($evalList as $func) {
132 // @todo: This is ugly: The code should find out on it's own whether an eval definition is a
133 // @todo: keyword like "date", or a class reference. The global registration could be dropped then
134 // Pair hook to the one in \TYPO3\CMS\Core\DataHandling\DataHandler::checkValue_input_Eval()
135 // There is a similar hook for "evaluateFieldValue" in DataHandler and InputTextElement
136 if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tce']['formevals'][$func])) {
137 if (class_exists($func)) {
138 $evalObj = GeneralUtility::makeInstance($func);
139 if (method_exists($evalObj, 'deevaluateFieldValue')) {
140 $_params = [
141 'value' => $itemValue
142 ];
143 $itemValue = $evalObj->deevaluateFieldValue($_params);
144 }
145 }
146 }
147 }
148
149 $fieldId = StringUtility::getUniqueId('formengine-textarea-');
150
151 $attributes = [
152 'id' => $fieldId,
153 'name' => htmlspecialchars($parameterArray['itemFormElName']),
154 'data-formengine-validation-rules' => $this->getValidationDataAsJsonString($config),
155 'data-formengine-input-name' => htmlspecialchars($parameterArray['itemFormElName']),
156 'rows' => $rows,
157 'wrap' => $config['wrap'] ?: 'virtual',
158 'onChange' => implode('', $parameterArray['fieldChangeFunc']),
159 ];
160 $classes = [
161 'form-control',
162 't3js-formengine-textarea',
163 'formengine-textarea',
164 ];
165 if ($config['fixedFont']) {
166 $classes[] = 'text-monospace';
167 }
168 if ($config['enableTabulator']) {
169 $classes[] = 't3js-enable-tab';
170 }
171 $attributes['class'] = implode(' ', $classes);
172 $maximumHeight = (int)$backendUser->uc['resizeTextareas_MaxHeight'];
173 if ($maximumHeight > 0) {
174 // add the max-height from the users' preference to it
175 $attributes['style'] = 'max-height: ' . $maximumHeight . 'px';
176 }
177 if (isset($config['max']) && (int)$config['max'] > 0) {
178 $attributes['maxlength'] = (int)$config['max'];
179 }
180 if (!empty($config['placeholder'])) {
181 $attributes['placeholder'] = htmlspecialchars(trim($config['placeholder']));
182 }
183
184 $valuePickerHtml = [];
185 if (isset($config['valuePicker']['items']) && is_array($config['valuePicker']['items'])) {
186 $mode = $config['valuePicker']['mode'] ?? '';
187 $itemName = $parameterArray['itemFormElName'];
188 $fieldChangeFunc = $parameterArray['fieldChangeFunc'];
189 if ($mode === 'append') {
190 $assignValue = 'document.querySelectorAll(' . GeneralUtility::quoteJSvalue('[data-formengine-input-name="' . $itemName . '"]') . ')[0]'
191 . '.value=\'\'+this.options[this.selectedIndex].value+document.editform[' . GeneralUtility::quoteJSvalue($itemName) . '].value';
192 } elseif ($mode === 'prepend') {
193 $assignValue = 'document.querySelectorAll(' . GeneralUtility::quoteJSvalue('[data-formengine-input-name="' . $itemName . '"]') . ')[0]'
194 . '.value+=\'\'+this.options[this.selectedIndex].value';
195 } else {
196 $assignValue = 'document.querySelectorAll(' . GeneralUtility::quoteJSvalue('[data-formengine-input-name="' . $itemName . '"]') . ')[0]'
197 . '.value=this.options[this.selectedIndex].value';
198 }
199 $valuePickerHtml[] = '<select';
200 $valuePickerHtml[] = ' class="form-control tceforms-select tceforms-wizardselect"';
201 $valuePickerHtml[] = ' onchange="' . htmlspecialchars($assignValue . ';this.blur();this.selectedIndex=0;' . implode('', $fieldChangeFunc)) . '"';
202 $valuePickerHtml[] = '>';
203 $valuePickerHtml[] = '<option></option>';
204 foreach ($config['valuePicker']['items'] as $item) {
205 $valuePickerHtml[] = '<option value="' . htmlspecialchars($item[1]) . '">' . htmlspecialchars($languageService->sL($item[0])) . '</option>';
206 }
207 $valuePickerHtml[] = '</select>';
208 }
209
210 $fieldControlResult = $this->renderFieldControl();
211 $fieldControlHtml = $fieldControlResult['html'];
212 $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldControlResult, false);
213
214 $fieldWizardResult = $this->renderFieldWizard();
215 $fieldWizardHtml = $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[] = '<textarea ' . GeneralUtility::implodeAttributes($attributes, true) . '>' . htmlspecialchars($itemValue) . '</textarea>';
223 $mainFieldHtml[] = '</div>';
224 if (!empty($valuePickerHtml) || !empty($fieldControlHtml)) {
225 $mainFieldHtml[] = '<div class="form-wizards-items-aside">';
226 $mainFieldHtml[] = '<div class="btn-group">';
227 $mainFieldHtml[] = implode(LF, $valuePickerHtml);
228 $mainFieldHtml[] = $fieldControlHtml;
229 $mainFieldHtml[] = '</div>';
230 $mainFieldHtml[] = '</div>';
231 }
232 if (!empty($fieldWizardHtml)) {
233 $mainFieldHtml[] = '<div class="form-wizards-items-bottom">';
234 $mainFieldHtml[] = $fieldWizardHtml;
235 $mainFieldHtml[] = '</div>';
236 }
237 $mainFieldHtml[] = '</div>';
238 $mainFieldHtml[] = '</div>';
239 $mainFieldHtml = implode(LF, $mainFieldHtml);
240
241 $fullElement = $mainFieldHtml;
242 if ($this->hasNullCheckboxButNoPlaceholder()) {
243 $checked = $itemValue !== null ? ' checked="checked"' : '';
244 $fullElement = [];
245 $fullElement[] = '<div class="t3-form-field-disable"></div>';
246 $fullElement[] = '<div class="checkbox t3-form-field-eval-null-checkbox">';
247 $fullElement[] = '<label for="' . $nullControlNameEscaped . '">';
248 $fullElement[] = '<input type="hidden" name="' . $nullControlNameEscaped . '" value="0" />';
249 $fullElement[] = '<input type="checkbox" name="' . $nullControlNameEscaped . '" id="' . $nullControlNameEscaped . '" value="1"' . $checked . ' />';
250 $fullElement[] = $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.nullCheckbox');
251 $fullElement[] = '</label>';
252 $fullElement[] = '</div>';
253 $fullElement[] = $mainFieldHtml;
254 $fullElement = implode(LF, $fullElement);
255 } elseif ($this->hasNullCheckboxWithPlaceholder()) {
256 $checked = $itemValue !== null ? ' checked="checked"' : '';
257 $placeholder = $shortenedPlaceholder = $config['placeholder'] ?? '';
258 $disabled = '';
259 $fallbackValue = 0;
260 if (strlen($placeholder) > 0) {
261 $shortenedPlaceholder = GeneralUtility::fixed_lgd_cs($placeholder, 20);
262 if ($placeholder !== $shortenedPlaceholder) {
263 $overrideLabel = sprintf(
264 $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.placeholder.override'),
265 '<span title="' . htmlspecialchars($placeholder) . '">' . htmlspecialchars($shortenedPlaceholder) . '</span>'
266 );
267 } else {
268 $overrideLabel = sprintf(
269 $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.placeholder.override'),
270 htmlspecialchars($placeholder)
271 );
272 }
273 } else {
274 $overrideLabel = $languageService->sL(
275 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.placeholder.override_not_available'
276 );
277 }
278 $fullElement = [];
279 $fullElement[] = '<div class="checkbox t3js-form-field-eval-null-placeholder-checkbox">';
280 $fullElement[] = '<label for="' . $nullControlNameEscaped . '">';
281 $fullElement[] = '<input type="hidden" name="' . $nullControlNameEscaped . '" value="' . $fallbackValue . '" />';
282 $fullElement[] = '<input type="checkbox" name="' . $nullControlNameEscaped . '" id="' . $nullControlNameEscaped . '" value="1"' . $checked . $disabled . ' />';
283 $fullElement[] = $overrideLabel;
284 $fullElement[] = '</label>';
285 $fullElement[] = '</div>';
286 $fullElement[] = '<div class="t3js-formengine-placeholder-placeholder">';
287 $fullElement[] = '<div class="form-control-wrap" style="max-width:' . $width . 'px">';
288 $fullElement[] = '<textarea';
289 $fullElement[] = ' class="form-control formengine-textarea' . (isset($config['fixedFont']) ? ' text-monospace' : '') . '"';
290 $fullElement[] = ' disabled="disabled"';
291 $fullElement[] = ' rows="' . htmlspecialchars($attributes['rows']) . '"';
292 $fullElement[] = ' wrap="' . htmlspecialchars($attributes['wrap']) . '"';
293 $fullElement[] = isset($attributes['style']) ? ' style="' . htmlspecialchars($attributes['style']) . '"' : '';
294 $fullElement[] = isset($attributes['maxlength']) ? ' maxlength="' . htmlspecialchars($attributes['maxlength']) . '"' : '';
295 $fullElement[] = '>';
296 $fullElement[] = htmlspecialchars($shortenedPlaceholder);
297 $fullElement[] = '</textarea>';
298 $fullElement[] = '</div>';
299 $fullElement[] = '</div>';
300 $fullElement[] = '<div class="t3js-formengine-placeholder-formfield">';
301 $fullElement[] = $mainFieldHtml;
302 $fullElement[] = '</div>';
303 $fullElement = implode(LF, $fullElement);
304 }
305
306 $resultArray['requireJsModules'][] = ['TYPO3/CMS/Backend/FormEngine/Element/TextElement' => '
307 function(TextElement) {
308 new TextElement(' . GeneralUtility::quoteJSvalue($fieldId) . ');
309 }'
310 ];
311 $resultArray['html'] = '<div class="formengine-field-item t3js-formengine-field-item">' . $fieldInformationHtml . $fullElement . '</div>';
312 return $resultArray;
313 }
314
315 /**
316 * @return BackendUserAuthentication
317 */
318 protected function getBackendUserAuthentication()
319 {
320 return $GLOBALS['BE_USER'];
321 }
322
323 /**
324 * @return LanguageService
325 */
326 protected function getLanguageService()
327 {
328 return $GLOBALS['LANG'];
329 }
330 }