Completed
Push — master ( 59e3b8...147766 )
by AJ
05:23
created

ShopifyAPIComponent::callsLeft()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 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
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
            'host' => $this->shop_domain,
149
            'scheme' => 'https',
150
            'headers' => (($this->is_private_app != 'true') ? (['X-Shopify-Access-Token' => $this->token]) : []),
151
            'auth' => (($this->is_private_app != 'true') ? [] : (['username' => $this->api_key, 'password' => $this->private_app_password]))
152
        ]);
153
154
        $this->response = $http->{strtolower($method)}(
155
            $path,
156
            ((in_array($method, ['POST', 'PUT'])) ? json_encode($params) : $params),
157
            ((in_array($method, ['POST', 'PUT'])) ? ['type' => 'json'] : [])
158
        );
159
        $this->response = $this->response->json;
160
161
        return (is_array($this->response) && (count($this->response) > 0)) ? array_shift($this->response) : $this->response;
162
    }
163
164
    /**
165
     * @param int $index
166
     * @return int
167
     */
168
    private function shopApiCallLimitParam($index)
169
    {
170
        $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...
171
172
        return (int)$params[$index];
173
    }
174
175
    /**
176
     * @param string $shopDomain
177
     * @param string $redirectUrl
178
     * @return int
0 ignored issues
show
Documentation introduced by
Should the return type not be 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.

Loading history...
179
     */
180
    public function getAuthorizeUrl($shopDomain, $redirectUrl)
181
    {
182
        $url = 'https://' . $shopDomain . '/admin/oauth/authorize?client_id=' . $this->api_key;
183
        $url .= '&scope=' . urlencode($this->scope);
184
        $url .= '&redirect_uri=' . urlencode($redirectUrl);
185
        $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...
186
        return $url;
187
    }
188
189
    /**
190
     * @param string $shopDomain
191
     * @param string $code
192
     * @return string|bool
193
     */
194
    public function getAccessToken($shopDomain, $code)
195
    {
196
        $this->shop_domain = $shopDomain;
197
198
        $http = new Client([
199
            'host' => $shopDomain,
200
            'scheme' => 'https'
201
        ]);
202
203
        $response = $http->post('/admin/oauth/access_token', 'client_id=' . $this->api_key .
204
                                    '&client_secret=' . $this->shared_secret .
205
                                    '&code=' . $code);
206
        $response = $response->json;
207
        ;
208
209
        if (isset($response['access_token'])) {
210
            $this->token = $response['access_token'];
211
212
            return $this->token;
213
        } else {
214
            return false;
215
        }
216
    }
217
218
    /**
219
     * @param string $shopDomain
220
     * @return string|null
221
     */
222
    public function setNonce($shopDomain)
223
    {
224
        return $this->nonce = md5(strtolower($shopDomain));
225
    }
226
227
    /**
228
     * @return string|null
229
     */
230
    public function getNonce()
231
    {
232
        return $this->nonce;
233
    }
234
235
    /**
236
     * @param string $shopDomain
237
     * @return bool
238
     */
239
    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...
240
    {
241
        return true;
242
    }
243
244
    /**
245
     * @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...
246
     */
247
    public function getShopData()
248
    {
249
        return $this->call('GET', '/admin/shop.json');
250
    }
251
252
    /**
253
     * @param array $query
254
     * @return bool
255
     */
256
    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...
257
    {
258
        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...
259
            return false;
260
        }
261
262
        $dataString = [];
263
264
        foreach ($query as $key => $value) {
265
            $key = $this->_urlEncode(str_replace('=', '%3D', $key));
266
            $value = $this->_urlEncode($value);
267
            if ($key != 'hmac') {
268
                $dataString[] = $key . '=' . $value;
269
            }
270
        }
271
272
        sort($dataString);
273
        $string = implode("&", $dataString);
274
275
        return $query['hmac'] === hash_hmac('sha256', $string, $this->shared_secret);
276
    }
277
278
    /**
279
     * @param string $url
280
     * @return string
281
     */
282
    private function _urlEncode($url)
283
    {
284
        $url = str_replace('&', '%26', $url);
285
        $url = str_replace('%', '%25', $url);
286
        return $url;
287
    }
288
289
    /**
290
     * @return bool
291
     */
292
    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...
293
    {
294
        return strlen($this->shop_domain) > 0 && strlen($this->token) > 0;
295
    }
296
}
297