MailHelper   A
last analyzed

Complexity

Total Complexity 39

Size/Duplication

Total Lines 253
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 113
dl 0
loc 253
rs 9.28
c 0
b 0
f 0
wmc 39

6 Methods

Rating   Name   Duplication   Size   Complexity  
B sendNotification() 0 53 7
B createAndSendRegistrationMail() 0 30 8
B generateEmailSubject() 0 22 7
B createAndSendUserMail() 0 33 8
A __construct() 0 10 1
B mailUsers() 0 45 8
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Zikula package.
7
 *
8
 * Copyright Zikula - https://ziku.la/
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Zikula\UsersBundle\Helper;
15
16
use Psr\Log\LoggerInterface;
17
use Symfony\Bundle\SecurityBundle\Security;
18
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
19
use Symfony\Component\Mailer\MailerInterface;
20
use Symfony\Component\Mime\Address;
21
use Symfony\Component\Mime\Email;
22
use Symfony\Contracts\Translation\TranslatorInterface;
23
use Twig\Environment;
24
use Twig\Error\LoaderError;
25
use Zikula\CoreBundle\Site\SiteDefinitionInterface;
26
use Zikula\UsersBundle\Entity\User;
27
28
class MailHelper
29
{
30
    public function __construct(
31
        private readonly TranslatorInterface $translator,
32
        private readonly Environment $twig,
33
        private readonly MailerInterface $mailer,
34
        private readonly LoggerInterface $mailLogger, // $mailLogger var name auto-injects the mail channel handler
35
        private readonly Security $security,
36
        private readonly SiteDefinitionInterface $site,
37
        private readonly ?string $registrationNotificationEmail,
38
        private readonly bool $mailLoggingEnabled
39
    ) {
40
    }
41
42
    /**
43
     * Creates a new registration mail.
44
     *
45
     * @param bool   $userNotification       Whether the user should be notified of the new registration or not; however
46
     *                                       if the user's password was created for him, then he will receive at
47
     *                                       least that mail without regard to this setting
48
     * @param bool   $adminNotification      Whether the configured administrator mail e-mail address should be
49
     *                                       sent mail of the new registration
50
     * @param string $passwordCreatedForUser The password that was created for the user either automatically or by an
51
     *                                       administrator (but not by the user himself)
52
     *
53
     * @return array of errors created from the mail process
54
     */
55
    public function createAndSendRegistrationMail(
56
        User $userEntity,
57
        bool $userNotification = true,
58
        bool $adminNotification = true,
59
        string $passwordCreatedForUser = ''
60
    ): array {
61
        $mailErrors = [];
62
        $rendererArgs = [];
63
        $rendererArgs['user'] = $userEntity;
64
        $rendererArgs['createdpassword'] = $passwordCreatedForUser;
65
        $rendererArgs['createdByAdmin'] = $this->security->isGranted('ROLE_ADMIN');
66
67
        if (!empty($passwordCreatedForUser) || ($userNotification && $userEntity->isApproved())) {
68
            $mailSent = $this->sendNotification($userEntity->getEmail(), 'welcome', $rendererArgs);
69
            if (!$mailSent) {
70
                $mailErrors[] = $this->translator->trans('Warning! The welcoming email for the new registration could not be sent.', [], 'mail');
71
            }
72
        }
73
        if ($adminNotification) {
74
            // send notification email to inform admin about registration
75
            if (!empty($this->registrationNotificationEmail)) {
76
                $rendererArgs['isVerified'] = false; // TODO replace ZAuth data
77
                $mailSent = $this->sendNotification($this->registrationNotificationEmail, 'regadminnotify', $rendererArgs);
78
                if (!$mailSent) {
79
                    $mailErrors[] = $this->translator->trans('Warning! The notification email for the new registration could not be sent.', [], 'mail');
80
                }
81
            }
82
        }
83
84
        return $mailErrors;
85
    }
86
87
    /**
88
     * Creates a new users mail.
89
     *
90
     * @param bool  $userNotification        Whether the user should be notified of the new registration or not;
91
     *                                       however if the user's password was created for him, then he will
92
     *                                       receive at least that mail without regard to this setting
93
     * @param bool $adminNotification        Whether the configured administrator mail e-mail address should
94
     *                                       be sent mail of the new registration
95
     * @param string $passwordCreatedForUser The password that was created for the user either automatically or by
96
     *                                       an administrator (but not by the user himself)
97
     *
98
     * @return array of mail errors
99
     */
100
    public function createAndSendUserMail(
101
        User $userEntity,
102
        bool $userNotification = true,
103
        bool $adminNotification = true,
104
        string $passwordCreatedForUser = ''
105
    ): array {
106
        $mailErrors = [];
107
        $rendererArgs = [];
108
        $rendererArgs['user'] = $userEntity;
109
        $rendererArgs['createdpassword'] = $passwordCreatedForUser;
110
        $rendererArgs['createdByAdmin'] = $this->security->isGranted('ROLE_ADMIN');
111
112
        if ($userNotification || !empty($passwordCreatedForUser)) {
113
            $mailSent = $this->sendNotification($userEntity->getEmail(), 'welcome', $rendererArgs);
114
            if (!$mailSent) {
115
                $mailErrors[] = $this->translator->trans('Warning! The welcoming email for the newly created user could not be sent.', [], 'mail');
116
            }
117
        }
118
        if ($adminNotification) {
119
            // send notification email to inform admin about registration
120
            if (!empty($this->registrationNotificationEmail)) {
121
                $authMapping = $this->authenticationMappingRepository->getByZikulaId($userEntity->getUid());
0 ignored issues
show
Bug Best Practice introduced by
The property authenticationMappingRepository does not exist on Zikula\UsersBundle\Helper\MailHelper. Did you maybe forget to declare it?
Loading history...
Bug introduced by
The method getUid() does not exist on Zikula\UsersBundle\Entity\User. ( Ignorable by Annotation )

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

121
                $authMapping = $this->authenticationMappingRepository->getByZikulaId($userEntity->/** @scrutinizer ignore-call */ getUid());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
122
                $rendererArgs['isVerified'] = null === $authMapping || $authMapping->isVerifiedEmail();
123
124
                $subject = $this->translator->trans('New registration: %userName%', ['%userName%' => $userEntity->getUname()]);
0 ignored issues
show
Bug introduced by
The method getUname() does not exist on Zikula\UsersBundle\Entity\User. ( Ignorable by Annotation )

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

124
                $subject = $this->translator->trans('New registration: %userName%', ['%userName%' => $userEntity->/** @scrutinizer ignore-call */ getUname()]);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
125
                $mailSent = $this->sendNotification($this->registrationNotificationEmail, 'regadminnotify', $rendererArgs, $subject);
126
                if (!$mailSent) {
127
                    $mailErrors[] = $this->translator->trans('Warning! The notification email for the newly created user could not be sent.', [], 'mail');
128
                }
129
            }
130
        }
131
132
        return $mailErrors;
133
    }
134
135
    /**
136
     * Send same mail to selected user(s). If more than one user, BCC and batch size are used.
137
     *
138
     * @param User[] $users
139
     * @param array $messageData
140
     *  required keys
141
     *      'replyto'
142
     *      'from'
143
     *      'message'
144
     *      'subject'
145
     *      'batchsize'
146
     *      'format'
147
     */
148
    public function mailUsers(array $users, array $messageData): bool
149
    {
150
        $sender = new Address($messageData['replyto'], $messageData['from']);
151
        $email = (new Email())
152
            ->from($sender)
153
            ->subject($messageData['subject'])
154
            ->html($messageData['message'])
155
        ;
156
        if (1 === count($users)) {
157
            $email->to(new Address($users[0]->getEmail(), $users[0]->getUname()));
158
        } else {
159
            $email->to($sender);
160
        }
161
        try {
162
            if (1 < count($users)) {
163
                $bcc = [];
164
                foreach ($users as $user) {
165
                    if (!$user->getEmail()) {
166
                        continue;
167
                    }
168
                    $bcc[] = new Address($user->getEmail(), $user->getUname());
169
                    if (count($bcc) === $messageData['batchsize']) {
170
                        $email->bcc(...$bcc);
171
                        $this->mailer->send($email);
172
                        $bcc = [];
173
                    }
174
                }
175
                $email->bcc(...$bcc);
176
            }
177
            $this->mailer->send($email);
178
        } catch (TransportExceptionInterface $exception) {
179
            $this->mailLogger->error($exception->getMessage(), [
180
                'in' => __METHOD__
181
            ]);
182
183
            return false;
184
        }
185
        if ($this->mailLoggingEnabled) {
186
            $this->mailLogger->info(sprintf('Email sent to %s', 'multiple users'), [
187
                'in' => __METHOD__,
188
                'users' => array_reduce($users, function ($result, User $user) { return $result . $user->getEmail() . ','; }, 'emails: ')
189
            ]);
190
        }
191
192
        return true;
193
    }
194
195
    /**
196
     * Sends a notification e-mail of a specified type to a user or registrant.
197
     *
198
     * @param string $toAddress The destination e-mail address
199
     * @param string $notificationType The type of notification, converted to the name of a template
200
     *                                     in the form users_userapi_{type}mail.tpl and/or .txt
201
     * @param array $templateArgs One or more arguments to pass to the renderer for use in the template
202
     * @param string $subject The e-mail subject, overriding the template's subject
203
     */
204
    public function sendNotification(
205
        string $toAddress,
206
        string $notificationType = '',
207
        array $templateArgs = [],
208
        string $subject = ''
209
    ): bool {
210
        $templateName = "@ZikulaUsersBundle/Email/{$notificationType}.html.twig";
211
        try {
212
            $html = true;
213
            $htmlBody = $this->twig->render($templateName, $templateArgs);
214
        } catch (LoaderError $e) {
215
            $html = false;
216
            $htmlBody = '';
217
        }
218
219
        $templateName = "@ZikulaUsersBundle/Email/{$notificationType}.txt.twig";
220
        try {
221
            $textBody = $this->twig->render($templateName, $templateArgs);
222
        } catch (LoaderError $e) {
223
            $textBody = '';
224
        }
225
226
        if (empty($subject)) {
227
            $subject = $this->generateEmailSubject($notificationType, $templateArgs);
228
        }
229
230
        $email = (new Email())
231
            ->from(new Address($this->site->getAdminMail(), $this->site->getName()))
0 ignored issues
show
Bug introduced by
It seems like $this->site->getAdminMail() can also be of type null; however, parameter $address of Symfony\Component\Mime\Address::__construct() 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

231
            ->from(new Address(/** @scrutinizer ignore-type */ $this->site->getAdminMail(), $this->site->getName()))
Loading history...
232
            ->to($toAddress)
233
            ->subject($subject)
234
            ->text($textBody)
235
        ;
236
        if ($html) {
237
            $email->html($htmlBody);
238
        }
239
        try {
240
            $this->mailer->send($email);
241
        } catch (TransportExceptionInterface $exception) {
242
            $this->mailLogger->error($exception->getMessage(), [
243
                'in' => __METHOD__,
244
                'type' => $notificationType
245
            ]);
246
247
            return false;
248
        }
249
        if ($this->mailLoggingEnabled) {
250
            $this->mailLogger->info(sprintf('Email sent to %s', $toAddress), [
251
                'in' => __METHOD__,
252
                'type' => $notificationType
253
            ]);
254
        }
255
256
        return true;
257
    }
258
259
    private function generateEmailSubject(string $notificationType, array $templateArgs = []): string
260
    {
261
        $siteName = $this->site->getName();
262
        switch ($notificationType) {
263
            case 'regadminnotify':
264
                if (!$templateArgs['user']->isApproved()) {
265
                    return $this->translator->trans('New registration pending approval: %userName%', ['%userName%' => $templateArgs['user']['uname']], 'mail');
266
                }
267
                if (isset($templateArgs['isVerified']) && !$templateArgs['isVerified']) {
268
                    return $this->translator->trans('New registration pending email verification: %userName%', ['%userName%' => $templateArgs['user']['uname']], 'mail');
269
                }
270
271
                return $this->translator->trans('New user activated: %userName%', ['%userName%' => $templateArgs['user']['uname']], 'mail');
272
273
            case 'regdeny':
274
                return $this->translator->trans('Your recent request at %siteName%.', ['%siteName%' => $siteName], 'mail');
275
276
            case 'welcome':
277
                return $this->translator->trans('Welcome to %siteName%, %userName%!', ['%siteName%' => $siteName, '%userName%' => $templateArgs['user']['uname']], 'mail');
278
279
            default:
280
                return $this->translator->trans('A message from %siteName%.', ['%siteName%' => $siteName], 'mail');
281
        }
282
    }
283
}
284