Passed
Push — master ( 593257...e1cd2d )
by Gaetano
03:24
created

Server::error_occurred()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 1
eloc 1
c 1
b 1
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
ccs 2
cts 2
cp 1
crap 1
1
<?php
2
3
namespace PhpXmlRpc;
4
5
use PhpXmlRpc\Helper\Logger;
6
use PhpXmlRpc\Helper\Charset;
7
use PhpXmlRpc\Helper\XMLParser;
8
9
/**
10
 * Allows effortless implementation of XML-RPC servers
11
 */
12
class Server
13
{
14
    /**
15
     * Array defining php functions exposed as xmlrpc methods by this server.
16
     */
17
    protected $dmap = array();
18
19
    /**
20
     * Defines how functions in dmap will be invoked: either using an xmlrpc request object
21
     * or plain php values.
22
     * Valid strings are 'xmlrpcvals', 'phpvals' or 'epivals'
23
     */
24
    public $functions_parameters_type = 'xmlrpcvals';
25
26
    /**
27
     * Option used for fine-tuning the encoding the php values returned from
28
     * functions registered in the dispatch map when the functions_parameters_types
29
     * member is set to 'phpvals'
30
     * @see Encoder::encode for a list of values
31
     */
32
    public $phpvals_encoding_options = array('auto_dates');
33
34
    /**
35
     * Controls whether the server is going to echo debugging messages back to the client as comments in response body.
36
     * Valid values: 0,1,2,3
37
     */
38
    public $debug = 1;
39
40
    /**
41
     * Controls behaviour of server when the invoked user function throws an exception:
42
     * 0 = catch it and return an 'internal error' xmlrpc response (default)
43
     * 1 = catch it and return an xmlrpc response with the error corresponding to the exception
44
     * 2 = allow the exception to float to the upper layers
45
     */
46
    public $exception_handling = 0;
47
48
    /**
49
     * When set to true, it will enable HTTP compression of the response, in case
50
     * the client has declared its support for compression in the request.
51
     * Set at constructor time.
52
     */
53
    public $compress_response = false;
54
55
    /**
56
     * List of http compression methods accepted by the server for requests. Set at constructor time.
57
     * NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib
58
     */
59
    public $accepted_compression = array();
60
61
    /// Shall we serve calls to system.* methods?
62
    public $allow_system_funcs = true;
63
64
    /**
65
     * List of charset encodings natively accepted for requests.
66
     * Set at constructor time.
67
     * UNUSED so far...
68
     */
69
    public $accepted_charset_encodings = array();
70
71
    /**
72
     * Charset encoding to be used for response.
73
     * NB: if we can, we will convert the generated response from internal_encoding to the intended one.
74
     * Can be: a supported xml encoding (only UTF-8 and ISO-8859-1 at present, unless mbstring is enabled),
75
     * null (leave unspecified in response, convert output stream to US_ASCII),
76
     * 'default' (use xmlrpc library default as specified in xmlrpc.inc, convert output stream if needed),
77
     * or 'auto' (use client-specified charset encoding or same as request if request headers do not specify it (unless request is US-ASCII: then use library default anyway).
78
     * NB: pretty dangerous if you accept every charset and do not have mbstring enabled)
79
     */
80
    public $response_charset_encoding = '';
81
82
    /**
83
     * Storage for internal debug info.
84
     */
85
    protected $debug_info = '';
86
87
    /**
88
     * Extra data passed at runtime to method handling functions. Used only by EPI layer
89
     */
90
    public $user_data = null;
91
92
    protected static $_xmlrpc_debuginfo = '';
93
    protected static $_xmlrpcs_occurred_errors = '';
94
    protected static $_xmlrpcs_prev_ehandler = '';
95
96
    /**
97
     * @param array $dispatchMap the dispatch map with definition of exposed services
98
     * @param boolean $serviceNow set to false to prevent the server from running upon construction
99
     */
100 414
    public function __construct($dispatchMap = null, $serviceNow = true)
101
    {
102
        // if ZLIB is enabled, let the server by default accept compressed requests,
103
        // and compress responses sent to clients that support them
104 414
        if (function_exists('gzinflate')) {
105 414
            $this->accepted_compression = array('gzip', 'deflate');
106 414
            $this->compress_response = true;
107
        }
108
109
        // by default the xml parser can support these 3 charset encodings
110 414
        $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII');
111
112
        // dispMap is a dispatch array of methods mapped to function names and signatures.
113
        // If a method doesn't appear in the map then an unknown method error is generated
114
        /* milosch - changed to make passing dispMap optional.
115
        * instead, you can use the class add_to_map() function
116
        * to add functions manually (borrowed from SOAPX4)
117
        */
118 414
        if ($dispatchMap) {
119 413
            $this->dmap = $dispatchMap;
120 413
            if ($serviceNow) {
121
                $this->service();
122
            }
123
        }
124 414
    }
125
126
    /**
127
     * Set debug level of server.
128
     *
129
     * @param integer $level debug lvl: determines info added to xmlrpc responses (as xml comments)
130
     *                    0 = no debug info,
131
     *                    1 = msgs set from user with debugmsg(),
132
     *                    2 = add complete xmlrpc request (headers and body),
133
     *                    3 = add also all processing warnings happened during method processing
134
     *                    (NB: this involves setting a custom error handler, and might interfere
135
     *                    with the standard processing of the php function exposed as method. In
136
     *                    particular, triggering an USER_ERROR level error will not halt script
137
     *                    execution anymore, but just end up logged in the xmlrpc response)
138
     *                    Note that info added at level 2 and 3 will be base64 encoded
139
     */
140 413
    public function setDebug($level)
141
    {
142 413
        $this->debug = $level;
143 413
    }
144
145
    /**
146
     * Add a string to the debug info that can be later serialized by the server as part of the response message.
147
     * Note that for best compatibility, the debug string should be encoded using the PhpXmlRpc::$xmlrpc_internalencoding
148
     * character set.
149
     *
150
     * @param string $msg
151
     * @access public
152
     */
153 2
    public static function xmlrpc_debugmsg($msg)
154
    {
155 2
        static::$_xmlrpc_debuginfo .= $msg . "\n";
156 2
    }
157
158
    /**
159
     * @param string $msg
160
     */
161 17
    public static function error_occurred($msg)
162
    {
163 17
        static::$_xmlrpcs_occurred_errors .= $msg . "\n";
164 17
    }
165
166
    /**
167
     * Return a string with the serialized representation of all debug info.
168
     *
169
     * @param string $charsetEncoding the target charset encoding for the serialization
170
     *
171
     * @return string an XML comment (or two)
172
     */
173 413
    public function serializeDebug($charsetEncoding = '')
174
    {
175
        // Tough encoding problem: which internal charset should we assume for debug info?
176
        // It might contain a copy of raw data received from client, ie with unknown encoding,
177
        // intermixed with php generated data and user generated data...
178
        // so we split it: system debug is base 64 encoded,
179
        // user debug info should be encoded by the end user using the INTERNAL_ENCODING
180 413
        $out = '';
181 413
        if ($this->debug_info != '') {
182 413
            $out .= "<!-- SERVER DEBUG INFO (BASE64 ENCODED):\n" . base64_encode($this->debug_info) . "\n-->\n";
183
        }
184 413
        if (static::$_xmlrpc_debuginfo != '') {
185 2
            $out .= "<!-- DEBUG INFO:\n" . Charset::instance()->encodeEntities(str_replace('--', '_-', static::$_xmlrpc_debuginfo), PhpXmlRpc::$xmlrpc_internalencoding, $charsetEncoding) . "\n-->\n";
186
            // NB: a better solution MIGHT be to use CDATA, but we need to insert it
187
            // into return payload AFTER the beginning tag
188
            //$out .= "<![CDATA[ DEBUG INFO:\n\n" . str_replace(']]>', ']_]_>', static::$_xmlrpc_debuginfo) . "\n]]>\n";
189
        }
190
191 413
        return $out;
192
    }
193
194
    /**
195
     * Execute the xmlrpc request, printing the response.
196
     *
197
     * @param string $data the request body. If null, the http POST request will be examined
198
     * @param bool $returnPayload When true, return the response but do not echo it or any http header
199
     *
200
     * @return Response|string the response object (usually not used by caller...) or its xml serialization
201
     *
202
     * @throws \Exception in case the executed method does throw an exception (and depending on server configuration)
203
     */
204 413
    public function service($data = null, $returnPayload = false)
205
    {
206 413
        if ($data === null) {
207 413
            $data = file_get_contents('php://input');
208
        }
209 413
        $rawData = $data;
210
211
        // reset internal debug info
212 413
        $this->debug_info = '';
213
214
        // Save what we received, before parsing it
215 413
        if ($this->debug > 1) {
216 413
            $this->debugmsg("+++GOT+++\n" . $data . "\n+++END+++");
217
        }
218
219 413
        $r = $this->parseRequestHeaders($data, $reqCharset, $respCharset, $respEncoding);
220 413
        if (!$r) {
221
            // this actually executes the request
222 413
            $r = $this->parseRequest($data, $reqCharset);
223
        }
224
225
        // save full body of request into response, for more debugging usages
226 413
        $r->raw_data = $rawData;
227
228 413
        if ($this->debug > 2 && static::$_xmlrpcs_occurred_errors) {
229 17
            $this->debugmsg("+++PROCESSING ERRORS AND WARNINGS+++\n" .
230 17
                static::$_xmlrpcs_occurred_errors . "+++END+++");
231
        }
232
233 413
        $payload = $this->xml_header($respCharset);
234 413
        if ($this->debug > 0) {
235 413
            $payload = $payload . $this->serializeDebug($respCharset);
236
        }
237
238
        // Do not create response serialization if it has already happened. Helps building json magic
239 413
        if (empty($r->payload)) {
240 413
            $r->serialize($respCharset);
241
        }
242 413
        $payload = $payload . $r->payload;
243
244 413
        if ($returnPayload) {
245
            return $payload;
246
        }
247
248
        // if we get a warning/error that has output some text before here, then we cannot
249
        // add a new header. We cannot say we are sending xml, either...
250 413
        if (!headers_sent()) {
251 413
            header('Content-Type: ' . $r->content_type);
252
            // we do not know if client actually told us an accepted charset, but if he did
253
            // we have to tell him what we did
254 413
            header("Vary: Accept-Charset");
255
256
            // http compression of output: only
257
            // if we can do it, and we want to do it, and client asked us to,
258
            // and php ini settings do not force it already
259 413
            $phpNoSelfCompress = !ini_get('zlib.output_compression') && (ini_get('output_handler') != 'ob_gzhandler');
260 413
            if ($this->compress_response && function_exists('gzencode') && $respEncoding != ''
261 100
                && $phpNoSelfCompress
262
            ) {
263 100
                if (strpos($respEncoding, 'gzip') !== false) {
264 50
                    $payload = gzencode($payload);
265 50
                    header("Content-Encoding: gzip");
266 50
                    header("Vary: Accept-Encoding");
267 50
                } elseif (strpos($respEncoding, 'deflate') !== false) {
268 50
                    $payload = gzcompress($payload);
269 50
                    header("Content-Encoding: deflate");
270 50
                    header("Vary: Accept-Encoding");
271
                }
272
            }
273
274
            // Do not output content-length header if php is compressing output for us:
275
            // it will mess up measurements.
276
            // Note that Apache/mod_php will add (and even alter!) the Content-Length header on its own, but only for
277
            // responses up to 8000 bytes
278 413
            if ($phpNoSelfCompress) {
279 413
                header('Content-Length: ' . (int)strlen($payload));
280
            }
281
        } else {
282
            Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': http headers already sent before response is fully generated. Check for php warning or error messages');
283
        }
284
285 413
        print $payload;
286
287
        // return request, in case subclasses want it
288 413
        return $r;
289
    }
