Failed Conditions
Push — remote-argumentcount ( e1215f )
by Henry
03:06
created

inc/Remote/Api.php (2 issues)

Labels
Severity

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
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
class Api
38
{
39
40
    /**
41
     * @var ApiCore
42
     */
43
    private $coreMethods = null;
44
45
    /**
46
     * @var array remote methods provided by dokuwiki plugins - will be filled lazy via
47
     * {@see dokuwiki\Remote\RemoteAPI#getPluginMethods}
48
     */
49
    private $pluginMethods = null;
50
51
    /**
52
     * @var array contains custom calls to the api. Plugins can use the XML_CALL_REGISTER event.
53
     * The data inside is 'custom.call.something' => array('plugin name', 'remote method name')
54
     *
55
     * The remote method name is the same as in the remote name returned by _getMethods().
56
     */
57
    private $pluginCustomCalls = null;
58
59
    private $dateTransformation;
60
    private $fileTransformation;
61
62
    /**
63
     * constructor
64
     */
65
    public function __construct()
66
    {
67
        $this->dateTransformation = array($this, 'dummyTransformation');
68
        $this->fileTransformation = array($this, 'dummyTransformation');
69
    }
70
71
    /**
72
     * Get all available methods with remote access.
73
     *
74
     * @return array with information to all available methods
75
     * @throws RemoteException
76
     */
77
    public function getMethods()
78
    {
79
        return array_merge($this->getCoreMethods(), $this->getPluginMethods());
80
    }
81
82
    /**
83
     * Call a method via remote api.
84
     *
85
     * @param string $method name of the method to call.
86
     * @param array $args arguments to pass to the given method
87
     * @return mixed result of method call, must be a primitive type.
88
     * @throws RemoteException
89
     */
90
    public function call($method, $args = array())
91
    {
92
        if ($args === null) {
93
            $args = array();
94
        }
95
        list($type, $pluginName, /* $call */) = explode('.', $method, 3);
96
        if ($type === 'plugin') {
97
            return $this->callPlugin($pluginName, $method, $args);
98
        }
99
        if ($this->coreMethodExist($method)) {
100
            return $this->callCoreMethod($method, $args);
101
        }
102
        return $this->callCustomCallPlugin($method, $args);
103
    }
104
105
    /**
106
     * Check existance of core methods
107
     *
108
     * @param string $name name of the method
109
     * @return bool if method exists
110
     */
111
    private function coreMethodExist($name)
112
    {
113
        $coreMethods = $this->getCoreMethods();
114
        return array_key_exists($name, $coreMethods);
115
    }
116
117
    /**
118
     * Try to call custom methods provided by plugins
119
     *
120
     * @param string $method name of method
121
     * @param array $args
122
     * @return mixed
123
     * @throws RemoteException if method not exists
124
     */
125
    private function callCustomCallPlugin($method, $args)
126
    {
127
        $customCalls = $this->getCustomCallPlugins();
128
        if (!array_key_exists($method, $customCalls)) {
129
            throw new RemoteException('Method does not exist', -32603);
130
        }
131
        $customCall = $customCalls[$method];
132
        return $this->callPlugin($customCall[0], $customCall[1], $args);
133
    }
134
135
    /**
136
     * Returns plugin calls that are registered via RPC_CALL_ADD action
137
     *
138
     * @return array with pairs of custom plugin calls
139
     * @triggers RPC_CALL_ADD
140
     */
141
    private function getCustomCallPlugins()
142
    {
143
        if ($this->pluginCustomCalls === null) {
144
            $data = array();
145
            Event::createAndTrigger('RPC_CALL_ADD', $data);
146
            $this->pluginCustomCalls = $data;
147
        }
148
        return $this->pluginCustomCalls;
149
    }
150
151
    /**
152
     * Call a plugin method
153
     *
154
     * @param string $pluginName
155
     * @param string $method method name
156
     * @param array $args
157
     * @return mixed return of custom method
158
     * @throws RemoteException
159
     */
160
    private function callPlugin($pluginName, $method, $args)
161
    {
162
        $plugin = plugin_load('remote', $pluginName);
163
        $methods = $this->getPluginMethods();
164
        if (!$plugin) {
165
            throw new RemoteException('Method does not exist', -32603);
166
        }
167
        $this->checkAccess($methods[$method]);
168
        $name = $this->getMethodName($methods, $method);
169
        try {
170
            return call_user_func_array(array($plugin, $name), $args);
171
        } catch (\ArgumentCountError $th) {
0 ignored issues
show
The class ArgumentCountError does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
172
            throw new RemoteException('Method does not exist - wrong parameter count.', -32603);
173
        }
174
    }
175
176
    /**
177
     * Call a core method
178
     *
179
     * @param string $method name of method
180
     * @param array $args
181
     * @return mixed
182
     * @throws RemoteException if method not exist
183
     */
184
    private function callCoreMethod($method, $args)
185
    {
186
        $coreMethods = $this->getCoreMethods();
187
        $this->checkAccess($coreMethods[$method]);
188
        if (!isset($coreMethods[$method])) {
189
            throw new RemoteException('Method does not exist', -32603);
190
        }
191
        $this->checkArgumentLength($coreMethods[$method], $args);
192
        try {
193
            return call_user_func_array(array($this->coreMethods, $this->getMethodName($coreMethods, $method)), $args);
194
        } catch (\ArgumentCountError $th) {
0 ignored issues
show
The class ArgumentCountError does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
195
            throw new RemoteException('Method does not exist - wrong parameter count.', -32603);
196
        }
197
    }
198
199
    /**
200
     * Check if access should be checked
201
     *
202
     * @param array $methodMeta data about the method
203
     * @throws AccessDeniedException
204
     */
205
    private function checkAccess($methodMeta)
206
    {
207
        if (!isset($methodMeta['public'])) {
208
            $this->forceAccess();
209
        } else {
210
            if ($methodMeta['public'] == '0') {
211
                $this->forceAccess();
212
            }
213
        }
214
    }
215
216
    /**
217
     * Check the number of parameters
218
     *
219
     * @param array $methodMeta data about the method
220
     * @param array $args
221
     * @throws RemoteException if wrong parameter count
222
     */
223
    private function checkArgumentLength($methodMeta, $args)
224
    {
225
        if (count($methodMeta['args']) < count($args)) {
226
            throw new RemoteException('Method does not exist - wrong parameter count.', -32603);
227
        }
228
    }
229
230
    /**
231
     * Determine the name of the real method
232
     *
233
     * @param array $methodMeta list of data of the methods
234
     * @param string $method name of method
235
     * @return string
236
     */
237
    private function getMethodName($methodMeta, $method)
238
    {
239
        if (isset($methodMeta[$method]['name'])) {
240
            return $methodMeta[$method]['name'];
241
        }
242
        $method = explode('.', $method);
243
        return $method[count($method) - 1];
244
    }
245
246
    /**
247
     * Perform access check for current user
248
     *
249
     * @return bool true if the current user has access to remote api.
250
     * @throws AccessDeniedException If remote access disabled
251
     */
252
    public function hasAccess()
253
    {
254
        global $conf;
255
        global $USERINFO;
256
        /** @var \dokuwiki\Input\Input $INPUT */
257
        global $INPUT;
258
259
        if (!$conf['remote']) {
260
            throw new AccessDeniedException('server error. RPC server not enabled.', -32604);
261
        }
262
        if (trim($conf['remoteuser']) == '!!not set!!') {
263
            return false;
264
        }
265
        if (!$conf['useacl']) {
266
            return true;
267
        }
268
        if (trim($conf['remoteuser']) == '') {
269
            return true;
270
        }
271
272
        return auth_isMember($conf['remoteuser'], $INPUT->server->str('REMOTE_USER'), (array) $USERINFO['grps']);
273
    }
274
275
    /**
276
     * Requests access
277
     *
278
     * @return void
279
     * @throws AccessDeniedException On denied access.
280
     */
281
    public function forceAccess()
282
    {
283
        if (!$this->hasAccess()) {
284
            throw new AccessDeniedException('server error. not authorized to call method', -32604);
285
        }
286
    }
287
288
    /**
289
     * Collects all the methods of the enabled Remote Plugins
290
     *
291
     * @return array all plugin methods.
292
     * @throws RemoteException if not implemented
293
     */
294
    public function getPluginMethods()
295
    {
296
        if ($this->pluginMethods === null) {
297
            $this->pluginMethods = array();
298
            $plugins = plugin_list('remote');
299
300
            foreach ($plugins as $pluginName) {
301
                /** @var RemotePlugin $plugin */
302
                $plugin = plugin_load('remote', $pluginName);
303
                if (!is_subclass_of($plugin, 'dokuwiki\Extension\RemotePlugin')) {
304
                    throw new RemoteException(
305
                        "Plugin $pluginName does not implement dokuwiki\Plugin\DokuWiki_Remote_Plugin"
306
                    );
307
                }
308
309
                try {
310
                    $methods = $plugin->_getMethods();
311
                } catch (\ReflectionException $e) {
312
                    throw new RemoteException('Automatic aggregation of available remote methods failed', 0, $e);
313
                }
314
315
                foreach ($methods as $method => $meta) {
316
                    $this->pluginMethods["plugin.$pluginName.$method"] = $meta;
317
                }
318
            }
319
        }
320
        return $this->pluginMethods;
321
    }
322
323
    /**
324
     * Collects all the core methods
325
     *
326
     * @param ApiCore $apiCore this parameter is used for testing. Here you can pass a non-default RemoteAPICore
327
     *                         instance. (for mocking)
328
     * @return array all core methods.
329
     */
330
    public function getCoreMethods($apiCore = null)
331
    {
332
        if ($this->coreMethods === null) {
333
            if ($apiCore === null) {
334
                $this->coreMethods = new ApiCore($this);
335
            } else {
336
                $this->coreMethods = $apiCore;
337
            }
338
        }
339
        return $this->coreMethods->__getRemoteInfo();
340
    }
341
342
    /**
343
     * Transform file to xml
344
     *
345
     * @param mixed $data
346
     * @return mixed
347
     */
348
    public function toFile($data)
349
    {
350
        return call_user_func($this->fileTransformation, $data);
351
    }
352
353
    /**
354
     * Transform date to xml
355
     *
356
     * @param mixed $data
357
     * @return mixed
358
     */
359
    public function toDate($data)
360
    {
361
        return call_user_func($this->dateTransformation, $data);
362
    }
363
364
    /**
365
     * A simple transformation
366
     *
367
     * @param mixed $data
368
     * @return mixed
369
     */
370
    public function dummyTransformation($data)
371
    {
372
        return $data;
373
    }
374
375
    /**
376
     * Set the transformer function
377
     *
378
     * @param callback $dateTransformation
379
     */
380
    public function setDateTransformation($dateTransformation)
381
    {
382
        $this->dateTransformation = $dateTransformation;
383
    }
384
385
    /**
386
     * Set the transformer function
387
     *
388
     * @param callback $fileTransformation
389
     */
390
    public function setFileTransformation($fileTransformation)
391
    {
392
        $this->fileTransformation = $fileTransformation;
393
    }
394
}
395