1dd87735dSAndreas Gohr<?php 2dd87735dSAndreas Gohr 3dd87735dSAndreas Gohrnamespace dokuwiki\Remote; 4dd87735dSAndreas Gohr 5dd87735dSAndreas Gohruse Doku_Renderer_xhtml; 60c3a5702SAndreas Gohruse dokuwiki\ChangeLog\MediaChangeLog; 70c3a5702SAndreas Gohruse dokuwiki\ChangeLog\PageChangeLog; 8104a3b7cSAndreas Gohruse dokuwiki\Extension\AuthPlugin; 9cbb44eabSAndreas Gohruse dokuwiki\Extension\Event; 102d85e841SAndreas Gohruse dokuwiki\Utf8\Sort; 11dd87735dSAndreas Gohr 12dd87735dSAndreas Gohr/** 13dd87735dSAndreas Gohr * Provides the core methods for the remote API. 14dd87735dSAndreas Gohr * The methods are ordered in 'wiki.<method>' and 'dokuwiki.<method>' namespaces 15dd87735dSAndreas Gohr */ 16dd87735dSAndreas Gohrclass ApiCore 17dd87735dSAndreas Gohr{ 18dd87735dSAndreas Gohr /** @var int Increased whenever the API is changed */ 1974981a4eSAndreas Gohr public const API_VERSION = 11; 20dd87735dSAndreas Gohr 21dd87735dSAndreas Gohr 22dd87735dSAndreas Gohr /** @var Api */ 23dd87735dSAndreas Gohr private $api; 24dd87735dSAndreas Gohr 25dd87735dSAndreas Gohr /** 26dd87735dSAndreas Gohr * @param Api $api 27dd87735dSAndreas Gohr */ 28dd87735dSAndreas Gohr public function __construct(Api $api) 29dd87735dSAndreas Gohr { 30dd87735dSAndreas Gohr $this->api = $api; 31dd87735dSAndreas Gohr } 32dd87735dSAndreas Gohr 33dd87735dSAndreas Gohr /** 34dd87735dSAndreas Gohr * Returns details about the core methods 35dd87735dSAndreas Gohr * 36dd87735dSAndreas Gohr * @return array 37dd87735dSAndreas Gohr */ 38e6a9d76fSSyntaxseed public function getRemoteInfo() 39dd87735dSAndreas Gohr { 40104a3b7cSAndreas Gohr return [ 4142e66c7aSAndreas Gohr 'dokuwiki.getVersion' => new ApiCall('getVersion'), 4242e66c7aSAndreas Gohr 'dokuwiki.login' => (new ApiCall([$this, 'login'])) 4342e66c7aSAndreas Gohr ->setPublic(), 4442e66c7aSAndreas Gohr 'dokuwiki.logoff' => new ApiCall([$this, 'logoff']), 4542e66c7aSAndreas Gohr 'dokuwiki.getPagelist' => new ApiCall([$this, 'readNamespace']), 4642e66c7aSAndreas Gohr 'dokuwiki.search' => new ApiCall([$this, 'search']), 47*fe9f11e2SAndreas Gohr 'dokuwiki.getTime' => (new ApiCall([$this, 'time'])), 4842e66c7aSAndreas Gohr 'dokuwiki.setLocks' => new ApiCall([$this, 'setLocks']), 4942e66c7aSAndreas Gohr 'dokuwiki.getTitle' => (new ApiCall([$this, 'getTitle'])) 5042e66c7aSAndreas Gohr ->setPublic(), 5142e66c7aSAndreas Gohr 'dokuwiki.appendPage' => new ApiCall([$this, 'appendPage']), 5242e66c7aSAndreas Gohr 'dokuwiki.createUser' => new ApiCall([$this, 'createUser']), 5342e66c7aSAndreas Gohr 'dokuwiki.deleteUsers' => new ApiCall([$this, 'deleteUsers']), 5442e66c7aSAndreas Gohr 'wiki.getPage' => (new ApiCall([$this, 'rawPage'])) 550ff4031cSAndreas Gohr ->limitArgs(['page']), 5642e66c7aSAndreas Gohr 'wiki.getPageVersion' => (new ApiCall([$this, 'rawPage'])) 5742e66c7aSAndreas Gohr ->setSummary('Get a specific revision of a wiki page'), 5842e66c7aSAndreas Gohr 'wiki.getPageHTML' => (new ApiCall([$this, 'htmlPage'])) 590ff4031cSAndreas Gohr ->limitArgs(['page']), 6042e66c7aSAndreas Gohr 'wiki.getPageHTMLVersion' => (new ApiCall([$this, 'htmlPage'])) 6142e66c7aSAndreas Gohr ->setSummary('Get the HTML for a specific revision of a wiki page'), 6242e66c7aSAndreas Gohr 'wiki.getAllPages' => new ApiCall([$this, 'listPages']), 6342e66c7aSAndreas Gohr 'wiki.getAttachments' => new ApiCall([$this, 'listAttachments']), 6442e66c7aSAndreas Gohr 'wiki.getBackLinks' => new ApiCall([$this, 'listBackLinks']), 6542e66c7aSAndreas Gohr 'wiki.getPageInfo' => (new ApiCall([$this, 'pageInfo'])) 660ff4031cSAndreas Gohr ->limitArgs(['page']), 6742e66c7aSAndreas Gohr 'wiki.getPageInfoVersion' => (new ApiCall([$this, 'pageInfo'])) 6842e66c7aSAndreas Gohr ->setSummary('Get some basic data about a specific revison of a wiki page'), 6942e66c7aSAndreas Gohr 'wiki.getPageVersions' => new ApiCall([$this, 'pageVersions']), 7042e66c7aSAndreas Gohr 'wiki.putPage' => new ApiCall([$this, 'putPage']), 7142e66c7aSAndreas Gohr 'wiki.listLinks' => new ApiCall([$this, 'listLinks']), 7242e66c7aSAndreas Gohr 'wiki.getRecentChanges' => new ApiCall([$this, 'getRecentChanges']), 7342e66c7aSAndreas Gohr 'wiki.getRecentMediaChanges' => new ApiCall([$this, 'getRecentMediaChanges']), 7442e66c7aSAndreas Gohr 'wiki.aclCheck' => new ApiCall([$this, 'aclCheck']), 7542e66c7aSAndreas Gohr 'wiki.putAttachment' => new ApiCall([$this, 'putAttachment']), 7642e66c7aSAndreas Gohr 'wiki.deleteAttachment' => new ApiCall([$this, 'deleteAttachment']), 7742e66c7aSAndreas Gohr 'wiki.getAttachment' => new ApiCall([$this, 'getAttachment']), 7842e66c7aSAndreas Gohr 'wiki.getAttachmentInfo' => new ApiCall([$this, 'getAttachmentInfo']), 7942e66c7aSAndreas Gohr 'dokuwiki.getXMLRPCAPIVersion' => (new ApiCall([$this, 'getAPIVersion']))->setPublic(), 8042e66c7aSAndreas Gohr 'wiki.getRPCVersionSupported' => (new ApiCall([$this, 'wikiRpcVersion']))->setPublic(), 81104a3b7cSAndreas Gohr ]; 82dd87735dSAndreas Gohr } 83dd87735dSAndreas Gohr 84dd87735dSAndreas Gohr /** 85e7323dfbSAndreas Gohr * Return the current server time 86e7323dfbSAndreas Gohr * 87*fe9f11e2SAndreas Gohr * Returns a Unix timestamp (seconds since 1970-01-01 00:00:00 UTC). 88*fe9f11e2SAndreas Gohr * 89*fe9f11e2SAndreas Gohr * You can use this to compensate for differences between your client's time and the 90*fe9f11e2SAndreas Gohr * server's time when working with last modified timestamps (revisions). 91e7323dfbSAndreas Gohr * 92e7323dfbSAndreas Gohr * @return int A unix timestamp 93e7323dfbSAndreas Gohr */ 94*fe9f11e2SAndreas Gohr public function time() 95*fe9f11e2SAndreas Gohr { 96e7323dfbSAndreas Gohr return time(); 97e7323dfbSAndreas Gohr } 98e7323dfbSAndreas Gohr 99e7323dfbSAndreas Gohr /** 100dd87735dSAndreas Gohr * Return a raw wiki page 101dd87735dSAndreas Gohr * 1020ff4031cSAndreas Gohr * @param string $page wiki page id 1038a9282a2SAndreas Gohr * @param int $rev revision timestamp of the page 1048a9282a2SAndreas Gohr * @return string the syntax of the page 105dd87735dSAndreas Gohr * @throws AccessDeniedException if no permission for page 106dd87735dSAndreas Gohr */ 1070ff4031cSAndreas Gohr public function rawPage($page, $rev = '') 108dd87735dSAndreas Gohr { 1090ff4031cSAndreas Gohr $page = $this->resolvePageId($page); 1100ff4031cSAndreas Gohr if (auth_quickaclcheck($page) < AUTH_READ) { 111dd87735dSAndreas Gohr throw new AccessDeniedException('You are not allowed to read this file', 111); 112dd87735dSAndreas Gohr } 1130ff4031cSAndreas Gohr $text = rawWiki($page, $rev); 114dd87735dSAndreas Gohr if (!$text) { 1150ff4031cSAndreas Gohr return pageTemplate($page); 116dd87735dSAndreas Gohr } else { 117dd87735dSAndreas Gohr return $text; 118dd87735dSAndreas Gohr } 119dd87735dSAndreas Gohr } 120dd87735dSAndreas Gohr 121dd87735dSAndreas Gohr /** 122dd87735dSAndreas Gohr * Return a media file 123dd87735dSAndreas Gohr * 1240ff4031cSAndreas Gohr * @param string $media file id 125*fe9f11e2SAndreas Gohr * @return string media file contents 126dd87735dSAndreas Gohr * @throws AccessDeniedException no permission for media 127dd87735dSAndreas Gohr * @throws RemoteException not exist 128104a3b7cSAndreas Gohr * @author Gina Haeussge <osd@foosel.net> 129104a3b7cSAndreas Gohr * 130dd87735dSAndreas Gohr */ 1310ff4031cSAndreas Gohr public function getAttachment($media) 132dd87735dSAndreas Gohr { 1330ff4031cSAndreas Gohr $media = cleanID($media); 1340ff4031cSAndreas Gohr if (auth_quickaclcheck(getNS($media) . ':*') < AUTH_READ) { 135dd87735dSAndreas Gohr throw new AccessDeniedException('You are not allowed to read this file', 211); 136dd87735dSAndreas Gohr } 137dd87735dSAndreas Gohr 1380ff4031cSAndreas Gohr $file = mediaFN($media); 139dd87735dSAndreas Gohr if (!@ file_exists($file)) { 140dd87735dSAndreas Gohr throw new RemoteException('The requested file does not exist', 221); 141dd87735dSAndreas Gohr } 142dd87735dSAndreas Gohr 143dd87735dSAndreas Gohr $data = io_readFile($file, false); 144dd87735dSAndreas Gohr return $this->api->toFile($data); 145dd87735dSAndreas Gohr } 146dd87735dSAndreas Gohr 147dd87735dSAndreas Gohr /** 148dd87735dSAndreas Gohr * Return info about a media file 149dd87735dSAndreas Gohr * 150*fe9f11e2SAndreas Gohr * @param string $media file id 151dd87735dSAndreas Gohr * @return array 152104a3b7cSAndreas Gohr * @author Gina Haeussge <osd@foosel.net> 153104a3b7cSAndreas Gohr * 154dd87735dSAndreas Gohr */ 1550ff4031cSAndreas Gohr public function getAttachmentInfo($media) 156dd87735dSAndreas Gohr { 1570ff4031cSAndreas Gohr $media = cleanID($media); 158104a3b7cSAndreas Gohr $info = ['lastModified' => $this->api->toDate(0), 'size' => 0]; 159dd87735dSAndreas Gohr 1600ff4031cSAndreas Gohr $file = mediaFN($media); 1610ff4031cSAndreas Gohr if (auth_quickaclcheck(getNS($media) . ':*') >= AUTH_READ) { 162dd87735dSAndreas Gohr if (file_exists($file)) { 163dd87735dSAndreas Gohr $info['lastModified'] = $this->api->toDate(filemtime($file)); 164dd87735dSAndreas Gohr $info['size'] = filesize($file); 165dd87735dSAndreas Gohr } else { 166dd87735dSAndreas Gohr //Is it deleted media with changelog? 1670ff4031cSAndreas Gohr $medialog = new MediaChangeLog($media); 168dd87735dSAndreas Gohr $revisions = $medialog->getRevisions(0, 1); 169dd87735dSAndreas Gohr if (!empty($revisions)) { 170dd87735dSAndreas Gohr $info['lastModified'] = $this->api->toDate($revisions[0]); 171dd87735dSAndreas Gohr } 172dd87735dSAndreas Gohr } 173dd87735dSAndreas Gohr } 174dd87735dSAndreas Gohr 175dd87735dSAndreas Gohr return $info; 176dd87735dSAndreas Gohr } 177dd87735dSAndreas Gohr 178dd87735dSAndreas Gohr /** 1798a9282a2SAndreas Gohr * Return a wiki page rendered to HTML 180dd87735dSAndreas Gohr * 1810ff4031cSAndreas Gohr * @param string $page page id 1828a9282a2SAndreas Gohr * @param string $rev revision timestamp 1838a9282a2SAndreas Gohr * @return string Rendered HTML for the page 184dd87735dSAndreas Gohr * @throws AccessDeniedException no access to page 185dd87735dSAndreas Gohr */ 1860ff4031cSAndreas Gohr public function htmlPage($page, $rev = '') 187dd87735dSAndreas Gohr { 1880ff4031cSAndreas Gohr $page = $this->resolvePageId($page); 1890ff4031cSAndreas Gohr if (auth_quickaclcheck($page) < AUTH_READ) { 190dd87735dSAndreas Gohr throw new AccessDeniedException('You are not allowed to read this page', 111); 191dd87735dSAndreas Gohr } 1920ff4031cSAndreas Gohr return p_wiki_xhtml($page, $rev, false); 193dd87735dSAndreas Gohr } 194dd87735dSAndreas Gohr 195dd87735dSAndreas Gohr /** 1968a9282a2SAndreas Gohr * List all pages 1978a9282a2SAndreas Gohr * 198*fe9f11e2SAndreas Gohr * This uses the search index and only returns pages that have been indexed already 199dd87735dSAndreas Gohr * 200*fe9f11e2SAndreas Gohr * @return array[] A list of all pages with id, perms, size, lastModified 201dd87735dSAndreas Gohr */ 202dd87735dSAndreas Gohr public function listPages() 203dd87735dSAndreas Gohr { 204104a3b7cSAndreas Gohr $list = []; 205dd87735dSAndreas Gohr $pages = idx_get_indexer()->getPages(); 206dd87735dSAndreas Gohr $pages = array_filter(array_filter($pages, 'isVisiblePage'), 'page_exists'); 2072d85e841SAndreas Gohr Sort::ksort($pages); 208dd87735dSAndreas Gohr 209dd87735dSAndreas Gohr foreach (array_keys($pages) as $idx) { 210dd87735dSAndreas Gohr $perm = auth_quickaclcheck($pages[$idx]); 211dd87735dSAndreas Gohr if ($perm < AUTH_READ) { 212dd87735dSAndreas Gohr continue; 213dd87735dSAndreas Gohr } 214104a3b7cSAndreas Gohr $page = []; 215dd87735dSAndreas Gohr $page['id'] = trim($pages[$idx]); 216dd87735dSAndreas Gohr $page['perms'] = $perm; 217dd87735dSAndreas Gohr $page['size'] = @filesize(wikiFN($pages[$idx])); 218dd87735dSAndreas Gohr $page['lastModified'] = $this->api->toDate(@filemtime(wikiFN($pages[$idx]))); 219dd87735dSAndreas Gohr $list[] = $page; 220dd87735dSAndreas Gohr } 221dd87735dSAndreas Gohr 222dd87735dSAndreas Gohr return $list; 223dd87735dSAndreas Gohr } 224dd87735dSAndreas Gohr 225dd87735dSAndreas Gohr /** 226dd87735dSAndreas Gohr * List all pages in the given namespace (and below) 227dd87735dSAndreas Gohr * 228dd87735dSAndreas Gohr * @param string $ns 229dd87735dSAndreas Gohr * @param array $opts 230dd87735dSAndreas Gohr * $opts['depth'] recursion level, 0 for all 231dd87735dSAndreas Gohr * $opts['hash'] do md5 sum of content? 232*fe9f11e2SAndreas Gohr * @return array[] A list of matching pages with id, rev, mtime, size, (hash) 233dd87735dSAndreas Gohr */ 234104a3b7cSAndreas Gohr public function readNamespace($ns, $opts = []) 235dd87735dSAndreas Gohr { 236dd87735dSAndreas Gohr global $conf; 237dd87735dSAndreas Gohr 238104a3b7cSAndreas Gohr if (!is_array($opts)) $opts = []; 239dd87735dSAndreas Gohr 240dd87735dSAndreas Gohr $ns = cleanID($ns); 241dd87735dSAndreas Gohr $dir = utf8_encodeFN(str_replace(':', '/', $ns)); 242104a3b7cSAndreas Gohr $data = []; 243dd87735dSAndreas Gohr $opts['skipacl'] = 0; // no ACL skipping for XMLRPC 244dd87735dSAndreas Gohr search($data, $conf['datadir'], 'search_allpages', $opts, $dir); 245dd87735dSAndreas Gohr return $data; 246dd87735dSAndreas Gohr } 247dd87735dSAndreas Gohr 248dd87735dSAndreas Gohr /** 2498a9282a2SAndreas Gohr * Do a fulltext search 250dd87735dSAndreas Gohr * 251*fe9f11e2SAndreas Gohr * This executes a full text search and returns the results. The query uses the standard 252*fe9f11e2SAndreas Gohr * DokuWiki search syntax. 2538a9282a2SAndreas Gohr * 254*fe9f11e2SAndreas Gohr * Snippets are provided for the first 15 results only. The title is either the first heading 255*fe9f11e2SAndreas Gohr * or the page id depending on the wiki's configuration. 256*fe9f11e2SAndreas Gohr * 257*fe9f11e2SAndreas Gohr * @link https://www.dokuwiki.org/search#syntax 2588a9282a2SAndreas Gohr * @param string $query The search query as supported by the DokuWiki search 259*fe9f11e2SAndreas Gohr * @return array[] A list of matching pages with id, score, rev, mtime, size, snippet, title 260dd87735dSAndreas Gohr */ 261dd87735dSAndreas Gohr public function search($query) 262dd87735dSAndreas Gohr { 263104a3b7cSAndreas Gohr $regex = []; 264dd87735dSAndreas Gohr $data = ft_pageSearch($query, $regex); 265104a3b7cSAndreas Gohr $pages = []; 266dd87735dSAndreas Gohr 267dd87735dSAndreas Gohr // prepare additional data 268dd87735dSAndreas Gohr $idx = 0; 269dd87735dSAndreas Gohr foreach ($data as $id => $score) { 270dd87735dSAndreas Gohr $file = wikiFN($id); 271dd87735dSAndreas Gohr 272dd87735dSAndreas Gohr if ($idx < FT_SNIPPET_NUMBER) { 273dd87735dSAndreas Gohr $snippet = ft_snippet($id, $regex); 274dd87735dSAndreas Gohr $idx++; 275dd87735dSAndreas Gohr } else { 276dd87735dSAndreas Gohr $snippet = ''; 277dd87735dSAndreas Gohr } 278dd87735dSAndreas Gohr 279104a3b7cSAndreas Gohr $pages[] = [ 280dd87735dSAndreas Gohr 'id' => $id, 281104a3b7cSAndreas Gohr 'score' => (int)$score, 282dd87735dSAndreas Gohr 'rev' => filemtime($file), 283dd87735dSAndreas Gohr 'mtime' => filemtime($file), 284dd87735dSAndreas Gohr 'size' => filesize($file), 285dd87735dSAndreas Gohr 'snippet' => $snippet, 286dd87735dSAndreas Gohr 'title' => useHeading('navigation') ? p_get_first_heading($id) : $id 287104a3b7cSAndreas Gohr ]; 288dd87735dSAndreas Gohr } 289dd87735dSAndreas Gohr return $pages; 290dd87735dSAndreas Gohr } 291dd87735dSAndreas Gohr 292dd87735dSAndreas Gohr /** 293*fe9f11e2SAndreas Gohr * Returns the wiki title 294dd87735dSAndreas Gohr * 295*fe9f11e2SAndreas Gohr * @link https://www.dokuwiki.org/config:title 296dd87735dSAndreas Gohr * @return string 297dd87735dSAndreas Gohr */ 298dd87735dSAndreas Gohr public function getTitle() 299dd87735dSAndreas Gohr { 300dd87735dSAndreas Gohr global $conf; 301dd87735dSAndreas Gohr return $conf['title']; 302dd87735dSAndreas Gohr } 303dd87735dSAndreas Gohr 304dd87735dSAndreas Gohr /** 305dd87735dSAndreas Gohr * List all media files. 306dd87735dSAndreas Gohr * 307dd87735dSAndreas Gohr * Available options are 'recursive' for also including the subnamespaces 308dd87735dSAndreas Gohr * in the listing, and 'pattern' for filtering the returned files against 309dd87735dSAndreas Gohr * a regular expression matching their name. 310dd87735dSAndreas Gohr * 311dd87735dSAndreas Gohr * @param string $ns 312dd87735dSAndreas Gohr * @param array $options 313dd87735dSAndreas Gohr * $options['depth'] recursion level, 0 for all 314dd87735dSAndreas Gohr * $options['showmsg'] shows message if invalid media id is used 315dd87735dSAndreas Gohr * $options['pattern'] check given pattern 316dd87735dSAndreas Gohr * $options['hash'] add hashes to result list 317dd87735dSAndreas Gohr * @return array 318dd87735dSAndreas Gohr * @throws AccessDeniedException no access to the media files 319104a3b7cSAndreas Gohr * @author Gina Haeussge <osd@foosel.net> 320104a3b7cSAndreas Gohr * 321dd87735dSAndreas Gohr */ 322104a3b7cSAndreas Gohr public function listAttachments($ns, $options = []) 323dd87735dSAndreas Gohr { 324dd87735dSAndreas Gohr global $conf; 325dd87735dSAndreas Gohr 326dd87735dSAndreas Gohr $ns = cleanID($ns); 327dd87735dSAndreas Gohr 328104a3b7cSAndreas Gohr if (!is_array($options)) $options = []; 329dd87735dSAndreas Gohr $options['skipacl'] = 0; // no ACL skipping for XMLRPC 330dd87735dSAndreas Gohr 331dd87735dSAndreas Gohr if (auth_quickaclcheck($ns . ':*') >= AUTH_READ) { 332dd87735dSAndreas Gohr $dir = utf8_encodeFN(str_replace(':', '/', $ns)); 333dd87735dSAndreas Gohr 334104a3b7cSAndreas Gohr $data = []; 335dd87735dSAndreas Gohr search($data, $conf['mediadir'], 'search_media', $options, $dir); 336dd87735dSAndreas Gohr $len = count($data); 337104a3b7cSAndreas Gohr if (!$len) return []; 338dd87735dSAndreas Gohr 339dd87735dSAndreas Gohr for ($i = 0; $i < $len; $i++) { 340dd87735dSAndreas Gohr unset($data[$i]['meta']); 341dd87735dSAndreas Gohr $data[$i]['perms'] = $data[$i]['perm']; 342dd87735dSAndreas Gohr unset($data[$i]['perm']); 343dd87735dSAndreas Gohr $data[$i]['lastModified'] = $this->api->toDate($data[$i]['mtime']); 344dd87735dSAndreas Gohr } 345dd87735dSAndreas Gohr return $data; 346dd87735dSAndreas Gohr } else { 347dd87735dSAndreas Gohr throw new AccessDeniedException('You are not allowed to list media files.', 215); 348dd87735dSAndreas Gohr } 349dd87735dSAndreas Gohr } 350dd87735dSAndreas Gohr 351dd87735dSAndreas Gohr /** 352dd87735dSAndreas Gohr * Return a list of backlinks 353dd87735dSAndreas Gohr * 3540ff4031cSAndreas Gohr * @param string $page page id 355*fe9f11e2SAndreas Gohr * @return string[] 356dd87735dSAndreas Gohr */ 3570ff4031cSAndreas Gohr public function listBackLinks($page) 358dd87735dSAndreas Gohr { 3590ff4031cSAndreas Gohr return ft_backlinks($this->resolvePageId($page)); 360dd87735dSAndreas Gohr } 361dd87735dSAndreas Gohr 362dd87735dSAndreas Gohr /** 363dd87735dSAndreas Gohr * Return some basic data about a page 364dd87735dSAndreas Gohr * 3650ff4031cSAndreas Gohr * @param string $page page id 366dd87735dSAndreas Gohr * @param string|int $rev revision timestamp or empty string 367dd87735dSAndreas Gohr * @return array 368dd87735dSAndreas Gohr * @throws AccessDeniedException no access for page 369dd87735dSAndreas Gohr * @throws RemoteException page not exist 370dd87735dSAndreas Gohr */ 3710ff4031cSAndreas Gohr public function pageInfo($page, $rev = '') 372dd87735dSAndreas Gohr { 3730ff4031cSAndreas Gohr $page = $this->resolvePageId($page); 3740ff4031cSAndreas Gohr if (auth_quickaclcheck($page) < AUTH_READ) { 375dd87735dSAndreas Gohr throw new AccessDeniedException('You are not allowed to read this page', 111); 376dd87735dSAndreas Gohr } 3770ff4031cSAndreas Gohr $file = wikiFN($page, $rev); 378dd87735dSAndreas Gohr $time = @filemtime($file); 379dd87735dSAndreas Gohr if (!$time) { 380dd87735dSAndreas Gohr throw new RemoteException('The requested page does not exist', 121); 381dd87735dSAndreas Gohr } 382dd87735dSAndreas Gohr 383dd87735dSAndreas Gohr // set revision to current version if empty, use revision otherwise 384dd87735dSAndreas Gohr // as the timestamps of old files are not necessarily correct 385dd87735dSAndreas Gohr if ($rev === '') { 386dd87735dSAndreas Gohr $rev = $time; 387dd87735dSAndreas Gohr } 388dd87735dSAndreas Gohr 3890ff4031cSAndreas Gohr $pagelog = new PageChangeLog($page, 1024); 390dd87735dSAndreas Gohr $info = $pagelog->getRevisionInfo($rev); 391dd87735dSAndreas Gohr 392104a3b7cSAndreas Gohr $data = [ 3930ff4031cSAndreas Gohr 'name' => $page, 394dd87735dSAndreas Gohr 'lastModified' => $this->api->toDate($rev), 395104a3b7cSAndreas Gohr 'author' => is_array($info) ? ($info['user'] ?: $info['ip']) : null, 396dd87735dSAndreas Gohr 'version' => $rev 397104a3b7cSAndreas Gohr ]; 398dd87735dSAndreas Gohr 399dd87735dSAndreas Gohr return ($data); 400dd87735dSAndreas Gohr } 401dd87735dSAndreas Gohr 402dd87735dSAndreas Gohr /** 403dd87735dSAndreas Gohr * Save a wiki page 404dd87735dSAndreas Gohr * 405*fe9f11e2SAndreas Gohr * Saves the given wiki text to the given page. If the page does not exist, it will be created. 406*fe9f11e2SAndreas Gohr * 407*fe9f11e2SAndreas Gohr * You need write permissions for the given page. 408*fe9f11e2SAndreas Gohr * 4090ff4031cSAndreas Gohr * @param string $page page id 410dd87735dSAndreas Gohr * @param string $text wiki text 411dd87735dSAndreas Gohr * @param array $params parameters: summary, minor edit 412dd87735dSAndreas Gohr * @return bool 413dd87735dSAndreas Gohr * @throws AccessDeniedException no write access for page 414dd87735dSAndreas Gohr * @throws RemoteException no id, empty new page or locked 415104a3b7cSAndreas Gohr * @author Michael Klier <chi@chimeric.de> 416dd87735dSAndreas Gohr */ 4170ff4031cSAndreas Gohr public function putPage($page, $text, $params = []) 418dd87735dSAndreas Gohr { 419dd87735dSAndreas Gohr global $TEXT; 420dd87735dSAndreas Gohr global $lang; 421dd87735dSAndreas Gohr 4220ff4031cSAndreas Gohr $page = $this->resolvePageId($page); 423dd87735dSAndreas Gohr $TEXT = cleanText($text); 42453585189SAndreas Gohr $sum = $params['sum'] ?? ''; 42553585189SAndreas Gohr $minor = $params['minor'] ?? false; 426dd87735dSAndreas Gohr 4270ff4031cSAndreas Gohr if (empty($page)) { 428dd87735dSAndreas Gohr throw new RemoteException('Empty page ID', 131); 429dd87735dSAndreas Gohr } 430dd87735dSAndreas Gohr 4310ff4031cSAndreas Gohr if (!page_exists($page) && trim($TEXT) == '') { 432dd87735dSAndreas Gohr throw new RemoteException('Refusing to write an empty new wiki page', 132); 433dd87735dSAndreas Gohr } 434dd87735dSAndreas Gohr 4350ff4031cSAndreas Gohr if (auth_quickaclcheck($page) < AUTH_EDIT) { 436dd87735dSAndreas Gohr throw new AccessDeniedException('You are not allowed to edit this page', 112); 437dd87735dSAndreas Gohr } 438dd87735dSAndreas Gohr 439dd87735dSAndreas Gohr // Check, if page is locked 4400ff4031cSAndreas Gohr if (checklock($page)) { 441dd87735dSAndreas Gohr throw new RemoteException('The page is currently locked', 133); 442dd87735dSAndreas Gohr } 443dd87735dSAndreas Gohr 444dd87735dSAndreas Gohr // SPAM check 445dd87735dSAndreas Gohr if (checkwordblock()) { 446dd87735dSAndreas Gohr throw new RemoteException('Positive wordblock check', 134); 447dd87735dSAndreas Gohr } 448dd87735dSAndreas Gohr 449dd87735dSAndreas Gohr // autoset summary on new pages 4500ff4031cSAndreas Gohr if (!page_exists($page) && empty($sum)) { 451dd87735dSAndreas Gohr $sum = $lang['created']; 452dd87735dSAndreas Gohr } 453dd87735dSAndreas Gohr 454dd87735dSAndreas Gohr // autoset summary on deleted pages 4550ff4031cSAndreas Gohr if (page_exists($page) && empty($TEXT) && empty($sum)) { 456dd87735dSAndreas Gohr $sum = $lang['deleted']; 457dd87735dSAndreas Gohr } 458dd87735dSAndreas Gohr 4590ff4031cSAndreas Gohr lock($page); 460dd87735dSAndreas Gohr 4610ff4031cSAndreas Gohr saveWikiText($page, $TEXT, $sum, $minor); 462dd87735dSAndreas Gohr 4630ff4031cSAndreas Gohr unlock($page); 464dd87735dSAndreas Gohr 465dd87735dSAndreas Gohr // run the indexer if page wasn't indexed yet 4660ff4031cSAndreas Gohr idx_addPage($page); 467dd87735dSAndreas Gohr 468dd87735dSAndreas Gohr return true; 469dd87735dSAndreas Gohr } 470dd87735dSAndreas Gohr 471dd87735dSAndreas Gohr /** 472*fe9f11e2SAndreas Gohr * Appends text to the end of a wiki page 473*fe9f11e2SAndreas Gohr * 474*fe9f11e2SAndreas Gohr * If the page does not exist, it will be created. The call will create a new page revision. 475*fe9f11e2SAndreas Gohr * 476*fe9f11e2SAndreas Gohr * You need write permissions for the given page. 477dd87735dSAndreas Gohr * 4780ff4031cSAndreas Gohr * @param string $page page id 479dd87735dSAndreas Gohr * @param string $text wiki text 480dd87735dSAndreas Gohr * @param array $params such as summary,minor 481dd87735dSAndreas Gohr * @return bool|string 482dd87735dSAndreas Gohr * @throws RemoteException 483dd87735dSAndreas Gohr */ 4840ff4031cSAndreas Gohr public function appendPage($page, $text, $params = []) 485dd87735dSAndreas Gohr { 4860ff4031cSAndreas Gohr $currentpage = $this->rawPage($page); 487dd87735dSAndreas Gohr if (!is_string($currentpage)) { 488dd87735dSAndreas Gohr return $currentpage; 489dd87735dSAndreas Gohr } 4900ff4031cSAndreas Gohr return $this->putPage($page, $currentpage . $text, $params); 491dd87735dSAndreas Gohr } 492dd87735dSAndreas Gohr 493dd87735dSAndreas Gohr /** 494*fe9f11e2SAndreas Gohr * Create a new user 4950e0fd3b7SMichael Wegener * 496*fe9f11e2SAndreas Gohr * If no password is provided, a password is auto generated. 4970e0fd3b7SMichael Wegener * 498*fe9f11e2SAndreas Gohr * You need to be a superuser to create users. 4990e0fd3b7SMichael Wegener * 500*fe9f11e2SAndreas Gohr * @param array[] $userStruct User struct with user, password, name, mail, groups, notify 501*fe9f11e2SAndreas Gohr * @return boolean Was the user successfully created? 5020e0fd3b7SMichael Wegener * @throws AccessDeniedException 5030e0fd3b7SMichael Wegener * @throws RemoteException 5040e0fd3b7SMichael Wegener */ 505f0e32bb9SMichael Wegener public function createUser($userStruct) 5060e0fd3b7SMichael Wegener { 5070e0fd3b7SMichael Wegener if (!auth_isadmin()) { 5080e0fd3b7SMichael Wegener throw new AccessDeniedException('Only admins are allowed to create users', 114); 5090e0fd3b7SMichael Wegener } 5100e0fd3b7SMichael Wegener 511104a3b7cSAndreas Gohr /** @var AuthPlugin $auth */ 5120e0fd3b7SMichael Wegener global $auth; 5130e0fd3b7SMichael Wegener 5140e0fd3b7SMichael Wegener if (!$auth->canDo('addUser')) { 5150e0fd3b7SMichael Wegener throw new AccessDeniedException( 5160e0fd3b7SMichael Wegener sprintf('Authentication backend %s can\'t do addUser', $auth->getPluginName()), 5170e0fd3b7SMichael Wegener 114 5180e0fd3b7SMichael Wegener ); 5190e0fd3b7SMichael Wegener } 5200e0fd3b7SMichael Wegener 521f0e32bb9SMichael Wegener $user = trim($auth->cleanUser($userStruct['user'] ?? '')); 522f0e32bb9SMichael Wegener $password = $userStruct['password'] ?? ''; 523f0e32bb9SMichael Wegener $name = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/', '', $userStruct['name'] ?? '')); 524f0e32bb9SMichael Wegener $mail = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/', '', $userStruct['mail'] ?? '')); 525f0e32bb9SMichael Wegener $groups = $userStruct['groups'] ?? []; 5260e0fd3b7SMichael Wegener 527d95846aaSAndreas Gohr $notify = (bool)($userStruct['notify'] ?? false); 528f0e32bb9SMichael Wegener 529b1d4a667SAndreas Gohr if ($user === '') throw new RemoteException('empty or invalid user', 401); 530b1d4a667SAndreas Gohr if ($name === '') throw new RemoteException('empty or invalid user name', 402); 531b1d4a667SAndreas Gohr if (!mail_isvalid($mail)) throw new RemoteException('empty or invalid mail address', 403); 532f0e32bb9SMichael Wegener 533104a3b7cSAndreas Gohr if ((string)$password === '') { 534f0e32bb9SMichael Wegener $password = auth_pwgen($user); 535f0e32bb9SMichael Wegener } 536f0e32bb9SMichael Wegener 537104a3b7cSAndreas Gohr if (!is_array($groups) || $groups === []) { 538f0e32bb9SMichael Wegener $groups = null; 539f0e32bb9SMichael Wegener } 540f0e32bb9SMichael Wegener 541104a3b7cSAndreas Gohr $ok = $auth->triggerUserMod('create', [$user, $password, $name, $mail, $groups]); 542f0e32bb9SMichael Wegener 543f0e32bb9SMichael Wegener if ($ok !== false && $ok !== null) { 544f0e32bb9SMichael Wegener $ok = true; 545f0e32bb9SMichael Wegener } 546f0e32bb9SMichael Wegener 5470e0fd3b7SMichael Wegener if ($ok) { 548f0e32bb9SMichael Wegener if ($notify) { 549f0e32bb9SMichael Wegener auth_sendPassword($user, $password); 5500e0fd3b7SMichael Wegener } 5510e0fd3b7SMichael Wegener } 552f0e32bb9SMichael Wegener 553f0e32bb9SMichael Wegener return $ok; 5540e0fd3b7SMichael Wegener } 5550e0fd3b7SMichael Wegener 5560e0fd3b7SMichael Wegener 5570e0fd3b7SMichael Wegener /** 5588eb28c6dSAndreas Gohr * Remove one or more users from the list of registered users 5598eb28c6dSAndreas Gohr * 560*fe9f11e2SAndreas Gohr * You need to be a superuser to delete users. 561*fe9f11e2SAndreas Gohr * 5628eb28c6dSAndreas Gohr * @param string[] $usernames List of usernames to remove 563*fe9f11e2SAndreas Gohr * @return bool if the users were successfully deleted 5648eb28c6dSAndreas Gohr * @throws AccessDeniedException 5658eb28c6dSAndreas Gohr */ 5668eb28c6dSAndreas Gohr public function deleteUsers($usernames) 5678eb28c6dSAndreas Gohr { 5688eb28c6dSAndreas Gohr if (!auth_isadmin()) { 5698eb28c6dSAndreas Gohr throw new AccessDeniedException('Only admins are allowed to delete users', 114); 5708eb28c6dSAndreas Gohr } 571104a3b7cSAndreas Gohr /** @var AuthPlugin $auth */ 5728eb28c6dSAndreas Gohr global $auth; 573104a3b7cSAndreas Gohr return (bool)$auth->triggerUserMod('delete', [$usernames]); 5748eb28c6dSAndreas Gohr } 5758eb28c6dSAndreas Gohr 5768eb28c6dSAndreas Gohr /** 577*fe9f11e2SAndreas Gohr * Uploads a file to the wiki 578dd87735dSAndreas Gohr * 5790ff4031cSAndreas Gohr * @param string $media media id 580*fe9f11e2SAndreas Gohr * @param string $data file contents 581dd87735dSAndreas Gohr * @param array $params such as overwrite 582dd87735dSAndreas Gohr * @return false|string 583dd87735dSAndreas Gohr * @throws RemoteException 584*fe9f11e2SAndreas Gohr * @author Michael Klier <chi@chimeric.de> 585dd87735dSAndreas Gohr */ 586*fe9f11e2SAndreas Gohr public function putAttachment($media, $data, $params = []) 587dd87735dSAndreas Gohr { 5880ff4031cSAndreas Gohr $media = cleanID($media); 5890ff4031cSAndreas Gohr $auth = auth_quickaclcheck(getNS($media) . ':*'); 590dd87735dSAndreas Gohr 5910ff4031cSAndreas Gohr if (!isset($media)) { 592dd87735dSAndreas Gohr throw new RemoteException('Filename not given.', 231); 593dd87735dSAndreas Gohr } 594dd87735dSAndreas Gohr 595dd87735dSAndreas Gohr global $conf; 596dd87735dSAndreas Gohr 5970ff4031cSAndreas Gohr $ftmp = $conf['tmpdir'] . '/' . md5($media . clientIP()); 598dd87735dSAndreas Gohr 599dd87735dSAndreas Gohr // save temporary file 600dd87735dSAndreas Gohr @unlink($ftmp); 601*fe9f11e2SAndreas Gohr io_saveFile($ftmp, $data); 602dd87735dSAndreas Gohr 6030ff4031cSAndreas Gohr $res = media_save(['name' => $ftmp], $media, $params['ow'], $auth, 'rename'); 604dd87735dSAndreas Gohr if (is_array($res)) { 605dd87735dSAndreas Gohr throw new RemoteException($res[0], -$res[1]); 606dd87735dSAndreas Gohr } else { 607dd87735dSAndreas Gohr return $res; 608dd87735dSAndreas Gohr } 609dd87735dSAndreas Gohr } 610dd87735dSAndreas Gohr 611dd87735dSAndreas Gohr /** 612*fe9f11e2SAndreas Gohr * Deletes a file from the wiki 613*fe9f11e2SAndreas Gohr * 614*fe9f11e2SAndreas Gohr * You need to have delete permissions for the file. 615dd87735dSAndreas Gohr * 6160ff4031cSAndreas Gohr * @param string $media media id 617dd87735dSAndreas Gohr * @return int 618dd87735dSAndreas Gohr * @throws AccessDeniedException no permissions 619dd87735dSAndreas Gohr * @throws RemoteException file in use or not deleted 620104a3b7cSAndreas Gohr * @author Gina Haeussge <osd@foosel.net> 621104a3b7cSAndreas Gohr * 622dd87735dSAndreas Gohr */ 6230ff4031cSAndreas Gohr public function deleteAttachment($media) 624dd87735dSAndreas Gohr { 6250ff4031cSAndreas Gohr $media = cleanID($media); 6260ff4031cSAndreas Gohr $auth = auth_quickaclcheck(getNS($media) . ':*'); 6270ff4031cSAndreas Gohr $res = media_delete($media, $auth); 628dd87735dSAndreas Gohr if ($res & DOKU_MEDIA_DELETED) { 629dd87735dSAndreas Gohr return 0; 630dd87735dSAndreas Gohr } elseif ($res & DOKU_MEDIA_NOT_AUTH) { 631dd87735dSAndreas Gohr throw new AccessDeniedException('You don\'t have permissions to delete files.', 212); 632dd87735dSAndreas Gohr } elseif ($res & DOKU_MEDIA_INUSE) { 633dd87735dSAndreas Gohr throw new RemoteException('File is still referenced', 232); 634dd87735dSAndreas Gohr } else { 635dd87735dSAndreas Gohr throw new RemoteException('Could not delete file', 233); 636dd87735dSAndreas Gohr } 637dd87735dSAndreas Gohr } 638dd87735dSAndreas Gohr 639dd87735dSAndreas Gohr /** 640dd87735dSAndreas Gohr * Returns the permissions of a given wiki page for the current user or another user 641dd87735dSAndreas Gohr * 6420ff4031cSAndreas Gohr * @param string $page page id 643dd87735dSAndreas Gohr * @param string|null $user username 644dd87735dSAndreas Gohr * @param array|null $groups array of groups 645dd87735dSAndreas Gohr * @return int permission level 646dd87735dSAndreas Gohr */ 6470ff4031cSAndreas Gohr public function aclCheck($page, $user = null, $groups = null) 648dd87735dSAndreas Gohr { 649104a3b7cSAndreas Gohr /** @var AuthPlugin $auth */ 650dd87735dSAndreas Gohr global $auth; 651dd87735dSAndreas Gohr 6520ff4031cSAndreas Gohr $page = $this->resolvePageId($page); 653dd87735dSAndreas Gohr if ($user === null) { 6540ff4031cSAndreas Gohr return auth_quickaclcheck($page); 655dd87735dSAndreas Gohr } else { 656dd87735dSAndreas Gohr if ($groups === null) { 657dd87735dSAndreas Gohr $userinfo = $auth->getUserData($user); 658dd87735dSAndreas Gohr if ($userinfo === false) { 659104a3b7cSAndreas Gohr $groups = []; 660dd87735dSAndreas Gohr } else { 661dd87735dSAndreas Gohr $groups = $userinfo['grps']; 662dd87735dSAndreas Gohr } 663dd87735dSAndreas Gohr } 6640ff4031cSAndreas Gohr return auth_aclcheck($page, $user, $groups); 665dd87735dSAndreas Gohr } 666dd87735dSAndreas Gohr } 667dd87735dSAndreas Gohr 668dd87735dSAndreas Gohr /** 669dd87735dSAndreas Gohr * Lists all links contained in a wiki page 670dd87735dSAndreas Gohr * 6710ff4031cSAndreas Gohr * @param string $page page id 672dd87735dSAndreas Gohr * @return array 673dd87735dSAndreas Gohr * @throws AccessDeniedException no read access for page 674104a3b7cSAndreas Gohr * @author Michael Klier <chi@chimeric.de> 675104a3b7cSAndreas Gohr * 676dd87735dSAndreas Gohr */ 6770ff4031cSAndreas Gohr public function listLinks($page) 678dd87735dSAndreas Gohr { 6790ff4031cSAndreas Gohr $page = $this->resolvePageId($page); 6800ff4031cSAndreas Gohr if (auth_quickaclcheck($page) < AUTH_READ) { 681dd87735dSAndreas Gohr throw new AccessDeniedException('You are not allowed to read this page', 111); 682dd87735dSAndreas Gohr } 683104a3b7cSAndreas Gohr $links = []; 684dd87735dSAndreas Gohr 685dd87735dSAndreas Gohr // resolve page instructions 6860ff4031cSAndreas Gohr $ins = p_cached_instructions(wikiFN($page)); 687dd87735dSAndreas Gohr 688dd87735dSAndreas Gohr // instantiate new Renderer - needed for interwiki links 689dd87735dSAndreas Gohr $Renderer = new Doku_Renderer_xhtml(); 690dd87735dSAndreas Gohr $Renderer->interwiki = getInterwiki(); 691dd87735dSAndreas Gohr 692dd87735dSAndreas Gohr // parse parse instructions 693dd87735dSAndreas Gohr foreach ($ins as $in) { 694104a3b7cSAndreas Gohr $link = []; 695dd87735dSAndreas Gohr switch ($in[0]) { 696dd87735dSAndreas Gohr case 'internallink': 697dd87735dSAndreas Gohr $link['type'] = 'local'; 698dd87735dSAndreas Gohr $link['page'] = $in[1][0]; 699dd87735dSAndreas Gohr $link['href'] = wl($in[1][0]); 700104a3b7cSAndreas Gohr $links[] = $link; 701dd87735dSAndreas Gohr break; 702dd87735dSAndreas Gohr case 'externallink': 703dd87735dSAndreas Gohr $link['type'] = 'extern'; 704dd87735dSAndreas Gohr $link['page'] = $in[1][0]; 705dd87735dSAndreas Gohr $link['href'] = $in[1][0]; 706104a3b7cSAndreas Gohr $links[] = $link; 707dd87735dSAndreas Gohr break; 708dd87735dSAndreas Gohr case 'interwikilink': 709dd87735dSAndreas Gohr $url = $Renderer->_resolveInterWiki($in[1][2], $in[1][3]); 710dd87735dSAndreas Gohr $link['type'] = 'extern'; 711dd87735dSAndreas Gohr $link['page'] = $url; 712dd87735dSAndreas Gohr $link['href'] = $url; 713104a3b7cSAndreas Gohr $links[] = $link; 714dd87735dSAndreas Gohr break; 715dd87735dSAndreas Gohr } 716dd87735dSAndreas Gohr } 717dd87735dSAndreas Gohr 718dd87735dSAndreas Gohr return ($links); 719dd87735dSAndreas Gohr } 720dd87735dSAndreas Gohr 721dd87735dSAndreas Gohr /** 7228a9282a2SAndreas Gohr * Returns a list of recent changes since given timestamp 723dd87735dSAndreas Gohr * 724*fe9f11e2SAndreas Gohr * The results are limited to date range configured in $conf['recent'] 725*fe9f11e2SAndreas Gohr * 726*fe9f11e2SAndreas Gohr * @link https://www.dokuwiki.org/config:recent 727dd87735dSAndreas Gohr * @param int $timestamp unix timestamp 728dd87735dSAndreas Gohr * @return array 729dd87735dSAndreas Gohr * @throws RemoteException no valid timestamp 730104a3b7cSAndreas Gohr * @author Michael Klier <chi@chimeric.de> 731104a3b7cSAndreas Gohr * 732104a3b7cSAndreas Gohr * @author Michael Hamann <michael@content-space.de> 733dd87735dSAndreas Gohr */ 734dd87735dSAndreas Gohr public function getRecentChanges($timestamp) 735dd87735dSAndreas Gohr { 736dd87735dSAndreas Gohr if (strlen($timestamp) != 10) { 737dd87735dSAndreas Gohr throw new RemoteException('The provided value is not a valid timestamp', 311); 738dd87735dSAndreas Gohr } 739dd87735dSAndreas Gohr 740dd87735dSAndreas Gohr $recents = getRecentsSince($timestamp); 741dd87735dSAndreas Gohr 742104a3b7cSAndreas Gohr $changes = []; 743dd87735dSAndreas Gohr 744dd87735dSAndreas Gohr foreach ($recents as $recent) { 745104a3b7cSAndreas Gohr $change = []; 746dd87735dSAndreas Gohr $change['name'] = $recent['id']; 747dd87735dSAndreas Gohr $change['lastModified'] = $this->api->toDate($recent['date']); 748dd87735dSAndreas Gohr $change['author'] = $recent['user']; 749dd87735dSAndreas Gohr $change['version'] = $recent['date']; 750dd87735dSAndreas Gohr $change['perms'] = $recent['perms']; 751dd87735dSAndreas Gohr $change['size'] = @filesize(wikiFN($recent['id'])); 752104a3b7cSAndreas Gohr $changes[] = $change; 753dd87735dSAndreas Gohr } 754dd87735dSAndreas Gohr 755104a3b7cSAndreas Gohr if ($changes !== []) { 756dd87735dSAndreas Gohr return $changes; 757dd87735dSAndreas Gohr } else { 758dd87735dSAndreas Gohr // in case we still have nothing at this point 759dd87735dSAndreas Gohr throw new RemoteException('There are no changes in the specified timeframe', 321); 760dd87735dSAndreas Gohr } 761dd87735dSAndreas Gohr } 762dd87735dSAndreas Gohr 763dd87735dSAndreas Gohr /** 7648a9282a2SAndreas Gohr * Returns a list of recent media changes since given timestamp 765dd87735dSAndreas Gohr * 766dd87735dSAndreas Gohr * @param int $timestamp unix timestamp 767dd87735dSAndreas Gohr * @return array 768dd87735dSAndreas Gohr * @throws RemoteException no valid timestamp 769104a3b7cSAndreas Gohr * @author Michael Klier <chi@chimeric.de> 770104a3b7cSAndreas Gohr * 771104a3b7cSAndreas Gohr * @author Michael Hamann <michael@content-space.de> 772dd87735dSAndreas Gohr */ 773dd87735dSAndreas Gohr public function getRecentMediaChanges($timestamp) 774dd87735dSAndreas Gohr { 775dd87735dSAndreas Gohr if (strlen($timestamp) != 10) 776dd87735dSAndreas Gohr throw new RemoteException('The provided value is not a valid timestamp', 311); 777dd87735dSAndreas Gohr 778dd87735dSAndreas Gohr $recents = getRecentsSince($timestamp, null, '', RECENTS_MEDIA_CHANGES); 779dd87735dSAndreas Gohr 780104a3b7cSAndreas Gohr $changes = []; 781dd87735dSAndreas Gohr 782dd87735dSAndreas Gohr foreach ($recents as $recent) { 783104a3b7cSAndreas Gohr $change = []; 784dd87735dSAndreas Gohr $change['name'] = $recent['id']; 785dd87735dSAndreas Gohr $change['lastModified'] = $this->api->toDate($recent['date']); 786dd87735dSAndreas Gohr $change['author'] = $recent['user']; 787dd87735dSAndreas Gohr $change['version'] = $recent['date']; 788dd87735dSAndreas Gohr $change['perms'] = $recent['perms']; 789dd87735dSAndreas Gohr $change['size'] = @filesize(mediaFN($recent['id'])); 790104a3b7cSAndreas Gohr $changes[] = $change; 791dd87735dSAndreas Gohr } 792dd87735dSAndreas Gohr 793104a3b7cSAndreas Gohr if ($changes !== []) { 794dd87735dSAndreas Gohr return $changes; 795dd87735dSAndreas Gohr } else { 796dd87735dSAndreas Gohr // in case we still have nothing at this point 797dd87735dSAndreas Gohr throw new RemoteException('There are no changes in the specified timeframe', 321); 798dd87735dSAndreas Gohr } 799dd87735dSAndreas Gohr } 800dd87735dSAndreas Gohr 801dd87735dSAndreas Gohr /** 802dd87735dSAndreas Gohr * Returns a list of available revisions of a given wiki page 803dd87735dSAndreas Gohr * Number of returned pages is set by $conf['recent'] 804dd87735dSAndreas Gohr * However not accessible pages are skipped, so less than $conf['recent'] could be returned 805dd87735dSAndreas Gohr * 8060ff4031cSAndreas Gohr * @param string $page page id 807dd87735dSAndreas Gohr * @param int $first skip the first n changelog lines 808dd87735dSAndreas Gohr * 0 = from current(if exists) 809dd87735dSAndreas Gohr * 1 = from 1st old rev 810dd87735dSAndreas Gohr * 2 = from 2nd old rev, etc 811dd87735dSAndreas Gohr * @return array 812dd87735dSAndreas Gohr * @throws AccessDeniedException no read access for page 813dd87735dSAndreas Gohr * @throws RemoteException empty id 814104a3b7cSAndreas Gohr * @author Michael Klier <chi@chimeric.de> 815104a3b7cSAndreas Gohr * 816dd87735dSAndreas Gohr */ 8170ff4031cSAndreas Gohr public function pageVersions($page, $first = 0) 818dd87735dSAndreas Gohr { 8190ff4031cSAndreas Gohr $page = $this->resolvePageId($page); 8200ff4031cSAndreas Gohr if (auth_quickaclcheck($page) < AUTH_READ) { 821dd87735dSAndreas Gohr throw new AccessDeniedException('You are not allowed to read this page', 111); 822dd87735dSAndreas Gohr } 823dd87735dSAndreas Gohr global $conf; 824dd87735dSAndreas Gohr 825104a3b7cSAndreas Gohr $versions = []; 826dd87735dSAndreas Gohr 8270ff4031cSAndreas Gohr if (empty($page)) { 828dd87735dSAndreas Gohr throw new RemoteException('Empty page ID', 131); 829dd87735dSAndreas Gohr } 830dd87735dSAndreas Gohr 831dd87735dSAndreas Gohr $first = (int)$first; 832dd87735dSAndreas Gohr $first_rev = $first - 1; 833efc3ac2fSsplitbrain $first_rev = max(0, $first_rev); 834104a3b7cSAndreas Gohr 8350ff4031cSAndreas Gohr $pagelog = new PageChangeLog($page); 836dd87735dSAndreas Gohr $revisions = $pagelog->getRevisions($first_rev, $conf['recent']); 837dd87735dSAndreas Gohr 838dd87735dSAndreas Gohr if ($first == 0) { 839dd87735dSAndreas Gohr array_unshift($revisions, ''); // include current revision 840dd87735dSAndreas Gohr if (count($revisions) > $conf['recent']) { 841dd87735dSAndreas Gohr array_pop($revisions); // remove extra log entry 842dd87735dSAndreas Gohr } 843dd87735dSAndreas Gohr } 844dd87735dSAndreas Gohr 845dd87735dSAndreas Gohr if (!empty($revisions)) { 846dd87735dSAndreas Gohr foreach ($revisions as $rev) { 8470ff4031cSAndreas Gohr $file = wikiFN($page, $rev); 848dd87735dSAndreas Gohr $time = @filemtime($file); 849dd87735dSAndreas Gohr // we check if the page actually exists, if this is not the 850dd87735dSAndreas Gohr // case this can lead to less pages being returned than 851dd87735dSAndreas Gohr // specified via $conf['recent'] 852dd87735dSAndreas Gohr if ($time) { 853dd87735dSAndreas Gohr $pagelog->setChunkSize(1024); 854104a3b7cSAndreas Gohr $info = $pagelog->getRevisionInfo($rev ?: $time); 855dd87735dSAndreas Gohr if (!empty($info)) { 856104a3b7cSAndreas Gohr $data = []; 857dd87735dSAndreas Gohr $data['user'] = $info['user']; 858dd87735dSAndreas Gohr $data['ip'] = $info['ip']; 859dd87735dSAndreas Gohr $data['type'] = $info['type']; 860dd87735dSAndreas Gohr $data['sum'] = $info['sum']; 861dd87735dSAndreas Gohr $data['modified'] = $this->api->toDate($info['date']); 862dd87735dSAndreas Gohr $data['version'] = $info['date']; 863104a3b7cSAndreas Gohr $versions[] = $data; 864dd87735dSAndreas Gohr } 865dd87735dSAndreas Gohr } 866dd87735dSAndreas Gohr } 867dd87735dSAndreas Gohr return $versions; 868dd87735dSAndreas Gohr } else { 869104a3b7cSAndreas Gohr return []; 870dd87735dSAndreas Gohr } 871dd87735dSAndreas Gohr } 872dd87735dSAndreas Gohr 873dd87735dSAndreas Gohr /** 874dd87735dSAndreas Gohr * The version of Wiki RPC API supported 87542e66c7aSAndreas Gohr * 87642e66c7aSAndreas Gohr * This is the version of the Wiki RPC specification implemented. Since that specification 87742e66c7aSAndreas Gohr * is no longer maintained, this will always return 2 87842e66c7aSAndreas Gohr * 87942e66c7aSAndreas Gohr * You probably want to look at dokuwiki.getXMLRPCAPIVersion instead 88042e66c7aSAndreas Gohr * 88142e66c7aSAndreas Gohr * @return int 882dd87735dSAndreas Gohr */ 883dd87735dSAndreas Gohr public function wikiRpcVersion() 884dd87735dSAndreas Gohr { 885dd87735dSAndreas Gohr return 2; 886dd87735dSAndreas Gohr } 887dd87735dSAndreas Gohr 888dd87735dSAndreas Gohr /** 889dd87735dSAndreas Gohr * Locks or unlocks a given batch of pages 890dd87735dSAndreas Gohr * 891dd87735dSAndreas Gohr * Give an associative array with two keys: lock and unlock. Both should contain a 892dd87735dSAndreas Gohr * list of pages to lock or unlock 893dd87735dSAndreas Gohr * 894dd87735dSAndreas Gohr * Returns an associative array with the keys locked, lockfail, unlocked and 895dd87735dSAndreas Gohr * unlockfail, each containing lists of pages. 896dd87735dSAndreas Gohr * 8978a9282a2SAndreas Gohr * @param array[] $set list pages with ['lock' => [], 'unlock' => []] 8988a9282a2SAndreas Gohr * @return array[] list of pages with ['locked' => [], 'lockfail' => [], 'unlocked' => [], 'unlockfail' => []] 899dd87735dSAndreas Gohr */ 900dd87735dSAndreas Gohr public function setLocks($set) 901dd87735dSAndreas Gohr { 902104a3b7cSAndreas Gohr $locked = []; 903104a3b7cSAndreas Gohr $lockfail = []; 904104a3b7cSAndreas Gohr $unlocked = []; 905104a3b7cSAndreas Gohr $unlockfail = []; 906dd87735dSAndreas Gohr 907104a3b7cSAndreas Gohr foreach ($set['lock'] as $id) { 908dd87735dSAndreas Gohr $id = $this->resolvePageId($id); 909dd87735dSAndreas Gohr if (auth_quickaclcheck($id) < AUTH_EDIT || checklock($id)) { 910dd87735dSAndreas Gohr $lockfail[] = $id; 911dd87735dSAndreas Gohr } else { 912dd87735dSAndreas Gohr lock($id); 913dd87735dSAndreas Gohr $locked[] = $id; 914dd87735dSAndreas Gohr } 915dd87735dSAndreas Gohr } 916dd87735dSAndreas Gohr 917104a3b7cSAndreas Gohr foreach ($set['unlock'] as $id) { 918dd87735dSAndreas Gohr $id = $this->resolvePageId($id); 919dd87735dSAndreas Gohr if (auth_quickaclcheck($id) < AUTH_EDIT || !unlock($id)) { 920dd87735dSAndreas Gohr $unlockfail[] = $id; 921dd87735dSAndreas Gohr } else { 922dd87735dSAndreas Gohr $unlocked[] = $id; 923dd87735dSAndreas Gohr } 924dd87735dSAndreas Gohr } 925dd87735dSAndreas Gohr 926104a3b7cSAndreas Gohr return [ 927dd87735dSAndreas Gohr 'locked' => $locked, 928dd87735dSAndreas Gohr 'lockfail' => $lockfail, 929dd87735dSAndreas Gohr 'unlocked' => $unlocked, 930104a3b7cSAndreas Gohr 'unlockfail' => $unlockfail 931104a3b7cSAndreas Gohr ]; 932dd87735dSAndreas Gohr } 933dd87735dSAndreas Gohr 934dd87735dSAndreas Gohr /** 9358a9282a2SAndreas Gohr * Return the API version 9368a9282a2SAndreas Gohr * 9378a9282a2SAndreas Gohr * This is the version of the DokuWiki API. It increases whenever the API definition changes. 9388a9282a2SAndreas Gohr * 9398a9282a2SAndreas Gohr * When developing a client, you should check this version and make sure you can handle it. 940dd87735dSAndreas Gohr * 941dd87735dSAndreas Gohr * @return int 942dd87735dSAndreas Gohr */ 943dd87735dSAndreas Gohr public function getAPIVersion() 944dd87735dSAndreas Gohr { 945dd87735dSAndreas Gohr return self::API_VERSION; 946dd87735dSAndreas Gohr } 947dd87735dSAndreas Gohr 948dd87735dSAndreas Gohr /** 949dd87735dSAndreas Gohr * Login 950dd87735dSAndreas Gohr * 9518a9282a2SAndreas Gohr * This will use the given credentials and attempt to login the user. This will set the 9528a9282a2SAndreas Gohr * appropriate cookies, which can be used for subsequent requests. 9538a9282a2SAndreas Gohr * 954*fe9f11e2SAndreas Gohr * Use of this mechanism is discouraged. Using token authentication is preferred. 955*fe9f11e2SAndreas Gohr * 9568a9282a2SAndreas Gohr * @param string $user The user name 9578a9282a2SAndreas Gohr * @param string $pass The password 958*fe9f11e2SAndreas Gohr * @return int If the login was successful 959dd87735dSAndreas Gohr */ 960dd87735dSAndreas Gohr public function login($user, $pass) 961dd87735dSAndreas Gohr { 962dd87735dSAndreas Gohr global $conf; 963104a3b7cSAndreas Gohr /** @var AuthPlugin $auth */ 964dd87735dSAndreas Gohr global $auth; 965dd87735dSAndreas Gohr 966dd87735dSAndreas Gohr if (!$conf['useacl']) return 0; 9676547cfc7SGerrit Uitslag if (!$auth instanceof AuthPlugin) return 0; 968dd87735dSAndreas Gohr 969dd87735dSAndreas Gohr @session_start(); // reopen session for login 97081e99965SPhy $ok = null; 971dd87735dSAndreas Gohr if ($auth->canDo('external')) { 972dd87735dSAndreas Gohr $ok = $auth->trustExternal($user, $pass, false); 97381e99965SPhy } 97481e99965SPhy if ($ok === null) { 975104a3b7cSAndreas Gohr $evdata = [ 976dd87735dSAndreas Gohr 'user' => $user, 977dd87735dSAndreas Gohr 'password' => $pass, 978dd87735dSAndreas Gohr 'sticky' => false, 979104a3b7cSAndreas Gohr 'silent' => true 980104a3b7cSAndreas Gohr ]; 981cbb44eabSAndreas Gohr $ok = Event::createAndTrigger('AUTH_LOGIN_CHECK', $evdata, 'auth_login_wrapper'); 982dd87735dSAndreas Gohr } 983dd87735dSAndreas Gohr session_write_close(); // we're done with the session 984dd87735dSAndreas Gohr 985dd87735dSAndreas Gohr return $ok; 986dd87735dSAndreas Gohr } 987dd87735dSAndreas Gohr 988dd87735dSAndreas Gohr /** 989dd87735dSAndreas Gohr * Log off 990dd87735dSAndreas Gohr * 9918a9282a2SAndreas Gohr * Attempt to log out the current user, deleting the appropriate cookies 9928a9282a2SAndreas Gohr * 9938a9282a2SAndreas Gohr * @return int 0 on failure, 1 on success 994dd87735dSAndreas Gohr */ 995dd87735dSAndreas Gohr public function logoff() 996dd87735dSAndreas Gohr { 997dd87735dSAndreas Gohr global $conf; 998dd87735dSAndreas Gohr global $auth; 999dd87735dSAndreas Gohr if (!$conf['useacl']) return 0; 10006547cfc7SGerrit Uitslag if (!$auth instanceof AuthPlugin) return 0; 1001dd87735dSAndreas Gohr 1002dd87735dSAndreas Gohr auth_logoff(); 1003dd87735dSAndreas Gohr 1004dd87735dSAndreas Gohr return 1; 1005dd87735dSAndreas Gohr } 1006dd87735dSAndreas Gohr 1007dd87735dSAndreas Gohr /** 1008dd87735dSAndreas Gohr * Resolve page id 1009dd87735dSAndreas Gohr * 1010dd87735dSAndreas Gohr * @param string $id page id 1011dd87735dSAndreas Gohr * @return string 1012dd87735dSAndreas Gohr */ 1013dd87735dSAndreas Gohr private function resolvePageId($id) 1014dd87735dSAndreas Gohr { 1015dd87735dSAndreas Gohr $id = cleanID($id); 1016dd87735dSAndreas Gohr if (empty($id)) { 1017dd87735dSAndreas Gohr global $conf; 1018dd87735dSAndreas Gohr $id = cleanID($conf['start']); 1019dd87735dSAndreas Gohr } 1020dd87735dSAndreas Gohr return $id; 1021dd87735dSAndreas Gohr } 1022dd87735dSAndreas Gohr} 1023