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