[FEATURE] Add Contexts for storing data access modes 04/57104/21
authorBenni Mack <benni@typo3.org>
Thu, 21 Jun 2018 20:23:58 +0000 (22:23 +0200)
committerAndreas Fernandez <a.fernandez@scripting-base.de>
Wed, 27 Jun 2018 11:27:05 +0000 (13:27 +0200)
A new "Context" concept is added which allows to keep
the state of common TYPO3 Request Data in form of
so-called Aspects.

An aspect contains properties which can be fetched,
but only the ones that are really necessary, instead of
exposing a full object (e.g. BE_USER).

The main goal is to centralize some global variables
distributed in various places.

In the first step the following variables are considered:

- $TSFE->showHiddenPages
- $TSFE->showHiddenRecords
- $TSFE->beUserLogin
- $TSFE->gr_list
- $TSFE->loginUser
- $GLOBALS[SIM_EXEC_TIME]
- $GLOBALS['BE_USER']->workspace

For now the Context is a singleton object, but should
be fetched from a DI container.

Sometimes a custom context is necessary, so it is
cloned (see usage in TSFE).

The difference to the PSR-7 request attributes is that the
context is ONLY related to data access (like permissions / visibility)
and also independent if TYPO3 is running via HTTP or CLI
(thus, can be used in CLI mode as well).

Next Steps:
- Migrate PageRepository->versioningWorkspaceId
- Migrate TSFE->simUserGroup
- Use DateTimeAspect everywhere
- Introduce Language + Page Aspects
- Introduce the context object into ContentObjectRenderer and cObjects
- Use Contexts in RestrictionContainers
- Use Contexts in TYPO3 Backend
- Decouple sys_page behaviour from TSFE where applicable
- Ensure TypoScript conditions continue to work / have a documented alternative

Resolves: #85389
Releases: master
Change-Id: I9e27e581a1632fcd8c3c6a9e0954b76b91f42c52
Reviewed-on: https://review.typo3.org/57104
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Stefan Bürk <stefan.buerk@pure-metal.de>
Tested-by: Stefan Bürk <stefan.buerk@pure-metal.de>
Reviewed-by: Susanne Moog <susanne.moog@typo3.org>
Tested-by: Susanne Moog <susanne.moog@typo3.org>
Reviewed-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Tested-by: Andreas Fernandez <a.fernandez@scripting-base.de>
52 files changed:
typo3/sysext/adminpanel/Classes/Modules/InfoModule.php
typo3/sysext/adminpanel/Classes/Modules/PreviewModule.php
typo3/sysext/backend/Classes/Http/Application.php
typo3/sysext/backend/Classes/Middleware/BackendUserAuthenticator.php
typo3/sysext/core/Classes/Console/CommandApplication.php
typo3/sysext/core/Classes/Context/AspectInterface.php [new file with mode: 0644]
typo3/sysext/core/Classes/Context/Context.php [new file with mode: 0644]
typo3/sysext/core/Classes/Context/DateTimeAspect.php [new file with mode: 0644]
typo3/sysext/core/Classes/Context/Exception/AspectNotFoundException.php [new file with mode: 0644]
typo3/sysext/core/Classes/Context/Exception/AspectPropertyNotFoundException.php [new file with mode: 0644]
typo3/sysext/core/Classes/Context/UserAspect.php [new file with mode: 0644]
typo3/sysext/core/Classes/Context/VisibilityAspect.php [new file with mode: 0644]
typo3/sysext/core/Classes/Context/WorkspaceAspect.php [new file with mode: 0644]
typo3/sysext/core/Classes/Database/Query/Restriction/FrontendGroupRestriction.php
typo3/sysext/core/Classes/Database/Query/Restriction/FrontendRestrictionContainer.php
typo3/sysext/core/Documentation/Changelog/master/Deprecation-85389-VariousPublicPropertiesInFavorOfContextAPI.rst [new file with mode: 0644]
typo3/sysext/core/Documentation/Changelog/master/Feature-85389-ContextAPIForConsistentDataHandling.rst [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Context/ContextTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Context/DateTimeAspectTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Context/UserAspectTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Context/VisibilityAspectTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Context/WorkspaceAspectTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Database/Query/Restriction/FrontendRestrictionContainerTest.php
typo3/sysext/core/Tests/Unit/Utility/RootlineUtilityTest.php
typo3/sysext/extbase/Tests/Unit/Persistence/Generic/Storage/Typo3DbBackendTest.php
typo3/sysext/extbase/Tests/Unit/Persistence/Generic/Storage/Typo3DbQueryParserTest.php
typo3/sysext/extbase/Tests/Unit/Service/ExtensionServiceTest.php
typo3/sysext/felogin/Classes/Controller/FrontendLoginController.php
typo3/sysext/felogin/Tests/Unit/Controller/FrontendLoginControllerTest.php
typo3/sysext/fluid/Classes/ViewHelpers/Security/IfAuthenticatedViewHelper.php
typo3/sysext/fluid/Classes/ViewHelpers/Security/IfHasRoleViewHelper.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Security/IfAuthenticatedViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Security/IfHasRoleViewHelperTest.php
typo3/sysext/frontend/Classes/Configuration/TypoScript/ConditionMatching/ConditionMatcher.php
typo3/sysext/frontend/Classes/ContentObject/ContentObjectRenderer.php
typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php
typo3/sysext/frontend/Classes/Http/Application.php
typo3/sysext/frontend/Classes/Middleware/BackendUserAuthenticator.php
typo3/sysext/frontend/Classes/Middleware/FrontendUserAuthenticator.php
typo3/sysext/frontend/Classes/Middleware/PageResolver.php
typo3/sysext/frontend/Classes/Page/PageRepository.php
typo3/sysext/frontend/Classes/Typolink/AbstractTypolinkBuilder.php
typo3/sysext/frontend/Tests/Functional/Controller/TypoScriptFrontendControllerTest.php
typo3/sysext/frontend/Tests/Functional/Page/PageRepositoryTest.php
typo3/sysext/frontend/Tests/Unit/Configuration/TypoScript/ConditionMatching/ConditionMatcherTest.php
typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php
typo3/sysext/frontend/Tests/Unit/Page/PageRepositoryTest.php
typo3/sysext/indexed_search/Classes/Domain/Repository/IndexSearchRepository.php
typo3/sysext/indexed_search/Classes/Indexer.php
typo3/sysext/install/Classes/Http/Application.php
typo3/sysext/install/Configuration/ExtensionScanner/Php/PropertyPublicMatcher.php
typo3/sysext/workspaces/Classes/Middleware/WorkspacePreview.php

index 3cac594..c1e5448 100644 (file)
@@ -16,6 +16,8 @@ namespace TYPO3\CMS\Adminpanel\Modules;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\UserAspect;
 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Fluid\View\StandaloneView;
@@ -40,17 +42,19 @@ class InfoModule extends AbstractModule
         $view->setPartialRootPaths([$this->extResources . '/Partials']);
         $tsfe = $this->getTypoScriptFrontendController();
 
+        /** @var UserAspect $frontendUserAspect */
+        $frontendUserAspect = GeneralUtility::makeInstance(Context::class)->getAspect('frontend.user');
         $view->assignMultiple([
             'info' => [
                 'pageUid' => $tsfe->id,
                 'pageType' => $tsfe->type,
-                'groupList' => $tsfe->gr_list,
+                'groupList' => implode(',', $frontendUserAspect->getGroupIds()),
                 'noCache' => $this->isNoCacheEnabled(),
                 'countUserInt' => \count($tsfe->config['INTincScript'] ?? []),
                 'totalParsetime' => $this->getTimeTracker()->getParseTime(),
                 'feUser' => [
-                    'uid' => $tsfe->fe_user->user['uid'] ?? 0,
-                    'username' => $tsfe->fe_user->user['username'] ?? ''
+                    'uid' => $frontendUserAspect->get('id'),
+                    'username' => $frontendUserAspect->get('username')
                 ],
                 'imagesOnPage' => $this->collectImagesOnPage(),
                 'documentSize' => $this->collectDocumentSize()
index 262b5c8..e1c7612 100644 (file)
@@ -17,6 +17,10 @@ namespace TYPO3\CMS\Adminpanel\Modules;
  */
 
 use TYPO3\CMS\Adminpanel\Repositories\FrontendGroupsRepository;
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\DateTimeAspect;
+use TYPO3\CMS\Core\Context\UserAspect;
+use TYPO3\CMS\Core\Context\VisibilityAspect;
 use TYPO3\CMS\Core\Http\ServerRequest;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -170,17 +174,20 @@ class PreviewModule extends AbstractModule
      */
     protected function initializeFrontendPreview()
     {
+        $context = GeneralUtility::makeInstance(Context::class);
         $tsfe = $this->getTypoScriptFrontendController();
         $tsfe->clear_preview();
         $tsfe->fePreview = 1;
-        $tsfe->showHiddenPage = (bool)$this->getConfigurationOption('showHiddenPages');
-        $tsfe->showHiddenRecords = (bool)$this->getConfigurationOption('showHiddenRecords');
+        $showHiddenPage = (bool)$this->getConfigurationOption('showHiddenPages');
+        $showHiddenRecords = (bool)$this->getConfigurationOption('showHiddenRecords');
         // Simulate date
         $simTime = $this->getConfigurationOption('simulateDate');
         if ($simTime) {
             $GLOBALS['SIM_EXEC_TIME'] = $simTime;
             $GLOBALS['SIM_ACCESS_TIME'] = $simTime - $simTime % 60;
+            $context->setAspect('date', GeneralUtility::makeInstance(DateTimeAspect::class, new \DateTimeImmutable('@' . $GLOBALS['SIM_EXEC_TIME'])));
         }
+        $context->setAspect('visibility', GeneralUtility::makeInstance(VisibilityAspect::class, $showHiddenPage, $showHiddenRecords));
         // simulate user
         $tsfe->simUserGroup = $this->getConfigurationOption('simulateUserGroup');
         if ($tsfe->simUserGroup) {
@@ -192,6 +199,7 @@ class PreviewModule extends AbstractModule
                     $tsfe->fe_user->usergroup_column => $tsfe->simUserGroup,
                 ];
             }
+            $context->setAspect('frontend.user', GeneralUtility::makeInstance(UserAspect::class, $tsfe->fe_user));
         }
         if (!$tsfe->simUserGroup && !$simTime && !$tsfe->showHiddenPage && !$tsfe->showHiddenRecords) {
             $tsfe->fePreview = 0;
index ab79a68..7c0791e 100644 (file)
@@ -18,8 +18,12 @@ namespace TYPO3\CMS\Backend\Http;
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Core\Configuration\ConfigurationManager;
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\DateTimeAspect;
+use TYPO3\CMS\Core\Context\VisibilityAspect;
 use TYPO3\CMS\Core\Http\AbstractApplication;
 use TYPO3\CMS\Core\Http\RedirectResponse;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * Entry point for the TYPO3 Backend (HTTP requests)
@@ -58,6 +62,8 @@ class Application extends AbstractApplication
         if (!$this->checkIfEssentialConfigurationExists()) {
             return $this->installToolRedirect();
         }
+        // Set up the initial context
+        $this->initializeContext();
         return parent::handle($request);
     }
 
@@ -80,4 +86,16 @@ class Application extends AbstractApplication
     {
         return new RedirectResponse('./install.php', 302);
     }
+
+    /**
+     * Initializes the Context used for accessing data and finding out the current state of the application
+     * Will be moved to a DI-like concept once introduced, for now, this is a singleton
+     */
+    protected function initializeContext()
+    {
+        GeneralUtility::makeInstance(Context::class, [
+            'date' => new DateTimeAspect(new \DateTimeImmutable('@' . $GLOBALS['EXEC_TIME'])),
+            'visibility' => new VisibilityAspect(true, true)
+        ]);
+    }
 }
index d4a5b89..0afc7e6 100644 (file)
@@ -19,7 +19,12 @@ use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
 use Psr\Http\Server\MiddlewareInterface;
 use Psr\Http\Server\RequestHandlerInterface;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\UserAspect;
+use TYPO3\CMS\Core\Context\WorkspaceAspect;
 use TYPO3\CMS\Core\Core\Bootstrap;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * Initializes the backend user authentication object (BE_USER) and the global LANG object.
@@ -58,6 +63,8 @@ class BackendUserAuthenticator implements MiddlewareInterface
         // @todo: once this logic is in this method, the redirect URL should be handled as response here
         Bootstrap::initializeBackendAuthentication($this->isLoggedInBackendUserRequired($pathToRoute));
         Bootstrap::initializeLanguageObject();
+        // Register the backend user as aspect
+        $this->setBackendUserAspect(GeneralUtility::makeInstance(Context::class), $GLOBALS['BE_USER']);
 
         return $handler->handle($request);
     }
@@ -73,4 +80,16 @@ class BackendUserAuthenticator implements MiddlewareInterface
     {
         return in_array($routePath, $this->publicRoutes, true);
     }
+
+    /**
+     * Register the backend user as aspect
+     *
+     * @param Context $context
+     * @param BackendUserAuthentication $user
+     */
+    protected function setBackendUserAspect(Context $context, BackendUserAuthentication $user)
+    {
+        $context->setAspect('backend.user', GeneralUtility::makeInstance(UserAspect::class, $user));
+        $context->setAspect('workspace', GeneralUtility::makeInstance(WorkspaceAspect::class, $user ? $user->workspace : 0));
+    }
 }
index 6e0d6c5..6941bd6 100644 (file)
@@ -14,6 +14,11 @@ namespace TYPO3\CMS\Core\Console;
  * The TYPO3 project - inspiring people to share!
  */
 use Symfony\Component\Console\Input\ArgvInput;
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\DateTimeAspect;
+use TYPO3\CMS\Core\Context\UserAspect;
+use TYPO3\CMS\Core\Context\VisibilityAspect;
+use TYPO3\CMS\Core\Context\WorkspaceAspect;
 use TYPO3\CMS\Core\Core\ApplicationInterface;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
@@ -38,6 +43,7 @@ class CommandApplication implements ApplicationInterface
      */
     public function run(callable $execute = null)
     {
+        $this->initializeContext();
         $handler = GeneralUtility::makeInstance(CommandRequestHandler::class);
         $handler->handleRequest(new ArgvInput());
 
@@ -55,4 +61,18 @@ class CommandApplication implements ApplicationInterface
             die('Not called from a command line interface (e.g. a shell or scheduler).' . LF);
         }
     }
+
+    /**
+     * Initializes the Context used for accessing data and finding out the current state of the application
+     * Will be moved to a DI-like concept once introduced, for now, this is a singleton
+     */
+    protected function initializeContext()
+    {
+        GeneralUtility::makeInstance(Context::class, [
+            'date' => new DateTimeAspect(new \DateTimeImmutable('@' . $GLOBALS['EXEC_TIME'])),
+            'visibility' => new VisibilityAspect(true, true),
+            'workspace' => new WorkspaceAspect(0),
+            'backend.user' => new UserAspect(null),
+        ]);
+    }
 }
