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
|
|||||
117 | } |
||||
118 | |||||
119 | /** |
||||
120 | * @inheritdoc |
||||
121 | */ |
||||
122 | public function setCharset($charset) |
||||
123 | { |
||||
124 | return true; |
||||
0 ignored issues
–
show
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.
}
}
![]() |
|||||
125 | } |
||||
126 | |||||
127 | /** |
||||
128 | * @inheritdoc |
||||
129 | */ |
||||
130 | public function getFrom() |
||||
131 | { |
||||
132 | $fromMail = null; |
||||
133 | reset($this->from); |
||||
0 ignored issues
–
show
It seems like
$this->from can also be of type string ; however, parameter $array of reset() does only seem to accept array|object , 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
![]() |
|||||
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); |
||||
0 ignored issues
–
show
It seems like
$this->from can also be of type string ; however, parameter $array of reset() does only seem to accept array|object , 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
![]() |
|||||
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.'/'); |
||||
0 ignored issues
–
show
It seems like
Yii::getAlias('@app/runtime/' . $uid . '/') can also be of type boolean . However, the property $attachmentsTmdDir is declared as type string . 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 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;
}
![]() |
|||||
423 | $status = true; |
||||
424 | if (file_exists($this->attachmentsTmdDir) === false) { |
||||
0 ignored issues
–
show
It seems like
$this->attachmentsTmdDir can also be of type boolean ; however, parameter $filename of file_exists() does only seem to accept string , 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
![]() |
|||||
425 | $status = mkdir($this->attachmentsTmdDir, 0755, true); |
||||
0 ignored issues
–
show
It seems like
$this->attachmentsTmdDir can also be of type boolean ; however, parameter $directory of mkdir() does only seem to accept string , 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
![]() |
|||||
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
|
|||||
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) { |
||||
0 ignored issues
–
show
|
|||||
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 | } |
In the issue above, the returned value is violating the contract defined by the mentioned interface.
Let's take a look at an example: