array( * 'args' => array( * 'type eg. string|int|...|date|file', * ) * 'name' => 'method name in class', * 'return' => 'type', * 'public' => 1/0 - method bypass default group check (used by login) * ['doc' = 'method documentation'], * ) * ) * * plugin names are formed the following: * core methods begin by a 'dokuwiki' or 'wiki' followed by a . and the method name itself. * i.e.: dokuwiki.version or wiki.getPage * * plugin methods are formed like 'plugin..'. * i.e.: plugin.clock.getTime or plugin.clock_gmt.getTime */ class Api { /** @var ApiCall[] core methods provided by dokuwiki */ protected $coreMethods; /** @var ApiCall[] remote methods provided by dokuwiki plugins */ protected $pluginMethods; private $dateTransformation; private $fileTransformation; /** * constructor */ public function __construct() { $this->dateTransformation = [$this, 'dummyTransformation']; $this->fileTransformation = [$this, 'dummyTransformation']; } /** * Get all available methods with remote access. * * @return ApiCall[] with information to all available methods * @throws RemoteException */ public function getMethods() { return array_merge($this->getCoreMethods(), $this->getPluginMethods()); } /** * Collects all the core methods * * @param ApiCore|\RemoteAPICoreTest $apiCore this parameter is used for testing. * Here you can pass a non-default RemoteAPICore instance. (for mocking) * @return ApiCall[] all core methods. */ public function getCoreMethods($apiCore = null) { if (!$this->coreMethods) { if ($apiCore === null) { $this->coreMethods = (new ApiCore($this))->getRemoteInfo(); } else { $this->coreMethods = $apiCore->getRemoteInfo(); } } return $this->coreMethods; } /** * Collects all the methods of the enabled Remote Plugins * * @return ApiCall[] all plugin methods. */ public function getPluginMethods() { if ($this->pluginMethods) return $this->pluginMethods; $plugins = plugin_list('remote'); foreach ($plugins as $pluginName) { /** @var RemotePlugin $plugin */ $plugin = plugin_load('remote', $pluginName); if (!is_subclass_of($plugin, RemotePlugin::class)) { Logger::error("Remote Plugin $pluginName does not implement dokuwiki\Extension\RemotePlugin"); continue; } try { $methods = $plugin->getMethods(); } catch (\ReflectionException $e) { Logger::error( "Remote Plugin $pluginName failed to return methods", $e->getMessage(), $e->getFile(), $e->getLine() ); continue; } foreach ($methods as $method => $call) { $this->pluginMethods["plugin.$pluginName.$method"] = $call; } } return $this->pluginMethods; } /** * Call a method via remote api. * * @param string $method name of the method to call. * @param array $args arguments to pass to the given method * @return mixed result of method call, must be a primitive type. * @throws RemoteException */ public function call($method, $args = []) { if ($args === null) { $args = []; } // pre-flight checks $this->ensureApiIsEnabled(); $methods = $this->getMethods(); if (!isset($methods[$method])) { throw new RemoteException('Method does not exist', -32603); } $this->ensureAccessIsAllowed($methods[$method]); // invoke the ApiCall try { return $methods[$method]($args); } catch (\ArgumentCountError $th) { throw new RemoteException('Method does not exist - wrong parameter count.', -32603); } } /** * Check that the API is generally enabled * * @return void * @throws RemoteException thrown when the API is disabled */ public function ensureApiIsEnabled() { global $conf; if (!$conf['remote'] || trim($conf['remoteuser']) == '!!not set!!') { throw new AccessDeniedException('Server Error. API is not enabled in config.', -32604); } } /** * Check if the current user is allowed to call the given method * * @param ApiCall $method * @return void * @throws AccessDeniedException Thrown when the user is not allowed to call the method */ public function ensureAccessIsAllowed(ApiCall $method) { global $conf; global $INPUT; global $USERINFO; if ($method->isPublic()) return; // public methods are always allowed if (!$conf['useacl']) return; // ACL is not enabled, so we can't check users if (trim($conf['remoteuser']) === '') return; // all users are allowed if (auth_isMember($conf['remoteuser'], $INPUT->server->str('REMOTE_USER'), (array)($USERINFO['grps'] ?? []))) { return; // user is allowed } // still here? no can do throw new AccessDeniedException('server error. not authorized to call method', -32604); } /** * Transform file to xml * * @param mixed $data * @return mixed */ public function toFile($data) { return call_user_func($this->fileTransformation, $data); } /** * Transform date to xml * * @param mixed $data * @return mixed */ public function toDate($data) { return call_user_func($this->dateTransformation, $data); } /** * A simple transformation * * @param mixed $data * @return mixed */ public function dummyTransformation($data) { return $data; } /** * Set the transformer function * * @param callback $dateTransformation */ public function setDateTransformation($dateTransformation) { $this->dateTransformation = $dateTransformation; } /** * Set the transformer function * * @param callback $fileTransformation */ public function setFileTransformation($fileTransformation) { $this->fileTransformation = $fileTransformation; } }