Completed
Push — master ( 836beb...de328f )
by AJ
06:03
created

ShopifyAPIComponent::getAccessToken()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 27
rs 8.8571
c 0
b 0
f 0
cc 2
eloc 15
nc 2
nop 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 $api_key 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...
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...
Coding Style introduced by
The property $shared_secret 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...
Coding Style introduced by
The property $is_private_app 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...
Coding Style introduced by
The property $private_app_password 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 $api_key;
0 ignored issues
show
Coding Style introduced by
$api_key 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...
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 $shared_secret;
0 ignored issues
show
Coding Style introduced by
$shared_secret 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...
33
    private $is_private_app;
0 ignored issues
show
Coding Style introduced by
$is_private_app 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...
34
    private $private_app_password;
0 ignored issues
show
Coding Style introduced by
$private_app_password 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...
35
    private $nonce;
36
37
    public $controller = null;
38
39
    public function initialize(array $config = [])
40
    {
41
        parent::initialize($config);
42
43
        $this->api_key = isset($config['api_key']) ? $config['api_key'] : '';
44
45
        if (!empty($this->api_key)) {
46
            $this->shared_secret = Configure::read('Multidimensional/Cakephpify.' . $this->api_key . '.shared_secret');
47
            $this->scope = Configure::read('Multidimensional/Cakephpify.' . $this->api_key . '.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...
48
            $this->is_private_app = Configure::read('Multidimensional/Cakephpify.' . $this->api_key . '.is_private_app');
49
            $this->private_app_password = Configure::read('Multidimensional/Cakephpify.' . $this->api_key . '.private_app_password');
50
        } else {
51
            throw new NotImplementedException(__('Shopify API key not found'));
52
        }
53
54
        if (!$this->shared_secret) {
55
            throw new NotImplementedException(__('Shopify shared secret not found'));
56
        }
57
    }
58
59
    /**
60
     * @param Event $event
61
     * @return void
62
     */
63
    public function startup(Event $event)
64
    {
65
        $this->setController($event->subject());
66
    }
67
68
    /**
69
     * @param $controller
70
     * @return void
71
     */
72
    public function setController($controller)
73
    {
74
        $this->controller = $controller;
75
        if (!isset($this->controller->paginate)) {
76
            $this->controller->paginate = [];
77
        }
78
    }
79
80
    /**
81
     * @param string $shopDomain
82
     * @return string|null
83
     */
84
    public function setShopDomain($shopDomain)
85
    {
86
        return $this->shop_domain = $shopDomain;
87
    }
88
    
89
    /**
90
     * @return string|null
91
     */
92
    public function getShopDomain()
93
    {
94
        return $this->shop_domain;
95
    }
96
97
    /**
98
     * @param string $token
99
     * @return string|null
100
     */
101
    public function setAccessToken($token)
102
    {
103
        return $this->token = $token;
104
    }
105
106
    /**
107
     * @return int|null
108
     */
109
    public function callsMade()
110
    {
111
        return $this->shopApiCallLimitParam(0);
112
    }
113
    
114
    /**
115
     * @return int|null
116
     */
117
    public function callLimit()
118
    {
119
        return $this->shopApiCallLimitParam(1);
120
    }
121
122
    /**
123
     * @param $responseHeaders
124
     * @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...
125
     */
126
    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...
127
    {
128
        return $this->callLimit() - $this->callsMade();
129
    }
130
131
    /**
132
     * @param string $method
133
     * @param string $path
134
     * @param array  $params
135
     * @return array|null
136
     */
137
    public function call($method, $path, $params = [])
138
    {
139
        if (!$this->_isReady()) {
140
            return false;
141
        }
142
143
        if (!in_array($method, ['POST', 'PUT', 'GET', 'DELETE'])) {
144
            return false;
145
        }
146
147
        $http = new Client(
148
            [
149
            'host' => $this->shop_domain,
150
            'scheme' => 'https',
151
            'headers' => (($this->is_private_app != 'true') ? (['X-Shopify-Access-Token' => $this->token]) : []),
152
            'auth' => (($this->is_private_app != 'true') ? [] : (['username' => $this->api_key, 'password' => $this->private_app_password]))
153
            ]
154
        );
155
156
        $this->response = $http->{strtolower($method)}(
157
            $path,
158
            ((in_array($method, ['POST', 'PUT'])) ? json_encode($params) : $params),
159
            ((in_array($method, ['POST', 'PUT'])) ? ['type' => 'json'] : [])
160
        );
161
        $this->response = $this->response->json;
162
163
        return (is_array($this->response) && (count($this->response) > 0)) ? array_shift($this->response) : $this->response;
164
    }
165
166
    /**
167
     * @param int $index
168
     * @return int
169
     */
170
    private function shopApiCallLimitParam($index)
171
    {
172
        $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...
173
174
        return (int) $params[$index];
175
    }
176
177
    /**
178
     * @param string $shopDomain
179
     * @param string $redirectUrl
180
     * @return string
181
     */
182
    public function getAuthorizeUrl($shopDomain, $redirectUrl)
183
    {
184
        $url = 'https://' . $shopDomain . '/admin/oauth/authorize?client_id=' . $this->api_key;
185
        $url .= '&scope=' . urlencode($this->scope);
186
        $url .= '&redirect_uri=' . urlencode($redirectUrl);
187
        $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...
188
        
189
        return $url;
190
    }
191
192
    /**
193
     * @param string $shopDomain
194
     * @param string $code
195
     * @return string|bool
196
     */
197
    public function getAccessToken($shopDomain, $code)
198
    {
199
        $this->shop_domain = $shopDomain;
200
201
        $http = new Client(
202
            [
203
            'host' => $shopDomain,
204
            'scheme' => 'https'
205
            ]
206
        );
207
208
        $response = $http->post(
209
            '/admin/oauth/access_token', 'client_id=' . $this->api_key .
210
                                    '&client_secret=' . $this->shared_secret .
211
            '&code=' . $code
212
        );
213
        $response = $response->json;
214
        ;
215
216
        if (isset($response['access_token'])) {
217
            $this->token = $response['access_token'];
218
219
            return $this->token;
220
        } else {
221
            return false;
222
        }
223
    }
224
225
    /**
226
     * @param string $shopDomain
227
     * @return string|null
228
     */
229
    public function setNonce($shopDomain)
230
    {
231
        return $this->nonce = md5(strtolower($shopDomain));
232
    }
233
234
    /**
235
     * @return string|null
236
     */
237
    public function getNonce()
238
    {
239
        return $this->nonce;
240
    }
241
242
    /**
243
     * @param string $shopDomain
244
     * @return bool
245
     */
246
    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...
247
    {
248
        return true;
249
    }
250
251
    /**
252
     * @return json
0 ignored issues
show
Documentation introduced by
Should the return type not be array|null?

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...
253
     */
254
    public function getShopData()
255
    {
256
        return $this->call('GET', '/admin/shop.json');
257
    }
258
259
    /**
260
     * @param array $query
261
     * @return bool
262
     */
263
    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...
264
    {
265
        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...
266
            return false;
267
        }
268
269
        $dataString = [];
270
271
        foreach ($query as $key => $value) {
272
            $key = $this->_urlEncode(str_replace('=', '%3D', $key));
273
            $value = $this->_urlEncode($value);
274
            if ($key != 'hmac') {
275
                $dataString[] = $key . '=' . $value;
276
            }
277
        }
278
279
        sort($dataString);
280
        $string = implode("&", $dataString);
281
282
        return $query['hmac'] === hash_hmac('sha256', $string, $this->shared_secret);
283
    }
284
285
    /**
286
     * @param string $url
287
     * @return string
288
     */
289
    private function _urlEncode($url)
290
    {
291
        $url = str_replace('&', '%26', $url);
292
        $url = str_replace('%', '%25', $url);
293
        
294
        return $url;
295
    }
296
297
    /**
298
     * @return bool
299
     */
300
    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...
301
    {
302
        return strlen($this->shop_domain) > 0 && strlen($this->token) > 0;
303
    }
304
}
305