add validation, fix register customer route, add missing mail send

This commit is contained in:
arti0090 2021-03-16 16:39:12 +01:00
parent 5ba7a4e2a0
commit 3272f454de
23 changed files with 378 additions and 64 deletions

View file

@ -17,7 +17,7 @@ Feature: Verifying account's email address
And I should be able to log in as "valkyrie@cain.com" with "sylius" password
And my account should be verified
@ui
@ui @api
Scenario: Being unable to verify with invalid token
When I try to verify using "twinklelittlestar" token
Then I should be notified that the verification token is invalid
@ -45,14 +45,14 @@ Feature: Verifying account's email address
And I have already verified my account
Then I should not be able to resend the verification email
@ui @email
@ui @email @api
Scenario: Receiving account verification email after registration
When I register with email "ghastly@bespoke.com" and password "suitsarelife"
Then I should be notified that my account has been created and the verification email has been sent
And 2 emails should be sent to "ghastly@bespoke.com"
But I should not be able to log in as "ghastly@bespoke.com" with "suitsarelife" password
@ui @email
@ui @email @api
Scenario: Do not send verification email when account verification on the channel is not required
Given "United States" channel has account verification disabled
When I register with email "ghastly@bespoke.com" and password "suitsarelife"

View file

@ -44,6 +44,14 @@ final class EmailContext implements Context
);
}
/**
* @Then :count email(s) should be sent to :recipient
*/
public function numberOfEmailsShouldBeSentTo(int $count, string $recipient): void
{
Assert::same($this->emailChecker->countMessagesTo($recipient), $count);
}
private function assertEmailContainsMessageTo(string $message, string $recipient): void
{
Assert::true($this->emailChecker->hasMessageTo($message, $recipient));

View file

@ -48,6 +48,9 @@ final class CustomerContext implements Context
/** @var ShopSecurityContext */
private $shopApiSecurityContext;
/** @var string */
private $verificationToken = '';
public function __construct(
ApiClientInterface $customerClient,
ApiClientInterface $orderShopClient,
@ -186,12 +189,25 @@ final class CustomerContext implements Context
*/
public function iTryToVerifyMyAccountUsingTheLinkFromEmail(ShopUserInterface $user): void
{
$request = Request::custom(
\sprintf('/api/v2/shop/account-verification-requests/%s', (string) $user->getEmailVerificationToken()),
HttpRequest::METHOD_PATCH,
);
$this->verificationToken = $user->getEmailVerificationToken();
$this->verifyAccount($this->verificationToken);
}
$this->customerClient->executeCustomRequest($request);
/**
* @When I (try to )verify using :token token
*/
public function iTryToVerifyUsing(string $token): void
{
$this->verifyAccount($token);
}
/**
* @When I register with email :email and password :password
*/
public function iRegisterWithEmailAndPassword(string $email, string $password): void
{
$this->iRegisterThisAccount($email, $password);
$this->loginContext->iLogInAsWithPassword($email, $password);
}
/**
@ -287,6 +303,17 @@ final class CustomerContext implements Context
);
}
/**
* @Then I should be notified that the verification token is invalid
*/
public function iShouldBeNotifiedThatTheVerificationTokenIsInvalid(): void
{
$this->isViolationWithMessageInResponse(
$this->customerClient->getLastResponse(),
sprintf('There is no shop user with %s email verification token.', $this->verificationToken)
);
}
/**
* @When I browse my orders
*/
@ -337,20 +364,10 @@ final class CustomerContext implements Context
$this->responseChecker->isCreationSuccessful($this->customerClient->getLastResponse());
}
private function isViolationWithMessageInResponse(Response $response, string $message): bool
{
$violations = $this->responseChecker->getResponseContent($response)['violations'];
foreach ($violations as $violation) {
if ($violation['message'] === $message) {
return true;
}
}
return false;
}
/**
* @Then I should be notified that my password has been successfully changed
* @Then I should be notified that new account has been successfully created
* @Then I should be notified that my account has been created and the verification email has been sent
*/
public function iShouldBeNotifiedThatItHasBeenSuccessfullyChanged(): void
{
@ -408,8 +425,48 @@ final class CustomerContext implements Context
$user = $this->sharedStorage->get('user');
$this->loginContext->iLogInAsWithPassword($user->getEmail(), 'sylius');
$response = $this->customerClient->show((string) $user->getId());
$response = $this->customerClient->show((string) $user->getCustomer()->getId());
Assert::true($this->responseChecker->getResponseContent($response)['user']['verified']);
}
private function isViolationWithMessageInResponse(Response $response, string $message): bool
{
$violations = $this->responseChecker->getResponseContent($response)['violations'];
foreach ($violations as $violation) {
if ($violation['message'] === $message) {
return true;
}
}
return false;
}
private function verifyAccount(string $token): void
{
$request = Request::custom(
\sprintf('/api/v2/shop/account-verification-requests/%s', $token),
HttpRequest::METHOD_PATCH,
);
$this->customerClient->executeCustomRequest($request);
}
private function iRegisterThisAccount(?string $email = 'example@example.com', ?string $password = 'example'): void
{
$request = Request::create(
'shop',
'customers',
'',
);
$request->setContent([
'firstName' => 'First',
'lastName' => 'Last',
'email' => $email,
'password' => $password,
]);
$this->customerClient->executeCustomRequest($request);
}
}

