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

SMTPs::_server_authenticate()   F

Complexity

Conditions 21
Paths 1020

Size

Total Lines 173
Code Lines 68

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 21
eloc 68
nc 1020
nop 0
dl 0
loc 173
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * Copyright (C)            Walter Torres               <[email protected]> [with a *lot* of help!]
5
 * Copyright (C) 2005-2015  Laurent Destailleur         <[email protected]>
6
 * Copyright (C) 2006-2011  Regis Houssin
7
 * Copyright (C) 2016       Jonathan TISSEAU            <[email protected]>
8
 * Copyright (C) 2024       MDW                         <[email protected]>
9
 * Copyright (C) 2024       Frédéric France             <[email protected]>
10
 * Copyright (C) 2024       Rafael San José             <[email protected]>
11
 *
12
 * This program is free software; you can redistribute it and/or modify
13
 * it under the terms of the GNU General Public License as published by
14
 * the Free Software Foundation; either version 3 of the License, or
15
 * (at your option) any later version.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
 * GNU General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU General Public License
23
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
24
 */
25
26
namespace Dolibarr\Code\Core\Classes;
27
28
/**
29
 *  \file       htdocs/core/class/smtps.class.php
30
 *  \brief      Class to construct and send SMTP compliant email, even to a secure
31
 *              SMTP server, regardless of platform.
32
 * Goals:
33
 *  - mime compliant
34
 *  - multiple Reciptiants
35
 *    - TO
36
 *    - CC
37
 *    - BCC
38
 *  - multi-part message
39
 *    - plain text
40
 *    - HTML
41
 *    - inline attachments
42
 *    - attachments
43
 *  - GPG access
44
 * This Class is based off of 'SMTP PHP MAIL' by Dirk Paehl, http://www.paehl.de
45
 */
46
47
48
/**
49
 *  Class to construct and send SMTP compliant email, even
50
 *  to a secure SMTP server, regardless of platform.
51
 */