290
291
    /**
292
     * Add a method to the dispatch map.
293
     *
294
     * @param string $methodName the name with which the method will be made available
295
     * @param string $function the php function that will get invoked
296
     * @param array $sig the array of valid method signatures
297
     * @param string $doc method documentation
298
     * @param array $sigDoc the array of valid method signatures docs (one string per param, one for return type)
299
     */
300
    public function add_to_map($methodName, $function, $sig = null, $doc = false, $sigDoc = false)
301
    {
302
        $this->dmap[$methodName] = array(
303
            'function' => $function,
304
            'docstring' => $doc,
305
        );
306
        if ($sig) {
307
            $this->dmap[$methodName]['signature'] = $sig;
308
        }
309
        if ($sigDoc) {
310
            $this->dmap[$methodName]['signature_docs'] = $sigDoc;
311
        }
312
    }
313
314
    /**
315
     * Verify type and number of parameters received against a list of known signatures.
316
     *
317
     * @param array|Request $in array of either xmlrpc value objects or xmlrpc type definitions
318
     * @param array $sigs array of known signatures to match against
319
     *
320
     * @return array
321
     */
322 395
    protected function verifySignature($in, $sigs)
323
    {
324
        // check each possible signature in turn
325 395
        if (is_object($in)) {
326 395
            $numParams = $in->getNumParams();
327
        } else {
328
            $numParams = count($in);
329
        }
330 395
        foreach ($sigs as $curSig) {
331 395
            if (count($curSig) == $numParams + 1) {
332 395
                $itsOK = 1;
333 395
                for ($n = 0; $n < $numParams; $n++) {
334 379
                    if (is_object($in)) {
335 379
                        $p = $in->getParam($n);
336 379
                        if ($p->kindOf() == 'scalar') {
337 331
                            $pt = $p->scalartyp();
338
                        } else {
339 113
                            $pt = $p->kindOf();
340
                        }
341
                    } else {
342
                        $pt = ($in[$n] == 'i4') ? 'int' : strtolower($in[$n]); // dispatch maps never use i4...
343
                    }
344
345
                    // param index is $n+1, as first member of sig is return type
346 379
                    if ($pt != $curSig[$n + 1] && $curSig[$n + 1] != Value::$xmlrpcValue) {
347 17
                        $itsOK = 0;
348 17
                        $pno = $n + 1;
349 17
                        $wanted = $curSig[$n + 1];
350 17
                        $got = $pt;
351 17
                        break;
352
                    }
353
                }
354 395
                if ($itsOK) {
355 395
                    return array(1, '');
356
                }
357
            }
358
        }
359 17
        if (isset($wanted)) {
360
            return array(0, "Wanted ${wanted}, got ${got} at param ${pno}");
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $got does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $pno does not seem to be defined for all execution paths leading up to this point.
Loading history...
361
        } else {
362 17
            return array(0, "No method signature matches number of parameters");
363
        }
364
    }
