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) |
|
|
|
|
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
|
|
|
|