Completed
Push — master ( 8fafa2...c3e9d9 )
by
unknown
13s
created

Client::_isAssocArray()   A

Complexity

Conditions 2
Paths 2

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 2
eloc 2
nc 2
nop 1
1
<?php
2
// Copyright 1999-2016. Parallels IP Holdings GmbH.
3
4
namespace PleskX\Api;
5
use SimpleXMLElement;
6
7
/**
8
 * Client for Plesk XML-RPC API
9
 */
10
class Client
11
{
12
    const RESPONSE_SHORT = 1;
13
    const RESPONSE_FULL = 2;
14
15
    protected $_host;
16
    protected $_port;
17
    protected $_protocol;
18
    protected $_login;
19
    protected $_password;
20
    protected $_secretKey;
21
    protected $_version = '';
22
23
    protected static $_isExecutionsLogEnabled = false;
24
    protected static $_executionLog = [];
25
26
    protected $_operatorsCache = [];
27
28
    /**
29
     * @var callable
30
     */
31
    protected $_verifyResponseCallback;
32
33
    /**
34
     * Create client
35
     *
36
     * @param string $host
37
     * @param int $port
38
     * @param string $protocol
39
     */
40
    public function __construct($host, $port = 8443, $protocol = 'https')
41
    {
42
        $this->_host = $host;
43
        $this->_port = $port;
44
        $this->_protocol = $protocol;
45
    }
46
47
    /**
48
     * Setup credentials for authentication
49
     *
50
     * @param string $login
51
     * @param string $password
52
     */
53
    public function setCredentials($login, $password)
54
    {
55
        $this->_login = $login;
56
        $this->_password = $password;
57
    }
58
59
    /**
60
     * Define secret key for alternative authentication
61
     *
62
     * @param string $secretKey
63
     */
64
    public function setSecretKey($secretKey)
65
    {
66
        $this->_secretKey = $secretKey;
67
    }
68
69
    /**
70
     * Set default version for requests
71
     *
72
     * @param string $version
73
     */
74
    public function setVersion($version)
75
    {
76
        $this->_version = $version;
77
    }
78
79
    /**
80
     * Set custom function to verify response of API call according your own needs. Default verifying will be used if it is not specified
81
     *
82
     * @param callable|null $function
83
     */
84
    public function setVerifyResponse(callable $function = null)
85
    {
86
        $this->_verifyResponseCallback = $function;
87
    }
88
89
    /**
90
     * Retrieve host used for communication
91
     *
92
     * @return string
93
     */
94
    public function getHost()
95
    {
96
        return $this->_host;
97
    }
98
99
    /**
100
     * Retrieve port used for communication
101
     *
102
     * @return int
103
     */
104
    public function getPort()
105
    {
106
        return $this->_port;
107
    }
108
109
    /**
110
     * Retrieve name of the protocol (http or https) used for communication
111
     *
112
     * @return string
113
     */
114
    public function getProtocol()
115
    {
116
        return $this->_protocol;
117
    }
118
119
    /**
120
     * Retrieve XML template for packet
121
     *
122
     * @param string|null $version
123
     * @return SimpleXMLElement
124
     */
125
    public function getPacket($version = null)
126
    {
127
        $protocolVersion = !is_null($version) ? $version : $this->_version;
128
        $content = "<?xml version='1.0' encoding='UTF-8' ?>";
129
        $content .= "<packet" . ("" === $protocolVersion ? "" : " version='$protocolVersion'") . "/>";
130
        return new SimpleXMLElement($content);
131
    }
132
133
    /**
134
     * Perform API request
135
     *
136
     * @param string|array|SimpleXMLElement $request
137
     * @param int $mode
138
     * @return XmlResponse
139
     */
140
    public function request($request, $mode = self::RESPONSE_SHORT)
141
    {
142
        if ($request instanceof SimpleXMLElement) {
143
            $request = $request->asXml();
144
        } else {
145
            $xml = $this->getPacket();
146
147
            if (is_array($request)) {
148
                $request = $this->_arrayToXml($request, $xml)->asXML();
149
            } else if (preg_match('/^[a-z]/', $request)) {
150
                $request = $this->_expandRequestShortSyntax($request, $xml);
151
            }
152
        }
153
154
        if ('sdk' == $this->_protocol) {
155
            $version = ('' == $this->_version) ? null : $this->_version;
156
            $requestXml = new SimpleXMLElement((string)$request);
157
            $xml = \pm_ApiRpc::getService($version)->call($requestXml->children()[0]->asXml(), $this->_login);
158
        } else {
159
            $xml = $this->_performHttpRequest($request);
0 ignored issues
show
Security Bug introduced by
It seems like $request can also be of type false; however, PleskX\Api\Client::_performHttpRequest() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
160
        }
161
162
        $this->_verifyResponseCallback
163
            ? call_user_func($this->_verifyResponseCallback, $xml)
164
            : $this->_verifyResponse($xml);
165
166
        return (self::RESPONSE_FULL == $mode) ? $xml : $xml->xpath('//result')[0];
167
    }
168
169
    /**
170
     * Perform HTTP request to end-point
171
     *
172
     * @param string $request
173
     * @return XmlResponse
174
     * @throws Client\Exception
175
     */
176
    private function _performHttpRequest($request)
177
    {
178
        $curl = curl_init();
179
180
        curl_setopt($curl, CURLOPT_URL, "$this->_protocol://$this->_host:$this->_port/enterprise/control/agent.php");
181
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
182
        curl_setopt($curl, CURLOPT_POST, true);
183
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
184
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
185
        curl_setopt($curl, CURLOPT_HTTPHEADER, $this->_getHeaders());
186
        curl_setopt($curl, CURLOPT_POSTFIELDS, $request);
187
188
        $result = curl_exec($curl);
189
190
        if (false === $result) {
191
            throw new Client\Exception(curl_error($curl), curl_errno($curl));
192
        }
193
194
        if (self::$_isExecutionsLogEnabled) {
195
            self::$_executionLog[] = [
196
                'trace' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS),
197
                'request' => $request,
198
                'response' => $result,
199
            ];
200
        }
201
202
        curl_close($curl);
203
204
        $xml = new XmlResponse($result);
205
        return $xml;
206
    }
