xref: /dokuwiki/lib/exe/xmlrpc.php (revision 9f9db1ad22c6352bfd4291b205f7e24ebd5ac9c9)
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