52
class SMTPs
53
{
54
    /**
55
     * Host Name or IP of SMTP Server to use
56
     */
57
    private $_smtpsHost = 'localhost';
58
59
    /**
60
     * SMTP Server Port definition. 25 is default value
61
     * This can be defined via a INI file or via a setter method
62
     *
63
     * @var int
64
     */
65
    private $_smtpsPort = 25;
66
67
    /**
68
     * Secure SMTP Server access ID
69
     * This can be defined via a INI file or via a setter method
70
     */
71
    private $_smtpsID = null;
72
73
    /**
74
     * Secure SMTP Server access Password
75
     * This can be defined via a INI file or via a setter method
76
     */
77
    private $_smtpsPW = null;
78
79
    /**
80
     * Token in case we use OAUTH2
81
     */
82
    private $_smtpsToken = null;
83
84
    /**
85
     * Who sent the Message
86
     * This can be defined via a INI file or via a setter method
87
     */
88
    private $_msgFrom = null;
89
90
    /**
91
     * Where are replies and errors to be sent to
92
     * This can be defined via a INI file or via a setter method
93
     */
94
    private $_msgReplyTo = null;
95
96
    /**
97
     * List of In-Reply-To
98
     */
99
    private $_msgInReplyTo = null;
100
101
    /**
102
     * List of Msg-Id
103
     */
104
    private $_msgReferences = null;
105
106
    /**
107
     * Who will the Message be sent to; TO, CC, BCC
108
     * Multi-diminsional array containing addresses the message will
109
     * be sent TO, CC or BCC
110
     */
111
    private $_msgRecipients = null;
112
113
    /**
114
     * Message Subject
115
     */
116
    private $_msgSubject = null;
117
118
    /**
119
     * Message Content
120
     *
121
     * @var array{html?:array{mimeType:string,data:string,dataText:string,md5?:string},plain?:array{mimeType:string,data:string,dataText:string,md5?:string},image:array<string,array{imageName:string,cid:string,md5?:string,data:string}>,attachment:array<string,array{filename:string,cid?:string,md5?:string,data:string}>}    $msgContent Array of messages
122
     */
123
    private $_msgContent = array();
124
125
    /**
126
     * Custom X-Headers
127
     */
128
    private $_msgXheader = null;
129
130
    /**
131
     * Character set
132
     * Defaulted to 'iso-8859-1'
133
     */
134
    private $_smtpsCharSet = 'iso-8859-1';
135
136
    /**
137
     * Message Sensitivity
138
     * Defaults to ZERO - None
139
     */
140
    private $_msgSensitivity = 0;
141
142
    /**
143
     * Message Sensitivity
144
     */
145
    private $_arySensitivity = array(false,
146
                                  'Personal',
147
                                  'Private',
148
                                  'Company Confidential');
149
150
    /**
151
     * Message Sensitivity
152
     * Defaults to 3 - Normal
153
     */
154
    private $_msgPriority = 3;
155
156
    /**
157
     * Message Priority
158
     */
159
    private $_aryPriority = array('Bulk',
160
                                'Highest',
161
                                'High',
162
                                'Normal',
163
                                'Low',
164
                                'Lowest');
165
166
    /**
167
     * Content-Transfer-Encoding
168
     * Defaulted to 0 - 7bit
169
     */
170
    private $_smtpsTransEncodeType = 0;
171
172
    /**
173
     * Content-Transfer-Encoding
174
     */
175
    private $_smtpsTransEncodeTypes = array('7bit', // Simple 7-bit ASCII
176
                                         '8bit', // 8-bit coding with line termination characters
177
                                         'base64', // 3 octets encoded into 4 sextets with offset
178
                                         'binary', // Arbitrary binary stream
179
                                         'mac-binhex40', // Macintosh binary to hex encoding
180
                                         'quoted-printable', // Mostly 7-bit, with 8-bit characters encoded as "=HH"
181
                                         'uuencode'); // UUENCODE encoding
182
183
    /**
184
     * Content-Transfer-Encoding
185
     * Defaulted to '7bit'
186
     */
187
    private $_smtpsTransEncode = '7bit';
188
189
    /**
190
     * Boundary String for MIME separation
191
     */
192
    private $_smtpsBoundary = null;
193
194
    /**
195
     * Related Boundary
196
     */
197
    private $_smtpsRelatedBoundary = null;
198
199
    /**
200
     * Alternative Boundary
201
     */
202
    private $_smtpsAlternativeBoundary = null;
203
204
    /**
205
     * Determines the method inwhich the message are to be sent.
206
     * - 'sockets' [0] - connect via network to SMTP server - default
207
     * - 'pipe     [1] - use UNIX path to EXE
208
     * - 'phpmail  [2] - use the PHP built-in mail function
209
     * NOTE: Only 'sockets' is implemented
210
     */
211
    private $_transportType = 0;
212
213
    /**
214
     * If '$_transportType' is set to '1', then this variable is used
215
     * to define the UNIX file system path to the sendmail executable
216
     */
217
    private $_mailPath = '/usr/lib/sendmail'; // @phpstan-ignore-line
0 ignored issues
show
introduced by
The private property $_mailPath is not used, and could be removed.
Loading history...
218
219
    /**
220
     * Sets the SMTP server timeout in seconds.
221
     */
222
    private $_smtpTimeout = 10;
223
224
    /**
225
     * Determines whether to calculate message MD5 checksum.
226
     */
227
    private $_smtpMD5 = false;
228
229
    /**
230
     * Class error codes and messages
231
     */
232
    private $_smtpsErrors = null;
233
234
    /**
235
     * Defines log level
236
     *  0 - no logging
237
     *  1 - connectivity logging
238
     *  2 - message generation logging
239
     *  3 - detail logging
240
     */
241
    private $_log_level = 0; // @phpstan-ignore-line
0 ignored issues
show
introduced by
The private property $_log_level is not used, and could be removed.
Loading history...
242
243
    /**
244
     * Place Class in" debug" mode
245
     */
246
    private $_debug = false;
247
248
249
    // @CHANGE LDR
250
    public $log = '';
251
    public $lastretval = '';
252
253
    /**
254
     * @var resource
255
     */
256
    public $socket;
257
258
    /**
259
     * @var int
260
     */
261
    public $errno;
262
263
    /**
264
     * @var string
265
     */
266
    public $errstr;
267
268
    private $_errorsTo = '';
269
    private $_deliveryReceipt = 0;
270
    private $_trackId = '';
271
    private $_moreinheader = '';
272
273
    /**
274
     * An array of options for stream_context_create()
275
     */
276
    private $_options = array();
277
278
    /**
279
     * Set delivery receipt
280
     *
281
     * @param   array       $_options       An array of options for stream_context_create()
282
     * @return  void
283
     */
284
    public function setOptions($_options = array())
285
    {
286
        $this->_options = $_options;
287
    }
288
289
    /**
290
     * Set delivery receipt
291
     *
292
     * @param   int     $_val       Value
293
     * @return  void
294
     */
295
    public function setDeliveryReceipt($_val = 0)
296
    {
297
        $this->_deliveryReceipt = $_val;
298
    }
299
300
    /**
301
     * get delivery receipt
302
     *
303
     * @return  int     Delivery receipt
304
     */
305
    public function getDeliveryReceipt()
306
    {
307
        return $this->_deliveryReceipt;
308
    }
309
310
    /**
311
     * Set trackid
312
     *
313
     * @param   string      $_val       Value
314
     * @return  void
315
     */
316
    public function setTrackId($_val = '')
317
    {
318
        $this->_trackId = $_val;
319
    }
320
321
    /**
322
     * Set moreInHeader
323
     *
324
     * @param   string      $_val       Value
325
     * @return  void
326
     */
327
    public function setMoreInHeader($_val = '')
328
    {
329
        $this->_moreinheader = $_val;
330
    }
331
332
    /**
333
     * get trackid
334
     *
335
     * @return  string      Track id
336
     */
337
    public function getTrackId()
338
    {
339
        return $this->_trackId;
340
    }
341
342
    /**
343
     * get moreInHeader
344
     *
345
     * @return  string      moreInHeader
346
     */
347
    public function getMoreInHeader()
348
    {
349
        return $this->_moreinheader;
350
    }
351
352
    /**
353
     * Set errors to
354
     *
355
     * @param   string      $_strErrorsTo       Errors to
356
     * @return  void
357
     */
358
    public function setErrorsTo($_strErrorsTo)
359
    {
360
        if ($_strErrorsTo) {
361
            $this->_errorsTo = $this->_strip_email($_strErrorsTo);
362
        }
363
    }
364
365
    /**
366
     * Get errors to
367
     *
368
     * @param   boolean     $_part      Variant
369
     * @return  string                  Errors to
370
     */
371
    public function getErrorsTo($_part = true)
372
    {
373
        $_retValue = '';
374
375
        if ($_part === true) {
376
            $_retValue = $this->_errorsTo;
377
        } else {
378
            $_retValue = $this->_errorsTo[$_part];
379
        }
380
381
        return $_retValue;
382
    }
383
384
    /**
385
     * Set debug
386
     *
387
     * @param   boolean     $_vDebug        Value for debug
388
     * @return  void
389
     */
390
    public function setDebug($_vDebug = false)
391
    {
392
        $this->_debug = $_vDebug;
393
    }
394
395
    /**
396
     * build RECIPIENT List, all addresses who will receive this message
397
     *
398
     * @return void
399
     */
400
    public function buildRCPTlist()
401
    {
402
        // Pull TO list
403
        $_aryToList = $this->getTo();
404
    }
405
406
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
407
    /**
408
     * Attempt a connection to mail server
409
     *
410
     * @return mixed  $_retVal   Boolean indicating success or failure on connection
411
     */
412
    private function _server_connect()
413
    {
414
		// phpcs:enable
415
        // Default return value
416
        $_retVal = true;
417
418
        // We have to make sure the HOST given is valid
419
        // This is done here because '@fsockopen' will not give me this
420
        // information if it fails to connect because it can't find the HOST
421
        $host = $this->getHost();
422
        $usetls = preg_match('@tls://@i', $host);
423
424
        $host = preg_replace('@tcp://@i', '', $host); // Remove prefix
425
        $host = preg_replace('@ssl://@i', '', $host); // Remove prefix
426
        $host = preg_replace('@tls://@i', '', $host); // Remove prefix
427
428
        // @CHANGE LDR
429
        include_once DOL_DOCUMENT_ROOT . '/core/lib/functions2.lib.php';
430
431
        if ((!is_ip($host)) && ((gethostbyname($host)) == $host)) {
432
            $this->_setErr(99, $host . ' is either offline or is an invalid host name.');
433
            $_retVal = false;
434
        } else {
435
            if (function_exists('stream_socket_client') && !empty($this->_options)) {
436
                $socket_context = stream_context_create($this->_options); // An array of options for stream_context_create()
437
                $this->socket = @stream_socket_client(
0 ignored issues
show
Documentation Bug introduced by
It seems like @stream_socket_client(pr...NNECT, $socket_context) can also be of type false. However, the property $socket is declared as type resource. 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...
438
                    preg_replace('@tls://@i', '', $this->getHost()) . // Host to 'hit', IP or domain
439
                    ':' . $this->getPort(), // which Port number to use
440
                    $this->errno, // actual system level error
441
                    $this->errstr, // and any text that goes with the error
442
                    $this->_smtpTimeout, // timeout for reading/writing data over the socket
443
                    STREAM_CLIENT_CONNECT,
444
                    $socket_context                     // Options for connection
445
                );
446
            } else {
447
                $this->socket = @fsockopen(
448
                    preg_replace('@tls://@i', '', $this->getHost()), // Host to 'hit', IP or domain
449
                    $this->getPort(), // which Port number to use
450
                    $this->errno, // actual system level error
451
                    $this->errstr, // and any text that goes with the error
452
                    $this->_smtpTimeout     // timeout for reading/writing data over the socket
453
                );
454
            }
455
456
            //See if we can connect to the SMTP server
457
            if (is_resource($this->socket)) {
458
                // Fix from PHP SMTP class by 'Chris Ryan'
459
                // Sometimes the SMTP server takes a little longer to respond
460
                // so we will give it a longer timeout for the first read
461
                // Windows still does not have support for this timeout function
462
                if (function_exists('stream_set_timeout')) {
463
                    stream_set_timeout($this->socket, $this->_smtpTimeout, 0);
464
                }
465
466
                // Check response from Server
467
                if ($_retVal = $this->server_parse($this->socket, "220")) {
468
                    $_retVal = $this->socket;
469
                }
470
            } else {
471
                // This connection attempt failed.
472
                // @CHANGE LDR
473
                if (empty($this->errstr)) {
474
                    $this->errstr = 'Failed to connect with fsockopen host=' . $this->getHost() . ' port=' . $this->getPort();
475
                }
476
                $this->_setErr($this->errno, $this->errstr);
477
                $_retVal = false;
478
            }
479
        }
480
481
        return $_retVal;
482
    }
483
484
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
485
    /**
486
     * Attempt mail server authentication for a secure connection
487
     *
488
     * @return boolean|null  $_retVal   Boolean indicating success or failure of authentication
489
     */
490
    private function _server_authenticate()
491
    {
492
		// phpcs:enable
493
        global $conf;
494
495
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/geturl.lib.php';
496
        // Send the RFC2554 specified EHLO.
497
        // This improvement as provided by 'SirSir' to
498
        // accommodate both SMTP AND ESMTP capable servers
499
        $host = $this->getHost();
500
        $usetls = preg_match('@tls://@i', $host);
501
502
        $host = preg_replace('@tcp://@i', '', $host); // Remove prefix
503
        $host = preg_replace('@ssl://@i', '', $host); // Remove prefix
504
        $host = preg_replace('@tls://@i', '', $host); // Remove prefix
505
506
        if ($usetls && getDolGlobalString('MAIN_SMTPS_ADD_TLS_TO_HOST_FOR_HELO')) {
507
            $host = 'tls://' . $host;
508
        }
509
510
        $hosth = $host; // so for example 'localhost' or 'smtp-relay.gmail.com'
511
512
        if (getDolGlobalString('MAIL_SMTP_USE_FROM_FOR_HELO')) {
513
            if (!is_numeric(getDolGlobalString('MAIL_SMTP_USE_FROM_FOR_HELO'))) {
514
                // If value of MAIL_SMTP_USE_FROM_FOR_HELO is a string, we use it as domain name
515
                $hosth = getDolGlobalString('MAIL_SMTP_USE_FROM_FOR_HELO');
516
            } elseif (getDolGlobalInt('MAIL_SMTP_USE_FROM_FOR_HELO') == 1) {
517
                // If value of MAIL_SMTP_USE_FROM_FOR_HELO is 1, we use the domain in the from.
518
                // So if the from to is 'aaa <[email protected]>', we will keep 'ccc.com'
519
                $hosth = $this->getFrom('addr');
520
                $hosth = preg_replace('/^.*</', '', $hosth);
521
                $hosth = preg_replace('/>.*$/', '', $hosth);
522
                $hosth = preg_replace('/.*@/', '', $hosth);
523
            } elseif (getDolGlobalInt('MAIL_SMTP_USE_FROM_FOR_HELO') == 2) {
524
                // If value of MAIL_SMTP_USE_FROM_FOR_HELO is 2, we use the domain in the $dolibarr_main_url_root.
525
                global $dolibarr_main_url_root;
526
                $hosth = getDomainFromURL($dolibarr_main_url_root, 1);
527
            }
528
        }
529
530
        if ($_retVal = $this->socket_send_str('EHLO ' . $hosth, '250')) {
531
            if ($usetls) {
532
                /*
533
                The following dialog illustrates how a client and server can start a TLS STARTTLS session:
534
                S: <waits for connection on TCP port 25>
535
                C: <opens connection>
536
                S: 220 mail.imc.org SMTP service ready
537
                C: EHLO mail.ietf.org
538
                S: 250-mail.imc.org offers a warm hug of welcome
539
                S: 250 STARTTLS
540
                C: STARTTLS
541
                S: 220 Go ahead
542
                C: <starts TLS negotiation>
543
                C & S: <negotiate a TLS session>
544
                C & S: <check result of negotiation>
545
                // Second pass EHLO
546
                C: EHLO client-domain.com
547
                S: 250-server-domain.com
548
                S: 250 AUTH LOGIN
549
                C: <continues by sending an SMTP command
550
551
                Another example here:
552
                S: 220 smtp.server.com Simple Mail Transfer Service Ready
553
                C: EHLO client.example.com
554
                S: 250-smtp.server.com Hello client.example.com
555
                S: 250-SIZE 1000000
556
                S: 250-AUTH LOGIN PLAIN CRAM-MD5
557
                S: 250-STARTTLS
558
                S: 250 HELP
559
                C: STARTTLS
560
                S: 220 TLS go ahead
561
                C: EHLO client.example.com *
562
                S: 250-smtp.server.com Hello client.example.com
563
                S: 250-SIZE 1000000
564
                S: 250-AUTH LOGIN PLAIN CRAM-MD5
565
                S: 250 HELP
566
                C: AUTH LOGIN
567
                S: 334 VXNlcm5hbWU6
568
                C: adlxdkej
569
                S: 334 UGFzc3dvcmQ6
570
                C: lkujsefxlj
571
                S: 235 2.7.0 Authentication successful
572
                C: MAIL FROM:<[email protected]>
573
                S: 250 OK
574
                C: RCPT TO:<[email protected]>
575
                S: 250 OK
576
                C: DATA
577
                S: 354 Send message, end with a "." on a line by itself
578
                C: <The message data (body text, subject, e-mail header, attachments etc) is sent>
579
                S .
580
                S: 250 OK, message accepted for delivery: queued as 12345
581
                C: QUIT
582
                S: 221 Bye
583
                */
584
                if (!$_retVal = $this->socket_send_str('STARTTLS', 220)) {
585
                    $this->_setErr(131, 'STARTTLS connection is not supported.');
586
                    return $_retVal;
587
                }
588
589
                // Before 5.6.7:
590
                // STREAM_CRYPTO_METHOD_SSLv23_CLIENT = STREAM_CRYPTO_METHOD_SSLv2_CLIENT|STREAM_CRYPTO_METHOD_SSLv3_CLIENT
591
                // STREAM_CRYPTO_METHOD_TLS_CLIENT = STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT|STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT|STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
592
                // PHP >= 5.6.7:
593
                // STREAM_CRYPTO_METHOD_SSLv23_CLIENT = STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT|STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT|STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT
594
                // STREAM_CRYPTO_METHOD_TLS_CLIENT = STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT
595
596
                $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
597
                if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
598
                    $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
599
                    $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
600
                }
601
602
                if (!stream_socket_enable_crypto($this->socket, true, $crypto_method)) {
603
                    $this->_setErr(132, 'STARTTLS connection failed.');
604
                    return $_retVal;
605
                }
606
                // Most servers expect a 2nd pass of EHLO after TLS is established to get another time
607
                // the answer with list of supported AUTH methods. They may differs between non STARTTLS and with STARTTLS.
608
                if (! $_retVal = $this->socket_send_str('EHLO ' . $hosth, '250')) {
609
                    $this->_setErr(126, '"' . $hosth . '" does not support authenticated connections or temporary error. Error after 2nd sending EHLO ' . $hosth . ' : ' . $this->lastretval);
610
                    return $_retVal;
611
                }
612
            }
613
614
            // Default authentication method is LOGIN
615
            if (!getDolGlobalString('MAIN_MAIL_SMTPS_AUTH_TYPE')) {
616
                $conf->global->MAIN_MAIL_SMTPS_AUTH_TYPE = 'LOGIN';
617
            }
618
619
            // Send Authentication to Server
620
            // Check for errors along the way
621
            switch ($conf->global->MAIN_MAIL_SMTPS_AUTH_TYPE) {
622
                case 'NONE':
623
                    // Do not send the 'AUTH type' message. For test purpose, if you don't need authentication, it is better to not enter login/pass into setup.
624
                    $_retVal = true;
625
                    break;
626
                case 'PLAIN':
627
                    $this->socket_send_str('AUTH PLAIN', '334');
628
                    // The error here just means the ID/password combo doesn't work.
629
                    $_retVal = $this->socket_send_str(base64_encode("\0" . $this->_smtpsID . "\0" . $this->_smtpsPW), '235');
630
                    break;
631
                case 'XOAUTH2':
632
                    // "user=$email\1auth=Bearer $token\1\1"
633
                    $user = $this->_smtpsID;
634
                    $token = $this->_smtpsToken;
635
                    $initRes = "user=" . $user . "\001auth=Bearer " . $token . "\001\001";
636
                    $_retVal = $this->socket_send_str('AUTH XOAUTH2 ' . base64_encode($initRes), '235');
637
                    if (!$_retVal) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $_retVal of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
638
                        $this->_setErr(130, 'Error when asking for AUTH XOAUTH2');
639
                    }
640
                    break;
641
                case 'LOGIN':   // most common case
642
                default:
643
                    $_retVal = $this->socket_send_str('AUTH LOGIN', '334');
644
                    if (!$_retVal) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $_retVal of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
645
                        $this->_setErr(130, 'Error when asking for AUTH LOGIN');
646
                    } else {
647
                        // User name will not return any error, server will take anything we give it.
648
                        $this->socket_send_str(base64_encode($this->_smtpsID), '334');
649
                        // The error here just means the ID/password combo doesn't work.
650
                        // There is no method to determine which is the problem, ID or password
651
                        $_retVal = $this->socket_send_str(base64_encode($this->_smtpsPW), '235');
652
                    }
653
                    break;
654
            }
