Passed
Push — master ( b6cd05...9f5262 )
by Gaetano
05:39
created

Client::setUserAgent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 4
ccs 0
cts 0
cp 0
crap 2
rs 10
1
<?php
2
3
namespace PhpXmlRpc;
4
5
//use PhpXmlRpc\Helper\Charset;
6
use PhpXmlRpc\Helper\XMLParser;
7
use PhpXmlRpc\Traits\LoggerAware;
8
9
/**
10
 * Used to represent a client of an XML-RPC server.
11
 *
12
 * @todo add a setTimeout method, in the vein of other options which can be set to the Client
13
 */
14
class Client
15
{
16
    use LoggerAware;
17
18
    const USE_CURL_NEVER = 0;
19
    const USE_CURL_ALWAYS = 1;
20
    const USE_CURL_AUTO = 2;
21
22
    /** @var string */
23
    protected static $requestClass = '\\PhpXmlRpc\\Request';
24
    /** @var string */
25
    protected static $responseClass = '\\PhpXmlRpc\\Response';
26
27
    /**
28
     * @var int
29
     * @deprecated will be removed in the future
30
     */
31
    public $errno;
32
    /**
33
     * @var string
34
     * @deprecated will be removed in the future
35
     */
36
    public $errstr;
37
38
    /// @todo: do all the ones below need to be public?
39
40
    public $method = 'http';
41
    public $server;
42
    public $port = 0;
43
    public $path;
44
45
    /**
46
     * @var int
47
     * @internal use setDebug
48
     */
49
    public $debug = 0;
50
51
    /**
52
     * @var string
53
     * @internal use setCredentials
54
     */
55
    public $username = '';
56
    /**
57
     * @var string
58
     * @internal use setCredentials
59
     */
60
    public $password = '';
61
    /**
62
     * @var int
63
     * @internal use setCredentials
64
     */
65
    public $authtype = 1;
66
67
    /**
68
     * @var string
69
     * @internal use setCertificate
70
     */
71
    public $cert = '';
72
    /**
73
     * @var string
74
     * @internal use setCertificate
75
     */
76
    public $certpass = '';
77
    /**
78
     * @var string
79
     * @internal use setCaCertificate
80
     */
81
    public $cacert = '';
82
    /**
83
     * @var string
84
     * @internal use setCaCertificate
85
     */
86
    public $cacertdir = '';
87
    /**
88
     * @var string
89
     * @internal use setKey
90
     */
91
    public $key = '';
92
    /**
93
     * @var string
94
     * @internal use setKey
95
     */
96
    public $keypass = '';
97
    /**
98
     * @var bool
99
     * @internal use setSSLVerifyPeer
100
     */
101
    public $verifypeer = true;
102
    /**
103
     * @var int
104
     * @internal use setSSLVerifyHost
105
     */
106
    public $verifyhost = 2;
107
    /**
108
     * @var int
109
     * @internal use setSSLVersion
110
     */
111
    public $sslversion = 0; // corresponds to CURL_SSLVERSION_DEFAULT
112
113
    /**
114
     * @var string
115
     * @internal use setProxy
116
     */
117
    public $proxy = '';
118
    /**
119
     * @var int
120
     * @internal use setProxy
121
     */
122 2
    public $proxyport = 0;
123
    /**
124 2
     * @var string
125 2
     * @internal use setProxy
126
     */
127 2
    public $proxy_user = '';
128
    /**
129
     * @var string
130
     * @internal use setProxy
131
     */
132
    public $proxy_pass = '';
133
    /**
134
     * @var int
135
     * @internal use setProxy
136
     */
137
    public $proxy_authtype = 1;
138
139
    /**
140
     * @var array
141
     * @internal use setCookie
142
     */
143
    public $cookies = array();
144
145
    /**
146
     * @var array
147
     * @internal use setCurlOptions
148
     */
149
    public $extracurlopts = array();
150
151
    /**
152 718
     * @var int
153
     * @internal use setTimeout
154
     */
155 718
    public $timeout = 0;
156 7
157 7
    /**
158 7
     * @var int
159 7
     * @internal use setUseCurl
160
     */
161
    public $use_curl = self::USE_CURL_AUTO;
162 7
163
    /**
164
     * @var bool
165 7
     *
166
     * This determines whether the multicall() method will try to take advantage of the system.multicall xml-rpc method
167
     * to dispatch to the server an array of requests in a single http roundtrip or simply execute many consecutive http
168 7
     * calls. Defaults to FALSE, but it will be enabled automatically on the first failure of execution of
169 7
     * system.multicall.
170
     */
171 7
    public $no_multicall = false;
172
173
    /**
174 7
     * @var array
175
     *
176
     * List of http compression methods accepted by the client for responses.
177
     * NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib.
178 718
     *
179
     * NNB: you can set it to any non-empty array for HTTP11 and HTTPS, since in those cases it will be up to CURL to
180
     * decide the compression methods it supports. You might check for the presence of 'zlib' in the output of
181 718
     * curl_version() to determine whether compression is supported or not
182
     *
183 718
     * @internal use setAcceptedCompression
184 718
     */
185 3
    public $accepted_compression = array();
186
187 718
    /**
188 7
     * @var string|null
189
     *
190
     * Name of compression scheme to be used for sending requests.
191
     * Either null, 'gzip' or 'deflate'.
192 718
     *
193
     * @internal use setRequestCompression
194 711
     */
195
    public $request_compression = '';
196
197 718
    /**
198
     * @var bool
199
     *
200
     * Whether to use persistent connections for http 1.1 and https. Value set at constructor time
201 718
     */
202
    public $keepalive = false;
203
204 718
    /**
205
     * @var string[]
206
     *
207
     * Charset encodings that can be decoded without problems by the client. Value set at constructor time
208
     */
209
    public $accepted_charset_encodings = array();
210
211
    /**
212
     * @var string
213
     *
214
     * The charset encoding that will be used for serializing request sent by the client.
215
     * It defaults to NULL, which means using US-ASCII and encoding all characters outside the ASCII printable range
216
     * using their xml character entity representation (this has the benefit that line end characters will not be mangled
217 718
     * in the transfer, a CR-LF will be preserved as well as a singe LF).
218 718
     * Valid values are 'US-ASCII', 'UTF-8' and 'ISO-8859-1'.
219
     * For the fastest mode of operation, set your both your app internal encoding and this to UTF-8.
220
     */
221
    public $request_charset_encoding = '';
222
223
    /**
224
     * @var string
225
     *
226
     * Decides the content of Response objects returned by calls to send() and multicall().
227
     * Valid values are 'xmlrpcvals', 'phpvals' or 'xml'.
228
     *
229
     * Determines whether the value returned inside a Response object as results of calls to the send() and multicall()
230
     * methods will be a Value object, a plain php value or a raw xml string.
231
     * Allowed values are 'xmlrpcvals' (the default), 'phpvals' and 'xml'.
232
     * To allow the user to differentiate between a correct and a faulty response, fault responses will be returned as
233 714
     * Response objects in any case.
234
     * Note that the 'phpvals' setting will yield faster execution times, but some of the information from the original
235 714
     * response will be lost. It will be e.g. impossible to tell whether a particular php string value was sent by the
236 714
     * server as an xml-rpc string or base64 value.
237
     */
238
    public $return_type = XMLParser::RETURN_XMLRPCVALS;
239
240
    /**
241
     * @var string
242
     *
243
     * Sent to servers in http headers. Value set at constructor time.
244
     *
245
     * @internal use setUserAgent
246
     */
247
    public $user_agent;
248
249
    /**
250
     * CURL handle: used for keep-alive
251
     * @internal
252 66
     */
253
    public $xmlrpc_curl_handle = null;
254 66
255 66
    /**
256 66
     * @param string $path either the PATH part of the xml-rpc server URL, or complete server URL (in which case you
257 66
     *                     should use and empty string for all other parameters)
258
     *                     e.g. /xmlrpc/server.php
259
     *                     e.g. http://phpxmlrpc.sourceforge.net/server.php
260
     *                     e.g. https://james:[email protected]:444/xmlrpcserver?agent=007
261
     *                     e.g. h2://fast-and-secure-services.org/endpoint
262
     * @param string $server the server name / ip address
263
     * @param integer $port the port the server is listening on, when omitted defaults to 80 or 443 depending on
264
     *                      protocol used
265
     * @param string $method the http protocol variant: defaults to 'http'; 'https', 'http11', 'h2' and 'h2c' can
266
     *                       be used if CURL is installed. The value set here can be overridden in any call to $this->send().
267
     *                       Use 'h2' to make the lib attempt to use http/2 over a secure connection, and 'h2c'
268
     *                       for http/2 without tls. Note that 'h2c' will not use the h2c 'upgrade' method, and be
269
     *                       thus incompatible with any server/proxy not supporting http/2. This is because POST
270
     *                       request are not compatible with h2c upgrade.
271
     */
272
    public function __construct($path, $server = '', $port = '', $method = '')
273
    {
274
        // allow user to specify all params in $path
275
        if ($server == '' && $port == '' && $method == '') {
276
            $parts = parse_url($path);
277
            $server = $parts['host'];
278
            $path = isset($parts['path']) ? $parts['path'] : '';
279
            if (isset($parts['query'])) {
280
                $path .= '?' . $parts['query'];
281
            }
282
            if (isset($parts['fragment'])) {
283
                $path .= '#' . $parts['fragment'];
284
            }
285
            if (isset($parts['port'])) {
286
                $port = $parts['port'];
287
            }
288
            if (isset($parts['scheme'])) {
289
                $method = $parts['scheme'];
290
            }
291
            if (isset($parts['user'])) {
292
                $this->username = $parts['user'];
293
            }
294
            if (isset($parts['pass'])) {
295
                $this->password = $parts['pass'];
296
            }
297
        }
298
        if ($path == '' || $path[0] != '/') {
299
            $this->path = '/' . $path;
300
        } else {
301
            $this->path = $path;
302
        }
303
        $this->server = $server;
304
        if ($port != '') {
305
            $this->port = $port;
306
        }
307
        if ($method != '') {
308
            $this->method = $method;
309
        }
310
311
        // if ZLIB is enabled, let the client by default accept compressed responses
312
        if (function_exists('gzinflate') || (
313
                function_exists('curl_version') && (($info = curl_version()) &&
314
                    ((is_string($info) && strpos($info, 'zlib') !== null) || isset($info['libz_version'])))
315
            )
316 132
        ) {
317
            $this->accepted_compression = array('gzip', 'deflate');
318 132
        }
319 132
320
        // keepalives: enabled by default
321
        $this->keepalive = true;
322
323
        // by default the xml parser can support these 3 charset encodings
324
        $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII');
325
326
        // NB: this is disabled to avoid making all the requests sent huge... mbstring supports more than 80 charsets!
327
        //$ch = Charset::instance();
328 132
        //$this->accepted_charset_encodings = $ch->knownCharsets();
329
330 132
        // initialize user_agent string
331 132
        $this->user_agent = PhpXmlRpc::$xmlrpcName . ' ' . PhpXmlRpc::$xmlrpcVersion;
332
    }
333
334
    /**
335
     * Enable/disable the echoing to screen of the xml-rpc responses received. The default is not to output anything.
336
     *
337
     * The debugging information at level 1 includes the raw data returned from the XML-RPC server it was querying
338 132
     * (including bot HTTP headers and the full XML payload), and the PHP value the client attempts to create to
339
     * represent the value returned by the server.
340 132
     * At level 2, the complete payload of the xml-rpc request is also printed, before being sent to the server.
341 132
     * At level -1, the Response objects returned by send() calls will not carry information about the http response's
342
     * cookies, headers and body, which might save some memory
343
     *
344
     * This option can be very useful when debugging servers as it allows you to see exactly what the client sends and
345
     * the server returns. Never leave it enabled for production!
346
     *
347
     * @param integer $level values -1, 0, 1 and 2 are supported
348
     * @return $this
349
     */
350
    public function setDebug($level)
351
    {
352
        $this->debug = $level;
353
        return $this;
354
    }
355 99
356
    /**
357 99
     * Sets the username and password for authorizing the client to the server.
358 99
     *
359 99
     * With the default (HTTP) transport, this information is used for HTTP Basic authorization.
360 99
     * Note that username and password can also be set using the class constructor.
361 99
     * With HTTP 1.1 and HTTPS transport, NTLM and Digest authentication protocols are also supported. To enable them use
362 99
     * the constants CURLAUTH_DIGEST and CURLAUTH_NTLM as values for the auth type parameter.
363
     *
364
     * @param string $user username
365
     * @param string $password password
366
     * @param integer $authType auth type. See curl_setopt man page for supported auth types. Defaults to CURLAUTH_BASIC
367
     *                          (basic auth). Note that auth types NTLM and Digest will only work if the Curl php
368
     *                          extension is enabled.
369
     * @return $this
370
     */
371
    public function setCredentials($user, $password, $authType = 1)
372
    {
373
        $this->username = $user;
374
        $this->password = $password;
375
        $this->authtype = $authType;
376
        return $this;
377
    }
378
379
    /**
380
     * Set the optional certificate and passphrase used in SSL-enabled communication with a remote server.
381
     *
382
     * Note: to retrieve information about the client certificate on the server side, you will need to look into the
383
     * environment variables which are set up by the webserver. Different webservers will typically set up different
384
     * variables.
385
     *
386
     * @param string $cert the name of a file containing a PEM formatted certificate
387
     * @param string $certPass the password required to use it
388
     * @return $this
389
     */
390
    public function setCertificate($cert, $certPass = '')
391
    {
392
        $this->cert = $cert;
393
        $this->certpass = $certPass;
394
        return $this;
395
    }
396
397
    /**
398
     * Add a CA certificate to verify server with in SSL-enabled communication when SetSSLVerifypeer has been set to TRUE.
399
     *
400
     * See the php manual page about CURLOPT_CAINFO for more details.
401
     *
402
     * @param string $caCert certificate file name (or dir holding certificates)
403
     * @param bool $isDir set to true to indicate cacert is a dir. defaults to false
404
     * @return $this
405
     */
406
    public function setCaCertificate($caCert, $isDir = false)
407
    {
408
        if ($isDir) {
409
            $this->cacertdir = $caCert;
410
        } else {
411
            $this->cacert = $caCert;
412
        }
413
        return $this;
414
    }
415
416
    /**
417
     * Set attributes for SSL communication: private SSL key.
418 580
     *
419
     * NB: does not work in older php/curl installs.
420 580
     * Thanks to Daniel Convissor.
421 580
     *
422
     * @param string $key The name of a file containing a private SSL key
423
     * @param string $keyPass The secret password needed to use the private SSL key
424
     * @return $this
425
     */
426
    public function setKey($key, $keyPass)
427 580
    {
428
        $this->key = $key;
429 580
        $this->keypass = $keyPass;
430
        return $this;
431
    }
432
433
    /**
434
     * Set attributes for SSL communication: verify the remote host's SSL certificate, and cause the connection to fail
435
     * if the cert verification fails.
436
     *
437
     * By default, verification is enabled.
438
     * To specify custom SSL certificates to validate the server with, use the setCaCertificate method.
439
     *
440
     * @param bool $i enable/disable verification of peer certificate
441
     * @return $this
442
     */
443
    public function setSSLVerifyPeer($i)
444
    {
445
        $this->verifypeer = $i;
446 66
        return $this;
447
    }
448 66
449 66
    /**
450
     * Set attributes for SSL communication: verify the remote host's SSL certificate's common name (CN).
451
     *
452
     * Note that support for value 1 has been removed in cURL 7.28.1
453
     *
454
     * @param int $i Set to 1 to only the existence of a CN, not that it matches
455
     * @return $this
456
     */
457
    public function setSSLVerifyHost($i)
458
    {
459
        $this->verifyhost = $i;
460
        return $this;
461
    }
462
463
    /**
464
     * Set attributes for SSL communication: SSL version to use. Best left at 0 (default value): let cURL decide
465
     *
466
     * @param int $i
467
     * @return $this
468
     */
469
    public function setSSLVersion($i)
470
    {
471
        $this->sslversion = $i;
472
        return $this;
473
    }
474
475
    /**
476
     * Set proxy info.
477
     *
478
     * NB: CURL versions before 7.11.10 cannot use a proxy to communicate with https servers.
479
     *
480
     * @param string $proxyHost
481
     * @param string $proxyPort Defaults to 8080 for HTTP and 443 for HTTPS
482
     * @param string $proxyUsername Leave blank if proxy has public access
483
     * @param string $proxyPassword Leave blank if proxy has public access
484
     * @param int $proxyAuthType defaults to CURLAUTH_BASIC (Basic authentication protocol); set to constant CURLAUTH_NTLM
485
     *                           to use NTLM auth with proxy (has effect only when the client uses the HTTP 1.1 protocol)
486
     * @return $this
487
     */
488
    public function setProxy($proxyHost, $proxyPort, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1)
489
    {
490
        $this->proxy = $proxyHost;
491
        $this->proxyport = $proxyPort;
0 ignored issues
show
Documentation Bug introduced by
The property $proxyport was declared of type integer, but $proxyPort is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
492
        $this->proxy_user = $proxyUsername;
493
        $this->proxy_pass = $proxyPassword;
494
        $this->proxy_authtype = $proxyAuthType;
495 697
        return $this;
496
    }
497
498
    /**
499 697
     * Enables/disables reception of compressed xml-rpc responses.
500 117
     *
501
     * This requires the "zlib" extension to be enabled in your php install. If it is, by default xmlrpc_client
502
     * instances will enable reception of compressed content.
503 697
     * Note that enabling reception of compressed responses merely adds some standard http headers to xml-rpc requests.
504
     * It is up to the xml-rpc server to return compressed responses when receiving such requests.
505 66
     *
506
     * @param string $compMethod either 'gzip', 'deflate', 'any' or ''
507 66
     * @return $this
508 697
     */
509 28
    public function setAcceptedCompression($compMethod)
510 28
    {
511 28
        if ($compMethod == 'any') {
512
            $this->accepted_compression = array('gzip', 'deflate');
513
        } elseif ($compMethod == false) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $compMethod of type string to the boolean false. If you are specifically checking for an empty string, consider using the more explicit === '' instead.
Loading history...
514
            $this->accepted_compression = array();
515 697
        } else {
516
            $this->accepted_compression = array($compMethod);
517
        }
518
        return $this;
519 697
    }
