Passed
Push — EXTRACT_CLASSES ( 0382f2...c25e41 )
by Rafael
52:18
created

CMailFile::_encode_file()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 9
nc 2
nop 1
dl 0
loc 13
rs 9.9666
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
                include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
801
                $hookmanager = new HookManager($db);
802
            }
803
            $hookmanager->initHooks(array('mail'));
804
805
            $parameters = array();
806
            $action = '';
807
            $reshook = $hookmanager->executeHooks('sendMail', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
808
            if ($reshook < 0) {
809
                $this->error = "Error in hook maildao sendMail " . $reshook;
810
                dol_syslog("CMailFile::sendfile: mail end error=" . $this->error, LOG_ERR);
811
812
                return false;
813
            }
814
            if ($reshook == 1) {    // Hook replace standard code
815
                return true;
816
            }
817
818
            $sendingmode = $this->sendmode;
819
            if ($this->sendcontext == 'emailing' && getDolGlobalString('MAILING_NO_USING_PHPMAIL') && $sendingmode == 'mail') {
820
                // List of sending methods
821
                $listofmethods = array();
822
                $listofmethods['mail'] = 'PHP mail function';
823
                //$listofmethods['simplemail']='Simplemail class';
824
                $listofmethods['smtps'] = 'SMTP/SMTPS socket library';
825
826
                // 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.
827
                // You ensure that every user is using its own SMTP server when using the mass emailing module.
828
                $linktoadminemailbefore = '<a href="' . constant('BASE_URL') . '/admin/mails.php">';
829
                $linktoadminemailend = '</a>';
830
                $this->error = $langs->trans("MailSendSetupIs", $listofmethods[$sendingmode]);
831
                $this->errors[] = $langs->trans("MailSendSetupIs", $listofmethods[$sendingmode]);
832
                $this->error .= '<br>' . $langs->trans("MailSendSetupIs2", $linktoadminemailbefore, $linktoadminemailend, $langs->transnoentitiesnoconv("MAIN_MAIL_SENDMODE"), $listofmethods['smtps']);
833
                $this->errors[] = $langs->trans("MailSendSetupIs2", $linktoadminemailbefore, $linktoadminemailend, $langs->transnoentitiesnoconv("MAIN_MAIL_SENDMODE"), $listofmethods['smtps']);
834
                if (getDolGlobalString('MAILING_SMTP_SETUP_EMAILS_FOR_QUESTIONS')) {
835
                    $this->error .= '<br>' . $langs->trans("MailSendSetupIs3", getDolGlobalString('MAILING_SMTP_SETUP_EMAILS_FOR_QUESTIONS'));
836
                    $this->errors[] = $langs->trans("MailSendSetupIs3", getDolGlobalString('MAILING_SMTP_SETUP_EMAILS_FOR_QUESTIONS'));
837
                }
838
839
                dol_syslog("CMailFile::sendfile: mail end error=" . $this->error, LOG_WARNING);
840
                return false;
841
            }
842
843
            // Check number of recipient is lower or equal than MAIL_MAX_NB_OF_RECIPIENTS_IN_SAME_EMAIL
844
            if (!getDolGlobalString('MAIL_MAX_NB_OF_RECIPIENTS_TO_IN_SAME_EMAIL')) {
845
                $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_TO_IN_SAME_EMAIL = 10;
846
            }
847
            $tmparray1 = explode(',', $this->addr_to);
848
            if (count($tmparray1) > $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_TO_IN_SAME_EMAIL) {
849
                $this->error = 'Too much recipients in to:';
850
                dol_syslog("CMailFile::sendfile: mail end error=" . $this->error, LOG_WARNING);
851
                return false;
852
            }
853
            if (!getDolGlobalString('MAIL_MAX_NB_OF_RECIPIENTS_CC_IN_SAME_EMAIL')) {
854
                $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_CC_IN_SAME_EMAIL = 10;
855
            }
856
            $tmparray2 = explode(',', $this->addr_cc);
857
            if (count($tmparray2) > $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_CC_IN_SAME_EMAIL) {
858
                $this->error = 'Too much recipients in cc:';
859
                dol_syslog("CMailFile::sendfile: mail end error=" . $this->error, LOG_WARNING);
860
                return false;
861
            }
862
            if (!getDolGlobalString('MAIL_MAX_NB_OF_RECIPIENTS_BCC_IN_SAME_EMAIL')) {
863
                $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_BCC_IN_SAME_EMAIL = 10;
864
            }
865
            $tmparray3 = explode(',', $this->addr_bcc);
866
            if (count($tmparray3) > $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_BCC_IN_SAME_EMAIL) {
867
                $this->error = 'Too much recipients in bcc:';
868
                dol_syslog("CMailFile::sendfile: mail end error=" . $this->error, LOG_WARNING);
869
                return false;
870
            }
871
            if (!getDolGlobalString('MAIL_MAX_NB_OF_RECIPIENTS_IN_SAME_EMAIL')) {
872
                $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_IN_SAME_EMAIL = 10;
873
            }
874
            if ((count($tmparray1) + count($tmparray2) + count($tmparray3)) > $conf->global->MAIL_MAX_NB_OF_RECIPIENTS_IN_SAME_EMAIL) {
875
                $this->error = 'Too much recipients in to:, cc:, bcc:';
876
                dol_syslog("CMailFile::sendfile: mail end error=" . $this->error, LOG_WARNING);
877
                return false;
878
            }
879
880
            $keyforsmtpserver = 'MAIN_MAIL_SMTP_SERVER';
881
            $keyforsmtpport  = 'MAIN_MAIL_SMTP_PORT';
882
            $keyforsmtpid    = 'MAIN_MAIL_SMTPS_ID';
883
            $keyforsmtppw    = 'MAIN_MAIL_SMTPS_PW';
884
            $keyforsmtpauthtype = 'MAIN_MAIL_SMTPS_AUTH_TYPE';
885
            $keyforsmtpoauthservice = 'MAIN_MAIL_SMTPS_OAUTH_SERVICE';
886
            $keyfortls       = 'MAIN_MAIL_EMAIL_TLS';
887
            $keyforstarttls  = 'MAIN_MAIL_EMAIL_STARTTLS';
888
            $keyforsslseflsigned = 'MAIN_MAIL_EMAIL_SMTP_ALLOW_SELF_SIGNED';
889
            if (!empty($this->sendcontext)) {
890
                $smtpContextKey = strtoupper($this->sendcontext);
891
                $smtpContextSendMode = getDolGlobalString('MAIN_MAIL_SENDMODE_' . $smtpContextKey);
892
                if (!empty($smtpContextSendMode) && $smtpContextSendMode != 'default') {
893
                    $keyforsmtpserver = 'MAIN_MAIL_SMTP_SERVER_' . $smtpContextKey;
894
                    $keyforsmtpport   = 'MAIN_MAIL_SMTP_PORT_' . $smtpContextKey;
895
                    $keyforsmtpid     = 'MAIN_MAIL_SMTPS_ID_' . $smtpContextKey;
896
                    $keyforsmtppw     = 'MAIN_MAIL_SMTPS_PW_' . $smtpContextKey;
897
                    $keyforsmtpauthtype = 'MAIN_MAIL_SMTPS_AUTH_TYPE_' . $smtpContextKey;
898
                    $keyforsmtpoauthservice = 'MAIN_MAIL_SMTPS_OAUTH_SERVICE_' . $smtpContextKey;
899
                    $keyfortls        = 'MAIN_MAIL_EMAIL_TLS_' . $smtpContextKey;
900
                    $keyforstarttls   = 'MAIN_MAIL_EMAIL_STARTTLS_' . $smtpContextKey;
901
                    $keyforsslseflsigned = 'MAIN_MAIL_EMAIL_SMTP_ALLOW_SELF_SIGNED_' . $smtpContextKey;
902
                }
903
            }
904
905
            // Action according to chose sending method
906
            if ($this->sendmode == 'mail') {
907
                // Use mail php function (default PHP method)
908
                // ------------------------------------------
909
                dol_syslog("CMailFile::sendfile addr_to=" . $this->addr_to . ", subject=" . $this->subject, LOG_DEBUG);
910
                //dol_syslog("CMailFile::sendfile header=\n".$this->headers, LOG_DEBUG);
911
                //dol_syslog("CMailFile::sendfile message=\n".$message);
912
913
                // If Windows, sendmail_from must be defined
914
                if (isset($_SERVER["WINDIR"])) {
915
                    if (empty($this->addr_from)) {
916
                        $this->addr_from = '[email protected]';
917
                    }
918
                    @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

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

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

1107
                            /** @scrutinizer ignore-call */ 
1108
                            $tokenobj = $apiService->refreshAccessToken($tokenobj);
Loading history...
1108
                            $tokenobj->setRefreshToken($refreshtoken);
1109
                            $storage->storeAccessToken($OAUTH_SERVICENAME, $tokenobj);
1110
                        }
1111
1112
                        $tokenobj = $storage->retrieveAccessToken($OAUTH_SERVICENAME);
1113
                        if (is_object($tokenobj)) {
1114
                            $this->smtps->setToken($tokenobj->getAccessToken());
1115
                        } else {
1116
                            $this->error = "Token not found";
1117
                        }
1118
                    } catch (Exception $e) {
1119
                        // Return an error if token not found
1120
                        $this->error = $e->getMessage();
1121
                        dol_syslog("CMailFile::sendfile: mail end error=" . $this->error, LOG_ERR);
1122
                    }
1123
                }
