diff --git a/composer.json b/composer.json index 128e525ad5..7950aa9c85 100644 --- a/composer.json +++ b/composer.json @@ -126,6 +126,9 @@ "allow-plugins": { "composer/package-versions-deprecated": true, "*": false + }, + "audit": { + "ignore": ["PKSA-w9tt-7782-78jx"] } }, "scripts": { diff --git a/dependencies.json b/dependencies.json new file mode 100644 index 0000000000..b18c7f710a --- /dev/null +++ b/dependencies.json @@ -0,0 +1,11 @@ +{ + "recipesEndpoint": "", + "packages": [ + { + "requirement": "dev-IBX-12204-siteaccessservice-scope-stack as 6.0.x-dev", + "repositoryUrl": "https://github.com/ibexa/behat.git", + "package": "ibexa/behat", + "shouldBeAddedAsVCS": false + } + ] +} diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index fab0bda625..0c1d92a7d2 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1596,18 +1596,6 @@ parameters: count: 1 path: src/bundle/Core/EventListener/ConfigScopeListener.php - - - message: '#^Cannot access property \$matchingType on Ibexa\\Core\\MVC\\Symfony\\SiteAccess\|null\.$#' - identifier: property.nonObject - count: 2 - path: src/bundle/Core/EventListener/ConsoleCommandListener.php - - - - message: '#^Cannot access property \$name on Ibexa\\Core\\MVC\\Symfony\\SiteAccess\|null\.$#' - identifier: property.nonObject - count: 3 - path: src/bundle/Core/EventListener/ConsoleCommandListener.php - - message: '#^Method Ibexa\\Bundle\\Core\\EventListener\\ConsoleCommandListener\:\:onConsoleCommand\(\) has no return type specified\.$#' identifier: missingType.return @@ -1626,18 +1614,6 @@ parameters: count: 1 path: src/bundle/Core/EventListener/ConsoleCommandListener.php - - - message: '#^Method Ibexa\\Bundle\\Core\\EventListener\\ConsoleCommandListener\:\:setSiteAccess\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: src/bundle/Core/EventListener/ConsoleCommandListener.php - - - - message: '#^Parameter \#1 \$siteAccess of class Ibexa\\Core\\MVC\\Symfony\\Event\\ScopeChangeEvent constructor expects Ibexa\\Core\\MVC\\Symfony\\SiteAccess, Ibexa\\Core\\MVC\\Symfony\\SiteAccess\|null given\.$#' - identifier: argument.type - count: 1 - path: src/bundle/Core/EventListener/ConsoleCommandListener.php - - message: '#^Method Ibexa\\Bundle\\Core\\EventListener\\ContentDownloadRouteReferenceListener\:\:configureOptions\(\) has no return type specified\.$#' identifier: missingType.return @@ -2316,12 +2292,6 @@ parameters: count: 1 path: src/bundle/Core/Fragment/InlineFragmentRenderer.php - - - message: '#^Property Ibexa\\Bundle\\Core\\Fragment\\InlineFragmentRenderer\:\:\$siteAccess is never read, only written\.$#' - identifier: property.onlyWritten - count: 1 - path: src/bundle/Core/Fragment/InlineFragmentRenderer.php - - message: '#^Trait Ibexa\\Bundle\\Core\\Fragment\\SiteAccessSerializationTrait is used zero times and is not analysed\.$#' identifier: trait.unused @@ -2370,24 +2340,6 @@ parameters: count: 1 path: src/bundle/Core/Imagine/Cache/AliasGeneratorDecorator.php - - - message: '#^Method Ibexa\\Bundle\\Core\\Imagine\\Cache\\AliasGeneratorDecorator\:\:setSiteAccess\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: src/bundle/Core/Imagine/Cache/AliasGeneratorDecorator.php - - - - message: '#^Property Ibexa\\Bundle\\Core\\Imagine\\Cache\\AliasGeneratorDecorator\:\:\$siteAccess \(Ibexa\\Core\\MVC\\Symfony\\SiteAccess\) does not accept Ibexa\\Core\\MVC\\Symfony\\SiteAccess\|null\.$#' - identifier: assign.propertyType - count: 1 - path: src/bundle/Core/Imagine/Cache/AliasGeneratorDecorator.php - - - - message: '#^Property Ibexa\\Core\\MVC\\Symfony\\SiteAccess\:\:\$name \(string\) on left side of \?\? is not nullable\.$#' - identifier: nullCoalesce.property - count: 2 - path: src/bundle/Core/Imagine/Cache/AliasGeneratorDecorator.php - - message: '#^Method Ibexa\\Bundle\\Core\\Imagine\\Cache\\Resolver\\RelativeResolver\:\:rewriteUrl\(\) has parameter \$url with no type specified\.$#' identifier: missingType.parameter @@ -2736,12 +2688,6 @@ parameters: count: 1 path: src/bundle/Core/Matcher/ServiceAwareMatcherFactory.php - - - message: '#^Cannot access property \$name on Ibexa\\Core\\MVC\\Symfony\\SiteAccess\|null\.$#' - identifier: property.nonObject - count: 2 - path: src/bundle/Core/SiteAccess/Config/ComplexConfigProcessor.php - - message: '#^Method Ibexa\\Bundle\\Core\\SiteAccess\\Matcher\:\:setMatchingConfiguration\(\) has no return type specified\.$#' identifier: missingType.return @@ -6636,18 +6582,6 @@ parameters: count: 1 path: src/lib/Helper/ContentPreviewHelper.php - - - message: '#^Method Ibexa\\Core\\Helper\\ContentPreviewHelper\:\:setSiteAccess\(\) has no return type specified\.$#' - identifier: missingType.return - count: 1 - path: src/lib/Helper/ContentPreviewHelper.php - - - - message: '#^Property Ibexa\\Core\\Helper\\ContentPreviewHelper\:\:\$originalSiteAccess \(Ibexa\\Core\\MVC\\Symfony\\SiteAccess\) does not accept Ibexa\\Core\\MVC\\Symfony\\SiteAccess\|null\.$#' - identifier: assign.propertyType - count: 1 - path: src/lib/Helper/ContentPreviewHelper.php - - message: '#^Cannot access property \$fieldTypeIdentifier on Ibexa\\Contracts\\Core\\Repository\\Values\\ContentType\\FieldDefinition\|null\.$#' identifier: property.nonObject diff --git a/phpunit.baseline.xml b/phpunit.baseline.xml index a5f3364ad8..0b1d89c96e 100644 --- a/phpunit.baseline.xml +++ b/phpunit.baseline.xml @@ -38,6 +38,8 @@ + + diff --git a/src/bundle/Core/Command/DebugConfigResolverCommand.php b/src/bundle/Core/Command/DebugConfigResolverCommand.php index 8062d34248..8d7ee62cb4 100644 --- a/src/bundle/Core/Command/DebugConfigResolverCommand.php +++ b/src/bundle/Core/Command/DebugConfigResolverCommand.php @@ -8,7 +8,7 @@ namespace Ibexa\Bundle\Core\Command; use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; -use Ibexa\Core\MVC\Symfony\SiteAccess; +use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; @@ -28,15 +28,14 @@ class DebugConfigResolverCommand extends Command /** @var \Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface */ private $configResolver; - /** @var \Ibexa\Core\MVC\Symfony\SiteAccess */ - private $siteAccess; + private SiteAccessServiceInterface $siteAccessService; public function __construct( ConfigResolverInterface $configResolver, - SiteAccess $siteAccess + SiteAccessServiceInterface $siteAccessService ) { $this->configResolver = $configResolver; - $this->siteAccess = $siteAccess; + $this->siteAccessService = $siteAccessService; parent::__construct(); } @@ -102,7 +101,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int return self::SUCCESS; } - $output->writeln('SiteAccess name: ' . $this->siteAccess->name); + $siteAccess = $this->siteAccessService->getCurrent(); + $output->writeln('SiteAccess name: ' . ($siteAccess !== null ? $siteAccess->name : 'none')); $output->writeln('Parameter:'); $cloner = new VarCloner(); diff --git a/src/bundle/Core/DependencyInjection/Compiler/FragmentPass.php b/src/bundle/Core/DependencyInjection/Compiler/FragmentPass.php index 2ddea81531..e7ec9ae847 100644 --- a/src/bundle/Core/DependencyInjection/Compiler/FragmentPass.php +++ b/src/bundle/Core/DependencyInjection/Compiler/FragmentPass.php @@ -11,6 +11,7 @@ use Ibexa\Bundle\Core\Fragment\FragmentListenerFactory; use Ibexa\Bundle\Core\Fragment\InlineFragmentRenderer; use Ibexa\Bundle\Core\Fragment\SiteAccessSerializerInterface; +use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -49,7 +50,11 @@ public function process(ContainerBuilder $container): void $container->setDefinition($renamedId, $definition); $decoratedDef = new ChildDefinition(DecoratedFragmentRenderer::class); - $decoratedDef->setArguments([new Reference($renamedId), new Reference(SiteAccessSerializerInterface::class)]); + $decoratedDef->setArguments([ + new Reference($renamedId), + new Reference(SiteAccessSerializerInterface::class), + new Reference(SiteAccessServiceInterface::class), + ]); $decoratedDef->setPublic($public); $decoratedDef->setTags($tags); // Special treatment for inline fragment renderer, to fit ESI renderer constructor type hinting (forced to InlineFragmentRenderer) diff --git a/src/bundle/Core/DependencyInjection/Compiler/SecurityPass.php b/src/bundle/Core/DependencyInjection/Compiler/SecurityPass.php index 43e5d14dc5..7c9a2b3e4b 100644 --- a/src/bundle/Core/DependencyInjection/Compiler/SecurityPass.php +++ b/src/bundle/Core/DependencyInjection/Compiler/SecurityPass.php @@ -12,7 +12,7 @@ use Ibexa\Core\MVC\Symfony\Security\Authentication\AnonymousUserAccessListener; use Ibexa\Core\MVC\Symfony\Security\Authentication\DefaultAuthenticationSuccessHandler; use Ibexa\Core\MVC\Symfony\Security\HttpUtils; -use Ibexa\Core\MVC\Symfony\SiteAccess; +use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\OutOfBoundsException; @@ -39,10 +39,7 @@ private function registerHttpUtils(ContainerBuilder $container): void $httpUtilsDef = $container->findDefinition('security.http_utils'); $httpUtilsDef->setClass(HttpUtils::class); - $httpUtilsDef->addMethodCall( - 'setSiteAccess', - [new Reference(SiteAccess::class)] - ); + $httpUtilsDef->setArgument('$siteAccessService', new Reference(SiteAccessServiceInterface::class)); } private function registerSecurityAuthenticationSuccessHandler(ContainerBuilder $container): void diff --git a/src/bundle/Core/EventListener/ConsoleCommandListener.php b/src/bundle/Core/EventListener/ConsoleCommandListener.php index ab882de5c5..fd5af47a96 100644 --- a/src/bundle/Core/EventListener/ConsoleCommandListener.php +++ b/src/bundle/Core/EventListener/ConsoleCommandListener.php @@ -8,16 +8,13 @@ namespace Ibexa\Bundle\Core\EventListener; use Ibexa\Core\MVC\Exception\InvalidSiteAccessException; -use Ibexa\Core\MVC\Symfony\Event\ScopeChangeEvent; -use Ibexa\Core\MVC\Symfony\MVCEvents; use Ibexa\Core\MVC\Symfony\SiteAccess; -use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessAware; +use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface; use Symfony\Component\Console\ConsoleEvents; use Symfony\Component\Console\Event\ConsoleCommandEvent; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -class ConsoleCommandListener implements EventSubscriberInterface, SiteAccessAware +class ConsoleCommandListener implements EventSubscriberInterface { /** @var string */ private $defaultSiteAccessName; @@ -25,11 +22,7 @@ class ConsoleCommandListener implements EventSubscriberInterface, SiteAccessAwar /** @var \Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessProviderInterface */ private $siteAccessProvider; - /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface */ - private $eventDispatcher; - - /** @var \Ibexa\Core\MVC\Symfony\SiteAccess|null */ - private $siteAccess; + private SiteAccessServiceInterface $siteAccessService; /** @var bool */ private $debug; @@ -37,12 +30,12 @@ class ConsoleCommandListener implements EventSubscriberInterface, SiteAccessAwar public function __construct( string $defaultSiteAccessName, SiteAccess\SiteAccessProviderInterface $siteAccessProvider, - EventDispatcherInterface $eventDispatcher, + SiteAccessServiceInterface $siteAccessService, bool $debug = false ) { $this->defaultSiteAccessName = $defaultSiteAccessName; $this->siteAccessProvider = $siteAccessProvider; - $this->eventDispatcher = $eventDispatcher; + $this->siteAccessService = $siteAccessService; $this->debug = $debug; } @@ -57,24 +50,19 @@ public static function getSubscribedEvents(): array public function onConsoleCommand(ConsoleCommandEvent $event) { - $this->siteAccess->name = $event->getInput()->getParameterOption('--siteaccess', $this->defaultSiteAccessName); - $this->siteAccess->matchingType = 'cli'; + $siteAccessName = $event->getInput()->getParameterOption('--siteaccess', $this->defaultSiteAccessName); + $siteAccess = new SiteAccess($siteAccessName, 'cli'); - if (!$this->siteAccessProvider->isDefined($this->siteAccess->name)) { + if (!$this->siteAccessProvider->isDefined($siteAccess->name)) { throw new InvalidSiteAccessException( - $this->siteAccess->name, + $siteAccess->name, $this->siteAccessProvider, - $this->siteAccess->matchingType, + $siteAccess->matchingType, $this->debug ); } - $this->eventDispatcher->dispatch(new ScopeChangeEvent($this->siteAccess), MVCEvents::CONFIG_SCOPE_CHANGE); - } - - public function setSiteAccess(?SiteAccess $siteAccess = null) - { - $this->siteAccess = $siteAccess; + $this->siteAccessService->changeSiteAccess($siteAccess); } public function setDebug($debug = false) diff --git a/src/bundle/Core/Fragment/DecoratedFragmentRenderer.php b/src/bundle/Core/Fragment/DecoratedFragmentRenderer.php index b13bc5d6c3..2f0e68d37e 100644 --- a/src/bundle/Core/Fragment/DecoratedFragmentRenderer.php +++ b/src/bundle/Core/Fragment/DecoratedFragmentRenderer.php @@ -8,32 +8,29 @@ namespace Ibexa\Bundle\Core\Fragment; use Ibexa\Core\MVC\Symfony\SiteAccess; -use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessAware; +use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Controller\ControllerReference; use Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface; use Symfony\Component\HttpKernel\Fragment\RoutableFragmentRenderer; -class DecoratedFragmentRenderer implements FragmentRendererInterface, SiteAccessAware +class DecoratedFragmentRenderer implements FragmentRendererInterface { private FragmentRendererInterface $innerRenderer; - private ?SiteAccess $siteAccess = null; - private SiteAccessSerializerInterface $siteAccessSerializer; + private SiteAccessServiceInterface $siteAccessService; + public function __construct( FragmentRendererInterface $innerRenderer, - SiteAccessSerializerInterface $siteAccessSerializer + SiteAccessSerializerInterface $siteAccessSerializer, + SiteAccessServiceInterface $siteAccessService ) { $this->innerRenderer = $innerRenderer; $this->siteAccessSerializer = $siteAccessSerializer; - } - - public function setSiteAccess(?SiteAccess $siteAccess = null): void - { - $this->siteAccess = $siteAccess; + $this->siteAccessService = $siteAccessService; } public function setFragmentPath(string $path): void @@ -42,7 +39,7 @@ public function setFragmentPath(string $path): void return; } - $matcher = $this->siteAccess?->matcher; + $matcher = $this->siteAccessService->getCurrent()?->matcher; if ($matcher instanceof SiteAccess\URILexer) { $path = $matcher->analyseLink($path); } diff --git a/src/bundle/Core/Fragment/InlineFragmentRenderer.php b/src/bundle/Core/Fragment/InlineFragmentRenderer.php index cb7f348f2f..3607a53984 100644 --- a/src/bundle/Core/Fragment/InlineFragmentRenderer.php +++ b/src/bundle/Core/Fragment/InlineFragmentRenderer.php @@ -7,8 +7,6 @@ namespace Ibexa\Bundle\Core\Fragment; -use Ibexa\Core\MVC\Symfony\SiteAccess; -use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessAware; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Controller\ControllerReference; @@ -16,12 +14,10 @@ use Symfony\Component\HttpKernel\Fragment\InlineFragmentRenderer as BaseRenderer; use Symfony\Component\HttpKernel\Fragment\RoutableFragmentRenderer; -class InlineFragmentRenderer extends BaseRenderer implements SiteAccessAware, FragmentRendererInterface +class InlineFragmentRenderer extends BaseRenderer implements FragmentRendererInterface { protected FragmentRendererInterface $innerRenderer; - private ?SiteAccess $siteAccess = null; - private SiteAccessSerializerInterface $siteAccessSerializer; public function __construct( @@ -39,11 +35,6 @@ public function setFragmentPath($path): void } } - public function setSiteAccess(?SiteAccess $siteAccess = null): void - { - $this->siteAccess = $siteAccess; - } - /** * @param array $options */ diff --git a/src/bundle/Core/Imagine/Cache/AliasGeneratorDecorator.php b/src/bundle/Core/Imagine/Cache/AliasGeneratorDecorator.php index 3416d2068f..03ec2447b6 100644 --- a/src/bundle/Core/Imagine/Cache/AliasGeneratorDecorator.php +++ b/src/bundle/Core/Imagine/Cache/AliasGeneratorDecorator.php @@ -11,8 +11,7 @@ use Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo; use Ibexa\Contracts\Core\Variation\Values\Variation; use Ibexa\Contracts\Core\Variation\VariationHandler; -use Ibexa\Core\MVC\Symfony\SiteAccess; -use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessAware; +use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface; use Ibexa\Core\Persistence\Cache\Identifier\CacheIdentifierGeneratorInterface; use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface; use Symfony\Component\Routing\RequestContext; @@ -20,7 +19,7 @@ /** * Persistence Cache layer for AliasGenerator. */ -class AliasGeneratorDecorator implements VariationHandler, SiteAccessAware +class AliasGeneratorDecorator implements VariationHandler { private const IMAGE_VARIATION_IDENTIFIER = 'image_variation'; private const IMAGE_VARIATION_SITEACCESS_IDENTIFIER = 'image_variation_siteaccess'; @@ -36,15 +35,14 @@ class AliasGeneratorDecorator implements VariationHandler, SiteAccessAware /** @var \Symfony\Component\Cache\Adapter\TagAwareAdapterInterface */ private $cache; - /** @var \Ibexa\Core\MVC\Symfony\SiteAccess */ - private $siteAccess; - /** @var \Symfony\Component\Routing\RequestContext */ private $requestContext; /** @var \Ibexa\Core\Persistence\Cache\Identifier\CacheIdentifierGeneratorInterface */ private $cacheIdentifierGenerator; + private SiteAccessServiceInterface $siteAccessService; + /** * @param \Ibexa\Contracts\Core\Variation\VariationHandler $aliasGenerator * @param \Symfony\Component\Cache\Adapter\TagAwareAdapterInterface $cache @@ -55,12 +53,14 @@ public function __construct( VariationHandler $aliasGenerator, TagAwareAdapterInterface $cache, RequestContext $requestContext, - CacheIdentifierGeneratorInterface $cacheIdentifierGenerator + CacheIdentifierGeneratorInterface $cacheIdentifierGenerator, + SiteAccessServiceInterface $siteAccessService ) { $this->aliasGenerator = $aliasGenerator; $this->cache = $cache; $this->requestContext = $requestContext; $this->cacheIdentifierGenerator = $cacheIdentifierGenerator; + $this->siteAccessService = $siteAccessService; } /** @@ -87,14 +87,6 @@ public function getVariation(Field $field, VersionInfo $versionInfo, string $var return $image; } - /** - * @param \Ibexa\Core\MVC\Symfony\SiteAccess|null $siteAccess - */ - public function setSiteAccess(?SiteAccess $siteAccess = null) - { - $this->siteAccess = $siteAccess; - } - /** * @param \Ibexa\Contracts\Core\Repository\Values\Content\Field $field * @param \Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo $versionInfo @@ -104,9 +96,11 @@ public function setSiteAccess(?SiteAccess $siteAccess = null) */ private function getCacheKey(Field $field, VersionInfo $versionInfo, $variationName): string { + $siteAccess = $this->siteAccessService->getCurrent(); + return sprintf( $this->cacheIdentifierGenerator->generateKey(self::IMAGE_VARIATION_IDENTIFIER, [], true) . '-%s-%s-%s-%d-%d-%d-%s-%s', - $this->siteAccess->name ?? 'default', + $siteAccess !== null ? $siteAccess->name : 'default', $this->requestContext->getScheme(), $this->requestContext->getHost(), $this->requestContext->getScheme() === 'https' ? $this->requestContext->getHttpsPort() : $this->requestContext->getHttpPort(), @@ -120,11 +114,12 @@ private function getCacheKey(Field $field, VersionInfo $versionInfo, $variationN private function getTagsForVariation(Field $field, VersionInfo $versionInfo, string $variationName): array { $contentId = $versionInfo->getContentInfo()->id; + $siteAccess = $this->siteAccessService->getCurrent(); return [ $this->cacheIdentifierGenerator->generateTag(self::IMAGE_VARIATION_IDENTIFIER), $this->cacheIdentifierGenerator->generateTag(self::IMAGE_VARIATION_NAME_IDENTIFIER, [$variationName]), - $this->cacheIdentifierGenerator->generateTag(self::IMAGE_VARIATION_SITEACCESS_IDENTIFIER, [$this->siteAccess->name ?? 'default']), + $this->cacheIdentifierGenerator->generateTag(self::IMAGE_VARIATION_SITEACCESS_IDENTIFIER, [$siteAccess !== null ? $siteAccess->name : 'default']), $this->cacheIdentifierGenerator->generateTag(self::IMAGE_VARIATION_CONTENT_IDENTIFIER, [$contentId]), $this->cacheIdentifierGenerator->generateTag(self::IMAGE_VARIATION_FIELD_IDENTIFIER, [$field->id]), $this->cacheIdentifierGenerator->generateTag(self::CONTENT_IDENTIFIER, [$contentId]), diff --git a/src/bundle/Core/Resources/config/commands.yml b/src/bundle/Core/Resources/config/commands.yml index f8d089acc5..87ebdf2176 100644 --- a/src/bundle/Core/Resources/config/commands.yml +++ b/src/bundle/Core/Resources/config/commands.yml @@ -12,7 +12,7 @@ services: class: Ibexa\Bundle\Core\Command\DebugConfigResolverCommand arguments: - '@ibexa.config.resolver' - - '@Ibexa\Core\MVC\Symfony\SiteAccess' + - '@Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface' tags: - { name: console.command } diff --git a/src/bundle/Core/Resources/config/helpers.yml b/src/bundle/Core/Resources/config/helpers.yml index ceb16c89cf..9e154f1e8d 100644 --- a/src/bundle/Core/Resources/config/helpers.yml +++ b/src/bundle/Core/Resources/config/helpers.yml @@ -16,9 +16,9 @@ services: arguments: ['@Ibexa\Core\Helper\TranslationHelper', '@ibexa.api.service.field_type'] Ibexa\Core\Helper\ContentPreviewHelper: - arguments: ["@event_dispatcher", '@Ibexa\Core\MVC\Symfony\SiteAccess\Router'] - calls: - - [setSiteAccess, ['@Ibexa\Core\MVC\Symfony\SiteAccess']] + arguments: + - '@Ibexa\Core\MVC\Symfony\SiteAccess\Router' + - '@Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface' Ibexa\Bundle\Core\EventListener\ConfigScopeListener: arguments: @@ -50,7 +50,7 @@ services: Ibexa\Bundle\Core\SiteAccess\Config\ComplexConfigProcessor: arguments: $configResolver: '@ibexa.config.resolver' - $siteAccessService: '@Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessService' + $siteAccessService: '@Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface' Ibexa\Contracts\Core\SiteAccess\ConfigProcessor: alias: Ibexa\Bundle\Core\SiteAccess\Config\ComplexConfigProcessor diff --git a/src/bundle/Core/Resources/config/image.yml b/src/bundle/Core/Resources/config/image.yml index d3a1f4fe91..c26d10e462 100644 --- a/src/bundle/Core/Resources/config/image.yml +++ b/src/bundle/Core/Resources/config/image.yml @@ -68,8 +68,7 @@ services: - '@ibexa.cache_pool' - '@router.request_context' - '@Ibexa\Core\Persistence\Cache\Identifier\CacheIdentifierGeneratorInterface' - calls: - - [setSiteAccess, ['@Ibexa\Core\MVC\Symfony\SiteAccess']] + - '@Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface' ibexa.image_alias.imagine.alias_generator: class: Ibexa\Bundle\Core\Imagine\AliasGenerator diff --git a/src/bundle/Core/Resources/config/routing.yml b/src/bundle/Core/Resources/config/routing.yml index 1e0fbe170d..0e04eff1ee 100644 --- a/src/bundle/Core/Resources/config/routing.yml +++ b/src/bundle/Core/Resources/config/routing.yml @@ -20,8 +20,7 @@ services: $siteAccessRouter: '@Ibexa\Core\MVC\Symfony\SiteAccess\Router' $nonSiteAccessAwareRoutes: '%ibexa.default_router.non_site_access_aware_routes%' $logger: '@?logger' - calls: - - [setSiteAccess, ['@?Ibexa\Core\MVC\Symfony\SiteAccess']] + $siteAccessService: '@Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface' tags: - { name: monolog.logger, channel: router } @@ -37,6 +36,7 @@ services: $siteAccessRouter: '@Ibexa\Core\MVC\Symfony\SiteAccess\Router' $eventDispatcher: '@event_dispatcher' $serializer: '@ibexa.core.mvc.serializer' + $siteAccessService: '@Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface' tags: - { name: kernel.event_subscriber } @@ -45,6 +45,7 @@ services: arguments: $requestStack: '@request_stack' $eventDispatcher: '@event_dispatcher' + $siteAccessService: '@Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface' tags: - { name: kernel.event_subscriber } @@ -53,7 +54,6 @@ services: abstract: true calls: - [setRequestContext, ["@router.request_context"]] - - [setSiteAccess, ['@?Ibexa\Core\MVC\Symfony\SiteAccess']] - [setSiteAccessRouter, ['@Ibexa\Core\MVC\Symfony\SiteAccess\Router']] - [setLogger, ["@?logger"]] @@ -77,6 +77,7 @@ services: - '@ibexa.api.repository' - "@router.default" - '@ibexa.config.resolver' + - '@Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface' - '%ibexa.urlalias_generator.charmap%' parent: Ibexa\Core\MVC\Symfony\Routing\Generator @@ -110,8 +111,7 @@ services: arguments: - '@ibexa.siteaccess.provider' - '@ibexa.config.resolver' - calls: - - [setSiteAccess, ['@Ibexa\Core\MVC\Symfony\SiteAccess']] + - '@event_dispatcher' tags: - { name: kernel.event_subscriber } diff --git a/src/bundle/Core/Resources/config/services.yml b/src/bundle/Core/Resources/config/services.yml index 8501e54cbc..59ecbcc452 100644 --- a/src/bundle/Core/Resources/config/services.yml +++ b/src/bundle/Core/Resources/config/services.yml @@ -74,10 +74,8 @@ services: arguments: - '%ibexa.site_access.default%' - '@ibexa.siteaccess.provider' - - "@event_dispatcher" + - '@Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface' - "%kernel.debug%" - calls: - - [setSiteAccess, ['@Ibexa\Core\MVC\Symfony\SiteAccess']] tags: - { name: kernel.event_subscriber } @@ -169,7 +167,6 @@ services: arguments: [] calls: - [setFragmentPath, ["%fragment.path%"]] - - [setSiteAccess, ['@Ibexa\Core\MVC\Symfony\SiteAccess']] abstract: true Ibexa\Bundle\Core\Fragment\DirectFragmentRenderer: diff --git a/src/bundle/Core/Routing/DefaultRouter.php b/src/bundle/Core/Routing/DefaultRouter.php index da3000e2e3..e99fd8055f 100644 --- a/src/bundle/Core/Routing/DefaultRouter.php +++ b/src/bundle/Core/Routing/DefaultRouter.php @@ -11,8 +11,8 @@ use Ibexa\Core\MVC\Symfony\Routing\RequestContextFactory; use Ibexa\Core\MVC\Symfony\Routing\SimplifiedRequest; use Ibexa\Core\MVC\Symfony\SiteAccess; -use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessAware; use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessRouterInterface; +use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface; use Ibexa\Core\MVC\Symfony\SiteAccess\URILexer; use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\Request; @@ -28,10 +28,8 @@ * Matching honours the `semanticPathinfo` request attribute set by the SiteAccess matcher, and link generation * prepends the SiteAccess URI part (for URI-based SiteAccess matchers) and supports the `siteaccess` route parameter. */ -final class DefaultRouter implements RouterInterface, RequestMatcherInterface, WarmableInterface, SiteAccessAware +final class DefaultRouter implements RouterInterface, RequestMatcherInterface, WarmableInterface { - private ?SiteAccess $siteAccess = null; - /** * @param string[] $nonSiteAccessAwareRoutes route name prefixes that are not supposed to be SiteAccess aware, * i.e. routes pointing to asset generation @@ -39,16 +37,12 @@ final class DefaultRouter implements RouterInterface, RequestMatcherInterface, W public function __construct( private readonly RouterInterface&RequestMatcherInterface $innerRouter, private readonly SiteAccessRouterInterface $siteAccessRouter, + private readonly SiteAccessServiceInterface $siteAccessService, private readonly array $nonSiteAccessAwareRoutes = [], private readonly ?LoggerInterface $logger = null ) { } - public function setSiteAccess(?SiteAccess $siteAccess = null): void - { - $this->siteAccess = $siteAccess; - } - public function setContext(RequestContext $context): void { $this->innerRouter->setContext($context); @@ -93,7 +87,7 @@ public function matchRequest(Request $request): array */ public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH): string { - $siteAccess = $this->siteAccess; + $siteAccess = $this->siteAccessService->getCurrent(); $originalContext = $context = $this->getContext(); $isSiteAccessAware = $this->isSiteAccessAwareRoute($name); @@ -105,7 +99,7 @@ public function generate(string $name, array $parameters = [], int $referenceTyp $context = $this->getContextBySimplifiedRequest($siteAccess->matcher->getRequest()); $this->setContext($context); } else { - $siteAccess = $this->siteAccess; + $siteAccess = $this->siteAccessService->getCurrent(); $this->logger?->notice("Could not generate a link using provided 'siteaccess' parameter: {$parameters['siteaccess']}. Generating using current context."); } diff --git a/src/bundle/Core/SiteAccess/Config/ComplexConfigProcessor.php b/src/bundle/Core/SiteAccess/Config/ComplexConfigProcessor.php index 5329e4dffc..8ea2c66d60 100644 --- a/src/bundle/Core/SiteAccess/Config/ComplexConfigProcessor.php +++ b/src/bundle/Core/SiteAccess/Config/ComplexConfigProcessor.php @@ -11,8 +11,9 @@ use Ibexa\Bundle\Core\DependencyInjection\Configuration\ComplexSettings\ComplexSettingParser; use Ibexa\Contracts\Core\SiteAccess\ConfigProcessor; use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; +use Ibexa\Core\Base\Exceptions\InvalidArgumentException; use Ibexa\Core\MVC\Exception\ParameterNotFoundException; -use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessService; +use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface; use function str_replace; final class ComplexConfigProcessor implements ConfigProcessor @@ -22,7 +23,7 @@ final class ComplexConfigProcessor implements ConfigProcessor /** @var \Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface */ private $configResolver; - /** @var \Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessService */ + /** @var \Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface */ private $siteAccessService; /** @var \Ibexa\Bundle\Core\DependencyInjection\Configuration\ComplexSettings\ComplexSettingParserInterface */ @@ -30,7 +31,7 @@ final class ComplexConfigProcessor implements ConfigProcessor public function __construct( ConfigResolverInterface $configResolver, - SiteAccessService $siteAccessService + SiteAccessServiceInterface $siteAccessService ) { $this->configResolver = $configResolver; $this->siteAccessService = $siteAccessService; @@ -41,7 +42,7 @@ public function __construct( public function processComplexSetting(string $setting): string { - $siteAccessName = $this->siteAccessService->getCurrent()->name; + $siteAccessName = $this->getCurrentSiteAccessName(); if (!$this->configResolver->hasParameter($setting, null, $siteAccessName)) { throw new ParameterNotFoundException($setting, null, [$siteAccessName]); @@ -74,7 +75,7 @@ public function processSettingValue(string $value): string $dynamicSettingValue = $this->configResolver->getParameter( $parts['param'], $parts['namespace'], - $parts['scope'] ?? $this->siteAccessService->getCurrent()->name + $parts['scope'] ?? $this->getCurrentSiteAccessName() ); $value = str_replace($dynamicSetting, $dynamicSettingValue, $value); @@ -82,4 +83,14 @@ public function processSettingValue(string $value): string return $value; } + + private function getCurrentSiteAccessName(): string + { + $siteAccess = $this->siteAccessService->getCurrent(); + if ($siteAccess === null) { + throw new InvalidArgumentException('siteAccess', 'no SiteAccess given and none currently set'); + } + + return $siteAccess->name; + } } diff --git a/src/lib/Helper/ContentPreviewHelper.php b/src/lib/Helper/ContentPreviewHelper.php index ed375dd2a7..d57f458ccc 100644 --- a/src/lib/Helper/ContentPreviewHelper.php +++ b/src/lib/Helper/ContentPreviewHelper.php @@ -9,23 +9,16 @@ use Ibexa\Contracts\Core\Repository\Values\Content\Content; use Ibexa\Contracts\Core\Repository\Values\Content\Location; -use Ibexa\Core\MVC\Symfony\Event\ScopeChangeEvent; -use Ibexa\Core\MVC\Symfony\MVCEvents; use Ibexa\Core\MVC\Symfony\SiteAccess; -use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessAware; use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessRouterInterface; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface; -class ContentPreviewHelper implements SiteAccessAware +class ContentPreviewHelper { - /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface */ - protected $eventDispatcher; - /** @var \Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessRouterInterface */ protected $siteAccessRouter; - /** @var \Ibexa\Core\MVC\Symfony\SiteAccess */ - protected $originalSiteAccess; + private SiteAccessServiceInterface $siteAccessService; /** @var bool */ private $previewActive = false; @@ -36,25 +29,18 @@ class ContentPreviewHelper implements SiteAccessAware /** @var \Ibexa\Contracts\Core\Repository\Values\Content\Location */ private $previewedLocation; - public function __construct(EventDispatcherInterface $eventDispatcher, SiteAccessRouterInterface $siteAccessRouter) + public function __construct(SiteAccessRouterInterface $siteAccessRouter, SiteAccessServiceInterface $siteAccessService) { - $this->eventDispatcher = $eventDispatcher; $this->siteAccessRouter = $siteAccessRouter; - } - - public function setSiteAccess(?SiteAccess $siteAccess = null) - { - $this->originalSiteAccess = $siteAccess; + $this->siteAccessService = $siteAccessService; } /** * Return original SiteAccess. - * - * @return \Ibexa\Core\MVC\Symfony\SiteAccess */ - public function getOriginalSiteAccess() + public function getOriginalSiteAccess(): ?SiteAccess { - return $this->originalSiteAccess; + return $this->siteAccessService->getCurrent(); } /** @@ -66,23 +52,15 @@ public function getOriginalSiteAccess() */ public function changeConfigScope($siteAccessName) { - $event = new ScopeChangeEvent($this->siteAccessRouter->matchByName($siteAccessName)); - $this->eventDispatcher->dispatch($event, MVCEvents::CONFIG_SCOPE_CHANGE); - - return $event->getSiteAccess(); + return $this->siteAccessService->changeSiteAccess($this->siteAccessRouter->matchByName($siteAccessName)); } /** * Restores original config scope. - * - * @return \Ibexa\Core\MVC\Symfony\SiteAccess */ - public function restoreConfigScope() + public function restoreConfigScope(): ?SiteAccess { - $event = new ScopeChangeEvent($this->originalSiteAccess); - $this->eventDispatcher->dispatch($event, MVCEvents::CONFIG_SCOPE_RESTORE); - - return $event->getSiteAccess(); + return $this->siteAccessService->restoreSiteAccess(); } /** @@ -99,7 +77,6 @@ public function isPreviewActive() public function setPreviewActive($previewActive) { $this->previewActive = (bool)$previewActive; - $this->originalSiteAccess = clone $this->originalSiteAccess; } /** diff --git a/src/lib/MVC/Symfony/Controller/Content/PreviewController.php b/src/lib/MVC/Symfony/Controller/Content/PreviewController.php index 5ce1da3e2a..98e8009073 100644 --- a/src/lib/MVC/Symfony/Controller/Content/PreviewController.php +++ b/src/lib/MVC/Symfony/Controller/Content/PreviewController.php @@ -16,6 +16,7 @@ use Ibexa\Contracts\Core\Repository\Values\Content\Content; use Ibexa\Contracts\Core\Repository\Values\Content\Location; use Ibexa\Core\Base\Exceptions\BadStateException; +use Ibexa\Core\Base\Exceptions\InvalidArgumentException; use Ibexa\Core\Helper\ContentPreviewHelper; use Ibexa\Core\Helper\PreviewLocationProvider; use Ibexa\Core\MVC\Symfony\Routing\Generator\UrlAliasGenerator; @@ -120,40 +121,46 @@ public function previewContentAction( throw new AccessDeniedException(); } - $siteAccess = $this->previewHelper->getOriginalSiteAccess(); - // Only switch if $siteAccessName is set and different from original - if ($siteAccessName !== null && $siteAccessName !== $siteAccess->name) { - $siteAccess = $this->previewHelper->changeConfigScope($siteAccessName); - } - try { - $viewType = $request->query->get('viewType', ViewManagerInterface::VIEW_TYPE_FULL); - $response = $this->kernel->handle( - $this->getForwardRequest($location, $content, $siteAccess, $request, $language, $viewType), - HttpKernelInterface::SUB_REQUEST, - false - ); - } catch (APINotFoundException $e) { - $message = sprintf('Location (%s) not found or not available in requested language (%s)', $location->id, $language); - $this->logger->warning( - sprintf('%s %s', $message, 'when loading the preview page'), - ['exception' => $e] - ); - if ($this->debugMode) { - throw new BadStateException('Preview page', $message, $e); + $siteAccess = $this->previewHelper->getOriginalSiteAccess(); + if ($siteAccess === null) { + throw new InvalidArgumentException('siteAccess', 'no SiteAccess given and none currently set'); } - return new Response($message); - } catch (Exception $e) { - return $this->buildResponseForGenericPreviewError($location, $content, $e); - } - $response->headers->addCacheControlDirective('no-cache', true); - $response->setPrivate(); + // Only switch if $siteAccessName is set and different from original + if ($siteAccessName !== null && $siteAccessName !== $siteAccess->name) { + $siteAccess = $this->previewHelper->changeConfigScope($siteAccessName); + } - $this->previewHelper->restoreConfigScope(); - $this->previewHelper->setPreviewActive(false); + try { + $viewType = $request->query->get('viewType', ViewManagerInterface::VIEW_TYPE_FULL); + $response = $this->kernel->handle( + $this->getForwardRequest($location, $content, $siteAccess, $request, $language, $viewType), + HttpKernelInterface::SUB_REQUEST, + false + ); + } catch (APINotFoundException $e) { + $message = sprintf('Location (%s) not found or not available in requested language (%s)', $location->id, $language); + $this->logger->warning( + sprintf('%s %s', $message, 'when loading the preview page'), + ['exception' => $e] + ); + if ($this->debugMode) { + throw new BadStateException('Preview page', $message, $e); + } + + return new Response($message); + } catch (Exception $e) { + return $this->buildResponseForGenericPreviewError($location, $content, $e); + } + $response->headers->addCacheControlDirective('no-cache', true); + $response->setPrivate(); - return $response; + return $response; + } finally { + $this->previewHelper->restoreConfigScope(); + $this->previewHelper->setPreviewActive(false); + } } /** diff --git a/src/lib/MVC/Symfony/EventListener/SiteAccessMatchListener.php b/src/lib/MVC/Symfony/EventListener/SiteAccessMatchListener.php index c172971cfe..2f7dacd594 100644 --- a/src/lib/MVC/Symfony/EventListener/SiteAccessMatchListener.php +++ b/src/lib/MVC/Symfony/EventListener/SiteAccessMatchListener.php @@ -12,10 +12,12 @@ use Ibexa\Core\MVC\Symfony\Routing\SimplifiedRequest; use Ibexa\Core\MVC\Symfony\SiteAccess; use Ibexa\Core\MVC\Symfony\SiteAccess\Router as SiteAccessRouter; +use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\Serializer\SerializerInterface; @@ -31,14 +33,18 @@ class SiteAccessMatchListener implements EventSubscriberInterface private SerializerInterface $serializer; + private SiteAccessServiceInterface $siteAccessService; + public function __construct( SiteAccessRouter $siteAccessRouter, EventDispatcherInterface $eventDispatcher, - SerializerInterface $serializer + SerializerInterface $serializer, + SiteAccessServiceInterface $siteAccessService ) { $this->siteAccessRouter = $siteAccessRouter; $this->eventDispatcher = $eventDispatcher; $this->serializer = $serializer; + $this->siteAccessService = $siteAccessService; } public static function getSubscribedEvents(): array @@ -89,6 +95,15 @@ public function onKernelRequest(RequestEvent $event): void if ($siteaccess instanceof SiteAccess) { $siteAccessEvent = new PostSiteAccessMatchEvent($siteaccess, $request, $event->getRequestType()); $this->eventDispatcher->dispatch($siteAccessEvent, MVCEvents::SITEACCESS); + + // SiteAccessService establishes the base of its stack by reacting to this same event + // for MAIN_REQUEST (see SiteAccessService::onSiteAccessMatch()); a sub-request's + // SiteAccess is a nested change on top of that, so push it the same way any other + // caller would, via changeSiteAccess(). SiteAccessRestoreListener pops it back off via + // restoreSiteAccess() once this sub-request finishes. + if ($event->getRequestType() === HttpKernelInterface::SUB_REQUEST) { + $this->siteAccessService->changeSiteAccess($siteaccess); + } } } diff --git a/src/lib/MVC/Symfony/EventListener/SiteAccessRestoreListener.php b/src/lib/MVC/Symfony/EventListener/SiteAccessRestoreListener.php index 7f21fad7d0..c9ea80ee44 100644 --- a/src/lib/MVC/Symfony/EventListener/SiteAccessRestoreListener.php +++ b/src/lib/MVC/Symfony/EventListener/SiteAccessRestoreListener.php @@ -11,6 +11,7 @@ use Ibexa\Core\MVC\Symfony\Event\PostSiteAccessMatchEvent; use Ibexa\Core\MVC\Symfony\MVCEvents; use Ibexa\Core\MVC\Symfony\SiteAccess; +use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\RequestStack; @@ -19,9 +20,12 @@ use Symfony\Component\HttpKernel\KernelEvents; /** - * Restores the parent request's SiteAccess when a sub-request finishes, by re-dispatching - * MVCEvents::SITEACCESS — its listeners mutate shared state for every sub-request. Mirrors - * {@see \Symfony\Component\HttpKernel\EventListener\RouterListener::onKernelFinishRequest()}. + * Restores the parent request's SiteAccess when a sub-request finishes: + * - for SiteAccessService, by calling restoreSiteAccess(), popping the entry + * SiteAccessMatchListener pushed via changeSiteAccess() when this sub-request started. + * - for not-yet-migrated listeners still reacting to MVCEvents::SITEACCESS directly (mutating + * shared state for every sub-request), by re-dispatching that event. Mirrors + * {@see \Symfony\Component\HttpKernel\EventListener\RouterListener::onKernelFinishRequest()}. */ final class SiteAccessRestoreListener implements EventSubscriberInterface { @@ -29,12 +33,16 @@ final class SiteAccessRestoreListener implements EventSubscriberInterface private EventDispatcherInterface $eventDispatcher; + private SiteAccessServiceInterface $siteAccessService; + public function __construct( RequestStack $requestStack, - EventDispatcherInterface $eventDispatcher + EventDispatcherInterface $eventDispatcher, + SiteAccessServiceInterface $siteAccessService ) { $this->requestStack = $requestStack; $this->eventDispatcher = $eventDispatcher; + $this->siteAccessService = $siteAccessService; } public static function getSubscribedEvents(): array @@ -50,6 +58,12 @@ public function onKernelFinishRequest(FinishRequestEvent $event): void return; } + // Mirrors the condition SiteAccessMatchListener used to decide whether to push, so the + // pop here only ever undoes what this exact sub-request pushed. + if ($event->getRequest()->attributes->get('siteaccess') instanceof SiteAccess) { + $this->siteAccessService->restoreSiteAccess(); + } + // The finishing request is still on the stack, so this is the request control returns to $parentRequest = $this->requestStack->getParentRequest(); if ($parentRequest === null) { diff --git a/src/lib/MVC/Symfony/Routing/Generator.php b/src/lib/MVC/Symfony/Routing/Generator.php index 9729d0b90a..16d8982e8a 100644 --- a/src/lib/MVC/Symfony/Routing/Generator.php +++ b/src/lib/MVC/Symfony/Routing/Generator.php @@ -8,8 +8,8 @@ namespace Ibexa\Core\MVC\Symfony\Routing; use Ibexa\Core\MVC\Symfony\SiteAccess; -use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessAware; use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessRouterInterface; +use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface; use Psr\Log\LoggerInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Routing\RequestContext; @@ -17,16 +17,18 @@ /** * Base class for Ibexa Url generation. */ -abstract class Generator implements SiteAccessAware +abstract class Generator { protected RequestContext $requestContext; protected SiteAccessRouterInterface $siteAccessRouter; - protected ?SiteAccess $siteAccess; - protected ?LoggerInterface $logger; + public function __construct(private readonly SiteAccessServiceInterface $siteAccessService) + { + } + public function setRequestContext(RequestContext $requestContext): void { $this->requestContext = $requestContext; @@ -37,11 +39,6 @@ public function setSiteAccessRouter(SiteAccessRouterInterface $siteAccessRouter) $this->siteAccessRouter = $siteAccessRouter; } - public function setSiteAccess(?SiteAccess $siteAccess = null): void - { - $this->siteAccess = $siteAccess; - } - public function setLogger(?LoggerInterface $logger = null): void { $this->logger = $logger; @@ -57,7 +54,7 @@ public function setLogger(?LoggerInterface $logger = null): void */ public function generate(mixed $urlResource, array $parameters, int $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH): string { - $siteAccess = $this->siteAccess; + $siteAccess = $this->siteAccessService->getCurrent(); $requestContext = $this->requestContext; // Retrieving the appropriate SiteAccess to generate the link for. @@ -69,7 +66,7 @@ public function generate(mixed $urlResource, array $parameters, int $referenceTy $siteAccess->matcher->getRequest() ); } elseif (isset($this->logger)) { - $siteAccess = $this->siteAccess; + $siteAccess = $this->siteAccessService->getCurrent(); $this->logger->notice("Could not generate a link using provided 'siteaccess' parameter: {$parameters['siteaccess']}. Generating using current context."); unset($parameters['siteaccess']); } diff --git a/src/lib/MVC/Symfony/Routing/Generator/UrlAliasGenerator.php b/src/lib/MVC/Symfony/Routing/Generator/UrlAliasGenerator.php index de2c8b66f2..ae10adaa2d 100644 --- a/src/lib/MVC/Symfony/Routing/Generator/UrlAliasGenerator.php +++ b/src/lib/MVC/Symfony/Routing/Generator/UrlAliasGenerator.php @@ -11,6 +11,7 @@ use Ibexa\Contracts\Core\Repository\Values\Content\Location; use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; use Ibexa\Core\MVC\Symfony\Routing\Generator; +use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface; use Symfony\Component\Routing\RouterInterface; /** @@ -47,8 +48,15 @@ class UrlAliasGenerator extends Generator /** * @param array $unsafeCharMap */ - public function __construct(Repository $repository, RouterInterface $defaultRouter, ConfigResolverInterface $configResolver, array $unsafeCharMap = []) - { + public function __construct( + Repository $repository, + RouterInterface $defaultRouter, + ConfigResolverInterface $configResolver, + SiteAccessServiceInterface $siteAccessService, + array $unsafeCharMap = [] + ) { + parent::__construct($siteAccessService); + $this->repository = $repository; $this->defaultRouter = $defaultRouter; $this->configResolver = $configResolver; diff --git a/src/lib/MVC/Symfony/Security/HttpUtils.php b/src/lib/MVC/Symfony/Security/HttpUtils.php index 4e1e8c725b..9000c9674e 100644 --- a/src/lib/MVC/Symfony/Security/HttpUtils.php +++ b/src/lib/MVC/Symfony/Security/HttpUtils.php @@ -8,22 +8,28 @@ namespace Ibexa\Core\MVC\Symfony\Security; use Ibexa\Core\MVC\Symfony\SiteAccess; -use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessAware; +use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\Matcher\RequestMatcherInterface; +use Symfony\Component\Routing\Matcher\UrlMatcherInterface; use Symfony\Component\Security\Http\HttpUtils as BaseHttpUtils; -class HttpUtils extends BaseHttpUtils implements SiteAccessAware +class HttpUtils extends BaseHttpUtils { - private ?SiteAccess $siteAccess; - - public function setSiteAccess(?SiteAccess $siteAccess = null): void - { - $this->siteAccess = $siteAccess; + public function __construct( + ?UrlGeneratorInterface $urlGenerator = null, + UrlMatcherInterface|RequestMatcherInterface|null $urlMatcher = null, + ?string $domainRegexp = null, + ?string $secureDomainRegexp = null, + private ?SiteAccessServiceInterface $siteAccessService = null, + ) { + parent::__construct($urlGenerator, $urlMatcher, $domainRegexp, $secureDomainRegexp); } private function analyzeLink(string $path): string { - $matcher = $this->siteAccess?->matcher; + $matcher = $this->siteAccessService?->getCurrent()?->matcher; if ($path[0] === '/' && $matcher instanceof SiteAccess\URILexer) { $path = $matcher->analyseLink($path); } diff --git a/src/lib/MVC/Symfony/SiteAccess/SiteAccessService.php b/src/lib/MVC/Symfony/SiteAccess/SiteAccessService.php index 41eebdd75b..e25d2ccd7d 100644 --- a/src/lib/MVC/Symfony/SiteAccess/SiteAccessService.php +++ b/src/lib/MVC/Symfony/SiteAccess/SiteAccessService.php @@ -11,57 +11,101 @@ use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; use Ibexa\Core\Base\Exceptions\InvalidArgumentException; use Ibexa\Core\Base\Exceptions\NotFoundException; +use Ibexa\Core\MVC\Symfony\Event\PostSiteAccessMatchEvent; use Ibexa\Core\MVC\Symfony\Event\ScopeChangeEvent; use Ibexa\Core\MVC\Symfony\MVCEvents; use Ibexa\Core\MVC\Symfony\SiteAccess; use function iterator_to_array; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\HttpKernelInterface; -class SiteAccessService implements SiteAccessServiceInterface, SiteAccessAware, EventSubscriberInterface +/** + * changeSiteAccess()/restoreSiteAccess() are the single, supported way to swap the current + * SiteAccess (content preview, CLI --siteaccess, sub-requests). Manually dispatching + * ScopeChangeEvent under MVCEvents::CONFIG_SCOPE_CHANGE/CONFIG_SCOPE_RESTORE is deprecated and + * only kept as a fallback for not-yet-migrated listeners — see onConfigScopeChange(). + */ +class SiteAccessService implements SiteAccessServiceInterface, EventSubscriberInterface { /** @var list<\Ibexa\Core\MVC\Symfony\SiteAccess> */ private array $siteAccessStack = []; + private bool $isDispatchingInternally = false; + public function __construct( private readonly SiteAccessProviderInterface $provider, - private readonly ConfigResolverInterface $configResolver + private readonly ConfigResolverInterface $configResolver, + private readonly EventDispatcherInterface $eventDispatcher ) { } public static function getSubscribedEvents(): array { return [ + MVCEvents::SITEACCESS => 'onSiteAccessMatch', MVCEvents::CONFIG_SCOPE_CHANGE => 'onConfigScopeChange', MVCEvents::CONFIG_SCOPE_RESTORE => 'onConfigScopeRestore', ]; } - public function setSiteAccess(?SiteAccess $siteAccess = null): void + /** + * Establishes the base of the SiteAccess stack for a new top-level request. Sub-requests are + * NOT handled here: the listener matching their SiteAccess calls changeSiteAccess() directly, + * same as any other caller. + */ + public function onSiteAccessMatch(PostSiteAccessMatchEvent $event): void { - $this->siteAccessStack = $siteAccess !== null ? [$siteAccess] : []; + if ($event->getRequestType() !== HttpKernelInterface::MAIN_REQUEST) { + return; + } + + $this->siteAccessStack = [$event->getSiteAccess()]; } /** - * Pushes the new SiteAccess onto the stack, so that getCurrent() reflects it until the - * matching restore happens. + * @deprecated 6.0, to be removed in 7.0. Dispatching ScopeChangeEvent manually under + * MVCEvents::CONFIG_SCOPE_CHANGE is deprecated; call changeSiteAccess() instead, + * which dispatches this event for you. Kept so not-yet-migrated listeners keep + * working, but no longer the supported way to change the current SiteAccess. */ public function onConfigScopeChange(ScopeChangeEvent $event): void { + if ($this->isDispatchingInternally) { + return; + } + + trigger_deprecation( + 'ibexa/core', + '6.0', + 'Dispatching %s manually under MVCEvents::CONFIG_SCOPE_CHANGE is deprecated and will no longer be reflected by %s::getCurrent() in 7.0. Call %s::changeSiteAccess() instead.', + ScopeChangeEvent::class, + self::class, + SiteAccessServiceInterface::class + ); + $this->siteAccessStack[] = $event->getSiteAccess(); } /** - * Pops the SiteAccess pushed by the matching onConfigScopeChange(), but never below one - * remaining entry: the bottom-most entry (the current request's SiteAccess) must survive an - * unbalanced restore. + * @deprecated 6.0, to be removed in 7.0. See onConfigScopeChange(). */ public function onConfigScopeRestore(ScopeChangeEvent $event): void { - if (count($this->siteAccessStack) <= 1) { + if ($this->isDispatchingInternally) { return; } - array_pop($this->siteAccessStack); + trigger_deprecation( + 'ibexa/core', + '6.0', + 'Dispatching %s manually under MVCEvents::CONFIG_SCOPE_RESTORE is deprecated and will no longer be reflected by %s::getCurrent() in 7.0. Call %s::restoreSiteAccess() instead.', + ScopeChangeEvent::class, + self::class, + SiteAccessServiceInterface::class + ); + + $this->popSiteAccessStack(); } public function exists(string $name): bool @@ -85,12 +129,40 @@ public function getAll(): iterable public function getCurrent(): ?SiteAccess { - return $this->siteAccessStack !== [] ? end($this->siteAccessStack) : null; + if ($this->siteAccessStack === []) { + return null; + } + + // Defensive: a SiteAccess with an "uninitialized" matcher (see + // SiteAccess::MATCHING_TYPE_UNINITIALIZED) is not a real current SiteAccess even if it + // somehow ended up on the stack — surface it as null rather than a misleading value. + $current = end($this->siteAccessStack); + + return $current->matchingType !== SiteAccess::MATCHING_TYPE_UNINITIALIZED ? $current : null; + } + + public function changeSiteAccess(SiteAccess $siteAccess): SiteAccess + { + $this->siteAccessStack[] = $siteAccess; + $this->dispatchScopeChangeEvent($siteAccess, MVCEvents::CONFIG_SCOPE_CHANGE); + + return $siteAccess; + } + + public function restoreSiteAccess(): ?SiteAccess + { + $this->popSiteAccessStack(); + $siteAccess = $this->getCurrent(); + if ($siteAccess !== null) { + $this->dispatchScopeChangeEvent($siteAccess, MVCEvents::CONFIG_SCOPE_RESTORE); + } + + return $siteAccess; } public function getSiteAccessesRelation(?SiteAccess $siteAccess = null): array { - $siteAccess = $siteAccess ?? $this->getCurrent(); + $siteAccess ??= $this->getCurrent(); if ($siteAccess === null) { throw new InvalidArgumentException('siteAccess', 'no SiteAccess given and none currently set'); } @@ -122,4 +194,30 @@ public function getSiteAccessesRelation(?SiteAccess $siteAccess = null): array return $saRelationMap[$repository][$rootLocationId]; } + + /** + * Pops the SiteAccess stack, but never below one remaining entry: the bottom-most entry + * (the current top-level request's SiteAccess) must survive an unbalanced restore. + */ + private function popSiteAccessStack(): void + { + if (count($this->siteAccessStack) > 1) { + array_pop($this->siteAccessStack); + } + } + + /** + * Dispatches $eventName with isDispatchingInternally set, so onConfigScopeChange()/ + * onConfigScopeRestore() can tell this dispatch apart from a manually-constructed one and + * skip reacting to it (the stack mutation already happened above). + */ + private function dispatchScopeChangeEvent(SiteAccess $siteAccess, string $eventName): void + { + $this->isDispatchingInternally = true; + try { + $this->eventDispatcher->dispatch(new ScopeChangeEvent($siteAccess), $eventName); + } finally { + $this->isDispatchingInternally = false; + } + } } diff --git a/src/lib/MVC/Symfony/SiteAccess/SiteAccessServiceInterface.php b/src/lib/MVC/Symfony/SiteAccess/SiteAccessServiceInterface.php index b95cfda289..61c43278e4 100644 --- a/src/lib/MVC/Symfony/SiteAccess/SiteAccessServiceInterface.php +++ b/src/lib/MVC/Symfony/SiteAccess/SiteAccessServiceInterface.php @@ -29,6 +29,20 @@ public function getAll(): iterable; public function getCurrent(): ?SiteAccess; + /** + * Switches the current SiteAccess to $siteAccess, remembering the previous one so it can be + * restored later via restoreSiteAccess(). Dispatches a ScopeChangeEvent under + * MVCEvents::CONFIG_SCOPE_CHANGE. This is the supported way to change the current SiteAccess — + * do not dispatch that event manually. + */ + public function changeSiteAccess(SiteAccess $siteAccess): SiteAccess; + + /** + * Restores the SiteAccess that was current before the last changeSiteAccess() call. + * Dispatches a ScopeChangeEvent under MVCEvents::CONFIG_SCOPE_RESTORE. + */ + public function restoreSiteAccess(): ?SiteAccess; + /** * Handles relation between SiteAccesses. Related SiteAccesses share the same repository and root location id. * diff --git a/tests/bundle/Core/DependencyInjection/Compiler/FragmentPassTest.php b/tests/bundle/Core/DependencyInjection/Compiler/FragmentPassTest.php index 72c78331d0..6f1b82ef2d 100644 --- a/tests/bundle/Core/DependencyInjection/Compiler/FragmentPassTest.php +++ b/tests/bundle/Core/DependencyInjection/Compiler/FragmentPassTest.php @@ -12,6 +12,7 @@ use Ibexa\Bundle\Core\Fragment\FragmentListenerFactory; use Ibexa\Bundle\Core\Fragment\InlineFragmentRenderer; use Ibexa\Bundle\Core\Fragment\SiteAccessSerializerInterface; +use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface; use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractCompilerPassTestCase; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; @@ -67,7 +68,7 @@ public function testProcess(): void $decoratedInlineDef = $this->container->getDefinition('fragment.renderer.inline'); self::assertSame(['kernel.fragment_renderer' => [[]]], $decoratedInlineDef->getTags()); self::assertEquals( - [new Reference('fragment.renderer.inline.inner'), new Reference(SiteAccessSerializerInterface::class)], + [new Reference('fragment.renderer.inline.inner'), new Reference(SiteAccessSerializerInterface::class), new Reference(SiteAccessServiceInterface::class)], $decoratedInlineDef->getArguments() ); self::assertSame(InlineFragmentRenderer::class, $decoratedInlineDef->getClass()); @@ -76,7 +77,7 @@ public function testProcess(): void $decoratedEsiDef = $this->container->getDefinition('fragment.renderer.esi'); self::assertSame(['kernel.fragment_renderer' => [[]]], $decoratedEsiDef->getTags()); self::assertEquals( - [new Reference('fragment.renderer.esi.inner'), new Reference(SiteAccessSerializerInterface::class)], + [new Reference('fragment.renderer.esi.inner'), new Reference(SiteAccessSerializerInterface::class), new Reference(SiteAccessServiceInterface::class)], $decoratedEsiDef->getArguments() ); @@ -84,7 +85,7 @@ public function testProcess(): void $decoratedHincludeDef = $this->container->getDefinition('fragment.renderer.hinclude'); self::assertSame(['kernel.fragment_renderer' => [[]]], $decoratedHincludeDef->getTags()); self::assertEquals( - [new Reference('fragment.renderer.hinclude.inner'), new Reference(SiteAccessSerializerInterface::class)], + [new Reference('fragment.renderer.hinclude.inner'), new Reference(SiteAccessSerializerInterface::class), new Reference(SiteAccessServiceInterface::class)], $decoratedHincludeDef->getArguments() ); } diff --git a/tests/bundle/Core/DependencyInjection/Compiler/SecurityPassTest.php b/tests/bundle/Core/DependencyInjection/Compiler/SecurityPassTest.php index ea08ce8da5..c4b1a87eef 100644 --- a/tests/bundle/Core/DependencyInjection/Compiler/SecurityPassTest.php +++ b/tests/bundle/Core/DependencyInjection/Compiler/SecurityPassTest.php @@ -13,7 +13,7 @@ use Ibexa\Contracts\Core\Repository\UserService; use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; use Ibexa\Core\MVC\Symfony\Security\Authentication\AnonymousUserAccessListener; -use Ibexa\Core\MVC\Symfony\SiteAccess; +use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface; use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractCompilerPassTestCase; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; @@ -29,7 +29,7 @@ protected function setUp(): void $this->setDefinition('security.http_utils', new Definition()); $this->setDefinition('security.authentication.success_handler', new Definition()); $this->setDefinition(ConfigResolverInterface::class, new Definition()); - $this->setDefinition(SiteAccess::class, new Definition()); + $this->setDefinition(SiteAccessServiceInterface::class, new Definition()); $this->setDefinition(PermissionResolver::class, new Definition()); $this->setDefinition(UserService::class, new Definition()); } @@ -43,10 +43,10 @@ public function testAlteredHttpUtils(): void { $this->compile(); - $this->assertContainerBuilderHasServiceDefinitionWithMethodCall( + $this->assertContainerBuilderHasServiceDefinitionWithArgument( 'security.http_utils', - 'setSiteAccess', - [new Reference(SiteAccess::class)] + '$siteAccessService', + new Reference(SiteAccessServiceInterface::class) ); } diff --git a/tests/bundle/Core/EventListener/ConsoleCommandListenerTest.php b/tests/bundle/Core/EventListener/ConsoleCommandListenerTest.php index 0bd8a53484..7d1e2cbdef 100644 --- a/tests/bundle/Core/EventListener/ConsoleCommandListenerTest.php +++ b/tests/bundle/Core/EventListener/ConsoleCommandListenerTest.php @@ -10,6 +10,7 @@ use Ibexa\Bundle\Core\EventListener\ConsoleCommandListener; use Ibexa\Core\MVC\Exception\InvalidSiteAccessException; use Ibexa\Core\MVC\Symfony\SiteAccess; +use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface; use Ibexa\Tests\Bundle\Core\EventListener\Stubs\TestOutput; use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; @@ -20,17 +21,13 @@ use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\Output; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; class ConsoleCommandListenerTest extends TestCase { private const INVALID_SA_NAME = 'foo'; - /** @var \Ibexa\Core\MVC\Symfony\SiteAccess */ - private $siteAccess; - - /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface|\PHPUnit\Framework\MockObject\MockObject */ - private $dispatcher; + /** @var \Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface|\PHPUnit\Framework\MockObject\MockObject */ + private $siteAccessService; /** @var \Ibexa\Bundle\Core\EventListener\ConsoleCommandListener */ private $listener; @@ -46,11 +43,8 @@ class ConsoleCommandListenerTest extends TestCase protected function setUp(): void { parent::setUp(); - $this->siteAccess = new SiteAccess('test'); - $this->dispatcher = $this->createMock(EventDispatcherInterface::class); - $this->listener = new ConsoleCommandListener('default', $this->getSiteAccessProviderMock(), $this->dispatcher); - $this->listener->setSiteAccess($this->siteAccess); - $this->dispatcher->addSubscriber($this->listener); + $this->siteAccessService = $this->createMock(SiteAccessServiceInterface::class); + $this->listener = new ConsoleCommandListener('default', $this->getSiteAccessProviderMock(), $this->siteAccessService); $this->inputDefinition = new InputDefinition([new InputOption('siteaccess', null, InputOption::VALUE_OPTIONAL)]); $this->testOutput = new TestOutput(Output::VERBOSITY_QUIET, true); $this->command = self::createStub(Command::class); @@ -71,8 +65,8 @@ public function testInvalidSiteAccessDev(): void $this->expectException(InvalidSiteAccessException::class); $this->expectExceptionMessageMatches('/^Invalid SiteAccess \'foo\', matched by .+\\. Valid SiteAccesses are/'); - $this->dispatcher->expects(self::never()) - ->method('dispatch'); + $this->siteAccessService->expects(self::never()) + ->method('changeSiteAccess'); $input = new ArrayInput(['--siteaccess' => 'foo'], $this->inputDefinition); $event = new ConsoleCommandEvent($this->command, $input, $this->testOutput); $this->listener->setDebug(true); @@ -84,8 +78,8 @@ public function testInvalidSiteAccessProd(): void $this->expectException(InvalidSiteAccessException::class); $this->expectExceptionMessageMatches('/^Invalid SiteAccess \'foo\', matched by .+\\.$/'); - $this->dispatcher->expects(self::never()) - ->method('dispatch'); + $this->siteAccessService->expects(self::never()) + ->method('changeSiteAccess'); $input = new ArrayInput(['--siteaccess' => 'foo'], $this->inputDefinition); $event = new ConsoleCommandEvent($this->command, $input, $this->testOutput); $this->listener->setDebug(false); @@ -94,22 +88,22 @@ public function testInvalidSiteAccessProd(): void public function testValidSiteAccess(): void { - $this->dispatcher->expects(self::once()) - ->method('dispatch'); + $this->siteAccessService->expects(self::once()) + ->method('changeSiteAccess') + ->with(new SiteAccess('site1', 'cli')); $input = new ArrayInput(['--siteaccess' => 'site1'], $this->inputDefinition); $event = new ConsoleCommandEvent($this->command, $input, $this->testOutput); $this->listener->onConsoleCommand($event); - self::assertEquals(new SiteAccess('site1', 'cli'), $this->siteAccess); } public function testDefaultSiteAccess(): void { - $this->dispatcher->expects(self::once()) - ->method('dispatch'); + $this->siteAccessService->expects(self::once()) + ->method('changeSiteAccess') + ->with(new SiteAccess('default', 'cli')); $input = new ArrayInput([], $this->inputDefinition); $event = new ConsoleCommandEvent($this->command, $input, $this->testOutput); $this->listener->onConsoleCommand($event); - self::assertEquals(new SiteAccess('default', 'cli'), $this->siteAccess); } private function getSiteAccessProviderMock(): SiteAccess\SiteAccessProviderInterface diff --git a/tests/bundle/Core/Fragment/DecoratedFragmentRendererTest.php b/tests/bundle/Core/Fragment/DecoratedFragmentRendererTest.php index 3867ebe41e..e63383a559 100644 --- a/tests/bundle/Core/Fragment/DecoratedFragmentRendererTest.php +++ b/tests/bundle/Core/Fragment/DecoratedFragmentRendererTest.php @@ -10,6 +10,7 @@ use Ibexa\Bundle\Core\Fragment\DecoratedFragmentRenderer; use Ibexa\Bundle\Core\Fragment\SiteAccessSerializer; use Ibexa\Core\MVC\Symfony\SiteAccess; +use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\MockObject\MockObject; use Symfony\Component\HttpFoundation\Request; @@ -24,10 +25,13 @@ class DecoratedFragmentRendererTest extends FragmentRendererBaseTestCase { protected FragmentRendererInterface & MockObject $innerRenderer; + protected SiteAccessServiceInterface & MockObject $siteAccessService; + protected function setUp(): void { parent::setUp(); $this->innerRenderer = $this->createMock(FragmentRendererInterface::class); + $this->siteAccessService = $this->createMock(SiteAccessServiceInterface::class); } public function testSetFragmentPathNotRoutableRenderer(): void @@ -38,8 +42,9 @@ public function testSetFragmentPathNotRoutableRenderer(): void ->expects(self::never()) ->method('analyseLink'); + $this->siteAccessService->method('getCurrent')->willReturn($siteAccess); + $renderer = $this->getRenderer(); - $renderer->setSiteAccess($siteAccess); if ($renderer instanceof RoutableFragmentRenderer) { $renderer->setFragmentPath('foo'); } @@ -55,13 +60,14 @@ public function testSetFragmentPath(): void ->with('/foo') ->willReturn('/bar/foo'); + $this->siteAccessService->method('getCurrent')->willReturn($siteAccess); + $innerRenderer = $this->createMock(RoutableFragmentRenderer::class); $innerRenderer ->expects(self::once()) ->method('setFragmentPath') ->with('/bar/foo'); - $renderer = new DecoratedFragmentRenderer($innerRenderer, new SiteAccessSerializer($this->getSerializer())); - $renderer->setSiteAccess($siteAccess); + $renderer = new DecoratedFragmentRenderer($innerRenderer, new SiteAccessSerializer($this->getSerializer()), $this->siteAccessService); $renderer->setFragmentPath('/foo'); } @@ -136,11 +142,8 @@ public function getRequest(SiteAccess $siteAccess): Request return $request; } - /** - * @return \Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface&\Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessAware - */ public function getRenderer(): FragmentRendererInterface { - return new DecoratedFragmentRenderer($this->innerRenderer, new SiteAccessSerializer($this->getSerializer())); + return new DecoratedFragmentRenderer($this->innerRenderer, new SiteAccessSerializer($this->getSerializer()), $this->siteAccessService); } } diff --git a/tests/bundle/Core/Routing/DefaultRouterTest.php b/tests/bundle/Core/Routing/DefaultRouterTest.php index 27fd6e65ce..23764a69c7 100644 --- a/tests/bundle/Core/Routing/DefaultRouterTest.php +++ b/tests/bundle/Core/Routing/DefaultRouterTest.php @@ -12,6 +12,7 @@ use Ibexa\Bundle\Core\SiteAccess\Matcher; use Ibexa\Core\MVC\Symfony\Routing\SimplifiedRequest; use Ibexa\Core\MVC\Symfony\SiteAccess; +use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\MockObject; @@ -34,6 +35,8 @@ final class DefaultRouterTest extends TestCase private SiteAccess\SiteAccessRouterInterface&MockObject $siteAccessRouter; + private SiteAccessServiceInterface&MockObject $siteAccessService; + private RequestContext $requestContext; protected function setUp(): void @@ -41,13 +44,14 @@ protected function setUp(): void parent::setUp(); $this->innerRouter = $this->createMock(Router::class); $this->siteAccessRouter = $this->createMock(SiteAccess\SiteAccessRouterInterface::class); + $this->siteAccessService = $this->createMock(SiteAccessServiceInterface::class); $this->requestContext = new RequestContext(); $this->innerRouter->method('getContext')->willReturnCallback(fn (): RequestContext => $this->requestContext); } private function createRouter(): DefaultRouter { - return new DefaultRouter($this->innerRouter, $this->siteAccessRouter, self::NON_SITEACCESS_AWARE_ROUTES); + return new DefaultRouter($this->innerRouter, $this->siteAccessRouter, $this->siteAccessService, self::NON_SITEACCESS_AWARE_ROUTES); } public function testMatchRequestWithSemanticPathinfo(): void @@ -169,8 +173,8 @@ public function testGenerateWithSiteAccess( substr($urlComponents['path'], 0, strpos($urlComponents['path'], $relevantUri)) ); + $this->siteAccessService->method('getCurrent')->willReturn(new SiteAccess($saName, 'test', $matcher)); $router = $this->createRouter(); - $router->setSiteAccess(new SiteAccess($saName, 'test', $matcher)); self::assertSame($expectedUrl, $router->generate($routeName, [], $referenceType)); } @@ -214,8 +218,8 @@ public function testGenerateReverseSiteAccessMatch(): void ->with($routeName, []) ->willReturn($urlGenerated); + $this->siteAccessService->method('getCurrent')->willReturn(new SiteAccess('test', 'test', $this->createMock(Matcher::class))); $router = $this->createRouter(); - $router->setSiteAccess(new SiteAccess('test', 'test', $this->createMock(Matcher::class))); self::assertSame( $urlGenerated, @@ -266,7 +270,7 @@ public function testWarmUpDelegatesToInnerRouter(): void ->with('/cache', '/build') ->willReturn(['/cache/routes.php']); - $router = new DefaultRouter($innerRouter, $this->siteAccessRouter); + $router = new DefaultRouter($innerRouter, $this->siteAccessRouter, $this->siteAccessService); self::assertSame(['/cache/routes.php'], $router->warmUp('/cache', '/build')); } diff --git a/tests/bundle/IO/DependencyInjection/IbexaIOExtensionTest.php b/tests/bundle/IO/DependencyInjection/IbexaIOExtensionTest.php index 984e4f1d9c..a46cb7b31b 100644 --- a/tests/bundle/IO/DependencyInjection/IbexaIOExtensionTest.php +++ b/tests/bundle/IO/DependencyInjection/IbexaIOExtensionTest.php @@ -14,8 +14,12 @@ use Ibexa\Bundle\IO\DependencyInjection\ConfigurationFactory; use Ibexa\Bundle\IO\DependencyInjection\IbexaIOExtension; use Ibexa\Core\IO\UrlDecorator\AbsolutePrefix; +use Ibexa\Core\MVC\Symfony\SiteAccess; +use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessService; use Ibexa\Tests\Integration\Core\Repository\Container\Compiler\SetAllServicesPublicPass; use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractExtensionTestCase; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\Yaml\Yaml; class IbexaIOExtensionTest extends AbstractExtensionTestCase @@ -86,6 +90,10 @@ public function testUrlPrefixConfigurationIsUsedToDecorateUrl(): void ); $this->buildMinimalContainerForUrlPrefixTest(); + $siteAccessService = $this->container->get(SiteAccessService::class); + self::assertInstanceOf(SiteAccessService::class, $siteAccessService); + $siteAccessService->changeSiteAccess(new SiteAccess('site')); + $decorator = $this->container->get(AbsolutePrefix::class); self::assertEquals( @@ -101,6 +109,7 @@ private function buildMinimalContainerForUrlPrefixTest(): void $this->container->setParameter('kernel.debug', true); $this->container->setParameter('kernel.project_dir', self::FIXTURES_DIR); $this->container->setParameter('kernel.cache_dir', self::FIXTURES_DIR . '/cache'); + $this->container->setDefinition('event_dispatcher', new Definition(EventDispatcher::class)); $this->container->addCompilerPass(new ChainConfigResolverPass()); $this->container->addCompilerPass(new SetAllServicesPublicPass()); diff --git a/tests/integration/Core/LegacyTestContainerBuilder.php b/tests/integration/Core/LegacyTestContainerBuilder.php index 7d2e45f2dd..7038601452 100644 --- a/tests/integration/Core/LegacyTestContainerBuilder.php +++ b/tests/integration/Core/LegacyTestContainerBuilder.php @@ -13,7 +13,7 @@ use Ibexa\Contracts\Core\SiteAccess\ConfigProcessor; use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; use Ibexa\Core\Base\Container\Compiler; -use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessService; +use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface; use Ibexa\Tests\Integration\Core\Repository\Container\Compiler\SetAllServicesPublicPass; use Symfony\Component\Cache\Adapter\RedisAdapter; use Symfony\Component\Config\FileLocator; @@ -166,7 +166,7 @@ private function registerSiteAccessConfigProcessingServices(): void $definition = new Definition(ComplexConfigProcessor::class); $definition->setArgument('$configResolver', new Reference(ConfigResolverInterface::class)); - $definition->setArgument('$siteAccessService', new Reference(SiteAccessService::class)); + $definition->setArgument('$siteAccessService', new Reference(SiteAccessServiceInterface::class)); $this->setDefinition(ConfigProcessor::class, $definition); } diff --git a/tests/integration/Core/Resources/settings/common.yml b/tests/integration/Core/Resources/settings/common.yml index 5a43ba3cdb..e7c57f574c 100644 --- a/tests/integration/Core/Resources/settings/common.yml +++ b/tests/integration/Core/Resources/settings/common.yml @@ -106,13 +106,15 @@ services: Ibexa\Core\MVC\Symfony\SiteAccess: arguments: ['default', !php/const Ibexa\Core\MVC\Symfony\SiteAccess::MATCHING_TYPE_UNINITIALIZED] + event_dispatcher: + class: Symfony\Component\EventDispatcher\EventDispatcher + Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessService: class: Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessService arguments: - '@ibexa.siteaccess.provider' - '@ibexa.config.resolver' - calls: - - [setSiteAccess, ['@Ibexa\Core\MVC\Symfony\SiteAccess']] + - '@event_dispatcher' Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface: alias: 'Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessService' diff --git a/tests/lib/Helper/ContentPreviewHelperTest.php b/tests/lib/Helper/ContentPreviewHelperTest.php index fd6940ab60..7c6b680608 100644 --- a/tests/lib/Helper/ContentPreviewHelperTest.php +++ b/tests/lib/Helper/ContentPreviewHelperTest.php @@ -10,17 +10,15 @@ use Ibexa\Contracts\Core\Repository\Values\Content\Content as APIContent; use Ibexa\Contracts\Core\Repository\Values\Content\Location as APILocation; use Ibexa\Core\Helper\ContentPreviewHelper; -use Ibexa\Core\MVC\Symfony\Event\ScopeChangeEvent; -use Ibexa\Core\MVC\Symfony\MVCEvents; use Ibexa\Core\MVC\Symfony\SiteAccess; use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessRouterInterface; +use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface; use PHPUnit\Framework\TestCase; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; class ContentPreviewHelperTest extends TestCase { /** @var \PHPUnit\Framework\MockObject\MockObject */ - private $eventDispatcher; + private $siteAccessService; /** @var \PHPUnit\Framework\MockObject\MockObject */ private $siteAccessRouter; @@ -31,9 +29,9 @@ class ContentPreviewHelperTest extends TestCase protected function setUp(): void { parent::setUp(); - $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class); + $this->siteAccessService = $this->createMock(SiteAccessServiceInterface::class); $this->siteAccessRouter = $this->createMock(SiteAccessRouterInterface::class); - $this->previewHelper = new ContentPreviewHelper($this->eventDispatcher, $this->siteAccessRouter); + $this->previewHelper = new ContentPreviewHelper($this->siteAccessRouter, $this->siteAccessService); } public function testChangeConfigScope(): void @@ -47,14 +45,12 @@ public function testChangeConfigScope(): void ->with(self::equalTo($newSiteAccessName)) ->willReturn($newSiteAccess); - $event = new ScopeChangeEvent($newSiteAccess); - $this->eventDispatcher + $this->siteAccessService ->expects(self::once()) - ->method('dispatch') - ->with(self::equalTo($event), MVCEvents::CONFIG_SCOPE_CHANGE); + ->method('changeSiteAccess') + ->with($newSiteAccess) + ->willReturn($newSiteAccess); - $originalSiteAccess = new SiteAccess('foo', 'bar'); - $this->previewHelper->setSiteAccess($originalSiteAccess); self::assertEquals( $newSiteAccess, $this->previewHelper->changeConfigScope($newSiteAccessName) @@ -64,31 +60,34 @@ public function testChangeConfigScope(): void public function testRestoreConfigScope(): void { $originalSiteAccess = new SiteAccess('foo', 'bar'); - $event = new ScopeChangeEvent($originalSiteAccess); - $this->eventDispatcher + $this->siteAccessService ->expects(self::once()) - ->method('dispatch') - ->with(self::equalTo($event), MVCEvents::CONFIG_SCOPE_RESTORE); + ->method('restoreSiteAccess') + ->willReturn($originalSiteAccess); - $this->previewHelper->setSiteAccess($originalSiteAccess); self::assertEquals( $originalSiteAccess, $this->previewHelper->restoreConfigScope() ); } - public function testPreviewActive(): void + public function testGetOriginalSiteAccess(): void { $originalSiteAccess = new SiteAccess('foo', 'bar'); - $this->previewHelper->setSiteAccess($originalSiteAccess); + $this->siteAccessService + ->method('getCurrent') + ->willReturn($originalSiteAccess); + + self::assertSame($originalSiteAccess, $this->previewHelper->getOriginalSiteAccess()); + } + public function testPreviewActive(): void + { self::assertFalse($this->previewHelper->isPreviewActive()); $this->previewHelper->setPreviewActive(true); self::assertTrue($this->previewHelper->isPreviewActive()); $this->previewHelper->setPreviewActive(false); self::assertFalse($this->previewHelper->isPreviewActive()); - - self::assertNotSame($originalSiteAccess, $this->previewHelper->getOriginalSiteAccess()); } public function testPreviewedContent(): void diff --git a/tests/lib/MVC/Symfony/EventListener/SiteAccessMatchListenerTest.php b/tests/lib/MVC/Symfony/EventListener/SiteAccessMatchListenerTest.php index 8b07fade12..59e680e18c 100644 --- a/tests/lib/MVC/Symfony/EventListener/SiteAccessMatchListenerTest.php +++ b/tests/lib/MVC/Symfony/EventListener/SiteAccessMatchListenerTest.php @@ -25,6 +25,7 @@ use Ibexa\Core\MVC\Symfony\SiteAccess; use Ibexa\Core\MVC\Symfony\SiteAccess\Matcher; use Ibexa\Core\MVC\Symfony\SiteAccess\Router; +use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface; use Ibexa\Core\MVC\Symfony\SiteAccessGroup; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\MockObject\MockObject; @@ -51,6 +52,8 @@ final class SiteAccessMatchListenerTest extends TestCase private SiteAccessMatcherRegistryInterface&MockObject $registry; + private SiteAccessServiceInterface&MockObject $siteAccessService; + private SiteAccessMatchListener $listener; protected function setUp(): void @@ -59,10 +62,12 @@ protected function setUp(): void $this->saRouter = $this->createMock(Router::class); $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class); $this->registry = $this->createMock(SiteAccessMatcherRegistryInterface::class); + $this->siteAccessService = $this->createMock(SiteAccessServiceInterface::class); $this->listener = new SiteAccessMatchListener( $this->saRouter, $this->eventDispatcher, - $this->getSerializer() + $this->getSerializer(), + $this->siteAccessService ); } @@ -229,6 +234,21 @@ public function testOnKernelRequestUserHashWithOriginalRequest(): void $this->assertRequestHasSiteAccess($request, $originalRequest, $siteAccess); } + /** + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException + */ + public function testOnKernelRequestSubRequestPushesToSiteAccessService(): void + { + $siteAccess = new SiteAccess('test'); + $scheme = 'https'; + $host = 'phoenix-rises.fm'; + $port = 1234; + $path = '/foo/bar'; + $request = Request::create(sprintf('%s://%s:%d%s', $scheme, $host, $port, $path)); + $this->assertRequestHasSiteAccess($request, null, $siteAccess, HttpKernelInterface::SUB_REQUEST); + } + private function serializeSiteAccess(SiteAccess $siteAccess): string { return $this->getSerializer()->serialize($siteAccess, 'json'); @@ -241,13 +261,14 @@ private function serializeSiteAccess(SiteAccess $siteAccess): string private function assertRequestHasSiteAccess( Request $request, ?Request $originalRequest, - SiteAccess $siteAccess + SiteAccess $siteAccess, + int $requestType = HttpKernelInterface::MAIN_REQUEST ): void { $originalRequest ??= $request; $event = new RequestEvent( self::createStub(HttpKernelInterface::class), $request, - HttpKernelInterface::MAIN_REQUEST + $requestType ); $simplifiedRequest = new SimplifiedRequest( @@ -274,6 +295,17 @@ private function assertRequestHasSiteAccess( ->with(self::equalTo($postSAMatchEvent), MVCEvents::SITEACCESS) ; + if ($requestType === HttpKernelInterface::SUB_REQUEST) { + $this->siteAccessService + ->expects(self::once()) + ->method('changeSiteAccess') + ->with($siteAccess); + } else { + $this->siteAccessService + ->expects(self::never()) + ->method('changeSiteAccess'); + } + $this->listener->onKernelRequest($event); self::assertSame($siteAccess, $request->attributes->get('siteaccess')); } @@ -300,6 +332,10 @@ private function dispatchRequestEvent(Request $request, SiteAccess $siteAccess): ->method('dispatch') ->with($postSAMatchEvent, MVCEvents::SITEACCESS); + $this->siteAccessService + ->expects(self::never()) + ->method('changeSiteAccess'); + $this->listener->onKernelRequest($event); } diff --git a/tests/lib/MVC/Symfony/EventListener/SiteAccessRestoreListenerTest.php b/tests/lib/MVC/Symfony/EventListener/SiteAccessRestoreListenerTest.php index cb36739c25..7b69a30a9f 100644 --- a/tests/lib/MVC/Symfony/EventListener/SiteAccessRestoreListenerTest.php +++ b/tests/lib/MVC/Symfony/EventListener/SiteAccessRestoreListenerTest.php @@ -12,6 +12,7 @@ use Ibexa\Core\MVC\Symfony\EventListener\SiteAccessRestoreListener; use Ibexa\Core\MVC\Symfony\MVCEvents; use Ibexa\Core\MVC\Symfony\SiteAccess; +use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\MockObject; @@ -33,6 +34,8 @@ final class SiteAccessRestoreListenerTest extends TestCase private RequestStack $requestStack; + private SiteAccessServiceInterface&MockObject $siteAccessService; + private SiteAccessRestoreListener $listener; protected function setUp(): void @@ -41,9 +44,11 @@ protected function setUp(): void $this->kernel = self::createStub(HttpKernelInterface::class); $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class); $this->requestStack = new RequestStack(); + $this->siteAccessService = $this->createMock(SiteAccessServiceInterface::class); $this->listener = new SiteAccessRestoreListener( $this->requestStack, - $this->eventDispatcher + $this->eventDispatcher, + $this->siteAccessService ); } @@ -78,6 +83,10 @@ static function (PostSiteAccessMatchEvent $event) use ($parentSiteAccess, $paren MVCEvents::SITEACCESS ); + $this->siteAccessService + ->expects(self::once()) + ->method('restoreSiteAccess'); + $this->listener->onKernelFinishRequest( new FinishRequestEvent($this->kernel, $subRequest, HttpKernelInterface::SUB_REQUEST) ); @@ -97,12 +106,26 @@ public function testNoDispatch(array $requests, int $requestType): void self::assertNotNull($finishingRequest); $this->eventDispatcher->expects(self::never())->method('dispatch'); + $this->siteAccessService->expects(self::never())->method('restoreSiteAccess'); $this->listener->onKernelFinishRequest( new FinishRequestEvent($this->kernel, $finishingRequest, $requestType) ); } + public function testRestoresSiteAccessServiceWithoutDispatchingWhenNoParentRequest(): void + { + $subRequest = self::createRequestWithSiteAccess(new SiteAccess('admin', 'uri:element')); + $this->requestStack->push($subRequest); + + $this->eventDispatcher->expects(self::never())->method('dispatch'); + $this->siteAccessService->expects(self::once())->method('restoreSiteAccess'); + + $this->listener->onKernelFinishRequest( + new FinishRequestEvent($this->kernel, $subRequest, HttpKernelInterface::SUB_REQUEST) + ); + } + /** * @return iterable */ diff --git a/tests/lib/MVC/Symfony/Routing/GeneratorTest.php b/tests/lib/MVC/Symfony/Routing/GeneratorTest.php index f2b355648b..2e2798b0cd 100644 --- a/tests/lib/MVC/Symfony/Routing/GeneratorTest.php +++ b/tests/lib/MVC/Symfony/Routing/GeneratorTest.php @@ -10,6 +10,7 @@ use Ibexa\Core\MVC\Symfony\Routing\Generator; use Ibexa\Core\MVC\Symfony\SiteAccess; use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessRouterInterface; +use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface; use Ibexa\Core\MVC\Symfony\SiteAccess\URILexer; use Ibexa\Core\Repository\Values\Content\Location; use PHPUnit\Framework\Attributes\DataProvider; @@ -29,12 +30,19 @@ class GeneratorTest extends TestCase /** @var \PHPUnit\Framework\MockObject\MockObject */ private $logger; + /** @var \Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface|\PHPUnit\Framework\MockObject\MockObject */ + private $siteAccessService; + protected function setUp(): void { parent::setUp(); $this->siteAccessRouter = $this->createMock(SiteAccessRouterInterface::class); $this->logger = $this->createMock(LoggerInterface::class); - $this->generator = $this->getMockForAbstractClass(Generator::class); + $this->siteAccessService = $this->createMock(SiteAccessServiceInterface::class); + $this->generator = $this->getMockBuilder(Generator::class) + ->setConstructorArgs([$this->siteAccessService]) + ->onlyMethods(['doGenerate']) + ->getMock(); $this->generator->setSiteAccessRouter($this->siteAccessRouter); $this->generator->setLogger($this->logger); } @@ -61,7 +69,7 @@ public static function generateProvider(): array public function testSimpleGenerate($urlResource, array $parameters, $referenceType): void { $matcher = $this->createMock(URILexer::class); - $this->generator->setSiteAccess(new SiteAccess('test', 'fake', $matcher)); + $this->siteAccessService->method('getCurrent')->willReturn(new SiteAccess('test', 'fake', $matcher)); $baseUrl = '/base/url'; $requestContext = new RequestContext($baseUrl); @@ -72,14 +80,14 @@ public function testSimpleGenerate($urlResource, array $parameters, $referenceTy ->expects(self::once()) ->method('doGenerate') ->with($urlResource, $parameters) - ->will(self::returnValue($uri)); + ->willReturn($uri); $fullUri = $baseUrl . $uri; $matcher ->expects(self::once()) ->method('analyseLink') ->with($uri) - ->will(self::returnValue($uri)); + ->willReturn($uri); if ($referenceType === UrlGeneratorInterface::ABSOLUTE_URL) { $fullUri = $requestContext->getScheme() . '://' . $requestContext->getHost() . $baseUrl . $uri; @@ -92,7 +100,7 @@ public function testSimpleGenerate($urlResource, array $parameters, $referenceTy public function testGenerateWithSiteAccessNoReverseMatch($urlResource, array $parameters, $referenceType): void { $matcher = $this->createMock(URILexer::class); - $this->generator->setSiteAccess(new SiteAccess('test', 'test', $matcher)); + $this->siteAccessService->method('getCurrent')->willReturn(new SiteAccess('test', 'test', $matcher)); $baseUrl = '/base/url'; $requestContext = new RequestContext($baseUrl); @@ -103,14 +111,14 @@ public function testGenerateWithSiteAccessNoReverseMatch($urlResource, array $pa ->expects(self::once()) ->method('doGenerate') ->with($urlResource, $parameters) - ->will(self::returnValue($uri)); + ->willReturn($uri); $fullUri = $baseUrl . $uri; $matcher ->expects(self::once()) ->method('analyseLink') ->with($uri) - ->will(self::returnValue($uri)); + ->willReturn($uri); if ($referenceType === UrlGeneratorInterface::ABSOLUTE_URL) { $fullUri = $requestContext->getScheme() . '://' . $requestContext->getHost() . $baseUrl . $uri; @@ -121,7 +129,7 @@ public function testGenerateWithSiteAccessNoReverseMatch($urlResource, array $pa ->expects(self::once()) ->method('matchByName') ->with($siteAccessName) - ->will(self::returnValue(null)); + ->willReturn(null); $this->logger ->expects(self::once()) ->method('notice'); diff --git a/tests/lib/MVC/Symfony/Routing/UrlAliasGeneratorTest.php b/tests/lib/MVC/Symfony/Routing/UrlAliasGeneratorTest.php index 813ca4f24b..7e99a2426c 100644 --- a/tests/lib/MVC/Symfony/Routing/UrlAliasGeneratorTest.php +++ b/tests/lib/MVC/Symfony/Routing/UrlAliasGeneratorTest.php @@ -16,6 +16,7 @@ use Ibexa\Core\MVC\Symfony\Routing\Generator\UrlAliasGenerator; use Ibexa\Core\MVC\Symfony\SiteAccess; use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessRouterInterface; +use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface; use Ibexa\Core\Repository\Mapper\RoleDomainMapper; use Ibexa\Core\Repository\Permission\LimitationService; use Ibexa\Core\Repository\Permission\PermissionResolver; @@ -52,6 +53,9 @@ class UrlAliasGeneratorTest extends TestCase /** @var \PHPUnit\Framework\MockObject\MockObject */ private $configResolver; + /** @var \Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface|\PHPUnit\Framework\MockObject\MockObject */ + private $siteAccessService; + protected function setUp(): void { parent::setUp(); @@ -59,6 +63,7 @@ protected function setUp(): void $this->logger = $this->createMock(LoggerInterface::class); $this->siteAccessRouter = self::createStub(SiteAccessRouterInterface::class); $this->configResolver = $this->createMock(ConfigResolverInterface::class); + $this->siteAccessService = $this->createMock(SiteAccessServiceInterface::class); $repositoryClass = Repository::class; $this->repository = $repository = $this ->getMockBuilder($repositoryClass) @@ -95,6 +100,7 @@ protected function setUp(): void $this->repository, $this->router, $this->configResolver, + $this->siteAccessService, $urlAliasCharmap ); $this->urlAliasGenerator->setLogger($this->logger); @@ -176,7 +182,7 @@ public function testDoGenerate(URLAlias $urlAlias, array $parameters, $expected) ->with($location, false) ->will(self::returnValue([$urlAlias])); - $this->urlAliasGenerator->setSiteAccess(new SiteAccess('test', 'fake', self::createStub(SiteAccess\URILexer::class))); + $this->siteAccessService->method('getCurrent')->willReturn(new SiteAccess('test', 'fake', self::createStub(SiteAccess\URILexer::class))); self::assertSame($expected, $this->urlAliasGenerator->doGenerate($location, $parameters)); } @@ -276,7 +282,7 @@ static function ($location) use ($treeRootUrlAlias): URLAlias { ) ); - $this->urlAliasGenerator->setSiteAccess(new SiteAccess('test', 'fake', self::createStub(SiteAccess\URILexer::class))); + $this->siteAccessService->method('getCurrent')->willReturn(new SiteAccess('test', 'fake', self::createStub(SiteAccess\URILexer::class))); self::assertSame($expected, $this->urlAliasGenerator->doGenerate($location, $parameters)); } @@ -390,7 +396,7 @@ public function testDoGenerateWithSiteAccessLoadsLocationWithLanguages(): void ->with($location, null, false, ['ger-DE']) ->willReturn($treeRootUrlAliases[$location->id]); - $this->urlAliasGenerator->setSiteAccess( + $this->siteAccessService->method('getCurrent')->willReturn( new SiteAccess( $gerSiteAccess, 'default', diff --git a/tests/lib/MVC/Symfony/Routing/UrlAliasRouterTest.php b/tests/lib/MVC/Symfony/Routing/UrlAliasRouterTest.php index 6c48dbf3af..2e0bd7de37 100644 --- a/tests/lib/MVC/Symfony/Routing/UrlAliasRouterTest.php +++ b/tests/lib/MVC/Symfony/Routing/UrlAliasRouterTest.php @@ -18,6 +18,7 @@ use Ibexa\Core\MVC\Symfony\Routing\UrlAliasRouter; use Ibexa\Core\MVC\Symfony\SiteAccess; use Ibexa\Core\MVC\Symfony\SiteAccess\Matcher; +use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface; use Ibexa\Core\MVC\Symfony\View\Manager as ViewManager; use Ibexa\Core\Repository\Repository; use Ibexa\Core\Repository\Values\Content\Location; @@ -78,6 +79,7 @@ protected function setUp(): void $repository, self::createStub(RouterInterface::class), self::createStub(ConfigResolverInterface::class), + self::createStub(SiteAccessServiceInterface::class), ] ) ->getMock(); diff --git a/tests/lib/MVC/Symfony/Security/HttpUtilsTest.php b/tests/lib/MVC/Symfony/Security/HttpUtilsTest.php index 6dc92ec9cc..575183e575 100644 --- a/tests/lib/MVC/Symfony/Security/HttpUtilsTest.php +++ b/tests/lib/MVC/Symfony/Security/HttpUtilsTest.php @@ -9,6 +9,7 @@ use Ibexa\Core\MVC\Symfony\Security\HttpUtils; use Ibexa\Core\MVC\Symfony\SiteAccess; +use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; @@ -20,8 +21,8 @@ class HttpUtilsTest extends TestCase public function testGenerateUriStandard($uri, $isUriRouteName, $expected): void { $urlGenerator = $this->createMock(UrlGeneratorInterface::class); - $httpUtils = new HttpUtils($urlGenerator); - $httpUtils->setSiteAccess(new SiteAccess('test')); + $siteAccessService = $this->createSiteAccessServiceReturning(new SiteAccess('test')); + $httpUtils = new HttpUtils($urlGenerator, null, null, null, $siteAccessService); $request = Request::create('http://ezpublish.dev/'); $request->attributes->set('siteaccess', new SiteAccess('test')); $requestAttributes = ['foo' => 'bar', 'some' => 'thing']; @@ -32,7 +33,7 @@ public function testGenerateUriStandard($uri, $isUriRouteName, $expected): void ->expects(self::once()) ->method('generate') ->with($uri, $requestAttributes, UrlGeneratorInterface::ABSOLUTE_URL) - ->will(self::returnValue($expected . '?' . http_build_query($requestAttributes))); + ->willReturn($expected . '?' . http_build_query($requestAttributes)); } self::assertSame($expected, $httpUtils->generateUri($request, $uri)); @@ -62,13 +63,13 @@ public function testGenerateUri($uri, $isUriRouteName, $siteAccessUri, $expected ->expects(self::once()) ->method('analyseLink') ->with($uri) - ->will(self::returnValue($siteAccessUri . $uri)); + ->willReturn($siteAccessUri . $uri); $siteAccess->matcher = $matcher; } $urlGenerator = $this->createMock(UrlGeneratorInterface::class); - $httpUtils = new HttpUtils($urlGenerator); - $httpUtils->setSiteAccess($siteAccess); + $siteAccessService = $this->createSiteAccessServiceReturning($siteAccess); + $httpUtils = new HttpUtils($urlGenerator, null, null, null, $siteAccessService); $request = Request::create('http://ezpublish.dev/'); $request->attributes->set('siteaccess', $siteAccess); $requestAttributes = ['foo' => 'bar', 'some' => 'thing']; @@ -79,7 +80,7 @@ public function testGenerateUri($uri, $isUriRouteName, $siteAccessUri, $expected ->expects(self::once()) ->method('generate') ->with($uri, $requestAttributes, UrlGeneratorInterface::ABSOLUTE_URL) - ->will(self::returnValue($expected . '?' . http_build_query($requestAttributes))); + ->willReturn($expected . '?' . http_build_query($requestAttributes)); } $res = $httpUtils->generateUri($request, $uri); @@ -102,8 +103,8 @@ public static function generateUriProvider(): array public function testCheckRequestPathStandard(): void { - $httpUtils = new HttpUtils(); - $httpUtils->setSiteAccess(new SiteAccess('test')); + $siteAccessService = $this->createSiteAccessServiceReturning(new SiteAccess('test')); + $httpUtils = new HttpUtils(null, null, null, null, $siteAccessService); $request = Request::create('http://ezpublish.dev/foo/bar'); self::assertTrue($httpUtils->checkRequestPath($request, '/foo/bar')); } @@ -118,12 +119,12 @@ public function testCheckRequestPath($path, $siteAccessUri, $requestUri, $expect ->expects(self::once()) ->method('analyseLink') ->with($path) - ->will(self::returnValue($siteAccessUri . $path)); + ->willReturn($siteAccessUri . $path); $siteAccess->matcher = $matcher; } - $httpUtils = new HttpUtils(); - $httpUtils->setSiteAccess($siteAccess); + $siteAccessService = $this->createSiteAccessServiceReturning($siteAccess); + $httpUtils = new HttpUtils(null, null, null, null, $siteAccessService); $request = Request::create($requestUri); self::assertSame($expected, $httpUtils->checkRequestPath($request, $path)); } @@ -142,4 +143,12 @@ public static function checkRequestPathProvider(): array ['/foo/bar', '/blabla', 'http://ezpublish.dev/blabla/foo/bar', true], ]; } + + private function createSiteAccessServiceReturning(SiteAccess $siteAccess): SiteAccessServiceInterface + { + $siteAccessService = $this->createMock(SiteAccessServiceInterface::class); + $siteAccessService->method('getCurrent')->willReturn($siteAccess); + + return $siteAccessService; + } } diff --git a/tests/lib/MVC/Symfony/SiteAccess/SiteAccessServiceTest.php b/tests/lib/MVC/Symfony/SiteAccess/SiteAccessServiceTest.php index 58056d3e4c..83458a7d73 100644 --- a/tests/lib/MVC/Symfony/SiteAccess/SiteAccessServiceTest.php +++ b/tests/lib/MVC/Symfony/SiteAccess/SiteAccessServiceTest.php @@ -11,13 +11,19 @@ use ArrayIterator; use Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException; use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; +use Ibexa\Core\MVC\Symfony\Event\PostSiteAccessMatchEvent; use Ibexa\Core\MVC\Symfony\Event\ScopeChangeEvent; use Ibexa\Core\MVC\Symfony\MVCEvents; use Ibexa\Core\MVC\Symfony\SiteAccess; use Ibexa\Core\MVC\Symfony\SiteAccess\Provider\StaticSiteAccessProvider; use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessProviderInterface; use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessService; +use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface; use PHPUnit\Framework\TestCase; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; class SiteAccessServiceTest extends TestCase { @@ -25,10 +31,10 @@ class SiteAccessServiceTest extends TestCase private const UNDEFINED_SA_NAME = 'undefined_sa'; private const SA_GROUP = 'group'; - /** @var \Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessProviderInterface|\PHPUnit\Framework\MockObject\MockObject */ + /** @var \Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessProviderInterface&\PHPUnit\Framework\MockObject\MockObject */ private $provider; - /** @var \Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface|\PHPUnit\Framework\MockObject\MockObject */ + /** @var \Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface&\PHPUnit\Framework\MockObject\MockObject */ private $configResolver; /** @var \Ibexa\Core\MVC\Symfony\SiteAccess */ @@ -50,27 +56,39 @@ protected function setUp(): void $this->configResolverParameters = $this->getConfigResolverParameters(); } - public function testGetCurrentSiteAccess(): void + public function testGetCurrentIsNullInitially(): void { - $service = new SiteAccessService( - self::createStub(SiteAccessProviderInterface::class), - self::createStub(ConfigResolverInterface::class) - ); + self::assertNull($this->createService()->getCurrent()); + } - self::assertNull($service->getCurrent()); + public function testOnSiteAccessMatchResetsStackOnMainRequest(): void + { + $service = $this->createService(); $siteAccess = new SiteAccess('default'); - $service->setSiteAccess($siteAccess); + $service->onSiteAccessMatch($this->createSiteAccessMatchEvent($siteAccess, HttpKernelInterface::MAIN_REQUEST)); + self::assertSame($siteAccess, $service->getCurrent()); + } + + public function testOnSiteAccessMatchIsNoopOnSubRequest(): void + { + $service = $this->createService(); - $service->setSiteAccess(null); - self::assertNull($service->getCurrent()); + $mainSiteAccess = new SiteAccess('main'); + $service->onSiteAccessMatch($this->createSiteAccessMatchEvent($mainSiteAccess, HttpKernelInterface::MAIN_REQUEST)); + + $subRequestSiteAccess = new SiteAccess('sub_request'); + $service->onSiteAccessMatch($this->createSiteAccessMatchEvent($subRequestSiteAccess, HttpKernelInterface::SUB_REQUEST)); + + self::assertSame($mainSiteAccess, $service->getCurrent()); } public function testGetSubscribedEvents(): void { self::assertSame( [ + MVCEvents::SITEACCESS => 'onSiteAccessMatch', MVCEvents::CONFIG_SCOPE_CHANGE => 'onConfigScopeChange', MVCEvents::CONFIG_SCOPE_RESTORE => 'onConfigScopeRestore', ], @@ -78,77 +96,119 @@ public function testGetSubscribedEvents(): void ); } - public function testOnConfigScopeChangeMakesGetCurrentReflectTheNewSiteAccess(): void + public function testChangeSiteAccessPushesAndReturnsTheGivenSiteAccess(): void { - $service = new SiteAccessService( - self::createStub(SiteAccessProviderInterface::class), - self::createStub(ConfigResolverInterface::class) - ); + $service = $this->createService(); $baseSiteAccess = new SiteAccess('base'); - $service->setSiteAccess($baseSiteAccess); + $service->onSiteAccessMatch($this->createSiteAccessMatchEvent($baseSiteAccess, HttpKernelInterface::MAIN_REQUEST)); $previewSiteAccess = new SiteAccess('preview'); - $service->onConfigScopeChange(new ScopeChangeEvent($previewSiteAccess)); + self::assertSame($previewSiteAccess, $service->changeSiteAccess($previewSiteAccess)); + self::assertSame($previewSiteAccess, $service->getCurrent()); + } + public function testChangeSiteAccessDispatchesEventWithoutTriggeringTheDeprecatedHandler(): void + { + $eventDispatcher = new EventDispatcher(); + $service = $this->createService($eventDispatcher); + + $dispatchedEvents = []; + $eventDispatcher->addListener(MVCEvents::CONFIG_SCOPE_CHANGE, static function (ScopeChangeEvent $event) use (&$dispatchedEvents): void { + $dispatchedEvents[] = $event->getSiteAccess(); + }); + + $previewSiteAccess = new SiteAccess('preview'); + $service->changeSiteAccess($previewSiteAccess); + + // The event is still dispatched for other, unrelated listeners to observe... + self::assertSame([$previewSiteAccess], $dispatchedEvents); + // ...but the stack was mutated exactly once by changeSiteAccess() itself, proving + // the service's own onConfigScopeChange() (which would push a 2nd time) did not react. self::assertSame($previewSiteAccess, $service->getCurrent()); } - public function testOnConfigScopeRestoreBringsBackThePreviousSiteAccess(): void + public function testRestoreSiteAccessPopsStackAndReturnsThePreviousSiteAccess(): void { - $service = new SiteAccessService( - self::createStub(SiteAccessProviderInterface::class), - self::createStub(ConfigResolverInterface::class) - ); + $service = $this->createService(); $baseSiteAccess = new SiteAccess('base'); - $service->setSiteAccess($baseSiteAccess); + $service->onSiteAccessMatch($this->createSiteAccessMatchEvent($baseSiteAccess, HttpKernelInterface::MAIN_REQUEST)); $previewSiteAccess = new SiteAccess('preview'); - $service->onConfigScopeChange(new ScopeChangeEvent($previewSiteAccess)); - $service->onConfigScopeRestore(new ScopeChangeEvent($baseSiteAccess)); + $service->changeSiteAccess($previewSiteAccess); + self::assertSame($baseSiteAccess, $service->restoreSiteAccess()); self::assertSame($baseSiteAccess, $service->getCurrent()); } - public function testOnConfigScopeRestoreNeverDropsTheBaseSiteAccess(): void + public function testRestoreSiteAccessNeverDropsBelowTheBaseSiteAccess(): void { - $service = new SiteAccessService( - self::createStub(SiteAccessProviderInterface::class), - self::createStub(ConfigResolverInterface::class) - ); + $service = $this->createService(); $baseSiteAccess = new SiteAccess('base'); - $service->setSiteAccess($baseSiteAccess); - - $service->onConfigScopeRestore(new ScopeChangeEvent($baseSiteAccess)); + $service->onSiteAccessMatch($this->createSiteAccessMatchEvent($baseSiteAccess, HttpKernelInterface::MAIN_REQUEST)); + self::assertSame($baseSiteAccess, $service->restoreSiteAccess()); self::assertSame($baseSiteAccess, $service->getCurrent()); } - public function testNestedConfigScopeChangesAndRestoresRoundTripLikeAStack(): void + public function testRestoreSiteAccessReturnsNullWhenStackIsEmpty(): void { - $service = new SiteAccessService( - self::createStub(SiteAccessProviderInterface::class), - self::createStub(ConfigResolverInterface::class) - ); + self::assertNull($this->createService()->restoreSiteAccess()); + } + + public function testNestedChangeSiteAccessAndRestoreSiteAccessRoundTripLikeAStack(): void + { + $service = $this->createService(); $baseSiteAccess = new SiteAccess('base'); - $service->setSiteAccess($baseSiteAccess); + $service->onSiteAccessMatch($this->createSiteAccessMatchEvent($baseSiteAccess, HttpKernelInterface::MAIN_REQUEST)); $firstSiteAccess = new SiteAccess('first'); $secondSiteAccess = new SiteAccess('second'); - $service->onConfigScopeChange(new ScopeChangeEvent($firstSiteAccess)); + $service->changeSiteAccess($firstSiteAccess); self::assertSame($firstSiteAccess, $service->getCurrent()); - $service->onConfigScopeChange(new ScopeChangeEvent($secondSiteAccess)); + $service->changeSiteAccess($secondSiteAccess); self::assertSame($secondSiteAccess, $service->getCurrent()); - $service->onConfigScopeRestore(new ScopeChangeEvent($firstSiteAccess)); - self::assertSame($firstSiteAccess, $service->getCurrent()); + self::assertSame($firstSiteAccess, $service->restoreSiteAccess()); + self::assertSame($baseSiteAccess, $service->restoreSiteAccess()); + } + + public function testOnConfigScopeChangeReactsToAManuallyDispatchedEventAndTriggersDeprecation(): void + { + $this->expectUserDeprecationMessage('Since ibexa/core 6.0: Dispatching ' . ScopeChangeEvent::class . ' manually under MVCEvents::CONFIG_SCOPE_CHANGE is deprecated and will no longer be reflected by ' . SiteAccessService::class . '::getCurrent() in 7.0. Call ' . SiteAccessServiceInterface::class . '::changeSiteAccess() instead.'); + + $eventDispatcher = new EventDispatcher(); + $service = $this->createService($eventDispatcher); + + $baseSiteAccess = new SiteAccess('base'); + $service->onSiteAccessMatch($this->createSiteAccessMatchEvent($baseSiteAccess, HttpKernelInterface::MAIN_REQUEST)); + + $previewSiteAccess = new SiteAccess('preview'); + $eventDispatcher->dispatch(new ScopeChangeEvent($previewSiteAccess), MVCEvents::CONFIG_SCOPE_CHANGE); + + self::assertSame($previewSiteAccess, $service->getCurrent()); + } + + public function testOnConfigScopeRestoreReactsToAManuallyDispatchedEventAndTriggersDeprecation(): void + { + $this->expectUserDeprecationMessage('Since ibexa/core 6.0: Dispatching ' . ScopeChangeEvent::class . ' manually under MVCEvents::CONFIG_SCOPE_RESTORE is deprecated and will no longer be reflected by ' . SiteAccessService::class . '::getCurrent() in 7.0. Call ' . SiteAccessServiceInterface::class . '::restoreSiteAccess() instead.'); + + $eventDispatcher = new EventDispatcher(); + $service = $this->createService($eventDispatcher); + + $baseSiteAccess = new SiteAccess('base'); + $service->onSiteAccessMatch($this->createSiteAccessMatchEvent($baseSiteAccess, HttpKernelInterface::MAIN_REQUEST)); + + $previewSiteAccess = new SiteAccess('preview'); + $service->changeSiteAccess($previewSiteAccess); + + $eventDispatcher->dispatch(new ScopeChangeEvent($baseSiteAccess), MVCEvents::CONFIG_SCOPE_RESTORE); - $service->onConfigScopeRestore(new ScopeChangeEvent($baseSiteAccess)); self::assertSame($baseSiteAccess, $service->getCurrent()); } @@ -160,7 +220,8 @@ public function testGetSiteAccess(): void ); $service = new SiteAccessService( $staticSiteAccessProvider, - self::createStub(ConfigResolverInterface::class) + self::createStub(ConfigResolverInterface::class), + new EventDispatcher() ); self::assertEquals( @@ -177,7 +238,8 @@ public function testGetSiteAccessThrowsNotFoundException(): void ); $service = new SiteAccessService( $staticSiteAccessProvider, - self::createStub(ConfigResolverInterface::class) + self::createStub(ConfigResolverInterface::class), + new EventDispatcher() ); $this->expectException(NotFoundException::class); @@ -213,10 +275,26 @@ public function testGetFirstSiteAccessesRelation(): void ); } + private function createService(?EventDispatcherInterface $eventDispatcher = null): SiteAccessService + { + $eventDispatcher ??= new EventDispatcher(); + $service = new SiteAccessService($this->provider, $this->configResolver, $eventDispatcher); + $eventDispatcher->addSubscriber($service); + + return $service; + } + + private function createSiteAccessMatchEvent(SiteAccess $siteAccess, int $requestType): PostSiteAccessMatchEvent + { + return new PostSiteAccessMatchEvent($siteAccess, new Request(), $requestType); + } + private function getSiteAccessService(): SiteAccessService { - $siteAccessService = new SiteAccessService($this->provider, $this->configResolver); - $siteAccessService->setSiteAccess($this->siteAccess); + $siteAccessService = $this->createService(); + $siteAccessService->onSiteAccessMatch( + $this->createSiteAccessMatchEvent($this->siteAccess, HttpKernelInterface::MAIN_REQUEST) + ); return $siteAccessService; }