Completed
Push — master ( 448067...f49780 )
by Gaetano
07:37 queued 01:54
created

Client::setUserAgent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
namespace PhpXmlRpc;
4
5
use PhpXmlRpc\Helper\Logger;
6
7
/**
8
 * The basic class used to represent a client of an XML-RPC server.
9
 */
10
class Client
11
{
12
    /// @todo: do these need to be public?
13
    public $method = 'http';
14
    public $server;
15
    public $port = 0;
16
    public $path;
17
18
    public $errno;
19
    public $errstr;
20
    public $debug = 0;
21
22
    public $username = '';
23
    public $password = '';
24
    public $authtype = 1;
25
26
    public $cert = '';
27
    public $certpass = '';
28
    public $cacert = '';
29
    public $cacertdir = '';
30
    public $key = '';
31
    public $keypass = '';
32
    public $verifypeer = true;
33
    public $verifyhost = 2;
34
    public $sslversion = 0; // corresponds to CURL_SSLVERSION_DEFAULT
35
36
    public $proxy = '';
37
    public $proxyport = 0;
38
    public $proxy_user = '';
39
    public $proxy_pass = '';
40
    public $proxy_authtype = 1;
41
42
    public $cookies = array();
43
    public $extracurlopts = array();
44
45
    /**
46
     * @var bool
47
     *
48
     * This determines whether the multicall() method will try to take advantage of the system.multicall xmlrpc method
49
     * to dispatch to the server an array of requests in a single http roundtrip or simply execute many consecutive http
50
     * calls. Defaults to FALSE, but it will be enabled automatically on the first failure of execution of
51
     * system.multicall.
52
     */
53
    public $no_multicall = false;
54
55
    /**
56
     * List of http compression methods accepted by the client for responses.
57
     * NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib.
58
     *
59
     * NNB: you can set it to any non-empty array for HTTP11 and HTTPS, since
60
     * in those cases it will be up to CURL to decide the compression methods
61
     * it supports. You might check for the presence of 'zlib' in the output of
62
     * curl_version() to determine wheter compression is supported or not
63
     */
64
    public $accepted_compression = array();
65
66
    /**
67
     * Name of compression scheme to be used for sending requests.
68
     * Either null, gzip or deflate.
69
     */
70
71
    public $request_compression = '';
72
    /**
73
     * CURL handle: used for keep-alive connections (PHP 4.3.8 up, see:
74
     * http://curl.haxx.se/docs/faq.html#7.3).
75
     */
76
    public $xmlrpc_curl_handle = null;
77
78
    /// Whether to use persistent connections for http 1.1 and https
79
    public $keepalive = false;
80
81
    /// Charset encodings that can be decoded without problems by the client
82
    public $accepted_charset_encodings = array();
83
84
    /**
85
     * The charset encoding that will be used for serializing request sent by the client.
86
     *  If defaults to NULL, which means using US-ASCII and encoding all characters outside of the ASCII range using
87
     * their xml character entity representation (this has the benefit that line end characters will not be mangled in
88
     * the transfer, a CR-LF will be preserved as well as a singe LF).
89
     *  Valid values are 'US-ASCII', 'UTF-8' and 'ISO-8859-1'
90
     */
91
    public $request_charset_encoding = '';
92
93
    /**
94
     * Decides the content of Response objects returned by calls to send() and multicall().
95
     * Valid values are 'xmlrpcvals', 'phpvals' or 'xml'.
96
     *
97
     * Determines whether the value returned inside an Response object as results of calls to the send() and multicall()
98
     * methods will be a Value object, a plain php value or a raw xml string.
99
     * Allowed values are 'xmlrpcvals' (the default), 'phpvals' and 'xml'.
100
     * To allow the user to differentiate between a correct and a faulty response, fault responses will be returned as
101
     * Response objects in any case.
102
     * Note that the 'phpvals' setting will yield faster execution times, but some of the information from the original
103
     * response will be lost. It will be e.g. impossible to tell whether a particular php string value was sent by the
104
     * server as an xmlrpc string or base64 value.
105
     */
106
    public $return_type = 'xmlrpcvals';
107
108
    /**
109
     * Sent to servers in http headers.
110
     */
111
    public $user_agent;
112
113
    /**
114
     * @param string $path either the PATH part of the xmlrpc server URL, or complete server URL (in which case you
115
     *                     should use and empty string for all other parameters)
116
     *                     e.g. /xmlrpc/server.php
117
     *                     e.g. http://phpxmlrpc.sourceforge.net/server.php
118
     *                     e.g. https://james:[email protected]:443/xmlrpcserver?agent=007
119
     * @param string $server the server name / ip address
120
     * @param integer $port the port the server is listening on, when omitted defaults to 80 or 443 depending on
121
     *                      protocol used
122
     * @param string $method the http protocol variant: defaults to 'http'; 'https' and 'http11' can be used if CURL is
123
     *                       installed. The value set here can be overridden in any call to $this->send().
124
     */
125
    public function __construct($path, $server = '', $port = '', $method = '')
126
    {
127
        // allow user to specify all params in $path
128
        if ($server == '' and $port == '' and $method == '') {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
129
            $parts = parse_url($path);
130
            $server = $parts['host'];
131
            $path = isset($parts['path']) ? $parts['path'] : '';
132
            if (isset($parts['query'])) {
133
                $path .= '?' . $parts['query'];
134
            }
135
            if (isset($parts['fragment'])) {
136
                $path .= '#' . $parts['fragment'];
137
            }
138
            if (isset($parts['port'])) {
139
                $port = $parts['port'];
140
            }
141
            if (isset($parts['scheme'])) {
142
                $method = $parts['scheme'];
143
            }
144
            if (isset($parts['user'])) {
145
                $this->username = $parts['user'];
146
            }
147
            if (isset($parts['pass'])) {
148
                $this->password = $parts['pass'];
149
            }
150
        }
151
        if ($path == '' || $path[0] != '/') {
152
            $this->path = '/' . $path;
153
        } else {
154
            $this->path = $path;
155
        }
156
        $this->server = $server;
157
        if ($port != '') {
158
            $this->port = $port;
159
        }
160
        if ($method != '') {
161
            $this->method = $method;
162
        }
163
164
        // if ZLIB is enabled, let the client by default accept compressed responses
165
        if (function_exists('gzinflate') || (
166
                function_exists('curl_init') && (($info = curl_version()) &&
167
                    ((is_string($info) && strpos($info, 'zlib') !== null) || isset($info['libz_version'])))
168
            )
169
        ) {
170
            $this->accepted_compression = array('gzip', 'deflate');
171
        }
172
173
        // keepalives: enabled by default
174
        $this->keepalive = true;
175
176
        // by default the xml parser can support these 3 charset encodings
177
        $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII');
178
179
        // Add all charsets which mbstring can handle, but remove junk not found in IANA registry at
180
        // in http://www.iana.org/assignments/character-sets/character-sets.xhtml
181
        // NB: this is disabled to avoid making all the requests sent huge... mbstring supports more than 80 charsets!
182
        /*if (function_exists('mb_list_encodings')) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
63% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
183
184
            $encodings = array_diff(mb_list_encodings(), array('pass', 'auto', 'wchar', 'BASE64', 'UUENCODE', 'ASCII',
185
                'HTML-ENTITIES', 'Quoted-Printable', '7bit','8bit', 'byte2be', 'byte2le', 'byte4be', 'byte4le'));
186
            $this->accepted_charset_encodings = array_unique(array_merge($this->accepted_charset_encodings, $encodings));
187
        }*/
188
189
        // initialize user_agent string
190
        $this->user_agent = PhpXmlRpc::$xmlrpcName . ' ' . PhpXmlRpc::$xmlrpcVersion;
191
    }
192
193
    /**
194
     * Enable/disable the echoing to screen of the xmlrpc responses received. The default is not no output anything.
195
     *
196
     * The debugging information at level 1 includes the raw data returned from the XML-RPC server it was querying
197
     * (including bot HTTP headers and the full XML payload), and the PHP value the client attempts to create to
198
     * represent the value returned by the server
199
     * At level2, the complete payload of the xmlrpc request is also printed, before being sent t the server.
200
     *
201
     * This option can be very useful when debugging servers as it allows you to see exactly what the client sends and
202
     * the server returns.
203
     *
204
     * @param integer $in values 0, 1 and 2 are supported (2 = echo sent msg too, before received response)
0 ignored issues
show
Bug introduced by
There is no parameter named $in. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
205
     */
206
    public function setDebug($level)
207
    {
208
        $this->debug = $level;
209
    }
210
211
    /**
212
     * Sets the username and password for authorizing the client to the server.
213
     *
214
     * With the default (HTTP) transport, this information is used for HTTP Basic authorization.
215
     * Note that username and password can also be set using the class constructor.
216
     * With HTTP 1.1 and HTTPS transport, NTLM and Digest authentication protocols are also supported. To enable them use
217
     * the constants CURLAUTH_DIGEST and CURLAUTH_NTLM as values for the auth type parameter.
218
     *
219
     * @param string $user username
220
     * @param string $password password
221
     * @param integer $authType auth type. See curl_setopt man page for supported auth types. Defaults to CURLAUTH_BASIC
222
     *                          (basic auth). Note that auth types NTLM and Digest will only work if the Curl php
223
     *                          extension is enabled.
224
     */
225
    public function setCredentials($user, $password, $authType = 1)
226
    {
227
        $this->username = $user;
228
        $this->password = $password;
229
        $this->authtype = $authType;
230
    }
231
232
    /**
233
     * Set the optional certificate and passphrase used in SSL-enabled communication with a remote server.
234
     *
235
     * Note: to retrieve information about the client certificate on the server side, you will need to look into the
236
     * environment variables which are set up by the webserver. Different webservers will typically set up different
237
     * variables.
238
     *
239
     * @param string $cert the name of a file containing a PEM formatted certificate
240
     * @param string $certPass the password required to use it
241
     */
242
    public function setCertificate($cert, $certPass = '')
243
    {
244
        $this->cert = $cert;
245
        $this->certpass = $certPass;
246
    }
247
248
    /**
249
     * Add a CA certificate to verify server with in SSL-enabled communication when SetSSLVerifypeer has been set to TRUE.
250
     *
251
     * See the php manual page about CURLOPT_CAINFO for more details.
252
     *
253
     * @param string $caCert certificate file name (or dir holding certificates)
254
     * @param bool $isDir set to true to indicate cacert is a dir. defaults to false
255
     */
256
    public function setCaCertificate($caCert, $isDir = false)
257
    {
258
        if ($isDir) {
259
            $this->cacertdir = $caCert;
260
        } else {
261
            $this->cacert = $caCert;
262
        }
263
    }
264
265
    /**
266
     * Set attributes for SSL communication: private SSL key.
267
     *
268
     * NB: does not work in older php/curl installs.
269
     * Thanks to Daniel Convissor.
270
     *
271
     * @param string $key The name of a file containing a private SSL key
272
     * @param string $keyPass The secret password needed to use the private SSL key
273
     */
274
    public function setKey($key, $keyPass)
275
    {
276
        $this->key = $key;
277
        $this->keypass = $keyPass;
278
    }
279
280
    /**
281
     * Set attributes for SSL communication: verify the remote host's SSL certificate, and cause the connection to fail
282
     * if the cert verification fails.
283
     *
284
     * By default, verification is enabled.
285
     * To specify custom SSL certificates to validate the server with, use the setCaCertificate method.
286
     *
287
     * @param bool $i enable/disable verification of peer certificate
288
     */
289
    public function setSSLVerifyPeer($i)
290
    {
291
        $this->verifypeer = $i;
292
    }
293
294
    /**
295
     * Set attributes for SSL communication: verify the remote host's SSL certificate's common name (CN).
296
     *
297
     * Note that support for value 1 has been removed in cURL 7.28.1
298
     *
299
     * @param int $i Set to 1 to only the existence of a CN, not that it matches
300
     */
301
    public function setSSLVerifyHost($i)
302
    {
303
        $this->verifyhost = $i;
304
    }
305
306
    /**
307
     * Set attributes for SSL communication: SSL version to use. Best left at 0 (default value ): let cURL decide
308
     *
309
     * @param int $i
310
     */
311
    public function setSSLVersion($i)
312
    {
313
        $this->sslversion = $i;
314
    }
315
316
    /**
317
     * Set proxy info.
318
     *
319
     * NB: CURL versions before 7.11.10 cannot use a proxy to communicate with https servers.
320
     *
321
     * @param string $proxyHost
322
     * @param string $proxyPort Defaults to 8080 for HTTP and 443 for HTTPS
323
     * @param string $proxyUsername Leave blank if proxy has public access
324
     * @param string $proxyPassword Leave blank if proxy has public access
325
     * @param int $proxyAuthType defaults to CURLAUTH_BASIC (Basic authentication protocol); set to constant CURLAUTH_NTLM
326
     *                           to use NTLM auth with proxy (has effect only when the client uses the HTTP 1.1 protocol)
327
     */
328
    public function setProxy($proxyHost, $proxyPort, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1)
329
    {
330
        $this->proxy = $proxyHost;
331
        $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...
332
        $this->proxy_user = $proxyUsername;
333
        $this->proxy_pass = $proxyPassword;
334
        $this->proxy_authtype = $proxyAuthType;
335
    }
336
337
    /**
338
     * Enables/disables reception of compressed xmlrpc responses.
339
     *
340
     * This requires the "zlib" extension to be enabled in your php install. If it is, by default xmlrpc_client
341
     * instances will enable reception of compressed content.
342
     * Note that enabling reception of compressed responses merely adds some standard http headers to xmlrpc requests.
343
     * It is up to the xmlrpc server to return compressed responses when receiving such requests.
344
     *
345
     * @param string $compMethod either 'gzip', 'deflate', 'any' or ''
346
     */
347
    public function setAcceptedCompression($compMethod)
348
    {
349
        if ($compMethod == 'any') {
350
            $this->accepted_compression = array('gzip', 'deflate');
351
        } 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...
352
            $this->accepted_compression = array();
353
        } else {
354
            $this->accepted_compression = array($compMethod);
355
        }
356
    }
