]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/XmlRpcServer.php
Fixed the boolean values: false => 0
[SourceForge/phpwiki.git] / lib / XmlRpcServer.php
1 <?php 
2 // $Id: XmlRpcServer.php,v 1.9 2004-12-10 00:14:41 rurban Exp $
3 /* Copyright (C) 2002, Lawrence Akka <lakka@users.sourceforge.net>
4  *
5  * LICENCE
6  * =======
7  * This file is part of PhpWiki.
8  * 
9  * PhpWiki is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  * 
14  * PhpWiki is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  * 
19  * You should have received a copy of the GNU General Public License
20  * along with PhpWiki; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  *
23  * LIBRARY USED - POSSIBLE PROBLEMS
24  * ================================
25  * 
26  * This file provides an XML-RPC interface for PhpWiki.  It uses the XML-RPC 
27  * library for PHP by Edd Dumbill - see http://xmlrpc.usefulinc.com/php.html 
28  * for details.
29  *
30  * PHP >= 4.1.0 includes experimental support for the xmlrpc-epi c library 
31  * written by Dan Libby (see http://uk2.php.net/manual/en/ref.xmlrpc.php).  
32  * This is not compiled into PHP by default.  If it *is* compiled into your installation
33  * (ie you have --with-xmlrpc) there may well be namespace conflicts with the xml-rpc
34  * library used by this code, and you will get errors.
35  * 
36  * INTERFACE SPECIFICTION
37  * ======================
38  *  
39  * The interface specification is that discussed at 
40  * http://www.ecyrd.com/JSPWiki/Wiki.jsp?page=WikiRPCInterface
41  * 
42  * See also http://www.usemod.com/cgi-bin/mb.pl?XmlRpc
43  * or http://www.devshed.com/c/a/PHP/Using-XMLRPC-with-PHP/
44  * 
45  * Note: All XMLRPC methods are automatically prefixed with "wiki."
46  *       eg. "wiki.getAllPages"
47 */
48
49 /*
50 ToDo:
51         Resolve namespace conflicts
52         Remove all warnings from xmlrpc.inc 
53         Return list of external links in listLinks
54         Support RSS2 cloud subscription
55         Test hwiki.jar xmlrpc interface (java visualization plugin)
56 Done:
57         Make use of the xmlrpc extension if found. http://xmlrpc-epi.sourceforge.net/
58 */
59
60 // Intercept GET requests from confused users.  Only POST is allowed here!
61 // There is some indication that $HTTP_SERVER_VARS is deprecated in php > 4.1.0
62 // in favour of $_Server, but as far as I know, it still works.
63 if ($GLOBALS['HTTP_SERVER_VARS']['REQUEST_METHOD'] != "POST")  
64 {
65     die('This is the address of the XML-RPC interface.' .
66         '  You must use XML-RPC calls to access information here');
67 }
68
69 // All these global declarations make it so that this file
70 // (XmlRpcServer.php) can be included within a function body
71 // (not in global scope), and things will still work....
72
73 global $xmlrpcI4, $xmlrpcInt, $xmlrpcBoolean, $xmlrpcDouble, $xmlrpcString;
74 global $xmlrpcDateTime, $xmlrpcBase64, $xmlrpcArray, $xmlrpcStruct;
75 global $xmlrpcTypes;
76 global $xmlEntities;
77 global $xmlrpcerr, $xmlrpcstr;
78 global $xmlrpc_defencoding;
79 global $xmlrpcName, $xmlrpcVersion;
80 global $xmlrpcerruser, $xmlrpcerrxml;
81 global $xmlrpc_backslash;
82 global $_xh;
83
84 if (loadPhpExtension('xmlrpc')) { // fast c lib
85     define('XMLRPC_EXT_LOADED', true);
86
87     global $xmlrpc_util_path;
88     $xmlrpc_util_path = dirname(__FILE__)."/XMLRPC/";
89     include_once("lib/XMLRPC/xmlrpc_emu.inc"); 
90     include_once("lib/XMLRPC/xmlrpcs_emu.inc");
91
92  } else { // slow php lib
93     define('XMLRPC_EXT_LOADED', true);
94
95     // Include the php XML-RPC library
96     include_once("lib/XMLRPC/xmlrpc.inc");
97
98     global $_xmlrpcs_dmap;
99     global $_xmlrpcs_debug;
100     include_once("lib/XMLRPC/xmlrpcs.inc");
101 }
102  
103
104 //  API version
105 define ("WIKI_XMLRPC_VERSION", "1.1");
106
107 /**
108  * Helper function:  Looks up a page revision (most recent by default) in the wiki database
109  * 
110  * @param xmlrpcmsg $params :  string pagename [int version]
111  * @return WikiDB _PageRevision object, or false if no such page
112  */
113
114 function _getPageRevision ($params)
115 {
116     global $request;
117     $ParamPageName = $params->getParam(0);
118     $ParamVersion = $params->getParam(1);
119     $pagename = short_string_decode($ParamPageName->scalarval());
120     $version =  ($ParamVersion) ? ($ParamVersion->scalarval()):(0);
121     // FIXME:  test for version <=0 ??
122     $dbh = $request->getDbh();
123     if ($dbh->isWikiPage($pagename)) {
124         $page = $dbh->getPage($pagename);
125         if (!$version) {
126             $revision = $page->getCurrentRevision();
127         } else {
128             $revision = $page->getRevision($version);
129         } 
130         return $revision;
131     } 
132     return false;
133
134
135 /*
136  * Helper functions for encoding/decoding strings.
137  *
138  * According to WikiRPC spec, all returned strings take one of either
139  * two forms.  Short strings (page names, and authors) are converted to
140  * UTF-8, then rawurlencode()d, and returned as XML-RPC <code>strings</code>.
141  * Long strings (page content) are converted to UTF-8 then returned as
142  * XML-RPC <code>base64</code> binary objects.
143  */
144
145 /**
146  * Urlencode ASCII control characters.
147  *
148  * (And control characters...)
149  *
150  * @param string $str
151  * @return string
152  * @see urlencode
153  */
154 function UrlencodeControlCharacters($str) {
155     return preg_replace('/([\x00-\x1F])/e', "urlencode('\\1')", $str);
156 }
157
158 /**
159  * Convert a short string (page name, author) to xmlrpcval.
160  */
161 function short_string ($str) {
162     return new xmlrpcval(UrlencodeControlCharacters(utf8_encode($str)), 'string');
163 }
164
165 /**
166  * Convert a large string (page content) to xmlrpcval.
167  */
168 function long_string ($str) {
169     return new xmlrpcval(utf8_encode($str), 'base64');
170 }
171
172 /**
173  * Decode a short string (e.g. page name)
174  */
175 function short_string_decode ($str) {
176     return utf8_decode(urldecode($str));
177 }
178
179 /**
180  * Get an xmlrpc "No such page" error message
181  */
182 function NoSuchPage () 
183 {
184     global $xmlrpcerruser;
185     return new xmlrpcresp(0, $xmlrpcerruser + 1, "No such page");
186 }
187
188
189 // ****************************************************************************
190 // Main API functions follow
191 // ****************************************************************************
192 global $wiki_dmap;
193
194 /**
195  * int getRPCVersionSupported(): Returns 1 for this version of the API 
196  */
197 $wiki_dmap['getRPCVersionSupported']
198 = array('signature'     => array(array($xmlrpcInt)),
199         'documentation' => 'Get the version of the wiki API',
200         'function'      => 'getRPCVersionSupported');
201
202 // The function must be a function in the global scope which services the XML-RPC
203 // method.
204 function getRPCVersionSupported($params)
205 {
206     return new xmlrpcresp(new xmlrpcval(WIKI_XMLRPC_VERSION, "string"));
207 }
208
209 /**
210  * array getRecentChanges(Date timestamp) : Get list of changed pages since 
211  * timestamp, which should be in UTC. The result is an array, where each element
212  * is a struct: 
213  *     name (string) : Name of the page. The name is UTF-8 with URL encoding to make it ASCII. 
214  *     lastModified (date) : Date of last modification, in UTC. 
215  *     author (string) : Name of the author (if available). Again, name is UTF-8 with URL encoding. 
216  *         version (int) : Current version. 
217  * A page MAY be specified multiple times. A page MAY NOT be specified multiple 
218  * times with the same modification date.
219  */
220 $wiki_dmap['getRecentChanges']
221 = array('signature'     => array(array($xmlrpcArray, $xmlrpcDateTime)),
222         'documentation' => 'Get a list of changed pages since [timestamp]',
223         'function'      => 'getRecentChanges');
224
225 function getRecentChanges($params)
226 {
227     global $request;
228     // Get the first parameter as an ISO 8601 date.  Assume UTC
229     $encoded_date = $params->getParam(0);
230     $datetime = iso8601_decode($encoded_date->scalarval(), 1);
231     $dbh = $request->getDbh();
232     $pages = array();
233     $iterator = $dbh->mostRecent(array('since' => $datetime));
234     while ($page = $iterator->next()) {
235         // $page contains a WikiDB_PageRevision object
236         // no need to url encode $name, because it is already stored in that format ???
237         $name = short_string($page->getPageName());
238         $lastmodified = new xmlrpcval(iso8601_encode($page->get('mtime')), "dateTime.iso8601");
239         $author = short_string($page->get('author'));
240         $version = new xmlrpcval($page->getVersion(), 'int');
241
242         // Build an array of xmlrpc structs
243         $pages[] = new xmlrpcval(array('name'=>$name, 
244                                        'lastModified'=>$lastmodified,
245                                        'author'=>$author,
246                                        'version'=>$version),
247                                  'struct');
248     } 
249     return new xmlrpcresp(new xmlrpcval($pages, "array"));
250
251
252
253 /**
254  * base64 getPage( String pagename ): Get the raw Wiki text of page, latest version. 
255  * Page name must be UTF-8, with URL encoding. Returned value is a binary object,
256  * with UTF-8 encoded page data.
257  */
258 $wiki_dmap['getPage']
259 = array('signature'     => array(array($xmlrpcBase64, $xmlrpcString)),
260         'documentation' => 'Get the raw Wiki text of the current version of a page',
261         'function'      => 'getPage');
262
263 function getPage($params)
264 {
265     $revision = _getPageRevision($params);
266
267     if (! $revision)
268         return NoSuchPage();
269
270     return new xmlrpcresp(long_string($revision->getPackedContent()));
271 }
272  
273
274 /**
275  * base64 getPageVersion( String pagename, int version ): Get the raw Wiki text of page.
276  * Returns UTF-8, expects UTF-8 with URL encoding.
277  */
278 $wiki_dmap['getPageVersion']
279 = array('signature'     => array(array($xmlrpcBase64, $xmlrpcString, $xmlrpcInt)),
280         'documentation' => 'Get the raw Wiki text of a page version',
281         'function'      => 'getPageVersion');
282
283 function getPageVersion($params)
284 {
285     // error checking is done in getPage
286     return getPage($params);
287
288
289 /**
290  * base64 getPageHTML( String pagename ): Return page in rendered HTML. 
291  * Returns UTF-8, expects UTF-8 with URL encoding.
292  */
293
294 $wiki_dmap['getPageHTML']
295 = array('signature'     => array(array($xmlrpcBase64, $xmlrpcString)),
296         'documentation' => 'Get the current version of a page rendered in HTML',
297         'function'      => 'getPageHTML');
298
299 function getPageHTML($params)
300 {
301     $revision = _getPageRevision($params);
302     if (!$revision)
303         return NoSuchPage();
304     
305     $content = $revision->getTransformedContent();
306     $html = $content->asXML();
307     // HACK: Get rid of outer <div class="wikitext">
308     if (preg_match('/^\s*<div class="wikitext">/', $html, $m1)
309         && preg_match('@</div>\s*$@', $html, $m2)) {
310         $html = substr($html, strlen($m1[0]), -strlen($m2[0]));
311     }
312
313     return new xmlrpcresp(long_string($html));
314
315
316 /**
317  * base64 getPageHTMLVersion( String pagename, int version ): Return page in rendered HTML, UTF-8.
318  */
319 $wiki_dmap['getPageHTMLVersion']
320 = array('signature'     => array(array($xmlrpcBase64, $xmlrpcString, $xmlrpcInt)),
321         'documentation' => 'Get a version of a page rendered in HTML',
322         'function'      => 'getPageHTMLVersion');
323
324 function getPageHTMLVersion($params)
325 {
326     return getPageHTML($params);
327
328
329 /**
330  * getAllPages(): Returns a list of all pages. The result is an array of strings.
331  */
332 $wiki_dmap['getAllPages']
333 = array('signature'     => array(array($xmlrpcArray)),
334         'documentation' => 'Returns a list of all pages as an array of strings', 
335         'function'      => 'getAllPages');
336
337 function getAllPages($params)
338 {
339     global $request;
340     $dbh = $request->getDbh();
341     $iterator = $dbh->getAllPages();
342     $pages = array();
343     while ($page = $iterator->next()) {
344         $pages[] = short_string($page->getName());
345     } 
346     return new xmlrpcresp(new xmlrpcval($pages, "array"));
347
348
349 /**
350  * struct getPageInfo( string pagename ) : returns a struct with elements: 
351  *   name (string): the canonical page name 
352  *   lastModified (date): Last modification date 
353  *   version (int): current version 
354  *       author (string): author name 
355  */
356 $wiki_dmap['getPageInfo']
357 = array('signature'     => array(array($xmlrpcStruct, $xmlrpcString)),
358         'documentation' => 'Gets info about the current version of a page',
359         'function'      => 'getPageInfo');
360
361 function getPageInfo($params)
362 {
363     $revision = _getPageRevision($params);
364     if (!$revision)
365         return NoSuchPage();
366     
367     $name = short_string($revision->getPageName());
368     $version = new xmlrpcval ($revision->getVersion(), "int");
369     $lastmodified = new xmlrpcval(iso8601_encode($revision->get('mtime'), 0),
370                                   "dateTime.iso8601");
371     $author = short_string($revision->get('author'));
372         
373     return new xmlrpcresp(new xmlrpcval(array('name' => $name, 
374                                               'lastModified' => $lastmodified,
375                                               'version' => $version, 
376                                               'author' => $author), 
377                                         "struct"));
378
379
380 /**
381  * struct getPageInfoVersion( string pagename, int version ) : returns
382  * a struct just like plain getPageInfo(), but this time for a
383  * specific version.
384  */
385 $wiki_dmap['getPageInfoVersion']
386 = array('signature'     => array(array($xmlrpcStruct, $xmlrpcString, $xmlrpcInt)),
387         'documentation' => 'Gets info about a page version',
388         'function'      => 'getPageInfoVersion');
389
390 function getPageInfoVersion($params)
391 {
392     return getPageInfo($params);
393 }
394
395  
396 /*  array listLinks( string pagename ): Lists all links for a given page. The
397  *  returned array contains structs, with the following elements: 
398  *       name (string) : The page name or URL the link is to. 
399  *       type (int) : The link type. Zero (0) for internal Wiki link,
400  *         one (1) for external link (URL - image link, whatever).
401  */
402 $wiki_dmap['listLinks']
403 = array('signature'     => array(array($xmlrpcArray, $xmlrpcString)),
404         'documentation' => 'Lists all links for a given page',
405         'function'      => 'listLinks');
406
407 function listLinks($params)
408 {
409     global $request;
410     
411     $ParamPageName = $params->getParam(0);
412     $pagename = short_string_decode($ParamPageName->scalarval());
413     $dbh = $request->getDbh();
414     if (! $dbh->isWikiPage($pagename))
415         return NoSuchPage();
416
417     $page = $dbh->getPage($pagename);
418     /*
419     $linkiterator = $page->getLinks(false);
420     $linkstruct = array();
421     while ($currentpage = $linkiterator->next()) {
422         $currentname = $currentpage->getName();
423         $name = short_string($currentname);
424         // NB no clean way to extract a list of external links yet, so
425         // only internal links returned.  ie all type 'local'.
426         $type = new xmlrpcval('local');
427
428         // Compute URL to page
429         $args = array();
430         $currentrev = $currentpage->getCurrentRevision();
431         if ($currentrev->hasDefaultContents())
432             $args['action'] = 'edit';
433
434         // FIXME: Autodetected value of VIRTUAL_PATH wrong,
435         // this make absolute URLs contstructed by WikiURL wrong.
436         // Also, if USE_PATH_INFO is false, WikiURL is wrong
437         // due to its use of SCRIPT_NAME.
438         $use_abspath = USE_PATH_INFO && ! preg_match('/RPC2.php$/', VIRTUAL_PATH);
439         $href = new xmlrpcval(WikiURL($currentname, $args, $use_abspath));
440             
441         $linkstruct[] = new xmlrpcval(array('name'=> $name,
442                                             'type'=> $type,
443                                             'href' => $href),
444                                       "struct");
445     }
446     */
447     
448     $current = $page->getCurrentRevision();
449     $content = $current->getTransformedContent();
450     $links = $content->getLinkInfo();
451
452     foreach ($links as $link) {
453         // We used to give an href for unknown pages that
454         // included action=edit.  I think that's probably the
455         // wrong thing to do.
456         $linkstruct[] = new xmlrpcval(array('name'=> short_string($link->page),
457                                             'type'=> new xmlrpcval($link->type),
458                                             'href' => short_string($link->href)),
459                                       "struct");
460     }
461         
462     return new xmlrpcresp(new xmlrpcval ($linkstruct, "array"));
463
464
465 /**
466  * Publish-Subscribe
467  * Client subscribes to a RecentChanges-like channel, getting a short 
468  * callback notification on every change. Like PageChangeNotification, just shorter 
469  * and more complicated
470  * RSS2 support (not yet), since radio userland's rss-0.92. now called RSS2.
471  * BTW: Radio Userland deprecated this interface.
472  *
473  * boolean wiki.rssPleaseNotify ( notifyProcedure, port, path, protocol, urlList )
474  *   returns: true or false 
475  *
476  * Check of the channel behind the rssurl has a cloud element, 
477  * if the client has a direct IP connection (no NAT),
478  * register the client on the WikiDB notification handler
479  *
480  * http://backend.userland.com/publishSubscribeWalkthrough
481  * http://www.soapware.org/xmlStorageSystem#rssPleaseNotify
482  * http://www.thetwowayweb.com/soapmeetsrss#rsscloudInterface
483  */
484 $wiki_dmap['rssPleaseNotify']
485 = array('signature'     => array(array($xmlrpcStruct, $xmlrpcBoolean)),
486         'documentation' => 'RSS2 change notification subscriber channel',
487         'function'      => 'rssPleaseNotify');
488
489 function rssPleaseNotify($params)
490 {
491     // register the clients IP
492     return new xmlrpcresp(new xmlrpcval (0, "boolean"));
493 }
494
495 $wiki_dmap['mailPasswordToUser']
496 = array('signature'     => array(array($xmlrpcStruct, $xmlrpcString)),
497         'documentation' => 'RSS2 change notification subscriber channel',
498         'function'      => 'mailPasswordToUser');
499
500 function mailPasswordToUser($params)
501 {
502     return new xmlrpcresp(new xmlrpcval (0, "boolean"));
503 }
504  
505 /** 
506  * Construct the server instance, and set up the dispatch map, 
507  * which maps the XML-RPC methods on to the wiki functions.
508  * Provide the "wiki." prefix to each function
509  */
510 class XmlRpcServer extends xmlrpc_server
511 {
512     function XmlRpcServer ($request = false) {
513         global $wiki_dmap;
514         foreach ($wiki_dmap as $name => $val)
515             $dmap['wiki.' . $name] = $val;
516
517         $this->xmlrpc_server($dmap, 0 /* delay service*/);
518     }
519
520     function service () {
521         global $ErrorManager;
522
523         $ErrorManager->pushErrorHandler(new WikiMethodCb($this, '_errorHandler'));
524         xmlrpc_server::service();
525         $ErrorManager->popErrorHandler();
526     }
527     
528     function _errorHandler ($e) {
529         $msg = htmlspecialchars($e->asString());
530         // '--' not allowed within xml comment
531         $msg = str_replace('--', '&#45;&#45;', $msg);
532         xmlrpc_debugmsg($msg);
533         return true;
534     }
535 }
536
537
538 // (c-file-style: "gnu")
539 // Local Variables:
540 // mode: php
541 // tab-width: 8
542 // c-basic-offset: 4
543 // c-hanging-comment-ender-p: nil
544 // indent-tabs-mode: nil
545 // End:   
546 ?>