365
366
    /**
367
     * Parse http headers received along with xmlrpc request. If needed, inflate request.
368
     *
369
     * @return mixed Response|null on success or an error Response
370
     */
371 413
    protected function parseRequestHeaders(&$data, &$reqEncoding, &$respEncoding, &$respCompression)
372
    {
373
        // check if $_SERVER is populated: it might have been disabled via ini file
374
        // (this is true even when in CLI mode)
375 413
        if (count($_SERVER) == 0) {
376
            Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': cannot parse request headers as $_SERVER is not populated');
377
        }
378
379 413
        if ($this->debug > 1) {
380 413
            if (function_exists('getallheaders')) {
381 413
                $this->debugmsg(''); // empty line
382 413
                foreach (getallheaders() as $name => $val) {
383 413
                    $this->debugmsg("HEADER: $name: $val");
384
                }
385
            }
386
        }
387
388 413
        if (isset($_SERVER['HTTP_CONTENT_ENCODING'])) {
389 100
            $contentEncoding = str_replace('x-', '', $_SERVER['HTTP_CONTENT_ENCODING']);
390
        } else {
391 313
            $contentEncoding = '';
392
        }
393
394
        // check if request body has been compressed and decompress it
395 413
        if ($contentEncoding != '' && strlen($data)) {
396 100
            if ($contentEncoding == 'deflate' || $contentEncoding == 'gzip') {
397
                // if decoding works, use it. else assume data wasn't gzencoded
398 100
                if (function_exists('gzinflate') && in_array($contentEncoding, $this->accepted_compression)) {
399 100
                    if ($contentEncoding == 'deflate' && $degzdata = @gzuncompress($data)) {
400 50
                        $data = $degzdata;
401 50
                        if ($this->debug > 1) {
402 50
                            $this->debugmsg("\n+++INFLATED REQUEST+++[" . strlen($data) . " chars]+++\n" . $data . "\n+++END+++");
403
                        }
404 50
                    } elseif ($contentEncoding == 'gzip' && $degzdata = @gzinflate(substr($data, 10))) {
405 50
                        $data = $degzdata;
406 50
                        if ($this->debug > 1) {
407 50
                            $this->debugmsg("+++INFLATED REQUEST+++[" . strlen($data) . " chars]+++\n" . $data . "\n+++END+++");
408
                        }
409
                    } else {
410
                        $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_decompress_fail'], PhpXmlRpc::$xmlrpcstr['server_decompress_fail']);
411
412
                        return $r;
413
                    }
414
                } else {
415
                    $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_cannot_decompress'], PhpXmlRpc::$xmlrpcstr['server_cannot_decompress']);
416
417
                    return $r;
418
                }
419
            }
420
        }
421
422
        // check if client specified accepted charsets, and if we know how to fulfill
423
        // the request
424 413
        if ($this->response_charset_encoding == 'auto') {
425
            $respEncoding = '';
426
            if (isset($_SERVER['HTTP_ACCEPT_CHARSET'])) {
427
                // here we should check if we can match the client-requested encoding
428
                // with the encodings we know we can generate.
429
                /// @todo we should parse q=0.x preferences instead of getting first charset specified...
430
                $clientAcceptedCharsets = explode(',', strtoupper($_SERVER['HTTP_ACCEPT_CHARSET']));
431
                // Give preference to internal encoding
432
                $knownCharsets = array(PhpXmlRpc::$xmlrpc_internalencoding, 'UTF-8', 'ISO-8859-1', 'US-ASCII');
433
                foreach ($knownCharsets as $charset) {
434
                    foreach ($clientAcceptedCharsets as $accepted) {
435
                        if (strpos($accepted, $charset) === 0) {
436
                            $respEncoding = $charset;
437
                            break;
438
                        }
439
                    }
440
                    if ($respEncoding) {
441
                        break;
442
                    }
443
                }
444
            }
445
        } else {
446 413
            $respEncoding = $this->response_charset_encoding;
447
        }
