1<?php 2if(!defined('DOKU_INC')) define('DOKU_INC',dirname(__FILE__).'/../../'); 3 4// fix when '<?xml' isn't on the very first line 5if(isset($HTTP_RAW_POST_DATA)) $HTTP_RAW_POST_DATA = trim($HTTP_RAW_POST_DATA); 6 7 8require_once(DOKU_INC.'inc/init.php'); 9require_once(DOKU_INC.'inc/common.php'); 10require_once(DOKU_INC.'inc/auth.php'); 11session_write_close(); //close session 12 13if(!$conf['xmlrpc']) { 14 die('XML-RPC server not enabled.'); 15 // FIXME check for groups allowed 16} 17 18require_once(DOKU_INC.'inc/IXR_Library.php'); 19 20 21/** 22 * Contains needed wrapper functions and registers all available 23 * XMLRPC functions. 24 */ 25class dokuwiki_xmlrpc_server extends IXR_IntrospectionServer { 26 var $methods = array(); 27 28 /** 29 * Constructor. Register methods and run Server 30 */ 31 function dokuwiki_xmlrpc_server(){ 32 $this->IXR_IntrospectionServer(); 33 34 /* DokuWiki's own methods */ 35 $this->addCallback( 36 'dokuwiki.getVersion', 37 'getVersion', 38 array('string'), 39 'Returns the running DokuWiki version.' 40 ); 41 42 $this->addCallback( 43 'dokuwiki.getPagelist', 44 'this:readNamespace', 45 array('struct','string','struct'), 46 'List all pages within the given namespace.' 47 ); 48 49 $this->addCallback( 50 'dokuwiki.getTime', 51 'time', 52 array('int'), 53 'Return the current time at the wiki server.' 54 ); 55 56 /* Wiki API v2 http://www.jspwiki.org/wiki/WikiRPCInterface2 */ 57 $this->addCallback( 58 'wiki.getRPCVersionSupported', 59 'this:wiki_RPCVersion', 60 array('int'), 61 'Returns 2 with the supported RPC API version.' 62 ); 63 $this->addCallback( 64 'wiki.getPage', 65 'this:rawPage', 66 array('string','string'), 67 'Get the raw Wiki text of page, latest version.' 68 ); 69 $this->addCallback( 70 'wiki.getPageVersion', 71 'this:rawPage', 72 array('string','string','int'), 73 'Get the raw Wiki text of page.' 74 ); 75 $this->addCallback( 76 'wiki.getPageHTML', 77 'this:htmlPage', 78 array('string','string'), 79 'Return page in rendered HTML, latest version.' 80 ); 81 $this->addCallback( 82 'wiki.getPageHTMLVersion', 83 'this:htmlPage', 84 array('string','string','int'), 85 'Return page in rendered HTML.' 86 ); 87 $this->addCallback( 88 'wiki.getAllPages', 89 'this:listPages', 90 array('struct'), 91 'Returns a list of all pages. The result is an array of utf8 pagenames.' 92 ); 93 $this->addCallback( 94 'wiki.getAttachments', 95 'this:listAttachments', 96 array('struct', 'string', 'struct'), 97 'Returns a list of all media files.' 98 ); 99 $this->addCallback( 100 'wiki.getBackLinks', 101 'this:listBackLinks', 102 array('struct','string'), 103 'Returns the pages that link to this page.' 104 ); 105 $this->addCallback( 106 'wiki.getPageInfo', 107 'this:pageInfo', 108 array('struct','string'), 109 'Returns a struct with infos about the page.' 110 ); 111 $this->addCallback( 112 'wiki.getPageInfoVersion', 113 'this:pageInfo', 114 array('struct','string','int'), 115 'Returns a struct with infos about the page.' 116 ); 117 $this->addCallback( 118 'wiki.getPageVersions', 119 'this:pageVersions', 120 array('struct','string','int'), 121 'Returns the available revisions of the page.' 122 ); 123 $this->addCallback( 124 'wiki.putPage', 125 'this:putPage', 126 array('int', 'string', 'string', 'struct'), 127 'Saves a wiki page.' 128 ); 129 $this->addCallback( 130 'wiki.listLinks', 131 'this:listLinks', 132 array('struct','string'), 133 'Lists all links contained in a wiki page.' 134 ); 135 $this->addCallback( 136 'wiki.getRecentChanges', 137 'this:getRecentChanges', 138 array('struct','int'), 139 'Returns a struct about all recent changes since given timestamp.' 140 ); 141 $this->addCallback( 142 'wiki.getRecentMediaChanges', 143 'this:getRecentMediaChanges', 144 array('struct','int'), 145 'Returns a struct about all recent media changes since given timestamp.' 146 ); 147 $this->addCallback( 148 'wiki.aclCheck', 149 'this:aclCheck', 150 array('int', 'string'), 151 'Returns the permissions of a given wiki page.' 152 ); 153 $this->addCallback( 154 'wiki.putAttachment', 155 'this:putAttachment', 156 array('struct', 'string', 'base64', 'struct'), 157 'Upload a file to the wiki.' 158 ); 159 $this->addCallback( 160 'wiki.deleteAttachment', 161 'this:deleteAttachment', 162 array('int', 'string'), 163 'Delete a file from the wiki.' 164 ); 165 $this->addCallback( 166 'wiki.getAttachment', 167 'this:getAttachment', 168 array('base64', 'string'), 169 'Download a file from the wiki.' 170 ); 171 $this->addCallback( 172 'wiki.getAttachmentInfo', 173 'this:getAttachmentInfo', 174 array('struct', 'string'), 175 'Returns a struct with infos about the attachment.' 176 ); 177 178 /** 179 * Trigger XMLRPC_CALLBACK_REGISTER, action plugins can use this event 180 * to extend the XMLRPC interface and register their own callbacks. 181 * 182 * Event data: 183 * The XMLRPC server object: 184 * 185 * $event->data->addCallback() - register a callback, the second 186 * paramter has to be of the form "plugin:<pluginname>:<plugin 187 * method>" 188 * 189 * $event->data->callbacks - an array which holds all awaylable 190 * callbacks 191 */ 192 trigger_event('XMLRPC_CALLBACK_REGISTER', $this); 193 194 $this->serve(); 195 } 196 197 /** 198 * Return a raw wiki page 199 */ 200 function rawPage($id,$rev=''){ 201 if(auth_quickaclcheck($id) < AUTH_READ){ 202 return new IXR_Error(1, 'You are not allowed to read this page'); 203 } 204 $text = rawWiki($id,$rev); 205 if(!$text) { 206 $data = array($id); 207 return trigger_event('HTML_PAGE_FROMTEMPLATE',$data,'pageTemplate',true); 208 } else { 209 return $text; 210 } 211 } 212 213 /** 214 * Return a media file encoded in base64 215 * 216 * @author Gina Haeussge <osd@foosel.net> 217 */ 218 function getAttachment($id){ 219 $id = cleanID($id); 220 if (auth_quickaclcheck(getNS($id).':*') < AUTH_READ) 221 return new IXR_Error(1, 'You are not allowed to read this file'); 222 223 $file = mediaFN($id); 224 if (!@ file_exists($file)) 225 return new IXR_Error(1, 'The requested file does not exist'); 226 227 $data = io_readFile($file, false); 228 $base64 = base64_encode($data); 229 return $base64; 230 } 231 232 /** 233 * Return info about a media file 234 * 235 * @author Gina Haeussge <osd@foosel.net> 236 */ 237 function getAttachmentInfo($id){ 238 $id = cleanID($id); 239 $info = array( 240 'lastModified' => 0, 241 'size' => 0, 242 ); 243 244 $file = mediaFN($id); 245 if ((auth_quickaclcheck(getNS($id).':*') >= AUTH_READ) && file_exists($file)){ 246 $info['lastModified'] = new IXR_Date(filemtime($file)); 247 $info['size'] = filesize($file); 248 } 249 250 return $info; 251 } 252 253 /** 254 * Return a wiki page rendered to html 255 */ 256 function htmlPage($id,$rev=''){ 257 if(auth_quickaclcheck($id) < AUTH_READ){ 258 return new IXR_Error(1, 'You are not allowed to read this page'); 259 } 260 return p_wiki_xhtml($id,$rev,false); 261 } 262 263 /** 264 * List all pages - we use the indexer list here 265 */ 266 function listPages(){ 267 global $conf; 268 269 $list = array(); 270 $pages = file($conf['indexdir'] . '/page.idx'); 271 $pages = array_filter($pages, 'isVisiblePage'); 272 273 foreach(array_keys($pages) as $idx) { 274 if(page_exists($pages[$idx])) { 275 $perm = auth_quickaclcheck($pages[$idx]); 276 if($perm >= AUTH_READ) { 277 $page = array(); 278 $page['id'] = trim($pages[$idx]); 279 $page['perms'] = $perm; 280 $page['size'] = @filesize(wikiFN($pages[$idx])); 281 $page['lastModified'] = new IXR_Date(@filemtime(wikiFN($pages[$idx]))); 282 $list[] = $page; 283 } 284 } 285 } 286 287 return $list; 288 } 289 290 /** 291 * List all pages in the given namespace (and below) 292 */ 293 function readNamespace($ns,$opts){ 294 global $conf; 295 296 if(!is_array($opts)) $opts=array(); 297 298 $ns = cleanID($ns); 299 $dir = utf8_encodeFN(str_replace(':', '/', $ns)); 300dbglog('ggg'); 301 $data = array(); 302 require_once(DOKU_INC.'inc/search.php'); 303 search($data, $conf['datadir'], 'search_allpages', $opts, $dir); 304dbglog($data); 305 return $data; 306 } 307 308 /** 309 * List all media files. 310 * 311 * Available options are 'recursive' for also including the subnamespaces 312 * in the listing, and 'pattern' for filtering the returned files against 313 * a regular expression matching their name. 314 * 315 * @author Gina Haeussge <osd@foosel.net> 316 */ 317 function listAttachments($ns, $options = array()) { 318 global $conf; 319 global $lang; 320 321 $ns = cleanID($ns); 322 323 if (!is_array($options)) 324 $options = array(); 325 326 if (!isset($options['recursive'])) $options['recursive'] = false; 327 328 if(auth_quickaclcheck($ns.':*') >= AUTH_READ) { 329 $dir = utf8_encodeFN(str_replace(':', '/', $ns)); 330 331 $data = array(); 332 require_once(DOKU_INC.'inc/search.php'); 333 search($data, $conf['mediadir'], 'search_media', array('recursive' => $options['recursive']), $dir); 334 335 if(!count($data)) { 336 return array(); 337 } 338 339 $files = array(); 340 foreach($data as $item) { 341 if (isset($options['pattern']) && !@preg_match($options['pattern'], $item['id'])) 342 continue; 343 $file = array(); 344 $file['id'] = $item['id']; 345 $file['size'] = $item['size']; 346 $file['lastModified'] = new IXR_Date($item['mtime']); 347 $file['isimg'] = $item['isimg']; 348 $file['writable'] = $item['writeable']; 349 $file['perms'] = auth_quickaclcheck(getNS($item['id']).':*'); 350 array_push($files, $file); 351 } 352 353 return $files; 354 355 } else { 356 return new IXR_Error(1, 'You are not allowed to list media files.'); 357 } 358 } 359 360 /** 361 * Return a list of backlinks 362 */ 363 function listBackLinks($id){ 364 require_once(DOKU_INC.'inc/fulltext.php'); 365 return ft_backlinks($id); 366 } 367 368 /** 369 * Return some basic data about a page 370 */ 371 function pageInfo($id,$rev=''){ 372 if(auth_quickaclcheck($id) < AUTH_READ){ 373 return new IXR_Error(1, 'You are not allowed to read this page'); 374 } 375 $file = wikiFN($id,$rev); 376 $time = @filemtime($file); 377 if(!$time){ 378 return new IXR_Error(10, 'The requested page does not exist'); 379 } 380 381 $info = getRevisionInfo($id, $time, 1024); 382 383 $data = array( 384 'name' => $id, 385 'lastModified' => new IXR_Date($time), 386 'author' => (($info['user']) ? $info['user'] : $info['ip']), 387 'version' => $time 388 ); 389 390 return ($data); 391 } 392 393 /** 394 * Save a wiki page 395 * 396 * @author Michael Klier <chi@chimeric.de> 397 */ 398 function putPage($id, $text, $params) { 399 global $TEXT; 400 global $lang; 401 global $conf; 402 403 $id = cleanID($id); 404 $TEXT = trim($text); 405 $sum = $params['sum']; 406 $minor = $params['minor']; 407 408 if(empty($id)) 409 return new IXR_Error(1, 'Empty page ID'); 410 411 if(!page_exists($id) && empty($TEXT)) { 412 return new IXR_ERROR(1, 'Refusing to write an empty new wiki page'); 413 } 414 415 if(auth_quickaclcheck($id) < AUTH_EDIT) 416 return new IXR_Error(1, 'You are not allowed to edit this page'); 417 418 // Check, if page is locked 419 if(checklock($id)) 420 return new IXR_Error(1, 'The page is currently locked'); 421 422 // SPAM check 423 if(checkwordblock()) 424 return new IXR_Error(1, 'Positive wordblock check'); 425 426 // autoset summary on new pages 427 if(!page_exists($id) && empty($sum)) { 428 $sum = $lang['created']; 429 } 430 431 // autoset summary on deleted pages 432 if(page_exists($id) && empty($TEXT) && empty($sum)) { 433 $sum = $lang['deleted']; 434 } 435 436 lock($id); 437 438 saveWikiText($id,$TEXT,$sum,$minor); 439 440 unlock($id); 441 442 // run the indexer if page wasn't indexed yet 443 if(!@file_exists(metaFN($id, '.indexed'))) { 444 // try to aquire a lock 445 $lock = $conf['lockdir'].'/_indexer.lock'; 446 while(!@mkdir($lock,$conf['dmode'])){ 447 usleep(50); 448 if(time()-@filemtime($lock) > 60*5){ 449 // looks like a stale lock - remove it 450 @rmdir($lock); 451 }else{ 452 return false; 453 } 454 } 455 if($conf['dperm']) chmod($lock, $conf['dperm']); 456 457 require_once(DOKU_INC.'inc/indexer.php'); 458 459 // do the work 460 idx_addPage($id); 461 462 // we're finished - save and free lock 463 io_saveFile(metaFN($id,'.indexed'),INDEXER_VERSION); 464 @rmdir($lock); 465 } 466 467 return 0; 468 } 469 470 /** 471 * Uploads a file to the wiki. 472 * 473 * Michael Klier <chi@chimeric.de> 474 */ 475 function putAttachment($id, $file, $params) { 476 global $conf; 477 global $lang; 478 479 $auth = auth_quickaclcheck(getNS($id).':*'); 480 if($auth >= AUTH_UPLOAD) { 481 if(!isset($id)) { 482 return new IXR_ERROR(1, 'Filename not given.'); 483 } 484 485 $ftmp = $conf['tmpdir'] . '/' . $id; 486 487 // save temporary file 488 @unlink($ftmp); 489 $buff = base64_decode($file); 490 io_saveFile($ftmp, $buff); 491 492 // get filename 493 list($iext, $imime,$dl) = mimetype($id); 494 $id = cleanID($id); 495 $fn = mediaFN($id); 496 497 // get filetype regexp 498 $types = array_keys(getMimeTypes()); 499 $types = array_map(create_function('$q','return preg_quote($q,"/");'),$types); 500 $regex = join('|',$types); 501 502 // because a temp file was created already 503 if(preg_match('/\.('.$regex.')$/i',$fn)) { 504 //check for overwrite 505 $overwrite = @file_exists($fn); 506 if($overwrite && (!$params['ow'] || $auth < AUTH_DELETE)) { 507 return new IXR_ERROR(1, $lang['uploadexist']); 508 } 509 // check for valid content 510 @require_once(DOKU_INC.'inc/media.php'); 511 $ok = media_contentcheck($ftmp, $imime); 512 if($ok == -1) { 513 return new IXR_ERROR(1, sprintf($lang['uploadexist'], ".$iext")); 514 } elseif($ok == -2) { 515 return new IXR_ERROR(1, $lang['uploadspam']); 516 } elseif($ok == -3) { 517 return new IXR_ERROR(1, $lang['uploadxss']); 518 } 519 520 // prepare event data 521 $data[0] = $ftmp; 522 $data[1] = $fn; 523 $data[2] = $id; 524 $data[3] = $imime; 525 $data[4] = $overwrite; 526 527 // trigger event 528 require_once(DOKU_INC.'inc/events.php'); 529 return trigger_event('MEDIA_UPLOAD_FINISH', $data, array($this, '_media_upload_action'), true); 530 531 } else { 532 return new IXR_ERROR(1, $lang['uploadwrong']); 533 } 534 } else { 535 return new IXR_ERROR(1, "You don't have permissions to upload files."); 536 } 537 } 538 539 /** 540 * Deletes a file from the wiki. 541 * 542 * @author Gina Haeussge <osd@foosel.net> 543 */ 544 function deleteAttachment($id){ 545 $auth = auth_quickaclcheck(getNS($id).':*'); 546 if($auth < AUTH_DELETE) return new IXR_ERROR(1, "You don't have permissions to delete files."); 547 global $conf; 548 global $lang; 549 550 // check for references if needed 551 $mediareferences = array(); 552 if($conf['refcheck']){ 553 require_once(DOKU_INC.'inc/fulltext.php'); 554 $mediareferences = ft_mediause($id,$conf['refshow']); 555 } 556 557 if(!count($mediareferences)){ 558 $file = mediaFN($id); 559 if(@unlink($file)){ 560 require_once(DOKU_INC.'inc/changelog.php'); 561 addMediaLogEntry(time(), $id, DOKU_CHANGE_TYPE_DELETE); 562 io_sweepNS($id,'mediadir'); 563 return 0; 564 } 565 //something went wrong 566 return new IXR_ERROR(1, 'Could not delete file'); 567 } else { 568 return new IXR_ERROR(1, 'File is still referenced'); 569 } 570 } 571 572 /** 573 * Moves the temporary file to its final destination. 574 * 575 * Michael Klier <chi@chimeric.de> 576 */ 577 function _media_upload_action($data) { 578 global $conf; 579 580 if(is_array($data) && count($data)===5) { 581 io_createNamespace($data[2], 'media'); 582 if(rename($data[0], $data[1])) { 583 chmod($data[1], $conf['fmode']); 584 media_notify($data[2], $data[1], $data[3]); 585 // add a log entry to the media changelog 586 require_once(DOKU_INC.'inc/changelog.php'); 587 if ($data[4]) { 588 addMediaLogEntry(time(), $data[2], DOKU_CHANGE_TYPE_EDIT); 589 } else { 590 addMediaLogEntry(time(), $data[2], DOKU_CHANGE_TYPE_CREATE); 591 } 592 return $data[2]; 593 } else { 594 return new IXR_ERROR(1, 'Upload failed.'); 595 } 596 } else { 597 return new IXR_ERROR(1, 'Upload failed.'); 598 } 599 } 600 601 /** 602 * Returns the permissions of a given wiki page 603 */ 604 function aclCheck($id) { 605 return auth_quickaclcheck($id); 606 } 607 608 /** 609 * Lists all links contained in a wiki page 610 * 611 * @author Michael Klier <chi@chimeric.de> 612 */ 613 function listLinks($id) { 614 if(auth_quickaclcheck($id) < AUTH_READ){ 615 return new IXR_Error(1, 'You are not allowed to read this page'); 616 } 617 $links = array(); 618 619 // resolve page instructions 620 $ins = p_cached_instructions(wikiFN(cleanID($id))); 621 622 // instantiate new Renderer - needed for interwiki links 623 include(DOKU_INC.'inc/parser/xhtml.php'); 624 $Renderer = new Doku_Renderer_xhtml(); 625 $Renderer->interwiki = getInterwiki(); 626 627 // parse parse instructions 628 foreach($ins as $in) { 629 $link = array(); 630 switch($in[0]) { 631 case 'internallink': 632 $link['type'] = 'local'; 633 $link['page'] = $in[1][0]; 634 $link['href'] = wl($in[1][0]); 635 array_push($links,$link); 636 break; 637 case 'externallink': 638 $link['type'] = 'extern'; 639 $link['page'] = $in[1][0]; 640 $link['href'] = $in[1][0]; 641 array_push($links,$link); 642 break; 643 case 'interwikilink': 644 $url = $Renderer->_resolveInterWiki($in[1][2],$in[1][3]); 645 $link['type'] = 'extern'; 646 $link['page'] = $url; 647 $link['href'] = $url; 648 array_push($links,$link); 649 break; 650 } 651 } 652 653 return ($links); 654 } 655 656 /** 657 * Returns a list of recent changes since give timestamp 658 * 659 * @author Michael Hamann <michael@content-space.de> 660 * @author Michael Klier <chi@chimeric.de> 661 */ 662 function getRecentChanges($timestamp) { 663 if(strlen($timestamp) != 10) 664 return new IXR_Error(20, 'The provided value is not a valid timestamp'); 665 666 require_once(DOKU_INC.'inc/changelog.php'); 667 require_once(DOKU_INC.'inc/pageutils.php'); 668 669 $recents = getRecentsSince($timestamp); 670 671 $changes = array(); 672 673 foreach ($recents as $recent) { 674 $change = array(); 675 $change['name'] = $recent['id']; 676 $change['lastModified'] = new IXR_Date($recent['date']); 677 $change['author'] = $recent['user']; 678 $change['version'] = $recent['date']; 679 $change['perms'] = $recent['perms']; 680 $change['size'] = @filesize(wikiFN($recent['id'])); 681 array_push($changes, $change); 682 } 683 684 if (!empty($changes)) { 685 return $changes; 686 } else { 687 // in case we still have nothing at this point 688 return new IXR_Error(30, 'There are no changes in the specified timeframe'); 689 } 690 } 691 692 /** 693 * Returns a list of recent media changes since give timestamp 694 * 695 * @author Michael Hamann <michael@content-space.de> 696 * @author Michael Klier <chi@chimeric.de> 697 */ 698 function getRecentMediaChanges($timestamp) { 699 if(strlen($timestamp) != 10) 700 return new IXR_Error(20, 'The provided value is not a valid timestamp'); 701 702 require_once(DOKU_INC.'inc/changelog.php'); 703 require_once(DOKU_INC.'inc/pageutils.php'); 704 705 $recents = getRecentsSince($timestamp, null, '', RECENTS_MEDIA_CHANGES); 706 707 $changes = array(); 708 709 foreach ($recents as $recent) { 710 $change = array(); 711 $change['name'] = $recent['id']; 712 $change['lastModified'] = new IXR_Date($recent['date']); 713 $change['author'] = $recent['user']; 714 $change['version'] = $recent['date']; 715 $change['perms'] = $recent['perms']; 716 $change['size'] = @filesize(wikiFN($recent['id'])); 717 array_push($changes, $change); 718 } 719 720 if (!empty($changes)) { 721 return $changes; 722 } else { 723 // in case we still have nothing at this point 724 return new IXR_Error(30, 'There are no changes in the specified timeframe'); 725 } 726 } 727 728 /** 729 * Returns a list of available revisions of a given wiki page 730 * 731 * @author Michael Klier <chi@chimeric.de> 732 */ 733 function pageVersions($id, $first) { 734 global $conf; 735 736 $versions = array(); 737 738 if(empty($id)) 739 return new IXR_Error(1, 'Empty page ID'); 740 741 require_once(DOKU_INC.'inc/changelog.php'); 742 743 $revisions = getRevisions($id, $first, $conf['recent']+1); 744 745 if(count($revisions)==0 && $first!=0) { 746 $first=0; 747 $revisions = getRevisions($id, $first, $conf['recent']+1); 748 } 749 750 if(count($revisions)>0 && $first==0) { 751 array_unshift($revisions, ''); // include current revision 752 array_pop($revisions); // remove extra log entry 753 } 754 755 $hasNext = false; 756 if(count($revisions)>$conf['recent']) { 757 $hasNext = true; 758 array_pop($revisions); // remove extra log entry 759 } 760 761 if(!empty($revisions)) { 762 foreach($revisions as $rev) { 763 $file = wikiFN($id,$rev); 764 $time = @filemtime($file); 765 // we check if the page actually exists, if this is not the 766 // case this can lead to less pages being returned than 767 // specified via $conf['recent'] 768 if($time){ 769 $info = getRevisionInfo($id, $time, 1024); 770 if(!empty($info)) { 771 $data['user'] = $info['user']; 772 $data['ip'] = $info['ip']; 773 $data['type'] = $info['type']; 774 $data['sum'] = $info['sum']; 775 $data['modified'] = new IXR_Date($info['date']); 776 $data['version'] = $info['date']; 777 array_push($versions, $data); 778 } 779 } 780 } 781 return $versions; 782 } else { 783 return array(); 784 } 785 } 786 787 /** 788 * The version of Wiki RPC API supported 789 */ 790 function wiki_RPCVersion(){ 791 return 2; 792 } 793 794} 795 796$server = new dokuwiki_xmlrpc_server(); 797 798// vim:ts=4:sw=4:et:enc=utf-8: 799