655
            if (!$_retVal) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $_retVal of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
656
                $this->_setErr(130, 'Invalid Authentication Credentials.');
657
            }
658
        } else {
659
            $this->_setErr(126, '"' . $host . '" does not support authenticated connections or temporary error. Error after sending EHLO ' . $hosth . ' : ' . $this->lastretval);
660
        }
661
662
        return $_retVal;
663
    }
664
665
    /**
666
     * Now send the message
667
     *
668
     * @return boolean|null   Result
669
     */
670
    public function sendMsg()
671
    {
672
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/geturl.lib.php';
673
674
        // Default return value
675
        $_retVal = false;
676
677
        // Connect to Server
678
        if ($this->socket = $this->_server_connect()) {
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->_server_connect() can also be of type false. However, the property $socket is declared as type resource. 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...
679
            // If a User ID *and* a password is given, assume Authentication is desired
680
            if (!empty($this->_smtpsID) && (!empty($this->_smtpsPW) || !empty($this->_smtpsToken))) {
681
                // Send the RFC2554 specified EHLO.
682
                $_retVal = $this->_server_authenticate();
683
            } else {
684
                // This is a "normal" SMTP Server "handshack"
685
                // Send the RFC821 specified HELO.
686
                $host = $this->getHost();
687
                $usetls = preg_match('@tls://@i', $host);
688
689
                $host = preg_replace('@tcp://@i', '', $host); // Remove prefix
690
                $host = preg_replace('@ssl://@i', '', $host); // Remove prefix
691
                $host = preg_replace('@tls://@i', '', $host); // Remove prefix
692
693
                if ($usetls && getDolGlobalString('MAIN_SMTPS_ADD_TLS_TO_HOST_FOR_HELO')) {
694
                    $host = 'tls://' . $host;
695
                }
696
697
                $hosth = $host;
698
699
                if (getDolGlobalString('MAIL_SMTP_USE_FROM_FOR_HELO')) {
700
                    if (!is_numeric(getDolGlobalString('MAIL_SMTP_USE_FROM_FOR_HELO'))) {
701
                        // If value of MAIL_SMTP_USE_FROM_FOR_HELO is a string, we use it as domain name
702
                        $hosth = getDolGlobalString('MAIL_SMTP_USE_FROM_FOR_HELO');
703
                    } elseif (getDolGlobalInt('MAIL_SMTP_USE_FROM_FOR_HELO') == 1) {
704
                        // If value of MAIL_SMTP_USE_FROM_FOR_HELO is 1, we use the domain in the from.
705
                        // So if the from to is 'aaa <[email protected]>', we will keep 'ccc.com'
706
                        $hosth = $this->getFrom('addr');
707
                        $hosth = preg_replace('/^.*</', '', $hosth);
708
                        $hosth = preg_replace('/>.*$/', '', $hosth);
709
                        $hosth = preg_replace('/.*@/', '', $hosth);
710
                    } elseif (getDolGlobalInt('MAIL_SMTP_USE_FROM_FOR_HELO') == 2) {
711
                        // If value of MAIL_SMTP_USE_FROM_FOR_HELO is 2, we use the domain in the $dolibarr_main_url_root.
712
                        global $dolibarr_main_url_root;
713
                        $hosth = getDomainFromURL($dolibarr_main_url_root, 1);
714
                    }
715
                }
716
717
                // Send the HELO message to the SMTP server
718
                $_retVal = $this->socket_send_str('HELO ' . $hosth, '250');
719
            }
720
721
            // Well, did we get the server answer with correct code ?
722
            if ($_retVal) {
723
                // From this point onward most server response codes should be 250
724
                // Specify who the mail is from....
725
                // This has to be the raw email address, strip the "name" off
726
                $resultmailfrom = $this->socket_send_str('MAIL FROM: ' . $this->getFrom('addr'), '250');
727
                if (!$resultmailfrom) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $resultmailfrom of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
728
                    fclose($this->socket);
729
                    return false;
730
                }
731
732
                // 'RCPT TO:' must be given a single address, so this has to loop
733
                // through the list of addresses, regardless of TO, CC or BCC
734
                // and send it out "single file"
735
                foreach ($this->get_RCPT_list() as $_address) {
736
                    /* Note:
737
                     * BCC email addresses must be listed in the RCPT TO command list,
738
                     * but the BCC header should not be printed under the DATA command.
739
                     * http://stackoverflow.com/questions/2750211/sending-bcc-emails-using-a-smtp-server
740
                     */
741
742
                    /*
743
                     * TODO
744
                     * After each 'RCPT TO:' is sent, we need to make sure it was kosher,
745
                     * if not, the whole message will fail
746
                     * If any email address fails, we will need to RESET the connection,
747
                     * mark the last address as "bad" and start the address loop over again.
748
                     * If any address fails, the entire message fails.
749
                     */
750
                    $this->socket_send_str('RCPT TO: <' . $_address . '>', '250');
751
                }
752
753
                // Tell the server we are ready to start sending data
754
                // with any custom headers...
755
                // This is the last response code we look for until the end of the message.
756
                $this->socket_send_str('DATA', '354');
757
758
                // Now we are ready for the message...
759
                // Ok, all the ingredients are mixed in let's cook this puppy...
760
                $this->socket_send_str($this->getHeader() . $this->getBodyContent() . "\r\n" . '.', '250');
761
762
                // Now tell the server we are done and close the socket...
763
                fwrite($this->socket, 'QUIT');
764
            } else {
765
                // We got error code into $this->lastretval
766
            }
767
768
            fclose($this->socket);
769
        }
770
771
        return $_retVal;
772
    }
773
774
    // =============================================================
775
    // ** Setter & Getter methods
776
777
    // ** Basic System configuration
778
779
    /**
780
     * setConfig() is used to populate select class properties from either
781
     * a user defined INI file or the systems 'php.ini' file
782
     *
783
     * If a user defined INI is to be used, the files complete path is passed
784
     * as the method single parameter. The INI can define any class and/or
785
     * user properties. Only properties defined within this file will be setter
786
     * and/or orverwritten
787
     *
788
     * If the systems 'php.ini' file is to be used, the method is called without
789
     * parameters. In this case, only HOST, PORT and FROM properties will be set
790
     * as they are the only properties that are defined within the 'php.ini'.
791
     *
792
     * If secure SMTP is to be used, the user ID and Password can be defined with
793
     * the user INI file, but the properties are not defined with the systems
794
     * 'php.ini'file, they must be defined via their setter methods
795
     *
796
     * This method can be called twice, if desired. Once without a parameter to
797
     * load the properties as defined within the systems 'php.ini' file, and a
798
     * second time, with a path to a user INI file for other properties to be
799
     * defined.
800
     *
801
     * @param mixed $_strConfigPath path to config file or VOID
802
     * @return boolean
803
     */
804
    public function setConfig($_strConfigPath = null)
805
    {
806
        /**
807
         * Returns constructed SELECT Object string or boolean upon failure
808
         * Default value is set at true
809
         */
810
        $_retVal = true;
811
812
        // if we have a path...
813
        if (!empty($_strConfigPath)) {
814
            // If the path is not valid, this will NOT generate an error,
815
            // it will simply return false.
816
            if (!@include $_strConfigPath) {
817
                $this->_setErr(110, '"' . $_strConfigPath . '" is not a valid path.');
818
                $_retVal = false;
819
            }
820
        } else {
821
            // Read the Systems php.ini file
822
            // Set these properties ONLY if they are set in the php.ini file.
823
            // Otherwise the default values will be used.
824
            if ($_host = ini_get('SMTPs')) {
825
                $this->setHost($_host);
826
            }
827
828
            if ($_port = ini_get('smtp_port')) {
829
                $this->setPort($_port);
830
            }
831
832
            if ($_from = ini_get('sendmail_from')) {
833
                $this->setFrom($_from);
834
            }
835
        }
836
837
        // Send back what we have
838
        return $_retVal;
839
    }
840
841
    /**
842
     * Determines the method inwhich the messages are to be sent.
843
     * - 'sockets' [0] - connect via network to SMTP server
844
     * - 'pipe     [1] - use UNIX path to EXE
845
     * - 'phpmail  [2] - use the PHP built-in mail function
846
     *
847
     * @param int $_type  Integer value representing Mail Transport Type
848
     * @return void
849
     */
850
    public function setTransportType($_type = 0)
851
    {
852
        if ((is_numeric($_type)) && (($_type >= 0) && ($_type <= 3))) {
853
            $this->_transportType = $_type;
854
        }
855
    }
856
857
    /**
858
     * Return the method inwhich the message is to be sent.
859
     * - 'sockets' [0] - connect via network to SMTP server
860
     * - 'pipe     [1] - use UNIX path to EXE
861
     * - 'phpmail  [2] - use the PHP built-in mail function
862
     *
863
     * @return int $_strHost Host Name or IP of the Mail Server to use
864
     */
865
    public function getTransportType()
866
    {
867
        return $this->_transportType;
868
    }
869
870
    /**
871
     * Path to the sendmail executable
872
     *
873
     * @param string $_path Path to the sendmail executable
874
     * @return boolean
875
     *
876
     */
877
    public function setMailPath($_path)
878
    {
879
        // This feature is not yet implemented
880
        return true;
881
882
        //if ( $_path ) $this->_mailPath = $_path;
883
    }
884
885
    /**
886
     * Defines the Host Name or IP of the Mail Server to use.
887
     * This is defaulted to 'localhost'
888
     * This is  used only with 'socket' based mail transmission
889
     *
890
     * @param   string  $_strHost       Host Name or IP of the Mail Server to use
891
     * @return  void
892
     */
893
    public function setHost($_strHost)
894
    {
895
        if ($_strHost) {
896
            $this->_smtpsHost = $_strHost;
897
        }
898
    }
899
900
    /**
901
     * Retrieves the Host Name or IP of the Mail Server to use
902
     * This is  used only with 'socket' based mail transmission
903
     *
904
     * @return  string  $_strHost       Host Name or IP of the Mail Server to use
905
     */
906
    public function getHost()
