This commit is contained in:
Loïc Frémont 2026-05-19 11:06:30 +02:00 committed by GitHub
commit f1e6197dd1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 273 additions and 14 deletions

View file

@ -62,6 +62,7 @@ sylius_core:
shop_register: false shop_register: false
shop_reset_password: false shop_reset_password: false
shop_request_password_reset_token: false shop_request_password_reset_token: false
shop_verify_user: false
sylius_api: sylius_api:
enabled: true enabled: true

View file

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Sylius\Bundle\CoreBundle\Command\Shop\Account;
/**
* @experimental
*/
class SendAccountRegistrationEmail
{
public function __construct(
public readonly string $shopUserEmail,
public readonly string $channelCode,
public readonly string $localeCode,
) {
}
}

View file

@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Sylius\Bundle\CoreBundle\Command\Shop\Account;
/**
* @experimental
*/
class VerifyShopUser
{
public function __construct(
public readonly string $token,
public readonly string $channelCode,
public readonly string $localeCode,
) {
}
}

View file

@ -0,0 +1,51 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Sylius\Bundle\CoreBundle\CommandHandler\Shop\Account;
use Sylius\Bundle\CoreBundle\Command\Shop\Account\SendAccountRegistrationEmail;
use Sylius\Bundle\CoreBundle\Mailer\AccountRegistrationEmailManagerInterface;
use Sylius\Component\Channel\Repository\ChannelRepositoryInterface;
use Sylius\Component\Core\Model\ChannelInterface;
use Sylius\Component\Core\Model\ShopUserInterface;
use Sylius\Component\User\Repository\UserRepositoryInterface;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Webmozart\Assert\Assert;
#[AsMessageHandler]
final readonly class SendAccountRegistrationEmailHandler
{
public function __construct(
private UserRepositoryInterface $shopUserRepository,
private ChannelRepositoryInterface $channelRepository,
private AccountRegistrationEmailManagerInterface $accountRegistrationEmailManager,
) {
}
public function __invoke(SendAccountRegistrationEmail $command): void
{
/** @var ShopUserInterface|null $shopUser */
$shopUser = $this->shopUserRepository->findOneByEmail($command->shopUserEmail);
Assert::notNull($shopUser, sprintf('There is no shop user with %s email', $command->shopUserEmail));
/** @var ChannelInterface|null $channel */
$channel = $this->channelRepository->findOneByCode($command->channelCode);
Assert::notNull($channel, sprintf('There is no channel with %s email', $command->channelCode));
if ($channel->isAccountVerificationRequired() && !$shopUser->isEnabled()) {
return;
}
$this->accountRegistrationEmailManager->sendAccountRegistrationEmail($shopUser, $channel, $command->localeCode);
}
}

View file

@ -0,0 +1,52 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Sylius\Bundle\CoreBundle\CommandHandler\Shop\Account;
use Sylius\Bundle\CoreBundle\Command\Shop\Account\SendAccountRegistrationEmail;
use Sylius\Bundle\CoreBundle\Command\Shop\Account\VerifyShopUser;
use Sylius\Component\Core\Model\ShopUserInterface;
use Sylius\Resource\Doctrine\Persistence\RepositoryInterface;
use Symfony\Component\Clock\ClockInterface;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Messenger\Stamp\DispatchAfterCurrentBusStamp;
use Webmozart\Assert\Assert;
#[AsMessageHandler]
final readonly class VerifyShopUserHandler
{
/** @param RepositoryInterface<ShopUserInterface> $shopUserRepository */
public function __construct(
private RepositoryInterface $shopUserRepository,
private ClockInterface $clock,
private MessageBusInterface $commandBus,
) {
}
public function __invoke(VerifyShopUser $command): void
{
/** @var ShopUserInterface|null $user */
$user = $this->shopUserRepository->findOneBy(['emailVerificationToken' => $command->token]);
Assert::notNull($user, sprintf('There is no shop user with %s email verification token', $command->token));
$user->setVerifiedAt($this->clock->now());
$user->setEmailVerificationToken(null);
$user->enable();
$this->commandBus->dispatch(
new SendAccountRegistrationEmail($user->getEmail(), $command->channelCode, $command->localeCode),
[new DispatchAfterCurrentBusStamp()],
);
}
}

