Completed
Push — master ( b4d6a5...352f6d )
by Basil
02:42
created

Mail   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 484
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 0
Metric Value
wmc 46
lcom 1
cbo 6
dl 0
loc 484
rs 8.72
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A getMailer() 0 28 4
A subject() 0 5 1
A body() 0 8 2
A convertMessageToAltBody() 0 6 1
A render() 0 6 1
A context() 0 6 1
A wrapLayout() 0 13 2
A addresses() 0 12 3
A ccAddresses() 0 12 3
A ccAddress() 0 6 2
A bccAddresses() 0 12 3
A bccAddress() 0 6 2
A addAttachment() 0 6 2
A addReplyTo() 0 6 2
A getError() 0 4 1
A cleanup() 0 4 1
A compose() 0 11 3
A address() 0 6 2
A send() 0 11 4
B smtpTest() 0 32 6

How to fix   Complexity   

Complex Class

Complex classes like Mail often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Mail, and based on these observations, apply Extract Interface, too.

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->setFrom($this->from, $this->fromName);
135
            $this->_mailer->isHTML(true);
136
            $this->_mailer->XMailer = ' ';
137
            // if sending over smtp, define the settings for the smpt server
138
            if ($this->isSMTP) {
139
                if ($this->debug) {
140
                    $this->_mailer->SMTPDebug = 2;
141
                }
142
                $this->_mailer->isSMTP();
143
                $this->_mailer->SMTPSecure = $this->smtpSecure;
144
                $this->_mailer->Host = $this->host;
145
                $this->_mailer->SMTPAuth= $this->smtpAuth;
146
                $this->_mailer->Username = $this->username;
147
                $this->_mailer->Password = $this->password;
148
                $this->_mailer->Port = $this->port;
149
                $this->_mailer->SMTPOptions = [
150
                    'ssl' => ['verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true],
151
                ];
152
            }
153
        }
154
155
        return $this->_mailer;
156
    }
157
    
158
    /**
159
     * Reset the mailer object to null
160
     *
161
     * @return void
162
     */
163
    public function cleanup()
164
    {
165
        $this->_mailer = null;
166
    }
167
    
168
    /**
169
     * Compose a new mail message.
170
     *
171
     * Make sure to change mailer object or global variables after composer command, as before it will flush the mailer object.
172
     *
173
     * @param string $subject The subject of the mail
174
     * @param string $body The HTML body of the mail message.
175
     * @return \luya\components\Mail
176
     */
177
    public function compose($subject = null, $body = null)
178
    {
179
        $this->cleanup();
180
        if ($subject !== null) {
181
            $this->subject($subject);
182
        }
183
        if ($body !== null) {
184
            $this->body($body);
185
        }
186
        return $this;
187
    }
188
    
189
    /**
190
     * Set the mail message subject of the mailer instance
191
     *
192
     * @param string $subject The subject message
193
     * @return \luya\components\Mail
194
     */
195
    public function subject($subject)
196
    {
197
        $this->getMailer()->Subject = $subject;
198
        return $this;
199
    }
200
    
201
    /**
202
     * Set the HTML body for the mailer message, if a layout is defined the layout
203
     * will automatically wrapped about the html body.
204
     *
205
     * @param string $body The HTML body message
206
     * @return \luya\components\Mail
207
     */
208
    public function body($body)
209
    {
210
        $message = $this->wrapLayout($body);
211
        $this->getMailer()->Body = $message;
212
        $alt = empty($this->altBody) ? $this->convertMessageToAltBody($message) : $this->altBody;
213
        $this->getMailer()->AltBody = $alt;
214
        return $this;
215
    }
216
217
    /**
218
     * Try to convert the message into an alt body tag.
219
     *
220
     * The alt body can only contain chars and newline. Therefore strip all tags and replace ending tags with newlines.
221
     * Also remove html head if there is any.
222
     *
223
     * @param string $message The message to convert into alt body format.
224
     * @return string Returns the alt body message compatible content
225
     * @since 1.0.11
226
     */
227
    public function convertMessageToAltBody($message)
228
    {
229
        $message = preg_replace('/<head>(.*?)<\/head>/s', '', $message);
230
        $tags = ['</p>', '<br />', '<br>', '<hr />', '<hr>', '</h1>', '</h2>', '</h3>', '</h4>', '</h5>', '</h6>'];
231
        return trim(strip_tags(str_replace($tags, PHP_EOL, $message)));
232
    }
