Failed Conditions
Push — psr2-config ( c6639e )
by Andreas
06:39 queued 03:33
created

Api   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 348
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 6

Importance

Changes 0
Metric Value
dl 0
loc 348
rs 8.3157
c 0
b 0
f 0
wmc 43
lcom 3
cbo 6

20 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A getMethods() 0 4 1
A call() 0 14 4
A coreMethodExist() 0 5 1
A callCustomCallPlugin() 0 9 2
A getCustomCallPlugins() 0 9 2
A callPlugin() 0 11 2
A callCoreMethod() 0 10 2
A checkAccess() 0 10 3
A checkArgumentLength() 0 6 2
A getMethodName() 0 8 2
B hasAccess() 0 22 5
A forceAccess() 0 6 2
B getPluginMethods() 0 26 6
A getCoreMethods() 0 11 3
A toFile() 0 4 1
A toDate() 0 4 1
A dummyTransformation() 0 4 1
A setDateTransformation() 0 4 1
A setFileTransformation() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Api often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Api, and based on these observations, apply Extract Interface, too.

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