PrestashopWebServiceLibrary::getVersion()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
c 0
b 0
f 0
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
namespace DansMaCulotte\PrestashopWebService;
4
5
use DansMaCulotte\PrestashopWebService\Exceptions\PrestashopWebServiceException;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, DansMaCulotte\Prestashop...shopWebServiceException.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
6
use DansMaCulotte\PrestashopWebService\Exceptions\PrestashopWebServiceRequestException;
7
use SimpleXMLElement;
8
9
/**
10
 * @package PrestaShopWebService
11
 */
12
class PrestashopWebServiceLibrary
13
{
14
    /** @var string Shop URL */
15
    protected $url;
16
17
    /** @var string Authentification key */
18
    protected $key;
19
20
    /** @var boolean is debug activated */
21
    protected $debug;
22
23
    /** @var string PS version */
24
    protected $version;
25
26
    /** @var boolean Are we running in a console */
27
    protected $runningInConsole;
28
29
    /** @var array compatible versions of PrestaShop WebService */
30
    const PS_COMPATIBLE_VERSION_MIN = '1.4.0.0';
31
    const PS_COMPATIBLE_VERSION_MAX = '1.7.99.99';
32
33
    /**
34
     * PrestaShopWebService constructor. Throw an exception when CURL is not installed/activated
35
     * <code>
36
     * <?php
37
     * require_once('./PrestaShopWebService.php');
38
     * try
39
     * {
40
     *    $ws = new PrestaShopWebService('http://mystore.com/', 'ZQ88PRJX5VWQHCWE4EE7SQ7HPNX00RAJ', false);
41
     *    // Now we have a WebService object to play with
42
     * }
43
     * catch (PrestashopWebServiceException $ex)
44
     * {
45
     *    echo 'Error : '.$ex->getMessage();
46
     * }
47
     * ?>
48
     * </code>
49
     * @param string $url Root URL for the shop
50
     * @param string $key Authentification key
51
     * @param mixed $debug Debug mode Activated (true) or deactivated (false)
52
     * @throws PrestashopWebServiceException
53
     */
54
    public function __construct($url, $key, $debug = true)
55
    {
56
        if (!extension_loaded('curl')) {
57
            $exception = 'Please activate the PHP extension \'curl\' to allow use of PrestaShop WebService library';
58
            throw new PrestashopWebServiceException($exception);
59
        }
60
61
        $this->url = $url;
62
        $this->key = $key;
63
        $this->debug = $debug;
64
        $this->version = 'unknown';
65
66
        $this->runningInConsole = app()->runningInConsole();
67
    }
68
69
    /**
70
     * Take the status code and throw an exception if the server didn't return 200 or 201 code
71
     * @param int $status_code Status code of an HTTP return
0 ignored issues
show
Bug introduced by
There is no parameter named $status_code. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
72
     * @return boolean
73
     * @throws PrestashopWebServiceException
74
     */
75
    protected function checkRequest($request)
76
    {
77
        if ($request['status_code'] === 200 || $request['status_code'] === 201) {
78
            return true;
79
        }
80
81
        $messages = [
82
            204 => 'No content',
83
            400 => 'Bad Request',
84
            401 => 'Unauthorized',
85
            404 => 'Not Found',
86
            405 => 'Method Not Allowed',
87
            500 => 'Internal Server Error',
88
        ];
89
90
        if (isset($messages[$request['status_code']])) {
91
            $xml = null;
92
            if ($request['response']) {
93
                $xml = $this->parseXML($request['response'], true);
94
            }
95
96
            throw new PrestashopWebServiceRequestException($messages[$request['status_code']], $request['status_code'], $xml);
97
        } else {
98
            $exception = 'This call to PrestaShop Web Services returned an unexpected HTTP status of: ';
99
            $exception.= $request['status_code'];
100
            throw new PrestashopWebServiceException($exception);
101
        }
102
    }
103
104
    /**
105
     * Throws exception if prestashop version is not supported
106
     * @param int $version The prestashop version
107
     * @throws PrestashopWebServiceException
108
     */
109
    public function isPrestashopVersionSupported($version)
110
    {
111
        if (version_compare($version, self::PS_COMPATIBLE_VERSION_MIN, '>=') === false ||
112
            version_compare($version, self::PS_COMPATIBLE_VERSION_MAX, '<=') === false
113
        ) {
114
            $exception = 'This library is not compatible with this version of PrestaShop. ';
115
            $exception.= 'Please upgrade/downgrade this library';
116
            throw new PrestashopWebServiceException($exception);
117
        }
118
    }
119
120
    /**
121
     * Prepares and validate a CURL request to PrestaShop WebService. Can throw exception.
122
     * @param string $url Resource name
123
     * @param mixed $curl_params CURL parameters (sent to curl_set_opt)
124
     * @return array status_code, response
125
     * @throws PrestashopWebServiceException
126
     */
127
    protected function executeRequest($url, $curl_params = [])
128
    {
129
        $defaultParams = [
130
            CURLOPT_HEADER => true,
131
            CURLOPT_RETURNTRANSFER => true,
132
            CURLINFO_HEADER_OUT => true,
133
            CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
134
            CURLOPT_USERPWD => $this->key.':',
135
            CURLOPT_HTTPHEADER => [ 'Expect:' ],
136
            CURLOPT_SSL_VERIFYPEER => config('app.env') === 'local' ? 0 : 1,
137
            CURLOPT_SSL_VERIFYHOST => config('app.env') === 'local' ? 0 : 2 // value 1 is not accepted https://curl.haxx.se/libcurl/c/CURLOPT_SSL_VERIFYHOST.html
138
        ];
139
140
        $curl_options = [];
141
        foreach ($defaultParams as $defkey => $defval) {
142
            if (isset($curl_params[$defkey])) {
143
                $curl_options[$defkey] = $curl_params[$defkey];
144
            } else {
145
                $curl_options[$defkey] = $defaultParams[$defkey];
146
            }
147
        }
148
        foreach ($curl_params as $defkey => $defval) {
149
            if (!isset($curl_options[$defkey])) {
150
                $curl_options[$defkey] = $curl_params[$defkey];
151
            }
152
        }
153
154
        list($response, $info, $error) = $this->executeCurl($url, $curl_options);
155
156
        $status_code = $info['http_code'];
157
        if ($status_code === 0 || $error) {
158
            throw new PrestashopWebServiceException('CURL Error: '.$error);
159
        }
160
161
        $index = $info['header_size'];
162
        if ($index === false && $curl_params[CURLOPT_CUSTOMREQUEST] !== 'HEAD') {
163
            throw new PrestashopWebServiceException('Bad HTTP response');
164
        }
165
166
        $header = substr($response, 0, $index);
167
        $body = substr($response, $index);
168
169
        $headerArray = [];
170
        foreach (explode("\n", $header) as $headerItem) {
171
            $tmp = explode(':', $headerItem, 2);
172
            if (count($tmp) === 2) {
173
                $tmp = array_map('trim', $tmp);
174
                $headerArray[$tmp[0]] = $tmp[1];
175
            }
176
        }
177
178
        if (array_key_exists('PSWS-Version', $headerArray)) {
179
            $this->isPrestashopVersionSupported($headerArray['PSWS-Version']);
180
            $this->version = $headerArray['PSWS-Version'];
181
        }
182
183
        $this->printDebug('HTTP REQUEST HEADER', $info['request_header']);
184
        $this->printDebug('HTTP RESPONSE HEADER', $header);
185
186
        if ($curl_params[CURLOPT_CUSTOMREQUEST] == 'PUT' || $curl_params[CURLOPT_CUSTOMREQUEST] == 'POST') {
187
            $this->printDebug('XML SENT', urldecode($curl_params[CURLOPT_POSTFIELDS]));
188
        }
189
        if ($curl_params[CURLOPT_CUSTOMREQUEST] != 'DELETE' && $curl_params[CURLOPT_CUSTOMREQUEST] != 'HEAD') {
190
            $this->printDebug('RETURN HTTP BODY', $body);
191
        }
192
193
        return [
194
            'status_code' => $status_code,
195
            'response' => $body,
196
            'header' => $header,
197
            'headers' => $headerArray
198
            ];
199
    }
200
201
    /**
202
     * Executes the CURL request to PrestaShop WebService.
203
     * @param string $url Resource name
204
     * @param mixed $options CURL parameters (sent to curl_setopt_array)
205
     * @return array response, info
206
     */
207
    protected function executeCurl($url, array $options = [])
208
    {
209
        $session = curl_init($url);
210
211
        if (count($options)) {
212
            curl_setopt_array($session, $options);
213
        }
214
215
        $response = curl_exec($session);
216
217
        $error = false;
218
        $info = curl_getinfo($session);
219
        if ($response === false) {
220
            $error = curl_error($session);
221
        }
222
223
        curl_close($session);
224
225
        return [$response, $info, $error];
226
    }
227
228
    public function printDebug($title, $content)
229
    {
230
        if ($this->debug) {
231
            if ($this->runningInConsole) {
232
                echo 'START '.$title."\n";
233
                echo $content . "\n";
234
                echo 'END '.$title."\n";
235
                echo "\n";
236
            } else {
237
                echo '<div style="display:table;background:#CCC;font-size:8pt;padding:7px">';
238
                echo '<h6 style="font-size:9pt;margin:0">'.$title.'</h6>';
239
                echo '<pre>'.htmlentities($content).'</pre>';
240
                echo '</div>';
241
            }
242
        }
243
    }
244
245
    public function getVersion()
246
    {
247
        return $this->version;
248
    }
249
250
    /**
251
     * Load XML from string. Can throw exception
252
     * @param string $response String from a CURL response
253
     * @param boolean $suppressExceptions Whether to throw exceptions on errors
254
     * @return SimpleXMLElement status_code, response
255
     * @throws PrestashopWebServiceException
256
     */
257
    protected function parseXML($response, $suppressExceptions = false)
258
    {
259
        if ($response != '') {
260
            libxml_clear_errors();
261
            libxml_use_internal_errors(true);
262
            $xml = simplexml_load_string($response, 'SimpleXMLElement', LIBXML_NOCDATA);
263
            if (libxml_get_errors()) {
264
                $msg = var_export(libxml_get_errors(), true);
265
                libxml_clear_errors();
266
267
                if (!$suppressExceptions) {
268
                    throw new PrestashopWebServiceException('HTTP XML response is not parsable: '.$msg);
269
                }
270
            }
271
272
            return $xml;
273
        } elseif (!$suppressExceptions) {
274
            throw new PrestashopWebServiceException('HTTP response is empty');
275
        }
276
277
        return null;
278
    }
279
280
    /**
281
     * Add (POST) a resource
282
     * <p>Unique parameter must take : <br><br>
283
     * 'resource' => Resource name<br>
284
     * 'postXml' => Full XML string to add resource<br><br>
285
     * Examples are given in the tutorial</p>
286
     * @param array $options
287
     * @return SimpleXMLElement status_code, response
288
     * @throws PrestashopWebServiceException
289
     */
290
    public function add($options)
291
    {
292
        $xml = '';
0 ignored issues
show
Unused Code introduced by
$xml is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
293
294
        if (isset($options['resource'], $options['postXml']) || isset($options['url'], $options['postXml'])) {
295
            $url = (isset($options['resource']) ? $this->url.'/api/'.$options['resource'] : $options['url']);
296
            $xml = $options['postXml'];
297
            if (isset($options['id_shop'])) {
298
                $url .= '&id_shop='.$options['id_shop'];
299
            }
300
            if (isset($options['id_group_shop'])) {
301
                $url .= '&id_group_shop='.$options['id_group_shop'];
302
            }
303
        } else {
304
            throw new PrestashopWebServiceException('Bad parameters given');
305
        }
306
        $request = $this->executeRequest($url, [CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_POSTFIELDS => $xml]);
307
308
        $this->checkRequest($request);
309
        return $this->parseXML($request['response']);
310
    }
311
312
    /**
313
     * Retrieve (GET) a resource
314
     * <p>Unique parameter must take : <br><br>
315
     * 'url' => Full URL for a GET request of WebService (ex: http://mystore.com/api/customers/1/)<br>
316
     * OR<br>
317
     * 'resource' => Resource name,<br>
318
     * 'id' => ID of a resource you want to get<br><br>
319
     * </p>
320
     * <code>
321
     * <?php
322
     * require_once('./PrestaShopWebService.php');
323
     * try
324
     * {
325
     * $ws = new PrestaShopWebService('http://mystore.com/', 'ZQ88PRJX5VWQHCWE4EE7SQ7HPNX00RAJ', false);
326
     * $xml = $ws->get(array('resource' => 'orders', 'id' => 1));
327
     *    // Here in $xml, a SimpleXMLElement object you can parse
328
     * foreach ($xml->children()->children() as $attName => $attValue)
329
     *    echo $attName.' = '.$attValue.'<br />';
330
     * }
331
     * catch (PrestashopWebServiceException $ex)
332
     * {
333
     *    echo 'Error : '.$ex->getMessage();
334
     * }
335
     * ?>
336
     * </code>
337
     * @param array $options Array representing resource to get.
338
     * @return SimpleXMLElement status_code, response
339
     * @throws PrestashopWebServiceException
340
     */
341
    public function get($options)
342
    {
343
        if (isset($options['url'])) {
344
            $url = $options['url'];
345
        } elseif (isset($options['resource'])) {
346
            $url = $this->url.'/api/'.$options['resource'];
347
            $url_params = [];
348
            if (isset($options['id'])) {
349
                $url .= '/'.$options['id'];
350
            }
351
352
            $params = ['filter', 'display', 'sort', 'limit', 'id_shop', 'id_group_shop','date', 'price'];
353 View Code Duplication
            foreach ($params as $p) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
354
                foreach ($options as $k => $o) {
355
                    if (strpos($k, $p) !== false) {
356
                        $url_params[$k] = $options[$k];
357
                    }
358
                }
359
            }
360
            if (count($url_params) > 0) {
361
                $url .= '?'.http_build_query($url_params);
362
            }
363
        } else {
364
            throw new PrestashopWebServiceException('Bad parameters given');
365
        }
366
367
        $request = $this->executeRequest($url, [CURLOPT_CUSTOMREQUEST => 'GET']);
368
369
        $this->checkRequest($request);// check the response validity
370
        return $this->parseXML($request['response']);
371
    }
