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()); |
|
|
|
|
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
|
|
|
|
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.