View file

@ -19,6 +19,8 @@ use Sylius\Bundle\CoreBundle\CommandHandler\Admin\Account\SendResetPasswordEmail
use Sylius\Bundle\CoreBundle\CommandHandler\ResendOrderConfirmationEmailHandler; use Sylius\Bundle\CoreBundle\CommandHandler\ResendOrderConfirmationEmailHandler;
use Sylius\Bundle\CoreBundle\CommandHandler\ResendShipmentConfirmationEmailHandler; use Sylius\Bundle\CoreBundle\CommandHandler\ResendShipmentConfirmationEmailHandler;
use Sylius\Bundle\CoreBundle\CommandHandler\Shop\Account\ResetPasswordHandler as ShopResetPasswordHandler; use Sylius\Bundle\CoreBundle\CommandHandler\Shop\Account\ResetPasswordHandler as ShopResetPasswordHandler;
use Sylius\Bundle\CoreBundle\CommandHandler\Shop\Account\SendAccountRegistrationEmailHandler;
use Sylius\Bundle\CoreBundle\CommandHandler\Shop\Account\VerifyShopUserHandler;
use Sylius\Bundle\CoreBundle\CommandHandler\Shop\ChangeShopUserPasswordHandler; use Sylius\Bundle\CoreBundle\CommandHandler\Shop\ChangeShopUserPasswordHandler;
use Sylius\Bundle\CoreBundle\CommandHandler\Shop\Account\RequestResetPasswordEmailHandler as ShopRequestResetPasswordEmailHandler; use Sylius\Bundle\CoreBundle\CommandHandler\Shop\Account\RequestResetPasswordEmailHandler as ShopRequestResetPasswordEmailHandler;
use Sylius\Bundle\CoreBundle\CommandHandler\Shop\Account\SendResetPasswordEmailHandler as ShopSendResetPasswordEmailHandler; use Sylius\Bundle\CoreBundle\CommandHandler\Shop\Account\SendResetPasswordEmailHandler as ShopSendResetPasswordEmailHandler;
@ -89,6 +91,16 @@ return static function (ContainerConfigurator $container) {
->tag('messenger.message_handler', ['bus' => 'sylius.command_bus']) ->tag('messenger.message_handler', ['bus' => 'sylius.command_bus'])
; ;
$services
->set('sylius.command_handler.shop.account.send_account_registration_email', SendAccountRegistrationEmailHandler::class)
->args([
service('sylius.repository.shop_user'),
service('sylius.repository.channel'),
service('sylius.mailer.account_registration_email_manager'),
])
->tag('messenger.message_handler', ['bus' => 'sylius.command_bus'])
;
$services $services
->set('sylius.command_handler.admin.account.send_reset_password_email', AdminSendResetPasswordEmailHandler::class) ->set('sylius.command_handler.admin.account.send_reset_password_email', AdminSendResetPasswordEmailHandler::class)
->args([ ->args([
@ -107,4 +119,14 @@ return static function (ContainerConfigurator $container) {
]) ])
->tag('messenger.message_handler', ['bus' => 'sylius.command_bus']) ->tag('messenger.message_handler', ['bus' => 'sylius.command_bus'])
; ;
$services
->set('sylius.command_handler.shop.account.verify_shop_user', VerifyShopUserHandler::class)
->args([
service('sylius.repository.shop_user'),
service('clock'),
service('sylius.command_bus'),
])
->tag('messenger.message_handler', ['bus' => 'sylius.command_bus'])
;
}; };

View file

