ApiClient::rawResponseToArray()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 7
cts 7
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 7
nc 2
nop 1
crap 2
1
<?php
2
/**
3
 * primaERP - Basic primaERP rest client class.
4
 *
5
 * @author     Vítězslav Dvořák <[email protected]>
6
 * @copyright  (C) 2017 Spoje.Net
7
 */
8
9
namespace primaERP;
10
11
/**
12
 * Basic class
13
 *
14
 * @url http://devdoc.primaerp.com/
15
 */
16
class ApiClient extends \Ease\Brick
17
{
18
    /**
19
     * Version of php-primaerp library
20
     *
21
     * @var string
22
     */
23
    public static $libVersion = '0.1';
24
25
    /**
26
     * Communication protocol version used.
27
     *
28
     * @var string API version
29
     */
30
    public $protoVersion = 'v1';
31
32
    /**
33
     * URL of object data in primaERP API
34
     * @var string url
35
     */
36
    public $apiURL = null;
37
38
    /**
39
     * Datový blok v poli odpovědi.
40
     * Data block in response field.
41
     *
42
     * @var string
43
     */
44
    public $resultField = 'results';
45
46
    /**
47
     * Section used by object
48
     *
49
     * @link http://devdoc.primaerp.com/rest/index.html
50
     * @var string
51
     */
52
    public $section = null;
53
54
    /**
55
     * Curl Handle.
56
     *
57
     * @var resource
58
     */
59
    public $curl = null;
60
61
    /**
62
     * tenant
63
     *
64
     * @link http://devdoc.primaerp.com/rest/authentication.html Identifikátor firmy
65
     * @var string
66
     */
67
    public $company = null;
68
69
    /**
70
     * Server[:port]
71
     * @var string
72
     */
73
    public $url = null;
74
75
    /**
76
     * REST API Username (usually user's email)
77
     * @var string
78
     */
79
    public $user = null;
80
81
    /**
82
     * REST API Password
83
     * @var string
84
     */
85
    public $password = null;
86
87
    /**
88
     * REST API Key
89
     * @var string
90
     */
91
    public $apikey = null;
92
93
    /**
94
     * @var array Pole HTTP hlaviček odesílaných s každým požadavkem
95
     */
96
    public $defaultHttpHeaders = ['User-Agent' => 'php-primaERP'];
97
98
    /**
99
     * Default additional request url parameters after question mark
100
     *
101
     * @var array
102
     */
103
    public $defaultUrlParams = [];
104
105
    /**
106
     * Identifikační řetězec.
107
     *
108
     * @var string
109
     */
110
    public $init = null;
111
112
    /**
113
     * Informace o posledním HTTP requestu.
114
     *
115
     * @var *
116
     */
117
    public $curlInfo;
118
119
    /**
120
     * Informace o poslední HTTP chybě.
121
     *
122
     * @var string
123
     */
124
    public $lastCurlError = null;
125
126
    /**
127
     * Used codes storage.
128
     *
129
     * @var array
130
     */
131
    public $codes = null;
132
133
    /**
134
     * Last Inserted ID.
135
     *
136
     * @var int
137
     */
138
    public $lastInsertedID = null;
139
140
    /**
141
     * Raw Content of last curl response
142
     *
143
     * @var string
144
     */
145
    public $lastCurlResponse;
146
147
    /**
148
     * HTTP Response code of last request
149
     *
150
     * @var int
151
     */
152
    public $lastResponseCode = null;
153
154
    /**
155
     * Body data  for next curl POST operation
156
     *
157
     * @var string
158
     */
159
    protected $postFields = null;
160
161
    /**
162
     * Last operation result data or message(s)
163
     *
164
     * @var array
165
     */
166
    public $lastResult = null;
167
168
    /**
169
     * Nuber from  @rowCount
170
     * @var int
171
     */
172
    public $rowCount = null;
173
174
    /**
175
     * Save 404 results to log ?
176
     * @var boolean
177
     */
178
    protected $ignoreNotFound = false;
179
180
    /**
181
     * Array of errors caused by last request
182
     * @var array
183
     */
184
    private $errors = [];
185
186
    /**
187
     * Access Token Info
188
     * @var Token
189
     */
190
    protected $tokener = null;
191
192
    /**
193
     * Class for read only interaction with IPEX.
194
     *
195
     * @param mixed $init default record id or initial data
196
     * @param array $options Connection settings override
197
     */
198 1
    public function __construct($init = null, $options = [])
199
    {
200 1
        $this->init = $init;
201
202 1
        parent::__construct();
203 1
        $this->setUp($options);
204 1
        $this->curlInit();
205
206 1
        if (get_class($this) != 'primaERP\Token') {
207 1
            $this->tokener = Token::instanced();
0 ignored issues
show
Documentation Bug introduced by
It seems like \primaERP\Token::instanced() of type object<Ease\Shared> is incompatible with the declared type object<primaERP\Token> of property $tokener.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
208
        }
209
210 1
        if (!empty($init)) {
211 1
            $this->processInit($init);
212
        }
213 1
    }
214
215
    /**
216
     * SetUp Object to be ready for connect
217
     *
218
     * @param array $options Object Options (company,url,user,password,section,
219
     *                                       defaultUrlParams,debug)
220
     */
221 2
    public function setUp($options = [])
222
    {
223 2
        $this->setupProperty($options, 'url', 'PRIMAERP_URL');
224 2
        $this->setupProperty($options, 'user', 'PRIMAERP_LOGIN');
225 2
        $this->setupProperty($options, 'password', 'PRIMAERP_PASSWORD');
226 2
        $this->setupProperty($options, 'apikey', 'PRIMAERP_APIKEY');
227 2
        if (isset($options['section'])) {
228 1
            $this->setSection($options['section']);
229
        }
230 2
        $this->setupProperty($options, 'defaultUrlParams');
231 2
        $this->setupProperty($options, 'debug');
232 2
        $this->updateApiURL();
233 2
    }
234
235
    /**
236
     * Inicializace CURL
237
     */
238 2
    public function curlInit()
239
    {
240 2
        $this->curl = \curl_init(); // create curl resource
241 2
        curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true); // return content as a string from curl_exec
242 2
        curl_setopt($this->curl, CURLOPT_FOLLOWLOCATION, true); // follow redirects (compatibility for future changes in IPEX)
243 2
        curl_setopt($this->curl, CURLOPT_HTTPAUTH, true);       // HTTP authentication
244 2
        curl_setopt($this->curl, CURLOPT_SSL_VERIFYPEER, false); // IPEX by default uses Self-Signed certificates
245 2
        curl_setopt($this->curl, CURLOPT_SSL_VERIFYHOST, false);
246 2
        curl_setopt($this->curl, CURLOPT_VERBOSE, ($this->debug === true)); // For debugging
247 2
        curl_setopt($this->curl, CURLOPT_USERPWD,
248 2
            $this->user.':'.$this->password); // set username and password
249 2
    }