448
449 413
        if (isset($_SERVER['HTTP_ACCEPT_ENCODING'])) {
450 100
            $respCompression = $_SERVER['HTTP_ACCEPT_ENCODING'];
451
        } else {
452 313
            $respCompression = '';
453
        }
454
455
        // 'guestimate' request encoding
456
        /// @todo check if mbstring is enabled and automagic input conversion is on: it might mingle with this check???
457 413
        $reqEncoding = XMLParser::guessEncoding(isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : '',
458
            $data);
459
460 413
        return null;
461
    }
462
463
    /**
464
     * Parse an xml chunk containing an xmlrpc request and execute the corresponding
465
     * php function registered with the server.
466
     *
467
     * @param string $data the xml request
468
     * @param string $reqEncoding (optional) the charset encoding of the xml request
469
     *
470
     * @return Response
471
     *
472
     * @throws \Exception in case the executed method does throw an exception (and depending on server configuration)
473
     */
474 414
    public function parseRequest($data, $reqEncoding = '')
475
    {
476
        // decompose incoming XML into request structure
477
478 414
        if ($reqEncoding != '') {
479
            // Since parsing will fail if
480
            // - charset is not specified in the xml prologue,
481
            // - the encoding is not UTF8 and
482
            // - there are non-ascii chars in the text,
483
            // we try to work round that...
484
            // The following code might be better for mb_string enabled installs, but
485
            // makes the lib about 200% slower...
486
            //if (!is_valid_charset($reqEncoding, array('UTF-8')))
487 413
            if (!in_array($reqEncoding, array('UTF-8', 'US-ASCII')) && !XMLParser::hasEncoding($data)) {
488 4
                if ($reqEncoding == 'ISO-8859-1') {
489 2
                    $data = utf8_encode($data);
490
                } else {
491 2
                    if (extension_loaded('mbstring')) {
492 2
                        $data = mb_convert_encoding($data, 'UTF-8', $reqEncoding);
493
                    } else {
494
                        Logger::instance()->errorLog('XML-RPC: ' . __METHOD__ . ': invalid charset encoding of received request: ' . $reqEncoding);
495
                    }
496
                }
497
            }
498
        }
499
500
        // PHP internally might use ISO-8859-1, so we have to tell the xml parser to give us back data in the expected charset.
501
        // What if internal encoding is not in one of the 3 allowed? We use the broadest one, ie. utf8
502
        // This allows to send data which is native in various charset,
503
        // by extending xmlrpc_encode_entities() and setting xmlrpc_internalencoding
504 414
        if (!in_array(PhpXmlRpc::$xmlrpc_internalencoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII'))) {
505
            $options = array(XML_OPTION_TARGET_ENCODING => 'UTF-8');
506
        } else {
507 414
            $options = array(XML_OPTION_TARGET_ENCODING => PhpXmlRpc::$xmlrpc_internalencoding);
508
        }
509
510 414
        $xmlRpcParser = new XMLParser($options);
511 414
        $xmlRpcParser->parse($data, $this->functions_parameters_type, XMLParser::ACCEPT_REQUEST);
512 414
        if ($xmlRpcParser->_xh['isf'] > 2) {
513
            // (BC) we return XML error as a faultCode
514
            preg_match('/^XML error ([0-9]+)/', $xmlRpcParser->_xh['isf_reason'], $matches);
515
            $r = new Response(0,
516
                PhpXmlRpc::$xmlrpcerrxml + $matches[1],
517
                $xmlRpcParser->_xh['isf_reason']);
518 414
        } elseif ($xmlRpcParser->_xh['isf']) {
519 1
            $r = new Response(0,
520 1
                PhpXmlRpc::$xmlrpcerr['invalid_request'],
521 1
                PhpXmlRpc::$xmlrpcstr['invalid_request'] . ' ' . $xmlRpcParser->_xh['isf_reason']);
522
        } else {
523
            // small layering violation in favor of speed and memory usage:
524
            // we should allow the 'execute' method handle this, but in the
525
            // most common scenario (xmlrpc values type server with some methods
526
            // registered as phpvals) that would mean a useless encode+decode pass
527 413
            if ($this->functions_parameters_type != 'xmlrpcvals' ||
528 413
                (isset($this->dmap[$xmlRpcParser->_xh['method']]['parameters_type']) &&
529
                    ($this->dmap[$xmlRpcParser->_xh['method']]['parameters_type'] == 'phpvals')
530
                )
531
            ) {
532
                if ($this->debug > 1) {
533
                    $this->debugmsg("\n+++PARSED+++\n" . var_export($xmlRpcParser->_xh['params'], true) . "\n+++END+++");
534
                }
535
                $r = $this->execute($xmlRpcParser->_xh['method'], $xmlRpcParser->_xh['params'], $xmlRpcParser->_xh['pt']);
536
            } else {
537
                // build a Request object with data parsed from xml
538 413
                $req = new Request($xmlRpcParser->_xh['method']);
0 ignored issues
show
Bug introduced by
$xmlRpcParser->_xh['method'] of type false is incompatible with the type string expected by parameter $methodName of PhpXmlRpc\Request::__construct(). ( Ignorable by Annotation )

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

538
                $req = new Request(/** @scrutinizer ignore-type */ $xmlRpcParser->_xh['method']);
Loading history...
539
                // now add parameters in
540 413
                for ($i = 0; $i < count($xmlRpcParser->_xh['params']); $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...
541 397
                    $req->addParam($xmlRpcParser->_xh['params'][$i]);
542
                }
543
544 413
                if ($this->debug > 1) {
545 413
                    $this->debugmsg("\n+++PARSED+++\n" . var_export($req, true) . "\n+++END+++");
546
                }
547 413
                $r = $this->execute($req);
548
            }
549
        }
550
551 414
        return $r;
552
    }
553
554
    /**
555
     * Execute a method invoked by the client, checking parameters used.
556
     *
557
     * @param mixed $req either a Request obj or a method name
558
     * @param array $params array with method parameters as php types (if m is method name only)
559
     * @param array $paramTypes array with xmlrpc types of method parameters (if m is method name only)
560
     *
561
     * @return Response
562
     *
563
     * @throws \Exception in case the executed method does throw an exception (and depending on server configuration)
564
     */
565 413
    protected function execute($req, $params = null, $paramTypes = null)
566
    {
567 413
        static::$_xmlrpcs_occurred_errors = '';
568 413
        static::$_xmlrpc_debuginfo = '';
569
570 413
        if (is_object($req)) {
571 413
            $methName = $req->method();
572
        } else {
573
            $methName = $req;
574
        }
575 413
        $sysCall = $this->allow_system_funcs && (strpos($methName, "system.") === 0);
576 413
        $dmap = $sysCall ? $this->getSystemDispatchMap() : $this->dmap;
577
578 413
        if (!isset($dmap[$methName]['function'])) {
579
            // No such method
580 65
            return new Response(0,
581 65
                PhpXmlRpc::$xmlrpcerr['unknown_method'],
582 65
                PhpXmlRpc::$xmlrpcstr['unknown_method']);
583
        }
584
585
        // Check signature
586 413
        if (isset($dmap[$methName]['signature'])) {
587 395
            $sig = $dmap[$methName]['signature'];
588 395
            if (is_object($req)) {
589 395
                list($ok, $errStr) = $this->verifySignature($req, $sig);
590
            } else {
591
                list($ok, $errStr) = $this->verifySignature($paramTypes, $sig);
592
            }
593 395
            if (!$ok) {
594
                // Didn't match.
595 17
                return new Response(
596 17
                    0,
597 17
                    PhpXmlRpc::$xmlrpcerr['incorrect_params'],
598 17
                    PhpXmlRpc::$xmlrpcstr['incorrect_params'] . ": ${errStr}"
599
                );
600
            }
601
        }
602
603 413
        $func = $dmap[$methName]['function'];
604
        // let the 'class::function' syntax be accepted in dispatch maps
605 413
        if (is_string($func) && strpos($func, '::')) {
606 97
            $func = explode('::', $func);
607
        }
608
609 413
        if (is_array($func)) {
610 115
            if (is_object($func[0])) {
611 19
                $funcName = get_class($func[0]) . '->' . $func[1];
612
            } else {
613 97
                $funcName = implode('::', $func);
614
            }
615 315
        } else if ($func instanceof \Closure) {
616 83
            $funcName = 'Closure';
617
        } else {
618 249
            $funcName = $func;
619
        }
620
621
        // verify that function to be invoked is in fact callable
622 413
        if (!is_callable($func)) {
623
            Logger::instance()->errorLog("XML-RPC: " . __METHOD__ . ": function '$funcName' registered as method handler is not callable");
624
            return new Response(
625
                0,
626
                PhpXmlRpc::$xmlrpcerr['server_error'],
627
                PhpXmlRpc::$xmlrpcstr['server_error'] . ": no function matches method"
628
            );
629
        }
630
631
        // If debug level is 3, we should catch all errors generated during
632
        // processing of user function, and log them as part of response
633 413
        if ($this->debug > 2) {
634 413
            self::$_xmlrpcs_prev_ehandler = set_error_handler(array('\PhpXmlRpc\Server', '_xmlrpcs_errorHandler'));
635
        }
636
637
        try {
638
            // Allow mixed-convention servers
639 413
            if (is_object($req)) {
640 413
                if ($sysCall) {
641 97
                    $r = call_user_func($func, $this, $req);
642
                } else {
643 333
                    $r = call_user_func($func, $req);
644
                }
645 411
                if (!is_a($r, 'PhpXmlRpc\Response')) {
646
                    Logger::instance()->errorLog("XML-RPC: " . __METHOD__ . ": function '$funcName' registered as method handler does not return an xmlrpc response object but a " . gettype($r));
647
                    if (is_a($r, 'PhpXmlRpc\Value')) {
648
                        $r = new Response($r);
649
                    } else {
650
                        $r = new Response(
651
                            0,
652
                            PhpXmlRpc::$xmlrpcerr['server_error'],
653
                            PhpXmlRpc::$xmlrpcstr['server_error'] . ": function does not return xmlrpc response object"
654
                        );
655
                    }
656
                }
657
            } else {
658
                // call a 'plain php' function
659
                if ($sysCall) {
660
                    array_unshift($params, $this);
0 ignored issues
show
Bug introduced by
It seems like $params can also be of type null; however, parameter $array of array_unshift() does only seem to accept array, 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

660
                    array_unshift(/** @scrutinizer ignore-type */ $params, $this);
Loading history...
661
                    $r = call_user_func_array($func, $params);
662
                } else {
663
                    // 3rd API convention for method-handling functions: EPI-style
664
                    if ($this->functions_parameters_type == 'epivals') {
665
                        $r = call_user_func_array($func, array($methName, $params, $this->user_data));
666
                        // mimic EPI behaviour: if we get an array that looks like an error, make it
667
                        // an eror response
668
                        if (is_array($r) && array_key_exists('faultCode', $r) && array_key_exists('faultString', $r)) {
669
                            $r = new Response(0, (integer)$r['faultCode'], (string)$r['faultString']);
670
                        } else {
671
                            // functions using EPI api should NOT return resp objects,
672
                            // so make sure we encode the return type correctly
673
                            $encoder = new Encoder();
674
                            $r = new Response($encoder->encode($r, array('extension_api')));
675
                        }
676
                    } else {
677
                        $r = call_user_func_array($func, $params);
0 ignored issues
show
Bug introduced by
It seems like $params can also be of type null; however, parameter $param_arr of call_user_func_array() does only seem to accept array, 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

677
                        $r = call_user_func_array($func, /** @scrutinizer ignore-type */ $params);
Loading history...
678
                    }
679
                }
680
                // the return type can be either a Response object or a plain php value...
681
                if (!is_a($r, '\PhpXmlRpc\Response')) {
682
                    // what should we assume here about automatic encoding of datetimes
683
                    // and php classes instances???
684
                    $encoder = new Encoder();
685
                    $r = new Response($encoder->encode($r, $this->phpvals_encoding_options));
686
                }
687
            }
688 35
        } catch (\Exception $e) {
689
            // (barring errors in the lib) an uncatched exception happened
690
            // in the called function, we wrap it in a proper error-response
691 35
            switch ($this->exception_handling) {
692 35
                case 2:
693
                    if ($this->debug > 2) {
694
                        if (self::$_xmlrpcs_prev_ehandler) {
695
                            set_error_handler(self::$_xmlrpcs_prev_ehandler);
696
                        } else {
697
                            restore_error_handler();
698
                        }
699
                    }
700
                    throw $e;
701
                    break;
702 35
                case 1:
703 2
                    $r = new Response(0, $e->getCode(), $e->getMessage());
704 2
                    break;
705
                default:
706 35
                    $r = new Response(0, PhpXmlRpc::$xmlrpcerr['server_error'], PhpXmlRpc::$xmlrpcstr['server_error']);
707
            }
708
        }
709 413
        if ($this->debug > 2) {
710
            // note: restore the error handler we found before calling the
711
            // user func, even if it has been changed inside the func itself
712 413
            if (self::$_xmlrpcs_prev_ehandler) {
713 49
                set_error_handler(self::$_xmlrpcs_prev_ehandler);
714
            } else {
715 365
                restore_error_handler();
716
            }
717
        }
718
719 413
        return $r;
720
    }
721
722
    /**
723
     * Add a string to the 'internal debug message' (separate from 'user debug message').
724
     *
725
     * @param string $string
726
     */
727 413
    protected function debugmsg($string)
728
    {
729 413
        $this->debug_info .= $string . "\n";
730 413
    }
731
732
    /**
733
     * @param string $charsetEncoding
734
     * @return string
735
     */
736 413
    protected function xml_header($charsetEncoding = '')
737
    {
738 413
        if ($charsetEncoding != '') {
739 50
            return "<?xml version=\"1.0\" encoding=\"$charsetEncoding\"?" . ">\n";
740
        } else {
741 363
            return "<?xml version=\"1.0\"?" . ">\n";
742
        }
743
    }
744
745
    /* Functions that implement system.XXX methods of xmlrpc servers */
746
747
    /**
748
     * @return array
749
     */
750 97
    public function getSystemDispatchMap()
751
    {
752
        return array(
753
            'system.listMethods' => array(
754 97
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_listMethods',
755
                // listMethods: signature was either a string, or nothing.
756
                // The useless string variant has been removed
757 97
                'signature' => array(array(Value::$xmlrpcArray)),
758 97
                'docstring' => 'This method lists all the methods that the XML-RPC server knows how to dispatch',
759
                'signature_docs' => array(array('list of method names')),
760
            ),
761
            'system.methodHelp' => array(
762 97
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_methodHelp',
763 97
                'signature' => array(array(Value::$xmlrpcString, Value::$xmlrpcString)),
764 97
                'docstring' => 'Returns help text if defined for the method passed, otherwise returns an empty string',
765
                'signature_docs' => array(array('method description', 'name of the method to be described')),
766
            ),
767
            'system.methodSignature' => array(
768 97
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_methodSignature',
769 97
                'signature' => array(array(Value::$xmlrpcArray, Value::$xmlrpcString)),
770 97
                'docstring' => 'Returns an array of known signatures (an array of arrays) for the method name passed. If no signatures are known, returns a none-array (test for type != array to detect missing signature)',
771
                'signature_docs' => array(array('list of known signatures, each sig being an array of xmlrpc type names', 'name of method to be described')),
772
            ),
773
            'system.multicall' => array(
774 97
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_multicall',
775 97
                'signature' => array(array(Value::$xmlrpcArray, Value::$xmlrpcArray)),
776 97
                'docstring' => 'Boxcar multiple RPC calls in one request. See http://www.xmlrpc.com/discuss/msgReader$1208 for details',
777
                'signature_docs' => array(array('list of response structs, where each struct has the usual members', 'list of calls, with each call being represented as a struct, with members "methodname" and "params"')),
778
            ),
779
            'system.getCapabilities' => array(
780 97
                'function' => 'PhpXmlRpc\Server::_xmlrpcs_getCapabilities',
781 97
                'signature' => array(array(Value::$xmlrpcStruct)),
782 97
                'docstring' => 'This method lists all the capabilites that the XML-RPC server has: the (more or less standard) extensions to the xmlrpc spec that it adheres to',
783
                'signature_docs' => array(array('list of capabilities, described as structs with a version number and url for the spec')),
784
            ),
785
        );
786
    }
787
788
    /**
789
     * @return array
790
     */
791
    public function getCapabilities()
792
    {
793
        $outAr = array(
794
            // xmlrpc spec: always supported
795
            'xmlrpc' => array(
796
                'specUrl' => 'http://www.xmlrpc.com/spec',
797
                'specVersion' => 1
798
            ),
799
            // if we support system.xxx functions, we always support multicall, too...
800
            // Note that, as of 2006/09/17, the following URL does not respond anymore
801
            'system.multicall' => array(
802
                'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
803
                'specVersion' => 1
804
            ),
805
            // introspection: version 2! we support 'mixed', too
806
            'introspection' => array(
807
                'specUrl' => 'http://phpxmlrpc.sourceforge.net/doc-2/ch10.html',
808
                'specVersion' => 2,
809
            ),
810
        );
811
812
        // NIL extension
813
        if (PhpXmlRpc::$xmlrpc_null_extension) {
814
            $outAr['nil'] = array(
815
                'specUrl' => 'http://www.ontosys.com/xml-rpc/extensions.php',
816
                'specVersion' => 1
817
            );
818
        }
819
820
        return $outAr;
821
    }
822
823
    /**
824
     * @param Server $server
825
     * @param Request $req
826
     * @return Response
827
     */
828
    public static function _xmlrpcs_getCapabilities($server, $req = null)
0 ignored issues
show
Unused Code introduced by
The parameter $req 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

828
    public static function _xmlrpcs_getCapabilities($server, /** @scrutinizer ignore-unused */ $req = null)

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...
829
    {
830
        $encoder = new Encoder();
831
        return new Response($encoder->encode($server->getCapabilities()));
832
    }
833
834
    /**
835
     * @param Server $server
836
     * @param Request $req if called in plain php values mode, second param is missing
837
     * @return Response
838
     */
839 17
    public static function _xmlrpcs_listMethods($server, $req = null)
0 ignored issues
show
Unused Code introduced by
The parameter $req 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

839
    public static function _xmlrpcs_listMethods($server, /** @scrutinizer ignore-unused */ $req = null)

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...
840
    {
841 17
        $outAr = array();
842 17
        foreach ($server->dmap as $key => $val) {
843 17
            $outAr[] = new Value($key, 'string');
844
        }
845 17
        if ($server->allow_system_funcs) {
846 17
            foreach ($server->getSystemDispatchMap() as $key => $val) {
847 17
                $outAr[] = new Value($key, 'string');
848
            }
849
        }
850
851 17
        return new Response(new Value($outAr, 'array'));
852
    }
853
854
    /**
855
     * @param Server $server
856
     * @param Request $req
857
     * @return Response
858
     */
859 81
    public static function _xmlrpcs_methodSignature($server, $req)
860
    {
861
        // let accept as parameter both an xmlrpc value or string
862 81
        if (is_object($req)) {
863 81
            $methName = $req->getParam(0);
864 81
            $methName = $methName->scalarval();
865
        } else {
866
            $methName = $req;
867
        }
868 81
        if (strpos($methName, "system.") === 0) {
869 65
            $dmap = $server->getSystemDispatchMap();
870
        } else {
871 17
            $dmap = $server->dmap;
872
        }
873 81
        if (isset($dmap[$methName])) {
874 81
            if (isset($dmap[$methName]['signature'])) {
875 81
                $sigs = array();
876 81
                foreach ($dmap[$methName]['signature'] as $inSig) {
877 81
                    $curSig = array();
878 81
                    foreach ($inSig as $sig) {
879 81
                        $curSig[] = new Value($sig, 'string');
880
                    }
881 81
                    $sigs[] = new Value($curSig, 'array');
882
                }
883 81
                $r = new Response(new Value($sigs, 'array'));
884
            } else {
885
                // NB: according to the official docs, we should be returning a
886
                // "none-array" here, which means not-an-array
887
                $r = new Response(new Value('undef', 'string'));
888
            }
889
        } else {
890
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['introspect_unknown'], PhpXmlRpc::$xmlrpcstr['introspect_unknown']);
891
        }
892
893 81
        return $r;
894
    }