372
373
    /**
374
     * Head method (HEAD) a resource
375
     *
376
     * @param array $options Array representing resource for head request.
377
     * @return SimpleXMLElement status_code, response
378
     * @throws PrestashopWebServiceException
379
     */
380
    public function head($options)
381
    {
382
        if (isset($options['url'])) {
383
            $url = $options['url'];
384
        } elseif (isset($options['resource'])) {
385
            $url = $this->url.'/api/'.$options['resource'];
386
            $url_params = [];
387
            if (isset($options['id'])) {
388
                $url .= '/'.$options['id'];
389
            }
390
391
            $params = ['filter', 'display', 'sort', 'limit'];
392 View Code Duplication
            foreach ($params as $p) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
393
                foreach ($options as $k => $o) {
394
                    if (strpos($k, $p) !== false) {
395
                        $url_params[$k] = $options[$k];
396
                    }
397
                }
398
            }
399
            if (count($url_params) > 0) {
400
                $url .= '?'.http_build_query($url_params);
401
            }
402
        } else {
403
            throw new PrestashopWebServiceException('Bad parameters given');
404
        }
405
        $request = $this->executeRequest($url, [CURLOPT_CUSTOMREQUEST => 'HEAD', CURLOPT_NOBODY => true]);
406
        $this->checkRequest($request);// check the response validity
