Issues (2407)

engine/library/Mail.php (5 issues)

1
<?php
2
/* 	Divine CMS - Open source CMS for widespread use.
3
    Copyright (c) 2019 Mykola Burakov ([email protected])
4
5
    See SOURCE.txt for other and additional information.
6
7
    This file is part of Divine CMS.
8
9
    This program is free software: you can redistribute it and/or modify
10
    it under the terms of the GNU General Public License as published by
11
    the Free Software Foundation, either version 3 of the License, or
12
    (at your option) any later version.
13
14
    This program is distributed in the hope that it will be useful,
15
    but WITHOUT ANY WARRANTY; without even the implied warranty of
16
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
    GNU General Public License for more details.
18
19
    You should have received a copy of the GNU General Public License
20
    along with this program. If not, see <http://www.gnu.org/licenses/>. */
21
22
namespace Divine\Engine\Library;
23
24
class Mail
25
{
26
    protected $to;
27
    protected $from;
28
    protected $sender;
29
    protected $reply_to;
30
    protected $subject;
31
    protected $text;
32
    protected $html;
33
    protected $attachments = array();
34
    public $protocol = 'mail';
35
    public $smtp_hostname;
36
    public $smtp_username;
37
    public $smtp_password;
38
    public $smtp_port = 25;
39
    public $smtp_timeout = 5;
40
    public $verp = false;
41
    public $parameter = '';
42
43
    public function __construct($config = array())
0 ignored issues
show
Expected 2 blank lines before function; 1 found
Loading history...
44
    {
45
        foreach ($config as $key => $value) {
46
            $this->$key = $value;
47
        }
48
    }
49
50
    public function setTo($to)
51
    {
52
        $this->to = $to;
53
    }
54
55
    public function setFrom($from)
0 ignored issues
show
The parameter $from is not used and could be removed. ( Ignorable by Annotation )

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

55
    public function setFrom(/** @scrutinizer ignore-unused */ $from)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
56
    {
57
        // http://forum.opencart-russia.ru/threads/nastrojka-pochty-gmail-ocstore-3-0-2-0.8374/#post-61155
58
        // $this->from = $from;
59
        $this->from = '[email protected]';
60
    }
61
62
    public function setSender($sender)
63
    {
64
        $this->sender = $sender;
65
    }
66
67
    public function setReplyTo($reply_to)
68
    {
69
        $this->reply_to = $reply_to;
70
    }
71
72
    public function setSubject($subject)
73
    {
74
        $this->subject = $subject;
75
    }
76
77
    public function setText($text)
78
    {
79
        $this->text = $text;
80
    }
81
82
    public function setHtml($html)
83
    {
84
        $this->html = $html;
85
    }
86
87
    public function addAttachment($filename)
88
    {
89
        $this->attachments[] = $filename;
90
    }
91
92
    public function send()
93
    {
94
        if (!$this->to) {
95
            throw new \Exception('Error: E-Mail to required!');
96
        }
97
98
        if (!$this->from) {
99
            throw new \Exception('Error: E-Mail from required!');
100
        }
101
102
        if (!$this->sender) {
103
            throw new \Exception('Error: E-Mail sender required!');
104
        }
105
106
        if (!$this->subject) {
107
            throw new \Exception('Error: E-Mail subject required!');
108
        }
109
110
        if ((!$this->text) && (!$this->html)) {
111
            throw new \Exception('Error: E-Mail message required!');
112
        }
113
114
        if (is_array($this->to)) {
115
            $to = implode(',', $this->to);
116
        } else {
117
            $to = $this->to;
118
        }
119
120
        $boundary = '----=_NextPart_' . md5(time());
121
122
        $header = 'MIME-Version: 1.0' . PHP_EOL;
123
124
        if ($this->protocol != 'mail') {
125
            $header .= 'To: <' . $to . '>' . PHP_EOL;
126
            $header .= 'Subject: =?UTF-8?B?' . base64_encode($this->subject) . '?=' . PHP_EOL;
127
        }
128
129
        $header .= 'Date: ' . date('D, d M Y H:i:s O') . PHP_EOL;
130
        $header .= 'From: =?UTF-8?B?' . base64_encode($this->sender) . '?= <' . $this->from . '>' . PHP_EOL;
131
132
        if (!$this->reply_to) {
133
            $header .= 'Reply-To: =?UTF-8?B?' . base64_encode($this->sender) . '?= <' . $this->from . '>' . PHP_EOL;
134
        } else {
135
            $header .= 'Reply-To: =?UTF-8?B?' . base64_encode($this->reply_to) . '?= <' . $this->reply_to . '>' . PHP_EOL;
136
        }
137
138
        $header .= 'Return-Path: ' . $this->from . PHP_EOL;
139
        $header .= 'X-Mailer: PHP/' . phpversion() . PHP_EOL;
140
        $header .= 'Content-Type: multipart/mixed; boundary="' . $boundary . '"' . PHP_EOL . PHP_EOL;
141
142
        if (!$this->html) {
143
            $message  = '--' . $boundary . PHP_EOL;
144
            $message .= 'Content-Type: text/plain; charset="utf-8"' . PHP_EOL;
145
            $message .= 'Content-Transfer-Encoding: 8bit' . PHP_EOL . PHP_EOL;
146
            $message .= $this->text . PHP_EOL;
147
        } else {
148
            $message  = '--' . $boundary . PHP_EOL;
149
            $message .= 'Content-Type: multipart/alternative; boundary="' . $boundary . '_alt"' . PHP_EOL . PHP_EOL;
150
            $message .= '--' . $boundary . '_alt' . PHP_EOL;
151
            $message .= 'Content-Type: text/plain; charset="utf-8"' . PHP_EOL;
152
            $message .= 'Content-Transfer-Encoding: 8bit' . PHP_EOL . PHP_EOL;
153
154
            if ($this->text) {
155
                $message .= $this->text . PHP_EOL;
156
            } else {
157
                $message .= 'This is a HTML email and your email client software does not support HTML email!' . PHP_EOL;
158
            }
159
160
            $message .= '--' . $boundary . '_alt' . PHP_EOL;
161
            $message .= 'Content-Type: text/html; charset="utf-8"' . PHP_EOL;
162
            $message .= 'Content-Transfer-Encoding: 8bit' . PHP_EOL . PHP_EOL;
163
            $message .= $this->html . PHP_EOL;
164
            $message .= '--' . $boundary . '_alt--' . PHP_EOL;
165
        }
166
167
        foreach ($this->attachments as $attachment) {
168
            if (file_exists($attachment)) {
169
                $handle = fopen($attachment, 'r');
170
171
                $content = fread($handle, filesize($attachment));
0 ignored issues
show
It seems like $handle can also be of type false; however, parameter $handle of fread() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

171
                $content = fread(/** @scrutinizer ignore-type */ $handle, filesize($attachment));
Loading history...
172
173
                fclose($handle);
0 ignored issues
show
It seems like $handle can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

173
                fclose(/** @scrutinizer ignore-type */ $handle);
Loading history...
174
175
                $message .= '--' . $boundary . PHP_EOL;
176
                $message .= 'Content-Type: application/octet-stream; name="' . basename($attachment) . '"' . PHP_EOL;
177
                $message .= 'Content-Transfer-Encoding: base64' . PHP_EOL;
178
                $message .= 'Content-Disposition: attachment; filename="' . basename($attachment) . '"' . PHP_EOL;
179
                $message .= 'Content-ID: <' . urlencode(basename($attachment)) . '>' . PHP_EOL;
180
                $message .= 'X-Attachment-Id: ' . urlencode(basename($attachment)) . PHP_EOL . PHP_EOL;
181
                $message .= chunk_split(base64_encode($content));
182
            }
183
        }
184
185
        $message .= '--' . $boundary . '--' . PHP_EOL;
186
187
        if ($this->protocol == 'mail') {
188
            ini_set('sendmail_from', $this->from);
189
190
            if ($this->parameter) {
191
                mail($to, '=?UTF-8?B?' . base64_encode($this->subject) . '?=', $message, $header, $this->parameter);
192
            } else {
193
                mail($to, '=?UTF-8?B?' . base64_encode($this->subject) . '?=', $message, $header);
194
            }
195
        } elseif ($this->protocol == 'smtp') {
196
            if (substr($this->smtp_hostname, 0, 3) == 'tls') {
197
                $hostname = substr($this->smtp_hostname, 6);
198
            } else {
199
                $hostname = $this->smtp_hostname;
200
            }
201
202
            $handle = fsockopen($hostname, $this->smtp_port, $errno, $errstr, $this->smtp_timeout);
203
204
            if (!$handle) {
0 ignored issues
show
$handle is of type false|resource, thus it always evaluated to false.
Loading history...
205
                throw new \Exception('Error: ' . $errstr . ' (' . $errno . ')');
206
            } else {
207
                if (substr(PHP_OS, 0, 3) != 'WIN') {
208
                    socket_set_timeout($handle, $this->smtp_timeout, 0);
209
                }
210
211
212
                while ($line = fgets($handle, 515)) {
213
                    if (substr($line, 3, 1) == ' ') {
214
                        break;
215
                    }
216
                }
217
218
                fputs($handle, 'EHLO ' . getenv('SERVER_NAME') . "\r\n");
219
220
                $reply = '';
221
222
                while ($line = fgets($handle, 515)) {
223
                    $reply .= $line;
224
225
                    //some SMTP servers respond with 220 code before responding with 250. hence, we need to ignore 220 response string
226
                    if (substr($reply, 0, 3) == 220 && substr($line, 3, 1) == ' ') {
227
                        $reply = '';
228
                        continue;
229
                    } elseif (substr($line, 3, 1) == ' ') {
230
                        break;
231
                    }
232
                }
233
234
                if (substr($reply, 0, 3) != 250) {
235
                    throw new \Exception('Error: EHLO not accepted from server!');
236
                }
237
238
                if (substr($this->smtp_hostname, 0, 3) == 'tls') {
239
                    fputs($handle, 'STARTTLS' . "\r\n");
240
241
                    $reply = '';
242
243
                    while ($line = fgets($handle, 515)) {
244
                        $reply .= $line;
245
246
                        if (substr($line, 3, 1) == ' ') {
247
                            break;
248
                        }
249
                    }
250
251
                    if (substr($reply, 0, 3) != 220) {
252
                        throw new \Exception('Error: STARTTLS not accepted from server!');
253
                    }
254
255
                    stream_socket_enable_crypto($handle, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
256
                }
257
258
                if (!empty($this->smtp_username)  && !empty($this->smtp_password)) {
259
                    fputs($handle, 'EHLO ' . getenv('SERVER_NAME') . "\r\n");
260
261
                    $reply = '';
262
263
                    while ($line = fgets($handle, 515)) {
264
                        $reply .= $line;
265
266
                        if (substr($line, 3, 1) == ' ') {
267
                            break;
268
                        }
269
                    }
270
271
                    if (substr($reply, 0, 3) != 250) {
272
                        throw new \Exception('Error: EHLO not accepted from server!');
273
                    }
274
275
                    fputs($handle, 'AUTH LOGIN' . "\r\n");
276
277
                    $reply = '';
278
279
                    while ($line = fgets($handle, 515)) {
280
                        $reply .= $line;
281
282
                        if (substr($line, 3, 1) == ' ') {
283
                            break;
284
                        }
285
                    }
286
287
                    if (substr($reply, 0, 3) != 334) {
288
                        throw new \Exception('Error: AUTH LOGIN not accepted from server!');
289
                    }
290
291
                    fputs($handle, base64_encode($this->smtp_username) . "\r\n");
292
293
                    $reply = '';
294
295
                    while ($line = fgets($handle, 515)) {
296
                        $reply .= $line;
297
298
                        if (substr($line, 3, 1) == ' ') {
299
                            break;
300
                        }
301
                    }
302
303
                    if (substr($reply, 0, 3) != 334) {
304
                        throw new \Exception('Error: Username not accepted from server!');
305
                    }
306
307
                    fputs($handle, base64_encode($this->smtp_password) . "\r\n");
308
309
                    $reply = '';
310
311
                    while ($line = fgets($handle, 515)) {
312
                        $reply .= $line;
313
314
                        if (substr($line, 3, 1) == ' ') {
315
                            break;
316
                        }
317
                    }
318
319
                    if (substr($reply, 0, 3) != 235) {
320
                        throw new \Exception('Error: Password not accepted from server!');
321
                    }
322
                } else {
323
                    fputs($handle, 'HELO ' . getenv('SERVER_NAME') . "\r\n");
324
325
                    $reply = '';
326
327
                    while ($line = fgets($handle, 515)) {
328
                        $reply .= $line;
329
330
                        if (substr($line, 3, 1) == ' ') {
331
                            break;
332
                        }
333
                    }
334
335
                    if (substr($reply, 0, 3) != 250) {
336
                        throw new \Exception('Error: HELO not accepted from server!');
337
                    }
338
                }
339
340
                if ($this->verp) {
341
                    fputs($handle, 'MAIL FROM: <' . $this->from . '>XVERP' . "\r\n");
342
                } else {
343
                    fputs($handle, 'MAIL FROM: <' . $this->from . '>' . "\r\n");
344
                }
345
346
                $reply = '';
347
348
                while ($line = fgets($handle, 515)) {
349
                    $reply .= $line;
350
351
                    if (substr($line, 3, 1) == ' ') {
352
                        break;
353
                    }
354
                }
355
356
                if (substr($reply, 0, 3) != 250) {
357
                    throw new \Exception('Error: MAIL FROM not accepted from server!');
358
                }
359
360
                if (!is_array($this->to)) {
361
                    fputs($handle, 'RCPT TO: <' . $this->to . '>' . "\r\n");
362
363
                    $reply = '';
364
365
                    while ($line = fgets($handle, 515)) {
366
                        $reply .= $line;
367
368
                        if (substr($line, 3, 1) == ' ') {
369
                            break;
370
                        }
371
                    }
372
373
                    if ((substr($reply, 0, 3) != 250) && (substr($reply, 0, 3) != 251)) {
374
                        throw new \Exception('Error: RCPT TO not accepted from server!');
375
                    }
376
                } else {
377
                    foreach ($this->to as $recipient) {
378
                        fputs($handle, 'RCPT TO: <' . $recipient . '>' . "\r\n");
379
380
                        $reply = '';
381
382
                        while ($line = fgets($handle, 515)) {
383
                            $reply .= $line;
384
385
                            if (substr($line, 3, 1) == ' ') {
386
                                break;
387
                            }
388
                        }
389
390
                        if ((substr($reply, 0, 3) != 250) && (substr($reply, 0, 3) != 251)) {
391
                            throw new \Exception('Error: RCPT TO not accepted from server!');
392
                        }
393
                    }
394
                }
395
396
                fputs($handle, 'DATA' . "\r\n");
397
398
                $reply = '';
399
400
                while ($line = fgets($handle, 515)) {
401
                    $reply .= $line;
402
403
                    if (substr($line, 3, 1) == ' ') {
404
                        break;
405
                    }
406
                }
407
408
                if (substr($reply, 0, 3) != 354) {
409
                    throw new \Exception('Error: DATA not accepted from server!');
410
                }
411
412
                // According to rfc 821 we should not send more than 1000 including the CRLF
413
                $message = str_replace("\r\n", "\n", $header . $message);
414
                $message = str_replace("\r", "\n", $message);
415
416
                $lines = explode("\n", $message);
417
418
                foreach ($lines as $line) {
419
                    $results = str_split($line, 998);
420
421
                    foreach ($results as $result) {
422
                        if (substr(PHP_OS, 0, 3) != 'WIN') {
423
                            fputs($handle, $result . "\r\n");
424
                        } else {
425
                            fputs($handle, str_replace("\n", "\r\n", $result) . "\r\n");
426
                        }
427
                    }
428
                }
429
430
                fputs($handle, '.' . "\r\n");
431
432
                $reply = '';
433
434
                while ($line = fgets($handle, 515)) {
435
                    $reply .= $line;
436
437
                    if (substr($line, 3, 1) == ' ') {
438
                        break;
439
                    }
440
                }
441
442
                if (substr($reply, 0, 3) != 250) {
443
                    throw new \Exception('Error: DATA not accepted from server!');
444
                }
445
446
                fputs($handle, 'QUIT' . "\r\n");
447
448
                $reply = '';
449
450
                while ($line = fgets($handle, 515)) {
451
                    $reply .= $line;
452
453
                    if (substr($line, 3, 1) == ' ') {
454
                        break;
455
                    }
456
                }
457
458
                if (substr($reply, 0, 3) != 221) {
459
                    throw new \Exception('Error: QUIT not accepted from server!');
460
                }
461
462
                fclose($handle);
463
            }
464
        }
465
    }
466
}
467