895
896
    /**
897
     * @param Server $server
898
     * @param Request $req
899
     * @return Response
900
     */
901 65
    public static function _xmlrpcs_methodHelp($server, $req)
902
    {
903
        // let accept as parameter both an xmlrpc value or string
904 65
        if (is_object($req)) {
905 65
            $methName = $req->getParam(0);
906 65
            $methName = $methName->scalarval();
907
        } else {
908
            $methName = $req;
909
        }
910 65
        if (strpos($methName, "system.") === 0) {
911 65
            $dmap = $server->getSystemDispatchMap();
912
        } else {
913 1
            $dmap = $server->dmap;
914
        }
915 65
        if (isset($dmap[$methName])) {
916 65
            if (isset($dmap[$methName]['docstring'])) {
917 65
                $r = new Response(new Value($dmap[$methName]['docstring']), 'string');
0 ignored issues
show
Bug introduced by
'string' of type string is incompatible with the type integer expected by parameter $fCode of PhpXmlRpc\Response::__construct(). ( Ignorable by Annotation )

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

917
                $r = new Response(new Value($dmap[$methName]['docstring']), /** @scrutinizer ignore-type */ 'string');
Loading history...
918
            } else {
919
                $r = new Response(new Value('', 'string'));
920
            }
921
        } else {
922
            $r = new Response(0, PhpXmlRpc::$xmlrpcerr['introspect_unknown'], PhpXmlRpc::$xmlrpcstr['introspect_unknown']);
923
        }
924
925 65
        return $r;
926
    }