@ -14,6 +14,7 @@ declare(strict_types=1);
namespace Sylius\Bundle\ShopBundle\Controller; namespace Sylius\Bundle\ShopBundle\Controller;
use Doctrine\Persistence\ObjectManager; use Doctrine\Persistence\ObjectManager;
use Sylius\Bundle\CoreBundle\Provider\FlashBagProvider;
use Sylius\Bundle\OrderBundle\Resetter\CartChangesResetterInterface; use Sylius\Bundle\OrderBundle\Resetter\CartChangesResetterInterface;
use Sylius\Bundle\ShopBundle\Form\Type\CartType; use Sylius\Bundle\ShopBundle\Form\Type\CartType;
use Sylius\Component\Order\Context\CartContextInterface; use Sylius\Component\Order\Context\CartContextInterface;
@ -25,8 +26,6 @@ use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\FlashBagAwareSessionInterface;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Routing\RouterInterface;
use Twig\Environment; use Twig\Environment;
use Webmozart\Assert\Assert; use Webmozart\Assert\Assert;
@ -84,10 +83,6 @@ final readonly class CartCheckoutAction
private function addErrorNotification(string $message): void private function addErrorNotification(string $message): void
{ {
$session = $this->requestStack->getSession(); FlashBagProvider::getFlashBag($this->requestStack)->add('error', $message);
Assert::isInstanceOf($session, FlashBagAwareSessionInterface::class);
$session->getFlashBag()->add('error', $message);
} }
} }

View file

@ -37,7 +37,6 @@ final readonly class RequestPasswordResetTokenAction
private RouterInterface $router, private RouterInterface $router,
private FormFactoryInterface $formFactory, private FormFactoryInterface $formFactory,
private MessageBusInterface $messageBus, private MessageBusInterface $messageBus,
private TranslatorInterface $translator,
private RequestStack $requestStack, private RequestStack $requestStack,
) { ) {
} }
@ -70,6 +69,6 @@ final readonly class RequestPasswordResetTokenAction
private function addSuccessNotification(): void private function addSuccessNotification(): void
{ {
FlashBagProvider::getFlashBag($this->requestStack)->add('success', $this->translator->trans('sylius.user.reset_password_request', [], 'flashes')); FlashBagProvider::getFlashBag($this->requestStack)->add('success', 'sylius.user.reset_password_request');
} }
} }

View file

@ -39,7 +39,6 @@ final readonly class ResetPasswordAction
private RouterInterface $router, private RouterInterface $router,
private FormFactoryInterface $formFactory, private FormFactoryInterface $formFactory,
private UserRepositoryInterface $userRepository, private UserRepositoryInterface $userRepository,
private TranslatorInterface $translator,
private RequestStack $requestStack, private RequestStack $requestStack,
private ResetPasswordDispatcherInterface $resetPasswordDispatcher, private ResetPasswordDispatcherInterface $resetPasswordDispatcher,
private string $tokenTtl, private string $tokenTtl,
@ -86,11 +85,11 @@ final readonly class ResetPasswordAction
private function addSuccessNotification(): void private function addSuccessNotification(): void
{ {
FlashBagProvider::getFlashBag($this->requestStack)->add('success', $this->translator->trans('sylius.user.reset_password', [], 'flashes')); FlashBagProvider::getFlashBag($this->requestStack)->add('success', 'sylius.user.reset_password');
} }
private function addErrorNotification(): void private function addErrorNotification(): void
{ {
FlashBagProvider::getFlashBag($this->requestStack)->add('error', $this->translator->trans('sylius.user.expire_password_reset_token', [], 'flashes')); FlashBagProvider::getFlashBag($this->requestStack)->add('error', 'sylius.user.expire_password_reset_token');
} }
} }

View file

