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