927
928 49
    public static function _xmlrpcs_multicall_error($err)
929
    {
930 49
        if (is_string($err)) {
931 49
            $str = PhpXmlRpc::$xmlrpcstr["multicall_${err}"];
932 49
            $code = PhpXmlRpc::$xmlrpcerr["multicall_${err}"];
933
        } else {
934 49
            $code = $err->faultCode();
935 49
            $str = $err->faultString();
936
        }
937 49
        $struct = array();
938 49
        $struct['faultCode'] = new Value($code, 'int');
939 49
        $struct['faultString'] = new Value($str, 'string');
940
941 49
        return new Value($struct, 'struct');
942
    }
943
944
    /**
945
     * @param Server $server
946
     * @param Value $call
947
     * @return Value
948
     */
949 49
    public static function _xmlrpcs_multicall_do_call($server, $call)
950
    {
951 49
        if ($call->kindOf() != 'struct') {
952
            return static::_xmlrpcs_multicall_error('notstruct');
953
        }
954 49
        $methName = @$call['methodName'];
955 49
        if (!$methName) {
956
            return static::_xmlrpcs_multicall_error('nomethod');
957
        }
958 49
        if ($methName->kindOf() != 'scalar' || $methName->scalartyp() != 'string') {
959
            return static::_xmlrpcs_multicall_error('notstring');
960
        }
961 49
        if ($methName->scalarval() == 'system.multicall') {
962 49
            return static::_xmlrpcs_multicall_error('recursion');
963
        }
964
965 49
        $params = @$call['params'];
966 49
        if (!$params) {
967
            return static::_xmlrpcs_multicall_error('noparams');
968
        }
969 49
        if ($params->kindOf() != 'array') {
970
            return static::_xmlrpcs_multicall_error('notarray');
971
        }
972
973 49
        $req = new Request($methName->scalarval());
974 49
        foreach($params as $i => $param) {
975 49
            if (!$req->addParam($param)) {
976
                $i++; // for error message, we count params from 1
977
                return static::_xmlrpcs_multicall_error(new Response(0,
978
                    PhpXmlRpc::$xmlrpcerr['incorrect_params'],
979
                    PhpXmlRpc::$xmlrpcstr['incorrect_params'] . ": probable xml error in param " . $i));
980
            }
981
        }
982
983 49
        $result = $server->execute($req);
984
985 49
        if ($result->faultCode() != 0) {
986 49
            return static::_xmlrpcs_multicall_error($result); // Method returned fault.
987
        }
988
989 49
        return new Value(array($result->value()), 'array');
990
    }