907
    {
908
        return $this->_smtpsHost;
909
    }
910
911
    /**
912
     * Defines the Port Number of the Mail Server to use
913
     * The default is 25
914
     * This is  used only with 'socket' based mail transmission
915
     *
916
     * @param   int|string  $_intPort       Port Number of the Mail Server to use
917
     * @return  void
918
     */
919
    public function setPort($_intPort)
920
    {
921
        if (
922
            (is_numeric($_intPort)) &&
923
            (($_intPort >= 1) && ($_intPort <= 65536))
924
        ) {
925
            $this->_smtpsPort = (int) $_intPort;
926
        }
927
    }
928
929
    /**
930
     * Retrieves the Port Number of the Mail Server to use
931
     * This is  used only with 'socket' based mail transmission
932
     *
933
     * @return  int         Port Number of the Mail Server to use
934
     */
935
    public function getPort()
936
    {
937
        return (int) $this->_smtpsPort;
938
    }
939
940
    /**
941
     * User Name for authentication on Mail Server
942
     *
943
     * @param   string  $_strID     User Name for authentication on Mail Server
944
     * @return  void
945
     */
946
    public function setID($_strID)
947
    {
948
        $this->_smtpsID = $_strID;
949
    }
950
951
    /**
952
     * Retrieves the User Name for authentication on Mail Server
953
     *
954
     * @return string   User Name for authentication on Mail Server
955
     */
956
    public function getID()
957
    {
958
        return $this->_smtpsID;
959
    }
960
961
    /**
962
     * User Password for authentication on Mail Server
963
     *
964
     * @param   string  $_strPW     User Password for authentication on Mail Server
965
     * @return  void
966
     */
967
    public function setPW($_strPW)
968
    {
969
        $this->_smtpsPW = $_strPW;
970
    }
971
972
    /**
973
     * Retrieves the User Password for authentication on Mail Server
974
     *
975
     * @return  string      User Password for authentication on Mail Server
976
     */
977
    public function getPW()
978
    {
979
        return $this->_smtpsPW;
980
    }
981
982
    /**
983
     * User token for OAUTH2
984
     *
985
     * @param   string  $_strToken  User token
986
     * @return  void
987
     */
988
    public function setToken($_strToken)
989
    {
990
        $this->_smtpsToken = $_strToken;
991
    }
992
993
    /**
994
     * Retrieves the User token for OAUTH2
995
     *
996
     * @return  string      User token for OAUTH2
997
     */
998
    public function getToken()
999
    {
1000
        return $this->_smtpsToken;
1001
    }
1002
1003
    /**
1004
     * Character set used for current message
1005
     * Character set is defaulted to 'iso-8859-1';
1006
     *
1007
     * @param string $_strCharSet Character set used for current message
1008
     * @return void
1009
     */
1010
    public function setCharSet($_strCharSet)
1011
    {
1012
        if ($_strCharSet) {
1013
            $this->_smtpsCharSet = $_strCharSet;
1014
        }
1015
    }
1016
1017
    /**
1018
     * Retrieves the Character set used for current message
1019
     *
1020
     * @return string $_smtpsCharSet Character set used for current message
1021
     */
1022
    public function getCharSet()
1023
    {
1024
        return $this->_smtpsCharSet;
1025
    }
1026
1027
    /**
1028
     * Content-Transfer-Encoding, Defaulted to '7bit'
1029
     * This can be changed for 2byte characters sets
1030
     * Known Encode Types
1031
     *  - 7bit               Simple 7-bit ASCII
1032
     *  - 8bit               8-bit coding with line termination characters
1033
     *  - base64             3 octets encoded into 4 sextets with offset
1034
     *  - binary             Arbitrary binary stream
1035
     *  - mac-binhex40       Macintosh binary to hex encoding
1036
     *  - quoted-printable   Mostly 7-bit, with 8-bit characters encoded as "=HH"
1037
     *  - uuencode           UUENCODE encoding
1038
     *
1039
     * @param string $_strTransEncode Content-Transfer-Encoding
1040
     * @return void
1041
     */
1042
    public function setTransEncode($_strTransEncode)
1043
    {
1044
        if (array_search($_strTransEncode, $this->_smtpsTransEncodeTypes)) {
1045
            $this->_smtpsTransEncode = $_strTransEncode;
1046
        }
1047
    }
1048
1049
    /**
1050
     * Retrieves the Content-Transfer-Encoding
1051
     *
1052
     * @return string $_smtpsTransEncode Content-Transfer-Encoding
1053
     */
1054
    public function getTransEncode()
1055
    {
1056
        return $this->_smtpsTransEncode;
1057
    }
1058
1059
    /**
1060
     * Content-Transfer-Encoding, Defaulted to '0' [ZERO]
1061
     * This can be changed for 2byte characters sets
1062
     * Known Encode Types
1063
     *  - [0] 7bit               Simple 7-bit ASCII
1064
     *  - [1] 8bit               8-bit coding with line termination characters
1065
     *  - [2] base64             3 octets encoded into 4 sextets with offset
1066
     *  - [3] binary             Arbitrary binary stream
1067
     *  - [4] mac-binhex40       Macintosh binary to hex encoding
1068
     *  - [5] quoted-printable   Mostly 7-bit, with 8-bit characters encoded as "=HH"
1069
     *  - [6] uuencode           UUENCODE encoding
1070
     *
1071
     * @param string $_strTransEncodeType Content-Transfer-Encoding
1072
     * @return void
1073
     *
1074
     */
1075
    public function setTransEncodeType($_strTransEncodeType)
1076
    {
1077
        if (array_search($_strTransEncodeType, $this->_smtpsTransEncodeTypes)) {
1078
            $this->_smtpsTransEncodeType = $_strTransEncodeType;
1079
        }
1080
    }
1081
1082
    /**
1083
     * Retrieves the Content-Transfer-Encoding
1084
     *
1085
     * @return  string      Content-Transfer-Encoding
1086
     */
1087
    public function getTransEncodeType()
1088
    {
1089
        return $this->_smtpsTransEncodeTypes[$this->_smtpsTransEncodeType];
1090
    }
1091
1092
1093
    // ** Message Construction
1094
1095
    /**
1096
     * FROM Address from which mail will be sent
1097
     *
1098
     * @param   string  $_strFrom   Address from which mail will be sent
1099
     * @return  void
1100
     */
1101
    public function setFrom($_strFrom)
1102
    {
1103
        if ($_strFrom) {
1104
            $this->_msgFrom = $this->_strip_email($_strFrom);
1105
        }
1106
    }
1107
1108
    /**
1109
     * Retrieves the Address from which mail will be sent
1110
     *
1111
     * @param   boolean $_part      To "strip" 'Real name' from address
1112
     * @return  string              Address from which mail will be sent
1113
     */
1114
    public function getFrom($_part = true)
1115
    {
1116
        $_retValue = '';
1117
1118
        if ($_part === true) {
1119
            $_retValue = $this->_msgFrom;
1120
        } else {
1121
            $_retValue = $this->_msgFrom[$_part];
1122
        }
1123
1124
        return $_retValue;
1125
    }
1126
1127
    /**
1128
     * Reply-To Address from which mail will be the reply-to
1129
     *
1130
     * @param   string  $_strReplyTo    Address from which mail will be the reply-to
1131
     * @return  void
1132
     */
1133
    public function setReplyTo($_strReplyTo)
1134
    {
1135
        if ($_strReplyTo) {
1136
            $this->_msgReplyTo = $this->_strip_email($_strReplyTo);
1137
        }
1138
    }
1139
1140
    /**
1141
     * Retrieves the Address from which mail will be the reply-to
1142
     *
1143
     * @param   boolean $_part      To "strip" 'Real name' from address
1144
     * @return  string              Address from which mail will be the reply-to
1145
     */
1146
    public function getReplyTo($_part = true)
1147
    {
1148
        $_retValue = '';
1149
1150
        if ($_part === true) {
1151
            $_retValue = $this->_msgReplyTo;
1152
        } else {
1153
            $_retValue = $this->_msgReplyTo[$_part];
1154
        }
1155
1156
        return $_retValue;
1157
    }
1158
1159
    /**
1160
     * Set References in the list of Msg-Id
1161
     *
1162
     * @param   string  $_strInReplyTo  List of Msg-Id
1163
     * @return  void
1164
     */
1165
    public function setInReplyTo($_strInReplyTo)
1166
    {
1167
        if ($_strInReplyTo) {
1168
            $this->_msgInReplyTo = $_strInReplyTo;
1169
        }
1170
    }
1171
1172
    /**
1173
     * Retrieves the InReplyTo from which mail we reply to
1174
     *
1175
     * @return  string              Msg-Id of email we reply to
1176
     */
1177
    public function getInReplyTo()
1178
    {
1179
        $_retValue = $this->_msgInReplyTo;
1180
1181
        return $_retValue;
1182
    }
1183
1184
    /**
1185
     * Set References in the list of Msg-Id
1186
     *
1187
     * @param   string  $_strReferences     List of Msg-Id
1188
     * @return  void
1189
     */
1190
    public function setReferences($_strReferences)
1191
    {
1192
        if ($_strReferences) {
1193
            $this->_msgReferences = $_strReferences;
1194
        }
1195
    }
1196
1197
    /**
1198
     * Retrieves the References from which mail will be the reply-to
1199
     *
1200
     * @return  string              List of Msg-Id
1201
     */
1202
    public function getReferences()
1203
    {
1204
        $_retValue = $this->_msgReferences;
1205
1206
        return $_retValue;
1207
    }
1208
1209
    /**
1210
     * Inserts given addresses into structured format.
1211
     * This method takes a list of given addresses, via an array or a COMMA delimited string, and inserts them into a highly
1212
     * structured array. This array is designed to remove duplicate addresses and to sort them by Domain.
1213
     *
1214
     * @param   string  $_type          TO, CC, or BCC lists to add addrresses into
1215
     * @param   mixed   $_addrList      Array or COMMA delimited string of addresses
1216
     * @return void
1217
     *
1218
     */
1219
    private function _buildAddrList($_type, $_addrList)
1220
    {
1221
        // Pull existing list
1222
        $aryHost = $this->_msgRecipients;
1223
1224
        // Only run this if we have something
1225
        if (!empty($_addrList)) {
1226
            // $_addrList can be a STRING or an array
1227
            if (is_string($_addrList)) {
1228
                // This could be a COMMA delimited string
1229
                if (strstr($_addrList, ',')) {
1230
                    // "explode "list" into an array
1231
                    $_addrList = explode(',', $_addrList);
1232
                } else {
1233
                    // Stick it in an array
1234
                    $_addrList = array($_addrList);
1235
                }
1236
            }
1237
1238
            // take the array of addresses and split them further
1239
            foreach ($_addrList as $_strAddr) {
1240
                // Strip off the end '>'
1241
                $_strAddr = str_replace('>', '', $_strAddr);
1242
1243
                // Separate "Real Name" from eMail address
1244
                $_tmpaddr = null;
1245
                $_tmpaddr = explode('<', $_strAddr);
1246
1247
                // We have a "Real Name" and eMail address
1248
                if (count($_tmpaddr) == 2) {
1249
                    $_tmpHost = explode('@', $_tmpaddr[1]);
1250
                    $_tmpaddr[0] = trim($_tmpaddr[0], ' ">');
1251
                    $aryHost[$_tmpHost[1]][$_type][$_tmpHost[0]] = $_tmpaddr[0];
1252
                } else {
1253
                    // We only have an eMail address
1254
                    // Strip off the beginning '<'
1255
                    $_strAddr = str_replace('<', '', $_strAddr);
1256
1257
                    $_tmpHost = explode('@', $_strAddr);
1258
                    $_tmpHost[0] = trim($_tmpHost[0]);
1259
                    $_tmpHost[1] = trim($_tmpHost[1]);
1260
1261
                    $aryHost[$_tmpHost[1]][$_type][$_tmpHost[0]] = '';
1262
                }
1263
            }
1264
        }
1265
        // replace list
1266
        $this->_msgRecipients = $aryHost;
1267
    }
