Failed Conditions
Pull Request — psr2 (#2426)
by Andreas
09:22 queued 06:07
created

inc/IXR_Library.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * IXR - The Incutio XML-RPC Library
5
 *
6
 * Copyright (c) 2010, Incutio Ltd.
7
 * All rights reserved.
8
 *
9
 * Redistribution and use in source and binary forms, with or without
10
 * modification, are permitted provided that the following conditions are met:
11
 *
12
 *  - Redistributions of source code must retain the above copyright notice,
13
 *    this list of conditions and the following disclaimer.
14
 *  - Redistributions in binary form must reproduce the above copyright
15
 *    notice, this list of conditions and the following disclaimer in the
16
 *    documentation and/or other materials provided with the distribution.
17
 *  - Neither the name of Incutio Ltd. nor the names of its contributors
18
 *    may be used to endorse or promote products derived from this software
19
 *    without specific prior written permission.
20
 *
21
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
22
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
23
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
25
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
26
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
27
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
28
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
29
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
31
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
 *
33
 * @package IXR
34
 * @since 1.5
35
 *
36
 * @copyright  Incutio Ltd 2010 (http://www.incutio.com)
37
 * @version    1.7.4 7th September 2010
38
 * @author     Simon Willison
39
 * @link       http://scripts.incutio.com/xmlrpc/ Site/manual
40
 *
41
 * Modified for DokuWiki
42
 * @author  Andreas Gohr <[email protected]>
43
 */
44
class IXR_Value {
45
46
    /** @var  IXR_Value[]|IXR_Date|IXR_Base64|int|bool|double|string */
47
    var $data;
48
    /** @var string */
49
    var $type;
50
51
    /**
52
     * @param mixed $data
53
     * @param bool $type
54
     */
55
    function __construct($data, $type = false) {
56
        $this->data = $data;
57
        if(!$type) {
58
            $type = $this->calculateType();
59
        }
60
        $this->type = $type;
61
        if($type == 'struct') {
62
            // Turn all the values in the array in to new IXR_Value objects
63
            foreach($this->data as $key => $value) {
64
                $this->data[$key] = new IXR_Value($value);
65
            }
66
        }
67
        if($type == 'array') {
68
            for($i = 0, $j = count($this->data); $i < $j; $i++) {
69
                $this->data[$i] = new IXR_Value($this->data[$i]);
70
            }
71
        }
72
    }
73
74
    /**
75
     * @return string
76
     */
77
    function calculateType() {
78
        if($this->data === true || $this->data === false) {
79
            return 'boolean';
80
        }
81
        if(is_integer($this->data)) {
82
            return 'int';
83
        }
84
        if(is_double($this->data)) {
85
            return 'double';
86
        }
87
88
        // Deal with IXR object types base64 and date
89
        if(is_object($this->data) && is_a($this->data, 'IXR_Date')) {
90
            return 'date';
91
        }
92
        if(is_object($this->data) && is_a($this->data, 'IXR_Base64')) {
93
            return 'base64';
94
        }
95
96
        // If it is a normal PHP object convert it in to a struct
97
        if(is_object($this->data)) {
98
            $this->data = get_object_vars($this->data);
99
            return 'struct';
100
        }
101
        if(!is_array($this->data)) {
102
            return 'string';
103
        }
104
105
        // We have an array - is it an array or a struct?
106
        if($this->isStruct($this->data)) {
107
            return 'struct';
108
        } else {
109
            return 'array';
110
        }
111
    }
112
113
    /**
114
     * @return bool|string
115
     */
116
    function getXml() {
117
        // Return XML for this value
118
        switch($this->type) {
119
            case 'boolean':
120
                return '<boolean>' . (($this->data) ? '1' : '0') . '</boolean>';
121
                break;
122
            case 'int':
123
                return '<int>' . $this->data . '</int>';
124
                break;
125
            case 'double':
126
                return '<double>' . $this->data . '</double>';
127
                break;
128
            case 'string':
129
                return '<string>' . htmlspecialchars($this->data) . '</string>';
130
                break;
131
            case 'array':
132
                $return = '<array><data>' . "\n";
133
                foreach($this->data as $item) {
134
                    $return .= '  <value>' . $item->getXml() . "</value>\n";
135
                }
136
                $return .= '</data></array>';
137
                return $return;
138
                break;
139
            case 'struct':
140
                $return = '<struct>' . "\n";
141
                foreach($this->data as $name => $value) {
142
                    $return .= "  <member><name>$name</name><value>";
143
                    $return .= $value->getXml() . "</value></member>\n";
144
                }
145
                $return .= '</struct>';
146
                return $return;
147
                break;
148
            case 'date':
149
            case 'base64':
150
                return $this->data->getXml();
151
                break;
152
        }
153
        return false;
154
    }
155
156
    /**
157
     * Checks whether or not the supplied array is a struct or not
158
     *
159
     * @param array $array
160
     * @return boolean
161
     */
162
    function isStruct($array) {
163
        $expected = 0;
164
        foreach($array as $key => $value) {
165
            if((string) $key != (string) $expected) {
166
                return true;
167
            }
168
            $expected++;
169
        }
170
        return false;
171
    }
172
}
173
174
/**
175
 * IXR_MESSAGE
176
 *
177
 * @package IXR
178
 * @since 1.5
179
 *
180
 */
181
class IXR_Message {
182
    var $message;
183
    var $messageType; // methodCall / methodResponse / fault
184
    var $faultCode;
185
    var $faultString;
186
    var $methodName;
187
    var $params;
188
189
    // Current variable stacks
190
    var $_arraystructs = array(); // The stack used to keep track of the current array/struct
191
    var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array
192
    var $_currentStructName = array(); // A stack as well
193
    var $_param;
194
    var $_value;
195
    var $_currentTag;
196
    var $_currentTagContents;
197
    var $_lastseen;
198
    // The XML parser
199
    var $_parser;
200
201
    /**
202
     * @param string $message
203
     */
204
    function __construct($message) {
205
        $this->message =& $message;
206
    }
207
208
    /**
209
     * @return bool
210
     */
211
    function parse() {
212
        // first remove the XML declaration
213
        // merged from WP #10698 - this method avoids the RAM usage of preg_replace on very large messages
214
        $header = preg_replace('/<\?xml.*?\?' . '>/', '', substr($this->message, 0, 100), 1);
215
        $this->message = substr_replace($this->message, $header, 0, 100);
216
217
        // workaround for a bug in PHP/libxml2, see http://bugs.php.net/bug.php?id=45996
218
        $this->message = str_replace('&lt;', '&#60;', $this->message);
219
        $this->message = str_replace('&gt;', '&#62;', $this->message);
220
        $this->message = str_replace('&amp;', '&#38;', $this->message);
221
        $this->message = str_replace('&apos;', '&#39;', $this->message);
222
        $this->message = str_replace('&quot;', '&#34;', $this->message);
223
        $this->message = str_replace("\x0b", ' ', $this->message); //vertical tab
224
        if(trim($this->message) == '') {
225
            return false;
226
        }
227
        $this->_parser = xml_parser_create();
228
        // Set XML parser to take the case of tags in to account
229
        xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);
230
        // Set XML parser callback functions
231
        xml_set_object($this->_parser, $this);
232
        xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');
233
        xml_set_character_data_handler($this->_parser, 'cdata');
234
        $chunk_size = 262144; // 256Kb, parse in chunks to avoid the RAM usage on very large messages
235
        $final = false;
236
        do {
237
            if(strlen($this->message) <= $chunk_size) {
238
                $final = true;
239
            }
240
            $part = substr($this->message, 0, $chunk_size);
241
            $this->message = substr($this->message, $chunk_size);
242
            if(!xml_parse($this->_parser, $part, $final)) {
243
                return false;
244
            }
245
            if($final) {
246
                break;
247
            }
248
        } while(true);
249
        xml_parser_free($this->_parser);
250
251
        // Grab the error messages, if any
252
        if($this->messageType == 'fault') {
253
            $this->faultCode = $this->params[0]['faultCode'];
254
            $this->faultString = $this->params[0]['faultString'];
255
        }
256
        return true;
257
    }
258
259
    /**
260
     * @param $parser
261
     * @param string $tag
262
     * @param $attr
263
     */
264
    function tag_open($parser, $tag, $attr) {
265
        $this->_currentTagContents = '';
266
        $this->_currentTag = $tag;
267
268
        switch($tag) {
269
            case 'methodCall':
270
            case 'methodResponse':
271
            case 'fault':
272
                $this->messageType = $tag;
273
                break;
274
            /* Deal with stacks of arrays and structs */
275
            case 'data': // data is to all intents and purposes more interesting than array
276
                $this->_arraystructstypes[] = 'array';
277
                $this->_arraystructs[] = array();
278
                break;
279
            case 'struct':
280
                $this->_arraystructstypes[] = 'struct';
281
                $this->_arraystructs[] = array();
282
                break;
283
        }
284
        $this->_lastseen = $tag;
285
    }
286
287
    /**
288
     * @param $parser
289
     * @param string $cdata
290
     */
291
    function cdata($parser, $cdata) {
292
        $this->_currentTagContents .= $cdata;
293
    }
294
295
    /**
296
     * @param $parser
297
     * @param $tag
298
     */
299
    function tag_close($parser, $tag) {
300
        $value = null;
301
        $valueFlag = false;
302
        switch($tag) {
303
            case 'int':
304
            case 'i4':
305
                $value = (int) trim($this->_currentTagContents);
306
                $valueFlag = true;
307
                break;
308
            case 'double':
309
                $value = (double) trim($this->_currentTagContents);
310
                $valueFlag = true;
311
                break;
312
            case 'string':
313
                $value = (string) $this->_currentTagContents;
314
                $valueFlag = true;
315
                break;
316
            case 'dateTime.iso8601':
317
                $value = new IXR_Date(trim($this->_currentTagContents));
318
                $valueFlag = true;
319
                break;
320
            case 'value':
321
                // "If no type is indicated, the type is string."
322
                if($this->_lastseen == 'value') {
323
                    $value = (string) $this->_currentTagContents;
324
                    $valueFlag = true;
325
                }
326
                break;
327
            case 'boolean':
328
                $value = (boolean) trim($this->_currentTagContents);
329
                $valueFlag = true;
330
                break;
331
            case 'base64':
332
                $value = base64_decode($this->_currentTagContents);
333
                $valueFlag = true;
334
                break;
335
            /* Deal with stacks of arrays and structs */
336
            case 'data':
337
            case 'struct':
338
                $value = array_pop($this->_arraystructs);
339
                array_pop($this->_arraystructstypes);
340
                $valueFlag = true;
341
                break;
342
            case 'member':
343
                array_pop($this->_currentStructName);
344
                break;
345
            case 'name':
346
                $this->_currentStructName[] = trim($this->_currentTagContents);
347
                break;
348
            case 'methodName':
349
                $this->methodName = trim($this->_currentTagContents);
350
                break;
351
        }
352
353
        if($valueFlag) {
354
            if(count($this->_arraystructs) > 0) {
355
                // Add value to struct or array
356
                if($this->_arraystructstypes[count($this->_arraystructstypes) - 1] == 'struct') {
357
                    // Add to struct
358
                    $this->_arraystructs[count($this->_arraystructs) - 1][$this->_currentStructName[count($this->_currentStructName) - 1]] = $value;
359
                } else {
360
                    // Add to array
361
                    $this->_arraystructs[count($this->_arraystructs) - 1][] = $value;
362
                }
363
            } else {
364
                // Just add as a parameter
365
                $this->params[] = $value;
366
            }
367
        }
368
        $this->_currentTagContents = '';
369
        $this->_lastseen = $tag;
370
    }
371
}
372
373
/**
374
 * IXR_Server
375
 *
376
 * @package IXR
377
 * @since 1.5
378
 */
379
class IXR_Server {
380
    var $data;
381
    /** @var array */
382
    var $callbacks = array();
383
    var $message;
384
    /** @var array */
385
    var $capabilities;
386
387
    /**
388
     * @param array|bool $callbacks
389
     * @param bool $data
390
     * @param bool $wait
391
     */
392
    function __construct($callbacks = false, $data = false, $wait = false) {
393
        $this->setCapabilities();
394
        if($callbacks) {
395
            $this->callbacks = $callbacks;
396
        }
397
        $this->setCallbacks();
398
399
        if(!$wait) {
400
            $this->serve($data);
401
        }
402
    }
403
404
    /**
405
     * @param bool|string $data
406
     */
407
    function serve($data = false) {
408
        if(!$data) {
409
410
            $postData = trim(http_get_raw_post_data());
411
            if(!$postData) {
412
                header('Content-Type: text/plain'); // merged from WP #9093
413
                die('XML-RPC server accepts POST requests only.');
414
            }
415
            $data = $postData;
416
        }
417
        $this->message = new IXR_Message($data);
418
        if(!$this->message->parse()) {
419
            $this->error(-32700, 'parse error. not well formed');
420
        }
421
        if($this->message->messageType != 'methodCall') {
422
            $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall');
423
        }
424
        $result = $this->call($this->message->methodName, $this->message->params);
425
426
        // Is the result an error?
427
        if(is_a($result, 'IXR_Error')) {
428
            $this->error($result);
429
        }
430
431
        // Encode the result
432
        $r = new IXR_Value($result);
433
        $resultxml = $r->getXml();
434
435
        // Create the XML
436
        $xml = <<<EOD
437
<methodResponse>
438
  <params>
439
    <param>
440
      <value>
441
        $resultxml
442
      </value>
443
    </param>
444
  </params>
445
</methodResponse>
446
447
EOD;
448
        // Send it
449
        $this->output($xml);
450
    }
451
452
    /**
453
     * @param string $methodname
454
     * @param array $args
455
     * @return IXR_Error|mixed
456
     */
457
    function call($methodname, $args) {
458
        if(!$this->hasMethod($methodname)) {
459
            return new IXR_Error(-32601, 'server error. requested method ' . $methodname . ' does not exist.');
460
        }
461
        $method = $this->callbacks[$methodname];
462
463
        // Perform the callback and send the response
464
465
        # Removed for DokuWiki to have a more consistent interface
466
        #        if (count($args) == 1) {
467
        #            // If only one parameter just send that instead of the whole array
468
        #            $args = $args[0];
469
        #        }
470
471
        # Adjusted for DokuWiki to use call_user_func_array
472
473
        // args need to be an array
474
        $args = (array) $args;
475
476
        // Are we dealing with a function or a method?
477
        if(is_string($method) && substr($method, 0, 5) == 'this:') {
478
            // It's a class method - check it exists
479
            $method = substr($method, 5);
480
            if(!method_exists($this, $method)) {
481
                return new IXR_Error(-32601, 'server error. requested class method "' . $method . '" does not exist.');
482
            }
483
            // Call the method
484
            #$result = $this->$method($args);
485
            $result = call_user_func_array(array(&$this, $method), $args);
486
        } elseif(substr($method, 0, 7) == 'plugin:') {
487
            list($pluginname, $callback) = explode(':', substr($method, 7), 2);
488
            if(!plugin_isdisabled($pluginname)) {
0 ignored issues
show
Deprecated Code introduced by
The function plugin_isdisabled() has been deprecated with message: 2018-07-20

This function has been deprecated. The supplier of the file has supplied an explanatory message.

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

Loading history...
489
                $plugin = plugin_load('action', $pluginname);
0 ignored issues
show
Deprecated Code introduced by
The function plugin_load() has been deprecated with message: 2018-07-20 we will probably keep this around for a long time though

This function has been deprecated. The supplier of the file has supplied an explanatory message.

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

Loading history...
490
                return call_user_func_array(array($plugin, $callback), $args);
491
            } else {
492
                return new IXR_Error(-99999, 'server error');
493
            }
494
        } else {
495
            // It's a function - does it exist?
496
            if(is_array($method)) {
497
                if(!is_callable(array($method[0], $method[1]))) {
498
                    return new IXR_Error(-32601, 'server error. requested object method "' . $method[1] . '" does not exist.');
499
                }
500
            } else if(!function_exists($method)) {
501
                return new IXR_Error(-32601, 'server error. requested function "' . $method . '" does not exist.');
502
            }
503
504
            // Call the function
505
            $result = call_user_func($method, $args);
506
        }
507
        return $result;
508
    }
509
510
    /**
511
     * @param int $error
512
     * @param string|bool $message
513
     */
514
    function error($error, $message = false) {
515
        // Accepts either an error object or an error code and message
516
        if($message && !is_object($error)) {
517
            $error = new IXR_Error($error, $message);
518
        }
519
        $this->output($error->getXml());
520
    }
521
522
    /**
523
     * @param string $xml
524
     */
525
    function output($xml) {
526
        header('Content-Type: text/xml; charset=utf-8');
527
        echo '<?xml version="1.0"?>', "\n", $xml;
528
        exit;
529
    }
530
531
    /**
532
     * @param string $method
533
     * @return bool
534
     */
535
    function hasMethod($method) {
536
        return in_array($method, array_keys($this->callbacks));
537
    }
538
539
    function setCapabilities() {
540
        // Initialises capabilities array
541
        $this->capabilities = array(
542
            'xmlrpc' => array(
543
                'specUrl' => 'http://www.xmlrpc.com/spec',
544
                'specVersion' => 1
545
            ),
546
            'faults_interop' => array(
547
                'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
548
                'specVersion' => 20010516
549
            ),
550
            'system.multicall' => array(
551
                'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
552
                'specVersion' => 1
553
            ),
554
        );
555
    }
556
557
    /**
558
     * @return mixed
559
     */
560
    function getCapabilities() {
561
        return $this->capabilities;
562
    }
563
564
    function setCallbacks() {
565
        $this->callbacks['system.getCapabilities'] = 'this:getCapabilities';
566
        $this->callbacks['system.listMethods'] = 'this:listMethods';
567
        $this->callbacks['system.multicall'] = 'this:multiCall';
568
    }
569
570
    /**
571
     * @return array
572
     */
573
    function listMethods() {
574
        // Returns a list of methods - uses array_reverse to ensure user defined
575
        // methods are listed before server defined methods
576
        return array_reverse(array_keys($this->callbacks));
577
    }
578
579
    /**
580
     * @param array $methodcalls
581
     * @return array
582
     */
583
    function multiCall($methodcalls) {
584
        // See http://www.xmlrpc.com/discuss/msgReader$1208
585
        $return = array();
586
        foreach($methodcalls as $call) {
587
            $method = $call['methodName'];
588
            $params = $call['params'];
589
            if($method == 'system.multicall') {
590
                $result = new IXR_Error(-32800, 'Recursive calls to system.multicall are forbidden');
591
            } else {
592
                $result = $this->call($method, $params);
593
            }
594
            if(is_a($result, 'IXR_Error')) {
595
                $return[] = array(
596
                    'faultCode' => $result->code,
597
                    'faultString' => $result->message
598
                );
599
            } else {
600
                $return[] = array($result);
601
            }
602
        }
603
        return $return;
604
    }
605
}
606
607
/**
608
 * IXR_Request
609
 *
610
 * @package IXR
611
 * @since 1.5
612
 */
613
class IXR_Request {
614
    /** @var string */
615
    var $method;
616
    /** @var array */
617
    var $args;
618
    /** @var string */
619
    var $xml;
620
621
    /**
622
     * @param string $method
623
     * @param array $args
624
     */
625
    function __construct($method, $args) {
626
        $this->method = $method;
627
        $this->args = $args;
628
        $this->xml = <<<EOD
629
<?xml version="1.0"?>
630
<methodCall>
631
<methodName>{$this->method}</methodName>
632
<params>
633
634
EOD;
635
        foreach($this->args as $arg) {
636
            $this->xml .= '<param><value>';
637
            $v = new IXR_Value($arg);
638
            $this->xml .= $v->getXml();
639
            $this->xml .= "</value></param>\n";
640
        }
641
        $this->xml .= '</params></methodCall>';
642
    }
643
644
    /**
645
     * @return int
646
     */
647
    function getLength() {
648
        return strlen($this->xml);
649
    }
650
651
    /**
652
     * @return string
653
     */
654
    function getXml() {
655
        return $this->xml;
656
    }
657
}
658
659
/**
660
 * IXR_Client
661
 *
662
 * @package IXR
663
 * @since 1.5
664
 *
665
 * Changed for DokuWiki to use DokuHTTPClient
666
 *
667
 * This should be compatible to the original class, but uses DokuWiki's
668
 * HTTP client library which will respect proxy settings
669
 *
670
 * Because the XMLRPC client is not used in DokuWiki currently this is completely
671
 * untested
672
 */
673
class IXR_Client extends DokuHTTPClient {
674
    var $posturl = '';
675
    /** @var IXR_Message|bool */
676
    var $message = false;
677
678
    // Storage place for an error message
679
    /** @var IXR_Error|bool */
680
    var $xmlerror = false;
681
682
    /**
683
     * @param string $server
684
     * @param string|bool $path
685
     * @param int $port
686
     * @param int $timeout
687
     */
688
    function __construct($server, $path = false, $port = 80, $timeout = 15) {
689
        parent::__construct();
690
        if(!$path) {
691
            // Assume we have been given a URL instead
692
            $this->posturl = $server;
693
        } else {
694
            $this->posturl = 'http://' . $server . ':' . $port . $path;
695
        }
696
        $this->timeout = $timeout;
697
    }
698
699
    /**
700
     * parameters: method and arguments
701
     * @return bool success or error
702
     */
703
    function query() {
704
        $args = func_get_args();
705
        $method = array_shift($args);
706
        $request = new IXR_Request($method, $args);
707
        $xml = $request->getXml();
708
709
        $this->headers['Content-Type'] = 'text/xml';
710
        if(!$this->sendRequest($this->posturl, $xml, 'POST')) {
711
            $this->xmlerror = new IXR_Error(-32300, 'transport error - ' . $this->error);
712
            return false;
713
        }
714
715
        // Check HTTP Response code
716
        if($this->status < 200 || $this->status > 206) {
717
            $this->xmlerror = new IXR_Error(-32300, 'transport error - HTTP status ' . $this->status);
718
            return false;
719
        }
720
721
        // Now parse what we've got back
722
        $this->message = new IXR_Message($this->resp_body);
723
        if(!$this->message->parse()) {
724
            // XML error
725
            $this->xmlerror = new IXR_Error(-32700, 'parse error. not well formed');
726
            return false;
727
        }
728
729
        // Is the message a fault?
730
        if($this->message->messageType == 'fault') {
731
            $this->xmlerror = new IXR_Error($this->message->faultCode, $this->message->faultString);
732
            return false;
733
        }
734
735
        // Message must be OK
736
        return true;
737
    }
738
739
    /**
740
     * @return mixed
741
     */
742
    function getResponse() {
743
        // methodResponses can only have one param - return that
744
        return $this->message->params[0];
745
    }
746
747
    /**
748
     * @return bool
749
     */
750
    function isError() {
751
        return (is_object($this->xmlerror));
752
    }
753
754
    /**
755
     * @return int
756
     */
757
    function getErrorCode() {
758
        return $this->xmlerror->code;
759
    }
760
761
    /**
762
     * @return string
763
     */
764
    function getErrorMessage() {
765
        return $this->xmlerror->message;
766
    }
767
}
768
769
/**
770
 * IXR_Error
771
 *
772
 * @package IXR
773
 * @since 1.5
774
 */
775
class IXR_Error {
776
    var $code;
777
    var $message;
778
779
    /**
780
     * @param int $code
781
     * @param string $message
782
     */
783
    function __construct($code, $message) {
784
        $this->code = $code;
785
        $this->message = htmlspecialchars($message);
786
    }
787
788
    /**
789
     * @return string
790
     */
791
    function getXml() {
792
        $xml = <<<EOD
793
<methodResponse>
794
  <fault>
795
    <value>
796
      <struct>
797
        <member>
798
          <name>faultCode</name>
799
          <value><int>{$this->code}</int></value>
800
        </member>
801
        <member>
802
          <name>faultString</name>
803
          <value><string>{$this->message}</string></value>
804
        </member>
805
      </struct>
806
    </value>
807
  </fault>
808
</methodResponse>
809
810
EOD;
811
        return $xml;
812
    }
813
}
814
815
/**
816
 * IXR_Date
817
 *
818
 * @package IXR
819
 * @since 1.5
820
 */
821
class IXR_Date {
822
823
    /** @var DateTime */
824
    protected $date;
825
826
    /**
827
     * @param int|string $time
828
     */
829
    public function __construct($time) {
830
        // $time can be a PHP timestamp or an ISO one
831
        if(is_numeric($time)) {
832
            $this->parseTimestamp($time);
833
        } else {
834
            $this->parseIso($time);
835
        }
836
    }
837
838
    /**
839
     * Parse unix timestamp
840
     *
841
     * @param int $timestamp
842
     */
843
    protected function parseTimestamp($timestamp) {
844
        $this->date = new DateTime('@' . $timestamp);
845
    }
846
847
    /**
848
     * Parses less or more complete iso dates and much more, if no timezone given assumes UTC
849
     *
850
     * @param string $iso
851
     */
852
    protected function parseIso($iso) {
853
        $this->date = new DateTime($iso, new DateTimeZone("UTC"));
854
    }
855
856
    /**
857
     * Returns date in ISO 8601 format
858
     *
859
     * @return string
860
     */
861
    public function getIso() {
862
        return $this->date->format(DateTime::ISO8601);
863
    }
864
865
    /**
866
     * Returns date in valid xml
867
     *
868
     * @return string
869
     */
870
    public function getXml() {
871
        return '<dateTime.iso8601>' . $this->getIso() . '</dateTime.iso8601>';
872
    }
873
874
    /**
875
     * Returns Unix timestamp
876
     *
877
     * @return int
878
     */
879
    function getTimestamp() {
880
        return $this->date->getTimestamp();
881
    }
882
}
883
884
/**
885
 * IXR_Base64
886
 *
887
 * @package IXR
888
 * @since 1.5
889
 */
890
class IXR_Base64 {
891
    var $data;
892
893
    /**
894
     * @param string $data
895
     */
896
    function __construct($data) {
897
        $this->data = $data;
898
    }
899
900
    /**
901
     * @return string
902
     */
903
    function getXml() {
904
        return '<base64>' . base64_encode($this->data) . '</base64>';
905
    }
906
}
907
908
/**
909
 * IXR_IntrospectionServer
910
 *
911
 * @package IXR
912
 * @since 1.5
913
 */
914
class IXR_IntrospectionServer extends IXR_Server {
915
    /** @var array[] */
916
    var $signatures;
917
    /** @var string[] */
918
    var $help;
919
920
    /**
921
     * Constructor
922
     */
923
    function __construct() {
924
        $this->setCallbacks();
925
        $this->setCapabilities();
926
        $this->capabilities['introspection'] = array(
927
            'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html',
928
            'specVersion' => 1
929
        );
930
        $this->addCallback(
931
            'system.methodSignature',
932
            'this:methodSignature',
933
            array('array', 'string'),
934
            'Returns an array describing the return type and required parameters of a method'
935
        );
936
        $this->addCallback(
937
            'system.getCapabilities',
938
            'this:getCapabilities',
939
            array('struct'),
940
            'Returns a struct describing the XML-RPC specifications supported by this server'
941
        );
942
        $this->addCallback(
943
            'system.listMethods',
944
            'this:listMethods',
945
            array('array'),
946
            'Returns an array of available methods on this server'
947
        );
948
        $this->addCallback(
949
            'system.methodHelp',
950
            'this:methodHelp',
951
            array('string', 'string'),
952
            'Returns a documentation string for the specified method'
953
        );
954
    }
955
956
    /**
957
     * @param string $method
958
     * @param string $callback
959
     * @param string[] $args
960
     * @param string $help
961
     */
962
    function addCallback($method, $callback, $args, $help) {
963
        $this->callbacks[$method] = $callback;
964
        $this->signatures[$method] = $args;
965
        $this->help[$method] = $help;
966
    }
967
968
    /**
969
     * @param string $methodname
970
     * @param array $args
971
     * @return IXR_Error|mixed
972
     */
973
    function call($methodname, $args) {
974
        // Make sure it's in an array
975
        if($args && !is_array($args)) {
976
            $args = array($args);
977
        }
978
979
        // Over-rides default call method, adds signature check
980
        if(!$this->hasMethod($methodname)) {
981
            return new IXR_Error(-32601, 'server error. requested method "' . $this->message->methodName . '" not specified.');
982
        }
983
        $method = $this->callbacks[$methodname];
984
        $signature = $this->signatures[$methodname];
985
        $returnType = array_shift($signature);
986
        // Check the number of arguments. Check only, if the minimum count of parameters is specified. More parameters are possible.
987
        // This is a hack to allow optional parameters...
988
        if(count($args) < count($signature)) {
989
            // print 'Num of args: '.count($args).' Num in signature: '.count($signature);
990
            return new IXR_Error(-32602, 'server error. wrong number of method parameters');
991
        }
992
993
        // Check the argument types
994
        $ok = true;
995
        $argsbackup = $args;
996
        for($i = 0, $j = count($args); $i < $j; $i++) {
997
            $arg = array_shift($args);
998
            $type = array_shift($signature);
999
            switch($type) {
1000
                case 'int':
1001
                case 'i4':
1002
                    if(is_array($arg) || !is_int($arg)) {
1003
                        $ok = false;
1004
                    }
1005
                    break;
1006
                case 'base64':
1007
                case 'string':
1008
                    if(!is_string($arg)) {
1009
                        $ok = false;
1010
                    }
1011
                    break;
1012
                case 'boolean':
1013
                    if($arg !== false && $arg !== true) {
1014
                        $ok = false;
1015
                    }
1016
                    break;
1017
                case 'float':
1018
                case 'double':
1019
                    if(!is_float($arg)) {
1020
                        $ok = false;
1021
                    }
1022
                    break;
1023
                case 'date':
1024
                case 'dateTime.iso8601':
1025
                    if(!is_a($arg, 'IXR_Date')) {
1026
                        $ok = false;
1027
                    }
1028
                    break;
1029
            }
1030
            if(!$ok) {
1031
                return new IXR_Error(-32602, 'server error. invalid method parameters');
1032
            }
1033
        }
1034
        // It passed the test - run the "real" method call
1035
        return parent::call($methodname, $argsbackup);
1036
    }
1037
1038
    /**
1039
     * @param string $method
1040
     * @return array|IXR_Error
1041
     */
1042
    function methodSignature($method) {
1043
        if(!$this->hasMethod($method)) {
1044
            return new IXR_Error(-32601, 'server error. requested method "' . $method . '" not specified.');
1045
        }
1046
        // We should be returning an array of types
1047
        $types = $this->signatures[$method];
1048
        $return = array();
1049
        foreach($types as $type) {
1050
            switch($type) {
1051
                case 'string':
1052
                    $return[] = 'string';
1053
                    break;
1054
                case 'int':
1055
                case 'i4':
1056
                    $return[] = 42;
1057
                    break;
1058
                case 'double':
1059
                    $return[] = 3.1415;
1060
                    break;
1061
                case 'dateTime.iso8601':
1062
                    $return[] = new IXR_Date(time());
1063
                    break;
1064
                case 'boolean':
1065
                    $return[] = true;
1066
                    break;
1067
                case 'base64':
1068
                    $return[] = new IXR_Base64('base64');
1069
                    break;
1070
                case 'array':
1071
                    $return[] = array('array');
1072
                    break;
1073
                case 'struct':
1074
                    $return[] = array('struct' => 'struct');
1075
                    break;
1076
            }
1077
        }
1078
        return $return;
1079
    }
1080
1081
    /**
1082
     * @param string $method
1083
     * @return mixed
1084
     */
1085
    function methodHelp($method) {
1086
        return $this->help[$method];
1087
    }
1088
}
1089
1090
/**
1091
 * IXR_ClientMulticall
1092
 *
1093
 * @package IXR
1094
 * @since 1.5
1095
 */
1096
class IXR_ClientMulticall extends IXR_Client {
1097
1098
    /** @var array[] */
1099
    var $calls = array();
1100
1101
    /**
1102
     * @param string $server
1103
     * @param string|bool $path
1104
     * @param int $port
1105
     */
1106
    function __construct($server, $path = false, $port = 80) {
1107
        parent::__construct($server, $path, $port);
1108
        //$this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)';
1109
    }
1110
1111
    /**
1112
     * Add a call
1113
     */
1114
    function addCall() {
1115
        $args = func_get_args();
1116
        $methodName = array_shift($args);
1117
        $struct = array(
1118
            'methodName' => $methodName,
1119
            'params' => $args
1120
        );
1121
        $this->calls[] = $struct;
1122
    }
1123
1124
    /**
1125
     * @return bool
1126
     */
1127
    function query() {
1128
        // Prepare multicall, then call the parent::query() method
1129
        return parent::query('system.multicall', $this->calls);
1130
    }
1131
}
1132
1133