233
    
234
    /**
235
     * Render a view file for the given Controller context.
236
     *
237
     * Assuming the following example inside a controller:
238
     *
239
     * ```php
240
     * Yii::$app->mail->compose('Send E-Mail')->render('@app/views/_mail', ['foo' => 'bar'])->address('[email protected]')->send();
241
     * ```
242
     *
243
     * @param string $viewFile The view file to render
244
     * @param array $params The parameters to pass to the view file.
245
     * @return \luya\components\Mail
246
     */
247
    public function render($viewFile, array $params = [])
248
    {
249
        $this->body(Yii::$app->view->render($viewFile, $params));
250
        
251
        return $this;
252
    }
253
    
254
    private $_context = [];
255
    
256
    /**
257
     * Pass option parameters to the layout files.
258
     *
259
     * @param array $vars
260
     * @return \luya\components\Mail
261
     */
262
    public function context(array $vars)
263
    {
264
        $this->_context = $vars;
265
        
266
        return $this;
267
    }
268
269
    /**
270
     * Wrap the layout from the `$layout` propertie and store
271
     * the passed  content as $content variable in the view.
272
     *
273
     * @param string $content The content to wrapp inside the layout.
274
     * @return string
275
     */
276
    protected function wrapLayout($content)
277
    {
278
        // do not wrap the content if layout is turned off.
279
        if ($this->layout === false) {
280
            return $content;
281
        }
282
        
283
        $view = Yii::$app->getView();
284
        
285
        $vars = array_merge($this->_context, ['content' => $content]);
286
        
287
        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...
288
    }
289
    
290
    /**
291
     * Add multiple addresses into the mailer object.
292
     *
293
     * If no key is used, the name is going to be ignored, if a string key is available it represents the name.
294
     *
295
     * ```php
296
     * addresses(['[email protected]', '[email protected]']);
297
     * ```
298
     *
299
     * or with names
300
     *
301
     * ```php
302
     * addresses(['John Doe' => '[email protected]', 'Jane Doe' => '[email protected]']);
303
     * ```
304
     *
305
     * @return \luya\components\Mail
306
     * @param array $emails An array with email addresses or name => email paring to use names.
307
     */
308
    public function addresses(array $emails)
309
    {
310
        foreach ($emails as $name => $mail) {
311
            if (is_int($name)) {
312
                $this->address($mail);
313
            } else {
314
                $this->address($mail, $name);
315
            }
316
        }
317
        
318
        return $this;
319
    }
320
    
321
    /**
322
     * Add a single address with optional name
323
     *
324
     * @param string $email The email address e.g. [email protected]
325
     * @param string $name The name for the address e.g. John Doe
326
     * @return \luya\components\Mail
327
     */
328
    public function address($email, $name = null)
329
    {
330
        $this->getMailer()->addAddress($email, (empty($name)) ? $email : $name);
331
332
        return $this;
333
    }
334
335
    /**
336
     * Add multiple CC addresses into the mailer object.
337
     *
338
     * If no key is used, the name is going to be ignored, if a string key is available it represents the name.
339
     *
340
     * ```php
341
     * ccAddresses(['[email protected]', '[email protected]']);
342
     * ```
343
     *
344
     * or with names
345
     *
346
     * ```php
347
     * ccAddresses(['John Doe' => '[email protected]', 'Jane Doe' => '[email protected]']);
348
     * ```
349
     *
350
     * @return \luya\components\Mail
351
     * @param array $emails An array with email addresses or name => email paring to use names.
352
     */
353
    public function ccAddresses(array $emails)
354
    {
355
        foreach ($emails as $name => $mail) {
356
            if (is_int($name)) {
357
                $this->ccAddress($mail);
358
            } else {
359
                $this->ccAddress($mail, $name);
360
            }
361
        }
362
363
        return $this;
364
    }
365
366
    /**
367
     * Add a single CC address with optional name
368
     *
369
     * @param string $email The email address e.g. [email protected]
370
     * @param string $name The name for the address e.g. John Doe
371
     * @return \luya\components\Mail
372
     */
373
    public function ccAddress($email, $name = null)
374
    {
375
        $this->getMailer()->addCC($email, (empty($name)) ? $email : $name);
376
377
        return $this;
378
    }
