MailgunSwiftTransport::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
namespace LeKoala\Mailgun;
4
5
use \Exception;
6
use \Swift_MimePart;
7
use \Swift_Transport;
8
use \Swift_Attachment;
9
use \Swift_Mime_Message;
10
use \Swift_Events_SendEvent;
11
use \Swift_Events_EventListener;
12
use Psr\Log\LoggerInterface;
13
use SilverStripe\Control\Director;
14
use SilverStripe\Assets\FileNameFilter;
15
use SilverStripe\Core\Injector\Injector;
16
use Mailgun\Mailgun;
17
18
/**
19
 * A Mailgun transport for Swift Mailer using the official client
20
 *
21
 * @author LeKoala <[email protected]>
22
 */
23
class MailgunSwiftTransport implements Swift_Transport
24
{
25
26
    /**
27
     * @var Swift_Transport_SimpleMailInvoker
28
     */
29
    protected $invoker;
30
31
    /**
32
     * @var Swift_Events_SimpleEventDispatcher
33
     */
34
    protected $eventDispatcher;
35
36
    /**
37
     * @var Mailgun
38
     */
39
    protected $client;
40
41
    /**
42
     * @var array
43
     */
44
    protected $resultApi;
45
46
    /**
47
     * @var string
48
     */
49
    protected $fromEmail;
50
51
    /**
52
     * @var boolean
53
     */
54
    protected $isStarted = false;
55
56
    public function __construct(Mailgun $client)
57
    {
58
        $this->client = $client;
59
60
        $this->invoker = new \Swift_Transport_SimpleMailInvoker();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Swift_Transport_SimpleMailInvoker() of type object<Swift_Transport_SimpleMailInvoker> is incompatible with the declared type object<LeKoala\Mailgun\S...port_SimpleMailInvoker> of property $invoker.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
61
        $this->eventDispatcher = new \Swift_Events_SimpleEventDispatcher();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Swift_Events_SimpleEventDispatcher() of type object<Swift_Events_SimpleEventDispatcher> is incompatible with the declared type object<LeKoala\Mailgun\S..._SimpleEventDispatcher> of property $eventDispatcher.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
62
    }
63
64
    /**
65
     * Not used
66
     */
67
    public function isStarted()
68
    {
69
        return $this->isStarted;
70
    }
71
72
    /**
73
     * Not used
74
     */
75
    public function start()
76
    {
77
        $this->isStarted = true;
78
    }
79
80
    /**
81
     * Not used
82
     */
83
    public function stop()
84
    {
85
        $this->isStarted = false;
86
    }
87
88
    /**
89
     * @param Swift_Mime_Message $message
90
     * @param null $failedRecipients
91
     * @return int Number of messages sent
92
     */
93
    public function send(Swift_Mime_Message $message, &$failedRecipients = null)
94
    {
95
        $this->resultApi = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $resultApi.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
96
        if ($event = $this->eventDispatcher->createSendEvent($this, $message)) {
97
            $this->eventDispatcher->dispatchEvent($event, 'beforeSendPerformed');
98
            if ($event->bubbleCancelled()) {
99
                return 0;
100
            }
101
        }
102
103
        $sendCount = 0;
0 ignored issues
show
Unused Code introduced by
$sendCount is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
104
        $disableSending = $message->getHeaders()->has('X-SendingDisabled') || MailgunHelper::config()->disable_sending;
105
106
        $emailData = $this->buildMessage($message);
107
108
        $client = $this->client;
109
110
        if ($disableSending) {
111
            $result = [
112
                'message' => 'Disabled',
113
                'id' => uniqid(),
114
            ];
115
            $queued = true;
116
        } else {
117
            $resultResponse = $client->messages()->send(MailgunHelper::getDomain(), $emailData);
118
            if ($resultResponse) {
119
                $result = [
120
                    'message' => $resultResponse->getMessage(),
121
                    'id' => $resultResponse->getId(),
122
                ];
123
                $queued = strpos($result['message'], 'Queued') !== false;
124
            }
125
        }
126
        $this->resultApi = $result;
0 ignored issues
show
Bug introduced by
The variable $result does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
127
128
        if (MailgunHelper::config()->enable_logging) {
129
            $this->logMessageContent($message, $result);
130
        }
131
132
        // Mailgun does not tell us how many messages are sent
133
        $sendCount = 1;
134
135
        // We don't know which recipients failed, so simply add fromEmail since it's the only one we know
136
        if (!$queued) {
0 ignored issues
show
Bug introduced by
The variable $queued does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
137
            $failedRecipients[] = $this->fromEmail;
138
            $sendCount = 0;
139
        }
140
141
        if ($event) {
142
            if ($sendCount > 0) {
143
                $event->setResult(Swift_Events_SendEvent::RESULT_SUCCESS);
144
            } else {
145
                $event->setResult(Swift_Events_SendEvent::RESULT_FAILED);
146
            }
147
148
            $this->eventDispatcher->dispatchEvent($event, 'sendPerformed');
149
        }
150
151
        return $sendCount;
152
    }
153
154
    /**
155
     * Log message content
156
     *
157
     * @param Swift_Mime_Message $message
158
     * @param array $results Results from the api
159
     * @return void
160
     */
161
    protected function logMessageContent(Swift_Mime_Message $message, $results = [])
162
    {
163
        $subject = $message->getSubject();
164
        $body = $message->getBody();
165
        $contentType = $this->getMessagePrimaryContentType($message);
166
167
        $logContent = $body;
168
169
        // Append some extra information at the end
170
        $logContent .= '<hr><pre>Debug infos:' . "\n\n";
171
        $logContent .= 'To : ' . print_r($message->getTo(), true) . "\n";
172
        $logContent .= 'Subject : ' . $subject . "\n";
173
        $logContent .= 'From : ' . print_r($message->getFrom(), true) . "\n";
174
        $logContent .= 'Headers:' . "\n";
175
        foreach ($message->getHeaders()->getAll() as $header) {
176
            $logContent .= '  ' . $header->getFieldName() . ': ' . $header->getFieldBody() . "\n";
177
        }
178
        if (!empty($message->getTo())) {
179
            $logContent .= 'Recipients : ' . print_r($message->getTo(), true) . "\n";
180
        }
181
        $logContent .= 'Results:' . "\n";
182
        foreach ($results as $resultKey => $resultValue) {
183
            $logContent .= '  ' . $resultKey . ': ' . $resultValue . "\n";
184
        }
185
        $logContent .= '</pre>';
186
187
        $logFolder = MailgunHelper::getLogFolder();
188
189
        // Generate filename
190
        $filter = new FileNameFilter();
191
        $title = substr($filter->filter($subject), 0, 35);
192
        $logName = date('Ymd_His') . '_' . $title;
193
194
        // Store attachments if any
195
        $attachments = $message->getChildren();
196
        if (!empty($attachments)) {
197
            $logContent .= '<hr />';
198
            foreach ($attachments as $attachment) {
199
                if ($attachment instanceof Swift_Attachment) {
200
                    $attachmentDestination = $logFolder . '/' . $logName . '_' . $attachment->getFilename();
201
                    file_put_contents($attachmentDestination, $attachment->getBody());
202
                    $logContent .= 'File : <a href="' . $attachmentDestination . '">' . $attachment->getFilename() . '</a><br/>';
203
                }
204
            }
205
        }
206
207
        // Store it
208
        $ext = ($contentType == 'text/html') ? 'html' : 'txt';
209
        $r = file_put_contents($logFolder . '/' . $logName . '.' . $ext, $logContent);
210
211
        if (!$r && Director::isDev()) {
212
            throw new Exception('Failed to store email in ' . $logFolder);
213
        }
214
    }
215
216
    /**
217
     * @return LoggerInterface
218
     */
219
    public function getLogger()
220
    {
221
        return Injector::inst()->get(LoggerInterface::class)->withName('Mailgun');
222
    }
223
224
    /**
225
     * @param Swift_Events_EventListener $plugin
226
     */
227
    public function registerPlugin(Swift_Events_EventListener $plugin)
228
    {
229
        $this->eventDispatcher->bindEventListener($plugin);
230
    }
231
232
    /**
233
     * @return array
234
     */
235
    protected function getSupportedContentTypes()
236
    {
237
        return array(
238
            'text/plain',
239
            'text/html'
240
        );
241
    }
242
243
    /**
244
     * @param string $contentType
245
     * @return bool
246
     */
247
    protected function supportsContentType($contentType)
248
    {
249
        return in_array($contentType, $this->getSupportedContentTypes());
250
    }
251
252
    /**
253
     * @param Swift_Mime_Message $message
254
     * @return string
255
     */
256
    protected function getMessagePrimaryContentType(Swift_Mime_Message $message)
257
    {
258
        $contentType = $message->getContentType();
259
260
        if ($this->supportsContentType($contentType)) {
261
            return $contentType;
262
        }
263
264
        // SwiftMailer hides the content type set in the constructor of Swift_Mime_Message as soon
265
        // as you add another part to the message. We need to access the protected property
266
        // _userContentType to get the original type.
267
        $messageRef = new \ReflectionClass($message);
268
        if ($messageRef->hasProperty('_userContentType')) {
269
            $propRef = $messageRef->getProperty('_userContentType');
270
            $propRef->setAccessible(true);
271
            $contentType = $propRef->getValue($message);
272
        }
273
274
        return $contentType;
275
    }
276
277
    /**
278
     * Convert a Swift Message for the api
279
     *
280
     * @param Swift_Mime_Message $message
281
     * @return array Mailgun Send Message
282
     * @throws \Swift_SwiftException
283
     */
284
    public function buildMessage(Swift_Mime_Message $message)
285
    {
286
        $contentType = $this->getMessagePrimaryContentType($message);
287
288
        $fromAddresses = $message->getFrom();
289
        $fromEmails = array_keys($fromAddresses);
0 ignored issues
show
Unused Code introduced by
$fromEmails is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
290
        $fromFirstEmail = key($fromAddresses);
291
        $fromFirstName = current($fromAddresses);
292
293
        if ($fromFirstName) {
294
            $this->fromEmail = sprintf('%s <%s>', $fromFirstName, $fromFirstEmail);
295
        } else {
296
            $this->fromEmail = $fromFirstEmail;
297
        }
298
299
        $toAddresses = $message->getTo();
300
        $ccAddresses = $message->getCc() ? $message->getCc() : [];
301
        $bccAddresses = $message->getBcc() ? $message->getBcc() : [];
302
        $replyToAddresses = $message->getReplyTo() ? $message->getReplyTo() : [];
303
304
        $recipients = array();
305
        $cc = array();
306
        $bcc = array();
307
        $attachments = array();
308
        $headers = array();
309
        $tags = array();
0 ignored issues
show
Unused Code introduced by
$tags is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
310
        $metadata = array();
0 ignored issues
show
Unused Code introduced by
$metadata is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
311
        $inlineCss = null;
312
313
        // Mandrill compatibility
314
        // Data is merge with transmission and removed from headers
315
        // @link https://mandrill.zendesk.com/hc/en-us/articles/205582467-How-to-Use-Tags-in-Mandrill
316 View Code Duplication
        if ($message->getHeaders()->has('X-MC-Tags')) {
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...
317
            $tagsHeader = $message->getHeaders()->get('X-MC-Tags');
318
            $tags = explode(',', $tagsHeader->getValue());
0 ignored issues
show
Unused Code introduced by
$tags is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
319
            $message->getHeaders()->remove('X-MC-Tags');
320
        }
321 View Code Duplication
        if ($message->getHeaders()->has('X-MC-Metadata')) {
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...
322
            $metadataHeader = $message->getHeaders()->get('X-MC-Metadata');
323
            $metadata = json_decode($metadataHeader->getValue(), JSON_OBJECT_AS_ARRAY);
0 ignored issues
show
Unused Code introduced by
$metadata is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
324
            $message->getHeaders()->remove('X-MC-Metadata');
325
        }
326
        if ($message->getHeaders()->has('X-MC-InlineCSS')) {
327
            $inlineCss = $message->getHeaders()->get('X-MC-InlineCSS')->getValue();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Swift_Mime_Header as the method getValue() does only exist in the following implementations of said interface: Swift_Mime_Headers_OpenDKIMHeader, Swift_Mime_Headers_ParameterizedHeader, Swift_Mime_Headers_UnstructuredHeader.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
328
            $message->getHeaders()->remove('X-MC-InlineCSS');
329
        }
330
331
        // Handle mailgun headers
332
        // Data is merge with message and removed from headers
333
        // @link https://documentation.mailgun.com/en/latest/user_manual.html#sending-via-smtp
334 View Code Duplication
        if ($message->getHeaders()->has('X-Mailgun-Tag')) {
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...
335
            $tagsHeader = $message->getHeaders()->get('X-Mailgun-Tag');
336
            $tags = explode(',', $tagsHeader->getValue());
0 ignored issues
show
Unused Code introduced by
$tags is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
337
            $message->getHeaders()->remove('X-Mailgun-Tag');
338
        }
339
        // @link https://documentation.mailgun.com/en/latest/user_manual.html#attaching-data-to-messages
340 View Code Duplication
        if ($message->getHeaders()->has('X-Mailgun-Variables')) {
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...
341
            $metadataHeader = $message->getHeaders()->get('X-Mailgun-Variables');
342
            $metadata = json_decode($metadataHeader->getValue(), JSON_OBJECT_AS_ARRAY);
0 ignored issues
show
Unused Code introduced by
$metadata is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
343
            $message->getHeaders()->remove('X-Mailgun-Variables');
344
        }
345
346
        // Build recipients
347
        $primaryEmail = null;
348
        foreach ($toAddresses as $toEmail => $toName) {
349
            if ($primaryEmail === null) {
350
                $primaryEmail = $toEmail;
351
            }
352
            if (!$toName) {
353
                $toName = $toEmail;
354
            }
355
            if ($toName) {
356
                $recipients[] = sprintf('%s <%s>', $toName, $toEmail);
357
            } else {
358
                $recipients[] = $toEmail;
359
            }
360
        }
361
362
        $reply_to = null;
363
        foreach ($replyToAddresses as $replyToEmail => $replyToName) {
364
            if ($replyToName) {
365
                $reply_to = sprintf('%s <%s>', $replyToName, $replyToEmail);
366
            } else {
367
                $reply_to = $replyToEmail;
368
            }
369
        }
370
371
        foreach ($ccAddresses as $ccEmail => $ccName) {
372
            if ($ccName) {
373
                $cc[] = sprintf('%s <%s>', $ccName, $ccEmail);
374
            } else {
375
                $cc[] = $ccEmail;
376
            }
377
        }
378
379
        foreach ($bccAddresses as $bccEmail => $bccName) {
380
            if ($bccName) {
381
                $bcc[] = sprintf('%s <%s>', $bccName, $bccEmail);
382
            } else {
383
                $bcc[] = $bccEmail;
384
            }
385
        }
386
387
        $bodyHtml = $bodyText = null;
388
389
        if ($contentType === 'text/plain') {
390
            $bodyText = $message->getBody();
391
        } elseif ($contentType === 'text/html') {
392
            $bodyHtml = $message->getBody();
393
        } else {
394
            $bodyHtml = $message->getBody();
395
        }
396
397
        foreach ($message->getChildren() as $child) {
398
            // File attachment. You can post multiple attachment values.
399
            // Important: You must use multipart/form-data encoding when sending attachments.
400
            if ($child instanceof Swift_Attachment) {
401
                // eg: 'attachment' => array('/path/to/file.txt', '/path/to/file.txt')
402
                $attachments[] = $child->getFilename();
403
            } elseif ($child instanceof Swift_MimePart && $this->supportsContentType($child->getContentType())) {
404
                if ($child->getContentType() == "text/html") {
405
                    $bodyHtml = $child->getBody();
406
                } elseif ($child->getContentType() == "text/plain") {
407
                    $bodyText = $child->getBody();
408
                }
409
            }
410
        }
411
412
        // If we ask to provide plain, use our custom method instead of the provided one
413
        if ($bodyHtml && MailgunHelper::config()->provide_plain) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $bodyHtml of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
414
            $bodyText = EmailUtils::convert_html_to_text($bodyHtml);
415
        }
416
417
        // Should we inline css
418
        if (!$inlineCss && MailgunHelper::config()->inline_styles) {
419
            $bodyHtml = EmailUtils::inline_styles($bodyHtml);
420
        }
421
422
        // Custom unsubscribe list
423
        if ($message->getHeaders()->has('List-Unsubscribe')) {
424
            $headers['List-Unsubscribe'] = $message->getHeaders()->get('List-Unsubscribe')->getValue();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Swift_Mime_Header as the method getValue() does only exist in the following implementations of said interface: Swift_Mime_Headers_OpenDKIMHeader, Swift_Mime_Headers_ParameterizedHeader, Swift_Mime_Headers_UnstructuredHeader.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
425
        }
426
427
        // Mailgun params format does not work well in yml, so we map them
428
        $rawParams = MailgunHelper::config()->default_params;
429
        $defaultParams =  [];
430
        foreach ($rawParams as $rawParamKey => $rawParamValue) {
431
            switch ($rawParamKey) {
432
                case 'inline':
433
                    $defaultParams['inline'] = $rawParamValue;
434
                    break;
435
                case 'tracking_opens':
436
                    $defaultParams['o:tracking-opens'] = $rawParamValue;
437
                    break;
438
                case 'tracking_clicks':
439
                    $defaultParams['o:tracking-clicks'] = $rawParamValue;
440
                    break;
441
                case 'testmode':
442
                    $defaultParams['o:testmode'] = $rawParamValue;
443
                    break;
444
                default:
445
                    $defaultParams[$rawParamKey] = $rawParamValue;
446
                    break;
447
            }
448
        }
449
450
        // Build base transmission
451
        $mailgunMessage = array(
452
            'to' => implode(',', $recipients),
453
            'from' => $this->fromEmail,
454
            'subject' => $message->getSubject(),
455
            'html' => $bodyHtml,
456
            'text' => $bodyText,
457
        );
458
        if ($reply_to) {
459
            $mailgunMessage['h:Reply-To'] = $reply_to;
460
        }
461
462
        // Add default params
463
        $mailgunMessage = array_merge($defaultParams, $mailgunMessage);
464
465
        // Add remaining elements
466
        if (!empty($cc)) {
467
            $mailgunMessage['cc'] = $cc;
468
        }
469
        if (!empty($bcc)) {
470
            $mailgunMessage['bcc'] = $bcc;
471
        }
472
        if (!empty($headers)) {
473
            foreach ($headers as $headerKey => $headerValue) {
474
                $mailgunMessage['h:' . $headerKey] = $headerValue;
475
            }
476
        }
477
        if (count($attachments) > 0) {
478
            $mailgunMessage['attachment'] = $attachments;
479
        }
480
481
        return $mailgunMessage;
482
    }
483
484
    /**
485
     * @return null|array
486
     */
487
    public function getResultApi()
488
    {
489
        return $this->resultApi;
490
    }
491
}
492