407
        return $request['header'];
408
    }
409
410
    /**
411
     * Edit (PUT) a resource
412
     * <p>Unique parameter must take : <br><br>
413
     * 'resource' => Resource name ,<br>
414
     * 'id' => ID of a resource you want to edit,<br>
415
     * 'putXml' => Modified XML string of a resource<br><br>
416
     * Examples are given in the tutorial</p>
417
     * @param array $options Array representing resource to edit.
418
     * @return SimpleXMLElement
419
     * @throws PrestashopWebServiceException
420
     */
421
    public function edit($options)
422
    {
423
        $xml = '';
424
        if (isset($options['url'])) {
425
            $url = $options['url'];
426
        } elseif ((isset($options['resource'], $options['id']) || isset($options['url'])) && $options['putXml']) {
427
            if (isset($options['url'])) {
428
                $url = $options['url'];
429 View Code Duplication
            } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
430
                $url = $this->url.'/api/'.$options['resource'].'/'.$options['id'];
431
            }
432
            $xml = $options['putXml'];
433
            if (isset($options['id_shop'])) {
434
                $url .= '&id_shop='.$options['id_shop'];
435
            }
436
            if (isset($options['id_group_shop'])) {
437
                $url .= '&id_group_shop='.$options['id_group_shop'];
438
            }
439
        } else {
440
            throw new PrestashopWebServiceException('Bad parameters given');
441
        }