250
251
    /**
252
     * Zinicializuje objekt dle daných dat. Možné hodnoty:
253
     *
254
     *  * 234                              - interní číslo záznamu k načtení
255
     *  * code:LOPATA                      - kód záznamu
256
     *  * BAGR                             - kód záznamu ka načtení
257
     *  * ['id'=>24,'nazev'=>'hoblík']     - pole hodnot k předvyplnění
258
     *  * 743.json?relations=adresa,vazby  - část url s parametry k načtení
259
     *
260
     * @param mixed $init číslo/"(code:)kód"/(část)URI záznamu k načtení | pole hodnot k předvyplnění
261
     */
262 2
    public function processInit($init)
263
    {
264 2
        if (empty($init) == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
265 2
            $this->loadFromAPI($init);
266
        }
267 2
    }
268
269
    /**
270
     * Nastaví Sekci pro Komunikaci.
271
     * Set section for communication
272
     *
273
     * @param string $section section pathName to use
274
     * @return boolean section switching status
275
     */
276 1
    public function setSection($section)
277
    {
278 1
        $this->section = $section;
279 1
        return $this->updateApiURL();
280
    }
281
282
    /**
283
     * Vrací právě používanou evidenci pro komunikaci
284
     * Obtain current used section
285
     *
286
     * @return string
287
     */
288 2
    public function getSection()
289
    {
290 2
        return $this->section;
291
    }
292
293
    /**
294
     * Převede rekurzivně Objekt na pole.
295
     *
296
     * @param object|array $object
297
     *
298
     * @return array
299
     */
