Test Failed
Push — master ( 61801d...f3c992 )
by Daniel
11:05
created

AbstractUserEmailFactory::getSubscribedServices()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 0
dl 0
loc 6
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the Silverback API Components 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\ApiComponentsBundle\Factory\User\Mailer;
15
16
use Psr\Container\ContainerInterface;
17
use Silverback\ApiComponentsBundle\Entity\User\AbstractUser;
18
use Silverback\ApiComponentsBundle\Event\UserEmailMessageEvent;
19
use Silverback\ApiComponentsBundle\Exception\BadMethodCallException;
20
use Silverback\ApiComponentsBundle\Exception\InvalidArgumentException;
21
use Silverback\ApiComponentsBundle\Exception\RfcComplianceException;
22
use Silverback\ApiComponentsBundle\Exception\UnexpectedValueException;
23
use Silverback\ApiComponentsBundle\Helper\RefererUrlResolver;
24
use Silverback\ApiComponentsBundle\Security\TokenGenerator;
25
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
26
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
27
use Symfony\Component\HttpFoundation\RequestStack;
28
use Symfony\Component\Mime\Address;
29
use Symfony\Component\Mime\Exception\RfcComplianceException as SymfonyRfcComplianceException;
30
use Symfony\Component\Mime\RawMessage;
31
32
/**
33
 * @author Daniel West <[email protected]>
34
 */
