[TASK] Use null coalescing operator where possible
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / Element / AbstractFormElement.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\Form\AbstractNode;
18 use TYPO3\CMS\Backend\Form\NodeFactory;
19 use TYPO3\CMS\Backend\Utility\BackendUtility;
20 use TYPO3\CMS\Core\Imaging\IconFactory;
21 use TYPO3\CMS\Core\Localization\LanguageService;
22 use TYPO3\CMS\Core\Utility\ArrayUtility;
23 use TYPO3\CMS\Core\Utility\GeneralUtility;
24 use TYPO3\CMS\Core\Utility\MathUtility;
25
26 /**
27 * Base class for form elements of FormEngine. Contains several helper methods used by single elements.
28 */
29 abstract class AbstractFormElement extends AbstractNode
30 {
31 /**
32 * Default width value for a couple of elements like text
33 *
34 * @var int
35 */
36 protected $defaultInputWidth = 30;
37
38 /**
39 * Minimum width value for a couple of elements like text
40 *
41 * @var int
42 */
43 protected $minimumInputWidth = 10;
44
45 /**
46 * Maximum width value for a couple of elements like text
47 *
48 * @var int
49 */
50 protected $maxInputWidth = 50;
51
52 /**
53 * @var IconFactory
54 */
55 protected $iconFactory;
56
57 /**
58 * Container objects give $nodeFactory down to other containers.
59 *
60 * @param NodeFactory $nodeFactory
61 * @param array $data
62 */
63 public function __construct(NodeFactory $nodeFactory, array $data)
64 {
65 parent::__construct($nodeFactory, $data);
66 $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
67 }
68
69 /**
70 * Merge field information configuration with default and render them.
71 *
72 * @return array Result array
73 */
74 protected function renderFieldInformation(): array
75 {
76 $options = $this->data;
77 $fieldInformation = $this->defaultFieldInformation;
78 $fieldInformationFromTca = $options['parameterArray']['fieldConf']['config']['fieldInformation'] ?? [];
79 ArrayUtility::mergeRecursiveWithOverrule($fieldInformation, $fieldInformationFromTca);
80 $options['renderType'] = 'fieldInformation';
81 $options['renderData']['fieldInformation'] = $fieldInformation;
82 return $this->nodeFactory->create($options)->render();
83 }
84
85 /**
86 * Merge field control configuration with default controls and render them.
87 *
88 * @return array Result array
89 */
90 protected function renderFieldControl(): array
91 {
92 $options = $this->data;
93 $fieldControl = $this->defaultFieldControl;
94 $fieldControlFromTca = $options['parameterArray']['fieldConf']['config']['fieldControl'] ?? [];
95 ArrayUtility::mergeRecursiveWithOverrule($fieldControl, $fieldControlFromTca);
96 $options['renderType'] = 'fieldControl';
97 $options['renderData']['fieldControl'] = $fieldControl;
98 return $this->nodeFactory->create($options)->render();
99 }
100
101 /**
102 * Merge field wizard configuration with default wizards and render them.
103 *
104 * @return array Result array
105 */
106 protected function renderFieldWizard(): array
107 {
108 $options = $this->data;
109 $fieldWizard = $this->defaultFieldWizard;
110 $fieldWizardFromTca = $options['parameterArray']['fieldConf']['config']['fieldWizard'] ?? [];
111 ArrayUtility::mergeRecursiveWithOverrule($fieldWizard, $fieldWizardFromTca);
112 $options['renderType'] = 'fieldWizard';
113 $options['renderData']['fieldWizard'] = $fieldWizard;
114 return $this->nodeFactory->create($options)->render();
115 }
116
117 /**
118 * Returns true if the "null value" checkbox should be rendered. This is used in some
119 * "text" based types like "text" and "input" for some renderType's.
120 *
121 * A field has eval=null set, but has no useOverridePlaceholder defined.
122 * Goal is to have a field that can distinct between NULL and empty string in the database.
123 * A checkbox and an additional hidden field will be created, both with the same name
124 * and prefixed with "control[active]". If the checkbox is set (value 1), the value from the casual
125 * input field will be written to the database. If the checkbox is not set, the hidden field
126 * transfers value=0 to DataHandler, the value of the input field will then be reset to NULL by the
127 * DataHandler at an early point in processing, so NULL will be written to DB as field value.
128 *
129 * All that only works if the field is not within flex form scope since flex forms
130 * can not store a "null" value or distinct it from "empty string".
131 *
132 * @return bool
133 */
134 protected function hasNullCheckboxButNoPlaceholder(): bool
135 {
136 $hasNullCheckboxNoPlaceholder = false;
137 $parameterArray = $this->data['parameterArray'];
138 $mode = $parameterArray['fieldConf']['config']['mode'] ?? '';
139 if (empty($this->data['flexFormDataStructureIdentifier'])
140 && !empty($parameterArray['fieldConf']['config']['eval'])
141 && GeneralUtility::inList($parameterArray['fieldConf']['config']['eval'], 'null')
142 && ($mode !== 'useOrOverridePlaceholder')
143 ) {
144 $hasNullCheckboxNoPlaceholder = true;
145 }
146 return $hasNullCheckboxNoPlaceholder;
147 }
148
149 /**
150 * Returns true if the "null value" checkbox should be rendered and the placeholder
151 * handling is enabled. This is used in some "text" based types like "text" and
152 * "input" for some renderType's.
153 *
154 * A field has useOverridePlaceholder set and null in eval and is not within a flex form.
155 * Here, a value from a deeper DB structure can be "fetched up" as value, and can also be overridden by a
156 * local value. This is used in FAL, where eg. the "title" field can have the default value from sys_file_metadata,
157 * the title field of sys_file_reference is then set to NULL. Or the "override" checkbox is set, and a string
158 * or an empty string is then written to the field of sys_file_reference.
159 * The situation is similar to hasNullCheckboxButNoPlaceholder(), but additionally a "default" value should be shown.
160 * To achieve this, again a hidden control[hidden] field is added together with a checkbox with the same name
161 * to transfer the information whether the default value should be used or not: Checkbox checked transfers 1 as
162 * value in control[active], meaning the overridden value should be used.
163 * Additionally to the casual input field, a second field is added containing the "placeholder" value. This
164 * field has no name attribute and is not transferred at all. Those two are then hidden / shown depending
165 * on the state of the above checkbox in via JS.
166 *
167 * @return bool
168 */
169 protected function hasNullCheckboxWithPlaceholder(): bool
170 {
171 $hasNullCheckboxWithPlaceholder = false;
172 $parameterArray = $this->data['parameterArray'];
173 $mode = $parameterArray['fieldConf']['config']['mode'] ?? '';
174 if (empty($this->data['flexFormDataStructureIdentifier'])
175 && !empty($parameterArray['fieldConf']['config']['eval'])
176 && GeneralUtility::inList($parameterArray['fieldConf']['config']['eval'], 'null')
177 && ($mode === 'useOrOverridePlaceholder')
178 ) {
179 $hasNullCheckboxWithPlaceholder = true;
180 }
181 return $hasNullCheckboxWithPlaceholder;
182 }
183
184 /**
185 * Format field content if 'format' is set to date, filesize, ..., user
186 *
187 * @param string $format Configuration for the display.
188 * @param string $itemValue The value to display
189 * @param array $formatOptions Format options
190 * @return string Formatted field value
191 */
192 protected function formatValue($format, $itemValue, $formatOptions = [])
193 {
194 switch ($format) {
195 case 'date':
196 if ($itemValue) {
197 $option = isset($formatOptions['option']) ? trim($formatOptions['option']) : '';
198 if ($option) {
199 if (isset($formatOptions['strftime']) && $formatOptions['strftime']) {
200 $value = strftime($option, $itemValue);
201 } else {
202 $value = date($option, $itemValue);
203 }
204 } else {
205 $value = date('d-m-Y', $itemValue);
206 }
207 } else {
208 $value = '';
209 }
210 if (isset($formatOptions['appendAge']) && $formatOptions['appendAge']) {
211 $age = BackendUtility::calcAge(
212 $GLOBALS['EXEC_TIME'] - $itemValue,
213 $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears')
214 );
215 $value .= ' (' . $age . ')';
216 }
217 $itemValue = $value;
218 break;
219 case 'datetime':
220 // compatibility with "eval" (type "input")
221 if ($itemValue !== '' && !is_null($itemValue)) {
222 $itemValue = date('H:i d-m-Y', (int)$itemValue);
223 }
224 break;
225 case 'time':
226 // compatibility with "eval" (type "input")
227 if ($itemValue !== '' && !is_null($itemValue)) {
228 $itemValue = gmdate('H:i', (int)$itemValue);
229 }
230 break;
231 case 'timesec':
232 // compatibility with "eval" (type "input")
233 if ($itemValue !== '' && !is_null($itemValue)) {
234 $itemValue = gmdate('H:i:s', (int)$itemValue);
235 }
236 break;
237 case 'year':
238 // compatibility with "eval" (type "input")
239 if ($itemValue !== '' && !is_null($itemValue)) {
240 $itemValue = date('Y', (int)$itemValue);
241 }
242 break;
243 case 'int':
244 $baseArr = ['dec' => 'd', 'hex' => 'x', 'HEX' => 'X', 'oct' => 'o', 'bin' => 'b'];
245 $base = isset($formatOptions['base']) ? trim($formatOptions['base']) : '';
246 $format = $baseArr[$base] ?? 'd';
247 $itemValue = sprintf('%' . $format, $itemValue);
248 break;
249 case 'float':
250 // default precision
251 $precision = 2;
252 if (isset($formatOptions['precision'])) {
253 $precision = MathUtility::forceIntegerInRange($formatOptions['precision'], 1, 10, $precision);
254 }
255 $itemValue = sprintf('%.' . $precision . 'f', $itemValue);
256 break;
257 case 'number':
258 $format = isset($formatOptions['option']) ? trim($formatOptions['option']) : '';
259 $itemValue = sprintf('%' . $format, $itemValue);
260 break;
261 case 'md5':
262 $itemValue = md5($itemValue);
263 break;
264 case 'filesize':
265 // We need to cast to int here, otherwise empty values result in empty output,
266 // but we expect zero.
267 $value = GeneralUtility::formatSize((int)$itemValue);
268 if (!empty($formatOptions['appendByteSize'])) {
269 $value .= ' (' . $itemValue . ')';
270 }
271 $itemValue = $value;
272 break;
273 case 'user':
274 $func = trim($formatOptions['userFunc']);
275 if ($func) {
276 $params = [
277 'value' => $itemValue,
278 'args' => $formatOptions['userFunc'],
279 'config' => [
280 'type' => 'none',
281 'format' => $format,
282 'format.' => $formatOptions,
283 ],
284 ];
285 $itemValue = GeneralUtility::callUserFunction($func, $params, $this);
286 }
287 break;
288 default:
289 // Do nothing e.g. when $format === ''
290 }
291 return $itemValue;
292 }
293
294 /**
295 * Returns the max width in pixels for an elements like input and text
296 *
297 * @param int $size The abstract size value (1-48)
298 * @return int Maximum width in pixels
299 */
300 protected function formMaxWidth($size = 48)
301 {
302 $compensationForLargeDocuments = 1.33;
303 $compensationForFormFields = 12;
304
305 $size = round($size * $compensationForLargeDocuments);
306 return ceil($size * $compensationForFormFields);
307 }
308
309 /**
310 * @return LanguageService
311 */
312 protected function getLanguageService()
313 {
314 return $GLOBALS['LANG'];
315 }
316 }