2 // $Id: XmlRpcServer.php,v 1.4 2002-09-17 23:26:09 dairiki Exp $
3 /* Copyright (C) 2002, Lawrence Akka <lakka@users.sourceforge.net>
7 * This file is part of PhpWiki.
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.
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.
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
23 * LIBRARY USED - POSSIBLE PROBLEMS
24 * ================================
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
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). This
32 * 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.
36 * INTERFACE SPECIFICTION
37 * ======================
39 * The interface specification is that discussed at
40 * http://www.ecyrd.com/JSPWiki/Wiki.jsp?page=WikiRPCInterface
42 * See also http://www.usemod.com/cgi-bin/mb.pl?XmlRpc
44 * NB: All XMLRPC methods should be prefixed with "wiki."
50 // Remove all warnings from xmlrpc.inc
51 // Return list of external links in listLinks
54 // Intercept GET requests from confused users. Only POST is allowed here!
55 // There is some indication that $HTTP_SERVER_VARS is deprecated in php > 4.1.0
56 // in favour of $_Server, but as far as I know, it still works.
57 if ($GLOBALS['HTTP_SERVER_VARS']['REQUEST_METHOD'] != "POST")
59 die('This is the address of the XML-RPC interface.' .
60 ' You must use XML-RPC calls to access information here');
63 // Include the php XML-RPC library
65 // All these global declarations make it so that this file
66 // (XmlRpcServer.php) can be included within a function body
67 // (not in global scope), and things will still work....
69 global $xmlrpcI4, $xmlrpcInt, $xmlrpcBoolean, $xmlrpcDouble, $xmlrpcString;
70 global $xmlrpcDateTime, $xmlrpcBase64, $xmlrpcArray, $xmlrpcStruct;
73 global $xmlrpcerr, $xmlrpcstr;
74 global $xmlrpc_defencoding;
75 global $xmlrpcName, $xmlrpcVersion;
76 global $xmlrpcerruser, $xmlrpcerrxml;
77 global $xmlrpc_backslash;
79 include_once("lib/XMLRPC/xmlrpc.inc");
81 global $_xmlrpcs_dmap;
82 global $_xmlrpcs_debug;
83 include_once("lib/XMLRPC/xmlrpcs.inc");
86 define ("WIKI_XMLRPC_VERSION", 1);
89 * Helper function: Looks up a page revision (most recent by default) in the wiki database
91 * @param xmlrpcmsg $params : string pagename [int version]
92 * @return WikiDB _PageRevision object, or false if no such page
95 function _getPageRevision ($params)
98 $ParamPageName = $params->getParam(0);
99 $ParamVersion = $params->getParam(1);
100 $pagename = short_string_decode($ParamPageName->scalarval());
101 $version = ($ParamVersion) ? ($ParamVersion->scalarval()):(0);
102 // FIXME: test for version <=0 ??
103 $dbh = $request->getDbh();
104 if ($dbh->isWikiPage($pagename)) {
105 $page = $dbh->getPage($pagename);
107 $revision = $page->getCurrentRevision();
109 $revision = $page->getRevision($version);
117 * Helper functions for encoding/decoding strings.
119 * According to WikiRPC spec, all returned strings take one of either
120 * two forms. Short strings (page names, and authors) are converted to
121 * UTF-8, then rawurlencode()d, and returned as XML-RPC <code>strings</code>.
122 * Long strings (page content) are converted to UTF-8 then returned as
123 * XML-RPC <code>base64</code> binary objects.
127 * Urlencode ASCII control characters.
129 * (And control characters...)
135 function UrlencodeControlCharacters($str) {
136 return preg_replace('/([\x00-\x1F])/e', "urlencode('\\1')", $str);
140 * Convert a short string (page name, author) to xmlrpcval.
142 function short_string ($str) {
143 return new xmlrpcval(UrlencodeControlCharacters(utf8_encode($str)), 'string');
147 * Convert a large string (page content) to xmlrpcval.
149 function long_string ($str) {
150 return new xmlrpcval(utf8_encode($str), 'base64');
154 * Decode a short string (e.g. page name)
156 function short_string_decode ($str) {
157 return utf8_decode(urldecode($str));
161 * Get an xmlrpc "No such page" error message
163 function NoSuchPage ()
165 global $xmlrpcerruser;
166 return new xmlrpcresp(0, $xmlrpcerruser + 1, "No such page");
170 // ****************************************************************************
171 // Main API functions follow
172 // ****************************************************************************
176 * int getRPCVersionSupported(): Returns 1 for this version of the API
178 $wiki_dmap['getRPCVersionSupported']
179 = array('signature' => array(array($xmlrpcInt)),
180 'documentation' => 'Get the version of the wiki API',
181 'function' => 'getRPCVersionSupported');
183 // The function must be a function in the global scope which services the XML-RPC
185 function getRPCVersionSupported($params)
187 return new xmlrpcresp(new xmlrpcval(WIKI_XMLRPC_VERSION, "int"));
191 * array getRecentChanges(Date timestamp) : Get list of changed pages since
192 * timestamp, which should be in UTC. The result is an array, where each element
194 * name (string) : Name of the page. The name is UTF-8 with URL encoding to make it ASCII.
195 * lastModified (date) : Date of last modification, in UTC.
196 * author (string) : Name of the author (if available). Again, name is UTF-8 with URL encoding.
197 * version (int) : Current version.
198 * A page MAY be specified multiple times. A page MAY NOT be specified multiple
199 * times with the same modification date.
201 $wiki_dmap['getRecentChanges']
202 = array('signature' => array(array($xmlrpcArray, $xmlrpcDateTime)),
203 'documentation' => 'Get a list of changed pages since [timestamp]',
204 'function' => 'getRecentChanges');
206 function getRecentChanges($params)
209 // Get the first parameter as an ISO 8601 date. Assume UTC
210 $encoded_date = $params->getParam(0);
211 $datetime = iso8601_decode($encoded_date->scalarval(), 1);
212 $dbh = $request->getDbh();
214 $iterator = $dbh->mostRecent(array('since' => $datetime));
215 while ($page = $iterator->next()) {
216 // $page contains a WikiDB_PageRevision object
217 // no need to url encode $name, because it is already stored in that format ???
218 $name = short_string($page->getPageName());
219 $lastmodified = new xmlrpcval(iso8601_encode($page->get('mtime')), "dateTime.iso8601");
220 $author = short_string($page->get('author'));
221 $version = new xmlrpcval($page->getVersion(), 'int');
223 // Build an array of xmlrpc structs
224 $pages[] = new xmlrpcval(array('name'=>$name,
225 'lastModified'=>$lastmodified,
227 'version'=>$version),
230 return new xmlrpcresp(new xmlrpcval($pages, "array"));
235 * base64 getPage( String pagename ): Get the raw Wiki text of page, latest version.
236 * Page name must be UTF-8, with URL encoding. Returned value is a binary object,
237 * with UTF-8 encoded page data.
239 $wiki_dmap['getPage']
240 = array('signature' => array(array($xmlrpcBase64, $xmlrpcString)),
241 'documentation' => 'Get the raw Wiki text of the current version of a page',
242 'function' => 'getPage');
244 function getPage($params)
246 $revision = _getPageRevision($params);
251 return new xmlrpcresp(long_string($revision->getPackedContent()));
256 * base64 getPageVersion( String pagename, int version ): Get the raw Wiki text of page.
257 * Returns UTF-8, expects UTF-8 with URL encoding.
259 $wiki_dmap['getPageVersion']
260 = array('signature' => array(array($xmlrpcBase64, $xmlrpcString, $xmlrpcInt)),
261 'documentation' => 'Get the raw Wiki text of a page version',
262 'function' => 'getPageVersion');
264 function getPageVersion($params)
266 // error checking is done in getPage
267 return getPage($params);
271 * base64 getPageHTML( String pagename ): Return page in rendered HTML.
272 * Returns UTF-8, expects UTF-8 with URL encoding.
275 $wiki_dmap['getPageHTML']
276 = array('signature' => array(array($xmlrpcBase64, $xmlrpcString)),
277 'documentation' => 'Get the current version of a page rendered in HTML',
278 'function' => 'getPageHTML');
280 function getPageHTML($params)
282 $revision = _getPageRevision($params);
286 include_once('lib/PageType.php');
287 $content = array(PageType($revision));
289 // Get rid of outer <div class="wikitext">
290 while (count($content) == 1 && isa($content[0], 'XmlContent')) {
291 if (isa($content[0], 'XmlElement') && $content[0]->getTag() != 'div')
293 $content = $content[0]->getContent();
296 return new xmlrpcresp(long_string(AsXML($content)));
300 * base64 getPageHTMLVersion( String pagename, int version ): Return page in rendered HTML, UTF-8.
302 $wiki_dmap['getPageHTMLVersion']
303 = array('signature' => array(array($xmlrpcBase64, $xmlrpcString, $xmlrpcInt)),
304 'documentation' => 'Get a version of a page rendered in HTML',
305 'function' => 'getPageHTMLVersion');
307 function getPageHTMLVersion($params)
309 return getPageHTML($params);
313 * getAllPages(): Returns a list of all pages. The result is an array of strings.
315 $wiki_dmap['getAllPages']
316 = array('signature' => array(array($xmlrpcArray)),
317 'documentation' => 'Returns a list of all pages as an array of strings',
318 'function' => 'getAllPages');
320 function getAllPages($params)
323 $dbh = $request->getDbh();
324 $iterator = $dbh->getAllPages();
326 while ($page = $iterator->next()) {
327 $pages[] = short_string($page->getName());
329 return new xmlrpcresp(new xmlrpcval($pages, "array"));
333 * struct getPageInfo( string pagename ) : returns a struct with elements:
334 * name (string): the canonical page name
335 * lastModified (date): Last modification date
336 * version (int): current version
337 * author (string): author name
339 $wiki_dmap['getPageInfo']
340 = array('signature' => array(array($xmlrpcStruct, $xmlrpcString)),
341 'documentation' => 'Gets info about the current version of a page',
342 'function' => 'getPageInfo');
344 function getPageInfo($params)
346 $revision = _getPageRevision($params);
350 $name = short_string($revision->getPageName());
351 $version = new xmlrpcval ($revision->getVersion(), "int");
352 $lastmodified = new xmlrpcval(iso8601_encode($revision->get('mtime'), 0),
354 $author = short_string($revision->get('author'));
356 return new xmlrpcresp(new xmlrpcval(array('name' => $name,
357 'lastModified' => $lastmodified,
358 'version' => $version,
359 'author' => $author),
364 * struct getPageInfoVersion( string pagename, int version ) : returns
365 * a struct just like plain getPageInfo(), but this time for a
368 $wiki_dmap['getPageInfoVersion']
369 = array('signature' => array(array($xmlrpcStruct, $xmlrpcString, $xmlrpcInt)),
370 'documentation' => 'Gets info about a page version',
371 'function' => 'getPageInfoVersion');
373 function getPageInfoVersion($params)
375 return getPageInfo($params);
379 /* array listLinks( string pagename ): Lists all links for a given page. The
380 * returned array contains structs, with the following elements:
381 * name (string) : The page name or URL the link is to.
382 * type (int) : The link type. Zero (0) for internal Wiki link,
383 * one (1) for external link (URL - image link, whatever).
385 $wiki_dmap['listLinks']
386 = array('signature' => array(array($xmlrpcArray, $xmlrpcString)),
387 'documentation' => 'Lists all links for a given page',
388 'function' => 'listLinks');
390 function listLinks($params)
394 $ParamPageName = $params->getParam(0);
395 $pagename = short_string_decode($ParamPageName->scalarval());
396 $dbh = $request->getDbh();
397 if (! $dbh->isWikiPage($pagename))
400 $page = $dbh->getPage($pagename);
402 $linkiterator = $page->getLinks(false);
403 $linkstruct = array();
404 while ($currentpage = $linkiterator->next()) {
405 $currentname = $currentpage->getName();
406 $name = short_string($currentname);
407 // NB no clean way to extract a list of external links yet, so
408 // only internal links returned. ie all type 'local'.
409 $type = new xmlrpcval('local');
411 // Compute URL to page
413 $currentrev = $currentpage->getCurrentRevision();
414 if ($currentrev->hasDefaultContents())
415 $args['action'] = 'edit';
417 // FIXME: Autodetected value of VIRTUAL_PATH wrong,
418 // this make absolute URLs contstructed by WikiURL wrong.
419 // Also, if USE_PATH_INFO is false, WikiURL is wrong
420 // due to its use of SCRIPT_NAME.
421 $use_abspath = USE_PATH_INFO && ! preg_match('/RPC2.php$/', VIRTUAL_PATH);
422 $href = new xmlrpcval(WikiURL($currentname, $args, $use_abspath));
424 $linkstruct[] = new xmlrpcval(array('name'=> $name,
431 $current = $page->getCurrentRevision();
432 list ($pages, $urls) = ExtractLinks($current->getContent());
434 $type = new xmlrpcval("internal");
435 foreach ($pages as $wikiword) {
436 $name = short_string($wikiword);
438 if (! $dbh->isWikiPage($wikiword))
439 $args['action'] = 'edit';
440 // FIXME: see comments in above commented sections
441 // about possible screwed up absolute URLS...
442 $href = short_string(WikiURL($wikiword, $args, 'abspath'));
443 $linkstruct[] = new xmlrpcval(array('name'=> $name,
449 $type = new xmlrpcval("external");
450 foreach ($urls as $url) {
451 $href = short_string($url);
452 $linkstruct[] = new xmlrpcval(array('name'=> $href,
458 return new xmlrpcresp(new xmlrpcval ($linkstruct, "array"));
461 // Construct the server instance, and set up the despatch map, which maps
462 // the XML-RPC methods onto the wiki functions
463 class XmlRpcServer extends xmlrpc_server
465 function XmlRpcServer ($request = false) {
467 foreach ($wiki_dmap as $name => $val)
468 $dmap['wiki.' . $name] = $val;
470 $this->xmlrpc_server($dmap, 0 /* delay service*/);
473 function service () {
474 global $ErrorManager;
476 $ErrorManager->pushErrorHandler(new WikiMethodCb($this, '_errorHandler'));
477 xmlrpc_server::service();
478 $ErrorManager->popErrorHandler();
481 function _errorHandler ($e) {
482 $msg = htmlspecialchars($e->asString());
483 // '--' not allowed within xml comment
484 $msg = str_replace('--', '--', $msg);
485 xmlrpc_debugmsg($msg);
491 // (c-file-style: "gnu")
496 // c-hanging-comment-ender-p: nil
497 // indent-tabs-mode: nil