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',1);
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']) {
18 die('XML-RPC server not enabled.');
19 // FIXME check for groups allowed
22 require_once(DOKU_INC
.'inc/IXR_Library.php');
26 * Contains needed wrapper functions and registers all available
29 class dokuwiki_xmlrpc_server
extends IXR_IntrospectionServer
{
30 var $methods = array();
33 * Constructor. Register methods and run Server
35 function dokuwiki_xmlrpc_server(){
36 $this->IXR_IntrospectionServer();
38 /* DokuWiki's own methods */
40 'dokuwiki.getXMLRPCAPIVersion',
43 'Returns the XMLRPC API version.'
47 'dokuwiki.getVersion',
50 'Returns the running DokuWiki version.'
56 array('integer','string','string'),
57 'Tries to login with the given credentials and sets auth cookies.'
61 'dokuwiki.getPagelist',
63 array('struct','string','struct'),
64 'List all pages within the given namespace.'
71 'Return the current time at the wiki server.'
77 array('struct','struct'),
78 'Lock or unlock pages.'
81 /* Wiki API v2 http://www.jspwiki.org/wiki/WikiRPCInterface2 */
83 'wiki.getRPCVersionSupported',
84 'this:wiki_RPCVersion',
86 'Returns 2 with the supported RPC API version.'
91 array('string','string'),
92 'Get the raw Wiki text of page, latest version.'
95 'wiki.getPageVersion',
97 array('string','string','int'),
98 'Get the raw Wiki text of page.'
103 array('string','string'),
104 'Return page in rendered HTML, latest version.'
107 'wiki.getPageHTMLVersion',
109 array('string','string','int'),
110 'Return page in rendered HTML.'
116 'Returns a list of all pages. The result is an array of utf8 pagenames.'
119 'wiki.getAttachments',
120 'this:listAttachments',
121 array('struct', 'string', 'struct'),
122 'Returns a list of all media files.'
126 'this:listBackLinks',
127 array('struct','string'),
128 'Returns the pages that link to this page.'
133 array('struct','string'),
134 'Returns a struct with infos about the page.'
137 'wiki.getPageInfoVersion',
139 array('struct','string','int'),
140 'Returns a struct with infos about the page.'
143 'wiki.getPageVersions',
145 array('struct','string','int'),
146 'Returns the available revisions of the page.'
151 array('int', 'string', 'string', 'struct'),
157 array('struct','string'),
158 'Lists all links contained in a wiki page.'
161 'wiki.getRecentChanges',
162 'this:getRecentChanges',
163 array('struct','int'),
164 'Returns a struct about all recent changes since given timestamp.'
167 'wiki.getRecentMediaChanges',
168 'this:getRecentMediaChanges',
169 array('struct','int'),
170 'Returns a struct about all recent media changes since given timestamp.'
175 array('int', 'string'),
176 'Returns the permissions of a given wiki page.'
179 'wiki.putAttachment',
180 'this:putAttachment',
181 array('struct', 'string', 'base64', 'struct'),
182 'Upload a file to the wiki.'
185 'wiki.deleteAttachment',
186 'this:deleteAttachment',
187 array('int', 'string'),
188 'Delete a file from the wiki.'
191 'wiki.getAttachment',
192 'this:getAttachment',
193 array('base64', 'string'),
194 'Download a file from the wiki.'
197 'wiki.getAttachmentInfo',
198 'this:getAttachmentInfo',
199 array('struct', 'string'),
200 'Returns a struct with infos about the attachment.'
204 * Trigger XMLRPC_CALLBACK_REGISTER, action plugins can use this event
205 * to extend the XMLRPC interface and register their own callbacks.
208 * The XMLRPC server object:
210 * $event->data->addCallback() - register a callback, the second
211 * paramter has to be of the form "plugin:<pluginname>:<plugin
214 * $event->data->callbacks - an array which holds all awaylable
217 trigger_event('XMLRPC_CALLBACK_REGISTER', $this);
223 * Return a raw wiki page
225 function rawPage($id,$rev=''){
226 if(auth_quickaclcheck($id) < AUTH_READ
){
227 return new IXR_Error(1, 'You are not allowed to read this page');
229 $text = rawWiki($id,$rev);
232 return trigger_event('HTML_PAGE_FROMTEMPLATE',$data,'pageTemplate',true);
239 * Return a media file encoded in base64
241 * @author Gina Haeussge <osd@foosel.net>
243 function getAttachment($id){
245 if (auth_quickaclcheck(getNS($id).':*') < AUTH_READ
)
246 return new IXR_Error(1, 'You are not allowed to read this file');
248 $file = mediaFN($id);
249 if (!@ file_exists($file))
250 return new IXR_Error(1, 'The requested file does not exist');
252 $data = io_readFile($file, false);
253 $base64 = base64_encode($data);
258 * Return info about a media file
260 * @author Gina Haeussge <osd@foosel.net>
262 function getAttachmentInfo($id){
269 $file = mediaFN($id);
270 if ((auth_quickaclcheck(getNS($id).':*') >= AUTH_READ
) && file_exists($file)){
271 $info['lastModified'] = new IXR_Date(filemtime($file));
272 $info['size'] = filesize($file);
279 * Return a wiki page rendered to html
281 function htmlPage($id,$rev=''){
282 if(auth_quickaclcheck($id) < AUTH_READ
){
283 return new IXR_Error(1, 'You are not allowed to read this page');
285 return p_wiki_xhtml($id,$rev,false);
289 * List all pages - we use the indexer list here
291 function listPages(){
295 $pages = file($conf['indexdir'] . '/page.idx');
296 $pages = array_filter($pages, 'isVisiblePage');
298 foreach(array_keys($pages) as $idx) {
299 if(page_exists($pages[$idx])) {
300 $perm = auth_quickaclcheck($pages[$idx]);
301 if($perm >= AUTH_READ
) {
303 $page['id'] = trim($pages[$idx]);
304 $page['perms'] = $perm;
305 $page['size'] = @filesize
(wikiFN($pages[$idx]));
306 $page['lastModified'] = new IXR_Date(@filemtime
(wikiFN($pages[$idx])));
316 * List all pages in the given namespace (and below)
318 function readNamespace($ns,$opts){
321 if(!is_array($opts)) $opts=array();
324 $dir = utf8_encodeFN(str_replace(':', '/', $ns));
326 require_once(DOKU_INC
.'inc/search.php');
327 search($data, $conf['datadir'], 'search_allpages', $opts, $dir);
332 * List all media files.
334 * Available options are 'recursive' for also including the subnamespaces
335 * in the listing, and 'pattern' for filtering the returned files against
336 * a regular expression matching their name.
338 * @author Gina Haeussge <osd@foosel.net>
340 function listAttachments($ns, $options = array()) {
346 if (!is_array($options))
350 if(auth_quickaclcheck($ns.':*') >= AUTH_READ
) {
351 $dir = utf8_encodeFN(str_replace(':', '/', $ns));
354 require_once(DOKU_INC
.'inc/search.php');
355 search($data, $conf['mediadir'], 'search_media', $options, $dir);
357 if(!$len) return array();
359 for($i=0; $i<$len; $i++
) {
360 unset($data[$i]['meta']);
361 $data[$i]['lastModified'] = new IXR_Date($data[$i]['mtime']);
365 return new IXR_Error(1, 'You are not allowed to list media files.');
370 * Return a list of backlinks
372 function listBackLinks($id){
373 require_once(DOKU_INC
.'inc/fulltext.php');
374 return ft_backlinks($id);
378 * Return some basic data about a page
380 function pageInfo($id,$rev=''){
381 if(auth_quickaclcheck($id) < AUTH_READ
){
382 return new IXR_Error(1, 'You are not allowed to read this page');
384 $file = wikiFN($id,$rev);
385 $time = @filemtime
($file);
387 return new IXR_Error(10, 'The requested page does not exist');
390 $info = getRevisionInfo($id, $time, 1024);
394 'lastModified' => new IXR_Date($time),
395 'author' => (($info['user']) ?
$info['user'] : $info['ip']),
405 * @author Michael Klier <chi@chimeric.de>
407 function putPage($id, $text, $params) {
414 $sum = $params['sum'];
415 $minor = $params['minor'];
418 return new IXR_Error(1, 'Empty page ID');
420 if(!page_exists($id) && empty($TEXT)) {
421 return new IXR_ERROR(1, 'Refusing to write an empty new wiki page');
424 if(auth_quickaclcheck($id) < AUTH_EDIT
)
425 return new IXR_Error(1, 'You are not allowed to edit this page');
427 // Check, if page is locked
429 return new IXR_Error(1, 'The page is currently locked');
433 return new IXR_Error(1, 'Positive wordblock check');
435 // autoset summary on new pages
436 if(!page_exists($id) && empty($sum)) {
437 $sum = $lang['created'];
440 // autoset summary on deleted pages
441 if(page_exists($id) && empty($TEXT) && empty($sum)) {
442 $sum = $lang['deleted'];
447 saveWikiText($id,$TEXT,$sum,$minor);
451 // run the indexer if page wasn't indexed yet
452 if(!@file_exists
(metaFN($id, '.indexed'))) {
453 // try to aquire a lock
454 $lock = $conf['lockdir'].'/_indexer.lock';
455 while(!@mkdir
($lock,$conf['dmode'])){
457 if(time()-@filemtime
($lock) > 60*5){
458 // looks like a stale lock - remove it
464 if($conf['dperm']) chmod($lock, $conf['dperm']);
466 require_once(DOKU_INC
.'inc/indexer.php');
471 // we're finished - save and free lock
472 io_saveFile(metaFN($id,'.indexed'),INDEXER_VERSION
);
480 * Uploads a file to the wiki.
482 * Michael Klier <chi@chimeric.de>
484 function putAttachment($id, $file, $params) {
488 $auth = auth_quickaclcheck(getNS($id).':*');
489 if($auth >= AUTH_UPLOAD
) {
491 return new IXR_ERROR(1, 'Filename not given.');
494 $ftmp = $conf['tmpdir'] . '/' . $id;
496 // save temporary file
498 $buff = base64_decode($file);
499 io_saveFile($ftmp, $buff);
502 list($iext, $imime,$dl) = mimetype($id);
506 // get filetype regexp
507 $types = array_keys(getMimeTypes());
508 $types = array_map(create_function('$q','return preg_quote($q,"/");'),$types);
509 $regex = join('|',$types);
511 // because a temp file was created already
512 if(preg_match('/\.('.$regex.')$/i',$fn)) {
513 //check for overwrite
514 $overwrite = @file_exists
($fn);
515 if($overwrite && (!$params['ow'] ||
$auth < AUTH_DELETE
)) {
516 return new IXR_ERROR(1, $lang['uploadexist'].'1');
518 // check for valid content
519 @require_once
(DOKU_INC
.'inc/media.php');
520 $ok = media_contentcheck($ftmp, $imime);
522 return new IXR_ERROR(1, sprintf($lang['uploadexist'].'2', ".$iext"));
523 } elseif($ok == -2) {
524 return new IXR_ERROR(1, $lang['uploadspam']);
525 } elseif($ok == -3) {
526 return new IXR_ERROR(1, $lang['uploadxss']);
529 // prepare event data
534 $data[4] = $overwrite;
537 require_once(DOKU_INC
.'inc/events.php');
538 return trigger_event('MEDIA_UPLOAD_FINISH', $data, array($this, '_media_upload_action'), true);
541 return new IXR_ERROR(1, $lang['uploadwrong']);
544 return new IXR_ERROR(1, "You don't have permissions to upload files.");
549 * Deletes a file from the wiki.
551 * @author Gina Haeussge <osd@foosel.net>
553 function deleteAttachment($id){
554 $auth = auth_quickaclcheck(getNS($id).':*');
555 if($auth < AUTH_DELETE
) return new IXR_ERROR(1, "You don't have permissions to delete files.");
559 // check for references if needed
560 $mediareferences = array();
561 if($conf['refcheck']){
562 require_once(DOKU_INC
.'inc/fulltext.php');
563 $mediareferences = ft_mediause($id,$conf['refshow']);
566 if(!count($mediareferences)){
567 $file = mediaFN($id);
569 require_once(DOKU_INC
.'inc/changelog.php');
570 addMediaLogEntry(time(), $id, DOKU_CHANGE_TYPE_DELETE
);
571 io_sweepNS($id,'mediadir');
574 //something went wrong
575 return new IXR_ERROR(1, 'Could not delete file');
577 return new IXR_ERROR(1, 'File is still referenced');
582 * Moves the temporary file to its final destination.
584 * Michael Klier <chi@chimeric.de>
586 function _media_upload_action($data) {
589 if(is_array($data) && count($data)===5) {
590 io_createNamespace($data[2], 'media');
591 if(rename($data[0], $data[1])) {
592 chmod($data[1], $conf['fmode']);
593 media_notify($data[2], $data[1], $data[3]);
594 // add a log entry to the media changelog
595 require_once(DOKU_INC
.'inc/changelog.php');
597 addMediaLogEntry(time(), $data[2], DOKU_CHANGE_TYPE_EDIT
);
599 addMediaLogEntry(time(), $data[2], DOKU_CHANGE_TYPE_CREATE
);
603 return new IXR_ERROR(1, 'Upload failed.');
606 return new IXR_ERROR(1, 'Upload failed.');
611 * Returns the permissions of a given wiki page
613 function aclCheck($id) {
614 return auth_quickaclcheck($id);
618 * Lists all links contained in a wiki page
620 * @author Michael Klier <chi@chimeric.de>
622 function listLinks($id) {
623 if(auth_quickaclcheck($id) < AUTH_READ
){
624 return new IXR_Error(1, 'You are not allowed to read this page');
628 // resolve page instructions
629 $ins = p_cached_instructions(wikiFN(cleanID($id)));
631 // instantiate new Renderer - needed for interwiki links
632 include(DOKU_INC
.'inc/parser/xhtml.php');
633 $Renderer = new Doku_Renderer_xhtml();
634 $Renderer->interwiki
= getInterwiki();
636 // parse parse instructions
637 foreach($ins as $in) {
641 $link['type'] = 'local';
642 $link['page'] = $in[1][0];
643 $link['href'] = wl($in[1][0]);
644 array_push($links,$link);
647 $link['type'] = 'extern';
648 $link['page'] = $in[1][0];
649 $link['href'] = $in[1][0];
650 array_push($links,$link);
652 case 'interwikilink':
653 $url = $Renderer->_resolveInterWiki($in[1][2],$in[1][3]);
654 $link['type'] = 'extern';
655 $link['page'] = $url;
656 $link['href'] = $url;
657 array_push($links,$link);
666 * Returns a list of recent changes since give timestamp
668 * @author Michael Hamann <michael@content-space.de>
669 * @author Michael Klier <chi@chimeric.de>
671 function getRecentChanges($timestamp) {
672 if(strlen($timestamp) != 10)
673 return new IXR_Error(20, 'The provided value is not a valid timestamp');
675 require_once(DOKU_INC
.'inc/changelog.php');
676 require_once(DOKU_INC
.'inc/pageutils.php');
678 $recents = getRecentsSince($timestamp);
682 foreach ($recents as $recent) {
684 $change['name'] = $recent['id'];
685 $change['lastModified'] = new IXR_Date($recent['date']);
686 $change['author'] = $recent['user'];
687 $change['version'] = $recent['date'];
688 $change['perms'] = $recent['perms'];
689 $change['size'] = @filesize
(wikiFN($recent['id']));
690 array_push($changes, $change);
693 if (!empty($changes)) {
696 // in case we still have nothing at this point
697 return new IXR_Error(30, 'There are no changes in the specified timeframe');
702 * Returns a list of recent media changes since give timestamp
704 * @author Michael Hamann <michael@content-space.de>
705 * @author Michael Klier <chi@chimeric.de>
707 function getRecentMediaChanges($timestamp) {
708 if(strlen($timestamp) != 10)
709 return new IXR_Error(20, 'The provided value is not a valid timestamp');
711 require_once(DOKU_INC
.'inc/changelog.php');
712 require_once(DOKU_INC
.'inc/pageutils.php');
714 $recents = getRecentsSince($timestamp, null, '', RECENTS_MEDIA_CHANGES
);
718 foreach ($recents as $recent) {
720 $change['name'] = $recent['id'];
721 $change['lastModified'] = new IXR_Date($recent['date']);
722 $change['author'] = $recent['user'];
723 $change['version'] = $recent['date'];
724 $change['perms'] = $recent['perms'];
725 $change['size'] = @filesize
(mediaFN($recent['id']));
726 array_push($changes, $change);
729 if (!empty($changes)) {
732 // in case we still have nothing at this point
733 return new IXR_Error(30, 'There are no changes in the specified timeframe');
738 * Returns a list of available revisions of a given wiki page
740 * @author Michael Klier <chi@chimeric.de>
742 function pageVersions($id, $first) {
748 return new IXR_Error(1, 'Empty page ID');
750 require_once(DOKU_INC
.'inc/changelog.php');
752 $revisions = getRevisions($id, $first, $conf['recent']+
1);
754 if(count($revisions)==0 && $first!=0) {
756 $revisions = getRevisions($id, $first, $conf['recent']+
1);
759 if(count($revisions)>0 && $first==0) {
760 array_unshift($revisions, ''); // include current revision
761 array_pop($revisions); // remove extra log entry
765 if(count($revisions)>$conf['recent']) {
767 array_pop($revisions); // remove extra log entry
770 if(!empty($revisions)) {
771 foreach($revisions as $rev) {
772 $file = wikiFN($id,$rev);
773 $time = @filemtime
($file);
774 // we check if the page actually exists, if this is not the
775 // case this can lead to less pages being returned than
776 // specified via $conf['recent']
778 $info = getRevisionInfo($id, $time, 1024);
780 $data['user'] = $info['user'];
781 $data['ip'] = $info['ip'];
782 $data['type'] = $info['type'];
783 $data['sum'] = $info['sum'];
784 $data['modified'] = new IXR_Date($info['date']);
785 $data['version'] = $info['date'];
786 array_push($versions, $data);
797 * The version of Wiki RPC API supported
799 function wiki_RPCVersion(){
805 * Locks or unlocks a given batch of pages
807 * Give an associative array with two keys: lock and unlock. Both should contain a
808 * list of pages to lock or unlock
810 * Returns an associative array with the keys locked, lockfail, unlocked and
811 * unlockfail, each containing lists of pages.
813 function setLocks($set){
817 $unlockfail = array();
819 foreach((array) $set['lock'] as $id){
828 foreach((array) $set['unlock'] as $id){
838 'lockfail' => $lockfail,
839 'unlocked' => $unlocked,
840 'unlockfail' => $unlockfail,
844 function getAPIVersion(){
845 return DOKU_XMLRPC_API_VERSION
;
848 function login($user,$pass){
851 if(!$conf['useacl']) return 0;
853 if($auth->canDo('external')){
854 return $auth->trustExternal($user,$pass,false);
856 return auth_login($user,$pass,false,true);
861 $server = new dokuwiki_xmlrpc_server();
863 // vim:ts=4:sw=4:et:enc=utf-8: