Completed
Pull Request — master (#262)
by
unknown
01:03
created

HttpClient::lookForErrors()   A

Complexity

Conditions 5
Paths 7

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 24
rs 9.2248
c 0
b 0
f 0
cc 5
nc 7
nop 1
1
<?php
2
/**
3
 * WooCommerce REST API HTTP Client
4
 *
5
 * @category HttpClient
6
 * @package  Automattic/WooCommerce
7
 */
8
9
namespace Automattic\WooCommerce\HttpClient;
10
11
use Automattic\WooCommerce\Client;
12
use Automattic\WooCommerce\HttpClient\BasicAuth;
13
use Automattic\WooCommerce\HttpClient\HttpClientException;
14
use Automattic\WooCommerce\HttpClient\OAuth;
15
use Automattic\WooCommerce\HttpClient\Options;
16
use Automattic\WooCommerce\HttpClient\Request;
17
use Automattic\WooCommerce\HttpClient\Response;
18
19
/**
20
 * REST API HTTP Client class.
21
 *
22
 * @package Automattic/WooCommerce
23
 */
24
class HttpClient
25
{
26
27
    /**
28
     * cURL handle.
29
     *
30
     * @var resource
31
     */
32
    protected $ch;
33
34
    /**
35
     * Store API URL.
36
     *
37
     * @var string
38
     */
39
    protected $url;
40
41
    /**
42
     * Consumer key.
43
     *
44
     * @var string
45
     */
46
    protected $consumerKey;
47
48
    /**
49
     * Consumer secret.
50
     *
51
     * @var string
52
     */
53
    protected $consumerSecret;
54
55
    /**
56
     * Client options.
57
     *
58
     * @var Options
59
     */
60
    protected $options;
61
62
    /**
63
     * Request.
64
     *
65
     * @var Request
66
     */
67
    private $request;
68
69
    /**
70
     * Response.
71
     *
72
     * @var Response
73
     */
74
    private $response;
75
76
    /**
77
     * Response headers.
78
     *
79
     * @var string
80
     */
81
    private $responseHeaders;
82
83
    /**
84
     * Initialize HTTP client.
85
     *
86
     * @param string $url            Store URL.
87
     * @param string $consumerKey    Consumer key.
88
     * @param string $consumerSecret Consumer Secret.
89
     * @param array  $options        Client options.
90
     */
91
    public function __construct($url, $consumerKey, $consumerSecret, $options)
92
    {
93
        if (!\function_exists('curl_version')) {
94
            throw new HttpClientException('cURL is NOT installed on this server', -1, new Request(), new Response());
95
        }
96
97
        $this->options        = new Options($options);
98
        $this->url            = $this->buildApiUrl($url);
99
        $this->consumerKey    = $consumerKey;
100
        $this->consumerSecret = $consumerSecret;
101
    }
102
103
    /**
104
     * Check if is under SSL.
105
     *
106
     * @return bool
107
     */
108
    protected function isSsl()
109
    {
110
        return 'https://' === \substr($this->url, 0, 8);
111
    }
112
113
    
114
    
115
    /**
116
     * Build API URL.
117
     *
118
     * @param string $url Store URL.
119
     *
120
     * @return string
121
     */
122
    protected function buildApiUrl($url)
123
    {
124
        $api = $this->options->isWPAPI() ? $this->options->apiPrefix() : '/wc-api/';
125
126
        return \rtrim($url, '/') . $api . $this->options->getVersion() . '/';
127
    }
128
129
    /**
130
     * Build URL.
131
     *
132
     * @param string $url        URL.
133
     * @param array  $parameters Query string parameters.
134
     *
135
     * @return string
136
     */
137
    protected function buildUrlQuery($url, $parameters = [])
138
    {
139
        if (!empty($parameters)) {
140
            $url .= '?' . \http_build_query($parameters);
141
        }
142
143
        return $url;
144
    }
145
146
    /**
147
     * Authenticate.
148
     *
149
     * @param string $url        Request URL.
150
     * @param string $method     Request method.
151
     * @param array  $parameters Request parameters.
152
     *
153
     * @return array
154
     */
155
    protected function authenticate($url, $method, $parameters = [])
156
    {
157
        // Setup authentication.
158
        if (this->options->useBasicAuth() && $this->isSsl()) {	
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_OBJECT_OPERATOR
Loading history...
159
            $basicAuth  = new BasicAuth(	
160
                $this->ch,	
161
                $this->consumerKey,	
162
                $this->consumerSecret,	
163
                $this->options->isQueryStringAuth(),	
164
                $parameters	
165
            );	
166
            $parameters = $basicAuth->getParameters();	
167
        } else {	
168
            $oAuth      = new OAuth(	            $oAuth      = new OAuth(
169
                $url,	                $url,
170
                $this->consumerKey,	                $this->consumerKey,
171
                $this->consumerSecret,	                $this->consumerSecret,
172
                $this->options->getVersion(),	                $this->options->getVersion(),
173
                $method,	                $method,
174
                $parameters,	                $parameters,
175
                $this->options->oauthTimestamp()	                $this->options->oauthTimestamp()
176
            );	            );
177
            $parameters = $oAuth->getParameters();	            $parameters = $oAuth->getParameters();
178
        }
179
        
180
        return $parameters;
181
    }
182
183
    /**
184
     * Setup method.
185
     *
186
     * @param string $method Request method.
187
     */
188
    protected function setupMethod($method)
189
    {
190
        if ('POST' == $method) {
191
            \curl_setopt($this->ch, CURLOPT_POST, true);
192
        } elseif (\in_array($method, ['PUT', 'DELETE', 'OPTIONS'])) {
193
            \curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST, $method);
194
        }
195
    }
196
197
    /**
198
     * Get request headers.
199
     *
200
     * @param  bool $sendData If request send data or not.
201
     *
202
     * @return array
203
     */
204
    protected function getRequestHeaders($sendData = false)
205
    {
206
        $headers = [
207
            'Accept'     => 'application/json',
208
            'User-Agent' => $this->options->userAgent() . '/' . Client::VERSION,
209
        ];
210
211
        if ($sendData) {
212
            $headers['Content-Type'] = 'application/json;charset=utf-8';
213
        }
214
215
        return $headers;
216
    }
217
218
    /**
219
     * Create request.
220
     *
221
     * @param string $endpoint   Request endpoint.
222
     * @param string $method     Request method.
223
     * @param array  $data       Request data.
224
     * @param array  $parameters Request parameters.
225
     *
226
     * @return Request
227
     */
228
    protected function createRequest($endpoint, $method, $data = [], $parameters = [])
229
    {
230
        $body    = '';
231
        $url     = $this->url . $endpoint;
232
        $hasData = !empty($data);
233
234
        // Setup authentication.
235
        $parameters = $this->authenticate($url, $method, $parameters);
236
237
        // Setup method.
238
        $this->setupMethod($method);
239
240
        // Include post fields.
241
        if ($hasData) {
242
            $body = \json_encode($data);
243
            \curl_setopt($this->ch, CURLOPT_POSTFIELDS, $body);
244
        }
245
246
        $this->request = new Request(
247
            $this->buildUrlQuery($url, $parameters),
248
            $method,
249
            $parameters,
250
            $this->getRequestHeaders($hasData),
251
            $body
252
        );
253
254
        return $this->getRequest();
255
    }
256
257
    /**
258
     * Get response headers.
259
     *
260
     * @return array
261
     */
262
    protected function getResponseHeaders()
263
    {
264
        $headers = [];
265
        $lines   = \explode("\n", $this->responseHeaders);
266
        $lines   = \array_filter($lines, 'trim');
267
268
        foreach ($lines as $index => $line) {
269
            // Remove HTTP/xxx params.
270
            if (strpos($line, ': ') === false) {
271
                continue;
272
            }
273
274
            list($key, $value) = \explode(': ', $line);
275
276
            $headers[$key] = isset($headers[$key]) ? $headers[$key] . ', ' . trim($value) : trim($value);
277
        }
278
279
        return $headers;
280
    }
281
282
    /**
283
     * Create response.
284
     *
285
     * @return Response
286
     */
287
    protected function createResponse()
288
    {
289
290
        // Set response headers.
291
        $this->responseHeaders = '';
292
        \curl_setopt($this->ch, CURLOPT_HEADERFUNCTION, function ($_, $headers) {
293
            $this->responseHeaders .= $headers;
294
            return \strlen($headers);
295
        });
296
297
        // Get response data.
298
        $body    = \curl_exec($this->ch);
299
        $code    = \curl_getinfo($this->ch, CURLINFO_HTTP_CODE);
300
        $headers = $this->getResponseHeaders();
301
302
        // Register response.
303
        $this->response = new Response($code, $headers, $body);
304
305
        return $this->getResponse();
306
    }
307
308
    /**
309
     * Set default cURL settings.
310
     */
311
    protected function setDefaultCurlSettings()
312
    {
313
        $verifySsl       = $this->options->verifySsl();
314
        $timeout         = $this->options->getTimeout();
315
        $followRedirects = $this->options->getFollowRedirects();
316
317
        \curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, $verifySsl);
318
        if (!$verifySsl) {
319
            \curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST, $verifySsl);
320
        }
