]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/XmlRpcServer.php
Fix ->_backend->qstr()
[SourceForge/phpwiki.git] / lib / XmlRpcServer.php
1 <?php
2 // $Id: XmlRpcServer.php,v 1.23 2007-01-10 20:47:45 rurban Exp $
3 /* Copyright (C) 2002, Lawrence Akka <lakka@users.sourceforge.net>
4  * Copyright (C) 2004, 2005 $ThePhpWikiProgrammingTeam
5  *
6  * LICENCE
7  * =======
8  * This file is part of PhpWiki.
9  * 
10  * PhpWiki is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  * 
15  * PhpWiki is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  * 
20  * You should have received a copy of the GNU General Public License
21  * along with PhpWiki; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23  *
24  * LIBRARY USED - POSSIBLE PROBLEMS
25  * ================================
26  * 
27  * This file provides an XML-RPC interface for PhpWiki. 
28  * It checks for the existence of the xmlrpc-epi c library by Dan Libby 
29  * (see http://uk2.php.net/manual/en/ref.xmlrpc.php), and falls back to 
30  * the slower PHP counterpart XML-RPC library by Edd Dumbill. 
31  * See http://xmlrpc.usefulinc.com/php.html for details.
32  * 
33  * INTERFACE SPECIFICTION
34  * ======================
35  *  
36  * The interface specification is that discussed at 
37  * http://www.ecyrd.com/JSPWiki/Wiki.jsp?page=WikiRPCInterface
38  * 
39  * See also http://www.usemod.com/cgi-bin/mb.pl?XmlRpc
40  * or http://www.devshed.com/c/a/PHP/Using-XMLRPC-with-PHP/
41  * 
42  * Note: All XMLRPC methods are automatically prefixed with "wiki."
43  *       eg. "wiki.getAllPages"
44 */
45
46 /*
47 ToDo:
48         Remove all warnings from xmlrpc.inc
49         Return list of external links in listLinks
50         Support RSS2 cloud subscription: wiki.rssPleaseNotify, pingback.ping
51 Done:
52         Test hwiki.jar xmlrpc interface (java visualization plugin)
53         Make use of the xmlrpc extension if found. http://xmlrpc-epi.sourceforge.net/
54         Resolved namespace conflicts
55         Added various phpwiki specific methods (mailPasswordToUser, getUploadedFileInfo, 
56         putPage, titleSearch, listPlugins, getPluginSynopsis, listRelations)
57         Use client methods in inter-phpwiki calls: SyncWiki, tests/xmlrpc/
58 */
59
60 // Intercept GET requests from confused users.  Only POST is allowed here!
61 if (empty($GLOBALS['HTTP_SERVER_VARS']))
62     $GLOBALS['HTTP_SERVER_VARS']  =& $_SERVER;
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 require_once("lib/XmlRpcClient.php");
70 if (loadPhpExtension('xmlrpc')) { // fast c lib
71     require_once("lib/XMLRPC/xmlrpcs_emu.inc");
72 } else { // slow php lib
73     global $_xmlrpcs_dmap;
74     require_once("lib/XMLRPC/xmlrpcs.inc");
75 }
76
77
78 /**
79  * Helper function:  Looks up a page revision (most recent by default) in the wiki database
80  * 
81  * @param xmlrpcmsg $params :  string pagename [int version]
82  * @return WikiDB _PageRevision object, or false if no such page
83  */
84
85 function _getPageRevision ($params)
86 {
87     global $request;
88     $ParamPageName = $params->getParam(0);
89     $ParamVersion = $params->getParam(1);
90     $pagename = short_string_decode($ParamPageName->scalarval());
91     $version =  ($ParamVersion) ? ($ParamVersion->scalarval()):(0);
92     // FIXME:  test for version <=0 ??
93     $dbh = $request->getDbh();
94     if ($dbh->isWikiPage($pagename)) {
95         $page = $dbh->getPage($pagename);
96         if (!$version) {
97             $revision = $page->getCurrentRevision();
98         } else {
99             $revision = $page->getRevision($version);
100         } 
101         return $revision;
102     } 
103     return false;
104
105
106 /**
107  * Get an xmlrpc "No such page" error message
108  */
109 function NoSuchPage ($pagename='') 
110 {
111     global $xmlrpcerruser;
112     return new xmlrpcresp(0, $xmlrpcerruser + 1, "No such page ".$pagename);
113 }
114
115
116 // ****************************************************************************
117 // Main API functions follow
118 // ****************************************************************************
119 global $wiki_dmap;
120
121 /**
122  * int getRPCVersionSupported(): Returns 1 for this version of the API 
123  */
124 $wiki_dmap['getRPCVersionSupported']
125 = array('signature'     => array(array($xmlrpcInt)),
126         'documentation' => 'Get the version of the wiki API',
127         'function'      => 'getRPCVersionSupported');
128
129 // The function must be a function in the global scope which services the XML-RPC
130 // method.
131 function getRPCVersionSupported($params)
132 {
133     return new xmlrpcresp(new xmlrpcval((integer)WIKI_XMLRPC_VERSION, "int"));
134 }
135
136 /**
137  * array getRecentChanges(Date timestamp) : Get list of changed pages since 
138  * timestamp, which should be in UTC. The result is an array, where each element
139  * is a struct: 
140  *     name (string) : Name of the page. The name is UTF-8 with URL encoding to make it ASCII. 
141  *     lastModified (date) : Date of last modification, in UTC. 
142  *     author (string) : Name of the author (if available). Again, name is UTF-8 with URL encoding. 
143  *         version (int) : Current version. 
144  * A page MAY be specified multiple times. A page MAY NOT be specified multiple 
145  * times with the same modification date.
146  */
147 $wiki_dmap['getRecentChanges']
148 = array('signature'     => array(array($xmlrpcArray, $xmlrpcDateTime)),
149         'documentation' => 'Get a list of changed pages since [timestamp]',
150         'function'      => 'getRecentChanges');
151
152 function getRecentChanges($params)
153 {
154     global $request;
155     // Get the first parameter as an ISO 8601 date. Assume UTC
156     $encoded_date = $params->getParam(0);
157     $datetime = iso8601_decode($encoded_date->scalarval(), 1);
158     $dbh = $request->getDbh();
159     $pages = array();
160     $iterator = $dbh->mostRecent(array('since' => $datetime));
161     while ($page = $iterator->next()) {
162         // $page contains a WikiDB_PageRevision object
163         // no need to url encode $name, because it is already stored in that format ???
164         $name = short_string($page->getPageName());
165         $lastmodified = new xmlrpcval(iso8601_encode($page->get('mtime')), "dateTime.iso8601");
166         $author = short_string($page->get('author'));
167         $version = new xmlrpcval($page->getVersion(), 'int');
168
169         // Build an array of xmlrpc structs
170         $pages[] = new xmlrpcval(array('name' => $name, 
171                                        'lastModified' => $lastmodified,
172                                        'author' => $author,
173                                        'summary' => short_string($page->get('summary')),
174                                        'version' => $version),
175                                  'struct');
176     } 
177     return new xmlrpcresp(new xmlrpcval($pages, "array"));
178
179
180
181 /**
182  * base64 getPage( String pagename ): Get the raw Wiki text of page, latest version. 
183  * Page name must be UTF-8, with URL encoding. Returned value is a binary object,
184  * with UTF-8 encoded page data.
185  */
186 $wiki_dmap['getPage']
187 = array('signature'     => array(array($xmlrpcBase64, $xmlrpcString)),
188         'documentation' => 'Get the raw Wiki text of the current version of a page',
189         'function'      => 'getPage');
190
191 function getPage($params)
192 {
193     $revision = _getPageRevision($params);
194
195     if (! $revision ) {
196         $ParamPageName = $params->getParam(0);
197         $pagename = short_string_decode($ParamPageName->scalarval());
198         return NoSuchPage($pagename);
199     }
200
201     return new xmlrpcresp(long_string($revision->getPackedContent()));
202 }
203  
204
205 /**
206  * base64 getPageVersion( String pagename, int version ): Get the raw Wiki text of page.
207  * Returns UTF-8, expects UTF-8 with URL encoding.
208  */
209 $wiki_dmap['getPageVersion']
210 = array('signature'     => array(array($xmlrpcBase64, $xmlrpcString, $xmlrpcInt)),
211         'documentation' => 'Get the raw Wiki text of a page version',
212         'function'      => 'getPageVersion');
213
214 function getPageVersion($params)
215 {
216     // error checking is done in getPage
217     return getPage($params);
218
219
220 /**
221  * base64 getPageHTML( String pagename ): Return page in rendered HTML. 
222  * Returns UTF-8, expects UTF-8 with URL encoding.
223  */
224
225 $wiki_dmap['getPageHTML']
226 = array('signature'     => array(array($xmlrpcBase64, $xmlrpcString)),
227         'documentation' => 'Get the current version of a page rendered in HTML',
228         'function'      => 'getPageHTML');
229
230 function getPageHTML($params)
231 {
232     $revision = _getPageRevision($params);
233     if (!$revision)
234         return NoSuchPage();
235     
236     $content = $revision->getTransformedContent();
237     $html = $content->asXML();
238     // HACK: Get rid of outer <div class="wikitext">
239     if (preg_match('/^\s*<div class="wikitext">/', $html, $m1)
240         && preg_match('@</div>\s*$@', $html, $m2)) {
241         $html = substr($html, strlen($m1[0]), -strlen($m2[0]));
242     }
243
244     return new xmlrpcresp(long_string($html));
245
246
247 /**
248  * base64 getPageHTMLVersion( String pagename, int version ): Return page in rendered HTML, UTF-8.
249  */
250 $wiki_dmap['getPageHTMLVersion']
251 = array('signature'     => array(array($xmlrpcBase64, $xmlrpcString, $xmlrpcInt)),
252         'documentation' => 'Get a version of a page rendered in HTML',
253         'function'      => 'getPageHTMLVersion');
254
255 function getPageHTMLVersion($params)
256 {
257     return getPageHTML($params);
258
259
260 /**
261  * getAllPages(): Returns a list of all pages. The result is an array of strings.
262  */
263 $wiki_dmap['getAllPages']
264 = array('signature'     => array(array($xmlrpcArray)),
265         'documentation' => 'Returns a list of all pages as an array of strings', 
266         'function'      => 'getAllPages');
267
268 function getAllPages($params)
269 {
270     global $request;
271     $dbh = $request->getDbh();
272     $iterator = $dbh->getAllPages();
273     $pages = array();
274     while ($page = $iterator->next()) {
275         $pages[] = short_string($page->getName());
276     } 
277     return new xmlrpcresp(new xmlrpcval($pages, "array"));
278
279
280 /**
281  * struct getPageInfo( string pagename ) : returns a struct with elements: 
282  *   name (string): the canonical page name 
283  *   lastModified (date): Last modification date 
284  *   version (int): current version 
285  *   author (string): author name 
286  */
287 $wiki_dmap['getPageInfo']
288 = array('signature'     => array(array($xmlrpcStruct, $xmlrpcString)),
289         'documentation' => 'Gets info about the current version of a page',
290         'function'      => 'getPageInfo');
291
292 function getPageInfo($params)
293 {
294     $revision = _getPageRevision($params);
295     if (!$revision)
296         return NoSuchPage();
297     
298     $name = short_string($revision->getPageName());
299     $version = new xmlrpcval ($revision->getVersion(), "int");
300     $lastmodified = new xmlrpcval(iso8601_encode($revision->get('mtime'), 0),
301                                   "dateTime.iso8601");
302     $author = short_string($revision->get('author'));
303         
304     return new xmlrpcresp(new xmlrpcval(array('name' => $name, 
305                                               'lastModified' => $lastmodified,
306                                               'version' => $version, 
307                                               'author' => $author), 
308                                         "struct"));
309
310
311 /**
312  * struct getPageInfoVersion( string pagename, int version ) : returns
313  * a struct just like plain getPageInfo(), but this time for a
314  * specific version.
315  */
316 $wiki_dmap['getPageInfoVersion']
317 = array('signature'     => array(array($xmlrpcStruct, $xmlrpcString, $xmlrpcInt)),
318         'documentation' => 'Gets info about a page version',
319         'function'      => 'getPageInfoVersion');
320
321 function getPageInfoVersion($params)
322 {
323     return getPageInfo($params);
324 }
325
326  
327 /*  array listLinks( string pagename ): Lists all links for a given page. The
328  *  returned array contains structs, with the following elements: 
329  *       name (string) : The page name or URL the link is to. 
330  *       type (int) : The link type. Zero (0) for internal Wiki link,
331  *         one (1) for external link (URL - image link, whatever).
332  */
333 $wiki_dmap['listLinks']
334 = array('signature'     => array(array($xmlrpcArray, $xmlrpcString)),
335         'documentation' => 'Lists all links for a given page',
336         'function'      => 'listLinks');
337
338 function listLinks($params)
339 {
340     global $request;
341     
342     $ParamPageName = $params->getParam(0);
343     $pagename = short_string_decode($ParamPageName->scalarval());
344     $dbh = $request->getDbh();
345     if (! $dbh->isWikiPage($pagename))
346         return NoSuchPage($pagename);
347
348     $page = $dbh->getPage($pagename);
349     
350     // The fast WikiDB method. below is the slow method which goes through the formatter
351     // NB no clean way to extract a list of external links yet, so
352     // only internal links returned.  i.e. all type 'local'.
353     $linkiterator = $page->getPageLinks();
354     $linkstruct = array();
355     while ($currentpage = $linkiterator->next()) {
356         $currentname = $currentpage->getName();
357         // Compute URL to page
358         $args = array();
359         // How to check external links?
360         if (!$currentpage->exists()) $args['action'] = 'edit';
361
362         // FIXME: Autodetected value of VIRTUAL_PATH wrong,
363         // this make absolute URLs constructed by WikiURL wrong.
364         // Also, if USE_PATH_INFO is false, WikiURL is wrong
365         // due to its use of SCRIPT_NAME.
366         //$use_abspath = USE_PATH_INFO && ! preg_match('/RPC2.php$/', VIRTUAL_PATH);
367         
368         // USE_PATH_INFO must be defined in index.php or config.ini but not before, 
369         // otherwise it is ignored and xmlrpc urls are wrong.
370         // SCRIPT_NAME here is always .../RPC2.php
371         if (USE_PATH_INFO and !$args) {
372             $url = preg_replace('/%2f/i', '/', rawurlencode($currentname));
373         } elseif (!USE_PATH_INFO) {
374             $url = str_replace("/RPC2.php","/index.php", WikiURL($currentname, $args, true));
375         } else {
376             $url = WikiURL($currentname, $args);
377         }
378         $linkstruct[] = new xmlrpcval(array('page'=> short_string($currentname),
379                                             'type'=> new xmlrpcval('local', 'string'),
380                                             'href' => short_string($url)),
381                                       "struct");
382     }
383    
384     /*
385     $current = $page->getCurrentRevision();
386     $content = $current->getTransformedContent();
387     $links = $content->getLinkInfo();
388     foreach ($links as $link) {
389         // We used to give an href for unknown pages that
390         // included action=edit.  I think that's probably the
391         // wrong thing to do.
392         $linkstruct[] = new xmlrpcval(array('page'=> short_string($link->page),
393                                             'type'=> new xmlrpcval($link->type, 'string'),
394                                             'href' => short_string($link->href),
395                                             //'pageref' => short_string($link->pageref),
396                                             ),
397                                       "struct");
398     }
399     */
400     return new xmlrpcresp(new xmlrpcval ($linkstruct, "array"));
401
402
403 /** 
404  * struct putPage(String pagename, String content, [String author[, String password]})
405  * returns a struct with elements: 
406  *   code (int): 200 on success, 400 or 401 on failure
407  *   message (string): success or failure message
408  *   version (int): version of new page
409  *
410  * @author: Arnaud Fontaine, Reini Urban
411  */
412 $wiki_dmap['putPage']
413 = array('signature'     => array(array($xmlrpcStruct, $xmlrpcString, $xmlrpcString, $xmlrpcString, $xmlrpcString)),
414         'documentation' => 'put the raw Wiki text into a page as new version',
415         'function'      => 'putPage');
416
417 function _getUser($userid='') {
418     global $request;
419     
420     if (! $userid ) {
421         if (!isset($_SERVER))
422             $_SERVER =& $GLOBALS['HTTP_SERVER_VARS'];
423         if (!isset($_ENV))
424             $_ENV =& $GLOBALS['HTTP_ENV_VARS'];
425         if (isset($_SERVER['REMOTE_USER']))
426             $userid = $_SERVER['REMOTE_USER'];
427         elseif (isset($_ENV['REMOTE_USER']))
428             $userid = $_ENV['REMOTE_USER'];
429         elseif (isset($_SERVER['REMOTE_ADDR']))
430             $userid = $_SERVER['REMOTE_ADDR'];
431         elseif (isset($_ENV['REMOTE_ADDR']))
432             $userid = $_ENV['REMOTE_ADDR'];
433         elseif (isset($GLOBALS['REMOTE_ADDR']))
434             $userid = $GLOBALS['REMOTE_ADDR'];
435     }
436
437     if (ENABLE_USER_NEW) {
438         return WikiUser($userid);
439     } else {
440         return new WikiUser($request, $userid);
441     }
442 }
443         
444 function putPage($params) {
445     global $request;
446
447     $ParamPageName = $params->getParam(0);
448     $ParamContent = $params->getParam(1);
449     $pagename = short_string_decode($ParamPageName->scalarval());
450     $content = short_string_decode($ParamContent->scalarval());
451     $passwd = '';
452     if (count($params->params) > 2) {
453         $ParamAuthor = $params->getParam(2);
454         $userid = short_string_decode($ParamAuthor->scalarval());
455         if (count($params->params) > 3) {
456             $ParamPassword = $params->getParam(3);
457             $passwd = short_string_decode($ParamPassword->scalarval());
458         }
459     } else {
460         $userid = $request->_user->_userid;
461     }
462     $request->_user = _getUser($userid);
463     $request->_user->_group = $request->getGroup();
464     $request->_user->AuthCheck($userid, $passwd);
465                                          
466     if (! mayAccessPage ('edit', $pagename)) {
467         return new xmlrpcresp(
468                               new xmlrpcval(
469                                             array('code' => new xmlrpcval(401, "int"), 
470                                                   'version' => new xmlrpcval(0, "int"), 
471                                                   'message' => 
472                                                   short_string("no permission for "
473                                                                .$request->_user->UserName())), 
474                                             "struct"));
475     }
476
477     $now = time();
478     $dbh = $request->getDbh();
479     $page = $dbh->getPage($pagename);
480     $current = $page->getCurrentRevision();
481     $content = trim($content);
482     $version = $current->getVersion();
483     // $version = -1 will force create a new version
484     if ($current->getPackedContent() != $content) {
485         $init_meta = array('ctime' => $now,
486                            'creator' => $userid,
487                            'creator_id' => $userid,
488                            );
489         $version_meta = array('author' => $userid,
490                               'author_id' => $userid,
491                               'markup' => 2.0,
492                               'summary' => isset($summary) ? $summary : _("xml-rpc change"),
493                               'mtime' => $now,
494                               'pagetype' => 'wikitext',
495                               'wikitext' => $init_meta,
496                               );
497         $version++;
498         $res = $page->save($content, $version, $version_meta);
499         if ($res)
500             $message = "Page $pagename version $version created";
501         else
502             $message = "Problem creating version $version of page $pagename";
503     } else {
504         $res = 0;
505         $message = $message = "Page $pagename unchanged";
506     }
507     return new xmlrpcresp(new xmlrpcval(array('code'    => new xmlrpcval($res ? 200 : 400, "int"), 
508                                               'version' => new xmlrpcval($version, "int"), 
509                                               'message' => short_string($message)), 
510                                         "struct"));
511 }
512
513 /**
514  * struct getUploadedFileInfo( string localpath ) : returns a struct with elements: 
515  *   lastModified (date): Last modification date 
516  *   size (int): current version 
517  * This is to sync uploaded files up to a remote master wiki. (SyncWiki)
518  * Not existing files return both 0.
519  */
520 $wiki_dmap['getUploadedFileInfo']
521 = array('signature'     => array(array($xmlrpcStruct, $xmlrpcString)),
522         'documentation' => 'Gets date and size about an uploaded local file',
523         'function'      => 'getUploadedFileInfo');
524
525 function getUploadedFileInfo($params)
526 {
527     // localpath is the relative part after "Upload:"   
528     $ParamPath = $params->getParam(0);
529     $localpath = short_string_decode($ParamPath->scalarval());
530     preg_replace("/^[\\ \/ \.]/", "", $localpath); // strip hacks
531     $file = getUploadFilePath() . $localpath;
532     if (file_exists($file)) {
533         $size = filesize($file);
534         $lastmodified = filemtime($file);
535     } else {
536         $size = 0;
537         $lastmodified = 0;
538     }        
539     return new xmlrpcresp(new xmlrpcval
540         (array('lastModified' => new xmlrpcval(iso8601_encode($lastmodified, 1),
541                                                "dateTime.iso8601"),
542                'size' => new xmlrpcval($size, "int")), 
543         "struct"));
544 }
545
546 /**
547  * Publish-Subscribe
548  * Client subscribes to a RecentChanges-like channel, getting a short 
549  * callback notification on every change. Like PageChangeNotification, just shorter 
550  * and more complicated
551  * RSS2 support (not yet), since radio userland's rss-0.92. now called RSS2.
552  * BTW: Radio Userland deprecated this interface.
553  *
554  * boolean wiki.rssPleaseNotify ( notifyProcedure, port, path, protocol, urlList )
555  *   returns: true or false 
556  *
557  * Check of the channel behind the rssurl has a cloud element, 
558  * if the client has a direct IP connection (no NAT),
559  * register the client on the WikiDB notification handler
560  *
561  * http://backend.userland.com/publishSubscribeWalkthrough
562  * http://www.soapware.org/xmlStorageSystem#rssPleaseNotify
563  * http://www.thetwowayweb.com/soapmeetsrss#rsscloudInterface
564  */
565 $wiki_dmap['rssPleaseNotify']
566 = array('signature'     => array(array($xmlrpcBoolean, $xmlrpcStruct)),
567         'documentation' => 'RSS2 change notification subscriber channel',
568         'function'      => 'rssPleaseNotify');
569
570 function rssPleaseNotify($params)
571 {
572     // register the clients IP
573     return new xmlrpcresp(new xmlrpcval (0, "boolean"));
574 }
575
576 /*
577  *  boolean wiki.mailPasswordToUser ( username )
578  *  returns: true or false 
579
580  */
581 $wiki_dmap['mailPasswordToUser']
582 = array('signature'     => array(array($xmlrpcBoolean, $xmlrpcString)),
583         'documentation' => 'RSS2 user management helper',
584         'function'      => 'mailPasswordToUser');
585
586 function mailPasswordToUser($params)
587 {
588     global $request;
589     $ParamUserid = $params->getParam(0);
590     $userid = short_string_decode($ParamUserid->scalarval());
591     $request->_user = _getUser($userid);
592     //$request->_prefs =& $request->_user->_prefs;
593     $email = $request->getPref('email');
594     $success = 0;
595     if ($email) {
596         $body = WikiURL('') . "\nPassword: " . $request->getPref('passwd');
597         $success = mail($email, "[".WIKI_NAME."} Password Request", 
598                         $body);
599     }
600     return new xmlrpcresp(new xmlrpcval ($success, "boolean"));
601 }
602
603 /** 
604  * array wiki.titleSearch(String substring [, String option = "0"])
605  * returns an array of matching pagenames.
606  * TODO: standardize options
607  *
608  * @author: Reini Urban
609  */
610 $wiki_dmap['titleSearch']
611 = array('signature'     => array(array($xmlrpcArray, $xmlrpcString, $xmlrpcString)),
612         'documentation' => "Return matching pagenames. 
613 Option 1: caseexact, 2: regex, 4: starts_with, 8: exact",
614         'function'      => 'titleSearch');
615
616 function titleSearch($params)
617 {
618     global $request;
619     $ParamPageName = $params->getParam(0);
620     $searchstring = short_string_decode($ParamPageName->scalarval());
621     if (count($params->params) > 1) {
622         $ParamOption = $params->getParam(1);
623         $option = (int) $ParamOption->scalarval();
624     } else 
625         $option = 0;
626         // default option: substring, case-inexact
627
628     $case_exact = $option & 1;
629     $regex      = $option & 2;
630     if (!$regex) {
631         if ($option & 4) { // STARTS_WITH
632             $regex = true;
633             $searchstring = "^".$searchstring;
634         }
635         if ($option & 8) { // EXACT
636             $regex = true;
637             $searchstring = "^".$searchstring."$";
638         }
639     } else {
640         if ($option & 4 or $option & 8) { 
641             global $xmlrpcerruser;
642             return new xmlrpcresp(0, $xmlrpcerruser + 1, "Invalid option");
643         }
644     }
645     include_once("lib/TextSearchQuery.php");
646     $query = new TextSearchQuery($searchstring, $case_exact, $regex ? 'auto' : 'none');
647     $dbh = $request->getDbh();
648     $iterator = $dbh->titleSearch($query);
649     $pages = array();
650     while ($page = $iterator->next()) {
651         $pages[] = short_string($page->getName());
652     } 
653     return new xmlrpcresp(new xmlrpcval($pages, "array"));
654 }
655
656 /** 
657  * array wiki.listPlugins()
658  *
659  * Returns an array of all available plugins. 
660  * For EditToolbar pluginPulldown via AJAX
661  *
662  * @author: Reini Urban
663  */
664 $wiki_dmap['listPlugins']
665 = array('signature'     => array(array($xmlrpcArray)),
666         'documentation' => "Return names of all plugins",
667         'function'      => 'listPlugins');
668
669 function listPlugins($params)
670 {
671     $plugin_dir = 'lib/plugin';
672     if (defined('PHPWIKI_DIR'))
673         $plugin_dir = PHPWIKI_DIR . "/$plugin_dir";
674     $pd = new fileSet($plugin_dir, '*.php');
675     $plugins = $pd->getFiles();
676     unset($pd);
677     sort($plugins);
678     $RetArray = array();
679     if (!empty($plugins)) {
680         require_once("lib/WikiPlugin.php");
681         $w = new WikiPluginLoader;
682         foreach ($plugins as $plugin) {
683             $pluginName = str_replace(".php", "", $plugin);
684             $p = $w->getPlugin($pluginName, false); // second arg?
685             // trap php files which aren't WikiPlugin~s: wikiplugin + wikiplugin_cached only
686             if (strtolower(substr(get_parent_class($p), 0, 10)) == 'wikiplugin') {
687                 $RetArray[] = short_string($pluginName);
688             }
689         }
690     }
691   
692     return new xmlrpcresp(new xmlrpcval($RetArray, "array"));
693 }
694
695 /** 
696  * String wiki.getPluginSynopsis(String plugin)
697  *
698  * For EditToolbar pluginPulldown via AJAX
699  *
700  * @author: Reini Urban
701  */
702 $wiki_dmap['getPluginSynopsis']
703 = array('signature'     => array(array($xmlrpcArray, $xmlrpcString)),
704         'documentation' => "Return plugin synopsis",
705         'function'      => 'getPluginSynopsis');
706
707 function getPluginSynopsis($params)
708 {
709     $ParamPlugin = $params->getParam(0);
710     $pluginName = short_string_decode($ParamPlugin->scalarval());
711
712     require_once("lib/WikiPlugin.php");
713     $w = new WikiPluginLoader;
714     $synopsis = '';
715     $p = $w->getPlugin($pluginName, false); // second arg?
716     // trap php files which aren't WikiPlugin~s: wikiplugin + wikiplugin_cached only
717     if (strtolower(substr(get_parent_class($p), 0, 10)) == 'wikiplugin') {
718         $plugin_args = '';
719         $desc = $p->getArgumentsDescription();
720         $src = array("\n",'"',"'",'|','[',']','\\');
721         $replace = array('%0A','%22','%27','%7C','%5B','%5D','%5C');
722         $desc = str_replace("<br />",' ',$desc->asXML());
723         if ($desc)
724             $plugin_args = '\n'.str_replace($src, $replace, $desc);
725         $synopsis = "<?plugin ".$pluginName.$plugin_args."?>"; // args?
726     }
727    
728     return new xmlrpcresp(short_string($synopsis));
729 }
730
731 /** 
732  * array wiki.callPlugin(String name, String args)
733  *
734  * Returns an array of pages as returned by the plugins PageList call. 
735  * Only valid for plugins returning pagelists, e.g. BackLinks, AllPages, ...
736  * For various AJAX or WikiFormRich calls.
737  *
738  * @author: Reini Urban
739  */
740 $wiki_dmap['callPlugin']
741 = array('signature'     => array(array($xmlrpcArray, $xmlrpcString, $xmlrpcString)),
742         'documentation' => "Returns an array of pages as returned by the plugins PageList call",
743         'function'      => 'callPlugin');
744
745 function callPlugin($params)
746 {
747     global $request;
748     $dbi = $request->getDbh();
749     $ParamPlugin = $params->getParam(0);
750     $pluginName = short_string_decode($ParamPlugin->scalarval());
751     $ParamArgs = $params->getParam(1);
752     $plugin_args = short_string_decode($ParamArgs->scalarval());
753
754     $basepage = ''; //$pluginName;
755     require_once("lib/WikiPlugin.php");
756     $w = new WikiPluginLoader;
757     $p = $w->getPlugin($pluginName, false); // second arg?
758     $pagelist = $p->run($dbi, $plugin_args, $request, $basepage);
759     $list = array();
760     if (is_object($pagelist) and isa($pagelist, 'PageList')) {
761         foreach ($pagelist->_pages as $page) {
762             $list[] = $page->getName();
763         }
764     }
765     return new xmlrpcresp(new xmlrpcval($list, "array"));
766 }
767
768
769 /** 
770  * array wiki.listRelations([ Integer option = 1 ])
771  *
772  * Returns an array of all available relation names.
773  *   option: 1 relations only ( with 0 also )
774  *   option: 2 attributes only
775  *   option: 3 both, all names of relations and attributes 
776  *   option: 4 unsorted, this might be added as bitvalue: 7 = 4+3. default: sorted
777  * For some semanticweb autofill methods.
778  *
779  * @author: Reini Urban
780  */
781 $wiki_dmap['listRelations']
782 = array('signature'     => array(array($xmlrpcArray, $xmlrpcInt)),
783         'documentation' => "Return names of all relations",
784         'function'      => 'listRelations');
785
786 function listRelations($params)
787 {
788     global $request;
789     $dbh = $request->getDbh();
790     if (count($params->params) > 0) {
791         $ParamOption = $params->getParam(0);
792         $option = (int) $ParamOption->scalarval();
793     } else 
794         $option = 1;
795     $also_attributes = $option & 2; 
796     $only_attributes = $option & 2 and !($option & 1); 
797     $sorted = !($option & 4);
798     return new xmlrpcresp(new xmlrpcval($dbh->listRelations($also_attributes,
799                                                             $only_attributes,
800                                                             $sorted), 
801                                         "array"));
802 }
803
804 /** 
805  * String pingback.ping(String sourceURI, String targetURI)
806
807 Spec: http://www.hixie.ch/specs/pingback/pingback
808
809 Parameters
810     sourceURI of type string
811         The absolute URI of the post on the source page containing the
812         link to the target site.
813     targetURI of type string
814         The absolute URI of the target of the link, as given on the source page.
815 Return Value
816     A string, as described below.
817 Faults
818     If an error condition occurs, then the appropriate fault code from
819     the following list should be used. Clients can quickly determine
820     the kind of error from bits 5-8. 0×001x fault codes are used for
821     problems with the source URI, 0×002x codes are for problems with
822     the target URI, and 0×003x codes are used when the URIs are fine
823     but the pingback cannot be acknowledged for some other reaon.
824
825     0 
826         A generic fault code. Servers MAY use this error code instead
827         of any of the others if they do not have a way of determining
828         the correct fault code.
829     0×0010 (16)
830         The source URI does not exist.
831     0×0011 (17)
832         The source URI does not contain a link to the target URI, and
833         so cannot be used as a source.
834     0×0020 (32)
835         The specified target URI does not exist. This MUST only be
836         used when the target definitely does not exist, rather than
837         when the target may exist but is not recognised. See the next
838         error.
839     0×0021 (33)
840         The specified target URI cannot be used as a target. It either
841         doesn't exist, or it is not a pingback-enabled resource. For
842         example, on a blog, typically only permalinks are
843         pingback-enabled, and trying to pingback the home page, or a
844         set of posts, will fail with this error.
845     0×0030 (48)
846         The pingback has already been registered.
847     0×0031 (49)
848         Access denied.
849     0×0032 (50)
850         The server could not communicate with an upstream server, or
851         received an error from an upstream server, and therefore could
852         not complete the request. This is similar to HTTP's 402 Bad
853         Gateway error. This error SHOULD be used by pingback proxies
854         when propagating errors.
855
856     In addition, [FaultCodes] defines some standard fault codes that
857     servers MAY use to report higher level errors.
858
859 Servers MUST respond to this function call either with a single string
860 or with a fault code.
861
862 If the pingback request is successful, then the return value MUST be a
863 single string, containing as much information as the server deems
864 useful. This string is only expected to be used for debugging
865 purposes.
866
867 If the result is unsuccessful, then the server MUST respond with an
868 RPC fault value. The fault code should be either one of the codes
869 listed above, or the generic fault code zero if the server cannot
870 determine the correct fault code.
871
872 Clients MAY ignore the return value, whether the request was
873 successful or not. It is RECOMMENDED that clients do not show the
874 result of successful requests to the user.
875
876 Upon receiving a request, servers MAY do what they like. However, the
877 following steps are RECOMMENDED:
878
879    1. The server MAY attempt to fetch the source URI to verify that
880    the source does indeed link to the target.
881    2. The server MAY check its own data to ensure that the target
882    exists and is a valid entry.
883    3. The server MAY check that the pingback has not already been registered.
884    4. The server MAY record the pingback.
885    5. The server MAY regenerate the site's pages (if the pages are static).
886
887  * @author: Reini Urban
888  */
889 $wiki_dmap['pingback.ping']
890 = array('signature'     => array(array($xmlrpcString, $xmlrpcString, $xmlrpcString)),
891         'documentation' => "",
892         'function'      => 'pingBack');
893 function pingBack($params)
894 {
895     global $request;
896     $Param0 = $params->getParam(0);
897     $sourceURI = short_string_decode($Param0->scalarval());
898     $Param1 = $params->getParam(1);
899     $targetURI = short_string_decode($Param1->scalarval());
900     // TODO...
901 }
902  
903 /** 
904  * Construct the server instance, and set up the dispatch map, 
905  * which maps the XML-RPC methods on to the wiki functions.
906  * Provide the "wiki." prefix to each function. Besides 
907  * the blog - pingback, ... - functions with a seperate namespace.
908  */
909 class XmlRpcServer extends xmlrpc_server
910 {
911     function XmlRpcServer ($request = false) {
912         global $wiki_dmap;
913         foreach ($wiki_dmap as $name => $val) {
914             if ($name == 'pingback.ping') // non-wiki methods
915                 $dmap[$name] = $val;
916             else
917                 $dmap['wiki.' . $name] = $val;
918         }
919
920         $this->xmlrpc_server($dmap, 0 /* delay service*/);
921     }
922
923     function service () {
924         global $ErrorManager;
925
926         $ErrorManager->pushErrorHandler(new WikiMethodCb($this, '_errorHandler'));
927         xmlrpc_server::service();
928         $ErrorManager->popErrorHandler();
929     }
930     
931     function _errorHandler ($e) {
932         $msg = htmlspecialchars($e->asString());
933         // '--' not allowed within xml comment
934         $msg = str_replace('--', '&#45;&#45;', $msg);
935         if (function_exists('xmlrpc_debugmsg'))
936             xmlrpc_debugmsg($msg);
937         return true;
938     }
939 }
940
941 /*
942  $Log: not supported by cvs2svn $
943  Revision 1.22  2007/01/07 18:44:11  rurban
944  Add summary to getRecentChanges result
945
946  Revision 1.21  2007/01/04 16:42:13  rurban
947  Use require, not include!
948
949  Revision 1.20  2007/01/03 21:25:52  rurban
950  add option argument to listRelations.
951
952  Revision 1.19  2007/01/02 13:21:21  rurban
953  split client from server. added getUploadedFileInfo (for SyncWiki), callPlugin (for WikiFormRich)
954
955  Revision 1.18  2006/05/18 06:10:45  rurban
956  add xmlrpc listRelations signature
957
958  Revision 1.17  2005/10/31 16:49:31  rurban
959  fix doc
960
961  Revision 1.16  2005/10/29 14:17:51  rurban
962  fix doc
963
964  Revision 1.15  2005/10/29 08:57:12  rurban
965  fix for !register_long_arrays
966  new: array wiki.listPlugins()
967       String wiki.getPluginSynopsis(String plugin)
968       String pingback.ping(String sourceURI, String targetURI) (preliminary)
969
970
971  */
972
973 // (c-file-style: "gnu")
974 // Local Variables:
975 // mode: php
976 // tab-width: 8
977 // c-basic-offset: 4
978 // c-hanging-comment-ender-p: nil
979 // indent-tabs-mode: nil
980 // End:   
981 ?>