4330e9158fe099586d5ae5828ed32dc329bf045a
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / FormDataProvider / TcaRecordTitle.php
1 <?php
2 namespace TYPO3\CMS\Backend\Form\FormDataProvider;
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\FormDataProviderInterface;
18 use TYPO3\CMS\Backend\Utility\BackendUtility;
19 use TYPO3\CMS\Core\Database\Query\QueryHelper;
20 use TYPO3\CMS\Core\Localization\LanguageService;
21 use TYPO3\CMS\Core\Utility\GeneralUtility;
22
23 /**
24 * Determine the title of a record and write it to $result['recordTitle'].
25 *
26 * TCA ctrl fields like label and label_alt are evaluated and their
27 * current values from databaseRow used to create the title.
28 */
29 class TcaRecordTitle implements FormDataProviderInterface
30 {
31 /**
32 * Enrich the processed record information with the resolved title
33 *
34 * @param array $result Incoming result array
35 * @return array Modified array
36 */
37 public function addData(array $result)
38 {
39 if (!isset($result['processedTca']['ctrl']['label'])) {
40 throw new \UnexpectedValueException(
41 'TCA of table ' . $result['tableName'] . ' misses required [\'ctrl\'][\'label\'] definition.',
42 1443706103
43 );
44 }
45
46 if ($result['isInlineChild'] && isset($result['processedTca']['ctrl']['formattedLabel_userFunc'])) {
47 // inline child with formatted user func is first
48 $parameters = [
49 'table' => $result['tableName'],
50 'row' => $result['databaseRow'],
51 'title' => '',
52 'isOnSymmetricSide' => $result['isOnSymmetricSide'],
53 'options' => $result['processedTca']['ctrl']['formattedLabel_userFunc_options'] ?? [],
54 'parent' => [
55 'uid' => $result['databaseRow']['uid'],
56 'config' => $result['inlineParentConfig']
57 ]
58 ];
59 // callUserFunction requires a third parameter, but we don't want to give $this as reference!
60 $null = null;
61 GeneralUtility::callUserFunction($result['processedTca']['ctrl']['formattedLabel_userFunc'], $parameters, $null);
62 $result['recordTitle'] = $parameters['title'];
63 } elseif ($result['isInlineChild'] && (isset($result['inlineParentConfig']['foreign_label'])
64 || isset($result['inlineParentConfig']['symmetric_label']))
65 ) {
66 // inline child with foreign label or symmetric inline child with symmetric_label
67 $fieldName = $result['isOnSymmetricSide']
68 ? $result['inlineParentConfig']['symmetric_label']
69 : $result['inlineParentConfig']['foreign_label'];
70 $result['recordTitle'] = $this->getRecordTitleForField($fieldName, $result);
71 } elseif (isset($result['processedTca']['ctrl']['label_userFunc'])) {
72 // userFunc takes precedence over everything else
73 $parameters = [
74 'table' => $result['tableName'],
75 'row' => $result['databaseRow'],
76 'title' => '',
77 'options' => $result['processedTca']['ctrl']['label_userFunc_options'] ?? [],
78 ];
79 $null = null;
80 GeneralUtility::callUserFunction($result['processedTca']['ctrl']['label_userFunc'], $parameters, $null);
81 $result['recordTitle'] = $parameters['title'];
82 } else {
83 // standard record
84 $result = $this->getRecordTitleByLabelProperties($result);
85 }
86
87 return $result;
88 }
89
90 /**
91 * Build the record title from label, label_alt and label_alt_force properties
92 *
93 * @param array $result Incoming result array
94 * @return array Modified result array
95 */
96 protected function getRecordTitleByLabelProperties(array $result)
97 {
98 $titles = [];
99 $titleByLabel = $this->getRecordTitleForField($result['processedTca']['ctrl']['label'], $result);
100 if (!empty($titleByLabel)) {
101 $titles[] = $titleByLabel;
102 }
103
104 $labelAltForce = isset($result['processedTca']['ctrl']['label_alt_force'])
105 ? (bool)$result['processedTca']['ctrl']['label_alt_force']
106 : false;
107 if (!empty($result['processedTca']['ctrl']['label_alt']) && ($labelAltForce || empty($titleByLabel))) {
108 // Dive into label_alt evaluation if label_alt_force is set or if label did not came up with a title yet
109 $labelAltFields = GeneralUtility::trimExplode(',', $result['processedTca']['ctrl']['label_alt'], true);
110 foreach ($labelAltFields as $fieldName) {
111 $titleByLabelAlt = $this->getRecordTitleForField($fieldName, $result);
112 if (!empty($titleByLabelAlt)) {
113 $titles[] = $titleByLabelAlt;
114 }
115 if (!$labelAltForce && !empty($titleByLabelAlt)) {
116 // label_alt_force creates a comma separated list of multiple fields.
117 // If not set, one found field with content is enough
118 break;
119 }
120 }
121 }
122
123 $result['recordTitle'] = implode(', ', $titles);
124 return $result;
125 }
126
127 /**
128 * Record title of a single field
129 *
130 * @param string $fieldName Field to handle
131 * @param array $result Incoming result array
132 * @return string
133 */
134 protected function getRecordTitleForField($fieldName, $result)
135 {
136 if ($fieldName === 'uid') {
137 // uid return field content directly since it usually has not TCA definition
138 return $result['databaseRow']['uid'];
139 }
140
141 if (!isset($result['processedTca']['columns'][$fieldName]['config']['type'])
142 || !is_string($result['processedTca']['columns'][$fieldName]['config']['type'])
143 ) {
144 return '';
145 }
146
147 $recordTitle = '';
148 $rawValue = null;
149 if (array_key_exists($fieldName, $result['databaseRow'])) {
150 $rawValue = $result['databaseRow'][$fieldName];
151 }
152 $fieldConfig = $result['processedTca']['columns'][$fieldName]['config'];
153 switch ($fieldConfig['type']) {
154 case 'radio':
155 $recordTitle = $this->getRecordTitleForRadioType($rawValue, $fieldConfig);
156 break;
157 case 'inline':
158 $recordTitle = $this->getRecordTitleForInlineType(
159 $rawValue,
160 $result['processedTca']['columns'][$fieldName]['children']
161 );
162 break;
163 case 'select':
164 $recordTitle = $this->getRecordTitleForSelectType($rawValue, $fieldConfig);
165 break;
166 case 'group':
167 $recordTitle = $this->getRecordTitleForGroupType($rawValue, $fieldConfig);
168 break;
169 case 'check':
170 $recordTitle = $this->getRecordTitleForCheckboxType($rawValue, $fieldConfig);
171 break;
172 case 'input':
173 $recordTitle = $this->getRecordTitleForInputType($rawValue, $fieldConfig);
174 break;
175 case 'text':
176 $recordTitle = $this->getRecordTitleForTextType($rawValue);
177 break;
178 case 'flex':
179 // @todo: Check if and how a label could be generated from flex field data
180 default:
181
182 }
183
184 return $recordTitle;
185 }
186
187 /**
188 * Return the record title for radio fields
189 *
190 * @param mixed $value Current database value of this field
191 * @param array $fieldConfig TCA field configuration
192 * @return string
193 */
194 protected function getRecordTitleForRadioType($value, $fieldConfig)
195 {
196 if (!isset($fieldConfig['items']) || !is_array($fieldConfig['items'])) {
197 return '';
198 }
199 foreach ($fieldConfig['items'] as $item) {
200 list($itemLabel, $itemValue) = $item;
201 if ((string)$value === (string)$itemValue) {
202 return $itemLabel;
203 }
204 }
205 return '';
206 }
207
208 /**
209 * @param int $value
210 * @param array $children
211 * @return string
212 */
213 protected function getRecordTitleForInlineType($value, array $children)
214 {
215 foreach ($children as $child) {
216 if ((int)$value === $child['vanillaUid']) {
217 return $child['recordTitle'];
218 }
219 }
220
221 return '';
222 }
223
224 /**
225 * Return the record title for database records
226 *
227 * @param mixed $value Current database value of this field
228 * @param array $fieldConfig TCA field configuration
229 * @return string
230 */
231 protected function getRecordTitleForSelectType($value, $fieldConfig)
232 {
233 if (!is_array($value)) {
234 return '';
235 }
236 $labelParts = [];
237 foreach ($value as $itemValue) {
238 $itemKey = array_search($itemValue, array_column($fieldConfig['items'], 1));
239 if ($itemKey !== false) {
240 $labelParts[] = $fieldConfig['items'][$itemKey][0];
241 }
242 }
243 $title = implode(', ', $labelParts);
244 if (empty($title) && !empty($value)) {
245 $title = implode(', ', $value);
246 }
247 return $title;
248 }
249
250 /**
251 * Return the record title for database records
252 *
253 * @param mixed $value Current database value of this field
254 * @param array $fieldConfig TCA field configuration
255 * @return string
256 */
257 protected function getRecordTitleForGroupType($value, $fieldConfig)
258 {
259 $labelParts = [];
260 foreach ($value as $singleValue) {
261 if (isset($singleValue['uidOrPath'])) {
262 $labelParts[] = $singleValue['uidOrPath'];
263 } elseif (isset($singleValue['folder'])) {
264 $labelParts[] = $singleValue['folder'];
265 } else {
266 $labelParts[] = $singleValue['title'];
267 }
268 }
269 return implode(', ', $labelParts);
270 }
271
272 /**
273 * Returns the record title for checkbox fields
274 *
275 * @param mixed $value Current database value of this field
276 * @param array $fieldConfig TCA field configuration
277 * @return string
278 */
279 protected function getRecordTitleForCheckboxType($value, $fieldConfig)
280 {
281 $languageService = $this->getLanguageService();
282 if (empty($fieldConfig['items']) || !is_array($fieldConfig['items'])) {
283 $title = (bool)$value
284 ? $languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:yes')
285 : $languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:no');
286 } else {
287 $labelParts = [];
288 foreach ($fieldConfig['items'] as $key => $val) {
289 if ($value & pow(2, $key)) {
290 $labelParts[] = $val[0];
291 }
292 }
293 $title = implode(', ', $labelParts);
294 }
295 return $title;
296 }
297
298 /**
299 * Returns the record title for input fields
300 *
301 * @param mixed $value Current database value of this field
302 * @param array $fieldConfig TCA field configuration
303 * @return string
304 */
305 protected function getRecordTitleForInputType($value, $fieldConfig)
306 {
307 if (!isset($value)) {
308 return '';
309 }
310 $title = $value;
311 $dateTimeFormats = QueryHelper::getDateTimeFormats();
312 if (GeneralUtility::inList($fieldConfig['eval'], 'date')) {
313 // Handle native date field
314 if (isset($fieldConfig['dbType']) && $fieldConfig['dbType'] === 'date') {
315 $value = $value === $dateTimeFormats['date']['empty'] ? 0 : (int)strtotime($value);
316 } else {
317 $value = (int)$value;
318 }
319 if (!empty($value)) {
320 $ageSuffix = '';
321 // Generate age suffix as long as not explicitly suppressed
322 if (!isset($fieldConfig['disableAgeDisplay']) || (bool)$fieldConfig['disableAgeDisplay'] === false) {
323 $ageDelta = $GLOBALS['EXEC_TIME'] - $value;
324 $calculatedAge = BackendUtility::calcAge(
325 abs($ageDelta),
326 $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears')
327 );
328 $ageSuffix = ' (' . ($ageDelta > 0 ? '-' : '') . $calculatedAge . ')';
329 }
330 $title = BackendUtility::date($value) . $ageSuffix;
331 }
332 } elseif (GeneralUtility::inList($fieldConfig['eval'], 'time')) {
333 // Handle native time field
334 if (isset($fieldConfig['dbType']) && $fieldConfig['dbType'] === 'time') {
335 $value = $value === $dateTimeFormats['time']['empty'] ? 0 : (int)strtotime('1970-01-01 ' . $value);
336 } else {
337 $value = (int)$value;
338 }
339 if (!empty($value)) {
340 $title = gmdate('H:i', (int)$value);
341 }
342 } elseif (GeneralUtility::inList($fieldConfig['eval'], 'timesec')) {
343 // Handle native time field
344 if (isset($fieldConfig['dbType']) && $fieldConfig['dbType'] === 'time') {
345 $value = $value === $dateTimeFormats['time']['empty'] ? 0 : (int)strtotime('1970-01-01 ' . $value);
346 } else {
347 $value = (int)$value;
348 }
349 if (!empty($value)) {
350 $title = gmdate('H:i:s', (int)$value);
351 }
352 } elseif (GeneralUtility::inList($fieldConfig['eval'], 'datetime')) {
353 // Handle native datetime field
354 if (isset($fieldConfig['dbType']) && $fieldConfig['dbType'] === 'datetime') {
355 $value = $value === $dateTimeFormats['datetime']['empty'] ? 0 : (int)strtotime($value);
356 } else {
357 $value = (int)$value;
358 }
359 if (!empty($value)) {
360 $title = BackendUtility::datetime($value);
361 }
362 }
363 return $title;
364 }
365
366 /**
367 * Returns the record title for text fields
368 *
369 * @param mixed $value Current database value of this field
370 * @return string
371 */
372 protected function getRecordTitleForTextType($value)
373 {
374 return trim(strip_tags($value));
375 }
376
377 /**
378 * @return LanguageService
379 */
380 protected function getLanguageService()
381 {
382 return $GLOBALS['LANG'];
383 }
384 }