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