Passed
Push — hypernext ( 7e5acc...403616 )
by Nico
11:23
created

Email::getAllEmails()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 18
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 12
nc 8
nop 1
dl 0
loc 18
ccs 0
cts 5
cp 0
crap 20
rs 9.8666
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Nicolas CARPi <[email protected]>
4
 * @copyright 2012 Nicolas CARPi
5
 * @see https://www.elabftw.net Official website
6
 * @license AGPL-3.0
7
 * @package elabftw
8
 */
9
declare(strict_types=1);
10
11
namespace Elabftw\Services;
12
13
use function array_column;
14
use function count;
15
use Defuse\Crypto\Crypto;
16
use Defuse\Crypto\Key;
17
use Elabftw\Elabftw\Db;
18
use Elabftw\Elabftw\Tools;
19
use Elabftw\Exceptions\ImproperActionException;
20
use Elabftw\Models\Config;
21
use Elabftw\Models\Users;
22
use PDO;
23
use function rtrim;
24
use Swift_Mailer;
25
use Swift_Message;
26
use Swift_SendmailTransport;
27
use Swift_SmtpTransport;
28
use Symfony\Component\HttpFoundation\Request;
29
30
/**
31
 * Email service
32
 */
33
class Email
34
{
35
    public string $footer;
36
37
    public function __construct(private Config $Config, private Users $Users)
38
    {
39
        $this->footer = $this->makeFooter();
40
    }
41
42
    /**
43
     * Send an email
44 53
     *
45
     * @throws ImproperActionException
46 53
     * @return int number of email sent
47 53
     */
48 53
    public function send(Swift_Message $message): int
49
    {
50
        $mailer = $this->getMailer();
51
        $res = $mailer->send($message);
52
        if ($res === 0) {
53
            throw new ImproperActionException('Could not send email!');
54
        }
55
        return $res;
56
    }
57
58
    /**
59
     * Send a test email
60
     *
61
     * @throws ImproperActionException
62
     */
63
    public function testemailSend(string $email): bool
64
    {
65
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
66
            throw new ImproperActionException('Bad email!');
67
        }
68
69
        $message = (new Swift_Message())
70
        // Give the message a subject
71
        ->setSubject(_('[eLabFTW] Test email'))
72
        // Set the From address with an associative array
73
        ->setFrom(array($this->Config->configArr['mail_from'] => 'eLabFTW'))
74
        // Set the To addresses with an associative array
75
        ->setTo(array($email => 'Admin eLabFTW'))
76
        // Give it a body
77
        ->setBody('Congratulations, you correctly configured eLabFTW to send emails! :)' . $this->footer);
78
79
        return (bool) $this->send($message);
80
    }
81
82
    /**
83
     * Send a mass email to all users
84
     *
85
     * @return int number of emails sent
86
     */
87
    public function massEmail(string $subject, string $body, bool $teamFilter = false): int
88
    {
89
        if (empty($subject)) {
90
            $subject = 'No subject';
91
        }
92
93
        // set from
94
        if ($teamFilter) {
95
            $from = array($this->Users->userData['email'] => $this->Users->userData['fullname']);
96
        } else {
97
            $from = array($this->Config->configArr['mail_from'] => 'eLabFTW');
98
        }
99
100
        // get all email addresses
101
        $emails = $this->getAllEmails($teamFilter);
102
103
        $message = (new Swift_Message())
104
        ->setSubject($subject)
105
        ->setFrom($from)
106
        ->setTo($from)
107
        // Set recipients in BCC to protect email addresses
108
        ->setBcc($emails)
109
        ->setBody($body . $this->footer);
110
111
        return $this->send($message);
112
    }
113
114
    /**
115
     * Send an email to the admin of a team
116
     *
117
     * @param array<string, mixed> $userInfo to get the email and name of new user
118
     */
119
    public function alertAdmin(int $team, array $userInfo, bool $needValidation = true): void
120
    {
121
        if ($this->Config->configArr['mail_from'] === '[email protected]') {
122
            return;
123
        }
124
        // now let's get the URL so we can have a nice link in the email
125
        $Request = Request::createFromGlobals();
126
        $url = rtrim(Tools::getUrl($Request), '/') . '/admin.php';
127
128
        // Create the message
129
        $main = sprintf(
130
            _('Hi. A new user registered an account on eLabFTW: %s (%s).'),
131
            $userInfo['name'],
132
            $userInfo['email'],
133
        );
134
        if ($needValidation) {
135
            $main .= ' ' . sprintf(
136
                _('Head to the admin panel to validate the account: %s'),
137
                $url,
138
            );
139
        }
140
141
        $message = (new Swift_Message())
142
        // Give the message a subject
143
        ->setSubject(_('[eLabFTW] New user registered'))
144
        // Set the From address with an associative array
145
        ->setFrom(array($this->Config->configArr['mail_from'] => 'eLabFTW'))
146
        // Set the To
147
        ->setTo($this->getAdminEmail($team))
148
        // Give it a body
149
        ->setBody($main . $this->footer);
150
        // SEND EMAIL
151
        $this->send($message);
152
    }
153
154
    /**
155
     * Send an email to a new user to notify that admin validation is required.
156
     * This exists because experience shows that users don't read the notification and expect
157
     * their account to work right away.
158
     *
159
     * @param string $email email of the user to notify
160
     */
161
    public function alertUserNeedValidation($email): void
