Completed
Push — master ( 9902d6...c7939a )
by Alexei
10s
created

Client::setCredentials()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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