991
992
    /**
993
     * @param Server $server
994
     * @param Value $call
995
     * @return Value
996
     */
997
    public static function _xmlrpcs_multicall_do_call_phpvals($server, $call)
998
    {
999
        if (!is_array($call)) {
0 ignored issues
show
introduced by
The condition is_array($call) is always false.
Loading history...
1000
            return static::_xmlrpcs_multicall_error('notstruct');
1001
        }
1002
        if (!array_key_exists('methodName', $call)) {
1003
            return static::_xmlrpcs_multicall_error('nomethod');
1004
        }
1005
        if (!is_string($call['methodName'])) {
1006
            return static::_xmlrpcs_multicall_error('notstring');
1007
        }
1008
        if ($call['methodName'] == 'system.multicall') {
1009
            return static::_xmlrpcs_multicall_error('recursion');
1010
        }
1011
        if (!array_key_exists('params', $call)) {
1012
            return static::_xmlrpcs_multicall_error('noparams');
1013
        }
1014
        if (!is_array($call['params'])) {
1015
            return static::_xmlrpcs_multicall_error('notarray');
1016
        }
1017
1018
        // this is a real dirty and simplistic hack, since we might have received a
1019
        // base64 or datetime values, but they will be listed as strings here...
1020
        $pt = array();
1021
        $wrapper = new Wrapper();
1022
        foreach ($call['params'] as $val) {
1023
            $pt[] = $wrapper->php2XmlrpcType(gettype($val));
1024
        }
1025
1026
        $result = $server->execute($call['methodName'], $call['params'], $pt);
1027
1028
        if ($result->faultCode() != 0) {
1029
            return static::_xmlrpcs_multicall_error($result); // Method returned fault.
1030
        }
1031
1032
        return new Value(array($result->value()), 'array');
1033
    }
