[!!!][TASK] Improve flex and TCA handling in FormEngine
[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\Backend\Utility\BackendUtility;
18 use TYPO3\CMS\Core\Imaging\Icon;
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 "input type=text"
25 */
26 class InputTextElement extends AbstractFormElement
27 {
28 /**
29 * This will render a single-line input form field, possibly with various control/validation features
30 *
31 * @return array As defined in initializeResultArray() of AbstractNode
32 */
33 public function render()
34 {
35 $table = $this->data['tableName'];
36 $fieldName = $this->data['fieldName'];
37 $row = $this->data['databaseRow'];
38 $parameterArray = $this->data['parameterArray'];
39 $resultArray = $this->initializeResultArray();
40 $isDateField = false;
41
42 $config = $parameterArray['fieldConf']['config'];
43 $specConf = BackendUtility::getSpecConfParts($parameterArray['fieldConf']['defaultExtras']);
44 $size = MathUtility::forceIntegerInRange($config['size'] ?: $this->defaultInputWidth, $this->minimumInputWidth, $this->maxInputWidth);
45 $evalList = GeneralUtility::trimExplode(',', $config['eval'], true);
46 $classes = [];
47 $attributes = [];
48
49 // readonly
50 if ($config['readOnly']) {
51 $itemFormElValue = $parameterArray['itemFormElValue'];
52 if (in_array('date', $evalList)) {
53 $config['format'] = 'date';
54 } elseif (in_array('datetime', $evalList)) {
55 $config['format'] = 'datetime';
56 } elseif (in_array('time', $evalList)) {
57 $config['format'] = 'time';
58 }
59 if (in_array('password', $evalList)) {
60 $itemFormElValue = $itemFormElValue ? '*********' : '';
61 }
62 $options = $this->data;
63 $options['parameterArray'] = [
64 'fieldConf' => [
65 'config' => $config,
66 ],
67 'itemFormElValue' => $itemFormElValue,
68 ];
69 $options['renderType'] = 'none';
70 return $this->nodeFactory->create($options)->render();
71 }
72
73 if (in_array('datetime', $evalList, true)
74 || in_array('date', $evalList)) {
75 $classes[] = 't3js-datetimepicker';
76 $isDateField = true;
77 if (in_array('datetime', $evalList)) {
78 $attributes['data-date-type'] = 'datetime';
79 } elseif (in_array('date', $evalList)) {
80 $attributes['data-date-type'] = 'date';
81 }
82
83 // convert timestamp to proper ISO-8601 date so we get rid of timezone issues on the client.
84 // This only handles integer timestamps; if the field is a date(time), it already was converted to an
85 // ISO-8601 date by DatabaseRowDateTimeFields.
86 if (MathUtility::canBeInterpretedAsInteger($parameterArray['itemFormElValue']) && $parameterArray['itemFormElValue'] != 0) {
87 // output date as a ISO-8601 date; the stored value is the server time zone, so we need to treat it as such.
88 $timestamp = $parameterArray['itemFormElValue'];
89 $timestamp += date('Z', $timestamp);
90 $parameterArray['itemFormElValue'] = gmdate('c', $timestamp);
91 }
92
93 if (isset($config['range']['lower'])) {
94 $attributes['data-date-minDate'] = (int)$config['range']['lower'];
95 }
96 if (isset($config['range']['upper'])) {
97 $attributes['data-date-maxDate'] = (int)$config['range']['upper'];
98 }
99 } elseif (in_array('time', $evalList)) {
100 $isDateField = true;
101 $classes[] = 't3js-datetimepicker';
102 $attributes['data-date-type'] = 'time';
103 } elseif (in_array('timesec', $evalList)) {
104 $isDateField = true;
105 $classes[] = 't3js-datetimepicker';
106 $attributes['data-date-type'] = 'timesec';
107 }
108
109 // @todo: The whole eval handling is a mess and needs refactoring
110 foreach ($evalList as $func) {
111 switch ($func) {
112 case 'required':
113 $attributes['data-formengine-validation-rules'] = $this->getValidationDataAsJsonString(['required' => true]);
114 break;
115 default:
116 // @todo: This is ugly: The code should find out on it's own whether a eval definition is a
117 // @todo: keyword like "date", or a class reference. The global registration could be dropped then
118 // Pair hook to the one in \TYPO3\CMS\Core\DataHandling\DataHandler::checkValue_input_Eval()
119 if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tce']['formevals'][$func])) {
120 if (class_exists($func)) {
121 $evalObj = GeneralUtility::makeInstance($func);
122 if (method_exists($evalObj, 'deevaluateFieldValue')) {
123 $_params = [
124 'value' => $parameterArray['itemFormElValue']
125 ];
126 $parameterArray['itemFormElValue'] = $evalObj->deevaluateFieldValue($_params);
127 }
128 }
129 }
130 }
131 }
132 $paramsList = [
133 'field' => $parameterArray['itemFormElName'],
134 'evalList' => implode(',', $evalList),
135 'is_in' => trim($config['is_in']),
136 ];
137 // set classes
138 $classes[] = 'form-control';
139 $classes[] = 't3js-clearable';
140 $classes[] = 'hasDefaultValue';
141
142 // calculate attributes
143 $attributes['data-formengine-validation-rules'] = $this->getValidationDataAsJsonString($config);
144 $attributes['data-formengine-input-params'] = json_encode($paramsList);
145 $attributes['data-formengine-input-name'] = htmlspecialchars($parameterArray['itemFormElName']);
146 $attributes['id'] = StringUtility::getUniqueId('formengine-input-');
147 $attributes['value'] = '';
148 if (isset($config['max']) && (int)$config['max'] > 0) {
149 $attributes['maxlength'] = (int)$config['max'];
150 }
151 if (!empty($classes)) {
152 $attributes['class'] = implode(' ', $classes);
153 }
154
155 // This is the EDITABLE form field.
156 if (!empty($config['placeholder'])) {
157 $attributes['placeholder'] = trim($config['placeholder']);
158 }
159
160 if (isset($config['autocomplete'])) {
161 $attributes['autocomplete'] = empty($config['autocomplete']) ? 'new-' . $fieldName : 'on';
162 }
163
164 // Build the attribute string
165 $attributeString = '';
166 foreach ($attributes as $attributeName => $attributeValue) {
167 $attributeString .= ' ' . $attributeName . '="' . htmlspecialchars($attributeValue) . '"';
168 }
169
170 $html = '<input type="text"' . $attributeString . ' />';
171
172 // This is the ACTUAL form field - values from the EDITABLE field must be transferred to this field which is the one that is written to the database.
173 $html .= '<input type="hidden" name="' . $parameterArray['itemFormElName'] . '" value="' . htmlspecialchars($parameterArray['itemFormElValue']) . '" />';
174
175 // Going through all custom evaluations configured for this field
176 // @todo: Similar to above code!
177 foreach ($evalList as $evalData) {
178 if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tce']['formevals'][$evalData])) {
179 if (class_exists($evalData)) {
180 $evalObj = GeneralUtility::makeInstance($evalData);
181 if (method_exists($evalObj, 'returnFieldJS')) {
182 $resultArray['additionalJavaScriptPost'][] = 'TBE_EDITOR.customEvalFunctions[' . GeneralUtility::quoteJSvalue($evalData) . '] = function(value) {' . $evalObj->returnFieldJS() . '};';
183 }
184 }
185 }
186 }
187
188 // add HTML wrapper
189 if ($isDateField) {
190 $html = '
191 <div class="input-group">
192 ' . $html . '
193 <span class="input-group-btn">
194 <label class="btn btn-default" for="' . $attributes['id'] . '">
195 ' . $this->iconFactory->getIcon('actions-edit-pick-date', Icon::SIZE_SMALL)->render() . '
196 </label>
197 </span>
198 </div>';
199 }
200
201 // Wrap a wizard around the item?
202 $html = $this->renderWizards(
203 [$html],
204 $config['wizards'],
205 $table,
206 $row,
207 $fieldName,
208 $parameterArray,
209 $parameterArray['itemFormElName'],
210 $specConf
211 );
212
213 // Add a wrapper to remain maximum width
214 $width = (int)$this->formMaxWidth($size);
215 $html = '<div class="form-control-wrap"' . ($width ? ' style="max-width: ' . $width . 'px"' : '') . '>' . $html . '</div>';
216 $resultArray['html'] = $html;
217 return $resultArray;
218 }
219 }