Completed
Push — master ( 291fe5...d05493 )
by Shingo
01:42 queued 12s
created

SendgridTransport::getAttachments()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 9.3222
c 0
b 0
f 0
cc 5
nc 3
nop 1
1
<?php
2
namespace Sichikawa\LaravelSendgridDriver\Transport;
3
4
use GuzzleHttp\Client;
5
use GuzzleHttp\ClientInterface;
6
use GuzzleHttp\Exception\GuzzleException;
7
use Illuminate\Mail\Transport\Transport;
8
use Illuminate\Support\Arr;
9
use Psr\Http\Message\ResponseInterface;
10
use Sichikawa\LaravelSendgridDriver\SendGrid;
11
use Swift_Attachment;
12
use Swift_Image;
13
use Swift_Mime_SimpleMessage;
14
use Swift_MimePart;
15
16
class SendgridTransport extends Transport
17
{
18
    use SendGrid {
19
        sgDecode as decode;
20
    }
21
22
    const SMTP_API_NAME = 'sendgrid/x-smtpapi';
23
    const BASE_URL = 'https://api.sendgrid.com/v3/mail/send';
24
25
    /**
26
     * @var Client
27
     */
28
    private $client;
29
    private $attachments;
30
    private $numberOfRecipients;
31
    private $apiKey;
32
    private $endpoint;
33
34
    public function __construct(ClientInterface $client, $api_key, $endpoint = null)
35
    {
36
        $this->client = $client;
0 ignored issues
show
Documentation Bug introduced by
$client is of type object<GuzzleHttp\ClientInterface>, but the property $client was declared to be of type object<GuzzleHttp\Client>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
37
        $this->apiKey = $api_key;
38
        $this->endpoint = isset($endpoint) ? $endpoint : self::BASE_URL;
39
    }
40
41
    /**
42
     * {@inheritdoc}
43
     */
44
    public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null)
45
    {
46
        $this->beforeSendPerformed($message);
47
48
        $data = [
49
            'personalizations' => $this->getPersonalizations($message),
50
            'from'             => $this->getFrom($message),
51
            'subject'          => $message->getSubject(),
52
        ];
53
54
        if ($contents = $this->getContents($message)) {
55
            $data['content'] = $contents;
56
        }
57
58
        if ($reply_to = $this->getReplyTo($message)) {
59
            $data['reply_to'] = $reply_to;
60
        }
61
62
        $attachments = $this->getAttachments($message);
63
        if (count($attachments) > 0) {
64
            $data['attachments'] = $attachments;
65
        }
66
67
        $data = $this->setParameters($message, $data);
68
69
        $payload = [
70
            'headers' => [
71
                'Authorization' => 'Bearer ' . $this->apiKey,
72
                'Content-Type' => 'application/json',
73
            ],
74
            'json' => $data,
75
        ];
76
77
        $response = $this->post($payload);
78
79
        $message->getHeaders()->addTextHeader('X-Sendgrid-Message-Id', $response->getHeaderLine('X-Message-Id'));
80
81
        $this->sendPerformed($message);
82
83
        return $this->numberOfRecipients ?: $this->numberOfRecipients($message);
84
    }
85
86
    /**
87
     * @param Swift_Mime_SimpleMessage $message
88
     * @return array
89
     */
90
    private function getPersonalizations(Swift_Mime_SimpleMessage $message)
91
    {
92
        $setter = function (array $addresses) {
93
            $recipients = [];
94
            foreach ($addresses as $email => $name) {
95
                $address = [];
96
                $address['email'] = $email;
97
                if ($name) {
98
                    $address['name'] = $name;
99
                }
100
                $recipients[] = $address;
101
            }
102
            return $recipients;
103
        };
104
105
        $personalization['to'] = $setter($message->getTo());
0 ignored issues
show
Coding Style Comprehensibility introduced by
$personalization was never initialized. Although not strictly required by PHP, it is generally a good practice to add $personalization = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
106
107
        if ($cc = $message->getCc()) {
108
            $personalization['cc'] = $setter($cc);
109
        }
110
111
        if ($bcc = $message->getBcc()) {
112
            $personalization['bcc'] = $setter($bcc);
113
        }
114
115
        return [$personalization];
116
    }
117
118
    /**
119
     * Get From Addresses.
120
     *
121
     * @param Swift_Mime_SimpleMessage $message
122
     * @return array
123
     */
124
    private function getFrom(Swift_Mime_SimpleMessage $message)
125
    {
126
        if ($message->getFrom()) {
127
            foreach ($message->getFrom() as $email => $name) {
128
                return ['email' => $email, 'name' => $name];
129
            }
130
        }
131
        return [];
132
    }
133
134
    /**
135
     * Get ReplyTo Addresses.
136
     *
137
     * @param Swift_Mime_SimpleMessage $message
138
     * @return array
139
     */
140
    private function getReplyTo(Swift_Mime_SimpleMessage $message)
