80a3687d4774777c5ecd150431d42fc90e02b765
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / FormDataProvider / TcaInputPlaceholders.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\FormDataCompiler;
18 use TYPO3\CMS\Backend\Form\FormDataGroup\TcaInputPlaceholderRecord;
19 use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
20 use TYPO3\CMS\Backend\Utility\BackendUtility;
21 use TYPO3\CMS\Core\Utility\GeneralUtility;
22 use TYPO3\CMS\Core\Utility\StringUtility;
23
24 /**
25 * Resolve placeholders for fields of type input or text. The placeholder value
26 * in the processedTca section of the result will be replaced with the resolved
27 * value.
28 */
29 class TcaInputPlaceholders extends AbstractItemProvider implements FormDataProviderInterface
30 {
31 /**
32 * Resolve placeholders for input/text fields. Placeholders that are simple
33 * strings will be returned unmodified. Placeholders beginning with __row are
34 * being resolved, possibly traversing multiple tables.
35 *
36 * @param array $result
37 * @return array
38 */
39 public function addData(array $result)
40 {
41 foreach ($result['processedTca']['columns'] as $fieldName => $fieldConfig) {
42 // Placeholders are only valid for input and text type fields
43 if (
44 ($fieldConfig['config']['type'] !== 'input' && $fieldConfig['config']['type'] !== 'text')
45 || !isset($fieldConfig['config']['placeholder'])
46 ) {
47 continue;
48 }
49
50 // Resolve __row|field type placeholders
51 if (StringUtility::beginsWith($fieldConfig['config']['placeholder'], '__row|')) {
52 // split field names into array and remove the __row indicator
53 $fieldNameArray = array_slice(
54 GeneralUtility::trimExplode('|', $fieldConfig['config']['placeholder'], true),
55 1
56 );
57 $result['processedTca']['columns'][$fieldName]['config']['placeholder'] = $this->getPlaceholderValue($fieldNameArray, $result);
58 }
59
60 // Remove empty placeholders
61 if (empty($fieldConfig['config']['placeholder'])) {
62 unset($result['processedTca']['columns'][$fieldName]['config']['placeholder']);
63 }
64 }
65
66 return $result;
67 }
68
69 /**
70 * Recursively resolve the placeholder value. A placeholder string with a
71 * syntax of __row|field1|field2|field3 will be recursively resolved to a
72 * final value.
73 *
74 * @param array $fieldNameArray
75 * @param array $result
76 * @param int $recursionLevel
77 * @return string
78 */
79 protected function getPlaceholderValue($fieldNameArray, $result, $recursionLevel = 0)
80 {
81 if ($recursionLevel > 99) {
82 // This should not happen, treat as misconfiguration
83 return '';
84 }
85
86 $fieldName = array_shift($fieldNameArray);
87 $fieldConfig = $result['processedTca']['columns'][$fieldName]['config'];
88
89 // Skip if a defined field was actually not present in the database row
90 // Using array_key_exists here, since NULL values are valid as well.
91 if (!array_key_exists($fieldName, $result['databaseRow'])) {
92 return '';
93 }
94
95 $value = $result['databaseRow'][$fieldName];
96
97 switch ($fieldConfig['type']) {
98 case 'select':
99 // The FormDataProviders already resolved the select items to an array of uids
100 $possibleUids = $value;
101 $foreignTableName = $fieldConfig['foreign_table'];
102 break;
103 case 'group':
104 $possibleUids = $this->getRelatedGroupFieldUids($fieldConfig, $value);
105 $foreignTableName = $this->getAllowedTableForGroupField($fieldConfig);
106 break;
107 case 'inline':
108 $possibleUids = array_filter(GeneralUtility::trimExplode(',', $value, true));
109 $foreignTableName = $fieldConfig['foreign_table'];
110 break;
111 default:
112 $possibleUids = [];
113 $foreignTableName = '';
114 }
115
116 if (!empty($possibleUids) && !empty($fieldNameArray)) {
117 $relatedFormData = $this->getRelatedFormData($foreignTableName, $possibleUids[0], $fieldNameArray[0]);
118 $value = $this->getPlaceholderValue($fieldNameArray, $relatedFormData, $recursionLevel + 1);
119 }
120
121 // @todo: This might not be the best solution. The database row
122 // @todo: can include array type values. Final resolution would
123 // @todo: need to take the recursion into account.
124 return (string)$value;
125 }
126
127 /**
128 * Compile a formdata result set based on the tablename and record uid.
129 *
130 * @param string $tableName Name of the table for which to compile formdata
131 * @param int $uid UID of the record for which to compile the formdata
132 * @param string $columnToProcess The column that is required from the record
133 * @return array The compiled formdata
134 */
135 protected function getRelatedFormData($tableName, $uid, $columnToProcess)
136 {
137 $fakeDataInput = [
138 'command' => 'edit',
139 'vanillaUid' => (int)$uid,
140 'tableName' => $tableName,
141 'inlineCompileExistingChildren' => false,
142 'columnsToProcess' => [$columnToProcess],
143 ];
144 /** @var TcaInputPlaceholderRecord $formDataGroup */
145 $formDataGroup = GeneralUtility::makeInstance(TcaInputPlaceholderRecord::class);
146 /** @var FormDataCompiler $formDataCompiler */
147 $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
148 $compilerResult = $formDataCompiler->compile($fakeDataInput);
149 return $compilerResult;
150 }
151
152 /**
153 * Return uids of related records for group type fields. Uids consisting of
154 * multiple parts like [table]_[uid]|[title] will be reduced to integers and
155 * validated against the allowed table. Uids without a table prefix are
156 * accepted in any case.
157 *
158 * @param array $fieldConfig TCA "config" section for the group type field.
159 * @param string $value A comma separated list of records
160 * @return array
161 */
162 protected function getRelatedGroupFieldUids(array $fieldConfig, $value)
163 {
164 $relatedUids = [];
165 $allowedTable = $this->getAllowedTableForGroupField($fieldConfig);
166
167 // Skip if it's not a database relation with a resolvable foreign table
168 if (($fieldConfig['internal_type'] !== 'db') || ($allowedTable === false)) {
169 return $relatedUids;
170 }
171
172 $values = GeneralUtility::trimExplode(',', $value, true);
173 foreach ($values as $groupValue) {
174 list($foreignIdentifier, $_) = GeneralUtility::trimExplode('|', $groupValue);
175 list($recordForeignTable, $foreignUid) = BackendUtility::splitTable_Uid($foreignIdentifier);
176 // skip records that do not match the allowed table
177 if (!empty($recordForeignTable) && ($recordForeignTable !== $allowedTable)) {
178 continue;
179 }
180 $relatedUids[] = $foreignUid;
181 }
182
183 return $relatedUids;
184 }
185
186 /**
187 * Will read the "allowed" value from the given field configuration
188 * and returns FALSE if none or more than one has been defined.
189 * Otherwise the name of the allowed table will be returned.
190 *
191 * @param array $fieldConfig TCA "config" section for the group type field.
192 * @return bool|string
193 */
194 protected function getAllowedTableForGroupField(array $fieldConfig)
195 {
196 $allowedTable = false;
197
198 $allowedTables = GeneralUtility::trimExplode(',', $fieldConfig['allowed'], true);
199 if (count($allowedTables) === 1) {
200 $allowedTable = $allowedTables[0];
201 }
202
203 return $allowedTable;
204 }
205 }