diff --git a/changelog/_unreleased/2024-06-21-add-new-context-request-attr-on-customer-login.md b/changelog/_unreleased/2024-06-21-add-new-context-request-attr-on-customer-login.md new file mode 100644 index 00000000000..572714aef18 --- /dev/null +++ b/changelog/_unreleased/2024-06-21-add-new-context-request-attr-on-customer-login.md @@ -0,0 +1,8 @@ +--- +title: Add new context request attribute on customer login +issue: NEXT-36874 +--- +# Core +* Added listener on `CustomerLoginEvent` to `\Shopware\Core\Framework\Adapter\Cache\Http\CacheResponseSubscriber` in order to save the new Context to the request, which is later used in the subscriber to generate the cache state. +* Removed manually adding the context to the request in the `\Shopware\Storefront\Controller\AuthController` and replaced it with the listener, that should handle all cases (e.g. login, double-opt-in, etc). + diff --git a/src/Core/Framework/Adapter/Cache/Http/CacheResponseSubscriber.php b/src/Core/Framework/Adapter/Cache/Http/CacheResponseSubscriber.php index 2b3ac55d703..d138fbe60f6 100644 --- a/src/Core/Framework/Adapter/Cache/Http/CacheResponseSubscriber.php +++ b/src/Core/Framework/Adapter/Cache/Http/CacheResponseSubscriber.php @@ -4,6 +4,7 @@ use Shopware\Core\Checkout\Cart\Cart; use Shopware\Core\Checkout\Cart\SalesChannel\CartService; +use Shopware\Core\Checkout\Customer\Event\CustomerLoginEvent; use Shopware\Core\Framework\Adapter\Cache\CacheStateSubscriber; use Shopware\Core\Framework\Event\BeforeSendResponseEvent; use Shopware\Core\Framework\Log\Package; @@ -14,6 +15,7 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\Event\ResponseEvent; @@ -47,6 +49,7 @@ public function __construct( private readonly int $defaultTtl, private readonly bool $httpCacheEnabled, private readonly MaintenanceModeResolver $maintenanceResolver, + private readonly RequestStack $requestStack, private readonly bool $reverseProxyEnabled, private readonly ?string $staleWhileRevalidate, private readonly ?string $staleIfError @@ -65,6 +68,7 @@ public static function getSubscribedEvents(): array ['setResponseCacheHeader', 1500], ], BeforeSendResponseEvent::class => 'updateCacheControlForBrowser', + CustomerLoginEvent::class => 'onCustomerLogin', ]; } @@ -217,6 +221,16 @@ private function hasInvalidationState(array $cacheStates, array $states): bool return false; } + public function onCustomerLogin(CustomerLoginEvent $event): void + { + $request = $this->requestStack->getCurrentRequest(); + if (!$request) { + return; + } + + $request->attributes->set(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT, $event->getSalesChannelContext()); + } + private function buildCacheHash(SalesChannelContext $context): string { return md5(json_encode([ diff --git a/src/Core/Framework/DependencyInjection/cache.xml b/src/Core/Framework/DependencyInjection/cache.xml index bdbd335d117..66a14c505d9 100644 --- a/src/Core/Framework/DependencyInjection/cache.xml +++ b/src/Core/Framework/DependencyInjection/cache.xml @@ -160,6 +160,7 @@ %shopware.http.cache.default_ttl% %shopware.http.cache.enabled% + %shopware.http_cache.reverse_proxy.enabled% %shopware.http_cache.stale_while_revalidate% %shopware.http_cache.stale_if_error% diff --git a/src/Storefront/Controller/AuthController.php b/src/Storefront/Controller/AuthController.php index c4aed685a66..75a685bf7dd 100644 --- a/src/Storefront/Controller/AuthController.php +++ b/src/Storefront/Controller/AuthController.php @@ -20,9 +20,6 @@ use Shopware\Core\Framework\Validation\DataBag\DataBag; use Shopware\Core\Framework\Validation\DataBag\RequestDataBag; use Shopware\Core\Framework\Validation\Exception\ConstraintViolationException; -use Shopware\Core\PlatformRequest; -use Shopware\Core\System\SalesChannel\Context\SalesChannelContextServiceInterface; -use Shopware\Core\System\SalesChannel\Context\SalesChannelContextServiceParameters; use Shopware\Core\System\SalesChannel\SalesChannelContext; use Shopware\Storefront\Checkout\Cart\SalesChannel\StorefrontCartFacade; use Shopware\Storefront\Framework\Routing\RequestTransformer; @@ -54,8 +51,7 @@ public function __construct( private readonly AbstractLoginRoute $loginRoute, private readonly AbstractLogoutRoute $logoutRoute, private readonly StorefrontCartFacade $cartFacade, - private readonly AccountRecoverPasswordPageLoader $recoverPasswordPageLoader, - private readonly SalesChannelContextServiceInterface $salesChannelContextService + private readonly AccountRecoverPasswordPageLoader $recoverPasswordPageLoader ) { } @@ -161,20 +157,6 @@ public function login(Request $request, RequestDataBag $data, SalesChannelContex $token = $this->loginRoute->login($data, $context)->getToken(); $cartBeforeNewContext = $this->cartFacade->get($token, $context); - $newContext = $this->salesChannelContextService->get( - new SalesChannelContextServiceParameters( - $context->getSalesChannelId(), - $token, - $context->getLanguageId(), - $context->getCurrencyId(), - $context->getDomainId(), - $context->getContext() - ) - ); - - // Update the sales channel context for CacheResponseSubscriber - $request->attributes->set(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT, $newContext); - if (!empty($token)) { $this->addCartErrors($cartBeforeNewContext); diff --git a/src/Storefront/DependencyInjection/controller.xml b/src/Storefront/DependencyInjection/controller.xml index 687d906814e..82696c2f3f8 100644 --- a/src/Storefront/DependencyInjection/controller.xml +++ b/src/Storefront/DependencyInjection/controller.xml @@ -86,7 +86,6 @@ - diff --git a/tests/integration/Storefront/Controller/AuthControllerTest.php b/tests/integration/Storefront/Controller/AuthControllerTest.php index c986d20543c..c24ba5396ba 100644 --- a/tests/integration/Storefront/Controller/AuthControllerTest.php +++ b/tests/integration/Storefront/Controller/AuthControllerTest.php @@ -704,8 +704,7 @@ private function getAuthController(?AbstractSendPasswordRecoveryMailRoute $sendP $this->getContainer()->get(LoginRoute::class), $this->createMock(AbstractLogoutRoute::class), $this->getContainer()->get(StorefrontCartFacade::class), - $this->getContainer()->get(AccountRecoverPasswordPageLoader::class), - $this->getContainer()->get(SalesChannelContextService::class) + $this->getContainer()->get(AccountRecoverPasswordPageLoader::class) ); $controller->setContainer($this->getContainer()); $controller->setTwig($this->getContainer()->get('twig')); diff --git a/tests/integration/Storefront/Controller/ControllerRateLimiterTest.php b/tests/integration/Storefront/Controller/ControllerRateLimiterTest.php index 27a68cb952b..8e77a1cad97 100644 --- a/tests/integration/Storefront/Controller/ControllerRateLimiterTest.php +++ b/tests/integration/Storefront/Controller/ControllerRateLimiterTest.php @@ -32,7 +32,6 @@ use Shopware\Core\SalesChannelRequest; use Shopware\Core\System\SalesChannel\Context\AbstractSalesChannelContextFactory; use Shopware\Core\System\SalesChannel\Context\SalesChannelContextFactory; -use Shopware\Core\System\SalesChannel\Context\SalesChannelContextService; use Shopware\Core\System\SalesChannel\SalesChannelContext; use Shopware\Storefront\Checkout\Cart\SalesChannel\StorefrontCartFacade; use Shopware\Storefront\Controller\AuthController; @@ -122,8 +121,7 @@ public function testGenerateAccountRecoveryRateLimit(): void $this->getContainer()->get(LoginRoute::class), $this->getContainer()->get(LogoutRoute::class), $this->getContainer()->get(StorefrontCartFacade::class), - $this->getContainer()->get(AccountRecoverPasswordPageLoader::class), - $this->getContainer()->get(SalesChannelContextService::class) + $this->getContainer()->get(AccountRecoverPasswordPageLoader::class) ); $controller->setContainer($this->getContainer()); @@ -154,8 +152,7 @@ public function testAuthControllerGuestLoginShowsRateLimit(): void $this->createMock(LoginRoute::class), $this->createMock(AbstractLogoutRoute::class), $this->getContainer()->get(StorefrontCartFacade::class), - $this->getContainer()->get(AccountRecoverPasswordPageLoader::class), - $this->getContainer()->get(SalesChannelContextService::class) + $this->getContainer()->get(AccountRecoverPasswordPageLoader::class) ); $controller->setContainer($this->getContainer()); $controller->setTwig($this->getContainer()->get('twig')); @@ -192,8 +189,7 @@ public function testAuthControllerLoginShowsRateLimit(): void $loginRoute, $this->createMock(AbstractLogoutRoute::class), $this->getContainer()->get(StorefrontCartFacade::class), - $this->getContainer()->get(AccountRecoverPasswordPageLoader::class), - $this->getContainer()->get(SalesChannelContextService::class) + $this->getContainer()->get(AccountRecoverPasswordPageLoader::class) ); $controller->setContainer($this->getContainer()); diff --git a/tests/integration/Storefront/Controller/RegisterControllerTest.php b/tests/integration/Storefront/Controller/RegisterControllerTest.php index 2b42e133ea5..ddcf8dadc8e 100644 --- a/tests/integration/Storefront/Controller/RegisterControllerTest.php +++ b/tests/integration/Storefront/Controller/RegisterControllerTest.php @@ -25,6 +25,7 @@ use Shopware\Core\Framework\Uuid\Uuid; use Shopware\Core\Framework\Validation\DataBag\QueryDataBag; use Shopware\Core\Framework\Validation\DataBag\RequestDataBag; +use Shopware\Core\PlatformRequest; use Shopware\Core\SalesChannelRequest; use Shopware\Core\System\SalesChannel\Context\SalesChannelContextFactory; use Shopware\Core\System\SalesChannel\SalesChannelContext; @@ -55,10 +56,7 @@ class RegisterControllerTest extends TestCase use MailTemplateTestBehaviour; use StorefrontControllerTestBehaviour; - /** - * @var SalesChannelContext - */ - private $salesChannelContext; + private SalesChannelContext $salesChannelContext; protected function setUp(): void { @@ -115,6 +113,7 @@ public function testGuestRegisterWithRequirePasswordConfirmation(): void static::assertEquals(200, $response->getStatusCode()); static::assertCount(1, $customers); + static::assertTrue($request->attributes->has(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT)); } public function testGuestRegister(): void @@ -130,6 +129,7 @@ public function testGuestRegister(): void static::assertEquals(200, $response->getStatusCode()); static::assertCount(1, $customers); + static::assertTrue($request->attributes->has(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT)); } public function testRegisterWithDoubleOptIn(): void @@ -165,6 +165,8 @@ public function testRegisterWithDoubleOptIn(): void $response = $registerController->register($request, $data, $this->salesChannelContext); + static::assertFalse($request->attributes->has(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT)); + static::assertEquals(302, $response->getStatusCode()); static::assertInstanceOf(RedirectResponse::class, $response); static::assertEquals('/account/register', $response->getTargetUrl()); @@ -217,6 +219,8 @@ public function testRegisterWithDoubleOptInDomainChanged(): void $response = $registerController->register($request, $data, $this->salesChannelContext); + static::assertFalse($request->attributes->has(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT)); + static::assertEquals(302, $response->getStatusCode()); static::assertInstanceOf(RedirectResponse::class, $response); static::assertEquals('/account/register', $response->getTargetUrl()); @@ -275,6 +279,8 @@ public function testConfirmRegisterWithRedirectTo(): void $registerController->register($request, $data, $this->salesChannelContext); + static::assertFalse($request->attributes->has(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT)); + static::assertInstanceOf(CustomerDoubleOptInRegistrationEvent::class, $event); $customer = $customerRepository->search(new Criteria([$event->getCustomer()->getId()]), $this->salesChannelContext->getContext())->getEntities(); @@ -285,6 +291,8 @@ public function testConfirmRegisterWithRedirectTo(): void $response = $registerController->confirmRegistration($this->salesChannelContext, $queryData); + static::assertTrue($request->attributes->has(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT)); + static::assertEquals(302, $response->getStatusCode()); static::assertInstanceOf(RedirectResponse::class, $response); static::assertEquals('/checkout/confirm', $response->getTargetUrl()); diff --git a/tests/unit/Core/Framework/Adapter/Cache/Http/CacheResponseSubscriberTest.php b/tests/unit/Core/Framework/Adapter/Cache/Http/CacheResponseSubscriberTest.php index 76a0aeb5cb0..35ee294384b 100644 --- a/tests/unit/Core/Framework/Adapter/Cache/Http/CacheResponseSubscriberTest.php +++ b/tests/unit/Core/Framework/Adapter/Cache/Http/CacheResponseSubscriberTest.php @@ -9,6 +9,7 @@ use Shopware\Core\Checkout\Cart\LineItem\LineItem; use Shopware\Core\Checkout\Cart\SalesChannel\CartService; use Shopware\Core\Checkout\Customer\CustomerEntity; +use Shopware\Core\Checkout\Customer\Event\CustomerLoginEvent; use Shopware\Core\Defaults; use Shopware\Core\Framework\Adapter\Cache\Http\CacheResponseSubscriber; use Shopware\Core\Framework\Event\BeforeSendResponseEvent; @@ -52,6 +53,7 @@ public function testHasEvents(): void ['setResponseCacheHeader', 1500], ], BeforeSendResponseEvent::class => 'updateCacheControlForBrowser', + CustomerLoginEvent::class => 'onCustomerLogin', ]; static::assertSame($expected, CacheResponseSubscriber::getSubscribedEvents()); @@ -64,6 +66,7 @@ public function testNoHeadersAreSetIfCacheIsDisabled(): void 100, false, new MaintenanceModeResolver(new EventDispatcher()), + new RequestStack(), false, null, null @@ -98,6 +101,7 @@ public function testNoAutoCacheControlHeader(): void 100, true, new MaintenanceModeResolver(new EventDispatcher()), + new RequestStack(), false, null, null @@ -127,6 +131,7 @@ public function testNoAutoCacheControlHeaderCacheDisabled(): void 100, false, new MaintenanceModeResolver(new EventDispatcher()), + new RequestStack(), false, null, null @@ -156,6 +161,7 @@ public function testNoAutoCacheControlHeaderNoHttpCacheRoute(): void 100, true, new MaintenanceModeResolver(new EventDispatcher()), + new RequestStack(), false, null, null @@ -189,6 +195,7 @@ public function testGenerateCashHashWithItemsInCart(?CustomerEntity $customer, C 100, true, new MaintenanceModeResolver(new EventDispatcher()), + new RequestStack(), false, null, null @@ -270,6 +277,7 @@ public function testMaintenanceRequest(bool $active, array $whitelist, bool $sho 100, true, new MaintenanceModeResolver(new EventDispatcher()), + $requestStack, false, null, null @@ -308,6 +316,32 @@ public function testMaintenanceRequest(bool $active, array $whitelist, bool $sho $subscriber->setResponseCache($event); } + public function testOnCustomerLogin(): void + { + $requestStack = new RequestStack(); + + $subscriber = new CacheResponseSubscriber( + $this->createMock(CartService::class), + 100, + true, + new MaintenanceModeResolver(new EventDispatcher()), + $requestStack, + false, + null, + null + ); + + $salesChannelContext = $this->createMock(SalesChannelContext::class); + + $request = new Request(); + $requestStack->push($request); + + $event = new CustomerLoginEvent($salesChannelContext, new CustomerEntity(), 'token'); + $subscriber->onCustomerLogin($event); + + static::assertSame($salesChannelContext, $request->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT)); + } + /** * @return array> */ @@ -352,6 +386,7 @@ public function testResponseHeaders(bool $reverseProxyEnabled, ?string $beforeHe 100, true, new MaintenanceModeResolver(new EventDispatcher()), + new RequestStack(), $reverseProxyEnabled, null, null @@ -422,6 +457,7 @@ public function testAddHttpCacheToCoreRoutes(): void 1, true, new MaintenanceModeResolver(new EventDispatcher()), + new RequestStack(), false, null, null @@ -442,6 +478,7 @@ public function testCurrencyChange(?string $currencyId): void 100, true, new MaintenanceModeResolver(new EventDispatcher()), + new RequestStack(), false, null, null @@ -485,6 +522,7 @@ public function testStatesGetDeletedOnEmptyState(): void 100, true, new MaintenanceModeResolver(new EventDispatcher()), + new RequestStack(), false, null, null @@ -517,6 +555,7 @@ public function testNotCacheablePages(Request $request): void 100, true, new MaintenanceModeResolver(new EventDispatcher()), + new RequestStack(), false, null, null @@ -561,6 +600,7 @@ public function testNoCachingWhenInvalidateStateMatches(): void 100, true, new MaintenanceModeResolver(new EventDispatcher()), + new RequestStack(), false, null, null @@ -597,6 +637,7 @@ public function testMakeGetsCached(): void 100, true, new MaintenanceModeResolver(new EventDispatcher()), + new RequestStack(), false, '5', '6' @@ -681,6 +722,7 @@ public function testSetResponseCacheOnLogin( 100, true, new MaintenanceModeResolver(new EventDispatcher()), + new RequestStack(), false, null, null diff --git a/tests/unit/Storefront/Controller/AuthControllerTest.php b/tests/unit/Storefront/Controller/AuthControllerTest.php index f597b2b4faa..2ad67686c07 100644 --- a/tests/unit/Storefront/Controller/AuthControllerTest.php +++ b/tests/unit/Storefront/Controller/AuthControllerTest.php @@ -5,16 +5,11 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Shopware\Core\Checkout\Customer\CustomerEntity; use Shopware\Core\Checkout\Customer\SalesChannel\AbstractLoginRoute; use Shopware\Core\Checkout\Customer\SalesChannel\AbstractLogoutRoute; use Shopware\Core\Checkout\Customer\SalesChannel\AbstractResetPasswordRoute; use Shopware\Core\Checkout\Customer\SalesChannel\AbstractSendPasswordRecoveryMailRoute; use Shopware\Core\Framework\Validation\DataBag\RequestDataBag; -use Shopware\Core\PlatformRequest; -use Shopware\Core\System\SalesChannel\Context\SalesChannelContextServiceInterface; -use Shopware\Core\System\SalesChannel\ContextTokenResponse; -use Shopware\Core\System\SalesChannel\SalesChannelContext; use Shopware\Core\Test\Generator; use Shopware\Storefront\Checkout\Cart\SalesChannel\StorefrontCartFacade; use Shopware\Storefront\Controller\AuthController; @@ -41,8 +36,6 @@ class AuthControllerTest extends TestCase private MockObject&AbstractLoginRoute $loginRoute; - private MockObject&SalesChannelContextServiceInterface $salesChannelContextService; - protected function setUp(): void { $this->accountLoginPageLoader = $this->createMock(AccountLoginPageLoader::class); @@ -52,7 +45,6 @@ protected function setUp(): void $logoutRoute = $this->createMock(AbstractLogoutRoute::class); $cartFacade = $this->createMock(StorefrontCartFacade::class); $recoverPasswordRoute = $this->createMock(AccountRecoverPasswordPageLoader::class); - $this->salesChannelContextService = $this->createMock(SalesChannelContextServiceInterface::class); $this->controller = new AuthControllerTestClass( $this->accountLoginPageLoader, @@ -62,7 +54,6 @@ protected function setUp(): void $logoutRoute, $cartFacade, $recoverPasswordRoute, - $this->salesChannelContextService, ); $containerBuilder = new ContainerBuilder(); @@ -94,37 +85,6 @@ public function testAccountRegister(): void static::assertInstanceOf(AccountLoginPageLoadedHook::class, $this->controller->calledHook); } - public function testLoginNewContextIsAdded(): void - { - $this->loginRoute - ->method('login') - ->willReturn(new ContextTokenResponse('context_token_response')); - - $newSalesChannelContext = Generator::createSalesChannelContext(); - $this->salesChannelContextService - ->expects(static::once()) - ->method('get') - ->willReturn($newSalesChannelContext); - - $oldSalesChannelContext = Generator::createSalesChannelContext(); - $oldSalesChannelContext->assign(['customer' => null]); - - $request = new Request(); - $request->attributes->set(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT, $oldSalesChannelContext); - - $response = $this->controller->login($request, new RequestDataBag(), $oldSalesChannelContext); - - /** @var SalesChannelContext $newSalesChannelContext */ - $newSalesChannelContext = $request->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT); - static::assertNotSame( - $oldSalesChannelContext, - $newSalesChannelContext, - 'Sales Channel context should have been changed after login to update the states in cache' - ); - static::assertInstanceOf(CustomerEntity::class, $newSalesChannelContext->getCustomer()); - static::assertSame(Response::HTTP_OK, $response->getStatusCode()); - } - public function testGuestLoginPageWithoutRedirectParametersRedirects(): void { $context = Generator::createSalesChannelContext();