Completed
Push — master ( c6d68c...a74e45 )
by AJ
02:24
created

ShopifyAPIComponent::requestAccessToken()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 28
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 4.6041

Importance

Changes 0
Metric Value
dl 0
loc 28
ccs 2
cts 15
cp 0.1333
rs 8.8571
c 0
b 0
f 0
cc 2
eloc 16
nc 2
nop 2
crap 4.6041
1
<?php
2
/**
3
 * CakePHPify : CakePHP Plugin for Shopify API Authentication
4
 * Copyright (c) Multidimension.al (http://multidimension.al)
5
 * Github : https://github.com/multidimension-al/cakephpify
6
 *
7
 * Licensed under The MIT License
8
 * For full copyright and license information, please see the LICENSE file
9
 * Redistributions of files must retain the above copyright notice.
10
 *
11
 * @copyright (c) Multidimension.al (http://multidimension.al)
12
 * @link      https://github.com/multidimension-al/cakephpify CakePHPify Github
13
 * @license   http://www.opensource.org/licenses/mit-license.php MIT License
14
 */
15
16
namespace Multidimensional\Cakephpify\Controller\Component;
17
18
use Cake\Controller\Component;
19
use Cake\Core\Configure;
20
use Cake\Event\Event;
21
use Cake\Network\Exception\NotImplementedException;
22
use Cake\Network\Http\Client;
23
use Cake\Routing\Router;
24
25
class ShopifyAPIComponent extends Component
26
{
27
28
    public $apiKey;
29
30
    private $shopDomain;
31
    private $token;
32
    private $sharedSecret;
33
    private $privateApp;
34
    private $privateAppPassword;
35
    private $nonce;
36
37
    public $controller = null;
38
39
    /**
40
     * @param array $config
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
41
     * @return void
42
     */
43 18
    public function initialize(array $config = [])
44
    {
45 18
        parent::initialize($config);
46
47 18
        $this->apiKey = isset($config['apiKey']) ? $config['apiKey'] : '';
48
49 18
        if (!empty($this->apiKey)) {
50 18
            $this->sharedSecret = Configure::read('Multidimensional/Cakephpify.' . $this->apiKey . '.sharedSecret');
51 18
            $this->scope = Configure::read('Multidimensional/Cakephpify.' . $this->apiKey . '.scope');
0 ignored issues
show
Bug introduced by
The property scope does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
52 18
            $this->privateApp = Configure::read('Multidimensional/Cakephpify.' . $this->apiKey . '.privateApp');
53 18
            $this->privateAppPassword = Configure::read('Multidimensional/Cakephpify.' . $this->apiKey . '.privateAppPassword');
54 18
        } else {
55
            throw new NotImplementedException(__('Shopify API key not found'));
56
        }
57
58 18
        if (!$this->sharedSecret) {
59
            throw new NotImplementedException(__('Shopify Shared Secret not found'));
60
        }
61 18
    }
62
63
    /**
64
     * @param Event $event
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
65
     * @return void
66
     */
67 18
    public function startup(Event $event)
68
    {
69 18
        $this->setController($event->subject());
70 18
    }
71
72
    /**
73
     * @param controller $controller
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
74
     * @return void
75
     */
76 18
    public function setController($controller)
77
    {
78 18
        $this->controller = $controller;
79 18
    }
80
81
    /**
82
     * @param string $shopDomain
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
83
     * @return string|null
84
     */
85 6
    public function setShopDomain($shopDomain)
86
    {
87 6
        return $this->shopDomain = $shopDomain;
88
    }
89
90
    /**
91
     * @return string|null
92
     */
93 6
    public function getShopDomain()
94
    {
95 6
        return $this->shopDomain;
96
    }
97
98
    /**
99
     * @param string $shopDomain
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
100
     * @return bool
101
     */
102 6
    public function validDomain($shopDomain)
0 ignored issues
show
Coding Style introduced by
function validDomain() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
103
    {
104 6
        return preg_match('/^([A-Za-z0-9]{1}(?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9]{1})?)\.myshopify\.com$/i', $shopDomain) !== 0;
105
    }
106
    
107
    /**
108
     * @param string $token
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
109
     * @return string|null
110
     */
111
    public function setAccessToken($token)
112
    {
113
        return $this->token = $token;
114
    }
115
    
116
    /**
117
     * @return string|null
118
     */
119
    public function getAccessToken()
120
    {
121
        return $this->token;
122
    }
123
124
    /**
125
     * @return int|null
126
     */
127
    public function callsMade()
128
    {
129
        return $this->shopApiCallLimitParam(0);
130
    }
131
132
    /**
133
     * @return int|null
134
     */
135
    public function callLimit()
136
    {
137
        return $this->shopApiCallLimitParam(1);
138
    }
139
140
    /**
141
     * @param Response $responseHeaders
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
142
     * @return int|null
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|double?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
143
     */
144
    public function callsLeft($responseHeaders)
0 ignored issues
show
Unused Code introduced by
The parameter $responseHeaders is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
145
    {
146
        return $this->callLimit() - $this->callsMade();
147
    }
148
149
    /**
150
     * @param string $method
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
151
     * @param string $path
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
152
     * @param array  $params
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
153
     * @return array|null
154
     */
155
    public function call($method, $path, $params = [])
156
    {
157
        if (!$this->_isReady()) {
158
            return false;
159
        }
160
161
        if (!in_array($method, ['POST', 'PUT', 'GET', 'DELETE'])) {
162
            return false;
163
        }
164
165
        $http = new Client(
166
            [
167
            'host' => $this->shopDomain,
168
            'scheme' => 'https',
169
            'headers' => (($this->privateApp != 'true') ? (['X-Shopify-Access-Token' => $this->token]) : []),
170
            'auth' => (($this->privateApp != 'true') ? [] : (['username' => $this->apiKey, 'password' => $this->privateAppPassword]))
171
            ]
172
        );
173
174
        $this->response = $http->{strtolower($method)}(
175
            $path,
176
            ((in_array($method, ['POST', 'PUT'])) ? json_encode($params) : $params),
177
            ((in_array($method, ['POST', 'PUT'])) ? ['type' => 'json'] : [])
178
        );
179
        $this->response = $this->response->json;
180
181
        return (is_array($this->response) && (count($this->response) > 0)) ? array_shift($this->response) : $this->response;
182
    }
183
184
    /**
185
     * @param int $index
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
186
     * @return int
187
     */
188
    private function shopApiCallLimitParam($index)
0 ignored issues
show
Coding Style introduced by
Private method name "ShopifyAPIComponent::shopApiCallLimitParam" must be prefixed with an underscore
Loading history...
189
    {
190
        $params = explode("/", $this->response->getHeaderLine('http_x_shopify_shop_api_call_limit'));
0 ignored issues
show
Bug introduced by
The method getHeaderLine() does not exist on Cake\Network\Response. Did you maybe mean header()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
Coding Style Comprehensibility introduced by
The string literal / does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
191
192
        return (int)$params[$index];
193
    }
194
195
    /**
196
     * @param string $shopDomain
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
197
     * @param string $redirectUrl
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
198
     * @return string
199
     */
200
    public function getAuthorizeUrl($shopDomain, $redirectUrl)
201
    {
202
        $url = 'https://' . $shopDomain . '/admin/oauth/authorize?client_id=' . $this->apiKey;
203
        $url .= '&scope=' . urlencode($this->scope);
204
        $url .= '&redirect_uri=' . urlencode($redirectUrl);
205
        $url .= '&state=' . $this->getNonce($shopDomain);
0 ignored issues
show
Unused Code introduced by
The call to ShopifyAPIComponent::getNonce() has too many arguments starting with $shopDomain.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
206
207
        return $url;
208
    }
209
210
    /**
211
     * @param string $shopDomain
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
212
     * @param string $code
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
213
     * @return string|false
214
     */
215
    public function requestAccessToken($shopDomain, $code)
216
    {
217
        $this->shopDomain = $shopDomain;
218
219
        $http = new Client(
220
            [
221
            'host' => $shopDomain,
222
            'scheme' => 'https'
223
            ]
224
        );
225
226
        $response = $http->post(
227
            '/admin/oauth/access_token',
228
            'client_id=' . $this->apiKey .
229
                                    '&client_secret=' . $this->sharedSecret .
230
            '&code=' . $code
231
        );
232
        $response = $response->json;
233
        ;
234
235
        if (isset($response['access_token'])) {
236
            $this->token = $response['access_token'];
237
238
            return $this->token;
239
        } else {
240 6
            return false;
241
        }
242 6
    }
243
244
    /**
245
     * @param string $shopDomain
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
246
     * @return string|null
247
     */
248 6
    public function setNonce($shopDomain)
249
    {
250 6
        return $this->nonce = md5(strtolower($shopDomain));
251
    }
252
253
    /**
254
     * @return string|null
255
     */
256
    public function getNonce()
257
    {
258
        return $this->nonce;
259
    }
260
261
    /**
262
     * @return json
0 ignored issues
show
Documentation introduced by
Should the return type not be array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
263
     */
264
    public function getShopData()
265 6
    {
266
        return $this->call('GET', '/admin/shop.json');
267 6
    }
268 6
269
    /**
270
     * @param array $query
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
271 6
     * @return bool
272
     */
273 6
    public function validateHMAC($query)
0 ignored issues
show
Coding Style introduced by
function validateHMAC() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
274 6
    {
275 6
        if (!is_array($query) || empty($query['hmac']) || !is_string($query['hmac']) || (isset($query['state']) && $query['state'] != $this->getNonce($query['shop']))) {
0 ignored issues
show
Unused Code introduced by
The call to ShopifyAPIComponent::getNonce() has too many arguments starting with $query['shop'].

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
276 6
            return false;
277
        }
278
279 6
        $dataString = [];
280
281 6
        foreach ($query as $key => $value) {
282 6
            $key = $this->_urlEncode(str_replace('=', '%3D', $key));
283
            $value = $this->_urlEncode($value);
284 6
            if ($key != 'hmac') {
285
                $dataString[] = $key . '=' . $value;
286
            }
287
        }
288
289
        sort($dataString);
290
        $string = implode("&", $dataString);
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal & does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
291 6
292
        return $query['hmac'] === hash_hmac('sha256', $string, $this->sharedSecret);
293 6
    }
294 6
295
    /**
296 6
     * @param string $url
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
297
     * @return string
298
     */
299
    private function _urlEncode($url)
300
    {
301
        $url = str_replace('&', '%26', $url);
302
        $url = str_replace('%', '%25', $url);
303
304
        return $url;
305
    }
306
307
    /**
308
     * @return bool
309
     */
310
    private function _isReady()
0 ignored issues
show
Coding Style introduced by
function _isReady() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
311
    {
312
        return strlen($this->shopDomain) > 0 && strlen($this->token) > 0;
313
    }
314
}
315