dc8a85dc128f37094d442e80d8092c13028fae3e
[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 // Extract UID from value formed like {table_name}_{uid}|{default_value}
102 // @todo: This needs adaption as soon as the group format is changed
103 if (!MathUtility::canBeInterpretedAsInteger($foreignUid)) {
104 list($foreignUid) = explode('|', $foreignUid);
105 $foreignUid = str_replace($foreignTable . '_', '', $foreignUid);
106 }
107 // Fetch field of this foreign row from db
108 $foreignRow = $this->getDatabaseRow($foreignTable, $foreignUid, $foreignTableTypeField);
109 if ($foreignRow[$foreignTableTypeField]) {
110 // @todo: It might be necessary to fetch the value from default language record as well here,
111 // @todo: this was buggy in the "old" implementation and never worked. It was therefor left out here for now.
112 // @todo: To implement that, see if the foreign row is a localized overlay, fetch default and merge exclude
113 // @todo: and mergeIfNotBlank if needed.
114 $recordTypeValue = $foreignRow[$foreignTableTypeField];
115 }
116 }
117 }
118 }
119
120 // Throw another exception if determined value and '0' and '1' do not exist
121 if (empty($result['processedTca']['types'][$recordTypeValue])
122 && empty($result['processedTca']['types']['0'])
123 && empty($result['processedTca']['types']['1'])
124 ) {
125 throw new \UnexpectedValueException(
126 'Type value ' . $recordTypeValue . ' from database record not defined in TCA of table '
127 . $result['tableName'] . ' and neither 0 nor 1 are defined as fallback.',
128 1438185437
129 );
130 }
131
132 // Check the determined value actually exists as types key, otherwise fall back to 0 or 1, 1 for "historical reasons"
133 if (empty($result['processedTca']['types'][$recordTypeValue])) {
134 $recordTypeValue = !empty($result['processedTca']['types']['0']) ? '0' : '1';
135 }
136
137 $result['recordTypeValue'] = (string)$recordTypeValue;
138 return $result;
139 }
140
141 /**
142 * Retrieve the requested row from the database
143 *
144 * @param string $tableName
145 * @param int $uid
146 * @param string $fieldName
147 * @return array
148 */
149 protected function getDatabaseRow(string $tableName, int $uid, string $fieldName): array
150 {
151 $row = BackendUtility::getRecord($tableName, $uid, $fieldName);
152
153 return $row ?: [];
154 }
155 /**
156 * If a localized row is handled, the field value of the default language record
157 * is used instead if tca is configured as "exclude" or "mergeIfNotBlank" with
158 * empty localized value.
159 *
160 * @param array $result Main "$result" data array
161 * @param string $field Field name to fetch value for
162 * @return string field value
163 */
164 protected function getValueFromDefaultLanguageRecordIfConfigured($result, $field)
165 {
166 $value = $result['databaseRow'][$field];
167 if (
168 // is a localized record
169 !empty($result['processedTca']['ctrl']['languageField'])
170 && $result['databaseRow'][$result['processedTca']['ctrl']['languageField']] > 0
171 // l10n_mode for field is configured
172 && !empty($result['processedTca']['columns'][$field]['l10n_mode'])
173 && (
174 // is exclude -> fall back to value of default record
175 $result['processedTca']['columns'][$field]['l10n_mode'] === 'exclude'
176 // is mergeIfNotBlank and own value is empty -> fall back to value of default record
177 || (
178 $result['processedTca']['columns'][$field]['l10n_mode'] === 'mergeIfNotBlank'
179 // 0 means "not empty"
180 && $result['databaseRow'][$field] === ''
181 )
182 )
183 ) {
184 $value = $result['defaultLanguageRow'][$field];
185 }
186 return $value;
187 }
188 }