MandrillApiTransport::__toString()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
namespace LeKoala\Mandrill;
4
5
use Mandrill;
6
use Exception;
7
use Psr\Log\LoggerInterface;
8
use Symfony\Component\Mime\Email;
9
use SilverStripe\Control\Director;
10
use Symfony\Component\Mailer\Envelope;
11
use SilverStripe\Assets\FileNameFilter;
12
use Symfony\Component\HttpClient\Response\MockResponse;
13
use Symfony\Component\Mailer\SentMessage;
14
use Symfony\Component\Mailer\Header\TagHeader;
15
use Symfony\Component\Mailer\Header\MetadataHeader;
16
use Symfony\Contracts\HttpClient\ResponseInterface;
17
use Symfony\Component\Mime\Header\UnstructuredHeader;
18
use Symfony\Contracts\HttpClient\HttpClientInterface;
19
use Symfony\Component\Mailer\Transport\AbstractApiTransport;
20
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
21
22
/**
23
 * We create our own class
24
 * We cannot extend easily due to private methods
25
 *
26
 * @link https://www.Mandrill.com/api#/reference/introduction
27
 * @link https://github.com/symfony/symfony/blob/6.3/src/Symfony/Component/Mailer/Bridge/Mailchimp/Transport/MandrillApiTransport.php
28
 * @author LeKoala <[email protected]>
29
 */
