Issues (12)

src/Message.php (3 issues)

1
<?php
2
/**
3
 * Message.php
4
 *
5
 * PHP version 5.6+
6
 *
7
 * @author Manuel Avelar <[email protected]>
8
 * @copyright 2024 Manuel Avelar
9
 * @license http://www.pixelcreart.com/license license
10
 * @version 1.0.0
11
 * @link http://www.pixelcreart.com
12
 * @package pixelcreart\sendgrid
13
 */
14
15
namespace pixelcreart\sendgrid;
16
17
use Yii;
18
use yii\base\InvalidConfigException;
19
use yii\base\InvalidArgumentException;
20
use yii\mail\BaseMessage;
21
use yii\mail\MailerInterface;
22
23
/**
24
 * This component allow user to send an email
25
 *
26
 * @author Manuel Avelar <[email protected]>
27
 * @copyright 2024 Manuel Avelar
28
 * @license http://www.pixelcreart.com/license license
29
 * @version 1.0.0
30
 * @link http://www.pixelcreart.com
31
 * @package pixelcreart\sendgrid
32
 * @since 1.0.0
33
 */
34
class Message extends BaseMessage
35
{
36
    /**
37
     * @var string|array from
38
     */
39
    protected $from;
40
41
    /**
42
     * @var array
43
     */
44
    protected $to = [];
45
46
    /**
47
     * @var string|array reply to
48
     */
49
    protected $replyTo;
50
51
    /**
52
     * @var array
53
     */
54
    protected $cc = [];
55
56
    /**
57
     * @var array
58
     */
59
    protected $bcc = [];
60
61
    /**
62
     * @var string
63
     */
64
    protected $subject;
65
66
    /**
67
     * @var string
68
     */
69
    protected $textBody;
70
71
    /**
72
     * @var string
73
     */
74
    protected $htmlBody;
75
76
    /**
77
     * @var array
78
     */
79
    protected $attachments = [];
80
81
    /**
82
     * @var string temporary attachment directory
83
     */
84
    protected $attachmentsTmdDir;
85
86
    /**
87
     * @var array
88
     */
89
    protected $uniqueArguments = [];
90
91
    /**
92
     * @var array
93
     */
94
    protected $headers = [];
95
96
    /**
97
     * @var string
98
     */
99
    protected $templateId;
100
101
    /**
102
     * @var array
103
     */
104
    protected $templateModel;
105
106
    /**
107
     * @var array substitution pairs used to mark expandable vars in template mode https://github.com/sendgrid/sendgrid-php#setsubstitutions
108
     */
109
    public $substitutionsPairs = ['{', '}'];
110
111
    /**
112
     * @inheritdoc
113
     */
114
    public function getCharset()
115
    {
116
        return true;
0 ignored issues
show
Bug Best Practice introduced by
The expression return true returns the type true which is incompatible with the return type mandated by yii\mail\MessageInterface::getCharset() of string.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
117
    }
118
119
    /**
120
     * @inheritdoc
121
     */
122
    public function setCharset($charset)
123
    {
124
        return true;
0 ignored issues
show
Bug Best Practice introduced by
The expression return true returns the type true which is incompatible with the return type mandated by yii\mail\MessageInterface::setCharset() of yii\mail\MessageInterface.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
125
    }
126
127
    /**
128
     * @inheritdoc
129
     */
130
    public function getFrom()
131
    {
132
        $fromMail = null;
133
        reset($this->from);
134
        foreach($this->from as $email => $name) {
135
            if (is_numeric($email) === true) {
136
                $fromMail = $name;
137
            } else {
138
                $fromMail = $email;
139
            }
140
        }
141
        
142
        return $fromMail;
143
    }
144
145
    /**
146
     * @return string|null extract and return the name associated with from
147
     * @since 1.0.0
148
     */
149
    public function getFromName()
150
    {
151
        reset($this->from);
152
        foreach($this->from as $email => $name) {
153
            if (is_numeric($email) === false) {
154
                return $name;
155
            } else {
156
                return null;
157
            }
158
        }
159
    }
160
161
    /**
162
     * @inheritdoc
163
     */
164
    public function setFrom($from)
165
    {
166
        if (is_string($from) === true) {
167
            $from = [$from];
168
        }
169
        $this->from = $from;
170
        return $this;
171
    }
172
173
    /**
174
     * @inheritdoc
175
     */
176
    public function getTo()
177
    {
178
        return self::normalizeEmails($this->to);
179
    }
180
181
    /**
182
     * @inheritdoc
183
     */
184
    public function setTo($to)
185
    {
186
        $this->to = $to;
187
        return $this;
188
    }
189
190
    /**
191
     * @inheritdoc
192
     */
193
    public function getReplyTo()
194
    {
195
        $replyTo = null;
196
        if (is_array($this->replyTo) === true) {
197
            reset($this->replyTo);
198
            foreach($this->replyTo as $email => $name) {
199
                if (is_numeric($email) === true) {
200
                    $replyTo = $name;
201
                } else {
202
                    $replyTo = $email;
203
                }
204
            }
205
        }
206
        return $replyTo;
207
    }
208
209
    /**
210
     * @inheritdoc
211
     */
212
    public function setReplyTo($replyTo)
213
    {
214
        if (is_string($replyTo) === true) {
215
            $replyTo = [$replyTo];
216
        }
217
        $this->replyTo = $replyTo;
218
        return $this;
219
    }
220
221
    /**
222
     * @inheritdoc
223
     */
224
    public function getCc()
225
    {
226
        return $this->cc;
227
    }
228
229
    /**
230
     * @inheritdoc
231
     */
232
    public function setCc($cc)
233
    {
234
        $this->cc = self::normalizeEmails($cc);
235
        return $this;
236
    }
237
238
    /**
239
     * @inheritdoc
240
     */
241
    public function getBcc()
242
    {
243
        return $this->bcc;
244
    }
245
246
    /**
247
     * @inheritdoc
248
     */
249
    public function setBcc($bcc)
250
    {
251
        $this->bcc = self::normalizeEmails($bcc);
252
        return $this;
253
    }
254
255
    /**
256
     * @inheritdoc
257
     */
258
    public function getSubject()
259
    {
260
        return $this->subject;
261
    }
262
263
    /**
264
     * @inheritdoc
265
     */
266
    public function setSubject($subject)
267
    {
268
        $this->subject = $subject;
269
        return $this;
270
    }
271
272
    /**
273
     * @return string|null text body of the message
274
     * @since 1.0.0
275
     */
276
    public function getTextBody()
277
    {
278
        return $this->textBody;
279
    }
280
281
    /**
282
     * @inheritdoc
283
     */
284
    public function setTextBody($text)
285
    {
286
        $this->textBody = $text;
287
        return $this;
288
    }
289
290
    /**
291
     * @return string|null html body of the message
292
     * @since 1.0.0
293
     */
294
    public function getHtmlBody()
295
    {
296
        return $this->htmlBody;
297
    }
298
299
    /**
300
     * @inheritdoc
301
     */
302
    public function setHtmlBody($html)
303
    {
304
        $this->htmlBody = $html;
305
        return $this;
306
    }
307
308
    /**
309
     * @return array list of unique arguments attached to the email
310
     * @since 1.0.0
311
     */
312
    public function getUniqueArguments()
313
    {
314
        return $this->uniqueArguments;
315
    }
316
317
    /**
318
     * @param string $key key of the unique argument
319
     * @param string $value value of the unique argument which will be added to the mail
320
     * @return $this
321
     * @since 1.0.0
322
     */
323
    public function addUniqueArgument($key, $value)
324
    {
325
        $this->uniqueArguments[$key] = $value;
326
        return $this;
327
    }
328
329
    /**
330
     * @param string $templateId template Id used. in this case, Subject / HtmlBody / TextBody are discarded
331
     * @return $this
332
     * @since 1.0.0
333
     */
334
    public function setTemplateId($templateId)
335
    {
336
        $this->templateId = $templateId;
337
        return $this;
338
    }
339
340
    /**
341
     * @return string|null current templateId
342
     * @since 1.0.0
343
     */
344
    public function getTemplateId()
345
    {
346
        return $this->templateId;
347
    }
348
349
    /**
350
     * @param array $templateModel model associated with the template
351
     * @return $this
352
     * @since 1.0.0
353
     */
354
    public function setTemplateModel($templateModel)
355
    {
356
        $this->templateModel = $templateModel;
357
        return $this;
358
    }
359
360
    /**
361
     * @return array current template model
362
     * @since 1.0.0
363
     */
364
    public function getTemplateModel()
365
    {
366
        return !empty($this->templateModel) ? $this->templateModel : [];
367
    }
368
369
    /**
370
     * @param array $header add custom header to the mail
371
     * @since 1.0.0
372
     */
373
    public function addHeader($header)
374
    {
375
        $this->headers[] = $header;
376
    }
377
378
    /**
379
     * @return array|null headers which should be added to the mail
380
     * @since 1.0.0
381
     */
382
    public function getHeaders()
383
    {
384
        return empty($this->headers) ? [] : $this->headers;
385
    }
386
387
    /**
388
     * @return array|null list of attachments
389
     * @since 1.0.0
390
     */
391
    public function getAttachments()
392
    {
393
        return empty($this->attachments) ? [] : $this->attachments;
394
    }
395
396
    /**
397
     * @inheritdoc
398
     */
399
    public function attach($fileName, array $options = [])
400
    {
401
        $attachment = [
402
            'File' => $fileName
403
        ];
404
        if (!empty($options['fileName'])) {
405
            $attachment['Name'] = $options['fileName'];
406
        } else {
407
            $attachment['Name'] = pathinfo($fileName, PATHINFO_BASENAME);
408
        }
409
        $this->attachments[] = $attachment;
410
        return $this;
411
    }
412
413
    /**
414
     * @return string temporary directory to store contents
415
     * @since 1.0.0
416
     * @throws InvalidConfigException
417
     */
418
    protected function getTempDir()
419
    {
420
        if ($this->attachmentsTmdDir === null) {
421
            $uid = uniqid();
422
            $this->attachmentsTmdDir = Yii::getAlias('@app/runtime/'.$uid.'/');
423
            $status = true;
424
            if (file_exists($this->attachmentsTmdDir) === false) {
425
                $status = mkdir($this->attachmentsTmdDir, 0755, true);
426
            }
427
            if ($status === false) {
428
                throw new InvalidConfigException('Directory \''.$this->attachmentsTmdDir.'\' cannot be created');
429
            }
430
        }
431
        return $this->attachmentsTmdDir;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->attachmentsTmdDir also could return the type boolean which is incompatible with the documented return type string.
Loading history...
432
    }
433
434
    /**
435
     * @inheritdoc
436
     */
437
    public function attachContent($content, array $options = [])
438
    {
439
        if (!isset($options['fileName']) || empty($options['fileName'])) {
440
            throw new InvalidArgumentException('Filename is missing');
441
        }
442
        $filePath = $this->getTempDir().'/'.$options['fileName'];
443
        if (file_put_contents($filePath, $content) === false) {
444
            throw new InvalidConfigException('Cannot write file \''.$filePath.'\'');
445
        }
446
        $this->attach($filePath, $options);
447
        return $this;
448
    }
449
450
    /**
451
     * @inheritdoc
452
     */
453
    public function embed($fileName, array $options = [])
454
    {
455
        $embed = [
456
            'File' => $fileName
457
        ];
458
        if (!empty($options['fileName'])) {
459
            $embed['Name'] = $options['fileName'];
460
        } else {
461
            $embed['Name'] = pathinfo($fileName, PATHINFO_BASENAME);
462
        }
463
        $embed['ContentID'] = 'cid:' . uniqid();
464
        $this->attachments[] = $embed;
465
        return $embed['ContentID'];
466
    }
467
468
    /**
469
     * @inheritdoc
470
     */
471
    public function embedContent($content, array $options = [])
472
    {
473
        if (isset($options['fileName']) === false || empty($options['fileName'])) {
474
            throw new InvalidArgumentException('fileName is missing');
475
        }
476
        $filePath = $this->getTempDir().'/'.$options['fileName'];
477
        if (file_put_contents($filePath, $content) === false) {
478
            throw new InvalidConfigException('Cannot write file \''.$filePath.'\'');
479
        }
480
        $cid = $this->embed($filePath, $options);
481
        return $cid;
482
    }
483
484
    /**
485
     * @inheritdoc
486
     * @todo make real serialization to make message compliant with PostmarkAPI
487
     */
488
    public function toString()
489
    {
490
        return serialize($this);
491
    }
492
493
494
    /**
495
     * @param array|string $emailsData email can be defined as string. In this case no transformation is done
496
     *                                 or as an array ['[email protected]', '[email protected]' => 'Email 2']
497
     * @return string|null
498
     * @since 1.0.0
499
     */
500
    public static function stringifyEmails($emailsData)
501
    {
502
        $emails = null;
503
        if (empty($emailsData) === false) {
504
            if (is_array($emailsData) === true) {
505
                foreach ($emailsData as $key => $email) {
506
                    if (is_int($key) === true) {
507
                        $emails[] = $email;
508
                    } else {
509
                        if (preg_match('/[.,:]/', $email) > 0) {
510
                            $email = '"'. $email .'"';
511
                        }
512
                        $emails[] = $email . ' ' . '<' . $key . '>';
513
                    }
514
                }
515
                $emails = implode(', ', $emails);
516
            } elseif (is_string($emailsData) === true) {
517
                $emails = $emailsData;
518
            }
519
        }
520
        return $emails;
521
    }
522
523
    public static function normalizeEmails($emailsData)
524
    {
525
        $emails = null;
526
        if (empty($emailsData) === false) {
527
            if (is_array($emailsData) === true) {
528
                foreach ($emailsData as $key => $email) {
529
                    if (is_int($key) === true) {
530
                        $emails[$email] = null;
531
                    } else {
532
                        $emails[$key] = $email;
533
                    }
534
                }
535
            } elseif (is_string($emailsData) === true) {
536
                $emails[$emailsData] = null;
537
            }
538
        }
539
        return $emails;
540
    }
541
542
    public function send(MailerInterface $mailer = null)
543
    {
544
        $result = parent::send($mailer);
545
        if ($this->attachmentsTmdDir !== null) {
546
            //TODO: clean up tmpdir after ourselves
547
        }
548
        return $result;
549
    }
550
551
552
}