[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

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

   1  <?php
   2  
   3  namespace dokuwiki\Remote;
   4  
   5  use Doku_Renderer_xhtml;
   6  use dokuwiki\ChangeLog\MediaChangeLog;
   7  use dokuwiki\ChangeLog\PageChangeLog;
   8  use dokuwiki\Extension\Event;
   9  use dokuwiki\Utf8\Sort;
  10  
  11  define('DOKU_API_VERSION', 11);
  12  
  13  /**
  14   * Provides the core methods for the remote API.
  15   * The methods are ordered in 'wiki.<method>' and 'dokuwiki.<method>' namespaces
  16   */
  17  class ApiCore
  18  {
  19      /** @var int Increased whenever the API is changed */
  20      const API_VERSION = 11;
  21  
  22  
  23      /** @var Api */
  24      private $api;
  25  
  26      /**
  27       * @param Api $api
  28       */
  29      public function __construct(Api $api)
  30      {
  31          $this->api = $api;
  32      }
  33  
  34      /**
  35       * Returns details about the core methods
  36       *
  37       * @return array
  38       */
  39      public function getRemoteInfo()
  40      {
  41          return array(
  42              'dokuwiki.getVersion' => array(
  43                  'args' => array(),
  44                  'return' => 'string',
  45                  'doc' => 'Returns the running DokuWiki version.'
  46              ), 'dokuwiki.login' => array(
  47                  'args' => array('string', 'string'),
  48                  'return' => 'int',
  49                  'doc' => 'Tries to login with the given credentials and sets auth cookies.',
  50                  'public' => '1'
  51              ), 'dokuwiki.logoff' => array(
  52                  'args' => array(),
  53                  'return' => 'int',
  54                  'doc' => 'Tries to logoff by expiring auth cookies and the associated PHP session.'
  55              ), 'dokuwiki.getPagelist' => array(
  56                  'args' => array('string', 'array'),
  57                  'return' => 'array',
  58                  'doc' => 'List all pages within the given namespace.',
  59                  'name' => 'readNamespace'
  60              ), 'dokuwiki.search' => array(
  61                  'args' => array('string'),
  62                  'return' => 'array',
  63                  'doc' => 'Perform a fulltext search and return a list of matching pages'
  64              ), 'dokuwiki.getTime' => array(
  65                  'args' => array(),
  66                  'return' => 'int',
  67                  'doc' => 'Returns the current time at the remote wiki server as Unix timestamp.',
  68              ), 'dokuwiki.setLocks' => array(
  69                  'args' => array('array'),
  70                  'return' => 'array',
  71                  'doc' => 'Lock or unlock pages.'
  72              ), 'dokuwiki.getTitle' => array(
  73                  'args' => array(),
  74                  'return' => 'string',
  75                  'doc' => 'Returns the wiki title.',
  76                  'public' => '1'
  77              ), 'dokuwiki.appendPage' => array(
  78                  'args' => array('string', 'string', 'array'),
  79                  'return' => 'bool',
  80                  'doc' => 'Append text to a wiki page.'
  81              ), 'dokuwiki.createUser' => array(
  82                  'args' => array('struct'),
  83                  'return' => 'bool',
  84                  'doc' => 'Create a user. The result is boolean'
  85              ),'dokuwiki.deleteUsers' => array(
  86                  'args' => array('array'),
  87                  'return' => 'bool',
  88                  'doc' => 'Remove one or more users from the list of registered users.'
  89              ),  'wiki.getPage' => array(
  90                  'args' => array('string'),
  91                  'return' => 'string',
  92                  'doc' => 'Get the raw Wiki text of page, latest version.',
  93                  'name' => 'rawPage',
  94              ), 'wiki.getPageVersion' => array(
  95                  'args' => array('string', 'int'),
  96                  'name' => 'rawPage',
  97                  'return' => 'string',
  98                  'doc' => 'Return a raw wiki page'
  99              ), 'wiki.getPageHTML' => array(
 100                  'args' => array('string'),
 101                  'return' => 'string',
 102                  'doc' => 'Return page in rendered HTML, latest version.',
 103                  'name' => 'htmlPage'
 104              ), 'wiki.getPageHTMLVersion' => array(
 105                  'args' => array('string', 'int'),
 106                  'return' => 'string',
 107                  'doc' => 'Return page in rendered HTML.',
 108                  'name' => 'htmlPage'
 109              ), 'wiki.getAllPages' => array(
 110                  'args' => array(),
 111                  'return' => 'array',
 112                  'doc' => 'Returns a list of all pages. The result is an array of utf8 pagenames.',
 113                  'name' => 'listPages'
 114              ), 'wiki.getAttachments' => array(
 115                  'args' => array('string', 'array'),
 116                  'return' => 'array',
 117                  'doc' => 'Returns a list of all media files.',
 118                  'name' => 'listAttachments'
 119              ), 'wiki.getBackLinks' => array(
 120                  'args' => array('string'),
 121                  'return' => 'array',
 122                  'doc' => 'Returns the pages that link to this page.',
 123                  'name' => 'listBackLinks'
 124              ), 'wiki.getPageInfo' => array(
 125                  'args' => array('string'),
 126                  'return' => 'array',
 127                  'doc' => 'Returns a struct with info about the page, latest version.',
 128                  'name' => 'pageInfo'
 129              ), 'wiki.getPageInfoVersion' => array(
 130                  'args' => array('string', 'int'),
 131                  'return' => 'array',
 132                  'doc' => 'Returns a struct with info about the page.',
 133                  'name' => 'pageInfo'
 134              ), 'wiki.getPageVersions' => array(
 135                  'args' => array('string', 'int'),
 136                  'return' => 'array',
 137                  'doc' => 'Returns the available revisions of the page.',
 138                  'name' => 'pageVersions'
 139              ), 'wiki.putPage' => array(
 140                  'args' => array('string', 'string', 'array'),
 141                  'return' => 'bool',
 142                  'doc' => 'Saves a wiki page.'
 143              ), 'wiki.listLinks' => array(
 144                  'args' => array('string'),
 145                  'return' => 'array',
 146                  'doc' => 'Lists all links contained in a wiki page.'
 147              ), 'wiki.getRecentChanges' => array(
 148                  'args' => array('int'),
 149                  'return' => 'array',
 150                  'doc' => 'Returns a struct about all recent changes since given timestamp.'
 151              ), 'wiki.getRecentMediaChanges' => array(
 152                  'args' => array('int'),
 153                  'return' => 'array',
 154                  'doc' => 'Returns a struct about all recent media changes since given timestamp.'
 155              ), 'wiki.aclCheck' => array(
 156                  'args' => array('string', 'string', 'array'),
 157                  'return' => 'int',
 158                  'doc' => 'Returns the permissions of a given wiki page. By default, for current user/groups'
 159              ), 'wiki.putAttachment' => array(
 160                  'args' => array('string', 'file', 'array'),
 161                  'return' => 'array',
 162                  'doc' => 'Upload a file to the wiki.'
 163              ), 'wiki.deleteAttachment' => array(
 164                  'args' => array('string'),
 165                  'return' => 'int',
 166                  'doc' => 'Delete a file from the wiki.'
 167              ), 'wiki.getAttachment' => array(
 168                  'args' => array('string'),
 169                  'doc' => 'Return a media file',
 170                  'return' => 'file',
 171                  'name' => 'getAttachment',
 172              ), 'wiki.getAttachmentInfo' => array(
 173                  'args' => array('string'),
 174                  'return' => 'array',
 175                  'doc' => 'Returns a struct with info about the attachment.'
 176              ), 'dokuwiki.getXMLRPCAPIVersion' => array(
 177                  'args' => array(),
 178                  'name' => 'getAPIVersion',
 179                  'return' => 'int',
 180                  'doc' => 'Returns the XMLRPC API version.',
 181                  'public' => '1',
 182              ), 'wiki.getRPCVersionSupported' => array(
 183                  'args' => array(),
 184                  'name' => 'wikiRpcVersion',
 185                  'return' => 'int',
 186                  'doc' => 'Returns 2 with the supported RPC API version.',
 187                  'public' => '1'
 188              ),
 189  
 190          );
 191      }
 192  
 193      /**
 194       * @return string
 195       */
 196      public function getVersion()
 197      {
 198          return getVersion();
 199      }
 200  
 201      /**
 202       * @return int unix timestamp
 203       */
 204      public function getTime()
 205      {
 206          return time();
 207      }
 208  
 209      /**
 210       * Return a raw wiki page
 211       *
 212       * @param string $id wiki page id
 213       * @param int|string $rev revision timestamp of the page or empty string
 214       * @return string page text.
 215       * @throws AccessDeniedException if no permission for page
 216       */
 217      public function rawPage($id, $rev = '')
 218      {
 219          $id = $this->resolvePageId($id);
 220          if (auth_quickaclcheck($id) < AUTH_READ) {
 221              throw new AccessDeniedException('You are not allowed to read this file', 111);
 222          }
 223          $text = rawWiki($id, $rev);
 224          if (!$text) {
 225              return pageTemplate($id);
 226          } else {
 227              return $text;
 228          }
 229      }
 230  
 231      /**
 232       * Return a media file
 233       *
 234       * @author Gina Haeussge <osd@foosel.net>
 235       *
 236       * @param string $id file id
 237       * @return mixed media file
 238       * @throws AccessDeniedException no permission for media
 239       * @throws RemoteException not exist
 240       */
 241      public function getAttachment($id)
 242      {
 243          $id = cleanID($id);
 244          if (auth_quickaclcheck(getNS($id) . ':*') < AUTH_READ) {
 245              throw new AccessDeniedException('You are not allowed to read this file', 211);
 246          }
 247  
 248          $file = mediaFN($id);
 249          if (!@ file_exists($file)) {
 250              throw new RemoteException('The requested file does not exist', 221);
 251          }
 252  
 253          $data = io_readFile($file, false);
 254          return $this->api->toFile($data);
 255      }
 256  
 257      /**
 258       * Return info about a media file
 259       *
 260       * @author Gina Haeussge <osd@foosel.net>
 261       *
 262       * @param string $id page id
 263       * @return array
 264       */
 265      public function getAttachmentInfo($id)
 266      {
 267          $id = cleanID($id);
 268          $info = array(
 269              'lastModified' => $this->api->toDate(0),
 270              'size' => 0,
 271          );
 272  
 273          $file = mediaFN($id);
 274          if (auth_quickaclcheck(getNS($id) . ':*') >= AUTH_READ) {
 275              if (file_exists($file)) {
 276                  $info['lastModified'] = $this->api->toDate(filemtime($file));
 277                  $info['size'] = filesize($file);
 278              } else {
 279                  //Is it deleted media with changelog?
 280                  $medialog = new MediaChangeLog($id);
 281                  $revisions = $medialog->getRevisions(0, 1);
 282                  if (!empty($revisions)) {
 283                      $info['lastModified'] = $this->api->toDate($revisions[0]);
 284                  }
 285              }
 286          }
 287  
 288          return $info;
 289      }
 290  
 291      /**
 292       * Return a wiki page rendered to html
 293       *
 294       * @param string $id page id
 295       * @param string|int $rev revision timestamp or empty string
 296       * @return null|string html
 297       * @throws AccessDeniedException no access to page
 298       */
 299      public function htmlPage($id, $rev = '')
 300      {
 301          $id = $this->resolvePageId($id);
 302          if (auth_quickaclcheck($id) < AUTH_READ) {
 303              throw new AccessDeniedException('You are not allowed to read this page', 111);
 304          }
 305          return p_wiki_xhtml($id, $rev, false);
 306      }
 307  
 308      /**
 309       * List all pages - we use the indexer list here
 310       *
 311       * @return array
 312       */
 313      public function listPages()
 314      {
 315          $list = array();
 316          $pages = idx_get_indexer()->getPages();
 317          $pages = array_filter(array_filter($pages, 'isVisiblePage'), 'page_exists');
 318          Sort::ksort($pages);
 319  
 320          foreach (array_keys($pages) as $idx) {
 321              $perm = auth_quickaclcheck($pages[$idx]);
 322              if ($perm < AUTH_READ) {
 323                  continue;
 324              }
 325              $page = array();
 326              $page['id'] = trim($pages[$idx]);
 327              $page['perms'] = $perm;
 328              $page['size'] = @filesize(wikiFN($pages[$idx]));
 329              $page['lastModified'] = $this->api->toDate(@filemtime(wikiFN($pages[$idx])));
 330              $list[] = $page;
 331          }
 332  
 333          return $list;
 334      }
 335  
 336      /**
 337       * List all pages in the given namespace (and below)
 338       *
 339       * @param string $ns
 340       * @param array $opts
 341       *    $opts['depth']   recursion level, 0 for all
 342       *    $opts['hash']    do md5 sum of content?
 343       * @return array
 344       */
 345      public function readNamespace($ns, $opts = array())
 346      {
 347          global $conf;
 348  
 349          if (!is_array($opts)) $opts = array();
 350  
 351          $ns = cleanID($ns);
 352          $dir = utf8_encodeFN(str_replace(':', '/', $ns));
 353          $data = array();
 354          $opts['skipacl'] = 0; // no ACL skipping for XMLRPC
 355          search($data, $conf['datadir'], 'search_allpages', $opts, $dir);
 356          return $data;
 357      }
 358  
 359      /**
 360       * List all pages in the given namespace (and below)
 361       *
 362       * @param string $query
 363       * @return array
 364       */
 365      public function search($query)
 366      {
 367          $regex = array();
 368          $data = ft_pageSearch($query, $regex);
 369          $pages = array();
 370  
 371          // prepare additional data
 372          $idx = 0;
 373          foreach ($data as $id => $score) {
 374              $file = wikiFN($id);
 375  
 376              if ($idx < FT_SNIPPET_NUMBER) {
 377                  $snippet = ft_snippet($id, $regex);
 378                  $idx++;
 379              } else {
 380                  $snippet = '';
 381              }
 382  
 383              $pages[] = array(
 384                  'id' => $id,
 385                  'score' => intval($score),
 386                  'rev' => filemtime($file),
 387                  'mtime' => filemtime($file),
 388                  'size' => filesize($file),
 389                  'snippet' => $snippet,
 390                  'title' => useHeading('navigation') ? p_get_first_heading($id) : $id
 391              );
 392          }
 393          return $pages;
 394      }
 395  
 396      /**
 397       * Returns the wiki title.
 398       *
 399       * @return string
 400       */
 401      public function getTitle()
 402      {
 403          global $conf;
 404          return $conf['title'];
 405      }
 406  
 407      /**
 408       * List all media files.
 409       *
 410       * Available options are 'recursive' for also including the subnamespaces
 411       * in the listing, and 'pattern' for filtering the returned files against
 412       * a regular expression matching their name.
 413       *
 414       * @author Gina Haeussge <osd@foosel.net>
 415       *
 416       * @param string $ns
 417       * @param array $options
 418       *   $options['depth']     recursion level, 0 for all
 419       *   $options['showmsg']   shows message if invalid media id is used
 420       *   $options['pattern']   check given pattern
 421       *   $options['hash']      add hashes to result list
 422       * @return array
 423       * @throws AccessDeniedException no access to the media files
 424       */
 425      public function listAttachments($ns, $options = array())
 426      {
 427          global $conf;
 428  
 429          $ns = cleanID($ns);
 430  
 431          if (!is_array($options)) $options = array();
 432          $options['skipacl'] = 0; // no ACL skipping for XMLRPC
 433  
 434          if (auth_quickaclcheck($ns . ':*') >= AUTH_READ) {
 435              $dir = utf8_encodeFN(str_replace(':', '/', $ns));
 436  
 437              $data = array();
 438              search($data, $conf['mediadir'], 'search_media', $options, $dir);
 439              $len = count($data);
 440              if (!$len) return array();
 441  
 442              for ($i = 0; $i < $len; $i++) {
 443                  unset($data[$i]['meta']);
 444                  $data[$i]['perms'] = $data[$i]['perm'];
 445                  unset($data[$i]['perm']);
 446                  $data[$i]['lastModified'] = $this->api->toDate($data[$i]['mtime']);
 447              }
 448              return $data;
 449          } else {
 450              throw new AccessDeniedException('You are not allowed to list media files.', 215);
 451          }
 452      }
 453  
 454      /**
 455       * Return a list of backlinks
 456       *
 457       * @param string $id page id
 458       * @return array
 459       */
 460      public function listBackLinks($id)
 461      {
 462          return ft_backlinks($this->resolvePageId($id));
 463      }
 464  
 465      /**
 466       * Return some basic data about a page
 467       *
 468       * @param string $id page id
 469       * @param string|int $rev revision timestamp or empty string
 470       * @return array
 471       * @throws AccessDeniedException no access for page
 472       * @throws RemoteException page not exist
 473       */
 474      public function pageInfo($id, $rev = '')
 475      {
 476          $id = $this->resolvePageId($id);
 477          if (auth_quickaclcheck($id) < AUTH_READ) {
 478              throw new AccessDeniedException('You are not allowed to read this page', 111);
 479          }
 480          $file = wikiFN($id, $rev);
 481          $time = @filemtime($file);
 482          if (!$time) {
 483              throw new RemoteException('The requested page does not exist', 121);
 484          }
 485  
 486          // set revision to current version if empty, use revision otherwise
 487          // as the timestamps of old files are not necessarily correct
 488          if ($rev === '') {
 489              $rev = $time;
 490          }
 491  
 492          $pagelog = new PageChangeLog($id, 1024);
 493          $info = $pagelog->getRevisionInfo($rev);
 494  
 495          $data = array(
 496              'name' => $id,
 497              'lastModified' => $this->api->toDate($rev),
 498              'author' => is_array($info) ? (($info['user']) ? $info['user'] : $info['ip']) : null,
 499              'version' => $rev
 500          );
 501  
 502          return ($data);
 503      }
 504  
 505      /**
 506       * Save a wiki page
 507       *
 508       * @author Michael Klier <chi@chimeric.de>
 509       *
 510       * @param string $id page id
 511       * @param string $text wiki text
 512       * @param array $params parameters: summary, minor edit
 513       * @return bool
 514       * @throws AccessDeniedException no write access for page
 515       * @throws RemoteException no id, empty new page or locked
 516       */
 517      public function putPage($id, $text, $params = array())
 518      {
 519          global $TEXT;
 520          global $lang;
 521  
 522          $id = $this->resolvePageId($id);
 523          $TEXT = cleanText($text);
 524          $sum = $params['sum'];
 525          $minor = $params['minor'];
 526  
 527          if (empty($id)) {
 528              throw new RemoteException('Empty page ID', 131);
 529          }
 530  
 531          if (!page_exists($id) && trim($TEXT) == '') {
 532              throw new RemoteException('Refusing to write an empty new wiki page', 132);
 533          }
 534  
 535          if (auth_quickaclcheck($id) < AUTH_EDIT) {
 536              throw new AccessDeniedException('You are not allowed to edit this page', 112);
 537          }
 538  
 539          // Check, if page is locked
 540          if (checklock($id)) {
 541              throw new RemoteException('The page is currently locked', 133);
 542          }
 543  
 544          // SPAM check
 545          if (checkwordblock()) {
 546              throw new RemoteException('Positive wordblock check', 134);
 547          }
 548  
 549          // autoset summary on new pages
 550          if (!page_exists($id) && empty($sum)) {
 551              $sum = $lang['created'];
 552          }
 553  
 554          // autoset summary on deleted pages
 555          if (page_exists($id) && empty($TEXT) && empty($sum)) {
 556              $sum = $lang['deleted'];
 557          }
 558  
 559          lock($id);
 560  
 561          saveWikiText($id, $TEXT, $sum, $minor);
 562  
 563          unlock($id);
 564  
 565          // run the indexer if page wasn't indexed yet
 566          idx_addPage($id);
 567  
 568          return true;
 569      }
 570  
 571      /**
 572       * Appends text to a wiki page.
 573       *
 574       * @param string $id page id
 575       * @param string $text wiki text
 576       * @param array $params such as summary,minor
 577       * @return bool|string
 578       * @throws RemoteException
 579       */
 580      public function appendPage($id, $text, $params = array())
 581      {
 582          $currentpage = $this->rawPage($id);
 583          if (!is_string($currentpage)) {
 584              return $currentpage;
 585          }
 586          return $this->putPage($id, $currentpage . $text, $params);
 587      }
 588  
 589      /**
 590       * Create one or more users
 591       *
 592       * @param array[] $userStruct User struct
 593       *
 594       * @return boolean Create state
 595       *
 596       * @throws AccessDeniedException
 597       * @throws RemoteException
 598       */
 599      public function createUser($userStruct)
 600      {
 601          if (!auth_isadmin()) {
 602              throw new AccessDeniedException('Only admins are allowed to create users', 114);
 603          }
 604  
 605          /** @var \dokuwiki\Extension\AuthPlugin $auth */
 606          global $auth;
 607  
 608          if(!$auth->canDo('addUser')) {
 609              throw new AccessDeniedException(
 610                  sprintf('Authentication backend %s can\'t do addUser', $auth->getPluginName()),
 611                  114
 612              );
 613          }
 614  
 615          $user = trim($auth->cleanUser($userStruct['user'] ?? ''));
 616          $password = $userStruct['password'] ?? '';
 617          $name = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/', '', $userStruct['name'] ?? ''));
 618          $mail = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/', '', $userStruct['mail'] ?? ''));
 619          $groups = $userStruct['groups'] ?? [];
 620  
 621          $notify = (bool)$userStruct['notify'] ?? false;
 622  
 623          if ($user === '') throw new RemoteException('empty or invalid user', 401);
 624          if ($name === '') throw new RemoteException('empty or invalid user name', 402);
 625          if (!mail_isvalid($mail)) throw new RemoteException('empty or invalid mail address', 403);
 626  
 627          if(strlen($password) === 0) {
 628              $password = auth_pwgen($user);
 629          }
 630  
 631          if (!is_array($groups) || count($groups) === 0) {
 632              $groups = null;
 633          }
 634  
 635          $ok = $auth->triggerUserMod('create', array($user, $password, $name, $mail, $groups));
 636  
 637          if ($ok !== false && $ok !== null) {
 638              $ok = true;
 639          }
 640  
 641          if($ok) {
 642              if($notify) {
 643                  auth_sendPassword($user, $password);
 644              }
 645          }
 646  
 647          return $ok;
 648      }
 649  
 650  
 651      /**
 652       * Remove one or more users from the list of registered users
 653       *
 654       * @param string[] $usernames List of usernames to remove
 655       *
 656       * @return bool
 657       *
 658       * @throws AccessDeniedException
 659       */
 660      public function deleteUsers($usernames)
 661      {
 662          if (!auth_isadmin()) {
 663              throw new AccessDeniedException('Only admins are allowed to delete users', 114);
 664          }
 665          /** @var \dokuwiki\Extension\AuthPlugin $auth */
 666          global $auth;
 667          return (bool)$auth->triggerUserMod('delete', array($usernames));
 668      }
 669  
 670      /**
 671       * Uploads a file to the wiki.
 672       *
 673       * Michael Klier <chi@chimeric.de>
 674       *
 675       * @param string $id page id
 676       * @param string $file
 677       * @param array $params such as overwrite
 678       * @return false|string
 679       * @throws RemoteException
 680       */
 681      public function putAttachment($id, $file, $params = array())
 682      {
 683          $id = cleanID($id);
 684          $auth = auth_quickaclcheck(getNS($id) . ':*');
 685  
 686          if (!isset($id)) {
 687              throw new RemoteException('Filename not given.', 231);
 688          }
 689  
 690          global $conf;
 691  
 692          $ftmp = $conf['tmpdir'] . '/' . md5($id . clientIP());
 693  
 694          // save temporary file
 695          @unlink($ftmp);
 696          io_saveFile($ftmp, $file);
 697  
 698          $res = media_save(array('name' => $ftmp), $id, $params['ow'], $auth, 'rename');
 699          if (is_array($res)) {
 700              throw new RemoteException($res[0], -$res[1]);
 701          } else {
 702              return $res;
 703          }
 704      }
 705  
 706      /**
 707       * Deletes a file from the wiki.
 708       *
 709       * @author Gina Haeussge <osd@foosel.net>
 710       *
 711       * @param string $id page id
 712       * @return int
 713       * @throws AccessDeniedException no permissions
 714       * @throws RemoteException file in use or not deleted
 715       */
 716      public function deleteAttachment($id)
 717      {
 718          $id = cleanID($id);
 719          $auth = auth_quickaclcheck(getNS($id) . ':*');
 720          $res = media_delete($id, $auth);
 721          if ($res & DOKU_MEDIA_DELETED) {
 722              return 0;
 723          } elseif ($res & DOKU_MEDIA_NOT_AUTH) {
 724              throw new AccessDeniedException('You don\'t have permissions to delete files.', 212);
 725          } elseif ($res & DOKU_MEDIA_INUSE) {
 726              throw new RemoteException('File is still referenced', 232);
 727          } else {
 728              throw new RemoteException('Could not delete file', 233);
 729          }
 730      }
 731  
 732      /**
 733       * Returns the permissions of a given wiki page for the current user or another user
 734       *
 735       * @param string $id page id
 736       * @param string|null $user username
 737       * @param array|null $groups array of groups
 738       * @return int permission level
 739       */
 740      public function aclCheck($id, $user = null, $groups = null)
 741      {
 742          /** @var \dokuwiki\Extension\AuthPlugin $auth */
 743          global $auth;
 744  
 745          $id = $this->resolvePageId($id);
 746          if ($user === null) {
 747              return auth_quickaclcheck($id);
 748          } else {
 749              if ($groups === null) {
 750                  $userinfo = $auth->getUserData($user);
 751                  if ($userinfo === false) {
 752                      $groups = array();
 753                  } else {
 754                      $groups = $userinfo['grps'];
 755                  }
 756              }
 757              return auth_aclcheck($id, $user, $groups);
 758          }
 759      }
 760  
 761      /**
 762       * Lists all links contained in a wiki page
 763       *
 764       * @author Michael Klier <chi@chimeric.de>
 765       *
 766       * @param string $id page id
 767       * @return array
 768       * @throws AccessDeniedException  no read access for page
 769       */
 770      public function listLinks($id)
 771      {
 772          $id = $this->resolvePageId($id);
 773          if (auth_quickaclcheck($id) < AUTH_READ) {
 774              throw new AccessDeniedException('You are not allowed to read this page', 111);
 775          }
 776          $links = array();
 777  
 778          // resolve page instructions
 779          $ins = p_cached_instructions(wikiFN($id));
 780  
 781          // instantiate new Renderer - needed for interwiki links
 782          $Renderer = new Doku_Renderer_xhtml();
 783          $Renderer->interwiki = getInterwiki();
 784  
 785          // parse parse instructions
 786          foreach ($ins as $in) {
 787              $link = array();
 788              switch ($in[0]) {
 789                  case 'internallink':
 790                      $link['type'] = 'local';
 791                      $link['page'] = $in[1][0];
 792                      $link['href'] = wl($in[1][0]);
 793                      array_push($links, $link);
 794                      break;
 795                  case 'externallink':
 796                      $link['type'] = 'extern';
 797                      $link['page'] = $in[1][0];
 798                      $link['href'] = $in[1][0];
 799                      array_push($links, $link);
 800                      break;
 801                  case 'interwikilink':
 802                      $url = $Renderer->_resolveInterWiki($in[1][2], $in[1][3]);
 803                      $link['type'] = 'extern';
 804                      $link['page'] = $url;
 805                      $link['href'] = $url;
 806                      array_push($links, $link);
 807                      break;
 808              }
 809          }
 810  
 811          return ($links);
 812      }
 813  
 814      /**
 815       * Returns a list of recent changes since give timestamp
 816       *
 817       * @author Michael Hamann <michael@content-space.de>
 818       * @author Michael Klier <chi@chimeric.de>
 819       *
 820       * @param int $timestamp unix timestamp
 821       * @return array
 822       * @throws RemoteException no valid timestamp
 823       */
 824      public function getRecentChanges($timestamp)
 825      {
 826          if (strlen($timestamp) != 10) {
 827              throw new RemoteException('The provided value is not a valid timestamp', 311);
 828          }
 829  
 830          $recents = getRecentsSince($timestamp);
 831  
 832          $changes = array();
 833  
 834          foreach ($recents as $recent) {
 835              $change = array();
 836              $change['name'] = $recent['id'];
 837              $change['lastModified'] = $this->api->toDate($recent['date']);
 838              $change['author'] = $recent['user'];
 839              $change['version'] = $recent['date'];
 840              $change['perms'] = $recent['perms'];
 841              $change['size'] = @filesize(wikiFN($recent['id']));
 842              array_push($changes, $change);
 843          }
 844  
 845          if (!empty($changes)) {
 846              return $changes;
 847          } else {
 848              // in case we still have nothing at this point
 849              throw new RemoteException('There are no changes in the specified timeframe', 321);
 850          }
 851      }
 852  
 853      /**
 854       * Returns a list of recent media changes since give timestamp
 855       *
 856       * @author Michael Hamann <michael@content-space.de>
 857       * @author Michael Klier <chi@chimeric.de>
 858       *
 859       * @param int $timestamp unix timestamp
 860       * @return array
 861       * @throws RemoteException no valid timestamp
 862       */
 863      public function getRecentMediaChanges($timestamp)
 864      {
 865          if (strlen($timestamp) != 10)
 866              throw new RemoteException('The provided value is not a valid timestamp', 311);
 867  
 868          $recents = getRecentsSince($timestamp, null, '', RECENTS_MEDIA_CHANGES);
 869  
 870          $changes = array();
 871  
 872          foreach ($recents as $recent) {
 873              $change = array();
 874              $change['name'] = $recent['id'];
 875              $change['lastModified'] = $this->api->toDate($recent['date']);
 876              $change['author'] = $recent['user'];
 877              $change['version'] = $recent['date'];
 878              $change['perms'] = $recent['perms'];
 879              $change['size'] = @filesize(mediaFN($recent['id']));
 880              array_push($changes, $change);
 881          }
 882  
 883          if (!empty($changes)) {
 884              return $changes;
 885          } else {
 886              // in case we still have nothing at this point
 887              throw new RemoteException('There are no changes in the specified timeframe', 321);
 888          }
 889      }
 890  
 891      /**
 892       * Returns a list of available revisions of a given wiki page
 893       * Number of returned pages is set by $conf['recent']
 894       * However not accessible pages are skipped, so less than $conf['recent'] could be returned
 895       *
 896       * @author Michael Klier <chi@chimeric.de>
 897       *
 898       * @param string $id page id
 899       * @param int $first skip the first n changelog lines
 900       *                      0 = from current(if exists)
 901       *                      1 = from 1st old rev
 902       *                      2 = from 2nd old rev, etc
 903       * @return array
 904       * @throws AccessDeniedException no read access for page
 905       * @throws RemoteException empty id
 906       */
 907      public function pageVersions($id, $first = 0)
 908      {
 909          $id = $this->resolvePageId($id);
 910          if (auth_quickaclcheck($id) < AUTH_READ) {
 911              throw new AccessDeniedException('You are not allowed to read this page', 111);
 912          }
 913          global $conf;
 914  
 915          $versions = array();
 916  
 917          if (empty($id)) {
 918              throw new RemoteException('Empty page ID', 131);
 919          }
 920  
 921          $first = (int) $first;
 922          $first_rev = $first - 1;
 923          $first_rev = $first_rev < 0 ? 0 : $first_rev;
 924          $pagelog = new PageChangeLog($id);
 925          $revisions = $pagelog->getRevisions($first_rev, $conf['recent']);
 926  
 927          if ($first == 0) {
 928              array_unshift($revisions, '');  // include current revision
 929              if (count($revisions) > $conf['recent']) {
 930                  array_pop($revisions);          // remove extra log entry
 931              }
 932          }
 933  
 934          if (!empty($revisions)) {
 935              foreach ($revisions as $rev) {
 936                  $file = wikiFN($id, $rev);
 937                  $time = @filemtime($file);
 938                  // we check if the page actually exists, if this is not the
 939                  // case this can lead to less pages being returned than
 940                  // specified via $conf['recent']
 941                  if ($time) {
 942                      $pagelog->setChunkSize(1024);
 943                      $info = $pagelog->getRevisionInfo($rev ? $rev : $time);
 944                      if (!empty($info)) {
 945                          $data = array();
 946                          $data['user'] = $info['user'];
 947                          $data['ip'] = $info['ip'];
 948                          $data['type'] = $info['type'];
 949                          $data['sum'] = $info['sum'];
 950                          $data['modified'] = $this->api->toDate($info['date']);
 951                          $data['version'] = $info['date'];
 952                          array_push($versions, $data);
 953                      }
 954                  }
 955              }
 956              return $versions;
 957          } else {
 958              return array();
 959          }
 960      }
 961  
 962      /**
 963       * The version of Wiki RPC API supported
 964       */
 965      public function wikiRpcVersion()
 966      {
 967          return 2;
 968      }
 969  
 970      /**
 971       * Locks or unlocks a given batch of pages
 972       *
 973       * Give an associative array with two keys: lock and unlock. Both should contain a
 974       * list of pages to lock or unlock
 975       *
 976       * Returns an associative array with the keys locked, lockfail, unlocked and
 977       * unlockfail, each containing lists of pages.
 978       *
 979       * @param array[] $set list pages with array('lock' => array, 'unlock' => array)
 980       * @return array
 981       */
 982      public function setLocks($set)
 983      {
 984          $locked = array();
 985          $lockfail = array();
 986          $unlocked = array();
 987          $unlockfail = array();
 988  
 989          foreach ((array) $set['lock'] as $id) {
 990              $id = $this->resolvePageId($id);
 991              if (auth_quickaclcheck($id) < AUTH_EDIT || checklock($id)) {
 992                  $lockfail[] = $id;
 993              } else {
 994                  lock($id);
 995                  $locked[] = $id;
 996              }
 997          }
 998  
 999          foreach ((array) $set['unlock'] as $id) {
1000              $id = $this->resolvePageId($id);
1001              if (auth_quickaclcheck($id) < AUTH_EDIT || !unlock($id)) {
1002                  $unlockfail[] = $id;
1003              } else {
1004                  $unlocked[] = $id;
1005              }
1006          }
1007  
1008          return array(
1009              'locked' => $locked,
1010              'lockfail' => $lockfail,
1011              'unlocked' => $unlocked,
1012              'unlockfail' => $unlockfail,
1013          );
1014      }
1015  
1016      /**
1017       * Return API version
1018       *
1019       * @return int
1020       */
1021      public function getAPIVersion()
1022      {
1023          return self::API_VERSION;
1024      }
1025  
1026      /**
1027       * Login
1028       *
1029       * @param string $user
1030       * @param string $pass
1031       * @return int
1032       */
1033      public function login($user, $pass)
1034      {
1035          global $conf;
1036          /** @var \dokuwiki\Extension\AuthPlugin $auth */
1037          global $auth;
1038  
1039          if (!$conf['useacl']) return 0;
1040          if (!$auth) return 0;
1041  
1042          @session_start(); // reopen session for login
1043          $ok = null;
1044          if ($auth->canDo('external')) {
1045              $ok = $auth->trustExternal($user, $pass, false);
1046          }
1047          if ($ok === null){
1048              $evdata = array(
1049                  'user' => $user,
1050                  'password' => $pass,
1051                  'sticky' => false,
1052                  'silent' => true,
1053              );
1054              $ok = Event::createAndTrigger('AUTH_LOGIN_CHECK', $evdata, 'auth_login_wrapper');
1055          }
1056          session_write_close(); // we're done with the session
1057  
1058          return $ok;
1059      }
1060  
1061      /**
1062       * Log off
1063       *
1064       * @return int
1065       */
1066      public function logoff()
1067      {
1068          global $conf;
1069          global $auth;
1070          if (!$conf['useacl']) return 0;
1071          if (!$auth) return 0;
1072  
1073          auth_logoff();
1074  
1075          return 1;
1076      }
1077  
1078      /**
1079       * Resolve page id
1080       *
1081       * @param string $id page id
1082       * @return string
1083       */
1084      private function resolvePageId($id)
1085      {
1086          $id = cleanID($id);
1087          if (empty($id)) {
1088              global $conf;
1089              $id = cleanID($conf['start']);
1090          }
1091          return $id;
1092      }
1093  }