Commit 0824e6e8 authored by Christian Kuhn's avatar Christian Kuhn Committed by Andreas Fernandez
Browse files

[FEATURE] Introduce Site Handling

TYPO3 is famously known for the "multi-site"
functionality, allowing multiple websites running
within one TYPO3 instance.

However, configuring a multi-site had various downsides,
mostly regarding to domain/entrypoint handling for a site,
and if lots of languages were in place.

Concepts like "absRefPrefix", "baseURL", various language
related TypoScript settings, and the infamous
"L" GET parameter can now be seen obsolete.

Also, handling page-not-found or access-denied errors
have never been easier, as every admin/integrator is able
to configure this.

What TYPO3 calls a "site" is a entrypoint / pagetree,
and contains both configuration values relevant for
Backend and Frontend.

A site configuration has a unique (human-readable)
"site identifier" and the following additional values:

* Root page ID
This is a page on the root level (pid=0) or having
"is_siteroot" checked.

* The base path / base URL
This HTTP entry point e.g. https://www.mydomain.com/
 ("Base URL" / HTTP entry point,
like https://www.mydomain.com/)

-- This allows to fully identify a pagetree with
an entrypoint without having to guess during "runtime".

* The definition of all available languages for this
  pagetree, including the default language for this
  specific pagetree.

-- This includes both values for backend-related as
well as information, previously only settable via TypoScript.

This way, it is possible to have a TYPO3 installation
with 20 languages, but only using 5 languages within
one pagetree (site), using 15 different languages in
another site, while also giving meaning for all records
within one site.

A site configuration can be added or edited within the
TYPO3 Backend in a new backend module for admins,
and is then persisted in
"typo3conf/sites/site-identifier/config.yaml".

The configuration format yaml is chosen as it minimizes
the risk of doing hacks, but the concept of a SiteConfiguration
can be adapted / exchanged to be overloaded or found
in various other places in the future.

Adding a site configuration for a project has various
benefits:
- Configuration is in one place, stored in the file system
  thus, is deployable.
- Configuration can be done by an integrator/admin without
  any programming skills => in one place.
- The necessity to query sys_language is only needed when
  configuring a site.
- No need to configure TSconfig options like
  "previewDomains" and "defaultLanguageLabel" are gone.
  is
- No need to configure any TypoScript conditions, or
  even TypoScript settings related to language handling.
- It is possible to configure error handling on a per-site
  level, even language-dependant, without having to
  code or configure anything.

However, if no site is configured for a pagetree, the
previous behaviour is still in place, allowing to migrate
slowly to the sites handling, as some key functionality like
URL path handling for speaking URLs is not in place yet.

It is important to understand that adding a site
configuration comes with various restrictions:
- "sys_domain" handling is not necessary anymore, as a page
  is resolved via the domain+base URL given in the configuration
- Any previously configured TypoScript conditions based
  on L parameter do not apply anymore if a site is configured
- This also applies to any config.*language* related TypoScript
  setting.
- It is necessary to use a URI with scheme and path to configure
  a site, where as previously, TYPO3 would work without a base
  URL.
- mod_rewrite or something similar is a requirement for sites
  to work properly.

Further improvements not yet implemented:
- Ensure backend modules like Web->Page, Web->View, Web->List
and Web->Info only show records in the configured site languages.
- Enable the possibility to handle "domain entry aliases",
  also for multi-server setups with different domain names.
- Ensure the new Site module can handle "read-only" / deployable
  site configurations.
- Allow to activate a language for a site to be editable
  in the Backend but not be available in the Frontend for
  everybody.
- Handling "Storage Folder" on the top level with different
  language entries.
- Improve URL generation in frontend to skip sys_domain
  resolving.
- TypoScript conditions for [site = my-identifier] and
  [siteLanguage = dk].
- Improve proper caching for rootline resolving of pages
  without restrictions.
- Improve resolving of siteLanguage from the current request.
- Linking from one site to a page of a different site.
- Centralizing access to sys_language and sys_domain.
- Handle copying/moving of records to a different site
  with different languages and language settings.
- Handle configuration change (like deleting a language
  in the configuration - what should happen to the
  translated records?)

Next up for 9.3:
- Adding "Routers" on top of sites for URL resolving
  of pages and records.
- Handle Storage Folders on top level rootline
- Handle Mount Points

New API:
- New Entity classes "Site" and "SiteLanguage" are
  resolved as part of a PSR-15 middleware and available
  for pages in FE and BE when possible.
- A SiteFinder object is used to query Site and SiteLanguage
  objects and used in various places.
- The new PageUriBuilder allows to create links to pages
  without any relation to the current request, and
  are already in use in Frontend links and Backend preview links.
- A PageErrorHandlerInterface allows to custom error handlers
  to be introduced by extensions and configured on any site.

Site handling is considered still "under development"
until TYPO3 9 LTS, and implementation as well as the
configuration format might change in the next sprint
releases, in order to gather feedback on what is missing
in the implementation.

As a site configuration will be mandatory for TYPO3 v10.0,
some changes regarding sys_language and sys_domain will
follow.

Beware:
- Due to the definition of every record of default language
  (language=0), it might be possible to switch to locales
  for languages and get rid of the language ID.
- sys_domain won't make it any much longer.

Resolves: #84581
Releases: master
Change-Id: Iabeeb6835a98c8f5a71d502379ed63a68dfad6dd
Reviewed-on: https://review.typo3.org/56505

Tested-by: default avatarTYPO3com <no-reply@typo3.com>
Reviewed-by: Anja Leichsenring's avatarAnja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Anja Leichsenring's avatarAnja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Georg Ringer's avatarGeorg Ringer <georg.ringer@gmail.com>
Tested-by: Georg Ringer's avatarGeorg Ringer <georg.ringer@gmail.com>
Reviewed-by: Andreas Fernandez's avatarAndreas Fernandez <a.fernandez@scripting-base.de>
Tested-by: Andreas Fernandez's avatarAndreas Fernandez <a.fernandez@scripting-base.de>
parent 1eeedc59
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Backend\Configuration;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use Symfony\Component\Finder\Finder;
use TYPO3\CMS\Core\Package\PackageManager;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Helper class for the backend "Sites" module
*
* Load Site configuration TCA from ext:*Configuration/SiteConfigurationTCA
* and ext:*Configuration/SiteConfigurationTCA/Overrides
*/
class SiteTcaConfiguration
{
/**
* Returns a "fake TCA" array that is syntactically identical to
* "normal" TCA, and just isn't available as $GLOBALS['TCA'].
*
* @return array
*/
public function getTca(): array
{
// To allow casual ExtensionManagementUtility methods that works on $GLOBALS['TCA']
// to change our fake TCA, just kick original TCA, and reset to original at the end.
$originalTca = $GLOBALS['TCA'];
$GLOBALS['TCA'] = [];
$activePackages = GeneralUtility::makeInstance(PackageManager::class)->getActivePackages();
// First load "full table" files from Configuration/SiteConfigurationTCA
$finder = new Finder();
foreach ($activePackages as $package) {
try {
$finder->files()->depth(0)->name('*.php')->in($package->getPackagePath() . 'Configuration/SiteConfigurationTCA');
} catch (\InvalidArgumentException $e) {
// No such directory in this package
continue;
}
foreach ($finder as $fileInfo) {
$GLOBALS['TCA'][substr($fileInfo->getBasename(), 0, -4)] = require $fileInfo->getPathname();
}
}
// Execute override files from Configuration/TCA/Overrides
foreach ($activePackages as $package) {
try {
$finder->files()->depth(0)->name('*.php')->in($package->getPackagePath() . 'Configuration/SiteConfigurationTCA/Overrides');
} catch (\InvalidArgumentException $e) {
// No such directory in this package
continue;
}
foreach ($finder as $fileInfo) {
require $fileInfo->getPathname();
}
}
$result = $GLOBALS['TCA'];
$GLOBALS['TCA'] = $originalTca;
return $result;
}
}
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Backend\Exception;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Backend\Exception;
/**
* Exception thrown if site configuration for a page is not found
*/
class SiteValidationErrorException extends Exception
{
}
<?php
declare(strict_types=1);
namespace TYPO3\CMS\Backend\Form\FieldInformation;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Backend\Form\AbstractNode;
use TYPO3\CMS\Core\Localization\LanguageService;
/**
* Provides field information texts for form engine fields concerning site configuration module
*/
class SiteConfiguration extends AbstractNode
{
/**
* Handler for single nodes
*
* @return array As defined in initializeResultArray() of AbstractNode
*/
public function render()
{
$resultArray = $this->initializeResultArray();
$fieldInformationText = $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/siteconfiguration_fieldinformation.xlf:' . $this->data['tableName'] . '.' . $this->data['fieldName']);
if ($fieldInformationText !== $this->data['fieldName']) {
$resultArray['html'] = $fieldInformationText;
}
return $resultArray;
}
/**
* Returns the LanguageService
*
* @return LanguageService
*/
protected function getLanguageService()
{
return $GLOBALS['LANG'];
}
}
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Backend\Form\FormDataGroup;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Backend\Form\FormDataGroupInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* A data provider group for site and language configuration.
*
* This data group is for data fetched from sites yml files,
* it is fed by "fake TCA" since there are no real db records.
*
* It's similar to "fullDatabaseRecord", with some unused TCA types
* kicked out and some own data providers for record data and inline handling.
*/
class SiteConfigurationDataGroup implements FormDataGroupInterface
{
/**
* Compile form data
*
* @param array $result Initialized result array
* @return array Result filled with data
* @throws \UnexpectedValueException
*/
public function compile(array $result)
{
$orderedProviderList = GeneralUtility::makeInstance(OrderedProviderList::class);
$orderedProviderList->setProviderList(
$GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['formDataGroup']['siteConfiguration']
);
return $orderedProviderList->compile($result);
}
}
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Backend\Form\FormDataProvider;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
use TYPO3\CMS\Core\Site\SiteFinder;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Special data provider for the sites configuration module.
*
* Fetch "row" data from yml file and set as 'databaseRow'
*/
class SiteDatabaseEditRow implements FormDataProviderInterface
{
/**
* First level of ['customData']['siteData'] to ['databaseRow']
*
* @param array $result
* @return array
* @throws \RuntimeException
*/
public function addData(array $result): array
{
if ($result['command'] !== 'edit' || !empty($result['databaseRow'])) {
return $result;
}
$tableName = $result['tableName'];
$siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
if ($tableName === 'sys_site') {
$siteConfigurationForPageUid = (int)$result['vanillaUid'];
$rowData = $siteFinder->getSiteByRootPageId($siteConfigurationForPageUid)->getConfiguration();
$result['databaseRow']['uid'] = $rowData['rootPageId'];
$result['databaseRow']['identifier'] = $result['customData']['siteIdentifier'];
} elseif ($tableName === 'sys_site_errorhandling' || $tableName === 'sys_site_language') {
$siteConfigurationForPageUid = (int)($result['inlineTopMostParentUid'] ?? $result['inlineParentUid']);
$rowData = $siteFinder->getSiteByRootPageId($siteConfigurationForPageUid)->getConfiguration();
$parentFieldName = $result['inlineParentFieldName'];
if (!isset($rowData[$parentFieldName])) {
throw new \RuntimeException('Field "' . $parentFieldName . '" not found', 1520886092);
}
$rowData = $rowData[$parentFieldName][$result['vanillaUid']];
$result['databaseRow']['uid'] = $result['vanillaUid'];
} else {
throw new \RuntimeException('Other tables not implemented', 1520886234);
}
foreach ($rowData as $fieldName => $value) {
// Flat values only - databaseRow has no "tree"
if (!is_array($value)) {
$result['databaseRow'][$fieldName] = $value;
}
}
// All "records" are always on pid 0
$result['databaseRow']['pid'] = 0;
return $result;
}
}
<?php
declare(strict_types = 1);
namespace TYPO3\CMS\Backend\Form\FormDataProvider;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\CMS\Backend\Form\FormDataCompiler;
use TYPO3\CMS\Backend\Form\FormDataGroup\OnTheFly;
use TYPO3\CMS\Backend\Form\FormDataGroup\SiteConfigurationDataGroup;
use TYPO3\CMS\Backend\Form\FormDataProviderInterface;
use TYPO3\CMS\Backend\Form\InlineStackProcessor;
use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Site\SiteFinder;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Special data provider for the sites configuration module.
*
* Handle inline children of 'sys_site"
*/
class SiteTcaInline extends AbstractDatabaseRecordProvider implements FormDataProviderInterface
{
/**
* Resolve inline fields
*
* @param array $result
* @return array
*/
public function addData(array $result): array
{
$result = $this->addInlineFirstPid($result);
foreach ($result['processedTca']['columns'] as $fieldName => $fieldConfig) {
if (!$this->isInlineField($fieldConfig)) {
continue;
}
$childTableName = $fieldConfig['config']['foreign_table'];
if ($childTableName !== 'sys_site_errorhandling' && $childTableName !== 'sys_site_language') {
throw new \RuntimeException('Inline relation to other tables not implemented', 1522494737);
}
$result['processedTca']['columns'][$fieldName]['children'] = [];
$result = $this->resolveSiteRelatedChildren($result, $fieldName);
$result = $this->addForeignSelectorAndUniquePossibleRecords($result, $fieldName);
}
return $result;
}
/**
* Is column of type "inline"
*
* @param array $fieldConfig
* @return bool
*/
protected function isInlineField(array $fieldConfig): bool
{
return !empty($fieldConfig['config']['type']) && $fieldConfig['config']['type'] === 'inline';
}
/**
* The "entry" pid for inline records. Nested inline records can potentially hang around on different
* 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.
*
* @param array $result Incoming result
* @return array Modified result
* @todo: Find out when and if this is different from 'effectivePid'
*/
protected function addInlineFirstPid(array $result): array
{
if ($result['inlineFirstPid'] === null) {
$table = $result['tableName'];
$row = $result['databaseRow'];
// If the parent is a page, use the uid(!) of the (new?) page as pid for the child records:
if ($table === 'pages') {
$liveVersionId = BackendUtility::getLiveVersionIdOfRecord('pages', $row['uid']);
$pid = $liveVersionId === null ? $row['uid'] : $liveVersionId;
} elseif ($row['pid'] < 0) {
$prevRec = BackendUtility::getRecord($table, abs($row['pid']));
$pid = $prevRec['pid'];
} else {
$pid = $row['pid'];
}
$pageRecord = BackendUtility::getRecord('pages', $pid);
if ((int)$pageRecord[$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField']] > 0) {
$pid = (int)$pageRecord[$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField']];
}
$result['inlineFirstPid'] = (int)$pid;
}
return $result;
}
/**
* Substitute the value in databaseRow of this inline field with an array
* that contains the databaseRows of currently connected records and some meta information.
*
* @param array $result Result array
* @param string $fieldName Current handle field name
* @return array Modified item array
*/
protected function resolveSiteRelatedChildren(array $result, string $fieldName): array
{
$connectedUids = [];
if ($result['command'] === 'edit') {
$siteConfigurationForPageUid = (int)$result['databaseRow']['rootPageId'][0];
$siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
$site = $siteFinder->getSiteByRootPageId($siteConfigurationForPageUid);
$siteConfiguration = $site ? $site->getConfiguration() : [];
if (is_array($siteConfiguration[$fieldName])) {
$connectedUids = array_keys($siteConfiguration[$fieldName]);
}
}
// If we are dealing with sys_site_language, we *always* force a relation to sys_language "0"
$foreignTable = $result['processedTca']['columns'][$fieldName]['config']['foreign_table'];
if ($foreignTable === 'sys_site_language' && $result['command'] === 'new') {
// If new, just add a new default child
$child = $this->compileDefaultSysSiteLanguageChild($result, $fieldName);
$connectedUids[] = $child['databaseRow']['uid'];
$result['processedTca']['columns'][$fieldName]['children'][] = $child;
}
$result['databaseRow'][$fieldName] = implode(',', $connectedUids);
if ($result['inlineCompileExistingChildren']) {
foreach ($connectedUids as $uid) {
if (strpos((string)$uid, 'NEW') !== 0) {
$compiledChild = $this->compileChild($result, $fieldName, $uid);
$result['processedTca']['columns'][$fieldName]['children'][] = $compiledChild;
}
}
}
// If we are dealing with sys_site_language, we *always* force a relation to sys_language "0"
if ($foreignTable === 'sys_site_language' && $result['command'] === 'edit') {
// If edit, find out if a child using sys_language "0" exists, else add it on top
$defaultSysSiteLanguageChildFound = false;
foreach ($result['processedTca']['columns'][$fieldName]['children'] as $child) {
if (isset($child['databaseRow']['languageId']) && (int)$child['databaseRow']['languageId'][0] == 0) {
$defaultSysSiteLanguageChildFound = true;
}
}
if (!$defaultSysSiteLanguageChildFound) {
// Compile and add child as first child
$child = $this->compileDefaultSysSiteLanguageChild($result, $fieldName);
$result['databaseRow'][$fieldName] = $child['databaseRow']['uid'] . ',' . $result['databaseRow'][$fieldName];
array_unshift($result['processedTca']['columns'][$fieldName]['children'], $child);
}
}
return $result;
}
/**
* If there is a foreign_selector or foreign_unique configuration, fetch
* the list of possible records that can be connected and attach them to the
* inline configuration.
*
* @param array $result Result array
* @param string $fieldName Current handle field name
* @return array Modified item array
*/
protected function addForeignSelectorAndUniquePossibleRecords(array $result, string $fieldName): array
{
if (!is_array($result['processedTca']['columns'][$fieldName]['config']['selectorOrUniqueConfiguration'])) {
return $result;
}
$selectorOrUniqueConfiguration = $result['processedTca']['columns'][$fieldName]['config']['selectorOrUniqueConfiguration'];
$foreignFieldName = $selectorOrUniqueConfiguration['fieldName'];
$selectorOrUniquePossibleRecords = [];
if ($selectorOrUniqueConfiguration['config']['type'] === 'select') {
// Compile child table data for this field only
$selectDataInput = [
'tableName' => $result['processedTca']['columns'][$fieldName]['config']['foreign_table'],
'command' => 'new',
// Since there is no existing record that may have a type, it does not make sense to
// do extra handling of pageTsConfig merged here. Just provide "parent" pageTS as is
'pageTsConfig' => $result['pageTsConfig'],
'userTsConfig' => $result['userTsConfig'],
'databaseRow' => $result['databaseRow'],
'processedTca' => [
'ctrl' => [],
'columns' => [
$foreignFieldName => [
'config' => $selectorOrUniqueConfiguration['config'],
],
],
],
'inlineExpandCollapseStateArray' => $result['inlineExpandCollapseStateArray'],
];
$formDataGroup = GeneralUtility::makeInstance(OnTheFly::class);
$formDataGroup->setProviderList([TcaSelectItems::class]);
$formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
$compilerResult = $formDataCompiler->compile($selectDataInput);
$selectorOrUniquePossibleRecords = $compilerResult['processedTca']['columns'][$foreignFieldName]['config']['items'];
}
$result['processedTca']['columns'][$fieldName]['config']['selectorOrUniquePossibleRecords'] = $selectorOrUniquePossibleRecords;
return $result;
}
/**
* Compile a full child record
*
* @param array $result Result array of parent
* @param string $parentFieldName Name of parent field
* @param int $childUid Uid of child to compile
* @return array Full result array
*/
protected function compileChild(array $result, string $parentFieldName, int $childUid): array
{
$parentConfig = $result['processedTca']['columns'][$parentFieldName]['config'];
$childTableName = $parentConfig['foreign_table'];
$inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
$inlineStackProcessor->initializeByGivenStructure($result['inlineStructure']);
$inlineTopMostParent = $inlineStackProcessor->getStructureLevel(0);
$formDataGroup = GeneralUtility::makeInstance(SiteConfigurationDataGroup::class);
$formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
$formDataCompilerInput = [
'command' => 'edit',
'tableName' => $childTableName,
'vanillaUid' => $childUid,
// Give incoming returnUrl down to children so they generate a returnUrl back to
// the originally opening record, also see "originalReturnUrl" in inline container
// and FormInlineAjaxController
'returnUrl' => $result['returnUrl'],
'isInlineChild' => true,
'inlineStructure' => $result['inlineStructure'],
'inlineExpandCollapseStateArray' => $result['inlineExpandCollapseStateArray'],
'inlineFirstPid' => $result['inlineFirstPid'],
'inlineParentConfig' => $parentConfig,
// values of the current parent element
// it is always a string either an id or new...
'inlineParentUid' => $result['databaseRow']['uid'],
'inlineParentTableName' => $result['tableName'],
'inlineParentFieldName' => $parentFieldName,
// values of the top most parent element set on first level and not overridden on following levels
'inlineTopMostParentUid' => $result['inlineTopMostParentUid'] ?: $inlineTopMostParent['uid'],
'inlineTopMostParentTableName' => $result['inlineTopMostParentTableName'] ?: $inlineTopMostParent['table'],
'inlineTopMostParentFieldName' => $result['inlineTopMostParentFieldName'] ?: $inlineTopMostParent['field'],
];
if ($parentConfig['foreign_selector'] && $parentConfig['appearance']['useCombination']) {
throw new \RuntimeException('useCombination not implemented in sites module', 1522493097);
}
return $formDataCompiler->compile($formDataCompilerInput);
}
/**
* Compile default sys_site_language child using sys_language uid "0"
*
* @param array $result
* @param string $parentFieldName
* @return array
*/
protected function compileDefaultSysSiteLanguageChild(array $result, string $parentFieldName): array
{
$parentConfig = $result['processedTca']['columns'][$parentFieldName]['config'];
$inlineStackProcessor = GeneralUtility::makeInstance(InlineStackProcessor::class);
$inlineStackProcessor->initializeByGivenStructure($result['inlineStructure']);
$inlineTopMostParent = $inlineStackProcessor->getStructureLevel(0);
$formDataGroup = GeneralUtility::makeInstance(SiteConfigurationDataGroup::class);
$formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
$formDataCompilerInput = [
'command' => 'new',
'tableName' => 'sys_site_language',
'vanillaUid' => $result['inlineFirstPid'],
'returnUrl' => $result['returnUrl'],
'isInlineChild' => true,
'inlineStructure' => [],
'inlineExpandCollapseStateArray' => $result['inlineExpandCollapseStateArray'],
'inlineFirstPid' => $result['inlineFirstPid'],
'inlineParentConfig' => $parentConfig,
'inlineParentUid' => $result['databaseRow']['uid'],