Completed
Pull Request — master (#4187)
by Craig
04:32
created

MailHelper   A

Complexity

Total Complexity 37

Size/Duplication

Total Lines 280
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 113
dl 0
loc 280
rs 9.44
c 0
b 0
f 0
wmc 37

6 Methods

Rating   Name   Duplication   Size   Complexity  
B createAndSendRegistrationMail() 0 33 9
B generateEmailSubject() 0 22 7
B sendNotification() 0 44 6
B createAndSendUserMail() 0 34 8
B mailUsers() 0 31 6
A __construct() 0 14 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Zikula package.
7
 *
8
 * Copyright Zikula Foundation - 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\UsersModule\Helper;
15
16
use InvalidArgumentException;
17
use RuntimeException;
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\Component\Security\Core\Exception\AccessDeniedException;
23
use Symfony\Contracts\Translation\TranslatorInterface;
24
use Twig\Environment;
25
use Twig\Error\LoaderError;
26
use Zikula\ExtensionsModule\Api\ApiInterface\VariableApiInterface;
27
use Zikula\PermissionsModule\Api\ApiInterface\PermissionApiInterface;
28
use Zikula\UsersModule\Constant as UsersConstant;
29
use Zikula\UsersModule\Entity\UserEntity;
30
use Zikula\ZAuthModule\Entity\RepositoryInterface\AuthenticationMappingRepositoryInterface;
31
32
class MailHelper
33
{
34
    /**
35
     * @var TranslatorInterface
36
     */
37
    private $translator;
38
39
    /**
40
     * @var Environment
41
     */
42
    private $twig;
43
44
    /**
45
     * @var VariableApiInterface
46
     */
47
    private $variableApi;
48
49
    /**
50
     * @var MailerInterface
51
     */
52
    private $mailer;
53
54
    /**
55
     * @var PermissionApiInterface
56
     */
57
    private $permissionApi;
58
59
    /**
60
     * @var AuthenticationMappingRepositoryInterface
61
     */
62
    private $authenticationMappingRepository;
63
64
    public function __construct(
65
        TranslatorInterface $translator,
66
        Environment $twig,
67
        VariableApiInterface $variableApi,
68
        MailerInterface $mailer,
69
        PermissionApiInterface $permissionApi,
70
        AuthenticationMappingRepositoryInterface $authenticationMappingRepository
71
    ) {
72
        $this->translator = $translator;
73
        $this->twig = $twig;
74
        $this->variableApi = $variableApi;
75
        $this->mailer = $mailer;
76
        $this->permissionApi = $permissionApi;
77
        $this->authenticationMappingRepository = $authenticationMappingRepository;
78
    }
79
80
    /**
81
     * Creates a new registration mail.
82
     *
83
     * @param UserEntity $userEntity
84
     * @param bool   $userNotification       Whether the user should be notified of the new registration or not; however
85
     *                                       if the user's password was created for him, then he will receive at
86
     *                                       least that mail without regard to this setting
87
     * @param bool   $adminNotification      Whether the configured administrator mail e-mail address should be
88
     *                                       sent mail of the new registration
89
     * @param string $passwordCreatedForUser The password that was created for the user either automatically or by an
90
     *                                       administrator (but not by the user himself)
91
     *
92
     * @return array of errors created from the mail process
93
     *
94
     * @throws InvalidArgumentException Thrown if invalid parameters are received
95
     * @throws RuntimeException Thrown if the registration couldn't be saved
96
     */
97
    public function createAndSendRegistrationMail(
98
        UserEntity $userEntity,
99
        bool $userNotification = true,
100
        bool $adminNotification = true,
101
        string $passwordCreatedForUser = ''
102
    ): array {
103
        $mailErrors = [];
104
        $rendererArgs = [];
105
        $rendererArgs['reginfo'] = $userEntity;
106
        $rendererArgs['createdpassword'] = $passwordCreatedForUser;
107
        $rendererArgs['createdByAdmin'] = $this->permissionApi->hasPermission('ZikulaUsersModule::', '::', ACCESS_EDIT);
108
109
        if (!empty($passwordCreatedForUser) || ($userNotification && $userEntity->isApproved())) {
110
            $mailSent = $this->sendNotification($userEntity->getEmail(), 'welcome', $rendererArgs);
111
            if (!$mailSent) {
112
                $mailErrors[] = $this->translator->trans('Warning! The welcoming email for the new registration could not be sent.', [], 'mail');
113
            }
114
        }
115
        if ($adminNotification) {
116
            // mail notify email to inform admin about registration
117
            $notificationEmail = $this->variableApi->get('ZikulaUsersModule', UsersConstant::MODVAR_REGISTRATION_ADMIN_NOTIFICATION_EMAIL, '');
118
            if (!empty($notificationEmail)) {
119
                $authMapping = $this->authenticationMappingRepository->getByZikulaId($userEntity->getUid());
120
                $rendererArgs['isVerified'] = null === $authMapping || $authMapping->isVerifiedEmail();
121
122
                $mailSent = $this->sendNotification($notificationEmail, 'regadminnotify', $rendererArgs);
123
                if (!$mailSent) {
124
                    $mailErrors[] = $this->translator->trans('Warning! The notification email for the new registration could not be sent.', [], 'mail');
125
                }
126
            }
127
        }
128
129
        return $mailErrors;
130
    }
131
132
    /**
133
     * Creates a new users mail.
134
     *
135
     * @param UserEntity $userEntity
136
     * @param bool  $userNotification        Whether the user should be notified of the new registration or not;
137
     *                                       however if the user's password was created for him, then he will
138
     *                                       receive at least that mail without regard to this setting
139
     * @param bool $adminNotification        Whether the configured administrator mail e-mail address should
140
     *                                       be sent mail of the new registration
141
     * @param string $passwordCreatedForUser The password that was created for the user either automatically or by
142
     *                                       an administrator (but not by the user himself)
143
     *
144
     * @return array of mail errors
145
     *
146
     * @throws InvalidArgumentException Thrown if invalid parameters are received
147
     * @throws AccessDeniedException Thrown if the current user does not have overview access
148
     * @throws RuntimeException Thrown if the user couldn't be added to the relevant user groups or
149
     *                                  if the registration couldn't be saved
150
     */
151
    public function createAndSendUserMail(
152
        UserEntity $userEntity,
153
        bool $userNotification = true,
154
        bool $adminNotification = true,
155
        string $passwordCreatedForUser = ''
156
    ): array {
157
        $mailErrors = [];
158
        $rendererArgs = [];
159
        $rendererArgs['reginfo'] = $userEntity;
160
        $rendererArgs['createdpassword'] = $passwordCreatedForUser;
161
        $rendererArgs['createdByAdmin'] = $this->permissionApi->hasPermission('ZikulaUsersModule::', '::', ACCESS_EDIT);
162
163
        if ($userNotification || !empty($passwordCreatedForUser)) {
164
            $mailSent = $this->sendNotification($userEntity->getEmail(), 'welcome', $rendererArgs);
165
            if (!$mailSent) {
166
                $mailErrors[] = $this->translator->trans('Warning! The welcoming email for the newly created user could not be sent.', [], 'mail');
167
            }
168
        }
169
        if ($adminNotification) {
170
            // mail notify email to inform admin about registration
171
            $notificationEmail = $this->variableApi->get('ZikulaUsersModule', UsersConstant::MODVAR_REGISTRATION_ADMIN_NOTIFICATION_EMAIL, '');
172
            if (!empty($notificationEmail)) {
173
                $authMapping = $this->authenticationMappingRepository->getByZikulaId($userEntity->getUid());
174
                $rendererArgs['isVerified'] = null === $authMapping || $authMapping->isVerifiedEmail();
175
176
                $subject = $this->translator->trans('New registration: %userName%', ['%userName%' => $userEntity->getUname()]);
177
                $mailSent = $this->sendNotification($notificationEmail, 'regadminnotify', $rendererArgs, $subject);
178
                if (!$mailSent) {
179
                    $mailErrors[] = $this->translator->trans('Warning! The notification email for the newly created user could not be sent.', [], 'mail');
180
                }
181
            }
182
        }
183
184
        return $mailErrors;
185
    }
186
187
    /**
188
     * Send same mail to selected user(s). If more than one user, BCC and batch size are used.
189
     *
190
     * @param UserEntity[] $users
191
     * @param array $messageData
192
     *  required keys
193
     *      'replyto'
194
     *      'from'
195
     *      'message'
196
     *      'subject'
197
     *      'batchsize'
198
     *      'format'
199
     * @return bool
200
     */
201
    public function mailUsers(array $users, array $messageData): bool
202
    {
203
        $email = (new Email())
204
            ->from(new Address($messageData['replyto'], $messageData['from']))
205
            ->subject($messageData['subject'])
206
            ->html($messageData['message'])
207
        ;
208
        if (1 === count($users)) {
209
            $email->to(new Address($users[0]->getEmail(), $users[0]->getUname()));
210
        } else {
211
            $email->to(new Address($messageData['replyto'], $messageData['from']));
212
        }
213
        try {
214
            if (count($users) > 1) {
215
                $bcc = [];
216
                foreach ($users as $user) {
217
                    $bcc[] = $user->getEmail();
218
                    if (count($bcc) === $messageData['batchsize']) {
219
                        $email->bcc($bcc);
0 ignored issues
show
Bug introduced by
$bcc of type string[] is incompatible with the type Symfony\Component\Mime\Address|string expected by parameter $addresses of Symfony\Component\Mime\Email::bcc(). ( Ignorable by Annotation )

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

219
                        $email->bcc(/** @scrutinizer ignore-type */ $bcc);
Loading history...
220
                        $this->mailer->send($email);
221
                        $bcc = [];
222
                    }
223
                }
224
                $email->bcc($bcc);
0 ignored issues
show
Bug introduced by
$bcc of type array|string[] is incompatible with the type Symfony\Component\Mime\Address|string expected by parameter $addresses of Symfony\Component\Mime\Email::bcc(). ( Ignorable by Annotation )

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

224
                $email->bcc(/** @scrutinizer ignore-type */ $bcc);
Loading history...
225
            }
226
            $this->mailer->send($email);
227
        } catch (TransportExceptionInterface $exception) {
228
            return false;
229
        }
230
231
        return true;
232
    }
233
234
    /**
235
     * Sends a notification e-mail of a specified type to a user or registrant.
236
     *
237
     * @param string $toAddress The destination e-mail address
238
     * @param string $notificationType The type of notification, converted to the name of a template
239
     *                                     in the form users_userapi_{type}mail.tpl and/or .txt
240
     * @param array $templateArgs One or more arguments to pass to the renderer for use in the template
241
     * @param string $subject The e-mail subject, overriding the template's subject
242
     * @return bool
243
     */
244
    public function sendNotification(
245
        string $toAddress,
246
        string $notificationType = '',
247
        array $templateArgs = [],
248
        string $subject = ''
249
    ): bool {
250
        $templateName = "@ZikulaUsersModule/Email/{$notificationType}.html.twig";
251
        try {
252
            $html = true;
253
            $htmlBody = $this->twig->render($templateName, $templateArgs);
254
        } catch (LoaderError $e) {
255
            $html = false;
256
            $htmlBody = '';
257
        }
258
259
        $templateName = "@ZikulaUsersModule/Email/{$notificationType}.txt.twig";
260
        try {
261
            $textBody = $this->twig->render($templateName, $templateArgs);
262
        } catch (LoaderError $e) {
263
            $textBody = '';
264
        }
265
266
        if (empty($subject)) {
267
            $subject = $this->generateEmailSubject($notificationType, $templateArgs);
268
        }
269
270
        $siteName = $this->variableApi->getSystemVar('sitename', $this->variableApi->getSystemVar('sitename_en'));
271
272
        $email = (new Email())
273
            ->from(new Address($this->variableApi->getSystemVar('adminmail'), $siteName))
274
            ->to($toAddress)
275
            ->subject($subject)
276
            ->text($textBody)
277
        ;
278
        if ($html) {
279
            $email->html($htmlBody);
280
        }
281
        try {
282
            $this->mailer->send($email);
283
        } catch (TransportExceptionInterface $exception) {
284
            return false;
285
        }
286
287
        return true;
288
    }
289
290
    private function generateEmailSubject(string $notificationType, array $templateArgs = []): string
291
    {
292
        $siteName = $this->variableApi->getSystemVar('sitename');
293
        switch ($notificationType) {
294
            case 'regadminnotify':
295
                if (!$templateArgs['reginfo']->isApproved()) {
296
                    return $this->translator->trans('New registration pending approval: %userName%', ['%userName%' => $templateArgs['reginfo']['uname']], 'mail');
297
                }
298
                if (isset($templateArgs['isVerified']) && !$templateArgs['isVerified']) {
299
                    return $this->translator->trans('New registration pending email verification: %userName%', ['%userName%' => $templateArgs['reginfo']['uname']], 'mail');
300
                }
301
302
                return $this->translator->trans('New user activated: %userName%', ['%userName%' => $templateArgs['reginfo']['uname']], 'mail');
303
304
            case 'regdeny':
305
                return $this->translator->trans('Your recent request at %siteName%.', ['%siteName%' => $siteName], 'mail');
306
307
            case 'welcome':
308
                return $this->translator->trans('Welcome to %siteName%, %userName%!', ['%siteName%' => $siteName, '%userName%' => $templateArgs['reginfo']['uname']], 'mail');
309
310
            default:
311
                return $this->translator->trans('A message from %siteName%.', ['%siteName%' => $siteName], 'mail');
312
        }
313
    }
314
}
315