Failed Conditions
Pull Request — psr2 (#2426)
by Andreas
09:22 queued 06:07
created

inc/Remote/Api.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace dokuwiki\Remote;
4
5
use dokuwiki\Extension\Event;
6
use dokuwiki\Extension\RemotePlugin;
7
use Input;
8
9
/**
10
 * This class provides information about remote access to the wiki.
11
 *
12
 * == Types of methods ==
13
 * There are two types of remote methods. The first is the core methods.
14
 * These are always available and provided by dokuwiki.
15
 * The other is plugin methods. These are provided by remote plugins.
16
 *
17
 * == Information structure ==
18
 * The information about methods will be given in an array with the following structure:
19
 * array(
20
 *     'method.remoteName' => array(
21
 *          'args' => array(
22
 *              'type eg. string|int|...|date|file',
23
 *          )
24
 *          'name' => 'method name in class',
25
 *          'return' => 'type',
26
 *          'public' => 1/0 - method bypass default group check (used by login)
27
 *          ['doc' = 'method documentation'],
28
 *     )
29
 * )
30
 *
31
 * plugin names are formed the following:
32
 *   core methods begin by a 'dokuwiki' or 'wiki' followed by a . and the method name itself.
33
 *   i.e.: dokuwiki.version or wiki.getPage
34
 *
35
 * plugin methods are formed like 'plugin.<plugin name>.<method name>'.
36
 * i.e.: plugin.clock.getTime or plugin.clock_gmt.getTime
37
 */
38
class Api
39
{
40
41
    /**
42
     * @var ApiCore
43
     */
44
    private $coreMethods = null;
45
46
    /**
47
     * @var array remote methods provided by dokuwiki plugins - will be filled lazy via
48
     * {@see dokuwiki\Remote\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
    /**
64
     * constructor
65
     */
66
    public function __construct()
67
    {
68
        $this->dateTransformation = array($this, 'dummyTransformation');
69
        $this->fileTransformation = array($this, 'dummyTransformation');
70
    }
71
72
    /**
73
     * Get all available methods with remote access.
74
     *
75
     * @return array with information to all available methods
76
     * @throws RemoteException
77
     */
78
    public function getMethods()
79
    {
80
        return array_merge($this->getCoreMethods(), $this->getPluginMethods());
81
    }
82
83
    /**
84
     * Call a method via remote api.
85
     *
86
     * @param string $method name of the method to call.
87
     * @param array $args arguments to pass to the given method
88
     * @return mixed result of method call, must be a primitive type.
89
     * @throws RemoteException
90
     */
91
    public function call($method, $args = array())
92
    {
93
        if ($args === null) {
94
            $args = array();
95
        }
96
        list($type, $pluginName, /* $call */) = explode('.', $method, 3);
97
        if ($type === 'plugin') {
98
            return $this->callPlugin($pluginName, $method, $args);
99
        }
100
        if ($this->coreMethodExist($method)) {
101
            return $this->callCoreMethod($method, $args);
102
        }
103
        return $this->callCustomCallPlugin($method, $args);
104
    }
105
106
    /**
107
     * Check existance of core methods
108
     *
109
     * @param string $name name of the method
110
     * @return bool if method exists
111
     */
112
    private function coreMethodExist($name)
113
    {
114
        $coreMethods = $this->getCoreMethods();
115
        return array_key_exists($name, $coreMethods);
116
    }
117
118
    /**
119
     * Try to call custom methods provided by plugins
120
     *
121
     * @param string $method name of method
122
     * @param array $args
123
     * @return mixed
124
     * @throws RemoteException if method not exists
125
     */
126
    private function callCustomCallPlugin($method, $args)
127
    {
128
        $customCalls = $this->getCustomCallPlugins();
129
        if (!array_key_exists($method, $customCalls)) {
130
            throw new RemoteException('Method does not exist', -32603);
131
        }
132
        $customCall = $customCalls[$method];
133
        return $this->callPlugin($customCall[0], $customCall[1], $args);
134
    }
135
136
    /**
137
     * Returns plugin calls that are registered via RPC_CALL_ADD action
138
     *
139
     * @return array with pairs of custom plugin calls
140
     * @triggers RPC_CALL_ADD
141
     */
142
    private function getCustomCallPlugins()
143
    {
144
        if ($this->pluginCustomCalls === null) {
145
            $data = array();
146
            Event::createAndTrigger('RPC_CALL_ADD', $data);
147
            $this->pluginCustomCalls = $data;
148
        }
149
        return $this->pluginCustomCalls;
150
    }
151
152
    /**
153
     * Call a plugin method
154
     *
155
     * @param string $pluginName
156
     * @param string $method method name
157
     * @param array $args
158
     * @return mixed return of custom method
159
     * @throws RemoteException
160
     */
161
    private function callPlugin($pluginName, $method, $args)
162
    {
163
        $plugin = plugin_load('remote', $pluginName);
0 ignored issues
show
Deprecated Code introduced by
The function plugin_load() has been deprecated with message: 2018-07-20 we will probably keep this around for a long time though

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
164
        $methods = $this->getPluginMethods();
165
        if (!$plugin) {
166
            throw new RemoteException('Method does not exist', -32603);
167
        }
168
        $this->checkAccess($methods[$method]);
169
        $name = $this->getMethodName($methods, $method);
170
        return call_user_func_array(array($plugin, $name), $args);
171
    }
172
173
    /**
174
     * Call a core method
175
     *
176
     * @param string $method name of method
177
     * @param array $args
178
     * @return mixed
179
     * @throws RemoteException if method not exist
180
     */
181
    private function callCoreMethod($method, $args)
182
    {
183
        $coreMethods = $this->getCoreMethods();
184
        $this->checkAccess($coreMethods[$method]);
185
        if (!isset($coreMethods[$method])) {
186
            throw new RemoteException('Method does not exist', -32603);
187
        }
188
        $this->checkArgumentLength($coreMethods[$method], $args);
189
        return call_user_func_array(array($this->coreMethods, $this->getMethodName($coreMethods, $method)), $args);
190
    }
191
192
    /**
193
     * Check if access should be checked
194
     *
195
     * @param array $methodMeta data about the method
196
     * @throws AccessDeniedException
197
     */
198
    private function checkAccess($methodMeta)
199
    {
200
        if (!isset($methodMeta['public'])) {
201
            $this->forceAccess();
202
        } else {
203
            if ($methodMeta['public'] == '0') {
204
                $this->forceAccess();
205
            }
206
        }
207
    }
208
209
    /**
210
     * Check the number of parameters
211
     *
212
     * @param array $methodMeta data about the method
213
     * @param array $args
214
     * @throws RemoteException if wrong parameter count
215
     */
216
    private function checkArgumentLength($methodMeta, $args)
217
    {
218
        if (count($methodMeta['args']) < count($args)) {
219
            throw new RemoteException('Method does not exist - wrong parameter count.', -32603);
220
        }
221
    }
222
223
    /**
224
     * Determine the name of the real method
225
     *
226
     * @param array $methodMeta list of data of the methods
227
     * @param string $method name of method
228
     * @return string
229
     */
230
    private function getMethodName($methodMeta, $method)
231
    {
232
        if (isset($methodMeta[$method]['name'])) {
233
            return $methodMeta[$method]['name'];
234
        }
235
        $method = explode('.', $method);
236
        return $method[count($method) - 1];
237
    }
238
239
    /**
240
     * Perform access check for current user
241
     *
242
     * @return bool true if the current user has access to remote api.
243
     * @throws AccessDeniedException If remote access disabled
244
     */
245
    public function hasAccess()
246
    {
247
        global $conf;
248
        global $USERINFO;
249
        /** @var Input $INPUT */
250
        global $INPUT;
251
252
        if (!$conf['remote']) {
253
            throw new AccessDeniedException('server error. RPC server not enabled.', -32604);
254
        }
255
        if (trim($conf['remoteuser']) == '!!not set!!') {
256
            return false;
257
        }
258
        if (!$conf['useacl']) {
259
            return true;
260
        }
261
        if (trim($conf['remoteuser']) == '') {
262
            return true;
263
        }
264
265
        return auth_isMember($conf['remoteuser'], $INPUT->server->str('REMOTE_USER'), (array) $USERINFO['grps']);
266
    }
267
268
    /**
269
     * Requests access
270
     *
271
     * @return void
272
     * @throws AccessDeniedException On denied access.
273
     */
274
    public function forceAccess()
275
    {
276
        if (!$this->hasAccess()) {
277
            throw new AccessDeniedException('server error. not authorized to call method', -32604);
278
        }
279
    }
280
281
    /**
282
     * Collects all the methods of the enabled Remote Plugins
283
     *
284
     * @return array all plugin methods.
285
     * @throws RemoteException if not implemented
286
     */
287
    public function getPluginMethods()
288
    {
289
        if ($this->pluginMethods === null) {
290
            $this->pluginMethods = array();
291
            $plugins = plugin_list('remote');
0 ignored issues
show
Deprecated Code introduced by
The function plugin_list() has been deprecated with message: 2018-07-20

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
292
293
            foreach ($plugins as $pluginName) {
294
                /** @var RemotePlugin $plugin */
295
                $plugin = plugin_load('remote', $pluginName);
0 ignored issues
show
Deprecated Code introduced by
The function plugin_load() has been deprecated with message: 2018-07-20 we will probably keep this around for a long time though

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
296
                if (!is_subclass_of($plugin, 'dokuwiki\Extension\RemotePlugin')) {
297
                    throw new RemoteException("Plugin $pluginName does not implement dokuwiki\Plugin\DokuWiki_Remote_Plugin");
298
                }
299
300
                try {
301
                    $methods = $plugin->_getMethods();
302
                } catch (\ReflectionException $e) {
303
                    throw new RemoteException('Automatic aggregation of available remote methods failed', 0, $e);
304
                }
305
306
                foreach ($methods as $method => $meta) {
307
                    $this->pluginMethods["plugin.$pluginName.$method"] = $meta;
308
                }
309
            }
310
        }
311
        return $this->pluginMethods;
312
    }
313
314
    /**
315
     * Collects all the core methods
316
     *
317
     * @param ApiCore $apiCore this parameter is used for testing. Here you can pass a non-default RemoteAPICore
318
     *                         instance. (for mocking)
319
     * @return array all core methods.
320
     */
321
    public function getCoreMethods($apiCore = null)
322
    {
323
        if ($this->coreMethods === null) {
324
            if ($apiCore === null) {
325
                $this->coreMethods = new ApiCore($this);
326
            } else {
327
                $this->coreMethods = $apiCore;
328
            }
329
        }
330
        return $this->coreMethods->__getRemoteInfo();
331
    }
332
333
    /**
334
     * Transform file to xml
335
     *
336
     * @param mixed $data
337
     * @return mixed
338
     */
339
    public function toFile($data)
340
    {
341
        return call_user_func($this->fileTransformation, $data);
342
    }
343
344
    /**
345
     * Transform date to xml
346
     *
347
     * @param mixed $data
348
     * @return mixed
349
     */
350
    public function toDate($data)
351
    {
352
        return call_user_func($this->dateTransformation, $data);
353
    }
354
355
    /**
356
     * A simple transformation
357
     *
358
     * @param mixed $data
359
     * @return mixed
360
     */
361
    public function dummyTransformation($data)
362
    {
363
        return $data;
364
    }
365
366
    /**
367
     * Set the transformer function
368
     *
369
     * @param callback $dateTransformation
370
     */
371
    public function setDateTransformation($dateTransformation)
372
    {
373
        $this->dateTransformation = $dateTransformation;
374
    }
375
376
    /**
377
     * Set the transformer function
378
     *
379
     * @param callback $fileTransformation
380
     */
381
    public function setFileTransformation($fileTransformation)
382
    {
383
        $this->fileTransformation = $fileTransformation;
384
    }
385
}
386