520 697
521
    /**
522 697
     * Enables/disables http compression of xml-rpc request.
523 322
     *
524 322
     * This requires the "zlib" extension to be enabled in your php install.
525 322
     * Take care when sending compressed requests: servers might not support them (and automatic fallback to
526 322
     * uncompressed requests is not yet implemented).
527
     *
528 322
     * @param string $compMethod either 'gzip', 'deflate' or ''
529 322
     * @return $this
530 322
     */
531 322
    public function setRequestCompression($compMethod)
532 322
    {
533 322
        $this->request_compression = $compMethod;
534 322
        return $this;
535 322
    }
536 322
537 322
    /**
538 322
     * Adds a cookie to list of cookies that will be sent to server with every further request (useful e.g. for keeping
539 322
     * session info outside of the xml-rpc payload).
540
     *
541 322
     * NB: By default cookies are sent using the 'original/netscape' format, which is also the same as the RFC 2965;
542 322
     * setting any param but name and value will turn the cookie into a 'version 1' cookie (i.e. RFC 2109 cookie) that
543 322
     * might not be fully supported by the server. Note that RFC 2109 has currently 'historic' status...
544 322
     *
545 322
     * @param string $name nb: will not be escaped in the request's http headers. Take care not to use CTL chars or
546
     *                     separators!
547
     * @param string $value
548
     * @param string $path leave this empty unless the xml-rpc server only accepts RFC 2109 cookies
549 375
     * @param string $domain leave this empty unless the xml-rpc server only accepts RFC 2109 cookies
550 375
     * @param int $port leave this empty unless the xml-rpc server only accepts RFC 2109 cookies
551 375
     * @return $this
552 375
     *
553
     * @todo check correctness of urlencoding cookie value (copied from php way of doing it, but php is generally sending
554 375
     *       response not requests. We do the opposite...)
555 375
     * @todo strip invalid chars from cookie name? As per RFC6265, we should follow RFC2616, Section 2.2
556 375
     */