162
    {
163
        if ($this->Config->configArr['mail_from'] === '[email protected]') {
164
            return;
165
        }
166
        // Create the message
167
        $message = (new Swift_Message())
168
        // Give the message a subject
169
        ->setSubject(_('[eLabFTW] Your account has been created'))
170
        // Set the From address with an associative array
171
        ->setFrom(array($this->Config->configArr['mail_from'] => 'eLabFTW'))
172
        // Set the To
173
        ->setTo($email)
174
        // Give it a body
175
        ->setBody(_('Hi. Your account has been created but it is currently inactive (you cannot log in). The team admin has been notified and will validate your account. You will receive an email when it is done.') . $this->footer);
176
        // SEND EMAIL
177
        $this->send($message);
178
    }
179
180
    /**
181
     * Alert a user that they are validated
182
     *
183
     * @param string $email email of the newly validated user
184
     */
185
    public function alertUserIsValidated($email): void
186
    {
187
        if ($this->Config->configArr['mail_from'] === '[email protected]') {
188
            return;
189
        }
190
191
        // now let's get the URL so we can have a nice link in the email
192
        $Request = Request::createFromGlobals();
193
        $url = \rtrim(Tools::getUrl($Request), '/') . '/login.php';
194
195
        // Create the message
196
        $message = (new Swift_Message())
197
        // Give the message a subject
198
        // no i18n here
199
        ->setSubject('[eLabFTW] Account validated')
200
        // Set the From address with an associative array
201
        ->setFrom(array($this->Config->configArr['mail_from'] => 'eLabFTW'))
202
        // Set the To addresses with an associative array
203
        ->setTo(array($email => 'eLabFTW'))
204
        // Give it a body
205
        ->setBody(_('Hello. Your account on eLabFTW was validated by an admin. Follow this link to login: ') . $url . $this->footer);
206
        // now we try to send the email
207
        $this->send($message);
208
    }
209
210
    /**
211
     * Get email for all active users
212
     */
213
    private function getAllEmails(bool $fromTeam = false): array
214
    {
215
        $Db = Db::getConnection();
216
        $sql = 'SELECT email, teams_id FROM users CROSS JOIN users2teams ON (users2teams.users_id = users.userid) WHERE validated = 1 AND archived = 0';
217
        if ($fromTeam) {
218
            $sql .= ' AND users2teams.teams_id = :team';
219
        }
220
        $req = $Db->prepare($sql);
221
        if ($fromTeam) {
222
            $req->bindParam(':team', $this->Users->userData['team'], PDO::PARAM_INT);
223
        }
224
        $Db->execute($req);
225
226
        $res = $req->fetchAll();
227
        if ($res === false) {
228
            return array();
229
        }
230
        return array_column($res, 'email');
231
    }
232
233
    private function makeFooter(): string
234
    {
235
        $url = Tools::getUrl(Request::createFromGlobals());
236
        return sprintf("\n\n~~~\n%s %s\n", _('Sent from eLabFTW'), $url);
237
    }
238
239
    /**
240
     * Fetch the email(s) of the admin(s) for a team
241
     *
242
     * @param int $team
243
     *
244
     * @return scalar[]
245
     */
246
    private function getAdminEmail($team): array
247
    {
248
        // array for storing email addresses of admin(s)
249
        $arr = array();
250
        $Db = Db::getConnection();
251
252
        $sql = 'SELECT email FROM users
253
             CROSS JOIN users2teams ON (users2teams.users_id = users.userid AND users2teams.teams_id = :team)
254
             WHERE (`usergroup` = 1 OR `usergroup` = 2 OR `usergroup` = 3)';
255
        $req = $Db->prepare($sql);
256
        $req->bindParam(':team', $team, PDO::PARAM_INT);
257
        $req->execute();
258
259
        while ($email = $req->fetchColumn()) {
260
            $arr[] = (string) $email;
261
        }
262
263
        // if we have only one admin, we need to have an associative array
264
        if (count($arr) === 1) {
265
            return array($arr[0] => 'Admin eLabFTW');
266
        }
267
268
        return $arr;
269
    }
270
271
    /**
272
     * Return Swift_Mailer instance and choose between sendmail and smtp
273
     */
274
    private function getMailer(): Swift_Mailer
275
    {
276
277
        // Choose mail transport method; either smtp or sendmail
278
        if ($this->Config->configArr['mail_method'] === 'smtp') {
279
            if ($this->Config->configArr['smtp_encryption'] === 'none') {
280
                $transport = new Swift_SmtpTransport(
281
                    $this->Config->configArr['smtp_address'],
282
                    $this->Config->configArr['smtp_port']
283
                );
284
            } else {
285
                $transport = new Swift_SmtpTransport(
286
                    $this->Config->configArr['smtp_address'],
287
                    $this->Config->configArr['smtp_port'],
288
                    $this->Config->configArr['smtp_encryption']
289
                );
290
            }
291
292
            if ($this->Config->configArr['smtp_password']) {
293
                $transport->setUsername($this->Config->configArr['smtp_username'])
294
                ->setPassword(Crypto::decrypt(
295
                    $this->Config->configArr['smtp_password'],
296
                    Key::loadFromAsciiSafeString(\SECRET_KEY)
0 ignored issues
show
Bug introduced by
The constant SECRET_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
297
                ));
298
            }
299
        } else {
300
            // Use locally installed MTA (aka sendmail); Default
301
            $transport = new Swift_SendmailTransport($this->Config->configArr['sendmail_path'] . ' -bs');
302
        }
303
304
        return new Swift_Mailer($transport);
305
    }
306
}
307