1124
1125
                $res = true;
1126
                $from = $this->smtps->getFrom('org');
1127
                if ($res && !$from) {
1128
                    $this->error = "Failed to send mail with smtps lib to HOST=" . $server . ", PORT=" . getDolGlobalString($keyforsmtpport) . " - Sender address '$from' invalid";
1129
                    dol_syslog("CMailFile::sendfile: mail end error=" . $this->error, LOG_ERR);
1130
                    $res = false;
1131
                }
1132
                $dest = $this->smtps->getTo();
1133
                if ($res && !$dest) {
1134
                    $this->error = "Failed to send mail with smtps lib to HOST=" . $server . ", PORT=" . getDolGlobalString($keyforsmtpport) . " - Recipient address '$dest' invalid";
1135
                    dol_syslog("CMailFile::sendfile: mail end error=" . $this->error, LOG_ERR);
1136
                    $res = false;
1137
                }
1138
1139
                if ($res) {
1140
                    dol_syslog("CMailFile::sendfile: sendMsg, HOST=" . $server . ", PORT=" . getDolGlobalString($keyforsmtpport), LOG_DEBUG);
1141
1142
                    if (getDolGlobalString('MAIN_MAIL_DEBUG')) {
1143
                        $this->smtps->setDebug(true);
1144
                    }
1145
1146
                    $result = $this->smtps->sendMsg();
1147
1148
                    if (getDolGlobalString('MAIN_MAIL_DEBUG')) {
1149
                        $this->dump_mail();
1150
                    }
1151
1152
                    $smtperrorcode = 0;
1153
                    if (! $result) {
1154
                        $smtperrorcode = $this->smtps->lastretval;  // SMTP error code
1155
                        dol_syslog("CMailFile::sendfile: mail SMTP error code " . $smtperrorcode, LOG_WARNING);
1156
1157
                        if ($smtperrorcode == '421') {  // Try later
1158
                            // TODO Add a delay and try again
1159
                            /*
1160
                            dol_syslog("CMailFile::sendfile: Try later error, so we wait and we retry");
1161
                            sleep(2);
1162
1163
                            $result = $this->smtps->sendMsg();
1164
1165
                            if (!empty($conf->global->MAIN_MAIL_DEBUG)) {
1166
                                $this->dump_mail();
1167
                            }
1168
                            */
1169
                        }
1170
                    }
1171
1172
                    $result = $this->smtps->getErrors();    // applicative error code (not SMTP error code)
1173
                    if (empty($this->error) && empty($result)) {
1174
                        dol_syslog("CMailFile::sendfile: mail end success", LOG_DEBUG);
1175
                        $res = true;
1176
                    } else {
1177
                        if (empty($this->error)) {
1178
                            $this->error = $result;
1179
                        }
1180
                        dol_syslog("CMailFile::sendfile: mail end error with smtps lib to HOST=" . $server . ", PORT=" . getDolGlobalString($keyforsmtpport) . " - " . $this->error, LOG_ERR);
1181
                        $res = false;
1182
1183
                        if (getDolGlobalString('MAIN_MAIL_DEBUG')) {
1184
                            $this->save_dump_mail_in_err('Mail smtp error ' . $smtperrorcode . ' with topic ' . $this->subject);
1185
                        }
1186
                    }
1187
                }