557 375
    public function setCookie($name, $value = '', $path = '', $domain = '', $port = null)
558 375
    {
559 375
        $this->cookies[$name]['value'] = rawurlencode($value);
560 375
        if ($path || $domain || $port) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $port of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. 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 integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
561 375
            $this->cookies[$name]['path'] = $path;
562 375
            $this->cookies[$name]['domain'] = $domain;
563 375
            $this->cookies[$name]['port'] = $port;
564 375
            $this->cookies[$name]['version'] = 1;
565 375
        } else {
566
            $this->cookies[$name]['version'] = 0;
567 375
        }
568 375
        return $this;
569 375
    }
570
571
    /**
572
     * Directly set cURL options, for extra flexibility (when in cURL mode).
573 697
     *
574
     * It allows e.g. to bind client to a specific IP interface / address.
575
     *
576
     * @param array $options
577
     * @return $this
578
     */
579
    public function setCurlOptions($options)
580
    {
581
        $this->extracurlopts = $options;
582
        return $this;
583
    }
584
585
    /**
586
     * @param int $useCurlMode self::USE_CURL_ALWAYS, self::USE_CURL_AUTO or self::USE_CURL_NEVER
587
     * @return $this
588
     */
589
    public function setUseCurl($useCurlMode)
590
    {
591
        $this->use_curl = $useCurlMode;
592
        return $this;
593
    }
594
595
596
    /**
597
     * Set user-agent string that will be used by this client instance in http headers sent to the server.
598
     *
599
     * The default user agent string includes the name of this library and the version number.
600
     *
601
     * @param string $agentString
602
     * @return $this
603
     */
604
    public function setUserAgent($agentString)
605
    {
606
        $this->user_agent = $agentString;
607
        return $this;
608
    }
609
610
    /**
611
     * @param int $timeout
612
     * @return $this
613
     */
614
    public function setTimeout($timeout)
615
    {
616
        $this->timeout = $timeout;
617
        return $this;
618
    }
619
620
    /**
621
     * Send an xml-rpc request to the server.
622
     *
623
     * @param Request|Request[]|string $req The Request object, or an array of requests for using multicall, or the
624
     *                                      complete xml representation of a request.
625
     *                                      When sending an array of Request objects, the client will try to make use of
626
     *                                      a single 'system.multicall' xml-rpc method call to forward to the server all
627
     *                                      the requests in a single HTTP round trip, unless $this->no_multicall has
628
     *                                      been previously set to TRUE (see the multicall method below), in which case
629
     *                                      many consecutive xml-rpc requests will be sent. The method will return an
630
     *                                      array of Response objects in both cases.
631
     *                                      The third variant allows to build by hand (or any other means) a complete
632
     *                                      xml-rpc request message, and send it to the server. $req should be a string
633
     *                                      containing the complete xml representation of the request. It is e.g. useful
634
     *                                      when, for maximal speed of execution, the request is serialized into a
635
     *                                      string using the native php xml-rpc functions (see http://www.php.net/xmlrpc)
636
     * @param integer $timeout deprecated. Connection timeout, in seconds, If unspecified, the timeout set with setTimeout
637
     *                         will be used. If that is 0, a platform specific timeout will apply.
638
     *                         This timeout value is passed to fsockopen(). It is also used for detecting server
639
     *                         timeouts during communication (i.e. if the server does not send anything to the client
640
     *                         for $timeout seconds, the connection will be closed).
641
     * @param string $method deprecated. Use the same value in the constructor instead.
642
     *                       Valid values are 'http', 'http11', 'https', 'h2' and 'h2c'. If left empty,
643
     *                       the http protocol chosen during creation of the object will be used.
644
     *                       Use 'h2' to make the lib attempt to use http/2 over a secure connection, and 'h2c'
645
     *                       for http/2 without tls. Note that 'h2c' will not use the h2c 'upgrade' method, and be
646
     *                       thus incompatible with any server/proxy not supporting http/2. This is because POST
647
     *                       request are not compatible with h2c upgrade.
648
     * @return Response|Response[] Note that the client will always return a Response object, even if the call fails
649
     *
650
     * @todo allow throwing exceptions instead of returning responses in case of failed calls and/or Fault responses
651
     * @todo refactor: we now support many options besides connection timeout and http version to use. Why only privilege those?
652
     */