1268
1269
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1270
    /**
1271
     * Returns an array of the various parts of an email address
1272
     * This assumes a well formed address:
1273
     * - "Real name" <[email protected]>
1274
     * - "Real Name" is optional
1275
     * - if "Real Name" does not exist, the angle brackets are optional
1276
     * This will split an email address into 4 or 5 parts.
1277
     * - $_aryEmail[org]  = original string
1278
     * - $_aryEmail[real] = "real name" - if there is one
1279
     * - $_aryEmail[addr] = address part "[email protected]"
1280
     * - $_aryEmail[host] = "domain.tld"
1281
     * - $_aryEmail[user] = "userName"
1282
     *
1283
     *  @param      string      $_strAddr       Email address
1284
     *  @return     array{org:string,real?:string,addr:string,user:string,host:string}  An array of the various parts of an email address
1285
     */
1286
    private function _strip_email($_strAddr)
1287
    {
1288
		// phpcs:enable
1289
        $_aryEmail = array();
1290
        // Keep the original
1291
        $_aryEmail['org'] = $_strAddr;
1292
1293
        // Set entire string to Lower Case
1294
        $_strAddr = strtolower($_strAddr);
1295
1296
        // Drop "stuff' off the end
1297
        $_strAddr = trim($_strAddr, ' ">');
1298
1299
        // Separate "Real Name" from eMail address, if we have one
1300
        $_tmpAry = explode('<', $_strAddr);
1301
1302
        // Do we have a "Real name"
1303
        if (count($_tmpAry) == 2) {
1304
            // We may not really have a "Real Name"
1305
            if ($_tmpAry[0]) {
1306
                $_aryEmail['real'] = trim($_tmpAry[0], ' ">');
1307
            }
1308
1309
            $_aryEmail['addr'] = $_tmpAry[1];
1310
        } else {
1311
            $_aryEmail['addr'] = $_tmpAry[0];
1312
        }
1313
1314
        // Pull User Name and Host.tld apart
1315
        $_tmpHost = explode('@', $_aryEmail['addr']);
1316
        $_aryEmail['user'] = $_tmpHost[0];
1317
        $_aryEmail['host'] = $_tmpHost[1];
1318
1319
        // Put the brackets back around the address
1320
        $_aryEmail['addr'] = '<' . $_aryEmail['addr'] . '>';
1321
1322
        return $_aryEmail;
1323
    }
1324
1325
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1326
    /**
1327
     * Returns an array of bares addresses for use with 'RCPT TO:'
1328
     * This is a "build as you go" method. Each time this method is called
1329
     * the underlying array is destroyed and reconstructed.
1330
     *
1331
     * @return      array       Returns an array of bares addresses
1332
     */
1333
    public function get_RCPT_list()
1334
    {
1335
		// phpcs:enable
1336
        /**
1337
         * An array of bares addresses for use with 'RCPT TO:'
1338
         */
1339
        $_RCPT_list = array();
1340
1341
        // walk down Recipients array and pull just email addresses
1342
        foreach ($this->_msgRecipients as $_host => $_list) {
1343
            foreach ($_list as $_subList) {
1344
                foreach ($_subList as $_name => $_addr) {
1345
                    // build RCPT list
1346
                    $_RCPT_list[] = $_name . '@' . $_host;
1347
                }
1348
            }
1349
        }
1350
1351
        return $_RCPT_list;
1352
    }
1353
1354
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1355
    /**
1356
     * Returns an array of addresses for a specific type; TO, CC or BCC
1357
     *
1358
     * @param       string         $_which      Which collection of addresses to return ('to', 'cc', 'bcc')
1359
     * @return      string|false                Array of emaill address
1360
     */
1361
    public function get_email_list($_which = null)
1362
    {
1363
		// phpcs:enable
1364
        // We need to know which address segment to pull
1365
        if ($_which) {
1366
            // Make sure we have addresses to process
1367
            if ($this->_msgRecipients) {
1368
                $_RCPT_list = array();
1369
                // walk down Recipients array and pull just email addresses
1370
                foreach ($this->_msgRecipients as $_host => $_list) {
1371
                    if (!empty($this->_msgRecipients[$_host][$_which])) {
1372
                        foreach ($this->_msgRecipients[$_host][$_which] as $_addr => $_realName) {
1373
                            if ($_realName) {   // @CHANGE LDR
1374
                                $_realName = '"' . $_realName . '"';
1375
                                $_RCPT_list[] = $_realName . ' <' . $_addr . '@' . $_host . '>';
1376
                            } else {
1377
                                $_RCPT_list[] = $_addr . '@' . $_host;
1378
                            }
1379
                        }
1380
                    }
1381
                }
1382
1383
                return implode(', ', $_RCPT_list);
1384
            } else {
1385
                $this->_setErr(101, 'No eMail Address for message to be sent to.');
1386
                return false;
1387
            }
1388
        } else {
1389
            $this->_setErr(102, 'eMail type not defined.');
1390
            return false;
1391
        }
1392
    }
1393
1394
    /**
1395
     * TO Address[es] inwhich to send mail to
1396
     *
1397
     * @param   string  $_addrTo    TO Address[es] inwhich to send mail to
1398
     * @return  void
1399
     */
1400
    public function setTO($_addrTo)
1401
    {
1402
        if ($_addrTo) {
1403
            $this->_buildAddrList('to', $_addrTo);
1404
        }
1405
    }
1406
1407
    /**
1408
     * Retrieves the TO Address[es] inwhich to send mail to
1409
     *
1410
     * @return  string  TO Address[es] inwhich to send mail to
1411
     */
1412
    public function getTo()
1413
    {
1414
        return $this->get_email_list('to');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get_email_list('to') could also return false which is incompatible with the documented return type string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
1415
    }
1416
1417
    /**
1418
     * CC Address[es] inwhich to send mail to
1419
     *
1420
     * @param   string  $_strCC     CC Address[es] inwhich to send mail to
1421
     * @return  void
1422
     */
1423
    public function setCC($_strCC)
1424
    {
1425
        if ($_strCC) {
1426
            $this->_buildAddrList('cc', $_strCC);
1427
        }
1428
    }
1429
1430
    /**
1431
     * Retrieves the CC Address[es] inwhich to send mail to
1432
     *
1433
     * @return  string      CC Address[es] inwhich to send mail to
1434
     */
1435
    public function getCC()
1436
    {
1437
        return $this->get_email_list('cc');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get_email_list('cc') could also return false which is incompatible with the documented return type string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
1438
    }
1439
1440
    /**
1441
     * BCC Address[es] inwhich to send mail to
1442
     *
1443
     * @param   string      $_strBCC    Recipients BCC Address[es] inwhich to send mail to
1444
     * @return  void
1445
     */
1446
    public function setBCC($_strBCC)
1447
    {
1448
        if ($_strBCC) {
1449
            $this->_buildAddrList('bcc', $_strBCC);
1450
        }
1451
    }
1452
1453
    /**
1454
     * Retrieves the BCC Address[es] inwhich to send mail to
1455
     *
1456
     * @return  string      BCC Address[es] inwhich to send mail to
1457
     */
1458
    public function getBCC()
1459
    {
1460
        return $this->get_email_list('bcc');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get_email_list('bcc') could also return false which is incompatible with the documented return type string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
1461
    }
1462
1463
    /**
1464
     * Message Subject
1465
     *
1466
     * @param   string  $_strSubject    Message Subject
1467
     * @return  void
1468
     */
1469
    public function setSubject($_strSubject = '')
1470
    {
1471
        if ($_strSubject) {
1472
            $this->_msgSubject = $_strSubject;
1473
        }
1474
    }
1475
1476
    /**
1477
     * Retrieves the Message Subject
1478
     *
1479
     * @return  string      Message Subject
1480
     */
1481
    public function getSubject()
1482
    {
1483
        return $this->_msgSubject;
1484
    }
1485
1486
    /**
1487
     * Constructs and returns message header
1488
     *
1489
     * @return string Complete message header
1490
     */
1491
    public function getHeader()
1492
    {
1493
        global $conf;
1494
1495
        $_header = 'From: ' . $this->getFrom('org') . "\r\n"
1496
        . 'To: ' . $this->getTo() . "\r\n";
1497
1498
        if ($this->getCC()) {
1499
            $_header .= 'Cc: ' . $this->getCC() . "\r\n";
1500
        }
1501
1502
        /* Note:
1503
         * BCC email addresses must be listed in the RCPT TO command list,
1504
         * but the BCC header should not be printed under the DATA command.
1505
         * So it is included into the function sendMsg() but not here.
1506
         * http://stackoverflow.com/questions/2750211/sending-bcc-emails-using-a-smtp-server
1507
         */
1508
        /*
1509
        if ( $this->getBCC() )
1510
        $_header .= 'Bcc: ' . $this->getBCC()  . "\r\n";
1511
        */
1512
1513
        $host = dol_getprefix('email');
1514
1515
        //NOTE: Message-ID should probably contain the username of the user who sent the msg
1516
        $_header .= 'Subject: ' . $this->getSubject() . "\r\n";
1517
        $_header .= 'Date: ' . date("r") . "\r\n";
1518
1519
        $trackid = $this->getTrackId();
1520
        if ($trackid) {
1521
            $_header .= 'Message-ID: <' . time() . '.SMTPs-dolibarr-' . $trackid . '@' . $host . ">\r\n";
1522
            $_header .= 'X-Dolibarr-TRACKID: ' . $trackid . '@' . $host . "\r\n";
1523
        } else {
1524
            $_header .= 'Message-ID: <' . time() . '.SMTPs@' . $host . ">\r\n";
1525
        }
1526
        if (!empty($_SERVER['REMOTE_ADDR'])) {
1527
            $_header .= "X-RemoteAddr: " . $_SERVER['REMOTE_ADDR'] . "\r\n";
1528
        }
1529
        if ($this->getMoreInHeader()) {
1530
            $_header .= $this->getMoreInHeader(); // Value must include the "\r\n";
1531
        }
1532
1533
        if ($this->getSensitivity()) {
1534
            $_header .= 'Sensitivity: ' . $this->getSensitivity() . "\r\n";
1535
        }
1536
1537
        if ($this->_msgPriority != 3) {
1538
            $_header .= $this->getPriority();
1539
        }
1540
1541
1542
        // @CHANGE LDR
1543
        if ($this->getDeliveryReceipt()) {
1544
            $_header .= 'Disposition-Notification-To: ' . $this->getFrom('addr') . "\r\n";
1545
        }
1546
        if ($this->getErrorsTo()) {
1547
            $_header .= 'Errors-To: ' . $this->getErrorsTo('addr') . "\r\n";
1548
        }
1549
        if ($this->getReplyTo()) {
1550
            $_header .= "Reply-To: " . $this->getReplyTo('addr') . "\r\n";
1551
        }
1552
1553
        $_header .= 'X-Mailer: Dolibarr version ' . DOL_VERSION . ' (using SMTPs Mailer)' . "\r\n";
1554
        $_header .= 'X-Dolibarr-Option: ' . ($conf->global->MAIN_MAIL_USE_MULTI_PART ? 'MAIN_MAIL_USE_MULTI_PART' : 'No MAIN_MAIL_USE_MULTI_PART') . "\r\n";
1555
        $_header .= 'Mime-Version: 1.0' . "\r\n";
1556
1557
        // Add also $this->references and In-Reply-To
1558
        if ($this->getInReplyTo()) {
1559
            $_header .= "In-Reply-To: " . $this->getInReplyTo() . "\r\n";
1560
        }
1561
        if ($this->getReferences()) {
1562
            $_header .= "References: " . $this->getReferences() . "\r\n";
1563
        }
1564
1565
        return $_header;
1566
    }