207
208
    /**
209
     * Perform multiple API requests using single HTTP request
210
     *
211
     * @param $requests
212
     * @param int $mode
213
     * @return array
214
     * @throws Client\Exception
215
     */
216
    public function multiRequest($requests, $mode = self::RESPONSE_SHORT)
217
    {
218
219
        $requestXml = $this->getPacket();
220
221
        foreach ($requests as $request) {
222
            if ($request instanceof SimpleXMLElement) {
223
                throw new Client\Exception('SimpleXML type of request is not supported for multi requests.');
224
            } else {
225
                if (is_array($request)) {
226
                    $request = $this->_arrayToXml($request, $requestXml)->asXML();
227
                } else if (preg_match('/^[a-z]/', $request)) {
228
                    $this->_expandRequestShortSyntax($request, $requestXml);
229
                }
230
            }
231
            $responses[] = $this->request($request);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$responses was never initialized. Although not strictly required by PHP, it is generally a good practice to add $responses = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
232
        }
233
234
        if ('sdk' == $this->_protocol) {
235
            throw new Client\Exception('Multi requests are not supported via SDK.');
236
        } else {
237
            $responseXml = $this->_performHttpRequest($requestXml->asXML());
0 ignored issues
show
Security Bug introduced by
It seems like $requestXml->asXML() targeting SimpleXMLElement::asXML() can also be of type false; however, PleskX\Api\Client::_performHttpRequest() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
238
        }
239
240
        $responses = [];
241
        foreach ($responseXml->children() as $childNode) {
242
            $xml = $this->getPacket();
243
            $dom = dom_import_simplexml($xml)->ownerDocument;
244
245
            $childDomNode = dom_import_simplexml($childNode);
246
            $childDomNode = $dom->importNode($childDomNode, true);
247
            $dom->documentElement->appendChild($childDomNode);
248
249
            $response = simplexml_load_string($dom->saveXML());
250
            $responses[] = (self::RESPONSE_FULL == $mode) ? $response : $response->xpath('//result')[0];
251
        }
252
253
        return $responses;
254
    }
