Test Failed
Pull Request — feature/unit-tests (#37)
by Daniel
06:50
created

AbstractUserEmailFactory::validateContext()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 4
dl 0
loc 6
rs 10
c 1
b 0
f 0
cc 3
nc 3
nop 1
1
<?php
2
3
/*
4
 * This file is part of the Silverback API Component Bundle Project
5
 *
6
 * (c) Daniel West <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Silverback\ApiComponentBundle\Factory\Mailer\User;
15
16
use Psr\Container\ContainerInterface;
17
use Silverback\ApiComponentBundle\Entity\User\AbstractUser;
18
use Silverback\ApiComponentBundle\Event\UserEmailMessageEvent;
19
use Silverback\ApiComponentBundle\Exception\BadMethodCallException;
20
use Silverback\ApiComponentBundle\Exception\InvalidArgumentException;
21
use Silverback\ApiComponentBundle\Exception\RfcComplianceException;
22
use Silverback\ApiComponentBundle\Exception\UnexpectedValueException;
23
use Silverback\ApiComponentBundle\Url\RefererUrlHelper;
24
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
25
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
26
use Symfony\Component\HttpFoundation\RequestStack;
27
use Symfony\Component\Mime\Address;
28
use Symfony\Component\Mime\Exception\RfcComplianceException as SymfonyRfcComplianceException;
29
use Symfony\Component\Mime\RawMessage;
30
use Symfony\Contracts\Service\ServiceSubscriberInterface;
31
use Twig\Environment;
32
33
/**
34
 * @author Daniel West <[email protected]>
35
 */
36
abstract class AbstractUserEmailFactory implements ServiceSubscriberInterface
37
{
38
    protected ContainerInterface $container;
39
    private EventDispatcherInterface $eventDispatcher;
40
    protected bool $enabled;
41
    protected string $template;
42
    protected string $subject;
43
    protected array $emailContext;
44
    protected ?string $defaultRedirectPath;
45
    protected ?string $redirectPathQueryKey;
46
    protected ?RawMessage $message;
47
    private ?AbstractUser $user;
48
49
    public function __construct(
50
        ContainerInterface $container,
51
        EventDispatcherInterface $eventDispatcher,
52
        string $subject,
53
        bool $enabled = true,
54
        ?string $defaultRedirectPath = null,
55
        ?string $redirectPathQueryKey = null,
56
        array $emailContext = []
57
    ) {
58
        $this->container = $container;
59
        $this->eventDispatcher = $eventDispatcher;
60
        $this->subject = $subject;
61
        $this->enabled = $enabled;
62
        $this->emailContext = $emailContext;
63
        $this->defaultRedirectPath = $defaultRedirectPath;
64
        $this->redirectPathQueryKey = $redirectPathQueryKey;
65
    }
66
67
    public static function getSubscribedServices(): array
68
    {
69
        return [
70
            RequestStack::class,
71
            RefererUrlHelper::class,
72
            Environment::class,
73
        ];
74
    }
75
76
    abstract public function create(AbstractUser $user, array $context = []): ?RawMessage;
77
78
    abstract protected function getTemplate(): string;
79
80
    protected static function getRequiredContextKeys(): ?array
81
    {
82
        return [
83
            'website_name',
84
            'user',
85
        ];
86
    }
87
88
    protected function createEmailMessage(array $context = []): TemplatedEmail
89
    {
90
        if (!$this->user) {
91
            throw new BadMethodCallException('You must call the method `initUser` before `createEmailMessage`');
92
        }
93
94
        try {
95
            $toEmailAddress = Address::fromString((string) $this->user->getEmailAddress());
96
        } catch (SymfonyRfcComplianceException $exception) {
97
            $exception = new RfcComplianceException($exception->getMessage());
98
            throw $exception;
99
        }
100
101
        $context = array_replace_recursive([
102
            'user' => $this->user,
103
        ], $this->emailContext, $context);
104
        $this->validateContext($context);
105
106
        $twig = $this->container->get(Environment::class);
107
        $template = $twig->createTemplate($this->subject);
108
        $subject = $template->render($context);
109
110
        $email = (new TemplatedEmail())
111
            ->to($toEmailAddress)
112
            ->subject($subject)
113
            ->htmlTemplate('@SilverbackApiComponent/emails/' . $this->getTemplate())
114
            ->context($context);
115
116
        $event = new UserEmailMessageEvent(static::class, $email);
117
        $this->eventDispatcher->dispatch($event);
118
119
        return $event->getEmail();
120
    }
121
122
    protected function initUser(AbstractUser $user): void
123
    {
124
        if (null === $user->getUsername()) {
125
            throw new InvalidArgumentException('The user must have a username set to send them any email');
126
        }
127
128
        if (null === $user->getEmailAddress()) {
129
            throw new InvalidArgumentException('The user must have a username set to send them any email');
130
        }
131
132
        $this->user = $user;
133
    }
134
135
    protected function getTokenUrl(string $token, string $username): string
136
    {
137
        $path = $this->populatePathVariables($this->getTokenPath(), [
138
            'token' => $token,
139
            'username' => $username,
140
        ]);
141
142
        $refererUrlHelper = $this->container->get(RefererUrlHelper::class);
143
144
        return $refererUrlHelper->getAbsoluteUrl($path);
145
    }
146
147
    private function getTokenPath(): string
148
    {
149
        if (null === $this->defaultRedirectPath && null === $this->redirectPathQueryKey) {
150
            throw new InvalidArgumentException('The `defaultRedirectPath` or `redirectPathQueryKey` must be set');
151
        }
152
153
        $requestStack = $this->container->get(RequestStack::class);
154
        $request = $requestStack->getMasterRequest();
155
156
        $path = ($request && $this->redirectPathQueryKey) ?
157
            $request->query->get($this->redirectPathQueryKey, $this->defaultRedirectPath) :
158
            $this->defaultRedirectPath;
159
160
        if (null === $path) {
161
            throw new UnexpectedValueException(sprintf('The querystring key `%s` could not be found in the request to generate a token URL', $this->redirectPathQueryKey));
162
        }
163
164
        return $path;
165
    }
166
167
    private function populatePathVariables(string $path, array $variables): string
168
    {
169
        preg_match_all('/{{[\s]*(\w+)[\s]*}}/', $path, $matches);
170
        foreach ($matches[0] as $matchIndex => $fullMatch) {
171
            if (isset($variables[${$matches[1][$matchIndex]}])) {
172
                $path = str_replace($fullMatch, $variables[${$matches[1][$matchIndex]}], $path);
173
            }
174
        }
175
176
        return $path;
177
    }
178
179
    private function validateContext(array $context): void
180
    {
181
        $requiredKeys = self::getRequiredContextKeys();
182
        foreach ($requiredKeys as $requiredKey) {
183
            if (!\array_key_exists($requiredKey, $context)) {
184
                throw new InvalidArgumentException(sprintf('The context key `%s` is required to create the email message', $requiredKey));
185
            }
186
        }
187
    }
188
}
189