1<?php
2/**
3 * Description of IJR_Server
4 *
5 * @author  Andreas Gohr <andi@splitbrain.org>
6 * @author Magnus Wolf <mwolf2706@googlemail.com>
7 */
8class IJR_Server {
9
10    var $data;
11    var $callbacks = array();
12    var $message;
13    var $capabilities;
14    var $method;
15    var $id;
16
17
18    function IJR_Server($callbacks = false, $data = false)
19    {
20        $this->setCapabilities();
21        if ($callbacks) {
22            $this->callbacks = $callbacks;
23        }
24        $this->setCallbacks();
25        $this->serve($data);
26    }
27
28
29    private function verifyData($data)
30    {
31        if($data == NULL){
32            $this->error(-32700, 'decode error. no correct json string');
33        }
34
35        if(!$data['method']){
36        	$this->error(-32600, 'server error. invalid json-rpc. not conforming to spec. Request must be a method');
37        }
38
39        if(!$data['jsonrpc'] || $data['jsonrpc']!='2.0'){
40            $this->error(-32600, 'server error. invalid json-rpc. wrong jsonrpc-spec 2.0 is available');
41        }
42    }
43
44    function serve($data = false)
45    {
46        if (!$data) {
47            global $HTTP_RAW_POST_DATA;
48            if (!$HTTP_RAW_POST_DATA) {
49               die('JSON-RPC server accepts POST requests only.');
50            }
51            $data = $HTTP_RAW_POST_DATA;
52        }
53
54        $data = json_decode($data, true);
55        $this->verifyData($data);
56        $this->id = $data['id'];
57        $parameter = array();
58
59        foreach($data['params'] as $param){
60            foreach($param as $key => $value){
61        	$parameter[] = $value;
62            }
63        }
64
65        $res = $this->call($data['method']['methodName'], $parameter);
66        if (is_a($res, 'IJR_Error')) {
67            $this->error($res);
68        }
69
70        // Send it
71        $result['jsonrpc']='2.0';
72        $result['result']=$res;
73        $result['error']='';
74        $result['id']=$this->id;
75        $result = json_encode($result);
76        $this->output($result);
77    }
78
79
80    private function callClassMethod($method, $args)
81    {
82        $method = substr($method, 5);
83        if (!method_exists($this, $method)) {
84            return new IJR_Error(-32601, 'server error. requested class method "'.$method.'" does not exist.');
85        }
86        return call_user_func_array(array(&$this,$method),$args);
87    }
88
89
90    private function callPlugin($pluginname, $callback, $method, $args)
91    {
92        require_once(DOKU_INC.'inc/pluginutils.php');
93        list($pluginname, $callback) = explode(':', substr($method, 7), 2);
94        if(!plugin_isdisabled($pluginname))
95        {
96            $plugin = plugin_load('action', $pluginname);
97            return call_user_func_array(array($plugin, $callback), $args);
98        }
99        else
100        {
101            return new IJR_Error(-99999, 'server error');
102        }
103    }
104
105
106    private function callFunction($method, $args)
107    {
108        if (!function_exists($method))
109        {
110            return new IJR_Error(-32601, 'server error. requested function "'.$method.'" does not exist.');
111        }
112        return call_user_func_array($method,$args);
113    }
114
115
116    protected function call($methodname, $args)
117    {
118        if (!$this->hasMethod($methodname))
119        {
120            return new IJR_Error(-32601, 'server error. requested method '.$methodname.' does not exist.');
121        }
122        $method = $this->callbacks[$methodname];
123        // Perform the callback and send the response
124# Adjusted for DokuWiki to use call_user_func_array
125        // args need to be an array
126        $args = (array) $args;
127        if (substr($method, 0, 5) == 'this:')
128        {
129            $result = $this->callClassMethod($method, $args);
130        }
131        elseif (substr($method, 0, 7) == 'plugin:')
132        {
133            return $this->callPlugin($pluginname, $callback, $method, $args);
134        }
135        else
136        {
137            $result = $this->callFunction($method, $args);
138        }
139        return $result;
140    }
141
142
143    function error($error, $message = false)
144    {
145        if ($message && !is_object($error)) {
146            $error = new IJR_Error($error, $message);
147        }
148        $result['jsonrpc']='2.0';
149        $result['result']='';
150        $result['error']=$error->getJson();
151        $result['id']=$this->id;
152        $result = json_encode($result);
153        $this->output($result);
154    }
155
156
157    function output($json)
158    {
159        $length = strlen($json);
160        header('Connection: close');
161        header('Content-Length: '.$length);
162        header('Content-Type: application/json');
163        header('Date: '.date('r'));
164        echo $json;
165        exit;
166    }
167
168
169    function hasMethod($method) {
170        return in_array($method, array_keys($this->callbacks));
171    }
172
173
174    function setCapabilities() {
175        // Initialises capabilities array
176        $this->capabilities = array(
177            'xmlrpc' => array(
178                'specUrl' => 'http://www.xmlrpc.com/spec',
179                'specVersion' => 1
180            ),
181            'faults_interop' => array(
182                'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
183                'specVersion' => 20010516
184            ),
185            'system.multicall' => array(
186                'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
187                'specVersion' => 1
188            ),
189        );
190    }
191
192
193    function getCapabilities() {
194        return $this->capabilities;
195    }
196
197
198    function setCallbacks() {
199        $this->callbacks['system.getCapabilities'] = 'this:getCapabilities';
200        $this->callbacks['system.listMethods'] = 'this:listMethods';
201        $this->callbacks['system.multicall'] = 'this:multiCall';
202    }
203
204
205    function listMethods() {
206        // Returns a list of methods - uses array_reverse to ensure user defined
207        // methods are listed before server defined methods
208        return array_reverse(array_keys($this->callbacks));
209    }
210
211
212    function multiCall($methodcalls) {
213        // See http://www.xmlrpc.com/discuss/msgReader$1208
214        $return = array();
215        foreach ($methodcalls as $call) {
216            $method = $call['methodName'];
217            $params = $call['params'];
218            if ($method == 'system.multicall') {
219                $result = new IJR_Error(-32600, 'Recursive calls to system.multicall are forbidden');
220            } else {
221                $result = $this->call($method, $params);
222            }
223            if (is_a($result, 'IJR_Error')) {
224                $return[] = array(
225                    'faultCode' => $result->code,
226                    'faultString' => $result->message
227                );
228            } else {
229                $return[] = array($result);
230            }
231        }
232        return $return;
233    }
234}
235?>
236