1188
            } elseif ($this->sendmode == 'swiftmailer') {
1189
                // Use Swift Mailer library
1190
                // ------------------------------------------
1191
                require_once constant('DOL_DOCUMENT_ROOT') . '/includes/swiftmailer/lib/swift_required.php';
1192
1193
                // Clean parameters
1194
                if (empty($conf->global->$keyforsmtpserver)) {
1195
                    $conf->global->$keyforsmtpserver = ini_get('SMTP');
1196
                }
1197
                if (empty($conf->global->$keyforsmtpport)) {
1198
                    $conf->global->$keyforsmtpport = ini_get('smtp_port');
1199
                }
1200
1201
                // If we use SSL/TLS
1202
                $server = getDolGlobalString($keyforsmtpserver);
1203
                $secure = '';
1204
                if (!empty($conf->global->$keyfortls) && function_exists('openssl_open')) {
1205
                    $secure = 'ssl';
1206
                }
1207
                if (!empty($conf->global->$keyforstarttls) && function_exists('openssl_open')) {
1208
                    $secure = 'tls';
1209
                }
1210
1211
                $this->transport = new Swift_SmtpTransport($server, getDolGlobalString($keyforsmtpport), $secure);
1212
1213
                if (!empty($conf->global->$keyforsmtpid)) {
1214
                    $this->transport->setUsername($conf->global->$keyforsmtpid);
1215
                }
1216
                if (!empty($conf->global->$keyforsmtppw) && getDolGlobalString($keyforsmtpauthtype) != "XOAUTH2") {
1217
                    $this->transport->setPassword($conf->global->$keyforsmtppw);
1218
                }
1219
                if (getDolGlobalString($keyforsmtpauthtype) === "XOAUTH2") {
1220
                    require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/oauth.lib.php';
1221
1222
                    $supportedoauth2array = getSupportedOauth2Array();
1223
1224
                    $keyforsupportedoauth2array = getDolGlobalString($keyforsmtpoauthservice);
1225
                    if (preg_match('/^.*-/', $keyforsupportedoauth2array)) {
1226
                        $keyforprovider = preg_replace('/^.*-/', '', $keyforsupportedoauth2array);
1227
                    } else {
1228
                        $keyforprovider = '';
1229
                    }
1230
                    $keyforsupportedoauth2array = preg_replace('/-.*$/', '', $keyforsupportedoauth2array);
1231
                    $keyforsupportedoauth2array = 'OAUTH_' . $keyforsupportedoauth2array . '_NAME';
1232
1233
                    $OAUTH_SERVICENAME = 'Unknown';
1234
                    if (
1235
                        array_key_exists($keyforsupportedoauth2array, $supportedoauth2array)
1236
                        && array_key_exists('name', $supportedoauth2array[$keyforsupportedoauth2array])
1237
                        && !empty($supportedoauth2array[$keyforsupportedoauth2array]['name'])
1238
                    ) {
1239
                        $OAUTH_SERVICENAME = $supportedoauth2array[$keyforsupportedoauth2array]['name'] . (!empty($keyforprovider) ? '-' . $keyforprovider : '');
1240
                    }
1241
1242
                    require_once constant('DOL_DOCUMENT_ROOT') . '/includes/OAuth/bootstrap.php';
1243
1244
                    $storage = new DoliStorage($db, $conf, $keyforprovider);
1245
1246
                    try {
1247
                        $tokenobj = $storage->retrieveAccessToken($OAUTH_SERVICENAME);
1248
                        $expire = false;
1249
                        // Is token expired or will token expire in the next 30 seconds
1250
                        if (is_object($tokenobj)) {
1251
                            $expire = ($tokenobj->getEndOfLife() !== -9002 && $tokenobj->getEndOfLife() !== -9001 && time() > ($tokenobj->getEndOfLife() - 30));
1252
                        }
1253
                        // Token expired so we refresh it
1254
                        if (is_object($tokenobj) && $expire) {
1255
                            $credentials = new Credentials(
1256
                                getDolGlobalString('OAUTH_' . getDolGlobalString('MAIN_MAIL_SMTPS_OAUTH_SERVICE') . '_ID'),
1257
                                getDolGlobalString('OAUTH_' . getDolGlobalString('MAIN_MAIL_SMTPS_OAUTH_SERVICE') . '_SECRET'),
1258
                                getDolGlobalString('OAUTH_' . getDolGlobalString('MAIN_MAIL_SMTPS_OAUTH_SERVICE') . '_URLAUTHORIZE')
1259
                            );
1260
                            $serviceFactory = new \OAuth\ServiceFactory();
1261
                            $oauthname = explode('-', $OAUTH_SERVICENAME);
1262
                            // ex service is Google-Emails we need only the first part Google
1263
                            $apiService = $serviceFactory->createService($oauthname[0], $credentials, $storage, array());
1264
                            // We have to save the token because Google give it only once
1265
                            $refreshtoken = $tokenobj->getRefreshToken();
1266
                            $tokenobj = $apiService->refreshAccessToken($tokenobj);
1267
                            $tokenobj->setRefreshToken($refreshtoken);
1268
                            $storage->storeAccessToken($OAUTH_SERVICENAME, $tokenobj);
1269
                        }
1270
                        if (is_object($tokenobj)) {
1271
                            $this->transport->setAuthMode('XOAUTH2');
1272
                            $this->transport->setPassword($tokenobj->getAccessToken());
1273
                        } else {
1274
                            $this->errors[] = "Token not found";
1275
                        }
1276
                    } catch (Exception $e) {
1277
                        // Return an error if token not found
1278
                        $this->errors[] = $e->getMessage();
1279
                        dol_syslog("CMailFile::sendfile: mail end error=" . $e->getMessage(), LOG_ERR);
1280
                    }
1281
                }
1282
                if (getDolGlobalString($keyforsslseflsigned)) {
1283
                    $this->transport->setStreamOptions(array('ssl' => array('allow_self_signed' => true, 'verify_peer' => false)));
1284
                }
1285
                //$smtps->_msgReplyTo  = '[email protected]';
1286
1287
                // Switch content encoding to base64 - avoid the doubledot issue with quoted-printable
1288
                $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...
1289
                $this->message->setEncoder($contentEncoderBase64);
1290
1291
                // Create the Mailer using your created Transport
1292
                $this->mailer = new Swift_Mailer($this->transport);
1293
1294
                // DKIM SIGN
1295
                if (getDolGlobalString('MAIN_MAIL_EMAIL_DKIM_ENABLED')) {
1296
                    $privateKey = getDolGlobalString('MAIN_MAIL_EMAIL_DKIM_PRIVATE_KEY');
1297
                    $domainName = getDolGlobalString('MAIN_MAIL_EMAIL_DKIM_DOMAIN');
1298
                    $selector = getDolGlobalString('MAIN_MAIL_EMAIL_DKIM_SELECTOR');
1299
                    $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...
1300
                    $this->message->attachSigner($signer->ignoreHeader('Return-Path'));
1301
                }
1302
1303
                if (getDolGlobalString('MAIN_MAIL_DEBUG')) {
1304
                    // To use the ArrayLogger
1305
                    $this->logger = new Swift_Plugins_Loggers_ArrayLogger();
1306
                    // Or to use the Echo Logger
1307
                    //$this->logger = new Swift_Plugins_Loggers_EchoLogger();
1308
                    $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...
1309
                }
1310
1311
                dol_syslog("CMailFile::sendfile: mailer->send, HOST=" . $server . ", PORT=" . getDolGlobalString($keyforsmtpport), LOG_DEBUG);
1312
1313
                // send mail
1314
                $failedRecipients = array();
1315
                try {
1316
                    $result = $this->mailer->send($this->message, $failedRecipients);
1317
                } catch (Exception $e) {
1318
                    $this->errors[] = $e->getMessage();
1319
                }
1320
                if (getDolGlobalString('MAIN_MAIL_DEBUG')) {
1321
                    $this->dump_mail();
1322
                }
1323
1324
                $res = true;
1325
                if (!empty($this->error) || !empty($this->errors) || !$result) {
1326
                    if (!empty($failedRecipients)) {
1327
                        $this->errors[] = 'Transport failed for the following addresses: "' . implode('", "', $failedRecipients) . '".';
1328
                    }
1329
                    dol_syslog("CMailFile::sendfile: mail end error=" . $this->error, LOG_ERR);
1330
                    $res = false;
1331
1332
                    if (getDolGlobalString('MAIN_MAIL_DEBUG')) {
1333
                        $this->save_dump_mail_in_err('Mail with topic ' . $this->subject);
1334
                    }
1335
                } else {
1336
                    dol_syslog("CMailFile::sendfile: mail end success", LOG_DEBUG);
1337
                }
1338
            } else {
1339
                // Send mail method not correctly defined
1340
                // --------------------------------------
1341
1342
                $this->error = 'Bad value for sendmode';
1343
                return false;
1344
            }
1345
1346
            // Now we delete image files that were created dynamically to manage data inline files
1347
            foreach ($this->html_images as $val) {
1348
                if (!empty($val['type']) && $val['type'] == 'cidfromdata') {
1349
                    //dol_delete($val['fullpath']);
1350
                }
1351
            }
1352
1353
            $parameters = array('sent' => $res);
1354
            $action = '';
1355
            $reshook = $hookmanager->executeHooks('sendMailAfter', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1356
            if ($reshook < 0) {
1357
                $this->error = "Error in hook maildao sendMailAfter " . $reshook;
1358
                dol_syslog("CMailFile::sendfile: mail end error=" . $this->error, LOG_ERR);
1359
1360
                return false;
1361
            }
1362
        } else {
1363
            $this->error = 'No mail sent. Feature is disabled by option MAIN_DISABLE_ALL_MAILS';
1364
            dol_syslog("CMailFile::sendfile: " . $this->error, LOG_WARNING);
1365
        }
1366
1367
        error_reporting($errorlevel); // Reactive niveau erreur origine
1368
        return $res;
1369
    }
1370
1371
    /**
1372
     * Encode subject according to RFC 2822 - http://en.wikipedia.org/wiki/MIME#Encoded-Word
1373
     *
1374
     * @param string $stringtoencode String to encode
1375
     * @return string                string encoded
1376
     */
1377
    public static function encodetorfc2822($stringtoencode)
1378
    {
1379
        global $conf;
1380
        return '=?' . $conf->file->character_set_client . '?B?' . base64_encode($stringtoencode) . '?=';
1381
    }
1382
1383
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1384
    /**
1385
     * Read a file on disk and return encoded content for emails (mode = 'mail')
1386
     *
1387
     * @param   string  $sourcefile     Path to file to encode
1388
     * @return  int|string              Return integer <0 if KO, encoded string if OK
1389
     */
1390
    private function _encode_file($sourcefile)
