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