357
358
    /**
359
     * Enables/disables http compression of xmlrpc request.
360
     *
361
     * This requires the "zlib" extension to be enabled in your php install.
362
     * Take care when sending compressed requests: servers might not support them (and automatic fallback to
363
     * uncompressed requests is not yet implemented).
364
     *
365
     * @param string $compMethod either 'gzip', 'deflate' or ''
366
     */
367
    public function setRequestCompression($compMethod)
368
    {
369
        $this->request_compression = $compMethod;
370
    }
371
372
    /**
373
     * Adds a cookie to list of cookies that will be sent to server with every further request (useful e.g. for keeping
374
     * session info outside of the xml-rpc payload).
375
     *
376
     * NB: By default cookies are sent using the 'original/netscape' format, which is also the same as the RFC 2965;
377
     * setting any param but name and value will turn the cookie into a 'version 1' cookie (i.e. RFC 2109 cookie) that
378
     * might not be fully supported by the server. Note that RFC 2109 has currently 'historic' status...
379
     *
380
     * @param string $name nb: will not be escaped in the request's http headers. Take care not to use CTL chars or
381
     *                     separators!
382
     * @param string $value
383
     * @param string $path leave this empty unless the xml-rpc server only accepts RFC 2109 cookies
384
     * @param string $domain leave this empty unless the xml-rpc server only accepts RFC 2109 cookies
385
     * @param int $port leave this empty unless the xml-rpc server only accepts RFC 2109 cookies
386
     *
387
     * @todo check correctness of urlencoding cookie value (copied from php way of doing it, but php is generally sending
388
     *       response not requests. We do the opposite...)
389
     * @todo strip invalid chars from cookie name? As per RFC6265, we should follow RFC2616, Section 2.2
390
     */
