[ Index ] |
PHP Cross Reference of DokuWiki |
[Summary view] [Print] [Text view]
1 <?php 2 3 use dokuwiki\HTTP\DokuHTTPClient; 4 5 /** 6 * IXR - The Incutio XML-RPC Library 7 * 8 * Copyright (c) 2010, Incutio Ltd. 9 * All rights reserved. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions are met: 13 * 14 * - Redistributions of source code must retain the above copyright notice, 15 * this list of conditions and the following disclaimer. 16 * - Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * - Neither the name of Incutio Ltd. nor the names of its contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 24 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 25 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 26 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 27 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 28 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 29 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 30 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 31 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 33 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 * 35 * @package IXR 36 * @since 1.5 37 * 38 * @copyright Incutio Ltd 2010 (http://www.incutio.com) 39 * @version 1.7.4 7th September 2010 40 * @author Simon Willison 41 * @link http://scripts.incutio.com/xmlrpc/ Site/manual 42 * 43 * Modified for DokuWiki 44 * @author Andreas Gohr <andi@splitbrain.org> 45 */ 46 class IXR_Value { 47 48 /** @var IXR_Value[]|IXR_Date|IXR_Base64|int|bool|double|string */ 49 var $data; 50 /** @var string */ 51 var $type; 52 53 /** 54 * @param mixed $data 55 * @param bool $type 56 */ 57 function __construct($data, $type = false) { 58 $this->data = $data; 59 if(!$type) { 60 $type = $this->calculateType(); 61 } 62 $this->type = $type; 63 if($type == 'struct') { 64 // Turn all the values in the array in to new IXR_Value objects 65 foreach($this->data as $key => $value) { 66 $this->data[$key] = new IXR_Value($value); 67 } 68 } 69 if($type == 'array') { 70 for($i = 0, $j = count($this->data); $i < $j; $i++) { 71 $this->data[$i] = new IXR_Value($this->data[$i]); 72 } 73 } 74 } 75 76 /** 77 * @return string 78 */ 79 function calculateType() { 80 if($this->data === true || $this->data === false) { 81 return 'boolean'; 82 } 83 if(is_integer($this->data)) { 84 return 'int'; 85 } 86 if(is_double($this->data)) { 87 return 'double'; 88 } 89 90 // Deal with IXR object types base64 and date 91 if(is_object($this->data) && is_a($this->data, 'IXR_Date')) { 92 return 'date'; 93 } 94 if(is_object($this->data) && is_a($this->data, 'IXR_Base64')) { 95 return 'base64'; 96 } 97 98 // If it is a normal PHP object convert it in to a struct 99 if(is_object($this->data)) { 100 $this->data = get_object_vars($this->data); 101 return 'struct'; 102 } 103 if(!is_array($this->data)) { 104 return 'string'; 105 } 106 107 // We have an array - is it an array or a struct? 108 if($this->isStruct($this->data)) { 109 return 'struct'; 110 } else { 111 return 'array'; 112 } 113 } 114 115 /** 116 * @return bool|string 117 */ 118 function getXml() { 119 // Return XML for this value 120 switch($this->type) { 121 case 'boolean': 122 return '<boolean>' . (($this->data) ? '1' : '0') . '</boolean>'; 123 break; 124 case 'int': 125 return '<int>' . $this->data . '</int>'; 126 break; 127 case 'double': 128 return '<double>' . $this->data . '</double>'; 129 break; 130 case 'string': 131 return '<string>' . htmlspecialchars($this->data) . '</string>'; 132 break; 133 case 'array': 134 $return = '<array><data>' . "\n"; 135 foreach($this->data as $item) { 136 $return .= ' <value>' . $item->getXml() . "</value>\n"; 137 } 138 $return .= '</data></array>'; 139 return $return; 140 break; 141 case 'struct': 142 $return = '<struct>' . "\n"; 143 foreach($this->data as $name => $value) { 144 $return .= " <member><name>$name</name><value>"; 145 $return .= $value->getXml() . "</value></member>\n"; 146 } 147 $return .= '</struct>'; 148 return $return; 149 break; 150 case 'date': 151 case 'base64': 152 return $this->data->getXml(); 153 break; 154 } 155 return false; 156 } 157 158 /** 159 * Checks whether or not the supplied array is a struct or not 160 * 161 * @param array $array 162 * @return boolean 163 */ 164 function isStruct($array) { 165 $expected = 0; 166 foreach($array as $key => $value) { 167 if((string) $key != (string) $expected) { 168 return true; 169 } 170 $expected++; 171 } 172 return false; 173 } 174 } 175 176 /** 177 * IXR_MESSAGE 178 * 179 * @package IXR 180 * @since 1.5 181 * 182 */ 183 class IXR_Message { 184 var $message; 185 var $messageType; // methodCall / methodResponse / fault 186 var $faultCode; 187 var $faultString; 188 var $methodName; 189 var $params; 190 191 // Current variable stacks 192 var $_arraystructs = array(); // The stack used to keep track of the current array/struct 193 var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array 194 var $_currentStructName = array(); // A stack as well 195 var $_param; 196 var $_value; 197 var $_currentTag; 198 var $_currentTagContents; 199 var $_lastseen; 200 // The XML parser 201 var $_parser; 202 203 /** 204 * @param string $message 205 */ 206 function __construct($message) { 207 $this->message =& $message; 208 } 209 210 /** 211 * @return bool 212 */ 213 function parse() { 214 // first remove the XML declaration 215 // merged from WP #10698 - this method avoids the RAM usage of preg_replace on very large messages 216 $header = preg_replace('/<\?xml.*?\?' . '>/', '', substr($this->message, 0, 100), 1); 217 $this->message = substr_replace($this->message, $header, 0, 100); 218 219 // workaround for a bug in PHP/libxml2, see http://bugs.php.net/bug.php?id=45996 220 $this->message = str_replace('<', '<', $this->message); 221 $this->message = str_replace('>', '>', $this->message); 222 $this->message = str_replace('&', '&', $this->message); 223 $this->message = str_replace(''', ''', $this->message); 224 $this->message = str_replace('"', '"', $this->message); 225 $this->message = str_replace("\x0b", ' ', $this->message); //vertical tab 226 if(trim($this->message) == '') { 227 return false; 228 } 229 $this->_parser = xml_parser_create(); 230 // Set XML parser to take the case of tags in to account 231 xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false); 232 // Set XML parser callback functions 233 xml_set_object($this->_parser, $this); 234 xml_set_element_handler($this->_parser, 'tag_open', 'tag_close'); 235 xml_set_character_data_handler($this->_parser, 'cdata'); 236 $chunk_size = 262144; // 256Kb, parse in chunks to avoid the RAM usage on very large messages 237 $final = false; 238 do { 239 if(strlen($this->message) <= $chunk_size) { 240 $final = true; 241 } 242 $part = substr($this->message, 0, $chunk_size); 243 $this->message = substr($this->message, $chunk_size); 244 if(!xml_parse($this->_parser, $part, $final)) { 245 return false; 246 } 247 if($final) { 248 break; 249 } 250 } while(true); 251 xml_parser_free($this->_parser); 252 253 // Grab the error messages, if any 254 if($this->messageType == 'fault') { 255 $this->faultCode = $this->params[0]['faultCode']; 256 $this->faultString = $this->params[0]['faultString']; 257 } 258 return true; 259 } 260 261 /** 262 * @param $parser 263 * @param string $tag 264 * @param $attr 265 */ 266 function tag_open($parser, $tag, $attr) { 267 $this->_currentTagContents = ''; 268 $this->_currentTag = $tag; 269 270 switch($tag) { 271 case 'methodCall': 272 case 'methodResponse': 273 case 'fault': 274 $this->messageType = $tag; 275 break; 276 /* Deal with stacks of arrays and structs */ 277 case 'data': // data is to all intents and purposes more interesting than array 278 $this->_arraystructstypes[] = 'array'; 279 $this->_arraystructs[] = array(); 280 break; 281 case 'struct': 282 $this->_arraystructstypes[] = 'struct'; 283 $this->_arraystructs[] = array(); 284 break; 285 } 286 $this->_lastseen = $tag; 287 } 288 289 /** 290 * @param $parser 291 * @param string $cdata 292 */ 293 function cdata($parser, $cdata) { 294 $this->_currentTagContents .= $cdata; 295 } 296 297 /** 298 * @param $parser 299 * @param $tag 300 */ 301 function tag_close($parser, $tag) { 302 $value = null; 303 $valueFlag = false; 304 switch($tag) { 305 case 'int': 306 case 'i4': 307 $value = (int) trim($this->_currentTagContents); 308 $valueFlag = true; 309 break; 310 case 'double': 311 $value = (double) trim($this->_currentTagContents); 312 $valueFlag = true; 313 break; 314 case 'string': 315 $value = (string) $this->_currentTagContents; 316 $valueFlag = true; 317 break; 318 case 'dateTime.iso8601': 319 $value = new IXR_Date(trim($this->_currentTagContents)); 320 $valueFlag = true; 321 break; 322 case 'value': 323 // "If no type is indicated, the type is string." 324 if($this->_lastseen == 'value') { 325 $value = (string) $this->_currentTagContents; 326 $valueFlag = true; 327 } 328 break; 329 case 'boolean': 330 $value = (boolean) trim($this->_currentTagContents); 331 $valueFlag = true; 332 break; 333 case 'base64': 334 $value = base64_decode($this->_currentTagContents); 335 $valueFlag = true; 336 break; 337 /* Deal with stacks of arrays and structs */ 338 case 'data': 339 case 'struct': 340 $value = array_pop($this->_arraystructs); 341 array_pop($this->_arraystructstypes); 342 $valueFlag = true; 343 break; 344 case 'member': 345 array_pop($this->_currentStructName); 346 break; 347 case 'name': 348 $this->_currentStructName[] = trim($this->_currentTagContents); 349 break; 350 case 'methodName': 351 $this->methodName = trim($this->_currentTagContents); 352 break; 353 } 354 355 if($valueFlag) { 356 if(count($this->_arraystructs) > 0) { 357 // Add value to struct or array 358 if($this->_arraystructstypes[count($this->_arraystructstypes) - 1] == 'struct') { 359 // Add to struct 360 $this->_arraystructs[count($this->_arraystructs) - 1][$this->_currentStructName[count($this->_currentStructName) - 1]] = $value; 361 } else { 362 // Add to array 363 $this->_arraystructs[count($this->_arraystructs) - 1][] = $value; 364 } 365 } else { 366 // Just add as a parameter 367 $this->params[] = $value; 368 } 369 } 370 $this->_currentTagContents = ''; 371 $this->_lastseen = $tag; 372 } 373 } 374 375 /** 376 * IXR_Server 377 * 378 * @package IXR 379 * @since 1.5 380 */ 381 class IXR_Server { 382 var $data; 383 /** @var array */ 384 var $callbacks = array(); 385 var $message; 386 /** @var array */ 387 var $capabilities; 388 389 /** 390 * @param array|bool $callbacks 391 * @param bool $data 392 * @param bool $wait 393 */ 394 function __construct($callbacks = false, $data = false, $wait = false) { 395 $this->setCapabilities(); 396 if($callbacks) { 397 $this->callbacks = $callbacks; 398 } 399 $this->setCallbacks(); 400 401 if(!$wait) { 402 $this->serve($data); 403 } 404 } 405 406 /** 407 * @param bool|string $data 408 */ 409 function serve($data = false) { 410 if(!$data) { 411 412 $postData = trim(http_get_raw_post_data()); 413 if(!$postData) { 414 header('Content-Type: text/plain'); // merged from WP #9093 415 die('XML-RPC server accepts POST requests only.'); 416 } 417 $data = $postData; 418 } 419 $this->message = new IXR_Message($data); 420 if(!$this->message->parse()) { 421 $this->error(-32700, 'parse error. not well formed'); 422 } 423 if($this->message->messageType != 'methodCall') { 424 $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall'); 425 } 426 $result = $this->call($this->message->methodName, $this->message->params); 427 428 // Is the result an error? 429 if(is_a($result, 'IXR_Error')) { 430 $this->error($result); 431 } 432 433 // Encode the result 434 $r = new IXR_Value($result); 435 $resultxml = $r->getXml(); 436 437 // Create the XML 438 $xml = <<<EOD 439 <methodResponse> 440 <params> 441 <param> 442 <value> 443 $resultxml 444 </value> 445 </param> 446 </params> 447 </methodResponse> 448 449 EOD; 450 // Send it 451 $this->output($xml); 452 } 453 454 /** 455 * @param string $methodname 456 * @param array $args 457 * @return IXR_Error|mixed 458 */ 459 function call($methodname, $args) { 460 if(!$this->hasMethod($methodname)) { 461 return new IXR_Error(-32601, 'server error. requested method ' . $methodname . ' does not exist.'); 462 } 463 $method = $this->callbacks[$methodname]; 464 465 // Perform the callback and send the response 466 467 # Removed for DokuWiki to have a more consistent interface 468 # if (count($args) == 1) { 469 # // If only one parameter just send that instead of the whole array 470 # $args = $args[0]; 471 # } 472 473 # Adjusted for DokuWiki to use call_user_func_array 474 475 // args need to be an array 476 $args = (array) $args; 477 478 // Are we dealing with a function or a method? 479 if(is_string($method) && substr($method, 0, 5) == 'this:') { 480 // It's a class method - check it exists 481 $method = substr($method, 5); 482 if(!method_exists($this, $method)) { 483 return new IXR_Error(-32601, 'server error. requested class method "' . $method . '" does not exist.'); 484 } 485 // Call the method 486 #$result = $this->$method($args); 487 $result = call_user_func_array(array(&$this, $method), $args); 488 } elseif(substr($method, 0, 7) == 'plugin:') { 489 list($pluginname, $callback) = explode(':', substr($method, 7), 2); 490 if(!plugin_isdisabled($pluginname)) { 491 $plugin = plugin_load('action', $pluginname); 492 return call_user_func_array(array($plugin, $callback), $args); 493 } else { 494 return new IXR_Error(-99999, 'server error'); 495 } 496 } else { 497 // It's a function - does it exist? 498 if(is_array($method)) { 499 if(!is_callable(array($method[0], $method[1]))) { 500 return new IXR_Error(-32601, 'server error. requested object method "' . $method[1] . '" does not exist.'); 501 } 502 } else if(!function_exists($method)) { 503 return new IXR_Error(-32601, 'server error. requested function "' . $method . '" does not exist.'); 504 } 505 506 // Call the function 507 $result = call_user_func($method, $args); 508 } 509 return $result; 510 } 511 512 /** 513 * @param int $error 514 * @param string|bool $message 515 */ 516 function error($error, $message = false) { 517 // Accepts either an error object or an error code and message 518 if($message && !is_object($error)) { 519 $error = new IXR_Error($error, $message); 520 } 521 $this->output($error->getXml()); 522 } 523 524 /** 525 * @param string $xml 526 */ 527 function output($xml) { 528 header('Content-Type: text/xml; charset=utf-8'); 529 echo '<?xml version="1.0"?>', "\n", $xml; 530 exit; 531 } 532 533 /** 534 * @param string $method 535 * @return bool 536 */ 537 function hasMethod($method) { 538 return in_array($method, array_keys($this->callbacks)); 539 } 540 541 function setCapabilities() { 542 // Initialises capabilities array 543 $this->capabilities = array( 544 'xmlrpc' => array( 545 'specUrl' => 'http://www.xmlrpc.com/spec', 546 'specVersion' => 1 547 ), 548 'faults_interop' => array( 549 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php', 550 'specVersion' => 20010516 551 ), 552 'system.multicall' => array( 553 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208', 554 'specVersion' => 1 555 ), 556 ); 557 } 558 559 /** 560 * @return mixed 561 */ 562 function getCapabilities() { 563 return $this->capabilities; 564 } 565 566 function setCallbacks() { 567 $this->callbacks['system.getCapabilities'] = 'this:getCapabilities'; 568 $this->callbacks['system.listMethods'] = 'this:listMethods'; 569 $this->callbacks['system.multicall'] = 'this:multiCall'; 570 } 571 572 /** 573 * @return array 574 */ 575 function listMethods() { 576 // Returns a list of methods - uses array_reverse to ensure user defined 577 // methods are listed before server defined methods 578 return array_reverse(array_keys($this->callbacks)); 579 } 580 581 /** 582 * @param array $methodcalls 583 * @return array 584 */ 585 function multiCall($methodcalls) { 586 // See http://www.xmlrpc.com/discuss/msgReader$1208 587 $return = array(); 588 foreach($methodcalls as $call) { 589 $method = $call['methodName']; 590 $params = $call['params']; 591 if($method == 'system.multicall') { 592 $result = new IXR_Error(-32800, 'Recursive calls to system.multicall are forbidden'); 593 } else { 594 $result = $this->call($method, $params); 595 } 596 if(is_a($result, 'IXR_Error')) { 597 $return[] = array( 598 'faultCode' => $result->code, 599 'faultString' => $result->message 600 ); 601 } else { 602 $return[] = array($result); 603 } 604 } 605 return $return; 606 } 607 } 608 609 /** 610 * IXR_Request 611 * 612 * @package IXR 613 * @since 1.5 614 */ 615 class IXR_Request { 616 /** @var string */ 617 var $method; 618 /** @var array */ 619 var $args; 620 /** @var string */ 621 var $xml; 622 623 /** 624 * @param string $method 625 * @param array $args 626 */ 627 function __construct($method, $args) { 628 $this->method = $method; 629 $this->args = $args; 630 $this->xml = <<<EOD 631 <?xml version="1.0"?> 632 <methodCall> 633 <methodName>{$this->method}</methodName> 634 <params> 635 636 EOD; 637 foreach($this->args as $arg) { 638 $this->xml .= '<param><value>'; 639 $v = new IXR_Value($arg); 640 $this->xml .= $v->getXml(); 641 $this->xml .= "</value></param>\n"; 642 } 643 $this->xml .= '</params></methodCall>'; 644 } 645 646 /** 647 * @return int 648 */ 649 function getLength() { 650 return strlen($this->xml); 651 } 652 653 /** 654 * @return string 655 */ 656 function getXml() { 657 return $this->xml; 658 } 659 } 660 661 /** 662 * IXR_Client 663 * 664 * @package IXR 665 * @since 1.5 666 * 667 * Changed for DokuWiki to use DokuHTTPClient 668 * 669 * This should be compatible to the original class, but uses DokuWiki's 670 * HTTP client library which will respect proxy settings 671 * 672 * Because the XMLRPC client is not used in DokuWiki currently this is completely 673 * untested 674 */ 675 class IXR_Client extends DokuHTTPClient { 676 var $posturl = ''; 677 /** @var IXR_Message|bool */ 678 var $message = false; 679 680 // Storage place for an error message 681 /** @var IXR_Error|bool */ 682 var $xmlerror = false; 683 684 /** 685 * @param string $server 686 * @param string|bool $path 687 * @param int $port 688 * @param int $timeout 689 */ 690 function __construct($server, $path = false, $port = 80, $timeout = 15) { 691 parent::__construct(); 692 if(!$path) { 693 // Assume we have been given a URL instead 694 $this->posturl = $server; 695 } else { 696 $this->posturl = 'http://' . $server . ':' . $port . $path; 697 } 698 $this->timeout = $timeout; 699 } 700 701 /** 702 * parameters: method and arguments 703 * @return bool success or error 704 */ 705 function query() { 706 $args = func_get_args(); 707 $method = array_shift($args); 708 $request = new IXR_Request($method, $args); 709 $xml = $request->getXml(); 710 711 $this->headers['Content-Type'] = 'text/xml'; 712 if(!$this->sendRequest($this->posturl, $xml, 'POST')) { 713 $this->xmlerror = new IXR_Error(-32300, 'transport error - ' . $this->error); 714 return false; 715 } 716 717 // Check HTTP Response code 718 if($this->status < 200 || $this->status > 206) { 719 $this->xmlerror = new IXR_Error(-32300, 'transport error - HTTP status ' . $this->status); 720 return false; 721 } 722 723 // Now parse what we've got back 724 $this->message = new IXR_Message($this->resp_body); 725 if(!$this->message->parse()) { 726 // XML error 727 $this->xmlerror = new IXR_Error(-32700, 'parse error. not well formed'); 728 return false; 729 } 730 731 // Is the message a fault? 732 if($this->message->messageType == 'fault') { 733 $this->xmlerror = new IXR_Error($this->message->faultCode, $this->message->faultString); 734 return false; 735 } 736 737 // Message must be OK 738 return true; 739 } 740 741 /** 742 * @return mixed 743 */ 744 function getResponse() { 745 // methodResponses can only have one param - return that 746 return $this->message->params[0]; 747 } 748 749 /** 750 * @return bool 751 */ 752 function isError() { 753 return (is_object($this->xmlerror)); 754 } 755 756 /** 757 * @return int 758 */ 759 function getErrorCode() { 760 return $this->xmlerror->code; 761 } 762 763 /** 764 * @return string 765 */ 766 function getErrorMessage() { 767 return $this->xmlerror->message; 768 } 769 } 770 771 /** 772 * IXR_Error 773 * 774 * @package IXR 775 * @since 1.5 776 */ 777 class IXR_Error { 778 var $code; 779 var $message; 780 781 /** 782 * @param int $code 783 * @param string $message 784 */ 785 function __construct($code, $message) { 786 $this->code = $code; 787 $this->message = htmlspecialchars($message); 788 } 789 790 /** 791 * @return string 792 */ 793 function getXml() { 794 $xml = <<<EOD 795 <methodResponse> 796 <fault> 797 <value> 798 <struct> 799 <member> 800 <name>faultCode</name> 801 <value><int>{$this->code}</int></value> 802 </member> 803 <member> 804 <name>faultString</name> 805 <value><string>{$this->message}</string></value> 806 </member> 807 </struct> 808 </value> 809 </fault> 810 </methodResponse> 811 812 EOD; 813 return $xml; 814 } 815 } 816 817 /** 818 * IXR_Date 819 * 820 * @package IXR 821 * @since 1.5 822 */ 823 class IXR_Date { 824 825 const XMLRPC_ISO8601 = "Ymd\TH:i:sO" ; 826 /** @var DateTime */ 827 protected $date; 828 829 /** 830 * @param int|string $time 831 */ 832 public function __construct($time) { 833 // $time can be a PHP timestamp or an ISO one 834 if(is_numeric($time)) { 835 $this->parseTimestamp($time); 836 } else { 837 $this->parseIso($time); 838 } 839 } 840 841 /** 842 * Parse unix timestamp 843 * 844 * @param int $timestamp 845 */ 846 protected function parseTimestamp($timestamp) { 847 $this->date = new DateTime('@' . $timestamp); 848 } 849 850 /** 851 * Parses less or more complete iso dates and much more, if no timezone given assumes UTC 852 * 853 * @param string $iso 854 */ 855 protected function parseIso($iso) { 856 $this->date = new DateTime($iso, new DateTimeZone("UTC")); 857 } 858 859 /** 860 * Returns date in ISO 8601 format 861 * 862 * @return string 863 */ 864 public function getIso() { 865 return $this->date->format(self::XMLRPC_ISO8601); 866 } 867 868 /** 869 * Returns date in valid xml 870 * 871 * @return string 872 */ 873 public function getXml() { 874 return '<dateTime.iso8601>' . $this->getIso() . '</dateTime.iso8601>'; 875 } 876 877 /** 878 * Returns Unix timestamp 879 * 880 * @return int 881 */ 882 function getTimestamp() { 883 return $this->date->getTimestamp(); 884 } 885 } 886 887 /** 888 * IXR_Base64 889 * 890 * @package IXR 891 * @since 1.5 892 */ 893 class IXR_Base64 { 894 var $data; 895 896 /** 897 * @param string $data 898 */ 899 function __construct($data) { 900 $this->data = $data; 901 } 902 903 /** 904 * @return string 905 */ 906 function getXml() { 907 return '<base64>' . base64_encode($this->data) . '</base64>'; 908 } 909 } 910 911 /** 912 * IXR_IntrospectionServer 913 * 914 * @package IXR 915 * @since 1.5 916 */ 917 class IXR_IntrospectionServer extends IXR_Server { 918 /** @var array[] */ 919 var $signatures; 920 /** @var string[] */ 921 var $help; 922 923 /** 924 * Constructor 925 */ 926 function __construct() { 927 $this->setCallbacks(); 928 $this->setCapabilities(); 929 $this->capabilities['introspection'] = array( 930 'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html', 931 'specVersion' => 1 932 ); 933 $this->addCallback( 934 'system.methodSignature', 935 'this:methodSignature', 936 array('array', 'string'), 937 'Returns an array describing the return type and required parameters of a method' 938 ); 939 $this->addCallback( 940 'system.getCapabilities', 941 'this:getCapabilities', 942 array('struct'), 943 'Returns a struct describing the XML-RPC specifications supported by this server' 944 ); 945 $this->addCallback( 946 'system.listMethods', 947 'this:listMethods', 948 array('array'), 949 'Returns an array of available methods on this server' 950 ); 951 $this->addCallback( 952 'system.methodHelp', 953 'this:methodHelp', 954 array('string', 'string'), 955 'Returns a documentation string for the specified method' 956 ); 957 } 958 959 /** 960 * @param string $method 961 * @param string $callback 962 * @param string[] $args 963 * @param string $help 964 */ 965 function addCallback($method, $callback, $args, $help) { 966 $this->callbacks[$method] = $callback; 967 $this->signatures[$method] = $args; 968 $this->help[$method] = $help; 969 } 970 971 /** 972 * @param string $methodname 973 * @param array $args 974 * @return IXR_Error|mixed 975 */ 976 function call($methodname, $args) { 977 // Make sure it's in an array 978 if($args && !is_array($args)) { 979 $args = array($args); 980 } 981 982 // Over-rides default call method, adds signature check 983 if(!$this->hasMethod($methodname)) { 984 return new IXR_Error(-32601, 'server error. requested method "' . $this->message->methodName . '" not specified.'); 985 } 986 $method = $this->callbacks[$methodname]; 987 $signature = $this->signatures[$methodname]; 988 $returnType = array_shift($signature); 989 // Check the number of arguments. Check only, if the minimum count of parameters is specified. More parameters are possible. 990 // This is a hack to allow optional parameters... 991 if(count($args) < count($signature)) { 992 // print 'Num of args: '.count($args).' Num in signature: '.count($signature); 993 return new IXR_Error(-32602, 'server error. wrong number of method parameters'); 994 } 995 996 // Check the argument types 997 $ok = true; 998 $argsbackup = $args; 999 for($i = 0, $j = count($args); $i < $j; $i++) { 1000 $arg = array_shift($args); 1001 $type = array_shift($signature); 1002 switch($type) { 1003 case 'int': 1004 case 'i4': 1005 if(is_array($arg) || !is_int($arg)) { 1006 $ok = false; 1007 } 1008 break; 1009 case 'base64': 1010 case 'string': 1011 if(!is_string($arg)) { 1012 $ok = false; 1013 } 1014 break; 1015 case 'boolean': 1016 if($arg !== false && $arg !== true) { 1017 $ok = false; 1018 } 1019 break; 1020 case 'float': 1021 case 'double': 1022 if(!is_float($arg)) { 1023 $ok = false; 1024 } 1025 break; 1026 case 'date': 1027 case 'dateTime.iso8601': 1028 if(!is_a($arg, 'IXR_Date')) { 1029 $ok = false; 1030 } 1031 break; 1032 } 1033 if(!$ok) { 1034 return new IXR_Error(-32602, 'server error. invalid method parameters'); 1035 } 1036 } 1037 // It passed the test - run the "real" method call 1038 return parent::call($methodname, $argsbackup); 1039 } 1040 1041 /** 1042 * @param string $method 1043 * @return array|IXR_Error 1044 */ 1045 function methodSignature($method) { 1046 if(!$this->hasMethod($method)) { 1047 return new IXR_Error(-32601, 'server error. requested method "' . $method . '" not specified.'); 1048 } 1049 // We should be returning an array of types 1050 $types = $this->signatures[$method]; 1051 $return = array(); 1052 foreach($types as $type) { 1053 switch($type) { 1054 case 'string': 1055 $return[] = 'string'; 1056 break; 1057 case 'int': 1058 case 'i4': 1059 $return[] = 42; 1060 break; 1061 case 'double': 1062 $return[] = 3.1415; 1063 break; 1064 case 'dateTime.iso8601': 1065 $return[] = new IXR_Date(time()); 1066 break; 1067 case 'boolean': 1068 $return[] = true; 1069 break; 1070 case 'base64': 1071 $return[] = new IXR_Base64('base64'); 1072 break; 1073 case 'array': 1074 $return[] = array('array'); 1075 break; 1076 case 'struct': 1077 $return[] = array('struct' => 'struct'); 1078 break; 1079 } 1080 } 1081 return $return; 1082 } 1083 1084 /** 1085 * @param string $method 1086 * @return mixed 1087 */ 1088 function methodHelp($method) { 1089 return $this->help[$method]; 1090 } 1091 } 1092 1093 /** 1094 * IXR_ClientMulticall 1095 * 1096 * @package IXR 1097 * @since 1.5 1098 */ 1099 class IXR_ClientMulticall extends IXR_Client { 1100 1101 /** @var array[] */ 1102 var $calls = array(); 1103 1104 /** 1105 * @param string $server 1106 * @param string|bool $path 1107 * @param int $port 1108 */ 1109 function __construct($server, $path = false, $port = 80) { 1110 parent::__construct($server, $path, $port); 1111 //$this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)'; 1112 } 1113 1114 /** 1115 * Add a call 1116 */ 1117 function addCall() { 1118 $args = func_get_args(); 1119 $methodName = array_shift($args); 1120 $struct = array( 1121 'methodName' => $methodName, 1122 'params' => $args 1123 ); 1124 $this->calls[] = $struct; 1125 } 1126 1127 /** 1128 * @return bool 1129 */ 1130 function query() { 1131 // Prepare multicall, then call the parent::query() method 1132 return parent::query('system.multicall', $this->calls); 1133 } 1134 } 1135
title
Description
Body
title
Description
Body
title
Description
Body
title
Body