35
abstract class AbstractUserEmailFactory
36
{
37
    public const MESSAGE_ID_PREFIX = 'xx';
38
39
    protected ContainerInterface $container;
40
    private EventDispatcherInterface $eventDispatcher;
41
    protected string $subject;
42
    protected bool $enabled;
43
    protected ?string $defaultRedirectPath;
44
    protected ?string $redirectPathQueryKey;
45
    protected array $emailContext;
46
    protected ?RawMessage $message;
47
    private AbstractUser $user;
48
49
    public function __construct(ContainerInterface $container, EventDispatcherInterface $eventDispatcher, string $subject, bool $enabled = true, ?string $defaultRedirectPath = null, ?string $redirectPathQueryKey = null, array $emailContext = [])
50
    {
51 27
        $this->container = $container;
52
        $this->eventDispatcher = $eventDispatcher;
53 27
        $this->subject = $subject;
54 27
        $this->enabled = $enabled;
55 27
        $this->emailContext = $emailContext;
56 27
        $this->defaultRedirectPath = $defaultRedirectPath;
57 27
        $this->redirectPathQueryKey = $redirectPathQueryKey;
58 27
    }
59 27
60 27
    protected static function getContextKeys(): ?array
61
    {
62 2
        return [
63
            'website_name',
64
            'user',
65 2
        ];
66
    }
67
68
    protected function initUser(AbstractUser $user): void
69
    {
70
        if (!$user->getUsername()) {
71 7
            throw new InvalidArgumentException('The user must have a username set to send them any email');
72
        }
73
74 7
        if (!$user->getEmailAddress()) {
75
            throw new InvalidArgumentException('The user must have an email address set to send them any email');
76
        }
77
78
        $this->user = $user;
79 15
    }
80
81 15
    protected function createEmailMessage(array $context = []): ?TemplatedEmail
82 1
    {
83
        if (!$this->enabled) {
84
            return null;
85 14
        }
86 1
87
        if (!isset($this->user)) {
88
            throw new BadMethodCallException('You must call the method `initUser` before `createEmailMessage`');
89 13
        }
90 13
91
        try {
92 12
            // symfony/mime 5.2 deprecated fromString
93
            if (method_exists(Address::class, 'create')) {
94 12
                $toEmailAddress = Address::create((string) $this->user->getEmailAddress());
95 1
            } else {
96
                $toEmailAddress = Address::fromString((string) $this->user->getEmailAddress());
0 ignored issues
show
Deprecated Code introduced by
The function Symfony\Component\Mime\Address::fromString() has been deprecated: since Symfony 5.2, use "create()" instead. ( Ignorable by Annotation )

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

96
                $toEmailAddress = /** @scrutinizer ignore-deprecated */ Address::fromString((string) $this->user->getEmailAddress());

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
97
            }
98 11
        } catch (SymfonyRfcComplianceException $exception) {
99 1
            $exception = new RfcComplianceException($exception->getMessage());
100
            throw $exception;
101
        }
102
103
        $context = array_replace_recursive(
104 10
            [
105 10
                'user' => $this->user,
106
            ],
107 9
            $this->emailContext,
108
            $context
109 1
        );
110 1
        $this->validateContext($context);
111 1
112
        $twig = $this->container->get('twig');
113
        $template = $twig->createTemplate($this->subject);
114 9
        $subject = $template->render($context);
115
116 9
        $email = (new TemplatedEmail())
117
            ->to($toEmailAddress)
118 9
            ->subject($subject)
119
            ->htmlTemplate('@SilverbackApiComponents/emails/' . $this->getTemplate())
120
            ->context($context);
121 9
122
        $event = new UserEmailMessageEvent(static::class, $email);
123 8
        $this->eventDispatcher->dispatch($event);
124 8
125 8
        $email->getHeaders()->addTextHeader('X-Message-ID', sprintf('%s-%s', static::MESSAGE_ID_PREFIX, TokenGenerator::generateToken()));
126
127 8
        return $event->getEmail();
128 8
    }
129 8
130 8
    protected function getTokenUrl(string $token, string $username, ?string $newEmail = null): string
131 8
    {
132
        $path = $this->populatePathVariables(
133 8
            $this->getTokenPath(),
134 8
            [
135
                'token' => $token,
136 8
                'username' => $username,
137
                'new_email' => $newEmail,
138 8
            ]
139
        );
140
141 8
        $refererUrlResolver = $this->container->get(RefererUrlResolver::class);
142
143 8
        return $refererUrlResolver->getAbsoluteUrl($path);
144 8
    }
145
146 6
    private function getTokenPath(): string
147 6
    {
148 6
        if (null === $this->defaultRedirectPath && null === $this->redirectPathQueryKey) {
149
            throw new InvalidArgumentException('The `defaultRedirectPath` or `redirectPathQueryKey` must be set');
150
        }
151
152 6
        $requestStack = $this->container->get(RequestStack::class);
153
        $request = $requestStack->getMainRequest();
154 6
155
        $path = ($request && $this->redirectPathQueryKey) ?
156
            $request->query->get($this->redirectPathQueryKey, $this->defaultRedirectPath) :
157 8
            $this->defaultRedirectPath;
158
159 8
        if (null === $path) {
160 1
            throw new UnexpectedValueException(sprintf('The querystring key `%s` could not be found in the request to generate a token URL', $this->redirectPathQueryKey));
161
        }
162
163 7
        return $path;
164 7
    }
165
166 7
    private function populatePathVariables(string $path, array $variables): string
167 3
    {
168 7
        preg_match_all('/{{[\s]*(\w+)[\s]*}}/', $path, $matches);
169
        foreach ($matches[0] as $matchIndex => $fullMatch) {
170 7
            if (\array_key_exists($varKey = $matches[1][$matchIndex], $variables) && null !== $variables[$varKey]) {
171 1
                $path = str_replace($fullMatch, rawurlencode($variables[$varKey]), $path);
172
            }
173
        }
174 6
175
        return $path;
176
    }
177 6
178
    private function validateContext(array $context): void
179 6
    {
180 6
        $contextKeys = static::getContextKeys();
181 1
        $keys = array_keys($context);
182 1
        if (\count($differences = array_diff($contextKeys, $keys))) {
183
            throw new InvalidArgumentException(sprintf('You have not specified required context key(s) for the user email factory factory `%s` (expected: `%s`)', static::class, implode('`, `', $differences)));
184
        }
185
    }
186 6
187
    abstract public function create(AbstractUser $user, array $context = []): ?RawMessage;
188
189 9
    abstract protected function getTemplate(): string;
190
}
191