Completed
Push — master ( 131a24...b0667d )
by Basil
07:00
created

Mail::convertMessageToAltBody()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
namespace luya\components;
4
5
use Yii;
6
use yii\base\Component;
7
use luya\Exception;
8
use PHPMailer\PHPMailer\PHPMailer;
9
use PHPMailer\PHPMailer\SMTP;
10
11
/**
12
 * LUYA mail component to compose messages and send them via SMTP.
13
 *
14
 * This component is registered on each LUYA instance, how to use:
15
 *
16
 * ```php
17
 * if (Yii::$app->mail->compose('Subject', 'Message body of the Mail')->address('[email protected]')->send()) {
18
 *     echo "Mail has been sent!";
19
 * } else {
20
 *     echo "Error" : Yii::$app->mail->error;
21
 * }
22
 * ```
23
 *
24
 * SMTP debug help:
25
 *
26
 * ```
27
 * swaks -s HOST -p 587 -ehlo localhost -au AUTH_USER -to TO_ADDRESSE -tls
28
 * ```
29
 *
30
 * @property \PHPMailer\PHPMailer\PHPMailer $mailer The PHP Mailer object
31
 *
32
 * @author Basil Suter <[email protected]>
33
 * @since 1.0.0
34
 */
35
class Mail extends Component
36
{
37
    private $_mailer;
38
39
    /**
40
     * @var string sender email address, like `[email protected]`.
41
     */
42
    public $from;
43
44
    /**
45
     * @var string The from sender name like `LUYA Mailer`.
46
     */
47
    public $fromName;
48
49
    /**
50
     * @var boolean When enabled the debug print is echoed directly into the frontend output, this is built in PHPMailer debug.
51
     */
52
    public $debug = false;
53
54
    /**
55
     * @var string alternate text message if email client doesn't support HTML
56
     */
57
    public $altBody;
58
    
59
    /**
60
     * @var string|boolean Define a layout template file which is going to be wrapped around the body()
61
     * content. The file alias will be resolved so an example layout could look as followed:
62
     *
63
     * ```php
64
     * $layout = '@app/views/maillayout.php';
65
     * ```
66
     *
67
     * In your config or any mailer object. As in layouts the content of the mail specific html can be access
68
     * in the `$content` variable. The example content of `maillayout.php` from above could look like this:
69
     *
70
     * ```php
71
     * <h1>My Company</h1>
72
     * <div><?= $content; ?></div>
73
     * ```
74
     */
75
    public $layout = false;
76
    
77
    /**
78
     * @var boolean Whether mailer sends mails trough an an smtp server or via php mail() function. In order to configure the smtp use:
79
     *
80
     * + {{Mail::$username}}
81
     * + {{Mail::$password}}
82
     * + {{Mail::$host}}
83
     * + {{Mail::$port}}
84
     * + {{Mail::$smtpSecure}}
85
     * + {{Mail::$smtpAuth}}
86
     */
87
    public $isSMTP = true;
88
89
    // smtp settings
90
    
91
    /**
92
     * @var string The host address of the SMTP server for authentification like `mail.luya.io`, if {{Mail::$isSMTP}} is disabled, this property has no effect.
93
     */
94
    public $host;
95
    
96
    /**
97
     * @var string The username which should be used for SMTP auth e.g `[email protected]`, if {{Mail::$isSMTP}} is disabled, this property has no effect.
98
     */
99
    public $username;
100
    
101
    /**
102
     * @var string The password which should be used for SMTP auth, if {{Mail::$isSMTP}} is disabled, this property has no effect.
103
     */
104
    public $password;
105
    
106
    /**
107
     * @var integer The port which is used to connect to the SMTP server (default is 587), if {{Mail::$isSMTP}} is disabled, this property has no effect.
108
     */
109
    public $port = 587;
110
    
111
    /**
112
     * @var string Posible values are `tls` or `ssl` or empty `` (default is tls), if {{Mail::$isSMTP}} is disabled, this property has no effect.
113
     */
114
    public $smtpSecure = 'tls';
115
    
116
    /**
117
     * @var boolean Whether the SMTP requires authentication or not, enabled by default. If {{Mail::$isSMTP}} is disabled, this property has no effect. If
118
     * enabled the following properties can be used:
119
     * + {{Mail::$username}}
120
     * + {{Mail::$password}}
121
     */
122
    public $smtpAuth = true;
123
    
124
    /**
125
     * Getter for the mailer object
126
     *
127
     * @return \PHPMailer\PHPMailer\PHPMailer
128
     */
129
    public function getMailer()
130
    {
131
        if ($this->_mailer === null) {
132
            $this->_mailer = new PHPMailer();
133
            $this->_mailer->CharSet = 'UTF-8';
134
            $this->_mailer->From = $this->from;
135
            $this->_mailer->FromName = $this->fromName;
136
            $this->_mailer->isHTML(true);
137
            $this->_mailer->XMailer = ' ';
138
            // if sending over smtp, define the settings for the smpt server
139
            if ($this->isSMTP) {
140
                if ($this->debug) {
141
                    $this->_mailer->SMTPDebug = 2;
142
                }
143
                $this->_mailer->isSMTP();
144
                $this->_mailer->SMTPSecure = $this->smtpSecure;
145
                $this->_mailer->Host = $this->host;
146
                $this->_mailer->SMTPAuth= $this->smtpAuth;
147
                $this->_mailer->Username = $this->username;
148
                $this->_mailer->Password = $this->password;
149
                $this->_mailer->Port = $this->port;
150
                $this->_mailer->SMTPOptions = [
151
                    'ssl' => ['verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true],
152
                ];
153
            }
154
        }
155
156
        return $this->_mailer;
157
    }
158
    
159
    /**
160
     * Reset the mailer object to null
161
     *
162
     * @return void
163
     */
164
    public function cleanup()
165
    {
166
        $this->_mailer = null;
167
    }
168
    
169
    /**
170
     * Compose a new mail message.
171
     *
172
     * Make sure to change mailer object or global variables after composer command, as before it will flush the mailer object.
173
     *
174
     * @param string $subject The subject of the mail
175
     * @param string $body The HTML body of the mail message.
176
     * @return \luya\components\Mail
177
     */
178
    public function compose($subject = null, $body = null)
179
    {
180
        $this->cleanup();
181
        if ($subject !== null) {
182
            $this->subject($subject);
183
        }
184
        if ($body !== null) {
185
            $this->body($body);
186
        }
187
        return $this;
188
    }
189
    
190
    /**
191
     * Set the mail message subject of the mailer instance
192
     *
193
     * @param string $subject The subject message
194
     * @return \luya\components\Mail
195
     */
196
    public function subject($subject)
197
    {
198
        $this->getMailer()->Subject = $subject;
199
        return $this;
200
    }
201
    
202
    /**
203
     * Set the HTML body for the mailer message, if a layout is defined the layout
204
     * will automatically wrapped about the html body.
205
     *
206
     * @param string $body The HTML body message
207
     * @return \luya\components\Mail
208
     */
209
    public function body($body)
210
    {
211
        $message = $this->wrapLayout($body);
212
        $this->getMailer()->Body = $message;
213
        if (empty($this->altBody)) {
214
            $this->altBody = $this->convertMessageToAltBody($message);
215
        }
216
        $this->getMailer()->AltBody = $this->altBody;
217
        return $this;
218
    }
219
220
    /**
221
     * Try to convert the message into an alt body tag.
222
     * 
223
     * The alt body can only contain chars and newline. Therefore strip all tags and replace ending tags with newlines.
224
     * Also remove html head if there is any.
225
     * 
226
     * @param string $message The message to convert into alt body format.
227
     * @return string
228
     */
229
    public function convertMessageToAltBody($message)
230
    {
231
        $message = preg_replace('/<head>(.*?)<\/head>/s', '', $message);
232
        $tags = ['</p>', '<br />', '<br>', '<hr />', '<hr>', '</h1>', '</h2>', '</h3>', '</h4>', '</h5>', '</h6>'];
233
        return trim(strip_tags(str_replace($tags, PHP_EOL, $message)));
234
    }
235
    
236
    /**
237
     * Render a view file for the given Controller context.
238
     *
239
     * Assuming the following example inside a controller:
240
     *
241
     * ```php
242
     * Yii::$app->mail->compose('Send E-Mail')->render('@app/views/_mail', ['foo' => 'bar'])->address('[email protected]')->send();
243
     * ```
244
     *
245
     * @param string $viewFile The view file to render
246
     * @param array $params The parameters to pass to the view file.
247
     * @return \luya\components\Mail
248
     */
249
    public function render($viewFile, array $params = [])
250
    {
251
        $this->body(Yii::$app->view->render($viewFile, $params));
252
        
253
        return $this;
254
    }
255
    
256
    private $_context = [];
257
    
258
    /**
259
     * Pass option parameters to the layout files.
260
     *
261
     * @param array $vars
262
     * @return \luya\components\Mail
263
     */
264
    public function context(array $vars)
265
    {
266
        $this->_context = $vars;
267
        
268
        return $this;
269
    }
270
271
    /**
272
     * Wrap the layout from the `$layout` propertie and store
273
     * the passed  content as $content variable in the view.
274
     *
275
     * @param string $content The content to wrapp inside the layout.
276
     * @return string
277
     */
278
    protected function wrapLayout($content)
279
    {
280
        // do not wrap the content if layout is turned off.
281
        if ($this->layout === false) {
282
            return $content;
283
        }
284
        
285
        $view = Yii::$app->getView();
286
        
287
        $vars = array_merge($this->_context, ['content' => $content]);
288
        
289
        return $view->renderPhpFile(Yii::getAlias($this->layout), $vars);
0 ignored issues
show
Bug introduced by
It seems like $this->layout can also be of type boolean; however, yii\BaseYii::getAlias() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
290
    }
291
    
292
    /**
293
     * Add multiple addresses into the mailer object.
294
     *
295
     * If no key is used, the name is going to be ignored, if a string key is available it represents the name.
296
     *
297
     * ```php
298
     * addresses(['[email protected]', '[email protected]']);
299
     * ```
300
     *
301
     * or with names
302
     *
303
     * ```php
304
     * addresses(['John Doe' => '[email protected]', 'Jane Doe' => '[email protected]']);
305
     * ```
306
     *
307
     * @return \luya\components\Mail
308
     * @param array $emails An array with email addresses or name => email paring to use names.
309
     */
310
    public function addresses(array $emails)
311
    {
312
        foreach ($emails as $name => $mail) {
313
            if (is_int($name)) {
314
                $this->address($mail);
315
            } else {
316
                $this->address($mail, $name);
317
            }
318
        }
319
        
320
        return $this;
321
    }
322
    
323
    /**
324
     * Add a single address with optional name
325
     *
326
     * @param string $email The email address e.g. [email protected]
327
     * @param string $name The name for the address e.g. John Doe
328
     * @return \luya\components\Mail
329
     */
330
    public function address($email, $name = null)
331
    {
332
        $this->getMailer()->addAddress($email, (empty($name)) ? $email : $name);
333
334
        return $this;
335
    }
336
337
    /**
338
     * Add multiple CC addresses into the mailer object.
339
     *
340
     * If no key is used, the name is going to be ignored, if a string key is available it represents the name.
341
     *
342
     * ```php
343
     * ccAddresses(['[email protected]', '[email protected]']);
344
     * ```
345
     *
346
     * or with names
347
     *
348
     * ```php
349
     * ccAddresses(['John Doe' => '[email protected]', 'Jane Doe' => '[email protected]']);
350
     * ```
351
     *
352
     * @return \luya\components\Mail
353
     * @param array $emails An array with email addresses or name => email paring to use names.
354
     */
355
    public function ccAddresses(array $emails)
356
    {
357
        foreach ($emails as $name => $mail) {
358
            if (is_int($name)) {
359
                $this->ccAddress($mail);
360
            } else {
361
                $this->ccAddress($mail, $name);
362
            }
363
        }
364
365
        return $this;
366
    }
367
368
    /**
369
     * Add a single CC address with optional name
370
     *
371
     * @param string $email The email address e.g. [email protected]
372
     * @param string $name The name for the address e.g. John Doe
373
     * @return \luya\components\Mail
374
     */
375
    public function ccAddress($email, $name = null)
376
    {
377
        $this->getMailer()->addCC($email, (empty($name)) ? $email : $name);
378
379
        return $this;
380
    }
381
382
    /**
383
     * Add multiple BCC addresses into the mailer object.
384
     *
385
     * If no key is used, the name is going to be ignored, if a string key is available it represents the name.
386
     *
387
     * ```php
388
     * bccAddresses(['[email protected]', '[email protected]']);
389
     * ```
390
     *
391
     * or with names
392
     *
393
     * ```php
394
     * bccAddresses(['John Doe' => '[email protected]', 'Jane Doe' => '[email protected]']);
395
     * ```
396
     *
397
     * @return \luya\components\Mail
398
     * @param array $emails An array with email addresses or name => email paring to use names.
399
     */
400
    public function bccAddresses(array $emails)
401
    {
402
        foreach ($emails as $name => $mail) {
403
            if (is_int($name)) {
404
                $this->bccAddress($mail);
405
            } else {
406
                $this->bccAddress($mail, $name);
407
            }
408
        }
409
410
        return $this;
411
    }
412
413
    /**
414
     * Add a single BCC address with optional name
415
     *
416
     * @param string $email The email address e.g. [email protected]
417
     * @param string $name The name for the address e.g. John Doe
418
     * @return \luya\components\Mail
419
     */
420
    public function bccAddress($email, $name = null)
421
    {
422
        $this->getMailer()->addBCC($email, (empty($name)) ? $email : $name);
423
424
        return $this;
425
    }
426
    
427
    /**
428
     * Add attachment.
429
     *
430
     * @param string $filePath The path to the file, will be checked with `is_file`.
431
     * @param string $name An optional name to use for the Attachment.
432
     * @return \luya\components\Mail
433
     */
434
    public function addAttachment($filePath, $name = null)
435
    {
436
        $this->getMailer()->addAttachment($filePath, empty($name) ? '' : $name);
437
        
438
        return $this;
439
    }
440
    
441
    /**
442
     * Add ReplyTo Address.
443
     *
444
     * @param string $email
445
     * @param string $name
446
     * @return \luya\components\Mail
447
     */
448
    public function addReplyTo($email, $name = null)
449
    {
450
        $this->getMailer()->addReplyTo($email, empty($name) ? $email : $name);
451
        
452
        return $this;
453
    }
454
455
    /**
456
     * Trigger the send event of the mailer
457
     * @return bool
458
     * @throws Exception
459
     */
460
    public function send()
461
    {
462
        if (empty($this->mailer->Subject) || empty($this->mailer->Body)) {
463
            throw new Exception("Mail subject() and body() can not be empty in order to send mail.");
464
        }
465
        if (!$this->getMailer()->send()) {
466
            Yii::error($this->getError(), __METHOD__);
467
            return false;
468
        }
469
        return true;
470
    }
471
472
    /**
473
     * Get the mailer error info if any.
474
     *
475
     * @return string
476
     */
477
    public function getError()
478
    {
479
        return $this->getMailer()->ErrorInfo;
480
    }
481
482
    /**
483
     * Test connection for smtp.
484
     *
485
     * @see https://github.com/PHPMailer/PHPMailer/blob/master/examples/smtp_check.phps
486
     * @throws Exception
487
     */
488
    public function smtpTest($verbose)
489
    {
490
        //Create a new SMTP instance
491
        $smtp = new SMTP();
492
        
493
        if ($verbose) {
494
            // Enable connection-level debug output
495
            $smtp->do_debug = 3;
496
        }
497
        
498
        try {
499
            // connect to an SMTP server
500
            if ($smtp->connect($this->host, $this->port)) {
501
                // yay hello
502
                if ($smtp->hello('localhost')) {
503
                    if ($smtp->authenticate($this->username, $this->password)) {
504
                        return true;
505
                    } else {
506
                        $data = [$this->host, $this->port, $this->smtpSecure, $this->username];
507
                        throw new Exception('Authentication failed ('.implode(',', $data).'): '.$smtp->getLastReply() . PHP_EOL . print_r($smtp->getError(), true));
508
                    }
509
                } else {
510
                    throw new Exception('HELO failed: '.$smtp->getLastReply());
511
                }
512
            } else {
513
                throw new Exception('Connect failed');
514
            }
515
        } catch (\Exception $e) {
516
            $smtp->quit(true);
517
            throw new \yii\base\Exception($e->getMessage());
518
        }
519
    }
520
}
521