Completed
Push — master ( dd21cf...b0fced )
by AJ
07:09
created

ShopifyAPIComponent::_urlEncode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 1
crap 1
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 $token
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
100
     * @return string|null
101
     */
102
    public function setAccessToken($token)
103
    {
104
        return $this->token = $token;
105
    }
106
107
    /**
108
     * @return int|null
109
     */
110
    public function callsMade()
111
    {
112
        return $this->shopApiCallLimitParam(0);
113
    }
114
115
    /**
116
     * @return int|null
117
     */
118
    public function callLimit()
119
    {
120
        return $this->shopApiCallLimitParam(1);
121
    }
122
123
    /**
124
     * @param Response $responseHeaders
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
125
     * @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...
126
     */
127
    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...
128
    {
129
        return $this->callLimit() - $this->callsMade();
130
    }
131
132
    /**
133
     * @param string $method
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
134
     * @param string $path
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
135
     * @param array  $params
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
136
     * @return array|null
137
     */
138
    public function call($method, $path, $params = [])
139
    {
140
        if (!$this->_isReady()) {
141
            return false;
142
        }
143
144
        if (!in_array($method, ['POST', 'PUT', 'GET', 'DELETE'])) {
145
            return false;
146
        }
147
148
        $http = new Client(
149
            [
150
            'host' => $this->shopDomain,
151
            'scheme' => 'https',
152
            'headers' => (($this->privateApp != 'true') ? (['X-Shopify-Access-Token' => $this->token]) : []),
153
            'auth' => (($this->privateApp != 'true') ? [] : (['username' => $this->apiKey, 'password' => $this->privateAppPassword]))
154
            ]
155
        );
156
157
        $this->response = $http->{strtolower($method)}(
158
            $path,
159
            ((in_array($method, ['POST', 'PUT'])) ? json_encode($params) : $params),
160
            ((in_array($method, ['POST', 'PUT'])) ? ['type' => 'json'] : [])
161
        );
162
        $this->response = $this->response->json;
163
164
        return (is_array($this->response) && (count($this->response) > 0)) ? array_shift($this->response) : $this->response;
165
    }
166
167
    /**
168
     * @param int $index
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
169
     * @return int
170
     */
171
    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...
172
    {
173
        $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...
174
175
        return (int)$params[$index];
176
    }
177
178
    /**
179
     * @param string $shopDomain
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
180
     * @param string $redirectUrl
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
181
     * @return string
182
     */
183
    public function getAuthorizeUrl($shopDomain, $redirectUrl)
184
    {
185
        $url = 'https://' . $shopDomain . '/admin/oauth/authorize?client_id=' . $this->apiKey;
186
        $url .= '&scope=' . urlencode($this->scope);
187
        $url .= '&redirect_uri=' . urlencode($redirectUrl);
188
        $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...
189
190
        return $url;
191
    }
192
193
    /**
194
     * @param string $shopDomain
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
195
     * @param string $code
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
196
     * @return string|bool
197
     */
198
    public function getAccessToken($shopDomain, $code)
199
    {
200
        $this->shopDomain = $shopDomain;
201
202
        $http = new Client(
203
            [
204
            'host' => $shopDomain,
205
            'scheme' => 'https'
206
            ]
207
        );
208
209
        $response = $http->post(
210
            '/admin/oauth/access_token',
211
            'client_id=' . $this->apiKey .
212
                                    '&client_secret=' . $this->sharedSecret .
213
            '&code=' . $code
214
        );
215
        $response = $response->json;
216
        ;
217
218
        if (isset($response['access_token'])) {
219
            $this->token = $response['access_token'];
220
221
            return $this->token;
222
        } else {
223
            return false;
224
        }
225
    }
226
227
    /**
228
     * @param string $shopDomain
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
229
     * @return string|null
230
     */
231 6
    public function setNonce($shopDomain)
232
    {
233 6
        return $this->nonce = md5(strtolower($shopDomain));
234
    }
235
236
    /**
237
     * @return string|null
238
     */
239
    public function getNonce()
240
    {
241
        return $this->nonce;
242
    }
243
244
    /**
245
     * @param string $shopDomain
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
246
     * @return bool
247
     */
248 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...
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...
249
    {
250 6
        return true;
251
    }
252
253
    /**
254
     * @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...
255
     */
256
    public function getShopData()
257
    {
258
        return $this->call('GET', '/admin/shop.json');
259
    }
260
261
    /**
262
     * @param array $query
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
263
     * @return bool
264
     */
265 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...
266
    {
267 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...
268 6
            return false;
269
        }
270
271 6
        $dataString = [];
272
273 6
        foreach ($query as $key => $value) {
274 6
            $key = $this->_urlEncode(str_replace('=', '%3D', $key));
275 6
            $value = $this->_urlEncode($value);
276 6
            if ($key != 'hmac') {
277
                $dataString[] = $key . '=' . $value;
278
            }
279 6
        }
280
281 6
        sort($dataString);
282 6
        $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...
283
284 6
        return $query['hmac'] === hash_hmac('sha256', $string, $this->sharedSecret);
285
    }
286
287
    /**
288
     * @param string $url
0 ignored issues
show
introduced by
Missing parameter comment
Loading history...
289
     * @return string
290
     */
291 6
    private function _urlEncode($url)
292
    {
293 6
        $url = str_replace('&', '%26', $url);
294 6
        $url = str_replace('%', '%25', $url);
295
296 6
        return $url;
297
    }
298
299
    /**
300
     * @return bool
301
     */
302
    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...
303
    {
304
        return strlen($this->shopDomain) > 0 && strlen($this->token) > 0;
305
    }
306
}
307