View file

@ -211,6 +211,16 @@ final class LoginContext implements Context
$this->iShouldBeLoggedIn();
}
/**
* @Then I should not be able to log in as :email with :password password
*/
public function iShouldNotBeAbleToLogInAsWithPassword(string $email, string $password): void
{
$this->iLogInAsWithPassword($email, $password);
$this->iShouldNotBeLoggedIn();
}
private function addLocaleCode(string $localeCode): void
{
$this->request->updateContent(['localeCode' => $localeCode]);

View file

@ -112,7 +112,7 @@ final class RegistrationContext implements Context
{
$this->client->request(
'POST',
'/api/v2/shop/customers/',
'/api/v2/shop/customers',
[],
[],
['HTTP_ACCEPT' => 'application/ld+json', 'CONTENT_TYPE' => 'application/ld+json'],

View file

@ -16,6 +16,7 @@ default:
- sylius.behat.context.setup.shop_api_security
- sylius.behat.context.setup.user
- sylius.behat.context.api.email
- sylius.behat.context.api.shop.customer
- sylius.behat.context.api.shop.login

View file

@ -22,6 +22,8 @@ use Sylius\Component\Core\Model\ChannelInterface;
use Sylius\Component\Core\Model\ShopUserInterface;
use Sylius\Component\Resource\Factory\FactoryInterface;
use Sylius\Component\User\Security\Generator\GeneratorInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\GenericEvent;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
use Symfony\Component\Messenger\MessageBusInterface;
@ -46,13 +48,17 @@ final class RegisterShopUserHandler implements MessageHandlerInterface
/** @var MessageBusInterface */
private $commandBus;
/** @var EventDispatcherInterface */
private $eventDispatcher;
public function __construct(
FactoryInterface $shopUserFactory,
ObjectManager $shopUserManager,
CustomerProviderInterface $customerProvider,
ChannelRepositoryInterface $channelRepository,
GeneratorInterface $tokenGenerator,
MessageBusInterface $commandBus
MessageBusInterface $commandBus,
EventDispatcherInterface $eventDispatcher
) {
$this->shopUserFactory = $shopUserFactory;
$this->shopUserManager = $shopUserManager;
@ -60,6 +66,7 @@ final class RegisterShopUserHandler implements MessageHandlerInterface
$this->channelRepository = $channelRepository;
$this->tokenGenerator = $tokenGenerator;
$this->commandBus = $commandBus;
$this->eventDispatcher = $eventDispatcher;
}
public function __invoke(RegisterShopUser $command): void
@ -82,6 +89,11 @@ final class RegisterShopUserHandler implements MessageHandlerInterface
/** @var ChannelInterface $channel */
$channel = $this->channelRepository->findOneByCode($command->channelCode);
$this->shopUserManager->persist($user);
$this->shopUserManager->flush();
$this->eventDispatcher->dispatch(new GenericEvent($customer), 'sylius.customer.post_register');
if ($channel->isAccountVerificationRequired()) {
$token = $this->tokenGenerator->generate();
$user->setEmailVerificationToken($token);
@ -95,6 +107,5 @@ final class RegisterShopUserHandler implements MessageHandlerInterface
$user->setEnabled(true);
}
$this->shopUserManager->persist($user);
}
}

View file

@ -22,7 +22,7 @@
<collectionOperations>
<collectionOperation name="shop_post">
<attribute name="method">POST</attribute>
<attribute name="path">/shop/customers/</attribute>
<attribute name="path">/shop/customers</attribute>
<attribute name="openapi_context">
<attribute name="summary">Registers a new customer</attribute>
</attribute>

View file

@ -18,6 +18,7 @@
<resource class="Sylius\Bundle\ApiBundle\Command\VerifyCustomerAccount" shortName="VerifyCustomerAccount">
<attribute name="route_prefix">shop</attribute>
<attribute name="messenger">input</attribute>
<attribute name="validation_groups">sylius</attribute>
<collectionOperations />

View file

@ -23,6 +23,7 @@
<argument type="service" id="sylius.repository.channel" />
<argument type="service" id="sylius.shop_user.token_generator.email_verification" />
<argument type="service" id="sylius_default.bus" />
<argument type="service" id="event_dispatcher" />
<tag name="messenger.message_handler" bus="sylius_default.bus"/>
</service>
@ -154,5 +155,12 @@
<argument type="service" id="sylius.email_sender" />
<tag name="messenger.message_handler" bus="sylius_default.bus" />
</service>
<service id="Sylius\Bundle\ApiBundle\CommandHandler\SendUserRegistrationEmailHandler">
<argument type="service" id="sylius.repository.shop_user" />
<argument type="service" id="sylius.repository.channel" />
<argument type="service" id="sylius.email_sender" />
<tag name="messenger.message_handler" bus="sylius_default.bus" />
</service>
</services>
</container>

View file

@ -58,5 +58,10 @@
<argument type="service" id="sylius.promotion_coupon_eligibility_checker" />
<tag name="validator.constraint_validator" alias="sylius_api_promotion_coupon_eligibility" />
</service>
<service id="sylius.api.validator.account_verification_token_eligibility" class="Sylius\Bundle\ApiBundle\Validator\Constraints\AccountVerificationTokenEligibilityValidator">
<argument type="service" id="sylius.repository.shop_user" />
<tag name="validator.constraint_validator" alias="sylius_api_validator_account_verification_token_eligibility" />
</service>
</services>
</container>

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This file is part of the Sylius package.
(c) Paweł Jędrzejewski
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
-->
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/services/constraint-mapping-1.0.xsd">
<class name="Sylius\Bundle\ApiBundle\Command\VerifyCustomerAccount">
<constraint name="Sylius\Bundle\ApiBundle\Validator\Constraints\AccountVerificationTokenEligibility">
<option name="groups">
<value>sylius</value>
</option>
</constraint>
</class>
</constraint-mapping>

View file

@ -1,3 +1,6 @@
sylius:
account:
invalid_verification_token: 'There is no shop user with %verificationToken% email verification token.'
order:
not_empty: 'An empty order cannot be completed.'

View file

@ -27,6 +27,9 @@ use Symfony\Contracts\Translation\TranslatorInterface;
final class SendAccountVerificationEmailHandlerTest extends KernelTestCase
{
/**
* @test
*/
public function it_sends_account_verification_token_email_without_hostname(): void
{
$container = self::bootKernel()->getContainer();
@ -78,6 +81,9 @@ final class SendAccountVerificationEmailHandlerTest extends KernelTestCase
));
}
/**
* @test
*/
public function it_sends_account_verification_token_email_with_hostname(): void
{
$container = self::bootKernel()->getContainer();

View file

@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* 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\ApiBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/** @experimental */
final class AccountVerificationTokenEligibility extends Constraint
{
/** @var string */
public $message = 'sylius.account.invalid_verification_token';
public function validatedBy(): string
{
return 'sylius_api_validator_account_verification_token_eligibility';
}
public function getTargets(): string
{
return self::CLASS_CONSTRAINT;
}
}

View file

@ -0,0 +1,46 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* 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\ApiBundle\Validator\Constraints;
use Sylius\Bundle\ApiBundle\Command\VerifyCustomerAccount;
use Sylius\Component\Resource\Repository\RepositoryInterface;
use Sylius\Component\User\Model\UserInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
/** @experimental */
final class AccountVerificationTokenEligibilityValidator extends ConstraintValidator
{
/** @var RepositoryInterface */
private $shopUserRepository;
public function __construct(RepositoryInterface $shopUserRepository)
{
$this->shopUserRepository = $shopUserRepository;
}
/** @param VerifyCustomerAccount|mixed $value */
public function validate($value, Constraint $constraint)
{
/** @var UserInterface|null $user */
$user = $this->shopUserRepository->findOneBy(['emailVerificationToken' => $value->token]);
if (null === $user) {
$this->context->addViolation(
$constraint->message,
['%verificationToken%' => $value->token]
);
}
}
}

View file

@ -24,6 +24,8 @@ use Sylius\Component\Core\Model\CustomerInterface;
use Sylius\Component\Core\Model\ShopUserInterface;
use Sylius\Component\Resource\Factory\FactoryInterface;
use Sylius\Component\User\Security\Generator\GeneratorInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\GenericEvent;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
use Symfony\Component\Messenger\MessageBusInterface;
@ -36,7 +38,8 @@ final class RegisterShopUserHandlerSpec extends ObjectBehavior
CustomerProviderInterface $customerProvider,
ChannelRepositoryInterface $channelRepository,
GeneratorInterface $generator,
MessageBusInterface $commandBus
MessageBusInterface $commandBus,
EventDispatcherInterface $eventDispatcher
): void {
$this->beConstructedWith(
$shopUserFactory,
@ -44,7 +47,8 @@ final class RegisterShopUserHandlerSpec extends ObjectBehavior
$customerProvider,
$channelRepository,
$generator,
$commandBus
$commandBus,
$eventDispatcher
);
}
@ -62,7 +66,8 @@ final class RegisterShopUserHandlerSpec extends ObjectBehavior
ChannelRepositoryInterface $channelRepository,
ChannelInterface $channel,
GeneratorInterface $generator,
MessageBusInterface $commandBus
MessageBusInterface $commandBus,
EventDispatcherInterface $eventDispatcher
): void {
$command = new RegisterShopUser('Will', 'Smith', 'WILL.SMITH@example.com', 'iamrobot', '+13104322400');
$command->setChannelCode('CHANNEL_CODE');
@ -85,10 +90,12 @@ final class RegisterShopUserHandlerSpec extends ObjectBehavior
$generator->generate()->willReturn('TOKEN');
$shopUser->setEmailVerificationToken('TOKEN')->shouldBeCalled();
$sendEmailCommand = new SendAccountVerificationEmail('WILL.SMITH@example.com', 'en_US', 'CHANNEL_CODE');
$commandBus->dispatch($sendEmailCommand)->shouldBeCalled()->willReturn(new Envelope($sendEmailCommand));
$shopUserManager->persist($shopUser)->shouldBeCalled();
$shopUserManager->flush()->shouldBeCalled();
$eventDispatcher->dispatch(new GenericEvent($customer->getWrappedObject()), 'sylius.customer.post_register')->shouldBeCalled();
$sendVerificationEmailCommand = new SendAccountVerificationEmail('WILL.SMITH@example.com', 'en_US', 'CHANNEL_CODE');
$commandBus->dispatch($sendVerificationEmailCommand)->shouldBeCalled()->willReturn(new Envelope($sendVerificationEmailCommand));
$this($command);
}
@ -100,7 +107,8 @@ final class RegisterShopUserHandlerSpec extends ObjectBehavior
ShopUserInterface $shopUser,
CustomerInterface $customer,
ChannelRepositoryInterface $channelRepository,
ChannelInterface $channel
ChannelInterface $channel,
EventDispatcherInterface $eventDispatcher
): void {
$command = new RegisterShopUser('Will', 'Smith', 'WILL.SMITH@example.com', 'iamrobot', '+13104322400');
$command->setChannelCode('CHANNEL_CODE');
@ -118,11 +126,14 @@ final class RegisterShopUserHandlerSpec extends ObjectBehavior
$customer->setPhoneNumber('+13104322400')->shouldBeCalled();
$customer->setUser($shopUser)->shouldBeCalled();
$shopUserManager->persist($shopUser)->shouldBeCalled();
$shopUserManager->flush()->shouldBeCalled();
$eventDispatcher->dispatch(new GenericEvent($customer->getWrappedObject()), 'sylius.customer.post_register')->shouldBeCalled();
$channelRepository->findOneByCode('CHANNEL_CODE')->willReturn($channel);
$channel->isAccountVerificationRequired()->willReturn(false);
$shopUser->setEnabled(true);
$shopUserManager->persist($shopUser)->shouldBeCalled();
$this($command);
}
@ -133,7 +144,8 @@ final class RegisterShopUserHandlerSpec extends ObjectBehavior
CustomerProviderInterface $customerProvider,
ShopUserInterface $shopUser,
CustomerInterface $customer,
ShopUserInterface $existingShopUser
ShopUserInterface $existingShopUser,
EventDispatcherInterface $eventDispatcher
): void {
$shopUserFactory->createNew()->willReturn($shopUser);
$customerProvider->provide('WILL.SMITH@example.com')->willReturn($customer);
@ -141,6 +153,8 @@ final class RegisterShopUserHandlerSpec extends ObjectBehavior
$customer->getUser()->willReturn($existingShopUser);
$shopUserManager->persist($shopUser)->shouldNotBeCalled();
$shopUserManager->flush()->shouldNotBeCalled();
$eventDispatcher->dispatch(new GenericEvent($customer->getWrappedObject()), 'sylius.customer.post_register')->shouldNotBeCalled();
$this
->shouldThrow(\DomainException::class)

View file

@ -0,0 +1,80 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace spec\Sylius\Bundle\ApiBundle\Validator\Constraints;
use PhpSpec\ObjectBehavior;
use Sylius\Bundle\ApiBundle\Command\VerifyCustomerAccount;
use Sylius\Bundle\ApiBundle\Validator\Constraints\AccountVerificationTokenEligibility;
use Sylius\Component\Core\Model\ShopUserInterface;
use Sylius\Component\Resource\Repository\RepositoryInterface;
use Symfony\Component\Validator\ConstraintValidatorInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
final class AccountVerificationTokenEligibilityValidatorSpec extends ObjectBehavior
{
function let(RepositoryInterface $shopUserRepository): void
{
$this->beConstructedWith($shopUserRepository);
}
function it_is_a_constraint_validator(): void
{
$this->shouldImplement(ConstraintValidatorInterface::class);
}
function it_adds_violation_if_account_is_null(
RepositoryInterface $shopUserRepository,
ExecutionContextInterface $executionContext
): void {
$constraint = new AccountVerificationTokenEligibility();
$value = new VerifyCustomerAccount('TOKEN');
$this->initialize($executionContext);
$shopUserRepository->findOneBy(['emailVerificationToken' => 'TOKEN'])->willReturn(null);
$executionContext
->addViolation(
'sylius.account.invalid_verification_token',
['%verificationToken%' => 'TOKEN']
)
->shouldBeCalled()
;
$this->validate($value, $constraint);
}
function it_does_nothing_if_account_has_been_found(
RepositoryInterface $shopUserRepository,
ExecutionContextInterface $executionContext,
ShopUserInterface $user
): void {
$constraint = new AccountVerificationTokenEligibility();
$value = new VerifyCustomerAccount('TOKEN');
$this->initialize($executionContext);
$shopUserRepository->findOneBy(['emailVerificationToken' => 'TOKEN'])->willReturn($user);
$executionContext
->addViolation(
'sylius.account.invalid_verification_token',
['%verificationToken%' => 'TOKEN']
)
->shouldNotBeCalled()
;
$this->validate($value, $constraint);
}
}

View file

@ -9,9 +9,12 @@ sylius_mailer:
order_confirmation:
subject: sylius.emails.order_confirmation.subject
template: "@SyliusCore/Email/orderConfirmation.html.twig"
user_registration:
subject: sylius.emails.user_registration.subject
template: "@SyliusCore/Email/userRegistration.html.twig"
password_reset:
subject: sylius.emails.user.password_reset.subject
template: "@SyliusCore/Email/passwordReset.html.twig"
account_verification_token:
subject: sylius.emails.user.verification_token.subject
template: "@SyliusShop/Email/verification.html.twig"
template: "@SyliusCore/Email/accountVerification.html.twig"

View file

@ -244,6 +244,14 @@ sylius:
by_zones_and_channel: By zones and channel
email:
account_verification_token:
subject: Email address verification
user_registration:
start_shopping: 'Start shopping'
subject: 'User registration'
welcome_to_our_store: 'Welcome to our store!'
you_have_just_been_registered: 'You have just been registered. Thank you'
verification_token:
hello: 'Hello'
message: "Verify your account with token: "
subject: 'Email address verification'
to_verify_your_email_address: 'To verify your email address - click the link below'
verify_your_email_address: 'Verify your email address'

View file

@ -1,11 +1,7 @@
{% extends '@SyliusCore/Email/layout.html.twig' %}
{% block subject %}
{% if sylius_bundle_loaded_checker('SyliusShopBundle') %}
{{ 'sylius.email.verification_token.subject'|trans({}, null, localeCode) }}
{% else %}
{{ 'sylius.email.account_verification_token.subject'|trans({}, null, localeCode) }}
{% endif %}
{% endblock %}
{% block content %}
@ -25,7 +21,7 @@
</div>
{% else %}
<div style="text-align: center;">
<span>{{ 'sylius.email.account_verification_token.message'|trans({}, null, localeCode) }}{{ user.emailVerificationToken }}</span>
<span>{{ 'sylius.email.verification_token.message'|trans({}, null, localeCode) }}{{ user.emailVerificationToken }}</span>
</div>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,23 @@
{% extends '@SyliusShop/Email/layout.html.twig' %}
{% block subject %}
{{ 'sylius.email.user_registration.subject'|trans({}, null, localeCode) }}
{% endblock %}
{% block content %}
{% if sylius_bundle_loaded_checker('SyliusShopBundle') %}
{% set url = channel.hostname is not null ? 'http://' ~ channel.hostname ~ path('sylius_shop_homepage', {'_locale': localeCode}) : url('sylius_shop_homepage', {'_locale': localeCode}) %}
{% endif %}
<div style="text-align: center; margin-bottom: 30px;">
<div style="font-size: 24px;">
{{ 'sylius.email.user_registration.welcome_to_our_store'|trans({}, null, localeCode) }}<br>
</div>
{{ 'sylius.email.user_registration.you_have_just_been_registered'|trans({}, null, localeCode) }} {{ user.username }}.
</div>
<div style="text-align: center;">
<a href="{{ url|raw }}" style="display: inline-block; text-align: center; background: #1abb9c; padding: 18px 28px; color: #fff; text-decoration: none; border-radius: 3px;">
{{ 'sylius.email.user_registration.start_shopping'|trans({}, null, localeCode) }}
</a>
</div>
{% endblock %}

View file

@ -1,22 +1 @@
{% extends '@SyliusShop/Email/layout.html.twig' %}
{% block subject %}
{{ 'sylius.email.user_registration.subject'|trans({}, null, localeCode) }}
{% endblock %}
{% block content %}
{% set url = channel.hostname is not null ? 'http://' ~ channel.hostname ~ path('sylius_shop_homepage', {'_locale': localeCode}) : url('sylius_shop_homepage', {'_locale': localeCode}) %}
<div style="text-align: center; margin-bottom: 30px;">
<div style="font-size: 24px;">
{{ 'sylius.email.user_registration.welcome_to_our_store'|trans({}, null, localeCode) }}<br>
</div>
{{ 'sylius.email.user_registration.you_have_just_been_registered'|trans({}, null, localeCode) }} {{ user.username }}.
</div>
<div style="text-align: center;">
<a href="{{ url|raw }}" style="display: inline-block; text-align: center; background: #1abb9c; padding: 18px 28px; color: #fff; text-decoration: none; border-radius: 3px;">
{{ 'sylius.email.user_registration.start_shopping'|trans({}, null, localeCode) }}
</a>
</div>
{% endblock %}
{% extends '@SyliusCore/Email/userRegistration.html.twig' %}