Completed
Push — master ( b92a49...e8f5e1 )
by Rasmus
12:47 queued 09:15
created

MIMEWriter::writeContentEncodingHeader()   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
3
namespace Kodus\Mail;
4
5
class MIMEWriter extends Writer
6
{
7 15
    public function writeMessage(Message $message)
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
    /**
34
     * @param Message $message
35
     */
36 15
    public function writeMessageHeaders(Message $message)
37
    {
38 15
        $this->writeHeader("Date", date("r", $message->getDate()));
39
40 15
        $this->writeAddressHeader("To", $message->getTo());
0 ignored issues
show
Bug introduced by
It seems like $message->getTo() targeting Kodus\Mail\Message::getTo() can also be of type object<Kodus\Mail\Address>; however, Kodus\Mail\MIMEWriter::writeAddressHeader() does only seem to accept array<integer,object<Kodus\Mail\Address>>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
41 15
        $this->writeAddressHeader("From", $message->getFrom());
0 ignored issues
show
Bug introduced by
It seems like $message->getFrom() targeting Kodus\Mail\Message::getFrom() can also be of type object<Kodus\Mail\Address>; however, Kodus\Mail\MIMEWriter::writeAddressHeader() does only seem to accept array<integer,object<Kodus\Mail\Address>>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
42 15
        $this->writeAddressHeader("Cc", $message->getCC());
0 ignored issues
show
Bug introduced by
It seems like $message->getCC() targeting Kodus\Mail\Message::getCC() can also be of type object<Kodus\Mail\Address>; however, Kodus\Mail\MIMEWriter::writeAddressHeader() does only seem to accept array<integer,object<Kodus\Mail\Address>>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
43 15
        $this->writeAddressHeader("Bcc", $message->getBCC());
0 ignored issues
show
Bug introduced by
It seems like $message->getBCC() targeting Kodus\Mail\Message::getBCC() can also be of type object<Kodus\Mail\Address>; however, Kodus\Mail\MIMEWriter::writeAddressHeader() does only seem to accept array<integer,object<Kodus\Mail\Address>>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
44 15
        $this->writeAddressHeader("Reply-To", $message->getReplyTo());
0 ignored issues
show
Bug introduced by
It seems like $message->getReplyTo() targeting Kodus\Mail\Message::getReplyTo() can also be of type object<Kodus\Mail\Address>; however, Kodus\Mail\MIMEWriter::writeAddressHeader() does only seem to accept array<integer,object<Kodus\Mail\Address>>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
45
46 15
        $sender = $message->getSender();
47
48 15
        if ($sender) {
49 3
            $this->writeAddressHeader("Sender", [$sender]);
50 3
        } else {
51 13
            $from = $message->getFrom();
52
53 13
            if (count($from) > 1) {
54
                $this->writeAddressHeader("Sender", [$from[0]]);
55
            } else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
56
                // The contents of this field would be completely redundant with the "From" field.
57
                // The "Sender" field need not be present, and its use is discouraged - it's therefore left out.
58
            }
59
        }
60
61 15
        $this->writeHeader("Subject", $message->getSubject());
62
63 15
        $this->writeHeader("MIME-Version", "1.0");
64
65 15
        foreach ($message->getHeaders() as $header) {
66 2
            $this->writeHeader($header->getName(), $header->getValue());
67 15
        }
68 15
    }
69
70
    /**
71
     * Write a multipart Message with Attachments
72
     *
73
     * @param Message $message
74
     */
75 15
    public function writeMessageWithAttachments(Message $message)
76
    {
77 15
        if (empty($message->getAttachments())) {
78 9
            $this->writeMessageBody($message);
79
80 9
            return;
81
        }
82
83 7
        $boundary = $this->createMultipartBoundaryName("mixed");
84
85 7
        $this->writeMixedContentTypeHeader($boundary);
86
87 7
        $this->writeLine();
88 7
        $this->writeLine("This is a multipart message in MIME format.");
89 7
        $this->writeLine();
90
91 7
        $this->writeMultipartBoundary($boundary);
92
93 7
        $this->writeMessageBody($message);
94
95 7
        foreach ($message->getAttachments() as $attachment) {
96 7
            $this->writeMultipartBoundary($boundary);
97 7
            $this->writeAttachmentPart($attachment);
98 7
        }
99
100 7
        $this->writeMultipartBoundaryEnd($boundary);
101 7
    }
