Mereged updates from DokuWiki 38
[sudaraka-org:dokuwiki-mods.git] / inc / remote.php
1 <?php
2
3 if (!defined('DOKU_INC')) die();
4
5 class RemoteException extends Exception {}
6 class RemoteAccessDeniedException extends RemoteException {}
7
8 /**
9  * This class provides information about remote access to the wiki.
10  *
11  * == Types of methods ==
12  * There are two types of remote methods. The first is the core methods.
13  * These are always available and provided by dokuwiki.
14  * The other is plugin methods. These are provided by remote plugins.
15  *
16  * == Information structure ==
17  * The information about methods will be given in an array with the following structure:
18  * array(
19  *     'method.remoteName' => array(
20  *          'args' => array(
21  *              'type eg. string|int|...|date|file',
22  *          )
23  *          'name' => 'method name in class',
24  *          'return' => 'type',
25  *          'public' => 1/0 - method bypass default group check (used by login)
26  *          ['doc' = 'method documentation'],
27  *     )
28  * )
29  *
30  * plugin names are formed the following:
31  *   core methods begin by a 'dokuwiki' or 'wiki' followed by a . and the method name itself.
32  *   i.e.: dokuwiki.version or wiki.getPage
33  *
34  * plugin methods are formed like 'plugin.<plugin name>.<method name>'.
35  * i.e.: plugin.clock.getTime or plugin.clock_gmt.getTime
36  *
37  * @throws RemoteException
38  */
39 class RemoteAPI {
40
41     /**
42      * @var RemoteAPICore
43      */
44     private $coreMethods = null;
45
46     /**
47      * @var array remote methods provided by dokuwiki plugins - will be filled lazy via
48      * {@see RemoteAPI#getPluginMethods}
49      */
50     private $pluginMethods = null;
51
52     /**
53      * @var array contains custom calls to the api. Plugins can use the XML_CALL_REGISTER event.
54      * The data inside is 'custom.call.something' => array('plugin name', 'remote method name')
55      *
56      * The remote method name is the same as in the remote name returned by _getMethods().
57      */
58     private $pluginCustomCalls = null;
59
60     private $dateTransformation;
61     private $fileTransformation;
62
63     public function __construct() {
64         $this->dateTransformation = array($this, 'dummyTransformation');
65         $this->fileTransformation = array($this, 'dummyTransformation');
66     }
67
68     /**
69      * Get all available methods with remote access.
70      *
71      * @return array with information to all available methods
72      */
73     public function getMethods() {
74         return array_merge($this->getCoreMethods(), $this->getPluginMethods());
75     }
76
77     /**
78      * call a method via remote api.
79      *
80      * @param string $method name of the method to call.
81      * @param array $args arguments to pass to the given method
82      * @return mixed result of method call, must be a primitive type.
83      */
84     public function call($method, $args = array()) {
85         if ($args === null) {
86             $args = array();
87         }
88         list($type, $pluginName, $call) = explode('.', $method, 3);
89         if ($type === 'plugin') {
90             return $this->callPlugin($pluginName, $method, $args);
91         }
92         if ($this->coreMethodExist($method)) {
93             return $this->callCoreMethod($method, $args);
94         }
95         return $this->callCustomCallPlugin($method, $args);
96     }
97
98     private function coreMethodExist($name) {
99         $coreMethods = $this->getCoreMethods();
100         return array_key_exists($name, $coreMethods);
101     }
102
103     private function  callCustomCallPlugin($method, $args) {
104         $customCalls = $this->getCustomCallPlugins();
105         if (!array_key_exists($method, $customCalls)) {
106             throw new RemoteException('Method does not exist', -32603);
107         }
108         $customCall = $customCalls[$method];
109         return $this->callPlugin($customCall[0], $customCall[1], $args);
110     }
111
112     private function getCustomCallPlugins() {
113         if ($this->pluginCustomCalls === null) {
114             $data = array();
115             trigger_event('RPC_CALL_ADD', $data);
116             $this->pluginCustomCalls = $data;
117         }
118         return $this->pluginCustomCalls;
119     }
120
121     private function callPlugin($pluginName, $method, $args) {
122         $plugin = plugin_load('remote', $pluginName);
123         $methods = $this->getPluginMethods();
124         if (!$plugin) {
125             throw new RemoteException('Method does not exist', -32603);
126         }
127         $this->checkAccess($methods[$method]);
128         $name = $this->getMethodName($methods, $method);
129         return call_user_func_array(array($plugin, $name), $args);
130     }
131
132     private function callCoreMethod($method, $args) {
133         $coreMethods = $this->getCoreMethods();
134         $this->checkAccess($coreMethods[$method]);
135         if (!isset($coreMethods[$method])) {
136             throw new RemoteException('Method does not exist', -32603);
137         }
138         $this->checkArgumentLength($coreMethods[$method], $args);
139         return call_user_func_array(array($this->coreMethods, $this->getMethodName($coreMethods, $method)), $args);
140     }
141
142     private function checkAccess($methodMeta) {
143         if (!isset($methodMeta['public'])) {
144             $this->forceAccess();
145         } else{
146             if ($methodMeta['public'] == '0') {
147                 $this->forceAccess();
148             }
149         }
150     }
151
152     private function checkArgumentLength($method, $args) {
153         if (count($method['args']) < count($args)) {
154             throw new RemoteException('Method does not exist - wrong parameter count.', -32603);
155         }
156     }
157
158     private function getMethodName($methodMeta, $method) {
159         if (isset($methodMeta[$method]['name'])) {
160             return $methodMeta[$method]['name'];
161         }
162         $method = explode('.', $method);
163         return $method[count($method)-1];
164     }
165
166     /**
167      * @return bool true if the current user has access to remote api.
168      */
169     public function hasAccess() {
170         global $conf;
171         global $USERINFO;
172         if (!$conf['remote']) {
173             return false;
174         }
175         if(!$conf['useacl']) {
176             return true;
177         }
178         if(trim($conf['remoteuser']) == '') {
179             return true;
180         }
181
182         return auth_isMember($conf['remoteuser'], $_SERVER['REMOTE_USER'], (array) $USERINFO['grps']);
183     }
184
185     /**
186      * @throws RemoteException On denied access.
187      * @return void
188      */
189     public function forceAccess() {
190         if (!$this->hasAccess()) {
191             throw new RemoteAccessDeniedException('server error. not authorized to call method', -32604);
192         }
193     }
194
195     /**
196      * @return array all plugin methods.
197      */
198     public function getPluginMethods() {
199         if ($this->pluginMethods === null) {
200             $this->pluginMethods = array();
201             $plugins = plugin_list('remote');
202
203             foreach ($plugins as $pluginName) {
204                 $plugin = plugin_load('remote', $pluginName);
205                 if (!is_subclass_of($plugin, 'DokuWiki_Remote_Plugin')) {
206                     throw new RemoteException("Plugin $pluginName does not implement DokuWiki_Remote_Plugin");
207                 }
208
209                 $methods = $plugin->_getMethods();
210                 foreach ($methods as $method => $meta) {
211                     $this->pluginMethods["plugin.$pluginName.$method"] = $meta;
212                 }
213             }
214         }
215         return $this->pluginMethods;
216     }
217
218     /**
219      * @param RemoteAPICore $apiCore this parameter is used for testing. Here you can pass a non-default RemoteAPICore
220      *                               instance. (for mocking)
221      * @return array all core methods.
222      */
223     public function getCoreMethods($apiCore = null) {
224         if ($this->coreMethods === null) {
225             if ($apiCore === null) {
226                 $this->coreMethods = new RemoteAPICore($this);
227             } else {
228                 $this->coreMethods = $apiCore;
229             }
230         }
231         return $this->coreMethods->__getRemoteInfo();
232     }
233
234     public function toFile($data) {
235         return call_user_func($this->fileTransformation, $data);
236     }
237
238     public function toDate($data) {
239         return call_user_func($this->dateTransformation, $data);
240     }
241
242     public function dummyTransformation($data) {
243         return $data;
244     }
245
246     public function setDateTransformation($dateTransformation) {
247         $this->dateTransformation = $dateTransformation;
248     }
249
250     public function setFileTransformation($fileTransformation) {
251         $this->fileTransformation = $fileTransformation;
252     }
253 }