Passed
Push — feature/unit-tests ( 16a1aa...e0ee05 )
by Daniel
05:07
created

UserMailer   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 212
Duplicated Lines 0 %

Test Coverage

Coverage 78.06%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 25
eloc 116
c 1
b 0
f 0
dl 0
loc 212
ccs 89
cts 114
cp 0.7806
rs 10

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 22 1
A sendUsernameChangedEmail() 0 11 2
A sendPasswordResetEmail() 0 23 2
A createEmailMessage() 0 20 2
A sendChangeEmailConfirmationEmail() 0 13 1
A sendUserEnabledEmail() 0 11 2
A pathToReferrerUrl() 0 7 2
A getUserEmail() 0 7 2
A send() 0 8 2
A getUserUsername() 0 7 2
A sendPasswordChangedEmail() 0 11 2
A getEmailConfirmationUrl() 0 12 2
A sendUserWelcomeEmail() 0 21 3
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\Mailer;
15
16
use Silverback\ApiComponentBundle\Entity\User\AbstractUser;
17
use Silverback\ApiComponentBundle\Exception\InvalidArgumentException;
18
use Silverback\ApiComponentBundle\Exception\MailerTransportException;
19
use Silverback\ApiComponentBundle\Exception\RfcComplianceException;
20
use Silverback\ApiComponentBundle\Url\RefererUrlHelper;
21
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
22
use Symfony\Component\HttpFoundation\RequestStack;
23
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
24
use Symfony\Component\Mailer\MailerInterface;
25
use Symfony\Component\Mime\Address;
26
use Symfony\Component\Mime\RawMessage;
27
28
/**
29
 * @author Daniel West <[email protected]>
30
 */
