diff --git a/composer.json b/composer.json index 721dc1968f132368a2f7a56f2f8c96825b28a217..b85a7141e9a60e54004e47f75272bcba7032a79f 100644 --- a/composer.json +++ b/composer.json @@ -298,7 +298,8 @@ "TYPO3Tests\\TestDataMapper\\": "typo3/sysext/extbase/Tests/Functional/Fixtures/Extensions/test_data_mapper/Classes/", "TYPO3Tests\\TestFluidTemplate\\": "typo3/sysext/frontend/Tests/Functional/Fixtures/Extensions/test_fluid_template/Classes/", "TYPO3Tests\\TestLogger\\": "typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_logger/Classes/", - "TYPO3Tests\\TestTyposcriptAstFunctionEvent\\": "typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_ast_function_event/Classes/" + "TYPO3Tests\\TestTyposcriptAstFunctionEvent\\": "typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_ast_function_event/Classes/", + "TYPO3Tests\\TestTyposcriptPagetsconfigfactory\\": "typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Classes/" }, "classmap": [ "typo3/sysext/core/Tests/Unit/Core/Fixtures/test_extension/", diff --git a/typo3/sysext/backend/Classes/Form/Element/BackendLayoutWizardElement.php b/typo3/sysext/backend/Classes/Form/Element/BackendLayoutWizardElement.php index e6459ed57c76217c33cf124f09aaa7f8feb2ad0a..2075aa8bc3707e0fea8aa174be4c7a98142c7d1a 100644 --- a/typo3/sysext/backend/Classes/Form/Element/BackendLayoutWizardElement.php +++ b/typo3/sysext/backend/Classes/Form/Element/BackendLayoutWizardElement.php @@ -19,7 +19,6 @@ use TYPO3\CMS\Core\EventDispatcher\NoopEventDispatcher; use TYPO3\CMS\Core\Imaging\Icon; use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction; use TYPO3\CMS\Core\TypoScript\AST\AstBuilder; -use TYPO3\CMS\Core\TypoScript\Tokenizer\LossyTokenizer; use TYPO3\CMS\Core\TypoScript\TypoScriptStringFactory; use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -198,11 +197,7 @@ class BackendLayoutWizardElement extends AbstractFormElement if (!empty($this->data['parameterArray']['itemFormElValue'])) { // Parse the TypoScript a-like syntax in case we already have a config (e.g. database value or default from TCA) $typoScriptStringFactory = GeneralUtility::makeInstance(TypoScriptStringFactory::class); - $typoScriptTree = $typoScriptStringFactory->parseFromString( - $this->data['parameterArray']['itemFormElValue'], - new LossyTokenizer(), - new AstBuilder(new NoopEventDispatcher()) - ); + $typoScriptTree = $typoScriptStringFactory->parseFromString($this->data['parameterArray']['itemFormElValue'], new AstBuilder(new NoopEventDispatcher())); $typoScriptArray = $typoScriptTree->toArray(); if (is_array($typoScriptArray['backend_layout.'] ?? false)) { // Only evaluate, in case the "backend_layout." array exists on root level diff --git a/typo3/sysext/backend/Classes/Utility/BackendUtility.php b/typo3/sysext/backend/Classes/Utility/BackendUtility.php index 2a65669b4ba99ea0961dba6a077748c5c2c89c74..8b8b4ecd328a6940b729cf858b3efda62f951475 100644 --- a/typo3/sysext/backend/Classes/Utility/BackendUtility.php +++ b/typo3/sysext/backend/Classes/Utility/BackendUtility.php @@ -20,7 +20,7 @@ use Doctrine\DBAL\Types\Type; use Psr\Http\Message\ServerRequestInterface; use Psr\Log\LoggerInterface; use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider; -use TYPO3\CMS\Backend\Configuration\TypoScript\ConditionMatching\ConditionMatcher; +use TYPO3\CMS\Backend\Configuration\TypoScript\ConditionMatching\ConditionMatcher as BackendConditionMatcher; use TYPO3\CMS\Backend\Domain\Model\Element\ImmediateActionElement; use TYPO3\CMS\Backend\Module\ModuleProvider; use TYPO3\CMS\Backend\Routing\Route; @@ -28,7 +28,6 @@ use TYPO3\CMS\Backend\Routing\UriBuilder; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; use TYPO3\CMS\Core\Cache\CacheManager; use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface; -use TYPO3\CMS\Core\Configuration\PageTsConfig; use TYPO3\CMS\Core\Context\Context; use TYPO3\CMS\Core\Context\DateTimeAspect; use TYPO3\CMS\Core\Core\Environment; @@ -55,8 +54,11 @@ use TYPO3\CMS\Core\Resource\ResourceFactory; use TYPO3\CMS\Core\Routing\InvalidRouteArgumentsException; use TYPO3\CMS\Core\Routing\RouterInterface; use TYPO3\CMS\Core\Routing\UnableToLinkToPageException; +use TYPO3\CMS\Core\Site\Entity\NullSite; use TYPO3\CMS\Core\Site\SiteFinder; use TYPO3\CMS\Core\Type\Bitmask\Permission; +use TYPO3\CMS\Core\TypoScript\PageTsConfig; +use TYPO3\CMS\Core\TypoScript\PageTsConfigFactory; use TYPO3\CMS\Core\Utility\ArrayUtility; use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -687,27 +689,40 @@ class BackendUtility * TypoScript related * *******************************************/ + /** - * Returns the Page TSconfig for page with id, $id - * - * @param int $id Page uid for which to create Page TSconfig - * @return array Page TSconfig + * Returns the PageTsConfig for page with uid $pageUid */ - public static function getPagesTSconfig($id) + public static function getPagesTSconfig($pageUid): array { - $id = (int)$id; - $rootLine = self::BEgetRootLine($id, '', true); + $runtimeCache = static::getRuntimeCache(); + $pageTsConfig = $runtimeCache->get('pageTsConfig-' . $pageUid); + if ($pageTsConfig instanceof PageTsConfig) { + return $pageTsConfig->getPageTsConfigArray(); + } + + $pageUid = (int)$pageUid; + $rootLine = self::BEgetRootLine($pageUid, '', true); // Order correctly ksort($rootLine); try { - $site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($id); - } catch (SiteNotFoundException $exception) { - $site = null; - } - $matcher = GeneralUtility::makeInstance(ConditionMatcher::class, GeneralUtility::makeInstance(Context::class), $id, $rootLine); - $tsConfig = GeneralUtility::makeInstance(PageTsConfig::class); - return $tsConfig->getWithUserOverride($id, $rootLine, $site, $matcher, static::getBackendUserAuthentication()); + $site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($pageUid); + } catch (SiteNotFoundException) { + $site = new NullSite(); + } + + $conditionMatcher = GeneralUtility::makeInstance(BackendConditionMatcher::class, GeneralUtility::makeInstance(Context::class), $pageUid, $rootLine); + $pageTsConfigFactory = GeneralUtility::makeInstance(PageTsConfigFactory::class); + $pageTsConfig = $pageTsConfigFactory->create( + $rootLine, + $site, + $conditionMatcher, + static::getBackendUserAuthentication()?->getUserTsConfig() + ); + + $runtimeCache->set('pageTsConfig-' . $pageUid, $pageTsConfig); + return $pageTsConfig->getPageTsConfigArray(); } /******************************************* diff --git a/typo3/sysext/backend/Classes/View/BackendLayoutView.php b/typo3/sysext/backend/Classes/View/BackendLayoutView.php index 1502fe29fea251f66a1f2e0402b5a92b6fc0ca5b..43cf692d903dbebbe20106289a3fd392c08cced2 100644 --- a/typo3/sysext/backend/Classes/View/BackendLayoutView.php +++ b/typo3/sysext/backend/Classes/View/BackendLayoutView.php @@ -21,12 +21,10 @@ use TYPO3\CMS\Backend\View\BackendLayout\BackendLayout; use TYPO3\CMS\Backend\View\BackendLayout\DataProviderCollection; use TYPO3\CMS\Backend\View\BackendLayout\DataProviderContext; use TYPO3\CMS\Backend\View\BackendLayout\DefaultDataProvider; -use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend; use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Localization\LanguageService; use TYPO3\CMS\Core\SingletonInterface; -use TYPO3\CMS\Core\TypoScript\Tokenizer\TokenizerInterface; use TYPO3\CMS\Core\TypoScript\TypoScriptStringFactory; use TYPO3\CMS\Core\Utility\ArrayUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -47,8 +45,6 @@ class BackendLayoutView implements SingletonInterface private readonly DataProviderCollection $dataProviderCollection, private readonly TypoScriptStringFactory $typoScriptStringFactory, private readonly BackendConditionMatcher $conditionMatcher, - private readonly TokenizerInterface $tokenizer, - private readonly PhpFrontend $typoScriptCache, ) { $this->dataProviderCollection->add('default', DefaultDataProvider::class); if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['BackendLayoutDataProvider'])) { @@ -346,9 +342,7 @@ class BackendLayoutView implements SingletonInterface $typoScriptTree = $this->typoScriptStringFactory->parseFromStringWithIncludesAndConditions( 'backend-layout', $backendLayout->getConfiguration(), - $this->tokenizer, - $this->conditionMatcher, - $this->typoScriptCache + $this->conditionMatcher ); $backendLayoutData = []; diff --git a/typo3/sysext/backend/Classes/View/BackendViewFactory.php b/typo3/sysext/backend/Classes/View/BackendViewFactory.php index bb0fe3e75697839f11cb544fb6dd767db61ae204..e58f968264b4f47d5df105814c9bce13bb16089e 100644 --- a/typo3/sysext/backend/Classes/View/BackendViewFactory.php +++ b/typo3/sysext/backend/Classes/View/BackendViewFactory.php @@ -22,6 +22,7 @@ use TYPO3\CMS\Backend\Routing\Route; use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Core\Package\PackageManager; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\MathUtility; use TYPO3\CMS\Core\View\FluidViewAdapter; use TYPO3\CMS\Core\View\ViewInterface as CoreViewInterface; use TYPO3\CMS\Fluid\Core\Rendering\RenderingContextFactory; @@ -69,8 +70,15 @@ final class BackendViewFactory // @todo: This assumes the pageId is *always* given as 'id' in request. // @todo: It would be cool if a middleware adds final pageTS - already overlayed by userTS - as attribute to request, to use it here. + $pageTs = []; $pageId = $request->getParsedBody()['id'] ?? $request->getQueryParams()['id'] ?? 0; - $pageTs = BackendUtility::getPagesTSconfig($pageId); + if (MathUtility::canBeInterpretedAsInteger($pageId)) { + // Some BE controllers misuse the 'id' argument for something else than the page-uid (especially filelist module). + // We check if 'id' is an integer here to skip pageTsConfig calculation if that is the case. + // @todo: Mid-term, misuses should vanish, making 'id' a Backend convention. Affected is + // at least ext:filelist, plus record linking modals that use 'pid'. + $pageTs = BackendUtility::getPagesTSconfig((int)$pageId); + } $templatePaths = [ 'templateRootPaths' => [], diff --git a/typo3/sysext/backend/Configuration/Services.yaml b/typo3/sysext/backend/Configuration/Services.yaml index c0d28a11d0412db562921c82880e2989a3486534..1d00f5d171566980101fa706f62c160ecffa7b8e 100644 --- a/typo3/sysext/backend/Configuration/Services.yaml +++ b/typo3/sysext/backend/Configuration/Services.yaml @@ -70,10 +70,6 @@ services: TYPO3\CMS\Backend\View\AuthenticationStyleInformation: public: true - TYPO3\CMS\Backend\View\BackendLayoutView: - arguments: - $typoScriptCache: '@cache.typoscript' - TYPO3\CMS\Backend\Search\LiveSearch\SearchProviderRegistry: arguments: - !tagged_iterator livesearch.provider diff --git a/typo3/sysext/core/Classes/Authentication/BackendUserAuthentication.php b/typo3/sysext/core/Classes/Authentication/BackendUserAuthentication.php index 87dd392c3d2d81a8ae42a24a236e9e3b75cd17eb..497a60a8ce04fc2811e95b0b7425bf3946f6b9b4 100644 --- a/typo3/sysext/core/Classes/Authentication/BackendUserAuthentication.php +++ b/typo3/sysext/core/Classes/Authentication/BackendUserAuthentication.php @@ -15,7 +15,6 @@ namespace TYPO3\CMS\Core\Authentication; -use TYPO3\CMS\Backend\Configuration\TypoScript\ConditionMatching\ConditionMatcher; use TYPO3\CMS\Backend\Module\ModuleProvider; use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Core\Cache\CacheManager; @@ -46,7 +45,8 @@ use TYPO3\CMS\Core\Type\Bitmask\BackendGroupMountOption; use TYPO3\CMS\Core\Type\Bitmask\JsConfirmation; use TYPO3\CMS\Core\Type\Bitmask\Permission; use TYPO3\CMS\Core\Type\Exception\InvalidEnumerationValueException; -use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser; +use TYPO3\CMS\Core\TypoScript\UserTsConfig; +use TYPO3\CMS\Core\TypoScript\UserTsConfigFactory; use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\StringUtility; @@ -111,15 +111,13 @@ class BackendUserAuthentication extends AbstractUserAuthentication */ public $workspaceRec = []; - /** - * @var array Parsed user TSconfig - */ - protected $userTS = []; + protected ?UserTsConfig $userTsConfig = null; /** - * @var bool True if the user TSconfig was parsed and needs to be cached. + * True if the user TSconfig was parsed and needs to be cached. + * @todo: Should vanish, see todo below. */ - protected $userTSUpdated = false; + protected bool $userTSUpdated = false; /** * Contains last error message @@ -968,9 +966,19 @@ class BackendUserAuthentication extends AbstractUserAuthentication * * @return array Parsed and merged user TSconfig array */ - public function getTSConfig() + public function getTSConfig(): array + { + return $this->getUserTsConfig()?->getUserTsConfigArray() ?? []; + } + + /** + * Return the full UserTsConfig object instead of just the array as in getTSConfig() + * + * @internal for now until API stabilized + */ + public function getUserTsConfig(): ?UserTsConfig { - return $this->userTS; + return $this->userTsConfig; } /** @@ -1058,7 +1066,6 @@ class BackendUserAuthentication extends AbstractUserAuthentication * Generally this is required initialization of a backend user. * * @internal - * @see \TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser */ public function fetchGroupData() { @@ -1133,8 +1140,8 @@ class BackendUserAuthentication extends AbstractUserAuthentication } } - // Populating the $this->userGroupsUID -array with the groups in the order in which they were LAST included.!! - // Finally this is the list of group_uid's in the order they are parsed (including subgroups!) + // Populating the $this->userGroupsUID -array with the groups in the order in which they were LAST included. + // Finally, this is the list of group_uid's in the order they are parsed (including subgroups) // and without duplicates (duplicates are presented with their last entrance in the list, // which thus reflects the order of the TypoScript in TSconfig) $this->userGroupsUID = array_reverse(array_unique(array_reverse($this->userGroupsUID))); @@ -1214,43 +1221,28 @@ class BackendUserAuthentication extends AbstractUserAuthentication } /** - * This method parses the UserTSconfig from the current user and all their groups. - * If the contents are the same, parsing is skipped. No matching is applied here currently. + * Parse userTsConfig from current user and its groups and set it as $this->userTS. */ protected function prepareUserTsConfig(): void { - $collectedUserTSconfig = [ - 'default' => $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultUserTSconfig'], - ]; - // Default TSconfig for admin-users - if ($this->isAdmin()) { - $collectedUserTSconfig[] = 'admPanel.enable.all = 1'; - } - // Setting defaults for sys_note author / email - $collectedUserTSconfig[] = ' -TCAdefaults.sys_note.author = ' . $this->user['realName'] . ' -TCAdefaults.sys_note.email = ' . $this->user['email']; - - // Loop through all groups and add their 'TSconfig' fields - foreach ($this->userGroupsUID as $groupId) { - $collectedUserTSconfig['group_' . $groupId] = $this->userGroups[$groupId]['TSconfig'] ?? ''; - } - - $collectedUserTSconfig[] = $this->user['TSconfig']; - // Check external files - $collectedUserTSconfig = TypoScriptParser::checkIncludeLines_array($collectedUserTSconfig); - // Imploding with "[global]" will make sure that non-ended confinements with braces are ignored. - $userTS_text = implode("\n[GLOBAL]\n", $collectedUserTSconfig); - // Parsing the user TSconfig (or getting from cache) - $hash = md5('userTS:' . $userTS_text); - $cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('hash'); - if (!($this->userTS = $cache->get($hash))) { - $parseObj = GeneralUtility::makeInstance(TypoScriptParser::class); - $conditionMatcher = GeneralUtility::makeInstance(ConditionMatcher::class); - $parseObj->parse($userTS_text, $conditionMatcher); - $this->userTS = $parseObj->setup; - $cache->set($hash, $this->userTS, ['UserTSconfig'], 0); - // Ensure to update UC later + $tsConfigFactory = GeneralUtility::makeInstance(UserTsConfigFactory::class); + $this->userTsConfig = $tsConfigFactory->create($this); + if (!empty($this->getUserTsConfig()->getUserTsConfigArray()['setup.']['override.'])) { + // @todo: This logic is ugly. userTsConfig "setup.override." is used to force options + // in the user settings module, along with "setup.fields." and "setup.default.". + // See the docs about this. + // The fun part is, this is merged into user UC. As such, whenever these setup + // options are used, user UC has to be updated. The toggle below triggers this + // and initiates an update query of this users UC. + // Before v12, this was only triggered if userTsConfig could not be fetched from + // cache, but this was flawed, too: When two users had the same userTsConfig, UC + // of one user would be updated, but not UC of the other user if caches were not + // cleared in between their two calls. + // This toggle and UC overriding should vanish altogether: It would be better if + // userTsConfig no longer overlays UC, instead the settings / setup module + // controller should look at userTsConfig "setup." on the fly when rendering, and + // consumers that access user setup / settings values should get values overloaded + // on the fly as well using some helper or a late init logic or similar. $this->userTSUpdated = true; } } diff --git a/typo3/sysext/core/Classes/Configuration/Event/ModifyLoadedPageTsConfigEvent.php b/typo3/sysext/core/Classes/Configuration/Event/ModifyLoadedPageTsConfigEvent.php index fa7eafeb46cd90771d829e17d7ac6cc1fd2b8b12..26f4c0926181f7e9dd83cbb7cfa61d76b0c55437 100644 --- a/typo3/sysext/core/Classes/Configuration/Event/ModifyLoadedPageTsConfigEvent.php +++ b/typo3/sysext/core/Classes/Configuration/Event/ModifyLoadedPageTsConfigEvent.php @@ -19,30 +19,11 @@ namespace TYPO3\CMS\Core\Configuration\Event; /** * Extensions can modify pageTSConfig entries that can be overridden or added, based on the root line + * + * @deprecated since v12, will be removed in v13. Switch to \TYPO3\CMS\Core\TypoScript\IncludeTree\Event\ModifyLoadedPageTsConfigEvent. + * When removing, delete the class, adapt test_typoscript_pagetsconfigfactory test extension, related test and + * set event \TYPO3\CMS\Core\TypoScript\IncludeTree\Event\ModifyLoadedPageTsConfigEvent final. */ -final class ModifyLoadedPageTsConfigEvent +class ModifyLoadedPageTsConfigEvent extends \TYPO3\CMS\Core\TypoScript\IncludeTree\Event\ModifyLoadedPageTsConfigEvent { - public function __construct(private array $tsConfig, private readonly array $rootLine) - { - } - - public function getTsConfig(): array - { - return $this->tsConfig; - } - - public function addTsConfig(string $tsConfig): void - { - $this->tsConfig[] = $tsConfig; - } - - public function setTsConfig(array $tsConfig): void - { - $this->tsConfig = $tsConfig; - } - - public function getRootLine(): array - { - return $this->rootLine; - } } diff --git a/typo3/sysext/core/Classes/Configuration/ExtensionConfiguration.php b/typo3/sysext/core/Classes/Configuration/ExtensionConfiguration.php index 699c08b4bc89c8ca854b38f9f595bb14ee67d1b8..3b37a479df783a7e3d9ebafcf0974be087f76aed 100644 --- a/typo3/sysext/core/Classes/Configuration/ExtensionConfiguration.php +++ b/typo3/sysext/core/Classes/Configuration/ExtensionConfiguration.php @@ -22,7 +22,6 @@ use TYPO3\CMS\Core\Configuration\Exception\ExtensionConfigurationPathDoesNotExis use TYPO3\CMS\Core\EventDispatcher\NoopEventDispatcher; use TYPO3\CMS\Core\Package\PackageManager; use TYPO3\CMS\Core\TypoScript\AST\AstBuilder; -use TYPO3\CMS\Core\TypoScript\Tokenizer\LossyTokenizer; use TYPO3\CMS\Core\TypoScript\TypoScriptStringFactory; use TYPO3\CMS\Core\Utility\ArrayUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -249,7 +248,7 @@ class ExtensionConfiguration { $rawConfigurationString = $this->getDefaultConfigurationRawString($extensionKey); $typoScriptStringFactory = GeneralUtility::makeInstance(TypoScriptStringFactory::class); - $typoScriptTree = $typoScriptStringFactory->parseFromString($rawConfigurationString, new LossyTokenizer(), new AstBuilder(new NoopEventDispatcher())); + $typoScriptTree = $typoScriptStringFactory->parseFromString($rawConfigurationString, new AstBuilder(new NoopEventDispatcher())); return GeneralUtility::removeDotsFromTS($typoScriptTree->toArray()); } diff --git a/typo3/sysext/core/Classes/Configuration/Loader/PageTsConfigLoader.php b/typo3/sysext/core/Classes/Configuration/Loader/PageTsConfigLoader.php index d9d236762558cb45c4407e722904808b8534dbd7..dc90d1713e677fc7fb4bdce2c7efc595f70d6540 100644 --- a/typo3/sysext/core/Classes/Configuration/Loader/PageTsConfigLoader.php +++ b/typo3/sysext/core/Classes/Configuration/Loader/PageTsConfigLoader.php @@ -34,6 +34,9 @@ use TYPO3\CMS\Core\Utility\PathUtility; * * Currently, this accumulated information of the pages is NOT cached, as it would need to be tagged with any * page, also including external files. + * + * @deprecated since TYPO3 v12, will be removed with v13. Use PageTsConfigFactory instead. + * When removing, also remove entries in core ServiceProvider, AbstractServiceProvider and Services.yaml. */ class PageTsConfigLoader { @@ -42,6 +45,7 @@ class PageTsConfigLoader public function __construct(EventDispatcherInterface $eventDispatcher) { + trigger_error('Class ' . __CLASS__ . ' will be removed with TYPO3 v13.0. Use PageTsConfigFactory instead.', E_USER_DEPRECATED); $this->eventDispatcher = $eventDispatcher; } diff --git a/typo3/sysext/core/Classes/Configuration/PageTsConfig.php b/typo3/sysext/core/Classes/Configuration/PageTsConfig.php index 3413e38897b50b6c4ac9113323998570f95fdc87..b60007cacef1878d7dde71574f0ca2368399898e 100644 --- a/typo3/sysext/core/Classes/Configuration/PageTsConfig.php +++ b/typo3/sysext/core/Classes/Configuration/PageTsConfig.php @@ -26,6 +26,9 @@ use TYPO3\CMS\Core\Site\Entity\Site; /** * Main entry point for fetching PageTsConfig for frontend and backend. + * + * @deprecated since TYPO3 v12, will be removed with v13. Use PageTsConfigFactory instead. + * When removing, also remove entries in core Services.yaml and usage in TypoScriptFrontendController. */ class PageTsConfig { @@ -35,6 +38,7 @@ class PageTsConfig public function __construct(FrontendInterface $cache, PageTsConfigLoader $loader, PageTsConfigParser $parser) { + trigger_error('Class ' . __CLASS__ . ' will be removed with TYPO3 v13.0. Use PageTsConfigFactory instead.', E_USER_DEPRECATED); $this->cache = $cache; $this->loader = $loader; $this->parser = $parser; diff --git a/typo3/sysext/core/Classes/Configuration/Parser/PageTsConfigParser.php b/typo3/sysext/core/Classes/Configuration/Parser/PageTsConfigParser.php index f8d54e8268727a74d3f1b6845195b1241c3e743c..3fa09b8d58c316638ae387922025431ec84af4e2 100644 --- a/typo3/sysext/core/Classes/Configuration/Parser/PageTsConfigParser.php +++ b/typo3/sysext/core/Classes/Configuration/Parser/PageTsConfigParser.php @@ -27,6 +27,9 @@ use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser; * * This class does parsing of a compiled TSconfig string, and applies matching() based on the * Context (FE or BE) in it, allowing to be fully agnostic to the outside world. + * + * @deprecated since TYPO3 v12, will be removed with v13. Use PageTsConfigFactory instead. + * When removing, also remove entries in core Services.yaml. */ class PageTsConfigParser { @@ -35,6 +38,7 @@ class PageTsConfigParser public function __construct(TypoScriptParser $typoScriptParser, FrontendInterface $cache) { + trigger_error('Class ' . __CLASS__ . ' will be removed with TYPO3 v13.0. Use PageTsConfigFactory instead.', E_USER_DEPRECATED); $this->typoScriptParser = $typoScriptParser; $this->cache = $cache; } diff --git a/typo3/sysext/core/Classes/Package/AbstractServiceProvider.php b/typo3/sysext/core/Classes/Package/AbstractServiceProvider.php index d192ca447e0c69a469b5fb83a7fccc4d8b6fa97d..ca2effb8b5dee44b65d71eedaf460575986e9c6a 100644 --- a/typo3/sysext/core/Classes/Package/AbstractServiceProvider.php +++ b/typo3/sysext/core/Classes/Package/AbstractServiceProvider.php @@ -50,6 +50,7 @@ abstract class AbstractServiceProvider implements ServiceProviderInterface return [ 'middlewares' => [ static::class, 'configureMiddlewares' ], 'backend.routes' => [ static::class, 'configureBackendRoutes' ], + // @deprecated since v12, will be removed with v13 together with class PageTsConfigLoader. 'globalPageTsConfig' => [ static::class, 'configureGlobalPageTsConfig' ], 'backend.modules' => [ static::class, 'configureBackendModules' ], 'icons' => [ static::class, 'configureIcons' ], @@ -110,6 +111,9 @@ abstract class AbstractServiceProvider implements ServiceProviderInterface return $routes; } + /** + * @deprecated since v12, will be removed with v13 together with class PageTsConfigLoader. + */ public static function configureGlobalPageTsConfig(ContainerInterface $container, ArrayObject $tsConfigFiles, string $path = null): ArrayObject { $path = $path ?? static::getPackagePath(); diff --git a/typo3/sysext/core/Classes/ServiceProvider.php b/typo3/sysext/core/Classes/ServiceProvider.php index 86f7cdbfa70a0398507fc3a5caaab0e8ff1393a6..c722683e03c9b439eef39ad31349747b1457ddcb 100644 --- a/typo3/sysext/core/Classes/ServiceProvider.php +++ b/typo3/sysext/core/Classes/ServiceProvider.php @@ -30,6 +30,7 @@ use TYPO3\CMS\Core\DependencyInjection\ContainerBuilder; use TYPO3\CMS\Core\Imaging\IconRegistry; use TYPO3\CMS\Core\Package\AbstractServiceProvider; use TYPO3\CMS\Core\Package\PackageManager; +use TYPO3\CMS\Core\TypoScript\Tokenizer\LossyTokenizer; /** * @internal @@ -99,6 +100,7 @@ class ServiceProvider extends AbstractServiceProvider TypoScript\AST\Traverser\AstTraverser::class => [ static::class, 'getAstTraverser' ], TypoScript\AST\CommentAwareAstBuilder::class => [ static::class, 'getCommentAwareAstBuilder' ], TypoScript\Tokenizer\LosslessTokenizer::class => [ static::class, 'getLosslessTokenizer'], + // @deprecated since v12, will be removed with v13 together with class PageTsConfigLoader. 'globalPageTsConfig' => [ static::class, 'getGlobalPageTsConfig' ], 'icons' => [ static::class, 'getIcons' ], 'middlewares' => [ static::class, 'getMiddlewares' ], @@ -110,6 +112,7 @@ class ServiceProvider extends AbstractServiceProvider return [ Console\CommandRegistry::class => [ static::class, 'configureCommands' ], Imaging\IconRegistry::class => [ static::class, 'configureIconRegistry' ], + // @deprecated since v12, will be removed with v13 together with class PageTsConfigLoader. Configuration\Loader\PageTsConfigLoader::class => [ static::class, 'configurePageTsConfigLoader' ], EventDispatcherInterface::class => [ static::class, 'provideFallbackEventDispatcher' ], EventDispatcher\ListenerProvider::class => [ static::class, 'extendEventListenerProvider' ], @@ -326,11 +329,17 @@ class ServiceProvider extends AbstractServiceProvider return self::new($container, Imaging\IconRegistry::class, [$container->get('cache.assets'), $container->get(Package\Cache\PackageDependentCacheIdentifier::class)->withPrefix('BackendIcons')->toString()]); } + /** + * @deprecated since v12, will be removed with v13 together with class PageTsConfigLoader. + */ public static function getGlobalPageTsConfig(ContainerInterface $container): ArrayObject { return new ArrayObject(); } + /** + * @deprecated since v12, will be removed with v13 together with class PageTsConfigLoader. + */ public static function configurePageTsConfigLoader(ContainerInterface $container, PageTsConfigLoader $configLoader): PageTsConfigLoader { $cache = $container->get('cache.core'); @@ -480,7 +489,7 @@ class ServiceProvider extends AbstractServiceProvider public static function getTypoScriptStringFactory(ContainerInterface $container): TypoScript\TypoScriptStringFactory { - return new TypoScript\TypoScriptStringFactory($container); + return new TypoScript\TypoScriptStringFactory($container, new LossyTokenizer()); } public static function getTypoScriptService(ContainerInterface $container): TypoScript\TypoScriptService diff --git a/typo3/sysext/core/Classes/TypoScript/IncludeTree/Event/ModifyLoadedPageTsConfigEvent.php b/typo3/sysext/core/Classes/TypoScript/IncludeTree/Event/ModifyLoadedPageTsConfigEvent.php new file mode 100644 index 0000000000000000000000000000000000000000..7a47d045e2c5230accc6cc85d8cd4c0ad66b0b9a --- /dev/null +++ b/typo3/sysext/core/Classes/TypoScript/IncludeTree/Event/ModifyLoadedPageTsConfigEvent.php @@ -0,0 +1,50 @@ +<?php + +declare(strict_types=1); + +/* + * 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! + */ + +namespace TYPO3\CMS\Core\TypoScript\IncludeTree\Event; + +/** + * Extensions can modify pageTsConfig entries that can be overridden or added, based on the root line + * + * @todo: Set final in v13 when class \TYPO3\CMS\Core\Configuration\Event\ModifyLoadedPageTsConfigEvent is gone. + */ +class ModifyLoadedPageTsConfigEvent +{ + public function __construct(private array $tsConfig, private readonly array $rootLine) + { + } + + public function getTsConfig(): array + { + return $this->tsConfig; + } + + public function addTsConfig(string $tsConfig): void + { + $this->tsConfig[] = $tsConfig; + } + + public function setTsConfig(array $tsConfig): void + { + $this->tsConfig = $tsConfig; + } + + public function getRootLine(): array + { + return $this->rootLine; + } +} diff --git a/typo3/sysext/core/Classes/TypoScript/IncludeTree/IncludeNode/TsConfigInclude.php b/typo3/sysext/core/Classes/TypoScript/IncludeTree/IncludeNode/TsConfigInclude.php new file mode 100644 index 0000000000000000000000000000000000000000..3abbef82bee5239b3046890da3e208fd8338cdbd --- /dev/null +++ b/typo3/sysext/core/Classes/TypoScript/IncludeTree/IncludeNode/TsConfigInclude.php @@ -0,0 +1,27 @@ +<?php + +declare(strict_types=1); + +/* + * 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! + */ + +namespace TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode; + +/** + * An include type used by user and pages TsConfig for single TsConfig snippets. + * + * @internal: Internal tree structure. + */ +final class TsConfigInclude extends AbstractInclude +{ +} diff --git a/typo3/sysext/core/Classes/TypoScript/IncludeTree/TsConfigTreeBuilder.php b/typo3/sysext/core/Classes/TypoScript/IncludeTree/TsConfigTreeBuilder.php new file mode 100644 index 0000000000000000000000000000000000000000..0efa2734582a1b64af6ca6e51fc6aaa2caef2f26 --- /dev/null +++ b/typo3/sysext/core/Classes/TypoScript/IncludeTree/TsConfigTreeBuilder.php @@ -0,0 +1,190 @@ +<?php + +declare(strict_types=1); + +/* + * 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! + */ + +namespace TYPO3\CMS\Core\TypoScript\IncludeTree; + +use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; +use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend; +use TYPO3\CMS\Core\Configuration\Event\ModifyLoadedPageTsConfigEvent as LegacyModifyLoadedPageTsConfigEvent; +use TYPO3\CMS\Core\EventDispatcher\EventDispatcher; +use TYPO3\CMS\Core\Package\PackageManager; +use TYPO3\CMS\Core\TypoScript\IncludeTree\Event\ModifyLoadedPageTsConfigEvent; +use TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\RootInclude; +use TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\TsConfigInclude; +use TYPO3\CMS\Core\TypoScript\Tokenizer\TokenizerInterface; +use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\PathUtility; + +/** + * Build include tree for UserTsConfig and PageTsConfig. This is typically used only by + * UserTsConfigFactory and PageTsConfigFactory. + * + * @internal + */ +final class TsConfigTreeBuilder +{ + private TokenizerInterface $tokenizer; + private ?PhpFrontend $cache = null; + + public function __construct( + private readonly TreeFromLineStreamBuilder $treeFromTokenStreamBuilder, + private readonly PackageManager $packageManager, + private readonly EventDispatcher $eventDispatcher, + ) { + } + + public function getUserTsConfigTree( + BackendUserAuthentication $backendUser, + TokenizerInterface $tokenizer, + ?PhpFrontend $cache = null + ): RootInclude { + $this->tokenizer = $tokenizer; + $this->cache = $cache; + $includeTree = new RootInclude(); + if (!empty($GLOBALS['TYPO3_CONF_VARS']['BE']['defaultUserTSconfig'] ?? '')) { + $includeTree->addChild($this->getTreeFromString('userTsConfig-globals', $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultUserTSconfig'])); + } + if ($backendUser->isAdmin()) { + // @todo: Could we maybe solve this differently somehow? Maybe in ext:adminpanel in FE directly? + $includeTree->addChild($this->getTreeFromString('userTsConfig-admpanel', 'admPanel.enable.all = 1')); + } + // @todo: Get rid of this, maybe by using a hook in DataHandler? + $includeTree->addChild($this->getTreeFromString( + 'userTsConfig-sysnote', + 'TCAdefaults.sys_note.author = ' . ($backendUser->user['realName'] ?? '') . chr(10) . 'TCAdefaults.sys_note.email = ' . ($backendUser->user['email'] ?? '') + )); + foreach ($backendUser->userGroupsUID as $groupId) { + // Loop through all groups and add their 'TSconfig' fields + if (!empty($backendUser->userGroups[$groupId]['TSconfig'] ?? '')) { + $includeTree->addChild($this->getTreeFromString('userTsConfig-group-' . $groupId, $backendUser->userGroups[$groupId]['TSconfig'])); + } + } + if (!empty($backendUser->user['TSconfig'] ?? '')) { + $includeTree->addChild($this->getTreeFromString('userTsConfig-user', $backendUser->user['TSconfig'])); + } + return $includeTree; + } + + public function getPagesTsConfigTree( + array $rootLine, + TokenizerInterface $tokenizer, + ?PhpFrontend $cache = null + ): RootInclude { + $this->tokenizer = $tokenizer; + $this->cache = $cache; + + $collectedPagesTsConfigArray = []; + $gotPackagesPagesTsConfigFromCache = false; + if ($cache) { + $collectedPagesTsConfigArrayFromCache = $cache->require('pagestsconfig-packages-strings'); + if ($collectedPagesTsConfigArrayFromCache) { + $gotPackagesPagesTsConfigFromCache = true; + $collectedPagesTsConfigArray = $collectedPagesTsConfigArrayFromCache; + } + } + if (!$gotPackagesPagesTsConfigFromCache) { + foreach ($this->packageManager->getActivePackages() as $package) { + $packagePath = $package->getPackagePath(); + $tsConfigFile = null; + if (file_exists($packagePath . 'Configuration/page.tsconfig')) { + $tsConfigFile = $packagePath . 'Configuration/page.tsconfig'; + } elseif (file_exists($packagePath . 'Configuration/Page.tsconfig')) { + $tsConfigFile = $packagePath . 'Configuration/Page.tsconfig'; + } + if ($tsConfigFile) { + $typoScriptString = @file_get_contents($tsConfigFile); + if (!empty($typoScriptString)) { + $collectedPagesTsConfigArray['pagesTsConfig-package-' . $package->getPackageKey()] = $typoScriptString; + } + } + } + $cache?->set('pagestsconfig-packages-strings', 'return unserialize(\'' . addcslashes(serialize($collectedPagesTsConfigArray), '\'\\') . '\');'); + } + + if (!empty($GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPageTSconfig'] ?? '')) { + $collectedPagesTsConfigArray['pagesTsConfig-globals-defaultPageTSconfig'] = $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPageTSconfig']; + } + + foreach ($rootLine as $page) { + if (empty($page['uid'])) { + // Page 0 can happen when the rootline is given from BE context. It has not TSconfig. Skip this. + continue; + } + if (trim($page['tsconfig_includes'] ?? '')) { + $includeTsConfigFileList = GeneralUtility::trimExplode(',', $page['tsconfig_includes'], true); + foreach ($includeTsConfigFileList as $key => $includeTsConfigFile) { + if (PathUtility::isExtensionPath($includeTsConfigFile)) { + [$includeTsConfigFileExtensionKey, $includeTsConfigFilename] = explode('/', substr($includeTsConfigFile, 4), 2); + if ($includeTsConfigFileExtensionKey !== '' + && ExtensionManagementUtility::isLoaded($includeTsConfigFileExtensionKey) + && $includeTsConfigFilename !== '' + ) { + $extensionPath = ExtensionManagementUtility::extPath($includeTsConfigFileExtensionKey); + $includeTsConfigFileAndPath = PathUtility::getCanonicalPath($extensionPath . $includeTsConfigFilename); + if (str_starts_with($includeTsConfigFileAndPath, $extensionPath) && file_exists($includeTsConfigFileAndPath)) { + $typoScriptString = (string)file_get_contents($includeTsConfigFileAndPath); + if (!empty($typoScriptString)) { + $collectedPagesTsConfigArray['pagesTsConfig-page-' . $page['uid'] . '-includes-' . $key] = (string)file_get_contents($includeTsConfigFileAndPath); + } + } + } + } + } + } + if (!empty($page['TSconfig'])) { + $collectedPagesTsConfigArray['pagesTsConfig-page-' . $page['uid'] . '-tsConfig'] = $page['TSconfig']; + } + } + + // @deprecated since TYPO3 v12, remove together with event class and set IncludeTree\Event\ModifyLoadedPageTsConfigEvent final in v13. + /** @var LegacyModifyLoadedPageTsConfigEvent $event */ + $event = $this->eventDispatcher->dispatch(new LegacyModifyLoadedPageTsConfigEvent($collectedPagesTsConfigArray, $rootLine)); + $collectedPagesTsConfigArray = $event->getTsConfig(); + + /** @var ModifyLoadedPageTsConfigEvent $event */ + $event = $this->eventDispatcher->dispatch(new ModifyLoadedPageTsConfigEvent($collectedPagesTsConfigArray, $rootLine)); + $collectedPagesTsConfigArray = $event->getTsConfig(); + + $includeTree = new RootInclude(); + foreach ($collectedPagesTsConfigArray as $key => $typoScriptString) { + $includeTree->addChild($this->getTreeFromString((string)$key, $typoScriptString)); + } + return $includeTree; + } + + private function getTreeFromString( + string $name, + string $typoScriptString, + ): TsConfigInclude { + $lowercaseName = mb_strtolower($name); + $identifier = $lowercaseName . '-' . sha1($typoScriptString); + if ($this->cache) { + $includeNode = $this->cache->require($identifier); + if ($includeNode instanceof TsConfigInclude) { + return $includeNode; + } + } + $includeNode = new TsConfigInclude(); + $includeNode->setName($name); + $includeNode->setIdentifier($lowercaseName); + $includeNode->setLineStream($this->tokenizer->tokenize($typoScriptString)); + $this->treeFromTokenStreamBuilder->buildTree($includeNode, 'other', $this->tokenizer); + $this->cache?->set($identifier, 'return unserialize(\'' . addcslashes(serialize($includeNode), '\'\\') . '\');'); + return $includeNode; + } +} diff --git a/typo3/sysext/core/Classes/TypoScript/PageTsConfig.php b/typo3/sysext/core/Classes/TypoScript/PageTsConfig.php new file mode 100644 index 0000000000000000000000000000000000000000..d33886bb9939fdc5a256ced99dff09350a84cfcd --- /dev/null +++ b/typo3/sysext/core/Classes/TypoScript/PageTsConfig.php @@ -0,0 +1,46 @@ +<?php + +declare(strict_types=1); + +/* + * 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! + */ + +namespace TYPO3\CMS\Core\TypoScript; + +use TYPO3\CMS\Core\TypoScript\AST\Node\RootNode; + +/** + * A data object that carries the final PageTsConfig. This is created by PageTsConfigFactory. + * + * @internal Internal for now until API stabilized. Use BackendUtility::getPagesTSconfig(). + */ +final class PageTsConfig +{ + private readonly array $pageTsConfigArray; + + public function __construct( + private readonly RootNode $pageTsConfigTree + ) { + $this->pageTsConfigArray = $pageTsConfigTree->toArray(); + } + + public function getPageTsConfigTree(): RootNode + { + return $this->pageTsConfigTree; + } + + public function getPageTsConfigArray(): array + { + return $this->pageTsConfigArray; + } +} diff --git a/typo3/sysext/core/Classes/TypoScript/PageTsConfigFactory.php b/typo3/sysext/core/Classes/TypoScript/PageTsConfigFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..a5089ecf025109a049e44f3a70f486b4603e320e --- /dev/null +++ b/typo3/sysext/core/Classes/TypoScript/PageTsConfigFactory.php @@ -0,0 +1,132 @@ +<?php + +declare(strict_types=1); + +/* + * 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! + */ + +namespace TYPO3\CMS\Core\TypoScript; + +use Psr\Container\ContainerInterface; +use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend; +use TYPO3\CMS\Core\Configuration\TypoScript\ConditionMatching\ConditionMatcherInterface; +use TYPO3\CMS\Core\Site\Entity\Site; +use TYPO3\CMS\Core\Site\Entity\SiteInterface; +use TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\RootInclude; +use TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\SiteInclude; +use TYPO3\CMS\Core\TypoScript\IncludeTree\IncludeNode\TsConfigInclude; +use TYPO3\CMS\Core\TypoScript\IncludeTree\Traverser\ConditionVerdictAwareIncludeTreeTraverser; +use TYPO3\CMS\Core\TypoScript\IncludeTree\TsConfigTreeBuilder; +use TYPO3\CMS\Core\TypoScript\IncludeTree\Visitor\IncludeTreeAstBuilderVisitor; +use TYPO3\CMS\Core\TypoScript\IncludeTree\Visitor\IncludeTreeConditionMatcherVisitor; +use TYPO3\CMS\Core\TypoScript\IncludeTree\Visitor\IncludeTreeSetupConditionConstantSubstitutionVisitor; +use TYPO3\CMS\Core\TypoScript\Tokenizer\TokenizerInterface; + +/** + * Calculate PageTsConfig. This does the heavy lifting additionally supported by + * TsConfigTreeBuilder: Load basic pageTsConfig tree, overload with userTsConfig, parse + * site settings ("constants"), then build the PageTsConfig AST and return PageTsConfig DTO. + * + * @internal Internal for now until API stabilized. Use BackendUtility::getPagesTSconfig(). + */ +final class PageTsConfigFactory +{ + public function __construct( + private readonly ContainerInterface $container, + private readonly TokenizerInterface $tokenizer, + private readonly TsConfigTreeBuilder $tsConfigTreeBuilder, + private readonly PhpFrontend $cache, + ) { + } + + public function create( + array $rootLine, + SiteInterface $site, + ConditionMatcherInterface $conditionMatcher, + ?UserTsConfig $userTsConfig = null + ): PageTsConfig { + $pagesTsConfigTree = $this->tsConfigTreeBuilder->getPagesTsConfigTree($rootLine, $this->tokenizer, $this->cache); + + // Overloading with userTsConfig if hand over + if ($userTsConfig instanceof UserTsConfig) { + $userTsConfigAst = $userTsConfig->getUserTsConfigTree(); + $userTsConfigPageOverrides = ''; + $userTsConfigFlat = $userTsConfigAst->flatten(); + foreach ($userTsConfigFlat as $userTsConfigIdentifier => $userTsConfigValue) { + if (str_starts_with($userTsConfigIdentifier, 'page.')) { + $userTsConfigPageOverrides .= substr($userTsConfigIdentifier, 5) . ' = ' . $userTsConfigValue . chr(10); + } + } + if (!empty($userTsConfigPageOverrides)) { + $includeNode = new TsConfigInclude(); + $includeNode->setName('pageTsConfig-overrides-by-userTsConfig'); + $includeNode->setIdentifier('pagetsconfig-overrides-by-usertsconfig'); + $includeNode->setLineStream($this->tokenizer->tokenize($userTsConfigPageOverrides)); + $pagesTsConfigTree->addChild($includeNode); + } + } + + // Prepare site constants to be substituted + $includeTreeTraverserConditionVerdictAware = new ConditionVerdictAwareIncludeTreeTraverser(); + $siteSettingsFlat = []; + if ($site instanceof Site) { + $siteSettings = $site->getSettings(); + if (!$siteSettings->isEmpty()) { + $siteSettingsCacheIdentifier = 'site-settings-flat-' . sha1(json_encode($siteSettings, JSON_THROW_ON_ERROR)); + $gotSiteSettingsFromCache = false; + $siteSettingsNode = $this->cache->require($siteSettingsCacheIdentifier); + if ($siteSettingsNode) { + $gotSiteSettingsFromCache = true; + } + if (!$gotSiteSettingsFromCache) { + $siteConstants = ''; + $siteSettings = $siteSettings->getAllFlat(); + foreach ($siteSettings as $nodeIdentifier => $value) { + $siteConstants .= $nodeIdentifier . ' = ' . $value . LF; + } + $siteSettingsNode = new SiteInclude(); + $siteSettingsNode->setIdentifier($siteSettingsCacheIdentifier); + $siteSettingsNode->setName('Site constants settings of site ' . $site->getIdentifier()); + $siteSettingsNode->setLineStream($this->tokenizer->tokenize($siteConstants)); + $siteSettingsTreeRoot = new RootInclude(); + $siteSettingsTreeRoot->addChild($siteSettingsNode); + /** @var IncludeTreeAstBuilderVisitor $astBuilderVisitor */ + $astBuilderVisitor = $this->container->get(IncludeTreeAstBuilderVisitor::class); + $includeTreeTraverserConditionVerdictAware->resetVisitors(); + $includeTreeTraverserConditionVerdictAware->addVisitor($astBuilderVisitor); + $includeTreeTraverserConditionVerdictAware->traverse($siteSettingsTreeRoot); + $siteSettingsFlat = $astBuilderVisitor->getAst()->flatten(); + $this->cache->set($siteSettingsCacheIdentifier, 'return unserialize(\'' . addcslashes(serialize($siteSettingsFlat), '\'\\') . '\');'); + } + } + } + + // Create AST with constants from site and conditions + $includeTreeTraverserConditionVerdictAware->resetVisitors(); + if (!empty($siteSettingsFlat)) { + $setupConditionConstantSubstitutionVisitor = new IncludeTreeSetupConditionConstantSubstitutionVisitor(); + $setupConditionConstantSubstitutionVisitor->setFlattenedConstants($siteSettingsFlat); + $includeTreeTraverserConditionVerdictAware->addVisitor($setupConditionConstantSubstitutionVisitor); + } + $conditionMatcherVisitor = new IncludeTreeConditionMatcherVisitor(); + $conditionMatcherVisitor->setConditionMatcher($conditionMatcher); + $includeTreeTraverserConditionVerdictAware->addVisitor($conditionMatcherVisitor); + /** @var IncludeTreeAstBuilderVisitor $astBuilderVisitor */ + $astBuilderVisitor = $this->container->get(IncludeTreeAstBuilderVisitor::class); + $astBuilderVisitor->setFlatConstants($siteSettingsFlat); + $includeTreeTraverserConditionVerdictAware->addVisitor($astBuilderVisitor); + $includeTreeTraverserConditionVerdictAware->traverse($pagesTsConfigTree); + + return new PageTsConfig($astBuilderVisitor->getAst()); + } +} diff --git a/typo3/sysext/core/Classes/TypoScript/Parser/TypoScriptParser.php b/typo3/sysext/core/Classes/TypoScript/Parser/TypoScriptParser.php index 46ff1c6227e89d92cbfbe3c18c6691e33fa746ce..c1d913f7c78e255898155e70bb6f10243f981e63 100644 --- a/typo3/sysext/core/Classes/TypoScript/Parser/TypoScriptParser.php +++ b/typo3/sysext/core/Classes/TypoScript/Parser/TypoScriptParser.php @@ -32,8 +32,7 @@ use TYPO3\CMS\Frontend\Configuration\TypoScript\ConditionMatching\ConditionMatch /** * The TypoScript parser. * - * @deprecated This class should not be used anymore, last core usages will be removed during v12. - * Using methods or properties of this class will start logging deprecation messages. + * @deprecated This class should not be used anymore. Switch to the new parser construct instead. */ class TypoScriptParser { @@ -150,6 +149,11 @@ class TypoScriptParser */ public $lineNumberOffset = 0; + public function __construct() + { + trigger_error('Class ' . __CLASS__ . ' will be removed with TYPO3 v13.0. Use the new parser structure instead.', E_USER_DEPRECATED); + } + /** * Start parsing the input TypoScript text piece. The result is stored in $this->setup * diff --git a/typo3/sysext/core/Classes/TypoScript/TypoScriptStringFactory.php b/typo3/sysext/core/Classes/TypoScript/TypoScriptStringFactory.php index 2d931643741c4e36ca3b93dd7aee7b113bb07867..230a0a96822bb8d1743b973569826b37468c8c5d 100644 --- a/typo3/sysext/core/Classes/TypoScript/TypoScriptStringFactory.php +++ b/typo3/sysext/core/Classes/TypoScript/TypoScriptStringFactory.php @@ -18,6 +18,7 @@ declare(strict_types=1); namespace TYPO3\CMS\Core\TypoScript; use Psr\Container\ContainerInterface; +use TYPO3\CMS\Core\Cache\CacheManager; use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend; use TYPO3\CMS\Core\Configuration\TypoScript\ConditionMatching\ConditionMatcherInterface; use TYPO3\CMS\Core\TypoScript\AST\AstBuilderInterface; @@ -38,6 +39,7 @@ final class TypoScriptStringFactory { public function __construct( private readonly ContainerInterface $container, + private readonly TokenizerInterface $tokenizer, ) { } @@ -46,11 +48,15 @@ final class TypoScriptStringFactory * * @param non-empty-string $name A name used as cache identifier, [a-z,A-Z,-] only */ - public function parseFromStringWithIncludesAndConditions(string $name, string $typoScript, TokenizerInterface $tokenizer, ConditionMatcherInterface $conditionMatcher, ?PhpFrontend $cache = null): RootNode + public function parseFromStringWithIncludesAndConditions(string $name, string $typoScript, ConditionMatcherInterface $conditionMatcher): RootNode { + /** @var CacheManager $cacheManager */ + $cacheManager = $this->container->get(CacheManager::class); + /** @var PhpFrontend $cache */ + $cache = $cacheManager->getCache('typoscript'); /** @var StringTreeBuilder $stringTreeBuilder */ $stringTreeBuilder = $this->container->get(StringTreeBuilder::class); - $includeTree = $stringTreeBuilder->getTreeFromString($name, $typoScript, $tokenizer, $cache); + $includeTree = $stringTreeBuilder->getTreeFromString($name, $typoScript, $this->tokenizer, $cache); $conditionMatcherVisitor = new IncludeTreeConditionMatcherVisitor(); $conditionMatcherVisitor->setConditionMatcher($conditionMatcher); $includeTreeTraverserConditionVerdictAware = new ConditionVerdictAwareIncludeTreeTraverser(); @@ -64,13 +70,13 @@ final class TypoScriptStringFactory /** * Parse a single string *not* supporting imports, conditions and caching. - * Used in install tool context only. + * Detail method used in install tool and in a couple of other special cases. * * @internal */ - public function parseFromString(string $typoScript, TokenizerInterface $tokenizer, AstBuilderInterface $astBuilder): RootNode + public function parseFromString(string $typoScript, AstBuilderInterface $astBuilder): RootNode { - $lineStream = $tokenizer->tokenize($typoScript); + $lineStream = $this->tokenizer->tokenize($typoScript); return $astBuilder->build($lineStream, new RootNode()); } } diff --git a/typo3/sysext/core/Classes/TypoScript/UserTsConfig.php b/typo3/sysext/core/Classes/TypoScript/UserTsConfig.php new file mode 100644 index 0000000000000000000000000000000000000000..3617a20642f75ce5c2603774bb8c58e8700845a2 --- /dev/null +++ b/typo3/sysext/core/Classes/TypoScript/UserTsConfig.php @@ -0,0 +1,46 @@ +<?php + +declare(strict_types=1); + +/* + * 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! + */ + +namespace TYPO3\CMS\Core\TypoScript; + +use TYPO3\CMS\Core\TypoScript\AST\Node\RootNode; + +/** + * A data object that carries the final UserTsConfig. This is created by UserTsConfigFactory. + * + * @internal Internal for now until API stabilized. Use backendUser->getTSConfig(). + */ +final class UserTsConfig +{ + private readonly array $userTsConfigArray; + + public function __construct( + private readonly RootNode $userTsConfigTree + ) { + $this->userTsConfigArray = $userTsConfigTree->toArray(); + } + + public function getUserTsConfigTree(): RootNode + { + return $this->userTsConfigTree; + } + + public function getUserTsConfigArray(): array + { + return $this->userTsConfigArray; + } +} diff --git a/typo3/sysext/core/Classes/TypoScript/UserTsConfigFactory.php b/typo3/sysext/core/Classes/TypoScript/UserTsConfigFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..d97d75f3c2ab41685ab05ea798642f5e1d0dc18f --- /dev/null +++ b/typo3/sysext/core/Classes/TypoScript/UserTsConfigFactory.php @@ -0,0 +1,63 @@ +<?php + +declare(strict_types=1); + +/* + * 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! + */ + +namespace TYPO3\CMS\Core\TypoScript; + +use Psr\Container\ContainerInterface; +use TYPO3\CMS\Backend\Configuration\TypoScript\ConditionMatching\ConditionMatcher as BackendConditionMatcher; +use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; +use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend; +use TYPO3\CMS\Core\Context\Context; +use TYPO3\CMS\Core\TypoScript\IncludeTree\Traverser\ConditionVerdictAwareIncludeTreeTraverser; +use TYPO3\CMS\Core\TypoScript\IncludeTree\TsConfigTreeBuilder; +use TYPO3\CMS\Core\TypoScript\IncludeTree\Visitor\IncludeTreeAstBuilderVisitor; +use TYPO3\CMS\Core\TypoScript\IncludeTree\Visitor\IncludeTreeConditionMatcherVisitor; +use TYPO3\CMS\Core\TypoScript\Tokenizer\TokenizerInterface; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +/** + * Calculate UserTsConfig. This does the heavy lifting additionally supported by + * TsConfigTreeBuilder: Load basic userTsConfig tree, then build the UserTsConfig AST + * and return UserTsConfig DTO. + * + * @internal Internal for now until API stabilized. Use backendUser->getTSConfig(). + */ +final class UserTsConfigFactory +{ + public function __construct( + private readonly ContainerInterface $container, + private readonly TokenizerInterface $tokenizer, + private readonly TsConfigTreeBuilder $tsConfigTreeBuilder, + private readonly PhpFrontend $cache, + ) { + } + + public function create(BackendUserAuthentication $backendUser): UserTsConfig + { + $includeTreeTraverserConditionVerdictAware = new ConditionVerdictAwareIncludeTreeTraverser(); + $userTsConfigTree = $this->tsConfigTreeBuilder->getUserTsConfigTree($backendUser, $this->tokenizer, $this->cache); + $conditionMatcherVisitor = new IncludeTreeConditionMatcherVisitor(); + $backendConditionMatcher = GeneralUtility::makeInstance(BackendConditionMatcher::class, GeneralUtility::makeInstance(Context::class)); + $conditionMatcherVisitor->setConditionMatcher($backendConditionMatcher); + $includeTreeTraverserConditionVerdictAware->addVisitor($conditionMatcherVisitor); + /** @var IncludeTreeAstBuilderVisitor $astBuilderVisitor */ + $astBuilderVisitor = $this->container->get(IncludeTreeAstBuilderVisitor::class); + $includeTreeTraverserConditionVerdictAware->addVisitor($astBuilderVisitor); + $includeTreeTraverserConditionVerdictAware->traverse($userTsConfigTree); + return new UserTsConfig($astBuilderVisitor->getAst()); + } +} diff --git a/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php b/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php index 2bbe898daf3c2fa98f8772cb6169bb61c5d0b18e..2015d683e892999b82ef27fbe33c792375b83b46 100644 --- a/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php +++ b/typo3/sysext/core/Classes/Utility/ExtensionManagementUtility.php @@ -732,9 +732,7 @@ class ExtensionManagementUtility */ public static function addPageTSConfig(string $content): void { - $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPageTSconfig'] .= ' -[GLOBAL] -' . $content; + $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPageTSconfig'] .= chr(10) . $content; } /** @@ -746,9 +744,7 @@ class ExtensionManagementUtility */ public static function addUserTSConfig(string $content): void { - $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultUserTSconfig'] .= ' -[GLOBAL] -' . $content; + $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultUserTSconfig'] .= chr(10) . $content; } /** diff --git a/typo3/sysext/core/Configuration/Services.yaml b/typo3/sysext/core/Configuration/Services.yaml index e945bef45fe8f06f8f03ca56a3c19b4178b97c35..9fc1cd587901dfc13d95b84ba4f64973f97f9de0 100644 --- a/typo3/sysext/core/Configuration/Services.yaml +++ b/typo3/sysext/core/Configuration/Services.yaml @@ -57,14 +57,17 @@ services: TYPO3\CMS\Core\Http\MiddlewareDispatcher: autoconfigure: false + # @deprecated since v12, will be removed with v13 together with class PageTsConfigLoader. TYPO3\CMS\Core\Configuration\Loader\PageTsConfigLoader: public: true + # @deprecated since v12, will be removed with v13 together with class PageTsConfigParser. TYPO3\CMS\Core\Configuration\Parser\PageTsConfigParser: public: true arguments: $cache: '@cache.hash' + # @deprecated since v12, will be removed with v13 together with class PageTsConfig. TYPO3\CMS\Core\Configuration\PageTsConfig: public: true arguments: @@ -324,6 +327,9 @@ services: TYPO3\CMS\Core\TypoScript\IncludeTree\SysTemplateTreeBuilder: public: true + TYPO3\CMS\Core\TypoScript\IncludeTree\TsConfigTreeBuilder: + public: true + TYPO3\CMS\Core\TypoScript\IncludeTree\Visitor\IncludeTreeAstBuilderVisitor: public: true # Ast builder visitor creates state and should not be re-used @@ -337,6 +343,16 @@ services: TYPO3\CMS\Core\TypoScript\Tokenizer\TokenizerInterface: alias: TYPO3\CMS\Core\TypoScript\Tokenizer\LossyTokenizer + TYPO3\CMS\Core\TypoScript\PageTsConfigFactory: + public: true + arguments: + $cache: '@cache.typoscript' + + TYPO3\CMS\Core\TypoScript\UserTsConfigFactory: + public: true + arguments: + $cache: '@cache.typoscript' + # Core caches, cache.core and cache.assets are injected as early # entries in TYPO3\CMS\Core\Core\Bootstrap and therefore omitted here cache.hash: diff --git a/typo3/sysext/core/Documentation/Changelog/12.2/Deprecation-99120-DeprecateOldTypoScriptParser.rst b/typo3/sysext/core/Documentation/Changelog/12.2/Deprecation-99120-DeprecateOldTypoScriptParser.rst new file mode 100644 index 0000000000000000000000000000000000000000..6514700a602c9d9873af456ea0f30bf0a1633943 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/12.2/Deprecation-99120-DeprecateOldTypoScriptParser.rst @@ -0,0 +1,109 @@ +.. include:: /Includes.rst.txt + +.. _deprecation-99120-1670428555: + +==================================================== +Deprecation: #99120 - Deprecate old TypoScriptParser +==================================================== + +See :issue:`99120` + +Description +=========== + +To phase out usages of the old TypoScript parser by switching to the +:ref:`new parser approach <breaking-97816-1664800747>`, a couple of classes +and methods have been marked as deprecated in TYPO3 v12 that will be removed with v13: + +* Class :php:`\TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser` +* Class :php:`\TYPO3\CMS\Core\Configuration\Loader\PageTsConfigLoader` +* Class :php:`\TYPO3\CMS\Core\Configuration\PageTsConfig` +* Class :php:`\TYPO3\CMS\Core\Configuration\Parser\PageTsConfigParser` +* Event :php:`\TYPO3\CMS\Core\Configuration\Event\ModifyLoadedPageTsConfigEvent` +* Method :php:`\TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->getPagesTSconfig()` + +The existing main API to retrieve PageTsConfig using :php:`\TYPO3\CMS\Backend\Utility\BackendUtility::getPagesTSconfig()` +and UserTsConfig using :php:`$backendUser->getTSConfig()` is kept. + + +Impact +====== + +Using one of the above classes will raise a deprecation level log entry and +will stop working with TYPO3 v13. + + +Affected installations +====================== + +Instances with extensions that use one of the above classes are affected. The +extension scanner will find usages with a mixture of weak and strong matches +depending on the usage. + +Most deprecations are rather "internal" since extensions most likely use the +existing outer API already. Some may be affected when using the +:php:`\TYPO3\CMS\Core\Configuration\Event\ModifyLoadedPageTsConfigEvent` event +or the frontend related method :php:`TypoScriptFrontendController->getPagesTSconfig()`, +though. + + +Migration +========= + +:php:`\TYPO3\CMS\Core\Configuration\Event\ModifyLoadedPageTsConfigEvent` +------------------------------------------------------------------------ + +This event is consumed by some extensions to modify calculated PageTsConfig strings +before parsing. The event moved its namespace from +:php:`\TYPO3\CMS\Core\Configuration\Event\ModifyLoadedPageTsConfigEvent` to +:php:`\TYPO3\CMS\Core\TypoScript\IncludeTree\Event\ModifyLoadedPageTsConfigEvent` in +TYPO3 v12, but is kept as-is apart from that. The TYPO3 v12 core triggers *both* the old +and the new event, and TYPO3 v13 will stop calling the old event. + +Extension that want to stay compatible with both TYPO3 v11 and v12 should continue to +implement listen for the old event only. This will *not* raise a deprecation level log +entry in v12, but it will stop working with TYPO3 v13. +Extensions with compatibility for TYPO3 12 and above should switch to the new event. + +:php:`\TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->getPagesTSconfig()` +-------------------------------------------------------------------------------------- + +The TYPO3 Frontend should usually not need to retrieve Backend related PageTsConfig. +Extensions using the method should avoid relying on bringing Backend related configuration +into Frontend scope. However, the TYPO3 core comes with one place that does this: Class +:php:`TYPO3\CMS\Frontend\Typolink\DatabaseRecordLinkBuilder` uses PageTsConfig related +information in Frontend scope. Extensions with similar use cases could have a similar +implementation as done with :php:`DatabaseRecordLinkBuilder->getPageTsConfig()`. Note +any implementation of this will have to rely on :php:`@internal` usages of the new +TypoScript parser approach, and using this low level API may thus break without +further notice. Extensions are encouraged to cover usages with functional tests to find +issues quickly in case the TYPO3 core still changes used classes. + +:php:`\TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser` +--------------------------------------------------------- + +In general, extensions probably don't need to use the old :php:`TypoScriptParser` +often: Frontend TypoScript is :ref:`available as request attribute <deprecation-99020-1667911024>`, +pageTsConfig should be retrieved using :php:`BackendUtility::getPagesTSconfig()` and +userTsConfig should be retrieved using :php:`$backendUser->getTSConfig()`. + +In case extensions want to parse any other strings that follow a TypoScript-a-like syntax, +they can use :php:`\TYPO3\CMS\Core\TypoScript\TypoScriptStringFactory`, or could set up +their own factory using the new parser classes for more complex scenarios. Note the new parser +approach is still marked :php:`@internal`, using this low level API may thus break without +further notice. Extensions are encouraged to cover usages with functional tests to find +issues quickly in case the TYPO3 core still changes used classes. + +:php:`\TYPO3\CMS\Core\Configuration\PageTsConfig` +------------------------------------------------- + +There is little need to use :php:`\TYPO3\CMS\Core\Configuration\PageTsConfig` and their helper +classes :php:`\TYPO3\CMS\Core\Configuration\Loader\PageTsConfigLoader` and +:php:`\TYPO3\CMS\Core\Configuration\Parser\PageTsConfigParser` directly: The main API +in Backend context is :php:`\TYPO3\CMS\Backend\Utility\BackendUtility::getPagesTSconfig()`. + +See the hint on :php:`\TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->getPagesTSconfig()` +above for notes on how to migrate usages in Frontend context. + + +.. index:: PHP-API, TSConfig, TypoScript, FullyScanned, ext:core diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Classes/EventListener/LegacyModifyLoadedPageTsConfig.php b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Classes/EventListener/LegacyModifyLoadedPageTsConfig.php new file mode 100644 index 0000000000000000000000000000000000000000..8ae065ec3def20da186f37ddceccdf8a81eea03d --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Classes/EventListener/LegacyModifyLoadedPageTsConfig.php @@ -0,0 +1,31 @@ +<?php + +declare(strict_types=1); + +/* + * 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! + */ + +namespace TYPO3Tests\TestTyposcriptPagetsconfigfactory\EventListener; + +use TYPO3\CMS\Core\Configuration\Event\ModifyLoadedPageTsConfigEvent; + +/** + * @deprecated since v12, remove along with 'legacy' event class in v13. + */ +final class LegacyModifyLoadedPageTsConfig +{ + public function __invoke(ModifyLoadedPageTsConfigEvent $event): void + { + $event->addTsConfig('loadedFromLegacyEvent = loadedFromLegacyEvent'); + } +} diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Classes/EventListener/ModifyLoadedPageTsConfig.php b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Classes/EventListener/ModifyLoadedPageTsConfig.php new file mode 100644 index 0000000000000000000000000000000000000000..6bf69048f2667d6579b58a9a434e163054689a1a --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Classes/EventListener/ModifyLoadedPageTsConfig.php @@ -0,0 +1,28 @@ +<?php + +declare(strict_types=1); + +/* + * 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! + */ + +namespace TYPO3Tests\TestTyposcriptPagetsconfigfactory\EventListener; + +use TYPO3\CMS\Core\TypoScript\IncludeTree\Event\ModifyLoadedPageTsConfigEvent; + +final class ModifyLoadedPageTsConfig +{ + public function __invoke(ModifyLoadedPageTsConfigEvent $event): void + { + $event->addTsConfig('loadedFromEvent = loadedFromEvent'); + } +} diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Configuration/Services.yaml b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Configuration/Services.yaml new file mode 100644 index 0000000000000000000000000000000000000000..487c40bc412784873602ff9e0d53b766013e5e8d --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Configuration/Services.yaml @@ -0,0 +1,16 @@ +services: + _defaults: + autowire: true + autoconfigure: true + public: false + + TYPO3Tests\TestTyposcriptPagetsconfigfactory\EventListener\ModifyLoadedPageTsConfig: + tags: + - name: event.listener + identifier: 'typo3tests-test-typoscript-pagetsconfigfactory-event/modify-loaded-page-tsconfig' + + # @deprecated since v12, remove along with 'legacy' event class in v13. + TYPO3Tests\TestTyposcriptPagetsconfigfactory\EventListener\LegacyModifyLoadedPageTsConfig: + tags: + - name: event.listener + identifier: 'typo3tests-test-typoscript-pagetsconfigfactory-event/legacy-modify-loaded-page-tsconfig' diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Configuration/TsConfig/tsconfig-includes.tsconfig b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Configuration/TsConfig/tsconfig-includes.tsconfig new file mode 100644 index 0000000000000000000000000000000000000000..c9aeea6c6b17e74c6f32e14e4f89b11b545d6f81 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Configuration/TsConfig/tsconfig-includes.tsconfig @@ -0,0 +1 @@ +loadedFromTsconfigIncludes = loadedFromTsconfigIncludes diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Configuration/page.tsconfig b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Configuration/page.tsconfig new file mode 100644 index 0000000000000000000000000000000000000000..697c871653184f6a60c0f3120e36b97034a4909a --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/Configuration/page.tsconfig @@ -0,0 +1 @@ +loadedFromTestExtensionConfigurationPageTsConfig = loadedFromTestExtensionConfigurationPageTsConfig diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/ext_emconf.php b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/ext_emconf.php new file mode 100644 index 0000000000000000000000000000000000000000..b37805b84e4d70e2ef09cb15016644841af071c6 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory/ext_emconf.php @@ -0,0 +1,21 @@ +<?php + +declare(strict_types=1); + +$EM_CONF[$_EXTKEY] = [ + 'title' => 'TypoScript PageTsConfigFactory extension test', + 'description' => 'TypoScript PageTsConfigFactory extension test', + 'category' => 'example', + 'version' => '12.2.0', + 'state' => 'beta', + 'author' => 'Christian Kuhn', + 'author_email' => 'lolli@schwarzbu.ch', + 'author_company' => '', + 'constraints' => [ + 'depends' => [ + 'typo3' => '12.2.0', + ], + 'conflicts' => [], + 'suggests' => [], + ], +]; diff --git a/typo3/sysext/core/Tests/Functional/TypoScript/Fixtures/pageTsConfigTestFixture.csv b/typo3/sysext/core/Tests/Functional/TypoScript/Fixtures/pageTsConfigTestFixture.csv new file mode 100644 index 0000000000000000000000000000000000000000..925850b570b80a8255c5f03a388c4a1ed42786a9 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/TypoScript/Fixtures/pageTsConfigTestFixture.csv @@ -0,0 +1,3 @@ +"be_users" +,"uid","pid","username","admin","usergroup","TSconfig" +,1,0,"user1",0,,"page.valueOverriddenByUserTsConfig = overridden" diff --git a/typo3/sysext/core/Tests/Functional/TypoScript/Fixtures/userTsConfigTestFixture.csv b/typo3/sysext/core/Tests/Functional/TypoScript/Fixtures/userTsConfigTestFixture.csv new file mode 100644 index 0000000000000000000000000000000000000000..65c652159cca97ab9e600d8d97a052ed37ffc00c --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/TypoScript/Fixtures/userTsConfigTestFixture.csv @@ -0,0 +1,15 @@ +"be_users" +,"uid","pid","username","admin","usergroup","TSconfig" +,1,0,"user1",0,, +,2,0,"user2",0,,"loadedFromUser = loadedFromUser" +,3,0,"user3",0,"1", +,4,0,"user4",0,"1,2", +,5,0,"user5",1,,"isHttps = off +[request.getNormalizedParams().isHttps()] + isHttps = on +[end] +" +"be_groups" +,"uid","pid","title","TSconfig" +,1,0,"group1","loadedFromUserGroup = loadedFromUserGroup" +,2,0,"group2","loadedFromUserGroup = loadedFromUserGroupOverride" diff --git a/typo3/sysext/core/Tests/Functional/TypoScript/PageTsConfigFactoryTest.php b/typo3/sysext/core/Tests/Functional/TypoScript/PageTsConfigFactoryTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b81ecdcd4dd8c31bcc60cf5e1bf973bb69b18383 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/TypoScript/PageTsConfigFactoryTest.php @@ -0,0 +1,224 @@ +<?php + +declare(strict_types=1); + +/* + * 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! + */ + +namespace TYPO3\CMS\Core\Tests\Functional\TypoScript; + +use TYPO3\CMS\Backend\Configuration\TypoScript\ConditionMatching\ConditionMatcher; +use TYPO3\CMS\Core\Http\NormalizedParams; +use TYPO3\CMS\Core\Http\ServerRequest; +use TYPO3\CMS\Core\Site\Entity\NullSite; +use TYPO3\CMS\Core\Site\Entity\Site; +use TYPO3\CMS\Core\Site\Entity\SiteSettings; +use TYPO3\CMS\Core\TypoScript\PageTsConfigFactory; +use TYPO3\CMS\Core\TypoScript\UserTsConfigFactory; +use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; + +/** + * Tests PageTsConfigFactory and indirectly IncludeTree/TsConfigTreeBuilder + */ +class PageTsConfigFactoryTest extends FunctionalTestCase +{ + protected array $testExtensionsToLoad = [ + 'typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_pagetsconfigfactory', + ]; + + /** + * @test + */ + public function pageTsConfigLoadsDefaultsFromGlobals(): void + { + $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPageTSconfig'] = 'loadedFromGlobals = loadedFromGlobals'; + /** @var PageTsConfigFactory $subject */ + $subject = $this->get(PageTsConfigFactory::class); + $pageTsConfig = $subject->create([], new NullSite(), new ConditionMatcher()); + self::assertSame('loadedFromGlobals', $pageTsConfig->getPageTsConfigArray()['loadedFromGlobals']); + } + + /** + * @test + */ + public function pageTsConfigLoadsFromTestExtensionConfigurationFile(): void + { + /** @var PageTsConfigFactory $subject */ + $subject = $this->get(PageTsConfigFactory::class); + $pageTsConfig = $subject->create([], new NullSite(), new ConditionMatcher()); + self::assertSame('loadedFromTestExtensionConfigurationPageTsConfig', $pageTsConfig->getPageTsConfigArray()['loadedFromTestExtensionConfigurationPageTsConfig']); + } + + /** + * @test + */ + public function pageTsConfigLoadsFromPagesTestExtensionConfigurationFile(): void + { + /** @var PageTsConfigFactory $subject */ + $subject = $this->get(PageTsConfigFactory::class); + $pageTsConfig = $subject->create([], new NullSite(), new ConditionMatcher()); + self::assertSame('loadedFromTestExtensionConfigurationPageTsConfig', $pageTsConfig->getPageTsConfigArray()['loadedFromTestExtensionConfigurationPageTsConfig']); + } + + /** + * @test + */ + public function pageTsConfigLoadsFromPageRecordTsconfigField(): void + { + $rootLine = [ + [ + 'uid' => 1, + 'TSconfig' => 'loadedFromTsConfigField = loadedFromTsConfigField', + ], + ]; + /** @var PageTsConfigFactory $subject */ + $subject = $this->get(PageTsConfigFactory::class); + $pageTsConfig = $subject->create($rootLine, new NullSite(), new ConditionMatcher()); + self::assertSame('loadedFromTsConfigField', $pageTsConfig->getPageTsConfigArray()['loadedFromTsConfigField']); + } + + /** + * @test + */ + public function pageTsConfigLoadsFromPageRecordTsconfigFieldOverridesByLowerLevel(): void + { + $rootLine = [ + [ + 'uid' => 1, + 'TSconfig' => 'loadedFromTsConfigField1 = loadedFromTsConfigField1' + . chr(10) . 'loadedFromTsConfigField2 = loadedFromTsConfigField2', + ], + [ + 'uid' => 2, + 'TSconfig' => 'loadedFromTsConfigField1 = loadedFromTsConfigField1' + . chr(10) . 'loadedFromTsConfigField2 = loadedFromTsConfigField2Override', + ], + ]; + /** @var PageTsConfigFactory $subject */ + $subject = $this->get(PageTsConfigFactory::class); + $pageTsConfig = $subject->create($rootLine, new NullSite(), new ConditionMatcher()); + self::assertSame('loadedFromTsConfigField1', $pageTsConfig->getPageTsConfigArray()['loadedFromTsConfigField1']); + self::assertSame('loadedFromTsConfigField2Override', $pageTsConfig->getPageTsConfigArray()['loadedFromTsConfigField2']); + } + + /** + * @test + */ + public function pageTsConfigSubstitutesSettingsFromSite(): void + { + $rootLine = [ + [ + 'uid' => 1, + 'TSconfig' => 'siteSetting = {$aSiteSetting}', + ], + ]; + /** @var PageTsConfigFactory $subject */ + $subject = $this->get(PageTsConfigFactory::class); + $siteSettings = new SiteSettings(['aSiteSetting' => 'aSiteSettingValue']); + $site = new Site('siteIdentifier', 1, [], $siteSettings); + $pageTsConfig = $subject->create($rootLine, $site, new ConditionMatcher()); + self::assertSame('aSiteSettingValue', $pageTsConfig->getPageTsConfigArray()['siteSetting']); + } + + /** + * @test + */ + public function pageTsConfigMatchesRequestHttpsCondition(): void + { + $request = (new ServerRequest('https://www.example.com/', null, 'php://input', [], ['HTTPS' => 'ON'])); + $request = $request->withAttribute('normalizedParams', NormalizedParams::createFromRequest($request)); + $GLOBALS['TYPO3_REQUEST'] = $request; + $rootLine = [ + [ + 'uid' => 1, + 'TSconfig' => 'isHttps = off' + . chr(10) . '[request.getNormalizedParams().isHttps()]' + . chr(10) . ' isHttps = on' + . chr(10) . '[end]', + ], + ]; + /** @var PageTsConfigFactory $subject */ + $subject = $this->get(PageTsConfigFactory::class); + $pageTsConfig = $subject->create($rootLine, new NullSite(), new ConditionMatcher()); + self::assertSame('on', $pageTsConfig->getPageTsConfigArray()['isHttps']); + } + + /** + * @test + */ + public function pageTsConfigMatchesRequestHttpsConditionUsingSiteConstant(): void + { + $request = (new ServerRequest('https://www.example.com/', null, 'php://input', [], ['HTTPS' => 'ON'])); + $request = $request->withAttribute('normalizedParams', NormalizedParams::createFromRequest($request)); + $GLOBALS['TYPO3_REQUEST'] = $request; + $rootLine = [ + [ + 'uid' => 1, + 'TSconfig' => 'isHttps = off' + . chr(10) . '[{$aSiteSetting}]' + . chr(10) . ' isHttps = on' + . chr(10) . '[end]', + ], + ]; + /** @var PageTsConfigFactory $subject */ + $subject = $this->get(PageTsConfigFactory::class); + $siteSettings = new SiteSettings(['aSiteSetting' => 'request.getNormalizedParams().isHttps()']); + $site = new Site('siteIdentifier', 1, [], $siteSettings); + $pageTsConfig = $subject->create($rootLine, $site, new ConditionMatcher()); + self::assertSame('on', $pageTsConfig->getPageTsConfigArray()['isHttps']); + } + + /** + * @test + */ + public function pageTsConfigLoadsFromEvent(): void + { + /** @var PageTsConfigFactory $subject */ + $subject = $this->get(PageTsConfigFactory::class); + $pageTsConfig = $subject->create([], new NullSite(), new ConditionMatcher()); + self::assertSame('loadedFromEvent', $pageTsConfig->getPageTsConfigArray()['loadedFromEvent']); + } + + /** + * @test + */ + public function pageTsConfigLoadsFromLegacyEvent(): void + { + /** @var PageTsConfigFactory $subject */ + $subject = $this->get(PageTsConfigFactory::class); + $pageTsConfig = $subject->create([], new NullSite(), new ConditionMatcher()); + self::assertSame('loadedFromLegacyEvent', $pageTsConfig->getPageTsConfigArray()['loadedFromLegacyEvent']); + } + + /** + * @test + */ + public function pageTsConfigCanBeOverloadedWithUserTsConfig(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/pageTsConfigTestFixture.csv'); + $backendUser = $this->setUpBackendUser(1); + /** @var UserTsConfigFactory $userTsConfigFactory */ + $userTsConfigFactory = $this->get(UserTsConfigFactory::class); + $userTsConfig = $userTsConfigFactory->create($backendUser); + $rootLine = [ + [ + 'uid' => 1, + 'TSconfig' => 'valueOverriddenByUserTsConfig = base', + ], + ]; + /** @var PageTsConfigFactory $subject */ + $subject = $this->get(PageTsConfigFactory::class); + $pageTsConfig = $subject->create($rootLine, new NullSite(), new ConditionMatcher(), $userTsConfig); + self::assertSame('overridden', $pageTsConfig->getPageTsConfigArray()['valueOverriddenByUserTsConfig']); + } +} diff --git a/typo3/sysext/core/Tests/Functional/TypoScript/TypoScriptStringFactoryTest.php b/typo3/sysext/core/Tests/Functional/TypoScript/TypoScriptStringFactoryTest.php index 3c7fb9e14b13ad29f3cb0c267d7244d674be91dc..92febd8aa637d134cf6a84d5d1ae38062b209bf0 100644 --- a/typo3/sysext/core/Tests/Functional/TypoScript/TypoScriptStringFactoryTest.php +++ b/typo3/sysext/core/Tests/Functional/TypoScript/TypoScriptStringFactoryTest.php @@ -22,7 +22,6 @@ use TYPO3\CMS\Core\EventDispatcher\NoopEventDispatcher; use TYPO3\CMS\Core\TypoScript\AST\AstBuilder; use TYPO3\CMS\Core\TypoScript\AST\Node\ChildNode; use TYPO3\CMS\Core\TypoScript\AST\Node\RootNode; -use TYPO3\CMS\Core\TypoScript\Tokenizer\LossyTokenizer; use TYPO3\CMS\Core\TypoScript\TypoScriptStringFactory; use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; @@ -47,7 +46,6 @@ class TypoScriptStringFactoryTest extends FunctionalTestCase $result = $subject->parseFromStringWithIncludesAndConditions( 'testing', '@import \'EXT:core/Tests/Functional/TypoScript/Fixtures/SimpleCondition.typoscript\'', - new LossyTokenizer(), new BackendConditionMatcher() ); self::assertEquals($expected, $result); @@ -64,11 +62,7 @@ class TypoScriptStringFactoryTest extends FunctionalTestCase $expected->addChild($fooNode); /** @var TypoScriptStringFactory $subject */ $subject = $this->get(TypoScriptStringFactory::class); - $result = $subject->parseFromString( - 'foo = bar', - new LossyTokenizer(), - new AstBuilder(new NoopEventDispatcher()) - ); + $result = $subject->parseFromString('foo = bar', new AstBuilder(new NoopEventDispatcher())); self::assertEquals($expected, $result); } } diff --git a/typo3/sysext/core/Tests/Functional/TypoScript/UserTsConfigFactoryTest.php b/typo3/sysext/core/Tests/Functional/TypoScript/UserTsConfigFactoryTest.php new file mode 100644 index 0000000000000000000000000000000000000000..f00bd62405c46245a684e49122f278f0d3561cdc --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/TypoScript/UserTsConfigFactoryTest.php @@ -0,0 +1,104 @@ +<?php + +declare(strict_types=1); + +/* + * 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! + */ + +namespace TYPO3\CMS\Core\Tests\Functional\TypoScript; + +use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; +use TYPO3\CMS\Core\Http\NormalizedParams; +use TYPO3\CMS\Core\Http\ServerRequest; +use TYPO3\CMS\Core\TypoScript\UserTsConfigFactory; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; + +/** + * Tests UserTsConfigFactory and indirectly IncludeTree/TsConfigTreeBuilder + */ +class UserTsConfigFactoryTest extends FunctionalTestCase +{ + /** + * @test + */ + public function userTsConfigLoadsDefaultFromGlobals(): void + { + $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultUserTSconfig'] = 'loadedFromGlobals = loadedFromGlobals'; + $this->importCSVDataSet(__DIR__ . '/Fixtures/userTsConfigTestFixture.csv'); + $backendUser = $this->setUpBackendUser(1); + /** @var UserTsConfigFactory $subject */ + $subject = $this->get(UserTsConfigFactory::class); + $userTsConfig = $subject->create($backendUser); + self::assertSame('loadedFromGlobals', $userTsConfig->getUserTsConfigArray()['loadedFromGlobals']); + } + + /** + * @test + */ + public function userTsConfigLoadsDefaultFromBackendUserTsConfigField(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/userTsConfigTestFixture.csv'); + $backendUser = $this->setUpBackendUser(2); + /** @var UserTsConfigFactory $subject */ + $subject = $this->get(UserTsConfigFactory::class); + $userTsConfig = $subject->create($backendUser); + self::assertSame('loadedFromUser', $userTsConfig->getUserTsConfigArray()['loadedFromUser']); + } + + /** + * @test + */ + public function userTsConfigLoadsDefaultFromBackendUserGroupTsConfigField(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/userTsConfigTestFixture.csv'); + $backendUser = $this->setUpBackendUser(3); + /** @var UserTsConfigFactory $subject */ + $subject = $this->get(UserTsConfigFactory::class); + $userTsConfig = $subject->create($backendUser); + self::assertSame('loadedFromUserGroup', $userTsConfig->getUserTsConfigArray()['loadedFromUserGroup']); + } + + /** + * @test + */ + public function userTsConfigLoadsDefaultFromBackendUserGroupTsConfigFieldAndGroupOverride(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/userTsConfigTestFixture.csv'); + $backendUser = $this->setUpBackendUser(4); + /** @var UserTsConfigFactory $subject */ + $subject = $this->get(UserTsConfigFactory::class); + $userTsConfig = $subject->create($backendUser); + self::assertSame('loadedFromUserGroupOverride', $userTsConfig->getUserTsConfigArray()['loadedFromUserGroup']); + } + + /** + * @test + */ + public function userTsConfigMatchesRequestHttpsCondition(): void + { + $this->importCSVDataSet(__DIR__ . '/Fixtures/userTsConfigTestFixture.csv'); + $userRow = $this->getBackendUserRecordFromDatabase(5); + $backendUser = GeneralUtility::makeInstance(BackendUserAuthentication::class); + $request = (new ServerRequest('https://www.example.com/', null, 'php://input', [], ['HTTPS' => 'ON'])); + $session = $backendUser->createUserSession($userRow); + $request = $request->withCookieParams(['be_typo_user' => $session->getJwt()]); + $request = $request->withAttribute('normalizedParams', NormalizedParams::createFromRequest($request)); + $GLOBALS['TYPO3_REQUEST'] = $request; + $backendUser = $this->authenticateBackendUser($backendUser, $request); + /** @var UserTsConfigFactory $subject */ + $subject = $this->get(UserTsConfigFactory::class); + $userTsConfig = $subject->create($backendUser); + self::assertSame('on', $userTsConfig->getUserTsConfigArray()['isHttps']); + } +} diff --git a/typo3/sysext/core/Tests/Functional/TypoScript/Parser/TypoScriptParserTest.php b/typo3/sysext/core/Tests/FunctionalDeprecated/TypoScript/Parser/TypoScriptParserTest.php similarity index 96% rename from typo3/sysext/core/Tests/Functional/TypoScript/Parser/TypoScriptParserTest.php rename to typo3/sysext/core/Tests/FunctionalDeprecated/TypoScript/Parser/TypoScriptParserTest.php index bea10144744aaaceb9e60dc78e2a4d5e85a6b50b..b82e7752b31b80c820f335d0290c42858e983d64 100644 --- a/typo3/sysext/core/Tests/Functional/TypoScript/Parser/TypoScriptParserTest.php +++ b/typo3/sysext/core/Tests/FunctionalDeprecated/TypoScript/Parser/TypoScriptParserTest.php @@ -15,7 +15,7 @@ declare(strict_types=1); * The TYPO3 project - inspiring people to share! */ -namespace TYPO3\CMS\Core\Tests\Functional\TypoScript\Parser; +namespace TYPO3\CMS\Core\Tests\FunctionalDeprecated\TypoScript\Parser; use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser; use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; diff --git a/typo3/sysext/core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript b/typo3/sysext/core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript deleted file mode 100644 index b843d96cb232717b2843fdeb67c5d8050c060289..0000000000000000000000000000000000000000 --- a/typo3/sysext/core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript +++ /dev/null @@ -1 +0,0 @@ -test.Core.TypoScript = 1 \ No newline at end of file diff --git a/typo3/sysext/core/Tests/Unit/TypoScript/Fixtures/recursive_includes_setup.typoscript b/typo3/sysext/core/Tests/Unit/TypoScript/Fixtures/recursive_includes_setup.typoscript deleted file mode 100644 index 4c5a49bd4f2dd806c228de26773fd6f64e24c154..0000000000000000000000000000000000000000 --- a/typo3/sysext/core/Tests/Unit/TypoScript/Fixtures/recursive_includes_setup.typoscript +++ /dev/null @@ -1 +0,0 @@ -@import 'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript' diff --git a/typo3/sysext/core/Tests/Unit/Configuration/Loader/Fixtures/included.typoscript b/typo3/sysext/core/Tests/UnitDeprecated/Configuration/Loader/Fixtures/included.typoscript similarity index 100% rename from typo3/sysext/core/Tests/Unit/Configuration/Loader/Fixtures/included.typoscript rename to typo3/sysext/core/Tests/UnitDeprecated/Configuration/Loader/Fixtures/included.typoscript diff --git a/typo3/sysext/core/Tests/Unit/Configuration/Loader/PageTsConfigLoaderTest.php b/typo3/sysext/core/Tests/UnitDeprecated/Configuration/Loader/PageTsConfigLoaderTest.php similarity index 77% rename from typo3/sysext/core/Tests/Unit/Configuration/Loader/PageTsConfigLoaderTest.php rename to typo3/sysext/core/Tests/UnitDeprecated/Configuration/Loader/PageTsConfigLoaderTest.php index 8dce931a326e4c8f14c83d20c0c91374625f0d0f..39fc0f64fb92f4e1317515bd2f6083e28b18564a 100644 --- a/typo3/sysext/core/Tests/Unit/Configuration/Loader/PageTsConfigLoaderTest.php +++ b/typo3/sysext/core/Tests/UnitDeprecated/Configuration/Loader/PageTsConfigLoaderTest.php @@ -15,11 +15,12 @@ declare(strict_types=1); * The TYPO3 project - inspiring people to share! */ -namespace TYPO3\CMS\Core\Tests\Unit\Configuration\Loader; +namespace TYPO3\CMS\Core\Tests\UnitDeprecated\Configuration\Loader; use Psr\EventDispatcher\EventDispatcherInterface; use TYPO3\CMS\Core\Configuration\Event\ModifyLoadedPageTsConfigEvent; use TYPO3\CMS\Core\Configuration\Loader\PageTsConfigLoader; +use TYPO3\CMS\Core\EventDispatcher\NoopEventDispatcher; use TYPO3\TestingFramework\Core\Unit\UnitTestCase; class PageTsConfigLoaderTest extends UnitTestCase @@ -69,17 +70,15 @@ class PageTsConfigLoaderTest extends UnitTestCase public function loadExternalInclusionsCorrectlyAndKeepLoadingOrder(): void { $expected = [ + 'global' => '', 'default' => $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPageTSconfig'], 'page_13_includes_0' => 'Show_me = more ', 'page_13' => 'waiting for = love', 'page_27' => '', ]; - $rootLine = [['uid' => 13, 'TSconfig' => 'waiting for = love', 'tsconfig_includes' => 'EXT:core/Tests/Unit/Configuration/Loader/Fixtures/included.typoscript'], ['uid' => 27, 'TSconfig' => '']]; - $eventDispatcherMock = $this->createMock(EventDispatcherInterface::class); - $event = new ModifyLoadedPageTsConfigEvent($expected, $rootLine); - $eventDispatcherMock->method('dispatch')->with(self::isInstanceOf(ModifyLoadedPageTsConfigEvent::class))->willReturn($event); - $subject = new PageTsConfigLoader($eventDispatcherMock); + $rootLine = [['uid' => 13, 'TSconfig' => 'waiting for = love', 'tsconfig_includes' => 'EXT:core/Tests/UnitDeprecated/Configuration/Loader/Fixtures/included.typoscript'], ['uid' => 27, 'TSconfig' => '']]; + $subject = new PageTsConfigLoader(new NoopEventDispatcher()); $result = $subject->collect($rootLine); self::assertSame($expected, $result); } @@ -90,16 +89,14 @@ class PageTsConfigLoaderTest extends UnitTestCase public function invalidExternalFileIsNotLoaded(): void { $expected = [ + 'global' => '', 'default' => $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPageTSconfig'], 'page_13' => 'waiting for = love', 'page_27' => '', ]; $expectedString = implode("\n[GLOBAL]\n", $expected); - $rootLine = [['uid' => 13, 'TSconfig' => 'waiting for = love', 'tsconfig_includes' => 'EXT:core/Tests/Unit/Configuration/Loader/Fixtures/me_does_not_exist.typoscript'], ['uid' => 27, 'TSconfig' => '']]; - $eventDispatcherMock = $this->createMock(EventDispatcherInterface::class); - $event = new ModifyLoadedPageTsConfigEvent($expected, $rootLine); - $eventDispatcherMock->method('dispatch')->with(self::isInstanceOf(ModifyLoadedPageTsConfigEvent::class))->willReturn($event); - $subject = new PageTsConfigLoader($eventDispatcherMock); + $rootLine = [['uid' => 13, 'TSconfig' => 'waiting for = love', 'tsconfig_includes' => 'EXT:core/Tests/UnitDeprecated/Configuration/Loader/Fixtures/me_does_not_exist.typoscript'], ['uid' => 27, 'TSconfig' => '']]; + $subject = new PageTsConfigLoader(new NoopEventDispatcher()); $result = $subject->collect($rootLine); self::assertSame($expected, $result); diff --git a/typo3/sysext/core/Tests/Unit/Configuration/Parser/PageTsConfigParserTest.php b/typo3/sysext/core/Tests/UnitDeprecated/Configuration/Parser/PageTsConfigParserTest.php similarity index 98% rename from typo3/sysext/core/Tests/Unit/Configuration/Parser/PageTsConfigParserTest.php rename to typo3/sysext/core/Tests/UnitDeprecated/Configuration/Parser/PageTsConfigParserTest.php index 2311135bf2dd37b14e283f53256751a0b4f7d097..c97269b9f2bf2f5cc2d1f109771eddc6d33d1d3b 100644 --- a/typo3/sysext/core/Tests/Unit/Configuration/Parser/PageTsConfigParserTest.php +++ b/typo3/sysext/core/Tests/UnitDeprecated/Configuration/Parser/PageTsConfigParserTest.php @@ -15,7 +15,7 @@ declare(strict_types=1); * The TYPO3 project - inspiring people to share! */ -namespace TYPO3\CMS\Core\Tests\Unit\Configuration\Parser; +namespace TYPO3\CMS\Core\Tests\UnitDeprecated\Configuration\Parser; use Psr\Log\NullLogger; use TYPO3\CMS\Core\Cache\Backend\TransientMemoryBackend; diff --git a/typo3/sysext/core/Tests/Unit/TypoScript/Fixtures/badfilename.php b/typo3/sysext/core/Tests/UnitDeprecated/TypoScript/Fixtures/badfilename.php similarity index 100% rename from typo3/sysext/core/Tests/Unit/TypoScript/Fixtures/badfilename.php rename to typo3/sysext/core/Tests/UnitDeprecated/TypoScript/Fixtures/badfilename.php diff --git a/typo3/sysext/core/Tests/UnitDeprecated/TypoScript/Fixtures/recursive_includes_setup.typoscript b/typo3/sysext/core/Tests/UnitDeprecated/TypoScript/Fixtures/recursive_includes_setup.typoscript new file mode 100644 index 0000000000000000000000000000000000000000..6a355fc02c88b3b726fe868a1332989e262da904 --- /dev/null +++ b/typo3/sysext/core/Tests/UnitDeprecated/TypoScript/Fixtures/recursive_includes_setup.typoscript @@ -0,0 +1 @@ +@import 'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript' diff --git a/typo3/sysext/core/Tests/Unit/TypoScript/Fixtures/setup.typoscript b/typo3/sysext/core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript similarity index 100% rename from typo3/sysext/core/Tests/Unit/TypoScript/Fixtures/setup.typoscript rename to typo3/sysext/core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript diff --git a/typo3/sysext/core/Tests/Unit/TypoScript/Parser/TypoScriptParserTest.php b/typo3/sysext/core/Tests/UnitDeprecated/TypoScript/Parser/TypoScriptParserTest.php similarity index 89% rename from typo3/sysext/core/Tests/Unit/TypoScript/Parser/TypoScriptParserTest.php rename to typo3/sysext/core/Tests/UnitDeprecated/TypoScript/Parser/TypoScriptParserTest.php index 66c2ad83efb66bcbf28c9a43a9d35f25884fefd7..7d1f1e3424c4192147f115a2963c72d547021d37 100644 --- a/typo3/sysext/core/Tests/Unit/TypoScript/Parser/TypoScriptParserTest.php +++ b/typo3/sysext/core/Tests/UnitDeprecated/TypoScript/Parser/TypoScriptParserTest.php @@ -15,7 +15,7 @@ declare(strict_types=1); * The TYPO3 project - inspiring people to share! */ -namespace TYPO3\CMS\Core\Tests\Unit\TypoScript\Parser; +namespace TYPO3\CMS\Core\Tests\UnitDeprecated\TypoScript\Parser; use TYPO3\CMS\Backend\Configuration\TypoScript\ConditionMatching\ConditionMatcher; use TYPO3\CMS\Core\Cache\CacheManager; @@ -466,34 +466,34 @@ class TypoScriptParserTest extends UnitTestCase return [ 'Found include file as single file is imported' => [ // Input TypoScript - '@import "EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript"' + '@import "EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/ext_typoscript_setup.typoscript"' , // Expected ' -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' begin ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' begin ### test.Core.TypoScript = 1 -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' end ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' end ### ', ], 'Found include file is imported' => [ // Input TypoScript 'bennilove = before -@import "EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript" +@import "EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/ext_typoscript_setup.typoscript" ' , // Expected ' bennilove = before -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' begin ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' begin ### test.Core.TypoScript = 1 -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' end ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' end ### ', ], 'Not found file is not imported' => [ // Input TypoScript 'bennilove = before -@import "EXT:core/Tests/Unit/TypoScript/Fixtures/notfoundfile.typoscript" +@import "EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/notfoundfile.typoscript" ' , // Expected @@ -501,138 +501,138 @@ test.Core.TypoScript = 1 bennilove = before ### -### ERROR: No file or folder found for importing TypoScript on "EXT:core/Tests/Unit/TypoScript/Fixtures/notfoundfile.typoscript". +### ERROR: No file or folder found for importing TypoScript on "EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/notfoundfile.typoscript". ### ', ], 'All files with glob are imported' => [ // Input TypoScript 'bennilove = before -@import "EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript*" +@import "EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/ext_typoscript*" ' , // Expected ' bennilove = before -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' begin ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' begin ### test.Core.TypoScript = 1 -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' end ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' end ### ', ], 'Specific file with typoscript ending is imported' => [ // Input TypoScript 'bennilove = before -@import "EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript" +@import "EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript" ' , // Expected ' bennilove = before -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' begin ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' begin ### test.TYPO3Forever.TypoScript = 1 -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' end ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' end ### ', ], 'All files in folder are imported, sorted by name' => [ // Input TypoScript 'bennilove = before -@import "EXT:core/Tests/Unit/TypoScript/Fixtures/" +@import "EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/" ' , // Expected ' bennilove = before -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' begin ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' begin ### test.Core.TypoScript = 1 -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' end ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' end ### -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/recursive_includes_setup.typoscript\' begin ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/recursive_includes_setup.typoscript\' begin ### -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' begin ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' begin ### test.TYPO3Forever.TypoScript = 1 -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' end ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' end ### -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/recursive_includes_setup.typoscript\' end ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/recursive_includes_setup.typoscript\' end ### -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' begin ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' begin ### test.TYPO3Forever.TypoScript = 1 -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' end ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' end ### ', ], 'All files ending with typoscript in folder are imported' => [ // Input TypoScript 'bennilove = before -@import "EXT:core/Tests/Unit/TypoScript/Fixtures/*typoscript" +@import "EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/*typoscript" ' , // Expected ' bennilove = before -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' begin ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' begin ### test.Core.TypoScript = 1 -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' end ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' end ### -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/recursive_includes_setup.typoscript\' begin ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/recursive_includes_setup.typoscript\' begin ### -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' begin ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' begin ### test.TYPO3Forever.TypoScript = 1 -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' end ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' end ### -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/recursive_includes_setup.typoscript\' end ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/recursive_includes_setup.typoscript\' end ### -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' begin ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' begin ### test.TYPO3Forever.TypoScript = 1 -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' end ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' end ### ', ], 'All typoscript files in folder are imported' => [ // Input TypoScript 'bennilove = before -@import "EXT:core/Tests/Unit/TypoScript/Fixtures/*.typoscript" +@import "EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/*.typoscript" ' , // Expected ' bennilove = before -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' begin ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' begin ### test.Core.TypoScript = 1 -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' end ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/ext_typoscript_setup.typoscript\' end ### -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/recursive_includes_setup.typoscript\' begin ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/recursive_includes_setup.typoscript\' begin ### -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' begin ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' begin ### test.TYPO3Forever.TypoScript = 1 -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' end ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' end ### -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/recursive_includes_setup.typoscript\' end ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/recursive_includes_setup.typoscript\' end ### -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' begin ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' begin ### test.TYPO3Forever.TypoScript = 1 -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' end ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' end ### ', ], 'All typoscript files in folder with glob are not imported due to recursion level=0' => [ // Input TypoScript 'bennilove = before -@import "EXT:core/Tests/Unit/**/*.typoscript" +@import "EXT:core/Tests/UnitDeprecated/**/*.typoscript" ' , // Expected @@ -640,24 +640,24 @@ test.TYPO3Forever.TypoScript = 1 bennilove = before ### -### ERROR: No file or folder found for importing TypoScript on "EXT:core/Tests/Unit/**/*.typoscript". +### ERROR: No file or folder found for importing TypoScript on "EXT:core/Tests/UnitDeprecated/**/*.typoscript". ### ', ], 'TypoScript file ending is automatically added' => [ // Input TypoScript 'bennilove = before -@import "EXT:core/Tests/Unit/TypoScript/Fixtures/setup" +@import "EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup" ' , // Expected ' bennilove = before -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' begin ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' begin ### test.TYPO3Forever.TypoScript = 1 -### @import \'EXT:core/Tests/Unit/TypoScript/Fixtures/setup.typoscript\' end ### +### @import \'EXT:core/Tests/UnitDeprecated/TypoScript/Fixtures/setup.typoscript\' end ### ', ], ]; diff --git a/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php b/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php index f168a2e72f10d088cac707287ad42d325f30e587..4eaf76043bd9e572d2feac997de8c7881047f80c 100644 --- a/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php +++ b/typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php @@ -2543,9 +2543,14 @@ class TypoScriptFrontendController implements LoggerAwareInterface *******************************************/ /** * Returns the pages TSconfig array based on the current ->rootLine + * + * @deprecated since TYPO3 v12, will be removed in v13. Frontend should typically not depend on Backend TsConfig. + * If really needed, use PageTsConfigFactory, see usage in DatabaseRecordLinkBuilder. + * Remove together with class PageTsConfig. */ public function getPagesTSconfig(): array { + trigger_error('Method getPagesTSconfig() is deprecated since TYPO3 v12 and will be removed with TYPO3 v13.0.', E_USER_DEPRECATED); if (!is_array($this->pagesTSconfig)) { $matcher = GeneralUtility::makeInstance(FrontendConditionMatcher::class, $this->context, $this->id, $this->rootLine); $this->pagesTSconfig = GeneralUtility::makeInstance(PageTsConfig::class) diff --git a/typo3/sysext/frontend/Classes/Typolink/DatabaseRecordLinkBuilder.php b/typo3/sysext/frontend/Classes/Typolink/DatabaseRecordLinkBuilder.php index 96f0e202de1d5c5241aa920aacada296d94e45d7..7130b84d29391c9a3ecf910a26d6046c58d95eb2 100644 --- a/typo3/sysext/frontend/Classes/Typolink/DatabaseRecordLinkBuilder.php +++ b/typo3/sysext/frontend/Classes/Typolink/DatabaseRecordLinkBuilder.php @@ -17,9 +17,17 @@ declare(strict_types=1); namespace TYPO3\CMS\Frontend\Typolink; +use Psr\Http\Message\ServerRequestInterface; +use TYPO3\CMS\Core\Cache\CacheManager; +use TYPO3\CMS\Core\Context\Context; use TYPO3\CMS\Core\LinkHandling\TypoLinkCodecService; +use TYPO3\CMS\Core\Site\Entity\NullSite; +use TYPO3\CMS\Core\TypoScript\PageTsConfig; +use TYPO3\CMS\Core\TypoScript\PageTsConfigFactory; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Frontend\Configuration\TypoScript\ConditionMatching\ConditionMatcher as FrontendConditionMatcher; use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; +use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController; /** * Builds a TypoLink to a database record @@ -29,9 +37,9 @@ class DatabaseRecordLinkBuilder extends AbstractTypolinkBuilder public function build(array &$linkDetails, string $linkText, string $target, array $conf): LinkResultInterface { $tsfe = $this->getTypoScriptFrontendController(); - $pageTsConfig = $tsfe->getPagesTSconfig(); - $configurationKey = $linkDetails['identifier'] . '.'; $request = $this->contentObjectRenderer->getRequest(); + $pageTsConfig = $this->getPageTsConfig($tsfe, $request); + $configurationKey = $linkDetails['identifier'] . '.'; $typoScriptArray = $request->getAttribute('frontend.typoscript')->getSetupArray(); $configuration = $typoScriptArray['config.']['recordLinks.'] ?? []; $linkHandlerConfiguration = $pageTsConfig['TCEMAIN.']['linkHandler.'] ?? []; @@ -98,4 +106,27 @@ class DatabaseRecordLinkBuilder extends AbstractTypolinkBuilder $localContentObjectRenderer->parameters = $this->contentObjectRenderer->parameters; return $localContentObjectRenderer->createLink($linkText, $typoScriptConfiguration); } + + /** + * Helper method to calculate pageTsConfig in frontend scope, we can't use BackendUtility::getPagesTSconfig() here. + */ + protected function getPageTsConfig(TypoScriptFrontendController $tsfe, ServerRequestInterface $request): array + { + $id = $tsfe->id; + $runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime'); + $pageTsConfig = $runtimeCache->get('pageTsConfig-' . $id); + if ($pageTsConfig instanceof PageTsConfig) { + return $pageTsConfig->getPageTsConfigArray(); + } + $conditionMatcher = GeneralUtility::makeInstance(FrontendConditionMatcher::class, GeneralUtility::makeInstance(Context::class), $tsfe->id, $tsfe->rootLine); + $site = $request->getAttribute('site') ?? new NullSite(); + $pageTsConfigFactory = GeneralUtility::makeInstance(PageTsConfigFactory::class); + $pageTsConfig = $pageTsConfigFactory->create( + $tsfe->rootLine, + $site, + $conditionMatcher + ); + $runtimeCache->set('pageTsConfig-' . $id, $pageTsConfig); + return $pageTsConfig->getPageTsConfigArray(); + } } diff --git a/typo3/sysext/frontend/Tests/Unit/Typolink/DatabaseRecordLinkBuilderTest.php b/typo3/sysext/frontend/Tests/Unit/Typolink/DatabaseRecordLinkBuilderTest.php index 12d0ef785ffe07d99a17bb5417d0976f71678d39..c1806bea8c5686b550b697807bfb584e2f54c9c3 100644 --- a/typo3/sysext/frontend/Tests/Unit/Typolink/DatabaseRecordLinkBuilderTest.php +++ b/typo3/sysext/frontend/Tests/Unit/Typolink/DatabaseRecordLinkBuilderTest.php @@ -176,10 +176,9 @@ class DatabaseRecordLinkBuilderTest extends UnitTestCase $contentObjectRendererMock->expects(self::once())->method('start'); $contentObjectRendererMock->expects(self::once())->method('createLink'); - $frontendControllerMock->method('getPagesTSconfig')->willReturn($pageTsConfig); - // Act - $databaseRecordLinkBuilder = new DatabaseRecordLinkBuilder($contentObjectRendererMock, $frontendControllerMock); + $databaseRecordLinkBuilder = $this->getAccessibleMock(DatabaseRecordLinkBuilder::class, ['getPageTsConfig'], [$contentObjectRendererMock, $frontendControllerMock]); + $databaseRecordLinkBuilder->method('getPageTsConfig')->willReturn($pageTsConfig); try { $databaseRecordLinkBuilder->build($extractedLinkDetails, $linkText, $target, $confFromDb); } catch (UnableToLinkException) { diff --git a/typo3/sysext/info/Classes/Controller/InfoPageTyposcriptConfigController.php b/typo3/sysext/info/Classes/Controller/InfoPageTyposcriptConfigController.php index a471eef824d7f1ac1e65d539716174570b5452b0..0acc506c627b35a04acd5b8da857a8ddb1731fb6 100644 --- a/typo3/sysext/info/Classes/Controller/InfoPageTyposcriptConfigController.php +++ b/typo3/sysext/info/Classes/Controller/InfoPageTyposcriptConfigController.php @@ -20,12 +20,14 @@ namespace TYPO3\CMS\Info\Controller; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use TYPO3\CMS\Backend\Utility\BackendUtility; -use TYPO3\CMS\Core\Configuration\Loader\PageTsConfigLoader; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction; use TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction; use TYPO3\CMS\Core\Imaging\Icon; use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction; +use TYPO3\CMS\Core\TypoScript\IncludeTree\TsConfigTreeBuilder; +use TYPO3\CMS\Core\TypoScript\Tokenizer\Line\LineStream; +use TYPO3\CMS\Core\TypoScript\Tokenizer\LosslessTokenizer; use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\MathUtility; @@ -59,23 +61,30 @@ class InfoPageTyposcriptConfigController extends InfoModuleController if ((int)$moduleData->get('tsconf_parts') === 99) { $rootLine = BackendUtility::BEgetRootLine($this->id, '', true); - /** @var array<string, string> $TSparts */ - $TSparts = GeneralUtility::makeInstance(PageTsConfigLoader::class)->collect($rootLine); + + $tsConfigTreeBuilder = GeneralUtility::makeInstance(TsConfigTreeBuilder::class); + $pageTsConfigTree = $tsConfigTreeBuilder->getPagesTsConfigTree($rootLine, new LosslessTokenizer()); + // @todo: This is a bit dusty. It would be better to render the full tree similar to + // tstemplate Template Analyzer. For now, we simply create an array with the + // to-string'ed content and render this. + $TSparts = []; + foreach ($pageTsConfigTree->getNextChild() as $child) { + $lineStream = $child->getLineStream(); + if ($lineStream instanceof LineStream) { + $TSparts[$child->getName()] = (string)$lineStream; + } + } + $lines = []; $pUids = []; foreach ($TSparts as $key => $value) { - $line = []; - if ($key === 'global') { - $title = $this->getLanguageService()->sL('LLL:EXT:info/Resources/Private/Language/InfoPageTsConfig.xlf:editTSconfig_global'); - $line['title'] = $title; - } elseif ($key === 'default') { - $title = $this->getLanguageService()->sL('LLL:EXT:info/Resources/Private/Language/InfoPageTsConfig.xlf:editTSconfig_default'); - $line['title'] = $title; - } else { - // Remove the "page_" prefix - [, $pageId] = explode('_', $key, 3); - $pageId = (int)$pageId; + $title = $key; + $line = [ + 'title' => $key, + ]; + if (str_starts_with($key, 'pagesTsConfig-page-')) { + $pageId = (int)explode('-', $key)[2]; $pUids[] = $pageId; $row = BackendUtility::getRecordWSOL('pages', $pageId); $icon = $this->iconFactory->getIconForRecord('pages', $row, Icon::SIZE_SMALL); diff --git a/typo3/sysext/info/Resources/Private/Language/InfoPageTsConfig.xlf b/typo3/sysext/info/Resources/Private/Language/InfoPageTsConfig.xlf index 062036cda980697dc4b7e9b433617f4269e2cf85..cc503d491cd94c281aa6b08da5c8dd52f21996d8 100644 --- a/typo3/sysext/info/Resources/Private/Language/InfoPageTsConfig.xlf +++ b/typo3/sysext/info/Resources/Private/Language/InfoPageTsConfig.xlf @@ -36,12 +36,6 @@ <trans-unit id="editTSconfig_all" resname="editTSconfig_all"> <source>Edit TSconfig for all shown pages</source> </trans-unit> - <trans-unit id="editTSconfig_global" resname="editTSconfig_global"> - <source>Global Configuration</source> - </trans-unit> - <trans-unit id="editTSconfig_default" resname="editTSconfig_default"> - <source>Default Configuration from TYPO3_CONF_VARS</source> - </trans-unit> <trans-unit id="mod_pagetsconfig" resname="mod_pagetsconfig"> <source>Page TSconfig</source> </trans-unit> diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassNameMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassNameMatcher.php index 64d85dc2d75177da0c5ddf4e1993a633a02e48fd..d00c427452fba3f0d895d4c19e64bd17a767b213 100644 --- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassNameMatcher.php +++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassNameMatcher.php @@ -2208,4 +2208,29 @@ return [ 'Breaking-97816-NewTypoScriptParserInFrontend.rst', ], ], + 'TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser' => [ + 'restFiles' => [ + 'Deprecation-99120-DeprecateOldTypoScriptParser.rst', + ], + ], + 'TYPO3\CMS\Core\Configuration\Loader\PageTsConfigLoader' => [ + 'restFiles' => [ + 'Deprecation-99120-DeprecateOldTypoScriptParser.rst', + ], + ], + 'TYPO3\CMS\Core\Configuration\PageTsConfig' => [ + 'restFiles' => [ + 'Deprecation-99120-DeprecateOldTypoScriptParser.rst', + ], + ], + 'TYPO3\CMS\Core\Configuration\Parser\PageTsConfigParser' => [ + 'restFiles' => [ + 'Deprecation-99120-DeprecateOldTypoScriptParser.rst', + ], + ], + 'TYPO3\CMS\Core\Configuration\Event\ModifyLoadedPageTsConfigEvent' => [ + 'restFiles' => [ + 'Deprecation-99120-DeprecateOldTypoScriptParser.rst', + ], + ], ]; diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php index ae53a85365484914a45e46fc536a2a8035193aa4..d3ca927f81037bcd3330e9dcc92849e3f8ee8419 100644 --- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php +++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallMatcher.php @@ -5407,4 +5407,11 @@ return [ 'Deprecation-99170-ConfigbaseURLAndBaseTagFunctionality.rst', ], ], + 'TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController->getPagesTSconfig' => [ + 'numberOfMandatoryArguments' => 0, + 'maximumNumberOfArguments' => 0, + 'restFiles' => [ + 'Deprecation-99120-DeprecateOldTypoScriptParser.rst', + ], + ], ]; diff --git a/typo3/sysext/linkvalidator/Classes/Task/ValidatorTask.php b/typo3/sysext/linkvalidator/Classes/Task/ValidatorTask.php index 65457d9e691eec8d97d8d6a34824800d73f8f99f..041ca74de2cdffa450d11ab83e1590eb31f8709d 100644 --- a/typo3/sysext/linkvalidator/Classes/Task/ValidatorTask.php +++ b/typo3/sysext/linkvalidator/Classes/Task/ValidatorTask.php @@ -23,7 +23,8 @@ use Symfony\Component\Mime\Address; use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Core\Mail\FluidEmail; use TYPO3\CMS\Core\Mail\MailerInterface; -use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser; +use TYPO3\CMS\Core\TypoScript\AST\AstBuilder; +use TYPO3\CMS\Core\TypoScript\TypoScriptStringFactory; use TYPO3\CMS\Core\Utility\ArrayUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\MailUtility; @@ -326,19 +327,12 @@ class ValidatorTask extends AbstractTask */ protected function loadModTSconfig(): void { - $parseObj = GeneralUtility::makeInstance(TypoScriptParser::class); - $parseObj->parse($this->configuration); - if (!empty($parseObj->errors)) { - $parseErrorMessage = $this->getLanguageService()->sL($this->languageFile . ':tasks.error.invalidTSconfig') . '<br />'; - foreach ($parseObj->errors as $errorInfo) { - $parseErrorMessage .= $errorInfo[0] . '<br />'; - } - throw new \Exception($parseErrorMessage, 1295476989); - } $modTs = BackendUtility::getPagesTSconfig($this->page)['mod.']['linkvalidator.'] ?? []; - $overrideTs = $parseObj->setup['mod.']['linkvalidator.'] ?? []; - if (is_array($overrideTs) && $overrideTs !== []) { - ArrayUtility::mergeRecursiveWithOverrule($modTs, $overrideTs); + + if (!empty($this->configuration)) { + $typoScriptStringFactory = GeneralUtility::makeInstance(TypoScriptStringFactory::class); + $overrideTs = $typoScriptStringFactory->parseFromString($this->configuration, GeneralUtility::makeInstance(AstBuilder::class)); + ArrayUtility::mergeRecursiveWithOverrule($modTs, $overrideTs->toArray()['mod.']['linkvalidator.'] ?? []); } if (empty($modTs['mail.']['fromemail'])) { diff --git a/typo3/sysext/linkvalidator/Resources/Private/Language/locallang.xlf b/typo3/sysext/linkvalidator/Resources/Private/Language/locallang.xlf index f71788aaeed22fbc2bfd13f6048f46d663589e94..7e90ccd73f0d20b95aa6b48ca074fcc6ee44fa09 100644 --- a/typo3/sysext/linkvalidator/Resources/Private/Language/locallang.xlf +++ b/typo3/sysext/linkvalidator/Resources/Private/Language/locallang.xlf @@ -58,9 +58,6 @@ <trans-unit id="tasks.error.invalidFromEmail" resname="tasks.error.invalidFromEmail"> <source>Invalid format of the email address in the from header</source> </trans-unit> - <trans-unit id="tasks.error.invalidTSconfig" resname="tasks.error.invalidTSconfig"> - <source>Invalid TSconfig in the task configuration!</source> - </trans-unit> <trans-unit id="tasks.error.invalidPageUid" resname="tasks.error.invalidPageUid"> <source>The given page id %d is invalid!</source> </trans-unit>