2 if(!defined('DOKU_INC')) define('DOKU_INC',dirname(__FILE__
).'/../../');
4 // fix when '<?xml' isn't on the very first line
5 if(isset($HTTP_RAW_POST_DATA)) $HTTP_RAW_POST_DATA = trim($HTTP_RAW_POST_DATA);
8 * Increased whenever the API is changed
10 define('DOKU_XMLRPC_API_VERSION',2);
12 require_once(DOKU_INC
.'inc/init.php');
13 require_once(DOKU_INC
.'inc/common.php');
14 require_once(DOKU_INC
.'inc/auth.php');
15 session_write_close(); //close session
17 if(!$conf['xmlrpc']) die('XML-RPC server not enabled.');
19 require_once(DOKU_INC
.'inc/IXR_Library.php');
23 * Contains needed wrapper functions and registers all available
26 class dokuwiki_xmlrpc_server
extends IXR_IntrospectionServer
{
27 var $methods = array();
28 var $public_methods = array();
31 * Checks if the current user is allowed to execute non anonymous methods
37 if(!$conf['useacl']) return true; //no ACL - then no checks
39 $allowed = explode(',',$conf['xmlrpcuser']);
40 $allowed = array_map('trim', $allowed);
41 $allowed = array_unique($allowed);
42 $allowed = array_filter($allowed);
44 if(!count($allowed)) return true; //no restrictions
46 $user = $_SERVER['REMOTE_USER'];
47 $groups = (array) $USERINFO['grps'];
49 if(in_array($user,$allowed)) return true; //user explicitly mentioned
51 //check group memberships
52 foreach($groups as $group){
53 if(in_array('@'.$group,$allowed)) return true;
56 //still here? no access!
61 * Adds a callback, extends parent method
63 * add another parameter to define if anonymous access to
64 * this method should be granted.
66 function addCallback($method, $callback, $args, $help, $public=false){
67 if($public) $this->public_methods
[] = $method;
68 return parent
::addCallback($method, $callback, $args, $help);
72 * Execute a call, extends parent method
74 * Checks for authentication first
76 function call($methodname, $args){
77 if(!in_array($methodname,$this->public_methods
) && !$this->checkAuth()){
78 return new IXR_Error(-32603, 'server error. not authorized to call method "'.$methodname.'".');
80 return parent
::call($methodname, $args);
84 * Constructor. Register methods and run Server
86 function dokuwiki_xmlrpc_server(){
87 $this->IXR_IntrospectionServer();
89 /* DokuWiki's own methods */
91 'dokuwiki.getXMLRPCAPIVersion',
94 'Returns the XMLRPC API version.',
99 'dokuwiki.getVersion',
102 'Returns the running DokuWiki version.',
109 array('integer','string','string'),
110 'Tries to login with the given credentials and sets auth cookies.',
115 'dokuwiki.getPagelist',
116 'this:readNamespace',
117 array('struct','string','struct'),
118 'List all pages within the given namespace.'
125 'Return the current time at the wiki server.'
131 array('struct','struct'),
132 'Lock or unlock pages.'
135 /* Wiki API v2 http://www.jspwiki.org/wiki/WikiRPCInterface2 */
137 'wiki.getRPCVersionSupported',
138 'this:wiki_RPCVersion',
140 'Returns 2 with the supported RPC API version.',
146 array('string','string'),
147 'Get the raw Wiki text of page, latest version.'
150 'wiki.getPageVersion',
152 array('string','string','int'),
153 'Get the raw Wiki text of page.'
158 array('string','string'),
159 'Return page in rendered HTML, latest version.'
162 'wiki.getPageHTMLVersion',
164 array('string','string','int'),
165 'Return page in rendered HTML.'
171 'Returns a list of all pages. The result is an array of utf8 pagenames.'
174 'wiki.getAttachments',
175 'this:listAttachments',
176 array('struct', 'string', 'struct'),
177 'Returns a list of all media files.'
181 'this:listBackLinks',
182 array('struct','string'),
183 'Returns the pages that link to this page.'
188 array('struct','string'),
189 'Returns a struct with infos about the page.'
192 'wiki.getPageInfoVersion',
194 array('struct','string','int'),
195 'Returns a struct with infos about the page.'
198 'wiki.getPageVersions',
200 array('struct','string','int'),
201 'Returns the available revisions of the page.'
206 array('int', 'string', 'string', 'struct'),
212 array('struct','string'),
213 'Lists all links contained in a wiki page.'
216 'wiki.getRecentChanges',
217 'this:getRecentChanges',
218 array('struct','int'),
219 'Returns a struct about all recent changes since given timestamp.'
222 'wiki.getRecentMediaChanges',
223 'this:getRecentMediaChanges',
224 array('struct','int'),
225 'Returns a struct about all recent media changes since given timestamp.'
230 array('int', 'string'),
231 'Returns the permissions of a given wiki page.'
234 'wiki.putAttachment',
235 'this:putAttachment',
236 array('struct', 'string', 'base64', 'struct'),
237 'Upload a file to the wiki.'
240 'wiki.deleteAttachment',
241 'this:deleteAttachment',
242 array('int', 'string'),
243 'Delete a file from the wiki.'
246 'wiki.getAttachment',
247 'this:getAttachment',
248 array('base64', 'string'),
249 'Download a file from the wiki.'
252 'wiki.getAttachmentInfo',
253 'this:getAttachmentInfo',
254 array('struct', 'string'),
255 'Returns a struct with infos about the attachment.'
259 * Trigger XMLRPC_CALLBACK_REGISTER, action plugins can use this event
260 * to extend the XMLRPC interface and register their own callbacks.
263 * The XMLRPC server object:
265 * $event->data->addCallback() - register a callback, the second
266 * paramter has to be of the form "plugin:<pluginname>:<plugin
269 * $event->data->callbacks - an array which holds all awaylable
272 trigger_event('XMLRPC_CALLBACK_REGISTER', $this);
278 * Return a raw wiki page
280 function rawPage($id,$rev=''){
281 if(auth_quickaclcheck($id) < AUTH_READ
){
282 return new IXR_Error(1, 'You are not allowed to read this page');
284 $text = rawWiki($id,$rev);
287 return trigger_event('HTML_PAGE_FROMTEMPLATE',$data,'pageTemplate',true);
294 * Return a media file encoded in base64
296 * @author Gina Haeussge <osd@foosel.net>
298 function getAttachment($id){
300 if (auth_quickaclcheck(getNS($id).':*') < AUTH_READ
)
301 return new IXR_Error(1, 'You are not allowed to read this file');
303 $file = mediaFN($id);
304 if (!@ file_exists($file))
305 return new IXR_Error(1, 'The requested file does not exist');
307 $data = io_readFile($file, false);
308 $base64 = base64_encode($data);
313 * Return info about a media file
315 * @author Gina Haeussge <osd@foosel.net>
317 function getAttachmentInfo($id){
324 $file = mediaFN($id);
325 if ((auth_quickaclcheck(getNS($id).':*') >= AUTH_READ
) && file_exists($file)){
326 $info['lastModified'] = new IXR_Date(filemtime($file));
327 $info['size'] = filesize($file);
334 * Return a wiki page rendered to html
336 function htmlPage($id,$rev=''){
337 if(auth_quickaclcheck($id) < AUTH_READ
){
338 return new IXR_Error(1, 'You are not allowed to read this page');
340 return p_wiki_xhtml($id,$rev,false);
344 * List all pages - we use the indexer list here
346 function listPages(){
350 $pages = file($conf['indexdir'] . '/page.idx');
351 $pages = array_filter($pages, 'isVisiblePage');
353 foreach(array_keys($pages) as $idx) {
354 if(page_exists($pages[$idx])) {
355 $perm = auth_quickaclcheck($pages[$idx]);
356 if($perm >= AUTH_READ
) {
358 $page['id'] = trim($pages[$idx]);
359 $page['perms'] = $perm;
360 $page['size'] = @filesize
(wikiFN($pages[$idx]));
361 $page['lastModified'] = new IXR_Date(@filemtime
(wikiFN($pages[$idx])));
371 * List all pages in the given namespace (and below)
373 function readNamespace($ns,$opts){
376 if(!is_array($opts)) $opts=array();
379 $dir = utf8_encodeFN(str_replace(':', '/', $ns));
381 require_once(DOKU_INC
.'inc/search.php');
382 $opts['skipacl'] = 0; // no ACL skipping for XMLRPC
383 search($data, $conf['datadir'], 'search_allpages', $opts, $dir);
388 * List all media files.
390 * Available options are 'recursive' for also including the subnamespaces
391 * in the listing, and 'pattern' for filtering the returned files against
392 * a regular expression matching their name.
394 * @author Gina Haeussge <osd@foosel.net>
396 function listAttachments($ns, $options = array()) {
402 if (!is_array($options)) $options = array();
403 $options['skipacl'] = 0; // no ACL skipping for XMLRPC
406 if(auth_quickaclcheck($ns.':*') >= AUTH_READ
) {
407 $dir = utf8_encodeFN(str_replace(':', '/', $ns));
410 require_once(DOKU_INC
.'inc/search.php');
411 search($data, $conf['mediadir'], 'search_media', $options, $dir);
413 if(!$len) return array();
415 for($i=0; $i<$len; $i++
) {
416 unset($data[$i]['meta']);
417 $data[$i]['lastModified'] = new IXR_Date($data[$i]['mtime']);
421 return new IXR_Error(1, 'You are not allowed to list media files.');
426 * Return a list of backlinks
428 function listBackLinks($id){
429 require_once(DOKU_INC
.'inc/fulltext.php');
430 return ft_backlinks($id);
434 * Return some basic data about a page
436 function pageInfo($id,$rev=''){
437 if(auth_quickaclcheck($id) < AUTH_READ
){
438 return new IXR_Error(1, 'You are not allowed to read this page');
440 $file = wikiFN($id,$rev);
441 $time = @filemtime
($file);
443 return new IXR_Error(10, 'The requested page does not exist');
446 $info = getRevisionInfo($id, $time, 1024);
450 'lastModified' => new IXR_Date($time),
451 'author' => (($info['user']) ?
$info['user'] : $info['ip']),
461 * @author Michael Klier <chi@chimeric.de>
463 function putPage($id, $text, $params) {
469 $TEXT = cleanText($text);
470 $sum = $params['sum'];
471 $minor = $params['minor'];
474 return new IXR_Error(1, 'Empty page ID');
476 if(!page_exists($id) && trim($TEXT) == '' ) {
477 return new IXR_ERROR(1, 'Refusing to write an empty new wiki page');
480 if(auth_quickaclcheck($id) < AUTH_EDIT
)
481 return new IXR_Error(1, 'You are not allowed to edit this page');
483 // Check, if page is locked
485 return new IXR_Error(1, 'The page is currently locked');
489 return new IXR_Error(1, 'Positive wordblock check');
491 // autoset summary on new pages
492 if(!page_exists($id) && empty($sum)) {
493 $sum = $lang['created'];
496 // autoset summary on deleted pages
497 if(page_exists($id) && empty($TEXT) && empty($sum)) {
498 $sum = $lang['deleted'];
503 saveWikiText($id,$TEXT,$sum,$minor);
507 // run the indexer if page wasn't indexed yet
508 if(!@file_exists
(metaFN($id, '.indexed'))) {
509 // try to aquire a lock
510 $lock = $conf['lockdir'].'/_indexer.lock';
511 while(!@mkdir
($lock,$conf['dmode'])){
513 if(time()-@filemtime
($lock) > 60*5){
514 // looks like a stale lock - remove it
520 if($conf['dperm']) chmod($lock, $conf['dperm']);
522 require_once(DOKU_INC
.'inc/indexer.php');
527 // we're finished - save and free lock
528 io_saveFile(metaFN($id,'.indexed'),INDEXER_VERSION
);
536 * Uploads a file to the wiki.
538 * Michael Klier <chi@chimeric.de>
540 function putAttachment($id, $file, $params) {
544 $auth = auth_quickaclcheck(getNS($id).':*');
545 if($auth >= AUTH_UPLOAD
) {
547 return new IXR_ERROR(1, 'Filename not given.');
550 $ftmp = $conf['tmpdir'] . '/' . $id;
552 // save temporary file
554 $buff = base64_decode($file);
555 io_saveFile($ftmp, $buff);
558 list($iext, $imime,$dl) = mimetype($id);
562 // get filetype regexp
563 $types = array_keys(getMimeTypes());
564 $types = array_map(create_function('$q','return preg_quote($q,"/");'),$types);
565 $regex = join('|',$types);
567 // because a temp file was created already
568 if(preg_match('/\.('.$regex.')$/i',$fn)) {
569 //check for overwrite
570 $overwrite = @file_exists
($fn);
571 if($overwrite && (!$params['ow'] ||
$auth < AUTH_DELETE
)) {
572 return new IXR_ERROR(1, $lang['uploadexist'].'1');
574 // check for valid content
575 @require_once
(DOKU_INC
.'inc/media.php');
576 $ok = media_contentcheck($ftmp, $imime);
578 return new IXR_ERROR(1, sprintf($lang['uploadexist'].'2', ".$iext"));
579 } elseif($ok == -2) {
580 return new IXR_ERROR(1, $lang['uploadspam']);
581 } elseif($ok == -3) {
582 return new IXR_ERROR(1, $lang['uploadxss']);
585 // prepare event data
590 $data[4] = $overwrite;
593 require_once(DOKU_INC
.'inc/events.php');
594 return trigger_event('MEDIA_UPLOAD_FINISH', $data, array($this, '_media_upload_action'), true);
597 return new IXR_ERROR(1, $lang['uploadwrong']);
600 return new IXR_ERROR(1, "You don't have permissions to upload files.");
605 * Deletes a file from the wiki.
607 * @author Gina Haeussge <osd@foosel.net>
609 function deleteAttachment($id){
610 $auth = auth_quickaclcheck(getNS($id).':*');
611 if($auth < AUTH_DELETE
) return new IXR_ERROR(1, "You don't have permissions to delete files.");
615 // check for references if needed
616 $mediareferences = array();
617 if($conf['refcheck']){
618 require_once(DOKU_INC
.'inc/fulltext.php');
619 $mediareferences = ft_mediause($id,$conf['refshow']);
622 if(!count($mediareferences)){
623 $file = mediaFN($id);
625 require_once(DOKU_INC
.'inc/changelog.php');
626 addMediaLogEntry(time(), $id, DOKU_CHANGE_TYPE_DELETE
);
627 io_sweepNS($id,'mediadir');
630 //something went wrong
631 return new IXR_ERROR(1, 'Could not delete file');
633 return new IXR_ERROR(1, 'File is still referenced');
638 * Moves the temporary file to its final destination.
640 * Michael Klier <chi@chimeric.de>
642 function _media_upload_action($data) {
645 if(is_array($data) && count($data)===5) {
646 io_createNamespace($data[2], 'media');
647 if(rename($data[0], $data[1])) {
648 chmod($data[1], $conf['fmode']);
649 media_notify($data[2], $data[1], $data[3]);
650 // add a log entry to the media changelog
651 require_once(DOKU_INC
.'inc/changelog.php');
653 addMediaLogEntry(time(), $data[2], DOKU_CHANGE_TYPE_EDIT
);
655 addMediaLogEntry(time(), $data[2], DOKU_CHANGE_TYPE_CREATE
);
659 return new IXR_ERROR(1, 'Upload failed.');
662 return new IXR_ERROR(1, 'Upload failed.');
667 * Returns the permissions of a given wiki page
669 function aclCheck($id) {
670 return auth_quickaclcheck($id);
674 * Lists all links contained in a wiki page
676 * @author Michael Klier <chi@chimeric.de>
678 function listLinks($id) {
679 if(auth_quickaclcheck($id) < AUTH_READ
){
680 return new IXR_Error(1, 'You are not allowed to read this page');
684 // resolve page instructions
685 $ins = p_cached_instructions(wikiFN(cleanID($id)));
687 // instantiate new Renderer - needed for interwiki links
688 include(DOKU_INC
.'inc/parser/xhtml.php');
689 $Renderer = new Doku_Renderer_xhtml();
690 $Renderer->interwiki
= getInterwiki();
692 // parse parse instructions
693 foreach($ins as $in) {
697 $link['type'] = 'local';
698 $link['page'] = $in[1][0];
699 $link['href'] = wl($in[1][0]);
700 array_push($links,$link);
703 $link['type'] = 'extern';
704 $link['page'] = $in[1][0];
705 $link['href'] = $in[1][0];
706 array_push($links,$link);
708 case 'interwikilink':
709 $url = $Renderer->_resolveInterWiki($in[1][2],$in[1][3]);
710 $link['type'] = 'extern';
711 $link['page'] = $url;
712 $link['href'] = $url;
713 array_push($links,$link);
722 * Returns a list of recent changes since give timestamp
724 * @author Michael Hamann <michael@content-space.de>
725 * @author Michael Klier <chi@chimeric.de>
727 function getRecentChanges($timestamp) {
728 if(strlen($timestamp) != 10)
729 return new IXR_Error(20, 'The provided value is not a valid timestamp');
731 require_once(DOKU_INC
.'inc/changelog.php');
732 require_once(DOKU_INC
.'inc/pageutils.php');
734 $recents = getRecentsSince($timestamp);
738 foreach ($recents as $recent) {
740 $change['name'] = $recent['id'];
741 $change['lastModified'] = new IXR_Date($recent['date']);
742 $change['author'] = $recent['user'];
743 $change['version'] = $recent['date'];
744 $change['perms'] = $recent['perms'];
745 $change['size'] = @filesize
(wikiFN($recent['id']));
746 array_push($changes, $change);
749 if (!empty($changes)) {
752 // in case we still have nothing at this point
753 return new IXR_Error(30, 'There are no changes in the specified timeframe');
758 * Returns a list of recent media changes since give timestamp
760 * @author Michael Hamann <michael@content-space.de>
761 * @author Michael Klier <chi@chimeric.de>
763 function getRecentMediaChanges($timestamp) {
764 if(strlen($timestamp) != 10)
765 return new IXR_Error(20, 'The provided value is not a valid timestamp');
767 require_once(DOKU_INC
.'inc/changelog.php');
768 require_once(DOKU_INC
.'inc/pageutils.php');
770 $recents = getRecentsSince($timestamp, null, '', RECENTS_MEDIA_CHANGES
);
774 foreach ($recents as $recent) {
776 $change['name'] = $recent['id'];
777 $change['lastModified'] = new IXR_Date($recent['date']);
778 $change['author'] = $recent['user'];
779 $change['version'] = $recent['date'];
780 $change['perms'] = $recent['perms'];
781 $change['size'] = @filesize
(mediaFN($recent['id']));
782 array_push($changes, $change);
785 if (!empty($changes)) {
788 // in case we still have nothing at this point
789 return new IXR_Error(30, 'There are no changes in the specified timeframe');
794 * Returns a list of available revisions of a given wiki page
796 * @author Michael Klier <chi@chimeric.de>
798 function pageVersions($id, $first) {
804 return new IXR_Error(1, 'Empty page ID');
806 require_once(DOKU_INC
.'inc/changelog.php');
808 $revisions = getRevisions($id, $first, $conf['recent']+
1);
810 if(count($revisions)==0 && $first!=0) {
812 $revisions = getRevisions($id, $first, $conf['recent']+
1);
815 if(count($revisions)>0 && $first==0) {
816 array_unshift($revisions, ''); // include current revision
817 array_pop($revisions); // remove extra log entry
821 if(count($revisions)>$conf['recent']) {
823 array_pop($revisions); // remove extra log entry
826 if(!empty($revisions)) {
827 foreach($revisions as $rev) {
828 $file = wikiFN($id,$rev);
829 $time = @filemtime
($file);
830 // we check if the page actually exists, if this is not the
831 // case this can lead to less pages being returned than
832 // specified via $conf['recent']
834 $info = getRevisionInfo($id, $time, 1024);
836 $data['user'] = $info['user'];
837 $data['ip'] = $info['ip'];
838 $data['type'] = $info['type'];
839 $data['sum'] = $info['sum'];
840 $data['modified'] = new IXR_Date($info['date']);
841 $data['version'] = $info['date'];
842 array_push($versions, $data);
853 * The version of Wiki RPC API supported
855 function wiki_RPCVersion(){
861 * Locks or unlocks a given batch of pages
863 * Give an associative array with two keys: lock and unlock. Both should contain a
864 * list of pages to lock or unlock
866 * Returns an associative array with the keys locked, lockfail, unlocked and
867 * unlockfail, each containing lists of pages.
869 function setLocks($set){
873 $unlockfail = array();
875 foreach((array) $set['lock'] as $id){
884 foreach((array) $set['unlock'] as $id){
894 'lockfail' => $lockfail,
895 'unlocked' => $unlocked,
896 'unlockfail' => $unlockfail,
900 function getAPIVersion(){
901 return DOKU_XMLRPC_API_VERSION
;
904 function login($user,$pass){
907 if(!$conf['useacl']) return 0;
909 if($auth->canDo('external')){
910 return $auth->trustExternal($user,$pass,false);
912 return auth_login($user,$pass,false,true);
919 $server = new dokuwiki_xmlrpc_server();
921 // vim:ts=4:sw=4:et:enc=utf-8: