Dokuwiki XML RPC

DokuWiki supports WikiRPCInterface2 Remote Procedure Calls (RPC), speaking the Extensible Markup Language (XML). documented at dokwiki's XMLRPC.

As heavy DokuWiki user I am very interested to make use of this interface for various purposes. Starting with some basics, here is was a “hello world, what's your Dokuwiki version number?” example in PHP. - Now it exercises the DokuWiki and trac RPC interface, includes HTTP basic & digest authentication and HTTPS certificate checking; good enough to start building on top of it..

<?php
$dw_host="example.org";
$dw_path="/dokuwiki/";
$dw_user=NULL;
$dw_pass=NULL;
$dw_opts=array('cert'=>'', 'transport'=>'ssl://', 'port' => 443);
 
$trac_host="example.org";
$trac_path="/trac";
$trac_project="/";
$trac_user=NULL;
$trac_pass=NULL;
$trac_opts=array('cert'=>'', 'transport'=>'ssl://', 'port' => 443);
 
if (file_exists('dokurpc.conf.php')) require 'dokurpc.conf.php';
 
# END OF CONFIG
#######################################################################

srand(microtime()*1000000);
 
$dw_pass=NULL; # safeguard ;)

#$req = xmlrpc_encode_request("dokuwiki.getVersion", array());
$req = xmlrpc_encode_request("wiki.getPageInfo", array("robin:start"));
#$req = xmlrpc_encode_request("wiki.getPageInfo", array("wiki:start"));
$req = xmlrpc_encode_request("wiki.listLinks", array("wiki:start"));
$req = xmlrpc_encode_request("wiki.getBackLinks", array("wiki:start"));
$req = xmlrpc_encode_request("wiki.getRecentChanges", array(1219500627));
$req = xmlrpc_encode_request("wiki.getAttachments", array("wiki:", array('recursive'=>true, 'pattern'=>'/dif/')));
$req = xmlrpc_encode_request("wiki.getAllPages", array());
$req = xmlrpc_encode_request("wiki.getPageVersions", array("wiki:start", 0));
$req = xmlrpc_encode_request("wiki.getPageVersion", array("wiki:start", 1214855941));
$res= dokuXmlRpc($req);
$req = xmlrpc_encode_request("wiki.putPage", array("wiki:rpctest", $res, "rpc test"));
#$res= dokuXmlRpc($req);
print_r($res);
echo "\n\n";
 
#$req = xmlrpc_encode_request("wiki.getRPCVersionSupported", array());
#$req = xmlrpc_encode_request("wiki.getAllPages", array());
#$req = xmlrpc_encode_request("system.listMethods", array());
#$req = xmlrpc_encode_request("wiki.getPageInfo", array("TracGuide"));
$req = xmlrpc_encode_request("wiki.getPage", array("TracGuide", 1));
$res= tracXmlRpc($req);
print_r($res);
 
########################################################################
# XML-RPC
##

function tracXmlRpc($request) {
  global $trac_host, $trac_project, $trac_path, $trac_user, $trac_pass, $trac_opts;
 
  $path=$trac_path.'/'.$trac_project.'/login/xmlrpc';
  $x = httpPost($trac_host, $path, $request, $trac_opts,
                array('username'=>$trac_user, 'password'=>$trac_pass));
  return parseXmlRpcResponse($x);
}
 
function dokuXmlRpc($request) {
  global $dw_host, $dw_path, $dw_user, $dw_pass, $dw_opts;
 
  $path=$dw_path.'lib/exe/xmlrpc.php?';
  if ($dw_user) $path.='u='.urlencode($dw_user).'&';
  if ($dw_pass) $path.='p='.urlencode($dw_pass).'&';
  $path=preg_replace('![?&]$!', '', $path);
 
  $x = httpPost($dw_host, $path, $request, $dw_opts);
  return parseXmlRpcResponse($x);
}
 
function parseXmlRpcResponse($x) {
  $response = xmlrpc_decode($x);
  if (xmlrpc_is_fault($response)) {
    trigger_error("xmlrpc: $response[faultString] ($response[faultCode])");
  }
  return($response);
}
 
/***********************************************************************
 * HTTP[s] POST request
 *
 * $host: hostname ; eg 'example.org'
 * $path: request' eg '/index.php?id=123'
 * $data_to_send : data to POST after the HTTP header.
 *
 * @param $host: hostname ; eg 'example.org'
 * @param $path: request' eg '/index.php?id=123'
 * @param $data_to_send : data to POST after the HTTP header.
 * @param $opts various transport layer options (ssl, port, cert,..)
 * @param $auth optional username, password and type ('basic', 'nodigest')
 * @param $head custom http header
 * if $opts is an  empty array() a standard  HTTP to port 80 request is performed.
 *
 * set auth['type']='basic' to use plain-text auth,
 * digest-auth will be handled automatically if $auth['username'] is set and a 401
 * status is encountered. - use auth['type']='nodigest' to override.
 *
 **/
function httpPost($host, $path, $data_to_send,
                  $opts=array('cert'=>"", 'transport' =>'ssl://', 'port'=>443, 'headers'=>0),
                  $auth=array('username'=>"", 'password'=>"", 'type'=>""),
                  $head=array('Content-type' =>'text/xml')
                 ){
  $transport=''; $port=80;
  if (!empty($opts['transport'])) $transport=$opts['transport'];
  if (!empty($opts['port'])) $port=$opts['port'];
  $remote=$transport.$host.':'.$port;
 
  $context = stream_context_create();
  $result = stream_context_set_option($context, 'ssl', 'verify_host', true);
  if (!empty($opts['cert'])) {
    $result = stream_context_set_option($context, 'ssl', 'cafile', $opts['cert']);
    $result = stream_context_set_option($context, 'ssl', 'verify_peer', true);
  } else {
    $result = stream_context_set_option($context, 'ssl', 'allow_self_signed', true);
  }
  $fp = stream_socket_client($remote, $err, $errstr, 60, STREAM_CLIENT_CONNECT, $context);
 
  if (!$fp) {
    trigger_error('httpPost error: '.$errstr);
    return NULL;
  }
 
  $req='';
  $req.="POST $path HTTP/1.1\r\n";
  $req.="Host: $host\r\n";
  if ($auth['type']=='basic' && !empty($auth['username'])) {
    $req.="Authorization: Basic ";
    $req.=base64_encode($auth['username'].':'.$auth['password'])."\r\n";
  }
  elseif ($auth['type']=='digest' && !empty($auth['username'])) {
    $req.='Authorization: Digest ';
    foreach ($auth as $k => $v) {
      if (empty($k) || empty($v)) continue;
      if ($k=='password') continue;
      $req.=$k.'="'.$v.'", ';
    }
    $req.="\r\n";
  }
  foreach ($head as $k => $v) {
    $req.=$k.': '.$v."\r\n";
  }
  if (empty($head['Content-type'])) {
    $ct='text/xml';
    $req.="Content-type: $ct\r\n";
  }
  $req.='Content-length: '. strlen($data_to_send) ."\r\n";
  $req.="Connection: close\r\n\r\n";
 
  fputs($fp, $req);
  fputs($fp, $data_to_send);
 
  while(!feof($fp)) { $res .= fgets($fp, 128); }
  fclose($fp);
 
  if ($auth['type']!='nodigest'
        && !empty($auth['username'])
        && $auth['type']!='digest' # prev. digest AUTH failed.
        && preg_match("/^HTTP\/[0-9\.]* 401 /", $res)) {
    if (1 == preg_match("/WWW-Authenticate: Digest ([^\n\r]*)\r\n/Us", $res, $matches)) {
      foreach (split(",", $matches[1]) as $i) {
        $ii=split("=",trim($i),2);
        if (!empty($ii[1]) && !empty($ii[0])) {
          $auth[$ii[0]]=preg_replace("/^\"/",'', preg_replace("/\"$/",'', $ii[1]));
        }
      }
      $auth['type']='digest';
      $auth['uri']='https://'.$host.$path;
      $auth['cnonce']=randomNonce();
      $auth['nc']=1;
      $a1=md5($auth['username'].':'.$auth['realm'].':'.$auth['password']);
      $a2=md5('POST'.':'.$auth['uri']);
      $auth['response']=md5($a1.':'.$auth['nonce'].':'.$auth['nc'].':'.$auth['cnonce'].':'.$auth['qop'].':'.$a2);
      return httpPost($host, $path, $data_to_send, $opts, $auth);
    }
  }
 
  if (1 != preg_match("/^HTTP\/[0-9\.]* ([0-9]{3}) ([^\r\n]*)/", $res, $matches)) {
    trigger_error('httpPost: invalid HTTP reply.');
    return NULL;
  }
 
  if (1 != preg_match("/^2[0-9]{2}$/", $matches[1])) {
      trigger_error('httpPost: HTTP error: '.$matches[1].' '.$matches[2]);
      return NULL;
  }
 
  if (!$opts['headers']) {
    $res=preg_replace("/^.*\r\n\r\n/Us",'',$res);
  }
  return $res;
}
 
function randomNonce($len=0) {
  $chars = "ABCDEFGHIJKMNOPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz023456789";
  $i=0; $rv='';
  if ($len < 1) $len= (6+rand()%10);
  while ($i++ < $len) {
    $rv.=$chars[rand() % strlen($chars)];
  }
  return $rv;
}
 
// vi: set sw=2 ts=4 sts=4 et :

view dokurpc.php source

download dokurpc.php

Just googled for more example code. obviously there's plenty eg. http://snippets.dzone.com/tag/xmlrpc and I found this nice blog on the way: dokuwimki I'm gonna get side tracked.

 
wiki/dokuxmlrpc.txt · Last modified: 23.12.2011 21:26 (external edit)