300 1
    public static function object2array($object)
301
    {
302 1
        $result = null;
303 1
        if (is_object($object)) {
304 1
            $objectData = get_object_vars($object);
305 1
            if (is_array($objectData) && count($objectData)) {
306 1
                $result = array_map('self::object2array', $objectData);
307
            }
308
        } else {
309 1
            if (is_array($object)) {
310 1
                foreach ($object as $item => $value) {
311 1
                    $result[$item] = self::object2array($value);
312
                }
313
            } else {
314 1
                $result = $object;
315
            }
316
        }
317
318 1
        return $result;
319
    }
320
321
    /**
322
     * Připraví data pro odeslání do FlexiBee
323
     *
324
     * @param string $data
325
     */
326 1
    public function setPostFields($data)
327
    {
328 1
        $this->postFields = $data;
329 1
    }
330
331
    /**
332
     * Return basic URL for used Evidence
333
     *
334
     * @return string Evidence URL
335
     */
336 2
    public function getSectionURL()
337
    {
338 2
        $sectionUrl = $this->url.'/'.$this->protoVersion.'/';
339 2
        $section    = $this->getSection();
340 2
        if (!empty($section)) {
341 2
            $sectionUrl .= $section;
342
        }
343 2
        return $sectionUrl;
344
    }
345
346
    /**
347
     * Add suffix to Evidence URL
348
     *
349
     * @param string $urlSuffix
350
     *
351
     * @return string
352
     */
353 2
    public function sectionUrlWithSuffix($urlSuffix)
354
    {
355 2
        $sectionUrl = $this->getSectionURL();
356 2
        if (!empty($urlSuffix)) {
357 2
            $sectionUrl .= '/'.$urlSuffix;
358
        }
359 2
        return $sectionUrl;
360
    }
361
362
    /**
363
     * Update $this->apiURL
364
     */
365 2
    public function updateApiURL()
366
    {
367 2
        $this->apiURL = $this->getSectionURL();
368 2
    }
369
370
    /**
371
     * Funkce, která provede I/O operaci a vyhodnotí výsledek.
372
     *
373
     * @param string $urlSuffix část URL za identifikátorem firmy.
374
     * @param string $method    HTTP/REST metoda
375
     * 
376
     * @return array|boolean Výsledek operace
377
     */
378 1
    public function requestData($urlSuffix = null, $method = 'GET')
379
    {
380 1
        $this->rowCount = null;
381
382 1
        if (preg_match('/^http/', $urlSuffix)) {
383
            $url = $urlSuffix;
384 1
        } elseif ($urlSuffix[0] == '/') {
385
            $url = $this->url.$urlSuffix;
386
        } else {
387 1
            $url = $this->sectionUrlWithSuffix($urlSuffix);
388
        }
389
390 1
        $this->authentication();
391
392 1
        $url = $this->addDefaultUrlParams($url);
393
394 1
        $responseCode = $this->doCurlRequest($url, $method);
395
396 1
        return strlen($this->lastCurlResponse) ? $this->parseResponse($this->rawResponseToArray($this->lastCurlResponse,
397 1
                    $this->responseMimeType), $responseCode) : null;
0 ignored issues
show
Bug introduced by
The property responseMimeType 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...
Unused Code introduced by
The call to ApiClient::rawResponseToArray() has too many arguments starting with $this->responseMimeType.

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...
398
    }
399
400 2
    public function authentication()
401
    {
402 2
        if (!is_null($this->tokener)) {
403 2
            $this->defaultUrlParams['token'] = $this->getTokenString();
404
        }
405 2
    }
406
407
    /**
408
     * Add params to url
409
     *
410
     * @param string  $url      originall url
411
     * @param array   $params   value to add
412
     * @param boolean $override replace already existing values ?
413
     *
414
     * @return string url with parameters added
415
     */
416 2
    public function addUrlParams($url, $params, $override = false)