255
256
    /**
257
     * Retrieve list of headers needed for request
258
     *
259
     * @return array
260
     */
261
    protected function _getHeaders()
262
    {
263
        $headers = array(
264
            "Content-Type: text/xml",
265
            "HTTP_PRETTY_PRINT: TRUE",
266
        );
267
268
        if ($this->_secretKey) {
269
            $headers[] = "KEY: $this->_secretKey";
270
        } else {
271
            $headers[] = "HTTP_AUTH_LOGIN: $this->_login";
272
            $headers[] = "HTTP_AUTH_PASSWD: $this->_password";
273
        }
274
275
        return $headers;
276
    }
277
278
    /**
279
     * Enable or disable execution log
280
     *
281
     * @param bool $enable
282
     */
283
    public static function enableExecutionLog($enable = true)
284
    {
285
        self::$_isExecutionsLogEnabled = $enable;
286
    }
287
288
    /**
289
     * Retrieve execution log
290
     *
291
     * @return array
292
     */
293
    public static function getExecutionLog()
294
    {
295
        return self::$_executionLog;
296
    }
297
298
    /**
299
     * Verify that response does not contain errors
300
     *
301
     * @param XmlResponse $xml
302
     * @throws Exception
303
     */
304
    protected function _verifyResponse($xml)
305
    {
306
        if ($xml->system && $xml->system->status && 'error' == (string)$xml->system->status) {
307
            throw new Exception((string)$xml->system->errtext, (int)$xml->system->errcode);
308
        }
309
310
        if ($xml->xpath('//status[text()="error"]') && $xml->xpath('//errcode') && $xml->xpath('//errtext')) {
311
            $errorCode = (int)$xml->xpath('//errcode')[0];
312
            $errorMessage = (string)$xml->xpath('//errtext')[0];
313
            throw new Exception($errorMessage, $errorCode);
314
        }
315
    }
316
317
    /**
318
     * Expand short syntax (some.method.call) into full XML representation
319
     *
320
     * @param string $request
321
     * @param SimpleXMLElement $xml
322
     * @return string
323
     */
324
    protected function _expandRequestShortSyntax($request, SimpleXMLElement $xml)
325
    {
326
        $parts = explode('.', $request);
327
        $node = $xml;
328
329
        foreach ($parts as $part) {
330
            @list($name, $value) = explode('=', $part);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
331
            $node = $node->addChild($name, $value);
332
        }
333
334
        return $xml->asXML();
335
    }
336
337
    /**
338
     * Convert array to XML representation
339
     *
340
     * @param array $array
341
     * @param SimpleXMLElement $xml
342
     * @param string $parentEl
343
     * @return SimpleXMLElement
344
     */
345
    protected function _arrayToXml(array $array, SimpleXMLElement $xml, $parentEl = null)
346
    {
347
        foreach ($array as $key => $value) {
348
            $el = is_int($key) && $parentEl ? $parentEl : $key;
0 ignored issues
show
Bug Best Practice introduced by
The expression $parentEl of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
349
            if (is_array($value)) {
350
                $this->_arrayToXml($value, $this->_isAssocArray($value) ? $xml->addChild($el) : $xml, $el);
351
            } else {
352
                $xml->addChild($el, $value);
353
            }
354
        }
355
356
        return $xml;
357
    }
358
359
    /**
360
     * @param array $array
361
     * @return bool
362
     */
363
    protected function _isAssocArray(array $array)
