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