391
    public function setCookie($name, $value = '', $path = '', $domain = '', $port = null)
392
    {
393
        $this->cookies[$name]['value'] = urlencode($value);
394
        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 zero. 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...
395
            $this->cookies[$name]['path'] = $path;
396
            $this->cookies[$name]['domain'] = $domain;
397
            $this->cookies[$name]['port'] = $port;
398
            $this->cookies[$name]['version'] = 1;
399
        } else {
400
            $this->cookies[$name]['version'] = 0;
401
        }
402
    }
403
404
    /**
405
     * Directly set cURL options, for extra flexibility (when in cURL mode).
406
     *
407
     * It allows eg. to bind client to a specific IP interface / address.
408
     *
409
     * @param array $options
410
     */
411
    public function setCurlOptions($options)
412
    {
413
        $this->extracurlopts = $options;
414
    }
415
416
    /**
417
     * Set user-agent string that will be used by this client instance in http headers sent to the server.
418
     *
419
     * The default user agent string includes the name of this library and the version number.
420
     *
421
     * @param string $agentString
422
     */
423
    public function setUserAgent($agentString)
424
    {
425
        $this->user_agent = $agentString;
426
    }
427
428
    /**
429
     * Send an xmlrpc request to the server.
430
     *
431
     * @param Request|Request[]|string $req The Request object, or an array of requests for using multicall, or the
432
     *                                      complete xml representation of a request.
433
     *                                      When sending an array of Request objects, the client will try to make use of
434
     *                                      a single 'system.multicall' xml-rpc method call to forward to the server all
435
     *                                      the requests in a single HTTP round trip, unless $this->no_multicall has
436
     *                                      been previously set to TRUE (see the multicall method below), in which case
437
     *                                      many consecutive xmlrpc requests will be sent. The method will return an
438
     *                                      array of Response objects in both cases.
439
     *                                      The third variant allows to build by hand (or any other means) a complete
440
     *                                      xmlrpc request message, and send it to the server. $req should be a string
441
     *                                      containing the complete xml representation of the request. It is e.g. useful
442
     *                                      when, for maximal speed of execution, the request is serialized into a
443
     *                                      string using the native php xmlrpc functions (see http://www.php.net/xmlrpc)
444
     * @param integer $timeout Connection timeout, in seconds, If unspecified, a platform specific timeout will apply.
445
     *                         This timeout value is passed to fsockopen(). It is also used for detecting server
446
     *                         timeouts during communication (i.e. if the server does not send anything to the client
447
     *                         for $timeout seconds, the connection will be closed).
448
     * @param string $method valid values are 'http', 'http11' and 'https'. If left unspecified, the http protocol
449
     *                       chosen during creation of the object will be used.
450
     *
451
     *
452
     * @return Response|Response[] Note that the client will always return a Response object, even if the call fails
453
     */
