Completed
Push — master ( 251ec5...d73f1a )
by Frederik
02:24
created

MessageBodyCollection::extract()   B

Complexity

Conditions 5
Paths 2

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 21
ccs 11
cts 11
cp 1
rs 8.7624
c 0
b 0
f 0
cc 5
eloc 12
nc 2
nop 1
crap 5
1
<?php
2
declare(strict_types=1);
3
4
namespace Genkgo\Mail;
5
6
use Genkgo\Mail\Header\ContentType;
7
use Genkgo\Mail\Mime\Boundary;
8
use Genkgo\Mail\Mime\EmbeddedImage;
9
use Genkgo\Mail\Mime\HtmlPart;
10
use Genkgo\Mail\Mime\MultiPart;
11
use Genkgo\Mail\Mime\MultiPartInterface;
12
use Genkgo\Mail\Mime\PartInterface;
13
use Genkgo\Mail\Mime\PlainTextPart;
14
15
final class MessageBodyCollection
16
{
17
    /**
18
     * @var string
19
     */
20
    private $html = '';
21
22
    /**
23
     * @var AlternativeText
24
     */
25
    private $text;
26
27
    /**
28
     * @var array|PartInterface[]
29
     */
30
    private $attachments = [];
31
32
    /**
33
     * @var array|PartInterface[]
34
     */
35
    private $embedImages = [];
36
37
    /**
38
     * @param string $html
39
     */
40 16
    public function __construct(string $html = '')
41
    {
42 16
        $this->html = $html;
43 16
        $this->text = AlternativeText::fromHtml($html);
44 16
    }
45
46
    /**
47
     * @param string $html
48
     * @return MessageBodyCollection
49
     */
50 6
    public function withHtml(string $html): self
51
    {
52 6
        $clone = clone $this;
53 6
        $clone->html = $html;
54 6
        $clone->text = AlternativeText::fromHtml($html);
55 6
        return $clone;
56
    }
57
58
    /**
59
     * @param string $html
60
     * @return MessageBodyCollection
61
     */
62 3
    public function withHtmlAndNoGeneratedAlternativeText(string $html): self
63
    {
64 3
        $clone = clone $this;
65 3
        $clone->html = $html;
66 3
        return $clone;
67
    }
68
69
    /**
70
     * @param AlternativeText $text
71
     * @return MessageBodyCollection
72
     */
73 4
    public function withAlternativeText(AlternativeText $text): self
74
    {
75 4
        $clone = clone $this;
76 4
        $clone->text = $text;
77 4
        return $clone;
78
    }
79
80
    /**
81
     * @param PartInterface $part
82
     * @return MessageBodyCollection
83
     */
84 7
    public function withAttachment(PartInterface $part): self
85
    {
86
        try {
87 7
            $disposition = $part->getHeader('Content-Disposition')->getValue()->getRaw();
88 6
            if ($disposition !== 'attachment') {
89 1
                throw new \InvalidArgumentException(
90 6
                    'An attachment must have Content-Disposition header with value `attachment`'
91
                );
92
            }
93 2
        } catch (\UnexpectedValueException $e) {
94 1
            throw new \InvalidArgumentException(
95 1
                'An attachment must have an Content-Disposition header'
96
            );
97
        }
98
99 5
        $clone = clone $this;
100 5
        $clone->attachments[] = $part;
101 5
        return $clone;
102
    }
103
104
    /**
105
     * @param EmbeddedImage $embeddedImage
106
     * @return MessageBodyCollection
107
     */
108 5
    public function withEmbeddedImage(EmbeddedImage $embeddedImage): self
109
    {
110 5
        $clone = clone $this;
111 5
        $clone->embedImages[] = $embeddedImage;
112 5
        return $clone;
113
    }
114
115
    /**
116
     * @return string
117
     */
118 4
    public function getHtml(): string
119
    {
120 4
        return $this->html;
121
    }
122
123
    /**
124
     * @return AlternativeText
125
     */
126 4
    public function getText(): AlternativeText
127
    {
128 4
        return $this->text;
129
    }
130
131
    /**
132
     * @return array|PartInterface[]
133
     */
134 4
    public function getAttachments(): iterable
135
    {
136 4
        return $this->attachments;
137
    }
138
139
    /**
140
     * @return array|PartInterface[]
141
     */
142 4
    public function getEmbeddedImages(): iterable
143
    {
144 4
        return $this->embedImages;
145
    }
146
147
    /**
148
     * @return MessageInterface
149
     */
150 8
    public function createMessage(): MessageInterface
151
    {
152 8
        return (new MimeMessageFactory())->createMessage($this->createMessageRoot());
153
    }
154
155
    /**
156
     * @param MessageInterface $message
157
     * @return MessageInterface
158
     */
159 1
    public function attachToMessage(MessageInterface $message): MessageInterface
160
    {
161 1
        $newMessage = $this->createMessage();
162
163
        /** @var HeaderInterface[] $headers */
164 1
        foreach ($newMessage->getHeaders() as $headers) {
165 1
            foreach ($headers as $header) {
166 1
                $message = $message->withHeader($header);
167
            }
168
        }
169
170 1
        return $message->withBody($newMessage->getBody());
171
    }
172
173
    /**
174
     * @return PartInterface
175
     */
176 8
    private function createMessageRoot(): PartInterface
177
    {
178 8
        if (!empty($this->attachments)) {
179 3
            return (new MultiPart(
180 3
                Boundary::newRandom(),
181 3
                new ContentType('multipart/mixed')
182
            ))
183 3
                ->withPart($this->createMessageHumanReadable())
184 3
                ->withParts($this->attachments);
185
        }
186
187 5
        return $this->createMessageHumanReadable();
188
    }
189
190
    /**
191
     * @return PartInterface
192
     */
193 8
    private function createMessageHumanReadable(): PartInterface
194
    {
195 8
        if (!empty($this->embedImages)) {
196 3
            return (new MultiPart(
197 3
                Boundary::newRandom(),
198 3
                new ContentType('multipart/related')
199
            ))
200 3
                ->withPart($this->createMessageText())
201 3
                ->withParts($this->embedImages);
202
        }
203
204 5
        return $this->createMessageText();
205
    }
206
207
    /**
208
     * @return PartInterface
209
     */
210 8
    private function createMessageText(): PartInterface
211
    {
212 8
        if ($this->text->isEmpty() && $this->html === '') {
213 1
            return new PlainTextPart('');
214
        }
215
216 7
        if ($this->text->isEmpty()) {
217 2
            return new HtmlPart($this->html);
218
        }
219
220 5
        if ($this->html === '') {
221 1
            return new PlainTextPart((string)$this->text);
222
        }
223
224 4
        return (new MultiPart(
225 4
            Boundary::newRandom(),
226 4
            new ContentType('multipart/alternative')
227
        ))
228 4
            ->withPart(new PlainTextPart((string)$this->text))
229 4
            ->withPart(new HtmlPart($this->html));
230
    }
231
232
    /**
233
     * @param MessageInterface $message
234
     * @return MessageBodyCollection
235
     */
236 5
    public static function extract(MessageInterface $message): MessageBodyCollection
237
    {
238 5
        $collection = new self();
239
240
        try {
241 5
            $collection->extractFromMimePart(MultiPart::fromMessage($message));
242 2
        } catch (\InvalidArgumentException $e) {
243 2
            foreach ($message->getHeader('Content-Type') as $header) {
244 2
                $contentType = $header->getValue()->getRaw();
245 2
                if ($contentType === 'text/html') {
246 1
                    $collection->html = \rtrim((string)$message->getBody());
247
                }
248
249 2
                if ($contentType === 'text/plain') {
250 2
                    $collection->text = new AlternativeText(\rtrim((string)$message->getBody()));
251
                }
252
            }
253
        }
254
255 5
        return $collection;
256
    }
257
258
    /**
259
     * @param MultiPartInterface $parts
260
     */
261 3
    private function extractFromMimePart(MultiPartInterface $parts): void
262
    {
263 3
        foreach ($parts->getParts() as $part) {
264 3
            $contentType = $part->getHeader('Content-Type')->getValue()->getRaw();
265 3
            $hasDisposition = $part->hasHeader('Content-Disposition');
266
267 3
            if (!$hasDisposition && $contentType === 'text/html') {
268 3
                $this->html = (string)$part->getBody();
269 3
                continue;
270
            }
271
272 3
            if (!$hasDisposition && $contentType === 'text/plain') {
273 3
                $this->text = new AlternativeText((string)$part->getBody());
274 3
                continue;
275
            }
276
277 2
            if ($hasDisposition) {
278 2
                $disposition = $part->getHeader('Content-Disposition')->getValue()->getRaw();
279
280 2
                if ($disposition === 'attachment') {
281 2
                    $this->attachments[] = $part;
282 2
                    continue;
283
                }
284
285 2
                if ($disposition === 'inline' && \substr($contentType, 0, 6) === 'image/' && $part->hasHeader('Content-ID')) {
286 2
                    $this->embedImages[] = $part;
287 2
                    continue;
288
                }
289
            }
290
291 2
            if ($part instanceof MultiPartInterface) {
292 2
                $this->extractFromMimePart($part);
293
            }
294
        }
295 3
    }
296
}
297