653
    public function send($req, $timeout = 0, $method = '')
654
    {
655
        // if user does not specify http protocol, use native method of this client
656
        // (i.e. method set during call to constructor)
657
        if ($method == '') {
658
            $method = $this->method;
659
        }
660
661
        if ($timeout == 0) {
662
            $timeout = $this->timeout;
663
        }
664 375
665
        if (is_array($req)) {
666
            // $req is an array of Requests
667
            $r = $this->multicall($req, $timeout, $method);
668
669
            return $r;
670
        } elseif (is_string($req)) {
671 375
            $n = new  static::$requestClass('');
672 373
            $n->payload = $req;
673
            $req = $n;
674
        }
675
676 375
        // where req is a Request
677 346
        $req->setDebug($this->debug);
678
679
        /// @todo we could be smarter about this and force usage of curl in scenarios where it is both available and
680 375
        ///       needed, such as digest or ntlm auth. Do not attempt to use it for https if not present
681
        $useCurl = ($this->use_curl == self::USE_CURL_ALWAYS) || ($this->use_curl == self::USE_CURL_AUTO &&
682 375
            (in_array($method, array('https', 'http11', 'h2c', 'h2'))));
683 375
684 64
        if ($useCurl) {
685 32
            $r = $this->sendPayloadCURL(
686 32
                $req,
687 32
                $this->server,
688 32
                $this->port,
689
                $timeout,
690
                $this->username,
691 32
                $this->password,
692 32
                $this->authtype,
693 32
                $this->cert,
694 32
                $this->certpass,
695
                $this->cacert,
696
                $this->cacertdir,
697
                $this->proxy,
698
                $this->proxyport,
699
                $this->proxy_user,
700 375
                $this->proxy_pass,
701 375
                $this->proxy_authtype,
702 32
                // bc
703 32
                $method == 'http11' ? 'http' : $method,
704
                $this->keepalive,
705
                $this->key,
706
                $this->keypass,
707
                $this->sslversion
708 375
            );
709 375
        } else {
710 73
            $r = $this->sendPayloadSocket(
711
                $req,
712
                $this->server,
713 375
                $this->port,
714 375
                $timeout,
715 32
                $this->username,
716
                $this->password,
717
                $this->authtype,
718 32
                $this->cert,
719 32
                $this->certpass,
720 32
                $this->cacert,
721 32
                $this->cacertdir,
722 32
                $this->proxy,
723
                $this->proxyport,
724
                $this->proxy_user,
725
                $this->proxy_pass,
726 32
                $this->proxy_authtype,
727
                $method,
728
                $this->key,
729 343
                $this->keypass,
730 343
                $this->sslversion
731 343
            );
732 343
        }
733
734
        return $r;
735
    }
736 375
737 375
    /**
738 309
     * @deprecated
739 309
     *
740 309
     * @param Request $req
741
     * @param string $server
742
     * @param int $port
743
     * @param int $timeout
744
     * @param string $username
745
     * @param string $password
746
     * @param int $authType
747
     * @param string $proxyHost
748
     * @param int $proxyPort
749
     * @param string $proxyUsername
750
     * @param string $proxyPassword
751
     * @param int $proxyAuthType
752
     * @param string $method
753 309
     * @return Response
754
     */
755
    protected function sendPayloadHTTP10($req, $server, $port, $timeout = 0, $username = '', $password = '',
756 309
        $authType = 1, $proxyHost = '', $proxyPort = 0, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1,
757
        $method='http')
758
    {
759
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
760 375
761 375
        return $this->sendPayloadSocket($req, $server, $port, $timeout, $username, $password, $authType, null, null,
762
            null, null, $proxyHost, $proxyPort, $proxyUsername, $proxyPassword, $proxyAuthType, $method);
763
    }
764
765
    /**
766 375
     * @deprecated
767 375
     *
768 375
     * @param Request $req
769 375
     * @param string $server
770 375
     * @param int $port
771 375
     * @param int $timeout
772 375
     * @param string $username
773 375
     * @param string $password
774 375
     * @param int $authType
775 375
     * @param string $cert
776 375
     * @param string $certPass
777 375
     * @param string $caCert
778
     * @param string $caCertDir
779 375
     * @param string $proxyHost
780 2
     * @param int $proxyPort
781
     * @param string $proxyUsername
782
     * @param string $proxyPassword
783 375
     * @param int $proxyAuthType
784 375
     * @param bool $keepAlive
785 32
     * @param string $key
786
     * @param string $keyPass
787
     * @param int $sslVersion
788
     * @return Response
789
     */
790
    protected function sendPayloadHTTPS($req, $server, $port, $timeout = 0, $username = '',  $password = '',
791 32
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
792
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $keepAlive = false, $key = '', $keyPass = '',
793
        $sslVersion = 0)
794 32
    {
795
        //trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED);
796
797 32
        return $this->sendPayloadCURL($req, $server, $port, $timeout, $username,
798
            $password, $authType, $cert, $certPass, $caCert, $caCertDir, $proxyHost, $proxyPort,
799
            $proxyUsername, $proxyPassword, $proxyAuthType, 'https', $keepAlive, $key, $keyPass, $sslVersion);
800 32
    }
801 32
802
    /**
803
     * @param Request $req
804 375
     * @param string $server
805
     * @param int $port
806 375
     * @param int $timeout
807 64
     * @param string $username
808
     * @param string $password
809 311
     * @param int $authType only value supported is 1
810
     * @param string $cert
811
     * @param string $certPass
812 375
     * @param string $caCert
813 375
     * @param string $caCertDir
814
     * @param string $proxyHost
815 375
     * @param int $proxyPort
816 375
     * @param string $proxyUsername
817 375
     * @param string $proxyPassword
818 374
     * @param int $proxyAuthType only value supported is 1
819 367
     * @param string $method 'http' (synonym for 'http10'), 'http10' or 'https'
820
     * @param string $key
821
     * @param string $keyPass @todo not implemented yet.
822 1
     * @param int $sslVersion @todo not implemented yet. See http://php.net/manual/en/migration56.openssl.php
823
     * @return Response
824
     *
825
     * @todo refactor: we get many options for the call passed in, but some we use from $this. We should clean that up
826
     */
827 1
    protected function sendPayloadSocket($req, $server, $port, $timeout = 0, $username = '', $password = '',
828 1
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
829
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method='http', $key = '', $keyPass = '',
0 ignored issues
show
Unused Code introduced by
The parameter $keyPass is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

829
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method='http', $key = '', /** @scrutinizer ignore-unused */ $keyPass = '',

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
830 1
        $sslVersion = 0)
0 ignored issues
show
Unused Code introduced by
The parameter $sslVersion is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

830
        /** @scrutinizer ignore-unused */ $sslVersion = 0)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