454
    public function send($req, $timeout = 0, $method = '')
455
    {
456
        // if user does not specify http protocol, use native method of this client
457
        // (i.e. method set during call to constructor)
458
        if ($method == '') {
459
            $method = $this->method;
460
        }
461
462
        if (is_array($req)) {
463
            // $req is an array of Requests
464
            $r = $this->multicall($req, $timeout, $method);
465
466
            return $r;
467
        } elseif (is_string($req)) {
468
            $n = new Request('');
469
            $n->payload = $req;
470
            $req = $n;
471
        }
472
473
        // where req is a Request
474
        $req->setDebug($this->debug);
475
476
        if ($method == 'https') {
477
            $r = $this->sendPayloadHTTPS(
478
                $req,
479
                $this->server,
480
                $this->port,
481
                $timeout,
482
                $this->username,
483
                $this->password,
484
                $this->authtype,
485
                $this->cert,
486
                $this->certpass,
487
                $this->cacert,
488
                $this->cacertdir,
489
                $this->proxy,
490
                $this->proxyport,
491
                $this->proxy_user,
492
                $this->proxy_pass,
493
                $this->proxy_authtype,
494
                $this->keepalive,
495
                $this->key,
496
                $this->keypass,
497
                $this->sslversion
498
            );
499
        } elseif ($method == 'http11') {
500
            $r = $this->sendPayloadCURL(
501
                $req,
502
                $this->server,
503
                $this->port,
504
                $timeout,
505
                $this->username,
506
                $this->password,
507
                $this->authtype,
508
                null,
509
                null,
510
                null,
511
                null,
512
                $this->proxy,
513
                $this->proxyport,
514
                $this->proxy_user,
515
                $this->proxy_pass,
516
                $this->proxy_authtype,
517
                'http',
518
                $this->keepalive
519
            );
520
        } else {
521
            $r = $this->sendPayloadHTTP10(
522
                $req,
523
                $this->server,
524
                $this->port,
525
                $timeout,
526
                $this->username,
527
                $this->password,
528
                $this->authtype,
529
                $this->proxy,
530
                $this->proxyport,
531
                $this->proxy_user,
532
                $this->proxy_pass,
533
                $this->proxy_authtype,
534
                $method
535
            );
536
        }
537
538
        return $r;
539
    }
540
541
    /**
542
     * @param Request $req
543
     * @param string $server
544
     * @param int $port
545
     * @param int $timeout
546
     * @param string $username
547
     * @param string $password
548
     * @param int $authType
549
     * @param string $proxyHost
550
     * @param int $proxyPort
551
     * @param string $proxyUsername
552
     * @param string $proxyPassword
553
     * @param int $proxyAuthType
554
     * @param string $method
555
     * @return Response
556
     */
557
    protected function sendPayloadHTTP10($req, $server, $port, $timeout = 0, $username = '', $password = '',
558
        $authType = 1, $proxyHost = '', $proxyPort = 0, $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1,
559
        $method='http')
