2 // $Id: XmlRpcServer.php,v 1.8 2004-12-06 19:49:56 rurban 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).
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.
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 // Make use of the xmlrpc extension if found. Resolve namespace conflicts
51 // Remove all warnings from xmlrpc.inc
52 // Return list of external links in listLinks
53 // RSS2 cloud subscription
55 // Intercept GET requests from confused users. Only POST is allowed here!
56 // There is some indication that $HTTP_SERVER_VARS is deprecated in php > 4.1.0
57 // in favour of $_Server, but as far as I know, it still works.
58 if ($GLOBALS['HTTP_SERVER_VARS']['REQUEST_METHOD'] != "POST")
60 die('This is the address of the XML-RPC interface.' .
61 ' You must use XML-RPC calls to access information here');
64 // Include the php XML-RPC library
66 // All these global declarations make it so that this file
67 // (XmlRpcServer.php) can be included within a function body
68 // (not in global scope), and things will still work....
70 global $xmlrpcI4, $xmlrpcInt, $xmlrpcBoolean, $xmlrpcDouble, $xmlrpcString;
71 global $xmlrpcDateTime, $xmlrpcBase64, $xmlrpcArray, $xmlrpcStruct;
74 global $xmlrpcerr, $xmlrpcstr;
75 global $xmlrpc_defencoding;
76 global $xmlrpcName, $xmlrpcVersion;
77 global $xmlrpcerruser, $xmlrpcerrxml;
78 global $xmlrpc_backslash;
80 include_once("lib/XMLRPC/xmlrpc.inc");
82 global $_xmlrpcs_dmap;
83 global $_xmlrpcs_debug;
84 include_once("lib/XMLRPC/xmlrpcs.inc");
87 define ("WIKI_XMLRPC_VERSION", "1.1");
90 * Helper function: Looks up a page revision (most recent by default) in the wiki database
92 * @param xmlrpcmsg $params : string pagename [int version]
93 * @return WikiDB _PageRevision object, or false if no such page
96 function _getPageRevision ($params)
99 $ParamPageName = $params->getParam(0);
100 $ParamVersion = $params->getParam(1);
101 $pagename = short_string_decode($ParamPageName->scalarval());
102 $version = ($ParamVersion) ? ($ParamVersion->scalarval()):(0);
103 // FIXME: test for version <=0 ??
104 $dbh = $request->getDbh();
105 if ($dbh->isWikiPage($pagename)) {
106 $page = $dbh->getPage($pagename);
108 $revision = $page->getCurrentRevision();
110 $revision = $page->getRevision($version);
118 * Helper functions for encoding/decoding strings.
120 * According to WikiRPC spec, all returned strings take one of either
121 * two forms. Short strings (page names, and authors) are converted to
122 * UTF-8, then rawurlencode()d, and returned as XML-RPC <code>strings</code>.
123 * Long strings (page content) are converted to UTF-8 then returned as
124 * XML-RPC <code>base64</code> binary objects.
128 * Urlencode ASCII control characters.
130 * (And control characters...)
136 function UrlencodeControlCharacters($str) {
137 return preg_replace('/([\x00-\x1F])/e', "urlencode('\\1')", $str);
141 * Convert a short string (page name, author) to xmlrpcval.
143 function short_string ($str) {
144 return new xmlrpcval(UrlencodeControlCharacters(utf8_encode($str)), 'string');
148 * Convert a large string (page content) to xmlrpcval.
150 function long_string ($str) {
151 return new xmlrpcval(utf8_encode($str), 'base64');
155 * Decode a short string (e.g. page name)
157 function short_string_decode ($str) {
158 return utf8_decode(urldecode($str));
162 * Get an xmlrpc "No such page" error message
164 function NoSuchPage ()
166 global $xmlrpcerruser;
167 return new xmlrpcresp(0, $xmlrpcerruser + 1, "No such page");
171 // ****************************************************************************
172 // Main API functions follow
173 // ****************************************************************************
177 * int getRPCVersionSupported(): Returns 1 for this version of the API
179 $wiki_dmap['getRPCVersionSupported']
180 = array('signature' => array(array($xmlrpcInt)),
181 'documentation' => 'Get the version of the wiki API',
182 'function' => 'getRPCVersionSupported');
184 // The function must be a function in the global scope which services the XML-RPC
186 function getRPCVersionSupported($params)
188 return new xmlrpcresp(new xmlrpcval(WIKI_XMLRPC_VERSION, "string"));
192 * array getRecentChanges(Date timestamp) : Get list of changed pages since
193 * timestamp, which should be in UTC. The result is an array, where each element
195 * name (string) : Name of the page. The name is UTF-8 with URL encoding to make it ASCII.
196 * lastModified (date) : Date of last modification, in UTC.
197 * author (string) : Name of the author (if available). Again, name is UTF-8 with URL encoding.
198 * version (int) : Current version.
199 * A page MAY be specified multiple times. A page MAY NOT be specified multiple
200 * times with the same modification date.
202 $wiki_dmap['getRecentChanges']
203 = array('signature' => array(array($xmlrpcArray, $xmlrpcDateTime)),
204 'documentation' => 'Get a list of changed pages since [timestamp]',
205 'function' => 'getRecentChanges');
207 function getRecentChanges($params)
210 // Get the first parameter as an ISO 8601 date. Assume UTC
211 $encoded_date = $params->getParam(0);
212 $datetime = iso8601_decode($encoded_date->scalarval(), 1);
213 $dbh = $request->getDbh();
215 $iterator = $dbh->mostRecent(array('since' => $datetime));
216 while ($page = $iterator->next()) {
217 // $page contains a WikiDB_PageRevision object
218 // no need to url encode $name, because it is already stored in that format ???
219 $name = short_string($page->getPageName());
220 $lastmodified = new xmlrpcval(iso8601_encode($page->get('mtime')), "dateTime.iso8601");
221 $author = short_string($page->get('author'));
222 $version = new xmlrpcval($page->getVersion(), 'int');
224 // Build an array of xmlrpc structs
225 $pages[] = new xmlrpcval(array('name'=>$name,
226 'lastModified'=>$lastmodified,
228 'version'=>$version),
231 return new xmlrpcresp(new xmlrpcval($pages, "array"));
236 * base64 getPage( String pagename ): Get the raw Wiki text of page, latest version.
237 * Page name must be UTF-8, with URL encoding. Returned value is a binary object,
238 * with UTF-8 encoded page data.
240 $wiki_dmap['getPage']
241 = array('signature' => array(array($xmlrpcBase64, $xmlrpcString)),
242 'documentation' => 'Get the raw Wiki text of the current version of a page',
243 'function' => 'getPage');
245 function getPage($params)
247 $revision = _getPageRevision($params);
252 return new xmlrpcresp(long_string($revision->getPackedContent()));
257 * base64 getPageVersion( String pagename, int version ): Get the raw Wiki text of page.
258 * Returns UTF-8, expects UTF-8 with URL encoding.
260 $wiki_dmap['getPageVersion']
261 = array('signature' => array(array($xmlrpcBase64, $xmlrpcString, $xmlrpcInt)),
262 'documentation' => 'Get the raw Wiki text of a page version',
263 'function' => 'getPageVersion');
265 function getPageVersion($params)
267 // error checking is done in getPage
268 return getPage($params);
272 * base64 getPageHTML( String pagename ): Return page in rendered HTML.
273 * Returns UTF-8, expects UTF-8 with URL encoding.
276 $wiki_dmap['getPageHTML']
277 = array('signature' => array(array($xmlrpcBase64, $xmlrpcString)),
278 'documentation' => 'Get the current version of a page rendered in HTML',
279 'function' => 'getPageHTML');
281 function getPageHTML($params)
283 $revision = _getPageRevision($params);
287 $content = $revision->getTransformedContent();
288 $html = $content->asXML();
289 // HACK: Get rid of outer <div class="wikitext">
290 if (preg_match('/^\s*<div class="wikitext">/', $html, $m1)
291 && preg_match('@</div>\s*$@', $html, $m2)) {
292 $html = substr($html, strlen($m1[0]), -strlen($m2[0]));
295 return new xmlrpcresp(long_string($html));
299 * base64 getPageHTMLVersion( String pagename, int version ): Return page in rendered HTML, UTF-8.
301 $wiki_dmap['getPageHTMLVersion']
302 = array('signature' => array(array($xmlrpcBase64, $xmlrpcString, $xmlrpcInt)),
303 'documentation' => 'Get a version of a page rendered in HTML',
304 'function' => 'getPageHTMLVersion');
306 function getPageHTMLVersion($params)
308 return getPageHTML($params);
312 * getAllPages(): Returns a list of all pages. The result is an array of strings.
314 $wiki_dmap['getAllPages']
315 = array('signature' => array(array($xmlrpcArray)),
316 'documentation' => 'Returns a list of all pages as an array of strings',
317 'function' => 'getAllPages');
319 function getAllPages($params)
322 $dbh = $request->getDbh();
323 $iterator = $dbh->getAllPages();
325 while ($page = $iterator->next()) {
326 $pages[] = short_string($page->getName());
328 return new xmlrpcresp(new xmlrpcval($pages, "array"));
332 * struct getPageInfo( string pagename ) : returns a struct with elements:
333 * name (string): the canonical page name
334 * lastModified (date): Last modification date
335 * version (int): current version
336 * author (string): author name
338 $wiki_dmap['getPageInfo']
339 = array('signature' => array(array($xmlrpcStruct, $xmlrpcString)),
340 'documentation' => 'Gets info about the current version of a page',
341 'function' => 'getPageInfo');
343 function getPageInfo($params)
345 $revision = _getPageRevision($params);
349 $name = short_string($revision->getPageName());
350 $version = new xmlrpcval ($revision->getVersion(), "int");
351 $lastmodified = new xmlrpcval(iso8601_encode($revision->get('mtime'), 0),
353 $author = short_string($revision->get('author'));
355 return new xmlrpcresp(new xmlrpcval(array('name' => $name,
356 'lastModified' => $lastmodified,
357 'version' => $version,
358 'author' => $author),
363 * struct getPageInfoVersion( string pagename, int version ) : returns
364 * a struct just like plain getPageInfo(), but this time for a
367 $wiki_dmap['getPageInfoVersion']
368 = array('signature' => array(array($xmlrpcStruct, $xmlrpcString, $xmlrpcInt)),
369 'documentation' => 'Gets info about a page version',
370 'function' => 'getPageInfoVersion');
372 function getPageInfoVersion($params)
374 return getPageInfo($params);
378 /* array listLinks( string pagename ): Lists all links for a given page. The
379 * returned array contains structs, with the following elements:
380 * name (string) : The page name or URL the link is to.
381 * type (int) : The link type. Zero (0) for internal Wiki link,
382 * one (1) for external link (URL - image link, whatever).
384 $wiki_dmap['listLinks']
385 = array('signature' => array(array($xmlrpcArray, $xmlrpcString)),
386 'documentation' => 'Lists all links for a given page',
387 'function' => 'listLinks');
389 function listLinks($params)
393 $ParamPageName = $params->getParam(0);
394 $pagename = short_string_decode($ParamPageName->scalarval());
395 $dbh = $request->getDbh();
396 if (! $dbh->isWikiPage($pagename))
399 $page = $dbh->getPage($pagename);
401 $linkiterator = $page->getLinks(false);
402 $linkstruct = array();
403 while ($currentpage = $linkiterator->next()) {
404 $currentname = $currentpage->getName();
405 $name = short_string($currentname);
406 // NB no clean way to extract a list of external links yet, so
407 // only internal links returned. ie all type 'local'.
408 $type = new xmlrpcval('local');
410 // Compute URL to page
412 $currentrev = $currentpage->getCurrentRevision();
413 if ($currentrev->hasDefaultContents())
414 $args['action'] = 'edit';
416 // FIXME: Autodetected value of VIRTUAL_PATH wrong,
417 // this make absolute URLs contstructed by WikiURL wrong.
418 // Also, if USE_PATH_INFO is false, WikiURL is wrong
419 // due to its use of SCRIPT_NAME.
420 $use_abspath = USE_PATH_INFO && ! preg_match('/RPC2.php$/', VIRTUAL_PATH);
421 $href = new xmlrpcval(WikiURL($currentname, $args, $use_abspath));
423 $linkstruct[] = new xmlrpcval(array('name'=> $name,
430 $current = $page->getCurrentRevision();
431 $content = $current->getTransformedContent();
432 $links = $content->getLinkInfo();
434 foreach ($links as $link) {
435 // We used to give an href for unknown pages that
436 // included action=edit. I think that's probably the
437 // wrong thing to do.
438 $linkstruct[] = new xmlrpcval(array('name'=> short_string($link->page),
439 'type'=> new xmlrpcval($link->type),
440 'href' => short_string($link->href)),
444 return new xmlrpcresp(new xmlrpcval ($linkstruct, "array"));
449 * Client subscribes to a RecentChanges-like channel, getting a short
450 * callback notification on every change. Like PageChangeNotification, just shorter
451 * and more complicated
452 * RSS2 support (not yet), since radio userland's rss-0.92. now called RSS2.
453 * BTW: Radio Userland deprecated this interface.
455 * boolean wiki.rssPleaseNotify ( notifyProcedure, port, path, protocol, urlList )
456 * returns: true or false
458 * Check of the channel behind the rssurl has a cloud element,
459 * if the client has a direct IP connection (no NAT),
460 * register the client on the WikiDB notification handler
462 * http://backend.userland.com/publishSubscribeWalkthrough
463 * http://www.soapware.org/xmlStorageSystem#rssPleaseNotify
464 * http://www.thetwowayweb.com/soapmeetsrss#rsscloudInterface
466 $wiki_dmap['rssPleaseNotify']
467 = array('signature' => array(array($xmlrpcStruct, $xmlrpcBoolean)),
468 'documentation' => 'RSS2 change notification subscriber channel',
469 'function' => 'rssPleaseNotify');
471 function rssPleaseNotify($params)
473 // register the clients IP
474 return new xmlrpcresp(new xmlrpcval (false, "boolean"));
477 $wiki_dmap['mailPasswordToUser']
478 = array('signature' => array(array($xmlrpcStruct, $xmlrpcString)),
479 'documentation' => 'RSS2 change notification subscriber channel',
480 'function' => 'mailPasswordToUser');
482 function mailPasswordToUser($params)
484 return new xmlrpcresp(new xmlrpcval (false, "boolean"));
488 * Construct the server instance, and set up the dispatch map, which maps
489 * the XML-RPC methods on to the wiki functions.
491 class XmlRpcServer extends xmlrpc_server
493 function XmlRpcServer ($request = false) {
495 foreach ($wiki_dmap as $name => $val)
496 $dmap['wiki.' . $name] = $val;
498 $this->xmlrpc_server($dmap, 0 /* delay service*/);
501 function service () {
502 global $ErrorManager;
504 $ErrorManager->pushErrorHandler(new WikiMethodCb($this, '_errorHandler'));
505 xmlrpc_server::service();
506 $ErrorManager->popErrorHandler();
509 function _errorHandler ($e) {
510 $msg = htmlspecialchars($e->asString());
511 // '--' not allowed within xml comment
512 $msg = str_replace('--', '--', $msg);
513 xmlrpc_debugmsg($msg);
519 // (c-file-style: "gnu")
524 // c-hanging-comment-ender-p: nil
525 // indent-tabs-mode: nil