Message::withTextBody()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Mailer\SwiftMailer;
6
7
use DateTime;
8
use DateTimeImmutable;
9
use DateTimeInterface;
10
use Swift_Attachment;
11
use Swift_EmbeddedFile;
12
use Swift_Message;
13
use Swift_Mime_Headers_UnstructuredHeader;
14
use Swift_Mime_MimePart;
15
use Swift_Signer;
16
use Throwable;
17
use Yiisoft\Mailer\File;
18
use Yiisoft\Mailer\MessageInterface;
19
20
use function is_string;
21
use function reset;
22
23
/**
24
 * Message implements a message class based on SwiftMailer.
25
 *
26
 * @see https://swiftmailer.symfony.com/docs/messages.html
27
 * @see Mailer
28
 */
29
final class Message implements MessageInterface
30
{
31
    private Swift_Message $swiftMessage;
32
    private ?DateTimeImmutable $date = null;
33
    private ?Throwable $error = null;
34
35 57
    public function __construct()
36
    {
37 57
        $this->swiftMessage = new Swift_Message();
38
    }
39
40 56
    public function __clone()
41
    {
42 56
        $this->swiftMessage = clone $this->swiftMessage;
43
    }
44
45 3
    public function getCharset(): string
46
    {
47 3
        return $this->swiftMessage->getCharset();
48
    }
49
50 5
    public function withCharset(string $charset): self
51
    {
52 5
        $new = clone $this;
53 5
        $new->swiftMessage->setCharset($charset);
54 5
        return $new;
55
    }
56
57 5
    public function getFrom()
58
    {
59 5
        return $this->normalizeAddresses($this->swiftMessage->getFrom());
60
    }
61
62 12
    public function withFrom($from): self
63
    {
64 12
        $new = clone $this;
65 12
        $new->swiftMessage->setFrom($from);
66 12
        return $new;
67
    }
68
69 5
    public function getTo()
70
    {
71 5
        return $this->normalizeAddresses($this->swiftMessage->getTo());
72
    }
73
74 13
    public function withTo($to): self
75
    {
76 13
        $new = clone $this;
77 13
        $new->swiftMessage->setTo($to);
78 13
        return $new;
79
    }
80
81 5
    public function getReplyTo()
82
    {
83 5
        return $this->normalizeAddresses($this->swiftMessage->getReplyTo());
84
    }
85
86 6
    public function withReplyTo($replyTo): self
87
    {
88 6
        $new = clone $this;
89 6
        $new->swiftMessage->setReplyTo($replyTo);
90 6
        return $new;
91
    }
92
93 5
    public function getCc()
94
    {
95 5
        return $this->normalizeAddresses($this->swiftMessage->getCc());
96
    }
97
98 6
    public function withCc($cc): self
99
    {
100 6
        $new = clone $this;
101 6
        $new->swiftMessage->setCc($cc);
102 6
        return $new;
103
    }
104
105 5
    public function getBcc()
106
    {
107 5
        return $this->normalizeAddresses($this->swiftMessage->getBcc());
108
    }
109
110 6
    public function withBcc($bcc): self
111
    {
112 6
        $new = clone $this;
113 6
        $new->swiftMessage->setBcc($bcc);
114 6
        return $new;
115
    }
116
117 2
    public function getSubject(): string
118
    {
119
        /** @psalm-suppress RedundantCastGivenDocblockType */
120 2
        return (string) $this->swiftMessage->getSubject();
121
    }
122
123 10
    public function withSubject(string $subject): self
124
    {
125 10
        $new = clone $this;
126 10
        $new->swiftMessage->setSubject($subject);
127 10
        return $new;
128
    }
129
130 2
    public function getDate(): ?DateTimeImmutable
131
    {
132 2
        return $this->date;
133
    }
134
135 2
    public function withDate(DateTimeInterface $date): self
136
    {
137 2
        if ($date instanceof DateTime) {
138 2
            $immutable = new DateTimeImmutable('@' . $date->getTimestamp());
139 2
            $date = $immutable->setTimezone($date->getTimezone());
140
        }
141
142 2
        $new = $this->withHeader('Date', $date->format(DateTimeInterface::RFC2822));
143 2
        $new->date = $date;
144 2
        return $new;
145
    }
146
147 6
    public function getPriority(): int
148
    {
149
        /** @psalm-suppress RedundantCastGivenDocblockType */
150 6
        return (int) $this->swiftMessage->getPriority();
151
    }
152
153 7
    public function withPriority(int $priority): self
154
    {
155 7
        $new = clone $this;
156 7
        $new->swiftMessage->setPriority($priority);
157 7
        return $new;
158
    }
159
160 2
    public function getReturnPath(): string
161
    {
162
        /** @psalm-suppress RedundantCastGivenDocblockType */
163 2
        return (string) $this->swiftMessage->getReturnPath();
164
    }
165
166 3
    public function withReturnPath(string $address): self
167
    {
168 3
        $new = clone $this;
169 3
        $new->swiftMessage->setReturnPath($address);
170 3
        return $new;
171
    }
172
173 2
    public function getSender(): string
174
    {
175
        /** @var array<string, null>|null $sender */
176 2
        $sender = $this->swiftMessage->getSender();
177
        /** @psalm-suppress RedundantCastGivenDocblockType */
178 2
        return empty($sender) ? '' : (string) array_key_first($sender);
179
    }
180
181 2
    public function withSender(string $address): self
182
    {
183 2
        $new = clone $this;
184 2
        $new->swiftMessage->setSender($address);
185 2
        return $new;
186
    }
187
188 2
    public function getTextBody(): string
189
    {
190
        /** @psalm-suppress RedundantCastGivenDocblockType */
191 2
        return (string) $this->swiftMessage->getBody();
192
    }
193
194 7
    public function withTextBody(string $text): self
195
    {
196 7
        $new = clone $this;
197 7
        $new->setBody($text, 'text/plain');
198 7
        return $new;
199
    }
200
201 3
    public function getHtmlBody(): string
202
    {
203
        /** @psalm-suppress RedundantCastGivenDocblockType */
204 3
        return (string) $this->swiftMessage->getBody();
205
    }
206
207 7
    public function withHtmlBody(string $html): self
208
    {
209 7
        $new = clone $this;
210 7
        $new->setBody($html, 'text/html');
211 7
        return $new;
212
    }
213
214 3
    public function withAttached(File $file): self
215
    {
216 3
        $attachment = $file->path() === null
217 2
            ? new Swift_Attachment($file->content())
218 1
            : Swift_Attachment::fromPath($file->path())
219
        ;
220
221 3
        if (!empty($file->name())) {
222 3
            $attachment->setFilename($file->name());
223
        }
224
225 3
        if (!empty($file->contentType())) {
226 3
            $attachment->setContentType($file->contentType());
227
        }
228
229 3
        $new = clone $this;
230 3
        $new->swiftMessage->attach($attachment);
231 3
        return $new;
232
    }
233
234 3
    public function withEmbedded(File $file): self
235
    {
236 3
        $embedFile = $file->path() === null
237 2
            ? new Swift_EmbeddedFile($file->content())
238 1
            : Swift_EmbeddedFile::fromPath($file->path())
239
        ;
240
241 3
        if (!empty($file->name())) {
242 3
            $embedFile->setFilename($file->name());
243
        }
244
245 3
        if (!empty($file->contentType())) {
246 3
            $embedFile->setContentType($file->contentType());
247
        }
248
249 3
        $new = clone $this;
250 3
        $new->swiftMessage->embed($embedFile->setId($file->id()));
251 3
        return $new;
252
    }
253
254 7
    public function getHeader(string $name): array
255
    {
256 7
        $headerSet = $this->swiftMessage->getHeaders();
257
258 7
        if (!$headerSet->has($name)) {
259 1
            return [];
260
        }
261
262 6
        $headers = [];
263
264
        /** @var Swift_Mime_Headers_UnstructuredHeader $header */
265 6
        foreach ($headerSet->getAll($name) as $header) {
266 6
            $headers[] = $header->getValue();
267
        }
268
269 6
        return $headers;
270
    }
271
272 2
    public function withAddedHeader(string $name, string $value): self
273
    {
274 2
        $new = clone $this;
275 2
        $new->swiftMessage
276 2
            ->getHeaders()
277 2
            ->addTextHeader($name, $value);
278 2
        return $new;
279
    }
280
281 7
    public function withHeader(string $name, $value): self
282
    {
283 7
        $new = clone $this;
284 7
        $headerSet = $new->swiftMessage->getHeaders();
285
286 7
        if ($headerSet->has($name)) {
287 3
            $headerSet->remove($name);
288
        }
289
290 7
        foreach ((array) $value as $v) {
291 7
            $headerSet->addTextHeader($name, $v);
292
        }
293
294 7
        return $new;
295
    }
296
297 4
    public function withHeaders(array $headers): self
298
    {
299 4
        $new = clone $this;
300
301 4
        foreach ($headers as $name => $value) {
302 3
            $new = $new->withHeader($name, $value);
303
        }
304
305 4
        return $new;
306
    }
307
308 2
    public function getError(): ?Throwable
309
    {
310 2
        return $this->error;
311
    }
312
313 2
    public function withError(Throwable $e): self
314
    {
315 2
        $new = clone $this;
316 2
        $new->error = $e;
317 2
        return $new;
318
    }
319
320 3
    public function __toString(): string
321
    {
322 3
        return $this->swiftMessage->toString();
323
    }
324
325
    /**
326
     * Returns a Swift message instance.
327
     *
328
     * @return Swift_Message Swift message instance.
329
     */
330 10
    public function getSwiftMessage(): Swift_Message
331
    {
332 10
        return $this->swiftMessage;
333
    }
334
335
    /**
336
     * Returns the addresses to which a read-receipt will be sent.
337
     *
338
     * @return array<string, string>|string The receipt receive email addresses.
339
     */
340 5
    public function getReadReceiptTo()
341
    {
342 5
        return $this->normalizeAddresses($this->swiftMessage->getReadReceiptTo());
343
    }
344
345
    /**
346
     * Returns a new instance with the specified ask for a delivery receipt from the recipient to be sent to addresses.
347
     *
348
     * @param string|string[] $addresses The receipt receive email address(es).
349
     *
350
     * @return self
351
     */
352 6
    public function withReadReceiptTo($addresses): self
353
    {
354 6
        $new = clone $this;
355 6
        $new->swiftMessage->setReadReceiptTo((array) $addresses);
356 6
        return $new;
357
    }
358
359
    /**
360
     * Returns a new instance with the specified attached signers.
361
     *
362
     * @param Swift_Signer[] $signers
363
     *
364
     * @return self
365
     */
366 3
    public function withAttachedSigners(array $signers): self
367
    {
368 3
        $new = clone $this;
369
370 3
        foreach ($signers as $signer) {
371 2
            $new->swiftMessage->attachSigner($signer);
372
        }
373
374 3
        return $new;
375
    }
376
377
    /**
378
     * Sets the message body.
379
     *
380
     * If body is already set and its content type matches given one, it will
381
     * be overridden, if content type miss match the multipart message will be composed.
382
     *
383
     * @param string $body The body content.
384
     * @param string $contentType The body content type.
385
     */
386 11
    private function setBody(string $body, string $contentType): void
387
    {
388 11
        $oldBody = $this->swiftMessage->getBody();
389 11
        $charset = $this->swiftMessage->getCharset();
390
391 11
        if (!empty($oldBody)) {
392 3
            $oldContentType = $this->swiftMessage->getContentType();
393
394 3
            if ($oldContentType === $contentType) {
395 1
                $this->swiftMessage->setBody($body, $contentType);
396 1
                return;
397
            }
398
399 2
            $this->swiftMessage->setBody(null);
400
            /** @psalm-suppress NullArgument */
401 2
            $this->swiftMessage->setContentType(null);
402 2
            $this->swiftMessage->addPart($oldBody, $oldContentType, $charset);
403 2
            $this->swiftMessage->addPart($body, $contentType, $charset);
404 2
            return;
405
        }
406
407 11
        $parts = $this->swiftMessage->getChildren();
408 11
        $partFound = false;
409
410 11
        foreach ($parts as $key => $part) {
411 1
            if ($part instanceof Swift_Mime_MimePart && $part->getContentType() === $contentType) {
412 1
                $charset = $part->getCharset();
413 1
                unset($parts[$key]);
414 1
                $partFound = true;
415 1
                break;
416
            }
417
        }
418
419 11
        if (!$partFound) {
420 11
            $this->swiftMessage->setBody($body, $contentType);
421 11
            return;
422
        }
423
424 1
        reset($parts);
425 1
        $this->swiftMessage->setChildren($parts);
426 1
        $this->swiftMessage->addPart($body, $contentType, $charset);
427
    }
428
429
    /**
430
     * Normalizes email addresses and names to the correct format.
431
     *
432
     * @param mixed $addresses
433
     *
434
     * @return array<string, string>|string
435
     */
436 25
    private function normalizeAddresses($addresses)
437
    {
438 25
        if (empty($addresses)) {
439 1
            return '';
440
        }
441
442 24
        if (is_string($addresses)) {
443
            return $addresses;
444
        }
445
446 24
        $normalized = [];
447
448
        /** @var mixed $name */
449 24
        foreach ((array) $addresses as $address => $name) {
450 24
            $normalized[(string) $address] = is_string($name) ? $name : '';
451
        }
452
453 24
        return $normalized;
454
    }
455
}
456