1391
    {
1392
		// phpcs:enable
1393
        $newsourcefile = dol_osencode($sourcefile);
1394
1395
        if (is_readable($newsourcefile)) {
1396
            $contents = file_get_contents($newsourcefile); // Need PHP 4.3
1397
            $encoded = chunk_split(base64_encode($contents), 76, $this->eol); // 76 max is defined into http://tools.ietf.org/html/rfc2047
1398
            return $encoded;
1399
        } else {
1400
            $this->error = "Error in _encode_file() method: Can't read file '" . $sourcefile . "'";
1401
            dol_syslog("CMailFile::_encode_file: " . $this->error, LOG_ERR);
1402
            return -1;
1403
        }
1404
    }
1405
1406
1407
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1408
    /**
1409
     *  Write content of a SMTP request into a dump file (mode = all)
1410
     *  Used for debugging.
1411
     *  Note that to see full SMTP protocol, you can use tcpdump -w /tmp/smtp -s 2000 port 25"
1412
     *
1413
     *  @return void
1414
     */
1415
    public function dump_mail()
1416
    {
1417
		// phpcs:enable
1418
        global $dolibarr_main_data_root;
1419
1420
        if (@is_writable($dolibarr_main_data_root)) {   // Avoid fatal error on fopen with open_basedir
1421
            $outputfile = $dolibarr_main_data_root . "/dolibarr_mail.log";
1422
            $fp = fopen($outputfile, "w");  // overwrite
1423
1424
            if ($this->sendmode == 'mail') {
1425
                fwrite($fp, $this->headers);
1426
                fwrite($fp, $this->eol); // This eol is added by the mail function, so we add it in log
1427
                fwrite($fp, $this->message);
1428
            } elseif ($this->sendmode == 'smtps') {
1429
                fwrite($fp, $this->smtps->log); // this->smtps->log is filled only if MAIN_MAIL_DEBUG was set to on
1430
            } elseif ($this->sendmode == 'swiftmailer') {
1431
                fwrite($fp, "smtpheader=\n" . $this->message->getHeaders()->toString() . "\n");
1432
                fwrite($fp, $this->logger->dump()); // this->logger is filled only if MAIN_MAIL_DEBUG was set to on
1433
            }
1434
1435
            fclose($fp);
1436
            dolChmod($outputfile);
1437
1438
            // Move dolibarr_mail.log into a dolibarr_mail.YYYYMMDD.log
1439
            if (getDolGlobalString('MAIN_MAIL_DEBUG_LOG_WITH_DATE')) {
1440
                $destfile = $dolibarr_main_data_root . "/dolibarr_mail." . dol_print_date(dol_now(), 'dayhourlog', 'gmt') . ".log";
1441
1442
                require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
1443
                dol_move($outputfile, $destfile, 0, 1, 0, 0);
1444
            }
1445
        }
1446
    }
1447
1448
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1449
    /**
1450
     *  Save content if mail is in error
1451
     *  Used for debugging.
1452
     *
1453
     *  @param  string      $message        Add also a message
1454
     *  @return void
1455
     */
1456
    public function save_dump_mail_in_err($message = '')
1457
    {
1458
        global $dolibarr_main_data_root;
1459
1460
        if (@is_writable($dolibarr_main_data_root)) {   // Avoid fatal error on fopen with open_basedir
1461
            $srcfile = $dolibarr_main_data_root . "/dolibarr_mail.log";
1462
1463
            // Add message to dolibarr_mail.log. We do not use dol_syslog() on purpose,
1464
            // to be sure to write into dolibarr_mail.log
1465
            if ($message) {
1466
                // Test constant SYSLOG_FILE_NO_ERROR (should stay a constant defined with define('SYSLOG_FILE_NO_ERROR',1);
1467
                if (defined('SYSLOG_FILE_NO_ERROR')) {
1468
                    $filefd = @fopen($srcfile, 'a+');
1469
                } else {
1470
                    $filefd = fopen($srcfile, 'a+');
1471
                }
1472
                if ($filefd) {
1473
                    fwrite($filefd, $message . "\n");
1474
                    fclose($filefd);
1475
                    dolChmod($srcfile);
1476
                }
1477
            }
1478
1479
            // Move dolibarr_mail.log into a dolibarr_mail.err or dolibarr_mail.date.err
1480
            if (getDolGlobalString('MAIN_MAIL_DEBUG_ERR_WITH_DATE')) {
1481
                $destfile = $dolibarr_main_data_root . "/dolibarr_mail." . dol_print_date(dol_now(), 'dayhourlog', 'gmt') . ".err";
1482
            } else {
1483
                $destfile = $dolibarr_main_data_root . "/dolibarr_mail.err";
1484
            }
1485
1486
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
1487
            dol_move($srcfile, $destfile, 0, 1, 0, 0);
1488
        }
1489
    }
1490
1491
1492
    /**
1493
     * Correct an incomplete html string
1494
     *
1495
     * @param   string  $msg    String
1496
     * @return  string          Completed string
1497
     */
1498
    public function checkIfHTML($msg)
1499
    {
1500
        if (!preg_match('/^[\s\t]*<html/i', $msg)) {
1501
            $out = "<html><head><title></title>";
1502
            if (!empty($this->styleCSS)) {
1503
                $out .= $this->styleCSS;
1504
            }
1505
            $out .= "</head><body";
1506
            if (!empty($this->bodyCSS)) {
1507
                $out .= $this->bodyCSS;
1508
            }
1509
            $out .= ">";
1510
            $out .= $msg;
1511
            $out .= "</body></html>";
1512
        } else {
1513
            $out = $msg;
1514
        }
1515
1516
        return $out;
1517
    }
1518
1519
    /**
1520
     * Build a css style (mode = all) into this->styleCSS and this->bodyCSS
1521
     *
1522
     * @return void
1523
     */
1524
    public function buildCSS()
1525
    {
1526
        if (!empty($this->css)) {
1527
            // Style CSS
1528
            $this->styleCSS = '<style type="text/css">';
1529
            $this->styleCSS .= 'body {';
1530
1531
            if ($this->css['bgcolor']) {
1532
                $this->styleCSS .= '  background-color: ' . $this->css['bgcolor'] . ';';
1533
                $this->bodyCSS .= ' bgcolor="' . $this->css['bgcolor'] . '"';
1534
            }
1535
            if ($this->css['bgimage']) {
1536
                // TODO recuperer cid
1537
                $this->styleCSS .= ' background-image: url("cid:' . $this->css['bgimage_cid'] . '");';
1538
            }
1539
            $this->styleCSS .= '}';
1540
            $this->styleCSS .= '</style>';
1541
        }
1542
    }
1543
1544
1545
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1546
    /**
1547
     * Create SMTP headers (mode = 'mail')
1548
     *
1549
     * @return  string headers
1550
     */
1551
    public function write_smtpheaders()
