Completed
Pull Request — master (#7)
by
unknown
01:20
created

Xero::getRedirectOnError()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
namespace Invoiced\OAuth1\Client\Server;
4
5
use Exception;
6
use InvalidArgumentException;
7
use League\OAuth1\Client\Server\Server;
8
use GuzzleHttp\Client as GuzzleHttpClient;
9
use GuzzleHttp\Exception\BadResponseException;
10
use League\OAuth1\Client\Credentials\TokenCredentials;
11
use League\OAuth1\Client\Signature\SignatureInterface;
12
use League\OAuth1\Client\Credentials\ClientCredentials;
13
14
class Xero extends Server
15
{
16
    /**
17
     * @var string
18
     */
19
    protected $responseType = 'xml';
20
21
    /**
22
     * @var array
23
     */
24
    protected $httpClientOptions = [];
25
26
    /**
27
     * @var array
28
     */
29
    protected $lastTokenCredentialsResponse;
30
31
    /**
32
     * @var array
33
     */
34
    protected $scope = [];
35
36
    /**
37
     * @var bool
38
     */
39
    protected $redirectOnError = false;
40
41
    /**
42
     * {@inheritdoc}
43
     */
44
    public function __construct($clientCredentials, SignatureInterface $signature = null)
45
    {
46
        if (is_array($clientCredentials)) {
47
            $this->parseConfiguration($clientCredentials);
48
49
            $clientCredentials = $this->createClientCredentials($clientCredentials);
50
51
            if (!$signature && $clientCredentials instanceof RsaClientCredentials) {
52
                $signature = new RsaSha1Signature($clientCredentials);
53
            }
54
        }
55
56
        parent::__construct($clientCredentials, $signature);
57
    }
58
59
    /**
60
     * Sets the value of the scope parameter used during authorization.
61
     *
62
     * @param array $scope Enumerated array where each element is a string
63
     *                     containing a single privilege value (e.g. 'payroll.employees')
64
     */
65
    public function setScope(array $scope)
66
    {
67
        $this->scope = $scope;
68
    }
69
70
    /**
71
     * Sets the redirect on error parameter used during authorization.
72
     *
73
     * @param boolean $redirect Boolean to toggle this parameter.
74
     * 
75
     * @return void
76
     */
77
    public function setRedirectOnError(bool $redirect)
78
    {
79
        $this->redirectOnError = $redirect;
80
    }
81
82
    /**
83
     * Gets the current setting for redirect on error.
84
     *
85
     * @return boolean
86
     */
87
    public function getRedirectOnError()
88
    {
89
        return $this->redirectOnError;
90
    }
91
92
    /**
93
     * Creates a Guzzle HTTP client for the given URL.
94
     *
95
     * @return GuzzleHttpClient
96
     */
97
    public function createHttpClient()
98
    {
99
        return new GuzzleHttpClient($this->httpClientOptions);
100
    }
101
102
    public function urlTemporaryCredentials()
103
    {
104
        return 'https://api.xero.com/oauth/RequestToken';
105
    }
106
107
    public function urlAuthorization()
108
    {
109
        return 'https://api.xero.com/oauth/Authorize'
110
            . $this->buildUrlAuthorizationQueryString();
111
    }
112
113
    /**
114
     * @return string
115
     */
116
    protected function buildUrlAuthorizationQueryString()
117
    {
118
        if (!$this->scope && !$this->redirectOnError) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->scope of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
119
            return '';
120
        }
121
122
        if ($this->scope) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->scope of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
123
            $parameters[] = 'scope=' . implode(',', $this->scope);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$parameters was never initialized. Although not strictly required by PHP, it is generally a good practice to add $parameters = 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...
124
        }
125
126
        if ($this->redirectOnError) {
127
            $parameters[] = 'redirectOnError=true';
0 ignored issues
show
Bug introduced by
The variable $parameters 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...
128
        }
129
130
        return '?' . implode('&', $parameters);
131
    }
132
133
    public function urlTokenCredentials()
134
    {
135
        return 'https://api.xero.com/oauth/AccessToken';
136
    }
137
138
    public function urlUserDetails()
139
    {
140
        return $this->notSupportedByXero();
141
    }
142
143
    public function userDetails($data, TokenCredentials $tokenCredentials)
144
    {
145
        return $this->notSupportedByXero();
146
    }
147
148
    public function userUid($data, TokenCredentials $tokenCredentials)
149
    {
150
        return $this->notSupportedByXero();
151
    }
152
153
    public function userEmail($data, TokenCredentials $tokenCredentials)
154
    {
155
        return $this->notSupportedByXero();
156
    }
157
158
    public function userScreenName($data, TokenCredentials $tokenCredentials)
159
    {
160
        return $this->notSupportedByXero();
161
    }