@ -0,0 +1,67 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Sylius\Bundle\ShopBundle\Controller;
use Sylius\Bundle\CoreBundle\Command\Shop\Account\VerifyShopUser;
use Sylius\Bundle\CoreBundle\Provider\FlashBagProvider;
use Sylius\Component\Channel\Context\ChannelContextInterface;
use Sylius\Component\Locale\Context\LocaleContextInterface;
use Sylius\Component\User\Model\UserInterface;
use Sylius\Component\User\Repository\UserRepositoryInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Routing\RouterInterface;
/**
* @experimental
*/
final readonly class VerifyShopUserAction
{
public function __construct(
private RouterInterface $router,
private UserRepositoryInterface $userRepository,
private RequestStack $requestStack,
private ChannelContextInterface $channelContext,
private LocaleContextInterface $localeContext,
private MessageBusInterface $messageBus,
) {
}
public function __invoke(Request $request, string $token, ?string $redirect): Response
{
$redirect ??= 'sylius_shop_account_dashboard';
/** @var UserInterface|null $user */
$user = $this->userRepository->findOneBy(['emailVerificationToken' => $token]);
if (null === $user) {
FlashBagProvider::getFlashBag($this->requestStack)->add('error', 'sylius.user.verify_email_by_invalid_token');
return new RedirectResponse($this->router->generate($redirect));
}
$this->messageBus->dispatch(new VerifyShopUser(
$token,
$this->channelContext->getChannel()->getCode(),
$this->localeContext->getLocaleCode(),
));
FlashBagProvider::getFlashBag($this->requestStack)->add('success', 'sylius.user.verify_email');
return new RedirectResponse($this->router->generate($redirect));
}
}

View file

@ -12,7 +12,14 @@ sylius_shop_user_request_verification_token:
sylius_shop_user_verification: sylius_shop_user_verification:
path: /verify/{token} path: /verify/{token}
methods: [GET] methods: [GET]
condition: "context.isSyliusRoutingBcLayerEnabled('shop_verify_user')"
defaults: defaults:
_controller: sylius.controller.shop_user::verifyAction _controller: sylius.controller.shop_user::verifyAction
_sylius: _sylius:
redirect: sylius_shop_account_dashboard redirect: sylius_shop_account_dashboard
_sylius_shop_user_verification:
path: /verify/{token}
methods: [GET]
condition: "not context.isSyliusRoutingBcLayerEnabled('shop_verify_user')"
controller: sylius_shop.controller.verify_shop_user

View file

@ -23,6 +23,7 @@ use Sylius\Bundle\ShopBundle\Controller\OrderThankYouAction;
use Sylius\Bundle\ShopBundle\Controller\RegistrationThankYouController; use Sylius\Bundle\ShopBundle\Controller\RegistrationThankYouController;
use Sylius\Bundle\ShopBundle\Controller\RequestPasswordResetTokenAction; use Sylius\Bundle\ShopBundle\Controller\RequestPasswordResetTokenAction;
use Sylius\Bundle\ShopBundle\Controller\ResetPasswordAction; use Sylius\Bundle\ShopBundle\Controller\ResetPasswordAction;
use Sylius\Bundle\ShopBundle\Controller\VerifyShopUserAction;
return static function (ContainerConfigurator $container) { return static function (ContainerConfigurator $container) {
$services = $container->services(); $services = $container->services();
@ -126,7 +127,6 @@ return static function (ContainerConfigurator $container) {
service('router.default'), service('router.default'),
service('form.factory'), service('form.factory'),
service('sylius.repository.shop_user'), service('sylius.repository.shop_user'),
service('translator'),
service('request_stack'), service('request_stack'),
service('sylius.command_dispatcher.reset_password.shop'), service('sylius.command_dispatcher.reset_password.shop'),
param('sylius.shop_user.token.password_reset.ttl'), param('sylius.shop_user.token.password_reset.ttl'),
@ -141,9 +141,21 @@ return static function (ContainerConfigurator $container) {
service('router.default'), service('router.default'),
service('form.factory'), service('form.factory'),
service('sylius.command_bus'), service('sylius.command_bus'),
service('translator'),
service('request_stack'), service('request_stack'),
]) ])
->tag('controller.service_arguments') ->tag('controller.service_arguments')
; ;
$services
->set('sylius_shop.controller.verify_shop_user', VerifyShopUserAction::class)
->args([
service('router.default'),
service('sylius.repository.shop_user'),
service('request_stack'),
service('sylius.context.channel'),
service('sylius.context.locale'),
service('sylius.command_bus'),
])
->tag('controller.service_arguments')
;
}; };