1034
1035
    /**
1036
     * @param Server $server
1037
     * @param Request|array $req
1038
     * @return Response
1039
     */
1040 65
    public static function _xmlrpcs_multicall($server, $req)
1041
    {
1042 65
        $result = array();
1043
        // let accept a plain list of php parameters, beside a single xmlrpc msg object
1044 65
        if (is_object($req)) {
1045 65
            $calls = $req->getParam(0);
1046 65
            foreach($calls as $call) {
1047 49
                $result[] = static::_xmlrpcs_multicall_do_call($server, $call);
1048
            }
1049
        } else {
1050
            $numCalls = count($req);
1051
            for ($i = 0; $i < $numCalls; $i++) {
1052
                $result[$i] = static::_xmlrpcs_multicall_do_call_phpvals($server, $req[$i]);
1053
            }
1054
        }
1055
1056 65
        return new Response(new Value($result, 'array'));
1057
    }
1058
1059
    /**
1060
     * Error handler used to track errors that occur during server-side execution of PHP code.
1061
     * This allows to report back to the client whether an internal error has occurred or not
1062
     * using an xmlrpc response object, instead of letting the client deal with the html junk
1063
     * that a PHP execution error on the server generally entails.
1064
     *
1065
     * NB: in fact a user defined error handler can only handle WARNING, NOTICE and USER_* errors.
1066
     */
1067 33
    public static function _xmlrpcs_errorHandler($errCode, $errString, $filename = null, $lineNo = null, $context = null)
1068
    {
1069
        // obey the @ protocol
1070 33
        if (error_reporting() == 0) {
1071 17
            return;
1072
        }
1073
1074
        //if($errCode != E_NOTICE && $errCode != E_WARNING && $errCode != E_USER_NOTICE && $errCode != E_USER_WARNING)
1075 17
        if ($errCode != E_STRICT) {
1076 17
            \PhpXmlRpc\Server::error_occurred($errString);
1077
        }
1078
        // Try to avoid as much as possible disruption to the previous error handling
1079
        // mechanism in place
1080 17
        if (self::$_xmlrpcs_prev_ehandler == '') {
1081
            // The previous error handler was the default: all we should do is log error
1082
            // to the default error log (if level high enough)
1083 17
            if (ini_get('log_errors') && (intval(ini_get('error_reporting')) & $errCode)) {
1084 17
                Logger::instance()->errorLog($errString);
1085
            }
1086
        } else {
1087
            // Pass control on to previous error handler, trying to avoid loops...
1088
            if (self::$_xmlrpcs_prev_ehandler != array('\PhpXmlRpc\Server', '_xmlrpcs_errorHandler')) {
0 ignored issues
show
introduced by
The condition self::_xmlrpcs_prev_ehan..._xmlrpcs_errorHandler') is always true.
Loading history...
1089
                if (is_array(self::$_xmlrpcs_prev_ehandler)) {
0 ignored issues
show
introduced by
The condition is_array(self::_xmlrpcs_prev_ehandler) is always false.
Loading history...
1090
                    // the following works both with static class methods and plain object methods as error handler
1091
                    call_user_func_array(self::$_xmlrpcs_prev_ehandler, array($errCode, $errString, $filename, $lineNo, $context));
1092
                } else {
1093
                    $method = self::$_xmlrpcs_prev_ehandler;
1094
                    $method($errCode, $errString, $filename, $lineNo, $context);
1095
                }
1096
            }
1097
        }
1098 17
    }
1099
}
1100