[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/inc/Remote/ -> Api.php (source)

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