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

Post::extractFromMimePart()   C

Complexity

Conditions 12
Paths 9

Size

Total Lines 35
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 12

Importance

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

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