1552
    {
1553
		// phpcs:enable
1554
        $out = "";
1555
        $host = dol_getprefix('email');
1556
1557
        // Sender
1558
        //$out.= "Sender: ".getValidAddress($this->addr_from,2)).$this->eol2;
1559
        $out .= "From: " . $this->getValidAddress($this->addr_from, 3, 1) . $this->eol2;
1560
        if (getDolGlobalString('MAIN_MAIL_SENDMAIL_FORCE_BA')) {
1561
            $out .= "To: " . $this->getValidAddress($this->addr_to, 0, 1) . $this->eol2;
1562
        }
1563
        // 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.
1564
        $out .= "Return-Path: " . $this->getValidAddress($this->addr_from, 0, 1) . $this->eol2;
1565
        if (isset($this->reply_to) && $this->reply_to) {
1566
            $out .= "Reply-To: " . $this->getValidAddress($this->reply_to, 2) . $this->eol2;
1567
        }
1568
        if (isset($this->errors_to) && $this->errors_to) {
1569
            $out .= "Errors-To: " . $this->getValidAddress($this->errors_to, 2) . $this->eol2;
1570
        }
1571
1572
        // Receiver
1573
        if (isset($this->addr_cc) && $this->addr_cc) {
1574
            $out .= "Cc: " . $this->getValidAddress($this->addr_cc, 2) . $this->eol2;
1575
        }
1576
        if (isset($this->addr_bcc) && $this->addr_bcc) {
1577
            $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 ?
1578
        }
1579
1580
        // Delivery receipt
1581
        if (isset($this->deliveryreceipt) && $this->deliveryreceipt == 1) {
1582
            $out .= "Disposition-Notification-To: " . $this->getValidAddress($this->addr_from, 2) . $this->eol2;
1583
        }
1584
1585
        //$out.= "X-Priority: 3".$this->eol2;
1586
1587
        $out .= 'Date: ' . date("r") . $this->eol2;
1588
1589
        $trackid = $this->trackid;
1590
        if ($trackid) {
1591
            $this->msgid = time() . '.phpmail-dolibarr-' . $trackid . '@' . $host;
1592
            $out .= 'Message-ID: <' . $this->msgid . ">" . $this->eol2; // Uppercase seems replaced by phpmail
1593
            $out .= 'X-Dolibarr-TRACKID: ' . $trackid . '@' . $host . $this->eol2;
1594
        } else {
1595
            $this->msgid = time() . '.phpmail@' . $host;
1596
            $out .= 'Message-ID: <' . $this->msgid . ">" . $this->eol2;
1597
        }
1598
1599
        // Add 'In-Reply-To:' header with the Message-Id we answer
1600
        if (!empty($this->in_reply_to)) {
1601
            $out .= 'In-Reply-To: <' . $this->in_reply_to . '>' . $this->eol2;
1602
        }
1603
        // Add 'References:' header with list of all Message-ID in thread history
1604
        if (!empty($this->references)) {
1605
            $out .= 'References: ' . $this->references . $this->eol2;
1606
        }
1607
1608
        if (!empty($_SERVER['REMOTE_ADDR'])) {
1609
            $out .= "X-RemoteAddr: " . $_SERVER['REMOTE_ADDR'] . $this->eol2;
1610
        }
1611
        $out .= "X-Mailer: Dolibarr version " . DOL_VERSION . " (using php mail)" . $this->eol2;
1612
        $out .= "Mime-Version: 1.0" . $this->eol2;
1613
1614
        //$out.= "From: ".$this->getValidAddress($this->addr_from,3,1).$this->eol;
1615
1616
        $out .= "Content-Type: multipart/mixed;" . $this->eol2 . " boundary=\"" . $this->mixed_boundary . "\"" . $this->eol2;
1617
        $out .= "Content-Transfer-Encoding: 8bit" . $this->eol2; // TODO Seems to be ignored. Header is 7bit once received.
1618
1619
        dol_syslog("CMailFile::write_smtpheaders smtp_header=\n" . $out);
1620
        return $out;
1621
    }
1622
1623
1624
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1625
    /**
1626
     * Create header MIME (mode = 'mail')
1627
     *
1628
     * @param   array   $filename_list          Array of filenames
1629
     * @param   array   $mimefilename_list      Array of mime types
1630
     * @return  string                          mime headers
1631
     */
1632
    public function write_mimeheaders($filename_list, $mimefilename_list)
1633
    {
1634
		// phpcs:enable
1635
        $mimedone = 0;
1636
        $out = "";
1637
1638
        if (is_array($filename_list)) {
1639
            $filename_list_size = count($filename_list);
1640
            for ($i = 0; $i < $filename_list_size; $i++) {
1641
                if ($filename_list[$i]) {
1642
                    if ($mimefilename_list[$i]) {
1643
                        $filename_list[$i] = $mimefilename_list[$i];
1644
                    }
1645
                    $out .= "X-attachments: $filename_list[$i]" . $this->eol2;
1646
                }
1647
            }
1648
        }
1649
1650
        dol_syslog("CMailFile::write_mimeheaders mime_header=\n" . $out, LOG_DEBUG);
1651
        return $out;
1652
    }
1653
1654
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1655
    /**
1656
     * Return email content (mode = 'mail')
1657
     *
1658
     * @param   string      $msgtext        Message string
1659
     * @return  string                      String content
1660
     */
1661
    public function write_body($msgtext)
1662
    {
1663
		// phpcs:enable
1664
        global $conf;
1665
1666
        $out = '';
1667
1668
        $out .= "--" . $this->mixed_boundary . $this->eol;
1669
1670
        if ($this->atleastoneimage) {
1671
            $out .= "Content-Type: multipart/alternative;" . $this->eol . " boundary=\"" . $this->alternative_boundary . "\"" . $this->eol;
1672
            $out .= $this->eol;
1673
            $out .= "--" . $this->alternative_boundary . $this->eol;
1674
        }
1675
1676
        // Make RFC821 Compliant, replace bare linefeeds
1677
        $strContent = preg_replace("/(?<!\r)\n/si", "\r\n", $msgtext); // PCRE modifier /s means new lines are common chars
1678
        if (getDolGlobalString('MAIN_FIX_FOR_BUGGED_MTA')) {
1679
            $strContent = preg_replace("/\r\n/si", "\n", $strContent); // PCRE modifier /s means new lines are common chars
1680
        }
1681
1682
        $strContentAltText = '';
1683
        if ($this->msgishtml) {
1684
            // Similar code to forge a text from html is also in smtps.class.php
1685
            $strContentAltText = preg_replace("/<br\s*[^>]*>/", " ", $strContent);
1686
            // TODO We could replace <img ...> with [Filename.ext] like Gmail do.
1687
            $strContentAltText = html_entity_decode(strip_tags($strContentAltText));    // Remove any HTML tags
1688
            $strContentAltText = trim(wordwrap($strContentAltText, 75, !getDolGlobalString('MAIN_FIX_FOR_BUGGED_MTA') ? "\r\n" : "\n"));
1689
1690
            // Check if html header already in message, if not complete the message
1691
            $strContent = $this->checkIfHTML($strContent);      // This add a header and a body including custom CSS to the HTML content
1692
        }
1693
1694
        // Make RFC2045 Compliant, split lines
1695
        //$strContent = rtrim(chunk_split($strContent));    // Function chunck_split seems ko if not used on a base64 content
1696
        // TODO Encode main content into base64 and use the chunk_split, or quoted-printable
1697
        $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.
1698
1699
        if ($this->msgishtml) {
1700
            if ($this->atleastoneimage) {
1701
                $out .= "Content-Type: text/plain; charset=" . $conf->file->character_set_client . $this->eol;
1702
                //$out.= "Content-Transfer-Encoding: 7bit".$this->eol;
1703
                $out .= $this->eol . ($strContentAltText ? $strContentAltText : strip_tags($strContent)) . $this->eol; // Add plain text message
1704
                $out .= "--" . $this->alternative_boundary . $this->eol;
1705
                $out .= "Content-Type: multipart/related;" . $this->eol . " boundary=\"" . $this->related_boundary . "\"" . $this->eol;
1706
                $out .= $this->eol;
1707
                $out .= "--" . $this->related_boundary . $this->eol;
1708
            }
1709
1710
            if (!$this->atleastoneimage && $strContentAltText && getDolGlobalString('MAIN_MAIL_USE_MULTI_PART')) {    // Add plain text message part before html part
1711
                $out .= "Content-Type: multipart/alternative;" . $this->eol . " boundary=\"" . $this->alternative_boundary . "\"" . $this->eol;
1712
                $out .= $this->eol;
1713
                $out .= "--" . $this->alternative_boundary . $this->eol;
1714
                $out .= "Content-Type: text/plain; charset=" . $conf->file->character_set_client . $this->eol;
1715
                //$out.= "Content-Transfer-Encoding: 7bit".$this->eol;
1716
                $out .= $this->eol . $strContentAltText . $this->eol;
1717
                $out .= "--" . $this->alternative_boundary . $this->eol;
1718
            }
1719
1720
            $out .= "Content-Type: text/html; charset=" . $conf->file->character_set_client . $this->eol;
1721
            //$out.= "Content-Transfer-Encoding: 7bit".$this->eol;  // TODO Use base64
1722
            $out .= $this->eol . $strContent . $this->eol;
1723
1724
            if (!$this->atleastoneimage && $strContentAltText && getDolGlobalString('MAIN_MAIL_USE_MULTI_PART')) {    // Add plain text message part after html part
1725
                $out .= "--" . $this->alternative_boundary . "--" . $this->eol;
1726
            }
1727
        } else {
1728
            $out .= "Content-Type: text/plain; charset=" . $conf->file->character_set_client . $this->eol;
1729
            //$out.= "Content-Transfer-Encoding: 7bit".$this->eol;
1730
            $out .= $this->eol . $strContent . $this->eol;
1731
        }
1732
1733
        $out .= $this->eol;
1734
1735
        // Encode images
1736
        if ($this->atleastoneimage) {
1737
            $out .= $this->write_images($this->images_encoded);
1738
            // always end related and end alternative after inline images
1739
            $out .= "--" . $this->related_boundary . "--" . $this->eol;
1740
            $out .= $this->eol . "--" . $this->alternative_boundary . "--" . $this->eol;
1741
            $out .= $this->eol;
1742
        }
1743
1744
        return $out;
1745
    }