diff --git a/typo3/sysext/core/Classes/Context/AspectInterface.php b/typo3/sysext/core/Classes/Context/AspectInterface.php
new file mode 100644 (file)
index 0000000..179a61e
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Core\Context;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Context\Exception\AspectPropertyNotFoundException;
+
+/**
+ * Interface AspectInterface
+ * Think of an aspect like a property bag
+ */
+interface AspectInterface
+{
+    /**
+     * Get a property from an aspect
+     *
+     * @param string $name
+     * @return mixed
+     * @throws AspectPropertyNotFoundException
+     */
+    public function get(string $name);
+}
diff --git a/typo3/sysext/core/Classes/Context/Context.php b/typo3/sysext/core/Classes/Context/Context.php
new file mode 100644 (file)
index 0000000..d67f9fa
--- /dev/null
@@ -0,0 +1,141 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Core\Context;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Context\Exception\AspectNotFoundException;
+use TYPO3\CMS\Core\Context\Exception\AspectPropertyNotFoundException;
+use TYPO3\CMS\Core\SingletonInterface;
+
+/**
+ * Contains the state of a current page request, to be used when reading
+ * information of the current request in which configuration/context
+ * it is used.
+ *
+ * Typically, the current main context is initialized very early within each entry-point application,
+ * and is then modified overridden in e.g. PSR-15 middlewares (e.g. authentication, preview settings etc).
+ *
+ * For most use-cases, the current main context is fetched via GeneralUtility::makeInstance(Context::class),
+ * however, if custom settings for a single use-case is necessary, it is recommended to clone the base context:
+ *
+ * $mainContext = GeneralUtility::makeInstance(Context::class);
+ * $customContext = clone $mainContext;
+ * $customContext->setAspect(GeneralUtility::makeInstance(VisibilityAspect::class, true, true, false))
+ *
+ * ... which in turn can be injected in the various places where TYPO3 uses contexts.
+ *
+ *
+ * Classic aspect names to be used are:
+ * - date (DateTimeAspect)
+ * - workspace
+ * - visibility
+ * - frontend.user
+ * - backend.user
+ */
+class Context implements SingletonInterface
+{
+    /**
+     * @var AspectInterface[]
+     */
+    protected $aspects = [];
+
+    /**
+     * Sets up the context with pre-defined aspects
+     *
+     * @param array|null $defaultAspects
+     */
+    public function __construct(array $defaultAspects = null)
+    {
+        foreach ($defaultAspects ?? [] as $name => $defaultAspect) {
+            if ($defaultAspect instanceof AspectInterface) {
+                $this->aspects[$name] = $defaultAspect;
+            }
+        }
+        // Ensure the default aspects are set, this is mostly necessary for tests to not set up everything
+        if (!$this->hasAspect('date')) {
+            $this->setAspect('date', new DateTimeAspect(new \DateTimeImmutable('@' . $GLOBALS['EXEC_TIME'])));
+        }
+        if (!$this->hasAspect('visibility')) {
+            $this->setAspect('visibility', new VisibilityAspect());
+        }
+        if (!$this->hasAspect('backend.user')) {
+            $this->setAspect('backend.user', new UserAspect());
+        }
+        if (!$this->hasAspect('frontend.user')) {
+            $this->setAspect('frontend.user', new UserAspect());
+        }
+        if (!$this->hasAspect('workspace')) {
+            $this->setAspect('workspace', new WorkspaceAspect());
+        }
+    }
+
+    /**
+     * Checks if an aspect exists in the context
+     *
+     * @param string $name
+     * @return bool
+     */
+    public function hasAspect(string $name): bool
+    {
+        return isset($this->aspects[$name]);
+    }
+
+    /**
+     * Returns an aspect, if it is set
+     *
+     * @param string $name
+     * @return AspectInterface
+     * @throws AspectNotFoundException
+     */
+    public function getAspect(string $name): AspectInterface
+    {
+        if (!$this->hasAspect($name)) {
+            throw new AspectNotFoundException('No aspect named "' . $name . '" found.', 1527777641);
+        }
+        return $this->aspects[$name];
+    }
+
+    /**
+     * Returns a propert yfrom the aspect, but only if the property is found.
+     *
+     * @param string $name
+     * @param string $property
+     * @param null $default
+     * @return mixed|null
+     * @throws AspectNotFoundException
+     */
+    public function getPropertyFromAspect(string $name, string $property, $default = null)
+    {
+        if (!$this->hasAspect($name)) {
+            throw new AspectNotFoundException('No aspect named "' . $name . '" found.', 1527777868);
+        }
+        try {
+            return $this->aspects[$name]->get($property);
+        } catch (AspectPropertyNotFoundException $e) {
+            return $default;
+        }
+    }
+
+    /**
+     * Sets an aspect, or overrides an existing aspect if an aspect is already set
+     *
+     * @param string $name
+     * @param AspectInterface $aspect
+     */
+    public function setAspect(string $name, AspectInterface $aspect): void
+    {
+        $this->aspects[$name] = $aspect;
+    }
+}
diff --git a/typo3/sysext/core/Classes/Context/DateTimeAspect.php b/typo3/sysext/core/Classes/Context/DateTimeAspect.php
new file mode 100644 (file)
index 0000000..684c103
--- /dev/null
@@ -0,0 +1,78 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Core\Context;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Context\Exception\AspectPropertyNotFoundException;
+
+/**
+ * The Aspect is usually available as "date.*" properties in the Context.
+ *
+ * Contains the current time + date + timezone,
+ * and needs a DateTimeImmutable object
+ *
+ * Allowed properties:
+ * - timestamp - unix timestamp number
+ * - timezone - America/Los_Angeles
+ * - iso - ISO 8601
+ * - full - the DateTime object
+ */
+class DateTimeAspect implements AspectInterface
+{
+    /**
+     * @var \DateTimeImmutable
+     */
+    protected $dateTimeObject;
+
+    /**
+     * @param \DateTimeImmutable $dateTimeImmutable
+     */
+    public function __construct(\DateTimeImmutable $dateTimeImmutable)
+    {
+        $this->dateTimeObject = $dateTimeImmutable;
+    }
+
+    /**
+     * Fetch a property of the date time object or the object itself ("full").
+     *
+     * @param string $name
+     * @return \DateTimeImmutable|string
+     * @throws AspectPropertyNotFoundException
+     */
+    public function get(string $name)
+    {
+        switch ($name) {
+            case 'timestamp':
+                return $this->dateTimeObject->format('U');
+            case 'iso':
+                return $this->dateTimeObject->format('c');
+            case 'timezone':
+                return $this->dateTimeObject->format('e');
+            case 'full':
+                return $this->dateTimeObject;
+        }
+        throw new AspectPropertyNotFoundException('Property "' . $name . '" not found in Aspect "' . __CLASS__ . '".', 1527778767);
+    }
+
+    /**
+     * Return the full date time object
+     *
+     * @return \DateTimeImmutable
+     */
+    public function getDateTime(): \DateTimeImmutable
+    {
+        return $this->dateTimeObject;
+    }
+}
diff --git a/typo3/sysext/core/Classes/Context/Exception/AspectNotFoundException.php b/typo3/sysext/core/Classes/Context/Exception/AspectNotFoundException.php
new file mode 100644 (file)
index 0000000..dd6b51b
--- /dev/null
@@ -0,0 +1,25 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Core\Context\Exception;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Exception;
+
+/**
+ * Used when an aspect is requested but not found.
+ */
+class AspectNotFoundException extends Exception
+{
+}
diff --git a/typo3/sysext/core/Classes/Context/Exception/AspectPropertyNotFoundException.php b/typo3/sysext/core/Classes/Context/Exception/AspectPropertyNotFoundException.php
new file mode 100644 (file)
index 0000000..92f13cb
--- /dev/null
@@ -0,0 +1,25 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Core\Context\Exception;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Exception;
+
+/**
+ * Used when an aspect property is requested but not found.
+ */
+class AspectPropertyNotFoundException extends Exception
+{
+}
diff --git a/typo3/sysext/core/Classes/Context/UserAspect.php b/typo3/sysext/core/Classes/Context/UserAspect.php
new file mode 100644 (file)
index 0000000..4dceabc
--- /dev/null
@@ -0,0 +1,161 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Core\Context;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Authentication\AbstractUserAuthentication;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Context\Exception\AspectPropertyNotFoundException;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
+
+/**
+ * The aspect contains information about a user.
+ * Can be used for frontend and backend users.
+ *
+ * Allowed properties:
+ * - id
+ * - username
+ * - isLoggedIn
+ * - groupIds (Array of Ids)
+ * - groupNames
+ */
+class UserAspect implements AspectInterface
+{
+    /**
+     * @var AbstractUserAuthentication
+     */
+    protected $user;
+
+    /**
+     * Alternative list of groups, usually useful for frontend logins with "magic" groups like "-1" and "-2"
+     *
+     * @var int[]
+     */
+    protected $groups;
+
+    /**
+     * @param AbstractUserAuthentication|null $user
+     * @param array|null $alternativeGroups
+     */
+    public function __construct(AbstractUserAuthentication $user = null, array $alternativeGroups = null)
+    {
+        $this->user = $user ?? (object)['user' => []];
+        $this->groups = $alternativeGroups;
+    }
+
+    /**
+     * Fetch common information about the user
+     *
+     * @param string $name
+     * @return int|bool|string|array
+     * @throws AspectPropertyNotFoundException
+     */
+    public function get(string $name)
+    {
+        switch ($name) {
+            case 'id':
+                return (int)($this->user->user[$this->user->userid_column ?? 'uid'] ?? 0);
+            case 'username':
+                return (string)($this->user->user[$this->user->username_column ?? 'username'] ?? '');
+            case 'isLoggedIn':
+                return $this->isLoggedIn();
+            case 'groupIds':
+                return $this->getGroupIds();
+            case 'groupNames':
+                return $this->getGroupNames();
+        }
+        throw new AspectPropertyNotFoundException('Property "' . $name . '" not found in Aspect "' . __CLASS__ . '".', 1529996567);
+    }
+
+    /**
+     * If a frontend user is checked, he/she also needs to have a group, otherwise it is only
+     * checked if the frontend user has a uid > 0
+     *
+     * @return bool
+     */
+    public function isLoggedIn(): bool
+    {
+        if ($this->user instanceof FrontendUserAuthentication) {
+            return ($this->user->user[$this->user->userid_column ?? 'uid'] ?? 0) > 0 && !empty($this->user->groupData['uid'] ?? null);
+        }
+        return ($this->user->user[$this->user->userid_column ?? 'uid'] ?? 0) > 0;
+    }
+
+    /**
+     * Return the groups the user is a member of
+     *
+     * For Frontend Users there are two special groups:
+     * "-1" = hide at login
+     * "-2" = show at any login
+     *
+     * @return array
+     */
+    public function getGroupIds(): array
+    {
+        $groups = [];
+        if ($this->user instanceof BackendUserAuthentication) {
+            $groups = GeneralUtility::intExplode(',', $this->user->groupList, true);
+        }
+        if ($this->user instanceof FrontendUserAuthentication) {
+            // Alternative groups are set
+            if (is_array($this->groups)) {
+                $groups = $this->groups;
+            } elseif ($this->isLoggedIn()) {
+                // If a user is logged in, always add "-2"
+                $groups = [0, -2];
+                if (!empty($this->user->groupData['uid'])) {
+                    $groups = array_merge($groups, array_map('intval', $this->user->groupData['uid']));
+                }
+            } else {
+                $groups = [0, -1];
+            }
+        }
+        return $groups;
+    }
+
+    /**
+     * Get the name of all groups, used in Fluid's IfHasRole ViewHelper
+     *
+     * @return array
+     */
+    public function getGroupNames(): array
+    {
+        $groupNames = [];
+        if ($this->user instanceof FrontendUserAuthentication) {
+            $groupNames = $this->user->groupData['title'];
+        }
+        if ($this->user instanceof BackendUserAuthentication) {
+            foreach ($this->user->userGroups as $userGroup) {
+                $groupNames[] = $userGroup['title'];
+            }
+        }
+        return $groupNames;
+    }
+
+    /**
+     * Checking if a user is logged in or a group constellation different from "0,-1"
+     *
+     * @return bool TRUE if either a login user is found OR if the group list is set to something else than '0,-1' (could be done even without a user being logged in!)
+     */
+    public function isUserOrGroupSet(): bool
+    {
+        if ($this->user instanceof FrontendUserAuthentication) {
+            $groups = $this->getGroupIds();
+            return is_array($this->user->user ?? null) || implode(',', $groups) !== '0,-1';
+        }
+        return $this->isLoggedIn();
+    }
+}
diff --git a/typo3/sysext/core/Classes/Context/VisibilityAspect.php b/typo3/sysext/core/Classes/Context/VisibilityAspect.php
new file mode 100644 (file)
index 0000000..74e901d
--- /dev/null
@@ -0,0 +1,91 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Core\Context;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Context\Exception\AspectPropertyNotFoundException;
+
+/**
+ * The aspect contains whether to show hidden pages, records (content) or even deleted records.
+ *
+ * Allowed properties:
+ * - includeHiddenPages
+ * - includeHiddenContent
+ * - includeDeletedRecords
+ */
+class VisibilityAspect implements AspectInterface
+{
+    /**
+     * @var bool
+     */
+    protected $includeHiddenPages;
+
+    /**
+     * @var bool
+     */
+    protected $includeHiddenContent;
+
+    /**
+     * @var bool
+     */
+    protected $includeDeletedRecords;
+
+    /**
+     * @param bool $includeHiddenPages whether to include hidden=1 in pages tables
+     * @param bool $includeHiddenContent whether to include hidden=1 in tables except for pages
+     * @param bool $includeDeletedRecords whether to include deleted=1 records (only for use in recycler)
+     */
+    public function __construct(bool $includeHiddenPages = false, bool $includeHiddenContent = false, bool $includeDeletedRecords = false)
+    {
+        $this->includeHiddenPages = $includeHiddenPages;
+        $this->includeHiddenContent = $includeHiddenContent;
+        $this->includeDeletedRecords = $includeDeletedRecords;
+    }
+
+    /**
+     * Fetch the values
+     *
+     * @param string $name
+     * @return int|bool
+     * @throws AspectPropertyNotFoundException
+     */
+    public function get(string $name)
+    {
+        switch ($name) {
+            case 'includeHiddenPages':
+                return $this->includeHiddenPages;
+            case 'includeHiddenContent':
+                return $this->includeHiddenContent;
+            case 'includeDeletedRecords':
+                return $this->includeDeletedRecords;
+        }
+        throw new AspectPropertyNotFoundException('Property "' . $name . '" not found in Aspect "' . __CLASS__ . '".', 1527780439);
+    }
+
+    public function includeHiddenPages(): bool
+    {
+        return $this->includeHiddenPages;
+    }
+
+    public function includeHiddenContent(): bool
+    {
+        return $this->includeHiddenContent;
+    }
+
+    public function includeDeletedRecords(): bool
+    {
+        return $this->includeDeletedRecords;
+    }
+}
diff --git a/typo3/sysext/core/Classes/Context/WorkspaceAspect.php b/typo3/sysext/core/Classes/Context/WorkspaceAspect.php
new file mode 100644 (file)
index 0000000..9325444
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Core\Context;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Context\Exception\AspectPropertyNotFoundException;
+
+/**
+ * The aspect contains the workspace ID.
+ *
+ * Allowed properties:
+ * - id
+ * - isLive
+ * - isOffline
+ */
+class WorkspaceAspect implements AspectInterface
+{
+    /**
+     * @var int
+     */
+    protected $workspaceId;
+
+    /**
+     * @param int $workspaceId
+     */
+    public function __construct(int $workspaceId = 0)
+    {
+        $this->workspaceId = $workspaceId;
+    }
+
+    /**
+     * Fetch the workspace ID, or evaluated the state if it's 'online' or 'offline'
+     *
+     * @param string $name
+     * @return int|bool
+     * @throws AspectPropertyNotFoundException
+     */
+    public function get(string $name)
+    {
+        switch ($name) {
+            case 'id':
+                return $this->workspaceId;
+            case 'isLive':
+                return $this->isLive();
+            case 'isOffline':
+                return !$this->isLive();
+        }
+        throw new AspectPropertyNotFoundException('Property "' . $name . '" not found in Aspect "' . __CLASS__ . '".', 1527779447);
+    }
+
+    /**
+     * Return the workspace ID
+     *
+     * @return int
+     */
+    public function getId(): int
+    {
+        return $this->workspaceId;
+    }
+
+    /**
+     * Return whether this is live workspace or in a custom offline workspace
+     *
+     * @return bool
+     */
+    public function isLive(): bool
+    {
+        return $this->workspaceId === 0;
+    }
+}
index dff9a09..191aeae 100644 (file)
@@ -15,8 +15,11 @@ namespace TYPO3\CMS\Core\Database\Query\Restriction;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\UserAspect;
 use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
 use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * Restriction to filter records, which are limited to the given user groups
@@ -29,11 +32,17 @@ class FrontendGroupRestriction implements QueryRestrictionInterface
     protected $frontendGroupIds;
 
     /**
-     * @param array $frontendGroupIds Normalized array with user groups of currently logged in user (typically expolded value of $GLOBALS['TSFE']->gr_list
+     * @param array $frontendGroupIds Normalized array with user groups of currently logged in user (typically found in the Frontend Context)
      */
     public function __construct(array $frontendGroupIds = null)
     {
-        $this->frontendGroupIds = $frontendGroupIds ?? explode(',', $GLOBALS['TSFE']->gr_list);
+        if ($frontendGroupIds !== null) {
+            $this->frontendGroupIds = $frontendGroupIds;
+        } else {
+            /** @var UserAspect $frontendUserAspect */
+            $frontendUserAspect = GeneralUtility::makeInstance(Context::class)->getAspect('frontend.user');
+            $this->frontendGroupIds = $frontendUserAspect->getGroupIds();
+        }
     }
 
     /**
index 35a7b4f..bcfbeda 100644 (file)
@@ -15,9 +15,11 @@ namespace TYPO3\CMS\Core\Database\Query\Restriction;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\VisibilityAspect;
 use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
 use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
-use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * A collection of restrictions to be used in frontend context.
@@ -38,11 +40,19 @@ class FrontendRestrictionContainer extends AbstractRestrictionContainer
     ];
 
     /**
+     * @var Context
+     */
+    protected $context;
+
+    /**
      * FrontendRestrictionContainer constructor.
      * Initializes the default restrictions for frontend requests
+     *
+     * @param Context $context
      */