1567
1568
    /**
1569
     * Message Content
1570
     *
1571
     * @param   string  $strContent     Message Content
1572
     * @param   string  $strType        Type
1573
     * @return  void
1574
     */
1575
    public function setBodyContent($strContent, $strType = 'plain')
1576
    {
1577
        //if ( $strContent )
1578
        //{
1579
        if ($strType == 'html') {
1580
            $strMimeType = 'text/html';
1581
        } else {
1582
            $strMimeType = 'text/plain';
1583
        }
1584
1585
        // Make RFC821 Compliant, replace bare linefeeds
1586
        $strContent = preg_replace("/(?<!\r)\n/si", "\r\n", $strContent);
1587
1588
        $strContentAltText = '';
1589
        if ($strType == 'html') {
1590
            // Similar code to forge a text from html is also in CMailFile.class.php
1591
            $strContentAltText = preg_replace('/<head><title>.*<\/style><\/head>/', '', $strContent);
1592
            $strContentAltText = preg_replace("/<br\s*[^>]*>/", " ", $strContentAltText);
1593
            $strContentAltText = html_entity_decode(strip_tags($strContentAltText));
1594
            $strContentAltText = trim(wordwrap($strContentAltText, 75, "\r\n"));
1595
        }
1596
1597
        // Make RFC2045 Compliant
1598
        //$strContent = rtrim(chunk_split($strContent));    // Function chunck_split seems ko if not used on a base64 content
1599
        $strContent = rtrim(wordwrap($strContent, 75, "\r\n")); // TODO Using this method creates unexpected line break on text/plain content.
1600
1601
        $this->_msgContent[$strType] = array();
1602
1603
        $this->_msgContent[$strType]['mimeType'] = $strMimeType;
1604
        $this->_msgContent[$strType]['data']     = $strContent;
1605
        $this->_msgContent[$strType]['dataText'] = $strContentAltText;
1606
1607
        if ($this->getMD5flag()) {
1608
            $this->_msgContent[$strType]['md5'] = dol_hash($strContent, 3);
1609
        }
1610
        //}
1611
    }
1612
1613
    /**
1614
     * Retrieves the Message Content
1615
     *
1616
     * @return  string          Message Content
1617
     */
1618
    public function getBodyContent()
1619
    {
1620
        global $conf;
1621
1622
        // Generate a new Boundary string
1623
        $this->_setBoundary();
1624
1625
        // What type[s] of content do we have
1626
        $_types = array_keys($this->_msgContent);
1627
1628
        // How many content types do we have
1629
        $keyCount = count($_types);
1630
1631
        // If we have ZERO, we have a problem
1632
        if ($keyCount === 0) {
1633
            die("Sorry, no content");
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1634
        } elseif ($keyCount === 1 && !getDolGlobalString('MAIN_MAIL_USE_MULTI_PART')) {
1635
            // If we have ONE, we can use the simple format
1636
            $_msgData = $this->_msgContent;
1637
            $_msgData = $_msgData[$_types[0]];
1638
1639
            $content = 'Content-Type: ' . $_msgData['mimeType'] . '; charset="' . $this->getCharSet() . '"' . "\r\n"
1640
            . 'Content-Transfer-Encoding: ' . $this->getTransEncodeType() . "\r\n"
1641
            . 'Content-Disposition: inline' . "\r\n"
1642
            . 'Content-Description: Message' . "\r\n";
1643
1644
            if ($this->getMD5flag()) {
1645
                $content .= 'Content-MD5: ' . $_msgData['md5'] . "\r\n";
1646
            }
1647
1648
            $content .= "\r\n"
1649
            . $_msgData['data'] . "\r\n";
1650
        } elseif ($keyCount >= 1 || getDolGlobalString('MAIN_MAIL_USE_MULTI_PART')) {
1651
            // If we have more than ONE, we use the multi-part format
1652
            // Since this is an actual multi-part message
1653
            // We need to define a content message Boundary
1654
            // NOTE: This was 'multipart/alternative', but Windows based mail servers have issues with this.
1655
1656
            //$content = 'Content-Type: multipart/related; boundary="' . $this->_getBoundary() . '"'   . "\r\n";
1657
            $content = 'Content-Type: multipart/mixed; boundary="' . $this->_getBoundary('mixed') . '"' . "\r\n";
1658
1659
            //                     . "\r\n"
1660
            //                     . 'This is a multi-part message in MIME format.' . "\r\n";
1661
            $content .= "Content-Transfer-Encoding: 8bit\r\n";
1662
            $content .= "\r\n";
1663
1664
            $content .= "--" . $this->_getBoundary('mixed') . "\r\n";
1665
1666
            if (array_key_exists('image', $this->_msgContent)) {     // If inline image found
1667
                $content .= 'Content-Type: multipart/alternative; boundary="' . $this->_getBoundary('alternative') . '"' . "\r\n";
1668
                $content .= "\r\n";
1669
                $content .= "--" . $this->_getBoundary('alternative') . "\r\n";
1670
            }
1671
1672
1673
            // $this->_msgContent must be sorted with key 'text' or 'html' first then 'image' then 'attachment'
1674
1675
1676
            // Loop through message content array
1677
            foreach ($this->_msgContent as $type => $_content) {
1678
                if ($type == 'attachment') {
1679
                    // loop through all attachments
1680
                    foreach ($_content as $_file => $_data) {
1681
                        $content .= "--" . $this->_getBoundary('mixed') . "\r\n"
1682
                        . 'Content-Disposition: attachment; filename="' . $_data['fileName'] . '"' . "\r\n"
1683
                        . 'Content-Type: ' . $_data['mimeType'] . '; name="' . $_data['fileName'] . '"' . "\r\n"
1684
                        . 'Content-Transfer-Encoding: base64' . "\r\n"
1685
                        . 'Content-Description: ' . $_data['fileName'] . "\r\n";
1686
                        if (!empty($_data['cid'])) {
1687
                            $content .= "X-Attachment-Id: " . $_data['cid'] . "\r\n";
1688
                            $content .= "Content-ID: <" . $_data['cid'] . ">\r\n";
1689
                        }
1690
                        if ($this->getMD5flag()) {
1691
                            $content .= 'Content-MD5: ' . $_data['md5'] . "\r\n";
1692
                        }
1693
1694
                        $content .= "\r\n" . $_data['data'] . "\r\n\r\n";
1695
                    }
1696
                } elseif ($type == 'image') {
1697
                    // @CHANGE LDR
1698
                    // loop through all images
1699
                    foreach ($_content as $_image => $_data) {
1700
                        $content .= "--" . $this->_getBoundary('related') . "\r\n"; // always related for an inline image
1701
1702
                        $content .= 'Content-Type: ' . $_data['mimeType'] . '; name="' . $_data['imageName'] . '"' . "\r\n"
1703
                        . 'Content-Transfer-Encoding: base64' . "\r\n"
1704
                        . 'Content-Disposition: inline; filename="' . $_data['imageName'] . '"' . "\r\n"
1705
                        . 'Content-ID: <' . $_data['cid'] . '> ' . "\r\n";
1706
1707
                        if ($this->getMD5flag()) {
1708
                            $content .= 'Content-MD5: ' . $_data['md5'] . "\r\n";
1709
                        }
1710
1711
                        $content .= "\r\n"
1712
                        . $_data['data'] . "\r\n";
1713
                    }
1714
1715
                    // always end related and end alternative after inline images
1716
                    $content .= "--" . $this->_getBoundary('related') . "--\r\n";
1717
                    $content .= "\r\n--" . $this->_getBoundary('alternative') . "--\r\n";
1718
                    $content .= "\r\n";
1719
                } else {
1720
                    if (array_key_exists('image', $this->_msgContent)) {
1721
                        $content .= "Content-Type: text/plain; charset=" . $this->getCharSet() . "\r\n";
1722
                        $content .= "\r\n" . ($_content['dataText'] ? $_content['dataText'] : strip_tags($_content['data'])) . "\r\n"; // Add plain text message
1723
                        $content .= "--" . $this->_getBoundary('alternative') . "\r\n";
1724
                        $content .= 'Content-Type: multipart/related; boundary="' . $this->_getBoundary('related') . '"' . "\r\n";
1725
                        $content .= "\r\n";
1726
                        $content .= "--" . $this->_getBoundary('related') . "\r\n";
1727
                    }
1728
1729
                    if (!array_key_exists('image', $this->_msgContent) && $_content['dataText'] && getDolGlobalString('MAIN_MAIL_USE_MULTI_PART')) {
1730
                        // Add plain text message part before html part
1731
                        $content .= 'Content-Type: multipart/alternative; boundary="' . $this->_getBoundary('alternative') . '"' . "\r\n";
1732
                        $content .= "\r\n";
1733
                        $content .= "--" . $this->_getBoundary('alternative') . "\r\n";
1734
1735
                        $content .= "Content-Type: text/plain; charset=" . $this->getCharSet() . "\r\n";
1736
                        $content .= "\r\n" . $_content['dataText'] . "\r\n";
1737
                        $content .= "--" . $this->_getBoundary('alternative') . "\r\n";
1738
                    }
1739
1740
                    $content .= 'Content-Type: ' . $_content['mimeType'] . '; charset=' . $this->getCharSet();
1741
1742
                    $content .= "\r\n";
1743
1744
                    if ($this->getMD5flag()) {
1745
                        $content .= 'Content-MD5: ' . $_content['md5'] . "\r\n";
1746
                    }
1747
1748
                    $content .= "\r\n" . $_content['data'] . "\r\n";
1749
1750
                    if (!array_key_exists('image', $this->_msgContent) && $_content['dataText'] && getDolGlobalString('MAIN_MAIL_USE_MULTI_PART')) {
1751
                        // Add plain text message part after html part
1752
                        $content .= "--" . $this->_getBoundary('alternative') . "--\r\n";
1753
                    }
1754
1755
                    $content .= "\r\n";
1756
                }
1757
            }
1758
1759
            $content .= "--" . $this->_getBoundary('mixed') . '--' . "\r\n";
1760
        } else {
1761
            die("Sorry, no content");
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1762
        }
1763
1764
        return $content;
1765
    }