1746
1747
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1748
    /**
1749
     * Attach file to email (mode = 'mail')
1750
     *
1751
     * @param   array   $filename_list      Tableau
1752
     * @param   array   $mimetype_list      Tableau
1753
     * @param   array   $mimefilename_list  Tableau
1754
     * @param   array   $cidlist            Array of CID if file must be completed with CID code
1755
     * @return  string|int                      String with files encoded
1756
     */
1757
    private function write_files($filename_list, $mimetype_list, $mimefilename_list, $cidlist)
1758
    {
1759
		// phpcs:enable
1760
        $out = '';
1761
1762
        $filename_list_size = count($filename_list);
1763
        for ($i = 0; $i < $filename_list_size; $i++) {
1764
            if ($filename_list[$i]) {
1765
                dol_syslog("CMailFile::write_files: i=$i " . $filename_list[$i]);
1766
                $encoded = $this->_encode_file($filename_list[$i]);
1767
                if ($encoded !== -1) {
1768
                    if ($mimefilename_list[$i]) {
1769
                        $filename_list[$i] = $mimefilename_list[$i];
1770
                    }
1771
                    if (!$mimetype_list[$i]) {
1772
                        $mimetype_list[$i] = "application/octet-stream";
1773
                    }
1774
1775
                    $out .= "--" . $this->mixed_boundary . $this->eol;
1776
                    $out .= "Content-Disposition: attachment; filename=\"" . $filename_list[$i] . "\"" . $this->eol;
1777
                    $out .= "Content-Type: " . $mimetype_list[$i] . "; name=\"" . $filename_list[$i] . "\"" . $this->eol;
1778
                    $out .= "Content-Transfer-Encoding: base64" . $this->eol;
1779
                    $out .= "Content-Description: " . $filename_list[$i] . $this->eol;
1780
                    if (!empty($cidlist) && is_array($cidlist) && $cidlist[$i]) {
1781
                        $out .= "X-Attachment-Id: " . $cidlist[$i] . $this->eol;
1782
                        $out .= "Content-ID: <" . $cidlist[$i] . '>' . $this->eol;
1783
                    }
1784
                    $out .= $this->eol;
1785
                    $out .= $encoded;
1786
                    $out .= $this->eol;
1787
                    //$out.= $this->eol;
1788
                } else {
1789
                    return $encoded;
1790
                }
1791
            }
1792
        }
1793
1794
        return $out;
1795
    }
1796
1797
1798
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1799
    /**
1800
     * Attach an image to email (mode = 'mail')
1801
     *
1802
     * @param   array   $images_list    Array of array image
1803
     * @return  string                  Chaine images encodees
1804
     */
1805
    public function write_images($images_list)
1806
    {
1807
		// phpcs:enable
1808
        $out = '';
1809
1810
        if (is_array($images_list)) {
1811
            foreach ($images_list as $img) {
1812
                dol_syslog("CMailFile::write_images: " . $img["name"]);
1813
1814
                $out .= "--" . $this->related_boundary . $this->eol; // always related for an inline image
1815
                $out .= "Content-Type: " . $img["content_type"] . "; name=\"" . $img["name"] . "\"" . $this->eol;
1816
                $out .= "Content-Transfer-Encoding: base64" . $this->eol;
1817
                $out .= "Content-Disposition: inline; filename=\"" . $img["name"] . "\"" . $this->eol;
1818
                $out .= "Content-ID: <" . $img["cid"] . ">" . $this->eol;
1819
                $out .= $this->eol;
1820
                $out .= $img["image_encoded"];
1821
                $out .= $this->eol;
1822
            }
1823
        }
1824
1825
        return $out;
1826
    }
1827
1828
1829
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1830
    /**
1831
     * Try to create a socket connection
1832
     *
1833
     * @param   string      $host       Add ssl:// for SSL/TLS.
1834
     * @param   int         $port       Example: 25, 465
1835
     * @return  int                     Socket id if ok, 0 if KO
1836
     */
1837
    public function check_server_port($host, $port)
1838
    {
1839
		// phpcs:enable
1840
        global $conf;
1841
1842
        $_retVal = 0;
1843
        $timeout = 5; // Timeout in seconds
1844
1845
        if (function_exists('fsockopen')) {
1846
            $keyforsmtpserver = 'MAIN_MAIL_SMTP_SERVER';
1847
            $keyforsmtpport  = 'MAIN_MAIL_SMTP_PORT';
1848
            $keyforsmtpid    = 'MAIN_MAIL_SMTPS_ID';
1849
            $keyforsmtppw    = 'MAIN_MAIL_SMTPS_PW';
1850
            $keyforsmtpauthtype = 'MAIN_MAIL_SMTPS_AUTH_TYPE';
1851
            $keyforsmtpoauthservice = 'MAIN_MAIL_SMTPS_OAUTH_SERVICE';
1852
            $keyfortls       = 'MAIN_MAIL_EMAIL_TLS';
1853
            $keyforstarttls  = 'MAIN_MAIL_EMAIL_STARTTLS';
1854
            $keyforsslseflsigned = 'MAIN_MAIL_EMAIL_SMTP_ALLOW_SELF_SIGNED';
1855
1856
            if (!empty($this->sendcontext)) {
1857
                $smtpContextKey = strtoupper($this->sendcontext);
1858
                $smtpContextSendMode = getDolGlobalString('MAIN_MAIL_SENDMODE_' . $smtpContextKey);
1859
                if (!empty($smtpContextSendMode) && $smtpContextSendMode != 'default') {
1860
                    $keyforsmtpserver = 'MAIN_MAIL_SMTP_SERVER_' . $smtpContextKey;
1861
                    $keyforsmtpport   = 'MAIN_MAIL_SMTP_PORT_' . $smtpContextKey;
1862
                    $keyforsmtpid     = 'MAIN_MAIL_SMTPS_ID_' . $smtpContextKey;
1863
                    $keyforsmtppw     = 'MAIN_MAIL_SMTPS_PW_' . $smtpContextKey;
1864
                    $keyforsmtpauthtype = 'MAIN_MAIL_SMTPS_AUTH_TYPE_' . $smtpContextKey;
1865
                    $keyforsmtpoauthservice = 'MAIN_MAIL_SMTPS_OAUTH_SERVICE_' . $smtpContextKey;
1866
                    $keyfortls        = 'MAIN_MAIL_EMAIL_TLS_' . $smtpContextKey;
1867
                    $keyforstarttls   = 'MAIN_MAIL_EMAIL_STARTTLS_' . $smtpContextKey;
1868
                    $keyforsslseflsigned = 'MAIN_MAIL_EMAIL_SMTP_ALLOW_SELF_SIGNED_' . $smtpContextKey;
1869
                }
1870
            }
1871
1872
            // If we use SSL/TLS
1873
            if (!empty($conf->global->$keyfortls) && function_exists('openssl_open')) {
1874
                $host = 'ssl://' . $host;
1875
            }
1876
            // tls smtp start with no encryption
1877
            //if (!empty($conf->global->MAIN_MAIL_EMAIL_STARTTLS) && function_exists('openssl_open')) $host='tls://'.$host;
1878
1879
            dol_syslog("Try socket connection to host=" . $host . " port=" . $port . " timeout=" . $timeout);
1880
            //See if we can connect to the SMTP server
1881
            $errno = 0;
1882
            $errstr = '';
1883
            if (
1884
                $socket = @fsockopen(
1885
                $host, // Host to test, IP or domain. Add ssl:// for SSL/TLS.
1886
                $port, // which Port number to use
1887
                $errno, // actual system level error
1888
                $errstr, // and any text that goes with the error
1889
                $timeout     // timeout for reading/writing data over the socket
1890
                )
1891
            ) {
1892
                // Windows still does not have support for this timeout function
1893
                if (function_exists('stream_set_timeout')) {
1894
                    stream_set_timeout($socket, $timeout, 0);
1895
                }
1896
1897
                dol_syslog("Now we wait for answer 220");
1898
1899
                // Check response from Server
1900
                if ($_retVal = $this->server_parse($socket, "220")) {
1901
                    $_retVal = $socket;
1902
                }
1903
            } else {
1904
                $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...
1905
            }
1906
        }
1907
        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...
1908
    }
