Completed
Pull Request — master (#36)
by
unknown
05:44
created

Client::_getOperator()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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