Completed
Push — master ( 24c4c4...adb0f2 )
by Frederik
01:59
created

MimeMessageFactory::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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