1*dd87735dSAndreas Gohr<?php 2*dd87735dSAndreas Gohr 3*dd87735dSAndreas Gohrnamespace dokuwiki\Remote; 4*dd87735dSAndreas Gohr 5*dd87735dSAndreas Gohruse DokuWiki_Remote_Plugin; 6*dd87735dSAndreas Gohruse Input; 7*dd87735dSAndreas Gohr 8*dd87735dSAndreas Gohr/** 9*dd87735dSAndreas Gohr * This class provides information about remote access to the wiki. 10*dd87735dSAndreas Gohr * 11*dd87735dSAndreas Gohr * == Types of methods == 12*dd87735dSAndreas Gohr * There are two types of remote methods. The first is the core methods. 13*dd87735dSAndreas Gohr * These are always available and provided by dokuwiki. 14*dd87735dSAndreas Gohr * The other is plugin methods. These are provided by remote plugins. 15*dd87735dSAndreas Gohr * 16*dd87735dSAndreas Gohr * == Information structure == 17*dd87735dSAndreas Gohr * The information about methods will be given in an array with the following structure: 18*dd87735dSAndreas Gohr * array( 19*dd87735dSAndreas Gohr * 'method.remoteName' => array( 20*dd87735dSAndreas Gohr * 'args' => array( 21*dd87735dSAndreas Gohr * 'type eg. string|int|...|date|file', 22*dd87735dSAndreas Gohr * ) 23*dd87735dSAndreas Gohr * 'name' => 'method name in class', 24*dd87735dSAndreas Gohr * 'return' => 'type', 25*dd87735dSAndreas Gohr * 'public' => 1/0 - method bypass default group check (used by login) 26*dd87735dSAndreas Gohr * ['doc' = 'method documentation'], 27*dd87735dSAndreas Gohr * ) 28*dd87735dSAndreas Gohr * ) 29*dd87735dSAndreas Gohr * 30*dd87735dSAndreas Gohr * plugin names are formed the following: 31*dd87735dSAndreas Gohr * core methods begin by a 'dokuwiki' or 'wiki' followed by a . and the method name itself. 32*dd87735dSAndreas Gohr * i.e.: dokuwiki.version or wiki.getPage 33*dd87735dSAndreas Gohr * 34*dd87735dSAndreas Gohr * plugin methods are formed like 'plugin.<plugin name>.<method name>'. 35*dd87735dSAndreas Gohr * i.e.: plugin.clock.getTime or plugin.clock_gmt.getTime 36*dd87735dSAndreas Gohr */ 37*dd87735dSAndreas Gohrclass Api 38*dd87735dSAndreas Gohr{ 39*dd87735dSAndreas Gohr 40*dd87735dSAndreas Gohr /** 41*dd87735dSAndreas Gohr * @var ApiCore 42*dd87735dSAndreas Gohr */ 43*dd87735dSAndreas Gohr private $coreMethods = null; 44*dd87735dSAndreas Gohr 45*dd87735dSAndreas Gohr /** 46*dd87735dSAndreas Gohr * @var array remote methods provided by dokuwiki plugins - will be filled lazy via 47*dd87735dSAndreas Gohr * {@see dokuwiki\Remote\RemoteAPI#getPluginMethods} 48*dd87735dSAndreas Gohr */ 49*dd87735dSAndreas Gohr private $pluginMethods = null; 50*dd87735dSAndreas Gohr 51*dd87735dSAndreas Gohr /** 52*dd87735dSAndreas Gohr * @var array contains custom calls to the api. Plugins can use the XML_CALL_REGISTER event. 53*dd87735dSAndreas Gohr * The data inside is 'custom.call.something' => array('plugin name', 'remote method name') 54*dd87735dSAndreas Gohr * 55*dd87735dSAndreas Gohr * The remote method name is the same as in the remote name returned by _getMethods(). 56*dd87735dSAndreas Gohr */ 57*dd87735dSAndreas Gohr private $pluginCustomCalls = null; 58*dd87735dSAndreas Gohr 59*dd87735dSAndreas Gohr private $dateTransformation; 60*dd87735dSAndreas Gohr private $fileTransformation; 61*dd87735dSAndreas Gohr 62*dd87735dSAndreas Gohr /** 63*dd87735dSAndreas Gohr * constructor 64*dd87735dSAndreas Gohr */ 65*dd87735dSAndreas Gohr public function __construct() 66*dd87735dSAndreas Gohr { 67*dd87735dSAndreas Gohr $this->dateTransformation = array($this, 'dummyTransformation'); 68*dd87735dSAndreas Gohr $this->fileTransformation = array($this, 'dummyTransformation'); 69*dd87735dSAndreas Gohr } 70*dd87735dSAndreas Gohr 71*dd87735dSAndreas Gohr /** 72*dd87735dSAndreas Gohr * Get all available methods with remote access. 73*dd87735dSAndreas Gohr * 74*dd87735dSAndreas Gohr * @return array with information to all available methods 75*dd87735dSAndreas Gohr * @throws RemoteException 76*dd87735dSAndreas Gohr */ 77*dd87735dSAndreas Gohr public function getMethods() 78*dd87735dSAndreas Gohr { 79*dd87735dSAndreas Gohr return array_merge($this->getCoreMethods(), $this->getPluginMethods()); 80*dd87735dSAndreas Gohr } 81*dd87735dSAndreas Gohr 82*dd87735dSAndreas Gohr /** 83*dd87735dSAndreas Gohr * Call a method via remote api. 84*dd87735dSAndreas Gohr * 85*dd87735dSAndreas Gohr * @param string $method name of the method to call. 86*dd87735dSAndreas Gohr * @param array $args arguments to pass to the given method 87*dd87735dSAndreas Gohr * @return mixed result of method call, must be a primitive type. 88*dd87735dSAndreas Gohr * @throws RemoteException 89*dd87735dSAndreas Gohr */ 90*dd87735dSAndreas Gohr public function call($method, $args = array()) 91*dd87735dSAndreas Gohr { 92*dd87735dSAndreas Gohr if ($args === null) { 93*dd87735dSAndreas Gohr $args = array(); 94*dd87735dSAndreas Gohr } 95*dd87735dSAndreas Gohr list($type, $pluginName, /* $call */) = explode('.', $method, 3); 96*dd87735dSAndreas Gohr if ($type === 'plugin') { 97*dd87735dSAndreas Gohr return $this->callPlugin($pluginName, $method, $args); 98*dd87735dSAndreas Gohr } 99*dd87735dSAndreas Gohr if ($this->coreMethodExist($method)) { 100*dd87735dSAndreas Gohr return $this->callCoreMethod($method, $args); 101*dd87735dSAndreas Gohr } 102*dd87735dSAndreas Gohr return $this->callCustomCallPlugin($method, $args); 103*dd87735dSAndreas Gohr } 104*dd87735dSAndreas Gohr 105*dd87735dSAndreas Gohr /** 106*dd87735dSAndreas Gohr * Check existance of core methods 107*dd87735dSAndreas Gohr * 108*dd87735dSAndreas Gohr * @param string $name name of the method 109*dd87735dSAndreas Gohr * @return bool if method exists 110*dd87735dSAndreas Gohr */ 111*dd87735dSAndreas Gohr private function coreMethodExist($name) 112*dd87735dSAndreas Gohr { 113*dd87735dSAndreas Gohr $coreMethods = $this->getCoreMethods(); 114*dd87735dSAndreas Gohr return array_key_exists($name, $coreMethods); 115*dd87735dSAndreas Gohr } 116*dd87735dSAndreas Gohr 117*dd87735dSAndreas Gohr /** 118*dd87735dSAndreas Gohr * Try to call custom methods provided by plugins 119*dd87735dSAndreas Gohr * 120*dd87735dSAndreas Gohr * @param string $method name of method 121*dd87735dSAndreas Gohr * @param array $args 122*dd87735dSAndreas Gohr * @return mixed 123*dd87735dSAndreas Gohr * @throws \dokuwiki\Remote\RemoteException if method not exists 124*dd87735dSAndreas Gohr */ 125*dd87735dSAndreas Gohr private function callCustomCallPlugin($method, $args) 126*dd87735dSAndreas Gohr { 127*dd87735dSAndreas Gohr $customCalls = $this->getCustomCallPlugins(); 128*dd87735dSAndreas Gohr if (!array_key_exists($method, $customCalls)) { 129*dd87735dSAndreas Gohr throw new \dokuwiki\Remote\RemoteException('Method does not exist', -32603); 130*dd87735dSAndreas Gohr } 131*dd87735dSAndreas Gohr $customCall = $customCalls[$method]; 132*dd87735dSAndreas Gohr return $this->callPlugin($customCall[0], $customCall[1], $args); 133*dd87735dSAndreas Gohr } 134*dd87735dSAndreas Gohr 135*dd87735dSAndreas Gohr /** 136*dd87735dSAndreas Gohr * Returns plugin calls that are registered via RPC_CALL_ADD action 137*dd87735dSAndreas Gohr * 138*dd87735dSAndreas Gohr * @return array with pairs of custom plugin calls 139*dd87735dSAndreas Gohr * @triggers RPC_CALL_ADD 140*dd87735dSAndreas Gohr */ 141*dd87735dSAndreas Gohr private function getCustomCallPlugins() 142*dd87735dSAndreas Gohr { 143*dd87735dSAndreas Gohr if ($this->pluginCustomCalls === null) { 144*dd87735dSAndreas Gohr $data = array(); 145*dd87735dSAndreas Gohr trigger_event('RPC_CALL_ADD', $data); 146*dd87735dSAndreas Gohr $this->pluginCustomCalls = $data; 147*dd87735dSAndreas Gohr } 148*dd87735dSAndreas Gohr return $this->pluginCustomCalls; 149*dd87735dSAndreas Gohr } 150*dd87735dSAndreas Gohr 151*dd87735dSAndreas Gohr /** 152*dd87735dSAndreas Gohr * Call a plugin method 153*dd87735dSAndreas Gohr * 154*dd87735dSAndreas Gohr * @param string $pluginName 155*dd87735dSAndreas Gohr * @param string $method method name 156*dd87735dSAndreas Gohr * @param array $args 157*dd87735dSAndreas Gohr * @return mixed return of custom method 158*dd87735dSAndreas Gohr * @throws \dokuwiki\Remote\RemoteException 159*dd87735dSAndreas Gohr */ 160*dd87735dSAndreas Gohr private function callPlugin($pluginName, $method, $args) 161*dd87735dSAndreas Gohr { 162*dd87735dSAndreas Gohr $plugin = plugin_load('remote', $pluginName); 163*dd87735dSAndreas Gohr $methods = $this->getPluginMethods(); 164*dd87735dSAndreas Gohr if (!$plugin) { 165*dd87735dSAndreas Gohr throw new \dokuwiki\Remote\RemoteException('Method does not exist', -32603); 166*dd87735dSAndreas Gohr } 167*dd87735dSAndreas Gohr $this->checkAccess($methods[$method]); 168*dd87735dSAndreas Gohr $name = $this->getMethodName($methods, $method); 169*dd87735dSAndreas Gohr return call_user_func_array(array($plugin, $name), $args); 170*dd87735dSAndreas Gohr } 171*dd87735dSAndreas Gohr 172*dd87735dSAndreas Gohr /** 173*dd87735dSAndreas Gohr * Call a core method 174*dd87735dSAndreas Gohr * 175*dd87735dSAndreas Gohr * @param string $method name of method 176*dd87735dSAndreas Gohr * @param array $args 177*dd87735dSAndreas Gohr * @return mixed 178*dd87735dSAndreas Gohr * @throws \dokuwiki\Remote\RemoteException if method not exist 179*dd87735dSAndreas Gohr */ 180*dd87735dSAndreas Gohr private function callCoreMethod($method, $args) 181*dd87735dSAndreas Gohr { 182*dd87735dSAndreas Gohr $coreMethods = $this->getCoreMethods(); 183*dd87735dSAndreas Gohr $this->checkAccess($coreMethods[$method]); 184*dd87735dSAndreas Gohr if (!isset($coreMethods[$method])) { 185*dd87735dSAndreas Gohr throw new RemoteException('Method does not exist', -32603); 186*dd87735dSAndreas Gohr } 187*dd87735dSAndreas Gohr $this->checkArgumentLength($coreMethods[$method], $args); 188*dd87735dSAndreas Gohr return call_user_func_array(array($this->coreMethods, $this->getMethodName($coreMethods, $method)), $args); 189*dd87735dSAndreas Gohr } 190*dd87735dSAndreas Gohr 191*dd87735dSAndreas Gohr /** 192*dd87735dSAndreas Gohr * Check if access should be checked 193*dd87735dSAndreas Gohr * 194*dd87735dSAndreas Gohr * @param array $methodMeta data about the method 195*dd87735dSAndreas Gohr * @throws AccessDeniedException 196*dd87735dSAndreas Gohr */ 197*dd87735dSAndreas Gohr private function checkAccess($methodMeta) 198*dd87735dSAndreas Gohr { 199*dd87735dSAndreas Gohr if (!isset($methodMeta['public'])) { 200*dd87735dSAndreas Gohr $this->forceAccess(); 201*dd87735dSAndreas Gohr } else { 202*dd87735dSAndreas Gohr if ($methodMeta['public'] == '0') { 203*dd87735dSAndreas Gohr $this->forceAccess(); 204*dd87735dSAndreas Gohr } 205*dd87735dSAndreas Gohr } 206*dd87735dSAndreas Gohr } 207*dd87735dSAndreas Gohr 208*dd87735dSAndreas Gohr /** 209*dd87735dSAndreas Gohr * Check the number of parameters 210*dd87735dSAndreas Gohr * 211*dd87735dSAndreas Gohr * @param array $methodMeta data about the method 212*dd87735dSAndreas Gohr * @param array $args 213*dd87735dSAndreas Gohr * @throws RemoteException if wrong parameter count 214*dd87735dSAndreas Gohr */ 215*dd87735dSAndreas Gohr private function checkArgumentLength($methodMeta, $args) 216*dd87735dSAndreas Gohr { 217*dd87735dSAndreas Gohr if (count($methodMeta['args']) < count($args)) { 218*dd87735dSAndreas Gohr throw new RemoteException('Method does not exist - wrong parameter count.', -32603); 219*dd87735dSAndreas Gohr } 220*dd87735dSAndreas Gohr } 221*dd87735dSAndreas Gohr 222*dd87735dSAndreas Gohr /** 223*dd87735dSAndreas Gohr * Determine the name of the real method 224*dd87735dSAndreas Gohr * 225*dd87735dSAndreas Gohr * @param array $methodMeta list of data of the methods 226*dd87735dSAndreas Gohr * @param string $method name of method 227*dd87735dSAndreas Gohr * @return string 228*dd87735dSAndreas Gohr */ 229*dd87735dSAndreas Gohr private function getMethodName($methodMeta, $method) 230*dd87735dSAndreas Gohr { 231*dd87735dSAndreas Gohr if (isset($methodMeta[$method]['name'])) { 232*dd87735dSAndreas Gohr return $methodMeta[$method]['name']; 233*dd87735dSAndreas Gohr } 234*dd87735dSAndreas Gohr $method = explode('.', $method); 235*dd87735dSAndreas Gohr return $method[count($method) - 1]; 236*dd87735dSAndreas Gohr } 237*dd87735dSAndreas Gohr 238*dd87735dSAndreas Gohr /** 239*dd87735dSAndreas Gohr * Perform access check for current user 240*dd87735dSAndreas Gohr * 241*dd87735dSAndreas Gohr * @return bool true if the current user has access to remote api. 242*dd87735dSAndreas Gohr * @throws AccessDeniedException If remote access disabled 243*dd87735dSAndreas Gohr */ 244*dd87735dSAndreas Gohr public function hasAccess() 245*dd87735dSAndreas Gohr { 246*dd87735dSAndreas Gohr global $conf; 247*dd87735dSAndreas Gohr global $USERINFO; 248*dd87735dSAndreas Gohr /** @var Input $INPUT */ 249*dd87735dSAndreas Gohr global $INPUT; 250*dd87735dSAndreas Gohr 251*dd87735dSAndreas Gohr if (!$conf['remote']) { 252*dd87735dSAndreas Gohr throw new AccessDeniedException('server error. RPC server not enabled.', -32604); 253*dd87735dSAndreas Gohr } 254*dd87735dSAndreas Gohr if (trim($conf['remoteuser']) == '!!not set!!') { 255*dd87735dSAndreas Gohr return false; 256*dd87735dSAndreas Gohr } 257*dd87735dSAndreas Gohr if (!$conf['useacl']) { 258*dd87735dSAndreas Gohr return true; 259*dd87735dSAndreas Gohr } 260*dd87735dSAndreas Gohr if (trim($conf['remoteuser']) == '') { 261*dd87735dSAndreas Gohr return true; 262*dd87735dSAndreas Gohr } 263*dd87735dSAndreas Gohr 264*dd87735dSAndreas Gohr return auth_isMember($conf['remoteuser'], $INPUT->server->str('REMOTE_USER'), (array) $USERINFO['grps']); 265*dd87735dSAndreas Gohr } 266*dd87735dSAndreas Gohr 267*dd87735dSAndreas Gohr /** 268*dd87735dSAndreas Gohr * Requests access 269*dd87735dSAndreas Gohr * 270*dd87735dSAndreas Gohr * @return void 271*dd87735dSAndreas Gohr * @throws AccessDeniedException On denied access. 272*dd87735dSAndreas Gohr */ 273*dd87735dSAndreas Gohr public function forceAccess() 274*dd87735dSAndreas Gohr { 275*dd87735dSAndreas Gohr if (!$this->hasAccess()) { 276*dd87735dSAndreas Gohr throw new AccessDeniedException('server error. not authorized to call method', -32604); 277*dd87735dSAndreas Gohr } 278*dd87735dSAndreas Gohr } 279*dd87735dSAndreas Gohr 280*dd87735dSAndreas Gohr /** 281*dd87735dSAndreas Gohr * Collects all the methods of the enabled Remote Plugins 282*dd87735dSAndreas Gohr * 283*dd87735dSAndreas Gohr * @return array all plugin methods. 284*dd87735dSAndreas Gohr * @throws RemoteException if not implemented 285*dd87735dSAndreas Gohr */ 286*dd87735dSAndreas Gohr public function getPluginMethods() 287*dd87735dSAndreas Gohr { 288*dd87735dSAndreas Gohr if ($this->pluginMethods === null) { 289*dd87735dSAndreas Gohr $this->pluginMethods = array(); 290*dd87735dSAndreas Gohr $plugins = plugin_list('remote'); 291*dd87735dSAndreas Gohr 292*dd87735dSAndreas Gohr foreach ($plugins as $pluginName) { 293*dd87735dSAndreas Gohr /** @var DokuWiki_Remote_Plugin $plugin */ 294*dd87735dSAndreas Gohr $plugin = plugin_load('remote', $pluginName); 295*dd87735dSAndreas Gohr if (!is_subclass_of($plugin, 'DokuWiki_Remote_Plugin')) { 296*dd87735dSAndreas Gohr throw new RemoteException("Plugin $pluginName does not implement DokuWiki_Remote_Plugin"); 297*dd87735dSAndreas Gohr } 298*dd87735dSAndreas Gohr 299*dd87735dSAndreas Gohr try { 300*dd87735dSAndreas Gohr $methods = $plugin->_getMethods(); 301*dd87735dSAndreas Gohr } catch (\ReflectionException $e) { 302*dd87735dSAndreas Gohr throw new RemoteException('Automatic aggregation of available remote methods failed', 0, $e); 303*dd87735dSAndreas Gohr } 304*dd87735dSAndreas Gohr 305*dd87735dSAndreas Gohr foreach ($methods as $method => $meta) { 306*dd87735dSAndreas Gohr $this->pluginMethods["plugin.$pluginName.$method"] = $meta; 307*dd87735dSAndreas Gohr } 308*dd87735dSAndreas Gohr } 309*dd87735dSAndreas Gohr } 310*dd87735dSAndreas Gohr return $this->pluginMethods; 311*dd87735dSAndreas Gohr } 312*dd87735dSAndreas Gohr 313*dd87735dSAndreas Gohr /** 314*dd87735dSAndreas Gohr * Collects all the core methods 315*dd87735dSAndreas Gohr * 316*dd87735dSAndreas Gohr * @param ApiCore $apiCore this parameter is used for testing. Here you can pass a non-default RemoteAPICore 317*dd87735dSAndreas Gohr * instance. (for mocking) 318*dd87735dSAndreas Gohr * @return array all core methods. 319*dd87735dSAndreas Gohr */ 320*dd87735dSAndreas Gohr public function getCoreMethods($apiCore = null) 321*dd87735dSAndreas Gohr { 322*dd87735dSAndreas Gohr if ($this->coreMethods === null) { 323*dd87735dSAndreas Gohr if ($apiCore === null) { 324*dd87735dSAndreas Gohr $this->coreMethods = new ApiCore($this); 325*dd87735dSAndreas Gohr } else { 326*dd87735dSAndreas Gohr $this->coreMethods = $apiCore; 327*dd87735dSAndreas Gohr } 328*dd87735dSAndreas Gohr } 329*dd87735dSAndreas Gohr return $this->coreMethods->__getRemoteInfo(); 330*dd87735dSAndreas Gohr } 331*dd87735dSAndreas Gohr 332*dd87735dSAndreas Gohr /** 333*dd87735dSAndreas Gohr * Transform file to xml 334*dd87735dSAndreas Gohr * 335*dd87735dSAndreas Gohr * @param mixed $data 336*dd87735dSAndreas Gohr * @return mixed 337*dd87735dSAndreas Gohr */ 338*dd87735dSAndreas Gohr public function toFile($data) 339*dd87735dSAndreas Gohr { 340*dd87735dSAndreas Gohr return call_user_func($this->fileTransformation, $data); 341*dd87735dSAndreas Gohr } 342*dd87735dSAndreas Gohr 343*dd87735dSAndreas Gohr /** 344*dd87735dSAndreas Gohr * Transform date to xml 345*dd87735dSAndreas Gohr * 346*dd87735dSAndreas Gohr * @param mixed $data 347*dd87735dSAndreas Gohr * @return mixed 348*dd87735dSAndreas Gohr */ 349*dd87735dSAndreas Gohr public function toDate($data) 350*dd87735dSAndreas Gohr { 351*dd87735dSAndreas Gohr return call_user_func($this->dateTransformation, $data); 352*dd87735dSAndreas Gohr } 353*dd87735dSAndreas Gohr 354*dd87735dSAndreas Gohr /** 355*dd87735dSAndreas Gohr * A simple transformation 356*dd87735dSAndreas Gohr * 357*dd87735dSAndreas Gohr * @param mixed $data 358*dd87735dSAndreas Gohr * @return mixed 359*dd87735dSAndreas Gohr */ 360*dd87735dSAndreas Gohr public function dummyTransformation($data) 361*dd87735dSAndreas Gohr { 362*dd87735dSAndreas Gohr return $data; 363*dd87735dSAndreas Gohr } 364*dd87735dSAndreas Gohr 365*dd87735dSAndreas Gohr /** 366*dd87735dSAndreas Gohr * Set the transformer function 367*dd87735dSAndreas Gohr * 368*dd87735dSAndreas Gohr * @param callback $dateTransformation 369*dd87735dSAndreas Gohr */ 370*dd87735dSAndreas Gohr public function setDateTransformation($dateTransformation) 371*dd87735dSAndreas Gohr { 372*dd87735dSAndreas Gohr $this->dateTransformation = $dateTransformation; 373*dd87735dSAndreas Gohr } 374*dd87735dSAndreas Gohr 375*dd87735dSAndreas Gohr /** 376*dd87735dSAndreas Gohr * Set the transformer function 377*dd87735dSAndreas Gohr * 378*dd87735dSAndreas Gohr * @param callback $fileTransformation 379*dd87735dSAndreas Gohr */ 380*dd87735dSAndreas Gohr public function setFileTransformation($fileTransformation) 381*dd87735dSAndreas Gohr { 382*dd87735dSAndreas Gohr $this->fileTransformation = $fileTransformation; 383*dd87735dSAndreas Gohr } 384*dd87735dSAndreas Gohr} 385