Passed
Pull Request — dev (#8)
by Rafael
58:47
created

CMailFile::write_mimeheaders()   A

Complexity

Conditions 5
Paths 2

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 11
nc 2
nop 2
dl 0
loc 20
rs 9.6111
c 0
b 0
f 0
1
<?php
2
3
/* Copyright (C)            Dan Potter
4
 * Copyright (C)            Eric Seigne
5
 * Copyright (C) 2000-2005  Rodolphe Quiedeville        <[email protected]>
6
 * Copyright (C) 2003       Jean-Louis Bergamo          <[email protected]>
7
 * Copyright (C) 2004-2015  Laurent Destailleur         <[email protected]>
8
 * Copyright (C) 2005-2012  Regis Houssin               <[email protected]>
9
 * Copyright (C) 2019-2024  Frédéric France             <[email protected]>
10
 * Copyright (C) 2024       MDW                         <[email protected]>
11
 * Copyright (C) 2024       Rafael San José             <[email protected]>
12
 *
13
 * This program is free software; you can redistribute it and/or modify
14
 * it under the terms of the GNU General Public License as published by
15
 * the Free Software Foundation; either version 3 of the License, or
16
 * (at your option) any later version.
17
 *
18
 * This program is distributed in the hope that it will be useful,
19
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21
 * GNU General Public License for more details.
22
 *
23
 * You should have received a copy of the GNU General Public License
24
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
25
 * or see https://www.gnu.org/
26
 *
27
 * Lots of code inspired from Dan Potter's CMailFile class
28
 */
29
30
namespace Dolibarr\Code\Core\Classes;
31
32
/**
33
 *      \file       htdocs/core/class/CMailFile.class.php
34
 *      \brief      File of class to send emails (with attachments or not)
35
 */
36
37
use OAuth\Common\Storage\DoliStorage;
38
use OAuth\Common\Consumer\Credentials;
39
40
/**
41
 *  Class to send emails (with attachments or not)
42
 *  Usage: $mailfile = new CMailFile($subject,$sendto,$replyto,$message,$filepath,$mimetype,$filename,$cc,$ccc,$deliveryreceipt,$msgishtml,$errors_to,$css,$trackid,$moreinheader,$sendcontext,$replyto);
43
 *         $mailfile->sendfile();
44
 */
45
class CMailFile
46
{
47
    public $sendcontext;
48
    public $sendmode;
49
    public $sendsetup;
50
51
    /**
52
     * @var string Subject of email
53
     */
54
    public $subject;
55
    public $addr_from; // From:     Label and EMail of sender (must include '<>'). For example '<[email protected]>' or 'John Doe <[email protected]>' or '<[email protected]>'). Note that with gmail smtps, value here is forced by google to account (but not the reply-to).
56
    // Sender:      Who send the email ("Sender" has sent emails on behalf of "From").
57
    //              Use it when the "From" is an email of a domain that is a SPF protected domain, and the sending smtp server is not this domain. In such case, add Sender field with an email of the protected domain.
58
    // Return-Path: Email where to send bounds.
59
    public $reply_to; // Reply-To:  Email where to send replies from mailer software (mailer use From if reply-to not defined, Gmail use gmail account if reply-to not defined)
60
    public $errors_to; // Errors-To:    Email where to send errors.
61
    public $addr_to;
62
    public $addr_cc;
63
    public $addr_bcc;
64
    public $trackid;
65
66
    public $mixed_boundary;
67
    public $related_boundary;
68
    public $alternative_boundary;
69
    public $deliveryreceipt;
70
71
    public $atleastonefile;
72
73
    public $msg;
74
    public $eol;
75
    public $eol2;
76
77
    /**
78
     * @var string Error code (or message)
79
     */
80
    public $error = '';
81
82
    /**
83
     * @var string[] Array of Error code (or message)
84
     */
85
    public $errors = array();
86
87
88
    /**
89
     * @var SMTPS (if this method is used)
90
     */
91
    public $smtps;
92
    /**
93
     * @var Swift_Mailer (if the method is used)
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Core\Classes\Swift_Mailer was not found. Did you mean Swift_Mailer? If so, make sure to prefix the type with \.
Loading history...
94
     */
95
    public $mailer;
96
97
    /**
98
     * @var Swift_SmtpTransport
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Core\Classes\Swift_SmtpTransport was not found. Did you mean Swift_SmtpTransport? If so, make sure to prefix the type with \.
Loading history...
99
     */
100
    public $transport;
101
    /**
102
     * @var Swift_Plugins_Loggers_ArrayLogger
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Core\Class...ins_Loggers_ArrayLogger was not found. Did you mean Swift_Plugins_Loggers_ArrayLogger? If so, make sure to prefix the type with \.
Loading history...
103
     */
104
    public $logger;
105
106
    /**
107
     * @var string CSS
108
     */
109
    public $css;
110
    //! Defined css style for body background
111
    public $styleCSS;
112
    //! Defined background directly in body tag
113
    public $bodyCSS;
114
115
    /**
116
     * @var string  Message-ID of the email to send (generated)
117
     */
118
    public $msgid;
119
120
    /**
121
     * @var string  Value to use in In-reply-to when email is set as an answer of another email (The Msg-Id of received email)
122
     */
123
    public $in_reply_to;
124
125
    /**
126
     * @var string  References to add to the email to send (generated from the email we answer)
127
     */
128
    public $references;
129
130
    public $headers;
131
132
    public $message;
133
134
    /**
135
     * @var array fullfilenames list (full path of filename on file system)
136
     */
137
    public $filename_list = array();
138
    /**
139
     * @var array mimetypes of files list (List of MIME type of attached files)
140
     */
141
    public $mimetype_list = array();
142
    /**
143
     * @var array filenames list (List of attached file name in message)
144
     */
145
    public $mimefilename_list = array();
146
    /**
147
     * @var array filenames cid
148
     */
149
    public $cid_list = array();
150
151
    // Image
152
    public $html;
153
    public $msgishtml;
154
    public $image_boundary;
155
    public $atleastoneimage = 0; // at least one image file with file=xxx.ext into content (TODO Debug this. How can this case be tested. Remove if not used).
156
    public $html_images = array();
157
    public $images_encoded = array();
158
    public $image_types = array(
159
        'gif'  => 'image/gif',
160
        'jpg'  => 'image/jpeg',
161
        'jpeg' => 'image/jpeg',
162
        'jpe'  => 'image/jpeg',
163
        'bmp'  => 'image/bmp',
164
        'png'  => 'image/png',
165
        'tif'  => 'image/tiff',
166
        'tiff' => 'image/tiff',
167
    );
168
169
170
    /**
171
     *  CMailFile
172
     *
173
     *  @param  string  $subject             Topic/Subject of mail
174
     *  @param  string  $to                  Recipients emails (RFC 2822: "Name firstname <email>[, ...]" or "email[, ...]" or "<email>[, ...]"). Note: the keyword '__SUPERVISOREMAIL__' is not allowed here and must be replaced by caller.
175
     *  @param  string  $from                Sender email      (RFC 2822: "Name firstname <email>[, ...]" or "email[, ...]" or "<email>[, ...]")
176
     *  @param  string  $msg                 Message
177
     *  @param  array   $filename_list       List of files to attach (full path of filename on file system)
178
     *  @param  array   $mimetype_list       List of MIME type of attached files
179
     *  @param  array   $mimefilename_list   List of attached file name in message
180
     *  @param  string  $addr_cc             Email cc (Example: '[email protected], [email protected]')
181
     *  @param  string  $addr_bcc            Email bcc (Note: This is autocompleted with MAIN_MAIL_AUTOCOPY_TO if defined)
182
     *  @param  int     $deliveryreceipt     Ask a delivery receipt
183
     *  @param  int     $msgishtml           1=String IS already html, 0=String IS NOT html, -1=Unknown make autodetection (with fast mode, not reliable)
184
     *  @param  string  $errors_to           Email for errors-to
185
     *  @param  string  $css                 Css option
186
     *  @param  string  $trackid             Tracking string (contains type and id of related element)
187
     *  @param  string  $moreinheader        More in header. $moreinheader must contains the "\r\n" at end of each line
188
     *  @param  string  $sendcontext         'standard', 'emailing', 'ticket', 'password', ... (used to define which sending mode and parameters to use)
189
     *  @param  string  $replyto             Reply-to email (will be set to the same value than From by default if not provided)
190
     *  @param  string  $upload_dir_tmp      Temporary directory (used to convert images embedded as img src=data:image)
191
     *  @param  string  $in_reply_to         Message-ID of the message we reply to
192
     *  @param  string  $references          String with list of Message-ID of the thread ('<123> <456> ...')
193
     */
194
    public function __construct($subject, $to, $from, $msg, $filename_list = array(), $mimetype_list = array(), $mimefilename_list = array(), $addr_cc = "", $addr_bcc = "", $deliveryreceipt = 0, $msgishtml = 0, $errors_to = '', $css = '', $trackid = '', $moreinheader = '', $sendcontext = 'standard', $replyto = '', $upload_dir_tmp = '', $in_reply_to = '', $references = '')
195
    {
196
        global $conf, $dolibarr_main_data_root, $user;
197
198
        dol_syslog("CMailFile::CMailfile: charset=" . $conf->file->character_set_client . " from=$from, to=$to, addr_cc=$addr_cc, addr_bcc=$addr_bcc, errors_to=$errors_to, replyto=$replyto trackid=$trackid sendcontext=$sendcontext", LOG_DEBUG);
199
        dol_syslog("CMailFile::CMailfile: subject=" . $subject . ", deliveryreceipt=" . $deliveryreceipt . ", msgishtml=" . $msgishtml, LOG_DEBUG);
200
201
202
        // Clean values of $mimefilename_list
203
        if (is_array($mimefilename_list)) {
204
            foreach ($mimefilename_list as $key => $val) {
205
                $mimefilename_list[$key] = dol_string_unaccent($mimefilename_list[$key]);
206
            }
207
        }
208
209
        $cid_list = array();
210
211
        $this->sendcontext = $sendcontext;
212
213
        // Define this->sendmode ('mail', 'smtps', 'swiftmailer', ...) according to $sendcontext ('standard', 'emailing', 'ticket', 'password')
214
        $this->sendmode = '';
215
        if (!empty($this->sendcontext)) {
216
            $smtpContextKey = strtoupper($this->sendcontext);
217
            $smtpContextSendMode = getDolGlobalString('MAIN_MAIL_SENDMODE_' . $smtpContextKey);
218
            if (!empty($smtpContextSendMode) && $smtpContextSendMode != 'default') {
219
                $this->sendmode = $smtpContextSendMode;
220
            }
221
        }
222
        if (empty($this->sendmode)) {
223
            $this->sendmode = getDolGlobalString('MAIN_MAIL_SENDMODE', 'mail');
224
        }
225
226
        // Add a Feedback-ID. Must be used for stats on spam report only.
227
        if ($trackid) {
228
            //Examples:
229
            // LinkedIn – Feedback-ID: accept_invite_04:linkedin
230
            // Twitter – Feedback-ID: 0040162518f58f41d1f0:15491f3b2ee48656f8e7fb2fac:none:twitterESP
231
            // Amazon.com : Feedback-ID: 1.eu-west-1.kjoQSiqb8G+7lWWiDVsxjM2m0ynYd4I6yEFlfoox6aY=:AmazonSES
232
            $moreinheader .= "Feedback-ID: " . $trackid . ':' . dol_getprefix('email') . ":dolib\r\n";
233
        }
234
235
        // We define end of line (RFC 821).
236
        $this->eol = "\r\n";
237
        // We define end of line for header fields (RFC 822bis section 2.3 says header must contains \r\n).
238
        $this->eol2 = "\r\n";
239
        if (getDolGlobalString('MAIN_FIX_FOR_BUGGED_MTA')) {
240
            $this->eol = "\n";
241
            $this->eol2 = "\n";
242
            $moreinheader = str_replace("\r\n", "\n", $moreinheader);
243
        }
244
245
        // On defini mixed_boundary
246
        $this->mixed_boundary = "multipart_x." . time() . ".x_boundary";
247
248
        // On defini related_boundary
249
        $this->related_boundary = 'mul_' . dol_hash(uniqid("dolibarr2"), 3); // Force md5 hash (does not contain special chars)
250
251
        // On defini alternative_boundary
252
        $this->alternative_boundary = 'mul_' . dol_hash(uniqid("dolibarr3"), 3); // Force md5 hash (does not contain special chars)
253
254
        if (empty($subject)) {
255
            dol_syslog("CMailFile::CMailfile: Try to send an email with empty subject");
256
            $this->error = 'ErrorSubjectIsRequired';
257
            return;
258
        }
259
        if (empty($msg)) {
260
            dol_syslog("CMailFile::CMailfile: Try to send an email with empty body");
261
            $msg = '.'; // Avoid empty message (with empty message content, you will see a multipart structure)
262
        }
263
264
        // Detect if message is HTML (use fast method)
265
        if ($msgishtml == -1) {
266
            $this->msgishtml = 0;
267
            if (dol_textishtml($msg)) {
268
                $this->msgishtml = 1;
269
            }
270
        } else {
271
            $this->msgishtml = $msgishtml;
272
        }
273
274
        global $dolibarr_main_url_root;
275
276
        // Define $urlwithroot
277
        $urlwithouturlroot = preg_replace('/' . preg_quote(DOL_URL_ROOT, '/') . '$/i', '', trim($dolibarr_main_url_root));
278
        $urlwithroot = $urlwithouturlroot . DOL_URL_ROOT; // This is to use external domain name found into config file
279
        //$urlwithroot=DOL_MAIN_URL_ROOT;                   // This is to use same domain name than current
280
281
        // Replace relative /viewimage to absolute path
282
        $msg = preg_replace('/src="' . preg_quote(DOL_URL_ROOT, '/') . '\/viewimage\.php/ims', 'src="' . $urlwithroot . '/viewimage.php', $msg, -1);
283
284
        if (getDolGlobalString('MAIN_MAIL_FORCE_CONTENT_TYPE_TO_HTML')) {
285
            $this->msgishtml = 1; // To force to send everything with content type html.
286
        }
287
        dol_syslog("CMailFile::CMailfile: msgishtml=" . $this->msgishtml);
288
289
        // Detect images
290
        if ($this->msgishtml) {
291
            $this->html = $msg;
292
293
            $findimg = 0;
294
            if (getDolGlobalString('MAIN_MAIL_ADD_INLINE_IMAGES_IF_IN_MEDIAS')) {   // Off by default
295
                // Search into the body for <img tags of links in medias files to replace them with an embedded file
296
                // Note because media links are public, this should be useless, except avoid blocking images with email browser.
297
                // This converts an embed file with src="/viewimage.php?modulepart... into a cid link
298
                // TODO Exclude viewimage used for the read tracker ?
299
                $findimg = $this->findHtmlImages($dolibarr_main_data_root . '/medias');
300
                if ($findimg < 0) {
301
                    dol_syslog("CMailFile::CMailfile: Error on findHtmlImages");
302
                    $this->error = 'ErrorInAddAttachmentsImageBaseOnMedia';
303
                    return;
304
                }
305
            }
306
307
            if (getDolGlobalString('MAIN_MAIL_ADD_INLINE_IMAGES_IF_DATA')) {
308
                // Search into the body for <img src="data:image/ext;base64,..." to replace them with an embedded file
309
                // This convert an embedded file with src="data:image... into a cid link + attached file
310
                $resultImageData = $this->findHtmlImagesIsSrcData($upload_dir_tmp);
311
                if ($resultImageData < 0) {
312
                    dol_syslog("CMailFile::CMailfile: Error on findHtmlImagesInSrcData code=" . $resultImageData . " upload_dir_tmp=" . $upload_dir_tmp);
313
                    $this->error = 'ErrorInAddAttachmentsImageBaseIsSrcData';
314
                    return;
315
                }
316
                $findimg += $resultImageData;
317
            }
318
319
            // Set atleastoneimage if there is at least one embedded file (into ->html_images)
320
            if ($findimg > 0) {
321
                foreach ($this->html_images as $i => $val) {
322
                    if ($this->html_images[$i]) {
323
                        $this->atleastoneimage = 1;
324
                        if ($this->html_images[$i]['type'] == 'cidfromdata') {
325
                            if (!in_array($this->html_images[$i]['fullpath'], $filename_list)) {
326
                                // If this file path is not already into the $filename_list, we append it at end of array
327
                                $posindice = count($filename_list);
328
                                $filename_list[$posindice] = $this->html_images[$i]['fullpath'];
329
                                $mimetype_list[$posindice] = $this->html_images[$i]['content_type'];
330
                                $mimefilename_list[$posindice] = $this->html_images[$i]['name'];
331
                            } else {
332
                                $posindice = array_search($this->html_images[$i]['fullpath'], $filename_list);
333
                            }
334
                            // We complete the array of cid_list
335
                            $cid_list[$posindice] = $this->html_images[$i]['cid'];
336
                        }
337
                        dol_syslog("CMailFile::CMailfile: html_images[$i]['name']=" . $this->html_images[$i]['name'], LOG_DEBUG);
338
                    }
339
                }
340
            }
341
        }
342
        //var_dump($filename_list);
343
        //var_dump($cid_list);exit;
344
345
        // Set atleastoneimage if there is at least one file (into $filename_list array)
346
        if (is_array($filename_list)) {
347
            foreach ($filename_list as $i => $val) {
348
                if ($filename_list[$i]) {
349
                    $this->atleastonefile = 1;
350
                    dol_syslog("CMailFile::CMailfile: filename_list[$i]=" . $filename_list[$i] . ", mimetype_list[$i]=" . $mimetype_list[$i] . " mimefilename_list[$i]=" . $mimefilename_list[$i] . " cid_list[$i]=" . (empty($cid_list[$i]) ? '' : $cid_list[$i]), LOG_DEBUG);
351
                }
352
            }
353
        }
354
355
        // Add auto copy to if not already in $to (Note: Adding bcc for specific modules are also done from pages)
356
        // For example MAIN_MAIL_AUTOCOPY_TO can be '[email protected], __USER_EMAIL__, ...'
357
        if (getDolGlobalString('MAIN_MAIL_AUTOCOPY_TO')) {
358
            $listofemailstoadd = explode(',', getDolGlobalString('MAIN_MAIL_AUTOCOPY_TO'));
359
            foreach ($listofemailstoadd as $key => $val) {
360
                $emailtoadd = $listofemailstoadd[$key];
361
                if (trim($emailtoadd) == '__USER_EMAIL__') {
362
                    if (!empty($user) && !empty($user->email)) {
363
                        $emailtoadd = $user->email;
364
                    } else {
365
                        $emailtoadd = '';
366
                    }
367
                }
368
                if ($emailtoadd && preg_match('/' . preg_quote($emailtoadd, '/') . '/i', $to)) {
369
                    $emailtoadd = '';   // Email already in the "To"
370
                }
371
                if ($emailtoadd) {
372
                    $listofemailstoadd[$key] = $emailtoadd;
373
                } else {
374
                    unset($listofemailstoadd[$key]);
375
                }
376
            }
377
            if (!empty($listofemailstoadd)) {
378
                $addr_bcc .= ($addr_bcc ? ', ' : '') . implode(', ', $listofemailstoadd);
379
            }
380
        }
381
382
        // Verify if $to, $addr_cc and addr_bcc have unwanted addresses
383
        if (getDolGlobalString('MAIN_MAIL_FORCE_NOT_SENDING_TO')) {
384
            //Verify for $to
385
            $replaceto = false;
386
            $tabto = explode(",", $to);
387
            $listofemailstonotsendto = explode(',', getDolGlobalString('MAIN_MAIL_FORCE_NOT_SENDING_TO'));
388
            foreach ($tabto as $key => $addrto) {
389
                $addrto = array_keys($this->getArrayAddress($addrto));
390
                if (in_array($addrto[0], $listofemailstonotsendto)) {
391
                    unset($tabto[$key]);
392
                    $replaceto = true;
393
                }
394
            }
395
            if ($replaceto && getDolGlobalString('MAIN_MAIL_FORCE_NOT_SENDING_TO_REPLACE')) {
396
                $tabto[] = getDolGlobalString('MAIN_MAIL_FORCE_NOT_SENDING_TO_REPLACE');
397
            }
398
            $to = implode(',', $tabto);
399
400
            //Verify for $addr_cc
401
            $replacecc = false;
402
            $tabcc = explode(',', $addr_cc);
403
            foreach ($tabcc as $key => $cc) {
404
                $cc = array_keys($this->getArrayAddress($cc));
405
                if (in_array($cc[0], $listofemailstonotsendto)) {
406
                    unset($tabcc[$key]);
407
                    $replacecc = true;
408
                }
409
            }
410
            if ($replacecc && getDolGlobalString('MAIN_MAIL_FORCE_NOT_SENDING_TO_REPLACE')) {
411
                $tabcc[] = getDolGlobalString('MAIN_MAIL_FORCE_NOT_SENDING_TO_REPLACE');
412
            }
413
            $addr_cc = implode(',', $tabcc);
414
415
            //Verify for $addr_bcc
416
            $replacebcc = false;
417
            $tabbcc = explode(',', $addr_bcc);
418
            foreach ($tabbcc as $key => $bcc) {
419
                $bcc = array_keys($this->getArrayAddress($bcc));
420
                if (in_array($bcc[0], $listofemailstonotsendto)) {
421
                    unset($tabbcc[$key]);
422
                    $replacebcc = true;
423
                }
424
            }
425
            if ($replacebcc && getDolGlobalString('MAIN_MAIL_FORCE_NOT_SENDING_TO_REPLACE')) {
426
                $tabbcc[] = getDolGlobalString('MAIN_MAIL_FORCE_NOT_SENDING_TO_REPLACE');
427
            }
428
            $addr_bcc = implode(',', $tabbcc);
429
        }
430
431
        // We always use a replyto
432
        if (empty($replyto)) {
433
            $replyto = dol_sanitizeEmail($from);
434
        }
435
        // We can force the from
436
        if (getDolGlobalString('MAIN_MAIL_FORCE_FROM')) {
437
            $from = getDolGlobalString('MAIN_MAIL_FORCE_FROM');
438
        }
439
440
        $this->subject = $subject;
441
        $this->addr_to = dol_sanitizeEmail($to);
442
        $this->addr_from = dol_sanitizeEmail($from);
443
        $this->msg = $msg;
444
        $this->addr_cc = dol_sanitizeEmail($addr_cc);
445
        $this->addr_bcc = dol_sanitizeEmail($addr_bcc);
446
        $this->deliveryreceipt = $deliveryreceipt;
447
        $this->reply_to = dol_sanitizeEmail($replyto);
448
        $this->errors_to = dol_sanitizeEmail($errors_to);
449
        $this->trackid = $trackid;
450
        $this->in_reply_to = $in_reply_to;
451
        $this->references = $references;
452
        // Set arrays with attached files info
453
        $this->filename_list = $filename_list;
454
        $this->mimetype_list = $mimetype_list;
455
        $this->mimefilename_list = $mimefilename_list;
456
        $this->cid_list = $cid_list;
457
458
        if (getDolGlobalString('MAIN_MAIL_FORCE_SENDTO')) {
459
            $this->addr_to = dol_sanitizeEmail(getDolGlobalString('MAIN_MAIL_FORCE_SENDTO'));
460
            $this->addr_cc = '';
461
            $this->addr_bcc = '';
462
        }
463
464
        $keyforsslseflsigned = 'MAIN_MAIL_EMAIL_SMTP_ALLOW_SELF_SIGNED';
465
        if (!empty($this->sendcontext)) {
466
            $smtpContextKey = strtoupper($this->sendcontext);
467
            $smtpContextSendMode = getDolGlobalString('MAIN_MAIL_SENDMODE_' . $smtpContextKey);
468
            if (!empty($smtpContextSendMode) && $smtpContextSendMode != 'default') {
469
                $keyforsslseflsigned = 'MAIN_MAIL_EMAIL_SMTP_ALLOW_SELF_SIGNED_' . $smtpContextKey;
470
            }
471
        }
472
473
        dol_syslog("CMailFile::CMailfile: sendmode=" . $this->sendmode . " addr_bcc=$addr_bcc, replyto=$replyto", LOG_DEBUG);
474
475
        // We set all data according to chose sending method.
476
        // We also set a value for ->msgid
477
        if ($this->sendmode == 'mail') {
478
            // Use mail php function (default PHP method)
479
            // ------------------------------------------
480
481
            $smtp_headers = "";
482
            $mime_headers = "";
483
            $text_body = "";
484
            $files_encoded = "";
485
486
            // Define smtp_headers (this also set SMTP headers from ->msgid, ->in_reply_to and ->references)
487
            $smtp_headers = $this->write_smtpheaders();
488
            if (!empty($moreinheader)) {
489
                $smtp_headers .= $moreinheader; // $moreinheader contains the \r\n
490
            }
491
492
            // Define mime_headers
493
            $mime_headers = $this->write_mimeheaders($filename_list, $mimefilename_list);
494
495
            if (!empty($this->html)) {
496
                if (!empty($css)) {
497
                    $this->css = $css;
498
                    $this->buildCSS(); // Build a css style (mode = all) into this->styleCSS and this->bodyCSS
499
                }
500
501
                $msg = $this->html;
502
            }
503
504
            // Define body in text_body
505
            $text_body = $this->write_body($msg);
506
507
            // Add attachments to text_encoded
508
            if (!empty($this->atleastonefile)) {
509
                $files_encoded = $this->write_files($filename_list, $mimetype_list, $mimefilename_list, $cid_list);
510
            }
511
512
            // We now define $this->headers and $this->message
513
            $this->headers = $smtp_headers . $mime_headers;
514
            // Clean the header to avoid that it terminates with a CR character.
515
            // This avoid also empty lines at end that can be interpreted as mail injection by email servers.
516
            $this->headers = preg_replace("/([\r\n]+)$/i", "", $this->headers);
517
518
            //$this->message = $this->eol.'This is a message with multiple parts in MIME format.'.$this->eol;
519
            $this->message = 'This is a message with multiple parts in MIME format.' . $this->eol;
520
            $this->message .= $text_body . $files_encoded;
521
            $this->message .= "--" . $this->mixed_boundary . "--" . $this->eol;
522
        } elseif ($this->sendmode == 'smtps') {
523
            // Use SMTPS library
524
            // ------------------------------------------
525
            $host = dol_getprefix('email');
526
527
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/class/smtps.class.php';
528
            $smtps = new SMTPs();
529
            $smtps->setCharSet($conf->file->character_set_client);
530
531
            // Encode subject if required.
532
            $subjecttouse = $this->subject;
533
            if (!ascii_check($subjecttouse)) {
534
                $subjecttouse = $this->encodetorfc2822($subjecttouse);
535
            }
536
537
            $smtps->setSubject($subjecttouse);
538
            $smtps->setTO($this->getValidAddress($this->addr_to, 0, 1));
539
            $smtps->setFrom($this->getValidAddress($this->addr_from, 0, 1));
540
            $smtps->setReplyTo($this->getValidAddress($this->reply_to, 0, 1));
541
542
            $smtps->setTrackId($this->trackid);
543
544
            if (!empty($this->in_reply_to)) {
545
                $smtps->setInReplyTo($this->in_reply_to);
546
            }
547
            if (!empty($this->references)) {
548
                $smtps->setReferences($this->references);
549
            }
550
551
            if (!empty($moreinheader)) {
552
                $smtps->setMoreInHeader($moreinheader);
553
            }
554
555
            //X-Dolibarr-TRACKID, In-Reply-To, References and $moreinheader will be added to header inside the smtps->getHeader
556
557
            if (!empty($this->html)) {
558
                if (!empty($css)) {
559
                    $this->css = $css;
560
                    $this->buildCSS();
561
                }
562
                $msg = $this->html;
563
                $msg = $this->checkIfHTML($msg);        // This add a header and a body including custom CSS to the HTML content
564
            }
565
566
            // Replace . alone on a new line with .. to avoid to have SMTP interpret this as end of message
567
            $msg = preg_replace('/(\r|\n)\.(\r|\n)/ims', '\1..\2', $msg);
568
569
            if ($this->msgishtml) {
570
                $smtps->setBodyContent($msg, 'html');
571
            } else {
572
                $smtps->setBodyContent($msg, 'plain');
573
            }
574
575
            if ($this->atleastoneimage) {
576
                foreach ($this->images_encoded as $img) {
577
                    $smtps->setImageInline($img['image_encoded'], $img['name'], $img['content_type'], $img['cid']);
578
                }
579
            }
580
581
            if (!empty($this->atleastonefile)) {
582
                foreach ($filename_list as $i => $val) {
583
                    $content = file_get_contents($filename_list[$i]);
584
                    $smtps->setAttachment($content, $mimefilename_list[$i], $mimetype_list[$i], $cid_list[$i]);
585
                }
586
            }
587
588
            $smtps->setCC($this->addr_cc);
589
            $smtps->setBCC($this->addr_bcc);
590
            $smtps->setErrorsTo($this->errors_to);
591
            $smtps->setDeliveryReceipt($this->deliveryreceipt);
592
            if (getDolGlobalString($keyforsslseflsigned)) {
593
                $smtps->setOptions(array('ssl' => array('verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true)));
594
            }
595
596
            $this->msgid = time() . '.SMTPs-dolibarr-' . $this->trackid . '@' . $host;
597
598
            $this->smtps = $smtps;
599
        } elseif ($this->sendmode == 'swiftmailer') {
600
            // Use Swift Mailer library
601
            // ------------------------------------------
602
            $host = dol_getprefix('email');
603
604
            require_once constant('DOL_DOCUMENT_ROOT') . '/includes/swiftmailer/lexer/lib/Doctrine/Common/Lexer/AbstractLexer.php';
605
606
            // egulias autoloader lib
607
            require_once constant('DOL_DOCUMENT_ROOT') . '/includes/swiftmailer/autoload.php';
608
609
            require_once constant('DOL_DOCUMENT_ROOT') . '/includes/swiftmailer/lib/swift_required.php';
610
611
            // Create the message
612
            //$this->message = Swift_Message::newInstance();
613
            $this->message = new Swift_Message();
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Core\Classes\Swift_Message was not found. Did you mean Swift_Message? If so, make sure to prefix the type with \.
Loading history...
614
            //$this->message = new Swift_SignedMessage();
615
            // Adding a trackid header to a message
616
            $headers = $this->message->getHeaders();
617
618
            $headers->addTextHeader('X-Dolibarr-TRACKID', $this->trackid . '@' . $host);
619
            $this->msgid = time() . '.swiftmailer-dolibarr-' . $this->trackid . '@' . $host;
620
            $headerID = $this->msgid;
621
            $msgid = $headers->get('Message-ID');
622
            $msgid->setId($headerID);
623
624
            // Add 'In-Reply-To:' header
625
            if (!empty($this->in_reply_to)) {
626
                $headers->addIdHeader('In-Reply-To', $this->in_reply_to);
627
            }
628
            // Add 'References:' header
629
            if (!empty($this->references)) {
630
                $headers->addIdHeader('References', $this->references);
631
            }
632
633
            if (!empty($moreinheader)) {
634
                $moreinheaderarray = preg_split('/[\r\n]+/', $moreinheader);
635
                foreach ($moreinheaderarray as $moreinheaderval) {
636
                    $moreinheadervaltmp = explode(':', $moreinheaderval, 2);
637
                    if (!empty($moreinheadervaltmp[0]) && !empty($moreinheadervaltmp[1])) {
638
                        $headers->addTextHeader($moreinheadervaltmp[0], $moreinheadervaltmp[1]);
639
                    }
640
                }
641
            }
642
643
            // Give the message a subject
644
            try {
645
                $this->message->setSubject($this->subject);
646
            } catch (Exception $e) {
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Core\Classes\Exception was not found. Did you mean Exception? If so, make sure to prefix the type with \.
Loading history...
647
                $this->errors[] = $e->getMessage();
648
            }
649
650
            // Set the From address with an associative array
651
            //$this->message->setFrom(array('[email protected]' => 'John Doe'));
652
            if (!empty($this->addr_from)) {
653
                try {
654
                    if (getDolGlobalString('MAIN_FORCE_DISABLE_MAIL_SPOOFING')) {
655
                        // Prevent email spoofing for smtp server with a strict configuration
656
                        $regexp = '/([a-z0-9_\.\-\+])+\@(([a-z0-9\-])+\.)+([a-z0-9]{2,4})+/i'; // This regular expression extracts all emails from a string
657
                        $adressEmailFrom = array();
658
                        $emailMatchs = preg_match_all($regexp, $from, $adressEmailFrom);
659
                        $adressEmailFrom = reset($adressEmailFrom);
660
                        if ($emailMatchs !== false && filter_var($conf->global->MAIN_MAIL_SMTPS_ID, FILTER_VALIDATE_EMAIL) && $conf->global->MAIN_MAIL_SMTPS_ID !== $adressEmailFrom) {
661
                            $this->message->setFrom($conf->global->MAIN_MAIL_SMTPS_ID);
662
                        } else {
663
                            $this->message->setFrom($this->getArrayAddress($this->addr_from));
664
                        }
665
                    } else {
666
                        $this->message->setFrom($this->getArrayAddress($this->addr_from));
667
                    }
668
                } catch (Exception $e) {
669
                    $this->errors[] = $e->getMessage();
670
                }
671
            }
672
673
            // Set the To addresses with an associative array
674
            if (!empty($this->addr_to)) {
675
                try {
676
                    $this->message->setTo($this->getArrayAddress($this->addr_to));
677
                } catch (Exception $e) {
678
                    $this->errors[] = $e->getMessage();
679
                }
680
            }
681
682
            if (!empty($this->reply_to)) {
683
                try {
684
                    $this->message->SetReplyTo($this->getArrayAddress($this->reply_to));
685
                } catch (Exception $e) {
686
                    $this->errors[] = $e->getMessage();
687
                }
688
            }
689
690
            if (!empty($this->errors_to)) {
691
                try {
692
                    $headers->addTextHeader('Errors-To', $this->getArrayAddress($this->errors_to));
693
                } catch (Exception $e) {
694
                    $this->errors[] = $e->getMessage();
695
                }
696
            }
697
698
            try {
699
                $this->message->setCharSet($conf->file->character_set_client);
700
            } catch (Exception $e) {
701
                $this->errors[] = $e->getMessage();
702
            }
703
704
            if (!empty($this->html)) {
705
                if (!empty($css)) {
706
                    $this->css = $css;
707
                    $this->buildCSS();
708
                }
709
                $msg = $this->html;
710
                $msg = $this->checkIfHTML($msg);        // This add a header and a body including custom CSS to the HTML content
711
            }
712
713
            if ($this->atleastoneimage) {
714
                foreach ($this->html_images as $img) {
715
                    // $img['fullpath'],$img['image_encoded'],$img['name'],$img['content_type'],$img['cid']
716
                    $attachment = Swift_Image::fromPath($img['fullpath']);
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Core\Classes\Swift_Image was not found. Did you mean Swift_Image? If so, make sure to prefix the type with \.
Loading history...
717
                    // embed image
718
                    $imgcid = $this->message->embed($attachment);
719
                    // replace cid by the one created by swiftmail in html message
720
                    $msg = str_replace("cid:" . $img['cid'], $imgcid, $msg);
721
                }
722
                foreach ($this->images_encoded as $img) {
723
                    //$img['fullpath'],$img['image_encoded'],$img['name'],$img['content_type'],$img['cid']
724
                    $attachment = Swift_Image::fromPath($img['fullpath']);
725
                    // embed image
726
                    $imgcid = $this->message->embed($attachment);
727
                    // replace cid by the one created by swiftmail in html message
728
                    $msg = str_replace("cid:" . $img['cid'], $imgcid, $msg);
729
                }
730
            }
731
732
            if ($this->msgishtml) {
733
                $this->message->setBody($msg, 'text/html');
734
                // And optionally an alternative body
735
                $this->message->addPart(html_entity_decode(strip_tags($msg)), 'text/plain');
736
            } else {
737
                $this->message->setBody($msg, 'text/plain');
738
                // And optionally an alternative body
739
                $this->message->addPart(dol_nl2br($msg), 'text/html');
740
            }
741
742
            if (!empty($this->atleastonefile)) {
743
                foreach ($filename_list as $i => $val) {
744
                    //$this->message->attach(Swift_Attachment::fromPath($filename_list[$i],$mimetype_list[$i]));
745
                    $attachment = Swift_Attachment::fromPath($filename_list[$i], $mimetype_list[$i]);
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Core\Classes\Swift_Attachment was not found. Did you mean Swift_Attachment? If so, make sure to prefix the type with \.
Loading history...
746
                    if (!empty($mimefilename_list[$i])) {
747
                        $attachment->setFilename($mimefilename_list[$i]);
748
                    }
749
                    $this->message->attach($attachment);
750
                }
751
            }
752
753
            if (!empty($this->addr_cc)) {
754
                try {
755
                    $this->message->setCc($this->getArrayAddress($this->addr_cc));
756
                } catch (Exception $e) {
757
                    $this->errors[] = $e->getMessage();
758
                }
759
            }
760
            if (!empty($this->addr_bcc)) {
761
                try {
762
                    $this->message->setBcc($this->getArrayAddress($this->addr_bcc));
763
                } catch (Exception $e) {
764
                    $this->errors[] = $e->getMessage();
765
                }
766
            }
767
            //if (!empty($this->errors_to)) $this->message->setErrorsTo($this->getArrayAddress($this->errors_to));
768
            if (isset($this->deliveryreceipt) && $this->deliveryreceipt == 1) {
769
                try {
770
                    $this->message->setReadReceiptTo($this->getArrayAddress($this->addr_from));
771
                } catch (Exception $e) {
772
                    $this->errors[] = $e->getMessage();
773
                }
774
            }
775
        } else {
776
            // Send mail method not correctly defined
777
            // --------------------------------------
778
            $this->error = 'Bad value for sendmode';
779
        }
780
    }
781
782
    /**
783
     * Send mail that was prepared by constructor.
784
     *
785
     * @return    bool  True if mail sent, false otherwise.  Negative int if error in hook.  String if incorrect send mode.
786
     *
787
     * @phan-suppress PhanTypeMismatchReturnNullable  False positif by phan for unclear reason.
788
     */
789
    public function sendfile()
790
    {
791
        global $conf, $db, $langs, $hookmanager;
792
793
        $errorlevel = error_reporting();
794
        //error_reporting($errorlevel ^ E_WARNING);   // Desactive warnings
795
796
        $res = false;
797
798
        if (!getDolGlobalString('MAIN_DISABLE_ALL_MAILS')) {
799
            if (!is_object($hookmanager)) {
800
                $hookmanager = new HookManager($db);
801
            }
802
            $hookmanager->initHooks(array('mail'));
803
804
            $parameters = array();
805
            $action = '';
806
            $reshook = $hookmanager->executeHooks('sendMail', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
807
            if ($reshook < 0) {
808
                $this->error = "Error in hook maildao sendMail " . $reshook;
809
                dol_syslog("CMailFile::sendfile: mail end error=" . $this->error, LOG_ERR);
810
811
                return false;
812
            }
813
            if ($reshook == 1) {    // Hook replace standard code
814
                return true;
815
            }
816
817
            $sendingmode = $this->sendmode;
818
            if ($this->sendcontext == 'emailing' && getDolGlobalString('MAILING_NO_USING_PHPMAIL') && $sendingmode == 'mail') {
819
                // List of sending methods
820
                $listofmethods = array();
821
                $listofmethods['mail'] = 'PHP mail function';
822
                //$listofmethods['simplemail']='Simplemail class';
823
                $listofmethods['smtps'] = 'SMTP/SMTPS socket library';
824
825
                // EMailing feature may be a spam problem, so when you host several users/instance, having this option may force each user to use their own SMTP agent.
826
                // You ensure that every user is using its own SMTP server when using the mass emailing module.
827
                $linktoadminemailbefore = '<a href="' . constant('BASE_URL') . '/admin/mails.php">';
828
                $linktoadminemailend = '</a>';
829
                $this->error = $langs->trans("MailSendSetupIs", $listofmethods[$sendingmode]);
830
                $this->errors[] = $langs->trans("MailSendSetupIs", $listofmethods[$sendingmode]);
831
                $this->error .= '<br>' . $langs->trans("MailSendSetupIs2", $linktoadminemailbefore, $linktoadminemailend, $langs->transnoentitiesnoconv("MAIN_MAIL_SENDMODE"), $listofmethods['smtps']);
832
                $this->errors[] = $langs->trans("MailSendSetupIs2", $linktoadminemailbefore, $linktoadminemailend, $langs->transnoentitiesnoconv("MAIN_MAIL_SENDMODE"), $listofmethods['smtps']);
833
                if (getDolGlobalString('MAILING_SMTP_SETUP_EMAILS_FOR_QUESTIONS')) {
834
                    $this->error .= '<br>' . $langs->trans("MailSendSetupIs3", getDolGlobalString('MAILING_SMTP_SETUP_EMAILS_FOR_QUESTIONS'));
835
                    $this->errors[] = $langs->trans("MailSendSetupIs3", getDolGlobalString('MAILING_SMTP_SETUP_EMAILS_FOR_QUESTIONS'));
836
                }
837
838
                dol_syslog("CMailFile::sendfile: mail end error=" . $this->error, LOG_WARNING);
839
                return false;
840
            }
841
842
            // Check number of recipient is lower or equal than MAIL_MAX_NB_OF_RECIPIENTS_IN_SAME_EMAIL
843
            if (!getDolGlobalString('MAIL_MAX_NB_OF_RECIPIENTS_TO_IN_SAME_EMAIL')) {
844
                $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_TO_IN_SAME_EMAIL = 10;
845
            }
846
            $tmparray1 = explode(',', $this->addr_to);
847
            if (count($tmparray1) > $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_TO_IN_SAME_EMAIL) {
848
                $this->error = 'Too much recipients in to:';
849
                dol_syslog("CMailFile::sendfile: mail end error=" . $this->error, LOG_WARNING);
850
                return false;
851
            }
852
            if (!getDolGlobalString('MAIL_MAX_NB_OF_RECIPIENTS_CC_IN_SAME_EMAIL')) {
853
                $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_CC_IN_SAME_EMAIL = 10;
854
            }
855
            $tmparray2 = explode(',', $this->addr_cc);
856
            if (count($tmparray2) > $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_CC_IN_SAME_EMAIL) {
857
                $this->error = 'Too much recipients in cc:';
858
                dol_syslog("CMailFile::sendfile: mail end error=" . $this->error, LOG_WARNING);
859
                return false;
860
            }
861
            if (!getDolGlobalString('MAIL_MAX_NB_OF_RECIPIENTS_BCC_IN_SAME_EMAIL')) {
862
                $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_BCC_IN_SAME_EMAIL = 10;
863
            }
864
            $tmparray3 = explode(',', $this->addr_bcc);
865
            if (count($tmparray3) > $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_BCC_IN_SAME_EMAIL) {
866
                $this->error = 'Too much recipients in bcc:';
867
                dol_syslog("CMailFile::sendfile: mail end error=" . $this->error, LOG_WARNING);
868
                return false;
869
            }
870
            if (!getDolGlobalString('MAIL_MAX_NB_OF_RECIPIENTS_IN_SAME_EMAIL')) {
871
                $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_IN_SAME_EMAIL = 10;
872
            }
873
            if ((count($tmparray1) + count($tmparray2) + count($tmparray3)) > $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_IN_SAME_EMAIL) {
874
                $this->error = 'Too much recipients in to:, cc:, bcc:';
875
                dol_syslog("CMailFile::sendfile: mail end error=" . $this->error, LOG_WARNING);
876
                return false;
877
            }
878
879
            $keyforsmtpserver = 'MAIN_MAIL_SMTP_SERVER';
880
            $keyforsmtpport  = 'MAIN_MAIL_SMTP_PORT';
881
            $keyforsmtpid    = 'MAIN_MAIL_SMTPS_ID';
882
            $keyforsmtppw    = 'MAIN_MAIL_SMTPS_PW';
883
            $keyforsmtpauthtype = 'MAIN_MAIL_SMTPS_AUTH_TYPE';
884
            $keyforsmtpoauthservice = 'MAIN_MAIL_SMTPS_OAUTH_SERVICE';
885
            $keyfortls       = 'MAIN_MAIL_EMAIL_TLS';
886
            $keyforstarttls  = 'MAIN_MAIL_EMAIL_STARTTLS';
887
            $keyforsslseflsigned = 'MAIN_MAIL_EMAIL_SMTP_ALLOW_SELF_SIGNED';
888
            if (!empty($this->sendcontext)) {
889
                $smtpContextKey = strtoupper($this->sendcontext);
890
                $smtpContextSendMode = getDolGlobalString('MAIN_MAIL_SENDMODE_' . $smtpContextKey);
891
                if (!empty($smtpContextSendMode) && $smtpContextSendMode != 'default') {
892
                    $keyforsmtpserver = 'MAIN_MAIL_SMTP_SERVER_' . $smtpContextKey;
893
                    $keyforsmtpport   = 'MAIN_MAIL_SMTP_PORT_' . $smtpContextKey;
894
                    $keyforsmtpid     = 'MAIN_MAIL_SMTPS_ID_' . $smtpContextKey;
895
                    $keyforsmtppw     = 'MAIN_MAIL_SMTPS_PW_' . $smtpContextKey;
896
                    $keyforsmtpauthtype = 'MAIN_MAIL_SMTPS_AUTH_TYPE_' . $smtpContextKey;
897
                    $keyforsmtpoauthservice = 'MAIN_MAIL_SMTPS_OAUTH_SERVICE_' . $smtpContextKey;
898
                    $keyfortls        = 'MAIN_MAIL_EMAIL_TLS_' . $smtpContextKey;
899
                    $keyforstarttls   = 'MAIN_MAIL_EMAIL_STARTTLS_' . $smtpContextKey;
900
                    $keyforsslseflsigned = 'MAIN_MAIL_EMAIL_SMTP_ALLOW_SELF_SIGNED_' . $smtpContextKey;
901
                }
902
            }
903
904
            // Action according to chose sending method
905
            if ($this->sendmode == 'mail') {
906
                // Use mail php function (default PHP method)
907
                // ------------------------------------------
908
                dol_syslog("CMailFile::sendfile addr_to=" . $this->addr_to . ", subject=" . $this->subject, LOG_DEBUG);
909
                //dol_syslog("CMailFile::sendfile header=\n".$this->headers, LOG_DEBUG);
910
                //dol_syslog("CMailFile::sendfile message=\n".$message);
911
912
                // If Windows, sendmail_from must be defined
913
                if (isset($_SERVER["WINDIR"])) {
914
                    if (empty($this->addr_from)) {
915
                        $this->addr_from = '[email protected]';
916
                    }
917
                    @ini_set('sendmail_from', $this->getValidAddress($this->addr_from, 2));
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for ini_set(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

917
                    /** @scrutinizer ignore-unhandled */ @ini_set('sendmail_from', $this->getValidAddress($this->addr_from, 2));

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
918
                }
919
920
                // Force parameters
921
                //dol_syslog("CMailFile::sendfile conf->global->".$keyforsmtpserver."=".getDolGlobalString($keyforsmtpserver)." cpnf->global->".$keyforsmtpport."=".$conf->global->$keyforsmtpport, LOG_DEBUG);
922
                if (getDolGlobalString($keyforsmtpserver)) {
923
                    ini_set('SMTP', getDolGlobalString($keyforsmtpserver));
924
                }
925
                if (getDolGlobalString($keyforsmtpport)) {
926
                    ini_set('smtp_port', getDolGlobalString($keyforsmtpport));
927
                }
928
929
                $res = true;
930
                if ($res && !$this->subject) {
931
                    $this->error = "Failed to send mail with php mail to HOST=" . ini_get('SMTP') . ", PORT=" . ini_get('smtp_port') . "<br>Subject is empty";
932
                    dol_syslog("CMailFile::sendfile: mail end error=" . $this->error, LOG_ERR);
933
                    $res = false;
934
                }
935
                $dest = $this->getValidAddress($this->addr_to, 2);
936
                if ($res && !$dest) {
937
                    $this->error = "Failed to send mail with php mail to HOST=" . ini_get('SMTP') . ", PORT=" . ini_get('smtp_port') . "<br>Recipient address '$dest' invalid";
938
                    dol_syslog("CMailFile::sendfile: mail end error=" . $this->error, LOG_ERR);
939
                    $res = false;
940
                }
941
942
                if ($res) {
943
                    $additionnalparam = ''; // By default
944
                    if (getDolGlobalString('MAIN_MAIL_ALLOW_SENDMAIL_F')) {
945
                        // When using the phpmail function, the mail command may force the from to the user of the login, for example: [email protected]
946
                        // You can try to set this option to have the command use the From. if it does not work, you can also try the MAIN_MAIL_SENDMAIL_FORCE_BA.
947
                        // So forcing using the option -f of sendmail is possible if constant MAIN_MAIL_ALLOW_SENDMAIL_F is defined.
948
                        // Having this variable defined may create problems with some sendmail (option -f refused)
949
                        // Having this variable not defined may create problems with some other sendmail (option -f required)
950
                        $additionnalparam .= ($additionnalparam ? ' ' : '') . (getDolGlobalString('MAIN_MAIL_ERRORS_TO') ? '-f' . $this->getValidAddress($conf->global->MAIN_MAIL_ERRORS_TO, 2) : ($this->addr_from != '' ? '-f' . $this->getValidAddress($this->addr_from, 2) : ''));
951
                    }
952
                    if (getDolGlobalString('MAIN_MAIL_SENDMAIL_FORCE_BA')) {    // To force usage of -ba option. This option tells sendmail to read From: or Sender: to setup sender
953
                        $additionnalparam .= ($additionnalparam ? ' ' : '') . '-ba';
954
                    }
955
956
                    if (getDolGlobalString('MAIN_MAIL_SENDMAIL_FORCE_ADDPARAM')) {
957
                        $additionnalparam .= ($additionnalparam ? ' ' : '') . '-U ' . $additionnalparam; // Use -U to add additional params
958
                    }
959
960
                    $linuxlike = 1;
961
                    if (preg_match('/^win/i', PHP_OS)) {
962
                        $linuxlike = 0;
963
                    }
964
                    if (preg_match('/^mac/i', PHP_OS)) {
965
                        $linuxlike = 0;
966
                    }
967
968
                    dol_syslog("CMailFile::sendfile: mail start" . ($linuxlike ? '' : " HOST=" . ini_get('SMTP') . ", PORT=" . ini_get('smtp_port')) . ", additionnal_parameters=" . $additionnalparam, LOG_DEBUG);
969
970
                    $this->message = stripslashes($this->message);
971
972
                    if (getDolGlobalString('MAIN_MAIL_DEBUG')) {
973
                        $this->dump_mail();
974
                    }
975
976
                    // Encode subject if required.
977
                    $subjecttouse = $this->subject;
978
                    if (!ascii_check($subjecttouse)) {
979
                        $subjecttouse = $this->encodetorfc2822($subjecttouse);
980
                    }
981
982
                    if (!empty($additionnalparam)) {
983
                        $res = mail($dest, $subjecttouse, $this->message, $this->headers, $additionnalparam);
984
                    } else {
985
                        $res = mail($dest, $subjecttouse, $this->message, $this->headers);
986
                    }
987
988
                    if (!$res) {
989
                        $langs->load("errors");
990
                        $this->error = "Failed to send mail with php mail";
991
                        if (!$linuxlike) {
992
                            $this->error .= " to HOST=" . ini_get('SMTP') . ", PORT=" . ini_get('smtp_port'); // This values are value used only for non linuxlike systems
993
                        }
994
                        $this->error .= ".<br>";
995
                        $this->error .= $langs->trans("ErrorPhpMailDelivery");
996
                        dol_syslog("CMailFile::sendfile: mail end error=" . $this->error, LOG_ERR);
997
998
                        if (getDolGlobalString('MAIN_MAIL_DEBUG')) {
999
                            $this->save_dump_mail_in_err('Mail with topic ' . $this->subject);
1000
                        }
1001
                    } else {
1002
                        dol_syslog("CMailFile::sendfile: mail end success", LOG_DEBUG);
1003
                    }
1004
                }
1005
1006
                if (isset($_SERVER["WINDIR"])) {
1007
                    @ini_restore('sendmail_from');
0 ignored issues
show
Bug introduced by
Are you sure the usage of ini_restore('sendmail_from') is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Security Best Practice introduced by
It seems like you do not handle an error condition for ini_restore(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

1007
                    /** @scrutinizer ignore-unhandled */ @ini_restore('sendmail_from');

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1008
                }
1009
1010
                // Restore parameters
1011
                if (getDolGlobalString($keyforsmtpserver)) {
1012
                    ini_restore('SMTP');
1013
                }
1014
                if (getDolGlobalString($keyforsmtpport)) {
1015
                    ini_restore('smtp_port');
1016
                }
1017
            } elseif ($this->sendmode == 'smtps') {
1018
                if (!is_object($this->smtps)) {
1019
                    $this->error = "Failed to send mail with smtps lib<br>Constructor of object CMailFile was not initialized without errors.";
1020
                    dol_syslog("CMailFile::sendfile: mail end error=" . $this->error, LOG_ERR);
1021
                    return false;
1022
                }
1023
1024
                // Use SMTPS library
1025
                // ------------------------------------------
1026
                $this->smtps->setTransportType(0); // Only this method is coded in SMTPs library
1027
1028
                // Clean parameters
1029
                if (empty($conf->global->$keyforsmtpserver)) {
1030
                    $conf->global->$keyforsmtpserver = ini_get('SMTP');
1031
                }
1032
                if (empty($conf->global->$keyforsmtpport)) {
1033
                    $conf->global->$keyforsmtpport = ini_get('smtp_port');
1034
                }
1035
1036
                // If we use SSL/TLS
1037
                $server = getDolGlobalString($keyforsmtpserver);
1038
                $secure = '';
1039
                if (!empty($conf->global->$keyfortls) && function_exists('openssl_open')) {
1040
                    $secure = 'ssl';
1041
                }
1042
                if (!empty($conf->global->$keyforstarttls) && function_exists('openssl_open')) {
1043
                    $secure = 'tls';
1044
                }
1045
                $server = ($secure ? $secure . '://' : '') . $server;
1046
1047
                $port = getDolGlobalInt($keyforsmtpport);
1048
1049
                $this->smtps->setHost($server);
1050
                $this->smtps->setPort($port); // 25, 465...;
1051
1052
                $loginid = '';
1053
                $loginpass = '';
1054
                if (!empty($conf->global->$keyforsmtpid)) {
1055
                    $loginid = getDolGlobalString($keyforsmtpid);
1056
                    $this->smtps->setID($loginid);
1057
                }
1058
                if (!empty($conf->global->$keyforsmtppw)) {
1059
                    $loginpass = getDolGlobalString($keyforsmtppw);
1060
                    $this->smtps->setPW($loginpass);
1061
                }
1062
1063
                if (getDolGlobalString($keyforsmtpauthtype) === "XOAUTH2") {
1064
                    require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/oauth.lib.php'; // define $supportedoauth2array
1065
1066
                    $supportedoauth2array = getSupportedOauth2Array();
1067
1068
                    $keyforsupportedoauth2array = getDolGlobalString($keyforsmtpoauthservice);
1069
                    if (preg_match('/^.*-/', $keyforsupportedoauth2array)) {
1070
                        $keyforprovider = preg_replace('/^.*-/', '', $keyforsupportedoauth2array);
1071
                    } else {
1072
                        $keyforprovider = '';
1073
                    }
1074
                    $keyforsupportedoauth2array = preg_replace('/-.*$/', '', $keyforsupportedoauth2array);
1075
                    $keyforsupportedoauth2array = 'OAUTH_' . $keyforsupportedoauth2array . '_NAME';
1076
1077
                    if (!empty($supportedoauth2array)) {
1078
                        $OAUTH_SERVICENAME = (empty($supportedoauth2array[$keyforsupportedoauth2array]['name']) ? 'Unknown' : $supportedoauth2array[$keyforsupportedoauth2array]['name'] . ($keyforprovider ? '-' . $keyforprovider : ''));
1079
                    } else {
1080
                        $OAUTH_SERVICENAME = 'Unknown';
1081
                    }
1082
1083
                    require_once constant('DOL_DOCUMENT_ROOT') . '/includes/OAuth/bootstrap.php';
1084
1085
                    $storage = new DoliStorage($db, $conf, $keyforprovider);
1086
                    try {
1087
                        $tokenobj = $storage->retrieveAccessToken($OAUTH_SERVICENAME);
1088
                        $expire = false;
1089
                        // Is token expired or will token expire in the next 30 seconds
1090
                        if (is_object($tokenobj)) {
1091
                            $expire = ($tokenobj->getEndOfLife() !== -9002 && $tokenobj->getEndOfLife() !== -9001 && time() > ($tokenobj->getEndOfLife() - 30));
1092
                        }
1093
                        // Token expired so we refresh it
1094
                        if (is_object($tokenobj) && $expire) {
1095
                            $credentials = new Credentials(
1096
                                getDolGlobalString('OAUTH_' . getDolGlobalString('MAIN_MAIL_SMTPS_OAUTH_SERVICE') . '_ID'),
1097
                                getDolGlobalString('OAUTH_' . getDolGlobalString('MAIN_MAIL_SMTPS_OAUTH_SERVICE') . '_SECRET'),
1098
                                getDolGlobalString('OAUTH_' . getDolGlobalString('MAIN_MAIL_SMTPS_OAUTH_SERVICE') . '_URLAUTHORIZE')
1099
                            );
1100
                            $serviceFactory = new \OAuth\ServiceFactory();
1101
                            $oauthname = explode('-', $OAUTH_SERVICENAME);
1102
                            // ex service is Google-Emails we need only the first part Google
1103
                            $apiService = $serviceFactory->createService($oauthname[0], $credentials, $storage, array());
1104
                            // We have to save the token because Google give it only once
1105
                            $refreshtoken = $tokenobj->getRefreshToken();
1106
                            $tokenobj = $apiService->refreshAccessToken($tokenobj);
0 ignored issues
show
Bug introduced by
The method refreshAccessToken() does not exist on OAuth\Common\Service\ServiceInterface. It seems like you code against a sub-type of said class. However, the method does not exist in OAuth\OAuth1\Service\ServiceInterface or OAuth\OAuth2\Service\ServiceInterface or OAuth\Common\Service\AbstractService. Are you sure you never get one of those? ( Ignorable by Annotation )

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

1106
                            /** @scrutinizer ignore-call */ 
1107
                            $tokenobj = $apiService->refreshAccessToken($tokenobj);
Loading history...
1107
                            $tokenobj->setRefreshToken($refreshtoken);
1108
                            $storage->storeAccessToken($OAUTH_SERVICENAME, $tokenobj);
1109
                        }
1110
1111
                        $tokenobj = $storage->retrieveAccessToken($OAUTH_SERVICENAME);
1112
                        if (is_object($tokenobj)) {
1113
                            $this->smtps->setToken($tokenobj->getAccessToken());
1114
                        } else {
1115
                            $this->error = "Token not found";
1116
                        }
1117
                    } catch (Exception $e) {
1118
                        // Return an error if token not found
1119
                        $this->error = $e->getMessage();
1120
                        dol_syslog("CMailFile::sendfile: mail end error=" . $this->error, LOG_ERR);
1121
                    }
1122
                }
1123
1124
                $res = true;
1125
                $from = $this->smtps->getFrom('org');
1126
                if ($res && !$from) {
1127
                    $this->error = "Failed to send mail with smtps lib to HOST=" . $server . ", PORT=" . getDolGlobalString($keyforsmtpport) . " - Sender address '$from' invalid";
1128
                    dol_syslog("CMailFile::sendfile: mail end error=" . $this->error, LOG_ERR);
1129
                    $res = false;
1130
                }
1131
                $dest = $this->smtps->getTo();
1132
                if ($res && !$dest) {
1133
                    $this->error = "Failed to send mail with smtps lib to HOST=" . $server . ", PORT=" . getDolGlobalString($keyforsmtpport) . " - Recipient address '$dest' invalid";
1134
                    dol_syslog("CMailFile::sendfile: mail end error=" . $this->error, LOG_ERR);
1135
                    $res = false;
1136
                }
1137
1138
                if ($res) {
1139
                    dol_syslog("CMailFile::sendfile: sendMsg, HOST=" . $server . ", PORT=" . getDolGlobalString($keyforsmtpport), LOG_DEBUG);
1140
1141
                    if (getDolGlobalString('MAIN_MAIL_DEBUG')) {
1142
                        $this->smtps->setDebug(true);
1143
                    }
1144
1145
                    $result = $this->smtps->sendMsg();
1146
1147
                    if (getDolGlobalString('MAIN_MAIL_DEBUG')) {
1148
                        $this->dump_mail();
1149
                    }
1150
1151
                    $smtperrorcode = 0;
1152
                    if (! $result) {
1153
                        $smtperrorcode = $this->smtps->lastretval;  // SMTP error code
1154
                        dol_syslog("CMailFile::sendfile: mail SMTP error code " . $smtperrorcode, LOG_WARNING);
1155
1156
                        if ($smtperrorcode == '421') {  // Try later
1157
                            // TODO Add a delay and try again
1158
                            /*
1159
                            dol_syslog("CMailFile::sendfile: Try later error, so we wait and we retry");
1160
                            sleep(2);
1161
1162
                            $result = $this->smtps->sendMsg();
1163
1164
                            if (!empty($conf->global->MAIN_MAIL_DEBUG)) {
1165
                                $this->dump_mail();
1166
                            }
1167
                            */
1168
                        }
1169
                    }
1170
1171
                    $result = $this->smtps->getErrors();    // applicative error code (not SMTP error code)
1172
                    if (empty($this->error) && empty($result)) {
1173
                        dol_syslog("CMailFile::sendfile: mail end success", LOG_DEBUG);
1174
                        $res = true;
1175
                    } else {
1176
                        if (empty($this->error)) {
1177
                            $this->error = $result;
1178
                        }
1179
                        dol_syslog("CMailFile::sendfile: mail end error with smtps lib to HOST=" . $server . ", PORT=" . getDolGlobalString($keyforsmtpport) . " - " . $this->error, LOG_ERR);
1180
                        $res = false;
1181
1182
                        if (getDolGlobalString('MAIN_MAIL_DEBUG')) {
1183
                            $this->save_dump_mail_in_err('Mail smtp error ' . $smtperrorcode . ' with topic ' . $this->subject);
1184
                        }
1185
                    }
1186
                }
1187
            } elseif ($this->sendmode == 'swiftmailer') {
1188
                // Use Swift Mailer library
1189
                // ------------------------------------------
1190
                require_once constant('DOL_DOCUMENT_ROOT') . '/includes/swiftmailer/lib/swift_required.php';
1191
1192
                // Clean parameters
1193
                if (empty($conf->global->$keyforsmtpserver)) {
1194
                    $conf->global->$keyforsmtpserver = ini_get('SMTP');
1195
                }
1196
                if (empty($conf->global->$keyforsmtpport)) {
1197
                    $conf->global->$keyforsmtpport = ini_get('smtp_port');
1198
                }
1199
1200
                // If we use SSL/TLS
1201
                $server = getDolGlobalString($keyforsmtpserver);
1202
                $secure = '';
1203
                if (!empty($conf->global->$keyfortls) && function_exists('openssl_open')) {
1204
                    $secure = 'ssl';
1205
                }
1206
                if (!empty($conf->global->$keyforstarttls) && function_exists('openssl_open')) {
1207
                    $secure = 'tls';
1208
                }
1209
1210
                $this->transport = new Swift_SmtpTransport($server, getDolGlobalString($keyforsmtpport), $secure);
1211
1212
                if (!empty($conf->global->$keyforsmtpid)) {
1213
                    $this->transport->setUsername($conf->global->$keyforsmtpid);
1214
                }
1215
                if (!empty($conf->global->$keyforsmtppw) && getDolGlobalString($keyforsmtpauthtype) != "XOAUTH2") {
1216
                    $this->transport->setPassword($conf->global->$keyforsmtppw);
1217
                }
1218
                if (getDolGlobalString($keyforsmtpauthtype) === "XOAUTH2") {
1219
                    require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/oauth.lib.php';
1220
1221
                    $supportedoauth2array = getSupportedOauth2Array();
1222
1223
                    $keyforsupportedoauth2array = getDolGlobalString($keyforsmtpoauthservice);
1224
                    if (preg_match('/^.*-/', $keyforsupportedoauth2array)) {
1225
                        $keyforprovider = preg_replace('/^.*-/', '', $keyforsupportedoauth2array);
1226
                    } else {
1227
                        $keyforprovider = '';
1228
                    }
1229
                    $keyforsupportedoauth2array = preg_replace('/-.*$/', '', $keyforsupportedoauth2array);
1230
                    $keyforsupportedoauth2array = 'OAUTH_' . $keyforsupportedoauth2array . '_NAME';
1231
1232
                    $OAUTH_SERVICENAME = 'Unknown';
1233
                    if (
1234
                        array_key_exists($keyforsupportedoauth2array, $supportedoauth2array)
1235
                        && array_key_exists('name', $supportedoauth2array[$keyforsupportedoauth2array])
1236
                        && !empty($supportedoauth2array[$keyforsupportedoauth2array]['name'])
1237
                    ) {
1238
                        $OAUTH_SERVICENAME = $supportedoauth2array[$keyforsupportedoauth2array]['name'] . (!empty($keyforprovider) ? '-' . $keyforprovider : '');
1239
                    }
1240
1241
                    require_once constant('DOL_DOCUMENT_ROOT') . '/includes/OAuth/bootstrap.php';
1242
1243
                    $storage = new DoliStorage($db, $conf, $keyforprovider);
1244
1245
                    try {
1246
                        $tokenobj = $storage->retrieveAccessToken($OAUTH_SERVICENAME);
1247
                        $expire = false;
1248
                        // Is token expired or will token expire in the next 30 seconds
1249
                        if (is_object($tokenobj)) {
1250
                            $expire = ($tokenobj->getEndOfLife() !== -9002 && $tokenobj->getEndOfLife() !== -9001 && time() > ($tokenobj->getEndOfLife() - 30));
1251
                        }
1252
                        // Token expired so we refresh it
1253
                        if (is_object($tokenobj) && $expire) {
1254
                            $credentials = new Credentials(
1255
                                getDolGlobalString('OAUTH_' . getDolGlobalString('MAIN_MAIL_SMTPS_OAUTH_SERVICE') . '_ID'),
1256
                                getDolGlobalString('OAUTH_' . getDolGlobalString('MAIN_MAIL_SMTPS_OAUTH_SERVICE') . '_SECRET'),
1257
                                getDolGlobalString('OAUTH_' . getDolGlobalString('MAIN_MAIL_SMTPS_OAUTH_SERVICE') . '_URLAUTHORIZE')
1258
                            );
1259
                            $serviceFactory = new \OAuth\ServiceFactory();
1260
                            $oauthname = explode('-', $OAUTH_SERVICENAME);
1261
                            // ex service is Google-Emails we need only the first part Google
1262
                            $apiService = $serviceFactory->createService($oauthname[0], $credentials, $storage, array());
1263
                            // We have to save the token because Google give it only once
1264
                            $refreshtoken = $tokenobj->getRefreshToken();
1265
                            $tokenobj = $apiService->refreshAccessToken($tokenobj);
1266
                            $tokenobj->setRefreshToken($refreshtoken);
1267
                            $storage->storeAccessToken($OAUTH_SERVICENAME, $tokenobj);
1268
                        }
1269
                        if (is_object($tokenobj)) {
1270
                            $this->transport->setAuthMode('XOAUTH2');
1271
                            $this->transport->setPassword($tokenobj->getAccessToken());
1272
                        } else {
1273
                            $this->errors[] = "Token not found";
1274
                        }
1275
                    } catch (Exception $e) {
1276
                        // Return an error if token not found
1277
                        $this->errors[] = $e->getMessage();
1278
                        dol_syslog("CMailFile::sendfile: mail end error=" . $e->getMessage(), LOG_ERR);
1279
                    }
1280
                }
1281
                if (getDolGlobalString($keyforsslseflsigned)) {
1282
                    $this->transport->setStreamOptions(array('ssl' => array('allow_self_signed' => true, 'verify_peer' => false)));
1283
                }
1284
                //$smtps->_msgReplyTo  = '[email protected]';
1285
1286
                // Switch content encoding to base64 - avoid the doubledot issue with quoted-printable
1287
                $contentEncoderBase64 = new Swift_Mime_ContentEncoder_Base64ContentEncoder();
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Core\Class...er_Base64ContentEncoder was not found. Did you mean Swift_Mime_ContentEncoder_Base64ContentEncoder? If so, make sure to prefix the type with \.
Loading history...
1288
                $this->message->setEncoder($contentEncoderBase64);
1289
1290
                // Create the Mailer using your created Transport
1291
                $this->mailer = new Swift_Mailer($this->transport);
1292
1293
                // DKIM SIGN
1294
                if (getDolGlobalString('MAIN_MAIL_EMAIL_DKIM_ENABLED')) {
1295
                    $privateKey = getDolGlobalString('MAIN_MAIL_EMAIL_DKIM_PRIVATE_KEY');
1296
                    $domainName = getDolGlobalString('MAIN_MAIL_EMAIL_DKIM_DOMAIN');
1297
                    $selector = getDolGlobalString('MAIN_MAIL_EMAIL_DKIM_SELECTOR');
1298
                    $signer = new Swift_Signers_DKIMSigner($privateKey, $domainName, $selector);
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Core\Class...wift_Signers_DKIMSigner was not found. Did you mean Swift_Signers_DKIMSigner? If so, make sure to prefix the type with \.
Loading history...
1299
                    $this->message->attachSigner($signer->ignoreHeader('Return-Path'));
1300
                }
1301
1302
                if (getDolGlobalString('MAIN_MAIL_DEBUG')) {
1303
                    // To use the ArrayLogger
1304
                    $this->logger = new Swift_Plugins_Loggers_ArrayLogger();
1305
                    // Or to use the Echo Logger
1306
                    //$this->logger = new Swift_Plugins_Loggers_EchoLogger();
1307
                    $this->mailer->registerPlugin(new Swift_Plugins_LoggerPlugin($this->logger));
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Core\Class...ft_Plugins_LoggerPlugin was not found. Did you mean Swift_Plugins_LoggerPlugin? If so, make sure to prefix the type with \.
Loading history...
1308
                }
1309
1310
                dol_syslog("CMailFile::sendfile: mailer->send, HOST=" . $server . ", PORT=" . getDolGlobalString($keyforsmtpport), LOG_DEBUG);
1311
1312
                // send mail
1313
                $failedRecipients = array();
1314
                try {
1315
                    $result = $this->mailer->send($this->message, $failedRecipients);
1316
                } catch (Exception $e) {
1317
                    $this->errors[] = $e->getMessage();
1318
                }
1319
                if (getDolGlobalString('MAIN_MAIL_DEBUG')) {
1320
                    $this->dump_mail();
1321
                }
1322
1323
                $res = true;
1324
                if (!empty($this->error) || !empty($this->errors) || !$result) {
1325
                    if (!empty($failedRecipients)) {
1326
                        $this->errors[] = 'Transport failed for the following addresses: "' . implode('", "', $failedRecipients) . '".';
1327
                    }
1328
                    dol_syslog("CMailFile::sendfile: mail end error=" . $this->error, LOG_ERR);
1329
                    $res = false;
1330
1331
                    if (getDolGlobalString('MAIN_MAIL_DEBUG')) {
1332
                        $this->save_dump_mail_in_err('Mail with topic ' . $this->subject);
1333
                    }
1334
                } else {
1335
                    dol_syslog("CMailFile::sendfile: mail end success", LOG_DEBUG);
1336
                }
1337
            } else {
1338
                // Send mail method not correctly defined
1339
                // --------------------------------------
1340
1341
                $this->error = 'Bad value for sendmode';
1342
                return false;
1343
            }
1344
1345
            // Now we delete image files that were created dynamically to manage data inline files
1346
            foreach ($this->html_images as $val) {
1347
                if (!empty($val['type']) && $val['type'] == 'cidfromdata') {
1348
                    //dol_delete($val['fullpath']);
1349
                }
1350
            }
1351
1352
            $parameters = array('sent' => $res);
1353
            $action = '';
1354
            $reshook = $hookmanager->executeHooks('sendMailAfter', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1355
            if ($reshook < 0) {
1356
                $this->error = "Error in hook maildao sendMailAfter " . $reshook;
1357
                dol_syslog("CMailFile::sendfile: mail end error=" . $this->error, LOG_ERR);
1358
1359
                return false;
1360
            }
1361
        } else {
1362
            $this->error = 'No mail sent. Feature is disabled by option MAIN_DISABLE_ALL_MAILS';
1363
            dol_syslog("CMailFile::sendfile: " . $this->error, LOG_WARNING);
1364
        }
1365
1366
        error_reporting($errorlevel); // Reactive niveau erreur origine
1367
        return $res;
1368
    }
1369
1370
    /**
1371
     * Encode subject according to RFC 2822 - http://en.wikipedia.org/wiki/MIME#Encoded-Word
1372
     *
1373
     * @param string $stringtoencode String to encode
1374
     * @return string                string encoded
1375
     */
1376
    public static function encodetorfc2822($stringtoencode)
1377
    {
1378
        global $conf;
1379
        return '=?' . $conf->file->character_set_client . '?B?' . base64_encode($stringtoencode) . '?=';
1380
    }
1381
1382
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1383
    /**
1384
     * Read a file on disk and return encoded content for emails (mode = 'mail')
1385
     *
1386
     * @param   string  $sourcefile     Path to file to encode
1387
     * @return  int|string              Return integer <0 if KO, encoded string if OK
1388
     */
1389
    private function _encode_file($sourcefile)
1390
    {
1391
		// phpcs:enable
1392
        $newsourcefile = dol_osencode($sourcefile);
1393
1394
        if (is_readable($newsourcefile)) {
1395
            $contents = file_get_contents($newsourcefile); // Need PHP 4.3
1396
            $encoded = chunk_split(base64_encode($contents), 76, $this->eol); // 76 max is defined into http://tools.ietf.org/html/rfc2047
1397
            return $encoded;
1398
        } else {
1399
            $this->error = "Error in _encode_file() method: Can't read file '" . $sourcefile . "'";
1400
            dol_syslog("CMailFile::_encode_file: " . $this->error, LOG_ERR);
1401
            return -1;
1402
        }
1403
    }
1404
1405
1406
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1407
    /**
1408
     *  Write content of a SMTP request into a dump file (mode = all)
1409
     *  Used for debugging.
1410
     *  Note that to see full SMTP protocol, you can use tcpdump -w /tmp/smtp -s 2000 port 25"
1411
     *
1412
     *  @return void
1413
     */
1414
    public function dump_mail()
1415
    {
1416
		// phpcs:enable
1417
        global $dolibarr_main_data_root;
1418
1419
        if (@is_writable($dolibarr_main_data_root)) {   // Avoid fatal error on fopen with open_basedir
1420
            $outputfile = $dolibarr_main_data_root . "/dolibarr_mail.log";
1421
            $fp = fopen($outputfile, "w");  // overwrite
1422
1423
            if ($this->sendmode == 'mail') {
1424
                fwrite($fp, $this->headers);
1425
                fwrite($fp, $this->eol); // This eol is added by the mail function, so we add it in log
1426
                fwrite($fp, $this->message);
1427
            } elseif ($this->sendmode == 'smtps') {
1428
                fwrite($fp, $this->smtps->log); // this->smtps->log is filled only if MAIN_MAIL_DEBUG was set to on
1429
            } elseif ($this->sendmode == 'swiftmailer') {
1430
                fwrite($fp, "smtpheader=\n" . $this->message->getHeaders()->toString() . "\n");
1431
                fwrite($fp, $this->logger->dump()); // this->logger is filled only if MAIN_MAIL_DEBUG was set to on
1432
            }
1433
1434
            fclose($fp);
1435
            dolChmod($outputfile);
1436
1437
            // Move dolibarr_mail.log into a dolibarr_mail.YYYYMMDD.log
1438
            if (getDolGlobalString('MAIN_MAIL_DEBUG_LOG_WITH_DATE')) {
1439
                $destfile = $dolibarr_main_data_root . "/dolibarr_mail." . dol_print_date(dol_now(), 'dayhourlog', 'gmt') . ".log";
1440
1441
                require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
1442
                dol_move($outputfile, $destfile, 0, 1, 0, 0);
1443
            }
1444
        }
1445
    }
1446
1447
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1448
    /**
1449
     *  Save content if mail is in error
1450
     *  Used for debugging.
1451
     *
1452
     *  @param  string      $message        Add also a message
1453
     *  @return void
1454
     */
1455
    public function save_dump_mail_in_err($message = '')
1456
    {
1457
        global $dolibarr_main_data_root;
1458
1459
        if (@is_writable($dolibarr_main_data_root)) {   // Avoid fatal error on fopen with open_basedir
1460
            $srcfile = $dolibarr_main_data_root . "/dolibarr_mail.log";
1461
1462
            // Add message to dolibarr_mail.log. We do not use dol_syslog() on purpose,
1463
            // to be sure to write into dolibarr_mail.log
1464
            if ($message) {
1465
                // Test constant SYSLOG_FILE_NO_ERROR (should stay a constant defined with define('SYSLOG_FILE_NO_ERROR',1);
1466
                if (defined('SYSLOG_FILE_NO_ERROR')) {
1467
                    $filefd = @fopen($srcfile, 'a+');
1468
                } else {
1469
                    $filefd = fopen($srcfile, 'a+');
1470
                }
1471
                if ($filefd) {
1472
                    fwrite($filefd, $message . "\n");
1473
                    fclose($filefd);
1474
                    dolChmod($srcfile);
1475
                }
1476
            }
1477
1478
            // Move dolibarr_mail.log into a dolibarr_mail.err or dolibarr_mail.date.err
1479
            if (getDolGlobalString('MAIN_MAIL_DEBUG_ERR_WITH_DATE')) {
1480
                $destfile = $dolibarr_main_data_root . "/dolibarr_mail." . dol_print_date(dol_now(), 'dayhourlog', 'gmt') . ".err";
1481
            } else {
1482
                $destfile = $dolibarr_main_data_root . "/dolibarr_mail.err";
1483
            }
1484
1485
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
1486
            dol_move($srcfile, $destfile, 0, 1, 0, 0);
1487
        }
1488
    }
1489
1490
1491
    /**
1492
     * Correct an incomplete html string
1493
     *
1494
     * @param   string  $msg    String
1495
     * @return  string          Completed string
1496
     */
1497
    public function checkIfHTML($msg)
1498
    {
1499
        if (!preg_match('/^[\s\t]*<html/i', $msg)) {
1500
            $out = "<html><head><title></title>";
1501
            if (!empty($this->styleCSS)) {
1502
                $out .= $this->styleCSS;
1503
            }
1504
            $out .= "</head><body";
1505
            if (!empty($this->bodyCSS)) {
1506
                $out .= $this->bodyCSS;
1507
            }
1508
            $out .= ">";
1509
            $out .= $msg;
1510
            $out .= "</body></html>";
1511
        } else {
1512
            $out = $msg;
1513
        }
1514
1515
        return $out;
1516
    }
1517
1518
    /**
1519
     * Build a css style (mode = all) into this->styleCSS and this->bodyCSS
1520
     *
1521
     * @return void
1522
     */
1523
    public function buildCSS()
1524
    {
1525
        if (!empty($this->css)) {
1526
            // Style CSS
1527
            $this->styleCSS = '<style type="text/css">';
1528
            $this->styleCSS .= 'body {';
1529
1530
            if ($this->css['bgcolor']) {
1531
                $this->styleCSS .= '  background-color: ' . $this->css['bgcolor'] . ';';
1532
                $this->bodyCSS .= ' bgcolor="' . $this->css['bgcolor'] . '"';
1533
            }
1534
            if ($this->css['bgimage']) {
1535
                // TODO recuperer cid
1536
                $this->styleCSS .= ' background-image: url("cid:' . $this->css['bgimage_cid'] . '");';
1537
            }
1538
            $this->styleCSS .= '}';
1539
            $this->styleCSS .= '</style>';
1540
        }
1541
    }
1542
1543
1544
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1545
    /**
1546
     * Create SMTP headers (mode = 'mail')
1547
     *
1548
     * @return  string headers
1549
     */
1550
    public function write_smtpheaders()
1551
    {
1552
		// phpcs:enable
1553
        $out = "";
1554
        $host = dol_getprefix('email');
1555
1556
        // Sender
1557
        //$out.= "Sender: ".getValidAddress($this->addr_from,2)).$this->eol2;
1558
        $out .= "From: " . $this->getValidAddress($this->addr_from, 3, 1) . $this->eol2;
1559
        if (getDolGlobalString('MAIN_MAIL_SENDMAIL_FORCE_BA')) {
1560
            $out .= "To: " . $this->getValidAddress($this->addr_to, 0, 1) . $this->eol2;
1561
        }
1562
        // Return-Path is important because it is used by SPF. Some MTA does not read Return-Path from header but from command line. See option MAIN_MAIL_ALLOW_SENDMAIL_F for that.
1563
        $out .= "Return-Path: " . $this->getValidAddress($this->addr_from, 0, 1) . $this->eol2;
1564
        if (isset($this->reply_to) && $this->reply_to) {
1565
            $out .= "Reply-To: " . $this->getValidAddress($this->reply_to, 2) . $this->eol2;
1566
        }
1567
        if (isset($this->errors_to) && $this->errors_to) {
1568
            $out .= "Errors-To: " . $this->getValidAddress($this->errors_to, 2) . $this->eol2;
1569
        }
1570
1571
        // Receiver
1572
        if (isset($this->addr_cc) && $this->addr_cc) {
1573
            $out .= "Cc: " . $this->getValidAddress($this->addr_cc, 2) . $this->eol2;
1574
        }
1575
        if (isset($this->addr_bcc) && $this->addr_bcc) {
1576
            $out .= "Bcc: " . $this->getValidAddress($this->addr_bcc, 2) . $this->eol2; // TODO Question: bcc must not be into header, only into SMTP command "RCPT TO". Does php mail support this ?
1577
        }
1578
1579
        // Delivery receipt
1580
        if (isset($this->deliveryreceipt) && $this->deliveryreceipt == 1) {
1581
            $out .= "Disposition-Notification-To: " . $this->getValidAddress($this->addr_from, 2) . $this->eol2;
1582
        }
1583
1584
        //$out.= "X-Priority: 3".$this->eol2;
1585
1586
        $out .= 'Date: ' . date("r") . $this->eol2;
1587
1588
        $trackid = $this->trackid;
1589
        if ($trackid) {
1590
            $this->msgid = time() . '.phpmail-dolibarr-' . $trackid . '@' . $host;
1591
            $out .= 'Message-ID: <' . $this->msgid . ">" . $this->eol2; // Uppercase seems replaced by phpmail
1592
            $out .= 'X-Dolibarr-TRACKID: ' . $trackid . '@' . $host . $this->eol2;
1593
        } else {
1594
            $this->msgid = time() . '.phpmail@' . $host;
1595
            $out .= 'Message-ID: <' . $this->msgid . ">" . $this->eol2;
1596
        }
1597
1598
        // Add 'In-Reply-To:' header with the Message-Id we answer
1599
        if (!empty($this->in_reply_to)) {
1600
            $out .= 'In-Reply-To: <' . $this->in_reply_to . '>' . $this->eol2;
1601
        }
1602
        // Add 'References:' header with list of all Message-ID in thread history
1603
        if (!empty($this->references)) {
1604
            $out .= 'References: ' . $this->references . $this->eol2;
1605
        }
1606
1607
        if (!empty($_SERVER['REMOTE_ADDR'])) {
1608
            $out .= "X-RemoteAddr: " . $_SERVER['REMOTE_ADDR'] . $this->eol2;
1609
        }
1610
        $out .= "X-Mailer: Dolibarr version " . DOL_VERSION . " (using php mail)" . $this->eol2;
1611
        $out .= "Mime-Version: 1.0" . $this->eol2;
1612
1613
        //$out.= "From: ".$this->getValidAddress($this->addr_from,3,1).$this->eol;
1614
1615
        $out .= "Content-Type: multipart/mixed;" . $this->eol2 . " boundary=\"" . $this->mixed_boundary . "\"" . $this->eol2;
1616
        $out .= "Content-Transfer-Encoding: 8bit" . $this->eol2; // TODO Seems to be ignored. Header is 7bit once received.
1617
1618
        dol_syslog("CMailFile::write_smtpheaders smtp_header=\n" . $out);
1619
        return $out;
1620
    }
1621
1622
1623
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1624
    /**
1625
     * Create header MIME (mode = 'mail')
1626
     *
1627
     * @param   array   $filename_list          Array of filenames
1628
     * @param   array   $mimefilename_list      Array of mime types
1629
     * @return  string                          mime headers
1630
     */
1631
    public function write_mimeheaders($filename_list, $mimefilename_list)
1632
    {
1633
		// phpcs:enable
1634
        $mimedone = 0;
1635
        $out = "";
1636
1637
        if (is_array($filename_list)) {
1638
            $filename_list_size = count($filename_list);
1639
            for ($i = 0; $i < $filename_list_size; $i++) {
1640
                if ($filename_list[$i]) {
1641
                    if ($mimefilename_list[$i]) {
1642
                        $filename_list[$i] = $mimefilename_list[$i];
1643
                    }
1644
                    $out .= "X-attachments: $filename_list[$i]" . $this->eol2;
1645
                }
1646
            }
1647
        }
1648
1649
        dol_syslog("CMailFile::write_mimeheaders mime_header=\n" . $out, LOG_DEBUG);
1650
        return $out;
1651
    }
1652
1653
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1654
    /**
1655
     * Return email content (mode = 'mail')
1656
     *
1657
     * @param   string      $msgtext        Message string
1658
     * @return  string                      String content
1659
     */
1660
    public function write_body($msgtext)
1661
    {
1662
		// phpcs:enable
1663
        global $conf;
1664
1665
        $out = '';
1666
1667
        $out .= "--" . $this->mixed_boundary . $this->eol;
1668
1669
        if ($this->atleastoneimage) {
1670
            $out .= "Content-Type: multipart/alternative;" . $this->eol . " boundary=\"" . $this->alternative_boundary . "\"" . $this->eol;
1671
            $out .= $this->eol;
1672
            $out .= "--" . $this->alternative_boundary . $this->eol;
1673
        }
1674
1675
        // Make RFC821 Compliant, replace bare linefeeds
1676
        $strContent = preg_replace("/(?<!\r)\n/si", "\r\n", $msgtext); // PCRE modifier /s means new lines are common chars
1677
        if (getDolGlobalString('MAIN_FIX_FOR_BUGGED_MTA')) {
1678
            $strContent = preg_replace("/\r\n/si", "\n", $strContent); // PCRE modifier /s means new lines are common chars
1679
        }
1680
1681
        $strContentAltText = '';
1682
        if ($this->msgishtml) {
1683
            // Similar code to forge a text from html is also in smtps.class.php
1684
            $strContentAltText = preg_replace("/<br\s*[^>]*>/", " ", $strContent);
1685
            // TODO We could replace <img ...> with [Filename.ext] like Gmail do.
1686
            $strContentAltText = html_entity_decode(strip_tags($strContentAltText));    // Remove any HTML tags
1687
            $strContentAltText = trim(wordwrap($strContentAltText, 75, !getDolGlobalString('MAIN_FIX_FOR_BUGGED_MTA') ? "\r\n" : "\n"));
1688
1689
            // Check if html header already in message, if not complete the message
1690
            $strContent = $this->checkIfHTML($strContent);      // This add a header and a body including custom CSS to the HTML content
1691
        }
1692
1693
        // Make RFC2045 Compliant, split lines
1694
        //$strContent = rtrim(chunk_split($strContent));    // Function chunck_split seems ko if not used on a base64 content
1695
        // TODO Encode main content into base64 and use the chunk_split, or quoted-printable
1696
        $strContent = rtrim(wordwrap($strContent, 75, !getDolGlobalString('MAIN_FIX_FOR_BUGGED_MTA') ? "\r\n" : "\n")); // TODO Using this method creates unexpected line break on text/plain content.
1697
1698
        if ($this->msgishtml) {
1699
            if ($this->atleastoneimage) {
1700
                $out .= "Content-Type: text/plain; charset=" . $conf->file->character_set_client . $this->eol;
1701
                //$out.= "Content-Transfer-Encoding: 7bit".$this->eol;
1702
                $out .= $this->eol . ($strContentAltText ? $strContentAltText : strip_tags($strContent)) . $this->eol; // Add plain text message
1703
                $out .= "--" . $this->alternative_boundary . $this->eol;
1704
                $out .= "Content-Type: multipart/related;" . $this->eol . " boundary=\"" . $this->related_boundary . "\"" . $this->eol;
1705
                $out .= $this->eol;
1706
                $out .= "--" . $this->related_boundary . $this->eol;
1707
            }
1708
1709
            if (!$this->atleastoneimage && $strContentAltText && getDolGlobalString('MAIN_MAIL_USE_MULTI_PART')) {    // Add plain text message part before html part
1710
                $out .= "Content-Type: multipart/alternative;" . $this->eol . " boundary=\"" . $this->alternative_boundary . "\"" . $this->eol;
1711
                $out .= $this->eol;
1712
                $out .= "--" . $this->alternative_boundary . $this->eol;
1713
                $out .= "Content-Type: text/plain; charset=" . $conf->file->character_set_client . $this->eol;
1714
                //$out.= "Content-Transfer-Encoding: 7bit".$this->eol;
1715
                $out .= $this->eol . $strContentAltText . $this->eol;
1716
                $out .= "--" . $this->alternative_boundary . $this->eol;
1717
            }
1718
1719
            $out .= "Content-Type: text/html; charset=" . $conf->file->character_set_client . $this->eol;
1720
            //$out.= "Content-Transfer-Encoding: 7bit".$this->eol;  // TODO Use base64
1721
            $out .= $this->eol . $strContent . $this->eol;
1722
1723
            if (!$this->atleastoneimage && $strContentAltText && getDolGlobalString('MAIN_MAIL_USE_MULTI_PART')) {    // Add plain text message part after html part
1724
                $out .= "--" . $this->alternative_boundary . "--" . $this->eol;
1725
            }
1726
        } else {
1727
            $out .= "Content-Type: text/plain; charset=" . $conf->file->character_set_client . $this->eol;
1728
            //$out.= "Content-Transfer-Encoding: 7bit".$this->eol;
1729
            $out .= $this->eol . $strContent . $this->eol;
1730
        }
1731
1732
        $out .= $this->eol;
1733
1734
        // Encode images
1735
        if ($this->atleastoneimage) {
1736
            $out .= $this->write_images($this->images_encoded);
1737
            // always end related and end alternative after inline images
1738
            $out .= "--" . $this->related_boundary . "--" . $this->eol;
1739
            $out .= $this->eol . "--" . $this->alternative_boundary . "--" . $this->eol;
1740
            $out .= $this->eol;
1741
        }
1742
1743
        return $out;
1744
    }
1745
1746
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1747
    /**
1748
     * Attach file to email (mode = 'mail')
1749
     *
1750
     * @param   array   $filename_list      Tableau
1751
     * @param   array   $mimetype_list      Tableau
1752
     * @param   array   $mimefilename_list  Tableau
1753
     * @param   array   $cidlist            Array of CID if file must be completed with CID code
1754
     * @return  string|int                      String with files encoded
1755
     */
1756
    private function write_files($filename_list, $mimetype_list, $mimefilename_list, $cidlist)
1757
    {
1758
		// phpcs:enable
1759
        $out = '';
1760
1761
        $filename_list_size = count($filename_list);
1762
        for ($i = 0; $i < $filename_list_size; $i++) {
1763
            if ($filename_list[$i]) {
1764
                dol_syslog("CMailFile::write_files: i=$i " . $filename_list[$i]);
1765
                $encoded = $this->_encode_file($filename_list[$i]);
1766
                if ($encoded !== -1) {
1767
                    if ($mimefilename_list[$i]) {
1768
                        $filename_list[$i] = $mimefilename_list[$i];
1769
                    }
1770
                    if (!$mimetype_list[$i]) {
1771
                        $mimetype_list[$i] = "application/octet-stream";
1772
                    }
1773
1774
                    $out .= "--" . $this->mixed_boundary . $this->eol;
1775
                    $out .= "Content-Disposition: attachment; filename=\"" . $filename_list[$i] . "\"" . $this->eol;
1776
                    $out .= "Content-Type: " . $mimetype_list[$i] . "; name=\"" . $filename_list[$i] . "\"" . $this->eol;
1777
                    $out .= "Content-Transfer-Encoding: base64" . $this->eol;
1778
                    $out .= "Content-Description: " . $filename_list[$i] . $this->eol;
1779
                    if (!empty($cidlist) && is_array($cidlist) && $cidlist[$i]) {
1780
                        $out .= "X-Attachment-Id: " . $cidlist[$i] . $this->eol;
1781
                        $out .= "Content-ID: <" . $cidlist[$i] . '>' . $this->eol;
1782
                    }
1783
                    $out .= $this->eol;
1784
                    $out .= $encoded;
1785
                    $out .= $this->eol;
1786
                    //$out.= $this->eol;
1787
                } else {
1788
                    return $encoded;
1789
                }
1790
            }
1791
        }
1792
1793
        return $out;
1794
    }
1795
1796
1797
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1798
    /**
1799
     * Attach an image to email (mode = 'mail')
1800
     *
1801
     * @param   array   $images_list    Array of array image
1802
     * @return  string                  Chaine images encodees
1803
     */
1804
    public function write_images($images_list)
1805
    {
1806
		// phpcs:enable
1807
        $out = '';
1808
1809
        if (is_array($images_list)) {
1810
            foreach ($images_list as $img) {
1811
                dol_syslog("CMailFile::write_images: " . $img["name"]);
1812
1813
                $out .= "--" . $this->related_boundary . $this->eol; // always related for an inline image
1814
                $out .= "Content-Type: " . $img["content_type"] . "; name=\"" . $img["name"] . "\"" . $this->eol;
1815
                $out .= "Content-Transfer-Encoding: base64" . $this->eol;
1816
                $out .= "Content-Disposition: inline; filename=\"" . $img["name"] . "\"" . $this->eol;
1817
                $out .= "Content-ID: <" . $img["cid"] . ">" . $this->eol;
1818
                $out .= $this->eol;
1819
                $out .= $img["image_encoded"];
1820
                $out .= $this->eol;
1821
            }
1822
        }
1823
1824
        return $out;
1825
    }
1826
1827
1828
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1829
    /**
1830
     * Try to create a socket connection
1831
     *
1832
     * @param   string      $host       Add ssl:// for SSL/TLS.
1833
     * @param   int         $port       Example: 25, 465
1834
     * @return  int                     Socket id if ok, 0 if KO
1835
     */
1836
    public function check_server_port($host, $port)
1837
    {
1838
		// phpcs:enable
1839
        global $conf;
1840
1841
        $_retVal = 0;
1842
        $timeout = 5; // Timeout in seconds
1843
1844
        if (function_exists('fsockopen')) {
1845
            $keyforsmtpserver = 'MAIN_MAIL_SMTP_SERVER';
1846
            $keyforsmtpport  = 'MAIN_MAIL_SMTP_PORT';
1847
            $keyforsmtpid    = 'MAIN_MAIL_SMTPS_ID';
1848
            $keyforsmtppw    = 'MAIN_MAIL_SMTPS_PW';
1849
            $keyforsmtpauthtype = 'MAIN_MAIL_SMTPS_AUTH_TYPE';
1850
            $keyforsmtpoauthservice = 'MAIN_MAIL_SMTPS_OAUTH_SERVICE';
1851
            $keyfortls       = 'MAIN_MAIL_EMAIL_TLS';
1852
            $keyforstarttls  = 'MAIN_MAIL_EMAIL_STARTTLS';
1853
            $keyforsslseflsigned = 'MAIN_MAIL_EMAIL_SMTP_ALLOW_SELF_SIGNED';
1854
1855
            if (!empty($this->sendcontext)) {
1856
                $smtpContextKey = strtoupper($this->sendcontext);
1857
                $smtpContextSendMode = getDolGlobalString('MAIN_MAIL_SENDMODE_' . $smtpContextKey);
1858
                if (!empty($smtpContextSendMode) && $smtpContextSendMode != 'default') {
1859
                    $keyforsmtpserver = 'MAIN_MAIL_SMTP_SERVER_' . $smtpContextKey;
1860
                    $keyforsmtpport   = 'MAIN_MAIL_SMTP_PORT_' . $smtpContextKey;
1861
                    $keyforsmtpid     = 'MAIN_MAIL_SMTPS_ID_' . $smtpContextKey;
1862
                    $keyforsmtppw     = 'MAIN_MAIL_SMTPS_PW_' . $smtpContextKey;
1863
                    $keyforsmtpauthtype = 'MAIN_MAIL_SMTPS_AUTH_TYPE_' . $smtpContextKey;
1864
                    $keyforsmtpoauthservice = 'MAIN_MAIL_SMTPS_OAUTH_SERVICE_' . $smtpContextKey;
1865
                    $keyfortls        = 'MAIN_MAIL_EMAIL_TLS_' . $smtpContextKey;
1866
                    $keyforstarttls   = 'MAIN_MAIL_EMAIL_STARTTLS_' . $smtpContextKey;
1867
                    $keyforsslseflsigned = 'MAIN_MAIL_EMAIL_SMTP_ALLOW_SELF_SIGNED_' . $smtpContextKey;
1868
                }
1869
            }
1870
1871
            // If we use SSL/TLS
1872
            if (!empty($conf->global->$keyfortls) && function_exists('openssl_open')) {
1873
                $host = 'ssl://' . $host;
1874
            }
1875
            // tls smtp start with no encryption
1876
            //if (!empty($conf->global->MAIN_MAIL_EMAIL_STARTTLS) && function_exists('openssl_open')) $host='tls://'.$host;
1877
1878
            dol_syslog("Try socket connection to host=" . $host . " port=" . $port . " timeout=" . $timeout);
1879
            //See if we can connect to the SMTP server
1880
            $errno = 0;
1881
            $errstr = '';
1882
            if (
1883
                $socket = @fsockopen(
1884
                $host, // Host to test, IP or domain. Add ssl:// for SSL/TLS.
1885
                $port, // which Port number to use
1886
                $errno, // actual system level error
1887
                $errstr, // and any text that goes with the error
1888
                $timeout     // timeout for reading/writing data over the socket
1889
                )
1890
            ) {
1891
                // Windows still does not have support for this timeout function
1892
                if (function_exists('stream_set_timeout')) {
1893
                    stream_set_timeout($socket, $timeout, 0);
1894
                }
1895
1896
                dol_syslog("Now we wait for answer 220");
1897
1898
                // Check response from Server
1899
                if ($_retVal = $this->server_parse($socket, "220")) {
1900
                    $_retVal = $socket;
1901
                }
1902
            } else {
1903
                $this->error = utf8_check('Error ' . $errno . ' - ' . $errstr) ? 'Error ' . $errno . ' - ' . $errstr : mb_convert_encoding('Error ' . $errno . ' - ' . $errstr, 'UTF-8', 'ISO-8859-1');
0 ignored issues
show
Documentation Bug introduced by
It seems like utf8_check('Error ' . $e... 'UTF-8', 'ISO-8859-1') can also be of type array. However, the property $error is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
1904
            }
1905
        }
1906
        return $_retVal;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $_retVal also could return the type false|resource which is incompatible with the documented return type integer.
Loading history...
1907
    }
1908
1909
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1910
    /**
1911
     * This function has been modified as provided by SirSir to allow multiline responses when
1912
     * using SMTP Extensions.
1913
     *
1914
     * @param   resource    $socket         Socket
1915
     * @param   string      $response       Response string
1916
     * @return  boolean                     true if success
1917
     */
1918
    public function server_parse($socket, $response)
1919
    {
1920
		// phpcs:enable
1921
        $_retVal = true; // Indicates if Object was created or not
1922
        $server_response = '';
1923
1924
        while (substr($server_response, 3, 1) != ' ') {
1925
            if (!($server_response = fgets($socket, 256))) {
1926
                $this->error = "Couldn't get mail server response codes";
1927
                return false;
1928
            }
1929
        }
1930
1931
        if (!(substr($server_response, 0, 3) == $response)) {
1932
            $this->error = "Ran into problems sending Mail.\r\nResponse: $server_response";
1933
            $_retVal = false;
1934
        }
1935
1936
        return $_retVal;
1937
    }
1938
1939
    /**
1940
     * Search images into html message and init array this->images_encoded if found
1941
     *
1942
     * @param   string  $images_dir     Path to store physical images files. For example $dolibarr_main_data_root.'/medias'
1943
     * @return  int                     >0 if OK, <0 if KO
1944
     */
1945
    private function findHtmlImages($images_dir)
1946
    {
1947
        // Build the array of image extensions
1948
        $extensions = array_keys($this->image_types);
1949
1950
        // We search (into mail body this->html), if we find some strings like "... file=xxx.img"
1951
        // For example when:
1952
        // <img alt="" src="/viewimage.php?modulepart=medias&amp;entity=1&amp;file=image/picture.jpg" style="height:356px; width:1040px" />
1953
        $matches = array();
1954
        preg_match_all('/(?:"|\')([^"\']+\.(' . implode('|', $extensions) . '))(?:"|\')/Ui', $this->html, $matches); // If "xxx.ext" or 'xxx.ext' found
1955
1956
        if (!empty($matches) && !empty($matches[1])) {
1957
            $i = 0;
1958
            // We are interested in $matches[1] only (the second set of parenthesis into regex)
1959
            foreach ($matches[1] as $full) {
1960
                $regs = array();
1961
                if (preg_match('/file=([A-Za-z0-9_\-\/]+[\.]?[A-Za-z0-9]+)?$/i', $full, $regs)) {   // If xxx is 'file=aaa'
1962
                    $img = $regs[1];
1963
1964
                    if (file_exists($images_dir . '/' . $img)) {
1965
                        // Image path in src
1966
                        $src = preg_quote($full, '/');
1967
                        // Image full path
1968
                        $this->html_images[$i]["fullpath"] = $images_dir . '/' . $img;
1969
                        // Image name
1970
                        $this->html_images[$i]["name"] = $img;
1971
                        // Content type
1972
                        $regext = array();
1973
                        if (preg_match('/^.+\.(\w{3,4})$/', $img, $regext)) {
1974
                            $ext = strtolower($regext[1]);
1975
                            $this->html_images[$i]["content_type"] = $this->image_types[$ext];
1976
                        }
1977
                        // cid
1978
                        $this->html_images[$i]["cid"] = dol_hash($this->html_images[$i]["fullpath"], 'md5'); // Force md5 hash (does not contain special chars)
1979
                        // type
1980
                        $this->html_images[$i]["type"] = 'cidfromurl';
1981
1982
                        $this->html = preg_replace("/src=\"$src\"|src='$src'/i", "src=\"cid:" . $this->html_images[$i]["cid"] . "\"", $this->html);
1983
                    }
1984
                    $i++;
1985
                }
1986
            }
1987
1988
            if (!empty($this->html_images)) {
1989
                $inline = array();
1990
1991
                $i = 0;
1992
1993
                foreach ($this->html_images as $img) {
1994
                    $fullpath = $images_dir . '/' . $img["name"];
1995
1996
                    // If duplicate images are embedded, they may show up as attachments, so remove them.
1997
                    if (!in_array($fullpath, $inline)) {
1998
                        // Read image file
1999
                        if ($image = file_get_contents($fullpath)) {
2000
                            // On garde que le nom de l'image
2001
                            $regs = array();
2002
                            preg_match('/([A-Za-z0-9_-]+[\.]?[A-Za-z0-9]+)?$/i', $img["name"], $regs);
2003
                            $imgName = $regs[1];
2004
2005
                            $this->images_encoded[$i]['name'] = $imgName;
2006
                            $this->images_encoded[$i]['fullpath'] = $fullpath;
2007
                            $this->images_encoded[$i]['content_type'] = $img["content_type"];
2008
                            $this->images_encoded[$i]['cid'] = $img["cid"];
2009
                            // Encodage de l'image
2010
                            $this->images_encoded[$i]["image_encoded"] = chunk_split(base64_encode($image), 68, $this->eol);
2011
                            $inline[] = $fullpath;
2012
                        }
2013
                    }
2014
                    $i++;
2015
                }
2016
            } else {
2017
                return -1;
2018
            }
2019
2020
            return 1;
2021
        } else {
2022
            return 0;
2023
        }
2024
    }
2025
2026
    /**
2027
     * Seearch images with data:image format into html message.
2028
     * If we find some, we create it on disk.
2029
     *
2030
     * @param   string  $images_dir     Location of where to store physically images files. For example $dolibarr_main_data_root.'/medias'
2031
     * @return  int                     >0 if OK, <0 if KO
2032
     */
2033
    private function findHtmlImagesIsSrcData($images_dir)
2034
    {
2035
        global $conf;
2036
2037
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
2038
2039
        // Build the array of image extensions
2040
        $extensions = array_keys($this->image_types);
2041
2042
        if (empty($images_dir)) {
2043
            //$images_dir = $conf->admin->dir_output.'/temp/'.uniqid('cmailfile');
2044
            $images_dir = $conf->admin->dir_output . '/temp/cmailfile';
2045
        }
2046
2047
        if ($images_dir && !dol_is_dir($images_dir)) {
2048
            dol_mkdir($images_dir, DOL_DATA_ROOT);
2049
        }
2050
2051
        // Uncomment this for debug
2052
        /*
2053
        global $dolibarr_main_data_root;
2054
        $outputfile = $dolibarr_main_data_root."/dolibarr_mail.log";
2055
        $fp = fopen($outputfile, "w+");
2056
        fwrite($fp, $this->html);
2057
        fclose($fp);
2058
        */
2059
2060
        // We search (into mail body this->html), if we find some strings like "... file=xxx.img"
2061
        // For example when:
2062
        // <img alt="" src="/src="data:image....;base64,...." />
2063
        $matches = array();
2064
        preg_match_all('/src="data:image\/(' . implode('|', $extensions) . ');base64,([^"]+)"/Ui', $this->html, $matches); // If "xxx.ext" or 'xxx.ext' found
2065
2066
        if (!empty($matches) && !empty($matches[1])) {
2067
            if (empty($images_dir)) {
2068
                // No temp directory provided, so we are not able to support conversion of data:image into physical images.
2069
                $this->errors[] = 'NoTempDirProvidedInCMailConstructorSoCantConvertDataImgOnDisk';
2070
                return -1;
2071
            }
2072
2073
            $i = count($this->html_images);
2074
            foreach ($matches[1] as $key => $ext) {
2075
                // We save the image to send in disk
2076
                $filecontent = $matches[2][$key];
2077
2078
                $cid = 'cid000' . dol_hash($filecontent, 'md5');      // The id must not change if image is same
2079
2080
                $destfiletmp = $images_dir . '/' . $cid . '.' . $ext;
2081
2082
                if (!dol_is_file($destfiletmp)) {   // If file does not exist yet (this is the case for the first email sent with a data:image inside)
2083
                    dol_syslog("write the cid file " . $destfiletmp);
2084
                    $fhandle = @fopen($destfiletmp, 'w');
2085
                    if ($fhandle) {
2086
                        $nbofbyteswrote = fwrite($fhandle, base64_decode($filecontent));
2087
                        fclose($fhandle);
2088
                        dolChmod($destfiletmp);
2089
                    } else {
2090
                        $this->errors[] = "Failed to open file '" . $destfiletmp . "' for write";
2091
                        return -2;
2092
                    }
2093
                }
2094
2095
                if (file_exists($destfiletmp)) {
2096
                    // Image full path
2097
                    $this->html_images[$i]["fullpath"] = $destfiletmp;
2098
                    // Image name
2099
                    $this->html_images[$i]["name"] = basename($destfiletmp);
2100
                    // Content type
2101
                    $this->html_images[$i]["content_type"] = $this->image_types[strtolower($ext)];
2102
                    // cid
2103
                    $this->html_images[$i]["cid"] = $cid;
2104
                    // type
2105
                    $this->html_images[$i]["type"] = 'cidfromdata';
2106
2107
                    $this->html = str_replace('src="data:image/' . $ext . ';base64,' . $filecontent . '"', 'src="cid:' . $this->html_images[$i]["cid"] . '"', $this->html);
2108
                }
2109
                $i++;
2110
            }
2111
2112
            return 1;
2113
        } else {
2114
            return 0;
2115
        }
2116
    }
2117
2118
    /**
2119
     * Return a formatted address string for SMTP protocol
2120
     *
2121
     * @param   string      $address             Example: 'John Doe <[email protected]>, Alan Smith <[email protected]>' or '[email protected], [email protected]'
2122
     * @param   int         $format              0=auto, 1=emails with <>, 2=emails without <>, 3=auto + label between ", 4 label or email, 5 mailto link
2123
     * @param   int         $encode              0=No encode name, 1=Encode name to RFC2822
2124
     * @param   int         $maxnumberofemail    0=No limit. Otherwise, maximum number of emails returned ($address may contains several email separated with ','). Add '...' if there is more.
2125
     * @return  string                           If format 0: '<[email protected]>' or 'John Doe <[email protected]>' or '=?UTF-8?B?Sm9obiBEb2U=?= <[email protected]>'
2126
     *                                           If format 1: '<[email protected]>'
2127
     *                                           If format 2: '[email protected]'
2128
     *                                           If format 3: '<[email protected]>' or '"John Doe" <[email protected]>' or '"=?UTF-8?B?Sm9obiBEb2U=?=" <[email protected]>'
2129
     *                                           If format 4: 'John Doe' or '[email protected]' if no label exists
2130
     *                                           If format 5: <a href="mailto:[email protected]">John Doe</a> or <a href="mailto:[email protected]">[email protected]</a> if no label exists
2131
     * @see getArrayAddress()
2132
     */
2133
    public static function getValidAddress($address, $format, $encode = 0, $maxnumberofemail = 0)
2134
    {
2135
        global $conf;
2136
2137
        $ret = '';
2138
2139
        $arrayaddress = (!empty($address) ? explode(',', $address) : array());
2140
2141
        // Boucle sur chaque composant de l'address
2142
        $i = 0;
2143
        foreach ($arrayaddress as $val) {
2144
            $regs = array();
2145
            if (preg_match('/^(.*)<(.*)>$/i', trim($val), $regs)) {
2146
                $name  = trim($regs[1]);
2147
                $email = trim($regs[2]);
2148
            } else {
2149
                $name  = '';
2150
                $email = trim($val);
2151
            }
2152
2153
            if ($email) {
2154
                $i++;
2155
2156
                $newemail = '';
2157
                if ($format == 5) {
2158
                    $newemail = $name ? $name : $email;
2159
                    $newemail = '<a href="mailto:' . $email . '">' . $newemail . '</a>';
2160
                }
2161
                if ($format == 4) {
2162
                    $newemail = $name ? $name : $email;
2163
                }
2164
                if ($format == 2) {
2165
                    $newemail = $email;
2166
                }
2167
                if ($format == 1 || $format == 3) {
2168
                    $newemail = '<' . $email . '>';
2169
                }
2170
                if ($format == 0 || $format == 3) {
2171
                    if (getDolGlobalString('MAIN_MAIL_NO_FULL_EMAIL')) {
2172
                        $newemail = '<' . $email . '>';
2173
                    } elseif (!$name) {
2174
                        $newemail = '<' . $email . '>';
2175
                    } else {
2176
                        $newemail = ($format == 3 ? '"' : '') . ($encode ? self::encodetorfc2822($name) : $name) . ($format == 3 ? '"' : '') . ' <' . $email . '>';
2177
                    }
2178
                }
2179
2180
                $ret = ($ret ? $ret . ',' : '') . $newemail;
2181
2182
                // Stop if we have too much records
2183
                if ($maxnumberofemail && $i >= $maxnumberofemail) {
2184
                    if (count($arrayaddress) > $maxnumberofemail) {
2185
                        $ret .= '...';
2186
                    }
2187
                    break;
2188
                }
2189
            }
2190
        }
2191
2192
        return $ret;
2193
    }
2194
2195
    /**
2196
     * Return a formatted array of address string for SMTP protocol
2197
     *
2198
     * @param   string      $address        Example: 'John Doe <[email protected]>, Alan Smith <[email protected]>' or '[email protected], [email protected]'
2199
     * @return  array                       array(email => name)
2200
     * @see getValidAddress()
2201
     */
2202
    public static function getArrayAddress($address)
2203
    {
2204
        $ret = array();
2205
2206
        $arrayaddress = explode(',', $address);
2207
2208
        // Boucle sur chaque composant de l'address
2209
        foreach ($arrayaddress as $val) {
2210
            $regs = array();
2211
            if (preg_match('/^(.*)<(.*)>$/i', trim($val), $regs)) {
2212
                $name  = trim($regs[1]);
2213
                $email = trim($regs[2]);
2214
            } else {
2215
                $name  = null;
2216
                $email = trim($val);
2217
            }
2218
2219
            $ret[$email] = getDolGlobalString('MAIN_MAIL_NO_FULL_EMAIL') ? null : $name;
2220
        }
2221
2222
        return $ret;
2223
    }
2224
}
2225