Failed Conditions
Push — interwiki-remove-golucky ( 52fcdb...768be5 )
by Henry
12:48 queued 09:48
created

Api::toDate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
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;
0 ignored issues
show
Documentation Bug introduced by
It seems like $data of type * is incompatible with the declared type array of property $pluginCustomCalls.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

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