Completed
Push — master ( 6d2cf2...d81286 )
by Jared
11s
created

Xero::buildUrlAuthorizationQueryString()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 7
rs 9.4285
cc 2
eloc 4
nc 2
nop 0
1
<?php
2
3
namespace Invoiced\OAuth1\Client\Server;
4
5
use Exception;
6
use GuzzleHttp\Client as GuzzleHttpClient;
7
use GuzzleHttp\Exception\BadResponseException;
8
use InvalidArgumentException;
9
use League\OAuth1\Client\Credentials\ClientCredentials;
10
use League\OAuth1\Client\Credentials\TokenCredentials;
11
use League\OAuth1\Client\Server\Server;
12
use League\OAuth1\Client\Signature\SignatureInterface;
13
14
class Xero extends Server
15
{
16
    /**
17
     * @var string
18
     */
19
    protected $responseType = 'xml';
20
21
    /**
22
     * @var bool
23
     */
24
    protected $usePartnerApi = false;
25
26
    /**
27
     * @var array
28
     */
29
    protected $httpClientOptions = [];
30
31
    /**
32
     * @var array
33
     */
34
    protected $lastTokenCredentialsResponse;
35
36
    /**
37
     * @var array
38
     */
39
    protected $scope = [];
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 whether the Xero partner API should be used.
61
     *
62
     * @param bool $enable
63
     *
64
     * @return self
65
     */
66
    public function usePartnerApi($enable = true)
67
    {
68
        $this->usePartnerApi = $enable;
69
70
        return $this;
71
    }
72
73
    /**
74
     * Checks if the Xero partner API is used.
75
     *
76
     * @return bool
77
     */
78
    public function getUsePartnerApi()
79
    {
80
        return $this->usePartnerApi;
81
    }
82
83
    /**
84
     * Sets the value of the scope parameter used during authorization.
85
     *
86
     * @param array $scope Enumerated array where each element is a string
87
     *        containing a single privilege value (e.g. 'payroll.employees')
88
     */
89
    public function setScope(array $scope)
90
    {
91
        $this->scope = $scope;
92
    }
93
94
    /**
95
     * Creates a Guzzle HTTP client for the given URL.
96
     *
97
     * @return GuzzleHttpClient
98
     */
99
    public function createHttpClient()
100
    {
101
        return new GuzzleHttpClient($this->httpClientOptions);
102
    }
103
104
    public function urlTemporaryCredentials()
105
    {
106
        if ($this->usePartnerApi) {
107
            return 'https://api-partner.network.xero.com/oauth/RequestToken';
108
        }
109
110
        return 'https://api.xero.com/oauth/RequestToken';
111
    }
112
113
    public function urlAuthorization()
114
    {
115
        return 'https://api.xero.com/oauth/Authorize'
116
            . $this->buildUrlAuthorizationQueryString();
117
    }
118
119
    /**
120
     * @return string
121
     */
122
    protected function buildUrlAuthorizationQueryString()
123
    {
124
        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...
125
            return '';
126
        }
127
        return '?scope=' . implode(',', $this->scope);
128
    }
129
130
    public function urlTokenCredentials()
131
    {
132
        if ($this->usePartnerApi) {
133
            return 'https://api-partner.network.xero.com/oauth/AccessToken';
134
        }
135
136
        return 'https://api.xero.com/oauth/AccessToken';
137
    }
138
139
    public function urlUserDetails()
140
    {
141
        return $this->notSupportedByXero();
142
    }
143
144
    public function userDetails($data, TokenCredentials $tokenCredentials)
145
    {
146
        return $this->notSupportedByXero();
147
    }
148
149
    public function userUid($data, TokenCredentials $tokenCredentials)
150
    {
151
        return $this->notSupportedByXero();
152
    }
153
154
    public function userEmail($data, TokenCredentials $tokenCredentials)
155
    {
156
        return $this->notSupportedByXero();
157
    }
