Failed Conditions
Push — remote-argumentcount ( 63b4c8...6d7829 )
by Henry
09:36 queued 06:41
created

inc/Remote/Api.php (1 issue)

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