| [ Index ] |
PHP Cross Reference of DokuWiki |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * IXR - The Inutio XML-RPC Library - (c) Incutio Ltd 2002 4 * 5 * @version 1.61 6 * @author Simon Willison 7 * @date 11th July 2003 8 * @link http://scripts.incutio.com/xmlrpc/ 9 * @link http://scripts.incutio.com/xmlrpc/manual.php 10 * @license Artistic License http://www.opensource.org/licenses/artistic-license.php 11 * 12 * Modified for DokuWiki 13 * @author Andreas Gohr <andi@splitbrain.org> 14 */ 15 16 17 class IXR_Value { 18 var $data; 19 var $type; 20 function IXR_Value ($data, $type = false) { 21 $this->data = $data; 22 if (!$type) { 23 $type = $this->calculateType(); 24 } 25 $this->type = $type; 26 if ($type == 'struct') { 27 /* Turn all the values in the array in to new IXR_Value objects */ 28 foreach ($this->data as $key => $value) { 29 $this->data[$key] = new IXR_Value($value); 30 } 31 } 32 if ($type == 'array') { 33 for ($i = 0, $j = count($this->data); $i < $j; $i++) { 34 $this->data[$i] = new IXR_Value($this->data[$i]); 35 } 36 } 37 } 38 function calculateType() { 39 if ($this->data === true || $this->data === false) { 40 return 'boolean'; 41 } 42 if (is_integer($this->data)) { 43 return 'int'; 44 } 45 if (is_double($this->data)) { 46 return 'double'; 47 } 48 // Deal with IXR object types base64 and date 49 if (is_object($this->data) && is_a($this->data, 'IXR_Date')) { 50 return 'date'; 51 } 52 if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) { 53 return 'base64'; 54 } 55 // If it is a normal PHP object convert it in to a struct 56 if (is_object($this->data)) { 57 58 $this->data = get_object_vars($this->data); 59 return 'struct'; 60 } 61 if (!is_array($this->data)) { 62 return 'string'; 63 } 64 /* We have an array - is it an array or a struct ? */ 65 if ($this->isStruct($this->data)) { 66 return 'struct'; 67 } else { 68 return 'array'; 69 } 70 } 71 function getXml() { 72 /* Return XML for this value */ 73 switch ($this->type) { 74 case 'boolean': 75 return '<boolean>'.(($this->data) ? '1' : '0').'</boolean>'; 76 break; 77 case 'int': 78 return '<int>'.$this->data.'</int>'; 79 break; 80 case 'double': 81 return '<double>'.$this->data.'</double>'; 82 break; 83 case 'string': 84 return '<string>'.htmlspecialchars($this->data).'</string>'; 85 break; 86 case 'array': 87 $return = '<array><data>'."\n"; 88 foreach ($this->data as $item) { 89 $return .= ' <value>'.$item->getXml()."</value>\n"; 90 } 91 $return .= '</data></array>'; 92 return $return; 93 break; 94 case 'struct': 95 $return = '<struct>'."\n"; 96 foreach ($this->data as $name => $value) { 97 $return .= " <member><name>$name</name><value>"; 98 $return .= $value->getXml()."</value></member>\n"; 99 } 100 $return .= '</struct>'; 101 return $return; 102 break; 103 case 'date': 104 case 'base64': 105 return $this->data->getXml(); 106 break; 107 } 108 return false; 109 } 110 function isStruct($array) { 111 /* Nasty function to check if an array is a struct or not */ 112 $expected = 0; 113 foreach ($array as $key => $value) { 114 if ((string)$key != (string)$expected) { 115 return true; 116 } 117 $expected++; 118 } 119 return false; 120 } 121 } 122 123 124 class IXR_Message { 125 var $message; 126 var $messageType; // methodCall / methodResponse / fault 127 var $faultCode; 128 var $faultString; 129 var $methodName; 130 var $params; 131 // Current variable stacks 132 var $_arraystructs = array(); // The stack used to keep track of the current array/struct 133 var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array 134 var $_currentStructName = array(); // A stack as well 135 var $_param; 136 var $_value; 137 var $_currentTag; 138 var $_currentTagContents; 139 // The XML parser 140 var $_parser; 141 function IXR_Message ($message) { 142 $this->message = $message; 143 } 144 function parse() { 145 // first remove the XML declaration 146 $this->message = preg_replace('/<\?xml(.*)?\?'.'>/', '', $this->message); 147 if (trim($this->message) == '') { 148 return false; 149 } 150 $this->_parser = xml_parser_create(); 151 // Set XML parser to take the case of tags in to account 152 xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false); 153 // Set XML parser callback functions 154 xml_set_object($this->_parser, $this); 155 xml_set_element_handler($this->_parser, 'tag_open', 'tag_close'); 156 xml_set_character_data_handler($this->_parser, 'cdata'); 157 if (!xml_parse($this->_parser, $this->message)) { 158 /* die(sprintf('XML error: %s at line %d', 159 xml_error_string(xml_get_error_code($this->_parser)), 160 xml_get_current_line_number($this->_parser))); */ 161 return false; 162 } 163 xml_parser_free($this->_parser); 164 // Grab the error messages, if any 165 if ($this->messageType == 'fault') { 166 $this->faultCode = $this->params[0]['faultCode']; 167 $this->faultString = $this->params[0]['faultString']; 168 } 169 return true; 170 } 171 function tag_open($parser, $tag, $attr) { 172 $this->currentTag = $tag; 173 switch($tag) { 174 case 'methodCall': 175 case 'methodResponse': 176 case 'fault': 177 $this->messageType = $tag; 178 break; 179 /* Deal with stacks of arrays and structs */ 180 case 'data': // data is to all intents and puposes more interesting than array 181 $this->_arraystructstypes[] = 'array'; 182 $this->_arraystructs[] = array(); 183 break; 184 case 'struct': 185 $this->_arraystructstypes[] = 'struct'; 186 $this->_arraystructs[] = array(); 187 break; 188 } 189 } 190 function cdata($parser, $cdata) { 191 $this->_currentTagContents .= $cdata; 192 } 193 function tag_close($parser, $tag) { 194 $valueFlag = false; 195 switch($tag) { 196 case 'int': 197 case 'i4': 198 $value = (int)trim($this->_currentTagContents); 199 $this->_currentTagContents = ''; 200 $valueFlag = true; 201 break; 202 case 'double': 203 $value = (double)trim($this->_currentTagContents); 204 $this->_currentTagContents = ''; 205 $valueFlag = true; 206 break; 207 case 'string': 208 $value = (string)trim($this->_currentTagContents); 209 $this->_currentTagContents = ''; 210 $valueFlag = true; 211 break; 212 case 'dateTime.iso8601': 213 $value = new IXR_Date(trim($this->_currentTagContents)); 214 // $value = $iso->getTimestamp(); 215 $this->_currentTagContents = ''; 216 $valueFlag = true; 217 break; 218 case 'value': 219 // "If no type is indicated, the type is string." 220 if (trim($this->_currentTagContents) != '') { 221 $value = (string)$this->_currentTagContents; 222 $this->_currentTagContents = ''; 223 $valueFlag = true; 224 } 225 break; 226 case 'boolean': 227 $value = (boolean)trim($this->_currentTagContents); 228 $this->_currentTagContents = ''; 229 $valueFlag = true; 230 break; 231 case 'base64': 232 $value = base64_decode($this->_currentTagContents); 233 $this->_currentTagContents = ''; 234 $valueFlag = true; 235 break; 236 /* Deal with stacks of arrays and structs */ 237 case 'data': 238 case 'struct': 239 $value = array_pop($this->_arraystructs); 240 array_pop($this->_arraystructstypes); 241 $valueFlag = true; 242 break; 243 case 'member': 244 array_pop($this->_currentStructName); 245 break; 246 case 'name': 247 $this->_currentStructName[] = trim($this->_currentTagContents); 248 $this->_currentTagContents = ''; 249 break; 250 case 'methodName': 251 $this->methodName = trim($this->_currentTagContents); 252 $this->_currentTagContents = ''; 253 break; 254 } 255 if ($valueFlag) { 256 /* 257 if (!is_array($value) && !is_object($value)) { 258 $value = trim($value); 259 } 260 */ 261 if (count($this->_arraystructs) > 0) { 262 // Add value to struct or array 263 if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') { 264 // Add to struct 265 $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value; 266 } else { 267 // Add to array 268 $this->_arraystructs[count($this->_arraystructs)-1][] = $value; 269 } 270 } else { 271 // Just add as a paramater 272 $this->params[] = $value; 273 } 274 } 275 } 276 } 277 278 279 class IXR_Server { 280 var $data; 281 var $callbacks = array(); 282 var $message; 283 var $capabilities; 284 function IXR_Server($callbacks = false, $data = false) { 285 $this->setCapabilities(); 286 if ($callbacks) { 287 $this->callbacks = $callbacks; 288 } 289 $this->setCallbacks(); 290 $this->serve($data); 291 } 292 function serve($data = false) { 293 if (!$data) { 294 global $HTTP_RAW_POST_DATA; 295 if (!$HTTP_RAW_POST_DATA) { 296 die('XML-RPC server accepts POST requests only.'); 297 } 298 $data = $HTTP_RAW_POST_DATA; 299 } 300 $this->message = new IXR_Message($data); 301 if (!$this->message->parse()) { 302 $this->error(-32700, 'parse error. not well formed'); 303 } 304 if ($this->message->messageType != 'methodCall') { 305 $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall'); 306 } 307 $result = $this->call($this->message->methodName, $this->message->params); 308 // Is the result an error? 309 if (is_a($result, 'IXR_Error')) { 310 $this->error($result); 311 } 312 // Encode the result 313 $r = new IXR_Value($result); 314 $resultxml = $r->getXml(); 315 // Create the XML 316 $xml = <<<EOD 317 <methodResponse> 318 <params> 319 <param> 320 <value> 321 $resultxml 322 </value> 323 </param> 324 </params> 325 </methodResponse> 326 327 EOD; 328 // Send it 329 $this->output($xml); 330 } 331 function call($methodname, $args) { 332 if (!$this->hasMethod($methodname)) { 333 return new IXR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.'); 334 } 335 $method = $this->callbacks[$methodname]; 336 // Perform the callback and send the response 337 338 # Removed for DokuWiki to have a more consistent interface 339 # if (count($args) == 1) { 340 # // If only one paramater just send that instead of the whole array 341 # $args = $args[0]; 342 # } 343 344 345 # Adjusted for DokuWiki to use call_user_func_array 346 347 // Are we dealing with a function or a method? 348 if (substr($method, 0, 5) == 'this:') { 349 // It's a class method - check it exists 350 $method = substr($method, 5); 351 if (!method_exists($this, $method)) { 352 return new IXR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.'); 353 } 354 // Call the method 355 #$result = $this->$method($args); 356 $result = call_user_func_array(array(&$this,$method),$args); 357 } elseif (substr($method, 0, 7) == 'plugin:') { 358 require_once (DOKU_INC.'inc/pluginutils.php'); 359 list($pluginname, $callback) = explode(':', substr($method, 7), 2); 360 if(!plugin_isdisabled($pluginname)) { 361 $plugin = plugin_load('action', $pluginname); 362 return call_user_func_array(array($plugin, $callback), $args); 363 } else { 364 return new IXR_Error(-99999, 'server error'); 365 } 366 } else { 367 // It's a function - does it exist? 368 if (!function_exists($method)) { 369 return new IXR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.'); 370 } 371 // Call the function 372 #$result = $method($args); 373 $result = call_user_func_array($method,$args); 374 } 375 return $result; 376 } 377 378 function error($error, $message = false) { 379 // Accepts either an error object or an error code and message 380 if ($message && !is_object($error)) { 381 $error = new IXR_Error($error, $message); 382 } 383 $this->output($error->getXml()); 384 } 385 function output($xml) { 386 $xml = '<?xml version="1.0"?>'."\n".$xml; 387 $length = strlen($xml); 388 header('Connection: close'); 389 header('Content-Length: '.$length); 390 header('Content-Type: text/xml'); 391 header('Date: '.date('r')); 392 echo $xml; 393 exit; 394 } 395 function hasMethod($method) { 396 return in_array($method, array_keys($this->callbacks)); 397 } 398 function setCapabilities() { 399 // Initialises capabilities array 400 $this->capabilities = array( 401 'xmlrpc' => array( 402 'specUrl' => 'http://www.xmlrpc.com/spec', 403 'specVersion' => 1 404 ), 405 'faults_interop' => array( 406 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php', 407 'specVersion' => 20010516 408 ), 409 'system.multicall' => array( 410 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208', 411 'specVersion' => 1 412 ), 413 ); 414 } 415 function getCapabilities() { 416 return $this->capabilities; 417 } 418 function setCallbacks() { 419 $this->callbacks['system.getCapabilities'] = 'this:getCapabilities'; 420 $this->callbacks['system.listMethods'] = 'this:listMethods'; 421 $this->callbacks['system.multicall'] = 'this:multiCall'; 422 } 423 function listMethods() { 424 // Returns a list of methods - uses array_reverse to ensure user defined 425 // methods are listed before server defined methods 426 return array_reverse(array_keys($this->callbacks)); 427 } 428 function multiCall($methodcalls) { 429 // See http://www.xmlrpc.com/discuss/msgReader$1208 430 $return = array(); 431 foreach ($methodcalls as $call) { 432 $method = $call['methodName']; 433 $params = $call['params']; 434 if ($method == 'system.multicall') { 435 $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden'); 436 } else { 437 $result = $this->call($method, $params); 438 } 439 if (is_a($result, 'IXR_Error')) { 440 $return[] = array( 441 'faultCode' => $result->code, 442 'faultString' => $result->message 443 ); 444 } else { 445 $return[] = array($result); 446 } 447 } 448 return $return; 449 } 450 } 451 452 class IXR_Request { 453 var $method; 454 var $args; 455 var $xml; 456 function IXR_Request($method, $args) { 457 $this->method = $method; 458 $this->args = $args; 459 $this->xml = <<<EOD 460 <?xml version="1.0"?> 461 <methodCall> 462 <methodName>{$this->method}</methodName> 463 <params> 464 465 EOD; 466 foreach ($this->args as $arg) { 467 $this->xml .= '<param><value>'; 468 $v = new IXR_Value($arg); 469 $this->xml .= $v->getXml(); 470 $this->xml .= "</value></param>\n"; 471 } 472 $this->xml .= '</params></methodCall>'; 473 } 474 function getLength() { 475 return strlen($this->xml); 476 } 477 function getXml() { 478 return $this->xml; 479 } 480 } 481 482 /** 483 * Changed for DokuWiki to use DokuHTTPClient 484 * 485 * This should be compatible to the original class, but uses DokuWiki's 486 * HTTP client library which will respect proxy settings 487 * 488 * Because the XMLRPC client is not used in DokuWiki currently this is completely 489 * untested 490 */ 491 class IXR_Client extends DokuHTTPClient { 492 var $posturl = ''; 493 var $message = false; 494 var $xmlerror = false; 495 496 function IXR_Client($server, $path = false, $port = 80) { 497 $this->DokuHTTPClient(); 498 if (!$path) { 499 // Assume we have been given a URL instead