[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/inc/ -> remote.php (source)

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