Test Failed
Pull Request — feature/unit-tests (#37)
by Daniel
09:37 queued 03:47
created

AbstractUserEmailFactory::getRequiredContextKeys()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 3
dl 0
loc 5
rs 10
c 1
b 0
f 0
cc 1
nc 1
nop 0
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\Exception\BadMethodCallException;
19
use Silverback\ApiComponentBundle\Exception\InvalidArgumentException;
20
use Silverback\ApiComponentBundle\Exception\RfcComplianceException;
21
use Silverback\ApiComponentBundle\Exception\UnexpectedValueException;
22
use Silverback\ApiComponentBundle\Url\RefererUrlHelper;
23
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
24
use Symfony\Component\HttpFoundation\RequestStack;
25
use Symfony\Component\Mime\Address;
26
use Symfony\Component\Mime\Exception\RfcComplianceException as SymfonyRfcComplianceException;
27
use Symfony\Component\Mime\RawMessage;
28
use Symfony\Contracts\Service\ServiceSubscriberInterface;
29
30
/**
31
 * @author Daniel West <[email protected]>
32
 */
33
abstract class AbstractUserEmailFactory implements ServiceSubscriberInterface
34
{
35
    protected ContainerInterface $container;
36
    protected bool $enabled;
37
    protected string $template;
38
    protected string $subject;
39
    protected array $emailContext;
40
    protected ?string $defaultRedirectPath;
41
    protected ?string $redirectPathQueryKey;
42
    protected ?RawMessage $message;
43
    private ?AbstractUser $user;
44
45
    public function __construct(
46
        ContainerInterface $container,
47
        string $subject,
48
        bool $enabled = true,
49
        array $emailContext = [],
50
        ?string $defaultRedirectPath = null,
51
        ?string $redirectPathQueryKey = null
52
    ) {
53
        $this->container = $container;
54
        $this->enabled = $enabled;
55
        $this->subject = $subject;
56
        $this->emailContext = $emailContext;
57
        $this->defaultRedirectPath = $defaultRedirectPath;
58
        $this->redirectPathQueryKey = $redirectPathQueryKey;
59
    }
60
61
    public static function getSubscribedServices(): array
62
    {
63
        return [
64
            RequestStack::class,
65
            RefererUrlHelper::class,
66
        ];
67
    }
68
69
    abstract public function create(AbstractUser $user, array $context = []): ?RawMessage;
70
71
    abstract protected function getTemplate(): string;
72
73
    protected static function getRequiredContextKeys(): ?array
74
    {
75
        return [
76
            'website_name',
77
            'user',
78
        ];
79
    }
80
81
    protected function createEmailMessage(array $context = []): TemplatedEmail
82
    {
83
        if (!$this->user) {
84
            throw new BadMethodCallException('You must call the method `validateUser` before `createEmailMessage`');
85
        }
86
87
        try {
88
            $toEmailAddress = Address::fromString($this->user->getEmailAddress());
0 ignored issues
show
Bug introduced by
It seems like $this->user->getEmailAddress() can also be of type null; however, parameter $string of Symfony\Component\Mime\Address::fromString() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

88
            $toEmailAddress = Address::fromString(/** @scrutinizer ignore-type */ $this->user->getEmailAddress());
Loading history...
89
        } catch (SymfonyRfcComplianceException $exception) {
90
            $exception = new RfcComplianceException($exception->getMessage());
91
            throw $exception;
92
        }
93
94
        $context = array_replace_recursive([
95
            'user' => $this->user,
96
        ], $this->emailContext, $context);
97
        $this->validateContext($context);
98
99
        return (new TemplatedEmail())
100
            ->to($toEmailAddress)
101
            ->subject($this->subject)
102
            ->htmlTemplate('@SilverbackApiComponent/emails/' . $this->getTemplate())
103
            ->context($context);
104
    }
105
106
    protected function initUser(AbstractUser $user): void
107
    {
108
        if (!$user->getUsername()) {
109
            throw new InvalidArgumentException('The user must have a username set to send them any email');
110
        }
111
112
        if (!$userEmailAddress = $user->getEmailAddress()) {
0 ignored issues
show
Unused Code introduced by
The assignment to $userEmailAddress is dead and can be removed.
Loading history...
113
            throw new InvalidArgumentException('The user must have a username set to send them any email');
114
        }
115
116
        $this->user = $user;
117
    }
118
119
    protected function getTokenUrl(string $token, string $username): string
120
    {
121
        $path = $this->populatePathVariables($this->getTokenPath(), [
122
            'token' => $token,
123
            'username' => $username,
124
        ]);
125
126
        $refererUrlHelper = $this->container->get(RefererUrlHelper::class);
127
128
        return $refererUrlHelper->getAbsoluteUrl($path);
129
    }
130
131
    private function getTokenPath(): string
132
    {
133
        if (null === $this->defaultRedirectPath && null === $this->redirectPathQueryKey) {
134
            throw new InvalidArgumentException('The `defaultRedirectPath` or `redirectPathQueryKey` must be set');
135
        }
136
137
        $requestStack = $this->container->get(RequestStack::class);
138
        $request = $requestStack->getMasterRequest();
139
140
        $path = ($request && $this->redirectPathQueryKey) ?
141
            $request->query->get($this->redirectPathQueryKey, $this->defaultRedirectPath) :
142
            $this->defaultRedirectPath;
143
144
        if (null === $path) {
145
            throw new UnexpectedValueException(sprintf('The querystring key `%s` could not be found in the request to generate a token URL', $this->redirectPathQueryKey));
146
        }
147
148
        return $path;
149
    }
150
151
    private function populatePathVariables(string $path, array $variables): string
152
    {
153
        preg_match_all('/{{[\s]*(\w+)[\s]*}}/', $path, $matches);
154
        foreach ($matches[0] as $matchIndex => $fullMatch) {
155
            if (isset($variables[${$matches[1][$matchIndex]}])) {
156
                $path = str_replace($fullMatch, $variables[${$matches[1][$matchIndex]}], $path);
157
            }
158
        }
159
160
        return $path;
161
    }
162
163
    private function validateContext(array $context): void
164
    {
165
        $requiredKeys = self::getRequiredContextKeys();
166
        foreach ($requiredKeys as $requiredKey) {
167
            if (!\array_key_exists($requiredKey, $context)) {
168
                throw new InvalidArgumentException(sprintf('The context key `%s` is required to create the email message', $requiredKey));
169
            }
170
        }
171
    }
172
}
173