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