Initial commit
This commit is contained in:
		
							
								
								
									
										410
									
								
								content/inc/Remote/Api.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										410
									
								
								content/inc/Remote/Api.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,410 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace dokuwiki\Remote;
 | 
			
		||||
 | 
			
		||||
use dokuwiki\Extension\Event;
 | 
			
		||||
use dokuwiki\Extension\RemotePlugin;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class provides information about remote access to the wiki.
 | 
			
		||||
 *
 | 
			
		||||
 * == Types of methods ==
 | 
			
		||||
 * There are two types of remote methods. The first is the core methods.
 | 
			
		||||
 * These are always available and provided by dokuwiki.
 | 
			
		||||
 * The other is plugin methods. These are provided by remote plugins.
 | 
			
		||||
 *
 | 
			
		||||
 * == Information structure ==
 | 
			
		||||
 * The information about methods will be given in an array with the following structure:
 | 
			
		||||
 * array(
 | 
			
		||||
 *     'method.remoteName' => 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.<plugin name>.<method name>'.
 | 
			
		||||
 * i.e.: plugin.clock.getTime or plugin.clock_gmt.getTime
 | 
			
		||||
 */
 | 
			
		||||
class Api
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var ApiCore
 | 
			
		||||
     */
 | 
			
		||||
    private $coreMethods = null;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var array remote methods provided by dokuwiki plugins - will be filled lazy via
 | 
			
		||||
     * {@see dokuwiki\Remote\RemoteAPI#getPluginMethods}
 | 
			
		||||
     */
 | 
			
		||||
    private $pluginMethods = null;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var array contains custom calls to the api. Plugins can use the XML_CALL_REGISTER event.
 | 
			
		||||
     * The data inside is 'custom.call.something' => array('plugin name', 'remote method name')
 | 
			
		||||
     *
 | 
			
		||||
     * The remote method name is the same as in the remote name returned by _getMethods().
 | 
			
		||||
     */
 | 
			
		||||
    private $pluginCustomCalls = null;
 | 
			
		||||
 | 
			
		||||
    private $dateTransformation;
 | 
			
		||||
    private $fileTransformation;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * constructor
 | 
			
		||||
     */
 | 
			
		||||
    public function __construct()
 | 
			
		||||
    {
 | 
			
		||||
        $this->dateTransformation = array($this, 'dummyTransformation');
 | 
			
		||||
        $this->fileTransformation = array($this, 'dummyTransformation');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get all available methods with remote access.
 | 
			
		||||
     *
 | 
			
		||||
     * @return array with information to all available methods
 | 
			
		||||
     * @throws RemoteException
 | 
			
		||||
     */
 | 
			
		||||
    public function getMethods()
 | 
			
		||||
    {
 | 
			
		||||
        return array_merge($this->getCoreMethods(), $this->getPluginMethods());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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 = array())
 | 
			
		||||
    {
 | 
			
		||||
        if ($args === null) {
 | 
			
		||||
            $args = array();
 | 
			
		||||
        }
 | 
			
		||||
        list($type, $pluginName, /* $call */) = explode('.', $method, 3);
 | 
			
		||||
        if ($type === 'plugin') {
 | 
			
		||||
            return $this->callPlugin($pluginName, $method, $args);
 | 
			
		||||
        }
 | 
			
		||||
        if ($this->coreMethodExist($method)) {
 | 
			
		||||
            return $this->callCoreMethod($method, $args);
 | 
			
		||||
        }
 | 
			
		||||
        return $this->callCustomCallPlugin($method, $args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check existance of core methods
 | 
			
		||||
     *
 | 
			
		||||
     * @param string $name name of the method
 | 
			
		||||
     * @return bool if method exists
 | 
			
		||||
     */
 | 
			
		||||
    private function coreMethodExist($name)
 | 
			
		||||
    {
 | 
			
		||||
        $coreMethods = $this->getCoreMethods();
 | 
			
		||||
        return array_key_exists($name, $coreMethods);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Try to call custom methods provided by plugins
 | 
			
		||||
     *
 | 
			
		||||
     * @param string $method name of method
 | 
			
		||||
     * @param array $args
 | 
			
		||||
     * @return mixed
 | 
			
		||||
     * @throws RemoteException if method not exists
 | 
			
		||||
     */
 | 
			
		||||
    private function callCustomCallPlugin($method, $args)
 | 
			
		||||
    {
 | 
			
		||||
        $customCalls = $this->getCustomCallPlugins();
 | 
			
		||||
        if (!array_key_exists($method, $customCalls)) {
 | 
			
		||||
            throw new RemoteException('Method does not exist', -32603);
 | 
			
		||||
        }
 | 
			
		||||
        $customCall = $customCalls[$method];
 | 
			
		||||
        return $this->callPlugin($customCall[0], $customCall[1], $args);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns plugin calls that are registered via RPC_CALL_ADD action
 | 
			
		||||
     *
 | 
			
		||||
     * @return array with pairs of custom plugin calls
 | 
			
		||||
     * @triggers RPC_CALL_ADD
 | 
			
		||||
     */
 | 
			
		||||
    private function getCustomCallPlugins()
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->pluginCustomCalls === null) {
 | 
			
		||||
            $data = array();
 | 
			
		||||
            Event::createAndTrigger('RPC_CALL_ADD', $data);
 | 
			
		||||
            $this->pluginCustomCalls = $data;
 | 
			
		||||
        }
 | 
			
		||||
        return $this->pluginCustomCalls;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Call a plugin method
 | 
			
		||||
     *
 | 
			
		||||
     * @param string $pluginName
 | 
			
		||||
     * @param string $method method name
 | 
			
		||||
     * @param array $args
 | 
			
		||||
     * @return mixed return of custom method
 | 
			
		||||
     * @throws RemoteException
 | 
			
		||||
     */
 | 
			
		||||
    private function callPlugin($pluginName, $method, $args)
 | 
			
		||||
    {
 | 
			
		||||
        $plugin = plugin_load('remote', $pluginName);
 | 
			
		||||
        $methods = $this->getPluginMethods();
 | 
			
		||||
        if (!$plugin) {
 | 
			
		||||
            throw new RemoteException('Method does not exist', -32603);
 | 
			
		||||
        }
 | 
			
		||||
        $this->checkAccess($methods[$method]);
 | 
			
		||||
        $name = $this->getMethodName($methods, $method);
 | 
			
		||||
        try {
 | 
			
		||||
            set_error_handler(array($this, "argumentWarningHandler"), E_WARNING); // for PHP <7.1
 | 
			
		||||
            return call_user_func_array(array($plugin, $name), $args);
 | 
			
		||||
        } catch (\ArgumentCountError $th) {
 | 
			
		||||
            throw new RemoteException('Method does not exist - wrong parameter count.', -32603);
 | 
			
		||||
        } finally {
 | 
			
		||||
            restore_error_handler();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Call a core method
 | 
			
		||||
     *
 | 
			
		||||
     * @param string $method name of method
 | 
			
		||||
     * @param array $args
 | 
			
		||||
     * @return mixed
 | 
			
		||||
     * @throws RemoteException if method not exist
 | 
			
		||||
     */
 | 
			
		||||
    private function callCoreMethod($method, $args)
 | 
			
		||||
    {
 | 
			
		||||
        $coreMethods = $this->getCoreMethods();
 | 
			
		||||
        $this->checkAccess($coreMethods[$method]);
 | 
			
		||||
        if (!isset($coreMethods[$method])) {
 | 
			
		||||
            throw new RemoteException('Method does not exist', -32603);
 | 
			
		||||
        }
 | 
			
		||||
        $this->checkArgumentLength($coreMethods[$method], $args);
 | 
			
		||||
        try {
 | 
			
		||||
            set_error_handler(array($this, "argumentWarningHandler"), E_WARNING); // for PHP <7.1
 | 
			
		||||
            return call_user_func_array(array($this->coreMethods, $this->getMethodName($coreMethods, $method)), $args);
 | 
			
		||||
        } catch (\ArgumentCountError $th) {
 | 
			
		||||
            throw new RemoteException('Method does not exist - wrong parameter count.', -32603);
 | 
			
		||||
        } finally {
 | 
			
		||||
            restore_error_handler();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if access should be checked
 | 
			
		||||
     *
 | 
			
		||||
     * @param array $methodMeta data about the method
 | 
			
		||||
     * @throws AccessDeniedException
 | 
			
		||||
     */
 | 
			
		||||
    private function checkAccess($methodMeta)
 | 
			
		||||
    {
 | 
			
		||||
        if (!isset($methodMeta['public'])) {
 | 
			
		||||
            $this->forceAccess();
 | 
			
		||||
        } else {
 | 
			
		||||
            if ($methodMeta['public'] == '0') {
 | 
			
		||||
                $this->forceAccess();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check the number of parameters
 | 
			
		||||
     *
 | 
			
		||||
     * @param array $methodMeta data about the method
 | 
			
		||||
     * @param array $args
 | 
			
		||||
     * @throws RemoteException if wrong parameter count
 | 
			
		||||
     */
 | 
			
		||||
    private function checkArgumentLength($methodMeta, $args)
 | 
			
		||||
    {
 | 
			
		||||
        if (count($methodMeta['args']) < count($args)) {
 | 
			
		||||
            throw new RemoteException('Method does not exist - wrong parameter count.', -32603);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Determine the name of the real method
 | 
			
		||||
     *
 | 
			
		||||
     * @param array $methodMeta list of data of the methods
 | 
			
		||||
     * @param string $method name of method
 | 
			
		||||
     * @return string
 | 
			
		||||
     */
 | 
			
		||||
    private function getMethodName($methodMeta, $method)
 | 
			
		||||
    {
 | 
			
		||||
        if (isset($methodMeta[$method]['name'])) {
 | 
			
		||||
            return $methodMeta[$method]['name'];
 | 
			
		||||
        }
 | 
			
		||||
        $method = explode('.', $method);
 | 
			
		||||
        return $method[count($method) - 1];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Perform access check for current user
 | 
			
		||||
     *
 | 
			
		||||
     * @return bool true if the current user has access to remote api.
 | 
			
		||||
     * @throws AccessDeniedException If remote access disabled
 | 
			
		||||
     */
 | 
			
		||||
    public function hasAccess()
 | 
			
		||||
    {
 | 
			
		||||
        global $conf;
 | 
			
		||||
        global $USERINFO;
 | 
			
		||||
        /** @var \dokuwiki\Input\Input $INPUT */
 | 
			
		||||
        global $INPUT;
 | 
			
		||||
 | 
			
		||||
        if (!$conf['remote']) {
 | 
			
		||||
            throw new AccessDeniedException('server error. RPC server not enabled.', -32604);
 | 
			
		||||
        }
 | 
			
		||||
        if (trim($conf['remoteuser']) == '!!not set!!') {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        if (!$conf['useacl']) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        if (trim($conf['remoteuser']) == '') {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return auth_isMember($conf['remoteuser'], $INPUT->server->str('REMOTE_USER'), (array) $USERINFO['grps']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Requests access
 | 
			
		||||
     *
 | 
			
		||||
     * @return void
 | 
			
		||||
     * @throws AccessDeniedException On denied access.
 | 
			
		||||
     */
 | 
			
		||||
    public function forceAccess()
 | 
			
		||||
    {
 | 
			
		||||
        if (!$this->hasAccess()) {
 | 
			
		||||
            throw new AccessDeniedException('server error. not authorized to call method', -32604);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Collects all the methods of the enabled Remote Plugins
 | 
			
		||||
     *
 | 
			
		||||
     * @return array all plugin methods.
 | 
			
		||||
     * @throws RemoteException if not implemented
 | 
			
		||||
     */
 | 
			
		||||
    public function getPluginMethods()
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->pluginMethods === null) {
 | 
			
		||||
            $this->pluginMethods = array();
 | 
			
		||||
            $plugins = plugin_list('remote');
 | 
			
		||||
 | 
			
		||||
            foreach ($plugins as $pluginName) {
 | 
			
		||||
                /** @var RemotePlugin $plugin */
 | 
			
		||||
                $plugin = plugin_load('remote', $pluginName);
 | 
			
		||||
                if (!is_subclass_of($plugin, 'dokuwiki\Extension\RemotePlugin')) {
 | 
			
		||||
                    throw new RemoteException(
 | 
			
		||||
                        "Plugin $pluginName does not implement dokuwiki\Plugin\DokuWiki_Remote_Plugin"
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                try {
 | 
			
		||||
                    $methods = $plugin->_getMethods();
 | 
			
		||||
                } catch (\ReflectionException $e) {
 | 
			
		||||
                    throw new RemoteException('Automatic aggregation of available remote methods failed', 0, $e);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                foreach ($methods as $method => $meta) {
 | 
			
		||||
                    $this->pluginMethods["plugin.$pluginName.$method"] = $meta;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return $this->pluginMethods;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Collects all the core methods
 | 
			
		||||
     *
 | 
			
		||||
     * @param ApiCore $apiCore this parameter is used for testing. Here you can pass a non-default RemoteAPICore
 | 
			
		||||
     *                         instance. (for mocking)
 | 
			
		||||
     * @return array all core methods.
 | 
			
		||||
     */
 | 
			
		||||
    public function getCoreMethods($apiCore = null)
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->coreMethods === null) {
 | 
			
		||||
            if ($apiCore === null) {
 | 
			
		||||
                $this->coreMethods = new ApiCore($this);
 | 
			
		||||
            } else {
 | 
			
		||||
                $this->coreMethods = $apiCore;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return $this->coreMethods->__getRemoteInfo();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The error handler that catches argument-related warnings
 | 
			
		||||
     */
 | 
			
		||||
    public function argumentWarningHandler($errno, $errstr)
 | 
			
		||||
    {
 | 
			
		||||
        if (substr($errstr, 0, 17) == 'Missing argument ') {
 | 
			
		||||
            throw new RemoteException('Method does not exist - wrong parameter count.', -32603);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user