831
    {
832
        /// @todo log a warning if passed an unsupported method
833 374
834
        if ($port == 0) {
835
            $port = ($method === 'https') ? 443 : 80;
836
        }
837
838
        // Only create the payload if it was not created previously
839
        /// @todo what if the request's payload was created with a different encoding?
840
        if (empty($req->payload)) {
841
            $req->serialize($this->request_charset_encoding);
842
        }
843 374
844
        $payload = $req->payload;
845
        // Deflate request body and set appropriate request headers
846
        $encodingHdr = '';
847 374
        if (function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate')) {
848 374
            if ($this->request_compression == 'gzip') {
849 374
                $a = @gzencode($payload);
850
                if ($a) {
851 374
                    $payload = $a;
852
                    $encodingHdr = "Content-Encoding: gzip\r\n";
853 374
                }
854
            } else {
855
                $a = @gzcompress($payload);
856
                if ($a) {
857
                    $payload = $a;
858
                    $encodingHdr = "Content-Encoding: deflate\r\n";
859
                }
860
            }
861
        }
862
863
        // thanks to Grant Rauscher <[email protected]> for this
864
        $credentials = '';
865
        if ($username != '') {
866
            $credentials = 'Authorization: Basic ' . base64_encode($username . ':' . $password) . "\r\n";
867
            if ($authType != 1) {
868
                /// @todo make this a proper error, i.e. return a failure
869
                $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported with HTTP 1.0');
870
            }
871
        }
872
873
        $acceptedEncoding = '';
874
        if (is_array($this->accepted_compression) && count($this->accepted_compression)) {
875
            $acceptedEncoding = 'Accept-Encoding: ' . implode(', ', $this->accepted_compression) . "\r\n";
876
        }
877
878
        $proxyCredentials = '';
879
        if ($proxyHost) {
880
            if ($proxyPort == 0) {
881
                $proxyPort = 8080;
882
            }
883
            $connectServer = $proxyHost;
884
            $connectPort = $proxyPort;
885
            $transport = 'tcp';
886 322
            /// @todo check: should we not use https in some cases?
887
            $uri = 'http://' . $server . ':' . $port . $this->path;
888
            if ($proxyUsername != '') {
889
                if ($proxyAuthType != 1) {
890
                    /// @todo make this a proper error, i.e. return a failure
891 322
                    $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported with HTTP 1.0');
892
                }
893
                $proxyCredentials = 'Proxy-Authorization: Basic ' . base64_encode($proxyUsername . ':' . $proxyPassword) . "\r\n";
894
            }
895 322
        } else {
896
            $connectServer = $server;
897 96
            $connectPort = $port;
898 96
            $transport = ($method === 'https') ? 'tls' : 'tcp';
899
            $uri = $this->path;
900
        }
901
902
        // Cookie generation, as per rfc2965 (version 1 cookies) or netscape's rules (version 0 cookies)
903
        $cookieHeader = '';
904 322
        if (count($this->cookies)) {
905 322
            $version = '';
906
            foreach ($this->cookies as $name => $cookie) {
907
                if ($cookie['version']) {
908
                    $version = ' $Version="' . $cookie['version'] . '";';
909
                    $cookieHeader .= ' ' . $name . '="' . $cookie['value'] . '";';
910 322
                    if ($cookie['path']) {
911
                        $cookieHeader .= ' $Path="' . $cookie['path'] . '";';
912
                    }
913
                    if ($cookie['domain']) {
914
                        $cookieHeader .= ' $Domain="' . $cookie['domain'] . '";';
915 322
                    }
916
                    if ($cookie['port']) {
917 322
                        $cookieHeader .= ' $Port="' . $cookie['port'] . '";';
918
                    }
919
                } else {
920
                    $cookieHeader .= ' ' . $name . '=' . $cookie['value'] . ";";
921
                }
922
            }
923
            $cookieHeader = 'Cookie:' . $version . substr($cookieHeader, 0, -1) . "\r\n";
924
        }
925
926
        // omit port if default
927
        if (($port == 80 && in_array($method, array('http', 'http10'))) || ($port == 443 && $method == 'https')) {
928
            $port =  '';
929 322
        } else {
930
            $port = ':' . $port;
931
        }
932 1
933 1
        $op = 'POST ' . $uri . " HTTP/1.0\r\n" .
934 1
            'User-Agent: ' . $this->user_agent . "\r\n" .
935 1
            'Host: ' . $server . $port . "\r\n" .
936 1
            $credentials .
937
            $proxyCredentials .
938
            $acceptedEncoding .
939 322
            $encodingHdr .
940 160
            'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings) . "\r\n" .
941
            $cookieHeader .
942 322
            'Content-Type: ' . $req->content_type . "\r\nContent-Length: " .
943
            strlen($payload) . "\r\n\r\n" .
944 322
            $payload;
945
946
        if ($this->debug > 1) {
947
            $this->getLogger()->debugMessage("---SENDING---\n$op\n---END---");
948
        }
949
950 322
        $contextOptions = array();
951
        if ($method == 'https') {
952
            if ($cert != '') {
953 323
                $contextOptions['ssl']['local_cert'] = $cert;
954
                if ($certPass != '') {
955
                    $contextOptions['ssl']['passphrase'] = $certPass;
956
                }
957
            }
958 323
            if ($caCert != '') {
959 322
                $contextOptions['ssl']['cafile'] = $caCert;
960 226
            }
961
            if ($caCertDir != '') {
962 96
                $contextOptions['ssl']['capath'] = $caCertDir;
963
            }
964
            if ($key != '') {
965
                $contextOptions['ssl']['local_pk'] = $key;
966
            }
967 323
            $contextOptions['ssl']['verify_peer'] = $this->verifypeer;
968 302
            $contextOptions['ssl']['verify_peer_name'] = $this->verifypeer;
969
        }
970
971
        $context = stream_context_create($contextOptions);
972 323
973 323
        if ($timeout <= 0) {
974 64
            $connectTimeout = ini_get('default_socket_timeout');
975 32
        } else {
976 32
            $connectTimeout = $timeout;
977 32
        }
978 32
979
        $this->errno = 0;
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errno has been deprecated: will be removed in the future ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

979
        /** @scrutinizer ignore-deprecated */ $this->errno = 0;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
980
        $this->errstr = '';
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errstr has been deprecated: will be removed in the future ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

980
        /** @scrutinizer ignore-deprecated */ $this->errstr = '';

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
981 32
982 32
        $fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, $this->errstr, $connectTimeout,
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errno has been deprecated: will be removed in the future ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

982
        $fp = @stream_socket_client("$transport://$connectServer:$connectPort", /** @scrutinizer ignore-deprecated */ $this->errno, $this->errstr, $connectTimeout,

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errstr has been deprecated: will be removed in the future ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

982
        $fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, /** @scrutinizer ignore-deprecated */ $this->errstr, $connectTimeout,

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
Bug introduced by
It seems like $connectTimeout can also be of type string; however, parameter $timeout of stream_socket_client() does only seem to accept double|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

982
        $fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, $this->errstr, /** @scrutinizer ignore-type */ $connectTimeout,
Loading history...
983 32
            STREAM_CLIENT_CONNECT, $context);
984 64
        if ($fp) {
0 ignored issues
show
introduced by
$fp is of type false|resource, thus it always evaluated to false.
Loading history...
985
            if ($timeout > 0) {
986
                stream_set_timeout($fp, $timeout, 0);
987
            }
988 259
        } else {
989
            if ($this->errstr == '') {
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errstr has been deprecated: will be removed in the future ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

989
            if (/** @scrutinizer ignore-deprecated */ $this->errstr == '') {

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
990
                $err = error_get_last();
991 323
                $this->errstr = $err['message'];
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errstr has been deprecated: will be removed in the future ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

991
                /** @scrutinizer ignore-deprecated */ $this->errstr = $err['message'];

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
992 323
            }
993 64
994
            $this->errstr = 'Connect error: ' . $this->errstr;
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errstr has been deprecated: will be removed in the future ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

994
            $this->errstr = 'Connect error: ' . /** @scrutinizer ignore-deprecated */ $this->errstr;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
995 259
            $r = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr . ' (' . $this->errno . ')');
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errno has been deprecated: will be removed in the future ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

995
            $r = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr . ' (' . /** @scrutinizer ignore-deprecated */ $this->errno . ')');

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errstr has been deprecated: will be removed in the future ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

995
            $r = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], /** @scrutinizer ignore-deprecated */ $this->errstr . ' (' . $this->errno . ')');

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
996 32
997
            return $r;
998
        }
999 227
1000
        if (!fputs($fp, $op, strlen($op))) {
1001
            fclose($fp);
1002 323
            $this->errstr = 'Write error';
1003 323
            $r = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr);
1004 322
1005
            return $r;
1006
        }
1007 56
1008
        // Close socket before parsing.
1009
        // It should yield slightly better execution times, and make easier recursive calls (e.g. to follow http redirects)
1010
        $ipd = '';
1011 323
        do {
1012
            // shall we check for $data === FALSE?
1013 323
            // as per the manual, it signals an error
1014
            $ipd .= fread($fp, 32768);
1015
        } while (!feof($fp));