417
    {
418 2
        $urlParts = parse_url($url);
419 2
        $urlFinal = $urlParts['scheme'].'://'.$urlParts['host'];
420 2
        if (array_key_exists('path', $urlParts)) {
421 2
            $urlFinal .= $urlParts['path'];
422
        }
423 2
        if (array_key_exists('query', $urlParts)) {
424 1
            parse_str($urlParts['query'], $queryUrlParams);
425 1
            $urlParams = $override ? array_merge($params, $queryUrlParams) : array_merge($queryUrlParams,
426 1
                    $params);
427
        } else {
428 1
            $urlParams = $params;
429
        }
430 2
        if (count($urlParams)) {
431 2
            $urlFinal .= '?'.http_build_query($urlParams);
432
        }
433 2
        return $urlFinal;
434
    }
435
436
    /**
437
     * Add Default Url params to given url if not overrided
438
     *
439
     * @param string $urlRaw
440
     *
441
     * @return string url with default params added
442
     */
443 2
    public function addDefaultUrlParams($urlRaw)
444
    {
445 2
        return $this->addUrlParams($urlRaw, $this->defaultUrlParams, false);
446
    }
447
448
    /**
449
     * Parse primaERP API Response
450
     *
451
     * @param string $responseRaw raw response body
452
     *
453
     * @return array
454
     */
455 2
    public function rawResponseToArray($responseRaw)
456
    {
457 2
        $responseDecoded = json_decode($responseRaw, true, 10);
458 2
        $decodeError     = json_last_error_msg();
459 2
        if ($decodeError != 'No error') {
460 1
            $this->addStatusMessage('JSON Decoder: '.$decodeError, 'error');
461 1
            $this->addStatusMessage($responseRaw, 'debug');
462
        }
463 2
        return $responseDecoded;
464
    }
465
466
    /**
467
     * Parse Response array
468
     *
469
     * @param array $responseDecoded
470
     * @param int $responseCode Request Response Code
471
     *
472
     * @return array main data part of response
473
     */
474 1
    public function parseResponse($responseDecoded, $responseCode)
475
    {
476 1
        $response = null;
477
        switch ($responseCode) {
478 1
            case 201: //Success Write
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
479
                if (isset($responseDecoded[$this->resultField][0]['id'])) {
480
                    $this->lastInsertedID = $responseDecoded[$this->resultField][0]['id'];
481
                    $this->setMyKey($this->lastInsertedID);
482
                    $this->apiURL         = $this->getSectionURL().'/'.$this->lastInsertedID;
483
                } else {
484
                    $this->lastInsertedID = null;
485
                }
486 1
            case 200: //Success Read
487 1
                $response         = $this->lastResult = $responseDecoded;
488 1
                break;
489
490 1
            case 500: // Internal Server Error
491 1
            case 404: // Page not found
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
492 1
                if ($this->ignoreNotFound === true) {
493
                    break;
494
                }
495
            case 400: //Bad Request parameters
496
            default: //Something goes wrong
497 1
                $this->addStatusMessage($responseDecoded['status'].': '.
498 1
                    $this->curlInfo['url'], 'warning');
499 1
                if (is_array($responseDecoded)) {
500 1
                    $this->parseError($responseDecoded);
501
                }
502 1
                $this->logResult($responseDecoded, $this->curlInfo['url']);
503 1
                break;
504
        }
505 1
        return $response;
506
    }
507
508
    /**
509
     * Parse error message response
510
     *
511
     * @param array $responseDecoded
512
     * @return int number of errors processed
513
     */
514 1
    public function parseError(array $responseDecoded)
515
    {
516 1
        $this->errors = $responseDecoded;
517 1
        return count($this->errors);
518
    }
519
520
    /**
521
     * Vykonej HTTP požadavek
522
     *
523
     * @link https://www.ipex.eu/api/dokumentace/ref/urls/ Sestavování URL
524
     * @param string $url    URL požadavku
525
     * @param string $method HTTP Method GET|POST|PUT|OPTIONS|DELETE
526
     * @return int HTTP Response CODE
527
     */
528 2
    public function doCurlRequest($url, $method)
529
    {
530 2
        curl_setopt($this->curl, CURLOPT_URL, $url);
531
// Nastavení samotné operace
532 2
        curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, strtoupper($method));
533
//Vždy nastavíme byť i prázná postdata jako ochranu před chybou 411
534 2
        curl_setopt($this->curl, CURLOPT_POSTFIELDS, $this->postFields);
535
536 2
        $httpHeaders = $this->defaultHttpHeaders;