321
        if ($followRedirects) {
322
            \curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, true);
323
        }
324
        \curl_setopt($this->ch, CURLOPT_CONNECTTIMEOUT, $timeout);
325
        \curl_setopt($this->ch, CURLOPT_TIMEOUT, $timeout);
326
        \curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true);
327
        \curl_setopt($this->ch, CURLOPT_HTTPHEADER, $this->request->getRawHeaders());
328
        \curl_setopt($this->ch, CURLOPT_URL, $this->request->getUrl());
329
    }
330
331
    /**
332
     * Look for errors in the request.
333
     *
334
     * @param array $parsedResponse Parsed body response.
335
     */
336
    protected function lookForErrors($parsedResponse)
337
    {
338
        // Any non-200/201/202 response code indicates an error.
339
        if (!\in_array($this->response->getCode(), ['200', '201', '202'])) {
340
            $errors = isset($parsedResponse->errors) ? $parsedResponse->errors : $parsedResponse;
341
            $errorMessage = '';
342
            $errorCode = '';
343
344
            if (is_array($errors)) {
345
                $errorMessage = $errors[0]->message;
346
                $errorCode    = $errors[0]->code;
347
            } elseif (isset($errors->message, $errors->code)) {
348
                $errorMessage = $errors->message;
349
                $errorCode    = $errors->code;
350
            }
351
352
            throw new HttpClientException(
353
                \sprintf('Error: %s [%s]', $errorMessage, $errorCode),
354
                $this->response->getCode(),
355
                $this->request,
356
                $this->response
357
            );
358
        }
359
    }
360
361
    /**
362
     * Process response.
363
     *
364
     * @return array
365
     */
366
    protected function processResponse()
367
    {
368
        $body = $this->response->getBody();
369
370
        // Look for UTF-8 BOM and remove.
371
        if (0 === strpos(bin2hex(substr($body, 0, 4)), 'efbbbf')) {
372
            $body = substr($body, 3);
373
        }
374
375
        $parsedResponse = \json_decode($body);
376
377
        // Test if return a valid JSON.
378
        if (JSON_ERROR_NONE !== json_last_error()) {
379
            $message = function_exists('json_last_error_msg') ? json_last_error_msg() : 'Invalid JSON returned';
380
            throw new HttpClientException(
381
                sprintf('JSON ERROR: %s', $message),
382
                $this->response->getCode(),
383
                $this->request,
384
                $this->response
385
            );
386
        }
387
388
        $this->lookForErrors($parsedResponse);
389
390
        return $parsedResponse;
391
    }
392
393
    /**
394
     * Make requests.
395
     *
396
     * @param string $endpoint   Request endpoint.
397
     * @param string $method     Request method.
398
     * @param array  $data       Request data.
399
     * @param array  $parameters Request parameters.
400
     *
401
     * @return array
402
     */
403
    public function request($endpoint, $method, $data = [], $parameters = [])
404
    {
405
        // Initialize cURL.
406
        $this->ch = \curl_init();
407
408
        // Set request args.
409
        $request = $this->createRequest($endpoint, $method, $data, $parameters);
410
411
        // Default cURL settings.
412
        $this->setDefaultCurlSettings();
413
414
        // Get response.
415
        $response = $this->createResponse();
416
417
        // Check for cURL errors.
418
        if (\curl_errno($this->ch)) {
419
            throw new HttpClientException('cURL Error: ' . \curl_error($this->ch), 0, $request, $response);
420
        }
421
422
        \curl_close($this->ch);
423
424
        return $this->processResponse();
425
    }
426
427
    /**
428
     * Get request data.
429
     *
430
     * @return Request
431
     */
432
    public function getRequest()
433
    {
434
        return $this->request;
435
    }
436
437
    /**
438
     * Get response data.
439
     *
440
     * @return Response
441
     */
442
    public function getResponse()
443
    {
444
        return $this->response;
445
    }
446
}
447