Completed
Push — master ( 31b88f...fdf438 )
by Frederik
01:49
created

MimeMessageFactory   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 133
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 13

Test Coverage

Coverage 88.71%

Importance

Changes 0
Metric Value
wmc 17
lcom 1
cbo 13
dl 0
loc 133
ccs 55
cts 62
cp 0.8871
rs 10
c 0
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
A createMessage() 0 25 3
B createPartStream() 0 36 6
A createHeaderLines() 0 19 2
B pickOptimalCharset() 0 30 6
1
<?php
2
declare(strict_types=1);
3
4
namespace Genkgo\Mail;
5
6
use Genkgo\Mail\Header\ParsedHeader;
7
use Genkgo\Mail\Header\HeaderLine;
8
use Genkgo\Mail\Header\HeaderName;
9
use Genkgo\Mail\Header\HeaderValueParameter;
10
use Genkgo\Mail\Mime\MultiPartInterface;
11
use Genkgo\Mail\Mime\PartInterface;
12
use Genkgo\Mail\Stream\ConcatenatedStream;
13
use Genkgo\Mail\Stream\StringStream;
14
15
final class MimeMessageFactory
16
{
17
    /**
18
     * @param PartInterface $part
19
     * @return MessageInterface
20
     */
21 25
    public function createMessage(PartInterface $part): MessageInterface
22
    {
23 25
        $message = new GenericMessage();
24
25 25
        $part = $this->pickOptimalCharset($part);
26
27 25
        foreach ($part->getHeaders() as $header) {
28 25
            $message = $message->withHeader($header);
29
        }
30
31 25
        if ($part instanceof MultiPartInterface) {
32 16
            $message = $message->withBody(
33 16
                new ConcatenatedStream(
34 16
                    new \ArrayObject([
35 16
                        new StringStream("This is a multipart message in MIME format.\r\n"),
36 16
                        $this->createPartStream($part, false)
37
                    ])
38
                )
39
            );
40
        } else {
41 9
            $message = $message->withBody($this->createPartStream($part, false));
42
        }
43
44 25
        return $message;
45
    }
46
47
    /**
48
     * @param PartInterface $part
49
     * @param bool $headers
50
     * @return StreamInterface
51
     */
52 25
    private function createPartStream(PartInterface $part, bool $headers = true): StreamInterface
53
    {
54 25
        $part = $this->pickOptimalCharset($part);
55
56 25
        $partBody = $part->getBody();
57 25
        $streams = new \ArrayObject();
58 25
        if ($headers) {
59 16
            $streams->append(new StringStream($this->createHeaderLines($part)));
60 16
            $streams->append(new StringStream("\r\n\r\n"));
61
        }
62
63 25
        $streams->append($partBody);
64
65 25
        if ($part instanceof MultiPartInterface) {
66 16
            $childParts = $part->getParts();
67 16
            $boundary = $part->getBoundary();
68
69 16
            foreach ($childParts as $childPart) {
70 16
                $streams->append(new StringStream("\r\n"));
71 16
                $streams->append(new StringStream('--' . (string) $boundary));
72 16
                $streams->append(new StringStream("\r\n"));
73 16
                $streams->append($this->createPartStream($childPart));
74
            }
75
76 16
            if ($childParts) {
77 16
                $streams->append(new StringStream('--' . (string) $boundary . '--'));
78 16
                $streams->append(new StringStream("\r\n"));
79
            }
80
        }
81
82 25
        if ($partBody->getSize() !== 0) {
83 19
            $streams->append(new StringStream("\r\n"));
84
        }
85
86 25
        return new ConcatenatedStream($streams);
87
    }
88
89
    /**
90
     * @param PartInterface $part
91
     * @return string
92
     */
93 16
    private function createHeaderLines(PartInterface $part): string
94
    {
95 16
        $headers = $part->getHeaders();
96 16
        if (!\is_array($headers)) {
97
            $headers = \iterator_to_array($headers);
98
        }
99
100 16
        return \implode(
101 16
            "\r\n",
102 16
            \array_values(
103 16
                \array_map(
104
                    function (HeaderInterface $header) {
105 16
                        return (string) (new HeaderLine($header));
106 16
                    },
107 16
                    $headers
108
                )
109
            )
110
        );
111
    }
112
113
    /**
114
     * @param PartInterface $part
115
     * @return PartInterface
116
     */
117 25
    private function pickOptimalCharset(PartInterface $part): PartInterface
118
    {
119
        try {
120 25
            $transferEncoding = (string) $part->getHeader('Content-Transfer-Encoding')->getValue();
121 16
        } catch (\UnexpectedValueException $e) {
122 16
            return $part;
123
        }
124
125 25
        $contentTypeHeader = $part->getHeader('Content-Type')->getValue();
126 25
        if ($transferEncoding !== '7bit' || \substr((string)$contentTypeHeader, 0, 5) !== 'text/') {
127 15
            return $part;
128
        }
129
130
        try {
131 23
            if ($contentTypeHeader->getParameter('charset')->getValue() === 'us-ascii') {
132 23
                return $part;
133
            }
134
        } catch (\UnexpectedValueException $e) {
135
            return $part->withHeader(
136
                new ParsedHeader(
137
                    new HeaderName('Content-Type'),
138
                    $contentTypeHeader->withParameter(
139
                        new HeaderValueParameter('charset', 'us-ascii')
140
                    )
141
                )
142
            );
143
        }
144
145 15
        return $part;
146
    }
147
}
148