537
538 2
        if (!isset($httpHeaders['Accept'])) {
539 2
            $httpHeaders['Accept'] = 'application/json';
540
        }
541 2
        if (!isset($httpHeaders['Content-Type'])) {
542 2
            $httpHeaders['Content-Type'] = 'application/json';
543
        }
544 2
        $httpHeadersFinal = [];
545 2
        foreach ($httpHeaders as $key => $value) {
546 2
            if (($key == 'User-Agent') && ($value == 'php-primaERP')) {
547 2
                $value .= ' v'.self::$libVersion;
548
            }
549 2
            $httpHeadersFinal[] = $key.': '.$value;
550
        }
551
552 2
        curl_setopt($this->curl, CURLOPT_HTTPHEADER, $httpHeadersFinal);
553
554
// Proveď samotnou operaci
555 2
        $this->lastCurlResponse            = curl_exec($this->curl);
556 2
        $this->curlInfo                    = curl_getinfo($this->curl);
557 2
        $this->curlInfo['when']            = microtime();
558 2
        $this->curlInfo['request_headers'] = $httpHeadersFinal;
559 2
        $this->responseMimeType            = $this->curlInfo['content_type'];
560 2
        $this->lastResponseCode            = $this->curlInfo['http_code'];
561 2
        $this->lastCurlError               = curl_error($this->curl);
562 2
        if (strlen($this->lastCurlError)) {
563 1
            $this->addStatusMessage(sprintf('Curl Error (HTTP %d): %s',
564 1
                    $this->lastResponseCode, $this->lastCurlError), 'error');
565
        }
566
567 2
        if ($this->debug === true) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
568
//            $this->saveDebugFiles();
0 ignored issues
show
Unused Code Comprehensibility introduced by
72% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
569
        }
570
571 2
        return $this->lastResponseCode;
572
    }
573
574 1
    public function loadFromAPI($key)
575
    {
576 1
        return $this->takeData($this->requestData($key));
0 ignored issues
show
Bug introduced by
It seems like $this->requestData($key) targeting primaERP\ApiClient::requestData() can also be of type null; however, Ease\Sand::takeData() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
577
    }
578
579
    /**
580
     * Write Operation Result.
581
     *
582
     * @param array  $resultData
583
     * @param string $url        URL
584
     * @return boolean Log save success
585
     */
586 2
    public function logResult($resultData = null, $url = null)
587
    {
588 2
        $logResult = false;
589 2
        if (is_null($resultData)) {
590 1
            $resultData = $this->lastResult;
591
        }
592 2
        if (isset($url)) {
593 1
            $this->logger->addStatusMessage(urldecode($url));
594
        }
595 2
        if (array_key_exists('message', $resultData)) {
596 2
            $this->logger->addStatusMessage($resultData['code'].': '.$resultData['message'].array_key_exists('description',
597 2
                    $resultData) ? ' ('.$resultData['description'].')' : '',
598 2
                'warning');
599
        }
600
601 2
        return $logResult;
602
    }
603
604
    /**
605
     * Current Token String
606
     *
607
     * @return string
608
     */
609 2
    public function getTokenString()
610
    {
611 2
        return $this->tokener->getTokenString();
612
    }
613
614
    /**
615
     * Set or get ignore not found pages flag
616
     *
617
     * @param boolean $ignore set flag to
618
     *
619
     * @return boolean get flag state
620
     */
621 1
    public function ignore404($ignore = null)
622
    {
623 1
        if (!is_null($ignore)) {
624 1
            $this->ignoreNotFound = $ignore;
625
        }
626 1
        return $this->ignoreNotFound;
627
    }
628
629
    /**
630
     * Odpojení od primaERP.
631
     */
632 2
    public function disconnect()
633
    {
634 2
        if (is_resource($this->curl)) {
635 2
            curl_close($this->curl);
636
        }
637 2
        $this->curl = null;
638 2
    }
639
640
    /**
641
     * Reconnect After unserialization
642
     */
643 1
    public function __wakeup()
644
    {
645 1
        parent::__wakeup();
646 1
        $this->curlInit();
647 1
    }
648
649
    /**
650
     * Disconnect CURL befere pass away
651
     */
652 2
    public function __destruct()
653
    {
654 2
        $this->disconnect();
655 2
    }
656
}
657