1766
1767
    /**
1768
     * File attachments are added to the content array as sub-arrays,
1769
     * allowing for multiple attachments for each outbound email
1770
     *
1771
     * @param string $strContent  File data to attach to message
1772
     * @param string $strFileName File Name to give to attachment
1773
     * @param string $strMimeType File Mime Type of attachment
1774
     * @param string $strCid      File Cid of attachment (if defined, to be shown inline)
1775
     * @return void
1776
     */
1777
    public function setAttachment($strContent, $strFileName = 'unknown', $strMimeType = 'unknown', $strCid = '')
1778
    {
1779
        if ($strContent) {
1780
            $strContent = rtrim(chunk_split(base64_encode($strContent), 76, "\r\n")); // 76 max is defined into http://tools.ietf.org/html/rfc2047
1781
1782
            $this->_msgContent['attachment'][$strFileName]['mimeType'] = $strMimeType;
1783
            $this->_msgContent['attachment'][$strFileName]['fileName'] = $strFileName;
1784
            $this->_msgContent['attachment'][$strFileName]['data']     = $strContent;
1785
            $this->_msgContent['attachment'][$strFileName]['cid']      = $strCid;       // If defined, it means this attachment must be shown inline
1786
1787
            if ($this->getMD5flag()) {
1788
                $this->_msgContent['attachment'][$strFileName]['md5'] = dol_hash($strContent, 3);
1789
            }
1790
        }
1791
    }
1792
1793
1794
    // @CHANGE LDR
1795
1796
    /**
1797
     * Image attachments are added to the content array as sub-arrays,
1798
     * allowing for multiple images for each outbound email
1799
     *
1800
     * @param   string $strContent      Image data to attach to message
1801
     * @param   string $strImageName    Image Name to give to attachment
1802
     * @param   string $strMimeType     Image Mime Type of attachment
1803
     * @param   string $strImageCid     CID
1804
     * @return  void
1805
     */
1806
    public function setImageInline($strContent, $strImageName = 'unknown', $strMimeType = 'unknown', $strImageCid = 'unknown')
1807
    {
1808
        if ($strContent) {
1809
            $this->_msgContent['image'][$strImageName]['mimeType'] = $strMimeType;
1810
            $this->_msgContent['image'][$strImageName]['imageName'] = $strImageName;
1811
            $this->_msgContent['image'][$strImageName]['cid']      = $strImageCid;
1812
            $this->_msgContent['image'][$strImageName]['data']     = $strContent;
1813
1814
            if ($this->getMD5flag()) {
1815
                $this->_msgContent['image'][$strImageName]['md5'] = dol_hash($strContent, 3);
1816
            }
1817
        }
1818
    }
1819
    // END @CHANGE LDR
1820
1821
1822
    /**
1823
     * Message Content Sensitivity
1824
     * Message Sensitivity values:
1825
     *   - [0] None - default
1826
     *   - [1] Personal
1827
     *   - [2] Private
1828
     *   - [3] Company Confidential
1829
     *
1830
     * @param   integer $_value     Message Sensitivity
1831
     * @return  void
1832
     */
1833
    public function setSensitivity($_value = 0)
1834
    {
1835
        if (
1836
            (is_numeric($_value)) &&
1837
            (($_value >= 0) && ($_value <= 3))
1838
        ) {
1839
            $this->_msgSensitivity = $_value;
1840
        }
1841
    }
1842
1843
    /**
1844
     * Returns Message Content Sensitivity string
1845
     * Message Sensitivity values:
1846
     *   - [0] None - default
1847
     *   - [1] Personal
1848
     *   - [2] Private
1849
     *   - [3] Company Confidential
1850
     *
1851
     * @return  string|boolean
1852
     */
1853
    public function getSensitivity()
1854
    {
1855
        return $this->_arySensitivity[$this->_msgSensitivity];
1856
    }
1857
1858
    /**
1859
     * Message Content Priority
1860
     * Message Priority values:
1861
     *  - [0] 'Bulk'
1862
     *  - [1] 'Highest'
1863
     *  - [2] 'High'
1864
     *  - [3] 'Normal' - default
1865
     *  - [4] 'Low'
1866
     *  - [5] 'Lowest'
1867
     *
1868
     * @param   integer     $_value     Message Priority
1869
     * @return  void
1870
     */
1871
    public function setPriority($_value = 3)
1872
    {
1873
        if (
1874
            (is_numeric($_value)) &&
1875
            (($_value >= 0) && ($_value <= 5))
1876
        ) {
1877
            $this->_msgPriority = $_value;
1878
        }
1879
    }
1880
1881
    /**
1882
     * Message Content Priority
1883
     * Message Priority values:
1884
     *  - [0] 'Bulk'
1885
     *  - [1] 'Highest'
1886
     *  - [2] 'High'
1887
     *  - [3] 'Normal' - default
1888
     *  - [4] 'Low'
1889
     *  - [5] 'Lowest'
1890
     *
1891
     * @return string
1892
     */
1893
    public function getPriority()
1894
    {
1895
        return 'Importance: ' . $this->_aryPriority[$this->_msgPriority] . "\r\n"
1896
        . 'Priority: ' . $this->_aryPriority[$this->_msgPriority] . "\r\n"
1897
        . 'X-Priority: ' . $this->_msgPriority . ' (' . $this->_aryPriority[$this->_msgPriority] . ')' . "\r\n";
1898
    }
1899
1900
    /**
1901
     * Set flag which determines whether to calculate message MD5 checksum.
1902
     *
1903
     * @param   boolean     $_flag      MD5flag
1904
     * @return  void
1905
     */
1906
    public function setMD5flag($_flag = false)
1907
    {
1908
        $this->_smtpMD5 = $_flag;
1909
    }
1910
1911
    /**
1912
     * Gets flag which determines whether to calculate message MD5 checksum.
1913
     *
1914
     * @return  boolean                 MD5flag
1915
     */
1916
    public function getMD5flag()
1917
    {
1918
        return $this->_smtpMD5;
1919
    }
1920
1921
    /**
1922
     * Message X-Header Content
1923
     * This is a simple "insert". Whatever is given will be placed
1924
     * "as is" into the Xheader array.
1925
     *
1926
     * @param string $strXdata Message X-Header Content
1927
     * @return void
1928
     */
1929
    public function setXheader($strXdata)
1930
    {
1931
        if ($strXdata) {
1932
            $this->_msgXheader[] = $strXdata;
1933
        }
1934
    }
1935
1936
    /**
1937
     * Retrieves the Message X-Header Content
1938
     *
1939
     * @return array    $_msgContent    Message X-Header Content
1940
     */
1941
    public function getXheader()
1942
    {
1943
        return $this->_msgXheader;
1944
    }
1945
1946
    /**
1947
     * Generates Random string for MIME message Boundary
1948
     *
1949
     * @return void
1950
     */
1951
    private function _setBoundary()
1952
    {
1953
        $this->_smtpsBoundary = "multipart_x." . time() . ".x_boundary";
1954
        $this->_smtpsRelatedBoundary = 'mul_' . dol_hash(uniqid("dolibarr2"), 3);
1955
        $this->_smtpsAlternativeBoundary = 'mul_' . dol_hash(uniqid("dolibarr3"), 3);
1956
    }
1957
1958
    /**
1959
     * Retrieves the MIME message Boundary
1960
     *
1961
     * @param  string $type             Type of boundary
1962
     * @return string $_smtpsBoundary   MIME message Boundary
1963
     */
1964
    private function _getBoundary($type = 'mixed')
1965
    {
1966
        if ($type == 'mixed') {
1967
            return $this->_smtpsBoundary;
1968
        } elseif ($type == 'related') {
1969
            return $this->_smtpsRelatedBoundary;
1970
        } elseif ($type == 'alternative') {
1971
            return $this->_smtpsAlternativeBoundary;
1972
        }
1973
        return '';
1974
    }
1975
1976
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1977
    /**
1978
     * This function has been modified as provided by SirSir to allow multiline responses when
1979
     * using SMTP Extensions
1980
     *
1981
     * @param   resource    $socket         Socket handler
1982
     * @param   string      $response       Expected response ('250', ...). Example of response we can get:
1983
     *                                      "421 4.7.0 Try again later, closing connection. (EHLO) nb21-20020a1709071c9500b0093d0d964affsm869534ejc.73 - gsmtp"
1984
     *                                      "550 5.7.1  https://support.google.com/a/answer/6140680#invalidcred j21sm814390wre.3"
1985
     * @return  boolean                     True or false
1986
     */
1987
    public function server_parse($socket, $response)
1988
    {
1989
		// phpcs:enable
1990
        /**
1991
         * Returns constructed SELECT Object string or boolean upon failure
1992
         * Default value is set at true
1993
         */
1994
        $_retVal = true;
1995
1996
        $server_response = '';
1997
1998
        // avoid infinite loop
1999
        $limit = 0;
2000
2001
        while (substr($server_response, 3, 1) != ' ' && $limit < 100) {
2002
            if (!($server_response = fgets($socket, 256))) {
2003
                $this->_setErr(121, "Couldn't get mail server response codes");
2004
                $_retVal = false;
2005
                break;
2006
            }
2007
            $this->log .= $server_response;
2008
            $limit++;
2009
        }
2010
2011
        $this->lastretval = substr($server_response, 0, 3);
2012
2013
        if (!(substr($server_response, 0, 3) == $response)) {
2014
            $this->_setErr(120, "Ran into problems sending Mail.\r\nResponse: " . $server_response);
2015
            $_retVal = false;
2016
        }
2017
2018
        return $_retVal;
2019
    }
2020
2021
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2022
    /**
2023
     * Send str
2024
     *
2025
     * @param   string      $_strSend       String to send
2026
     * @param   string      $_returnCode    Expected return code
2027
     * @param   string      $CRLF           CRLF
2028
     * @return  boolean|null                True or false
2029
     */
2030
    public function socket_send_str($_strSend, $_returnCode = null, $CRLF = "\r\n")
2031
    {
2032
		// phpcs:enable
2033
        if ($this->_debug) {
2034
            $this->log .= $_strSend; // @CHANGE LDR for log
2035
        }
2036
        fwrite($this->socket, $_strSend . $CRLF);
2037
        if ($this->_debug) {
2038
            $this->log .= ' (' . $_returnCode . ')' . $CRLF;
2039
        }
2040
2041
        if ($_returnCode) {
2042
            return $this->server_parse($this->socket, $_returnCode);
2043
        }
2044
2045
        return null;
2046
    }
2047
2048
    // =============================================================
2049
    // ** Error handling methods
2050
2051
    /**
2052
     * Defines errors codes and messages for Class
2053
     *
2054
     * @param  int    $_errNum  Error Code Number
2055
     * @param  string $_errMsg  Error Message
2056
     * @return void
2057
     */
2058
    private function _setErr($_errNum, $_errMsg)
2059
    {
2060
        $this->_smtpsErrors[] = array(
2061
            'num' => $_errNum,
2062
            'msg' => $_errMsg,
2063
        );
2064
    }
