‪TYPO3CMS  10.4
SiteInlineAjaxController.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Psr\Http\Message\ResponseInterface;
21 use Psr\Http\Message\ServerRequestInterface;
34 
41 {
45  public function ‪__construct()
46  {
47  // Bring site TCA into global scope.
48  // @todo: We might be able to get rid of that later
49  ‪$GLOBALS['TCA'] = array_merge(‪$GLOBALS['TCA'], GeneralUtility::makeInstance(SiteTcaConfiguration::class)->getTca());
50  }
51 
59  public function ‪newInlineChildAction(ServerRequestInterface $request): ResponseInterface
60  {
61  $ajaxArguments = $request->getParsedBody()['ajax'] ?? $request->getQueryParams()['ajax'];
62  $parentConfig = $this->‪extractSignedParentConfigFromRequest((string)$ajaxArguments['context']);
63  $domObjectId = $ajaxArguments[0];
64  $inlineFirstPid = $this->‪getInlineFirstPidFromDomObjectId($domObjectId);
65  $childChildUid = null;
66  if (isset($ajaxArguments[1]) && ‪MathUtility::canBeInterpretedAsInteger($ajaxArguments[1])) {
67  $childChildUid = (int)$ajaxArguments[1];
68  }
69  // Parse the DOM identifier, add the levels to the structure stack
70  $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
71  $inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId);
72  $inlineStackProcessor->injectAjaxConfiguration($parentConfig);
73  $inlineTopMostParent = $inlineStackProcessor->getStructureLevel(0);
74  // Parent, this table embeds the child table
75  $parent = $inlineStackProcessor->getStructureLevel(-1);
76  // Child, a record from this table should be rendered
77  $child = $inlineStackProcessor->getUnstableStructure();
78  if (‪MathUtility::canBeInterpretedAsInteger($child['uid'])) {
79  // If uid comes in, it is the id of the record neighbor record "create after"
80  $childVanillaUid = -1 * abs((int)$child['uid']);
81  } else {
82  // Else inline first Pid is the storage pid of new inline records
83  $childVanillaUid = (int)$inlineFirstPid;
84  }
85  $childTableName = $parentConfig['foreign_table'];
86 
87  $defaultDatabaseRow = [];
88  if ($childTableName === 'site_language') {
89  // Feed new site_language row with data from sys_language record if possible
90  if ($childChildUid > 0) {
91  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_language');
92  $queryBuilder->getRestrictions()->removeByType(HiddenRestriction::class);
93  $row = $queryBuilder->select('*')->from('sys_language')
94  ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($childChildUid, \PDO::PARAM_INT)))
95  ->execute()->fetch();
96  if (empty($row)) {
97  throw new \RuntimeException('Referenced sys_language row not found', 1521783937);
98  }
99  if (!empty($row['language_isocode'])) {
100  $defaultDatabaseRow['iso-639-1'] = $row['language_isocode'];
101  $defaultDatabaseRow['base'] = '/' . $row['language_isocode'] . '/';
102 
103  ‪$locales = GeneralUtility::makeInstance(Locales::class);
104  $allLanguages = ‪$locales->getLanguages();
105  if (isset($allLanguages[$row['language_isocode']])) {
106  $defaultDatabaseRow['typo3Language'] = $row['language_isocode'];
107  }
108  }
109  if (!empty($row['flag']) && $row['flag'] === 'multiple') {
110  $defaultDatabaseRow['flag'] = 'global';
111  } elseif (!empty($row)) {
112  $defaultDatabaseRow['flag'] = $row['flag'];
113  }
114  if (!empty($row['title'])) {
115  $defaultDatabaseRow['title'] = $row['title'];
116  }
117  }
118  }
119 
120  $formDataGroup = GeneralUtility::makeInstance(SiteConfigurationDataGroup::class);
121  $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
122  $formDataCompilerInput = [
123  'command' => 'new',
124  'tableName' => $childTableName,
125  'vanillaUid' => $childVanillaUid,
126  'databaseRow' => $defaultDatabaseRow,
127  'isInlineChild' => true,
128  'inlineStructure' => $inlineStackProcessor->getStructure(),
129  'inlineFirstPid' => $inlineFirstPid,
130  'inlineParentUid' => $parent['uid'],
131  'inlineParentTableName' => $parent['table'],
132  'inlineParentFieldName' => $parent['field'],
133  'inlineParentConfig' => $parentConfig,
134  'inlineTopMostParentUid' => $inlineTopMostParent['uid'],
135  'inlineTopMostParentTableName' => $inlineTopMostParent['table'],
136  'inlineTopMostParentFieldName' => $inlineTopMostParent['field'],
137  ];
138  if ($childChildUid) {
139  $formDataCompilerInput['inlineChildChildUid'] = $childChildUid;
140  }
141  $childData = $formDataCompiler->compile($formDataCompilerInput);
142 
143  if ($parentConfig['foreign_selector'] && $parentConfig['appearance']['useCombination']) {
144  throw new \RuntimeException('useCombination not implemented in sites module', 1522493094);
145  }
146 
147  $childData['inlineParentUid'] = (int)$parent['uid'];
148  $childData['renderType'] = 'inlineRecordContainer';
149  $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
150  $childResult = $nodeFactory->create($childData)->render();
151 
152  $jsonArray = [
153  'data' => '',
154  'stylesheetFiles' => [],
155  'scriptCall' => [],
156  'compilerInput' => [
157  'uid' => $childData['databaseRow']['uid'],
158  'childChildUid' => $childChildUid,
159  'parentConfig' => $parentConfig,
160  ],
161  ];
162 
163  $jsonArray = $this->‪mergeChildResultIntoJsonResult($jsonArray, $childResult);
164 
165  return new ‪JsonResponse($jsonArray);
166  }
167 
175  public function ‪openInlineChildAction(ServerRequestInterface $request): ResponseInterface
176  {
177  $ajaxArguments = $request->getParsedBody()['ajax'] ?? $request->getQueryParams()['ajax'];
178 
179  $domObjectId = $ajaxArguments[0];
180  $inlineFirstPid = $this->‪getInlineFirstPidFromDomObjectId($domObjectId);
181  $parentConfig = $this->‪extractSignedParentConfigFromRequest((string)$ajaxArguments['context']);
182 
183  // Parse the DOM identifier, add the levels to the structure stack
184  $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
185  $inlineStackProcessor->initializeByParsingDomObjectIdString($domObjectId);
186  $inlineStackProcessor->injectAjaxConfiguration($parentConfig);
187 
188  // Parent, this table embeds the child table
189  $parent = $inlineStackProcessor->getStructureLevel(-1);
190  $parentFieldName = $parent['field'];
191 
192  // Set flag in config so that only the fields are rendered
193  // @todo: Solve differently / rename / whatever
194  $parentConfig['renderFieldsOnly'] = true;
195 
196  $parentData = [
197  'processedTca' => [
198  'columns' => [
199  $parentFieldName => [
200  'config' => $parentConfig,
201  ],
202  ],
203  ],
204  'tableName' => $parent['table'],
205  'inlineFirstPid' => $inlineFirstPid,
206  // Hand over given original return url to compile stack. Needed if inline children compile links to
207  // another view (eg. edit metadata in a nested inline situation like news with inline content element image),
208  // so the back link is still the link from the original request. See issue #82525. This is additionally
209  // given down in TcaInline data provider to compiled children data.
210  'returnUrl' => $parentConfig['originalReturnUrl'],
211  ];
212 
213  // Child, a record from this table should be rendered
214  $child = $inlineStackProcessor->getUnstableStructure();
215 
216  $childData = $this->‪compileChild($parentData, $parentFieldName, (int)$child['uid'], $inlineStackProcessor->getStructure());
217 
218  $childData['inlineParentUid'] = (int)$parent['uid'];
219  $childData['renderType'] = 'inlineRecordContainer';
220  $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
221  $childResult = $nodeFactory->create($childData)->render();
222 
223  $jsonArray = [
224  'data' => '',
225  'stylesheetFiles' => [],
226  'scriptCall' => [],
227  ];
228 
229  $jsonArray = $this->‪mergeChildResultIntoJsonResult($jsonArray, $childResult);
230 
231  return new ‪JsonResponse($jsonArray);
232  }
233 
247  protected function ‪compileChild(array $parentData, string $parentFieldName, int $childUid, array $inlineStructure): array
248  {
249  $parentConfig = $parentData['processedTca']['columns'][$parentFieldName]['config'];
250 
251  $inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
252  $inlineStackProcessor->initializeByGivenStructure($inlineStructure);
253  $inlineTopMostParent = $inlineStackProcessor->getStructureLevel(0);
254 
255  // @todo: do not use stack processor here ...
256  $child = $inlineStackProcessor->getUnstableStructure();
257  $childTableName = $child['table'];
258 
259  $formDataGroup = GeneralUtility::makeInstance(SiteConfigurationDataGroup::class);
260  $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
261  $formDataCompilerInput = [
262  'command' => 'edit',
263  'tableName' => $childTableName,
264  'vanillaUid' => (int)$childUid,
265  'returnUrl' => $parentData['returnUrl'],
266  'isInlineChild' => true,
267  'inlineStructure' => $inlineStructure,
268  'inlineFirstPid' => $parentData['inlineFirstPid'],
269  'inlineParentConfig' => $parentConfig,
270  'isInlineAjaxOpeningContext' => true,
271 
272  // values of the current parent element
273  // it is always a string either an id or new...
274  'inlineParentUid' => $parentData['databaseRow']['uid'],
275  'inlineParentTableName' => $parentData['tableName'],
276  'inlineParentFieldName' => $parentFieldName,
277 
278  // values of the top most parent element set on first level and not overridden on following levels
279  'inlineTopMostParentUid' => $inlineTopMostParent['uid'],
280  'inlineTopMostParentTableName' => $inlineTopMostParent['table'],
281  'inlineTopMostParentFieldName' => $inlineTopMostParent['field'],
282  ];
283  if ($parentConfig['foreign_selector'] && $parentConfig['appearance']['useCombination']) {
284  throw new \RuntimeException('useCombination not implemented in sites module', 1522493095);
285  }
286  return $formDataCompiler->compile($formDataCompilerInput);
287  }
288 
297  protected function ‪mergeChildResultIntoJsonResult(array $jsonResult, array $childResult): array
298  {
299  $jsonResult['data'] .= $childResult['html'];
300  $jsonResult['stylesheetFiles'] = [];
301  foreach ($childResult['stylesheetFiles'] as $stylesheetFile) {
302  $jsonResult['stylesheetFiles'][] = $this->‪getRelativePathToStylesheetFile($stylesheetFile);
303  }
304  if (!empty($childResult['inlineData'])) {
305  $jsonResult['inlineData'] = $childResult['inlineData'];
306  }
307  foreach ($childResult['additionalJavaScriptPost'] as $singleAdditionalJavaScriptPost) {
308  $jsonResult['scriptCall'][] = $singleAdditionalJavaScriptPost;
309  }
310  if (!empty($childResult['additionalInlineLanguageLabelFiles'])) {
311  $labels = [];
312  foreach ($childResult['additionalInlineLanguageLabelFiles'] as $additionalInlineLanguageLabelFile) {
314  $labels,
315  $this->‪getLabelsFromLocalizationFile($additionalInlineLanguageLabelFile)
316  );
317  }
318  $javaScriptCode = [];
319  $javaScriptCode[] = 'if (typeof TYPO3 === \'undefined\' || typeof TYPO3.lang === \'undefined\') {';
320  $javaScriptCode[] = ' TYPO3.lang = {}';
321  $javaScriptCode[] = '}';
322  $javaScriptCode[] = 'var additionalInlineLanguageLabels = ' . json_encode($labels) . ';';
323  $javaScriptCode[] = 'for (var attributeName in additionalInlineLanguageLabels) {';
324  $javaScriptCode[] = ' if (typeof TYPO3.lang[attributeName] === \'undefined\') {';
325  $javaScriptCode[] = ' TYPO3.lang[attributeName] = additionalInlineLanguageLabels[attributeName]';
326  $javaScriptCode[] = ' }';
327  $javaScriptCode[] = '}';
328 
329  $jsonResult['scriptCall'][] = implode(LF, $javaScriptCode);
330  }
331  $jsonResult['requireJsModules'] = $this->‪createExecutableStringRepresentationOfRegisteredRequireJsModules($childResult);
332 
333  return $jsonResult;
334  }
335 
346  protected function ‪extractSignedParentConfigFromRequest(string $contextString): array
347  {
348  if ($contextString === '') {
349  throw new \RuntimeException('Empty context string given', 1522771624);
350  }
351  $context = json_decode($contextString, true);
352  if (empty($context['config'])) {
353  throw new \RuntimeException('Empty context config section given', 1522771632);
354  }
355  if (!hash_equals(GeneralUtility::hmac((string)$context['config'], 'InlineContext'), (string)$context['hmac'])) {
356  throw new \RuntimeException('Hash does not validate', 1522771640);
357  }
358  return json_decode($context['config'], true);
359  }
360 
367  protected function ‪getInlineFirstPidFromDomObjectId(string $domObjectId): ?int
368  {
369  // Substitute FlexForm addition and make parsing a bit easier
370  $domObjectId = str_replace('---', ':', $domObjectId);
371  // The starting pattern of an object identifier (e.g. "data-<firstPidValue>-<anything>)
372  $pattern = '/^data-(.+?)-(.+)$/';
373  if (preg_match($pattern, $domObjectId, $match)) {
374  return (int)$match[1];
375  }
376  return null;
377  }
378 }
‪TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction
Definition: HiddenRestriction.php:27
‪TYPO3\CMS\Backend\Configuration\SiteTcaConfiguration
Definition: SiteTcaConfiguration.php:33
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger($var)
Definition: MathUtility.php:74
‪TYPO3\CMS\Backend\Controller\SiteInlineAjaxController\compileChild
‪array compileChild(array $parentData, string $parentFieldName, int $childUid, array $inlineStructure)
Definition: SiteInlineAjaxController.php:247
‪TYPO3\CMS\Backend\Controller\SiteInlineAjaxController\extractSignedParentConfigFromRequest
‪array extractSignedParentConfigFromRequest(string $contextString)
Definition: SiteInlineAjaxController.php:346
‪TYPO3\CMS\Backend\Controller\SiteInlineAjaxController\mergeChildResultIntoJsonResult
‪array mergeChildResultIntoJsonResult(array $jsonResult, array $childResult)
Definition: SiteInlineAjaxController.php:297
‪TYPO3\CMS\Core\Localization\Locales
Definition: Locales.php:30
‪TYPO3\CMS\Core\Utility\ArrayUtility\mergeRecursiveWithOverrule
‪static mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys=true, $includeEmptyValues=true, $enableUnsetFeature=true)
Definition: ArrayUtility.php:654
‪TYPO3\CMS\Backend\Controller\SiteInlineAjaxController\getInlineFirstPidFromDomObjectId
‪int null getInlineFirstPidFromDomObjectId(string $domObjectId)
Definition: SiteInlineAjaxController.php:367
‪TYPO3\CMS\Backend\Controller\SiteInlineAjaxController\openInlineChildAction
‪ResponseInterface openInlineChildAction(ServerRequestInterface $request)
Definition: SiteInlineAjaxController.php:175
‪TYPO3\CMS\Backend\Controller\SiteInlineAjaxController
Definition: SiteInlineAjaxController.php:41
‪TYPO3\CMS\Backend\Controller\AbstractFormEngineAjaxController\createExecutableStringRepresentationOfRegisteredRequireJsModules
‪array createExecutableStringRepresentationOfRegisteredRequireJsModules(array $result)
Definition: AbstractFormEngineAjaxController.php:42
‪TYPO3\CMS\Backend\Controller\AbstractFormEngineAjaxController\getRelativePathToStylesheetFile
‪string getRelativePathToStylesheetFile(string $stylesheetFile)
Definition: AbstractFormEngineAjaxController.php:80
‪$locales
‪$locales
Definition: be_users.php:7
‪TYPO3\CMS\Backend\Controller\AbstractFormEngineAjaxController\getLabelsFromLocalizationFile
‪array getLabelsFromLocalizationFile($file)
Definition: AbstractFormEngineAjaxController.php:99
‪TYPO3\CMS\Backend\Form\NodeFactory
Definition: NodeFactory.php:37
‪TYPO3\CMS\Core\Utility\ArrayUtility
Definition: ArrayUtility.php:24
‪TYPO3\CMS\Core\Http\JsonResponse
Definition: JsonResponse.php:26
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:22
‪TYPO3\CMS\Backend\Controller\AbstractFormEngineAjaxController
Definition: AbstractFormEngineAjaxController.php:34
‪TYPO3\CMS\Backend\Form\InlineStackProcessor
Definition: InlineStackProcessor.php:30
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:46
‪TYPO3\CMS\Backend\Form\FormDataCompiler
Definition: FormDataCompiler.php:25
‪TYPO3\CMS\Backend\Controller\SiteInlineAjaxController\__construct
‪__construct()
Definition: SiteInlineAjaxController.php:45
‪TYPO3\CMS\Backend\Form\FormDataGroup\SiteConfigurationDataGroup
Definition: SiteConfigurationDataGroup.php:33
‪TYPO3\CMS\Backend\Controller
Definition: AbstractFormEngineAjaxController.php:18
‪TYPO3\CMS\Backend\Controller\SiteInlineAjaxController\newInlineChildAction
‪ResponseInterface newInlineChildAction(ServerRequestInterface $request)
Definition: SiteInlineAjaxController.php:59