1<?php 2 3namespace dokuwiki\Remote; 4 5use Doku_Renderer_xhtml; 6use DokuWiki_Auth_Plugin; 7use MediaChangeLog; 8use PageChangeLog; 9 10define('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 */ 16class 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_Auth_Plugin $auth */ 598 global $auth; 599 return (bool)$auth->triggerUserMod('deleteUsers', $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_Auth_Plugin $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_Auth_Plugin $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 = trigger_event('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} 1024