2065
2066
    /**
2067
     * Returns applicative errors codes and messages for Class (not the SMTP error code)
2068
     *
2069
     * @return string $_errMsg  Error Message
2070
     */
2071
    public function getErrors()
2072
    {
2073
        $_errMsg = array();
2074
2075
        if (is_array($this->_smtpsErrors)) {
2076
            foreach ($this->_smtpsErrors as $_err => $_info) {
2077
                $_errMsg[] = 'Error [' . $_info['num'] . ']: ' . $_info['msg'];
2078
            }
2079
        }
2080
2081
        return implode("\n", $_errMsg);
2082
    }
2083
}
2084
2085
2086
// =============================================================
2087
// ** CSV Version Control Info
2088
2089
/**
2090
 * Revision      2011/09/12 07:49:59  eldy
2091
 * Doxygen
2092
 *
2093
 * Revision      2011/09/06 06:53:53  hregis
2094
 * Fix: use dol_hash instead md5 php function
2095
 *
2096
 * Revision      2011/09/03 00:14:27  eldy
2097
 * Doxygen
2098
 *
2099
 * Revision      2011/08/28 14:24:23  eldy
2100
 * Doxygen
2101
 *
2102
 * Revision      2011/07/12 22:19:02  eldy
2103
 * Fix: Attachment fails if content was empty
2104
 *
2105
 * Revision      2011/06/20 23:17:50  hregis
2106
 * Fix: use best structure of mail
2107
 *
2108
 * Revision      2010/04/13 20:58:37  eldy
2109
 * Fix: Can provide ip address on smtps. Better error reporting.
2110
 *
2111
 * Revision      2010/04/13 20:30:25  eldy
2112
 * Fix: Can provide ip address on smtps. Better error reporting.
2113
 *
2114
 * Revision      2010/01/12 13:02:07  hregis
2115
 * Fix: missing attach-files
2116
 *
2117
 * Revision      2009/11/01 14:16:30  eldy
2118
 * Fix: Sending mail with SMTPS was not working.
2119
 *
2120
 * Revision      2009/10/20 13:14:47  hregis
2121
 * Fix: function "split" is deprecated since php 5.3.0
2122
 *
2123
 * Revision      2009/05/13 19:10:07  eldy
2124
 * New: Can use inline images.Everything seems to work with thunderbird and webmail gmail. New to be tested on other mail browsers.
2125
 *
2126
 * Revision      2009/05/13 14:49:30  eldy
2127
 * Fix: Make code so much simpler and solve a lot of problem with new version.
2128
 *
2129
 * Revision      2009/02/09 00:04:35  eldy
2130
 * Added support for SMTPS protocol
2131
 *
2132
 * Revision       2008/04/16 23:11:45  eldy
2133
 * New: Add action "Test server connectivity"
2134
 *
2135
 * Revision 1.18  2007/01/12 22:17:08  ongardie
2136
 * - Added full_http_site_root() to utils-misc.php
2137
 * - Made SMTPs' getError() easier to use
2138
 * - Improved activity modified emails
2139
 *
2140
 * Revision 1.17  2006/04/05 03:15:40  ongardie
2141
 * -Fixed method name typo that resulted in a fatal error.
2142
 *
2143
 * Revision 1.16  2006/03/08 04:05:25  jswalter
2144
 *  - '$_smtpsTransEncode' was removed and '$_smtpsTransEncodeType' is now used
2145
 *  - '$_smtpsTransEncodeType' is defaulted to ZERO
2146
 *  - corrected 'setCharSet()'  internal vars
2147
 *  - defined '$_mailPath'
2148
 *  - added '$_smtpMD5' as a class property
2149
 *  - added 'setMD5flag()' to set above property
2150
 *  - added 'getMD5flag()' to retrieve above property
2151
 *  - 'setAttachment()' will add an MD5 checksum to attachments if above property is set
2152
 *  - 'setBodyContent()' will add an MD5 checksum to message parts if above property is set
2153
 *  - 'getBodyContent()' will insert the MD5 checksum for messages and attachments if above property is set
2154
 *  - removed leading dashes from message boundary
2155
 *  - added property "Close message boundary" tomessage block
2156
 *  - corrected some comments in various places
2157
 *  - removed some incorrect comments in others
2158
 *
2159
 * Revision 1.15  2006/02/21 02:00:07  vanmer
2160
 * - patch to add support for sending to exim mail server
2161
 * - thanks to Diego Ongaro at ETSZONE ([email protected])
2162
 *
2163
 * Revision 1.14  2005/08/29 16:22:10  jswalter
2164
 *  - change 'multipart/alternative' to 'multipart/mixed', but Windows based mail servers have issues with this.
2165
 * Bug 594
2166
 *
2167
 * Revision 1.13  2005/08/21 01:57:30  vanmer
2168
 * - added initialization for array if no recipients exist
2169
 *
2170
 * Revision 1.12  2005/08/20 12:04:30  braverock
2171
 * - remove potentially binary characters from Message-ID
2172
 * - add getHost to get the hostname of the mailserver
2173
 * - add username to Message-ID header
2174
 *
2175
 * Revision 1.11  2005/08/20 11:49:48  braverock
2176
 * - fix typos in boundary
2177
 * - remove potentially illegal characters from boundary
2178
 *
2179
 * Revision 1.10  2005/08/19 20:39:32  jswalter
2180
 *  - added _server_connect()' as a separate method to handle server connectivity.
2181
 *  - added '_server_authenticate()' as a separate method to handle server authentication.
2182
 *  - 'sendMsg()' now uses the new methods to handle server communication.
2183
 *  - modified 'server_parse()' and 'socket_send_str()' to give error codes and messages.
2184
 *
2185
 * Revision 1.9  2005/08/19 15:40:18  jswalter
2186
 *  - IMPORTANT: 'setAttachement()' is now spelled correctly: 'setAttachment()'
2187
 *  - added additional comment to several methods
2188
 *  - added '$_smtpsTransEncodeTypes' array to limit encode types
2189
 *  - added parameters to 'sendMsg()' for future development around debugging and logging
2190
 *  - added error code within 'setConfig()' if the given path is not found
2191
 *  - 'setTransportType()' now has parameter validation
2192
 *     [this still is not implemented]
2193
 *  - 'setPort()' now does parameter validation
2194
 *  - 'setTransEncode()' now has parameter validation against '$_smtpsTransEncodeTypes'
2195
 *  - modified 'get_email_list()' to handle error handling
2196
 *  - 'setSensitivity()' now has parameter validation
2197
 *  - 'setPriority()' now has parameter validation
2198
 *
2199
 * Revision 1.8  2005/06/24 21:00:20  jswalter
2200
 *   - corrected comments
2201
 *   - corrected the default value for 'setPriority()'
2202
 *   - modified 'setAttachement()' to process multiple attachments correctly
2203
 *   - modified 'getBodyContent()' to handle multiple attachments
2204
 * Bug 310
2205
 *
2206
 * Revision 1.7  2005/05/19 21:12:34  braverock
2207
 * - replace chunk_split() with wordwrap() to fix funky wrapping of templates
2208
 *
2209
 * Revision 1.6  2005/04/25 04:55:06  jswalter
2210
 *  - cloned from Master Version
2211
 *
2212
 * Revision 1.10  2005/04/25 04:54:10  walter
2213
 *  - "fixed" 'getBodyContent()' to handle a "simple" text only message
2214
 *
2215
 * Revision 1.9  2005/04/25 03:52:01  walter
2216
 *  - replace closing curly bracket. Removed it in last revision!
2217
 *
2218
 * Revision 1.8  2005/04/25 02:29:49  walter
2219
 *  - added '$_transportType' and its getter/setter methods.
2220
 *    for future use. NOT yet implemented.
2221
 *  - in 'sendMsg()', added HOST validation check
2222
 *  - added error check for initial Socket Connection
2223
 *  - created new method 'socket_send_str()' to process socket
2224
 *    communication in a unified means. Socket calls within
2225
 *    'sendMsg()' have been modified to use this new method.
2226
 *  - expanded comments in 'setConfig()'
2227
 *  - added "error" check on PHP ini file properties. If these
2228
 *    properties not set within the INI file, the default values
2229
 *    will be used.
2230
 *  - modified 'get_RCPT_list()' to reset itself each time it is called
2231
 *  - modified 'setBodyContent()' to store data in a sub-array for better
2232
 *    parsing within the 'getBodyContent()' method
2233
 *  - modified 'getBodyContent()' to process contents array better.
2234
 *    Also modified to handle attachments.
2235
 *  - added 'setAttachement()' so files and other data can be attached
2236
 *    to messages
2237
 *  - added '_setErr()' and 'getErrors()' as an attempt to begin an error
2238
 *    handling process within this class
2239
 *
2240
 * Revision 1.7  2005/04/13 15:23:50  walter
2241
 *  - made 'CC' a conditional insert
2242
 *  - made 'BCC' a conditional insert
2243
 *  - fixed 'Message-ID'
2244
 *  - corrected 'getSensitivity()'
2245
 *  - modified '$_aryPriority[]' to proper values
2246
 *  - updated 'setConfig()' to handle external Ini or 'php.ini'
2247
 *
2248
 * Revision 1.6  2005/03/15 17:34:06  walter
2249
 *  - corrected Message Sensitivity property and method comments
2250
 *  - added array to Message Sensitivity
2251
 *  - added getSensitivity() method to use new Sensitivity array
2252
 *  - created seters and getter for Priority with new Prioity value array property
2253
 *  - changed config file include from 'include_once'
2254
 *  - modified getHeader() to ustilize new Message Sensitivity and Priority properties
2255
 *
2256
 * Revision 1.5  2005/03/14 22:25:27  walter
2257
 *  - added references
2258
 *  - added Message sensitivity as a property with Getter/Setter methods
2259
 *  - boundary is now a property with Getter/Setter methods
2260
 *  - added 'builtRCPTlist()'
2261
 *  - 'sendMsg()' now uses Object properties and methods to build message
2262
 *  - 'setConfig()' to load external file
2263
 *  - 'setForm()' will "strip" the email address out of "address" string
2264
 *  - modified 'getFrom()' to handle "striping" the email address
2265
 *  - '_buildArrayList()' creates a multi-dimensional array of addresses
2266
 *    by domain, TO, CC & BCC and then by User Name.
2267
 *  - '_strip_email()' pulls email address out of "full Address" string'
2268
 *  - 'get_RCPT_list()' pulls out "bare" emaill address form address array
2269
 *  - 'getHeader()' builds message Header from Object properties
2270
 *  - 'getBodyContent()' builds full message body, even multi-part
2271
 *
2272
 * Revision 1.4  2005/03/02 20:53:35  walter
2273
 *  - core Setters & Getters defined
2274
 *  - added additional Class Properties
2275
 *
2276
 * Revision 1.3  2005/03/02 18:51:51  walter
2277
 *  - added base 'Class Properties'
2278
 *
2279
 * Revision 1.2  2005/03/01 19:37:52  walter
2280
 *  - CVS logging tags
2281
 *  - more comments
2282
 *  - more "shell"
2283
 *  - some constants
2284
 *
2285
 * Revision 1.1  2005/03/01 19:22:49  walter
2286
 *  - initial commit
2287
 *  - basic shell with some comments
2288
 *
2289
 */
2290