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 a3ed987b63..9fa19e53e7 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -1608,18 +1608,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
@@ -1638,18 +1626,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
@@ -2982,12 +2958,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
@@ -3036,24 +3006,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
@@ -3408,12 +3360,6 @@ parameters:
count: 1
path: src/bundle/Core/Routing/DefaultRouter.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
@@ -7362,18 +7308,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/src/bundle/Core/Command/DebugConfigResolverCommand.php b/src/bundle/Core/Command/DebugConfigResolverCommand.php
index 8062d34248..ee8cd02155 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 : ''));
$output->writeln('Parameter:');
$cloner = new VarCloner();
diff --git a/src/bundle/Core/DependencyInjection/Compiler/ChainRoutingPass.php b/src/bundle/Core/DependencyInjection/Compiler/ChainRoutingPass.php
index 76d9769a08..adc4b29a68 100644
--- a/src/bundle/Core/DependencyInjection/Compiler/ChainRoutingPass.php
+++ b/src/bundle/Core/DependencyInjection/Compiler/ChainRoutingPass.php
@@ -8,8 +8,8 @@
namespace Ibexa\Bundle\Core\DependencyInjection\Compiler;
use Ibexa\Core\MVC\Symfony\Routing\ChainRouter;
-use Ibexa\Core\MVC\Symfony\SiteAccess;
use Ibexa\Core\MVC\Symfony\SiteAccess\Router;
+use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
@@ -28,7 +28,7 @@ public function process(ContainerBuilder $container): void
// The default router will be given the highest priority so that it will be used by default
if ($container->hasDefinition('router.default')) {
$defaultRouter = $container->getDefinition('router.default');
- $defaultRouter->addMethodCall('setSiteAccess', [new Reference(SiteAccess::class)]);
+ $defaultRouter->setArgument('$siteAccessService', new Reference(SiteAccessServiceInterface::class));
$defaultRouter->addMethodCall('setConfigResolver', [new Reference('ibexa.config.resolver')]);
$defaultRouter->addMethodCall(
'setNonSiteAccessAwareRoutes',
diff --git a/src/bundle/Core/DependencyInjection/Compiler/FragmentPass.php b/src/bundle/Core/DependencyInjection/Compiler/FragmentPass.php
index 2ddea81531..dbb86c6478 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,10 +50,16 @@ 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)
+ // Note: InlineFragmentRenderer's constructor only declares the first 2 arguments (it has
+ // no use for SiteAccessServiceInterface); PHP silently ignores the extra 3rd argument.
if ($id === 'fragment.renderer.inline') {
$decoratedDef->setClass(InlineFragmentRenderer::class);
}
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 e74fe3208d..fb1366bcbe 100644
--- a/src/bundle/Core/Resources/config/routing.yml
+++ b/src/bundle/Core/Resources/config/routing.yml
@@ -31,7 +31,6 @@ services:
abstract: true
calls:
- [setRequestContext, ["@router.request_context"]]
- - [setSiteAccess, ['@?Ibexa\Core\MVC\Symfony\SiteAccess']]
- [setSiteAccessRouter, ['@Ibexa\Core\MVC\Symfony\SiteAccess\Router']]
- [setLogger, ["@?logger"]]
@@ -55,6 +54,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
@@ -88,8 +88,9 @@ services:
arguments:
- '@ibexa.siteaccess.provider'
- '@ibexa.config.resolver'
- calls:
- - [setSiteAccess, ['@Ibexa\Core\MVC\Symfony\SiteAccess']]
+ - '@event_dispatcher'
+ tags:
+ - { name: kernel.event_subscriber }
Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface:
alias: 'Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessService'
diff --git a/src/bundle/Core/Resources/config/services.yml b/src/bundle/Core/Resources/config/services.yml
index 129414be41..b55c1a37df 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 }
@@ -165,7 +163,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 b8999d2460..b0f3f06a45 100644
--- a/src/bundle/Core/Routing/DefaultRouter.php
+++ b/src/bundle/Core/Routing/DefaultRouter.php
@@ -11,9 +11,11 @@
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\Container\ContainerInterface;
+use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
@@ -22,10 +24,8 @@
/**
* Extension of Symfony default router implementing RequestMatcherInterface.
*/
-class DefaultRouter extends Router implements SiteAccessAware
+class DefaultRouter extends Router
{
- protected ?SiteAccess $siteAccess = null;
-
/** @var string[] */
protected array $nonSiteAccessAwareRoutes = [];
@@ -33,14 +33,25 @@ class DefaultRouter extends Router implements SiteAccessAware
protected SiteAccessRouterInterface $siteAccessRouter;
- public function setConfigResolver(ConfigResolverInterface $configResolver): void
- {
- $this->configResolver = $configResolver;
+ /**
+ * @param array $options
+ */
+ public function __construct(
+ ContainerInterface $container,
+ mixed $resource,
+ array $options = [],
+ ?RequestContext $context = null,
+ ?ContainerInterface $parameters = null,
+ ?LoggerInterface $logger = null,
+ ?string $defaultLocale = null,
+ private ?SiteAccessServiceInterface $siteAccessService = null,
+ ) {
+ parent::__construct($container, $resource, $options, $context, $parameters, $logger, $defaultLocale);
}
- public function setSiteAccess(?SiteAccess $siteAccess = null): void
+ public function setConfigResolver(ConfigResolverInterface $configResolver): void
{
- $this->siteAccess = $siteAccess;
+ $this->configResolver = $configResolver;
}
/**
@@ -80,7 +91,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);
@@ -92,7 +103,7 @@ public function generate(string $name, array $parameters = [], int $referenceTyp
$context = $this->getContextBySimplifiedRequest($siteAccess->matcher->getRequest());
$this->setContext($context);
} elseif ($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.");
}
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/contracts/Test/IbexaTestKernel.php b/src/contracts/Test/IbexaTestKernel.php
index cae200d744..4b473592cf 100644
--- a/src/contracts/Test/IbexaTestKernel.php
+++ b/src/contracts/Test/IbexaTestKernel.php
@@ -17,6 +17,8 @@
use Ibexa\Contracts\Core\Persistence\TransactionHandler;
use Ibexa\Contracts\Core\Repository;
use Ibexa\Contracts\Core\Test\Persistence\Fixture\YamlFixture;
+use Ibexa\Core\MVC\Symfony\SiteAccess;
+use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessService;
use Ibexa\Tests\Integration\Core\IO\FlysystemTestAdapter;
use Ibexa\Tests\Integration\Core\IO\FlysystemTestAdapterInterface;
use JMS\TranslationBundle\JMSTranslationBundle;
@@ -28,6 +30,7 @@
use Symfony\Bundle\SecurityBundle\SecurityBundle;
use Symfony\Bundle\TwigBundle\TwigBundle;
use Symfony\Component\Config\Loader\LoaderInterface;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
@@ -168,6 +171,21 @@ public function registerContainerConfiguration(LoaderInterface $loader): void
});
}
+ public function build(ContainerBuilder $container): void
+ {
+ parent::build($container);
+
+ // Registered as a compiler pass (rather than done directly in registerContainerConfiguration())
+ // because SiteAccessService's definition doesn't exist yet at that point: bundle extensions
+ // (which register it) only run once the container compiles.
+ $container->addCompilerPass(new class() implements CompilerPassInterface {
+ public function process(ContainerBuilder $container): void
+ {
+ IbexaTestKernel::seedDefaultSiteAccess($container);
+ }
+ });
+ }
+
/**
* @throws \Exception
*/
@@ -270,6 +288,23 @@ private static function setUpTestLogger(ContainerBuilder $container): void
$container->setDefinition('logger', new Definition(NullLogger::class));
}
+ /**
+ * Test cases using this kernel never dispatch a real request, so SiteAccessService would
+ * otherwise never have a current SiteAccess. Seed it the same way ConsoleCommandListener does
+ * for CLI: a fresh SiteAccess passed to changeSiteAccess(), not a shared/mutated singleton.
+ */
+ public static function seedDefaultSiteAccess(ContainerBuilder $container): void
+ {
+ if (!$container->hasDefinition(SiteAccessService::class)) {
+ return;
+ }
+
+ $container->getDefinition(SiteAccessService::class)->addMethodCall(
+ 'changeSiteAccess',
+ [new Definition(SiteAccess::class, ['default'])]
+ );
+ }
+
/**
* Creates synthetic services in container, allowing compilation of container when some services are missing.
* Additionally, those services can be replaced with mock implementations at runtime, allowing integration testing.
diff --git a/src/lib/Helper/ContentPreviewHelper.php b/src/lib/Helper/ContentPreviewHelper.php
index ed375dd2a7..af92128409 100644
--- a/src/lib/Helper/ContentPreviewHelper.php
+++ b/src/lib/Helper/ContentPreviewHelper.php
@@ -9,23 +9,15 @@
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 +28,20 @@ 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
+ * @return \Ibexa\Core\MVC\Symfony\SiteAccess|null
*/
public function getOriginalSiteAccess()
{
- return $this->originalSiteAccess;
+ return $this->siteAccessService->getCurrent();
}
/**
@@ -66,23 +53,17 @@ 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
+ * @return \Ibexa\Core\MVC\Symfony\SiteAccess|null
*/
public function restoreConfigScope()
{
- $event = new ScopeChangeEvent($this->originalSiteAccess);
- $this->eventDispatcher->dispatch($event, MVCEvents::CONFIG_SCOPE_RESTORE);
-
- return $event->getSiteAccess();
+ return $this->siteAccessService->restoreSiteAccess();
}
/**
@@ -99,7 +80,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..193f0ff08f 100644
--- a/src/lib/MVC/Symfony/Controller/Content/PreviewController.php
+++ b/src/lib/MVC/Symfony/Controller/Content/PreviewController.php
@@ -121,39 +121,51 @@ public function previewContentAction(
}
$siteAccess = $this->previewHelper->getOriginalSiteAccess();
+ if ($siteAccess === null) {
+ throw new BadStateException('siteAccess', 'no SiteAccess currently set, cannot preview');
+ }
+
// 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);
+ 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);
}
-
- return new Response($message);
- } catch (Exception $e) {
- return $this->buildResponseForGenericPreviewError($location, $content, $e);
+ $response->headers->addCacheControlDirective('no-cache', true);
+ $response->setPrivate();
+
+ return $response;
+ } finally {
+ // Guarantee the scope is restored and the preview flag is cleared however this method
+ // exits (success, a caught exception's early return, or an uncaught one) — otherwise
+ // SiteAccessService and ConfigResolver's scope can be left disagreeing once the request
+ // finishes and the stack silently unwinds without a CONFIG_SCOPE_RESTORE. Safe to call
+ // unconditionally even when the scope was never changed above: restoreSiteAccess()'s
+ // floor guard never pops past the request-matched entry, so it's a no-op in that case.
+ $this->previewHelper->restoreConfigScope();
+ $this->previewHelper->setPreviewActive(false);
}
- $response->headers->addCacheControlDirective('no-cache', true);
- $response->setPrivate();
-
- $this->previewHelper->restoreConfigScope();
- $this->previewHelper->setPreviewActive(false);
-
- return $response;
}
/**
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/SiteAccessAware.php b/src/lib/MVC/Symfony/SiteAccess/SiteAccessAware.php
index d3f09d2a6f..ae8c9549bf 100644
--- a/src/lib/MVC/Symfony/SiteAccess/SiteAccessAware.php
+++ b/src/lib/MVC/Symfony/SiteAccess/SiteAccessAware.php
@@ -11,6 +11,8 @@
/**
* Interface for SiteAccess aware services.
+ *
+ * @deprecated 6.0, to be removed in 7.0. Use \Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface::getCurrent() instead.
*/
interface SiteAccessAware
{
diff --git a/src/lib/MVC/Symfony/SiteAccess/SiteAccessService.php b/src/lib/MVC/Symfony/SiteAccess/SiteAccessService.php
index 0676707544..9411580f31 100644
--- a/src/lib/MVC/Symfony/SiteAccess/SiteAccessService.php
+++ b/src/lib/MVC/Symfony/SiteAccess/SiteAccessService.php
@@ -11,22 +11,56 @@
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\Event\FinishRequestEvent;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
+use Symfony\Component\HttpKernel\KernelEvents;
-class SiteAccessService implements SiteAccessServiceInterface, SiteAccessAware
+class SiteAccessService implements SiteAccessServiceInterface, EventSubscriberInterface
{
- private ?SiteAccess $siteAccess = null;
+ /** @var \Ibexa\Core\MVC\Symfony\SiteAccess[] */
+ private array $siteAccessStack = [];
public function __construct(
private readonly SiteAccessProviderInterface $provider,
- private readonly ConfigResolverInterface $configResolver
+ private readonly ConfigResolverInterface $configResolver,
+ private readonly EventDispatcherInterface $eventDispatcher
) {
}
- public function setSiteAccess(?SiteAccess $siteAccess = null): void
+ public static function getSubscribedEvents(): array
{
- $this->siteAccess = $siteAccess;
+ return [
+ MVCEvents::SITEACCESS => 'onSiteAccessMatch',
+ KernelEvents::FINISH_REQUEST => 'onKernelFinishRequest',
+ ];
+ }
+
+ /**
+ * Establishes the base of the SiteAccess stack for a top-level request, or pushes an
+ * additional entry for a sub-request (e.g. content preview, fragments, ESI).
+ */
+ public function onSiteAccessMatch(PostSiteAccessMatchEvent $event): void
+ {
+ if ($event->getRequestType() === HttpKernelInterface::MAIN_REQUEST) {
+ $this->siteAccessStack = [$event->getSiteAccess()];
+ } else {
+ $this->siteAccessStack[] = $event->getSiteAccess();
+ }
+ }
+
+ /**
+ * Undoes the push performed by onSiteAccessMatch() for the request/sub-request that just finished.
+ */
+ public function onKernelFinishRequest(FinishRequestEvent $event): void
+ {
+ $this->popSiteAccessStack();
}
public function exists(string $name): bool
@@ -50,12 +84,40 @@ public function getAll(): iterable
public function getCurrent(): ?SiteAccess
{
- return $this->siteAccess ?? 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->eventDispatcher->dispatch(new ScopeChangeEvent($siteAccess), MVCEvents::CONFIG_SCOPE_CHANGE);
+
+ return $siteAccess;
+ }
+
+ public function restoreSiteAccess(): ?SiteAccess
+ {
+ $this->popSiteAccessStack();
+ $siteAccess = $this->getCurrent();
+ if ($siteAccess !== null) {
+ $this->eventDispatcher->dispatch(new ScopeChangeEvent($siteAccess), MVCEvents::CONFIG_SCOPE_RESTORE);
+ }
+
+ return $siteAccess;
}
public function getSiteAccessesRelation(?SiteAccess $siteAccess = null): array
{
- $siteAccess = $siteAccess ?? $this->siteAccess;
+ $siteAccess ??= $this->getCurrent();
if ($siteAccess === null) {
throw new InvalidArgumentException('siteAccess', 'no SiteAccess given and none currently set');
}
@@ -87,4 +149,16 @@ 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, or the CLI SiteAccess established via
+ * changeSiteAccess()) must survive an unbalanced restore or an unrelated sub-request finishing.
+ */
+ private function popSiteAccessStack(): void
+ {
+ if (count($this->siteAccessStack) > 1) {
+ array_pop($this->siteAccessStack);
+ }
+ }
}
diff --git a/src/lib/MVC/Symfony/SiteAccess/SiteAccessServiceInterface.php b/src/lib/MVC/Symfony/SiteAccess/SiteAccessServiceInterface.php
index b95cfda289..feb664114e 100644
--- a/src/lib/MVC/Symfony/SiteAccess/SiteAccessServiceInterface.php
+++ b/src/lib/MVC/Symfony/SiteAccess/SiteAccessServiceInterface.php
@@ -37,4 +37,17 @@ public function getCurrent(): ?SiteAccess;
* @throws \Ibexa\Core\Base\Exceptions\InvalidArgumentException if no SiteAccess is given and none is currently set
*/
public function getSiteAccessesRelation(?SiteAccess $siteAccess = null): array;
+
+ /**
+ * 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.
+ */
+ 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;
}
diff --git a/src/lib/Resources/settings/roles.yml b/src/lib/Resources/settings/roles.yml
index 57b59122de..062081dfe2 100644
--- a/src/lib/Resources/settings/roles.yml
+++ b/src/lib/Resources/settings/roles.yml
@@ -71,7 +71,7 @@ services:
Ibexa\Core\Limitation\SiteAccessLimitationType:
class: Ibexa\Core\Limitation\SiteAccessLimitationType
- arguments: ['@Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessService']
+ arguments: ['@Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface']
tags:
- {name: ibexa.permissions.limitation_type, alias: SiteAccess}
diff --git a/tests/bundle/Core/DependencyInjection/Compiler/ChainRoutingPassTest.php b/tests/bundle/Core/DependencyInjection/Compiler/ChainRoutingPassTest.php
index a97122ffd3..77c6d26888 100644
--- a/tests/bundle/Core/DependencyInjection/Compiler/ChainRoutingPassTest.php
+++ b/tests/bundle/Core/DependencyInjection/Compiler/ChainRoutingPassTest.php
@@ -9,8 +9,8 @@
use Ibexa\Bundle\Core\DependencyInjection\Compiler\ChainRoutingPass;
use Ibexa\Core\MVC\Symfony\Routing\ChainRouter;
-use Ibexa\Core\MVC\Symfony\SiteAccess;
use Ibexa\Core\MVC\Symfony\SiteAccess\Router;
+use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface;
use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractCompilerPassTestCase;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
@@ -74,7 +74,7 @@ public function testAddRouterWithDefaultRouter($declaredPriority, $expectedPrior
{
$defaultRouter = new Definition();
$this->setDefinition('router.default', $defaultRouter);
- $this->setDefinition(SiteAccess::class, new Definition());
+ $this->setDefinition(SiteAccessServiceInterface::class, new Definition());
$this->setDefinition('ibexa.config.resolver', new Definition());
$this->setDefinition(Router::class, new Definition());
@@ -90,10 +90,10 @@ public function testAddRouterWithDefaultRouter($declaredPriority, $expectedPrior
$this->compile();
// Assertion for default router
- $this->assertContainerBuilderHasServiceDefinitionWithMethodCall(
+ $this->assertContainerBuilderHasServiceDefinitionWithArgument(
'router.default',
- 'setSiteAccess',
- [new Reference(SiteAccess::class)]
+ '$siteAccessService',
+ new Reference(SiteAccessServiceInterface::class)
);
$this->assertContainerBuilderHasServiceDefinitionWithMethodCall(
'router.default',
diff --git a/tests/bundle/Core/DependencyInjection/Compiler/FragmentPassTest.php b/tests/bundle/Core/DependencyInjection/Compiler/FragmentPassTest.php
index 6616520948..2dc18c5a98 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,11 @@ public function testProcess()
$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 +81,11 @@ public function testProcess()
$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 +93,11 @@ public function testProcess()
$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 511a6547db..d947d01f6c 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\TestCase;
use Symfony\Component\Console\Command\Command;
@@ -19,17 +20,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,12 @@ 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 = $this->createMock(Command::class);
@@ -71,8 +69,8 @@ public function testInvalidSiteAccessDev()
$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 +82,8 @@ public function testInvalidSiteAccessProd()
$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 +92,22 @@ public function testInvalidSiteAccessProd()
public function testValidSiteAccess()
{
- $this->dispatcher->expects(self::once())
- ->method('dispatch');
+ $this->siteAccessService->expects(self::once())
+ ->method('changeSiteAccess')
+ ->with(self::equalTo(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()
{
- $this->dispatcher->expects(self::once())
- ->method('dispatch');
+ $this->siteAccessService->expects(self::once())
+ ->method('changeSiteAccess')
+ ->with(self::equalTo(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 eda709b056..907cd26546 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\MockObject\MockObject;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -25,10 +26,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
@@ -39,8 +43,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');
}
@@ -56,13 +61,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');
}
@@ -137,11 +143,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 cd3a45a3a3..58410d3962 100644
--- a/tests/bundle/Core/Routing/DefaultRouterTest.php
+++ b/tests/bundle/Core/Routing/DefaultRouterTest.php
@@ -12,6 +12,7 @@
use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface;
use Ibexa\Core\MVC\Symfony\Routing\SimplifiedRequest;
use Ibexa\Core\MVC\Symfony\SiteAccess;
+use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface;
use PHPUnit\Framework\TestCase;
use ReflectionObject;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -52,12 +53,12 @@ protected function getRouterClass(): string
*
* @return \PHPUnit\Framework\MockObject\MockObject&\Ibexa\Bundle\Core\Routing\DefaultRouter
*/
- protected function generateRouter(array $mockedMethods = [])
+ protected function generateRouter(array $mockedMethods = [], ?SiteAccessServiceInterface $siteAccessService = null)
{
/** @var \PHPUnit\Framework\MockObject\MockObject&\Ibexa\Bundle\Core\Routing\DefaultRouter $router */
$router = $this
->getMockBuilder($this->getRouterClass())
- ->setConstructorArgs([$this->container, 'foo', [], $this->requestContext])
+ ->setConstructorArgs([$this->container, 'foo', [], $this->requestContext, null, null, null, $siteAccessService])
->setMethods(array_merge($mockedMethods))
->getMock();
$router->setConfigResolver($this->configResolver);
@@ -170,8 +171,10 @@ public function testGenerateWithSiteAccess($urlGenerated, $relevantUri, $expecte
->with($routeName)
->willReturn($urlGenerated);
+ $siteAccessService = $this->createMock(SiteAccessServiceInterface::class);
+
/** @var \Ibexa\Bundle\Core\Routing\DefaultRouter&\PHPUnit\Framework\MockObject\MockObject $router */
- $router = $this->generateRouter(['getGenerator']);
+ $router = $this->generateRouter(['getGenerator'], $siteAccessService);
$router
->expects(self::any())
->method('getGenerator')
@@ -198,7 +201,7 @@ public function testGenerateWithSiteAccess($urlGenerated, $relevantUri, $expecte
}
$sa = new SiteAccess($saName, 'test', $matcher);
- $router->setSiteAccess($sa);
+ $siteAccessService->method('getCurrent')->willReturn($sa);
$requestContext = new RequestContext();
$urlComponents = parse_url($urlGenerated);
@@ -272,9 +275,14 @@ public function testGenerateReverseSiteAccessMatch()
->method('setContext')
->with($this->requestContext);
- $router = new DefaultRouter($this->container, 'foo', [], $this->requestContext);
+ $router = new DefaultRouter(
+ $this->container,
+ 'foo',
+ [],
+ $this->requestContext,
+ siteAccessService: $this->getSiteAccessService(new SiteAccess('test', 'test', $this->createMock(Matcher::class)))
+ );
$router->setConfigResolver($this->configResolver);
- $router->setSiteAccess(new SiteAccess('test', 'test', $this->createMock(Matcher::class)));
$router->setSiteAccessRouter($siteAccessRouter);
$refRouter = new ReflectionObject($router);
$refGenerator = $refRouter->getProperty('generator');
@@ -342,4 +350,15 @@ private function getExpectedRequestContext($uri)
return $requestContext;
}
+
+ /**
+ * @return \Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface|\PHPUnit\Framework\MockObject\MockObject
+ */
+ private function getSiteAccessService(SiteAccess $siteAccess)
+ {
+ $siteAccessService = $this->createMock(SiteAccessServiceInterface::class);
+ $siteAccessService->method('getCurrent')->willReturn($siteAccess);
+
+ return $siteAccessService;
+ }
}
diff --git a/tests/bundle/Core/SiteAccess/Config/IOConfigResolverTest.php b/tests/bundle/Core/SiteAccess/Config/IOConfigResolverTest.php
index cf6b8a1586..b8d5416121 100644
--- a/tests/bundle/Core/SiteAccess/Config/IOConfigResolverTest.php
+++ b/tests/bundle/Core/SiteAccess/Config/IOConfigResolverTest.php
@@ -12,7 +12,7 @@
use Ibexa\Bundle\Core\SiteAccess\Config\IOConfigResolver;
use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface;
use Ibexa\Core\MVC\Symfony\SiteAccess;
-use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessService;
+use Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface;
use PHPUnit\Framework\TestCase;
/**
@@ -25,14 +25,14 @@ class IOConfigResolverTest extends TestCase
/** @var \Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface|\PHPUnit\Framework\MockObject\MockObject */
private $configResolver;
- /** @var \Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessService|\PHPUnit\Framework\MockObject\MockObject */
+ /** @var \Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface|\PHPUnit\Framework\MockObject\MockObject */
private $siteAccessService;
protected function setUp(): void
{
parent::setUp();
$this->configResolver = $this->createMock(ConfigResolverInterface::class);
- $this->siteAccessService = $this->createMock(SiteAccessService::class);
+ $this->siteAccessService = $this->createMock(SiteAccessServiceInterface::class);
}
public function testGetUrlPrefix(): void
diff --git a/tests/bundle/IO/DependencyInjection/IbexaIOExtensionTest.php b/tests/bundle/IO/DependencyInjection/IbexaIOExtensionTest.php
index ea3f5356b2..2f695f2af2 100644
--- a/tests/bundle/IO/DependencyInjection/IbexaIOExtensionTest.php
+++ b/tests/bundle/IO/DependencyInjection/IbexaIOExtensionTest.php
@@ -14,8 +14,14 @@
use Ibexa\Bundle\IO\DependencyInjection\ConfigurationFactory;
use Ibexa\Bundle\IO\DependencyInjection\IbexaIOExtension;
use Ibexa\Core\IO\UrlDecorator\AbsolutePrefix;
+use Ibexa\Core\MVC\Symfony\Event\PostSiteAccessMatchEvent;
+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\EventDispatcher\EventDispatcher;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\Yaml\Yaml;
class IbexaIOExtensionTest extends AbstractExtensionTestCase
@@ -86,6 +92,19 @@ public function testUrlPrefixConfigurationIsUsedToDecorateUrl(): void
);
$this->buildMinimalContainerForUrlPrefixTest();
+ // Simulate SiteAccess matching having happened for the "site" siteaccess, since
+ // ComplexConfigProcessor now resolves the current SiteAccess via SiteAccessService
+ // rather than reading a pre-set property.
+ //
+ // Fetched by concrete class (not SiteAccessServiceInterface): onSiteAccessMatch() is a
+ // subscriber-only method not part of the interface, and SetAllServicesPublicPass only
+ // exposes real service definitions, not the interface alias.
+ $siteAccessService = $this->container->get(SiteAccessService::class);
+ self::assertInstanceOf(SiteAccessService::class, $siteAccessService);
+ $siteAccessService->onSiteAccessMatch(
+ new PostSiteAccessMatchEvent(new SiteAccess('site'), new Request(), HttpKernelInterface::MAIN_REQUEST)
+ );
+
$decorator = $this->container->get(AbsolutePrefix::class);
self::assertEquals(
@@ -101,6 +120,9 @@ 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');
+ // needed by Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessService, pulled in transitively
+ // via the AliasGeneratorDecorator service definition
+ $this->container->register('event_dispatcher', 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..e293fe2755 100644
--- a/tests/integration/Core/Resources/settings/common.yml
+++ b/tests/integration/Core/Resources/settings/common.yml
@@ -106,13 +106,21 @@ services:
Ibexa\Core\MVC\Symfony\SiteAccess:
arguments: ['default', !php/const Ibexa\Core\MVC\Symfony\SiteAccess::MATCHING_TYPE_UNINITIALIZED]
+ # Dedicated SiteAccess used to seed SiteAccessService below: integration tests never dispatch a
+ # real request, so this establishes a default current SiteAccess the same way ConsoleCommandListener
+ # does for CLI (a fresh SiteAccess passed to changeSiteAccess(), no shared/mutated singleton).
+ ibexa.test.default_site_access:
+ class: Ibexa\Core\MVC\Symfony\SiteAccess
+ arguments: ['default']
+
Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessService:
class: Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessService
arguments:
- '@ibexa.siteaccess.provider'
- '@ibexa.config.resolver'
+ - '@Symfony\Component\EventDispatcher\EventDispatcher'
calls:
- - [setSiteAccess, ['@Ibexa\Core\MVC\Symfony\SiteAccess']]
+ - [changeSiteAccess, ['@ibexa.test.default_site_access']]
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 59afe15726..e09903d446 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;
+ /** @var \Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface|\PHPUnit\Framework\MockObject\MockObject */
+ 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()
@@ -47,14 +45,12 @@ public function testChangeConfigScope()
->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,13 +60,11 @@ public function testChangeConfigScope()
public function testRestoreConfigScope()
{
$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()
@@ -80,7 +74,9 @@ public function testRestoreConfigScope()
public function testPreviewActive()
{
$originalSiteAccess = new SiteAccess('foo', 'bar');
- $this->previewHelper->setSiteAccess($originalSiteAccess);
+ $this->siteAccessService
+ ->method('getCurrent')
+ ->willReturn($originalSiteAccess);
self::assertFalse($this->previewHelper->isPreviewActive());
$this->previewHelper->setPreviewActive(true);
@@ -88,7 +84,7 @@ public function testPreviewActive()
$this->previewHelper->setPreviewActive(false);
self::assertFalse($this->previewHelper->isPreviewActive());
- self::assertNotSame($originalSiteAccess, $this->previewHelper->getOriginalSiteAccess());
+ self::assertSame($originalSiteAccess, $this->previewHelper->getOriginalSiteAccess());
}
public function testPreviewedContent()
diff --git a/tests/lib/MVC/Symfony/Routing/GeneratorTest.php b/tests/lib/MVC/Symfony/Routing/GeneratorTest.php
index 51fc9a6c9e..dcf4248323 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\TestCase;
@@ -28,12 +29,16 @@ 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->getMockForAbstractClass(Generator::class, [$this->siteAccessService]);
$this->generator->setSiteAccessRouter($this->siteAccessRouter);
$this->generator->setLogger($this->logger);
}
@@ -59,7 +64,7 @@ public function generateProvider()
public function testSimpleGenerate($urlResource, array $parameters, $referenceType)
{
$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);
@@ -92,7 +97,7 @@ public function testSimpleGenerate($urlResource, array $parameters, $referenceTy
public function testGenerateWithSiteAccessNoReverseMatch($urlResource, array $parameters, $referenceType)
{
$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);
diff --git a/tests/lib/MVC/Symfony/Routing/UrlAliasGeneratorTest.php b/tests/lib/MVC/Symfony/Routing/UrlAliasGeneratorTest.php
index 845f3bdcca..463e9b7994 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;
@@ -51,6 +52,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();
@@ -58,6 +62,7 @@ protected function setUp(): void
$this->logger = $this->createMock(LoggerInterface::class);
$this->siteAccessRouter = $this->createMock(SiteAccessRouterInterface::class);
$this->configResolver = $this->createMock(ConfigResolverInterface::class);
+ $this->siteAccessService = $this->createMock(SiteAccessServiceInterface::class);
$repositoryClass = Repository::class;
$this->repository = $repository = $this
->getMockBuilder($repositoryClass)
@@ -94,6 +99,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', $this->createMock(SiteAccess\URILexer::class)));
+ $this->siteAccessService->method('getCurrent')->willReturn(new SiteAccess('test', 'fake', $this->createMock(SiteAccess\URILexer::class)));
self::assertSame($expected, $this->urlAliasGenerator->doGenerate($location, $parameters));
}
@@ -274,7 +280,7 @@ static function ($location) use ($treeRootUrlAlias) {
)
);
- $this->urlAliasGenerator->setSiteAccess(new SiteAccess('test', 'fake', $this->createMock(SiteAccess\URILexer::class)));
+ $this->siteAccessService->method('getCurrent')->willReturn(new SiteAccess('test', 'fake', $this->createMock(SiteAccess\URILexer::class)));
self::assertSame($expected, $this->urlAliasGenerator->doGenerate($location, $parameters));
}
@@ -385,7 +391,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 52ebd9be49..aaffaee777 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;
@@ -77,6 +78,7 @@ protected function setUp(): void
$repository,
$this->createMock(RouterInterface::class),
$this->createMock(ConfigResolverInterface::class),
+ $this->createMock(SiteAccessServiceInterface::class),
]
)
->getMock();
diff --git a/tests/lib/MVC/Symfony/Security/HttpUtilsTest.php b/tests/lib/MVC/Symfony/Security/HttpUtilsTest.php
index d196b24bd0..efb5d8fa3e 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\TestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
@@ -21,8 +22,7 @@ class HttpUtilsTest extends TestCase
public function testGenerateUriStandard($uri, $isUriRouteName, $expected)
{
$urlGenerator = $this->createMock(UrlGeneratorInterface::class);
- $httpUtils = new HttpUtils($urlGenerator);
- $httpUtils->setSiteAccess(new SiteAccess('test'));
+ $httpUtils = new HttpUtils($urlGenerator, siteAccessService: $this->getSiteAccessService(new SiteAccess('test')));
$request = Request::create('http://ezpublish.dev/');
$request->attributes->set('siteaccess', new SiteAccess('test'));
$requestAttributes = ['foo' => 'bar', 'some' => 'thing'];
@@ -67,8 +67,7 @@ public function testGenerateUri($uri, $isUriRouteName, $siteAccessUri, $expected
}
$urlGenerator = $this->createMock(UrlGeneratorInterface::class);
- $httpUtils = new HttpUtils($urlGenerator);
- $httpUtils->setSiteAccess($siteAccess);
+ $httpUtils = new HttpUtils($urlGenerator, siteAccessService: $this->getSiteAccessService($siteAccess));
$request = Request::create('http://ezpublish.dev/');
$request->attributes->set('siteaccess', $siteAccess);
$requestAttributes = ['foo' => 'bar', 'some' => 'thing'];
@@ -99,8 +98,7 @@ public function generateUriProvider()
public function testCheckRequestPathStandard()
{
- $httpUtils = new HttpUtils();
- $httpUtils->setSiteAccess(new SiteAccess('test'));
+ $httpUtils = new HttpUtils(siteAccessService: $this->getSiteAccessService(new SiteAccess('test')));
$request = Request::create('http://ezpublish.dev/foo/bar');
self::assertTrue($httpUtils->checkRequestPath($request, '/foo/bar'));
}
@@ -121,8 +119,7 @@ public function testCheckRequestPath($path, $siteAccessUri, $requestUri, $expect
$siteAccess->matcher = $matcher;
}
- $httpUtils = new HttpUtils();
- $httpUtils->setSiteAccess($siteAccess);
+ $httpUtils = new HttpUtils(siteAccessService: $this->getSiteAccessService($siteAccess));
$request = Request::create($requestUri);
self::assertSame($expected, $httpUtils->checkRequestPath($request, $path));
}
@@ -138,4 +135,15 @@ public function checkRequestPathProvider()
['/foo/bar', '/blabla', 'http://ezpublish.dev/blabla/foo/bar', true],
];
}
+
+ /**
+ * @return \Ibexa\Core\MVC\Symfony\SiteAccess\SiteAccessServiceInterface|\PHPUnit\Framework\MockObject\MockObject
+ */
+ private function getSiteAccessService(SiteAccess $siteAccess)
+ {
+ $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 7ef230d7ee..821a6fae15 100644
--- a/tests/lib/MVC/Symfony/SiteAccess/SiteAccessServiceTest.php
+++ b/tests/lib/MVC/Symfony/SiteAccess/SiteAccessServiceTest.php
@@ -11,11 +11,18 @@
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 PHPUnit\Framework\TestCase;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
class SiteAccessServiceTest extends TestCase
{
@@ -29,6 +36,9 @@ class SiteAccessServiceTest extends TestCase
/** @var \Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface|\PHPUnit\Framework\MockObject\MockObject */
private $configResolver;
+ /** @var \Symfony\Component\EventDispatcher\EventDispatcherInterface|\PHPUnit\Framework\MockObject\MockObject */
+ private $eventDispatcher;
+
/** @var \Ibexa\Core\MVC\Symfony\SiteAccess */
private $siteAccess;
@@ -43,28 +53,44 @@ protected function setUp(): void
parent::setUp();
$this->provider = $this->createMock(SiteAccessProviderInterface::class);
$this->configResolver = $this->createMock(ConfigResolverInterface::class);
+ $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class);
$this->siteAccess = new SiteAccess('current');
$this->availableSiteAccesses = $this->getAvailableSitAccesses(['current', 'first_sa', 'second_sa', 'default']);
$this->configResolverParameters = $this->getConfigResolverParameters();
}
- public function testGetCurrentSiteAccess(): void
+ public function testGetCurrentSiteAccessIsNullWhenStackIsEmpty(): void
{
- $service = new SiteAccessService(
- $this->createMock(SiteAccessProviderInterface::class),
- $this->createMock(ConfigResolverInterface::class)
- );
+ $service = $this->createSiteAccessService();
self::assertNull($service->getCurrent());
+ }
- $siteAccess = new SiteAccess('default');
- $service->setSiteAccess($siteAccess);
- self::assertSame($siteAccess, $service->getCurrent());
+ /**
+ * A SiteAccess with an "uninitialized" matcher (see SiteAccess::MATCHING_TYPE_UNINITIALIZED)
+ * is not a real current SiteAccess — getCurrent() must surface it as null.
+ */
+ public function testGetCurrentSiteAccessIsNullWhenTopOfStackIsUninitialized(): void
+ {
+ $service = $this->createSiteAccessService();
+
+ $service->changeSiteAccess(new SiteAccess('default', SiteAccess::MATCHING_TYPE_UNINITIALIZED));
- $service->setSiteAccess(null);
self::assertNull($service->getCurrent());
}
+ public function testGetCurrentSiteAccessAfterMainRequestMatch(): void
+ {
+ $service = $this->createSiteAccessService();
+
+ $siteAccess = new SiteAccess('default');
+ $service->onSiteAccessMatch(
+ new PostSiteAccessMatchEvent($siteAccess, new Request(), HttpKernelInterface::MAIN_REQUEST)
+ );
+
+ self::assertSame($siteAccess, $service->getCurrent());
+ }
+
public function testGetSiteAccess(): void
{
$staticSiteAccessProvider = new StaticSiteAccessProvider(
@@ -73,7 +99,8 @@ public function testGetSiteAccess(): void
);
$service = new SiteAccessService(
$staticSiteAccessProvider,
- $this->createMock(ConfigResolverInterface::class)
+ $this->createMock(ConfigResolverInterface::class),
+ $this->eventDispatcher
);
self::assertEquals(
@@ -90,7 +117,8 @@ public function testGetSiteAccessThrowsNotFoundException(): void
);
$service = new SiteAccessService(
$staticSiteAccessProvider,
- $this->createMock(ConfigResolverInterface::class)
+ $this->createMock(ConfigResolverInterface::class),
+ $this->eventDispatcher
);
$this->expectException(NotFoundException::class);
@@ -126,10 +154,159 @@ public function testGetFirstSiteAccessesRelation(): void
);
}
+ /**
+ * changeSiteAccess()/restoreSiteAccess() must behave as a real LIFO stack, and each call
+ * must dispatch a ScopeChangeEvent carrying the relevant SiteAccess under the right event name.
+ */
+ public function testChangeSiteAccessAndRestoreSiteAccessBehaveAsLifoStack(): void
+ {
+ $service = $this->createSiteAccessService();
+
+ $original = new SiteAccess('original');
+ $service->onSiteAccessMatch(
+ new PostSiteAccessMatchEvent($original, new Request(), HttpKernelInterface::MAIN_REQUEST)
+ );
+
+ $first = new SiteAccess('first_change');
+ $second = new SiteAccess('second_change');
+
+ $this->eventDispatcher
+ ->expects(self::exactly(4))
+ ->method('dispatch')
+ ->withConsecutive(
+ [self::equalTo(new ScopeChangeEvent($first)), MVCEvents::CONFIG_SCOPE_CHANGE],
+ [self::equalTo(new ScopeChangeEvent($second)), MVCEvents::CONFIG_SCOPE_CHANGE],
+ [self::equalTo(new ScopeChangeEvent($first)), MVCEvents::CONFIG_SCOPE_RESTORE],
+ [self::equalTo(new ScopeChangeEvent($original)), MVCEvents::CONFIG_SCOPE_RESTORE]
+ );
+
+ self::assertSame($first, $service->changeSiteAccess($first));
+ self::assertSame($first, $service->getCurrent());
+
+ self::assertSame($second, $service->changeSiteAccess($second));
+ self::assertSame($second, $service->getCurrent());
+
+ self::assertSame($first, $service->restoreSiteAccess());
+ self::assertSame($first, $service->getCurrent());
+
+ self::assertSame($original, $service->restoreSiteAccess());
+ self::assertSame($original, $service->getCurrent());
+ }
+
+ public function testOnSiteAccessMatchWithMainRequestResetsTheStack(): void
+ {
+ $service = $this->createSiteAccessService();
+
+ $service->onSiteAccessMatch(
+ new PostSiteAccessMatchEvent(new SiteAccess('first'), new Request(), HttpKernelInterface::MAIN_REQUEST)
+ );
+ $service->onSiteAccessMatch(
+ new PostSiteAccessMatchEvent(new SiteAccess('sub'), new Request(), HttpKernelInterface::SUB_REQUEST)
+ );
+
+ $second = new SiteAccess('second');
+ $service->onSiteAccessMatch(
+ new PostSiteAccessMatchEvent($second, new Request(), HttpKernelInterface::MAIN_REQUEST)
+ );
+
+ self::assertSame($second, $service->getCurrent());
+
+ // The stack was fully reset by the second MAIN_REQUEST match: an unbalanced
+ // finish-request must not be able to reveal any of the previous entries.
+ $service->onKernelFinishRequest($this->createFinishRequestEvent());
+ self::assertSame($second, $service->getCurrent());
+ }
+
+ public function testOnSiteAccessMatchWithSubRequestPushesWithoutResetting(): void
+ {
+ $service = $this->createSiteAccessService();
+
+ $main = new SiteAccess('main');
+ $service->onSiteAccessMatch(
+ new PostSiteAccessMatchEvent($main, new Request(), HttpKernelInterface::MAIN_REQUEST)
+ );
+
+ $sub = new SiteAccess('sub');
+ $service->onSiteAccessMatch(
+ new PostSiteAccessMatchEvent($sub, new Request(), HttpKernelInterface::SUB_REQUEST)
+ );
+
+ self::assertSame($sub, $service->getCurrent());
+
+ $service->onKernelFinishRequest($this->createFinishRequestEvent());
+ self::assertSame($main, $service->getCurrent());
+ }
+
+ public function testOnKernelFinishRequestNeverPopsBelowOneRemainingEntry(): void
+ {
+ $service = $this->createSiteAccessService();
+
+ $original = new SiteAccess('original');
+ $service->onSiteAccessMatch(
+ new PostSiteAccessMatchEvent($original, new Request(), HttpKernelInterface::MAIN_REQUEST)
+ );
+
+ // Simulate an extra, unbalanced finish-request event.
+ $service->onKernelFinishRequest($this->createFinishRequestEvent());
+
+ self::assertSame($original, $service->getCurrent());
+ }
+
+ /**
+ * Sub-request nesting scenario: a preview scope change followed by a sub-request (e.g. a
+ * fragment/ESI render) must unwind back to the preview scope once the sub-request finishes,
+ * and restoreSiteAccess() must then bring back the original SiteAccess.
+ */
+ public function testSubRequestNestingScenario(): void
+ {
+ $service = $this->createSiteAccessService();
+
+ $original = new SiteAccess('original');
+ $service->onSiteAccessMatch(
+ new PostSiteAccessMatchEvent($original, new Request(), HttpKernelInterface::MAIN_REQUEST)
+ );
+
+ $preview = new SiteAccess('preview');
+ $service->changeSiteAccess($preview);
+ self::assertSame($preview, $service->getCurrent());
+
+ $subSiteAccess = new SiteAccess('sub');
+ $service->onSiteAccessMatch(
+ new PostSiteAccessMatchEvent($subSiteAccess, new Request(), HttpKernelInterface::SUB_REQUEST)
+ );
+ self::assertSame($subSiteAccess, $service->getCurrent());
+
+ $service->onKernelFinishRequest($this->createFinishRequestEvent());
+ self::assertSame($preview, $service->getCurrent());
+
+ self::assertSame($original, $service->restoreSiteAccess());
+ self::assertSame($original, $service->getCurrent());
+ }
+
+ private function createFinishRequestEvent(): FinishRequestEvent
+ {
+ return new FinishRequestEvent(
+ $this->createMock(HttpKernelInterface::class),
+ new Request(),
+ HttpKernelInterface::MAIN_REQUEST
+ );
+ }
+
+ private function createSiteAccessService(): SiteAccessService
+ {
+ return new SiteAccessService(
+ $this->provider,
+ $this->configResolver,
+ $this->eventDispatcher
+ );
+ }
+
private function getSiteAccessService(): SiteAccessService
{
- $siteAccessService = new SiteAccessService($this->provider, $this->configResolver);
- $siteAccessService->setSiteAccess($this->siteAccess);
+ $siteAccessService = $this->createSiteAccessService();
+ $siteAccessService->onSiteAccessMatch(
+ new PostSiteAccessMatchEvent($this->siteAccess, new Request(), HttpKernelInterface::MAIN_REQUEST)
+ );
return $siteAccessService;
}