364
    {
365
        return $array && array_keys($array) !== range(0, count($array) - 1);
0 ignored issues
show
Bug Best Practice introduced by
The expression $array of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
366
    }
367
368
    /**
369
     * @param string $name
370
     * @return \PleskX\Api\Operator
371
     */
372
    protected function _getOperator($name)
373
    {
374
        if (!isset($this->_operatorsCache[$name])) {
375
            $className = '\\PleskX\\Api\\Operator\\' . $name;
376
            $this->_operatorsCache[$name] = new $className($this);
377
        }
378
379
        return $this->_operatorsCache[$name];
380
    }
381
382
    /**
383
     * @return Operator\Server
384
     */
385
    public function server()
386
    {
387
        return $this->_getOperator('Server');
388
    }
389
390
    /**
391
     * @return Operator\Customer
392
     */
393
    public function customer()
394
    {
395
        return $this->_getOperator('Customer');
396
    }
397
398
    /**
399
     * @return Operator\Webspace
400
     */
401
    public function webspace()
402
    {
403
        return $this->_getOperator('Webspace');
404
    }
405
406
    /**
407
     * @return Operator\Subdomain
408
     */
409
    public function subdomain()
410
    {
411
        return $this->_getOperator('Subdomain');
412
    }
413
414
    /**
415
     * @return Operator\Dns
416
     */
417
    public function dns()
418
    {
419
        return $this->_getOperator('Dns');
420
    }
421
422
    /**
423
     * @return Operator\DatabaseServer
424
     */
425
    public function databaseServer()
426
    {
427
        return $this->_getOperator('DatabaseServer');
428
    }
429
430
    /**
431
     * @return Operator\Mail
432
     */
433
    public function mail()
434
    {
435
        return $this->_getOperator('Mail');
436
    }
437
438
    /**
439
     * @return Operator\Migration
440
     */
441
    public function migration()
442
    {
443
        return $this->_getOperator('Migration');
444
    }
445
446
    /**
447
     * @return Operator\Certificate
448
     */
449
    public function certificate()
450
    {
451
        return $this->_getOperator('Certificate');
452
    }
453
454
    /**
455
     * @return Operator\SiteAlias
456
     */
457
    public function siteAlias()
458
    {
459
        return $this->_getOperator('SiteAlias');
460
    }
461
462
    /**
463
     * @return Operator\Ip
464
     */
465
    public function ip()
466
    {
467
        return $this->_getOperator('Ip');
468
    }
469
470
    /**
471
     * @return Operator\EventLog
472
     */
473
    public function eventLog()
474
    {
475
        return $this->_getOperator('EventLog');
476
    }
477
478
    /**
479
     * @return Operator\SpamFilter
480
     */
481
    public function spamFilter()
482
    {
483
        return $this->_getOperator('SpamFilter');
484
    }
485
486
    /**
487
     * @return Operator\SecretKey
488
     */
489
    public function secretKey()
490
    {
491
        return $this->_getOperator('SecretKey');
492
    }
493
494
    /**
495
     * @return Operator\Ui
496
     */
497
    public function ui()
498
    {
499
        return $this->_getOperator('Ui');
500
    }
501
502
    /**
503
     * @return Operator\ServicePlan
504
     */
505
    public function servicePlan()
506
    {
507
        return $this->_getOperator('ServicePlan');
508
    }
509
510
    /**
511
     * @return Operator\WebUser
512
     */
513
    public function webUser()
514
    {
515
        return $this->_getOperator('WebUser');
516
    }
517
518
    /**
519
     * @return Operator\MailList
520
     */
521
    public function mailList()
522
    {
523
        return $this->_getOperator('MailList');
524
    }
525
526
    /**
527
     * @return Operator\VirtualDirectory
528
     */
529
    public function virtualDirectory()
530
    {
531
        return $this->_getOperator('VirtualDirectory');
532
    }
533
534
    /**
535
     * @return Operator\Database
536
     */
537
    public function database()
