Completed
Push — master ( e7aed2...f10ba0 )
by AJ
06:23
created

ShopifyAPIComponent::_urlEncode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 0
cts 4
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 1
crap 2
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
0 ignored issues
show
Coding Style introduced by
The property $shop_domain is not named in camelCase.

This check marks property names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
26
{
27
28
    public $apiKey;
29
30
    private $shop_domain;
0 ignored issues
show
Coding Style introduced by
$shop_domain does not seem to conform to the naming convention (^[a-z][a-zA-Z0-9]*$).

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...
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 42
    public function initialize(array $config = [])
44
    {
45 42
        parent::initialize($config);
46
47 42
        $this->apiKey = isset($config['apiKey']) ? $config['apiKey'] : '';
48
49 42
        if (!empty($this->apiKey)) {
50
            $this->sharedSecret = Configure::read('Multidimensional/Cakephpify.' . $this->apiKey . '.sharedSecret');
51
            $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
            $this->privateApp = Configure::read('Multidimensional/Cakephpify.' . $this->apiKey . '.privateApp');
53
            $this->privateAppPassword = Configure::read('Multidimensional/Cakephpify.' . $this->apiKey . '.privateAppPassword');
54
        } else {
55 42
            throw new NotImplementedException(__('Shopify API key not found'));
56
        }
57
58
        if (!$this->sharedSecret) {
59
            throw new NotImplementedException(__('Shopify shared secret not found'));
60
        }
61
    }
62
63
    /**
64
     * @param Event $event
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
65
     * @return void
66
     */
67
    public function startup(Event $event)
68
    {
69
        $this->setController($event->subject());
70
    }
71
72
    /**
73
     * @param controller $controller
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
74
     * @return void
75
     */
76
    public function setController($controller)
77
    {
78
        $this->controller = $controller;
79
        if (!isset($this->controller->paginate)) {
80
            $this->controller->paginate = [];
81
        }
82
    }
83
84
    /**
85
     * @param string $shopDomain
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
86
     * @return string|null
87
     */
88
    public function setShopDomain($shopDomain)
89
    {
90
        return $this->shop_domain = $shopDomain;
91
    }
92
93
    /**
94
     * @return string|null
95
     */
96
    public function getShopDomain()
97
    {
98
        return $this->shop_domain;
99
    }
100
101
    /**
102
     * @param string $token
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
103
     * @return string|null
104
     */
105
    public function setAccessToken($token)
106
    {
107
        return $this->token = $token;
108
    }
109
110
    /**
111
     * @return int|null
112
     */
113
    public function callsMade()
114
    {
115
        return $this->shopApiCallLimitParam(0);
116
    }
117
118
    /**
119
     * @return int|null
120
     */
121
    public function callLimit()
122
    {
123
        return $this->shopApiCallLimitParam(1);
124
    }
125
126
    /**
127
     * @param Response $responseHeaders
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
128
     * @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...
129
     */
130
    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...
131
    {
132
        return $this->callLimit() - $this->callsMade();
133
    }
134
135
    /**
136
     * @param string $method
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
137
     * @param string $path
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
138
     * @param array  $params
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
139
     * @return array|null
140
     */
141
    public function call($method, $path, $params = [])
142
    {
143
        if (!$this->_isReady()) {
144
            return false;
145
        }
146
147
        if (!in_array($method, ['POST', 'PUT', 'GET', 'DELETE'])) {
148
            return false;
149
        }
150
151
        $http = new Client(
152
            [
153
            'host' => $this->shop_domain,
154
            'scheme' => 'https',
155
            'headers' => (($this->privateApp != 'true') ? (['X-Shopify-Access-Token' => $this->token]) : []),
156
            'auth' => (($this->privateApp != 'true') ? [] : (['username' => $this->apiKey, 'password' => $this->privateAppPassword]))
157
            ]
158
        );
159
160
        $this->response = $http->{strtolower($method)}(
161
            $path,
162
            ((in_array($method, ['POST', 'PUT'])) ? json_encode($params) : $params),
163
            ((in_array($method, ['POST', 'PUT'])) ? ['type' => 'json'] : [])
164
        );
165
        $this->response = $this->response->json;
166
167
        return (is_array($this->response) && (count($this->response) > 0)) ? array_shift($this->response) : $this->response;
168
    }
169
170
    /**
171
     * @param int $index
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
172
     * @return int
173
     */
174
    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...
175
    {
176
        $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...
177
178
        return (int)$params[$index];
179
    }
180
181
    /**
182
     * @param string $shopDomain
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
183
     * @param string $redirectUrl
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
184
     * @return string
185
     */
186
    public function getAuthorizeUrl($shopDomain, $redirectUrl)
187
    {
188
        $url = 'https://' . $shopDomain . '/admin/oauth/authorize?client_id=' . $this->apiKey;
189
        $url .= '&scope=' . urlencode($this->scope);
190
        $url .= '&redirect_uri=' . urlencode($redirectUrl);
191
        $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...
192
193
        return $url;
194
    }
195
196
    /**
197
     * @param string $shopDomain
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
198
     * @param string $code
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
199
     * @return string|bool
200
     */
201
    public function getAccessToken($shopDomain, $code)
202
    {
203
        $this->shop_domain = $shopDomain;
204
205
        $http = new Client(
206
            [
207
            'host' => $shopDomain,
208
            'scheme' => 'https'
209
            ]
210
        );
211
212
        $response = $http->post(
213
            '/admin/oauth/access_token',
214
            'client_id=' . $this->apiKey .
215
                                    '&client_secret=' . $this->sharedSecret .
216
            '&code=' . $code
217
        );
218
        $response = $response->json;
219
        ;
220
221
        if (isset($response['access_token'])) {
222
            $this->token = $response['access_token'];
223
224
            return $this->token;
225
        } else {
226
            return false;
227
        }
228
    }
229
230
    /**
231
     * @param string $shopDomain
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
232
     * @return string|null
233
     */
234
    public function setNonce($shopDomain)
235
    {
236
        return $this->nonce = md5(strtolower($shopDomain));
237
    }
238
239
    /**
240
     * @return string|null
241
     */
242
    public function getNonce()
243
    {
244
        return $this->nonce;
245
    }
246
247
    /**
248
     * @param string $shopDomain
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
249
     * @return bool
250
     */
251
    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...
Unused Code introduced by
The parameter $shopDomain 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...
252
    {
253
        return true;
254
    }
255
256
    /**
257
     * @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...
258
     */
259
    public function getShopData()
260
    {
261
        return $this->call('GET', '/admin/shop.json');
262
    }
263
264
    /**
265
     * @param array $query
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
266
     * @return bool
267
     */
268
    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...
269
    {
270
        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...
271
            return false;
272
        }
273
274
        $dataString = [];
275
276
        foreach ($query as $key => $value) {
277
            $key = $this->_urlEncode(str_replace('=', '%3D', $key));
278
            $value = $this->_urlEncode($value);
279
            if ($key != 'hmac') {
280
                $dataString[] = $key . '=' . $value;
281
            }
282
        }
283
284
        sort($dataString);
285
        $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...
286
287
        return $query['hmac'] === hash_hmac('sha256', $string, $this->sharedSecret);
288
    }
289
290
    /**
291
     * @param string $url
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
292
     * @return string
293
     */
294
    private function _urlEncode($url)
295
    {
296
        $url = str_replace('&', '%26', $url);
297
        $url = str_replace('%', '%25', $url);
298
299
        return $url;
300
    }
301
302
    /**
303
     * @return bool
304
     */
305
    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...
306
    {
307
        return strlen($this->shop_domain) > 0 && strlen($this->token) > 0;
308
    }
309
}
310