[BUGFIX] Show record title for inline element
[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\DatabaseConnection;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21 use TYPO3\CMS\Lang\LanguageService;
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' => isset($result['processedTca']['ctrl']['formattedLabel_userFunc_options'])
54 ? $result['processedTca']['ctrl']['formattedLabel_userFunc_options']
55 : [],
56 'parent' => [
57 'uid' => $result['databaseRow']['uid'],
58 'config' => $result['inlineParentConfig']
59 ]
60 ];
61 // callUserFunction requires a third parameter, but we don't want to give $this as reference!
62 $null = null;
63 GeneralUtility::callUserFunction($result['processedTca']['ctrl']['formattedLabel_userFunc'], $parameters, $null);
64 $result['recordTitle'] = $parameters['title'];
65 } elseif ($result['isInlineChild'] && (isset($result['inlineParentConfig']['foreign_label'])
66 || isset($result['inlineParentConfig']['symmetric_label']))
67 ) {
68 // inline child with foreign label or symmetric inline child with symmetric_label
69 $fieldName = $result['isOnSymmetricSide']
70 ? $result['inlineParentConfig']['symmetric_label']
71 : $result['inlineParentConfig']['foreign_label'];
72 $result['recordTitle'] = $this->getRecordTitleForField($fieldName, $result);
73 } elseif (isset($result['processedTca']['ctrl']['label_userFunc'])) {
74 // userFunc takes precedence over everything else
75 $parameters = [
76 'table' => $result['tableName'],
77 'row' => $result['databaseRow'],
78 'title' => '',
79 'options' => isset($result['processedTca']['ctrl']['label_userFunc_options'])
80 ? $result['processedTca']['ctrl']['label_userFunc_options']
81 : [],
82 ];
83 $null = null;
84 GeneralUtility::callUserFunction($result['processedTca']['ctrl']['label_userFunc'], $parameters, $null);
85 $result['recordTitle'] = $parameters['title'];
86 } else {
87 // standard record
88 $result = $this->getRecordTitleByLabelProperties($result);
89 }
90
91 return $result;
92 }
93
94 /**
95 * Build the record title from label, label_alt and label_alt_force properties
96 *
97 * @param array $result Incoming result array
98 * @return array Modified result array
99 */
100 protected function getRecordTitleByLabelProperties(array $result)
101 {
102 $titles = [];
103 $titleByLabel = $this->getRecordTitleForField($result['processedTca']['ctrl']['label'], $result);
104 if (!empty($titleByLabel)) {
105 $titles[] = $titleByLabel;
106 }
107
108 $labelAltForce = isset($result['processedTca']['ctrl']['label_alt_force'])
109 ? (bool)$result['processedTca']['ctrl']['label_alt_force']
110 : false;
111 if (!empty($result['processedTca']['ctrl']['label_alt']) && ($labelAltForce || empty($titleByLabel))) {
112 // Dive into label_alt evaluation if label_alt_force is set or if label did not came up with a title yet
113 $labelAltFields = GeneralUtility::trimExplode(',', $result['processedTca']['ctrl']['label_alt'], true);
114 foreach ($labelAltFields as $fieldName) {
115 $titleByLabelAlt = $this->getRecordTitleForField($fieldName, $result);
116 if (!empty($titleByLabelAlt)) {
117 $titles[] = $titleByLabelAlt;
118 }
119 if (!$labelAltForce && !empty($titleByLabelAlt)) {
120 // label_alt_force creates a comma separated list of multiple fields.
121 // If not set, one found field with content is enough
122 break;
123 }
124 }
125 }
126
127 $result['recordTitle'] = implode(', ', $titles);
128 return $result;
129 }
130
131 /**
132 * Record title of a single field
133 *
134 * @param string $fieldName Field to handle
135 * @param array $result Incoming result array
136 * @return string
137 */
138 protected function getRecordTitleForField($fieldName, $result)
139 {
140 if ($fieldName === 'uid') {
141 // uid return field content directly since it usually has not TCA definition
142 return $result['databaseRow']['uid'];
143 }
144
145 if (!isset($result['processedTca']['columns'][$fieldName]['config']['type'])
146 || !is_string($result['processedTca']['columns'][$fieldName]['config']['type'])
147 ) {
148 return '';
149 }
150
151 $recordTitle = '';
152 $rawValue = null;
153 if (array_key_exists($fieldName, $result['databaseRow'])) {
154 $rawValue = $result['databaseRow'][$fieldName];
155 }
156 $fieldConfig = $result['processedTca']['columns'][$fieldName]['config'];
157 switch ($fieldConfig['type']) {
158 case 'radio':
159 $recordTitle = $this->getRecordTitleForRadioType($rawValue, $fieldConfig);
160 break;
161 case 'inline':
162 $recordTitle = $this->getRecordTitleForInlineType($rawValue, $result['processedTca']['columns'][$fieldName]['children']);
163 break;
164 case 'select':
165 $recordTitle = $this->getRecordTitleForSelectType($rawValue, $fieldConfig);
166 break;
167 case 'group':
168 $recordTitle = $this->getRecordTitleForGroupType($rawValue, $fieldConfig);
169 break;
170 case 'check':
171 $recordTitle = $this->getRecordTitleForCheckboxType($rawValue, $fieldConfig);
172 break;
173 case 'input':
174 $recordTitle = $this->getRecordTitleForInputType($rawValue, $fieldConfig);
175 break;
176 case 'text':
177 $recordTitle = $this->getRecordTitleForTextType($rawValue);
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 *
212 * @return string
213 */
214 protected function getRecordTitleForInlineType($value, array $children)
215 {
216 foreach ($children as $child) {
217 if ((int)$value === $child['vanillaUid']) {
218 return $child['recordTitle'];
219 }
220 }
221
222 return '';
223 }
224
225 /**
226 * Return the record title for database records
227 *
228 * @param mixed $value Current database value of this field
229 * @param array $fieldConfig TCA field configuration
230 * @return string
231 */
232 protected function getRecordTitleForSelectType($value, $fieldConfig)
233 {
234 if (!is_array($value)) {
235 return '';
236 }
237 $labelParts = [];
238 foreach ($value as $itemValue) {
239 $itemKey = array_search($itemValue, array_column($fieldConfig['items'], 1));
240 if ($itemKey !== false) {
241 $labelParts[] = $fieldConfig['items'][$itemKey][0];
242 }
243 }
244 $title = implode(', ', $labelParts);
245 if (empty($title) && !empty($value)) {
246 $title = implode(', ', $value);
247 }
248 return $title;
249 }
250
251 /**
252 * Return the record title for database records
253 *
254 * @param mixed $value Current database value of this field
255 * @param array $fieldConfig TCA field configuration
256 * @return string
257 */
258 protected function getRecordTitleForGroupType($value, $fieldConfig)
259 {
260 if ($fieldConfig['internal_type'] !== 'db') {
261 return implode(', ', GeneralUtility::trimExplode(',', $value, true));
262 }
263 $labelParts = array_map(
264 function ($rawLabelItem) {
265 return array_pop(GeneralUtility::trimExplode('|', $rawLabelItem, true, 2));
266 },
267 GeneralUtility::trimExplode(',', $value, true)
268 );
269 if (!empty($labelParts)) {
270 sort($labelParts);
271 return implode(', ', $labelParts);
272 }
273 return '';
274 }
275
276 /**
277 * Returns the record title for checkbox fields
278 *
279 * @param mixed $value Current database value of this field
280 * @param array $fieldConfig TCA field configuration
281 * @return string
282 */
283 protected function getRecordTitleForCheckboxType($value, $fieldConfig)
284 {
285 $languageService = $this->getLanguageService();
286 if (empty($fieldConfig['items']) || !is_array($fieldConfig['items'])) {
287 $title = (bool)$value
288 ? $languageService->sL('LLL:EXT:lang/locallang_common.xlf:yes')
289 : $languageService->sL('LLL:EXT:lang/locallang_common.xlf:no');
290 } else {
291 $labelParts = [];
292 foreach ($fieldConfig['items'] as $key => $val) {
293 if ($value & pow(2, $key)) {
294 $labelParts[] = $val[0];
295 }
296 }
297 $title = implode(', ', $labelParts);
298 }
299 return $title;
300 }
301
302 /**
303 * Returns the record title for input fields
304 *
305 * @param mixed $value Current database value of this field
306 * @param array $fieldConfig TCA field configuration
307 * @return string
308 */
309 protected function getRecordTitleForInputType($value, $fieldConfig)
310 {
311 if (!isset($value)) {
312 return '';
313 }
314 $title = $value;
315 if (GeneralUtility::inList($fieldConfig['eval'], 'date')) {
316 if (isset($fieldConfig['dbType']) && $fieldConfig['dbType'] === 'date') {
317 $value = $value === '0000-00-00' ? 0 : (int)strtotime($value);
318 } else {
319 $value = (int)$value;
320 }
321 if (!empty($value)) {
322 $ageSuffix = '';
323 // Generate age suffix as long as not explicitly suppressed
324 if (!isset($fieldConfig['disableAgeDisplay']) || (bool)$fieldConfig['disableAgeDisplay'] === false) {
325 $ageDelta = $GLOBALS['EXEC_TIME'] - $value;
326 $calculatedAge = BackendUtility::calcAge(
327 abs($ageDelta),
328 $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.minutesHoursDaysYears')
329 );
330 $ageSuffix = ' (' . ($ageDelta > 0 ? '-' : '') . $calculatedAge . ')';
331 }
332 $title = BackendUtility::date($value) . $ageSuffix;
333 }
334 } elseif (GeneralUtility::inList($fieldConfig['eval'], 'time')) {
335 if (!empty($value)) {
336 $title = BackendUtility::time((int)$value, false);
337 }
338 } elseif (GeneralUtility::inList($fieldConfig['eval'], 'timesec')) {
339 if (!empty($value)) {
340 $title = BackendUtility::time((int)$value);
341 }
342 } elseif (GeneralUtility::inList($fieldConfig['eval'], 'datetime')) {
343 // Handle native date/time field
344 if (isset($fieldConfig['dbType']) && $fieldConfig['dbType'] === 'datetime') {
345 $value = $value === '0000-00-00 00:00:00' ? 0 : (int)strtotime($value);
346 } else {
347 $value = (int)$value;
348 }
349 if (!empty($value)) {
350 $title = BackendUtility::datetime($value);
351 }
352 }
353 return $title;
354 }
355
356 /**
357 * Returns the record title for text fields
358 *
359 * @param mixed $value Current database value of this field
360 * @return string
361 */
362 protected function getRecordTitleForTextType($value)
363 {
364 return trim(strip_tags($value));
365 }
366
367 /**
368 * @return DatabaseConnection
369 */
370 protected function getDatabaseConnection()
371 {
372 return $GLOBALS['TYPO3_DB'];
373 }
374
375 /**
376 * @return LanguageService
377 */
378 protected function getLanguageService()
379 {
380 return $GLOBALS['LANG'];
381 }
382 }