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