442
443
        $request = $this->executeRequest($url, [CURLOPT_CUSTOMREQUEST => 'PUT', CURLOPT_POSTFIELDS => $xml]);
444
        $this->checkRequest($request);// check the response validity
445
        return $this->parseXML($request['response']);
446
    }
447
448
    /**
449
     * Delete (DELETE) a resource.
450
     * Unique parameter must take : <br><br>
451
     * 'resource' => Resource name<br>
452
     * 'id' => ID or array which contains IDs of a resource(s) you want to delete<br><br>
453
     * <code>
454
     * <?php
455
     * require_once('./PrestaShopWebService.php');
456
     * try
457
     * {
458
     * $ws = new PrestaShopWebService('http://mystore.com/', 'ZQ88PRJX5VWQHCWE4EE7SQ7HPNX00RAJ', false);
459
     * $xml = $ws->delete(array('resource' => 'orders', 'id' => 1));
460
     *    // Following code will not be executed if an exception is thrown.
461
     *    echo 'Successfully deleted.';
462
     * }
463
     * catch (PrestashopWebServiceException $ex)
464
     * {
465
     *    echo 'Error : '.$ex->getMessage();
466
     * }
467
     * ?>
468
     * </code>
469
     * @param array $options Array representing resource to delete.
470
     * @return bool
471
     * @throws PrestashopWebServiceException
472
     */
473
    public function delete($options)
474
    {
475
        if (isset($options['url'])) {
476
            $url = $options['url'];
477
        } elseif (isset($options['resource']) && isset($options['id'])) {
478
            if (is_array($options['id'])) {
479
                $url = $this->url.'/api/'.$options['resource'].'/?id=['.implode(',', $options['id']).']';
480 View Code Duplication
            } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
481
                $url = $this->url.'/api/'.$options['resource'].'/'.$options['id'];
482
            }
483
        }
484
        if (isset($options['id_shop'])) {
485
            $url .= '&id_shop='.$options['id_shop'];
0 ignored issues
show
Bug introduced by
The variable $url does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
486
        }
487
        if (isset($options['id_group_shop'])) {
488
            $url .= '&id_group_shop='.$options['id_group_shop'];
489
        }
490
        $request = $this->executeRequest($url, [CURLOPT_CUSTOMREQUEST => 'DELETE']);
491
        $this->checkRequest($request);// check the response validity
492
        return true;
493
    }
494
}
495