]> git.agnieray.net Git - galette.git/blob - galette/lib/Galette/Core/GaletteMail.php
Merge branch 'hotfix/1.0.3'
[galette.git] / galette / lib / Galette / Core / GaletteMail.php
1 <?php
2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6 * Generic email for Galette
7 *
8 * PHP version 5
9 *
10 * Copyright © 2009-2023 The Galette Team
11 *
12 * This file is part of Galette (http://galette.tuxfamily.org).
13 *
14 * Galette is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation, either version 3 of the License, or
17 * (at your option) any later version.
18 *
19 * Galette is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with Galette. If not, see <http://www.gnu.org/licenses/>.
26 *
27 * @category Core
28 * @package Galette
29 *
30 * @author Johan Cwiklinski <johan@x-tnd.be>
31 * @copyright 2009-2023 The Galette Team
32 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
33 * @link http://galette.tuxfamily.org
34 * @since Available since 0.7dev - 2009-12-10
35 */
36
37 namespace Galette\Core;
38
39 use Throwable;
40 use Analog\Analog;
41 use PHPMailer\PHPMailer\PHPMailer;
42
43 /**
44 * Generic email for Galette
45 *
46 * @category Core
47 * @name GaletteMail
48 * @package Galette
49 * @author Johan Cwiklinski <johan@x-tnd.be>
50 * @copyright 2009-2023 The Galette Team
51 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
52 * @link http://galette.tuxfamily.org
53 * @since Available since 0.7dev - 2009-03-07
54 */
55 class GaletteMail
56 {
57 public const MAIL_ERROR = 0;
58 public const MAIL_SENT = 1;
59
60 public const METHOD_DISABLED = 0;
61 public const METHOD_PHPMAIL = 1;
62 public const METHOD_SMTP = 2;
63 public const METHOD_QMAIL = 3;
64 public const METHOD_GMAIL = 4;
65 public const METHOD_SENDMAIL = 5;
66
67 public const SENDER_PREFS = 0;
68 public const SENDER_CURRENT = 1;
69 public const SENDER_OTHER = 2;
70
71 private $sender_name;
72 private $sender_address;
73 private $subject;
74 private $message;
75 private $html;
76 private $word_wrap = 70;
77 private $timeout = 300;
78
79 private $errors = array();
80 private $recipients = array();
81
82 private $mail = null;
83 protected $attachments = array();
84
85 private $preferences;
86
87 /**
88 * Constructor
89 *
90 * @param Preferences $preferences Preferences instance
91 */
92 public function __construct(Preferences $preferences)
93 {
94 $this->preferences = $preferences;
95 $this->setSender(
96 $preferences->pref_email_nom,
97 $preferences->pref_email
98 );
99 }
100
101 /**
102 * Initialize PHPMailer
103 *
104 * @return void
105 */
106 private function initMailer()
107 {
108 global $i18n;
109
110 $this->mail = new PHPMailer();
111 $this->mail->Timeout = $this->timeout;
112
113 switch ($this->preferences->pref_mail_method) {
114 case self::METHOD_SMTP:
115 case self::METHOD_GMAIL:
116 //if we want to send emails using a smtp server
117 $this->mail->IsSMTP();
118 // enables SMTP debug information
119 if (Galette::isDebugEnabled()) {
120 $this->mail->SMTPDebug = 4;
121 //cannot use a callable here; this prevents class to be serialized
122 //see https://bugs.galette.eu/issues/1468
123 $this->mail->Debugoutput = 'error_log';
124 }
125
126 if ($this->preferences->pref_mail_method == self::METHOD_GMAIL) {
127 // sets GMAIL as the SMTP server
128 $this->mail->Host = "smtp.gmail.com";
129 // enable SMTP authentication
130 $this->mail->SMTPAuth = true;
131 // sets the prefix to the servier
132 $this->mail->SMTPSecure = "tls";
133 // set the SMTP port for the GMAIL server
134 $this->mail->Port = 587;
135 } else {
136 $this->mail->Host = $this->preferences->pref_mail_smtp_host;
137 $this->mail->SMTPAuth = $this->preferences->pref_mail_smtp_auth;
138
139 if (!$this->preferences->pref_mail_smtp_secure || $this->preferences->pref_mail_allow_unsecure) {
140 //Allow "unsecure" SMTP connections if user has asked fot it or
141 //if user did not request TLS explicitely
142 $this->mail->SMTPOptions = array(
143 'ssl' => array(
144 'verify_peer' => false,
145 'verify_peer_name' => false,
146 'allow_self_signed' => true
147 )
148 );
149 }
150
151 if (
152 $this->preferences->pref_mail_smtp_port
153 && $this->preferences->pref_mail_smtp_port != ''
154 ) {
155 // set the SMTP port for the SMTP server
156 $this->mail->Port = $this->preferences->pref_mail_smtp_port;
157 } else {
158 Analog::log(
159 '[' . get_class($this) .
160 ']No SMTP port provided. Switch to default (25).',
161 Analog::INFO
162 );
163 $this->mail->Port = $this->preferences->pref_mail_smtp_secure ? 587 : 25;
164 }
165
166 if ($this->preferences->pref_mail_smtp_secure && $this->mail->Port == 465) {
167 $this->mail->SMTPSecure = "ssl";
168 } elseif ($this->preferences->pref_mail_smtp_secure && $this->mail->Port == 587) {
169 $this->mail->SMTPSecure = "tls";
170 }
171 }
172
173 // SMTP account username
174 $this->mail->Username = $this->preferences->pref_mail_smtp_user;
175 // SMTP account password
176 $this->mail->Password = $this->preferences->pref_mail_smtp_password;
177 break;
178 case self::METHOD_SENDMAIL:
179 // telling the class to use Sendmail transport
180 $this->mail->IsSendmail();
181 break;
182 case self::METHOD_QMAIL:
183 // telling the class to use QMail transport
184 $this->mail->IsQmail();
185 break;
186 }
187
188 $this->mail->CharSet = 'UTF-8';
189 $this->mail->SetLanguage($i18n->getAbbrev());
190
191 if ($this->preferences->pref_bool_wrap_mails) {
192 $this->mail->WordWrap = $this->word_wrap;
193 } else {
194 $this->word_wrap = 0;
195 }
196 }
197
198 /**
199 * Sets the recipients
200 * For mailing convenience, all recipients will be added as BCC,
201 * regular recipient will be the sender.
202 *
203 * @param array $recipients Array (mail=>name) of all recipients
204 *
205 * @return bool
206 */
207 public function setRecipients($recipients)
208 {
209 $res = true;
210
211 if ($this->mail === null) {
212 $this->initMailer();
213 }
214
215 if (!empty($recipients)) {
216 $this->recipients = array();
217 foreach ($recipients as $mail => $name) {
218 if (self::isValidEmail($mail)) {
219 $this->recipients[$mail] = $name;
220 $this->mail->AddBCC($mail, $name);
221 } else {
222 //one of addresses is not valid :
223 //- set $res to false
224 //- clear BCCs
225 //- log an INFO
226 $res = false;
227 Analog::log(
228 '[' . get_class($this) .
229 '] One of recipients address is not valid.',
230 Analog::INFO
231 );
232 $this->mail->ClearBCCs();
233 break;
234 }
235 }
236 } else {
237 $this->mail->ClearBCCs();
238 }
239
240 return $res;
241 }
242
243 /**
244 * Apply final header to email and send it :-)
245 *
246 * @return integer Either GaletteMail::MAIL_ERROR|GaletteMail::MAIL_SENT
247 */
248 public function send()
249 {
250 if ($this->mail === null) {
251 $this->initMailer();
252 }
253
254 //set sender
255 $this->mail->SetFrom(
256 $this->getSenderAddress(),
257 $this->getSenderName()
258 );
259 // Add a Reply-To field in the email headers.
260 // Fix bug #6654.
261 if ($this->preferences->pref_email_reply_to) {
262 $this->mail->AddReplyTo($this->preferences->pref_email_reply_to);
263 } else {
264 $this->mail->AddReplyTo($this->getSenderAddress());
265 }
266
267
268 if ($this->html) {
269 //the email is html :(
270 $this->mail->AltBody = $this->cleanedHtml();
271 $this->mail->IsHTML(true);
272 } else {
273 //the email is plaintext :)
274 $this->mail->AltBody = '';
275 $this->mail->IsHTML(false);
276 }
277
278 $this->mail->Subject = $this->subject;
279 $this->mail->Body = $this->message;
280
281 //set at least on real recipient (not bcc)
282 if (count($this->recipients) === 1) {
283 //there is only one recipient, clean bcc and readd as simple recipient
284 $this->mail->ClearBCCs();
285 $this->mail->AddAddress(
286 key($this->recipients),
287 current($this->recipients)
288 );
289 } else {
290 //we're sending a mailing. Set main recipient to sender
291 $this->mail->AddAddress(
292 $this->getSenderAddress(),
293 $this->getSenderName()
294 );
295 }
296
297 $signature = $this->preferences->getMailSignature($this->mail);
298 if ($signature != '') {
299 if ($this->html) {
300 //we are sending html message
301 //apply email sign to text version
302 $this->mail->AltBody .= $signature;
303 //then apply email sign to html version
304 $sign_style = 'color:grey;border-top:1px solid #ccc;margin-top:2em';
305 $hsign = '<div style="' . $sign_style . '">' .
306 nl2br($signature) . '</div>';
307 $this->mail->Body .= $hsign;
308 } else {
309 $this->mail->Body .= $signature;
310 }
311 }
312
313 //join attachments
314 if (count($this->attachments) > 0) {
315 foreach ($this->attachments as $attachment) {
316 $this->mail->AddAttachment(
317 $attachment->getDestDir() . $attachment->getFileName()
318 );
319 }
320 }
321
322 try {
323 //reinit errors array
324 $this->errors = array();
325 //let's send the email
326 if (!$this->mail->Send()) {
327 $this->errors[] = $this->mail->ErrorInfo;
328 Analog::log(
329 'An error occurred sending email to: ' .
330 implode(', ', array_keys($this->recipients)) .
331 "\n" . $this->mail->ErrorInfo,
332 Analog::INFO
333 );
334 $this->mail = null;
335 return self::MAIL_ERROR;
336 } else {
337 $txt = '';
338 foreach ($this->recipients as $k => $v) {
339 $txt .= $v . ' (' . $k . '), ';
340 }
341 Analog::log(
342 'An email has been sent to: ' . $txt,
343 Analog::INFO
344 );
345 $this->mail = null;
346 return self::MAIL_SENT;
347 }
348 } catch (Throwable $e) {
349 Analog::log(
350 'Error sending message: ' . $e->getMessage(),
351 Analog::ERROR
352 );
353 $this->errors[] = $e->getMessage();
354 $this->mail = null;
355 return self::MAIL_ERROR;
356 }
357 }
358
359 /**
360 * Check if an email address is valid
361 *
362 * @param string $address the email address to check
363 *
364 * @return bool
365 */
366 public static function isValidEmail($address)
367 {
368 $valid = PHPMailer::ValidateAddress($address);
369 if (!$valid) {
370 Analog::log(
371 '[GaletteMail] Address `' . $address . '` is not valid ',
372 Analog::DEBUG
373 );
374 }
375 return $valid;
376 }
377
378 /**
379 * Check if a string is an url
380 *
381 * @param string $url the url to check
382 *
383 * @return bool
384 */
385 public static function isUrl($url)
386 {
387 $valid = preg_match(
388 '|^http(s)?://[a-z0-9-]+(.[a-z0-9-]+)*(:[0-9]+)?(/.*)?$|i',
389 $url
390 );
391 if (!$valid) {
392 Analog::log(
393 '[GaletteMail] `' . $url . '` is not an url',
394 Analog::DEBUG
395 );
396 }
397 return $valid;
398 }
399
400 /**
401 * Clean a string embedding html, producing AltText for html emails
402 *
403 * @return string current message in plaintext format
404 */
405 protected function cleanedHtml()
406 {
407 $html = $this->message;
408 $txt = \Soundasleep\Html2Text::convert($html);
409 return $txt;
410 }
411
412 /**
413 * Retrieve PHPMailer main object
414 *
415 * @return PHPMailer object
416 */
417 protected function getPhpMailer()
418 {
419 return $this->mail;
420 }
421
422 /**
423 * Is the email HTML formatted?
424 *
425 * @param boolean $set The value to set
426 *
427 * @return bool
428 */
429 public function isHTML($set = null)
430 {
431 if (is_bool($set)) {
432 $this->html = $set;
433 }
434 return $this->html;
435 }
436
437 /**
438 * Get sender name
439 *
440 * @return string
441 */
442 public function getSenderName()
443 {
444 return $this->sender_name;
445 }
446
447 /**
448 * Get sender address
449 *
450 * @return string
451 */
452 public function getSenderAddress()
453 {
454 return $this->sender_address;
455 }
456
457 /**
458 * Get the subject
459 *
460 * @return string The subject
461 */
462 public function getSubject()
463 {
464 return $this->subject;
465 }
466
467 /**
468 * Retrieve array of errors
469 *
470 * @return array
471 */
472 public function getErrors()
473 {
474 return $this->errors;
475 }
476
477 /**
478 * Get the message
479 *
480 * @return string The message
481 */
482 public function getMessage()
483 {
484 return $this->message;
485 }
486
487 /**
488 * Get the message, wrapped
489 *
490 * @return string Wrapped message
491 */
492 public function getWrappedMessage()
493 {
494 if ($this->word_wrap > 0) {
495 if ($this->mail === null) {
496 $this->initMailer();
497 }
498
499 return $this->mail->wrapText(
500 $this->message,
501 $this->word_wrap
502 );
503 } else {
504 return $this->message;
505 }
506 }
507
508 /**
509 * Sets the subject
510 *
511 * @param string $subject The subject
512 *
513 * @return GaletteMail
514 */
515 public function setSubject($subject)
516 {
517 $this->subject = $subject;
518 return $this;
519 }
520
521 /**
522 * Sets the message
523 *
524 * @param string $message The message
525 *
526 * @return GaletteMail
527 */
528 public function setMessage($message)
529 {
530 $this->message = $message;
531 return $this;
532 }
533
534 /**
535 * Sets the sender
536 *
537 * @param string $name Sender name
538 * @param string $address Sender address
539 *
540 * @return GaletteMail
541 */
542 public function setSender($name, $address)
543 {
544 $this->sender_name = $name;
545 $this->sender_address = $address;
546 return $this;
547 }
548
549 /**
550 * Set timeout on SMTP connexion
551 *
552 * @param integer $timeout SMTP timeout
553 *
554 * @return GaletteMail
555 */
556 public function setTimeout($timeout)
557 {
558 $this->timeout = $timeout;
559 return $this;
560 }
561 }