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