1016
        fclose($fp);
1017 323
1018
        $r = $req->parseResponse($ipd, false, $this->return_type);
1019 323
1020
        return $r;
1021 323
    }
1022
1023
    /**
1024 323
     * Contributed by Justin Miller <[email protected]>
1025
     * Requires curl to be built into PHP
1026
     * NB: CURL versions before 7.11.10 cannot use proxy to talk to https servers!
1027
     *
1028 323
     * @param Request $req
1029
     * @param string $server
1030
     * @param int $port
1031 66
     * @param int $timeout
1032 64
     * @param string $username
1033
     * @param string $password
1034 2
     * @param int $authType
1035
     * @param string $cert
1036
     * @param string $certPass
1037
     * @param string $caCert
1038 323
     * @param string $caCertDir
1039
     * @param string $proxyHost
1040 323
     * @param int $proxyPort
1041 161
     * @param string $proxyUsername
1042
     * @param string $proxyPassword
1043
     * @param int $proxyAuthType
1044 323
     * @param string $method 'http' (let curl decide), 'http10', 'http11', 'https', 'h2c' or 'h2'
1045 64
     * @param bool $keepAlive
1046
     * @param string $key
1047
     * @param string $keyPass
1048
     * @param int $sslVersion
1049
     * @return Response
1050 323
     *
1051
     * @todo refactor: we get many options for the call passed in, but some we use from $this. We should clean that up
1052 323
     */
1053
    protected function sendPayloadCURL($req, $server, $port, $timeout = 0, $username = '', $password = '',
1054 323
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
1055 272
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'https', $keepAlive = false, $key = '',
1056
        $keyPass = '', $sslVersion = 0)
1057
    {
1058 323
        if (!function_exists('curl_init')) {
1059 323
            $this->errstr = 'CURL unavailable on this install';
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errstr has been deprecated: will be removed in the future ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1059
            /** @scrutinizer ignore-deprecated */ $this->errstr = 'CURL unavailable on this install';

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1060 32
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_curl'], PhpXmlRpc::$xmlrpcstr['no_curl']);
1061 32
        }
1062 291
        if ($method == 'https' || $method == 'h2') {
1063
            // q: what about installs where we get back a string, but curl is linked to other ssl libs than openssl?
1064
            if (($info = curl_version()) &&
1065 291
                ((is_string($info) && strpos($info, 'OpenSSL') === null) || (is_array($info) && !isset($info['ssl_version'])))
0 ignored issues
show
introduced by
The condition is_string($info) is always false.
Loading history...
1066 32
            ) {
1067 32
                $this->errstr = 'SSL unavailable on this install';
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errstr has been deprecated: will be removed in the future ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1067
                /** @scrutinizer ignore-deprecated */ $this->errstr = 'SSL unavailable on this install';

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1068 259
                return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_ssl'], PhpXmlRpc::$xmlrpcstr['no_ssl']);
1069 32
            }
1070 32
        }
1071
        if (($method == 'h2' && !defined('CURL_HTTP_VERSION_2_0')) ||
1072
            ($method == 'h2c' && !defined('CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE'))) {
1073 323
            $this->errstr = 'HTTP/2 unavailable on this install';
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errstr has been deprecated: will be removed in the future ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1073
            /** @scrutinizer ignore-deprecated */ $this->errstr = 'HTTP/2 unavailable on this install';

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1074 32
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['no_http2'], PhpXmlRpc::$xmlrpcstr['no_http2']);
1075 32
        }
1076 32
1077
        $curl = $this->prepareCurlHandle($req, $server, $port, $timeout, $username, $password,
1078
            $authType, $cert, $certPass, $caCert, $caCertDir, $proxyHost, $proxyPort,
1079
            $proxyUsername, $proxyPassword, $proxyAuthType, $method, $keepAlive, $key,
1080
            $keyPass, $sslVersion);
1081
1082 323
        if (!$curl) {
1083
            return new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] . ': error during curl initialization. Check php error log for details');
1084 96
        }
1085
1086
        $result = curl_exec($curl);
1087
1088 96
        if ($this->debug > 1) {
1089
            $message = "---CURL INFO---\n";
1090
            foreach (curl_getinfo($curl) as $name => $val) {
1091
                if (is_array($val)) {
1092 96
                    $val = implode("\n", $val);
1093
                }
1094 96
                $message .= $name . ': ' . $val . "\n";
1095
            }
1096
            $message .= '---END---';
1097 96
            $this->getLogger()->debugMessage($message);
1098
        }
1099
1100
        if (!$result) {
1101 96
            /// @todo we should use a better check here - what if we get back '' or '0'?
1102
1103
            $this->errstr = 'no response';
0 ignored issues
show
Deprecated Code introduced by
The property PhpXmlRpc\Client::$errstr has been deprecated: will be removed in the future ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1103
            /** @scrutinizer ignore-deprecated */ $this->errstr = 'no response';

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1104
            $resp = new static::$responseClass(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] . ': ' . curl_error($curl));
1105 96
            curl_close($curl);
1106
            if ($keepAlive) {
1107
                $this->xmlrpc_curl_handle = null;
1108
            }
1109
        } else {
1110 96
            if (!$keepAlive) {
1111
                curl_close($curl);
1112 96
            }
1113
            $resp = $req->parseResponse($result, true, $this->return_type);
0 ignored issues
show
Bug introduced by
It seems like $result can also be of type true; however, parameter $data of PhpXmlRpc\Request::parseResponse() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1113
            $resp = $req->parseResponse(/** @scrutinizer ignore-type */ $result, true, $this->return_type);
Loading history...
1114
            if ($keepAlive) {
1115
                /// @todo if we got back a 302 or 308, we should not reuse the curl handle for later calls
1116 323
                if ($resp->faultCode() == PhpXmlRpc::$xmlrpcerr['http_error']) {
1117 64
                    curl_close($curl);
1118
                    $this->xmlrpc_curl_handle = null;
1119
                }
1120 64
            }
1121 64
        }
1122
1123
        return $resp;
1124
    }
1125
1126
    protected function prepareCurlHandle($req, $server, $port, $timeout = 0, $username = '', $password = '',
1127
         $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
1128
         $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'https', $keepAlive = false, $key = '',
1129
         $keyPass = '', $sslVersion = 0)
1130
    {
1131
        if ($port == 0) {
1132
            if (in_array($method, array('http', 'http10', 'http11', 'h2c'))) {
1133 323
                $port = 80;
1134 271
            } else {
1135 271
                $port = 443;
1136 271
            }
1137
        }
1138 271
1139
        // Only create the payload if it was not created previously
1140
        if (empty($req->payload)) {
1141 323
            $req->serialize($this->request_charset_encoding);
1142
        }
1143
1144
        // Deflate request body and set appropriate request headers
1145 323
        $payload = $req->payload;
1146
        $encodingHdr = '';
1147
        /// @todo test for existence of proper function, in case of polyfills
1148
        if (function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate')) {
1149 323
            if ($this->request_compression == 'gzip') {
1150
                $a = @gzencode($payload);
1151
                if ($a) {
1152
                    $payload = $a;
1153
                    $encodingHdr = 'Content-Encoding: gzip';
1154
                }
1155
            } else {
1156
                $a = @gzcompress($payload);
1157
                if ($a) {
1158
                    $payload = $a;
1159
                    $encodingHdr = 'Content-Encoding: deflate';
1160
                }
1161
            }
1162
        }
1163
1164
        if (!$keepAlive || !$this->xmlrpc_curl_handle) {
1165
            if ($method == 'http11' || $method == 'http10' || $method == 'h2c') {
1166
                $protocol = 'http';
1167
            } else {
1168
                if ($method == 'h2') {
1169
                    $protocol = 'https';
1170
                } else {
1171
                    // http, https
1172
                    $protocol = $method;
1173
                    if (strpos($protocol, ':') !== false) {
1174 66
                        $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ": warning - attempted hacking attempt?. The curl protocol requested for the call is: '$protocol'");
1175
                        return false;
1176 66
                    }
1177
                }
1178
            }
1179 66
            $curl = curl_init($protocol . '://' . $server . ':' . $port . $this->path);
1180 45
            if (!$curl) {
1181 45
                return false;
1182
            }
1183 45
            if ($keepAlive) {
1184
                $this->xmlrpc_curl_handle = $curl;
1185
            }
1186
        } else {
1187
            $curl = $this->xmlrpc_curl_handle;
1188
        }
