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