31
class UserMailer
32
{
33
    private MailerInterface $mailer;
34
    private RefererUrlHelper $refererUrlHelper;
35
    private RequestStack $requestStack;
36
    private string $websiteName;
37
    private string $defaultPasswordResetPath;
38
    private string $defaultChangeEmailVerifyPath;
39
    private bool $sendUserWelcomeEmailEnabled;
40
    private bool $sendUserEnabledEmailEnabled;
41
    private bool $sendUserUsernameChangedEmailEnabled;
42
    private bool $sendUserPasswordChangedEmailEnabled;
43
44 15
    public function __construct(
45
        MailerInterface $mailer,
46
        RefererUrlHelper $refererUrlHelper,
47
        RequestStack $requestStack,
48
        string $websiteName = 'Website Name',
49
        string $defaultPasswordResetPath = '/reset-password/{{ username }}/{{ token }}',
50
        string $defaultChangeEmailVerifyPath = '/verify-new-email/{{ username }}/{{ token }}',
51
        bool $sendUserWelcomeEmailEnabled = true,
52
        bool $sendUserEnabledEmailEnabled = true,
53
        bool $sendUserUsernameChangedEmailEnabled = true,
54
        bool $sendUserPasswordChangedEmailEnabled = true
55
    ) {
56 15
        $this->mailer = $mailer;
57 15
        $this->refererUrlHelper = $refererUrlHelper;
58 15
        $this->requestStack = $requestStack;
59 15
        $this->defaultPasswordResetPath = $defaultPasswordResetPath;
60 15
        $this->defaultChangeEmailVerifyPath = $defaultChangeEmailVerifyPath;
61 15
        $this->websiteName = $websiteName;
62 15
        $this->sendUserWelcomeEmailEnabled = $sendUserWelcomeEmailEnabled;
63 15
        $this->sendUserEnabledEmailEnabled = $sendUserEnabledEmailEnabled;
64 15
        $this->sendUserUsernameChangedEmailEnabled = $sendUserUsernameChangedEmailEnabled;
65 15
        $this->sendUserPasswordChangedEmailEnabled = $sendUserPasswordChangedEmailEnabled;
66 15
    }
67
68 7
    public function sendPasswordResetEmail(AbstractUser $user): void
69
    {
70 7
        $token = $user->getNewPasswordConfirmationToken();
71 7
        if (!$token) {
72 1
            throw new InvalidArgumentException('A new password confirmation token must be set to send the `password reset` email');
73
        }
74
75 6
        $userUsername = self::getUserUsername($user);
76 5
        $resetUrl = $this->pathToReferrerUrl(
77 5
            $token,
78
            $userUsername,
79 5
            'resetPath',
80 5
            $this->defaultPasswordResetPath
81
        );
82 5
        $email = $this->createEmailMessage(
83 5
            'Your password reset request',
84 5
            'user_forgot_password.html.twig',
85
            $user,
86
            [
87 5
                'reset_url' => $resetUrl,
88
            ]
89
        );
90 3
        $this->send($email);
91 2
    }
92
93 3
    public function sendChangeEmailConfirmationEmail(AbstractUser $user): void
94
    {
95 3
        $userUsername = self::getUserUsername($user);
96 2
        $verifyUrl = $this->getEmailConfirmationUrl($user, $userUsername);
97 1
        $email = $this->createEmailMessage(
98 1
            'Your password reset request',
99 1
            'user_verify_email.html.twig',
100
            $user,
101
            [
102 1
                'verify_url' => $verifyUrl,
103
            ]
104
        );
105 1
        $this->send($email);
106 1
    }
107
108 5
    public function sendUserWelcomeEmail(AbstractUser $user): void
109
    {
110 5
        if (!$this->sendUserWelcomeEmailEnabled) {
111
            return;
112
        }
113 5
        $userUsername = self::getUserUsername($user);
114
        try {
115 5
            $verifyUrl = $this->getEmailConfirmationUrl($user, $userUsername);
116 5
        } catch (InvalidArgumentException $exception) {
117
            // if we have not set the email verify token this will be thrown. this is an optional token though.
118 5
            $verifyUrl = null;
119
        }
120 5
        $email = $this->createEmailMessage(
121 5
            sprintf('Welcome to %s', $this->websiteName),
122 5
            'user_welcome.html.twig',
123
            $user,
124
            [
125 5
                'verify_url' => $verifyUrl,
126
            ]
127
        );
128 5
        $this->send($email);
129 5
    }
130
131
    public function sendUserEnabledEmail(AbstractUser $user): void
132
    {
133
        if (!$this->sendUserEnabledEmailEnabled) {
134
            return;
135
        }
136
        $email = $this->createEmailMessage(
137
            'Your account has been enabled',
138
            'user_enabled.html.twig',
139
            $user
140
        );
141
        $this->send($email);
142
    }
143
144
    public function sendUsernameChangedEmail(AbstractUser $user): void
145
    {
146
        if (!$this->sendUserUsernameChangedEmailEnabled) {
147
            return;
148
        }
149
        $email = $this->createEmailMessage(
150
            'Your username has been changed',
151
            'username_changed.html.twig',
152
            $user
153
        );
154
        $this->send($email);
155
    }
156
157
    public function sendPasswordChangedEmail(AbstractUser $user): void
158
    {
159
        if (!$this->sendUserPasswordChangedEmailEnabled) {
160
            return;
161
        }
162
        $email = $this->createEmailMessage(
163
            'Your password has been changed',
164
            'user_password_changed.html.twig',
165
            $user
166
        );
167
        $this->send($email);
168
    }
169
170 11
    private static function getUserEmail(AbstractUser $user): string
171
    {
172 11
        if (!($userEmail = $user->getEmailAddress())) {
173 1
            throw new InvalidArgumentException('The user must have an email address set to send them any email');
174
        }
175
176 10
        return $userEmail;
177
    }
178
179 14
    private static function getUserUsername(AbstractUser $user): string
180
    {
181 14
        if (!($userUsername = $user->getUsername())) {
182 2
            throw new InvalidArgumentException('The user must have a username set to send them any email');
183
        }
184
185 12
        return $userUsername;
186
    }
187
188 11
    private function createEmailMessage(string $subject, string $htmlTemplate, AbstractUser $user, array $context = [])
189
    {
190
        $defaultContext = [
191 11
            'user' => $user,
192 11
            'username' => self::getUserUsername($user),
193 11
            'website_name' => $this->websiteName,
194
        ];
195
196
        try {
197 11
            $toAddress = Address::fromString(self::getUserEmail($user));
198 2
        } catch (\Symfony\Component\Mime\Exception\RfcComplianceException $exception) {
199 1
            $exception = new RfcComplianceException($exception->getMessage());
200 1
            throw $exception;
201
        }
202
203 9
        return (new TemplatedEmail())
204 9
            ->to($toAddress)
205 9
            ->subject($subject)
206 9
            ->htmlTemplate('@SilverbackApiComponent/emails/' . $htmlTemplate)
207 9
            ->context(array_merge($defaultContext, $context));
208
    }
209
210 9
    private function send(RawMessage $message): void
211
    {
212
        try {
213 9
            $this->mailer->send($message);
214 1
        } catch (TransportExceptionInterface $exception) {
215 1
            $exception = new MailerTransportException($exception->getMessage());
216 1
            $exception->appendDebug($exception->getDebug());
217 1
            throw $exception;
218
        }
219 8
    }
220
221 7
    private function getEmailConfirmationUrl(AbstractUser $user, string $userUsername): string
222
    {
223 7
        $token = $user->getNewEmailVerificationToken();
224 7
        if (!$token) {
225 5
            throw new InvalidArgumentException('A new email verification token must be set to send the `email verification` email');
226
        }
227
228 2
        return $this->pathToReferrerUrl(
229 2
            $token,
230
            $userUsername,
231 2
            'verifyPath',
232 2
            $this->defaultChangeEmailVerifyPath
233
        );
234
    }
235
236 7
    private function pathToReferrerUrl(string $token, string $username, string $queryKey, string $defaultPath): string
237
    {
238 7
        $request = $this->requestStack->getMasterRequest();
239 7
        $path = $request ? $request->query->get($queryKey, $defaultPath) : $defaultPath;
240 7
        $path = str_replace(['{{ token }}', '{{ username }}'], [$token, $username], $path);
241
242 7
        return $this->refererUrlHelper->getAbsoluteUrl($path);
243
    }
244
}
245