1909
1910
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1911
    /**
1912
     * This function has been modified as provided by SirSir to allow multiline responses when
1913
     * using SMTP Extensions.
1914
     *
1915
     * @param   resource    $socket         Socket
1916
     * @param   string      $response       Response string
1917
     * @return  boolean                     true if success
1918
     */
1919
    public function server_parse($socket, $response)
1920
    {
1921
		// phpcs:enable
1922
        $_retVal = true; // Indicates if Object was created or not
1923
        $server_response = '';
1924
1925
        while (substr($server_response, 3, 1) != ' ') {
1926
            if (!($server_response = fgets($socket, 256))) {
1927
                $this->error = "Couldn't get mail server response codes";
1928
                return false;
1929
            }
1930
        }
1931
1932
        if (!(substr($server_response, 0, 3) == $response)) {
1933
            $this->error = "Ran into problems sending Mail.\r\nResponse: $server_response";
1934
            $_retVal = false;
1935
        }
1936
1937
        return $_retVal;
1938
    }
1939
1940
    /**
1941
     * Search images into html message and init array this->images_encoded if found
1942
     *
1943
     * @param   string  $images_dir     Path to store physical images files. For example $dolibarr_main_data_root.'/medias'
1944
     * @return  int                     >0 if OK, <0 if KO
1945
     */
1946
    private function findHtmlImages($images_dir)
1947
    {
1948
        // Build the array of image extensions
1949
        $extensions = array_keys($this->image_types);
1950
1951
        // We search (into mail body this->html), if we find some strings like "... file=xxx.img"
1952
        // For example when:
1953
        // <img alt="" src="/viewimage.php?modulepart=medias&amp;entity=1&amp;file=image/picture.jpg" style="height:356px; width:1040px" />
1954
        $matches = array();
1955
        preg_match_all('/(?:"|\')([^"\']+\.(' . implode('|', $extensions) . '))(?:"|\')/Ui', $this->html, $matches); // If "xxx.ext" or 'xxx.ext' found
1956
1957
        if (!empty($matches) && !empty($matches[1])) {
1958
            $i = 0;
1959
            // We are interested in $matches[1] only (the second set of parenthesis into regex)
1960
            foreach ($matches[1] as $full) {
1961
                $regs = array();
1962
                if (preg_match('/file=([A-Za-z0-9_\-\/]+[\.]?[A-Za-z0-9]+)?$/i', $full, $regs)) {   // If xxx is 'file=aaa'
1963
                    $img = $regs[1];
1964
1965
                    if (file_exists($images_dir . '/' . $img)) {
1966
                        // Image path in src
1967
                        $src = preg_quote($full, '/');
1968
                        // Image full path
1969
                        $this->html_images[$i]["fullpath"] = $images_dir . '/' . $img;
1970
                        // Image name
1971
                        $this->html_images[$i]["name"] = $img;
1972
                        // Content type
1973
                        $regext = array();
1974
                        if (preg_match('/^.+\.(\w{3,4})$/', $img, $regext)) {
1975
                            $ext = strtolower($regext[1]);
1976
                            $this->html_images[$i]["content_type"] = $this->image_types[$ext];
1977
                        }
1978
                        // cid
1979
                        $this->html_images[$i]["cid"] = dol_hash($this->html_images[$i]["fullpath"], 'md5'); // Force md5 hash (does not contain special chars)
1980
                        // type
1981
                        $this->html_images[$i]["type"] = 'cidfromurl';
1982
1983
                        $this->html = preg_replace("/src=\"$src\"|src='$src'/i", "src=\"cid:" . $this->html_images[$i]["cid"] . "\"", $this->html);
1984
                    }
1985
                    $i++;
1986
                }
1987
            }
1988
1989
            if (!empty($this->html_images)) {
1990
                $inline = array();
1991
1992
                $i = 0;
1993
1994
                foreach ($this->html_images as $img) {
1995
                    $fullpath = $images_dir . '/' . $img["name"];
1996
1997
                    // If duplicate images are embedded, they may show up as attachments, so remove them.
1998
                    if (!in_array($fullpath, $inline)) {
1999
                        // Read image file
2000
                        if ($image = file_get_contents($fullpath)) {
2001
                            // On garde que le nom de l'image
2002
                            $regs = array();
2003
                            preg_match('/([A-Za-z0-9_-]+[\.]?[A-Za-z0-9]+)?$/i', $img["name"], $regs);
2004
                            $imgName = $regs[1];
2005
2006
                            $this->images_encoded[$i]['name'] = $imgName;
2007
                            $this->images_encoded[$i]['fullpath'] = $fullpath;
2008
                            $this->images_encoded[$i]['content_type'] = $img["content_type"];
2009
                            $this->images_encoded[$i]['cid'] = $img["cid"];
2010
                            // Encodage de l'image
2011
                            $this->images_encoded[$i]["image_encoded"] = chunk_split(base64_encode($image), 68, $this->eol);
2012
                            $inline[] = $fullpath;
2013
                        }
2014
                    }
2015
                    $i++;
2016
                }
2017
            } else {
2018
                return -1;
2019
            }
2020
2021
            return 1;
2022
        } else {
2023
            return 0;
2024
        }
2025
    }
2026
2027
    /**
2028
     * Seearch images with data:image format into html message.
2029
     * If we find some, we create it on disk.
2030
     *
2031
     * @param   string  $images_dir     Location of where to store physically images files. For example $dolibarr_main_data_root.'/medias'
2032
     * @return  int                     >0 if OK, <0 if KO
2033
     */
2034
    private function findHtmlImagesIsSrcData($images_dir)