162
163
    /**
164
     * Gets the response of the last access token call. This might
165
     * be useful for partner applications to retrieve additional
166
     * OAuth parameters passed in by Xero.
167
     *
168
     * @return array|null
169
     */
170
    public function getLastTokenCredentialsResponse()
171
    {
172
        return $this->lastTokenCredentialsResponse;
173
    }
174
175
    /**
176
     * Refreshes an access token. Can be used by partner applications.
177
     *
178
     * @param TokenCredentials $tokenCredentials
179
     * @param string           $sessionHandle    Xero session handle
180
     *
181
     * @throws \League\OAuth1\Client\Credentials\CredentialsException when the access token cannot be refreshed
182
     *
183
     * @return TokenCredentials
184
     */
185
    public function refreshToken(TokenCredentials $tokenCredentials, $sessionHandle)
186
    {
187
        $client = $this->createHttpClient();
188
        $url = $this->urlTokenCredentials();
189
190
        $parameters = [
191
            'oauth_session_handle' => $sessionHandle,
192
        ];
193
194
        $headers = $this->getHeaders($tokenCredentials, 'POST', $url, $parameters);
195
196
        try {
197
            $response = $client->post($url, [
198
                'headers' => $headers,
199
                'form_params' => $parameters,
200
            ]);
201
        } catch (BadResponseException $e) {
202
            $this->handleTokenCredentialsBadResponse($e);
203
        }
204
205
        return $this->createTokenCredentials((string) $response->getBody());
206
    }
207
208
    protected function notSupportedByXero()
209
    {
210
        throw new Exception("Xero's API does not support retrieving the current user. Please see https://xero.uservoice.com/forums/5528-xero-accounting-api/suggestions/5688571-expose-which-user-connected-the-organization-via-o");
211
    }
212
213
    /**
214
     * Parse configuration array to set attributes.
215
     *
216
     * @param array $configuration
217
     */
218
    private function parseConfiguration(array $configuration = [])
219
    {
220
        $configToPropertyMap = [
221
            'http_client' => 'httpClientOptions',
222
        ];
223
        foreach ($configToPropertyMap as $config => $property) {
224
            if (isset($configuration[$config])) {
225
                $this->$property = $configuration[$config];
226
            }
227
        }
228
    }
229
230
    /**
231
     * Creates a client credentials instance from an array of credentials.
232
     *
233
     * @param array $clientCredentials
234
     *
235
     * @return ClientCredentials
236
     */
237
    protected function createClientCredentials(array $clientCredentials)
238
    {
239
        $keys = ['identifier', 'secret'];
240
241
        foreach ($keys as $key) {
242
            if (!isset($clientCredentials[$key])) {
243
                throw new InvalidArgumentException("Missing client credentials key [$key] from options.");
244
            }
245
        }
246
247
        if (isset($clientCredentials['rsa_private_key']) && isset($clientCredentials['rsa_public_key'])) {
248
            $_clientCredentials = new RsaClientCredentials();
249
            $_clientCredentials->setRsaPrivateKey($clientCredentials['rsa_private_key']);
250
            $_clientCredentials->setRsaPublicKey($clientCredentials['rsa_public_key']);
251
        } else {
252
            $_clientCredentials = new ClientCredentials();
253
        }
254
255
        $_clientCredentials->setIdentifier($clientCredentials['identifier']);
256
        $_clientCredentials->setSecret($clientCredentials['secret']);
257
258
        if (isset($clientCredentials['callback_uri'])) {
259
            $_clientCredentials->setCallbackUri($clientCredentials['callback_uri']);
260
        }
261
262
        return $_clientCredentials;
263
    }
264
265
    /**
266
     * Creates token credentials from the body response.
267
     *
268
     * @param string $body
269
     *
270
     * @return TokenCredentials
271
     */
272
    protected function createTokenCredentials($body)
273
    {
274
        parse_str($body, $data);
275
        $this->lastTokenCredentialsResponse = $data;
0 ignored issues
show
Documentation Bug introduced by
It seems like $data can be null. However, the property $lastTokenCredentialsResponse is declared as array. Maybe change the type of the property to array|null or add a type check?

Our type inference engine has found an assignment of a scalar value (like a string, an integer or null) to a property which is an array.

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

To type hint that a parameter can be either an array or null, you can set a type hint of array and a default value of null. The PHP interpreter will then accept both an array or null for that parameter.

function aContainsB(array $needle = null, array  $haystack) {
    if (!$needle) {
        return false;
    }

    return array_intersect($haystack, $needle) == $haystack;
}

The function can be called with either null or an array for the parameter $needle but will only accept an array as $haystack.

Loading history...
276
277
        return parent::createTokenCredentials($body);
278
    }
279
}
280