MIMEWriter   A
last analyzed

Complexity

Total Complexity 35

Size/Duplication

Total Lines 337
Duplicated Lines 0 %

Test Coverage

Coverage 98.75%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 115
dl 0
loc 337
ccs 158
cts 160
cp 0.9875
rs 9.6
c 1
b 0
f 0
wmc 35

21 Methods

Rating   Name   Duplication   Size   Complexity  
A writeContentEncodingHeader() 0 3 1
A adjustLineBreaks() 0 3 1
A writeMessage() 0 23 3
A writeContentTypeHeader() 0 3 1
A writeMixedContentTypeHeader() 0 3 1
A writeHeader() 0 5 1
A writeHTMLPart() 0 7 1
A writeRelatedContentTypeHeader() 0 3 1
A writeBase64EncodingHeader() 0 3 1
A writeAttachmentPart() 0 18 2
A writeMessageBody() 0 24 4
A writeQuotedPrintableEncodingHeader() 0 3 1
A writeAddressHeader() 0 17 3
A escapeHeaderValue() 0 7 2
A createMultipartBoundaryName() 0 5 1
A writeMultipartBoundaryEnd() 0 3 1
A writeMultipartBoundary() 0 3 1
A writeAlternativeContentTypeHeader() 0 3 1
A writeMessageWithAttachments() 0 26 3
A writeMessageHeaders() 0 37 4
A writeTextPart() 0 7 1
1
<?php
2
3
namespace Kodus\Mail;
4
5
class MIMEWriter extends Writer
6
{
7 15
    public function writeMessage(Message $message): void
8
    {
9 15
        $this->writeMessageHeaders($message);
10
11 15
        $inline_attachments = $message->getInlineAttachments();
12
13 15
        if (count($inline_attachments)) {
14 5
            $boundary = $this->createMultipartBoundaryName("related");
15
16 5
            $this->writeRelatedContentTypeHeader($boundary);
17 5
            $this->writeLine();
18
19 5
            $this->writeMultipartBoundary($boundary);
20 5
            $this->writeMessageWithAttachments($message);
21
22 5
            foreach ($inline_attachments as $inline) {
23 5
                $this->writeMultipartBoundary($boundary);
24 5
                $this->writeAttachmentPart($inline->getAttachment(), $inline->getContentID());
25 5
            }
26
27 5
            $this->writeMultipartBoundaryEnd($boundary);
28 5
        } else {
29 11
            $this->writeMessageWithAttachments($message);
30
        }
31 15
    }
32
33
    public function writeMessageHeaders(Message $message): void
34
    {
35
        $this->writeHeader("Date", $message->getDate()->format("r"));
36 15
37
        $this->writeAddressHeader("To", $message->getTo());
38 15
        $this->writeAddressHeader("From", $message->getFrom());
39
        $this->writeAddressHeader("Cc", $message->getCC());
40 15
        $this->writeAddressHeader("Reply-To", $message->getReplyTo());
41 15
42 15
        /**
43 15
         * NOTE: per section 3.6.3 of RFC2822, we do not write BCC recipients to headers.
44 15
         *
45
         * @see https://www.ietf.org/rfc/rfc2822.txt
46 15
         * @see https://github.com/kodus/mail/issues/10
47
         */
48 15
49 3
        $sender = $message->getSender();
50 3
51 13
        if ($sender) {
52
            $this->writeAddressHeader("Sender", [$sender]);
53 13
        } else {
54
            $from = $message->getFrom();
55
56
            if (count($from) > 1) {
57
                $this->writeAddressHeader("Sender", [$from[0]]);
58
            } else {
59
                // The contents of this field would be completely redundant with the "From" field.
60
                // The "Sender" field need not be present, and its use is discouraged - it's therefore left out.
61 15
            }
62
        }
63 15
64
        $this->writeHeader("Subject", $message->getSubject());
65 15
66 2
        $this->writeHeader("MIME-Version", "1.0");
67 15
68 15
        foreach ($message->getHeaders() as $header) {
69
            $this->writeHeader($header->getName(), $header->getValue());
70
        }
71
    }
72
73
    /**
74
     * Write a multipart Message with Attachments
75 15
     *
76
     * @param Message $message
77 15
     */
78 9
    public function writeMessageWithAttachments(Message $message): void
79
    {
80 9
        if (empty($message->getAttachments())) {
81
            $this->writeMessageBody($message);
82
83 7
            return;
84
        }
85 7
86
        $boundary = $this->createMultipartBoundaryName("mixed");
87 7
88 7
        $this->writeMixedContentTypeHeader($boundary);
89 7
90
        $this->writeLine();
91 7
        $this->writeLine("This is a multipart message in MIME format.");
92
        $this->writeLine();
93 7
94
        $this->writeMultipartBoundary($boundary);
95 7
96 7
        $this->writeMessageBody($message);
97 7
98 7
        foreach ($message->getAttachments() as $attachment) {
99
            $this->writeMultipartBoundary($boundary);
100 7
            $this->writeAttachmentPart($attachment);
101 7
        }
102
103
        $this->writeMultipartBoundaryEnd($boundary);
104
    }
105
106
    /**
107
     * Write the text and/or HTML message body parts
108 15
     *
109
     * @param Message $message
110 15
     */
111 15
    public function writeMessageBody(Message $message): void
112
    {
113 15
        $text = $message->getText();
114 11
        $html = $message->getHTML();
115 5
116
        if (! empty($text)) {
117 5
            if (! empty($html)) {
118 5
                $boundary = $this->createMultipartBoundaryName("alternative");
119
120 5
                $this->writeAlternativeContentTypeHeader($boundary);
121 5
                $this->writeLine();
122
123 5
                $this->writeMultipartBoundary($boundary);
124 5
                $this->writeTextPart($text);
125
126 5
                $this->writeMultipartBoundary($boundary);
127 5
                $this->writeHTMLPart($html);
128 7
129
                $this->writeMultipartBoundaryEnd($boundary);
130 15
            } else {
131 5
                $this->writeTextPart($text);
132 5
            }
133 15
        } elseif (! empty($html)) {
134
            $this->writeHTMLPart($html);
135
        }
136
    }
137
138
    /**
139
     * Write the "Content-Type" header and the plain-text body in quoted-printable format
140 11
     *
141
     * @param string $content
142 11
     */
143 11
    public function writeTextPart(string $content): void
144 11
    {
145 11
        $this->writeContentTypeHeader("text/plain; charset=UTF-8");
146 11
        $this->writeQuotedPrintableEncodingHeader();
147 11
        $this->writeLine();
148
        $this->writeQuotedPrintable($this->adjustLineBreaks($content));
149
        $this->writeLine();
150
    }
151
152
    /**
153
     * Write the "Content-Type" header and the plain-text body in quoted-printable format
154 9
     *
155
     * @param string $content
156 9
     */
157 9
    public function writeHTMLPart(string $content): void
158 9
    {
159 9
        $this->writeContentTypeHeader("text/html; charset=UTF-8");
160 9
        $this->writeQuotedPrintableEncodingHeader();
161 9
        $this->writeLine();
162
        $this->writeQuotedPrintable($this->adjustLineBreaks($content));
163
        $this->writeLine();
164
    }
165
166
    /**
167
     * Write the "Content-Type" header and the Attachment Content in base-64 encoded format
168
     *
169 9
     * @param Attachment  $attachment
170
     * @param string|null Content ID (for inline Attachments)
0 ignored issues
show
Bug introduced by
The type Kodus\Mail\Content was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
171 9
     */
172
    public function writeAttachmentPart(Attachment $attachment, ?string $content_id = null): void
173 9
    {
174 9
        $filename = $attachment->getFilename();
175
176 9
        $this->writeContentTypeHeader($attachment->getMIMEType());
177 7
        $this->writeBase64EncodingHeader();
178 7
179
        if ($content_id === null) {
180 5
            $this->writeHeader("Content-Disposition", "attachment; filename=\"{$filename}\"");
181 5
        } else {
182
            // inline Attachment with Content ID:
183
            $this->writeHeader("Content-Disposition", "inline; filename=\"{$filename}\"");
184 9
            $this->writeHeader("Content-ID", "<{$content_id}>");
185 9
        }
186 9
187 9
        $this->writeLine();
188
        $this->writeBase64($attachment->getContent());
189
        $this->writeLine();
190
    }
191
192 10
    /**
193
     * @param string $boundary
194 10
     */
195 10
    public function writeMultipartBoundary(string $boundary): void
196
    {
197
        $this->writeLine("--{$boundary}");
198
    }
199
200 10
    /**
201
     * @param string $boundary
202 10
     */
203 10
    public function writeMultipartBoundaryEnd(string $boundary): void
204
    {
205
        $this->writeLine("--{$boundary}--");
206
    }
207
208
    /**
209 15
     * @param string $name
210
     * @param string $value
211 15
     */
212
    public function writeHeader(string $name, string $value): void
213 15
    {
214 15
        $value = $this->escapeHeaderValue($value);
215
216
        $this->writeLine("{$name}: {$value}");
217
    }
218
219
    /**
220 15
     * @param string    $name      header name
221
     * @param Address[] $addresses list of Address objects
222 15
     */
223 15
    public function writeAddressHeader(string $name, array $addresses): void
224 15
    {
225 15
        if (count($addresses)) {
226 15
            $this->writeHeader(
227 15
                $name,
228 15
                implode(
229 15
                    ", ",
230 15
                    array_map(
231
                        function (Address $address) {
232 15
                            $email = $address->getEmail();
233 15
                            $name = $address->getName();
234 15
235 15
                            return empty($name)
236
                                ? $email
237 15
                                : $this->escapeHeaderValue($name) . " <{$email}>";
238 15
                        },
239 15
                        $addresses
240 15
                    )
241 15
                )
242
            );
243
        }
244
    }
245
246 15
    /**
247
     * @param string $type
248 15
     */
249 15
    public function writeContentTypeHeader(string $type): void
250
    {
251
        $this->writeHeader("Content-Type", $type);
252
    }
253
254 7
    /**
255
     * @param string $boundary
256 7
     */
257 7
    public function writeMixedContentTypeHeader(string $boundary): void
258
    {
259
        $this->writeContentTypeHeader("multipart/mixed; boundary=\"{$boundary}\"");
260
    }
261
262 5
    /**
263
     * @param string $boundary
264 5
     */
265 5
    public function writeAlternativeContentTypeHeader(string $boundary): void
266
    {
267
        $this->writeContentTypeHeader("multipart/alternative; boundary=\"{$boundary}\"");
268
    }
269
270 5
    /**
271
     * @param string $boundary
272 5
     */
273 5
    public function writeRelatedContentTypeHeader(string $boundary): void
274
    {
275
        $this->writeContentTypeHeader("multipart/related; boundary=\"{$boundary}\"");
276
    }
277
278 15
    /**
279
     * Writes the "Content-Transfer-Encoding" header with value "quoted-printable"
280 15
     */
281 15
    public function writeQuotedPrintableEncodingHeader(): void
282
    {
283
        $this->writeContentEncodingHeader("quoted-printable");
284
    }
285
286 9
    /**
287
     * Writes the "Content-Transfer-Encoding" header with value "base64"
288 9
     */
289 9
    public function writeBase64EncodingHeader(): void
290
    {
291
        $this->writeContentEncodingHeader("base64");
292
    }
293
294 15
    /**
295
     * @param string $encoding encoding (e.g. "quoted-printable", "base64" or "8bit")
296 15
     */
297 15
    protected function writeContentEncodingHeader($encoding): void
298
    {
299
        $this->writeHeader("Content-Transfer-Encoding", $encoding);
300
    }
301
302
    /**
303
     * Generates a unique MIME boundary name
304
     *
305
     * @param string $prefix static prefix (helps developers diagnose the output)
306 1
     *
307
     * @return string
308 1
     */
309
    protected function createMultipartBoundaryName(string $prefix): string
310 1
    {
311
        static $boundary_index = 1;
312
313
        return "++++{$prefix}-" . sha1(microtime(true) . $boundary_index++) . "++++";
314
    }
315
316
    /**
317
     * Escape UTF-8 string (if necessary) for use in a header-value
318
     *
319
     * @param string $value
320 15
     *
321
     * @return string
322 15
     */
323 15
    protected function escapeHeaderValue(string $value): string
324 15
    {
325
        $quoted_value = quoted_printable_encode($value);
326
        $quoted_value = str_replace("=\x0d\x0a",'', $quoted_value);
327
        return preg_match('/[\x80-\xFF]/', $value) === 1
328
            ? "=?UTF-8?Q?" . $quoted_value . "?="
329
            : $value; // as-is
330
    }
331
332
    /**
333
     * Adjusts line-breaks, correcting CR or LF as CRLF, to improve quoted-printable encoding.
334 15
     *
335
     * @param string $value
336 15
     *
337
     * @return string
338
     */
339
    protected function adjustLineBreaks(string $value): string
340
    {
341
        return preg_replace('/(?>\r\n|\n|\r)/u', "\r\n", $value);
342
    }
343
}
344