GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

SendgridTransport::initMail()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
ccs 0
cts 3
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Nip\Mail\Transport;
4
5
use Exception;
6
use Html2Text\Html2Text;
7
use Nip\Mail\Message;
8
use SendGrid;
9
use SendGrid\Attachment;
10
use SendGrid\Content;
11
use SendGrid\Email;
12
use SendGrid\Mail;
13
use SendGrid\Personalization;
14
use SendGrid\ReplyTo;
15
use Swift_Attachment;
16
use Swift_Image;
17
use Swift_Mime_Message as MessageInterface;
18
use Swift_MimePart;
19
use Swift_TransportException;
20
21
/**
22
 * Class SendgridTransport
23
 * @package Nip\Mail\Transport
24
 */
25
class SendgridTransport extends AbstractTransport
26
{
27
    /** @var string|null */
28
    protected $apiKey;
29
30
    /**
31
     * @var null|Mail|MessageInterface
32
     */
33
    protected $mail = null;
34
35
    /**
36
     * {@inheritdoc}
37
     */
38
    public function send(\Swift_Mime_Message $message, &$failedRecipients = null)
39
    {
40
        $this->initMail();
41
42
        $this->populateSenders($message);
43
        $this->populatePersonalization($message);
44
        $this->populateContent($message);
45
        $this->populateCustomArg($message);
46
47
        $sg = $this->createApi();
48
49
        /** @noinspection PhpUndefinedMethodInspection */
50
        /** @var SendGrid\Response $response */
51
        $response = $sg->client->mail()->send()->post($this->getMail());
52
53
        if ($response->statusCode() == '202') {
54
            return 1;
55
        } else {
56
            throw new Swift_TransportException(
57
                'Error sending email Code ['.$response->statusCode().']. '.
58
                $response->body().$response->headers()
59
            );
60
        }
61
    }
62
63
    public function initMail()
64
    {
65
        $this->setMail(new Mail());
66
    }
67
68
    /**
69
     * @param Message|MessageInterface $message
70
     */
71
    protected function populateSenders($message)
72
    {
73
        $from = $message->getFrom();
74
        foreach ($from as $address => $name) {
75
            $email = new Email($name, $address);
76
            $this->getMail()->setFrom($email);
77
78
            $reply_to = new ReplyTo($address);
79
            $this->getMail()->setReplyTo($reply_to);
80
        }
81
    }
82
83
    /**
84
     * @return null|Mail
85
     */
86
    public function getMail()
87
    {
88
        return $this->mail;
89
    }
90
91
    /**
92
     * @param null|Mail $mail
93
     */
94
    public function setMail($mail)
95
    {
96
        $this->mail = $mail;
97
    }
98
99
    /**
100
     * @param Message|MessageInterface $message
101
     * @throws Exception
102
     */
103
    protected function populatePersonalization($message)
104
    {
105
        $emailsTos = $message->getTo();
106
        if (!is_array($emailsTos) or count($emailsTos) < 1) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
107
            throw new Exception('Cannot send email withought reciepients');
108
        }
109
        $i = 0;
110
        foreach ($emailsTos as $emailTo => $nameTo) {
111
            $personalization = $this->generatePersonalization($emailTo, $nameTo, $message, $i);
0 ignored issues
show
Compatibility introduced by
$message of type object<Swift_Mime_Message> is not a sub-type of object<Nip\Mail\Message>. It seems like you assume a concrete implementation of the interface Swift_Mime_Message to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
112
            $this->getMail()->addPersonalization($personalization);
0 ignored issues
show
Bug introduced by
The method addPersonalization does only exist in SendGrid\Mail, but not in Swift_Mime_Message.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
113
            $i++;
114
        }
115
    }
116
117
    /**
118
     * @param $emailTo
119
     * @param $nameTo
120
     * @param Message $message
121
     * @param $i
122
     * @return Personalization
123
     */
124
    protected function generatePersonalization($emailTo, $nameTo, $message, $i)
125
    {
126
        $personalization = new Personalization();
127
128
        $email = new Email($nameTo, $emailTo);
129
        $personalization->addTo($email);
130
131
        $personalization->setSubject($message->getSubject());
132
133
        $mergeTags = $message->getMergeTags();
134
        foreach ($mergeTags as $varKey => $value) {
135
            if (is_array($value)) {
136
                $value = $value[$i];
137
            }
138
            $value = (string)$value;
139
            $personalization->addSubstitution('{{'.$varKey.'}}', $value);
140
        }
141
142
        return $personalization;
143
    }
144
145
    /**
146
     * @param Message|MessageInterface $message
147
     */
148
    protected function populateContent($message)
