Completed
Push — master ( 7626f9...b0eece )
by Alexei
01:16
created

Client::setProxy()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
// Copyright 1999-2020. Plesk International GmbH.
3
4
namespace PleskX\Api;
5
6
use SimpleXMLElement;
7
8
/**
9
 * Client for Plesk XML-RPC API.
10
 */
11
class Client
12
{
13
    const RESPONSE_SHORT = 1;
14
    const RESPONSE_FULL = 2;
15
16
    protected $_host;
17
    protected $_port;
18
    protected $_protocol;
19
    protected $_login;
20
    protected $_password;
21
    protected $_proxy = '';
22
    protected $_secretKey;
23
    protected $_version = '';
24
25
    protected $_operatorsCache = [];
26
27
    /**
28
     * @var callable
29
     */
30
    protected $_verifyResponseCallback;
31
32
    /**
33
     * Create client.
34
     *
35
     * @param string $host
36
     * @param int $port
37
     * @param string $protocol
38
     */
39
    public function __construct($host, $port = 8443, $protocol = 'https')
40
    {
41
        $this->_host = $host;
42
        $this->_port = $port;
43
        $this->_protocol = $protocol;
44
    }
45
46
    /**
47
     * Setup credentials for authentication.
48
     *
49
     * @param string $login
50
     * @param string $password
51
     */
52
    public function setCredentials($login, $password)
53
    {
54
        $this->_login = $login;
55
        $this->_password = $password;
56
    }
57
58
    /**
59
     * Define secret key for alternative authentication.
60
     *
61
     * @param string $secretKey
62
     */
63
    public function setSecretKey($secretKey)
64
    {
65
        $this->_secretKey = $secretKey;
66
    }
67
68
    /**
69
     * Set proxy server for requests
70
     *
71
     * @param string $proxy
72
     */
73
    public function setProxy($proxy)
74
    {
75
        $this->_proxy = $proxy;
76
    }
77
78
    /**
79
     * Set default version for requests.
80
     *
81
     * @param string $version
82
     */
83
    public function setVersion($version)
84
    {
85
        $this->_version = $version;
86
    }
87
88
    /**
89
     * Set custom function to verify response of API call according your own needs. Default verifying will be used if it is not specified.
90
     *
91
     * @param callable|null $function
92
     */
93
    public function setVerifyResponse(callable $function = null)
94
    {
95
        $this->_verifyResponseCallback = $function;
96
    }
97
98
    /**
99
     * Retrieve host used for communication.
100
     *
101
     * @return string
102
     */
103
    public function getHost()
104
    {
105
        return $this->_host;
106
    }
107
108
    /**
109
     * Retrieve port used for communication.
110
     *
111
     * @return int
112
     */
113
    public function getPort()
114
    {
115
        return $this->_port;
116
    }
117
118
    /**
119
     * Retrieve name of the protocol (http or https) used for communication.
120
     *
121
     * @return string
122
     */
123
    public function getProtocol()
124
    {
125
        return $this->_protocol;
126
    }
127
128
    /**
129
     * Retrieve XML template for packet.
130
     *
131
     * @param string|null $version
132
     *
133
     * @return SimpleXMLElement
134
     */
135
    public function getPacket($version = null)
136
    {
137
        $protocolVersion = !is_null($version) ? $version : $this->_version;
138
        $content = "<?xml version='1.0' encoding='UTF-8' ?>";
139
        $content .= '<packet'.('' === $protocolVersion ? '' : " version='$protocolVersion'").'/>';
140
141
        return new SimpleXMLElement($content);
142
    }
143
144
    /**
145
     * Perform API request.
146
     *
147
     * @param string|array|SimpleXMLElement $request
148
     * @param int $mode
149
     *
150
     * @return XmlResponse
151
     */
152
    public function request($request, $mode = self::RESPONSE_SHORT)
153
    {
154
        if ($request instanceof SimpleXMLElement) {
155
            $request = $request->asXml();
156 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...
157
            $xml = $this->getPacket();
158
159
            if (is_array($request)) {
160
                $request = $this->_arrayToXml($request, $xml)->asXML();
161
            } elseif (preg_match('/^[a-z]/', $request)) {
162
                $request = $this->_expandRequestShortSyntax($request, $xml);
163
            }
164
        }
165
166
        if ('sdk' == $this->_protocol) {
167
            $version = ('' == $this->_version) ? null : $this->_version;
168
            $requestXml = new SimpleXMLElement((string) $request);
169
            $xml = \pm_ApiRpc::getService($version)->call($requestXml->children()[0]->asXml(), $this->_login);
170
        } else {
171
            $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...
172
        }
173
174
        $this->_verifyResponseCallback
175
            ? call_user_func($this->_verifyResponseCallback, $xml)
176
            : $this->_verifyResponse($xml);
177
178
        return (self::RESPONSE_FULL == $mode) ? $xml : $xml->xpath('//result')[0];
179
    }
180
181
    /**
182
     * Perform HTTP request to end-point.
183
     *
184
     * @param string $request
185
     *
186
     * @throws Client\Exception
187
     *
188
     * @return XmlResponse
189
     */
190
    private function _performHttpRequest($request)
191
    {
192
        $curl = curl_init();
193
194
        curl_setopt($curl, CURLOPT_URL, "$this->_protocol://$this->_host:$this->_port/enterprise/control/agent.php");
195
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
196
        curl_setopt($curl, CURLOPT_POST, true);
197
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
198
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
199
        curl_setopt($curl, CURLOPT_HTTPHEADER, $this->_getHeaders());
200
        curl_setopt($curl, CURLOPT_POSTFIELDS, $request);
201
202
        if ('' !== $this->_proxy) {
203
            curl_setopt($curl, CURLOPT_PROXY, $this->_proxy);
204
        }
205
206
        $result = curl_exec($curl);
207
208
        if (false === $result) {
209
            throw new Client\Exception(curl_error($curl), curl_errno($curl));
210
        }
211
212
        curl_close($curl);
213
214
        $xml = new XmlResponse($result);
215
216
        return $xml;
217
    }
218
219
    /**
220
     * Perform multiple API requests using single HTTP request.
221
     *
222
     * @param $requests
223
     * @param int $mode
224
     *
225
     * @throws Client\Exception
226
     *
227
     * @return array
228
     */
229
    public function multiRequest($requests, $mode = self::RESPONSE_SHORT)
230
    {
231
        $requestXml = $this->getPacket();
232
233
        foreach ($requests as $request) {
234
            if ($request instanceof SimpleXMLElement) {
235
                throw new Client\Exception('SimpleXML type of request is not supported for multi requests.');
236 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...
237
                if (is_array($request)) {
238
                    $request = $this->_arrayToXml($request, $requestXml)->asXML();
239
                } elseif (preg_match('/^[a-z]/', $request)) {
240
                    $this->_expandRequestShortSyntax($request, $requestXml);
241
                }
242
            }
243
            $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...
244
        }
245
246
        if ('sdk' == $this->_protocol) {
247
            throw new Client\Exception('Multi requests are not supported via SDK.');
248
        } else {
249
            $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...
250
        }
251
252
        $responses = [];
253
        foreach ($responseXml->children() as $childNode) {
254
            $xml = $this->getPacket();
255
            $dom = dom_import_simplexml($xml)->ownerDocument;
256
257
            $childDomNode = dom_import_simplexml($childNode);
258
            $childDomNode = $dom->importNode($childDomNode, true);
259
            $dom->documentElement->appendChild($childDomNode);
260
261
            $response = simplexml_load_string($dom->saveXML());
262
            $responses[] = (self::RESPONSE_FULL == $mode) ? $response : $response->xpath('//result')[0];
263
        }
264
265
        return $responses;
266
    }
267
268
    /**
269
     * Retrieve list of headers needed for request.
270
     *
271
     * @return array
272
     */
273
    protected function _getHeaders()
274
    {
275
        $headers = [
276
            'Content-Type: text/xml',
277
            'HTTP_PRETTY_PRINT: TRUE',
278
        ];
279
280
        if ($this->_secretKey) {
281
            $headers[] = "KEY: $this->_secretKey";
282
        } else {
283
            $headers[] = "HTTP_AUTH_LOGIN: $this->_login";
284
            $headers[] = "HTTP_AUTH_PASSWD: $this->_password";
285
        }
286
287
        return $headers;
288
    }
289
290
    /**
291
     * Verify that response does not contain errors.
292
     *
293
     * @param XmlResponse $xml
294
     *
295
     * @throws Exception
296
     */
297
    protected function _verifyResponse($xml)
298
    {
299
        if ($xml->system && $xml->system->status && 'error' == (string) $xml->system->status) {
300
            throw new Exception((string) $xml->system->errtext, (int) $xml->system->errcode);
301
        }
302
303
        if ($xml->xpath('//status[text()="error"]') && $xml->xpath('//errcode') && $xml->xpath('//errtext')) {
304
            $errorCode = (int) $xml->xpath('//errcode')[0];
305
            $errorMessage = (string) $xml->xpath('//errtext')[0];
306
307
            throw new Exception($errorMessage, $errorCode);
308
        }
309
    }
310
311
    /**
312
     * Expand short syntax (some.method.call) into full XML representation.
313
     *
314
     * @param string $request
315
     * @param SimpleXMLElement $xml
316
     *
317
     * @return string
318
     */
319
    protected function _expandRequestShortSyntax($request, SimpleXMLElement $xml)
320
    {
321
        $parts = explode('.', $request);
322
        $node = $xml;
323
324
        foreach ($parts as $part) {
325
            @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...
326
            $node = $node->addChild($name, $value);
327
        }
328
329
        return $xml->asXML();
330
    }
331
332
    /**
333
     * Convert array to XML representation.
334
     *
335
     * @param array $array
336
     * @param SimpleXMLElement $xml
337
     * @param string $parentEl
338
     *
339
     * @return SimpleXMLElement
340
     */
341
    protected function _arrayToXml(array $array, SimpleXMLElement $xml, $parentEl = null)
342
    {
343
        foreach ($array as $key => $value) {
344
            $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...
345
            if (is_array($value)) {
346
                $this->_arrayToXml($value, $this->_isAssocArray($value) ? $xml->addChild($el) : $xml, $el);
347
            } else {
348
                $xml->addChild($el, $value);
349
            }
350
        }
351
352
        return $xml;
353
    }
354
355
    /**
356
     * @param array $array
357
     *
358
     * @return bool
359
     */
360
    protected function _isAssocArray(array $array)
361
    {
362
        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...
363
    }
364
365
    /**
366
     * @param string $name
367
     *
368
     * @return \PleskX\Api\Operator
369
     */
370
    protected function _getOperator($name)
371
    {
372
        if (!isset($this->_operatorsCache[$name])) {
373
            $className = '\\PleskX\\Api\\Operator\\'.$name;
374
            $this->_operatorsCache[$name] = new $className($this);
375
        }
376
377
        return $this->_operatorsCache[$name];
378
    }
379
380
    /**
381
     * @return Operator\Server
382
     */
383
    public function server()
384
    {
385
        return $this->_getOperator('Server');
386
    }
387
388
    /**
389
     * @return Operator\Customer
390
     */
391
    public function customer()
392
    {
393
        return $this->_getOperator('Customer');
394
    }
395
396
    /**
397
     * @return Operator\Webspace
398
     */
399
    public function webspace()
400
    {
401
        return $this->_getOperator('Webspace');
402
    }
403
404
    /**
405
     * @return Operator\Subdomain
406
     */
407
    public function subdomain()
408
    {
409
        return $this->_getOperator('Subdomain');
410
    }
411
412
    /**
413
     * @return Operator\Dns
414
     */
415
    public function dns()
416
    {
417
        return $this->_getOperator('Dns');
418
    }
419
420
    /**
421
     * @return Operator\DnsTemplate
422
     */
423
    public function dnsTemplate()
424
    {
425
        return $this->_getOperator('DnsTemplate');
426
    }
427
428
    /**
429
     * @return Operator\DatabaseServer
430
     */
431
    public function databaseServer()
432
    {
433
        return $this->_getOperator('DatabaseServer');
434
    }
435
436
    /**
437
     * @return Operator\Mail
438
     */
439
    public function mail()
440
    {
441
        return $this->_getOperator('Mail');
442
    }
443
444
    /**
445
     * @return Operator\Certificate
446
     */
447
    public function certificate()
448
    {
449
        return $this->_getOperator('Certificate');
450
    }
451
452
    /**
453
     * @return Operator\SiteAlias
454
     */
455
    public function siteAlias()
456
    {
457
        return $this->_getOperator('SiteAlias');
458
    }
459
460
    /**
461
     * @return Operator\Ip
462
     */
463
    public function ip()
464
    {
465
        return $this->_getOperator('Ip');
466
    }
467
468
    /**
469
     * @return Operator\EventLog
470
     */
471
    public function eventLog()
472
    {
473
        return $this->_getOperator('EventLog');
474
    }
475
476
    /**
477
     * @return Operator\SecretKey
478
     */
479
    public function secretKey()
480
    {
481
        return $this->_getOperator('SecretKey');
482
    }
483
484
    /**
485
     * @return Operator\Ui
486
     */
487
    public function ui()
488
    {
489
        return $this->_getOperator('Ui');
490
    }
491
492
    /**
493
     * @return Operator\ServicePlan
494
     */
495
    public function servicePlan()
496
    {
497
        return $this->_getOperator('ServicePlan');
498
    }
499
500
    /**
501
     * @return Operator\VirtualDirectory
502
     */
503
    public function virtualDirectory()
504
    {
505
        return $this->_getOperator('VirtualDirectory');
506
    }
507
508
    /**
509
     * @return Operator\Database
510
     */
511
    public function database()
512
    {
513
        return $this->_getOperator('Database');
514
    }
515
516
    /**
517
     * @return Operator\Session
518
     */
519
    public function session()
520
    {
521
        return $this->_getOperator('Session');
522
    }
523
524
    /**
525
     * @return Operator\Locale
526
     */
527
    public function locale()
528
    {
529
        return $this->_getOperator('Locale');
530
    }
531
532
    /**
533
     * @return Operator\LogRotation
534
     */
535
    public function logRotation()
536
    {
537
        return $this->_getOperator('LogRotation');
538
    }
539
540
    /**
541
     * @return Operator\ProtectedDirectory
542
     */
543
    public function protectedDirectory()
544
    {
545
        return $this->_getOperator('ProtectedDirectory');
546
    }
547
548
    /**
549
     * @return Operator\Reseller
550
     */
551
    public function reseller()
552
    {
553
        return $this->_getOperator('Reseller');
554
    }
555
556
    /**
557
     * @return Operator\ResellerPlan
558
     */
559
    public function resellerPlan()
560
    {
561
        return $this->_getOperator('ResellerPlan');
562
    }
563
564
    /**
565
     * @return Operator\Aps
566
     */
567
    public function aps()
568
    {
569
        return $this->_getOperator('Aps');
570
    }
571
572
    /**
573
     * @return Operator\ServicePlanAddon
574
     */
575
    public function servicePlanAddon()
576
    {
577
        return $this->_getOperator('ServicePlanAddon');
578
    }
579
580
    /**
581
     * @return Operator\Site
582
     */
583
    public function site()
584
    {
585
        return $this->_getOperator('Site');
586
    }
587
588
    /**
589
     * @return Operator\PhpHandler
590
     */
591
    public function phpHandler()
592
    {
593
        return $this->_getOperator('PhpHandler');
594
    }
595
}
596