379
380
    /**
381
     * Add multiple BCC addresses into the mailer object.
382
     *
383
     * If no key is used, the name is going to be ignored, if a string key is available it represents the name.
384
     *
385
     * ```php
386
     * bccAddresses(['[email protected]', '[email protected]']);
387
     * ```
388
     *
389
     * or with names
390
     *
391
     * ```php
392
     * bccAddresses(['John Doe' => '[email protected]', 'Jane Doe' => '[email protected]']);
393
     * ```
394
     *
395
     * @return \luya\components\Mail
396
     * @param array $emails An array with email addresses or name => email paring to use names.
397
     */
398
    public function bccAddresses(array $emails)
399
    {
400
        foreach ($emails as $name => $mail) {
401
            if (is_int($name)) {
402
                $this->bccAddress($mail);
403
            } else {
404
                $this->bccAddress($mail, $name);
405
            }
406
        }
407
408
        return $this;
409
    }
410
411
    /**
412
     * Add a single BCC address with optional name
413
     *
414
     * @param string $email The email address e.g. [email protected]
415
     * @param string $name The name for the address e.g. John Doe
416
     * @return \luya\components\Mail
417
     */
418
    public function bccAddress($email, $name = null)
419
    {
420
        $this->getMailer()->addBCC($email, (empty($name)) ? $email : $name);
421
422
        return $this;
423
    }
424
    
425
    /**
426
     * Add attachment.
427
     *
428
     * @param string $filePath The path to the file, will be checked with `is_file`.
429
     * @param string $name An optional name to use for the Attachment.
430
     * @return \luya\components\Mail
431
     */
432
    public function addAttachment($filePath, $name = null)
433
    {
434
        $this->getMailer()->addAttachment($filePath, empty($name) ? pathinfo($filePath, PATHINFO_BASENAME) : $name);
435
        
436
        return $this;
437
    }
438
    
439
    /**
440
     * Add ReplyTo Address.
441
     *
442
     * @param string $email
443
     * @param string $name
444
     * @return \luya\components\Mail
445
     */
446
    public function addReplyTo($email, $name = null)
447
    {
448
        $this->getMailer()->addReplyTo($email, empty($name) ? $email : $name);
449
        
450
        return $this;
451
    }
452
453
    /**
454
     * Trigger the send event of the mailer
455
     * @return bool
456
     * @throws Exception
457
     */
458
    public function send()
459
    {
460
        if (empty($this->mailer->Subject) || empty($this->mailer->Body)) {
461
            throw new Exception("Mail subject() and body() can not be empty in order to send mail.");
462
        }
463
        if (!$this->getMailer()->send()) {
464
            Yii::error($this->getError(), __METHOD__);
465
            return false;
466
        }
467
        return true;
468
    }
469
470
    /**
471
     * Get the mailer error info if any.
472
     *
473
     * @return string
474
     */
475
    public function getError()
476
    {
477
        return $this->getMailer()->ErrorInfo;
478
    }
479
480
    /**
481
     * Test connection for smtp.
482
     *
483
     * @see https://github.com/PHPMailer/PHPMailer/blob/master/examples/smtp_check.phps
484
     * @throws Exception
485
     */
486
    public function smtpTest($verbose)
487
    {
488
        //Create a new SMTP instance
489
        $smtp = new SMTP();
490
        
491
        if ($verbose) {
492
            // Enable connection-level debug output
493
            $smtp->do_debug = 3;
494
        }
495
        
496
        try {
497
            // connect to an SMTP server
498
            if ($smtp->connect($this->host, $this->port)) {
499
                // yay hello
500
                if ($smtp->hello('localhost')) {
501
                    if ($smtp->authenticate($this->username, $this->password)) {
502
                        return true;
503
                    } else {
504
                        $data = [$this->host, $this->port, $this->smtpSecure, $this->username];
505
                        throw new Exception('Authentication failed ('.implode(',', $data).'): '.$smtp->getLastReply() . PHP_EOL . print_r($smtp->getError(), true));
506
                    }
507
                } else {
508
                    throw new Exception('HELO failed: '.$smtp->getLastReply());
509
                }
510
            } else {
511
                throw new Exception('Connect failed');
512
            }
513
        } catch (\Exception $e) {
514
            $smtp->quit(true);
515
            throw new \yii\base\Exception($e->getMessage());
516
        }
517
    }
518
}
519