149
    {
150
        $contentType = $this->getMessagePrimaryContentType($message);
151
152
        $bodyHtml = $bodyText = null;
0 ignored issues
show
Unused Code introduced by
$bodyText 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...
153
154
        if ($contentType === 'text/plain') {
155
            $bodyText = $message->getBody();
156
        } else {
157
            $bodyHtml = $message->getBody();
158
            $bodyText = (new Html2Text($bodyHtml))->getText();
159
        }
160
161
        foreach ($message->getChildren() as $child) {
162
            if ($child instanceof Swift_Image) {
163
                $images[] = [
0 ignored issues
show
Coding Style Comprehensibility introduced by
$images was never initialized. Although not strictly required by PHP, it is generally a good practice to add $images = 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...
164
                    'type' => $child->getContentType(),
165
                    'name' => $child->getId(),
166
                    'content' => base64_encode($child->getBody()),
167
                ];
168
            } elseif ($child instanceof Swift_Attachment && !($child instanceof Swift_Image)) {
169
                $this->addAttachment($child);
170
            } elseif ($child instanceof Swift_MimePart && $this->supportsContentType($child->getContentType())) {
171
                if ($child->getContentType() == "text/html") {
172
                    $bodyHtml = $child->getBody();
173
                } elseif ($child->getContentType() == "text/plain") {
174
                    $bodyText = $child->getBody();
175
                }
176
            }
177
        }
178
179
        $content = new Content("text/plain", $bodyText);
180
        $this->getMail()->addContent($content);
0 ignored issues
show
Bug introduced by
The method addContent does only exist in SendGrid\Mail, but not in Swift_Mime_Message.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
181
182
        $content = new Content("text/html", $bodyHtml);
183
        $this->getMail()->addContent($content);
184
    }
185
186
    /**
187
     * @param MessageInterface $message
188
     * @return string
189
     */
190
    protected function getMessagePrimaryContentType(MessageInterface $message)
191
    {
192
        $contentType = $message->getContentType();
193
        if ($this->supportsContentType($contentType)) {
194
            return $contentType;
195
        }
196
        // SwiftMailer hides the content type set in the constructor of Swift_Mime_Message as soon
197
        // as you add another part to the message. We need to access the protected property
198
        // _userContentType to get the original type.
199
        $messageRef = new \ReflectionClass($message);
200
        if ($messageRef->hasProperty('_userContentType')) {
201
            $propRef = $messageRef->getProperty('_userContentType');
202
            $propRef->setAccessible(true);
203
            $contentType = $propRef->getValue($message);
204
        }
205
206
        return $contentType;
207
    }
208
209
    /**
210
     * @param string $contentType
211
     * @return bool
212
     */
213
    protected function supportsContentType($contentType)
214
    {
215
        return in_array($contentType, $this->getSupportedContentTypes());
216
    }
217
218
    /**
219
     * @return array
220
     */
221
    protected function getSupportedContentTypes()
222
    {
223
        return [
224
            'text/plain',
225
            'text/html',
226
        ];
227
    }
228
229
    /**
230
     * @param Swift_Attachment $attachment
231
     */
232
    protected function addAttachment($attachment)
233
    {
234
        $sgAttachment = new Attachment();
235
        $sgAttachment->setContent(base64_encode($attachment->getBody()));
236
        $sgAttachment->setType($attachment->getContentType());
237
        $sgAttachment->setFilename($attachment->getFilename());
238
        $sgAttachment->setDisposition("attachment");
239
        $sgAttachment->setContentID($attachment->getId());
240
        $this->getMail()->addAttachment($sgAttachment);
0 ignored issues
show
Bug introduced by
The method addAttachment does only exist in SendGrid\Mail, but not in Swift_Mime_Message.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
241
    }
242
243
    /**
244
     * @param Message|MessageInterface $message
245
     */
246
    protected function populateCustomArg($message)
247
    {
248
        $args = $message->getCustomArgs();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Swift_Mime_Message as the method getCustomArgs() does only exist in the following implementations of said interface: Nip\Mail\Message.

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...
249
        foreach ($args as $key => $value) {
250
            if ($key == 'category') {
251
                $this->getMail()->addCategory($value);
0 ignored issues
show
Bug introduced by
The method addCategory does only exist in SendGrid\Mail, but not in Swift_Mime_Message.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
252
            } else {
253
                $this->getMail()->addCustomArg($key, (string)$value);
0 ignored issues
show
Bug introduced by
The method addCustomArg does only exist in SendGrid\Mail, but not in Swift_Mime_Message.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
254
            }
255
        }
256
    }
257
258
    /**
259
     * @return SendGrid
260
     * @throws Swift_TransportException
261
     */
262
    protected function createApi()
263
    {
264
        if ($this->getApiKey() === null) {
265
            throw new Swift_TransportException('Cannot create instance of \Mandrill while API key is NULL');
266
        }
267
268
269
        $sg = new SendGrid($this->getApiKey());
270
271
        return $sg;
272
    }
273
274
    /**
275
     * @return null|string
276
     */
277
    public function getApiKey()
278
    {
279
        return $this->apiKey;
280
    }
281
282
    /**
283
     * @param string $apiKey
284
     * @return $this
285
     */
286
    public function setApiKey($apiKey)
287
    {
288
        $this->apiKey = $apiKey;
289
290
        return $this;
291
    }
292
}
293