538
    {
539
        return $this->_getOperator('Database');
540
    }
541
542
    /**
543
     * @return Operator\FtpUser
544
     */
545
    public function ftpUser()
546
    {
547
        return $this->_getOperator('FtpUser');
548
    }
549
550
    /**
551
     * @return Operator\Session
552
     */
553
    public function session()
554
    {
555
        return $this->_getOperator('Session');
556
    }
557
558
    /**
559
     * @return Operator\Updater
560
     */
561
    public function updater()
562
    {
563
        return $this->_getOperator('Updater');
564
    }
565
566
    /**
567
     * @return Operator\Locale
568
     */
569
    public function locale()
570
    {
571
        return $this->_getOperator('Locale');
572
    }
573
574
    /**
575
     * @return Operator\LogRotation
576
     */
577
    public function logRotation()
578
    {
579
        return $this->_getOperator('LogRotation');
580
    }
581
582
    /**
583
     * @return Operator\BackupManager
584
     */
585
    public function backupManager()
586
    {
587
        return $this->_getOperator('BackupManager');
588
    }
589
590
    /**
591
     * @return Operator\Sso
592
     */
593
    public function sso()
594
    {
595
        return $this->_getOperator('Sso');
596
    }
597
598
    /**
599
     * @return Operator\ProtectedDirectory
600
     */
601
    public function protectedDirectory()
602
    {
603
        return $this->_getOperator('ProtectedDirectory');
604
    }
605
606
    /**
607
     * @return Operator\Reseller
608
     */
609
    public function reseller()
610
    {
611
        return $this->_getOperator('Reseller');
612
    }
613
614
    /**
615
     * @return Operator\ResellerPlan
616
     */
617
    public function resellerPlan()
618
    {
619
        return $this->_getOperator('ResellerPlan');
620
    }
621
622
    /**
623
     * @return Operator\Aps
624
     */
625
    public function aps()
626
    {
627
        return $this->_getOperator('Aps');
628
    }
629
630
    /**
631
     * @return Operator\ServicePlanAddon
632
     */
633
    public function servicePlanAddon()
634
    {
635
        return $this->_getOperator('ServicePlanAddon');
636
    }
637
638
    /**
639
     * @return Operator\Site
640
     */
641
    public function site()
642
    {
643
        return $this->_getOperator('Site');
644
    }
645
646
    /**
647
     * @return Operator\User
648
     */
649
    public function user()
650
    {
651
        return $this->_getOperator('User');
652
    }
653
654
    /**
655
     * @return Operator\Role
656
     */
657
    public function role()
658
    {
659
        return $this->_getOperator('Role');
660
    }
661
662
    /**
663
     * @return Operator\BusinessLogicUpgrade
664
     */
665
    public function businessLogicUpgrade()
666
    {
667
        return $this->_getOperator('BusinessLogicUpgrade');
668
    }
669
670
    /**
671
     * @return Operator\Webmail
672
     */
673
    public function webmail()
674
    {
675
        return $this->_getOperator('Webmail');
676
    }
677
678
    /**
679
     * @return Operator\PlanItem
680
     */
681
    public function planItem()
682
    {
683
        return $this->_getOperator('PlanItem');
684
    }
685
686
    /**
687
     * @return Operator\Sitebuilder
688
     */
689
    public function sitebuilder()
690
    {
691
        return $this->_getOperator('Sitebuilder');
692
    }
693
694
    /**
695
     * @return Operator\ServiceNode
696
     */
697
    public function serviceNode()
698
    {
699
        return $this->_getOperator('ServiceNode');
700
    }
701
702
    /**
703
     * @return Operator\IpBan
704
     */
705
    public function ipBan()
706
    {
707
        return $this->_getOperator('IpBan');
708
    }
709
710
    /**
711
     * @return Operator\WpInstance
712
     */
713
    public function wpInstance()
714
    {
715
        return $this->_getOperator('WpInstance');
716
    }
717
718
}
719