560
    {
561
        if ($port == 0) {
562
            $port = ( $method === "https" ) ? 443 : 80;
563
        }
564
565
        // Only create the payload if it was not created previously
566
        if (empty($req->payload)) {
567
            $req->createPayload($this->request_charset_encoding);
568
        }
569
570
        $payload = $req->payload;
571
        // Deflate request body and set appropriate request headers
572 View Code Duplication
        if (function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
573
            if ($this->request_compression == 'gzip') {
574
                $a = @gzencode($payload);
575
                if ($a) {
576
                    $payload = $a;
577
                    $encodingHdr = "Content-Encoding: gzip\r\n";
578
                }
579
            } else {
580
                $a = @gzcompress($payload);
581
                if ($a) {
582
                    $payload = $a;
583
                    $encodingHdr = "Content-Encoding: deflate\r\n";
584
                }
585
            }
586
        } else {
587
            $encodingHdr = '';
588
        }
589
590
        // thanks to Grant Rauscher <[email protected]> for this
591
        $credentials = '';
592
        if ($username != '') {
593
            $credentials = 'Authorization: Basic ' . base64_encode($username . ':' . $password) . "\r\n";
594
            if ($authType != 1) {
595
                error_log('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported with HTTP 1.0');
596
            }
597
        }
598
599
        $acceptedEncoding = '';
600
        if (is_array($this->accepted_compression) && count($this->accepted_compression)) {
601
            $acceptedEncoding = 'Accept-Encoding: ' . implode(', ', $this->accepted_compression) . "\r\n";
602
        }
603
604
        $proxyCredentials = '';
605
        if ($proxyHost) {
606
            if ($proxyPort == 0) {
607
                $proxyPort = 8080;
608
            }
609
            $connectServer = $proxyHost;
610
            $connectPort = $proxyPort;
611
            $transport = "tcp";
612
            $uri = 'http://' . $server . ':' . $port . $this->path;
613
            if ($proxyUsername != '') {
614
                if ($proxyAuthType != 1) {
615
                    error_log('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported with HTTP 1.0');
616
                }
617
                $proxyCredentials = 'Proxy-Authorization: Basic ' . base64_encode($proxyUsername . ':' . $proxyPassword) . "\r\n";
618
            }
619
        } else {
620
            $connectServer = $server;
621
            $connectPort = $port;
622
            /// @todo if supporting https, we should support all its current options as well: peer name verification etc...
623
            $transport = ( $method === "https" ) ? "tls" : "tcp";
624
            $uri = $this->path;
625
        }
626
627
        // Cookie generation, as per rfc2965 (version 1 cookies) or
628
        // netscape's rules (version 0 cookies)
629
        $cookieHeader = '';
630
        if (count($this->cookies)) {
631
            $version = '';
632
            foreach ($this->cookies as $name => $cookie) {
633
                if ($cookie['version']) {
634
                    $version = ' $Version="' . $cookie['version'] . '";';
635
                    $cookieHeader .= ' ' . $name . '="' . $cookie['value'] . '";';
636
                    if ($cookie['path']) {
637
                        $cookieHeader .= ' $Path="' . $cookie['path'] . '";';
638
                    }
639
                    if ($cookie['domain']) {
640
                        $cookieHeader .= ' $Domain="' . $cookie['domain'] . '";';
641
                    }
642
                    if ($cookie['port']) {
643
                        $cookieHeader .= ' $Port="' . $cookie['port'] . '";';
644
                    }
645
                } else {
646
                    $cookieHeader .= ' ' . $name . '=' . $cookie['value'] . ";";
647
                }
648
            }
649
            $cookieHeader = 'Cookie:' . $version . substr($cookieHeader, 0, -1) . "\r\n";
650
        }
651
652
        // omit port if 80
653
        $port = ($port == 80) ? '' : (':' . $port);
654
655
        $op = 'POST ' . $uri . " HTTP/1.0\r\n" .
656
            'User-Agent: ' . $this->user_agent . "\r\n" .
657
            'Host: ' . $server . $port . "\r\n" .
658
            $credentials .
659
            $proxyCredentials .
660
            $acceptedEncoding .
661
            $encodingHdr .
0 ignored issues
show
Bug introduced by
The variable $encodingHdr does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
662
            'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings) . "\r\n" .
663
            $cookieHeader .
664
            'Content-Type: ' . $req->content_type . "\r\nContent-Length: " .
665
            strlen($payload) . "\r\n\r\n" .
666
            $payload;
667
668
        if ($this->debug > 1) {
669
            Logger::instance()->debugMessage("---SENDING---\n$op\n---END---");
670
        }
671
672
        if ($timeout > 0) {
673
            $fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, $this->errstr, $timeout);
674
        } else {
675
            $fp = @stream_socket_client("$transport://$connectServer:$connectPort", $this->errno, $this->errstr);
676
        }
677
        if ($fp) {
678
            if ($timeout > 0) {
679
                stream_set_timeout($fp, $timeout);
680
            }
681
        } else {
682
            $this->errstr = 'Connect error: ' . $this->errstr;
683
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr . ' (' . $this->errno . ')');
684
685
            return $r;
686
        }
687
688
        if (!fputs($fp, $op, strlen($op))) {
689
            fclose($fp);
690
            $this->errstr = 'Write error';
691
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['http_error'], $this->errstr);
692
693
            return $r;
694
        } else {
695
            // reset errno and errstr on successful socket connection
696
            $this->errstr = '';
697
        }
698
        // G. Giunta 2005/10/24: close socket before parsing.
699
        // should yield slightly better execution times, and make easier recursive calls (e.g. to follow http redirects)
700
        $ipd = '';
701
        do {
702
            // shall we check for $data === FALSE?
703
            // as per the manual, it signals an error
704
            $ipd .= fread($fp, 32768);
705
        } while (!feof($fp));
706
        fclose($fp);
707
        $r = $req->parseResponse($ipd, false, $this->return_type);
708
709
        return $r;
710
    }
711
712
    /**
713
     * @param Request $req
714
     * @param string $server
715
     * @param int $port
716
     * @param int $timeout
717
     * @param string $username
718
     * @param string $password
719
     * @param int $authType
720
     * @param string $cert
721
     * @param string $certPass
722
     * @param string $caCert
723
     * @param string $caCertDir
724
     * @param string $proxyHost
725
     * @param int $proxyPort
726
     * @param string $proxyUsername
727
     * @param string $proxyPassword
728
     * @param int $proxyAuthType
729
     * @param bool $keepAlive
730
     * @param string $key
731
     * @param string $keyPass
732
     * @param int $sslVersion
733
     * @return Response
734
     */
735
    protected function sendPayloadHTTPS($req, $server, $port, $timeout = 0, $username = '',  $password = '',
736
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
737
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $keepAlive = false, $key = '', $keyPass = '',
738
        $sslVersion = 0)
739
    {
740
        return $this->sendPayloadCURL($req, $server, $port, $timeout, $username,
741
            $password, $authType, $cert, $certPass, $caCert, $caCertDir, $proxyHost, $proxyPort,
742
            $proxyUsername, $proxyPassword, $proxyAuthType, 'https', $keepAlive, $key, $keyPass, $sslVersion);
743
    }
744
745
    /**
746
     * Contributed by Justin Miller <[email protected]>
747
     * Requires curl to be built into PHP
748
     * NB: CURL versions before 7.11.10 cannot use proxy to talk to https servers!
749
     *
750
     * @param Request $req
751
     * @param string $server
752
     * @param int $port
753
     * @param int $timeout
754
     * @param string $username
755
     * @param string $password
756
     * @param int $authType
757
     * @param string $cert
758
     * @param string $certPass
759
     * @param string $caCert
760
     * @param string $caCertDir
761
     * @param string $proxyHost
762
     * @param int $proxyPort
763
     * @param string $proxyUsername
764
     * @param string $proxyPassword
765
     * @param int $proxyAuthType
766
     * @param string $method
767
     * @param bool $keepAlive
768
     * @param string $key
769
     * @param string $keyPass
770
     * @param int $sslVersion
771
     * @return Response
772
     */
