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*e7323dfbSAndreas Gohr 'dokuwiki.getTime' => (new ApiCall([$this, 'time'])) 4842e66c7aSAndreas Gohr ->setSummary('Returns the current server time') 4942e66c7aSAndreas Gohr ->setReturnDescription('unix timestamp'), 5042e66c7aSAndreas Gohr 'dokuwiki.setLocks' => new ApiCall([$this, 'setLocks']), 5142e66c7aSAndreas Gohr 'dokuwiki.getTitle' => (new ApiCall([$this, 'getTitle'])) 5242e66c7aSAndreas Gohr ->setPublic(), 5342e66c7aSAndreas Gohr 'dokuwiki.appendPage' => new ApiCall([$this, 'appendPage']), 5442e66c7aSAndreas Gohr 'dokuwiki.createUser' => new ApiCall([$this, 'createUser']), 5542e66c7aSAndreas Gohr 'dokuwiki.deleteUsers' => new ApiCall([$this, 'deleteUsers']), 5642e66c7aSAndreas Gohr 'wiki.getPage' => (new ApiCall([$this, 'rawPage'])) 5742e66c7aSAndreas Gohr ->limitArgs(['id']), 5842e66c7aSAndreas Gohr 'wiki.getPageVersion' => (new ApiCall([$this, 'rawPage'])) 5942e66c7aSAndreas Gohr ->setSummary('Get a specific revision of a wiki page'), 6042e66c7aSAndreas Gohr 'wiki.getPageHTML' => (new ApiCall([$this, 'htmlPage'])) 6142e66c7aSAndreas Gohr ->limitArgs(['id']), 6242e66c7aSAndreas Gohr 'wiki.getPageHTMLVersion' => (new ApiCall([$this, 'htmlPage'])) 6342e66c7aSAndreas Gohr ->setSummary('Get the HTML for a specific revision of a wiki page'), 6442e66c7aSAndreas Gohr 'wiki.getAllPages' => new ApiCall([$this, 'listPages']), 6542e66c7aSAndreas Gohr 'wiki.getAttachments' => new ApiCall([$this, 'listAttachments']), 6642e66c7aSAndreas Gohr 'wiki.getBackLinks' => new ApiCall([$this, 'listBackLinks']), 6742e66c7aSAndreas Gohr 'wiki.getPageInfo' => (new ApiCall([$this, 'pageInfo'])) 6842e66c7aSAndreas Gohr ->limitArgs(['id']), 6942e66c7aSAndreas Gohr 'wiki.getPageInfoVersion' => (new ApiCall([$this, 'pageInfo'])) 7042e66c7aSAndreas Gohr ->setSummary('Get some basic data about a specific revison of a wiki page'), 7142e66c7aSAndreas Gohr 'wiki.getPageVersions' => new ApiCall([$this, 'pageVersions']), 7242e66c7aSAndreas Gohr 'wiki.putPage' => new ApiCall([$this, 'putPage']), 7342e66c7aSAndreas Gohr 'wiki.listLinks' => new ApiCall([$this, 'listLinks']), 7442e66c7aSAndreas Gohr 'wiki.getRecentChanges' => new ApiCall([$this, 'getRecentChanges']), 7542e66c7aSAndreas Gohr 'wiki.getRecentMediaChanges' => new ApiCall([$this, 'getRecentMediaChanges']), 7642e66c7aSAndreas Gohr 'wiki.aclCheck' => new ApiCall([$this, 'aclCheck']), 7742e66c7aSAndreas Gohr 'wiki.putAttachment' => new ApiCall([$this, 'putAttachment']), 7842e66c7aSAndreas Gohr 'wiki.deleteAttachment' => new ApiCall([$this, 'deleteAttachment']), 7942e66c7aSAndreas Gohr 'wiki.getAttachment' => new ApiCall([$this, 'getAttachment']), 8042e66c7aSAndreas Gohr 'wiki.getAttachmentInfo' => new ApiCall([$this, 'getAttachmentInfo']), 8142e66c7aSAndreas Gohr 'dokuwiki.getXMLRPCAPIVersion' => (new ApiCall([$this, 'getAPIVersion']))->setPublic(), 8242e66c7aSAndreas Gohr 'wiki.getRPCVersionSupported' => (new ApiCall([$this, 'wikiRpcVersion']))->setPublic(), 83104a3b7cSAndreas Gohr ]; 84dd87735dSAndreas Gohr } 85dd87735dSAndreas Gohr 86dd87735dSAndreas Gohr /** 87*e7323dfbSAndreas Gohr * Return the current server time 88*e7323dfbSAndreas Gohr * 89*e7323dfbSAndreas Gohr * Uses a Unix timestamp (seconds since 1970-01-01 00:00:00 UTC) 90*e7323dfbSAndreas Gohr * 91*e7323dfbSAndreas Gohr * @return int A unix timestamp 92*e7323dfbSAndreas Gohr */ 93*e7323dfbSAndreas Gohr public function time() { 94*e7323dfbSAndreas Gohr return time(); 95*e7323dfbSAndreas Gohr } 96*e7323dfbSAndreas Gohr 97*e7323dfbSAndreas Gohr /** 98dd87735dSAndreas Gohr * Return a raw wiki page 99dd87735dSAndreas Gohr * 100dd87735dSAndreas Gohr * @param string $id wiki page id 101dd87735dSAndreas Gohr * @param int|string $rev revision timestamp of the page or empty string 102dd87735dSAndreas Gohr * @return string page text. 103dd87735dSAndreas Gohr * @throws AccessDeniedException if no permission for page 104dd87735dSAndreas Gohr */ 105dd87735dSAndreas Gohr public function rawPage($id, $rev = '') 106dd87735dSAndreas Gohr { 107dd87735dSAndreas Gohr $id = $this->resolvePageId($id); 108dd87735dSAndreas Gohr if (auth_quickaclcheck($id) < AUTH_READ) { 109dd87735dSAndreas Gohr throw new AccessDeniedException('You are not allowed to read this file', 111); 110dd87735dSAndreas Gohr } 111dd87735dSAndreas Gohr $text = rawWiki($id, $rev); 112dd87735dSAndreas Gohr if (!$text) { 113dd87735dSAndreas Gohr return pageTemplate($id); 114dd87735dSAndreas Gohr } else { 115dd87735dSAndreas Gohr return $text; 116dd87735dSAndreas Gohr } 117dd87735dSAndreas Gohr } 118dd87735dSAndreas Gohr 119dd87735dSAndreas Gohr /** 120dd87735dSAndreas Gohr * Return a media file 121dd87735dSAndreas Gohr * 122dd87735dSAndreas Gohr * @param string $id file id 123dd87735dSAndreas Gohr * @return mixed media file 124dd87735dSAndreas Gohr * @throws AccessDeniedException no permission for media 125dd87735dSAndreas Gohr * @throws RemoteException not exist 126104a3b7cSAndreas Gohr * @author Gina Haeussge <osd@foosel.net> 127104a3b7cSAndreas Gohr * 128dd87735dSAndreas Gohr */ 129dd87735dSAndreas Gohr public function getAttachment($id) 130dd87735dSAndreas Gohr { 131dd87735dSAndreas Gohr $id = cleanID($id); 132dd87735dSAndreas Gohr if (auth_quickaclcheck(getNS($id) . ':*') < AUTH_READ) { 133dd87735dSAndreas Gohr throw new AccessDeniedException('You are not allowed to read this file', 211); 134dd87735dSAndreas Gohr } 135dd87735dSAndreas Gohr 136dd87735dSAndreas Gohr $file = mediaFN($id); 137dd87735dSAndreas Gohr if (!@ file_exists($file)) { 138dd87735dSAndreas Gohr throw new RemoteException('The requested file does not exist', 221); 139dd87735dSAndreas Gohr } 140dd87735dSAndreas Gohr 141dd87735dSAndreas Gohr $data = io_readFile($file, false); 142dd87735dSAndreas Gohr return $this->api->toFile($data); 143dd87735dSAndreas Gohr } 144dd87735dSAndreas Gohr 145dd87735dSAndreas Gohr /** 146dd87735dSAndreas Gohr * Return info about a media file 147dd87735dSAndreas Gohr * 148dd87735dSAndreas Gohr * @param string $id page id 149dd87735dSAndreas Gohr * @return array 150104a3b7cSAndreas Gohr * @author Gina Haeussge <osd@foosel.net> 151104a3b7cSAndreas Gohr * 152dd87735dSAndreas Gohr */ 153dd87735dSAndreas Gohr public function getAttachmentInfo($id) 154dd87735dSAndreas Gohr { 155dd87735dSAndreas Gohr $id = cleanID($id); 156104a3b7cSAndreas Gohr $info = ['lastModified' => $this->api->toDate(0), 'size' => 0]; 157dd87735dSAndreas Gohr 158dd87735dSAndreas Gohr $file = mediaFN($id); 159dd87735dSAndreas Gohr if (auth_quickaclcheck(getNS($id) . ':*') >= AUTH_READ) { 160dd87735dSAndreas Gohr if (file_exists($file)) { 161dd87735dSAndreas Gohr $info['lastModified'] = $this->api->toDate(filemtime($file)); 162dd87735dSAndreas Gohr $info['size'] = filesize($file); 163dd87735dSAndreas Gohr } else { 164dd87735dSAndreas Gohr //Is it deleted media with changelog? 165dd87735dSAndreas Gohr $medialog = new MediaChangeLog($id); 166dd87735dSAndreas Gohr $revisions = $medialog->getRevisions(0, 1); 167dd87735dSAndreas Gohr if (!empty($revisions)) { 168dd87735dSAndreas Gohr $info['lastModified'] = $this->api->toDate($revisions[0]); 169dd87735dSAndreas Gohr } 170dd87735dSAndreas Gohr } 171dd87735dSAndreas Gohr } 172dd87735dSAndreas Gohr 173dd87735dSAndreas Gohr return $info; 174dd87735dSAndreas Gohr } 175dd87735dSAndreas Gohr 176dd87735dSAndreas Gohr /** 177dd87735dSAndreas Gohr * Return a wiki page rendered to html 178dd87735dSAndreas Gohr * 179dd87735dSAndreas Gohr * @param string $id page id 180dd87735dSAndreas Gohr * @param string|int $rev revision timestamp or empty string 181dd87735dSAndreas Gohr * @return null|string html 182dd87735dSAndreas Gohr * @throws AccessDeniedException no access to page 183dd87735dSAndreas Gohr */ 184dd87735dSAndreas Gohr public function htmlPage($id, $rev = '') 185dd87735dSAndreas Gohr { 186dd87735dSAndreas Gohr $id = $this->resolvePageId($id); 187dd87735dSAndreas Gohr if (auth_quickaclcheck($id) < AUTH_READ) { 188dd87735dSAndreas Gohr throw new AccessDeniedException('You are not allowed to read this page', 111); 189dd87735dSAndreas Gohr } 190dd87735dSAndreas Gohr return p_wiki_xhtml($id, $rev, false); 191dd87735dSAndreas Gohr } 192dd87735dSAndreas Gohr 193dd87735dSAndreas Gohr /** 194dd87735dSAndreas Gohr * List all pages - we use the indexer list here 195dd87735dSAndreas Gohr * 196dd87735dSAndreas Gohr * @return array 197dd87735dSAndreas Gohr */ 198dd87735dSAndreas Gohr public function listPages() 199dd87735dSAndreas Gohr { 200104a3b7cSAndreas Gohr $list = []; 201dd87735dSAndreas Gohr $pages = idx_get_indexer()->getPages(); 202dd87735dSAndreas Gohr $pages = array_filter(array_filter($pages, 'isVisiblePage'), 'page_exists'); 2032d85e841SAndreas Gohr Sort::ksort($pages); 204dd87735dSAndreas Gohr 205dd87735dSAndreas Gohr foreach (array_keys($pages) as $idx) { 206dd87735dSAndreas Gohr $perm = auth_quickaclcheck($pages[$idx]); 207dd87735dSAndreas Gohr if ($perm < AUTH_READ) { 208dd87735dSAndreas Gohr continue; 209dd87735dSAndreas Gohr } 210104a3b7cSAndreas Gohr $page = []; 211dd87735dSAndreas Gohr $page['id'] = trim($pages[$idx]); 212dd87735dSAndreas Gohr $page['perms'] = $perm; 213dd87735dSAndreas Gohr $page['size'] = @filesize(wikiFN($pages[$idx])); 214dd87735dSAndreas Gohr $page['lastModified'] = $this->api->toDate(@filemtime(wikiFN($pages[$idx]))); 215dd87735dSAndreas Gohr $list[] = $page; 216dd87735dSAndreas Gohr } 217dd87735dSAndreas Gohr 218dd87735dSAndreas Gohr return $list; 219dd87735dSAndreas Gohr } 220dd87735dSAndreas Gohr 221dd87735dSAndreas Gohr /** 222dd87735dSAndreas Gohr * List all pages in the given namespace (and below) 223dd87735dSAndreas Gohr * 224dd87735dSAndreas Gohr * @param string $ns 225dd87735dSAndreas Gohr * @param array $opts 226dd87735dSAndreas Gohr * $opts['depth'] recursion level, 0 for all 227dd87735dSAndreas Gohr * $opts['hash'] do md5 sum of content? 228dd87735dSAndreas Gohr * @return array 229dd87735dSAndreas Gohr */ 230104a3b7cSAndreas Gohr public function readNamespace($ns, $opts = []) 231dd87735dSAndreas Gohr { 232dd87735dSAndreas Gohr global $conf; 233dd87735dSAndreas Gohr 234104a3b7cSAndreas Gohr if (!is_array($opts)) $opts = []; 235dd87735dSAndreas Gohr 236dd87735dSAndreas Gohr $ns = cleanID($ns); 237dd87735dSAndreas Gohr $dir = utf8_encodeFN(str_replace(':', '/', $ns)); 238104a3b7cSAndreas Gohr $data = []; 239dd87735dSAndreas Gohr $opts['skipacl'] = 0; // no ACL skipping for XMLRPC 240dd87735dSAndreas Gohr search($data, $conf['datadir'], 'search_allpages', $opts, $dir); 241dd87735dSAndreas Gohr return $data; 242dd87735dSAndreas Gohr } 243dd87735dSAndreas Gohr 244dd87735dSAndreas Gohr /** 245dd87735dSAndreas Gohr * List all pages in the given namespace (and below) 246dd87735dSAndreas Gohr * 247dd87735dSAndreas Gohr * @param string $query 248dd87735dSAndreas Gohr * @return array 249dd87735dSAndreas Gohr */ 250dd87735dSAndreas Gohr public function search($query) 251dd87735dSAndreas Gohr { 252104a3b7cSAndreas Gohr $regex = []; 253dd87735dSAndreas Gohr $data = ft_pageSearch($query, $regex); 254104a3b7cSAndreas Gohr $pages = []; 255dd87735dSAndreas Gohr 256dd87735dSAndreas Gohr // prepare additional data 257dd87735dSAndreas Gohr $idx = 0; 258dd87735dSAndreas Gohr foreach ($data as $id => $score) { 259dd87735dSAndreas Gohr $file = wikiFN($id); 260dd87735dSAndreas Gohr 261dd87735dSAndreas Gohr if ($idx < FT_SNIPPET_NUMBER) { 262dd87735dSAndreas Gohr $snippet = ft_snippet($id, $regex); 263dd87735dSAndreas Gohr $idx++; 264dd87735dSAndreas Gohr } else { 265dd87735dSAndreas Gohr $snippet = ''; 266dd87735dSAndreas Gohr } 267dd87735dSAndreas Gohr 268104a3b7cSAndreas Gohr $pages[] = [ 269dd87735dSAndreas Gohr 'id' => $id, 270104a3b7cSAndreas Gohr 'score' => (int)$score, 271dd87735dSAndreas Gohr 'rev' => filemtime($file), 272dd87735dSAndreas Gohr 'mtime' => filemtime($file), 273dd87735dSAndreas Gohr 'size' => filesize($file), 274dd87735dSAndreas Gohr 'snippet' => $snippet, 275dd87735dSAndreas Gohr 'title' => useHeading('navigation') ? p_get_first_heading($id) : $id 276104a3b7cSAndreas Gohr ]; 277dd87735dSAndreas Gohr } 278dd87735dSAndreas Gohr return $pages; 279dd87735dSAndreas Gohr } 280dd87735dSAndreas Gohr 281dd87735dSAndreas Gohr /** 282dd87735dSAndreas Gohr * Returns the wiki title. 283dd87735dSAndreas Gohr * 284dd87735dSAndreas Gohr * @return string 285dd87735dSAndreas Gohr */ 286dd87735dSAndreas Gohr public function getTitle() 287dd87735dSAndreas Gohr { 288dd87735dSAndreas Gohr global $conf; 289dd87735dSAndreas Gohr return $conf['title']; 290dd87735dSAndreas Gohr } 291dd87735dSAndreas Gohr 292dd87735dSAndreas Gohr /** 293dd87735dSAndreas Gohr * List all media files. 294dd87735dSAndreas Gohr * 295dd87735dSAndreas Gohr * Available options are 'recursive' for also including the subnamespaces 296dd87735dSAndreas Gohr * in the listing, and 'pattern' for filtering the returned files against 297dd87735dSAndreas Gohr * a regular expression matching their name. 298dd87735dSAndreas Gohr * 299dd87735dSAndreas Gohr * @param string $ns 300dd87735dSAndreas Gohr * @param array $options 301dd87735dSAndreas Gohr * $options['depth'] recursion level, 0 for all 302dd87735dSAndreas Gohr * $options['showmsg'] shows message if invalid media id is used 303dd87735dSAndreas Gohr * $options['pattern'] check given pattern 304dd87735dSAndreas Gohr * $options['hash'] add hashes to result list 305dd87735dSAndreas Gohr * @return array 306dd87735dSAndreas Gohr * @throws AccessDeniedException no access to the media files 307104a3b7cSAndreas Gohr * @author Gina Haeussge <osd@foosel.net> 308104a3b7cSAndreas Gohr * 309dd87735dSAndreas Gohr */ 310104a3b7cSAndreas Gohr public function listAttachments($ns, $options = []) 311dd87735dSAndreas Gohr { 312dd87735dSAndreas Gohr global $conf; 313dd87735dSAndreas Gohr 314dd87735dSAndreas Gohr $ns = cleanID($ns); 315dd87735dSAndreas Gohr 316104a3b7cSAndreas Gohr if (!is_array($options)) $options = []; 317dd87735dSAndreas Gohr $options['skipacl'] = 0; // no ACL skipping for XMLRPC 318dd87735dSAndreas Gohr 319dd87735dSAndreas Gohr if (auth_quickaclcheck($ns . ':*') >= AUTH_READ) { 320dd87735dSAndreas Gohr $dir = utf8_encodeFN(str_replace(':', '/', $ns)); 321dd87735dSAndreas Gohr 322104a3b7cSAndreas Gohr $data = []; 323dd87735dSAndreas Gohr search($data, $conf['mediadir'], 'search_media', $options, $dir); 324dd87735dSAndreas Gohr $len = count($data); 325104a3b7cSAndreas Gohr if (!$len) return []; 326dd87735dSAndreas Gohr 327dd87735dSAndreas Gohr for ($i = 0; $i < $len; $i++) { 328dd87735dSAndreas Gohr unset($data[$i]['meta']); 329dd87735dSAndreas Gohr $data[$i]['perms'] = $data[$i]['perm']; 330dd87735dSAndreas Gohr unset($data[$i]['perm']); 331dd87735dSAndreas Gohr $data[$i]['lastModified'] = $this->api->toDate($data[$i]['mtime']); 332dd87735dSAndreas Gohr } 333dd87735dSAndreas Gohr return $data; 334dd87735dSAndreas Gohr } else { 335dd87735dSAndreas Gohr throw new AccessDeniedException('You are not allowed to list media files.', 215); 336dd87735dSAndreas Gohr } 337dd87735dSAndreas Gohr } 338dd87735dSAndreas Gohr 339dd87735dSAndreas Gohr /** 340dd87735dSAndreas Gohr * Return a list of backlinks 341dd87735dSAndreas Gohr * 342dd87735dSAndreas Gohr * @param string $id page id 343dd87735dSAndreas Gohr * @return array 344dd87735dSAndreas Gohr */ 345dd87735dSAndreas Gohr public function listBackLinks($id) 346dd87735dSAndreas Gohr { 347dd87735dSAndreas Gohr return ft_backlinks($this->resolvePageId($id)); 348dd87735dSAndreas Gohr } 349dd87735dSAndreas Gohr 350dd87735dSAndreas Gohr /** 351dd87735dSAndreas Gohr * Return some basic data about a page 352dd87735dSAndreas Gohr * 353dd87735dSAndreas Gohr * @param string $id page id 354dd87735dSAndreas Gohr * @param string|int $rev revision timestamp or empty string 355dd87735dSAndreas Gohr * @return array 356dd87735dSAndreas Gohr * @throws AccessDeniedException no access for page 357dd87735dSAndreas Gohr * @throws RemoteException page not exist 358dd87735dSAndreas Gohr */ 359dd87735dSAndreas Gohr public function pageInfo($id, $rev = '') 360dd87735dSAndreas Gohr { 361dd87735dSAndreas Gohr $id = $this->resolvePageId($id); 362dd87735dSAndreas Gohr if (auth_quickaclcheck($id) < AUTH_READ) { 363dd87735dSAndreas Gohr throw new AccessDeniedException('You are not allowed to read this page', 111); 364dd87735dSAndreas Gohr } 365dd87735dSAndreas Gohr $file = wikiFN($id, $rev); 366dd87735dSAndreas Gohr $time = @filemtime($file); 367dd87735dSAndreas Gohr if (!$time) { 368dd87735dSAndreas Gohr throw new RemoteException('The requested page does not exist', 121); 369dd87735dSAndreas Gohr } 370dd87735dSAndreas Gohr 371dd87735dSAndreas Gohr // set revision to current version if empty, use revision otherwise 372dd87735dSAndreas Gohr // as the timestamps of old files are not necessarily correct 373dd87735dSAndreas Gohr if ($rev === '') { 374dd87735dSAndreas Gohr $rev = $time; 375dd87735dSAndreas Gohr } 376dd87735dSAndreas Gohr 377dd87735dSAndreas Gohr $pagelog = new PageChangeLog($id, 1024); 378dd87735dSAndreas Gohr $info = $pagelog->getRevisionInfo($rev); 379dd87735dSAndreas Gohr 380104a3b7cSAndreas Gohr $data = [ 381dd87735dSAndreas Gohr 'name' => $id, 382dd87735dSAndreas Gohr 'lastModified' => $this->api->toDate($rev), 383104a3b7cSAndreas Gohr 'author' => is_array($info) ? ($info['user'] ?: $info['ip']) : null, 384dd87735dSAndreas Gohr 'version' => $rev 385104a3b7cSAndreas Gohr ]; 386dd87735dSAndreas Gohr 387dd87735dSAndreas Gohr return ($data); 388dd87735dSAndreas Gohr } 389dd87735dSAndreas Gohr 390dd87735dSAndreas Gohr /** 391dd87735dSAndreas Gohr * Save a wiki page 392dd87735dSAndreas Gohr * 393dd87735dSAndreas Gohr * @param string $id page id 394dd87735dSAndreas Gohr * @param string $text wiki text 395dd87735dSAndreas Gohr * @param array $params parameters: summary, minor edit 396dd87735dSAndreas Gohr * @return bool 397dd87735dSAndreas Gohr * @throws AccessDeniedException no write access for page 398dd87735dSAndreas Gohr * @throws RemoteException no id, empty new page or locked 399104a3b7cSAndreas Gohr * @author Michael Klier <chi@chimeric.de> 400104a3b7cSAndreas Gohr * 401dd87735dSAndreas Gohr */ 402104a3b7cSAndreas Gohr public function putPage($id, $text, $params = []) 403dd87735dSAndreas Gohr { 404dd87735dSAndreas Gohr global $TEXT; 405dd87735dSAndreas Gohr global $lang; 406dd87735dSAndreas Gohr 407dd87735dSAndreas Gohr $id = $this->resolvePageId($id); 408dd87735dSAndreas Gohr $TEXT = cleanText($text); 40953585189SAndreas Gohr $sum = $params['sum'] ?? ''; 41053585189SAndreas Gohr $minor = $params['minor'] ?? false; 411dd87735dSAndreas Gohr 412dd87735dSAndreas Gohr if (empty($id)) { 413dd87735dSAndreas Gohr throw new RemoteException('Empty page ID', 131); 414dd87735dSAndreas Gohr } 415dd87735dSAndreas Gohr 416dd87735dSAndreas Gohr if (!page_exists($id) && trim($TEXT) == '') { 417dd87735dSAndreas Gohr throw new RemoteException('Refusing to write an empty new wiki page', 132); 418dd87735dSAndreas Gohr } 419dd87735dSAndreas Gohr 420dd87735dSAndreas Gohr if (auth_quickaclcheck($id) < AUTH_EDIT) { 421dd87735dSAndreas Gohr throw new AccessDeniedException('You are not allowed to edit this page', 112); 422dd87735dSAndreas Gohr } 423dd87735dSAndreas Gohr 424dd87735dSAndreas Gohr // Check, if page is locked 425dd87735dSAndreas Gohr if (checklock($id)) { 426dd87735dSAndreas Gohr throw new RemoteException('The page is currently locked', 133); 427dd87735dSAndreas Gohr } 428dd87735dSAndreas Gohr 429dd87735dSAndreas Gohr // SPAM check 430dd87735dSAndreas Gohr if (checkwordblock()) { 431dd87735dSAndreas Gohr throw new RemoteException('Positive wordblock check', 134); 432dd87735dSAndreas Gohr } 433dd87735dSAndreas Gohr 434dd87735dSAndreas Gohr // autoset summary on new pages 435dd87735dSAndreas Gohr if (!page_exists($id) && empty($sum)) { 436dd87735dSAndreas Gohr $sum = $lang['created']; 437dd87735dSAndreas Gohr } 438dd87735dSAndreas Gohr 439dd87735dSAndreas Gohr // autoset summary on deleted pages 440dd87735dSAndreas Gohr if (page_exists($id) && empty($TEXT) && empty($sum)) { 441dd87735dSAndreas Gohr $sum = $lang['deleted']; 442dd87735dSAndreas Gohr } 443dd87735dSAndreas Gohr 444dd87735dSAndreas Gohr lock($id); 445dd87735dSAndreas Gohr 446dd87735dSAndreas Gohr saveWikiText($id, $TEXT, $sum, $minor); 447dd87735dSAndreas Gohr 448dd87735dSAndreas Gohr unlock($id); 449dd87735dSAndreas Gohr 450dd87735dSAndreas Gohr // run the indexer if page wasn't indexed yet 451dd87735dSAndreas Gohr idx_addPage($id); 452dd87735dSAndreas Gohr 453dd87735dSAndreas Gohr return true; 454dd87735dSAndreas Gohr } 455dd87735dSAndreas Gohr 456dd87735dSAndreas Gohr /** 457dd87735dSAndreas Gohr * Appends text to a wiki page. 458dd87735dSAndreas Gohr * 459dd87735dSAndreas Gohr * @param string $id page id 460dd87735dSAndreas Gohr * @param string $text wiki text 461dd87735dSAndreas Gohr * @param array $params such as summary,minor 462dd87735dSAndreas Gohr * @return bool|string 463dd87735dSAndreas Gohr * @throws RemoteException 464dd87735dSAndreas Gohr */ 465104a3b7cSAndreas Gohr public function appendPage($id, $text, $params = []) 466dd87735dSAndreas Gohr { 467dd87735dSAndreas Gohr $currentpage = $this->rawPage($id); 468dd87735dSAndreas Gohr if (!is_string($currentpage)) { 469dd87735dSAndreas Gohr return $currentpage; 470dd87735dSAndreas Gohr } 471dd87735dSAndreas Gohr return $this->putPage($id, $currentpage . $text, $params); 472dd87735dSAndreas Gohr } 473dd87735dSAndreas Gohr 474dd87735dSAndreas Gohr /** 4750e0fd3b7SMichael Wegener * Create one or more users 4760e0fd3b7SMichael Wegener * 47705438aa9SMichael Wegener * @param array[] $userStruct User struct 4780e0fd3b7SMichael Wegener * 47905438aa9SMichael Wegener * @return boolean Create state 4800e0fd3b7SMichael Wegener * 4810e0fd3b7SMichael Wegener * @throws AccessDeniedException 4820e0fd3b7SMichael Wegener * @throws RemoteException 4830e0fd3b7SMichael Wegener */ 484f0e32bb9SMichael Wegener public function createUser($userStruct) 4850e0fd3b7SMichael Wegener { 4860e0fd3b7SMichael Wegener if (!auth_isadmin()) { 4870e0fd3b7SMichael Wegener throw new AccessDeniedException('Only admins are allowed to create users', 114); 4880e0fd3b7SMichael Wegener } 4890e0fd3b7SMichael Wegener 490104a3b7cSAndreas Gohr /** @var AuthPlugin $auth */ 4910e0fd3b7SMichael Wegener global $auth; 4920e0fd3b7SMichael Wegener 4930e0fd3b7SMichael Wegener if (!$auth->canDo('addUser')) { 4940e0fd3b7SMichael Wegener throw new AccessDeniedException( 4950e0fd3b7SMichael Wegener sprintf('Authentication backend %s can\'t do addUser', $auth->getPluginName()), 4960e0fd3b7SMichael Wegener 114 4970e0fd3b7SMichael Wegener ); 4980e0fd3b7SMichael Wegener } 4990e0fd3b7SMichael Wegener 500f0e32bb9SMichael Wegener $user = trim($auth->cleanUser($userStruct['user'] ?? '')); 501f0e32bb9SMichael Wegener $password = $userStruct['password'] ?? ''; 502f0e32bb9SMichael Wegener $name = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/', '', $userStruct['name'] ?? '')); 503f0e32bb9SMichael Wegener $mail = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/', '', $userStruct['mail'] ?? '')); 504f0e32bb9SMichael Wegener $groups = $userStruct['groups'] ?? []; 5050e0fd3b7SMichael Wegener 506d95846aaSAndreas Gohr $notify = (bool) ($userStruct['notify'] ?? false); 507f0e32bb9SMichael Wegener 508b1d4a667SAndreas Gohr if ($user === '') throw new RemoteException('empty or invalid user', 401); 509b1d4a667SAndreas Gohr if ($name === '') throw new RemoteException('empty or invalid user name', 402); 510b1d4a667SAndreas Gohr if (!mail_isvalid($mail)) throw new RemoteException('empty or invalid mail address', 403); 511f0e32bb9SMichael Wegener 512104a3b7cSAndreas Gohr if ((string)$password === '') { 513f0e32bb9SMichael Wegener $password = auth_pwgen($user); 514f0e32bb9SMichael Wegener } 515f0e32bb9SMichael Wegener 516104a3b7cSAndreas Gohr if (!is_array($groups) || $groups === []) { 517f0e32bb9SMichael Wegener $groups = null; 518f0e32bb9SMichael Wegener } 519f0e32bb9SMichael Wegener 520104a3b7cSAndreas Gohr $ok = $auth->triggerUserMod('create', [$user, $password, $name, $mail, $groups]); 521f0e32bb9SMichael Wegener 522f0e32bb9SMichael Wegener if ($ok !== false && $ok !== null) { 523f0e32bb9SMichael Wegener $ok = true; 524f0e32bb9SMichael Wegener } 525f0e32bb9SMichael Wegener 5260e0fd3b7SMichael Wegener if ($ok) { 527f0e32bb9SMichael Wegener if ($notify) { 528f0e32bb9SMichael Wegener auth_sendPassword($user, $password); 5290e0fd3b7SMichael Wegener } 5300e0fd3b7SMichael Wegener } 531f0e32bb9SMichael Wegener 532f0e32bb9SMichael Wegener return $ok; 5330e0fd3b7SMichael Wegener } 5340e0fd3b7SMichael Wegener 5350e0fd3b7SMichael Wegener 5360e0fd3b7SMichael Wegener /** 5378eb28c6dSAndreas Gohr * Remove one or more users from the list of registered users 5388eb28c6dSAndreas Gohr * 5398eb28c6dSAndreas Gohr * @param string[] $usernames List of usernames to remove 5408eb28c6dSAndreas Gohr * 5418eb28c6dSAndreas Gohr * @return bool 5428eb28c6dSAndreas Gohr * 5438eb28c6dSAndreas Gohr * @throws AccessDeniedException 5448eb28c6dSAndreas Gohr */ 5458eb28c6dSAndreas Gohr public function deleteUsers($usernames) 5468eb28c6dSAndreas Gohr { 5478eb28c6dSAndreas Gohr if (!auth_isadmin()) { 5488eb28c6dSAndreas Gohr throw new AccessDeniedException('Only admins are allowed to delete users', 114); 5498eb28c6dSAndreas Gohr } 550104a3b7cSAndreas Gohr /** @var AuthPlugin $auth */ 5518eb28c6dSAndreas Gohr global $auth; 552104a3b7cSAndreas Gohr return (bool)$auth->triggerUserMod('delete', [$usernames]); 5538eb28c6dSAndreas Gohr } 5548eb28c6dSAndreas Gohr 5558eb28c6dSAndreas Gohr /** 556dd87735dSAndreas Gohr * Uploads a file to the wiki. 557dd87735dSAndreas Gohr * 558dd87735dSAndreas Gohr * Michael Klier <chi@chimeric.de> 559dd87735dSAndreas Gohr * 560dd87735dSAndreas Gohr * @param string $id page id 561dd87735dSAndreas Gohr * @param string $file 562dd87735dSAndreas Gohr * @param array $params such as overwrite 563dd87735dSAndreas Gohr * @return false|string 564dd87735dSAndreas Gohr * @throws RemoteException 565dd87735dSAndreas Gohr */ 566104a3b7cSAndreas Gohr public function putAttachment($id, $file, $params = []) 567dd87735dSAndreas Gohr { 568dd87735dSAndreas Gohr $id = cleanID($id); 569dd87735dSAndreas Gohr $auth = auth_quickaclcheck(getNS($id) . ':*'); 570dd87735dSAndreas Gohr 571dd87735dSAndreas Gohr if (!isset($id)) { 572dd87735dSAndreas Gohr throw new RemoteException('Filename not given.', 231); 573dd87735dSAndreas Gohr } 574dd87735dSAndreas Gohr 575dd87735dSAndreas Gohr global $conf; 576dd87735dSAndreas Gohr 577dd87735dSAndreas Gohr $ftmp = $conf['tmpdir'] . '/' . md5($id . clientIP()); 578dd87735dSAndreas Gohr 579dd87735dSAndreas Gohr // save temporary file 580dd87735dSAndreas Gohr @unlink($ftmp); 581dd87735dSAndreas Gohr io_saveFile($ftmp, $file); 582dd87735dSAndreas Gohr 583104a3b7cSAndreas Gohr $res = media_save(['name' => $ftmp], $id, $params['ow'], $auth, 'rename'); 584dd87735dSAndreas Gohr if (is_array($res)) { 585dd87735dSAndreas Gohr throw new RemoteException($res[0], -$res[1]); 586dd87735dSAndreas Gohr } else { 587dd87735dSAndreas Gohr return $res; 588dd87735dSAndreas Gohr } 589dd87735dSAndreas Gohr } 590dd87735dSAndreas Gohr 591dd87735dSAndreas Gohr /** 592dd87735dSAndreas Gohr * Deletes a file from the wiki. 593dd87735dSAndreas Gohr * 594dd87735dSAndreas Gohr * @param string $id page id 595dd87735dSAndreas Gohr * @return int 596dd87735dSAndreas Gohr * @throws AccessDeniedException no permissions 597dd87735dSAndreas Gohr * @throws RemoteException file in use or not deleted 598104a3b7cSAndreas Gohr * @author Gina Haeussge <osd@foosel.net> 599104a3b7cSAndreas Gohr * 600dd87735dSAndreas Gohr */ 601dd87735dSAndreas Gohr public function deleteAttachment($id) 602dd87735dSAndreas Gohr { 603dd87735dSAndreas Gohr $id = cleanID($id); 604dd87735dSAndreas Gohr $auth = auth_quickaclcheck(getNS($id) . ':*'); 605dd87735dSAndreas Gohr $res = media_delete($id, $auth); 606dd87735dSAndreas Gohr if ($res & DOKU_MEDIA_DELETED) { 607dd87735dSAndreas Gohr return 0; 608dd87735dSAndreas Gohr } elseif ($res & DOKU_MEDIA_NOT_AUTH) { 609dd87735dSAndreas Gohr throw new AccessDeniedException('You don\'t have permissions to delete files.', 212); 610dd87735dSAndreas Gohr } elseif ($res & DOKU_MEDIA_INUSE) { 611dd87735dSAndreas Gohr throw new RemoteException('File is still referenced', 232); 612dd87735dSAndreas Gohr } else { 613dd87735dSAndreas Gohr throw new RemoteException('Could not delete file', 233); 614dd87735dSAndreas Gohr } 615dd87735dSAndreas Gohr } 616dd87735dSAndreas Gohr 617dd87735dSAndreas Gohr /** 618dd87735dSAndreas Gohr * Returns the permissions of a given wiki page for the current user or another user 619dd87735dSAndreas Gohr * 620dd87735dSAndreas Gohr * @param string $id page id 621dd87735dSAndreas Gohr * @param string|null $user username 622dd87735dSAndreas Gohr * @param array|null $groups array of groups 623dd87735dSAndreas Gohr * @return int permission level 624dd87735dSAndreas Gohr */ 625dd87735dSAndreas Gohr public function aclCheck($id, $user = null, $groups = null) 626dd87735dSAndreas Gohr { 627104a3b7cSAndreas Gohr /** @var AuthPlugin $auth */ 628dd87735dSAndreas Gohr global $auth; 629dd87735dSAndreas Gohr 630dd87735dSAndreas Gohr $id = $this->resolvePageId($id); 631dd87735dSAndreas Gohr if ($user === null) { 632dd87735dSAndreas Gohr return auth_quickaclcheck($id); 633dd87735dSAndreas Gohr } else { 634dd87735dSAndreas Gohr if ($groups === null) { 635dd87735dSAndreas Gohr $userinfo = $auth->getUserData($user); 636dd87735dSAndreas Gohr if ($userinfo === false) { 637104a3b7cSAndreas Gohr $groups = []; 638dd87735dSAndreas Gohr } else { 639dd87735dSAndreas Gohr $groups = $userinfo['grps']; 640dd87735dSAndreas Gohr } 641dd87735dSAndreas Gohr } 642dd87735dSAndreas Gohr return auth_aclcheck($id, $user, $groups); 643dd87735dSAndreas Gohr } 644dd87735dSAndreas Gohr } 645dd87735dSAndreas Gohr 646dd87735dSAndreas Gohr /** 647dd87735dSAndreas Gohr * Lists all links contained in a wiki page 648dd87735dSAndreas Gohr * 649dd87735dSAndreas Gohr * @param string $id page id 650dd87735dSAndreas Gohr * @return array 651dd87735dSAndreas Gohr * @throws AccessDeniedException no read access for page 652104a3b7cSAndreas Gohr * @author Michael Klier <chi@chimeric.de> 653104a3b7cSAndreas Gohr * 654dd87735dSAndreas Gohr */ 655dd87735dSAndreas Gohr public function listLinks($id) 656dd87735dSAndreas Gohr { 657dd87735dSAndreas Gohr $id = $this->resolvePageId($id); 658dd87735dSAndreas Gohr if (auth_quickaclcheck($id) < AUTH_READ) { 659dd87735dSAndreas Gohr throw new AccessDeniedException('You are not allowed to read this page', 111); 660dd87735dSAndreas Gohr } 661104a3b7cSAndreas Gohr $links = []; 662dd87735dSAndreas Gohr 663dd87735dSAndreas Gohr // resolve page instructions 664dd87735dSAndreas Gohr $ins = p_cached_instructions(wikiFN($id)); 665dd87735dSAndreas Gohr 666dd87735dSAndreas Gohr // instantiate new Renderer - needed for interwiki links 667dd87735dSAndreas Gohr $Renderer = new Doku_Renderer_xhtml(); 668dd87735dSAndreas Gohr $Renderer->interwiki = getInterwiki(); 669dd87735dSAndreas Gohr 670dd87735dSAndreas Gohr // parse parse instructions 671dd87735dSAndreas Gohr foreach ($ins as $in) { 672104a3b7cSAndreas Gohr $link = []; 673dd87735dSAndreas Gohr switch ($in[0]) { 674dd87735dSAndreas Gohr case 'internallink': 675dd87735dSAndreas Gohr $link['type'] = 'local'; 676dd87735dSAndreas Gohr $link['page'] = $in[1][0]; 677dd87735dSAndreas Gohr $link['href'] = wl($in[1][0]); 678104a3b7cSAndreas Gohr $links[] = $link; 679dd87735dSAndreas Gohr break; 680dd87735dSAndreas Gohr case 'externallink': 681dd87735dSAndreas Gohr $link['type'] = 'extern'; 682dd87735dSAndreas Gohr $link['page'] = $in[1][0]; 683dd87735dSAndreas Gohr $link['href'] = $in[1][0]; 684104a3b7cSAndreas Gohr $links[] = $link; 685dd87735dSAndreas Gohr break; 686dd87735dSAndreas Gohr case 'interwikilink': 687dd87735dSAndreas Gohr $url = $Renderer->_resolveInterWiki($in[1][2], $in[1][3]); 688dd87735dSAndreas Gohr $link['type'] = 'extern'; 689dd87735dSAndreas Gohr $link['page'] = $url; 690dd87735dSAndreas Gohr $link['href'] = $url; 691104a3b7cSAndreas Gohr $links[] = $link; 692dd87735dSAndreas Gohr break; 693dd87735dSAndreas Gohr } 694dd87735dSAndreas Gohr } 695dd87735dSAndreas Gohr 696dd87735dSAndreas Gohr return ($links); 697dd87735dSAndreas Gohr } 698dd87735dSAndreas Gohr 699dd87735dSAndreas Gohr /** 700dd87735dSAndreas Gohr * Returns a list of recent changes since give timestamp 701dd87735dSAndreas Gohr * 702dd87735dSAndreas Gohr * @param int $timestamp unix timestamp 703dd87735dSAndreas Gohr * @return array 704dd87735dSAndreas Gohr * @throws RemoteException no valid timestamp 705104a3b7cSAndreas Gohr * @author Michael Klier <chi@chimeric.de> 706104a3b7cSAndreas Gohr * 707104a3b7cSAndreas Gohr * @author Michael Hamann <michael@content-space.de> 708dd87735dSAndreas Gohr */ 709dd87735dSAndreas Gohr public function getRecentChanges($timestamp) 710dd87735dSAndreas Gohr { 711dd87735dSAndreas Gohr if (strlen($timestamp) != 10) { 712dd87735dSAndreas Gohr throw new RemoteException('The provided value is not a valid timestamp', 311); 713dd87735dSAndreas Gohr } 714dd87735dSAndreas Gohr 715dd87735dSAndreas Gohr $recents = getRecentsSince($timestamp); 716dd87735dSAndreas Gohr 717104a3b7cSAndreas Gohr $changes = []; 718dd87735dSAndreas Gohr 719dd87735dSAndreas Gohr foreach ($recents as $recent) { 720104a3b7cSAndreas Gohr $change = []; 721dd87735dSAndreas Gohr $change['name'] = $recent['id']; 722dd87735dSAndreas Gohr $change['lastModified'] = $this->api->toDate($recent['date']); 723dd87735dSAndreas Gohr $change['author'] = $recent['user']; 724dd87735dSAndreas Gohr $change['version'] = $recent['date']; 725dd87735dSAndreas Gohr $change['perms'] = $recent['perms']; 726dd87735dSAndreas Gohr $change['size'] = @filesize(wikiFN($recent['id'])); 727104a3b7cSAndreas Gohr $changes[] = $change; 728dd87735dSAndreas Gohr } 729dd87735dSAndreas Gohr 730104a3b7cSAndreas Gohr if ($changes !== []) { 731dd87735dSAndreas Gohr return $changes; 732dd87735dSAndreas Gohr } else { 733dd87735dSAndreas Gohr // in case we still have nothing at this point 734dd87735dSAndreas Gohr throw new RemoteException('There are no changes in the specified timeframe', 321); 735dd87735dSAndreas Gohr } 736dd87735dSAndreas Gohr } 737dd87735dSAndreas Gohr 738dd87735dSAndreas Gohr /** 739dd87735dSAndreas Gohr * Returns a list of recent media changes since give timestamp 740dd87735dSAndreas Gohr * 741dd87735dSAndreas Gohr * @param int $timestamp unix timestamp 742dd87735dSAndreas Gohr * @return array 743dd87735dSAndreas Gohr * @throws RemoteException no valid timestamp 744104a3b7cSAndreas Gohr * @author Michael Klier <chi@chimeric.de> 745104a3b7cSAndreas Gohr * 746104a3b7cSAndreas Gohr * @author Michael Hamann <michael@content-space.de> 747dd87735dSAndreas Gohr */ 748dd87735dSAndreas Gohr public function getRecentMediaChanges($timestamp) 749dd87735dSAndreas Gohr { 750dd87735dSAndreas Gohr if (strlen($timestamp) != 10) 751dd87735dSAndreas Gohr throw new RemoteException('The provided value is not a valid timestamp', 311); 752dd87735dSAndreas Gohr 753dd87735dSAndreas Gohr $recents = getRecentsSince($timestamp, null, '', RECENTS_MEDIA_CHANGES); 754dd87735dSAndreas Gohr 755104a3b7cSAndreas Gohr $changes = []; 756dd87735dSAndreas Gohr 757dd87735dSAndreas Gohr foreach ($recents as $recent) { 758104a3b7cSAndreas Gohr $change = []; 759dd87735dSAndreas Gohr $change['name'] = $recent['id']; 760dd87735dSAndreas Gohr $change['lastModified'] = $this->api->toDate($recent['date']); 761dd87735dSAndreas Gohr $change['author'] = $recent['user']; 762dd87735dSAndreas Gohr $change['version'] = $recent['date']; 763dd87735dSAndreas Gohr $change['perms'] = $recent['perms']; 764dd87735dSAndreas Gohr $change['size'] = @filesize(mediaFN($recent['id'])); 765104a3b7cSAndreas Gohr $changes[] = $change; 766dd87735dSAndreas Gohr } 767dd87735dSAndreas Gohr 768104a3b7cSAndreas Gohr if ($changes !== []) { 769dd87735dSAndreas Gohr return $changes; 770dd87735dSAndreas Gohr } else { 771dd87735dSAndreas Gohr // in case we still have nothing at this point 772dd87735dSAndreas Gohr throw new RemoteException('There are no changes in the specified timeframe', 321); 773dd87735dSAndreas Gohr } 774dd87735dSAndreas Gohr } 775dd87735dSAndreas Gohr 776dd87735dSAndreas Gohr /** 777dd87735dSAndreas Gohr * Returns a list of available revisions of a given wiki page 778dd87735dSAndreas Gohr * Number of returned pages is set by $conf['recent'] 779dd87735dSAndreas Gohr * However not accessible pages are skipped, so less than $conf['recent'] could be returned 780dd87735dSAndreas Gohr * 781dd87735dSAndreas Gohr * @param string $id page id 782dd87735dSAndreas Gohr * @param int $first skip the first n changelog lines 783dd87735dSAndreas Gohr * 0 = from current(if exists) 784dd87735dSAndreas Gohr * 1 = from 1st old rev 785dd87735dSAndreas Gohr * 2 = from 2nd old rev, etc 786dd87735dSAndreas Gohr * @return array 787dd87735dSAndreas Gohr * @throws AccessDeniedException no read access for page 788dd87735dSAndreas Gohr * @throws RemoteException empty id 789104a3b7cSAndreas Gohr * @author Michael Klier <chi@chimeric.de> 790104a3b7cSAndreas Gohr * 791dd87735dSAndreas Gohr */ 792a9284ce8SPhy public function pageVersions($id, $first = 0) 793dd87735dSAndreas Gohr { 794dd87735dSAndreas Gohr $id = $this->resolvePageId($id); 795dd87735dSAndreas Gohr if (auth_quickaclcheck($id) < AUTH_READ) { 796dd87735dSAndreas Gohr throw new AccessDeniedException('You are not allowed to read this page', 111); 797dd87735dSAndreas Gohr } 798dd87735dSAndreas Gohr global $conf; 799dd87735dSAndreas Gohr 800104a3b7cSAndreas Gohr $versions = []; 801dd87735dSAndreas Gohr 802dd87735dSAndreas Gohr if (empty($id)) { 803dd87735dSAndreas Gohr throw new RemoteException('Empty page ID', 131); 804dd87735dSAndreas Gohr } 805dd87735dSAndreas Gohr 806dd87735dSAndreas Gohr $first = (int)$first; 807dd87735dSAndreas Gohr $first_rev = $first - 1; 808efc3ac2fSsplitbrain $first_rev = max(0, $first_rev); 809104a3b7cSAndreas Gohr 810dd87735dSAndreas Gohr $pagelog = new PageChangeLog($id); 811dd87735dSAndreas Gohr $revisions = $pagelog->getRevisions($first_rev, $conf['recent']); 812dd87735dSAndreas Gohr 813dd87735dSAndreas Gohr if ($first == 0) { 814dd87735dSAndreas Gohr array_unshift($revisions, ''); // include current revision 815dd87735dSAndreas Gohr if (count($revisions) > $conf['recent']) { 816dd87735dSAndreas Gohr array_pop($revisions); // remove extra log entry 817dd87735dSAndreas Gohr } 818dd87735dSAndreas Gohr } 819dd87735dSAndreas Gohr 820dd87735dSAndreas Gohr if (!empty($revisions)) { 821dd87735dSAndreas Gohr foreach ($revisions as $rev) { 822dd87735dSAndreas Gohr $file = wikiFN($id, $rev); 823dd87735dSAndreas Gohr $time = @filemtime($file); 824dd87735dSAndreas Gohr // we check if the page actually exists, if this is not the 825dd87735dSAndreas Gohr // case this can lead to less pages being returned than 826dd87735dSAndreas Gohr // specified via $conf['recent'] 827dd87735dSAndreas Gohr if ($time) { 828dd87735dSAndreas Gohr $pagelog->setChunkSize(1024); 829104a3b7cSAndreas Gohr $info = $pagelog->getRevisionInfo($rev ?: $time); 830dd87735dSAndreas Gohr if (!empty($info)) { 831104a3b7cSAndreas Gohr $data = []; 832dd87735dSAndreas Gohr $data['user'] = $info['user']; 833dd87735dSAndreas Gohr $data['ip'] = $info['ip']; 834dd87735dSAndreas Gohr $data['type'] = $info['type']; 835dd87735dSAndreas Gohr $data['sum'] = $info['sum']; 836dd87735dSAndreas Gohr $data['modified'] = $this->api->toDate($info['date']); 837dd87735dSAndreas Gohr $data['version'] = $info['date']; 838104a3b7cSAndreas Gohr $versions[] = $data; 839dd87735dSAndreas Gohr } 840dd87735dSAndreas Gohr } 841dd87735dSAndreas Gohr } 842dd87735dSAndreas Gohr return $versions; 843dd87735dSAndreas Gohr } else { 844104a3b7cSAndreas Gohr return []; 845dd87735dSAndreas Gohr } 846dd87735dSAndreas Gohr } 847dd87735dSAndreas Gohr 848dd87735dSAndreas Gohr /** 849dd87735dSAndreas Gohr * The version of Wiki RPC API supported 85042e66c7aSAndreas Gohr * 85142e66c7aSAndreas Gohr * This is the version of the Wiki RPC specification implemented. Since that specification 85242e66c7aSAndreas Gohr * is no longer maintained, this will always return 2 85342e66c7aSAndreas Gohr * 85442e66c7aSAndreas Gohr * You probably want to look at dokuwiki.getXMLRPCAPIVersion instead 85542e66c7aSAndreas Gohr * 85642e66c7aSAndreas Gohr * @return int 857dd87735dSAndreas Gohr */ 858dd87735dSAndreas Gohr public function wikiRpcVersion() 859dd87735dSAndreas Gohr { 860dd87735dSAndreas Gohr return 2; 861dd87735dSAndreas Gohr } 862dd87735dSAndreas Gohr 863dd87735dSAndreas Gohr /** 864dd87735dSAndreas Gohr * Locks or unlocks a given batch of pages 865dd87735dSAndreas Gohr * 866dd87735dSAndreas Gohr * Give an associative array with two keys: lock and unlock. Both should contain a 867dd87735dSAndreas Gohr * list of pages to lock or unlock 868dd87735dSAndreas Gohr * 869dd87735dSAndreas Gohr * Returns an associative array with the keys locked, lockfail, unlocked and 870dd87735dSAndreas Gohr * unlockfail, each containing lists of pages. 871dd87735dSAndreas Gohr * 872dd87735dSAndreas Gohr * @param array[] $set list pages with array('lock' => array, 'unlock' => array) 873dd87735dSAndreas Gohr * @return array 874dd87735dSAndreas Gohr */ 875dd87735dSAndreas Gohr public function setLocks($set) 876dd87735dSAndreas Gohr { 877104a3b7cSAndreas Gohr $locked = []; 878104a3b7cSAndreas Gohr $lockfail = []; 879104a3b7cSAndreas Gohr $unlocked = []; 880104a3b7cSAndreas Gohr $unlockfail = []; 881dd87735dSAndreas Gohr 882104a3b7cSAndreas Gohr foreach ($set['lock'] as $id) { 883dd87735dSAndreas Gohr $id = $this->resolvePageId($id); 884dd87735dSAndreas Gohr if (auth_quickaclcheck($id) < AUTH_EDIT || checklock($id)) { 885dd87735dSAndreas Gohr $lockfail[] = $id; 886dd87735dSAndreas Gohr } else { 887dd87735dSAndreas Gohr lock($id); 888dd87735dSAndreas Gohr $locked[] = $id; 889dd87735dSAndreas Gohr } 890dd87735dSAndreas Gohr } 891dd87735dSAndreas Gohr 892104a3b7cSAndreas Gohr foreach ($set['unlock'] as $id) { 893dd87735dSAndreas Gohr $id = $this->resolvePageId($id); 894dd87735dSAndreas Gohr if (auth_quickaclcheck($id) < AUTH_EDIT || !unlock($id)) { 895dd87735dSAndreas Gohr $unlockfail[] = $id; 896dd87735dSAndreas Gohr } else { 897dd87735dSAndreas Gohr $unlocked[] = $id; 898dd87735dSAndreas Gohr } 899dd87735dSAndreas Gohr } 900dd87735dSAndreas Gohr 901104a3b7cSAndreas Gohr return [ 902dd87735dSAndreas Gohr 'locked' => $locked, 903dd87735dSAndreas Gohr 'lockfail' => $lockfail, 904dd87735dSAndreas Gohr 'unlocked' => $unlocked, 905104a3b7cSAndreas Gohr 'unlockfail' => $unlockfail 906104a3b7cSAndreas Gohr ]; 907dd87735dSAndreas Gohr } 908dd87735dSAndreas Gohr 909dd87735dSAndreas Gohr /** 910dd87735dSAndreas Gohr * Return API version 911dd87735dSAndreas Gohr * 912dd87735dSAndreas Gohr * @return int 913dd87735dSAndreas Gohr */ 914dd87735dSAndreas Gohr public function getAPIVersion() 915dd87735dSAndreas Gohr { 916dd87735dSAndreas Gohr return self::API_VERSION; 917dd87735dSAndreas Gohr } 918dd87735dSAndreas Gohr 919dd87735dSAndreas Gohr /** 920dd87735dSAndreas Gohr * Login 921dd87735dSAndreas Gohr * 922dd87735dSAndreas Gohr * @param string $user 923dd87735dSAndreas Gohr * @param string $pass 924dd87735dSAndreas Gohr * @return int 925dd87735dSAndreas Gohr */ 926dd87735dSAndreas Gohr public function login($user, $pass) 927dd87735dSAndreas Gohr { 928dd87735dSAndreas Gohr global $conf; 929104a3b7cSAndreas Gohr /** @var AuthPlugin $auth */ 930dd87735dSAndreas Gohr global $auth; 931dd87735dSAndreas Gohr 932dd87735dSAndreas Gohr if (!$conf['useacl']) return 0; 9336547cfc7SGerrit Uitslag if (!$auth instanceof AuthPlugin) return 0; 934dd87735dSAndreas Gohr 935dd87735dSAndreas Gohr @session_start(); // reopen session for login 93681e99965SPhy $ok = null; 937dd87735dSAndreas Gohr if ($auth->canDo('external')) { 938dd87735dSAndreas Gohr $ok = $auth->trustExternal($user, $pass, false); 93981e99965SPhy } 94081e99965SPhy if ($ok === null) { 941104a3b7cSAndreas Gohr $evdata = [ 942dd87735dSAndreas Gohr 'user' => $user, 943dd87735dSAndreas Gohr 'password' => $pass, 944dd87735dSAndreas Gohr 'sticky' => false, 945104a3b7cSAndreas Gohr 'silent' => true 946104a3b7cSAndreas Gohr ]; 947cbb44eabSAndreas Gohr $ok = Event::createAndTrigger('AUTH_LOGIN_CHECK', $evdata, 'auth_login_wrapper'); 948dd87735dSAndreas Gohr } 949dd87735dSAndreas Gohr session_write_close(); // we're done with the session 950dd87735dSAndreas Gohr 951dd87735dSAndreas Gohr return $ok; 952dd87735dSAndreas Gohr } 953dd87735dSAndreas Gohr 954dd87735dSAndreas Gohr /** 955dd87735dSAndreas Gohr * Log off 956dd87735dSAndreas Gohr * 957dd87735dSAndreas Gohr * @return int 958dd87735dSAndreas Gohr */ 959dd87735dSAndreas Gohr public function logoff() 960dd87735dSAndreas Gohr { 961dd87735dSAndreas Gohr global $conf; 962dd87735dSAndreas Gohr global $auth; 963dd87735dSAndreas Gohr if (!$conf['useacl']) return 0; 9646547cfc7SGerrit Uitslag if (!$auth instanceof AuthPlugin) return 0; 965dd87735dSAndreas Gohr 966dd87735dSAndreas Gohr auth_logoff(); 967dd87735dSAndreas Gohr 968dd87735dSAndreas Gohr return 1; 969dd87735dSAndreas Gohr } 970dd87735dSAndreas Gohr 971dd87735dSAndreas Gohr /** 972dd87735dSAndreas Gohr * Resolve page id 973dd87735dSAndreas Gohr * 974dd87735dSAndreas Gohr * @param string $id page id 975dd87735dSAndreas Gohr * @return string 976dd87735dSAndreas Gohr */ 977dd87735dSAndreas Gohr private function resolvePageId($id) 978dd87735dSAndreas Gohr { 979dd87735dSAndreas Gohr $id = cleanID($id); 980dd87735dSAndreas Gohr if (empty($id)) { 981dd87735dSAndreas Gohr global $conf; 982dd87735dSAndreas Gohr $id = cleanID($conf['start']); 983dd87735dSAndreas Gohr } 984dd87735dSAndreas Gohr return $id; 985dd87735dSAndreas Gohr } 986dd87735dSAndreas Gohr} 987