Completed
Push — master ( c3e9d9...78753a )
by Alexei
9s
created

Client::businessLogicUpgrade()   A

Complexity

Conditions 1
Paths 1

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 1
eloc 2
nc 1
nop 0
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\Certificate
440
     */
441
    public function certificate()
442
    {
443
        return $this->_getOperator('Certificate');
444
    }
445
446
    /**
447
     * @return Operator\SiteAlias
448
     */
449
    public function siteAlias()
450
    {
451
        return $this->_getOperator('SiteAlias');
452
    }
453
454
    /**
455
     * @return Operator\Ip
456
     */
457
    public function ip()
458
    {
459
        return $this->_getOperator('Ip');
460
    }
461
462
    /**
463
     * @return Operator\EventLog
464
     */
465
    public function eventLog()
466
    {
467
        return $this->_getOperator('EventLog');
468
    }
469
470
    /**
471
     * @return Operator\SecretKey
472
     */
473
    public function secretKey()
474
    {
475
        return $this->_getOperator('SecretKey');
476
    }
477
478
    /**
479
     * @return Operator\Ui
480
     */
481
    public function ui()
482
    {
483
        return $this->_getOperator('Ui');
484
    }
485
486
    /**
487
     * @return Operator\ServicePlan
488
     */
489
    public function servicePlan()
490
    {
491
        return $this->_getOperator('ServicePlan');
492
    }
493
494
    /**
495
     * @return Operator\VirtualDirectory
496
     */
497
    public function virtualDirectory()
498
    {
499
        return $this->_getOperator('VirtualDirectory');
500
    }
501
502
    /**
503
     * @return Operator\Database
504
     */
505
    public function database()
506
    {
507
        return $this->_getOperator('Database');
508
    }
509
510
    /**
511
     * @return Operator\Session
512
     */
513
    public function session()
514
    {
515
        return $this->_getOperator('Session');
516
    }
517
518
    /**
519
     * @return Operator\Locale
520
     */
521
    public function locale()
522
    {
523
        return $this->_getOperator('Locale');
524
    }
525
526
    /**
527
     * @return Operator\LogRotation
528
     */
529
    public function logRotation()
530
    {
531
        return $this->_getOperator('LogRotation');
532
    }
533
534
    /**
535
     * @return Operator\ProtectedDirectory
536
     */
537
    public function protectedDirectory()
538
    {
539
        return $this->_getOperator('ProtectedDirectory');
540
    }
541
542
    /**
543
     * @return Operator\Reseller
544
     */
545
    public function reseller()
546
    {
547
        return $this->_getOperator('Reseller');
548
    }
549
550
    /**
551
     * @return Operator\ResellerPlan
552
     */
553
    public function resellerPlan()
554
    {
555
        return $this->_getOperator('ResellerPlan');
556
    }
557
558
    /**
559
     * @return Operator\Aps
560
     */
561
    public function aps()
562
    {
563
        return $this->_getOperator('Aps');
564
    }
565
566
    /**
567
     * @return Operator\ServicePlanAddon
568
     */
569
    public function servicePlanAddon()
570
    {
571
        return $this->_getOperator('ServicePlanAddon');
572
    }
573
574
    /**
575
     * @return Operator\Site
576
     */
577
    public function site()
578
    {
579
        return $this->_getOperator('Site');
580
    }
581
}
582