2035
    {
2036
        global $conf;
2037
2038
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
2039
2040
        // Build the array of image extensions
2041
        $extensions = array_keys($this->image_types);
2042
2043
        if (empty($images_dir)) {
2044
            //$images_dir = $conf->admin->dir_output.'/temp/'.uniqid('cmailfile');
2045
            $images_dir = $conf->admin->dir_output . '/temp/cmailfile';
2046
        }
2047
2048
        if ($images_dir && !dol_is_dir($images_dir)) {
2049
            dol_mkdir($images_dir, DOL_DATA_ROOT);
2050
        }
2051
2052
        // Uncomment this for debug
2053
        /*
2054
        global $dolibarr_main_data_root;
2055
        $outputfile = $dolibarr_main_data_root."/dolibarr_mail.log";
2056
        $fp = fopen($outputfile, "w+");
2057
        fwrite($fp, $this->html);
2058
        fclose($fp);
2059
        */
2060
2061
        // We search (into mail body this->html), if we find some strings like "... file=xxx.img"
2062
        // For example when:
2063
        // <img alt="" src="/src="data:image....;base64,...." />
2064
        $matches = array();
2065
        preg_match_all('/src="data:image\/(' . implode('|', $extensions) . ');base64,([^"]+)"/Ui', $this->html, $matches); // If "xxx.ext" or 'xxx.ext' found
2066
2067
        if (!empty($matches) && !empty($matches[1])) {
2068
            if (empty($images_dir)) {
2069
                // No temp directory provided, so we are not able to support conversion of data:image into physical images.
2070
                $this->errors[] = 'NoTempDirProvidedInCMailConstructorSoCantConvertDataImgOnDisk';
2071
                return -1;
2072
            }
2073
2074
            $i = count($this->html_images);
2075
            foreach ($matches[1] as $key => $ext) {
2076
                // We save the image to send in disk
2077
                $filecontent = $matches[2][$key];
2078
2079
                $cid = 'cid000' . dol_hash($filecontent, 'md5');      // The id must not change if image is same
2080
2081
                $destfiletmp = $images_dir . '/' . $cid . '.' . $ext;
2082
2083
                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)
2084
                    dol_syslog("write the cid file " . $destfiletmp);
2085
                    $fhandle = @fopen($destfiletmp, 'w');
2086
                    if ($fhandle) {
2087
                        $nbofbyteswrote = fwrite($fhandle, base64_decode($filecontent));
2088
                        fclose($fhandle);
2089
                        dolChmod($destfiletmp);
2090
                    } else {
2091
                        $this->errors[] = "Failed to open file '" . $destfiletmp . "' for write";
2092
                        return -2;
2093
                    }
2094
                }
2095
2096
                if (file_exists($destfiletmp)) {
2097
                    // Image full path
2098
                    $this->html_images[$i]["fullpath"] = $destfiletmp;
2099
                    // Image name
2100
                    $this->html_images[$i]["name"] = basename($destfiletmp);
2101
                    // Content type
2102
                    $this->html_images[$i]["content_type"] = $this->image_types[strtolower($ext)];
2103
                    // cid
2104
                    $this->html_images[$i]["cid"] = $cid;
2105
                    // type
2106
                    $this->html_images[$i]["type"] = 'cidfromdata';
2107
2108
                    $this->html = str_replace('src="data:image/' . $ext . ';base64,' . $filecontent . '"', 'src="cid:' . $this->html_images[$i]["cid"] . '"', $this->html);
2109
                }
2110
                $i++;
2111
            }
2112
2113
            return 1;
2114
        } else {
2115
            return 0;
2116
        }
2117
    }
2118
2119
    /**
2120
     * Return a formatted address string for SMTP protocol
2121
     *
2122
     * @param   string      $address             Example: 'John Doe <[email protected]>, Alan Smith <[email protected]>' or '[email protected], [email protected]'
2123
     * @param   int         $format              0=auto, 1=emails with <>, 2=emails without <>, 3=auto + label between ", 4 label or email, 5 mailto link
2124
     * @param   int         $encode              0=No encode name, 1=Encode name to RFC2822
2125
     * @param   int         $maxnumberofemail    0=No limit. Otherwise, maximum number of emails returned ($address may contains several email separated with ','). Add '...' if there is more.
2126
     * @return  string                           If format 0: '<[email protected]>' or 'John Doe <[email protected]>' or '=?UTF-8?B?Sm9obiBEb2U=?= <[email protected]>'
2127
     *                                           If format 1: '<[email protected]>'
2128
     *                                           If format 2: '[email protected]'
2129
     *                                           If format 3: '<[email protected]>' or '"John Doe" <[email protected]>' or '"=?UTF-8?B?Sm9obiBEb2U=?=" <[email protected]>'
2130
     *                                           If format 4: 'John Doe' or '[email protected]' if no label exists
2131
     *                                           If format 5: <a href="mailto:[email protected]">John Doe</a> or <a href="mailto:[email protected]">[email protected]</a> if no label exists
2132
     * @see getArrayAddress()
2133
     */
2134
    public static function getValidAddress($address, $format, $encode = 0, $maxnumberofemail = 0)
2135
    {
2136
        global $conf;
2137
2138
        $ret = '';
2139
2140
        $arrayaddress = (!empty($address) ? explode(',', $address) : array());
2141
2142
        // Boucle sur chaque composant de l'address
2143
        $i = 0;
2144
        foreach ($arrayaddress as $val) {
2145
            $regs = array();
2146
            if (preg_match('/^(.*)<(.*)>$/i', trim($val), $regs)) {
2147
                $name  = trim($regs[1]);
2148
                $email = trim($regs[2]);
2149
            } else {
2150
                $name  = '';
2151
                $email = trim($val);
2152
            }
2153
2154
            if ($email) {
2155
                $i++;
2156
2157
                $newemail = '';
2158
                if ($format == 5) {
2159
                    $newemail = $name ? $name : $email;
2160
                    $newemail = '<a href="mailto:' . $email . '">' . $newemail . '</a>';
2161
                }
2162
                if ($format == 4) {
2163
                    $newemail = $name ? $name : $email;
2164
                }
2165
                if ($format == 2) {
2166
                    $newemail = $email;
2167
                }
2168
                if ($format == 1 || $format == 3) {
2169
                    $newemail = '<' . $email . '>';
2170
                }
2171
                if ($format == 0 || $format == 3) {
2172
                    if (getDolGlobalString('MAIN_MAIL_NO_FULL_EMAIL')) {
2173
                        $newemail = '<' . $email . '>';
2174
                    } elseif (!$name) {
2175
                        $newemail = '<' . $email . '>';
2176
                    } else {
2177
                        $newemail = ($format == 3 ? '"' : '') . ($encode ? self::encodetorfc2822($name) : $name) . ($format == 3 ? '"' : '') . ' <' . $email . '>';
2178
                    }
2179
                }
2180
2181
                $ret = ($ret ? $ret . ',' : '') . $newemail;
2182
2183
                // Stop if we have too much records
2184
                if ($maxnumberofemail && $i >= $maxnumberofemail) {
2185
                    if (count($arrayaddress) > $maxnumberofemail) {
2186
                        $ret .= '...';
2187
                    }
2188
                    break;
2189
                }
2190
            }
2191
        }
2192
2193
        return $ret;
2194
    }
2195
2196
    /**
2197
     * Return a formatted array of address string for SMTP protocol
2198
     *
2199
     * @param   string      $address        Example: 'John Doe <[email protected]>, Alan Smith <[email protected]>' or '[email protected], [email protected]'
2200
     * @return  array                       array(email => name)
2201
     * @see getValidAddress()
2202
     */
2203
    public static function getArrayAddress($address)
2204
    {
2205
        $ret = array();
2206
2207
        $arrayaddress = explode(',', $address);
2208
2209
        // Boucle sur chaque composant de l'address
2210
        foreach ($arrayaddress as $val) {
2211
            $regs = array();
2212
            if (preg_match('/^(.*)<(.*)>$/i', trim($val), $regs)) {
2213
                $name  = trim($regs[1]);
2214
                $email = trim($regs[2]);
2215
            } else {
2216
                $name  = null;
2217
                $email = trim($val);
2218
            }
2219
2220
            $ret[$email] = getDolGlobalString('MAIN_MAIL_NO_FULL_EMAIL') ? null : $name;
2221
        }
2222
2223
        return $ret;
2224
    }
2225
}
2226