[TASK] Clean-up in site handling
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Form / FormDataProvider / SiteTcaInline.php
1 <?php
2 declare(strict_types = 1);
3
4 namespace TYPO3\CMS\Backend\Form\FormDataProvider;
5
6 /*
7 * This file is part of the TYPO3 CMS project.
8 *
9 * It is free software; you can redistribute it and/or modify it under
10 * the terms of the GNU General Public License, either version 2
11 * of the License, or any later version.
12 *
13 * For the full copyright and license information, please read the
14 * LICENSE.txt file that was distributed with this source code.
15 *
16 * The TYPO3 project - inspiring people to share!
17 */
18
19 use TYPO3\CMS\Backend\Form\FormDataCompiler;
20 use TYPO3\CMS\Backend\Form\FormDataGroup\OnTheFly;
21 use TYPO3\CMS\Backend\Form\FormDataGroup\SiteConfigurationDataGroup;
22 use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
23 use TYPO3\CMS\Backend\Form\InlineStackProcessor;
24 use TYPO3\CMS\Backend\Utility\BackendUtility;
25 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
26 use TYPO3\CMS\Core\Site\SiteFinder;
27 use TYPO3\CMS\Core\Utility\GeneralUtility;
28
29 /**
30 * Special data provider for the sites configuration module.
31 *
32 * Handle inline children of 'sys_site"
33 */
34 class SiteTcaInline extends AbstractDatabaseRecordProvider implements FormDataProviderInterface
35 {
36 /**
37 * Resolve inline fields
38 *
39 * @param array $result
40 * @return array
41 */
42 public function addData(array $result): array
43 {
44 $result = $this->addInlineFirstPid($result);
45 foreach ($result['processedTca']['columns'] as $fieldName => $fieldConfig) {
46 if (!$this->isInlineField($fieldConfig)) {
47 continue;
48 }
49 $childTableName = $fieldConfig['config']['foreign_table'];
50 if ($childTableName !== 'sys_site_errorhandling' && $childTableName !== 'sys_site_language') {
51 throw new \RuntimeException('Inline relation to other tables not implemented', 1522494737);
52 }
53 $result['processedTca']['columns'][$fieldName]['children'] = [];
54 $result = $this->resolveSiteRelatedChildren($result, $fieldName);
55 $result = $this->addForeignSelectorAndUniquePossibleRecords($result, $fieldName);
56 }
57
58 return $result;
59 }
60
61 /**
62 * Is column of type "inline"
63 *
64 * @param array $fieldConfig
65 * @return bool
66 */
67 protected function isInlineField(array $fieldConfig): bool
68 {
69 return !empty($fieldConfig['config']['type']) && $fieldConfig['config']['type'] === 'inline';
70 }
71
72 /**
73 * The "entry" pid for inline records. Nested inline records can potentially hang around on different
74 * pid's, but the entry pid is needed for AJAX calls, so that they would know where the action takes place on the page structure.
75 *
76 * @param array $result Incoming result
77 * @return array Modified result
78 * @todo: Find out when and if this is different from 'effectivePid'
79 */
80 protected function addInlineFirstPid(array $result): array
81 {
82 if ($result['inlineFirstPid'] === null) {
83 $table = $result['tableName'];
84 $row = $result['databaseRow'];
85 // If the parent is a page, use the uid(!) of the (new?) page as pid for the child records:
86 if ($table === 'pages') {
87 $liveVersionId = BackendUtility::getLiveVersionIdOfRecord('pages', $row['uid']);
88 $pid = $liveVersionId ?? $row['uid'];
89 } elseif ($row['pid'] < 0) {
90 $prevRec = BackendUtility::getRecord($table, abs($row['pid']));
91 $pid = $prevRec['pid'];
92 } else {
93 $pid = $row['pid'];
94 }
95 $pageRecord = BackendUtility::getRecord('pages', $pid);
96 if ((int)$pageRecord[$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField']] > 0) {
97 $pid = (int)$pageRecord[$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField']];
98 }
99 $result['inlineFirstPid'] = (int)$pid;
100 }
101 return $result;
102 }
103
104 /**
105 * Substitute the value in databaseRow of this inline field with an array
106 * that contains the databaseRows of currently connected records and some meta information.
107 *
108 * @param array $result Result array
109 * @param string $fieldName Current handle field name
110 * @return array Modified item array
111 */
112 protected function resolveSiteRelatedChildren(array $result, string $fieldName): array
113 {
114 $connectedUids = [];
115 if ($result['command'] === 'edit') {
116 $siteConfigurationForPageUid = (int)$result['databaseRow']['rootPageId'][0];
117 $siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
118 $site = $siteFinder->getSiteByRootPageId($siteConfigurationForPageUid);
119 $siteConfiguration = $site ? $site->getConfiguration() : [];
120 if (is_array($siteConfiguration[$fieldName])) {
121 $connectedUids = array_keys($siteConfiguration[$fieldName]);
122 }
123 }
124
125 // If we are dealing with sys_site_language, we *always* force a relation to sys_language "0"
126 $foreignTable = $result['processedTca']['columns'][$fieldName]['config']['foreign_table'];
127 if ($foreignTable === 'sys_site_language' && $result['command'] === 'new') {
128 // If new, just add a new default child
129 $child = $this->compileDefaultSysSiteLanguageChild($result, $fieldName);
130 $connectedUids[] = $child['databaseRow']['uid'];
131 $result['processedTca']['columns'][$fieldName]['children'][] = $child;
132 }
133
134 $result['databaseRow'][$fieldName] = implode(',', $connectedUids);
135 if ($result['inlineCompileExistingChildren']) {
136 foreach ($connectedUids as $uid) {
137 if (strpos((string)$uid, 'NEW') !== 0) {
138 $compiledChild = $this->compileChild($result, $fieldName, $uid);
139 $result['processedTca']['columns'][$fieldName]['children'][] = $compiledChild;
140 }
141 }
142 }
143
144 // If we are dealing with sys_site_language, we *always* force a relation to sys_language "0"
145 if ($foreignTable === 'sys_site_language' && $result['command'] === 'edit') {
146 // If edit, find out if a child using sys_language "0" exists, else add it on top
147 $defaultSysSiteLanguageChildFound = false;
148 foreach ($result['processedTca']['columns'][$fieldName]['children'] as $child) {
149 if (isset($child['databaseRow']['languageId']) && (int)$child['databaseRow']['languageId'][0] == 0) {
150 $defaultSysSiteLanguageChildFound = true;
151 }
152 }
153 if (!$defaultSysSiteLanguageChildFound) {
154 // Compile and add child as first child
155 $child = $this->compileDefaultSysSiteLanguageChild($result, $fieldName);
156 $result['databaseRow'][$fieldName] = $child['databaseRow']['uid'] . ',' . $result['databaseRow'][$fieldName];
157 array_unshift($result['processedTca']['columns'][$fieldName]['children'], $child);
158 }
159 }
160
161 return $result;
162 }
163
164 /**
165 * If there is a foreign_selector or foreign_unique configuration, fetch
166 * the list of possible records that can be connected and attach them to the
167 * inline configuration.
168 *
169 * @param array $result Result array
170 * @param string $fieldName Current handle field name
171 * @return array Modified item array
172 */
173 protected function addForeignSelectorAndUniquePossibleRecords(array $result, string $fieldName): array
174 {
175 if (!is_array($result['processedTca']['columns'][$fieldName]['config']['selectorOrUniqueConfiguration'])) {
176 return $result;
177 }
178
179 $selectorOrUniqueConfiguration = $result['processedTca']['columns'][$fieldName]['config']['selectorOrUniqueConfiguration'];
180 $foreignFieldName = $selectorOrUniqueConfiguration['fieldName'];
181 $selectorOrUniquePossibleRecords = [];
182
183 if ($selectorOrUniqueConfiguration['config']['type'] === 'select') {
184 // Compile child table data for this field only
185 $selectDataInput = [
186 'tableName' => $result['processedTca']['columns'][$fieldName]['config']['foreign_table'],
187 'command' => 'new',
188 // Since there is no existing record that may have a type, it does not make sense to
189 // do extra handling of pageTsConfig merged here. Just provide "parent" pageTS as is
190 'pageTsConfig' => $result['pageTsConfig'],
191 'userTsConfig' => $result['userTsConfig'],
192 'databaseRow' => $result['databaseRow'],
193 'processedTca' => [
194 'ctrl' => [],
195 'columns' => [
196 $foreignFieldName => [
197 'config' => $selectorOrUniqueConfiguration['config'],
198 ],
199 ],
200 ],
201 'inlineExpandCollapseStateArray' => $result['inlineExpandCollapseStateArray'],
202 ];
203 $formDataGroup = GeneralUtility::makeInstance(OnTheFly::class);
204 $formDataGroup->setProviderList([TcaSelectItems::class]);
205 $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
206 $compilerResult = $formDataCompiler->compile($selectDataInput);
207 $selectorOrUniquePossibleRecords = $compilerResult['processedTca']['columns'][$foreignFieldName]['config']['items'];
208 }
209
210 $result['processedTca']['columns'][$fieldName]['config']['selectorOrUniquePossibleRecords'] = $selectorOrUniquePossibleRecords;
211
212 return $result;
213 }
214
215 /**
216 * Compile a full child record
217 *
218 * @param array $result Result array of parent
219 * @param string $parentFieldName Name of parent field
220 * @param int $childUid Uid of child to compile
221 * @return array Full result array
222 */
223 protected function compileChild(array $result, string $parentFieldName, int $childUid): array
224 {
225 $parentConfig = $result['processedTca']['columns'][$parentFieldName]['config'];
226 $childTableName = $parentConfig['foreign_table'];
227
228 $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
229 $inlineStackProcessor->initializeByGivenStructure($result['inlineStructure']);
230 $inlineTopMostParent = $inlineStackProcessor->getStructureLevel(0);
231
232 $formDataGroup = GeneralUtility::makeInstance(SiteConfigurationDataGroup::class);
233 $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
234 $formDataCompilerInput = [
235 'command' => 'edit',
236 'tableName' => $childTableName,
237 'vanillaUid' => $childUid,
238 // Give incoming returnUrl down to children so they generate a returnUrl back to
239 // the originally opening record, also see "originalReturnUrl" in inline container
240 // and FormInlineAjaxController
241 'returnUrl' => $result['returnUrl'],
242 'isInlineChild' => true,
243 'inlineStructure' => $result['inlineStructure'],
244 'inlineExpandCollapseStateArray' => $result['inlineExpandCollapseStateArray'],
245 'inlineFirstPid' => $result['inlineFirstPid'],
246 'inlineParentConfig' => $parentConfig,
247
248 // values of the current parent element
249 // it is always a string either an id or new...
250 'inlineParentUid' => $result['databaseRow']['uid'],
251 'inlineParentTableName' => $result['tableName'],
252 'inlineParentFieldName' => $parentFieldName,
253
254 // values of the top most parent element set on first level and not overridden on following levels
255 'inlineTopMostParentUid' => $result['inlineTopMostParentUid'] ?: $inlineTopMostParent['uid'],
256 'inlineTopMostParentTableName' => $result['inlineTopMostParentTableName'] ?: $inlineTopMostParent['table'],
257 'inlineTopMostParentFieldName' => $result['inlineTopMostParentFieldName'] ?: $inlineTopMostParent['field'],
258 ];
259
260 if ($parentConfig['foreign_selector'] && ($parentConfig['appearance']['useCombination'] ?? false)) {
261 throw new \RuntimeException('useCombination not implemented in sites module', 1522493097);
262 }
263 return $formDataCompiler->compile($formDataCompilerInput);
264 }
265
266 /**
267 * Compile default sys_site_language child using sys_language uid "0"
268 *
269 * @param array $result
270 * @param string $parentFieldName
271 * @return array
272 */
273 protected function compileDefaultSysSiteLanguageChild(array $result, string $parentFieldName): array
274 {
275 $parentConfig = $result['processedTca']['columns'][$parentFieldName]['config'];
276 $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
277 $inlineStackProcessor->initializeByGivenStructure($result['inlineStructure']);
278 $inlineTopMostParent = $inlineStackProcessor->getStructureLevel(0);
279 $formDataGroup = GeneralUtility::makeInstance(SiteConfigurationDataGroup::class);
280 $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
281 $formDataCompilerInput = [
282 'command' => 'new',
283 'tableName' => 'sys_site_language',
284 'vanillaUid' => $result['inlineFirstPid'],
285 'returnUrl' => $result['returnUrl'],
286 'isInlineChild' => true,
287 'inlineStructure' => [],
288 'inlineExpandCollapseStateArray' => $result['inlineExpandCollapseStateArray'],
289 'inlineFirstPid' => $result['inlineFirstPid'],
290 'inlineParentConfig' => $parentConfig,
291 'inlineParentUid' => $result['databaseRow']['uid'],
292 'inlineParentTableName' => $result['tableName'],
293 'inlineParentFieldName' => $parentFieldName,
294 'inlineTopMostParentUid' => $result['inlineTopMostParentUid'] ?: $inlineTopMostParent['uid'],
295 'inlineTopMostParentTableName' => $result['inlineTopMostParentTableName'] ?: $inlineTopMostParent['table'],
296 'inlineTopMostParentFieldName' => $result['inlineTopMostParentFieldName'] ?: $inlineTopMostParent['field'],
297 // The sys_language uid 0
298 'inlineChildChildUid' => 0,
299 ];
300 return $formDataCompiler->compile($formDataCompilerInput);
301 }
302
303 /**
304 * @return BackendUserAuthentication
305 */
306 protected function getBackendUser(): BackendUserAuthentication
307 {
308 return $GLOBALS['BE_USER'];
309 }
310 }