141
    {
142
        if ($message->getReplyTo()) {
143
            foreach ($message->getReplyTo() as $email => $name) {
0 ignored issues
show
Bug introduced by
The expression $message->getReplyTo() of type string is not traversable.
Loading history...
144
                return ['email' => $email, 'name' => $name];
145
            }
146
        }
147
        return null;
148
    }
149
150
    /**
151
     * Get contents.
152
     *
153
     * @param Swift_Mime_SimpleMessage $message
154
     * @return array
155
     */
156
    private function getContents(Swift_Mime_SimpleMessage $message)
157
    {
158
        $contentType = $message->getContentType();
159
        switch ($contentType) {
160
            case 'text/plain':
161
                return [
162
                    [
163
                        'type'  => 'text/plain',
164
                        'value' => $message->getBody(),
165
166
                    ],
167
                ];
168
            case 'text/html':
169
                return [
170
                    [
171
                        'type'  => 'text/html',
172
                        'value' => $message->getBody(),
173
                    ],
174
                ];
175
        }
176
177
        // Following RFC 1341, text/html after text/plain in multipart
178
        $content = [];
179
        foreach ($message->getChildren() as $child) {
180
            if ($child instanceof Swift_MimePart && $child->getContentType() === 'text/plain') {
181
                $content[] = [
182
                    'type'  => 'text/plain',
183
                    'value' => $child->getBody(),
184
                ];
185
            }
186
        }
187
188
        if (is_null($message->getBody())) {
189
            return null;
190
        }
191
192
        $content[] = [
193
            'type'  => 'text/html',
194
            'value' => $message->getBody(),
195
        ];
196
        return $content;
197
    }
198
199
    /**
200
     * @param Swift_Mime_SimpleMessage $message
201
     * @return array
202
     */
203
    private function getAttachments(Swift_Mime_SimpleMessage $message)
204
    {
205
        $attachments = [];
206
        foreach ($message->getChildren() as $attachment) {
207
            if ((!$attachment instanceof Swift_Attachment && !$attachment instanceof Swift_Image)
208
                || $attachment->getFilename() === self::SMTP_API_NAME
209
            ) {
210
                continue;
211
            }
212
            $attachments[] = [
213
                'content'     => base64_encode($attachment->getBody()),
214
                'filename'    => $attachment->getFilename(),
215
                'type'        => $attachment->getContentType(),
216
                'disposition' => $attachment->getDisposition(),
217
                'content_id'  => $attachment->getId(),
218
            ];
219
        }
220
        return $this->attachments = $attachments;
221
    }
222
223
    /**
224
     * Set Request Body Parameters
225
     *
226
     * @param Swift_Mime_SimpleMessage $message
227
     * @param array $data
228
     * @return array
229
     * @throws \Exception
230
     */
231
    protected function setParameters(Swift_Mime_SimpleMessage $message, $data)
232
    {
233
        $this->numberOfRecipients = 0;
234
235
        $smtp_api = [];
236
        foreach ($message->getChildren() as $attachment) {
237
            if (!$attachment instanceof Swift_Image
238
                || !in_array(self::SMTP_API_NAME, [$attachment->getFilename(), $attachment->getContentType()])
239
            ) {
240
                continue;
241
            }
242
            $smtp_api = self::decode($attachment->getBody());
243
        }
244
245
        if (!is_array($smtp_api)) {
246
            return $data;
247
        }
248
249
        foreach ($smtp_api as $key => $val) {
250
251
            switch ($key) {
252
253
                case 'api_key':
254
                    $this->apiKey = $val;
255
                    continue 2;
256
257
                case 'personalizations':
258
                    $this->setPersonalizations($data, $val);
259
                    continue 2;
260
261
                case 'attachments':
262
                    $val = array_merge($this->attachments, $val);
263
                    break;
264
265
                case 'unique_args':
266
                    throw new \Exception('Sendgrid v3 now uses custom_args instead of unique_args');
267
268
                case 'custom_args':
269
                    foreach ($val as $name => $value) {
270
                        if (!is_string($value)) {
271
                            throw new \Exception('Sendgrid v3 custom arguments have to be a string.');
272
                        }
273
                    }
274
                    break;
275
276
            }
277
278
            Arr::set($data, $key, $val);
279
        }
280
        return $data;
281
    }
282
283
    private function setPersonalizations(&$data, $personalizations)
284
    {
285
        foreach ($personalizations as $index => $params) {
286
            foreach ($params as $key => $val) {
287
                if (in_array($key, ['to', 'cc', 'bcc'])) {
288
                    Arr::set($data, 'personalizations.' . $index . '.' . $key, [$val]);
289
                    ++$this->numberOfRecipients;
290
                } else {
291
                    Arr::set($data, 'personalizations.' . $index . '.' . $key, $val);
292
                }
293
            }
294
        }
295
    }
296
297
    /**
298
     * @param $payload
299
     * @return ResponseInterface
300
     * @throws GuzzleException
301
     */
302
    private function post($payload)
303
    {
304
        return $this->client->request('POST', $this->endpoint, $payload);
305
    }
306
}
307