[!!!][TASK] Improve flex and TCA handling in FormEngine
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / FormDataProvider / DatabaseRecordTypeValue.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\Utility\MathUtility;
20
21 /**
22 * Determine the final TCA type value
23 */
24 class DatabaseRecordTypeValue implements FormDataProviderInterface
25 {
26 /**
27 * TCA type value depends on several parameters. The simple case is
28 * a direct lookup in the database row, which then just needs handling
29 * in case the row is a localization overlay.
30 * More complex is the field:field syntax that can look up the actual
31 * value in a different table.
32 *
33 * @param array $result
34 * @return array
35 * @throws \UnexpectedValueException
36 */
37 public function addData(array $result)
38 {
39 if (!isset($result['processedTca']['types'])
40 || !is_array($result['processedTca']['types'])
41 || empty($result['processedTca']['types'])
42 ) {
43 throw new \UnexpectedValueException(
44 'At least one "types" array must be defined for table ' . $result['tableName'] . ', preferred "0"',
45 1438185331
46 );
47 }
48
49 // Guard clause to suppress any calculation if record type value has been set from outside already
50 if ($result['recordTypeValue'] !== '') {
51 return $result;
52 }
53
54 $recordTypeValue = '0';
55 if (!empty($result['processedTca']['ctrl']['type'])) {
56 $tcaTypeField = $result['processedTca']['ctrl']['type'];
57
58 if (strpos($tcaTypeField, ':') === false) {
59 // $tcaTypeField is the name of a field in database row
60 if (!array_key_exists($tcaTypeField, $result['databaseRow'])) {
61 throw new \UnexpectedValueException(
62 'TCA table ' . $result['tableName'] . ' ctrl[\'type\'] is set to ' . $tcaTypeField . ', but'
63 . ' this field does not exist in the database of this table',
64 1438183881
65 );
66 }
67 $recordTypeValue = $this->getValueFromDefaultLanguageRecordIfConfigured($result, $tcaTypeField);
68 } else {
69 // If type is configured as localField:foreignField, fetch the type value from
70 // a foreign table. localField then point to a group or select field in the own table,
71 // this points to a record in a foreign table and the value of foreignField is then
72 // used as type field. This was introduced for some FAL scenarios.
73 list($pointerField, $foreignTableTypeField) = explode(':', $tcaTypeField);
74
75 $relationType = $result['processedTca']['columns'][$pointerField]['config']['type'];
76 if ($relationType !== 'select' && $relationType !== 'group') {
77 throw new \UnexpectedValueException(
78 'TCA foreign field pointer fields are only allowed to be used with group or select field types.'
79 . ' Handling field ' . $pointerField . ' with type configured as ' . $tcaTypeField,
80 1325862241
81 );
82 }
83
84 $foreignUid = $this->getValueFromDefaultLanguageRecordIfConfigured($result, $pointerField);
85 // Resolve the foreign record only if there is a uid, otherwise fall back 0
86 if (!empty($foreignUid)) {
87 // Determine table name to fetch record from
88 if ($relationType === 'select') {
89 $foreignTable = $result['processedTca']['columns'][$pointerField]['config']['foreign_table'];
90 } else {
91 $allowedTables = explode(',', $result['processedTca']['columns'][$pointerField]['config']['allowed']);
92 // Always take the first configured table.
93 $foreignTable = $allowedTables[0];
94 }
95 if (empty($foreignTable)) {
96 throw new \UnexpectedValueException(
97 'No target table defined for type config field ' . $pointerField . ' of table ' . $result['tableName'],
98 1438253614
99 );
100 }
101 if (!MathUtility::canBeInterpretedAsInteger($foreignUid) && is_array($foreignUid[0])) {
102 // A group relation - has been resolved to array by TcaGroup data provider already
103 $foreignUid = $foreignUid[0]['uid'];
104 }
105 // Fetch field of this foreign row from db
106 $foreignRow = $this->getDatabaseRow($foreignTable, $foreignUid, $foreignTableTypeField);
107 if ($foreignRow[$foreignTableTypeField]) {
108 // @todo: It might be necessary to fetch the value from default language record as well here,
109 // @todo: this was buggy in the "old" implementation and never worked. It was therefor left out here for now.
110 // @todo: To implement that, see if the foreign row is a localized overlay, fetch default and merge exclude
111 // @todo: and mergeIfNotBlank if needed.
112 $recordTypeValue = $foreignRow[$foreignTableTypeField];
113 }
114 }
115 }
116 }
117
118 // Throw another exception if determined value and '0' and '1' do not exist
119 if (empty($result['processedTca']['types'][$recordTypeValue])
120 && empty($result['processedTca']['types']['0'])
121 && empty($result['processedTca']['types']['1'])
122 ) {
123 throw new \UnexpectedValueException(
124 'Type value ' . $recordTypeValue . ' from database record not defined in TCA of table '
125 . $result['tableName'] . ' and neither 0 nor 1 are defined as fallback.',
126 1438185437
127 );
128 }
129
130 // Check the determined value actually exists as types key, otherwise fall back to 0 or 1, 1 for "historical reasons"
131 if (empty($result['processedTca']['types'][$recordTypeValue])) {
132 $recordTypeValue = !empty($result['processedTca']['types']['0']) ? '0' : '1';
133 }
134
135 $result['recordTypeValue'] = (string)$recordTypeValue;
136 return $result;
137 }
138
139 /**
140 * Retrieve the requested row from the database
141 *
142 * @param string $tableName
143 * @param int $uid
144 * @param string $fieldName
145 * @return array
146 */
147 protected function getDatabaseRow(string $tableName, int $uid, string $fieldName): array
148 {
149 $row = BackendUtility::getRecord($tableName, $uid, $fieldName);
150
151 return $row ?: [];
152 }
153
154 /**
155 * If a localized row is handled, the field value of the default language record
156 * is used instead if tca is configured as "exclude" or "mergeIfNotBlank" with
157 * empty localized value.
158 *
159 * @param array $result Main "$result" data array
160 * @param string $field Field name to fetch value for
161 * @return string field value
162 */
163 protected function getValueFromDefaultLanguageRecordIfConfigured($result, $field)
164 {
165 $value = $result['databaseRow'][$field];
166 if (
167 // is a localized record
168 !empty($result['processedTca']['ctrl']['languageField'])
169 && $result['databaseRow'][$result['processedTca']['ctrl']['languageField']] > 0
170 // l10n_mode for field is configured
171 && !empty($result['processedTca']['columns'][$field]['l10n_mode'])
172 && (
173 // is exclude -> fall back to value of default record
174 $result['processedTca']['columns'][$field]['l10n_mode'] === 'exclude'
175 // is mergeIfNotBlank and own value is empty -> fall back to value of default record
176 || (
177 $result['processedTca']['columns'][$field]['l10n_mode'] === 'mergeIfNotBlank'
178 // 0 means "not empty"
179 && $result['databaseRow'][$field] === ''
180 )
181 )
182 ) {
183 $value = $result['defaultLanguageRow'][$field];
184 }
185 return $value;
186 }
187 }