773
    protected function sendPayloadCURL($req, $server, $port, $timeout = 0, $username = '', $password = '',
774
        $authType = 1, $cert = '', $certPass = '', $caCert = '', $caCertDir = '', $proxyHost = '', $proxyPort = 0,
775
        $proxyUsername = '', $proxyPassword = '', $proxyAuthType = 1, $method = 'https', $keepAlive = false, $key = '',
776
        $keyPass = '', $sslVersion = 0)
777
    {
778
        if (!function_exists('curl_init')) {
779
            $this->errstr = 'CURL unavailable on this install';
780
            return new Response(0, PhpXmlRpc::$xmlrpcerr['no_curl'], PhpXmlRpc::$xmlrpcstr['no_curl']);
781
        }
782
        if ($method == 'https') {
783
            if (($info = curl_version()) &&
784
                ((is_string($info) && strpos($info, 'OpenSSL') === null) || (is_array($info) && !isset($info['ssl_version'])))
785
            ) {
786
                $this->errstr = 'SSL unavailable on this install';
787
                return new Response(0, PhpXmlRpc::$xmlrpcerr['no_ssl'], PhpXmlRpc::$xmlrpcstr['no_ssl']);
788
            }
789
        }
790
791
        if ($port == 0) {
792
            if ($method == 'http') {
793
                $port = 80;
794
            } else {
795
                $port = 443;
796
            }
797
        }
798
799
        // Only create the payload if it was not created previously
800
        if (empty($req->payload)) {
801
            $req->createPayload($this->request_charset_encoding);
802
        }
803
804
        // Deflate request body and set appropriate request headers
805
        $payload = $req->payload;
806 View Code Duplication
        if (function_exists('gzdeflate') && ($this->request_compression == 'gzip' || $this->request_compression == 'deflate')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
807
            if ($this->request_compression == 'gzip') {
808
                $a = @gzencode($payload);
809
                if ($a) {
810
                    $payload = $a;
811
                    $encodingHdr = 'Content-Encoding: gzip';
812
                }
813
            } else {
814
                $a = @gzcompress($payload);
815
                if ($a) {
816
                    $payload = $a;
817
                    $encodingHdr = 'Content-Encoding: deflate';
818
                }
819
            }
820
        } else {
821
            $encodingHdr = '';
822
        }
823
824
        if ($this->debug > 1) {
825
            Logger::instance()->debugMessage("---SENDING---\n$payload\n---END---");
826
        }
827
828
        if (!$keepAlive || !$this->xmlrpc_curl_handle) {
829
            $curl = curl_init($method . '://' . $server . ':' . $port . $this->path);
830
            if ($keepAlive) {
831
                $this->xmlrpc_curl_handle = $curl;
832
            }
833
        } else {
834
            $curl = $this->xmlrpc_curl_handle;
835
        }
836
837
        // results into variable
838
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
839
840
        if ($this->debug > 1) {
841
            curl_setopt($curl, CURLOPT_VERBOSE, true);
842
            /// @todo allow callers to redirect curlopt_stderr to some stream which can be buffered
843
        }
844
        curl_setopt($curl, CURLOPT_USERAGENT, $this->user_agent);
845
        // required for XMLRPC: post the data
846
        curl_setopt($curl, CURLOPT_POST, 1);
847
        // the data
848
        curl_setopt($curl, CURLOPT_POSTFIELDS, $payload);
849
850
        // return the header too
851
        curl_setopt($curl, CURLOPT_HEADER, 1);
852
853
        // NB: if we set an empty string, CURL will add http header indicating
854
        // ALL methods it is supporting. This is possibly a better option than
855
        // letting the user tell what curl can / cannot do...
856
        if (is_array($this->accepted_compression) && count($this->accepted_compression)) {
857
            //curl_setopt($curl, CURLOPT_ENCODING, implode(',', $this->accepted_compression));
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
858
            // empty string means 'any supported by CURL' (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
859
            if (count($this->accepted_compression) == 1) {
860
                curl_setopt($curl, CURLOPT_ENCODING, $this->accepted_compression[0]);
861
            } else {
862
                curl_setopt($curl, CURLOPT_ENCODING, '');
863
            }
864
        }
865
        // extra headers
866
        $headers = array('Content-Type: ' . $req->content_type, 'Accept-Charset: ' . implode(',', $this->accepted_charset_encodings));
867
        // if no keepalive is wanted, let the server know it in advance
868
        if (!$keepAlive) {
869
            $headers[] = 'Connection: close';
870
        }
871
        // request compression header
872
        if ($encodingHdr) {
873
            $headers[] = $encodingHdr;
0 ignored issues
show
Bug introduced by
The variable $encodingHdr does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
874
        }
875
876
        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
877
        // timeout is borked
878
        if ($timeout) {
879
            curl_setopt($curl, CURLOPT_TIMEOUT, $timeout == 1 ? 1 : $timeout - 1);
880
        }
881
882 View Code Duplication
        if ($username && $password) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
883
            curl_setopt($curl, CURLOPT_USERPWD, $username . ':' . $password);
884
            if (defined('CURLOPT_HTTPAUTH')) {
885
                curl_setopt($curl, CURLOPT_HTTPAUTH, $authType);
886
            } elseif ($authType != 1) {
887
                error_log('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth is supported by the current PHP/curl install');
888
            }
889
        }
890
891
        if ($method == 'https') {
892
            // set cert file
893
            if ($cert) {
894
                curl_setopt($curl, CURLOPT_SSLCERT, $cert);
895
            }
896
            // set cert password
897
            if ($certPass) {
898
                curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $certPass);
899
            }