30
class MandrillApiTransport extends AbstractApiTransport
31
{
32
    private const HOST = 'mandrillapp.com';
33
34
    /**
35
     * @var Mandrill
36
     */
37
    private $apiClient;
38
39
    private $apiResult;
40
41
    public function __construct(Mandrill $apiClient, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null, LoggerInterface $logger = null)
42
    {
43
        $this->apiClient = $apiClient;
44
45
        parent::__construct($client, $dispatcher, $logger);
46
    }
47
48
    public function __toString(): string
49
    {
50
        return sprintf('mandrill+api://%s', $this->getEndpoint());
51
    }
52
53
    protected function doSendApi(SentMessage $sentMessage, Email $email, Envelope $envelope): ResponseInterface
54
    {
55
        $disableSending = $email->getHeaders()->has('X-SendingDisabled') || !MandrillHelper::getSendingEnabled();
56
57
        // We don't really care about the actual response
58
        $response = new MockResponse();
59
        if ($disableSending) {
60
            $result = [];
61
            foreach ($email->getTo() as $recipient) {
62
                $result[] = [
63
                    'email' => $recipient->toString(),
64
                    'status' => 'sent',
65
                    'reject_reason' => '',
66
                    '_id' => uniqid(),
67
                    'disabled' => true,
68
                ];
69
            }
70
        } else {
71
            $payload = $this->getPayload($email, $envelope);
72
            $result = $this->apiClient->messages->send($payload);
73
74
            $sendCount = 0;
75
            foreach ($result as $item) {
76
                if ($item['status'] === 'sent' || $item['status'] === 'queued') {
77
                    $sendCount++;
78
                }
79
            }
80
        }
81
82
        $this->apiResult = $result;
83
84
        $firstRecipient = reset($result);
85
        if ($firstRecipient) {
86
            $sentMessage->setMessageId($firstRecipient['_id']);
87
        }
88
89
        if (MandrillHelper::getLoggingEnabled()) {
90
            $this->logMessageContent($email, $result);
91
        }
92
93
        return $response;
94
    }
95
96
    public function getApiResult()
97
    {
98
        return $this->apiResult;
99
    }
100
101
    private function getEndpoint(): ?string
102
    {
103
        return ($this->host ?: self::HOST) . ($this->port ? ':' . $this->port : '');
104
    }
105
106
    private function getPayload(Email $email, Envelope $envelope): array
107
    {
108
        $message = array_merge(MandrillHelper::config()->default_params, [
109
            'html' => $email->getHtmlBody(),
110
            'text' => $email->getTextBody(),
111
            'subject' => $email->getSubject(),
112
            'from_email' => $envelope->getSender()->getAddress(),
113
            'to' => $this->getRecipients($email, $envelope),
114
        ]);
115
116
        if ('' !== $envelope->getSender()->getName()) {
117
            $message['from_name'] = $envelope->getSender()->getName();
118
        }
119
120
        foreach ($email->getAttachments() as $attachment) {
121
            $headers = $attachment->getPreparedHeaders();
122
            $disposition = $headers->getHeaderBody('Content-Disposition');
123
            $att = [
124
                'content' => $attachment->bodyToString(),
125
                'type' => $headers->get('Content-Type')->getBody(),
126
            ];
127
            if ($name = $headers->getHeaderParameter('Content-Disposition', 'name')) {
128
                $att['name'] = $name;
129
            }
130
            if ('inline' === $disposition) {
131
                $message['images'][] = $att;
132
            } else {
133
                $message['attachments'][] = $att;
134
            }
135
        }
136
137
        $headersToBypass = ['from', 'to', 'cc', 'bcc', 'subject', 'content-type'];
138
        foreach ($email->getHeaders()->all() as $name => $header) {
139
            if (\in_array($name, $headersToBypass, true)) {
140
                continue;
141
            }
142
            if ($header instanceof TagHeader) {
143
                $message['tags'] = array_merge(
144
                    $message['tags'] ?? [],
145
                    explode(',', $header->getValue())
146
                );
147
                continue;
148
            }
149
            if ($header instanceof MetadataHeader) {
150
                $message['metadata'][$header->getKey()] = $header->getValue();
151
                continue;
152
            }
153
            $message['headers'][$header->getName()] = $header->getBodyAsString();
154
        }
155
156
        foreach ($email->getHeaders()->all() as $name => $header) {
157
            if (!($header instanceof UnstructuredHeader)) {
158
                continue;
159
            }
160
            $headerValue = $header->getValue();
161
            switch ($name) {
162
                case 'List-Unsubscribe':
163
                    $message['headers']['List-Unsubscribe'] = $headerValue;
164
                    break;
165
                case 'X-MC-InlineCSS':
166
                    $message['inline_css'] = $headerValue;
167
                    break;
168
                case 'X-MC-Tags':
169
                    $tags = $headerValue;
170
                    if (!is_array($tags)) {
171
                        $tags = explode(',', $tags);
172
                    }
173
                    $message['tags'] = $tags;
174
                    break;
175
                case 'X-MC-Autotext':
176
                    $autoText = $headerValue;
177
                    if (in_array($autoText, array('true', 'on', 'yes', 'y', true), true)) {
178
                        $message['auto_text'] = true;
179
                    }
180
                    if (in_array($autoText, array('false', 'off', 'no', 'n', false), true)) {
181
                        $message['auto_text'] = false;
182
                    }
183
                    break;
184
                case 'X-MC-GoogleAnalytics':
185
                    $analyticsDomains = explode(',', $headerValue);
186
                    if (is_array($analyticsDomains)) {
187
                        $message['google_analytics_domains'] = $analyticsDomains;
188
                    }
189
                    break;
190
                case 'X-MC-GoogleAnalyticsCampaign':
191
                    $message['google_analytics_campaign'] = $headerValue;
192
                    break;
193
                default:
194
                    if (strncmp($header->getName(), 'X-', 2) === 0) {
195
                        $message['headers'][$header->getName()] = $headerValue;
196
                    }
197
                    break;
198
            }
199
        }
200
201
        return $message;
202
    }
203
204
    protected function getRecipients(Email $email, Envelope $envelope): array
205
    {
206
        $recipients = [];
207
        foreach ($envelope->getRecipients() as $recipient) {
208
            $type = 'to';
209
            if (\in_array($recipient, $email->getBcc(), true)) {
210
                $type = 'bcc';
211
            } elseif (\in_array($recipient, $email->getCc(), true)) {
212
                $type = 'cc';
213
            }
214
215
            $recipientPayload = [
216
                'email' => $recipient->getAddress(),
217
                'type' => $type,
218
            ];
219
220
            if ('' !== $recipient->getName()) {
221
                $recipientPayload['name'] = $recipient->getName();
222
            }
223
224
            $recipients[] = $recipientPayload;
225
        }
226
227
        return $recipients;
228
    }
229
230
    /**
231
     * Log message content
232
     *
233
     * @param Email $message
234
     * @param array $results Results from the api
235
     * @throws Exception
236
     */
237
    protected function logMessageContent(Email $message, $results = [])
238
    {
239
        // Folder not set
240
        $logFolder = MandrillHelper::getLogFolder();
241
        if (!$logFolder) {
242
            return;
243
        }
244
        // Logging disabled
245
        if (!MandrillHelper::getLoggingEnabled()) {
246
            return;
247
        }
248
249
        $subject = $message->getSubject();
250
        $body = $message->getBody();
251
        $contentType = $message->getHtmlBody() !== null ? "text/html" : "text";
252
253
        $logContent = $body;
254
        if (is_object($logContent)) {
255
            $logContent = $logContent->toString();
256
        }
257
258
        // Append some extra information at the end
259
        $logContent .= '<hr><pre>Debug infos:' . "\n\n";
260
        $logContent .= 'To : ' . print_r($message->getTo(), true) . "\n";
0 ignored issues
show
Bug introduced by
Are you sure print_r($message->getTo(), true) of type string|true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

260
        $logContent .= 'To : ' . /** @scrutinizer ignore-type */ print_r($message->getTo(), true) . "\n";
Loading history...
261
        $logContent .= 'Subject : ' . $subject . "\n";
262
        $logContent .= 'From : ' . print_r($message->getFrom(), true) . "\n";
0 ignored issues
show
Bug introduced by
Are you sure print_r($message->getFrom(), true) of type string|true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

262
        $logContent .= 'From : ' . /** @scrutinizer ignore-type */ print_r($message->getFrom(), true) . "\n";
Loading history...
263
        $logContent .= 'Headers:' . "\n" . $message->getHeaders()->toString() . "\n";
264
        if (!empty($params['recipients'])) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $params seems to never exist and therefore empty should always be true.
Loading history...
265
            $logContent .= 'Recipients : ' . print_r($message->getTo(), true) . "\n";
266
        }
267
        $logContent .= 'Results:' . "\n";
268
        $logContent .= print_r($results, true) . "\n";
0 ignored issues
show
Bug introduced by
Are you sure print_r($results, true) of type string|true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

268
        $logContent .= /** @scrutinizer ignore-type */ print_r($results, true) . "\n";
Loading history...
269
        $logContent .= '</pre>';
270
271
        // Generate filename
272
        $filter = new FileNameFilter();
273
        $title = substr($filter->filter($subject), 0, 35);
274
        $logName = date('Ymd_His') . '_' . $title;
275
276
        // Store attachments if any
277
        $attachments = $message->getAttachments();
278
        if (!empty($attachments)) {
279
            $logContent .= '<hr />';
280
            foreach ($attachments as $attachment) {
281
                $attachmentDestination = $logFolder . '/' . $logName . '_' . $attachment->getFilename();
282
                file_put_contents($attachmentDestination, $attachment->getBody());
283
                $logContent .= 'File : <a href="' . $attachmentDestination . '">' . $attachment->getFilename() . '</a><br/>';
284
            }
285
        }
286
287
        // Store it
288
        $ext = ($contentType == 'text/html') ? 'html' : 'txt';
289
        $r = file_put_contents($logFolder . '/' . $logName . '.' . $ext, $logContent);
290
291
        if (!$r && Director::isDev()) {
292
            throw new Exception('Failed to store email in ' . $logFolder);
293
        }
294
    }
295
}
296