Completed
Push — master ( a3e512...b58337 )
by Chris
02:43 queued 01:00
created

MailCourier::buildAttachments()   A

Complexity

Conditions 5
Paths 9

Size

Total Lines 40
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 5.0187

Importance

Changes 0
Metric Value
eloc 23
dl 0
loc 40
ccs 20
cts 22
cp 0.9091
rs 9.2408
c 0
b 0
f 0
cc 5
nc 9
nop 1
crap 5.0187
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Courier;
6
7
use Courier\Exceptions\TransmissionException;
8
use Courier\Exceptions\UnsupportedContentException;
9
use PhpEmail\Address;
10
use PhpEmail\Content\Contracts\SimpleContent;
11
use PhpEmail\Content\Contracts\TemplatedContent;
12
use PhpEmail\Email;
13
14
/**
15
 * A courier that leverages the built-in `mail` function to deliver emails.
16
 *
17
 * This Courier is meant as a drop-in testing option for local development. The courier does not implement template
18
 * rendering, but will still deliver templated emails, describing the template ID and data provided. This functionally
19
 * can be extended by overwriting or wrapping the class, if desired.
20
 */
21
class MailCourier implements Courier
22
{
23
    /**
24
     * @param Email $email
25
     *
26
     * @throws TransmissionException
27
     * @throws UnsupportedContentException
28
     *
29
     * @return void
30
     */
31 2
    public function deliver(Email $email): void
32
    {
33 2
        if (!$this->supportsContent($email)) {
34
            throw new UnsupportedContentException($email->getContent());
35
        }
36
37 2
        $boundary = '==Multipart_Boundary_' . bin2hex(random_bytes(8));
38
39
        $headers = [
40 2
            'From: ' . $email->getFrom()->toRfc2822(),
41 2
            'Content-Type: multipart/alternative;boundary="' . $boundary . '"',
42
        ];
43
44 2
        if ($email->getCcRecipients()) {
45 1
            $headers[] = 'Cc: ' . $this->mapAddresses($email->getCcRecipients());
46
        }
47
48 2
        if ($email->getBccRecipients()) {
49 1
            $headers[] = 'Bcc: ' . $this->mapAddresses($email->getBccRecipients());
50
        }
51
52 2
        if ($email->getReplyTos()) {
53 1
            $headers[] = 'Reply-To: ' . $this->mapAddresses($email->getReplyTos());
54
        }
55
56 2
        if ($email->getHeaders()) {
57 1
            foreach ($email->getHeaders() as $header) {
58 1
                $headers[] = sprintf('%s: %s', $header->getField(), $header->getValue());
59
            }
60
        }
61
62 2
        $result = mail(
63 2
            $this->mapAddresses($email->getToRecipients()),
64 2
            $email->getSubject() ?? '',
65 2
            "--{$boundary}\r\n" . implode("\r\n--{$boundary}\r\n", array_merge($this->buildContent($email), $this->buildAttachments($email))),
66 2
            implode("\r\n", $headers)
67
        );
68
69 2
        if (!$result) {
70
            throw new TransmissionException(0, new \Exception(error_get_last()['message'] ?? 'Unknown mail error'));
71
        }
72
    }
73
74 2
    public function supportsContent(Email $email): bool
75
    {
76 2
        foreach ($this->supportedContent() as $type) {
77 2
            if ($email->getContent() instanceof $type) {
78 2
                return true;
79
            }
80
        }
81
82
        return false;
83
    }
84
85 2
    protected function supportedContent(): array
86
    {
87
        return [
88 2
            SimpleContent::class,
89
            TemplatedContent::class,
90
        ];
91
    }
92
93
    /**
94
     * Build the MIME parts for the content.
95
     *
96
     * @param Email $email
97
     *
98
     * @return array
99
     */
100 2
    protected function buildContent(Email $email): array
101
    {
102 2
        $content = $email->getContent();
103
104 2
        if ($content instanceof SimpleContent) {
105 1
            return $this->buildSimpleContent($content);
106 1
        } elseif ($content instanceof TemplatedContent) {
107 1
            return $this->buildTemplatedContent($content);
108
        }
109
110
        throw new UnsupportedContentException($content);
111
    }
112
113 1
    protected function buildSimpleContent(SimpleContent $content): array
114
    {
115 1
        $parts = [];
116
117 1
        if ($content->getText()) {
118 1
            $parts[] = <<<MIME
119 1
Content-Type: text/plain; {$content->getText()->getCharset()}
120
121 1
{$content->getText()->getBody()}
122
MIME;
123
        }
124
125 1
        if ($content->getHtml()) {
126 1
            $parts[] = <<<MIME
127 1
Content-Type: text/html; {$content->getHtml()->getCharset()}
128
129 1
{$content->getHtml()->getBody()}
130
MIME;
131
        }
132
133 1
        return $parts;
134
    }
135
136
    /**
137
     * Build the text representation of the template ID and data.
138
     *
139
     * @param TemplatedContent $content
140
     *
141
     * @return array
142
     */
143 1
    protected function buildTemplatedContent(TemplatedContent $content): array
144
    {
145 1
        $parts        = [];
146 1
        $templateData = json_encode($content->getTemplateData(), JSON_PRETTY_PRINT);
147
148 1
        $parts[] = <<<MIME
149
Content-Type: text/plain; utf-8
150
151 1
Template ID: {$content->getTemplateId()}
152
Template Data:
153
154 1
{$templateData}
155
MIME;
156
157 1
        return $parts;
158
    }
159
160
    private function mapAddresses(array $addresses): string
161
    {
162 2
        return implode(', ', array_map(function (Address $address) {
163 2
            return $address->toRfc2822();
164 2
        }, $addresses));
165
    }
166
167
    /**
168
     * Create the MIME parts for each of the attachments.
169
     *
170
     * @param Email $email
171
     *
172
     * @return array
173
     */
174 2
    private function buildAttachments(Email $email): array
175
    {
176 2
        $parts = [];
177
178 2
        foreach ($email->getAttachments() as $attachment) {
179 1
            $content     = chunk_split($attachment->getBase64Content());
180 1
            $contentType = $attachment->getContentType();
181
182 1
            if ($attachment->getCharset()) {
183
                $contentType .= ';' . $attachment->getCharset();
184
            }
185
186 1
            $parts[] = <<<MIME
187 1
Content-Type: {$contentType}
188
Content-Transfer-Encoding: base64
189 1
Content-Disposition: attachment; filename="{$attachment->getName()}"
190
191 1
$content
192
MIME;
193
        }
194
195 2
        foreach ($email->getEmbedded() as $attachment) {
196 1
            $content     = chunk_split($attachment->getBase64Content());
197 1
            $contentType = $attachment->getContentType();
198
199 1
            if ($attachment->getCharset()) {
200
                $contentType .= ';' . $attachment->getCharset();
201
            }
202
203 1
            $parts[] = <<<MIME
204 1
Content-Type: {$contentType}
205
Content-Transfer-Encoding: base64
206 1
Content-Disposition: inline; filename="{$attachment->getName()}"
207 1
Content-ID: <{$attachment->getContentId()}>
208
209 1
$content
210
MIME;
211
        }
212
213 2
        return $parts;
214
    }
215
}
216