900
            // whether to verify remote host's cert
901
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verifypeer);
902
            // set ca certificates file/dir
903
            if ($caCert) {
904
                curl_setopt($curl, CURLOPT_CAINFO, $caCert);
905
            }
906
            if ($caCertDir) {
907
                curl_setopt($curl, CURLOPT_CAPATH, $caCertDir);
908
            }
909
            // set key file (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
910
            if ($key) {
911
                curl_setopt($curl, CURLOPT_SSLKEY, $key);
912
            }
913
            // set key password (shall we catch errors in case CURLOPT_SSLKEY undefined ?)
914
            if ($keyPass) {
915
                curl_setopt($curl, CURLOPT_SSLKEYPASSWD, $keyPass);
916
            }
917
            // whether to verify cert's common name (CN); 0 for no, 1 to verify that it exists, and 2 to verify that it matches the hostname used
918
            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, $this->verifyhost);
919
            // allow usage of different SSL versions
920
            curl_setopt($curl, CURLOPT_SSLVERSION, $sslVersion);
921
        }
922
923
        // proxy info
924
        if ($proxyHost) {
925
            if ($proxyPort == 0) {
926
                $proxyPort = 8080; // NB: even for HTTPS, local connection is on port 8080
927
            }
928
            curl_setopt($curl, CURLOPT_PROXY, $proxyHost . ':' . $proxyPort);
929 View Code Duplication
            if ($proxyUsername) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
930
                curl_setopt($curl, CURLOPT_PROXYUSERPWD, $proxyUsername . ':' . $proxyPassword);
931
                if (defined('CURLOPT_PROXYAUTH')) {
932
                    curl_setopt($curl, CURLOPT_PROXYAUTH, $proxyAuthType);
933
                } elseif ($proxyAuthType != 1) {
934
                    error_log('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported by the current PHP/curl install');
935
                }
936
            }
937
        }
938
939
        // NB: should we build cookie http headers by hand rather than let CURL do it?
940
        // the following code does not honour 'expires', 'path' and 'domain' cookie attributes
941
        // set to client obj the the user...
942
        if (count($this->cookies)) {
943
            $cookieHeader = '';
944
            foreach ($this->cookies as $name => $cookie) {
945
                $cookieHeader .= $name . '=' . $cookie['value'] . '; ';
946
            }
947
            curl_setopt($curl, CURLOPT_COOKIE, substr($cookieHeader, 0, -2));
948
        }
949
950
        foreach ($this->extracurlopts as $opt => $val) {
951
            curl_setopt($curl, $opt, $val);
952
        }
953
954
        $result = curl_exec($curl);
955
956
        if ($this->debug > 1) {
957
            $message = "---CURL INFO---\n";
958
            foreach (curl_getinfo($curl) as $name => $val) {
959
                if (is_array($val)) {
960
                    $val = implode("\n", $val);
961
                }
962
                $message .= $name . ': ' . $val . "\n";
963
            }
964
            $message .= "---END---";
965
            Logger::instance()->debugMessage($message);
966
        }
967
968
        if (!$result) {
969
            /// @todo we should use a better check here - what if we get back '' or '0'?
970
971
            $this->errstr = 'no response';
972
            $resp = new Response(0, PhpXmlRpc::$xmlrpcerr['curl_fail'], PhpXmlRpc::$xmlrpcstr['curl_fail'] . ': ' . curl_error($curl));
973
            curl_close($curl);
974
            if ($keepAlive) {
975
                $this->xmlrpc_curl_handle = null;
976
            }
977
        } else {
978
            if (!$keepAlive) {
979
                curl_close($curl);
980
            }
981
            $resp = $req->parseResponse($result, true, $this->return_type);
982
            // if we got back a 302, we can not reuse the curl handle for later calls
983
            if ($resp->faultCode() == PhpXmlRpc::$xmlrpcerr['http_error'] && $keepAlive) {
984
                curl_close($curl);
985
                $this->xmlrpc_curl_handle = null;
986
            }
987
        }
988
989
        return $resp;
990
    }
991
992
    /**
993
     * Send an array of requests and return an array of responses.
994
     *
995
     * Unless $this->no_multicall has been set to true, it will try first to use one single xmlrpc call to server method
996
     * system.multicall, and revert to sending many successive calls in case of failure.
997
     * This failure is also stored in $this->no_multicall for subsequent calls.
998
     * Unfortunately, there is no server error code universally used to denote the fact that multicall is unsupported,
999
     * so there is no way to reliably distinguish between that and a temporary failure.
1000
     * If you are sure that server supports multicall and do not want to fallback to using many single calls, set the
1001
     * fourth parameter to FALSE.
1002
     *
1003
     * NB: trying to shoehorn extra functionality into existing syntax has resulted
1004
     * in pretty much convoluted code...
1005
     *
1006
     * @param Request[] $reqs an array of Request objects
1007
     * @param integer $timeout connection timeout (in seconds). See the details in the docs for the send() method
1008
     * @param string $method the http protocol variant to be used. See the details in the docs for the send() method
1009
     * @param boolean fallback When true, upon receiving an error during multicall, multiple single calls will be
1010
     *                         attempted
1011
     *
1012
     * @return Response[]
1013
     */
1014
    public function multicall($reqs, $timeout = 0, $method = '', $fallback = true)
1015
    {
1016
        if ($method == '') {
1017
            $method = $this->method;
1018
        }
1019
        if (!$this->no_multicall) {
1020
            $results = $this->_try_multicall($reqs, $timeout, $method);
1021
            if (is_array($results)) {
1022
                // System.multicall succeeded
1023
                return $results;
1024
            } else {
1025
                // either system.multicall is unsupported by server,
1026
                // or call failed for some other reason.
1027 View Code Duplication
                if ($fallback) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1028
                    // Don't try it next time...
1029
                    $this->no_multicall = true;
1030
                } else {
1031
                    if (is_a($results, '\PhpXmlRpc\Response')) {
1032
                        $result = $results;
1033
                    } else {
1034
                        $result = new Response(0, PhpXmlRpc::$xmlrpcerr['multicall_error'], PhpXmlRpc::$xmlrpcstr['multicall_error']);
1035
                    }
1036
                }
1037
            }
1038
        } else {
1039
            // override fallback, in case careless user tries to do two
1040
            // opposite things at the same time
1041
            $fallback = true;
1042
        }