-    public function __construct()
+    public function __construct(Context $context = null)
     {
+        $this->context = $context ?? GeneralUtility::makeInstance(Context::class);
         foreach ($this->defaultRestrictionTypes as $restrictionType) {
             $this->add($this->createRestriction($restrictionType));
         }
@@ -59,14 +69,18 @@ class FrontendRestrictionContainer extends AbstractRestrictionContainer
     public function buildExpression(array $queriedTables, ExpressionBuilder $expressionBuilder): CompositeExpression
     {
         $constraints = [];
-        /** @var TypoScriptFrontendController $typoScriptFrontendController */
-        $typoScriptFrontendController = $GLOBALS['TSFE'];
         foreach ($this->restrictions as $restriction) {
             foreach ($queriedTables as $tableAlias => $tableName) {
                 $disableRestriction = false;
                 if ($restriction instanceof HiddenRestriction) {
+                    /** @var VisibilityAspect $visibilityAspect */
+                    $visibilityAspect = $this->context->getAspect('visibility');
                     // If display of hidden records is requested, we must disable the hidden restriction.
-                    $disableRestriction = $tableName === 'pages' ? $typoScriptFrontendController->showHiddenPage : $typoScriptFrontendController->showHiddenRecords;
+                    if ($tableName === 'pages') {
+                        $disableRestriction = $visibilityAspect->includeHiddenPages();
+                    } else {
+                        $disableRestriction = $visibilityAspect->includeHiddenContent();
+                    }
                 }
                 if (!$disableRestriction) {
                     $constraints[] = $restriction->buildExpression([$tableAlias => $tableName], $expressionBuilder);
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-85389-VariousPublicPropertiesInFavorOfContextAPI.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-85389-VariousPublicPropertiesInFavorOfContextAPI.rst
new file mode 100644 (file)
index 0000000..fdb132f
--- /dev/null
@@ -0,0 +1,47 @@
+.. include:: ../../Includes.txt
+
+=======================================================================
+Deprecation: #85389 - Various public properties in favor of Context API
+=======================================================================
+
+See :issue:`85389`
+
+Description
+===========
+
+The following properties have been marked as deprecated in favor of the newly introduced Context API:
+
+* :php:`TypoScriptFrontendController->loginUser`
+* :php:`TypoScriptFrontendController->gr_list`
+* :php:`TypoScriptFrontendController->beUserLogin`
+* :php:`TypoScriptFrontendController->showHiddenPage`
+* :php:`TypoScriptFrontendController->showHiddenRecords`
+
+The Context API superseds the public properties in favor of decoupling the information from global objects.
+
+
+Impact
+======
+
+Reading or writing information on any of the public properties will trigger a deprecation entry,
+however the value is still stored and contains the same information as before.
+
+
+Affected Installations
+======================
+
+Any TYPO3 installation using extensions accessing this kind of information.
+
+
+Migration
+=========
+
+Use Context API / Aspects instead to read from this information:
+
+- :php:`$context->getPropertyFromAspect('visibility', 'includeHiddenPages')` instead of :php:`$TSFE->showHiddenPage`
+- :php:`$context->getPropertyFromAspect('visibility', 'includeHiddenContent')` instead of :php:`$TSFE->showHiddenRecords`
+- :php:`$context->getPropertyFromAspect('frontend.user', 'isLoggedIn')` instead of :php:`$TSFE->loginUser`
+- :php:`$context->getPropertyFromAspect('backend.user', 'isLoggedIn')` instead of :php:`$TSFE->beUserLogin`
+- :php:`$context->getPropertyFromAspect('frontend.user', 'groupIds')` instead of :php:`$TSFE->gr_list`
+
+.. index:: Frontend, PHP-API, PartiallyScanned
\ No newline at end of file
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-85389-ContextAPIForConsistentDataHandling.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-85389-ContextAPIForConsistentDataHandling.rst
new file mode 100644 (file)
index 0000000..eb2e799
--- /dev/null
@@ -0,0 +1,114 @@
+.. include:: ../../Includes.txt
+
+==========================================================
+Feature: #85389 - Context API for consistent data handling
+==========================================================
+
+See :issue:`85389`
+
+Description
+===========
+
+A new Context API is introduced, which encapsulates various information for data retrieval (e.g. inside
+the database) and analysis of current permissions and caching information.
+
+Previously, various information was distributed inside globally accessible objects (:php:`$TSFE` or :php:`$BE_USER`)
+like the current workspace ID or if a frontend or backend user is authenticated. Having a global object
+available was also dependent on the current request type (frontend or backend), instead of having
+one consistent place where all this data is located.
+
+The context is currently instantiated at the very beginning of each TYPO3 entry point, keeping track
+of the current time (formally known as :php:`$GLOBALS['EXEC_TIME']`, and if a user is logged in,
+and which workspace is currently accessed.
+
+This information is separated in so-called "Aspects", being responsible for a certain area:
+
+- VisibilityAspect, holding information if hidden/deleted records should be fetched from the database
+- DateTimeAspect, keeping the current date as immutable datetime object
+- UserAspect, holding frontend/backend user IDs, usernames and usergroups
+- WorkspaceAspect, holding the currently visible workspace (default to "0"/ live)
+
+Further aspects related to the current request (e.g. selected language information and fallback strategy)
+will follow, but extensions can add their own Aspects as well, as they only need to implement the AspectInterface.
+
+The Context object is used as a Singleton, available via :php:`GeneralUtility::makeInstance(Context::class)`.
+
+Adding or replacing an aspect has implications on the whole further request. The recommended way on doing
+so is done in a PSR-15 middleware. In the future (TYPO3 v10), the global context will have a "frozen" state
+after all PSR-15 middlewares are run through, to ensure a consistent object throughout all renderings
+within a backend.
+
+However, if, for a certain retrieval part a custom context is needed, the necessary PHP classes, like
+:php:`PageRepository` can receive a custom Context object. For this to work, a new Context object can be
+created via :php:`new Context()`  and or cloned from the master context via
+:php:`$myContext = clone GeneralUtility::makeInstance(Context::class);` to keep all existing aspects but only to
+override a certain aspect locally.
+
+A huge benefit when using the Context API is a strong decoupling of various architectural failures within
+TYPO3 Core, which are now "Context aware" and not depending if a certain global object is available.
+
+This will not unify the code quality, but also introduce a better standard, where hard intermingling within
+Extbase, PageRepository and TypoScriptFrontendController can be found.
+
+Impact
+======
+
+The new Context API replaces lots of places known for a very long time:
+
+* DateTimeAspect replaces :php:`$GLOBALS['SIM_EXEC_TIME']` and :php:`$GLOBALS['EXEC_TIME']`
+* VisibilityAspect replaces :php:`$GLOBALS['TSFE']->showHiddenPages` and :php:`$GLOBALS['TSFE']->showHiddenRecords`
+* WorkspaceAspect replaces :php:`$GLOBALS['BE_USER']->workspace`
+* UserAspect replaces various calls and checks on :php:`$GLOBALS['BE_USER']` and :php:`$GLOBALS['TSFE']->fe_user`
+options when only some information is needed.
+
+
+TYPO3 Core comes with the following Aspects within the global context:
+
+* date
+* frontend.user
+* backend.user
+* workspace
+* visibility
+
+Usage
+=====
+
+As for TYPO3 v9, the old properties can be used the same way as before, but will throw a deprecation warning.
+
+It is recommended to read data from the current global Context for custom extensions:
+
+.. code-block:: php
+
+    $context = GeneralUtility::makeInstance(Context::class);
+
+    // Reading the current data instead of $GLOBALS['EXEC_TIME']
+    $currentTimestamp = $context->getPropertyFromAspect('date', 'timestamp');
+
+    // Checking if a user is logged in
+    $userIsLoggedIn = $context->getPropertyFromAspect('frontend.user', 'isLoggedIn');
+
+
+Additionally, if custom DB queries need to be made, this can be solved via cloning the Context API
+
+.. code-block:: php
+
+    // Current global context
+    $context = GeneralUtility::makeInstance(Context::class);
+    $localContextWithoutFrontendUser = clone $context;
+    $localContextWithoutFrontendUser->setAspect('frontend.user', GeneralUtility::makeInstance(UserAspect::class, null);
+
+    // Fetch a page which is publically available, but not accessible when logged in
+    $sysPage = GeneralUtility::makeInstance(PageRepository::class, $localContextWithoutFrontendUser);
+    $pageRow = $sysPage->getPage($pageId);
+
+
+Further development
+===================
+
+There will be additional aspects that will be introduced in TYPO3 Core. Also see PSR-15 middlewares shipped with TYPO3
+Frontend or Backend to see how aspects can be modified.
+
+Aspects eventually will become the successor of Database Restrictions, as they contain all information
+necessary to restrict a database query.
+
+.. index:: PHP-API, ext:core
diff --git a/typo3/sysext/core/Tests/Unit/Context/ContextTest.php b/typo3/sysext/core/Tests/Unit/Context/ContextTest.php
new file mode 100644 (file)
index 0000000..b501b3a
--- /dev/null
@@ -0,0 +1,197 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Core\Tests\Unit\Context;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\Exception\AspectNotFoundException;
+use TYPO3\CMS\Core\Context\UserAspect;
+use TYPO3\CMS\Core\Context\WorkspaceAspect;
+use TYPO3\CMS\Core\Registry;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+class ContextTest extends UnitTestCase
+{
+    /**
+     * Date provider for hasAspectReturnsTrueOnExistingAspect
+     *
+     * @return array
+     */
+    public function validAspectKeysDataProvider(): array
+    {
+        return [
+            ['myfirst'],
+            ['mysecond'],
+            ['date'],
+            ['visibility'],
+            ['backend.user'],
+            ['frontend.user'],
+            ['workspace'],
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider validAspectKeysDataProvider
+     * @param string $aspectName
+     */
+    public function hasAspectReturnsTrueOnExistingAspect(string $aspectName)
+    {
+        $subject = new Context([
+            'myfirst' => new UserAspect(),
+            'mysecond' => new UserAspect(),
+        ]);
+        $this->assertTrue($subject->hasAspect($aspectName));
+    }
+
+    /**
+     * Date provider for hasAspectReturnsFalseOnNonExistingAspect
+     *
+     * @return array
+     */
+    public function invalidAspectKeysDataProvider(): array
+    {
+        return [
+            ['visible'],
+            ['frontenduser'],
+            ['compatibility'],
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider invalidAspectKeysDataProvider
+     * @param string $aspectName
+     */
+    public function hasAspectReturnsFalseOnNonExistingAspect(string $aspectName)
+    {
+        $subject = new Context([
+            'myfirst' => new UserAspect(),
+            'mysecond' => new UserAspect(),
+        ]);
+        $this->assertFalse($subject->hasAspect($aspectName));
+    }
+
+    /**
+     * @test
+     */
+    public function constructorAddsValidAspect()
+    {
+        $subject = new Context([
+            'coolio' => new UserAspect(),
+            'uncoolio' => new Registry()
+        ]);
+        $this->assertTrue($subject->hasAspect('coolio'));
+        $this->assertFalse($subject->hasAspect('uncoolio'));
+    }
+
+    /**
+     * @test
+     */
+    public function getAspectThrowsExceptionOnInvalidAspect()
+    {
+        $aspect = new UserAspect();
+        $subject = new Context([
+            'coolio' => $aspect
+        ]);
+
+        $this->expectException(AspectNotFoundException::class);
+        $subject->getAspect('uncoolio');
+    }
+
+    /**
+     * @test
+     */
+    public function getAspectReturnsValidAspect()
+    {
+        $aspect = new UserAspect();
+        $subject = new Context([
+            'coolio' => $aspect
+        ]);
+
+        $this->assertSame($aspect, $subject->getAspect('coolio'));
+    }
+
+    /**
+     * @test
+     */
+    public function invalidAspectFromgetPropertyFromAspectThrowsException()
+    {
+        $aspect = new UserAspect();
+        $subject = new Context([
+            'coolio' => $aspect
+        ]);
+
+        $this->expectException(AspectNotFoundException::class);
+        $subject->getPropertyFromAspect('uncoolio', 'does not matter');
+    }
+
+    /**
+     * @test
+     */
+    public function invalidPropertyFromgetPropertyFromAspectReturnsDefault()
+    {
+        $defaultValue = 'default value';
+        $aspect = new UserAspect();
+        $subject = new Context([
+            'coolio' => $aspect
+        ]);
+
+        $result = $subject->getPropertyFromAspect('coolio', 'unknownproperty', $defaultValue);
+        $this->assertEquals($defaultValue, $result);
+    }
+
+    /**
+     * @test
+     */
+    public function validPropertyFromgetPropertyFromAspectReturnsValue()
+    {
+        $aspect = new WorkspaceAspect(13);
+        $subject = new Context([
+            'coolio' => $aspect
+        ]);
+
+        $result = $subject->getPropertyFromAspect('coolio', 'id');
+        $this->assertEquals(13, $result);
+    }
+
+    /**
+     * @test
+     */
+    public function setAspectSetsAnAspectAndCanReturnIt()
+    {
+        $aspect = new UserAspect();
+        $subject = new Context();
+
+        $subject->setAspect('coolio', $aspect);
+        $this->assertSame($aspect, $subject->getAspect('coolio'));
+    }
+
+    /**
+     * @test
+     */
+    public function setAspectOverridesAnExisting()
+    {
+        $initialAspect = new UserAspect();
+        $aspectOverride = new UserAspect();
+        $subject = new Context([
+            'coolio' => $initialAspect
+        ]);
+
+        $subject->setAspect('coolio', $aspectOverride);
+        $this->assertNotSame($initialAspect, $subject->getAspect('coolio'));
+        $this->assertSame($aspectOverride, $subject->getAspect('coolio'));
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Context/DateTimeAspectTest.php b/typo3/sysext/core/Tests/Unit/Context/DateTimeAspectTest.php
new file mode 100644 (file)
index 0000000..2118bf1
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Core\Tests\Unit\Context;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Context\DateTimeAspect;
+use TYPO3\CMS\Core\Context\Exception\AspectPropertyNotFoundException;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+class DateTimeAspectTest extends UnitTestCase
+{
+    /**
+     * @test
+     */
+    public function getDateTimeReturnsSameObject()
+    {
+        $dateObject = new \DateTimeImmutable('2018-07-15', new \DateTimeZone('Europe/Moscow'));
+        $subject = new DateTimeAspect($dateObject);
+        $result = $subject->getDateTime();
+        $this->assertSame($dateObject, $result);
+    }
+
+    /**
+     * @test
+     */
+    public function getThrowsExceptionOnInvalidArgument()
+    {
+        $this->expectException(AspectPropertyNotFoundException::class);
+        $dateObject = new \DateTimeImmutable('2018-07-15', new \DateTimeZone('Europe/Moscow'));
+        $subject = new DateTimeAspect($dateObject);
+        $subject->get('football');
+    }
+
+    /**
+     * @return array
+     */
+    public function dateFormatValuesDataProvider()
+    {
+        return [
+            'timestamp' => [
+                'timestamp',
+                '1531648805'
+            ],
+            'iso' => [
+                'iso',
+                '2018-07-15T13:00:05+03:00'
+            ],
+            'timezone' => [
+                'timezone',
+                'Europe/Moscow'
+            ],
+            'full' => [
+                'full',
+                new \DateTimeImmutable('2018-07-15T13:00:05', new \DateTimeZone('Europe/Moscow'))
+            ],
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider dateFormatValuesDataProvider
+     * @param string $key
+     * @param string $expectedResult
+     */
+    public function getReturnsValidInformationFromProperty($key, $expectedResult)
+    {
+        $dateObject = new \DateTimeImmutable('2018-07-15T13:00:05', new \DateTimeZone('Europe/Moscow'));
+        $subject = new DateTimeAspect($dateObject);
+        $this->assertEquals($expectedResult, $subject->get($key));
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Context/UserAspectTest.php b/typo3/sysext/core/Tests/Unit/Context/UserAspectTest.php
new file mode 100644 (file)
index 0000000..1293b1d
--- /dev/null
@@ -0,0 +1,208 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Core\Tests\Unit\Context;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Context\Exception\AspectPropertyNotFoundException;
+use TYPO3\CMS\Core\Context\UserAspect;
+use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+class UserAspectTest extends UnitTestCase
+{
+    /**
+     * @test
+     */
+    public function getterReturnsProperDefaultValues()
+    {
+        $subject = new UserAspect(null, null);
+        $this->assertEquals(0, $subject->get('id'));
+        $this->assertEquals('', $subject->get('username'));
+        $this->assertFalse($subject->get('isLoggedIn'));
+        $this->assertEquals([], $subject->get('groupIds'));
+        $this->assertEquals([], $subject->get('groupNames'));
+    }
+
+    /**
+     * @test
+     */
+    public function getterReturnsValidUserId()
+    {
+        $user = new FrontendUserAuthentication();
+        $user->user = [
+            'uid' => 13
+        ];
+        $subject = new UserAspect($user);
+        $this->assertEquals(13, $subject->get('id'));
+    }
+
+    /**
+     * @test
+     */
+    public function getterReturnsValidUsername()
+    {
+        $user = new FrontendUserAuthentication();
+        $user->user = [
+            'uid' => 13,
+            'username' => 'Teddy'
+        ];
+        $subject = new UserAspect($user);
+        $this->assertEquals('Teddy', $subject->get('username'));
+    }
+
+    /**
+     * @test
+     */
+    public function isLoggedInReturnsFalseOnFrontendUserWithoutUserGroup()
+    {
+        $user = new FrontendUserAuthentication();
+        $user->user = [
+            'uid' => 13
+        ];
+        $subject = new UserAspect($user);
+        $this->assertFalse($subject->isLoggedIn());
+    }
+
+    /**
+     * @test
+     */
+    public function isLoggedInReturnsTrueOnFrontendUserWithUserGroup()
+    {
+        $user = new FrontendUserAuthentication();
+        $user->user = [
+            'uid' => 13
+        ];
+        $user->groupData['uid'] = [1, 5, 7];
+        $subject = new UserAspect($user);
+        $this->assertTrue($subject->isLoggedIn());
+    }
+
+    /**
+     * @test
+     */
+    public function isLoggedInReturnsTrueOnBackendUserWithId()
+    {
+        $user = new BackendUserAuthentication();
+        $user->user = [
+            'uid' => 13
+        ];
+        $subject = new UserAspect($user);
+        $this->assertTrue($subject->isLoggedIn());
+    }
+
+    /**
+     * @test
+     */
+    public function getGroupIdsReturnsFrontendUserGroups()
+    {
+        $user = new FrontendUserAuthentication();
+        $user->user = [
+            'uid' => 13
+        ];
+        $user->groupData['uid'] = [23, 54];
+        $subject = new UserAspect($user);
+        $this->assertEquals([0, -2, 23, 54], $subject->getGroupIds());
+    }
+
+    /**
+     * @test
+     */
+    public function getGroupIdsReturnsOverriddenGroups()
+    {
+        $user = new FrontendUserAuthentication();
+        // Not used, because overridden with 33
+        $user->groupData['uid'] = [23, 54];
+        $subject = new UserAspect($user, [33]);
+        $this->assertEquals([33], $subject->getGroupIds());
+    }
+
+    public function isUserOrGroupSetDataProvider()
+    {
+        return [
+            'Not logged in: no id or group set' => [
+                0,
+                null,
+                null,
+                false
+            ],
+            'only valid user id' => [
+                13,
+                null,
+                null,
+                true
+            ],
+            'valid user and overridden group' => [
+                13,
+                null,
+                [33],
+                true
+            ],
+            'no user and overridden group' => [
+                0,
+                null,
+                [33],
+                true
+            ],
+            'valid user, default groups and overridden group' => [
+                13,
+                [23],
+                [33],
+                true
+            ],
+            'no user, default groups and overridden group' => [
+                0,
+                [23],
+                [33],
+                true
+            ],
+            'Not logged in: no user, and classic group structure' => [
+                0,
+                null,
+                [0, -1],
+                false
+            ],
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider isUserOrGroupSetDataProvider
+     * @param $userId
+     * @param $userGroups
+     * @param $overriddenGroups
+     * @param bool $expectedResult
+     */
+    public function isUserOrGroupSetChecksForValidUser($userId, $userGroups, $overriddenGroups, $expectedResult)
+    {
+        $user = new FrontendUserAuthentication();
+        if ($userId) {
+            $user->user['uid'] = $userId;
+        }
+        $user->groupData['uid'] = $userGroups;
+        $subject = new UserAspect($user, $overriddenGroups);
+        $this->assertEquals($expectedResult, $subject->isUserOrGroupSet());
+    }
+
+    /**
+     * @test
+     */
+    public function getThrowsExceptionOnInvalidArgument()
+    {
+        $this->expectException(AspectPropertyNotFoundException::class);
+        $subject = new UserAspect();
+        $subject->get('football');
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Context/VisibilityAspectTest.php b/typo3/sysext/core/Tests/Unit/Context/VisibilityAspectTest.php
new file mode 100644 (file)
index 0000000..fc487af
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Core\Tests\Unit\Context;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Context\Exception\AspectPropertyNotFoundException;
+use TYPO3\CMS\Core\Context\VisibilityAspect;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+class VisibilityAspectTest extends UnitTestCase
+{
+    /**
+     * @test
+     */
+    public function getterReturnsProperDefaultValues()
+    {
+        $subject = new VisibilityAspect();
+        $this->assertFalse($subject->includeHiddenPages());
+        $this->assertFalse($subject->includeHiddenContent());
+        $this->assertFalse($subject->includeDeletedRecords());
+    }
+
+    /**
+     * @test
+     */
+    public function getterReturnsProperValues()
+    {
+        $subject = new VisibilityAspect(true, true, true);
+        $this->assertTrue($subject->includeHiddenPages());
+        $this->assertTrue($subject->includeHiddenContent());
+        $this->assertTrue($subject->includeDeletedRecords());
+    }
+
+    /**
+     * @test
+     */
+    public function getReturnsProperValues()
+    {
+        $subject = new VisibilityAspect(true, true, true);
+        $this->assertTrue($subject->get('includeHiddenPages'));
+        $this->assertTrue($subject->get('includeHiddenContent'));
+        $this->assertTrue($subject->get('includeDeletedRecords'));
+    }
+
+    /**
+     * @test
+     */
+    public function getThrowsExceptionOnInvalidArgument()
+    {
+        $this->expectException(AspectPropertyNotFoundException::class);
+        $subject = new VisibilityAspect();
+        $subject->get('football');
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Context/WorkspaceAspectTest.php b/typo3/sysext/core/Tests/Unit/Context/WorkspaceAspectTest.php
new file mode 100644 (file)
index 0000000..c0e6ba7
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Core\Tests\Unit\Context;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Context\Exception\AspectPropertyNotFoundException;
+use TYPO3\CMS\Core\Context\WorkspaceAspect;
+use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
+
+class WorkspaceAspectTest extends UnitTestCase
+{
+    /**
+     * @test
+     */
+    public function getterReturnsProperDefaultValues()
+    {
+        $subject = new WorkspaceAspect();
+        $this->assertTrue($subject->isLive());
+        $this->assertEquals(0, $subject->getId());
+        $this->assertEquals(0, $subject->get('id'));
+        $this->assertTrue($subject->get('isLive'));
+        $this->assertFalse($subject->get('isOffline'));
+    }
+
+    /**
+     * @test
+     */
+    public function getterReturnsProperCustomValues()
+    {
+        $subject = new WorkspaceAspect(13);
+        $this->assertEquals(13, $subject->getId());
+        $this->assertEquals(13, $subject->get('id'));
+        $this->assertFalse($subject->isLive());
+        $this->assertFalse($subject->get('isLive'));
+        $this->assertTrue($subject->get('isOffline'));
+    }
+
+    /**
+     * @test
+     */
+    public function getThrowsExceptionOnInvalidArgument()
+    {
+        $this->expectException(AspectPropertyNotFoundException::class);
+        $subject = new WorkspaceAspect();
+        $subject->get('football');
+    }
+}
index 35214f0..7109406 100644 (file)
@@ -15,11 +15,19 @@ namespace TYPO3\CMS\Core\Tests\Unit\Database\Query\Restriction;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\UserAspect;
+use TYPO3\CMS\Core\Context\VisibilityAspect;
+use TYPO3\CMS\Core\Context\WorkspaceAspect;
 use TYPO3\CMS\Core\Database\Query\Restriction\FrontendRestrictionContainer;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
 use TYPO3\CMS\Frontend\Page\PageRepository;
 
 class FrontendRestrictionContainerTest extends AbstractRestrictionTestCase
 {
+    protected $resetSingletonInstances = true;
+
     public function frontendStatesDataProvider()
     {
         return [
@@ -29,7 +37,7 @@ class FrontendRestrictionContainerTest extends AbstractRestrictionTestCase
                 'workspaceId' => 0,
                 'hiddenPagePreview' => false,
                 'hiddenRecordPreview' => false,
-                'feGroupList' => '0,-1',
+                'frontendUserGroups' => [0, -1],
                 'expectedSQL' => '("aTable"."deleted" = 0) AND (("aTable"."t3ver_state" <= 0) AND ("aTable"."pid" <> -1)) AND ("aTable"."myHiddenField" = 0) AND ("aTable"."myStartTimeField" <= 42) AND (("aTable"."myEndTimeField" = 0) OR ("aTable"."myEndTimeField" > 42)) AND (("aTable"."myGroupField" IS NULL) OR ("aTable"."myGroupField" = \'\') OR ("aTable"."myGroupField" = \'0\') OR (FIND_IN_SET(\'0\', "aTable"."myGroupField")) OR (FIND_IN_SET(\'-1\', "aTable"."myGroupField")))'
             ],
             'Live, with hidden record preview' => [
@@ -38,7 +46,7 @@ class FrontendRestrictionContainerTest extends AbstractRestrictionTestCase
                 'workspaceId' => 0,
                 'hiddenPagePreview' => true,
                 'hiddenRecordPreview' => true,
-                'feGroupList' => '0,-1',
+                'frontendUserGroups' => [0, -1],
                 'expectedSQL' => '("aTable"."deleted" = 0) AND (("aTable"."t3ver_state" <= 0) AND ("aTable"."pid" <> -1)) AND ("aTable"."myStartTimeField" <= 42) AND (("aTable"."myEndTimeField" = 0) OR ("aTable"."myEndTimeField" > 42)) AND (("aTable"."myGroupField" IS NULL) OR ("aTable"."myGroupField" = \'\') OR ("aTable"."myGroupField" = \'0\') OR (FIND_IN_SET(\'0\', "aTable"."myGroupField")) OR (FIND_IN_SET(\'-1\', "aTable"."myGroupField")))'
             ],
             'Workspace, with WS preview' => [
@@ -47,7 +55,7 @@ class FrontendRestrictionContainerTest extends AbstractRestrictionTestCase
                 'workspaceId' => 1,
                 'hiddenPagePreview' => false,
                 'hiddenRecordPreview' => false,
-                'feGroupList' => '0,-1',
+                'frontendUserGroups' => [0, -1],
                 'expectedSQL' => '("aTable"."deleted" = 0) AND ((("aTable"."t3ver_wsid" = 0) OR ("aTable"."t3ver_wsid" = 1)) AND ("aTable"."pid" <> -1)) AND ("aTable"."myHiddenField" = 0) AND ("aTable"."myStartTimeField" <= 42) AND (("aTable"."myEndTimeField" = 0) OR ("aTable"."myEndTimeField" > 42)) AND (("aTable"."myGroupField" IS NULL) OR ("aTable"."myGroupField" = \'\') OR ("aTable"."myGroupField" = \'0\') OR (FIND_IN_SET(\'0\', "aTable"."myGroupField")) OR (FIND_IN_SET(\'-1\', "aTable"."myGroupField")))'
             ],
             'Workspace, with WS preview and hidden record preview' => [
@@ -56,7 +64,7 @@ class FrontendRestrictionContainerTest extends AbstractRestrictionTestCase
                 'workspaceId' => 1,
                 'hiddenPagePreview' => true,
                 'hiddenRecordPreview' => true,
-                'feGroupList' => '0,-1',
+                'frontendUserGroups' => [0, -1],
                 'expectedSQL' => '("aTable"."deleted" = 0) AND ((("aTable"."t3ver_wsid" = 0) OR ("aTable"."t3ver_wsid" = 1)) AND ("aTable"."pid" <> -1)) AND ("aTable"."myStartTimeField" <= 42) AND (("aTable"."myEndTimeField" = 0) OR ("aTable"."myEndTimeField" > 42)) AND (("aTable"."myGroupField" IS NULL) OR ("aTable"."myGroupField" = \'\') OR ("aTable"."myGroupField" = \'0\') OR (FIND_IN_SET(\'0\', "aTable"."myGroupField")) OR (FIND_IN_SET(\'-1\', "aTable"."myGroupField")))'
             ],
             'Live page, no preview' => [
@@ -65,7 +73,7 @@ class FrontendRestrictionContainerTest extends AbstractRestrictionTestCase
                 'workspaceId' => 0,
                 'hiddenPagePreview' => false,
                 'hiddenRecordPreview' => false,
-                'feGroupList' => '0,-1',
+                'frontendUserGroups' => [0, -1],
                 'expectedSQL' => '("pages"."deleted" = 0) AND (("pages"."t3ver_state" <= 0) AND ("pages"."pid" <> -1)) AND ("pages"."hidden" = 0) AND ("pages"."starttime" <= 42) AND (("pages"."endtime" = 0) OR ("pages"."endtime" > 42)) AND (("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\') OR (FIND_IN_SET(\'0\', "pages"."fe_group")) OR (FIND_IN_SET(\'-1\', "pages"."fe_group")))'
             ],
             'Live page, with hidden page preview' => [
@@ -74,7 +82,7 @@ class FrontendRestrictionContainerTest extends AbstractRestrictionTestCase
                 'workspaceId' => 0,
                 'hiddenPagePreview' => true,
                 'hiddenRecordPreview' => true,
-                'feGroupList' => '0,-1',
+                'frontendUserGroups' => [0, -1],
                 'expectedSQL' => '("pages"."deleted" = 0) AND (("pages"."t3ver_state" <= 0) AND ("pages"."pid" <> -1)) AND ("pages"."starttime" <= 42) AND (("pages"."endtime" = 0) OR ("pages"."endtime" > 42)) AND (("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\') OR (FIND_IN_SET(\'0\', "pages"."fe_group")) OR (FIND_IN_SET(\'-1\', "pages"."fe_group")))'
             ],
             'Workspace page, with WS preview' => [
@@ -83,7 +91,7 @@ class FrontendRestrictionContainerTest extends AbstractRestrictionTestCase
                 'workspaceId' => 1,
                 'hiddenPagePreview' => false,
                 'hiddenRecordPreview' => false,
-                'feGroupList' => '0,-1',
+                'frontendUserGroups' => [0, -1],
                 'expectedSQL' => '("pages"."deleted" = 0) AND ("pages"."pid" <> -1) AND ("pages"."hidden" = 0) AND ("pages"."starttime" <= 42) AND (("pages"."endtime" = 0) OR ("pages"."endtime" > 42)) AND (("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\') OR (FIND_IN_SET(\'0\', "pages"."fe_group")) OR (FIND_IN_SET(\'-1\', "pages"."fe_group")))'
             ],
             'Workspace page, with WS preview and hidden pages preview' => [
@@ -92,7 +100,7 @@ class FrontendRestrictionContainerTest extends AbstractRestrictionTestCase
                 'workspaceId' => 1,
                 'hiddenPagePreview' => true,
                 'hiddenRecordPreview' => true,
-                'feGroupList' => '0,-1',
+                'frontendUserGroups' => [0, -1],
                 'expectedSQL' => '("pages"."deleted" = 0) AND ("pages"."pid" <> -1) AND ("pages"."starttime" <= 42) AND (("pages"."endtime" = 0) OR ("pages"."endtime" > 42)) AND (("pages"."fe_group" IS NULL) OR ("pages"."fe_group" = \'\') OR ("pages"."fe_group" = \'0\') OR (FIND_IN_SET(\'0\', "pages"."fe_group")) OR (FIND_IN_SET(\'-1\', "pages"."fe_group")))'
             ],
             'Live, no preview with alias' => [
@@ -101,7 +109,7 @@ class FrontendRestrictionContainerTest extends AbstractRestrictionTestCase
                 'workspaceId' => 0,
                 'hiddenPagePreview' => false,
                 'hiddenRecordPreview' => false,
-                'feGroupList' => '0,-1',
+                'frontendUserGroups' => [0, -1],
                 'expectedSQL' => '("a"."deleted" = 0) AND (("a"."t3ver_state" <= 0) AND ("a"."pid" <> -1)) AND ("a"."myHiddenField" = 0) AND ("a"."myStartTimeField" <= 42) AND (("a"."myEndTimeField" = 0) OR ("a"."myEndTimeField" > 42)) AND (("a"."myGroupField" IS NULL) OR ("a"."myGroupField" = \'\') OR ("a"."myGroupField" = \'0\') OR (FIND_IN_SET(\'0\', "a"."myGroupField")) OR (FIND_IN_SET(\'-1\', "a"."myGroupField")))'
             ],
         ];
@@ -113,7 +121,7 @@ class FrontendRestrictionContainerTest extends AbstractRestrictionTestCase
      * @param int $workspaceId
      * @param bool $hiddenPagePreview
      * @param bool $hiddenRecordPreview
-     * @param string $feGroupList
+     * @param array $frontendUserGroups
      * @param string $expectedSQL
      *
      * @test
@@ -125,7 +133,7 @@ class FrontendRestrictionContainerTest extends AbstractRestrictionTestCase
         int $workspaceId,
         bool $hiddenPagePreview,
         bool $hiddenRecordPreview,
-        string $feGroupList,
+        array $frontendUserGroups,
         string $expectedSQL
     ) {
         $GLOBALS['TCA'] = [
@@ -160,20 +168,22 @@ class FrontendRestrictionContainerTest extends AbstractRestrictionTestCase
                 'columns' => []
             ]
         ];
+        $context = new Context([
+            'visibility' => new VisibilityAspect($hiddenPagePreview, $hiddenRecordPreview),
+            'frontend.user' => new UserAspect(new FrontendUserAuthentication(), $frontendUserGroups),
+            'workspace' => new WorkspaceAspect($workspaceId)
+        ]);
+        GeneralUtility::setSingletonInstance(Context::class, $context);
 
         $pageRepository = $this->createMock(PageRepository::class);
+        $pageRepository->__set('context', $context);
         $pageRepository->versioningWorkspaceId = $workspaceId;
 
-        $typoScriptFrontendController = new \stdClass();
-        $typoScriptFrontendController->showHiddenPage = $hiddenPagePreview;
-        $typoScriptFrontendController->showHiddenRecords = $hiddenRecordPreview;
-        $typoScriptFrontendController->gr_list = $feGroupList;
-        $typoScriptFrontendController->sys_page = $pageRepository;
-
-        $GLOBALS['TSFE'] = $typoScriptFrontendController;
+        $GLOBALS['TSFE'] = new \stdClass();
+        $GLOBALS['TSFE']->sys_page = $pageRepository;
         $GLOBALS['SIM_ACCESS_TIME'] = 42;
 
-        $subject = new FrontendRestrictionContainer();
+        $subject = new FrontendRestrictionContainer($context);
         $expression = $subject->buildExpression([$tableAlias => $tableName], $this->expressionBuilder);
         $this->assertSame($expectedSQL, (string)$expression);
     }
index 681fbc6..2ff8871 100644 (file)
@@ -87,7 +87,7 @@ class RootlineUtilityTest extends UnitTestCase
      */
     public function isMountedPageWithoutMountPointsReturnsFalse(): void
     {
-        $this->subject->__construct(1);
+        $this->subject->__construct(1, '', $this->pageContextMock);
         $this->assertFalse($this->subject->isMountedPage());
     }
 
@@ -96,7 +96,7 @@ class RootlineUtilityTest extends UnitTestCase
      */
     public function isMountedPageWithMatchingMountPointParameterReturnsTrue(): void
     {
-        $this->subject->__construct(1, '1-99');
+        $this->subject->__construct(1, '1-99', $this->pageContextMock);
         $this->assertTrue($this->subject->isMountedPage());
     }
 
@@ -105,7 +105,7 @@ class RootlineUtilityTest extends UnitTestCase
      */
     public function isMountedPageWithNonMatchingMountPointParameterReturnsFalse(): void
     {
-        $this->subject->__construct(1, '99-99');
+        $this->subject->__construct(1, '99-99', $this->pageContextMock);
         $this->assertFalse($this->subject->isMountedPage());
     }
 
@@ -117,7 +117,7 @@ class RootlineUtilityTest extends UnitTestCase
         $this->expectException(\RuntimeException::class);
         $this->expectExceptionCode(1343464100);
 
-        $this->subject->__construct(1, '1-99');
+        $this->subject->__construct(1, '1-99', $this->pageContextMock);
         $this->subject->_call(
             'processMountedPage',
             ['uid' => 1],
@@ -130,7 +130,7 @@ class RootlineUtilityTest extends UnitTestCase
      */
     public function processMountedPageWithMountedPageNotThrowsException(): void
     {
-        $this->subject->__construct(1, '1-99');
+        $this->subject->__construct(1, '1-99', $this->pageContextMock);
         $this->assertNotEmpty($this->subject->_call(
             'processMountedPage',
             ['uid' => 1],
@@ -143,7 +143,7 @@ class RootlineUtilityTest extends UnitTestCase
      */
     public function processMountedPageWithMountedPageAddsMountedFromParameter(): void
     {
-        $this->subject->__construct(1, '1-99');
+        $this->subject->__construct(1, '1-99', $this->pageContextMock);
         $result = $this->subject->_call(
             'processMountedPage',
             ['uid' => 1],
@@ -158,7 +158,7 @@ class RootlineUtilityTest extends UnitTestCase
      */
     public function processMountedPageWithMountedPageAddsMountPointParameterToReturnValue(): void
     {
-        $this->subject->__construct(1, '1-99');
+        $this->subject->__construct(1, '1-99', $this->pageContextMock);
         $result = $this->subject->_call(
             'processMountedPage',
             ['uid' => 1],
@@ -173,7 +173,7 @@ class RootlineUtilityTest extends UnitTestCase
      */
     public function processMountedPageForMountPageIsOverlayAddsMountOLParameter(): void
     {
-        $this->subject->__construct(1, '1-99');
+        $this->subject->__construct(1, '1-99', $this->pageContextMock);
         $result = $this->subject->_call(
             'processMountedPage',
             ['uid' => 1],
@@ -188,7 +188,7 @@ class RootlineUtilityTest extends UnitTestCase
      */
     public function processMountedPageForMountPageIsOverlayAddsDataInformationAboutMountPage(): void
     {
-        $this->subject->__construct(1, '1-99');
+        $this->subject->__construct(1, '1-99', $this->pageContextMock);
         $result = $this->subject->_call('processMountedPage', ['uid' => 1], [
             'uid' => 99,
             'doktype' => PageRepository::DOKTYPE_MOUNTPOINT,
@@ -212,7 +212,7 @@ class RootlineUtilityTest extends UnitTestCase
             'mount_pid' => 1,
             'mount_pid_ol' => 0
         ];
-        $this->subject->__construct(1, '1-99');
+        $this->subject->__construct(1, '1-99', $this->pageContextMock);
         $result = $this->subject->_call('processMountedPage', ['uid' => 1], $mountPointPageData);
         $this->assertIsSubset($mountPointPageData, $result);
     }
index 12e76a6..6822dac 100644 (file)
@@ -32,11 +32,16 @@ class Typo3DbBackendTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
      */
     protected static $suppressNotices = true;
 
+    /**
+     * Due to nested PageRepository / FrontendRestriction Container issues, the Context object is set
+     * @var bool
+     */
+    protected $resetSingletonInstances = true;
+
     public function setUp()
     {
         parent::setUp();
         $GLOBALS['TSFE'] = new \stdClass();
-        $GLOBALS['TSFE']->gr_list = '';
     }
 
     /**
@@ -151,6 +156,7 @@ class Typo3DbBackendTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
         /** @var $pageRepositoryMock \TYPO3\CMS\Frontend\Page\PageRepository|\PHPUnit_Framework_MockObject_MockObject */
         $pageRepositoryMock = $this->getMockBuilder(\TYPO3\CMS\Frontend\Page\PageRepository::class)
             ->setMethods(['movePlhOL', 'getWorkspaceVersionOfRecord'])
+            ->disableOriginalConstructor()
             ->getMock();
         $pageRepositoryMock->expects($this->once())->method('getWorkspaceVersionOfRecord')->with($workspaceUid, 'tx_foo', '42')->will($this->returnValue($workspaceVersion));
         $mockTypo3DbBackend = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbBackend::class, ['dummy'], [], '', false);
index 42287a8..5a00003 100644 (file)
@@ -15,6 +15,7 @@ namespace TYPO3\CMS\Extbase\Tests\Unit\Persistence\Generic\Storage;
  */
 
 use Prophecy\Argument;
+use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
@@ -31,6 +32,7 @@ use TYPO3\CMS\Extbase\Persistence\Generic\Qom\OrInterface;
 use TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface;
 use TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser;
 use TYPO3\CMS\Extbase\Persistence\QueryInterface;
+use TYPO3\CMS\Frontend\Page\PageRepository;
 
 class Typo3DbQueryParserTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
 {
@@ -632,7 +634,7 @@ class Typo3DbQueryParserTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestC
             'delete' => 'deleted_column'
         ];
         $GLOBALS['TSFE'] = new \stdClass();
-        $GLOBALS['TSFE']->sys_page = new \TYPO3\CMS\Frontend\Page\PageRepository();
+        $GLOBALS['TSFE']->sys_page = new PageRepository(new Context());
         $GLOBALS['SIM_ACCESS_TIME'] = 123456789;
 
         $connectionProphet = $this->prophesize(Connection::class);
@@ -699,7 +701,7 @@ class Typo3DbQueryParserTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestC
             'delete' => 'deleted_column'
         ];
         $GLOBALS['TSFE'] = new \stdClass();
-        $GLOBALS['TSFE']->sys_page = new \TYPO3\CMS\Frontend\Page\PageRepository();
+        $GLOBALS['TSFE']->sys_page = new PageRepository(new Context());
         $GLOBALS['SIM_ACCESS_TIME'] = 123456789;
 
         $connectionProphet = $this->prophesize(Connection::class);
index bdc5b0e..cf4a34e 100644 (file)
@@ -44,10 +44,15 @@ class ExtensionServiceTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCas
      */
     protected $extensionService;
 
+    /**
+     * Due to nested PageRepository / FrontendRestriction Container issues, the Context object is set
+     * @var bool
+     */
+    protected $resetSingletonInstances = true;
+
     protected function setUp()
     {
         $GLOBALS['TSFE'] = new \stdClass();
-        $GLOBALS['TSFE']->gr_list = '';
         $this->extensionService = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Service\ExtensionService::class, ['dummy']);
         $this->mockConfigurationManager = $this->createMock(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::class);
         $this->extensionService->_set('configurationManager', $this->mockConfigurationManager);
index 00f5150..223ce11 100644 (file)
@@ -17,6 +17,7 @@ namespace TYPO3\CMS\Felogin\Controller;
 use Psr\Log\LoggerAwareInterface;
 use Psr\Log\LoggerAwareTrait;
 use TYPO3\CMS\Core\Authentication\LoginType;
+use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\Crypto\Random;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
@@ -153,7 +154,7 @@ class FrontendLoginController extends AbstractPlugin implements LoggerAwareInter
             $this->template = file_get_contents($template);
         }
         // Is user logged in?
-        $this->userIsLoggedIn = $this->frontendController->loginUser;
+        $this->userIsLoggedIn = GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('frontend.user', 'isLoggedIn');
         // Redirect
         if ($this->conf['redirectMode'] && !$this->conf['redirectDisable'] && !$this->noRedirect) {
             $redirectUrl = $this->processRedirect();
@@ -659,7 +660,7 @@ class FrontendLoginController extends AbstractPlugin implements LoggerAwareInter
         if ($this->conf['redirectMode']) {
             $redirectMethods = GeneralUtility::trimExplode(',', $this->conf['redirectMode'], true);
             foreach ($redirectMethods as $redirMethod) {
-                if ($this->frontendController->loginUser && $this->logintype === LoginType::LOGIN) {
+                if ($this->userIsLoggedIn && $this->logintype === LoginType::LOGIN) {
                     // Logintype is needed because the login-page wouldn't be accessible anymore after a login (would always redirect)
                     switch ($redirMethod) {
                         case 'groupLogin':
@@ -785,7 +786,7 @@ class FrontendLoginController extends AbstractPlugin implements LoggerAwareInter
                         'linkAccessRestrictedPages' => true
                     ]);
                     $redirect_url[] = $this->cObj->lastTypoLinkUrl;
-                } elseif ($this->logintype == '' && $redirMethod === 'logout' && $this->conf['redirectPageLogout'] && $this->frontendController->loginUser) {
+                } elseif ($this->logintype == '' && $redirMethod === 'logout' && $this->conf['redirectPageLogout'] && $this->userIsLoggedIn) {
                     // If logout and page not accessible
                     $redirect_url[] = $this->pi_getPageLink((int)$this->conf['redirectPageLogout']);
                 } elseif ($this->logintype === LoginType::LOGOUT) {
index 1db65eb..3603104 100644 (file)
@@ -62,7 +62,6 @@ class FrontendLoginControllerTest extends \TYPO3\TestingFramework\Core\Unit\Unit
     protected function setUp()
     {
         $GLOBALS['TSFE'] = new \stdClass();
-        $GLOBALS['TSFE']->gr_list = '0,-1';
         $this->testTableName = 'sys_domain';
         $this->testHostName = 'hostname.tld';
         $this->testSitePath = '/';
@@ -514,7 +513,7 @@ class FrontendLoginControllerTest extends \TYPO3\TestingFramework\Core\Unit\Unit
         $this->accessibleFixture->_set('referer', 'http://www.example.com/snafu');
         /** @var TypoScriptFrontendController $tsfe */
         $tsfe = $this->accessibleFixture->_get('frontendController');
-        $tsfe->loginUser = true;
+        $this->accessibleFixture->_set('userIsLoggedIn', true);
         $this->assertSame(['http://www.example.com/snafu'], $this->accessibleFixture->_call('processRedirect'));
     }
 }
index 8f3b70a..c8c8f66 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Fluid\ViewHelpers\Security;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractConditionViewHelper;
 
 /**
@@ -57,6 +59,6 @@ class IfAuthenticatedViewHelper extends AbstractConditionViewHelper
      */
     protected static function evaluateCondition($arguments = null)
     {
-        return isset($GLOBALS['TSFE']) && $GLOBALS['TSFE']->loginUser;
+        return GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('frontend.user', 'id', 0) > 0;
     }
 }
index 704702b..179920d 100644 (file)
@@ -14,6 +14,9 @@ namespace TYPO3\CMS\Fluid\ViewHelpers\Security;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\UserAspect;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractConditionViewHelper;
 
 /**
@@ -77,12 +80,15 @@ class IfHasRoleViewHelper extends AbstractConditionViewHelper
     protected static function evaluateCondition($arguments = null)
     {
         $role = $arguments['role'];
-        if (!isset($GLOBALS['TSFE']) || !$GLOBALS['TSFE']->loginUser) {
+        /** @var UserAspect $userAspect */
+        $userAspect = GeneralUtility::makeInstance(Context::class)->getAspect('frontend.user');
+        if (!$userAspect->isLoggedIn()) {
             return false;
         }
         if (is_numeric($role)) {
-            return is_array($GLOBALS['TSFE']->fe_user->groupData['uid']) && in_array($role, $GLOBALS['TSFE']->fe_user->groupData['uid']);
+            $groupIds = $userAspect->getGroupIds();
+            return in_array((int)$role, $groupIds, true);
         }
-        return is_array($GLOBALS['TSFE']->fe_user->groupData['title']) && in_array($role, $GLOBALS['TSFE']->fe_user->groupData['title']);
+        return in_array($role, $userAspect->getGroupNames(), true);
     }
 }
index 16b87fc..6aca2c9 100644 (file)
@@ -14,7 +14,11 @@ namespace TYPO3\CMS\Fluid\Tests\Unit\ViewHelpers\Security;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\UserAspect;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Fluid\ViewHelpers\Security\IfAuthenticatedViewHelper;
+use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
 use TYPO3\TestingFramework\Fluid\Unit\ViewHelpers\ViewHelperBaseTestcase;
 use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
 
@@ -28,10 +32,15 @@ class IfAuthenticatedViewHelperTest extends ViewHelperBaseTestcase
      */
     protected $viewHelper;
 
+    /**
+     * @var Context
+     */
+    protected $context;
+
     protected function setUp()
     {
         parent::setUp();
-        $GLOBALS['TSFE'] = new \stdClass();
+        $this->context = GeneralUtility::makeInstance(Context::class);
         $this->viewHelper = new IfAuthenticatedViewHelper();
         $this->injectDependenciesIntoViewHelper($this->viewHelper);
         $this->viewHelper->initializeArguments();
@@ -39,7 +48,7 @@ class IfAuthenticatedViewHelperTest extends ViewHelperBaseTestcase
 
     protected function tearDown()
     {
-        unset($GLOBALS['TSFE']);
+        GeneralUtility::removeSingletonInstance(Context::class, $this->context);
     }
 
     /**
@@ -47,7 +56,9 @@ class IfAuthenticatedViewHelperTest extends ViewHelperBaseTestcase
      */
     public function viewHelperRendersThenChildIfFeUserIsLoggedIn()
     {
-        $GLOBALS['TSFE']->loginUser = 1;
+        $user = new FrontendUserAuthentication();
+        $user->user['uid'] = 13;
+        $this->context->setAspect('frontend.user', new UserAspect($user));
 
         $actualResult = $this->viewHelper->renderStatic(
             ['then' => 'then child', 'else' => 'else child'],
@@ -64,7 +75,7 @@ class IfAuthenticatedViewHelperTest extends ViewHelperBaseTestcase
      */
     public function viewHelperRendersElseChildIfFeUserIsNotLoggedIn()
     {
-        $GLOBALS['TSFE']->loginUser = 0;
+        $this->context->setAspect('frontend.user', new UserAspect());
 
         $actualResult = $this->viewHelper->renderStatic(
             ['then' => 'then child', 'else' => 'else child'],
index 3b95df9..728b9d6 100644 (file)
@@ -14,7 +14,11 @@ namespace TYPO3\CMS\Fluid\Tests\Unit\ViewHelpers\Security;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\UserAspect;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Fluid\ViewHelpers\Security\IfHasRoleViewHelper;
+use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
 use TYPO3\TestingFramework\Fluid\Unit\ViewHelpers\ViewHelperBaseTestcase;
 use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
 
@@ -28,16 +32,22 @@ class IfHasRoleViewHelperTest extends ViewHelperBaseTestcase
      */
     protected $viewHelper;
 
+    /**
+     * @var Context
+     */
+    protected $context;
+
     protected function setUp()
     {
         parent::setUp();
-        $GLOBALS['TSFE'] = new \stdClass();
-        $GLOBALS['TSFE']->loginUser = 1;
-        $GLOBALS['TSFE']->fe_user = new \stdClass();
-        $GLOBALS['TSFE']->fe_user->groupData = [
+        $this->context = GeneralUtility::makeInstance(Context::class);
+        $user = new FrontendUserAuthentication();
+        $user->user['uid'] = 13;
+        $user->groupData = [
             'uid' => [1, 2],
             'title' => ['Editor', 'OtherRole']
         ];
+        $this->context->setAspect('frontend.user', new UserAspect($user, [1, 2]));
         $this->viewHelper = new IfHasRoleViewHelper();
         $this->injectDependenciesIntoViewHelper($this->viewHelper);
         $this->viewHelper->initializeArguments();
@@ -45,7 +55,7 @@ class IfHasRoleViewHelperTest extends ViewHelperBaseTestcase
 
     protected function tearDown()
     {
-        unset($GLOBALS['TSFE']);
+        GeneralUtility::removeSingletonInstance(Context::class, $this->context);
     }
 
     /**
index c3db655..26cf36a 100644 (file)
@@ -17,6 +17,8 @@ namespace TYPO3\CMS\Frontend\Configuration\TypoScript\ConditionMatching;
 
 use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Core\Configuration\TypoScript\ConditionMatching\AbstractConditionMatcher;
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\UserAspect;
 use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
@@ -29,6 +31,19 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 class ConditionMatcher extends AbstractConditionMatcher
 {
     /**
+     * @var Context
+     */
+    protected $context;
+
+    /**
+     * @param Context $context optional context to fetch data from
+     */
+    public function __construct(Context $context = null)
+    {
+        $this->context = $context ?? GeneralUtility::makeInstance(Context::class);
+    }
+
+    /**
      * Evaluates a TypoScript condition given as input,
      * eg. "[browser=net][...(other conditions)...]"
      *
@@ -206,7 +221,9 @@ class ConditionMatcher extends AbstractConditionMatcher
      */
     protected function getGroupList()
     {
-        return $this->getTypoScriptFrontendController()->gr_list;
+        /** @var UserAspect $userAspect */
+        $userAspect = $this->context->getAspect('frontend.user');
+        return implode(',', $userAspect->getGroupIds());
     }
 
     /**
@@ -246,7 +263,8 @@ class ConditionMatcher extends AbstractConditionMatcher
      */
     protected function getUserId()
     {
-        return $this->getTypoScriptFrontendController()->fe_user->user['uid'];
+        $userAspect = $this->context->getAspect('frontend.user');
+        return $userAspect->get('id');
     }
 
     /**
@@ -256,7 +274,9 @@ class ConditionMatcher extends AbstractConditionMatcher
      */
     protected function isUserLoggedIn()
     {
-        return (bool)$this->getTypoScriptFrontendController()->loginUser;
+        /** @var UserAspect $userAspect */
+        $userAspect = $this->context->getAspect('frontend.user');
+        return $userAspect->isLoggedIn();
     }
 
     /**
index 94972f0..e80cd19 100644 (file)
@@ -19,6 +19,7 @@ use Doctrine\DBAL\Driver\Statement;
 use Psr\Log\LoggerAwareInterface;
 use Psr\Log\LoggerAwareTrait;
 use TYPO3\CMS\Core\Cache\CacheManager;
+use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
@@ -6047,9 +6048,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
      */
     public function enableFields($table, $show_hidden = false, array $ignore_array = [])
     {
-        $tsfe = $this->getTypoScriptFrontendController();
-        $show_hidden = $show_hidden ?: ($table === 'pages' ? $tsfe->showHiddenPage : $tsfe->showHiddenRecords);
-        return $tsfe->sys_page->enableFields($table, (bool)$show_hidden, $ignore_array);
+        return $this->getTypoScriptFrontendController()->sys_page->enableFields($table, $show_hidden ? true : -1, $ignore_array);
     }
 
     /**
@@ -6106,7 +6105,7 @@ class ContentObjectRenderer implements LoggerAwareInterface
                 $addSelectFields,
                 $moreWhereClauses,
                 $prevId_array,
-                $tsfe->gr_list
+                GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('frontend.user', 'groupIds', [0, -1])
             ];
             $requestHash = md5(serialize($parameters));
             $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
index 166abc4..ea41a84 100644 (file)
@@ -24,6 +24,11 @@ use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Charset\CharsetConverter;
 use TYPO3\CMS\Core\Charset\UnknownCharsetException;
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\DateTimeAspect;
+use TYPO3\CMS\Core\Context\UserAspect;
+use TYPO3\CMS\Core\Context\VisibilityAspect;
+use TYPO3\CMS\Core\Context\WorkspaceAspect;
 use TYPO3\CMS\Core\Controller\ErrorPageController;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Database\ConnectionPool;
@@ -212,22 +217,25 @@ class TypoScriptFrontendController implements LoggerAwareInterface
      * a user really IS logged in. The group-list may show other groups (like added
      * by IP filter or so) even though there is no user.
      * @var bool
+     * @deprecated since TYPO3 v9.4, will be removed in TYPO3 v10.0. User the information within the context "frontend.user" aspect.
      */
-    public $loginUser = false;
+    protected $loginUser = false;
 
     /**
      * (RO=readonly) The group list, sorted numerically. Group '0,-1' is the default
      * group, but other groups may be added by other means than a user being logged
      * in though...
      * @var string
+     * @deprecated since TYPO3 v9.4, will be removed in TYPO3 v10.0. User the information within the context "frontend.user" aspect.
      */
-    public $gr_list = '';
+    protected $gr_list = '';
 
     /**
      * Flag that indicates if a backend user is logged in!
      * @var bool
+     * @deprecated since TYPO3 v9.4, will be removed in TYPO3 v10.0. User the information within the context "backend.user" aspect.
      */
-    public $beUserLogin = false;
+    protected $beUserLogin = false;
 
     /**
      * Integer, that indicates which workspace is being previewed.
@@ -266,16 +274,18 @@ class TypoScriptFrontendController implements LoggerAwareInterface
      * Flag indicating that hidden pages should be shown, selected and so on. This
      * goes for almost all selection of pages!
      * @var bool
+     * @deprecated since TYPO3 v9.4, will be removed in TYPO3 v10.0. User the information within the context "visibility" aspect.
      */
-    public $showHiddenPage = false;
+    protected $showHiddenPage = false;
 
     /**
      * Flag indicating that hidden records should be shown. This includes
      * sys_template and even fe_groups in addition to all
      * other regular content. So in effect, this includes everything except pages.
      * @var bool
+     * @deprecated since TYPO3 v9.4, will be removed in TYPO3 v10.0. User the information within the context "visibility" aspect.
      */
-    public $showHiddenRecords = false;
+    protected $showHiddenRecords = false;
 
     /**
      * Value that contains the simulated usergroup if any
@@ -774,6 +784,14 @@ class TypoScriptFrontendController implements LoggerAwareInterface
     protected $requestedId;
 
     /**
+     * The context for keeping the current state, mostly related to current page information,
+     * backend user / frontend user access, workspaceId
+     *
+     * @var Context
+     */
+    protected $context;
+
+    /**
      * Class constructor
      * Takes a number of GET/POST input variable as arguments and stores them internally.
      * The processing of these variables goes on later in this class.
@@ -814,6 +832,8 @@ class TypoScriptFrontendController implements LoggerAwareInterface
         }
         $this->cacheHash = GeneralUtility::makeInstance(CacheHashCalculator::class);
         $this->initCaches();
+        // Use the global context for now
+        $this->context = GeneralUtility::makeInstance(Context::class);
     }
 
     /**
@@ -926,43 +946,49 @@ class TypoScriptFrontendController implements LoggerAwareInterface
      */
     public function initUserGroups()
     {
+        $userGroups = [0];
         // This affects the hidden-flag selecting the fe_groups for the user!
         $this->fe_user->showHiddenRecords = $this->showHiddenRecords;
         // no matter if we have an active user we try to fetch matching groups which can be set without an user (simulation for instance!)
         $this->fe_user->fetchGroupData();
-        if (is_array($this->fe_user->user) && !empty($this->fe_user->groupData['uid'])) {
+        $isUserAndGroupSet = is_array($this->fe_user->user) && !empty($this->fe_user->groupData['uid']);
+        if ($isUserAndGroupSet) {
             // global flag!
             $this->loginUser = true;
-            // group -2 is not an existing group, but denotes a 'default' group when a user IS logged in. This is used to let elements be shown for all logged in users!
-            $this->gr_list = '0,-2';
-            $gr_array = $this->fe_user->groupData['uid'];
+            // group -2 is not an existing group, but denotes a 'default' group when a user IS logged in.
+            // This is used to let elements be shown for all logged in users!
+            $userGroups[] = -2;
+            $groupsFromUserRecord = $this->fe_user->groupData['uid'];
         } else {
             $this->loginUser = false;
-            // group -1 is not an existing group, but denotes a 'default' group when not logged in. This is used to let elements be hidden, when a user is logged in!
-            $this->gr_list = '0,-1';
+            // group -1 is not an existing group, but denotes a 'default' group when not logged in.
+            // This is used to let elements be hidden, when a user is logged in!
+            $userGroups[] = -1;
             if ($this->loginAllowedInBranch) {
                 // For cases where logins are not banned from a branch usergroups can be set based on IP masks so we should add the usergroups uids.
-                $gr_array = $this->fe_user->groupData['uid'];
+                $groupsFromUserRecord = $this->fe_user->groupData['uid'];
             } else {
                 // Set to blank since we will NOT risk any groups being set when no logins are allowed!
-                $gr_array = [];
+                $groupsFromUserRecord = [];
             }
         }
         // Clean up.
-        // Make unique...
-        $gr_array = array_unique($gr_array);
-        // sort
-        sort($gr_array);
-        if (!empty($gr_array) && !$this->loginAllowedInBranch_mode) {
-            $this->gr_list .= ',' . implode(',', $gr_array);
+        // Make unique and sort the groups
+        $groupsFromUserRecord = array_unique($groupsFromUserRecord);
+        if (!empty($groupsFromUserRecord) && !$this->loginAllowedInBranch_mode) {
+            sort($groupsFromUserRecord);
+            $userGroups = array_merge($userGroups, array_map('intval', $groupsFromUserRecord));
         }
 
+        $this->gr_list = implode(',', $userGroups);
+        $this->context->setAspect('frontend.user', GeneralUtility::makeInstance(UserAspect::class, $this->fe_user, $userGroups));
+
         // For every 60 seconds the is_online timestamp for a logged-in user is updated
-        if ($this->loginUser) {
+        if ($isUserAndGroupSet) {
             $this->fe_user->updateOnlineTimestamp();
         }
 
-        $this->logger->debug('Valid usergroups for TSFE: ' . $this->gr_list);
+        $this->logger->debug('Valid usergroups for TSFE: ' . implode(',', $userGroups));
     }
 
     /**
@@ -972,7 +998,9 @@ class TypoScriptFrontendController implements LoggerAwareInterface
      */
     public function isUserOrGroupSet()
     {
-        return is_array($this->fe_user->user) || $this->gr_list !== '0,-1';
+        /** @var UserAspect $userAspect */
+        $userAspect = $this->context->getAspect('frontend.user');
+        return $userAspect->isUserOrGroupSet();
     }
 
     /**
@@ -1002,11 +1030,15 @@ class TypoScriptFrontendController implements LoggerAwareInterface
      */
     public function clear_preview()
     {
-        $this->showHiddenPage = false;
-        $this->showHiddenRecords = false;
-        $GLOBALS['SIM_EXEC_TIME'] = $GLOBALS['EXEC_TIME'];
-        $GLOBALS['SIM_ACCESS_TIME'] = $GLOBALS['ACCESS_TIME'];
-        $this->fePreview = 0;
+        if ($this->fePreview || $GLOBALS['EXEC_TIME'] !== $GLOBALS['SIM_EXEC_TIME'] || $this->showHiddenPage || $this->showHiddenRecords) {
+            $this->showHiddenPage = false;
+            $this->showHiddenRecords = false;
+            $GLOBALS['SIM_EXEC_TIME'] = $GLOBALS['EXEC_TIME'];
+            $GLOBALS['SIM_ACCESS_TIME'] = $GLOBALS['ACCESS_TIME'];
+            $this->fePreview = 0;
+            $this->context->setAspect('date', GeneralUtility::makeInstance(DateTimeAspect::class, new \DateTimeImmutable('@' . $GLOBALS['SIM_EXEC_TIME'])));
+            $this->context->setAspect('visibility', GeneralUtility::makeInstance(VisibilityAspect::class));
+        }
     }
 
     /**
@@ -1016,7 +1048,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
      */
     public function isBackendUserLoggedIn()
     {
-        return (bool)$this->beUserLogin;
+        return (bool)$this->context->getPropertyFromAspect('backend.user', 'isLoggedIn', false);
     }
 
     /**
@@ -1066,6 +1098,8 @@ class TypoScriptFrontendController implements LoggerAwareInterface
         foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/index_ts.php']['postBeUser'] ?? [] as $_funcRef) {
             GeneralUtility::callUserFunction($_funcRef, $_params, $this);
         }
+        $this->context->setAspect('backend.user', GeneralUtility::makeInstance(UserAspect::class, $backendUserObject));
+        $this->context->setAspect('workspace', GeneralUtility::makeInstance(WorkspaceAspect::class, $backendUserObject ? $backendUserObject->workspace : 0));
         return $backendUserObject;
     }
 
@@ -1091,7 +1125,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
         // Now, get the id, validate access etc:
         $this->fetch_the_id();
         // Check if backend user has read access to this page. If not, recalculate the id.
-        if ($this->beUserLogin && $this->fePreview && !$this->getBackendUser()->doesUserHaveAccess($this->page, Permission::PAGE_SHOW)) {
+        if ($this->isBackendUserLoggedIn() && $this->fePreview && !$this->getBackendUser()->doesUserHaveAccess($this->page, Permission::PAGE_SHOW)) {
             // Resetting
             $this->clear_preview();
             $this->fe_user->user[$this->fe_user->usergroup_column] = $originalFrontendUserGroups;
@@ -1107,10 +1141,12 @@ class TypoScriptFrontendController implements LoggerAwareInterface
                 if ($this->loginAllowedInBranch_mode === 'all') {
                     // Clear out user and group:
                     $this->fe_user->hideActiveLogin();
-                    $this->gr_list = '0,-1';
+                    $userGroups = [0, -1];
                 } else {
-                    $this->gr_list = '0,-2';
+                    $userGroups = [0, -2];
                 }
+                $this->gr_list = implode(',', $userGroups);
+                $this->context->setAspect('frontend.user', GeneralUtility::makeInstance(UserAspect::class, $this->fe_user, $userGroups));
                 // Fetching the id again, now with the preview settings reset.
                 $this->fetch_the_id();
             }
@@ -1153,6 +1189,10 @@ class TypoScriptFrontendController implements LoggerAwareInterface
         // The preview flag is set if the current page turns out to be hidden
         if ($this->id && $this->determineIdIsHiddenPage()) {
             $this->fePreview = 1;
+            /** @var VisibilityAspect $aspect */
+            $aspect = $this->context->getAspect('visibility');
+            $newAspect = GeneralUtility::makeInstance(VisibilityAspect::class, true, $aspect->includeHiddenContent(), $aspect->includeDeletedRecords());
+            $this->context->setAspect('visibility', $newAspect);
             $this->showHiddenPage = true;
         }
         // The preview flag will be set if an offline workspace will be previewed
@@ -1192,8 +1232,10 @@ class TypoScriptFrontendController implements LoggerAwareInterface
 
         if ($this->whichWorkspace() > 0) {
             // Fetch overlay of page if in workspace and check if it is hidden
-            $pageSelectObject = GeneralUtility::makeInstance(PageRepository::class);
-            $pageSelectObject->init(false);
+            $customContext = clone $this->context;
+            $customContext->setAspect('workspace', GeneralUtility::makeInstance(WorkspaceAspect::class, $this->whichWorkspace()));
+            $customContext->setAspect('visibility', GeneralUtility::makeInstance(VisibilityAspect::class));
+            $pageSelectObject = GeneralUtility::makeInstance(PageRepository::class, $customContext);
             $targetPage = $pageSelectObject->getWorkspaceVersionOfRecord($this->whichWorkspace(), 'pages', $page['uid']);
             $result = $targetPage === -1 || $targetPage === -2;
         } else {
@@ -1269,9 +1311,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
         $timeTracker = $this->getTimeTracker();
         $timeTracker->push('fetch_the_id initialize/');
         // Initialize the page-select functions.
-        $this->sys_page = GeneralUtility::makeInstance(PageRepository::class);
-        $this->sys_page->versioningWorkspaceId = $this->whichWorkspace();
-        $this->sys_page->init($this->showHiddenPage);
+        $this->sys_page = GeneralUtility::makeInstance(PageRepository::class, $this->context);
         // Set the valid usergroups for FE
         $this->initUserGroups();
         // Sets sys_page where-clause
@@ -1570,7 +1610,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
 
             if ($this->rootLine[$a]['doktype'] == PageRepository::DOKTYPE_BE_USER_SECTION) {
                 // If there is a backend user logged in, check if he has read access to the page:
-                if ($this->beUserLogin) {
+                if ($this->isBackendUserLoggedIn()) {
                     $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
                         ->getQueryBuilderForTable('pages');
 
@@ -1647,7 +1687,9 @@ class TypoScriptFrontendController implements LoggerAwareInterface
     public function checkPageGroupAccess($row, $groupList = null)
     {
         if ($groupList === null) {
-            $groupList = $this->gr_list;
+            /** @var UserAspect $userAspect */
+            $userAspect = $this->context->getAspect('frontend.user');
+            $groupList = $userAspect->getGroupIds();
         }
         if (!is_array($groupList)) {
             $groupList = explode(',', $groupList);
@@ -2129,9 +2171,9 @@ class TypoScriptFrontendController implements LoggerAwareInterface
     public function initTemplate()
     {
         $this->tmpl = GeneralUtility::makeInstance(TemplateService::class);
-        $this->tmpl->setVerbose((bool)$this->beUserLogin);
+        $this->tmpl->setVerbose($this->isBackendUserLoggedIn());
         $this->tmpl->init();
-        $this->tmpl->tt_track = (bool)$this->beUserLogin;
+        $this->tmpl->tt_track = $this->isBackendUserLoggedIn();
     }
 
     /**
@@ -2279,7 +2321,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
     public function headerNoCache()
     {
         $disableAcquireCacheData = false;
-        if ($this->beUserLogin) {
+        if ($this->isBackendUserLoggedIn()) {
             if (strtolower($_SERVER['HTTP_CACHE_CONTROL']) === 'no-cache' || strtolower($_SERVER['HTTP_PRAGMA']) === 'no-cache') {
                 $disableAcquireCacheData = true;
             }
@@ -2333,10 +2375,14 @@ class TypoScriptFrontendController implements LoggerAwareInterface
         // Ensure the language base is used for the hash base calculation as well, otherwise TypoScript and page-related rendering
         // is not cached properly as we don't have any language-specific conditions anymore
         $siteBase = $this->getCurrentSiteLanguage() ? $this->getCurrentSiteLanguage()->getBase() : '';
+
+        // Fetch the list of user groups
+        /** @var UserAspect $userAspect */
+        $userAspect = $this->context->getAspect('frontend.user');
         $hashParameters = [
             'id' => (int)$this->id,
             'type' => (int)$this->type,
-            'gr_list' => (string)$this->gr_list,
+            'gr_list' => (string)implode(',', $userAspect->getGroupIds()),
             'MP' => (string)$this->MP,
             'siteBase' => $siteBase,
             'cHash' => $this->cHash_array,
@@ -3721,7 +3767,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
         // This variable will be TRUE unless cache headers are configured to be sent ONLY if a branch does not allow logins and logins turns out to be allowed anyway...
         $loginsDeniedCfg = empty($this->config['config']['sendCacheHeaders_onlyWhenLoginDeniedInBranch']) || empty($this->loginAllowedInBranch);
         // Finally, when backend users are logged in, do not send cache headers at all (Admin Panel might be displayed for instance).
-        if ($doCache && !$this->beUserLogin && !$this->doWorkspacePreview() && $loginsDeniedCfg) {
+        if ($doCache && !$this->isBackendUserLoggedIn() && !$this->doWorkspacePreview() && $loginsDeniedCfg) {
             // Build headers:
             $headers = [
                 'Expires: ' . gmdate('D, d M Y H:i:s T', $this->cacheExpires),
@@ -3739,7 +3785,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
             ];
             $this->isClientCachable = false;
             // Now, if a backend user is logged in, tell him in the Admin Panel log what the caching status would have been:
-            if ($this->beUserLogin) {
+            if ($this->isBackendUserLoggedIn()) {
                 if ($doCache) {
                     $this->getTimeTracker()->setTSlogMessage('Cache-headers with max-age "' . ($this->cacheExpires - $GLOBALS['EXEC_TIME']) . '" would have been sent');
                 } else {
@@ -3965,7 +4011,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
      */
     public function doWorkspacePreview()
     {
-        return $this->whichWorkspace() > 0;
+        return $this->context->getPropertyFromAspect('workspace', 'isOffline', false);
     }
 
     /**
@@ -3973,9 +4019,9 @@ class TypoScriptFrontendController implements LoggerAwareInterface
      *
      * @return int returns workspace integer for which workspace is being preview. 0 if none (= live workspace).
      */
-    public function whichWorkspace()
+    public function whichWorkspace(): int
     {
-        return $this->beUserLogin ? $this->getBackendUser()->workspace : 0;
+        return $this->context->getPropertyFromAspect('workspace', 'id', 0);
     }
 
     /********************************************
@@ -4772,4 +4818,166 @@ class TypoScriptFrontendController implements LoggerAwareInterface
         }
         return null;
     }
+
+    /**
+     * Deprecation messages for TYPO3 v9 - public properties of TSFE which have been moved as
+     * @todo: ensure that TypoScript conditions make use of this as well.
+     */
+
+    /**
+     * Checks if the property of the given name is set.
+     *
+     * Unmarked protected properties must return false as usual.
+     * Marked properties are evaluated by isset().
+     *
+     * This method is not called for public properties.
+     *
+     * @param string $propertyName
+     * @return bool
+     */
+    public function __isset(string $propertyName)
+    {
+        switch ($propertyName) {
+            case 'loginUser':
+                trigger_error('Property $TSFE->loginUser is not in use anymore as this information is now stored within the frontend.user aspect.');
+                return isset($this->$propertyName);
+                break;
+            case 'gr_list':
+                trigger_error('Property $TSFE->gr_list is not in use anymore as this information is now stored within the frontend.user aspect.');
+                return isset($this->$propertyName);
+            case 'beUserLogin':
+                trigger_error('Property $TSFE->beUserLogin is not in use anymore as this information is now stored within the backend.user aspect.');
+                return isset($this->$propertyName);
+            case 'showHiddenPage':
+                trigger_error('Property $TSFE->showHiddenPage is not in use anymore as this information is now stored within the visibility aspect.');
+                return isset($this->$propertyName);
+            case 'showHiddenRecords':
+                trigger_error('Property $TSFE->showHiddenRecords is not in use anymore as this information is now stored within the visibility aspect.');
+                return isset($this->$propertyName);
+        }
+        return false;
+    }
+
+    /**
+     * Gets the value of the property of the given name if tagged.
+     *
+     * The evaluation is done in the assumption that this method is never
+     * reached for a public property.
+     *
+     * @param string $propertyName
+     * @return mixed
+     */
+    public function __get(string $propertyName)
+    {
+        switch ($propertyName) {
+            case 'loginUser':
+                trigger_error('Property $TSFE->loginUser is not in use anymore as this information is now stored within the frontend.user aspect.');
+                return $this->context->getPropertyFromAspect('frontend.user', 'isLoggedIn', false);
+                break;
+            case 'gr_list':
+                trigger_error('Property $TSFE->gr_list is not in use anymore as this information is now stored within the frontend.user aspect.');
+                return implode(',', $this->context->getPropertyFromAspect('frontend.user', 'groupIds', [0, -1]));
+            case 'beUserLogin':
+                trigger_error('Property $TSFE->beUserLogin is not in use anymore as this information is now stored within the backend.user aspect.');
+                return $this->context->getPropertyFromAspect('backend.user', 'isLoggedIn', false);
+            case 'showHiddenPage':
+                trigger_error('Property $TSFE->showHiddenPage is not in use anymore as this information is now stored within the visibility aspect.');
+                return $this->context->getPropertyFromAspect('visibility', 'includeHiddenPages', false);
+            case 'showHiddenRecords':
+                trigger_error('Property $TSFE->showHiddenRecords is not in use anymore as this information is now stored within the visibility aspect.');
+                return $this->context->getPropertyFromAspect('visibility', 'includeHiddenContent', false);
+        }
+        return $this->$propertyName;
+    }
+
+    /**
+     * Sets the property of the given name if tagged.
+     *
+     * Additionally it's allowed to set unknown properties.
+     *
+     * The evaluation is done in the assumption that this method is never
+     * reached for a public property.
+     *
+     * @param string $propertyName
+     * @param mixed $propertyValue
+     */
+    public function __set(string $propertyName, $propertyValue)
+    {
+        switch ($propertyName) {
+            case 'loginUser':
+                trigger_error('Property $TSFE->loginUser is not in use anymore as this information is now stored within the frontend.user aspect.');
+                /** @var UserAspect $aspect */
+                $aspect = $this->context->getAspect('frontend.user');
+                if ($propertyValue) {
+                    $aspect = GeneralUtility::makeInstance(UserAspect::class, $this->fe_user, $aspect->getGroupIds());
+                } else {
+                    $aspect = GeneralUtility::makeInstance(UserAspect::class, null, $aspect->getGroupIds());
+                }
+                $this->context->setAspect('frontend.user', $aspect);
+                break;
+            case 'gr_list':
+                trigger_error('Property $TSFE->gr_list is not in use anymore as this information is now stored within the frontend.user aspect.');
+                $this->context->setAspect('frontend.user', GeneralUtility::makeInstance(UserAspect::class, $this->fe_user, GeneralUtility::intExplode(',', $propertyValue)));
+                break;
+            case 'beUserLogin':
+                trigger_error('Property $TSFE->beUserLogin is not in use anymore as this information is now stored within the backend.user aspect.');
+                if ($propertyValue) {
+                    $aspect = GeneralUtility::makeInstance(UserAspect::class, $GLOBALS['BE_USER']);
+                } else {
+                    $aspect = GeneralUtility::makeInstance(UserAspect::class);
+                }
+                $this->context->setAspect('backend.user', $aspect);
+                break;
+            case 'showHiddenPage':
+            case 'showHiddenRecords':
+                trigger_error('Property $TSFE->' . $propertyName . ' is not in use anymore as this information is now stored within the visibility aspect.');
+                /** @var VisibilityAspect $aspect */
+                $aspect = $this->context->getAspect('visibility');
+                if ($propertyName === 'showHiddenPage') {
+                    $newAspect = GeneralUtility::makeInstance(VisibilityAspect::class, (bool)$propertyValue, $aspect->includeHiddenContent(), $aspect->includeDeletedRecords());
+                } else {
+                    $newAspect = GeneralUtility::makeInstance(VisibilityAspect::class, $aspect->includeHiddenPages(), (bool)$propertyValue, $aspect->includeDeletedRecords());
+                }
+                $this->context->setAspect('visibility', $newAspect);
+                break;
+        }
+        $this->$propertyName = $propertyValue;
+    }
+
+    /**
+     * Unsets the property of the given name if tagged.
+     *
+     * @param string $propertyName
+     */
+    public function __unset(string $propertyName)
+    {
+        switch ($propertyName) {
+            case 'loginUser':
+                /** @var UserAspect $aspect */
+                $aspect = $this->context->getAspect('frontend.user');
+                $this->context->setAspect('frontend.user', GeneralUtility::makeInstance(UserAspect::class, null, $aspect->getGroupIds()));
+                break;
+            case 'gr_list':
+                trigger_error('Property $TSFE->gr_list is not in use anymore as this information is now stored within the frontend.user aspect.');
+                $this->context->setAspect('frontend.user', GeneralUtility::makeInstance(UserAspect::class, $this->fe_user, []));
+                break;
+            case 'beUserLogin':
+                trigger_error('Property $TSFE->beUserLogin is not in use anymore as this information is now stored within the backend.user aspect.');
+                $this->context->setAspect('backend.user', GeneralUtility::makeInstance(UserAspect::class));
+                break;
+            case 'showHiddenPage':
+            case 'showHiddenRecords':
+                trigger_error('Property $TSFE->' . $propertyName . ' is not in use anymore as this information is now stored within the visibility aspect.');
+                /** @var VisibilityAspect $aspect */
+                $aspect = $this->context->getAspect('visibility');
+                if ($propertyName === 'showHiddenPage') {
+                    $newAspect = GeneralUtility::makeInstance(VisibilityAspect::class, false, $aspect->includeHiddenContent(), $aspect->includeDeletedRecords());
+                } else {
+                    $newAspect = GeneralUtility::makeInstance(VisibilityAspect::class, $aspect->includeHiddenPages(), false, $aspect->includeDeletedRecords());
+                }
+                $this->context->setAspect('visibility', $newAspect);
+                break;
+        }
+        unset($this->$propertyName);
+    }
 }
index a1c7d04..229122a 100644 (file)
@@ -18,8 +18,14 @@ namespace TYPO3\CMS\Frontend\Http;
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
 use TYPO3\CMS\Core\Configuration\ConfigurationManager;
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\DateTimeAspect;
+use TYPO3\CMS\Core\Context\UserAspect;
+use TYPO3\CMS\Core\Context\VisibilityAspect;
+use TYPO3\CMS\Core\Context\WorkspaceAspect;
 use TYPO3\CMS\Core\Http\AbstractApplication;
 use TYPO3\CMS\Core\Http\RedirectResponse;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * Entry point for the TYPO3 Frontend
@@ -58,6 +64,7 @@ class Application extends AbstractApplication
         if (!$this->checkIfEssentialConfigurationExists()) {
             return $this->installToolRedirect();
         }
+        $this->initializeContext();
         return parent::handle($request);
     }
 
@@ -81,4 +88,19 @@ class Application extends AbstractApplication
         $path = TYPO3_mainDir . 'install.php';
         return new RedirectResponse($path, 302);
     }
+
+    /**
+     * Initializes the Context used for accessing data and finding out the current state of the application
+     * Will be moved to a DI-like concept once introduced, for now, this is a singleton
+     */
+    protected function initializeContext()
+    {
+        GeneralUtility::makeInstance(Context::class, [
+            'date' => new DateTimeAspect(new \DateTimeImmutable('@' . $GLOBALS['EXEC_TIME'])),
+            'visibility' => new VisibilityAspect(),
+            'workspace' => new WorkspaceAspect(0),
+            'backend.user' => new UserAspect(null),
+            'frontend.user' => new UserAspect(null, [0, -1]),
+        ]);
+    }
 }
index 1611085..8dc70fb 100644 (file)
@@ -22,6 +22,9 @@ use Psr\Http\Server\MiddlewareInterface;
 use Psr\Http\Server\RequestHandlerInterface;
 use TYPO3\CMS\Backend\FrontendBackendUserAuthentication;
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\UserAspect;
+use TYPO3\CMS\Core\Context\WorkspaceAspect;
 use TYPO3\CMS\Core\Core\Bootstrap;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
@@ -57,14 +60,9 @@ class BackendUserAuthenticator implements MiddlewareInterface
         // Initializing a possible logged-in Backend User
         // If the backend cookie is set,
         // we proceed and check if a backend user is logged in.
-        $GLOBALS['TSFE']->beUserLogin = false;
         $backendUserObject = null;
         if (isset($request->getCookieParams()[BackendUserAuthentication::getCookieName()])) {
             $backendUserObject = $this->initializeBackendUser();
-            // If the user is active now, let the controller know
-            if ($backendUserObject instanceof FrontendBackendUserAuthentication && !empty($backendUserObject->user['uid'])) {
-                $GLOBALS['TSFE']->beUserLogin = true;
-            }
         }
 
         $GLOBALS['BE_USER'] = $backendUserObject;
@@ -84,6 +82,7 @@ class BackendUserAuthenticator implements MiddlewareInterface
             Bootstrap::initializeLanguageObject();
             Bootstrap::initializeBackendRouter();
             Bootstrap::loadExtTables();
+            $this->setBackendUserAspect(GeneralUtility::makeInstance(Context::class), $GLOBALS['BE_USER']);
         }
 
         return $handler->handle($request);
@@ -94,7 +93,7 @@ class BackendUserAuthenticator implements MiddlewareInterface
      *
      * @return FrontendBackendUserAuthentication|null the backend user object or null if there was no valid user found
      */
-    public function initializeBackendUser()
+    protected function initializeBackendUser()
     {
         // New backend user object
         $backendUserObject = GeneralUtility::makeInstance(FrontendBackendUserAuthentication::class);
@@ -109,4 +108,16 @@ class BackendUserAuthenticator implements MiddlewareInterface
         }
         return $backendUserObject;
     }
+
+    /**
+     * Register the backend user as aspect
+     *
+     * @param Context $context
+     * @param BackendUserAuthentication $user
+     */
+    protected function setBackendUserAspect(Context $context, BackendUserAuthentication $user)
+    {
+        $context->setAspect('backend.user', GeneralUtility::makeInstance(UserAspect::class, $user));
+        $context->setAspect('workspace', GeneralUtility::makeInstance(WorkspaceAspect::class, $user ? $user->workspace : 0));
+    }
 }
index 87cd9dd..5e32a63 100644 (file)
@@ -19,6 +19,9 @@ use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
 use Psr\Http\Server\MiddlewareInterface;
 use Psr\Http\Server\RequestHandlerInterface;
+use TYPO3\CMS\Core\Authentication\AbstractUserAuthentication;
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\UserAspect;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
 
@@ -66,6 +69,10 @@ class FrontendUserAuthenticator implements MiddlewareInterface
         foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_fe.php']['initFEuser'] ?? [] as $_funcRef) {
             GeneralUtility::callUserFunction($_funcRef, $_params, $GLOBALS['TSFE']);
         }
+
+        // Register the frontend user as aspect
+        $this->setFrontendUserAspect(GeneralUtility::makeInstance(Context::class), $frontendUser);
+
         return $handler->handle($request);
     }
 
@@ -107,4 +114,15 @@ class FrontendUserAuthenticator implements MiddlewareInterface
         }
         return $request;
     }
+
+    /**
+     * Register the frontend user as aspect
+     *
+     * @param Context $context
+     * @param AbstractUserAuthentication $user
+     */
+    protected function setFrontendUserAspect(Context $context, AbstractUserAuthentication $user)
+    {
+        $context->setAspect('frontend.user', GeneralUtility::makeInstance(UserAspect::class, $user, $user === null ? [0, -1] : null));
+    }
 }
index 4400771..6fd6a8b 100644 (file)
@@ -19,6 +19,10 @@ use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
 use Psr\Http\Server\MiddlewareInterface;
 use Psr\Http\Server\RequestHandlerInterface;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\UserAspect;
+use TYPO3\CMS\Core\Context\WorkspaceAspect;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
@@ -48,7 +52,8 @@ class PageResolver implements MiddlewareInterface
         // No access? Then remove user & Re-evaluate the page-id
         if ($GLOBALS['TSFE']->isBackendUserLoggedIn() && !$GLOBALS['BE_USER']->doesUserHaveAccess($GLOBALS['TSFE']->page, Permission::PAGE_SHOW)) {
             unset($GLOBALS['BE_USER']);
-            $GLOBALS['TSFE']->beUserLogin = false;
+            // Register an empty backend user as aspect
+            $this->setBackendUserAspect(GeneralUtility::makeInstance(Context::class), null);
             $this->checkAlternativeIdMethods($GLOBALS['TSFE']);
             $GLOBALS['TSFE']->determineId();
         }
@@ -65,6 +70,8 @@ class PageResolver implements MiddlewareInterface
      * Two options:
      * 1) Use PATH_INFO (also Apache) to extract id and type from that var. Does not require any special modules compiled with apache. (less typical)
      * 2) Using hook which enables features like those provided from "realurl" extension (AKA "Speaking URLs")
+     *
+     * @param TypoScriptFrontendController $tsfe
      */
     protected function checkAlternativeIdMethods(TypoScriptFrontendController $tsfe)
     {
@@ -74,4 +81,16 @@ class PageResolver implements MiddlewareInterface
             GeneralUtility::callUserFunction($_funcRef, $_params, $tsfe);
         }
     }
+
+    /**
+     * Register the backend user as aspect
+     *
+     * @param Context $context
+     * @param BackendUserAuthentication $user
+     */
+    protected function setBackendUserAspect(Context $context, BackendUserAuthentication $user = null)
+    {
+        $context->setAspect('backend.user', GeneralUtility::makeInstance(UserAspect::class, $user));
+        $context->setAspect('workspace', GeneralUtility::makeInstance(WorkspaceAspect::class, $user ? $user->workspace : 0));
+    }
 }
index 5c82755..2501948 100644 (file)
@@ -17,6 +17,8 @@ namespace TYPO3\CMS\Frontend\Page;
 use Psr\Log\LoggerAwareInterface;
 use Psr\Log\LoggerAwareTrait;
 use TYPO3\CMS\Core\Compatibility\PublicPropertyDeprecationTrait;
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\UserAspect;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\QueryHelper;
@@ -179,6 +181,24 @@ class PageRepository implements LoggerAwareInterface
     const SHORTCUT_MODE_PARENT_PAGE = 3;
 
     /**
+     * @var Context
+     */
+    protected $context;
+
+    /**
+     * PageRepository constructor to set the base context, this will effectively remove the necessity for
+     * setting properties from the outside.
+     *
+     * @param Context $context
+     */
+    public function __construct(Context $context = null)
+    {
+        $this->context = $context ?? GeneralUtility::makeInstance(Context::class);
+        $this->versioningWorkspaceId = $this->context->getPropertyFromAspect('workspace', 'id');
+        $this->init($this->context->getPropertyFromAspect('visibility', 'includeHiddenPages'));
+    }
+
+    /**
      * init() MUST be run directly after creating a new template-object
      * This sets the internal variable $this->where_hid_del to the correct where
      * clause for page records taking deleted/hidden/starttime/endtime/t3ver_state
@@ -189,6 +209,10 @@ class PageRepository implements LoggerAwareInterface
      */
     public function init($show_hidden)
     {
+        // This usually happens only in tests.
+        if (!isset($GLOBALS['TCA']['pages'])) {
+            return;
+        }
         $this->where_groupAccess = '';
 
         if ($this->versioningWorkspaceId) {
@@ -1396,18 +1420,12 @@ class PageRepository implements LoggerAwareInterface
      */
     public function enableFields($table, $show_hidden = -1, $ignore_array = [], $noVersionPreview = false)
     {
-        if ($show_hidden === -1 && is_object($this->getTypoScriptFrontendController())) {
-            // If show_hidden was not set from outside and if TSFE is an object, set it
-            // based on showHiddenPage and showHiddenRecords from TSFE
-            $show_hidden = $table === 'pages'
-                ? $this->getTypoScriptFrontendController()->showHiddenPage
-                : $this->getTypoScriptFrontendController()->showHiddenRecords;
-        }
         if ($show_hidden === -1) {
-            $show_hidden = 0;
+            // If show_hidden was not set from outside, use the current context
+            $show_hidden = (int)$this->context->getPropertyFromAspect('visibility', $table === 'pages' ? 'includeHiddenPages' : 'includeHiddenContent', false);
         }
         // If show_hidden was not changed during the previous evaluation, do it here.
-        $ctrl = $GLOBALS['TCA'][$table]['ctrl'];
+        $ctrl = $GLOBALS['TCA'][$table]['ctrl'] ?? null;
         $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
             ->getQueryBuilderForTable($table)
             ->expr();
@@ -1503,7 +1521,9 @@ class PageRepository implements LoggerAwareInterface
         $expressionBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
             ->getQueryBuilderForTable($table)
             ->expr();
-        $memberGroups = GeneralUtility::intExplode(',', $this->getTypoScriptFrontendController()->gr_list);
+        /** @var UserAspect $userAspect */
+        $userAspect = $this->context->getAspect('frontend.user');
+        $memberGroups = $userAspect->getGroupIds();
         $orChecks = [];
         // If the field is empty, then OK
         $orChecks[] = $expressionBuilder->eq($field, $expressionBuilder->literal(''));
index 721676f..6129469 100644 (file)
@@ -200,7 +200,6 @@ abstract class AbstractTypolinkBuilder
                     (int)GeneralUtility::_GP('type')
             );
             $GLOBALS['TSFE']->sys_page = GeneralUtility::makeInstance(PageRepository::class);
-            $GLOBALS['TSFE']->sys_page->init(false);
             $GLOBALS['TSFE']->initTemplate();
         }
         return $GLOBALS['TSFE'];
index 04323da..b6aec4e 100644 (file)
@@ -36,7 +36,6 @@ class TypoScriptFrontendControllerTest extends FunctionalTestCase
         parent::setUp();
         $this->importDataSet(__DIR__ . '/fixtures.xml');
 
-        $GLOBALS['TSFE']->gr_list = '';
         $this->tsFrontendController = $this->getAccessibleMock(
             TypoScriptFrontendController::class,
             ['dummy'],
index b24b527..e501526 100644 (file)
@@ -35,7 +35,6 @@ class PageRepositoryTest extends \TYPO3\TestingFramework\Core\Functional\Functio
     protected function setUp()
     {
         parent::setUp();
-        $GLOBALS['TSFE']->gr_list = '';
         $this->importDataSet(__DIR__ . '/../Fixtures/pages.xml');
         $this->pageRepo = new PageRepository();
         $this->pageRepo->init(false);
index 6c87e06..de3e45d 100644 (file)
@@ -17,6 +17,8 @@ namespace TYPO3\CMS\Frontend\Tests\Unit\Configuration\TypoScript\ConditionMatchi
 
 use Prophecy\Argument;
 use TYPO3\CMS\Core\Configuration\TypoScript\Exception\InvalidTypoScriptConditionException;
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\UserAspect;
 use TYPO3\CMS\Core\Http\ServerRequest;
 use TYPO3\CMS\Core\Site\Entity\Site;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -30,11 +32,6 @@ use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
  */
 class ConditionMatcherTest extends UnitTestCase
 {
-    /**
-     * @var \TYPO3\CMS\Frontend\Configuration\TypoScript\ConditionMatching\ConditionMatcher Class under test
-     */
-    protected $matchCondition;
-
     protected function setUp(): void
     {
         $this->testGlobalNamespace = $this->getUniqueId('TEST');
@@ -47,7 +44,6 @@ class ConditionMatcherTest extends UnitTestCase
             1 => ['uid' => 111, 'pid' => 101],
             0 => ['uid' => 101, 'pid' => 0]
         ];
-        $this->matchCondition = GeneralUtility::makeInstance(ConditionMatcher::class);
     }
 
     /**
@@ -57,8 +53,8 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function simulateDisabledMatchAllConditionsFailsOnFaultyExpression(): void
     {
-        $this->matchCondition->matchAll = false;
-        $this->assertFalse($this->matchCondition->match('[nullCondition = This expression would return FALSE in general]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertFalse($subject->match('[nullCondition = This expression would return FALSE in general]'));
     }
 
     /**
@@ -68,8 +64,9 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function simulateEnabledMatchAllConditionsSucceeds(): void
     {
-        $this->matchCondition->setSimulateMatchResult(true);
-        $this->assertTrue($this->matchCondition->match('[nullCondition = This expression would return FALSE in general]'));
+        $subject = new ConditionMatcher(new Context());
+        $subject->setSimulateMatchResult(true);
+        $this->assertTrue($subject->match('[nullCondition = This expression would return FALSE in general]'));
     }
 
     /**
@@ -79,9 +76,10 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function simulateEnabledMatchSpecificConditionsSucceeds(): void
     {
+        $subject = new ConditionMatcher(new Context());
         $testCondition = '[' . $this->getUniqueId('test') . ' = Any condition to simulate a positive match]';
-        $this->matchCondition->setSimulateMatchConditions([$testCondition]);
-        $this->assertTrue($this->matchCondition->match($testCondition));
+        $subject->setSimulateMatchConditions([$testCondition]);
+        $this->assertTrue($subject->match($testCondition));
     }
 
     /**
@@ -91,9 +89,10 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function languageConditionMatchesSingleLanguageExpression(): void
     {
+        $subject = new ConditionMatcher(new Context());
         $_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'de-de,de;q=0.8,en-us;q=0.5,en;q=0.3';
-        $this->assertTrue($this->matchCondition->match('[language = *de*]'));
-        $this->assertTrue($this->matchCondition->match('[language = *de-de*]'));
+        $this->assertTrue($subject->match('[language = *de*]'));
+        $this->assertTrue($subject->match('[language = *de-de*]'));
     }
 
     /**
@@ -103,9 +102,10 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function languageConditionMatchesMultipleLanguagesExpression(): void
     {
+        $subject = new ConditionMatcher(new Context());
         $_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'de-de,de;q=0.8,en-us;q=0.5,en;q=0.3';
-        $this->assertTrue($this->matchCondition->match('[language = *en*,*de*]'));
-        $this->assertTrue($this->matchCondition->match('[language = *en-us*,*de-de*]'));
+        $this->assertTrue($subject->match('[language = *en*,*de*]'));
+        $this->assertTrue($subject->match('[language = *en-us*,*de-de*]'));
     }
 
     /**
@@ -115,8 +115,9 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function languageConditionMatchesCompleteLanguagesExpression(): void
     {
+        $subject = new ConditionMatcher(new Context());
         $_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'de-de,de;q=0.8,en-us;q=0.5,en;q=0.3';
-        $this->assertTrue($this->matchCondition->match('[language = de-de,de;q=0.8,en-us;q=0.5,en;q=0.3]'));
+        $this->assertTrue($subject->match('[language = de-de,de;q=0.8,en-us;q=0.5,en;q=0.3]'));
     }
 
     /**
@@ -126,8 +127,10 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function usergroupConditionMatchesSingleGroupId(): void
     {
-        $GLOBALS['TSFE']->gr_list = '13,14,15';
-        $this->assertTrue($this->matchCondition->match('[usergroup = 13]'));
+        $subject = new ConditionMatcher(new Context([
+            'frontend.user' => new UserAspect(new FrontendUserAuthentication(), [13, 14, 15])
+        ]));
+        $this->assertTrue($subject->match('[usergroup = 13]'));
     }
 
     /**
@@ -137,8 +140,10 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function usergroupConditionMatchesMultipleUserGroupId(): void
     {
-        $GLOBALS['TSFE']->gr_list = '13,14,15';
-        $this->assertTrue($this->matchCondition->match('[usergroup = 999,15,14,13]'));
+        $subject = new ConditionMatcher(new Context([
+            'frontend.user' => new UserAspect(new FrontendUserAuthentication(), [13, 14, 15])
+        ]));
+        $this->assertTrue($subject->match('[usergroup = 999,15,14,13]'));
     }
 
     /**
@@ -148,8 +153,10 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function usergroupConditionDoesNotMatchDefaulUserGroupIds(): void
     {
-        $GLOBALS['TSFE']->gr_list = '0,-1';
-        $this->assertFalse($this->matchCondition->match('[usergroup = 0,-1]'));
+        $subject = new ConditionMatcher(new Context([
+            'frontend.user' => new UserAspect(new FrontendUserAuthentication(), [0, -1])
+        ]));
+        $this->assertFalse($subject->match('[usergroup = 0,-1]'));
     }
 
     /**
@@ -159,9 +166,13 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function loginUserConditionMatchesAnyLoggedInUser(): void
     {
-        $GLOBALS['TSFE']->loginUser = true;
-        $GLOBALS['TSFE']->fe_user->user['uid'] = 13;
-        $this->assertTrue($this->matchCondition->match('[loginUser = *]'));
+        $user = new FrontendUserAuthentication();
+        $user->user['uid'] = 13;
+        $user->groupData['uid'] = [14];
+        $subject = new ConditionMatcher(new Context([
+            'frontend.user' => new UserAspect($user)
+        ]));
+        $this->assertTrue($subject->match('[loginUser = *]'));
     }
 
     /**
@@ -171,9 +182,13 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function loginUserConditionMatchesSingleLoggedInUser(): void
     {
-        $GLOBALS['TSFE']->loginUser = true;
-        $GLOBALS['TSFE']->fe_user->user['uid'] = 13;
-        $this->assertTrue($this->matchCondition->match('[loginUser = 13]'));
+        $user = new FrontendUserAuthentication();
+        $user->user['uid'] = 13;
+        $user->groupData['uid'] = [14];
+        $subject = new ConditionMatcher(new Context([
+            'frontend.user' => new UserAspect($user)
+        ]));
+        $this->assertTrue($subject->match('[loginUser = 13]'));
     }
 
     /**
@@ -183,9 +198,13 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function loginUserConditionMatchesMultipleLoggedInUsers(): void
     {
-        $GLOBALS['TSFE']->loginUser = true;
-        $GLOBALS['TSFE']->fe_user->user['uid'] = 13;
-        $this->assertTrue($this->matchCondition->match('[loginUser = 999,13]'));
+        $user = new FrontendUserAuthentication();
+        $user->user['uid'] = 13;
+        $user->groupData['uid'] = [14];
+        $subject = new ConditionMatcher(new Context([
+            'frontend.user' => new UserAspect($user)
+        ]));
+        $this->assertTrue($subject->match('[loginUser = 999,13]'));
     }
 
     /**
@@ -195,10 +214,13 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function loginUserConditionDoesNotMatchIfNotUserIsLoggedId(): void
     {
-        $GLOBALS['TSFE']->loginUser = false;
-        $GLOBALS['TSFE']->fe_user->user['uid'] = 13;
-        $this->assertFalse($this->matchCondition->match('[loginUser = *]'));
-        $this->assertFalse($this->matchCondition->match('[loginUser = 13]'));
+        $user = new FrontendUserAuthentication();
+        $user->user['uid'] = 13;
+        $subject = new ConditionMatcher(new Context([
+            'frontend.user' => new UserAspect($user)
+        ]));
+        $this->assertFalse($subject->match('[loginUser = *]'));
+        $this->assertFalse($subject->match('[loginUser = 13]'));
     }
 
     /**
@@ -208,8 +230,11 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function loginUserConditionMatchIfUserIsNotLoggedIn(): void
     {
-        $GLOBALS['TSFE']->loginUser = false;
-        $this->assertTrue($this->matchCondition->match('[loginUser = ]'));
+        $user = new FrontendUserAuthentication();
+        $subject = new ConditionMatcher(new Context([
+            'frontend.user' => new UserAspect($user)
+        ]));
+        $this->assertTrue($subject->match('[loginUser = ]'));
     }
 
     /**
@@ -219,10 +244,11 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function globalVarConditionMatchesOnEqualExpression(): void
     {
-        $this->assertTrue($this->matchCondition->match('[globalVar = LIT:10 = 10]'));
-        $this->assertTrue($this->matchCondition->match('[globalVar = LIT:10.1 = 10.1]'));
-        $this->assertTrue($this->matchCondition->match('[globalVar = LIT:10 == 10]'));
-        $this->assertTrue($this->matchCondition->match('[globalVar = LIT:10.1 == 10.1]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertTrue($subject->match('[globalVar = LIT:10 = 10]'));
+        $this->assertTrue($subject->match('[globalVar = LIT:10.1 = 10.1]'));
+        $this->assertTrue($subject->match('[globalVar = LIT:10 == 10]'));
+        $this->assertTrue($subject->match('[globalVar = LIT:10.1 == 10.1]'));
     }
 
     /**
@@ -232,14 +258,15 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function globalVarConditionMatchesOnEqualExpressionWithMultipleValues(): void
     {
-        $this->assertTrue($this->matchCondition->match('[globalVar = LIT:10 = 10|20|30]'));
-        $this->assertTrue($this->matchCondition->match('[globalVar = LIT:10.1 = 10.1|20.2|30.3]'));
-        $this->assertTrue($this->matchCondition->match('[globalVar = LIT:20 = 10|20|30]'));
-        $this->assertTrue($this->matchCondition->match('[globalVar = LIT:20.2 = 10.1|20.2|30.3]'));
-        $this->assertTrue($this->matchCondition->match('[globalVar = LIT:10 == 10|20|30]'));
-        $this->assertTrue($this->matchCondition->match('[globalVar = LIT:10.1 == 10.1|20.2|30.3]'));
-        $this->assertTrue($this->matchCondition->match('[globalVar = LIT:20 == 10|20|30]'));
-        $this->assertTrue($this->matchCondition->match('[globalVar = LIT:20.2 == 10.1|20.2|30.3]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertTrue($subject->match('[globalVar = LIT:10 = 10|20|30]'));
+        $this->assertTrue($subject->match('[globalVar = LIT:10.1 = 10.1|20.2|30.3]'));
+        $this->assertTrue($subject->match('[globalVar = LIT:20 = 10|20|30]'));
+        $this->assertTrue($subject->match('[globalVar = LIT:20.2 = 10.1|20.2|30.3]'));
+        $this->assertTrue($subject->match('[globalVar = LIT:10 == 10|20|30]'));
+        $this->assertTrue($subject->match('[globalVar = LIT:10.1 == 10.1|20.2|30.3]'));
+        $this->assertTrue($subject->match('[globalVar = LIT:20 == 10|20|30]'));
+        $this->assertTrue($subject->match('[globalVar = LIT:20.2 == 10.1|20.2|30.3]'));
     }
 
     /**
@@ -249,8 +276,9 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function globalVarConditionMatchesOnNotEqualExpression(): void
     {
-        $this->assertTrue($this->matchCondition->match('[globalVar = LIT:10 != 20]'));
-        $this->assertTrue($this->matchCondition->match('[globalVar = LIT:10.1 != 10.2]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertTrue($subject->match('[globalVar = LIT:10 != 20]'));
+        $this->assertTrue($subject->match('[globalVar = LIT:10.1 != 10.2]'));
     }
 
     /**
@@ -260,7 +288,8 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function globalVarConditionDoesNotMatchOnNotEqualExpression(): void
     {
-        $this->assertFalse($this->matchCondition->match('[globalVar = LIT:10 != 10]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertFalse($subject->match('[globalVar = LIT:10 != 10]'));
     }
 
     /**
@@ -270,8 +299,9 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function globalVarConditionMatchesOnNotEqualExpressionWithMultipleValues(): void
     {
-        $this->assertTrue($this->matchCondition->match('[globalVar = LIT:10 != 20|30]'));
-        $this->assertTrue($this->matchCondition->match('[globalVar = LIT:10.1 != 10.2|20.3]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertTrue($subject->match('[globalVar = LIT:10 != 20|30]'));
+        $this->assertTrue($subject->match('[globalVar = LIT:10.1 != 10.2|20.3]'));
     }
 
     /**
@@ -281,8 +311,9 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function globalVarConditionMatchesOnLowerThanExpression(): void
     {
-        $this->assertTrue($this->matchCondition->match('[globalVar = LIT:10 < 20]'));
-        $this->assertTrue($this->matchCondition->match('[globalVar = LIT:10.1 < 10.2]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertTrue($subject->match('[globalVar = LIT:10 < 20]'));
+        $this->assertTrue($subject->match('[globalVar = LIT:10.1 < 10.2]'));
     }
 
     /**
@@ -292,10 +323,11 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function globalVarConditionMatchesOnLowerThanOrEqualExpression(): void
     {
-        $this->assertTrue($this->matchCondition->match('[globalVar = LIT:10 <= 10]'));
-        $this->assertTrue($this->matchCondition->match('[globalVar = LIT:10 <= 20]'));
-        $this->assertTrue($this->matchCondition->match('[globalVar = LIT:10.1 <= 10.1]'));
-        $this->assertTrue($this->matchCondition->match('[globalVar = LIT:10.1 <= 10.2]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertTrue($subject->match('[globalVar = LIT:10 <= 10]'));
+        $this->assertTrue($subject->match('[globalVar = LIT:10 <= 20]'));
+        $this->assertTrue($subject->match('[globalVar = LIT:10.1 <= 10.1]'));
+        $this->assertTrue($subject->match('[globalVar = LIT:10.1 <= 10.2]'));
     }
 
     /**
@@ -305,8 +337,9 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function globalVarConditionMatchesOnGreaterThanExpression(): void
     {
-        $this->assertTrue($this->matchCondition->match('[globalVar = LIT:20 > 10]'));
-        $this->assertTrue($this->matchCondition->match('[globalVar = LIT:10.2 > 10.1]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertTrue($subject->match('[globalVar = LIT:20 > 10]'));
+        $this->assertTrue($subject->match('[globalVar = LIT:10.2 > 10.1]'));
     }
 
     /**
@@ -316,10 +349,11 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function globalVarConditionMatchesOnGreaterThanOrEqualExpression(): void
     {
-        $this->assertTrue($this->matchCondition->match('[globalVar = LIT:10 >= 10]'));
-        $this->assertTrue($this->matchCondition->match('[globalVar = LIT:20 >= 10]'));
-        $this->assertTrue($this->matchCondition->match('[globalVar = LIT:10.1 >= 10.1]'));
-        $this->assertTrue($this->matchCondition->match('[globalVar = LIT:10.2 >= 10.1]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertTrue($subject->match('[globalVar = LIT:10 >= 10]'));
+        $this->assertTrue($subject->match('[globalVar = LIT:20 >= 10]'));
+        $this->assertTrue($subject->match('[globalVar = LIT:10.1 >= 10.1]'));
+        $this->assertTrue($subject->match('[globalVar = LIT:10.2 >= 10.1]'));
     }
 
     /**
@@ -329,9 +363,10 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function globalVarConditionMatchesOnEmptyExpressionWithNoValueSet(): void
     {
+        $subject = new ConditionMatcher(new Context());
         $testKey = $this->getUniqueId('test');
-        $this->assertTrue($this->matchCondition->match('[globalVar = GP:' . $testKey . '=]'));
-        $this->assertTrue($this->matchCondition->match('[globalVar = GP:' . $testKey . ' = ]'));
+        $this->assertTrue($subject->match('[globalVar = GP:' . $testKey . '=]'));
+        $this->assertTrue($subject->match('[globalVar = GP:' . $testKey . ' = ]'));
     }
 
     /**
@@ -341,11 +376,12 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function globalVarConditionDoesNotMatchOnEmptyExpressionWithValueSetToZero(): void
     {
+        $subject = new ConditionMatcher(new Context());
         $testKey = $this->getUniqueId('test');
         $_GET = [];
         $_POST = [$testKey => 0];
-        $this->assertFalse($this->matchCondition->match('[globalVar = GP:' . $testKey . '=]'));
-        $this->assertFalse($this->matchCondition->match('[globalVar = GP:' . $testKey . ' = ]'));
+        $this->assertFalse($subject->match('[globalVar = GP:' . $testKey . '=]'));
+        $this->assertFalse($subject->match('[globalVar = GP:' . $testKey . ' = ]'));
     }
 
     /**
@@ -355,11 +391,12 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function globalVarConditionMatchesOnArrayExpressionWithZeroAsKey(): void
     {
+        $subject = new ConditionMatcher(new Context());
         $testKey = $this->getUniqueId('test');
         $testValue = '1';
         $_GET = [];
         $_POST = [$testKey => ['0' => $testValue]];
-        $this->assertTrue($this->matchCondition->match('[globalVar = GP:' . $testKey . '|0=' . $testValue . ']'));
+        $this->assertTrue($subject->match('[globalVar = GP:' . $testKey . '|0=' . $testValue . ']'));
     }
 
     /**
@@ -369,8 +406,9 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function globalStringConditionMatchesOnEqualExpression(): void
     {
-        $this->assertTrue($this->matchCondition->match('[globalString = LIT:TYPO3.Test.Condition = TYPO3.Test.Condition]'));
-        $this->assertFalse($this->matchCondition->match('[globalString = LIT:TYPO3.Test.Condition = TYPO3]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertTrue($subject->match('[globalString = LIT:TYPO3.Test.Condition = TYPO3.Test.Condition]'));
+        $this->assertFalse($subject->match('[globalString = LIT:TYPO3.Test.Condition = TYPO3]'));
     }
 
     /**
@@ -380,11 +418,12 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function globalStringConditionMatchesOnEmptyExpressionWithValueSetToEmptyString(): void
     {
+        $subject = new ConditionMatcher(new Context());
         $testKey = $this->getUniqueId('test');
         $_GET = [];
         $_POST = [$testKey => ''];
-        $this->assertTrue($this->matchCondition->match('[globalString = GP:' . $testKey . '=]'));
-        $this->assertTrue($this->matchCondition->match('[globalString = GP:' . $testKey . ' = ]'));
+        $this->assertTrue($subject->match('[globalString = GP:' . $testKey . '=]'));
+        $this->assertTrue($subject->match('[globalString = GP:' . $testKey . ' = ]'));
     }
 
     /**
@@ -394,8 +433,9 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function globalStringConditionMatchesOnEmptyLiteralExpressionWithValueSetToEmptyString(): void
     {
-        $this->assertTrue($this->matchCondition->match('[globalString = LIT:=]'));
-        $this->assertTrue($this->matchCondition->match('[globalString = LIT: = ]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertTrue($subject->match('[globalString = LIT:=]'));
+        $this->assertTrue($subject->match('[globalString = LIT: = ]'));
     }
 
     /**
@@ -405,9 +445,10 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function globalStringConditionMatchesWildcardExpression(): void
     {
-        $this->assertTrue($this->matchCondition->match('[globalString = LIT:TYPO3.Test.Condition = TYPO3?Test?Condition]'));
-        $this->assertTrue($this->matchCondition->match('[globalString = LIT:TYPO3.Test.Condition = TYPO3.T*t.Condition]'));
-        $this->assertTrue($this->matchCondition->match('[globalString = LIT:TYPO3.Test.Condition = TYPO3?T*t?Condition]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertTrue($subject->match('[globalString = LIT:TYPO3.Test.Condition = TYPO3?Test?Condition]'));
+        $this->assertTrue($subject->match('[globalString = LIT:TYPO3.Test.Condition = TYPO3.T*t.Condition]'));
+        $this->assertTrue($subject->match('[globalString = LIT:TYPO3.Test.Condition = TYPO3?T*t?Condition]'));
     }
 
     /**
@@ -417,9 +458,10 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function globalStringConditionMatchesRegularExpression(): void
     {
-        $this->assertTrue($this->matchCondition->match('[globalString = LIT:TYPO3.Test.Condition = /^[A-Za-z3.]+$/]'));
-        $this->assertTrue($this->matchCondition->match('[globalString = LIT:TYPO3.Test.Condition = /^TYPO3\\..+Condition$/]'));
-        $this->assertFalse($this->matchCondition->match('[globalString = LIT:TYPO3.Test.Condition = /^FALSE/]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertTrue($subject->match('[globalString = LIT:TYPO3.Test.Condition = /^[A-Za-z3.]+$/]'));
+        $this->assertTrue($subject->match('[globalString = LIT:TYPO3.Test.Condition = /^TYPO3\\..+Condition$/]'));
+        $this->assertFalse($subject->match('[globalString = LIT:TYPO3.Test.Condition = /^FALSE/]'));
     }
 
     /**
@@ -429,9 +471,10 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function globalStringConditionMatchesEmptyRegularExpression(): void
     {
+        $subject = new ConditionMatcher(new Context());
         $testKey = $this->getUniqueId('test');
         $_SERVER[$testKey] = '';
-        $this->assertTrue($this->matchCondition->match('[globalString = _SERVER|' . $testKey . ' = /^$/]'));
+        $this->assertTrue($subject->match('[globalString = _SERVER|' . $testKey . ' = /^$/]'));
     }
 
     /**
@@ -441,7 +484,8 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function treeLevelConditionMatchesSingleValue(): void
     {
-        $this->assertTrue($this->matchCondition->match('[treeLevel = 2]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertTrue($subject->match('[treeLevel = 2]'));
     }
 
     /**
@@ -451,7 +495,8 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function treeLevelConditionMatchesMultipleValues(): void
     {
-        $this->assertTrue($this->matchCondition->match('[treeLevel = 999,998,2]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertTrue($subject->match('[treeLevel = 999,998,2]'));
     }
 
     /**
@@ -461,7 +506,8 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function treeLevelConditionDoesNotMatchFaultyValue(): void
     {
-        $this->assertFalse($this->matchCondition->match('[treeLevel = 999]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertFalse($subject->match('[treeLevel = 999]'));
     }
 
     /**
@@ -472,7 +518,8 @@ class ConditionMatcherTest extends UnitTestCase
     public function PIDupinRootlineConditionMatchesSinglePageIdInRootline(): void
     {
         $GLOBALS['TSFE']->id = 121;
-        $this->assertTrue($this->matchCondition->match('[PIDupinRootline = 111]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertTrue($subject->match('[PIDupinRootline = 111]'));
     }
 
     /**
@@ -483,7 +530,8 @@ class ConditionMatcherTest extends UnitTestCase
     public function PIDupinRootlineConditionMatchesMultiplePageIdsInRootline(): void
     {
         $GLOBALS['TSFE']->id = 121;
-        $this->assertTrue($this->matchCondition->match('[PIDupinRootline = 999,111,101]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertTrue($subject->match('[PIDupinRootline = 999,111,101]'));
     }
 
     /**
@@ -494,7 +542,8 @@ class ConditionMatcherTest extends UnitTestCase
     public function PIDupinRootlineConditionDoesNotMatchPageIdNotInRootline(): void
     {
         $GLOBALS['TSFE']->id = 121;
-        $this->assertFalse($this->matchCondition->match('[PIDupinRootline = 999]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertFalse($subject->match('[PIDupinRootline = 999]'));
     }
 
     /**
@@ -505,7 +554,8 @@ class ConditionMatcherTest extends UnitTestCase
     public function PIDupinRootlineConditionDoesNotMatchLastPageIdInRootline(): void
     {
         $GLOBALS['TSFE']->id = 121;
-        $this->assertFalse($this->matchCondition->match('[PIDupinRootline = 121]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertFalse($subject->match('[PIDupinRootline = 121]'));
     }
 
     /**
@@ -516,7 +566,8 @@ class ConditionMatcherTest extends UnitTestCase
     public function PIDinRootlineConditionMatchesSinglePageIdInRootline(): void
     {
         $GLOBALS['TSFE']->id = 121;
-        $this->assertTrue($this->matchCondition->match('[PIDinRootline = 111]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertTrue($subject->match('[PIDinRootline = 111]'));
     }
 
     /**
@@ -527,7 +578,8 @@ class ConditionMatcherTest extends UnitTestCase
     public function PIDinRootlineConditionMatchesMultiplePageIdsInRootline(): void
     {
         $GLOBALS['TSFE']->id = 121;
-        $this->assertTrue($this->matchCondition->match('[PIDinRootline = 999,111,101]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertTrue($subject->match('[PIDinRootline = 999,111,101]'));
     }
 
     /**
@@ -538,7 +590,8 @@ class ConditionMatcherTest extends UnitTestCase
     public function PIDinRootlineConditionMatchesLastPageIdInRootline(): void
     {
         $GLOBALS['TSFE']->id = 121;
-        $this->assertTrue($this->matchCondition->match('[PIDinRootline = 121]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertTrue($subject->match('[PIDinRootline = 121]'));
     }
 
     /**
@@ -549,7 +602,8 @@ class ConditionMatcherTest extends UnitTestCase
     public function PIDinRootlineConditionDoesNotMatchPageIdNotInRootline(): void
     {
         $GLOBALS['TSFE']->id = 121;
-        $this->assertFalse($this->matchCondition->match('[PIDinRootline = 999]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertFalse($subject->match('[PIDinRootline = 999]'));
     }
 
     /**
@@ -560,7 +614,8 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function compatVersionConditionMatchesOlderRelease(): void
     {
-        $this->assertTrue($this->matchCondition->match('[compatVersion = 7.0]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertTrue($subject->match('[compatVersion = 7.0]'));
     }
 
     /**
@@ -571,7 +626,8 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function compatVersionConditionMatchesSameRelease(): void
     {
-        $this->assertTrue($this->matchCondition->match('[compatVersion = ' . TYPO3_branch . ']'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertTrue($subject->match('[compatVersion = ' . TYPO3_branch . ']'));
     }
 
     /**
@@ -582,7 +638,8 @@ class ConditionMatcherTest extends UnitTestCase
      */
     public function compatVersionConditionDoesNotMatchNewerRelease(): void
     {
-        $this->assertFalse($this->matchCondition->match('[compatVersion = 15.0]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertFalse($subject->match('[compatVersion = 15.0]'));
     }
 
     /**
@@ -594,8 +651,9 @@ class ConditionMatcherTest extends UnitTestCase
     {
         $_GET = ['testGet' => 'getTest'];
         $_POST = ['testPost' => 'postTest'];
-        $this->assertTrue($this->matchCondition->match('[globalString = GP:testGet = getTest]'));
-        $this->assertTrue($this->matchCondition->match('[globalString = GP:testPost = postTest]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertTrue($subject->match('[globalString = GP:testGet = getTest]'));
+        $this->assertTrue($subject->match('[globalString = GP:testPost = postTest]'));
     }
 
     /**
@@ -609,8 +667,9 @@ class ConditionMatcherTest extends UnitTestCase
         $GLOBALS['TSFE']->testSimpleObject = new \stdClass();
         $GLOBALS['TSFE']->testSimpleObject->testSimpleVariable = 'testValue';
 
-        $this->assertTrue($this->matchCondition->match('[globalString = TSFE:id = 1234567]'));
-        $this->assertTrue($this->matchCondition->match('[globalString = TSFE:testSimpleObject|testSimpleVariable = testValue]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertTrue($subject->match('[globalString = TSFE:id = 1234567]'));
+        $this->assertTrue($subject->match('[globalString = TSFE:testSimpleObject|testSimpleVariable = testValue]'));
     }
 
     /**
@@ -624,7 +683,8 @@ class ConditionMatcherTest extends UnitTestCase
         $prophecy->getSessionData(Argument::exact('foo'))->willReturn(['bar' => 1234567]);
         $GLOBALS['TSFE']->fe_user = $prophecy->reveal();
 
-        $this->assertTrue($this->matchCondition->match('[globalString = session:foo|bar = 1234567]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertTrue($subject->match('[globalString = session:foo|bar = 1234567]'));
     }
 
     /**
@@ -636,7 +696,8 @@ class ConditionMatcherTest extends UnitTestCase
     {
         $testKey = $this->getUniqueId('test');
         putenv($testKey . '=testValue');
-        $this->assertTrue($this->matchCondition->match('[globalString = ENV:' . $testKey . ' = testValue]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertTrue($subject->match('[globalString = ENV:' . $testKey . ' = testValue]'));
     }
 
     /**
@@ -647,7 +708,8 @@ class ConditionMatcherTest extends UnitTestCase
     public function genericGetVariablesSucceedsWithNamespaceIENV(): void
     {
         $_SERVER['HTTP_HOST'] = GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY') . ':1234567';
-        $this->assertTrue($this->matchCondition->match('[globalString = IENV:TYPO3_PORT = 1234567]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertTrue($subject->match('[globalString = IENV:TYPO3_PORT = 1234567]'));
     }
 
     /**
@@ -661,8 +723,9 @@ class ConditionMatcherTest extends UnitTestCase
             'first' => 'testFirst',
             'second' => ['third' => 'testThird']
         ];
-        $this->assertTrue($this->matchCondition->match('[globalString = ' . $this->testGlobalNamespace . '|first = testFirst]'));
-        $this->assertTrue($this->matchCondition->match('[globalString = ' . $this->testGlobalNamespace . '|second|third = testThird]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertTrue($subject->match('[globalString = ' . $this->testGlobalNamespace . '|first = testFirst]'));
+        $this->assertTrue($subject->match('[globalString = ' . $this->testGlobalNamespace . '|second|third = testThird]'));
     }
 
     /**
@@ -688,8 +751,9 @@ class ConditionMatcherTest extends UnitTestCase
         ]);
         $GLOBALS['TYPO3_REQUEST'] = new ServerRequest();
         $GLOBALS['TYPO3_REQUEST'] = $GLOBALS['TYPO3_REQUEST']->withAttribute('language', $site->getLanguageById(0));
-        $this->assertTrue($this->matchCondition->match('[siteLanguage = locale = en_US.UTF-8]'));
-        $this->assertTrue($this->matchCondition->match('[siteLanguage = locale = de_DE, locale = en_US.UTF-8]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertTrue($subject->match('[siteLanguage = locale = en_US.UTF-8]'));
+        $this->assertTrue($subject->match('[siteLanguage = locale = de_DE, locale = en_US.UTF-8]'));
     }
 
     /**
@@ -715,8 +779,9 @@ class ConditionMatcherTest extends UnitTestCase
         ]);
         $GLOBALS['TYPO3_REQUEST'] = new ServerRequest();
         $GLOBALS['TYPO3_REQUEST'] = $GLOBALS['TYPO3_REQUEST']->withAttribute('language', $site->getLanguageById(0));
-        $this->assertFalse($this->matchCondition->match('[siteLanguage = locale = en_UK.UTF-8]'));
-        $this->assertFalse($this->matchCondition->match('[siteLanguage = locale = de_DE, title = UK]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertFalse($subject->match('[siteLanguage = locale = en_UK.UTF-8]'));
+        $this->assertFalse($subject->match('[siteLanguage = locale = de_DE, title = UK]'));
     }
 
     /**
@@ -729,9 +794,10 @@ class ConditionMatcherTest extends UnitTestCase
         $site = new Site('angelo', 13, ['languages' => [], 'base' => 'https://typo3.org/']);
         $GLOBALS['TYPO3_REQUEST'] = new ServerRequest();
         $GLOBALS['TYPO3_REQUEST'] = $GLOBALS['TYPO3_REQUEST']->withAttribute('language', $site->getLanguageById(0));
-        $this->assertTrue($this->matchCondition->match('[site = identifier = angelo]'));
-        $this->assertTrue($this->matchCondition->match('[site = rootPageId = 13]'));
-        $this->assertTrue($this->matchCondition->match('[site = base = https://typo3.org/]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertTrue($subject->match('[site = identifier = angelo]'));
+        $this->assertTrue($subject->match('[site = rootPageId = 13]'));
+        $this->assertTrue($subject->match('[site = base = https://typo3.org/]'));
     }
 
     /**
@@ -757,8 +823,9 @@ class ConditionMatcherTest extends UnitTestCase
         ]);
         $GLOBALS['TYPO3_REQUEST'] = new ServerRequest();
         $GLOBALS['TYPO3_REQUEST'] = $GLOBALS['TYPO3_REQUEST']->withAttribute('language', $site->getLanguageById(0));
-        $this->assertFalse($this->matchCondition->match('[site = identifier = berta]'));
-        $this->assertFalse($this->matchCondition->match('[site = rootPageId = 14, rootPageId=23]'));
+        $subject = new ConditionMatcher(new Context());
+        $this->assertFalse($subject->match('[site = identifier = berta]'));
+        $this->assertFalse($subject->match('[site = rootPageId = 14, rootPageId=23]'));
     }
 
     /**
@@ -768,7 +835,8 @@ class ConditionMatcherTest extends UnitTestCase
     {
         $this->expectException(InvalidTypoScriptConditionException::class);
         $this->expectExceptionCode(1410286153);
-        $this->matchCondition->match('[stdClass = foo]');
+        $subject = new ConditionMatcher(new Context());
+        $subject->match('[stdClass = foo]');
     }
 
     /**
@@ -778,6 +846,7 @@ class ConditionMatcherTest extends UnitTestCase
     {
         $this->expectException(TestConditionException::class);
         $this->expectExceptionCode(1411581139);
-        $this->matchCondition->match('[TYPO3\\CMS\\Frontend\\Tests\\Unit\\Configuration\\TypoScript\\ConditionMatching\\Fixtures\\TestCondition = 7, != 6]');
+        $subject = new ConditionMatcher(new Context());
+        $subject->match('[TYPO3\\CMS\\Frontend\\Tests\\Unit\\Configuration\\TypoScript\\ConditionMatching\\Fixtures\\TestCondition = 7, != 6]');
     }
 }
index 183b2cd..1defc4f 100644 (file)
@@ -15,8 +15,11 @@ namespace TYPO3\CMS\Frontend\Tests\Unit\ContentObject;
  */
 
 use Prophecy\Argument;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface as CacheFrontendInterface;
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\UserAspect;
 use TYPO3\CMS\Core\Core\ApplicationContext;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\LinkHandling\LinkService;
@@ -140,6 +143,7 @@ class ContentObjectRendererTest extends UnitTestCase
                 '',
                 false
             );
+        $this->frontendControllerMock->_set('context', GeneralUtility::makeInstance(Context::class));
         $this->frontendControllerMock->tmpl = $this->templateServiceMock;
         $this->frontendControllerMock->config = [];
         $this->frontendControllerMock->page =  [];
@@ -4868,7 +4872,13 @@ class ContentObjectRendererTest extends UnitTestCase
         $param3,
         $will
     ) {
-        $GLOBALS['TSFE']->beUserLogin = $login;
+        if ($login) {
+            $backendUser = new BackendUserAuthentication();
+            $backendUser->user['uid'] = 13;
+            GeneralUtility::makeInstance(Context::class)->setAspect('backend.user', new UserAspect($backendUser));
+        } else {
+            GeneralUtility::makeInstance(Context::class)->setAspect('backend.user', new UserAspect());
+        }
         $subject = $this->getMockBuilder(ContentObjectRenderer::class)
             ->setMethods(['editIcons'])->getMock();
         $subject
@@ -5076,7 +5086,13 @@ class ContentObjectRendererTest extends UnitTestCase
         $times,
         $will
     ) {
-        $GLOBALS['TSFE']->beUserLogin = $login;
+        if ($login) {
+            $backendUser = new BackendUserAuthentication();
+            $backendUser->user['uid'] = 13;
+            GeneralUtility::makeInstance(Context::class)->setAspect('backend.user', new UserAspect($backendUser));
+        } else {
+            GeneralUtility::makeInstance(Context::class)->setAspect('backend.user', new UserAspect());
+        }
         $conf = ['editPanel.' => [$this->getUniqueId('editPanel.')]];
         $subject = $this->getMockBuilder(ContentObjectRenderer::class)
             ->setMethods(['editPanel'])->getMock();
index ec63f48..0ca4c52 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Frontend\Tests\Unit\Page;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Context\Context;
+
 /**
  * Test case
  */
@@ -48,7 +50,8 @@ class PageRepositoryTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
      */
     protected function setUp()
     {
-        $this->pageSelectObject = $this->getAccessibleMock(\TYPO3\CMS\Frontend\Page\PageRepository::class, ['getMultipleGroupsWhereClause']);
+        $this->pageSelectObject = $this->getAccessibleMock(\TYPO3\CMS\Frontend\Page\PageRepository::class, ['getMultipleGroupsWhereClause'], [], '', false);
+        $this->pageSelectObject->_set('context', new Context());
         $this->pageSelectObject->expects($this->any())->method('getMultipleGroupsWhereClause')->will($this->returnValue(' AND 1=1'));
     }
 
index 2f15055..4b3a239 100644 (file)
@@ -16,6 +16,7 @@ namespace TYPO3\CMS\IndexedSearch\Domain\Repository;
 
 use Doctrine\DBAL\Driver\Statement;
 use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
+use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\QueryHelper;
@@ -177,7 +178,7 @@ class IndexSearchRepository
         $this->indexerObj = GeneralUtility::makeInstance(Indexer::class);
         $this->externalParsers = $externalParsers;
         $this->searchRootPageIdList = $searchRootPageIdList;
-        $this->frontendUserGroupList = $this->getTypoScriptFrontendController()->gr_list;
+        $this->frontendUserGroupList = implode(',', GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('frontend.user', 'groupIds', [0, -1]));
         // Should we use joinPagesForQuery instead of long lists of uids?
         if ($settings['searchSkipExtendToSubpagesChecking']) {
             $this->joinPagesForQuery = 1;
index 701155f..9e22af7 100644 (file)
@@ -16,6 +16,7 @@ namespace TYPO3\CMS\IndexedSearch;
 
 use TYPO3\CMS\Core\Compatibility\PublicPropertyDeprecationTrait;
 use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
+use TYPO3\CMS\Core\Context\Context;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
@@ -295,8 +296,8 @@ class Indexer
                             // sys_language UID of the language of the indexing.
                             $this->conf['MP'] = $pObj->MP;
                             // MP variable, if any (Mount Points)
-                            $this->conf['gr_list'] = $pObj->gr_list;
                             // Group list
+                            $this->conf['gr_list'] = implode(',', GeneralUtility::makeInstance(Context::class)->getPropertyFromAspect('frontend.user', 'groupIds', [0, -1]));
                             $this->conf['cHash'] = $pObj->cHash;
                             // cHash string for additional parameters
                             $this->conf['cHash_array'] = $pObj->cHash_array;
index c1503c9..5ba6e3e 100644 (file)
@@ -16,8 +16,14 @@ namespace TYPO3\CMS\Install\Http;
 
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\DateTimeAspect;
+use TYPO3\CMS\Core\Context\UserAspect;
+use TYPO3\CMS\Core\Context\VisibilityAspect;
+use TYPO3\CMS\Core\Context\WorkspaceAspect;
 use TYPO3\CMS\Core\Http\AbstractApplication;
 use TYPO3\CMS\Core\Http\RequestHandlerInterface;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * Entry point for the TYPO3 Install Tool
@@ -52,12 +58,26 @@ class Application extends AbstractApplication
      */
     protected function handle(ServerRequestInterface $request): ResponseInterface
     {
+        $this->initializeContext();
         foreach ($this->availableRequestHandlers as $handler) {
             if ($handler->canHandleRequest($request)) {
                 return $handler->handleRequest($request);
             }
         }
-
         throw new \TYPO3\CMS\Core\Exception('No suitable request handler found.', 1518448686);
     }
+
+    /**
+     * Initializes the Context used for accessing data and finding out the current state of the application
+     * Will be moved to a DI-like concept once introduced, for now, this is a singleton
+     */
+    protected function initializeContext()
+    {
+        GeneralUtility::makeInstance(Context::class, [
+            'date' => new DateTimeAspect(new \DateTimeImmutable('@' . $GLOBALS['EXEC_TIME'])),
+            'visibility' => new VisibilityAspect(true, true, true),
+            'workspace' => new WorkspaceAspect(0),
+            'backend.user' => new UserAspect(),
+        ]);
+    }
 }
index 2b46e34..40a708f 100644 (file)
@@ -411,4 +411,29 @@ return [
             'Deprecation-85125-UsagesOfCharsetConverterInCore.rst'
         ],
     ],
+    'TYPO3\CMS\Core\Controller\TypoScriptFrontendController->showHiddenPage' => [
+        'restFiles' => [
+            'Deprecation-85389-VariousPublicPropertiesInFavorOfContextAPI.rst'
+        ],
+    ],
+    'TYPO3\CMS\Core\Controller\TypoScriptFrontendController->showHiddenRecords' => [
+        'restFiles' => [
+            'Deprecation-85389-VariousPublicPropertiesInFavorOfContextAPI.rst'
+        ],
+    ],
+    'TYPO3\CMS\Core\Controller\TypoScriptFrontendController->gr_list' => [
+        'restFiles' => [
+            'Deprecation-85389-VariousPublicPropertiesInFavorOfContextAPI.rst'
+        ],
+    ],
+    'TYPO3\CMS\Core\Controller\TypoScriptFrontendController->loginUser' => [
+        'restFiles' => [
+            'Deprecation-85389-VariousPublicPropertiesInFavorOfContextAPI.rst'
+        ],
+    ],
+    'TYPO3\CMS\Core\Controller\TypoScriptFrontendController->beUserLogin' => [
+        'restFiles' => [
+            'Deprecation-85389-VariousPublicPropertiesInFavorOfContextAPI.rst'
+        ],
+    ],
 ];
index 17a11d0..2e6d6cc 100644 (file)
@@ -19,6 +19,10 @@ use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
 use Psr\Http\Server\MiddlewareInterface;
 use Psr\Http\Server\RequestHandlerInterface;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Context\Context;
+use TYPO3\CMS\Core\Context\UserAspect;
+use TYPO3\CMS\Core\Context\WorkspaceAspect;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Http\HtmlResponse;
 use TYPO3\CMS\Core\Http\NormalizedParams;
@@ -79,7 +83,8 @@ class WorkspacePreview implements MiddlewareInterface
                         );
                         if ($previewUser) {
                             $GLOBALS['BE_USER'] = $previewUser;
-                            $GLOBALS['TSFE']->beUserLogin = true;
+                            // Register the preview user as aspect
+                            $this->setBackendUserAspect(GeneralUtility::makeInstance(Context::class), $previewUser);
                         }
                     }
             }
@@ -90,7 +95,8 @@ class WorkspacePreview implements MiddlewareInterface
         // workspace preview module.
         if ($request->getQueryParams()['ADMCMD_noBeUser']) {
             $GLOBALS['BE_USER'] = null;
-            $GLOBALS['TSFE']->beUserLogin = false;
+            // Register the backend user as aspect
+            $this->setBackendUserAspect(GeneralUtility::makeInstance(Context::class), null);
             // Caching is disabled, because otherwise generated URLs could include the ADMCMD_noBeUser parameter
             $GLOBALS['TSFE']->set_no_cache('GET Parameter ADMCMD_noBeUser was given', true);
         }
@@ -354,4 +360,16 @@ class WorkspacePreview implements MiddlewareInterface
     {
         return $GLOBALS['LANG'] ?: GeneralUtility::makeInstance(LanguageService::class);
     }
+
+    /**
+     * Register the backend user as aspect
+     *
+     * @param Context $context
+     * @param BackendUserAuthentication $user
+     */
+    protected function setBackendUserAspect(Context $context, BackendUserAuthentication $user = null)
+    {
+        $context->setAspect('backend.user', GeneralUtility::makeInstance(UserAspect::class, $user));
+        $context->setAspect('workspace', GeneralUtility::makeInstance(WorkspaceAspect::class, $user ? $user->workspace : 0));
+    }
 }