Completed
Pull Request — master (#48)
by Frederik
05:48
created

MessageBodyCollection::extractFromMimePart()   C

Complexity

Conditions 12
Paths 9

Size

Total Lines 35
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 35
rs 5.1612
c 0
b 0
f 0
cc 12
eloc 20
nc 9
nop 1

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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