1043
1044
        $results = array();
1045
        if ($fallback) {
1046
            // system.multicall is (probably) unsupported by server:
1047
            // emulate multicall via multiple requests
1048
            foreach ($reqs as $req) {
1049
                $results[] = $this->send($req, $timeout, $method);
1050
            }
1051
        } else {
1052
            // user does NOT want to fallback on many single calls:
1053
            // since we should always return an array of responses,
1054
            // return an array with the same error repeated n times
1055
            foreach ($reqs as $req) {
1056
                $results[] = $result;
0 ignored issues
show
Bug introduced by
The variable $result does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1057
            }
1058
        }
1059
1060
        return $results;
1061
    }
1062
1063
    /**
1064
     * Attempt to boxcar $reqs via system.multicall.
1065
     *
1066
     * Returns either an array of Response, a single error Response or false (when received response does not respect
1067
     * valid multicall syntax).
1068
     *
1069
     * @param Request[] $reqs
1070
     * @param int $timeout
1071
     * @param string $method
1072
     * @return Response[]|bool|mixed|Response
1073
     */
1074
    private function _try_multicall($reqs, $timeout, $method)
1075
    {
1076
        // Construct multicall request
1077
        $calls = array();
1078
        foreach ($reqs as $req) {
1079
            $call['methodName'] = new Value($req->method(), 'string');
0 ignored issues
show
Coding Style Comprehensibility introduced by
$call was never initialized. Although not strictly required by PHP, it is generally a good practice to add $call = 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...
1080
            $numParams = $req->getNumParams();
1081
            $params = array();
1082
            for ($i = 0; $i < $numParams; $i++) {
1083
                $params[$i] = $req->getParam($i);
1084
            }
1085
            $call['params'] = new Value($params, 'array');
0 ignored issues
show
Documentation introduced by
$params is of type array, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1086
            $calls[] = new Value($call, 'struct');
0 ignored issues
show
Documentation introduced by
$call is of type array<string,object<PhpX...ct<PhpXmlRpc\\Value>"}>, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1087
        }
1088
        $multiCall = new Request('system.multicall');
1089
        $multiCall->addParam(new Value($calls, 'array'));
0 ignored issues
show
Documentation introduced by
$calls is of type array, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1090
1091
        // Attempt RPC call
1092
        $result = $this->send($multiCall, $timeout, $method);
1093
1094
        if ($result->faultCode() != 0) {
1095
            // call to system.multicall failed
1096
            return $result;
1097
        }
1098
1099
        // Unpack responses.
1100
        $rets = $result->value();
1101
1102
        if ($this->return_type == 'xml') {
1103
            return $rets;
1104
        } elseif ($this->return_type == 'phpvals') {
1105
            /// @todo test this code branch...
1106
            $rets = $result->value();
1107
            if (!is_array($rets)) {
1108
                return false;       // bad return type from system.multicall
1109
            }
1110
            $numRets = count($rets);
1111
            if ($numRets != count($reqs)) {
1112
                return false;       // wrong number of return values.
1113
            }
1114
1115
            $response = array();
1116
            for ($i = 0; $i < $numRets; $i++) {
1117
                $val = $rets[$i];
1118
                if (!is_array($val)) {
1119
                    return false;
1120
                }
1121
                switch (count($val)) {
1122
                    case 1:
1123
                        if (!isset($val[0])) {
1124
                            return false;       // Bad value
1125
                        }
1126
                        // Normal return value
1127
                        $response[$i] = new Response($val[0], 0, '', 'phpvals');
1128
                        break;
1129
                    case 2:
1130
                        /// @todo remove usage of @: it is apparently quite slow
1131
                        $code = @$val['faultCode'];
1132
                        if (!is_int($code)) {
1133
                            return false;
1134
                        }
1135
                        $str = @$val['faultString'];
1136
                        if (!is_string($str)) {
1137
                            return false;
1138
                        }
1139
                        $response[$i] = new Response(0, $code, $str);
1140
                        break;
1141
                    default:
1142
                        return false;
1143
                }
1144
            }
1145
1146
            return $response;
1147
        } else {
1148
            // return type == 'xmlrpcvals'
1149
1150
            $rets = $result->value();
1151
            if ($rets->kindOf() != 'array') {
1152
                return false;       // bad return type from system.multicall
1153
            }
1154
            $numRets = $rets->count();
1155
            if ($numRets != count($reqs)) {
1156
                return false;       // wrong number of return values.
1157
            }
1158
1159
            $response = array();
1160
            foreach($rets as $val) {
1161
                switch ($val->kindOf()) {
1162
                    case 'array':
1163
                        if ($val->count() != 1) {
1164
                            return false;       // Bad value
1165
                        }
1166
                        // Normal return value
1167
                        $response[] = new Response($val[0]);
1168
                        break;
1169
                    case 'struct':
1170
                        $code = $val['faultCode'];
1171
                        if ($code->kindOf() != 'scalar' || $code->scalartyp() != 'int') {
1172
                            return false;
1173
                        }
1174
                        $str = $val['faultString'];
1175
                        if ($str->kindOf() != 'scalar' || $str->scalartyp() != 'string') {
1176
                            return false;
1177
                        }
1178
                        $response[] = new Response(0, $code->scalarval(), $str->scalarval());
1179
                        break;
1180
                    default:
1181
                        return false;
1182
                }
1183
            }
1184
1185
            return $response;
1186
        }
1187
    }
1188
}
1189