1189
1190
        // results into variable
1191
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
1192
1193
        if ($this->debug > 1) {
1194
            curl_setopt($curl, CURLOPT_VERBOSE, true);
1195
            /// @todo redirect curlopt_stderr to some stream which can be piped to the logger
1196
        }
1197
        curl_setopt($curl, CURLOPT_USERAGENT, $this->user_agent);
1198
        // required for XMLRPC: post the data
1199
        curl_setopt($curl, CURLOPT_POST, 1);
1200
        // the data
1201 23
        curl_setopt($curl, CURLOPT_POSTFIELDS, $payload);
1202
1203
        // return the header too
1204 23
        curl_setopt($curl, CURLOPT_HEADER, 1);
1205 23
1206
        // NB: if we set an empty string, CURL will add http header indicating
1207
        // ALL methods it is supporting. This is possibly a better option than letting the user tell what curl can / cannot do...
1208
        if (is_array($this->accepted_compression) && count($this->accepted_compression)) {
1209 23
            //curl_setopt($curl, CURLOPT_ENCODING, implode(',', $this->accepted_compression));
1210 23
            // empty string means 'any supported by CURL' (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1211
            if (count($this->accepted_compression) == 1) {
1212
                curl_setopt($curl, CURLOPT_ENCODING, $this->accepted_compression[0]);
1213
            } else {
1214
                curl_setopt($curl, CURLOPT_ENCODING, '');
1215
            }
1216
        }
1217
        // extra headers
1218
        $headers = array('Content-Type: ' . $req->content_type, 'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings));
1219
        // if no keepalive is wanted, let the server know it in advance
1220
        if (!$keepAlive) {
1221 23
            $headers[] = 'Connection: close';
1222
        }
1223
        // request compression header
1224
        if ($encodingHdr) {
1225
            $headers[] = $encodingHdr;
1226
        }
1227
1228
        // Fix the HTTP/1.1 417 Expectation Failed Bug (curl by default adds a 'Expect: 100-continue' header when POST
1229
        // size exceeds 1025 bytes, apparently)
1230
        $headers[] = 'Expect:';
1231
1232
        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
1233
        // timeout is borked
1234
        if ($timeout) {
1235 45
            curl_setopt($curl, CURLOPT_TIMEOUT, $timeout == 1 ? 1 : $timeout - 1);
1236
        }
1237
1238 45
        switch ($method) {
1239 45
            case 'http10':
1240 45
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
1241 45
                break;
1242 45
            case 'http11':
1243 45
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
1244 45
                break;
1245
            case 'h2c':
1246 45
                if (defined('CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE')) {
1247 45
                    curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE);
1248
                } else {
1249 45
                    /// @todo make this a proper error, i.e. return a failure
1250 45
                    $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': warning. HTTP2 is not supported by the current PHP/curl install');
1251
                }
1252
                break;
1253 45
            case 'h2':
1254
                curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
1255 45
                break;
1256
        }
1257
1258
        if ($username && $password) {
1259
            curl_setopt($curl, CURLOPT_USERPWD, $username . ':' . $password);
1260
            if (defined('CURLOPT_HTTPAUTH')) {
1261 45
                curl_setopt($curl, CURLOPT_HTTPAUTH, $authType);
1262
            } elseif ($authType != 1) {
1263 45
                /// @todo make this a proper error, i.e. return a failure
1264
                $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported by the current PHP/curl install');
1265 45
            }
1266
        }
1267 22
1268 22
        // note: h2c is http2 without the https. No need to have it in this IF
1269
        if ($method == 'https' || $method == 'h2') {
1270
            // set cert file
1271 22
            if ($cert) {
1272 22
                curl_setopt($curl, CURLOPT_SSLCERT, $cert);
1273
            }
1274
            // set cert password
1275
            if ($certPass) {
1276 22
                curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $certPass);
1277 22
            }
1278 22
            // whether to verify remote host's cert
1279 22
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verifypeer);
1280
            // set ca certificates file/dir
1281
            if ($caCert) {
1282 22
                curl_setopt($curl, CURLOPT_CAINFO, $caCert);
1283 22
            }
1284 22
            if ($caCertDir) {
1285
                curl_setopt($curl, CURLOPT_CAPATH, $caCertDir);
1286
            }
1287
            // set key file (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1288 22
            if ($key) {
1289 22
                curl_setopt($curl, CURLOPT_SSLKEY, $key);
1290 22
            }
1291
            // set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
1292 22
            if ($keyPass) {
1293 22
                curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $keyPass);
1294
            }
1295
            // whether to verify cert's common name (CN); 0 for no, 1 to verify that it exists, and 2 to verify that
1296 22
            // it matches the hostname used
1297 22
            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $this->verifyhost);
1298
            // allow usage of different SSL versions
1299
            curl_setopt($curl, CURLOPT_SSLVERSION, $sslVersion);
1300 22
        }
1301 22
1302
        // proxy info
1303
        if ($proxyHost) {
1304
            if ($proxyPort == 0) {
1305
                $proxyPort = 8080; // NB: even for HTTPS, local connection is on port 8080
1306
            }
1307 22
            curl_setopt($curl, CURLOPT_PROXY, $proxyHost . ':' . $proxyPort);
1308
            if ($proxyUsername) {
1309
                curl_setopt($curl, CURLOPT_PROXYUSERPWD, $proxyUsername . ':' . $proxyPassword);
1310
                if (defined('CURLOPT_PROXYAUTH')) {
1311 24
                    curl_setopt($curl, CURLOPT_PROXYAUTH, $proxyAuthType);
1312 24
                } elseif ($proxyAuthType != 1) {
1313
                    /// @todo make this a proper error, i.e. return a failure
1314
                    $this->getLogger()->errorLog('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported by the current PHP/curl install');
1315 24
                }
1316 24
            }
1317
        }
1318
1319
        // NB: should we build cookie http headers by hand rather than let CURL do it?
1320 24
        // the following code does not honour 'expires', 'path' and 'domain' cookie attributes set to client obj the the user...
1321 24
        if (count($this->cookies)) {
1322 24
            $cookieHeader = '';
1323 24
            foreach ($this->cookies as $name => $cookie) {
1324 24
                $cookieHeader .= $name . '=' . $cookie['value'] . '; ';
1325
            }
1326
            curl_setopt($curl, CURLOPT_COOKIE, substr($cookieHeader, 0, -2));
1327
        }
1328 24
1329 24
        foreach ($this->extracurlopts as $opt => $val) {
1330 22
            curl_setopt($curl, $opt, $val);
1331 22
        }
1332
1333 22
        if ($this->debug > 1) {
1334
            $this->getLogger()->debugMessage("---SENDING---\n$payload\n---END---");
1335
        }
1336 22
1337
        return $curl;
1338 22
    }
1339
1340
    /**
1341 22
     * Send an array of requests and return an array of responses.
1342 22
     *
1343
     * Unless $this->no_multicall has been set to true, it will try first to use one single xml-rpc call to server method
1344
     * system.multicall, and revert to sending many successive calls in case of failure.
1345
     * This failure is also stored in $this->no_multicall for subsequent calls.
1346
     * Unfortunately, there is no server error code universally used to denote the fact that multicall is unsupported,
1347
     * so there is no way to reliably distinguish between that and a temporary failure.
1348 24
     * If you are sure that server supports multicall and do not want to fallback to using many single calls, set the
1349
     * fourth parameter to FALSE.
1350
     *
1351
     * NB: trying to shoehorn extra functionality into existing syntax has resulted
1352
     * in pretty much convoluted code...
1353
     *
1354
     * @param Request[] $reqs an array of Request objects
1355
     * @param integer $timeout deprecated - connection timeout (in seconds). See the details in the docs for the send() method
1356
     * @param string $method deprecated - the http protocol variant to be used. See the details in the docs for the send() method
1357
     * @param boolean $fallback When true, upon receiving an error during multicall, multiple single calls will be
1358
     *                         attempted
1359
     * @return Response[]
1360
     */
