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