102
103
    /**
104
     * Write the text and/or HTML message body parts
105
     *
106
     * @param Message $message
107
     */
108 15
    public function writeMessageBody(Message $message)
109
    {
110 15
        $text = $message->getText();
111 15
        $html = $message->getHTML();
112
113 15
        if (! empty($text)) {
114 11
            if (! empty($html)) {
115 5
                $boundary = $this->createMultipartBoundaryName("alternative");
116
117 5
                $this->writeAlternativeContentTypeHeader($boundary);
118 5
                $this->writeLine();
119
120 5
                $this->writeMultipartBoundary($boundary);
121 5
                $this->writeTextPart($text);
122
123 5
                $this->writeMultipartBoundary($boundary);
124 5
                $this->writeHTMLPart($html);
125
126 5
                $this->writeMultipartBoundaryEnd($boundary);
127 5
            } else {
128 7
                $this->writeTextPart($text);
129
            }
130 15
        } elseif (! empty($html)) {
131 5
            $this->writeHTMLPart($html);
132 5
        }
133 15
    }
134
135
    /**
136
     * Write the "Content-Type" header and the plain-text body in quoted-printable format
137
     *
138
     * @param string $content
139
     */
140 11
    public function writeTextPart($content)
141
    {
142 11
        $this->writeContentTypeHeader("text/plain; charset=UTF-8");
143 11
        $this->writeQuotedPrintableEncodingHeader();
144 11
        $this->writeLine();
145 11
        $this->writeQuotedPrintable($this->adjustLineBreaks($content));
146 11
        $this->writeLine();
147 11
    }
148
149
    /**
150
     * Write the "Content-Type" header and the plain-text body in quoted-printable format
151
     *
152
     * @param string $content
153
     */
154 9
    public function writeHTMLPart($content)
155
    {
156 9
        $this->writeContentTypeHeader("text/html; charset=UTF-8");
157 9
        $this->writeQuotedPrintableEncodingHeader();
158 9
        $this->writeLine();
159 9
        $this->writeQuotedPrintable($this->adjustLineBreaks($content));
160 9
        $this->writeLine();
161 9
    }
162
163
    /**
164
     * Write the "Content-Type" header and the Attachment Content in base-64 encoded format
165
     *
166
     * @param Attachment  $attachment
167
     * @param string|null Content ID (for inline Attachments)
168
     */
169 9
    public function writeAttachmentPart(Attachment $attachment, $content_id = null)
170
    {
171 9
        $filename = $attachment->getFilename();
172
173 9
        $this->writeContentTypeHeader($attachment->getMIMEType());
174 9
        $this->writeBase64EncodingHeader();
175
176 9
        if ($content_id === null) {
177 7
            $this->writeHeader("Content-Disposition", "attachment; filename=\"{$filename}\"");
178 7
        } else {
179
            // inline Attachment with Content ID:
180 5
            $this->writeHeader("Content-Disposition", "inline; filename=\"{$filename}\"");
181 5
            $this->writeHeader("Content-ID", "<{$content_id}>");
182
        }
183
184 9
        $this->writeLine();
185 9
        $this->writeBase64($attachment->getContent());
186 9
        $this->writeLine();
187 9
    }
188
189
    /**
190
     * @param string $boundary
191
     */
192 10
    public function writeMultipartBoundary($boundary)
193
    {
194 10
        $this->writeLine("--{$boundary}");
195 10
    }
196
197
    /**
198
     * @param string $boundary
199
     */
200 10
    public function writeMultipartBoundaryEnd($boundary)
201
    {
202 10
        $this->writeLine("--{$boundary}--");
203 10
    }
204
205
    /**
206
     * @param string $name
207
     * @param string $value
208
     */
209 15
    public function writeHeader($name, $value)