1361
    public function multicall($reqs, $timeout = 0, $method = '', $fallback = true)
1362
    {
1363
        if ($method == '') {
1364
            $method = $this->method;
1365
        }
1366
1367
        if (!$this->no_multicall) {
1368
            $results = $this->_try_multicall($reqs, $timeout, $method);
1369
            /// @todo how to handle the case of $this->return_type = xml?
1370
            if (is_array($results)) {
1371
                // System.multicall succeeded
1372
                return $results;
1373
            } else {
1374
                // either system.multicall is unsupported by server, or the call failed for some other reason.
1375
                // Feature creep: is there a way to tell apart unsupported multicall from other faults?
1376
                if ($fallback) {
1377
                    // Don't try it next time...
1378
                    $this->no_multicall = true;
1379
                } else {
1380
                    $result = $results;
1381
                }
1382
            }
1383
        } else {
1384
            // override fallback, in case careless user tries to do two
1385
            // opposite things at the same time
1386
            $fallback = true;
1387
        }
1388
1389
        $results = array();
1390
        if ($fallback) {
1391
            // system.multicall is (probably) unsupported by server: emulate multicall via multiple requests
1392
            /// @todo use curl multi_ functions to make this quicker (see the implementation in the parallel.php demo)
1393
            foreach ($reqs as $req) {
1394
                $results[] = $this->send($req, $timeout, $method);
1395
            }
1396
        } else {
1397
            // user does NOT want to fallback on many single calls: since we should always return an array of responses,
1398
            // we return an array with the same error repeated n times
1399
            foreach ($reqs as $req) {
1400
                $results[] = $result;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $result does not seem to be defined for all execution paths leading up to this point.
Loading history...
1401
            }
1402
        }
1403
1404
        return $results;
1405
    }
1406
1407
    /**
1408
     * Attempt to boxcar $reqs via system.multicall.
1409
     *
1410
     * @param Request[] $reqs
1411
     * @param int $timeout
1412
     * @param string $method
1413
     * @return Response[]|Response a single Response when the call returned a fault / does not conform to what we expect
1414
     *                             from a multicall response
1415
     */
1416
    private function _try_multicall($reqs, $timeout, $method)
1417
    {
1418
        // Construct multicall request
1419
        $calls = array();
1420
        foreach ($reqs as $req) {
1421
            $call['methodName'] = new Value($req->method(), 'string');
1422
            $numParams = $req->getNumParams();
1423
            $params = array();
1424
            for ($i = 0; $i < $numParams; $i++) {
1425
                $params[$i] = $req->getParam($i);
1426
            }
1427
            $call['params'] = new Value($params, 'array');
1428
            $calls[] = new Value($call, 'struct');
1429
        }
1430
        $multiCall = new Request('system.multicall');
1431
        $multiCall->addParam(new Value($calls, 'array'));
1432
1433
        // Attempt RPC call
1434
        $result = $this->send($multiCall, $timeout, $method);
1435
1436
        if ($result->faultCode() != 0) {
1437
            // call to system.multicall failed
1438
            return $result;
1439
        }
1440
1441
        // Unpack responses.
1442
        $rets = $result->value();
1443
        $response = array();
1444
1445
        if ($this->return_type == 'xml') {
1446
            for ($i = 0; $i < count($reqs); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
1447
                $response[] = new Response($rets, 0, '', 'xml', $result->httpResponse());
1448
            }
1449
1450
        } elseif ($this->return_type == 'phpvals') {
1451
            if (!is_array($rets)) {
0 ignored issues
show
introduced by
The condition is_array($rets) is always false.
Loading history...
1452
                // bad return type from system.multicall
1453
                return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1454
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': not an array', 'phpvals', $result->httpResponse());
1455
            }
1456
            $numRets = count($rets);
1457
            if ($numRets != count($reqs)) {
1458
                // wrong number of return values.
1459
                return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1460
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': incorrect number of responses', 'phpvals',
1461
                    $result->httpResponse());
1462
            }
1463
1464
            for ($i = 0; $i < $numRets; $i++) {
1465
                $val = $rets[$i];
1466
                if (!is_array($val)) {
1467
                    return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1468
                        PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i is not an array or struct",
1469
                        'phpvals', $result->httpResponse());
1470
                }
1471
                switch (count($val)) {
1472
                    case 1:
1473
                        if (!isset($val[0])) {
1474
                            // Bad value
1475
                            return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1476
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has no value",
1477
                                'phpvals', $result->httpResponse());
1478
                        }
1479
                        // Normal return value
1480
                        $response[$i] = new Response($val[0], 0, '', 'phpvals', $result->httpResponse());
1481
                        break;
1482
                    case 2:
1483
                        /// @todo remove usage of @: it is apparently quite slow
1484
                        $code = @$val['faultCode'];
1485
                        if (!is_int($code)) {
1486
                            /// @todo should we check that it is != 0?
1487
                            return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1488
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
1489
                                'phpvals', $result->httpResponse());
1490
                        }
1491
                        $str = @$val['faultString'];
1492
                        if (!is_string($str)) {
1493
                            return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1494
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no FaultString",
1495
                                'phpvals', $result->httpResponse());
1496
                        }
1497
                        $response[$i] = new Response(0, $code, $str, 'phpvals', $result->httpResponse());
1498
                        break;
1499
                    default:
1500
                        return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1501
                            PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
1502
                            'phpvals', $result->httpResponse());
1503
                }
1504
            }
1505
1506
        } else {
1507
            // return type == 'xmlrpcvals'
1508
            if ($rets->kindOf() != 'array') {
0 ignored issues
show
Bug introduced by
The method kindOf() does not exist on integer. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1508
            if ($rets->/** @scrutinizer ignore-call */ kindOf() != 'array') {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1509
                return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1510
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i is not an array", 'xmlrpcvals',
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $i seems to be defined by a foreach iteration on line 1420. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
1511
                    $result->httpResponse());
1512
            }
1513
            $numRets = $rets->count();
1514
            if ($numRets != count($reqs)) {
1515
                // wrong number of return values.
1516
                return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1517
                    PhpXmlRpc::$xmlrpcstr['multicall_error'] . ': incorrect number of responses', 'xmlrpcvals',
1518
                    $result->httpResponse());
1519
            }
1520
1521
            foreach ($rets as $i => $val) {
0 ignored issues
show
Bug introduced by
The expression $rets of type integer is not traversable.
Loading history...
1522
                switch ($val->kindOf()) {
1523
                    case 'array':
1524
                        if ($val->count() != 1) {
1525
                            return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1526
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
1527
                                'phpvals', $result->httpResponse());
1528
                        }
1529
                        // Normal return value
1530
                        $response[] = new Response($val[0], 0, '', 'xmlrpcvals', $result->httpResponse());
1531
                        break;
1532
                    case 'struct':
1533
                        if ($val->count() != 2) {
1534
                            return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1535
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has too many items",
1536
                                'phpvals', $result->httpResponse());
1537
                        }
1538
                        /** @var Value $code */
1539
                        $code = $val['faultCode'];
1540
                        if ($code->kindOf() != 'scalar' || $code->scalartyp() != 'int') {
1541
                            return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1542
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
1543
                                'xmlrpcvals', $result->httpResponse());
1544
                        }
1545
                        /** @var Value $str */
1546
                        $str = $val['faultString'];
1547
                        if ($str->kindOf() != 'scalar' || $str->scalartyp() != 'string') {
1548
                            return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1549
                                PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i has invalid or no faultCode",
1550
                                'xmlrpcvals', $result->httpResponse());
1551
                        }
1552
                        $response[] = new Response(0, $code->scalarval(), $str->scalarval(), 'xmlrpcvals', $result->httpResponse());
1553
                        break;
1554
                    default:
1555
                        return new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'],
1556
                            PhpXmlRpc::$xmlrpcstr['multicall_error'] . ": response element $i is not an array or struct",
1557
                            'xmlrpcvals', $result->httpResponse());
1558
                }
1559
            }
1560
        }
1561
1562
        return $response;
1563
    }
1564
}
1565