158
159
    public function userScreenName($data, TokenCredentials $tokenCredentials)
160
    {
161
        return $this->notSupportedByXero();
162
    }
163
164
    /**
165
     * Gets the response of the last access token call. This might
166
     * be useful for partner applications to retrieve additional
167
     * OAuth parameters passed in by Xero.
168
     *
169
     * @return array|null
170
     */
171
    public function getLastTokenCredentialsResponse()
172
    {
173
        return $this->lastTokenCredentialsResponse;
174
    }
175
176
    /**
177
     * Refreshes an access token. Can be used by partner applications.
178
     *
179
     * @param TokenCredentials $tokenCredentials
180
     * @param string           $sessionHandle    Xero session handle
181
     *
182
     * @throws League\OAuth1\Client\Credentials\CredentialsException when the access token cannot be refreshed.
183
     *
184
     * @return TokenCredentials
185
     */
186
    public function refreshToken(TokenCredentials $tokenCredentials, $sessionHandle)
187
    {
188
        $client = $this->createHttpClient();
189
        $url = $this->urlTokenCredentials();
190
191
        $parameters = [
192
            'oauth_session_handle' => $sessionHandle,
193
        ];
194
195
        $headers = $this->getHeaders($tokenCredentials, 'POST', $url, $parameters);
196
197
        try {
198
            $response = $client->post($url, [
199
                'headers' => $headers,
200
                'form_params' => $parameters,
201
            ]);
202
        } catch (BadResponseException $e) {
203
            $this->handleTokenCredentialsBadResponse($e);
204
        }
205
206
        return $this->createTokenCredentials((string) $response->getBody());
207
    }
208
209
    protected function notSupportedByXero()
210
    {
211
        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");
212
    }
213
214
    /**
215
     * Parse configuration array to set attributes.
216
     *
217
     * @param array $configuration
218
     */
219
    private function parseConfiguration(array $configuration = array())
220
    {
221
        $configToPropertyMap = array(
222
            'partner' => 'usePartnerApi',
223
            'http_client' => 'httpClientOptions',
224
        );
225
        foreach ($configToPropertyMap as $config => $property) {
226
            if (isset($configuration[$config])) {
227
                $this->$property = $configuration[$config];
228
            }
229
        }
230
    }
231
232
    /**
233
     * Creates a client credentials instance from an array of credentials.
234
     *
235
     * @param array $clientCredentials
236
     *
237
     * @return ClientCredentials
238
     */
239
    protected function createClientCredentials(array $clientCredentials)
240
    {
241
        $keys = array('identifier', 'secret');
242
243
        foreach ($keys as $key) {
244
            if (!isset($clientCredentials[$key])) {
245
                throw new InvalidArgumentException("Missing client credentials key [$key] from options.");
246
            }
247
        }
248
249
        if (isset($clientCredentials['rsa_private_key']) && isset($clientCredentials['rsa_public_key'])) {
250
            $_clientCredentials = new RsaClientCredentials();
251
            $_clientCredentials->setRsaPrivateKey($clientCredentials['rsa_private_key']);
252
            $_clientCredentials->setRsaPublicKey($clientCredentials['rsa_public_key']);
253
        } else {
254
            $_clientCredentials = new ClientCredentials();
255
        }
256
257
        $_clientCredentials->setIdentifier($clientCredentials['identifier']);
258
        $_clientCredentials->setSecret($clientCredentials['secret']);
259
260
        if (isset($clientCredentials['callback_uri'])) {
261
            $_clientCredentials->setCallbackUri($clientCredentials['callback_uri']);
262
        }
263
264
        return $_clientCredentials;
265
    }
266
267
    /**
268
     * Creates token credentials from the body response.
269
     *
270
     * @param string $body
271
     *
272
     * @return TokenCredentials
273
     */
274
    protected function createTokenCredentials($body)
275
    {
276
        parse_str($body, $data);
277
        $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...
278
279
        return parent::createTokenCredentials($body);
280
    }
281
}
282