210
    {
211 15
        $value = $this->escapeHeaderValue($value);
212
213 15
        $this->writeLine("{$name}: {$value}");
214 15
    }
215
216
    /**
217
     * @param string    $name      header name
218
     * @param Address[] $addresses list of Address objects
219
     */
220 15
    public function writeAddressHeader($name, $addresses)
221
    {
222 15
        if (count($addresses)) {
223 15
            $this->writeHeader(
224 15
                $name,
225 15
                implode(
226 15
                    ", ",
227 15
                    array_map(
228 15 View Code Duplication
                        function (Address $address) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
229 15
                            $email = $address->getEmail();
230 15
                            $name = $address->getName();
231
232 15
                            return empty($name)
233 15
                                ? $email
234 15
                                : $this->escapeHeaderValue($name) . " <{$email}>";
235 15
                        },
236
                        $addresses
237 15
                    )
238 15
                )
239 15
            );
240 15
        }
241 15
    }
242
243
    /**
244
     * @param string $type
245
     */
246 15
    public function writeContentTypeHeader($type)
247
    {
248 15
        $this->writeHeader("Content-Type", $type);
249 15
    }
250
251
    /**
252
     * @param string $boundary
253
     */
254 7
    public function writeMixedContentTypeHeader($boundary)
255
    {
256 7
        $this->writeContentTypeHeader("multipart/mixed; boundary=\"{$boundary}\"");
257 7
    }
258
259
    /**
260
     * @param string $boundary
261
     */
262 5
    public function writeAlternativeContentTypeHeader($boundary)
263
    {
264 5
        $this->writeContentTypeHeader("multipart/alternative; boundary=\"{$boundary}\"");
265 5
    }
266
267
    /**
268
     * @param string $boundary
269
     */
270 5
    public function writeRelatedContentTypeHeader($boundary)
271
    {
272 5
        $this->writeContentTypeHeader("multipart/related; boundary=\"{$boundary}\"");
273 5
    }
274
275
    /**
276
     * Writes the "Content-Transfer-Encoding" header with value "quoted-printable"
277
     */
278 15
    public function writeQuotedPrintableEncodingHeader()
279
    {
280 15
        $this->writeContentEncodingHeader("quoted-printable");
281 15
    }
282
283
    /**
284
     * Writes the "Content-Transfer-Encoding" header with value "base64"
285
     */
286 9
    public function writeBase64EncodingHeader()
287
    {
288 9
        $this->writeContentEncodingHeader("base64");
289 9
    }
290
291
    /**
292
     * @param string $encoding encoding (e.g. "quoted-printable", "base64" or "8bit")
293
     */
294 15
    protected function writeContentEncodingHeader($encoding)
295
    {
296 15
        $this->writeHeader("Content-Transfer-Encoding", $encoding);
297 15
    }
298
299
    /**
300
     * Generates a unique MIME boundary name
301
     *
302
     * @param string $prefix static prefix (helps developers diagnose the output)
303
     *
304
     * @return string
305
     */
306 1
    protected function createMultipartBoundaryName($prefix)
307
    {
308 1
        static $boundary_index = 1;
309
310 1
        return "++++{$prefix}-" . sha1(microtime(true) . $boundary_index++) . "++++";
311
    }
312
313
    /**
314
     * Escape UTF-8 string (if necessary) for use in a header-value
315
     *
316
     * @param string $value
317
     *
318
     * @return string
319
     */
320 15
    protected function escapeHeaderValue($value)
321
    {
322 15
        return preg_match('/[\x80-\xFF]/', $value) === 1
323 15
            ? "=?UTF-8?Q?" . quoted_printable_encode($value) . "?="
324 15
            : $value; // as-is
325
    }
326
327
    /**
328
     * Adjusts line-breaks, correcting CR or LF as CRLF, to improve quoted-printable encoding.
329
     *
330
     * @param string $value
331
     *
332
     * @return string
333
     */
334 15
    protected function adjustLineBreaks($value)
335
    {
336 15
        return preg_replace('/(?>\r\n|\n|\r)/u', "\r\n", $value);
337
    }
338
}
339