1dd87735dSAndreas Gohr<?php 2dd87735dSAndreas Gohr 3dd87735dSAndreas Gohrnamespace dokuwiki\Remote; 4dd87735dSAndreas Gohr 5dd87735dSAndreas Gohruse Doku_Renderer_xhtml; 60c3a5702SAndreas Gohruse dokuwiki\ChangeLog\PageChangeLog; 7104a3b7cSAndreas Gohruse dokuwiki\Extension\AuthPlugin; 8cbb44eabSAndreas Gohruse dokuwiki\Extension\Event; 96cce3332SAndreas Gohruse dokuwiki\Remote\Response\Link; 106cce3332SAndreas Gohruse dokuwiki\Remote\Response\Media; 116cce3332SAndreas Gohruse dokuwiki\Remote\Response\MediaRevision; 128ddd9b69SAndreas Gohruse dokuwiki\Remote\Response\Page; 136cce3332SAndreas Gohruse dokuwiki\Remote\Response\PageHit; 146cce3332SAndreas Gohruse dokuwiki\Remote\Response\PageRevision; 156cce3332SAndreas Gohruse dokuwiki\Remote\Response\User; 162d85e841SAndreas Gohruse dokuwiki\Utf8\Sort; 17dd87735dSAndreas Gohr 18dd87735dSAndreas Gohr/** 19dd87735dSAndreas Gohr * Provides the core methods for the remote API. 20dd87735dSAndreas Gohr * The methods are ordered in 'wiki.<method>' and 'dokuwiki.<method>' namespaces 21dd87735dSAndreas Gohr */ 22dd87735dSAndreas Gohrclass ApiCore 23dd87735dSAndreas Gohr{ 24dd87735dSAndreas Gohr /** @var int Increased whenever the API is changed */ 256cce3332SAndreas Gohr public const API_VERSION = 12; 26dd87735dSAndreas Gohr 27dd87735dSAndreas Gohr /** 28dd87735dSAndreas Gohr * Returns details about the core methods 29dd87735dSAndreas Gohr * 30dd87735dSAndreas Gohr * @return array 31dd87735dSAndreas Gohr */ 326cce3332SAndreas Gohr public function getMethods() 33dd87735dSAndreas Gohr { 34104a3b7cSAndreas Gohr return [ 356cce3332SAndreas Gohr 'core.getAPIVersion' => (new ApiCall([$this, 'getAPIVersion'], 'info'))->setPublic(), 366cce3332SAndreas Gohr 376cce3332SAndreas Gohr 'core.getWikiVersion' => new ApiCall('getVersion', 'info'), 386cce3332SAndreas Gohr 'core.getWikiTitle' => (new ApiCall([$this, 'getWikiTitle'], 'info'))->setPublic(), 396cce3332SAndreas Gohr 'core.getWikiTime' => (new ApiCall([$this, 'getWikiTime'], 'info')), 406cce3332SAndreas Gohr 416cce3332SAndreas Gohr 'core.login' => (new ApiCall([$this, 'login'], 'user'))->setPublic(), 426cce3332SAndreas Gohr 'core.logoff' => new ApiCall([$this, 'logoff'], 'user'), 436cce3332SAndreas Gohr 'core.whoAmI' => (new ApiCall([$this, 'whoAmI'], 'user')), 446cce3332SAndreas Gohr 'core.aclCheck' => new ApiCall([$this, 'aclCheck'], 'user'), 456cce3332SAndreas Gohr 466cce3332SAndreas Gohr 'core.listPages' => new ApiCall([$this, 'listPages'], 'pages'), 476cce3332SAndreas Gohr 'core.searchPages' => new ApiCall([$this, 'searchPages'], 'pages'), 486cce3332SAndreas Gohr 'core.getRecentPageChanges' => new ApiCall([$this, 'getRecentPageChanges'], 'pages'), 496cce3332SAndreas Gohr 506cce3332SAndreas Gohr 'core.getPage' => (new ApiCall([$this, 'getPage'], 'pages')), 516cce3332SAndreas Gohr 'core.getPageHTML' => (new ApiCall([$this, 'getPageHTML'], 'pages')), 526cce3332SAndreas Gohr 'core.getPageInfo' => (new ApiCall([$this, 'getPageInfo'], 'pages')), 536cce3332SAndreas Gohr 'core.getPageVersions' => new ApiCall([$this, 'getPageVersions'], 'pages'), 546cce3332SAndreas Gohr 'core.getPageLinks' => new ApiCall([$this, 'getPageLinks'], 'pages'), 556cce3332SAndreas Gohr 'core.getPageBackLinks' => new ApiCall([$this, 'getPageBackLinks'], 'pages'), 566cce3332SAndreas Gohr 576cce3332SAndreas Gohr 'core.lockPages' => new ApiCall([$this, 'lockPages'], 'pages'), 586cce3332SAndreas Gohr 'core.unlockPages' => new ApiCall([$this, 'unlockPages'], 'pages'), 596cce3332SAndreas Gohr 'core.savePage' => new ApiCall([$this, 'savePage'], 'pages'), 606cce3332SAndreas Gohr 'core.appendPage' => new ApiCall([$this, 'appendPage'], 'pages'), 616cce3332SAndreas Gohr 626cce3332SAndreas Gohr 'core.listMedia' => new ApiCall([$this, 'listMedia'], 'media'), 636cce3332SAndreas Gohr // todo: implement searchMedia 646cce3332SAndreas Gohr 'core.getRecentMediaChanges' => new ApiCall([$this, 'getRecentMediaChanges'], 'media'), 656cce3332SAndreas Gohr 666cce3332SAndreas Gohr 'core.getMedia' => new ApiCall([$this, 'getMedia'], 'media'), 676cce3332SAndreas Gohr 'core.getMediaInfo' => new ApiCall([$this, 'getMediaInfo'], 'media'), 686cce3332SAndreas Gohr // todo: implement getMediaVersions 696cce3332SAndreas Gohr // todo: implement getMediaUsage 706cce3332SAndreas Gohr 716cce3332SAndreas Gohr 'core.saveMedia' => new ApiCall([$this, 'saveMedia'], 'media'), 726cce3332SAndreas Gohr 'core.deleteMedia' => new ApiCall([$this, 'deleteMedia'], 'media'), 73104a3b7cSAndreas Gohr ]; 74dd87735dSAndreas Gohr } 75dd87735dSAndreas Gohr 766cce3332SAndreas Gohr // region info 77dd87735dSAndreas Gohr 78dd87735dSAndreas Gohr /** 798a9282a2SAndreas Gohr * Return the API version 808a9282a2SAndreas Gohr * 818a9282a2SAndreas Gohr * This is the version of the DokuWiki API. It increases whenever the API definition changes. 828a9282a2SAndreas Gohr * 838a9282a2SAndreas Gohr * When developing a client, you should check this version and make sure you can handle it. 84dd87735dSAndreas Gohr * 85dd87735dSAndreas Gohr * @return int 86dd87735dSAndreas Gohr */ 87dd87735dSAndreas Gohr public function getAPIVersion() 88dd87735dSAndreas Gohr { 89dd87735dSAndreas Gohr return self::API_VERSION; 90dd87735dSAndreas Gohr } 91dd87735dSAndreas Gohr 92dd87735dSAndreas Gohr /** 936cce3332SAndreas Gohr * Returns the wiki title 946cce3332SAndreas Gohr * 956cce3332SAndreas Gohr * @link https://www.dokuwiki.org/config:title 966cce3332SAndreas Gohr * @return string 976cce3332SAndreas Gohr */ 986cce3332SAndreas Gohr public function getWikiTitle() 996cce3332SAndreas Gohr { 1006cce3332SAndreas Gohr global $conf; 1016cce3332SAndreas Gohr return $conf['title']; 1026cce3332SAndreas Gohr } 1036cce3332SAndreas Gohr 1046cce3332SAndreas Gohr /** 1056cce3332SAndreas Gohr * Return the current server time 1066cce3332SAndreas Gohr * 1076cce3332SAndreas Gohr * Returns a Unix timestamp (seconds since 1970-01-01 00:00:00 UTC). 1086cce3332SAndreas Gohr * 1096cce3332SAndreas Gohr * You can use this to compensate for differences between your client's time and the 1106cce3332SAndreas Gohr * server's time when working with last modified timestamps (revisions). 1116cce3332SAndreas Gohr * 1126cce3332SAndreas Gohr * @return int A unix timestamp 1136cce3332SAndreas Gohr */ 1146cce3332SAndreas Gohr public function getWikiTime() 1156cce3332SAndreas Gohr { 1166cce3332SAndreas Gohr return time(); 1176cce3332SAndreas Gohr } 1186cce3332SAndreas Gohr 1196cce3332SAndreas Gohr // endregion 1206cce3332SAndreas Gohr 1216cce3332SAndreas Gohr // region user 1226cce3332SAndreas Gohr 1236cce3332SAndreas Gohr /** 124dd87735dSAndreas Gohr * Login 125dd87735dSAndreas Gohr * 1268a9282a2SAndreas Gohr * This will use the given credentials and attempt to login the user. This will set the 1278a9282a2SAndreas Gohr * appropriate cookies, which can be used for subsequent requests. 1288a9282a2SAndreas Gohr * 129fe9f11e2SAndreas Gohr * Use of this mechanism is discouraged. Using token authentication is preferred. 130fe9f11e2SAndreas Gohr * 1318a9282a2SAndreas Gohr * @param string $user The user name 1328a9282a2SAndreas Gohr * @param string $pass The password 133fe9f11e2SAndreas Gohr * @return int If the login was successful 134dd87735dSAndreas Gohr */ 135dd87735dSAndreas Gohr public function login($user, $pass) 136dd87735dSAndreas Gohr { 137dd87735dSAndreas Gohr global $conf; 138104a3b7cSAndreas Gohr /** @var AuthPlugin $auth */ 139dd87735dSAndreas Gohr global $auth; 140dd87735dSAndreas Gohr 141dd87735dSAndreas Gohr if (!$conf['useacl']) return 0; 1426547cfc7SGerrit Uitslag if (!$auth instanceof AuthPlugin) return 0; 143dd87735dSAndreas Gohr 144dd87735dSAndreas Gohr @session_start(); // reopen session for login 14581e99965SPhy $ok = null; 146dd87735dSAndreas Gohr if ($auth->canDo('external')) { 147dd87735dSAndreas Gohr $ok = $auth->trustExternal($user, $pass, false); 14881e99965SPhy } 14981e99965SPhy if ($ok === null) { 150104a3b7cSAndreas Gohr $evdata = [ 151dd87735dSAndreas Gohr 'user' => $user, 152dd87735dSAndreas Gohr 'password' => $pass, 153dd87735dSAndreas Gohr 'sticky' => false, 154104a3b7cSAndreas Gohr 'silent' => true 155104a3b7cSAndreas Gohr ]; 156cbb44eabSAndreas Gohr $ok = Event::createAndTrigger('AUTH_LOGIN_CHECK', $evdata, 'auth_login_wrapper'); 157dd87735dSAndreas Gohr } 158dd87735dSAndreas Gohr session_write_close(); // we're done with the session 159dd87735dSAndreas Gohr 160dd87735dSAndreas Gohr return $ok; 161dd87735dSAndreas Gohr } 162dd87735dSAndreas Gohr 163dd87735dSAndreas Gohr /** 164dd87735dSAndreas Gohr * Log off 165dd87735dSAndreas Gohr * 1668a9282a2SAndreas Gohr * Attempt to log out the current user, deleting the appropriate cookies 1678a9282a2SAndreas Gohr * 1686cce3332SAndreas Gohr * Use of this mechanism is discouraged. Using token authentication is preferred. 1696cce3332SAndreas Gohr * 1708a9282a2SAndreas Gohr * @return int 0 on failure, 1 on success 171dd87735dSAndreas Gohr */ 172dd87735dSAndreas Gohr public function logoff() 173dd87735dSAndreas Gohr { 174dd87735dSAndreas Gohr global $conf; 175dd87735dSAndreas Gohr global $auth; 176dd87735dSAndreas Gohr if (!$conf['useacl']) return 0; 1776547cfc7SGerrit Uitslag if (!$auth instanceof AuthPlugin) return 0; 178dd87735dSAndreas Gohr 179dd87735dSAndreas Gohr auth_logoff(); 180dd87735dSAndreas Gohr 181dd87735dSAndreas Gohr return 1; 182dd87735dSAndreas Gohr } 183dd87735dSAndreas Gohr 184dd87735dSAndreas Gohr /** 1856cce3332SAndreas Gohr * Info about the currently authenticated user 1866cce3332SAndreas Gohr * 1876cce3332SAndreas Gohr * @return User 1886cce3332SAndreas Gohr */ 1896cce3332SAndreas Gohr public function whoAmI() 1906cce3332SAndreas Gohr { 1916cce3332SAndreas Gohr return new User([]); 1926cce3332SAndreas Gohr } 1936cce3332SAndreas Gohr 1946cce3332SAndreas Gohr /** 1956cce3332SAndreas Gohr * Check ACL Permissions 1966cce3332SAndreas Gohr * 1976cce3332SAndreas Gohr * This call allows to check the permissions for a given page/media and user/group combination. 1986cce3332SAndreas Gohr * If no user/group is given, the current user is used. 1996cce3332SAndreas Gohr * 2006cce3332SAndreas Gohr * Read the link below to learn more about the permission levels. 2016cce3332SAndreas Gohr * 2026cce3332SAndreas Gohr * @link https://www.dokuwiki.org/acl#background_info 2036cce3332SAndreas Gohr * @param string $page A page or media ID 2046cce3332SAndreas Gohr * @param string $user username 2056cce3332SAndreas Gohr * @param string[] $groups array of groups 2066cce3332SAndreas Gohr * @return int permission level 207*902647e6SAndreas Gohr * @throws RemoteException 2086cce3332SAndreas Gohr */ 2096cce3332SAndreas Gohr public function aclCheck($page, $user = '', $groups = []) 2106cce3332SAndreas Gohr { 2116cce3332SAndreas Gohr /** @var AuthPlugin $auth */ 2126cce3332SAndreas Gohr global $auth; 2136cce3332SAndreas Gohr 214*902647e6SAndreas Gohr $page = $this->checkPage($page, false, AUTH_NONE); 215*902647e6SAndreas Gohr 2166cce3332SAndreas Gohr if ($user === '') { 2176cce3332SAndreas Gohr return auth_quickaclcheck($page); 2186cce3332SAndreas Gohr } else { 2196cce3332SAndreas Gohr if ($groups === []) { 2206cce3332SAndreas Gohr $userinfo = $auth->getUserData($user); 2216cce3332SAndreas Gohr if ($userinfo === false) { 2226cce3332SAndreas Gohr $groups = []; 2236cce3332SAndreas Gohr } else { 2246cce3332SAndreas Gohr $groups = $userinfo['grps']; 2256cce3332SAndreas Gohr } 2266cce3332SAndreas Gohr } 2276cce3332SAndreas Gohr return auth_aclcheck($page, $user, $groups); 2286cce3332SAndreas Gohr } 2296cce3332SAndreas Gohr } 2306cce3332SAndreas Gohr 2316cce3332SAndreas Gohr // endregion 2326cce3332SAndreas Gohr 2336cce3332SAndreas Gohr // region pages 2346cce3332SAndreas Gohr 2356cce3332SAndreas Gohr /** 2366cce3332SAndreas Gohr * List all pages in the given namespace (and below) 2376cce3332SAndreas Gohr * 2386cce3332SAndreas Gohr * Setting the `depth` to `0` and the `namespace` to `""` will return all pages in the wiki. 2396cce3332SAndreas Gohr * 2406cce3332SAndreas Gohr * @param string $namespace The namespace to search. Empty string for root namespace 2416cce3332SAndreas Gohr * @param int $depth How deep to search. 0 for all subnamespaces 2426cce3332SAndreas Gohr * @param bool $hash Whether to include a MD5 hash of the page content 2436cce3332SAndreas Gohr * @return Page[] A list of matching pages 2446cce3332SAndreas Gohr */ 2456cce3332SAndreas Gohr public function listPages($namespace = '', $depth = 1, $hash = false) 2466cce3332SAndreas Gohr { 2476cce3332SAndreas Gohr global $conf; 2486cce3332SAndreas Gohr 2496cce3332SAndreas Gohr $namespace = cleanID($namespace); 2506cce3332SAndreas Gohr 2516cce3332SAndreas Gohr // shortcut for all pages 2526cce3332SAndreas Gohr if ($namespace === '' && $depth === 0) { 2536cce3332SAndreas Gohr return $this->getAllPages($hash); 2546cce3332SAndreas Gohr } 2556cce3332SAndreas Gohr 2566cce3332SAndreas Gohr // run our search iterator to get the pages 2576cce3332SAndreas Gohr $dir = utf8_encodeFN(str_replace(':', '/', $namespace)); 2586cce3332SAndreas Gohr $data = []; 2596cce3332SAndreas Gohr $opts['skipacl'] = 0; 2606cce3332SAndreas Gohr $opts['depth'] = $depth; // FIXME depth needs to be calculated relative to $dir 2616cce3332SAndreas Gohr $opts['hash'] = $hash; 2626cce3332SAndreas Gohr search($data, $conf['datadir'], 'search_allpages', $opts, $dir); 2636cce3332SAndreas Gohr 2646cce3332SAndreas Gohr return array_map(fn($item) => new Page($item), $data); 2656cce3332SAndreas Gohr } 2666cce3332SAndreas Gohr 2676cce3332SAndreas Gohr /** 2686cce3332SAndreas Gohr * Get all pages at once 2696cce3332SAndreas Gohr * 2706cce3332SAndreas Gohr * This is uses the page index and is quicker than iterating which is done in listPages() 2716cce3332SAndreas Gohr * 2726cce3332SAndreas Gohr * @return Page[] A list of all pages 2736cce3332SAndreas Gohr * @see listPages() 2746cce3332SAndreas Gohr */ 2756cce3332SAndreas Gohr protected function getAllPages($hash = false) 2766cce3332SAndreas Gohr { 2776cce3332SAndreas Gohr $list = []; 2786cce3332SAndreas Gohr $pages = idx_get_indexer()->getPages(); 2796cce3332SAndreas Gohr Sort::ksort($pages); 2806cce3332SAndreas Gohr 2816cce3332SAndreas Gohr foreach (array_keys($pages) as $idx) { 2826cce3332SAndreas Gohr $perm = auth_quickaclcheck($pages[$idx]); 2836cce3332SAndreas Gohr if ($perm < AUTH_READ || isHiddenPage($pages[$idx]) || !page_exists($pages[$idx])) { 2846cce3332SAndreas Gohr continue; 2856cce3332SAndreas Gohr } 2866cce3332SAndreas Gohr 2876cce3332SAndreas Gohr $page = new Page([ 2886cce3332SAndreas Gohr 'id' => $pages[$idx], 2896cce3332SAndreas Gohr 'perm' => $perm, 2906cce3332SAndreas Gohr ]); 2916cce3332SAndreas Gohr if ($hash) $page->calculateHash(); 2926cce3332SAndreas Gohr 2936cce3332SAndreas Gohr $list[] = $page; 2946cce3332SAndreas Gohr } 2956cce3332SAndreas Gohr 2966cce3332SAndreas Gohr return $list; 2976cce3332SAndreas Gohr } 2986cce3332SAndreas Gohr 2996cce3332SAndreas Gohr /** 3006cce3332SAndreas Gohr * Do a fulltext search 3016cce3332SAndreas Gohr * 3026cce3332SAndreas Gohr * This executes a full text search and returns the results. The query uses the standard 3036cce3332SAndreas Gohr * DokuWiki search syntax. 3046cce3332SAndreas Gohr * 3056cce3332SAndreas Gohr * Snippets are provided for the first 15 results only. The title is either the first heading 3066cce3332SAndreas Gohr * or the page id depending on the wiki's configuration. 3076cce3332SAndreas Gohr * 3086cce3332SAndreas Gohr * @link https://www.dokuwiki.org/search#syntax 3096cce3332SAndreas Gohr * @param string $query The search query as supported by the DokuWiki search 3106cce3332SAndreas Gohr * @return PageHit[] A list of matching pages 3116cce3332SAndreas Gohr */ 3126cce3332SAndreas Gohr public function searchPages($query) 3136cce3332SAndreas Gohr { 3146cce3332SAndreas Gohr $regex = []; 3156cce3332SAndreas Gohr $data = ft_pageSearch($query, $regex); 3166cce3332SAndreas Gohr $pages = []; 3176cce3332SAndreas Gohr 3186cce3332SAndreas Gohr // prepare additional data 3196cce3332SAndreas Gohr $idx = 0; 3206cce3332SAndreas Gohr foreach ($data as $id => $score) { 3216cce3332SAndreas Gohr $file = wikiFN($id); 3226cce3332SAndreas Gohr 3236cce3332SAndreas Gohr if ($idx < FT_SNIPPET_NUMBER) { 3246cce3332SAndreas Gohr $snippet = ft_snippet($id, $regex); 3256cce3332SAndreas Gohr $idx++; 3266cce3332SAndreas Gohr } else { 3276cce3332SAndreas Gohr $snippet = ''; 3286cce3332SAndreas Gohr } 3296cce3332SAndreas Gohr 3306cce3332SAndreas Gohr $pages[] = new PageHit([ 3316cce3332SAndreas Gohr 'id' => $id, 3326cce3332SAndreas Gohr 'score' => (int)$score, 3336cce3332SAndreas Gohr 'rev' => filemtime($file), 3346cce3332SAndreas Gohr 'mtime' => filemtime($file), 3356cce3332SAndreas Gohr 'size' => filesize($file), 3366cce3332SAndreas Gohr 'snippet' => $snippet, 3376cce3332SAndreas Gohr 'title' => useHeading('navigation') ? p_get_first_heading($id) : $id 3386cce3332SAndreas Gohr ]); 3396cce3332SAndreas Gohr } 3406cce3332SAndreas Gohr return $pages; 3416cce3332SAndreas Gohr } 3426cce3332SAndreas Gohr 3436cce3332SAndreas Gohr /** 3446cce3332SAndreas Gohr * Get recent page changes 3456cce3332SAndreas Gohr * 3466cce3332SAndreas Gohr * Returns a list of recent changes to wiki pages. The results can be limited to changes newer than 3476cce3332SAndreas Gohr * a given timestamp. 3486cce3332SAndreas Gohr * 3496cce3332SAndreas Gohr * Only changes within the configured `$conf['recent']` range are returned. This is the default 3506cce3332SAndreas Gohr * when no timestamp is given. 3516cce3332SAndreas Gohr * 3526cce3332SAndreas Gohr * @link https://www.dokuwiki.org/config:recent 3536cce3332SAndreas Gohr * @param int $timestamp Only show changes newer than this unix timestamp 3546cce3332SAndreas Gohr * @return PageRevision[] 3556cce3332SAndreas Gohr * @author Michael Klier <chi@chimeric.de> 3566cce3332SAndreas Gohr * @author Michael Hamann <michael@content-space.de> 3576cce3332SAndreas Gohr */ 3586cce3332SAndreas Gohr public function getRecentPageChanges($timestamp = 0) 3596cce3332SAndreas Gohr { 3606cce3332SAndreas Gohr $recents = getRecentsSince($timestamp); 3616cce3332SAndreas Gohr 3626cce3332SAndreas Gohr $changes = []; 3636cce3332SAndreas Gohr foreach ($recents as $recent) { 3646cce3332SAndreas Gohr $changes[] = new PageRevision([ 3656cce3332SAndreas Gohr 'id' => $recent['id'], 3666cce3332SAndreas Gohr 'revision' => $recent['date'], 3676cce3332SAndreas Gohr 'author' => $recent['user'], 3686cce3332SAndreas Gohr 'ip' => $recent['ip'], 3696cce3332SAndreas Gohr 'summary' => $recent['sum'], 3706cce3332SAndreas Gohr 'type' => $recent['type'], 3716cce3332SAndreas Gohr 'sizechange' => $recent['sizechange'], 3726cce3332SAndreas Gohr ]); 3736cce3332SAndreas Gohr } 3746cce3332SAndreas Gohr 3756cce3332SAndreas Gohr return $changes; 3766cce3332SAndreas Gohr } 3776cce3332SAndreas Gohr 3786cce3332SAndreas Gohr /** 3796cce3332SAndreas Gohr * Get a wiki page's syntax 3806cce3332SAndreas Gohr * 3816cce3332SAndreas Gohr * Returns the syntax of the given page. When no revision is given, the current revision is returned. 3826cce3332SAndreas Gohr * 3836cce3332SAndreas Gohr * A non-existing page (or revision) will return an empty string usually. For the current revision 3846cce3332SAndreas Gohr * a page template will be returned if configured. 3856cce3332SAndreas Gohr * 3866cce3332SAndreas Gohr * Read access is required for the page. 3876cce3332SAndreas Gohr * 3886cce3332SAndreas Gohr * @param string $page wiki page id 389*902647e6SAndreas Gohr * @param string $rev Revision timestamp to access an older revision 3906cce3332SAndreas Gohr * @return string the syntax of the page 391*902647e6SAndreas Gohr * @throws AccessDeniedException 392*902647e6SAndreas Gohr * @throws RemoteException 3936cce3332SAndreas Gohr */ 3946cce3332SAndreas Gohr public function getPage($page, $rev = '') 3956cce3332SAndreas Gohr { 396*902647e6SAndreas Gohr $page = $this->checkPage($page, false); 397*902647e6SAndreas Gohr 3986cce3332SAndreas Gohr $text = rawWiki($page, $rev); 3996cce3332SAndreas Gohr if (!$text && !$rev) { 4006cce3332SAndreas Gohr return pageTemplate($page); 4016cce3332SAndreas Gohr } else { 4026cce3332SAndreas Gohr return $text; 4036cce3332SAndreas Gohr } 4046cce3332SAndreas Gohr } 4056cce3332SAndreas Gohr 4066cce3332SAndreas Gohr /** 4076cce3332SAndreas Gohr * Return a wiki page rendered to HTML 4086cce3332SAndreas Gohr * 4096cce3332SAndreas Gohr * The page is rendered to HTML as it would be in the wiki. The HTML consist only of the data for the page 4106cce3332SAndreas Gohr * content itself, no surrounding structural tags, header, footers, sidebars etc are returned. 4116cce3332SAndreas Gohr * 4126cce3332SAndreas Gohr * References in the HTML are relative to the wiki base URL unless the `canonical` configuration is set. 4136cce3332SAndreas Gohr * 414*902647e6SAndreas Gohr * If the page does not exist, an error is returned. 4156cce3332SAndreas Gohr * 4166cce3332SAndreas Gohr * @link https://www.dokuwiki.org/config:canonical 4176cce3332SAndreas Gohr * @param string $page page id 418*902647e6SAndreas Gohr * @param string $rev revision timestamp 4196cce3332SAndreas Gohr * @return string Rendered HTML for the page 420*902647e6SAndreas Gohr * @throws AccessDeniedException 421*902647e6SAndreas Gohr * @throws RemoteException 4226cce3332SAndreas Gohr */ 4236cce3332SAndreas Gohr public function getPageHTML($page, $rev = '') 4246cce3332SAndreas Gohr { 425*902647e6SAndreas Gohr $page = $this->checkPage($page); 426*902647e6SAndreas Gohr 4276cce3332SAndreas Gohr return (string)p_wiki_xhtml($page, $rev, false); 4286cce3332SAndreas Gohr } 4296cce3332SAndreas Gohr 4306cce3332SAndreas Gohr /** 4316cce3332SAndreas Gohr * Return some basic data about a page 4326cce3332SAndreas Gohr * 4336cce3332SAndreas Gohr * The call will return an error if the requested page does not exist. 4346cce3332SAndreas Gohr * 4356cce3332SAndreas Gohr * Read access is required for the page. 4366cce3332SAndreas Gohr * 4376cce3332SAndreas Gohr * @param string $page page id 438*902647e6SAndreas Gohr * @param string $rev revision timestamp 4396cce3332SAndreas Gohr * @param bool $author whether to include the author information 4406cce3332SAndreas Gohr * @param bool $hash whether to include the MD5 hash of the page content 4416cce3332SAndreas Gohr * @return Page 442*902647e6SAndreas Gohr * @throws AccessDeniedException 443*902647e6SAndreas Gohr * @throws RemoteException 4446cce3332SAndreas Gohr */ 4456cce3332SAndreas Gohr public function getPageInfo($page, $rev = '', $author = false, $hash = false) 4466cce3332SAndreas Gohr { 447*902647e6SAndreas Gohr $page = $this->checkPage($page); 4486cce3332SAndreas Gohr 4496cce3332SAndreas Gohr $result = new Page(['id' => $page, 'rev' => $rev]); 4506cce3332SAndreas Gohr if ($author) $result->retrieveAuthor(); 4516cce3332SAndreas Gohr if ($hash) $result->calculateHash(); 4526cce3332SAndreas Gohr 4536cce3332SAndreas Gohr return $result; 4546cce3332SAndreas Gohr } 4556cce3332SAndreas Gohr 4566cce3332SAndreas Gohr /** 4576cce3332SAndreas Gohr * Returns a list of available revisions of a given wiki page 4586cce3332SAndreas Gohr * 4596cce3332SAndreas Gohr * The number of returned pages is set by `$conf['recent']`, but non accessible revisions pages 4606cce3332SAndreas Gohr * are skipped, so less than that may be returned. 4616cce3332SAndreas Gohr * 4626cce3332SAndreas Gohr * @link https://www.dokuwiki.org/config:recent 4636cce3332SAndreas Gohr * @param string $page page id 4646cce3332SAndreas Gohr * @param int $first skip the first n changelog lines, 0 starts at the current revision 4656cce3332SAndreas Gohr * @return PageRevision[] 466*902647e6SAndreas Gohr * @throws AccessDeniedException 467*902647e6SAndreas Gohr * @throws RemoteException 4686cce3332SAndreas Gohr * @author Michael Klier <chi@chimeric.de> 4696cce3332SAndreas Gohr */ 4706cce3332SAndreas Gohr public function getPageVersions($page, $first = 0) 4716cce3332SAndreas Gohr { 4726cce3332SAndreas Gohr global $conf; 4736cce3332SAndreas Gohr 474*902647e6SAndreas Gohr $page = $this->checkPage($page, false); 4756cce3332SAndreas Gohr 4766cce3332SAndreas Gohr $pagelog = new PageChangeLog($page); 4776cce3332SAndreas Gohr $pagelog->setChunkSize(1024); 4786cce3332SAndreas Gohr // old revisions are counted from 0, so we need to subtract 1 for the current one 4796cce3332SAndreas Gohr $revisions = $pagelog->getRevisions($first - 1, $conf['recent']); 4806cce3332SAndreas Gohr 4816cce3332SAndreas Gohr $result = []; 4826cce3332SAndreas Gohr foreach ($revisions as $rev) { 4836cce3332SAndreas Gohr if (!page_exists($page, $rev)) continue; // skip non-existing revisions 4846cce3332SAndreas Gohr $info = $pagelog->getRevisionInfo($rev); 4856cce3332SAndreas Gohr 4866cce3332SAndreas Gohr $result[] = new PageRevision([ 4876cce3332SAndreas Gohr 'id' => $page, 4886cce3332SAndreas Gohr 'revision' => $rev, 4896cce3332SAndreas Gohr 'author' => $info['user'], 4906cce3332SAndreas Gohr 'ip' => $info['ip'], 4916cce3332SAndreas Gohr 'summary' => $info['sum'], 4926cce3332SAndreas Gohr 'type' => $info['type'], 4936cce3332SAndreas Gohr 'sizechange' => $info['sizechange'], 4946cce3332SAndreas Gohr ]); 4956cce3332SAndreas Gohr } 4966cce3332SAndreas Gohr 4976cce3332SAndreas Gohr return $result; 4986cce3332SAndreas Gohr } 4996cce3332SAndreas Gohr 5006cce3332SAndreas Gohr /** 5016cce3332SAndreas Gohr * Get a page's links 5026cce3332SAndreas Gohr * 5036cce3332SAndreas Gohr * This returns a list of links found in the given page. This includes internal, external and interwiki links 5046cce3332SAndreas Gohr * 505*902647e6SAndreas Gohr * Read access for the given page is needed and page has to exist. 5066cce3332SAndreas Gohr * 5076cce3332SAndreas Gohr * @param string $page page id 5086cce3332SAndreas Gohr * @return Link[] A list of links found on the given page 509*902647e6SAndreas Gohr * @throws AccessDeniedException 510*902647e6SAndreas Gohr * @throws RemoteException 5116cce3332SAndreas Gohr * @todo returning link titles would be a nice addition 5126cce3332SAndreas Gohr * @todo hash handling seems not to be correct 513*902647e6SAndreas Gohr * @author Michael Klier <chi@chimeric.de> 5146cce3332SAndreas Gohr */ 5156cce3332SAndreas Gohr public function getPageLinks($page) 5166cce3332SAndreas Gohr { 517*902647e6SAndreas Gohr $page = $this->checkPage($page); 5186cce3332SAndreas Gohr 5196cce3332SAndreas Gohr // resolve page instructions 5206cce3332SAndreas Gohr $ins = p_cached_instructions(wikiFN($page)); 5216cce3332SAndreas Gohr 5226cce3332SAndreas Gohr // instantiate new Renderer - needed for interwiki links 5236cce3332SAndreas Gohr $Renderer = new Doku_Renderer_xhtml(); 5246cce3332SAndreas Gohr $Renderer->interwiki = getInterwiki(); 5256cce3332SAndreas Gohr 5266cce3332SAndreas Gohr // parse instructions 5276cce3332SAndreas Gohr $links = []; 5286cce3332SAndreas Gohr foreach ($ins as $in) { 5296cce3332SAndreas Gohr switch ($in[0]) { 5306cce3332SAndreas Gohr case 'internallink': 5316cce3332SAndreas Gohr $links[] = new Link([ 5326cce3332SAndreas Gohr 'type' => 'local', 5336cce3332SAndreas Gohr 'page' => $in[1][0], 5346cce3332SAndreas Gohr 'href' => wl($in[1][0]), 5356cce3332SAndreas Gohr ]); 5366cce3332SAndreas Gohr break; 5376cce3332SAndreas Gohr case 'externallink': 5386cce3332SAndreas Gohr $links[] = new Link([ 5396cce3332SAndreas Gohr 'type' => 'extern', 5406cce3332SAndreas Gohr 'page' => $in[1][0], 5416cce3332SAndreas Gohr 'href' => $in[1][0], 5426cce3332SAndreas Gohr ]); 5436cce3332SAndreas Gohr break; 5446cce3332SAndreas Gohr case 'interwikilink': 5456cce3332SAndreas Gohr $url = $Renderer->_resolveInterWiki($in[1][2], $in[1][3]); 5466cce3332SAndreas Gohr $links[] = new Link([ 5476cce3332SAndreas Gohr 'type' => 'interwiki', 5486cce3332SAndreas Gohr 'page' => $in[1][0], 5496cce3332SAndreas Gohr 'href' => $url, 5506cce3332SAndreas Gohr ]); 5516cce3332SAndreas Gohr break; 5526cce3332SAndreas Gohr } 5536cce3332SAndreas Gohr } 5546cce3332SAndreas Gohr 5556cce3332SAndreas Gohr return ($links); 5566cce3332SAndreas Gohr } 5576cce3332SAndreas Gohr 5586cce3332SAndreas Gohr /** 5596cce3332SAndreas Gohr * Get a page's backlinks 5606cce3332SAndreas Gohr * 5616cce3332SAndreas Gohr * A backlink is a wiki link on another page that links to the given page. 5626cce3332SAndreas Gohr * 563*902647e6SAndreas Gohr * Only links from pages readable by the current user are returned. The page itself 564*902647e6SAndreas Gohr * needs to be readable. Otherwise an error is returned. 5656cce3332SAndreas Gohr * 5666cce3332SAndreas Gohr * @param string $page page id 5676cce3332SAndreas Gohr * @return string[] A list of pages linking to the given page 568*902647e6SAndreas Gohr * @throws AccessDeniedException 569*902647e6SAndreas Gohr * @throws RemoteException 5706cce3332SAndreas Gohr */ 5716cce3332SAndreas Gohr public function getPageBackLinks($page) 5726cce3332SAndreas Gohr { 573*902647e6SAndreas Gohr $page = $this->checkPage($page, false); 574*902647e6SAndreas Gohr 575*902647e6SAndreas Gohr return ft_backlinks($page); 5766cce3332SAndreas Gohr } 5776cce3332SAndreas Gohr 5786cce3332SAndreas Gohr /** 5796cce3332SAndreas Gohr * Lock the given set of pages 5806cce3332SAndreas Gohr * 5816cce3332SAndreas Gohr * This call will try to lock all given pages. It will return a list of pages that were 5826cce3332SAndreas Gohr * successfully locked. If a page could not be locked, eg. because a different user is 5836cce3332SAndreas Gohr * currently holding a lock, that page will be missing from the returned list. 5846cce3332SAndreas Gohr * 5856cce3332SAndreas Gohr * You should always ensure that the list of returned pages matches the given list of 5866cce3332SAndreas Gohr * pages. It's up to you to decide how to handle failed locking. 5876cce3332SAndreas Gohr * 5886cce3332SAndreas Gohr * Note: you can only lock pages that you have write access for. It is possible to create 5896cce3332SAndreas Gohr * a lock for a page that does not exist, yet. 5906cce3332SAndreas Gohr * 5916cce3332SAndreas Gohr * Note: it is not necessary to lock a page before saving it. The `savePage()` call will 5926cce3332SAndreas Gohr * automatically lock and unlock the page for you. However if you plan to do related 5936cce3332SAndreas Gohr * operations on multiple pages, locking them all at once beforehand can be useful. 5946cce3332SAndreas Gohr * 5956cce3332SAndreas Gohr * @param string[] $pages A list of pages to lock 5966cce3332SAndreas Gohr * @return string[] A list of pages that were successfully locked 5976cce3332SAndreas Gohr */ 5986cce3332SAndreas Gohr public function lockPages($pages) 5996cce3332SAndreas Gohr { 6006cce3332SAndreas Gohr $locked = []; 6016cce3332SAndreas Gohr 6026cce3332SAndreas Gohr foreach ($pages as $id) { 603*902647e6SAndreas Gohr $id = cleanID($id); 6046cce3332SAndreas Gohr if ($id === '') continue; 6056cce3332SAndreas Gohr if (auth_quickaclcheck($id) < AUTH_EDIT || checklock($id)) { 6066cce3332SAndreas Gohr continue; 6076cce3332SAndreas Gohr } 6086cce3332SAndreas Gohr lock($id); 6096cce3332SAndreas Gohr $locked[] = $id; 6106cce3332SAndreas Gohr } 6116cce3332SAndreas Gohr return $locked; 6126cce3332SAndreas Gohr } 6136cce3332SAndreas Gohr 6146cce3332SAndreas Gohr /** 6156cce3332SAndreas Gohr * Unlock the given set of pages 6166cce3332SAndreas Gohr * 6176cce3332SAndreas Gohr * This call will try to unlock all given pages. It will return a list of pages that were 6186cce3332SAndreas Gohr * successfully unlocked. If a page could not be unlocked, eg. because a different user is 6196cce3332SAndreas Gohr * currently holding a lock, that page will be missing from the returned list. 6206cce3332SAndreas Gohr * 6216cce3332SAndreas Gohr * You should always ensure that the list of returned pages matches the given list of 6226cce3332SAndreas Gohr * pages. It's up to you to decide how to handle failed unlocking. 6236cce3332SAndreas Gohr * 6246cce3332SAndreas Gohr * Note: you can only unlock pages that you have write access for. 6256cce3332SAndreas Gohr * 6266cce3332SAndreas Gohr * @param string[] $pages A list of pages to unlock 6276cce3332SAndreas Gohr * @return string[] A list of pages that were successfully unlocked 6286cce3332SAndreas Gohr */ 6296cce3332SAndreas Gohr public function unlockPages($pages) 6306cce3332SAndreas Gohr { 6316cce3332SAndreas Gohr $unlocked = []; 6326cce3332SAndreas Gohr 6336cce3332SAndreas Gohr foreach ($pages as $id) { 634*902647e6SAndreas Gohr $id = cleanID($id); 6356cce3332SAndreas Gohr if ($id === '') continue; 6366cce3332SAndreas Gohr if (auth_quickaclcheck($id) < AUTH_EDIT || !unlock($id)) { 6376cce3332SAndreas Gohr continue; 6386cce3332SAndreas Gohr } 6396cce3332SAndreas Gohr $unlocked[] = $id; 6406cce3332SAndreas Gohr } 6416cce3332SAndreas Gohr 6426cce3332SAndreas Gohr return $unlocked; 6436cce3332SAndreas Gohr } 6446cce3332SAndreas Gohr 6456cce3332SAndreas Gohr /** 6466cce3332SAndreas Gohr * Save a wiki page 6476cce3332SAndreas Gohr * 6486cce3332SAndreas Gohr * Saves the given wiki text to the given page. If the page does not exist, it will be created. 6496cce3332SAndreas Gohr * Just like in the wiki, saving an empty text will delete the page. 6506cce3332SAndreas Gohr * 6516cce3332SAndreas Gohr * You need write permissions for the given page and the page may not be locked by another user. 6526cce3332SAndreas Gohr * 6536cce3332SAndreas Gohr * @param string $page page id 6546cce3332SAndreas Gohr * @param string $text wiki text 6556cce3332SAndreas Gohr * @param string $summary edit summary 6566cce3332SAndreas Gohr * @param bool $isminor whether this is a minor edit 6576cce3332SAndreas Gohr * @return bool Returns true on success 6586cce3332SAndreas Gohr * @throws AccessDeniedException no write access for page 6596cce3332SAndreas Gohr * @throws RemoteException no id, empty new page or locked 6606cce3332SAndreas Gohr * @author Michael Klier <chi@chimeric.de> 6616cce3332SAndreas Gohr */ 6626cce3332SAndreas Gohr public function savePage($page, $text, $summary = '', $isminor = false) 6636cce3332SAndreas Gohr { 6646cce3332SAndreas Gohr global $TEXT; 6656cce3332SAndreas Gohr global $lang; 6666cce3332SAndreas Gohr 667*902647e6SAndreas Gohr $page = $this->checkPage($page, false, AUTH_EDIT); 6686cce3332SAndreas Gohr $TEXT = cleanText($text); 6696cce3332SAndreas Gohr 6706cce3332SAndreas Gohr 6716cce3332SAndreas Gohr if (!page_exists($page) && trim($TEXT) == '') { 6726cce3332SAndreas Gohr throw new RemoteException('Refusing to write an empty new wiki page', 132); 6736cce3332SAndreas Gohr } 6746cce3332SAndreas Gohr 6756cce3332SAndreas Gohr // Check, if page is locked 6766cce3332SAndreas Gohr if (checklock($page)) { 6776cce3332SAndreas Gohr throw new RemoteException('The page is currently locked', 133); 6786cce3332SAndreas Gohr } 6796cce3332SAndreas Gohr 6806cce3332SAndreas Gohr // SPAM check 6816cce3332SAndreas Gohr if (checkwordblock()) { 6826cce3332SAndreas Gohr throw new RemoteException('Positive wordblock check', 134); 6836cce3332SAndreas Gohr } 6846cce3332SAndreas Gohr 6856cce3332SAndreas Gohr // autoset summary on new pages 6866cce3332SAndreas Gohr if (!page_exists($page) && empty($summary)) { 6876cce3332SAndreas Gohr $summary = $lang['created']; 6886cce3332SAndreas Gohr } 6896cce3332SAndreas Gohr 6906cce3332SAndreas Gohr // autoset summary on deleted pages 6916cce3332SAndreas Gohr if (page_exists($page) && empty($TEXT) && empty($summary)) { 6926cce3332SAndreas Gohr $summary = $lang['deleted']; 6936cce3332SAndreas Gohr } 6946cce3332SAndreas Gohr 695*902647e6SAndreas Gohr // FIXME auto set a summary in other cases "API Edit" might be a good idea? 696*902647e6SAndreas Gohr 6976cce3332SAndreas Gohr lock($page); 6986cce3332SAndreas Gohr saveWikiText($page, $TEXT, $summary, $isminor); 6996cce3332SAndreas Gohr unlock($page); 7006cce3332SAndreas Gohr 7016cce3332SAndreas Gohr // run the indexer if page wasn't indexed yet 7026cce3332SAndreas Gohr idx_addPage($page); 7036cce3332SAndreas Gohr 7046cce3332SAndreas Gohr return true; 7056cce3332SAndreas Gohr } 7066cce3332SAndreas Gohr 7076cce3332SAndreas Gohr /** 7086cce3332SAndreas Gohr * Appends text to the end of a wiki page 7096cce3332SAndreas Gohr * 7106cce3332SAndreas Gohr * If the page does not exist, it will be created. If a page template for the non-existant 7116cce3332SAndreas Gohr * page is configured, the given text will appended to that template. 7126cce3332SAndreas Gohr * 7136cce3332SAndreas Gohr * The call will create a new page revision. 7146cce3332SAndreas Gohr * 7156cce3332SAndreas Gohr * You need write permissions for the given page. 7166cce3332SAndreas Gohr * 7176cce3332SAndreas Gohr * @param string $page page id 7186cce3332SAndreas Gohr * @param string $text wiki text 7196cce3332SAndreas Gohr * @param string $summary edit summary 7206cce3332SAndreas Gohr * @param bool $isminor whether this is a minor edit 7216cce3332SAndreas Gohr * @return bool Returns true on success 7226cce3332SAndreas Gohr * @throws AccessDeniedException 7236cce3332SAndreas Gohr * @throws RemoteException 7246cce3332SAndreas Gohr */ 7256cce3332SAndreas Gohr public function appendPage($page, $text, $summary, $isminor) 7266cce3332SAndreas Gohr { 7276cce3332SAndreas Gohr $currentpage = $this->getPage($page); 7286cce3332SAndreas Gohr if (!is_string($currentpage)) { 7296cce3332SAndreas Gohr $currentpage = ''; 7306cce3332SAndreas Gohr } 7316cce3332SAndreas Gohr return $this->savePage($page, $currentpage . $text, $summary, $isminor); 7326cce3332SAndreas Gohr } 7336cce3332SAndreas Gohr 7346cce3332SAndreas Gohr // endregion 7356cce3332SAndreas Gohr 7366cce3332SAndreas Gohr // region media 7376cce3332SAndreas Gohr 7386cce3332SAndreas Gohr /** 7396cce3332SAndreas Gohr * List all media files in the given namespace (and below) 7406cce3332SAndreas Gohr * 7416cce3332SAndreas Gohr * Setting the `depth` to `0` and the `namespace` to `""` will return all media files in the wiki. 7426cce3332SAndreas Gohr * 7436cce3332SAndreas Gohr * When `pattern` is given, it needs to be a valid regular expression as understood by PHP's 7446cce3332SAndreas Gohr * `preg_match()` including delimiters. 7456cce3332SAndreas Gohr * The pattern is matched against the full media ID, including the namespace. 7466cce3332SAndreas Gohr * 7476cce3332SAndreas Gohr * @link https://www.php.net/manual/en/reference.pcre.pattern.syntax.php 7486cce3332SAndreas Gohr * @param string $namespace The namespace to search. Empty string for root namespace 7496cce3332SAndreas Gohr * @param string $pattern A regular expression to filter the returned files 7506cce3332SAndreas Gohr * @param int $depth How deep to search. 0 for all subnamespaces 7516cce3332SAndreas Gohr * @param bool $hash Whether to include a MD5 hash of the media content 7526cce3332SAndreas Gohr * @return Media[] 7536cce3332SAndreas Gohr * @throws AccessDeniedException no access to the media files 7546cce3332SAndreas Gohr * @author Gina Haeussge <osd@foosel.net> 7556cce3332SAndreas Gohr */ 7566cce3332SAndreas Gohr public function listMedia($namespace = '', $pattern = '', $depth = 1, $hash = false) 7576cce3332SAndreas Gohr { 7586cce3332SAndreas Gohr global $conf; 7596cce3332SAndreas Gohr 7606cce3332SAndreas Gohr $namespace = cleanID($namespace); 7616cce3332SAndreas Gohr 7626cce3332SAndreas Gohr if (auth_quickaclcheck($namespace . ':*') < AUTH_READ) { 7636cce3332SAndreas Gohr throw new AccessDeniedException('You are not allowed to list media files.', 215); 7646cce3332SAndreas Gohr } 7656cce3332SAndreas Gohr 7666cce3332SAndreas Gohr $options = [ 7676cce3332SAndreas Gohr 'skipacl' => 0, 7686cce3332SAndreas Gohr 'depth' => $depth, 7696cce3332SAndreas Gohr 'hash' => $hash, 7706cce3332SAndreas Gohr 'pattern' => $pattern, 7716cce3332SAndreas Gohr ]; 7726cce3332SAndreas Gohr 7736cce3332SAndreas Gohr $dir = utf8_encodeFN(str_replace(':', '/', $namespace)); 7746cce3332SAndreas Gohr $data = []; 7756cce3332SAndreas Gohr search($data, $conf['mediadir'], 'search_media', $options, $dir); 7766cce3332SAndreas Gohr return array_map(fn($item) => new Media($item), $data); 7776cce3332SAndreas Gohr } 7786cce3332SAndreas Gohr 7796cce3332SAndreas Gohr /** 7806cce3332SAndreas Gohr * Get recent media changes 7816cce3332SAndreas Gohr * 7826cce3332SAndreas Gohr * Returns a list of recent changes to media files. The results can be limited to changes newer than 7836cce3332SAndreas Gohr * a given timestamp. 7846cce3332SAndreas Gohr * 7856cce3332SAndreas Gohr * Only changes within the configured `$conf['recent']` range are returned. This is the default 7866cce3332SAndreas Gohr * when no timestamp is given. 7876cce3332SAndreas Gohr * 7886cce3332SAndreas Gohr * @link https://www.dokuwiki.org/config:recent 7896cce3332SAndreas Gohr * @param int $timestamp Only show changes newer than this unix timestamp 7906cce3332SAndreas Gohr * @return MediaRevision[] 7916cce3332SAndreas Gohr * @author Michael Klier <chi@chimeric.de> 7926cce3332SAndreas Gohr * @author Michael Hamann <michael@content-space.de> 7936cce3332SAndreas Gohr */ 7946cce3332SAndreas Gohr public function getRecentMediaChanges($timestamp = 0) 7956cce3332SAndreas Gohr { 7966cce3332SAndreas Gohr 7976cce3332SAndreas Gohr $recents = getRecentsSince($timestamp, null, '', RECENTS_MEDIA_CHANGES); 7986cce3332SAndreas Gohr 7996cce3332SAndreas Gohr $changes = []; 8006cce3332SAndreas Gohr foreach ($recents as $recent) { 8016cce3332SAndreas Gohr $changes[] = new MediaRevision([ 8026cce3332SAndreas Gohr 'id' => $recent['id'], 8036cce3332SAndreas Gohr 'revision' => $recent['date'], 8046cce3332SAndreas Gohr 'author' => $recent['user'], 8056cce3332SAndreas Gohr 'ip' => $recent['ip'], 8066cce3332SAndreas Gohr 'summary' => $recent['sum'], 8076cce3332SAndreas Gohr 'type' => $recent['type'], 8086cce3332SAndreas Gohr 'sizechange' => $recent['sizechange'], 8096cce3332SAndreas Gohr ]); 8106cce3332SAndreas Gohr } 8116cce3332SAndreas Gohr 8126cce3332SAndreas Gohr return $changes; 8136cce3332SAndreas Gohr } 8146cce3332SAndreas Gohr 8156cce3332SAndreas Gohr /** 8166cce3332SAndreas Gohr * Get a media file's content 8176cce3332SAndreas Gohr * 8186cce3332SAndreas Gohr * Returns the content of the given media file. When no revision is given, the current revision is returned. 8196cce3332SAndreas Gohr * 8206cce3332SAndreas Gohr * @link https://en.wikipedia.org/wiki/Base64 8216cce3332SAndreas Gohr * @param string $media file id 8226cce3332SAndreas Gohr * @param int $rev revision timestamp 8236cce3332SAndreas Gohr * @return string Base64 encoded media file contents 8246cce3332SAndreas Gohr * @throws AccessDeniedException no permission for media 8256cce3332SAndreas Gohr * @throws RemoteException not exist 8266cce3332SAndreas Gohr * @author Gina Haeussge <osd@foosel.net> 8276cce3332SAndreas Gohr * 8286cce3332SAndreas Gohr */ 8296cce3332SAndreas Gohr public function getMedia($media, $rev = '') 8306cce3332SAndreas Gohr { 8316cce3332SAndreas Gohr $media = cleanID($media); 8326cce3332SAndreas Gohr if (auth_quickaclcheck($media) < AUTH_READ) { 8336cce3332SAndreas Gohr throw new AccessDeniedException('You are not allowed to read this file', 211); 8346cce3332SAndreas Gohr } 8356cce3332SAndreas Gohr 8366cce3332SAndreas Gohr $file = mediaFN($media, $rev); 8376cce3332SAndreas Gohr if (!@ file_exists($file)) { 8386cce3332SAndreas Gohr throw new RemoteException('The requested file does not exist', 221); 8396cce3332SAndreas Gohr } 8406cce3332SAndreas Gohr 8416cce3332SAndreas Gohr $data = io_readFile($file, false); 8426cce3332SAndreas Gohr return base64_encode($data); 8436cce3332SAndreas Gohr } 8446cce3332SAndreas Gohr 8456cce3332SAndreas Gohr /** 8466cce3332SAndreas Gohr * Return info about a media file 8476cce3332SAndreas Gohr * 8486cce3332SAndreas Gohr * The call will return an error if the requested media file does not exist. 8496cce3332SAndreas Gohr * 8506cce3332SAndreas Gohr * Read access is required for the media file. 8516cce3332SAndreas Gohr * 8526cce3332SAndreas Gohr * @param string $media file id 8536cce3332SAndreas Gohr * @param int $rev revision timestamp 8546cce3332SAndreas Gohr * @param bool $hash whether to include the MD5 hash of the media content 8556cce3332SAndreas Gohr * @return Media 8566cce3332SAndreas Gohr * @throws AccessDeniedException no permission for media 8576cce3332SAndreas Gohr * @throws RemoteException if not exist 8586cce3332SAndreas Gohr * @author Gina Haeussge <osd@foosel.net> 8596cce3332SAndreas Gohr */ 8606cce3332SAndreas Gohr public function getMediaInfo($media, $rev = '', $hash = false) 8616cce3332SAndreas Gohr { 8626cce3332SAndreas Gohr $media = cleanID($media); 8636cce3332SAndreas Gohr if (auth_quickaclcheck($media) < AUTH_READ) { 8646cce3332SAndreas Gohr throw new AccessDeniedException('You are not allowed to read this file', 211); 8656cce3332SAndreas Gohr } 8666cce3332SAndreas Gohr if (!media_exists($media, $rev)) { 8676cce3332SAndreas Gohr throw new RemoteException('The requested media file does not exist', 221); 8686cce3332SAndreas Gohr } 8696cce3332SAndreas Gohr 8706cce3332SAndreas Gohr $file = mediaFN($media, $rev); 8716cce3332SAndreas Gohr 8726cce3332SAndreas Gohr $info = new Media([ 8736cce3332SAndreas Gohr 'id' => $media, 8746cce3332SAndreas Gohr 'mtime' => filemtime($file), 8756cce3332SAndreas Gohr 'size' => filesize($file), 8766cce3332SAndreas Gohr ]); 8776cce3332SAndreas Gohr if ($hash) $info->calculateHash(); 8786cce3332SAndreas Gohr 8796cce3332SAndreas Gohr return $info; 8806cce3332SAndreas Gohr } 8816cce3332SAndreas Gohr 8826cce3332SAndreas Gohr /** 8836cce3332SAndreas Gohr * Uploads a file to the wiki 8846cce3332SAndreas Gohr * 8856cce3332SAndreas Gohr * The file data has to be passed as a base64 encoded string. 8866cce3332SAndreas Gohr * 8876cce3332SAndreas Gohr * @link https://en.wikipedia.org/wiki/Base64 8886cce3332SAndreas Gohr * @param string $media media id 8896cce3332SAndreas Gohr * @param string $base64 Base64 encoded file contents 8906cce3332SAndreas Gohr * @param bool $overwrite Should an existing file be overwritten? 8916cce3332SAndreas Gohr * @return bool Should always be true 8926cce3332SAndreas Gohr * @throws RemoteException 8936cce3332SAndreas Gohr * @author Michael Klier <chi@chimeric.de> 8946cce3332SAndreas Gohr */ 8956cce3332SAndreas Gohr public function saveMedia($media, $base64, $overwrite = false) 8966cce3332SAndreas Gohr { 8976cce3332SAndreas Gohr $media = cleanID($media); 8986cce3332SAndreas Gohr $auth = auth_quickaclcheck(getNS($media) . ':*'); 8996cce3332SAndreas Gohr 9006cce3332SAndreas Gohr if ($media === '') { 9016cce3332SAndreas Gohr throw new RemoteException('Media ID not given.', 231); 9026cce3332SAndreas Gohr } 9036cce3332SAndreas Gohr 9046cce3332SAndreas Gohr // clean up base64 encoded data 9056cce3332SAndreas Gohr $base64 = strtr($base64, [ 9066cce3332SAndreas Gohr "\n" => '', // strip newlines 9076cce3332SAndreas Gohr "\r" => '', // strip carriage returns 9086cce3332SAndreas Gohr '-' => '+', // RFC4648 base64url 9096cce3332SAndreas Gohr '_' => '/', // RFC4648 base64url 9106cce3332SAndreas Gohr ' ' => '+', // JavaScript data uri 9116cce3332SAndreas Gohr ]); 9126cce3332SAndreas Gohr 9136cce3332SAndreas Gohr $data = base64_decode($base64, true); 9146cce3332SAndreas Gohr if ($data === false) { 9156cce3332SAndreas Gohr throw new RemoteException('Invalid base64 encoded data.', 231); // FIXME adjust code 9166cce3332SAndreas Gohr } 9176cce3332SAndreas Gohr 9186cce3332SAndreas Gohr // save temporary file 9196cce3332SAndreas Gohr global $conf; 9206cce3332SAndreas Gohr $ftmp = $conf['tmpdir'] . '/' . md5($media . clientIP()); 9216cce3332SAndreas Gohr @unlink($ftmp); 9226cce3332SAndreas Gohr io_saveFile($ftmp, $data); 9236cce3332SAndreas Gohr 9246cce3332SAndreas Gohr $res = media_save(['name' => $ftmp], $media, $overwrite, $auth, 'rename'); 9256cce3332SAndreas Gohr if (is_array($res)) { 9266cce3332SAndreas Gohr throw new RemoteException($res[0], -$res[1]); // FIXME adjust code -1 * -1 = 1, we want a 23x code 9276cce3332SAndreas Gohr } 9286cce3332SAndreas Gohr return (bool)$res; // should always be true at this point 9296cce3332SAndreas Gohr } 9306cce3332SAndreas Gohr 9316cce3332SAndreas Gohr /** 9326cce3332SAndreas Gohr * Deletes a file from the wiki 9336cce3332SAndreas Gohr * 9346cce3332SAndreas Gohr * You need to have delete permissions for the file. 9356cce3332SAndreas Gohr * 9366cce3332SAndreas Gohr * @param string $media media id 9376cce3332SAndreas Gohr * @return bool Should always be true 9386cce3332SAndreas Gohr * @throws AccessDeniedException no permissions 9396cce3332SAndreas Gohr * @throws RemoteException file in use or not deleted 9406cce3332SAndreas Gohr * @author Gina Haeussge <osd@foosel.net> 9416cce3332SAndreas Gohr * 9426cce3332SAndreas Gohr */ 9436cce3332SAndreas Gohr public function deleteMedia($media) 9446cce3332SAndreas Gohr { 9456cce3332SAndreas Gohr $media = cleanID($media); 9466cce3332SAndreas Gohr $auth = auth_quickaclcheck($media); 9476cce3332SAndreas Gohr $res = media_delete($media, $auth); 9486cce3332SAndreas Gohr if ($res & DOKU_MEDIA_DELETED) { 9496cce3332SAndreas Gohr return true; 9506cce3332SAndreas Gohr } elseif ($res & DOKU_MEDIA_NOT_AUTH) { 9516cce3332SAndreas Gohr throw new AccessDeniedException('You don\'t have permissions to delete files.', 212); 9526cce3332SAndreas Gohr } elseif ($res & DOKU_MEDIA_INUSE) { 9536cce3332SAndreas Gohr throw new RemoteException('File is still referenced', 232); 9546cce3332SAndreas Gohr } else { 9556cce3332SAndreas Gohr throw new RemoteException('Could not delete file', 233); 9566cce3332SAndreas Gohr } 9576cce3332SAndreas Gohr } 9586cce3332SAndreas Gohr 9596cce3332SAndreas Gohr // endregion 9606cce3332SAndreas Gohr 9616cce3332SAndreas Gohr 9626cce3332SAndreas Gohr /** 963*902647e6SAndreas Gohr * Convenience method for page checks 964*902647e6SAndreas Gohr * 965*902647e6SAndreas Gohr * This method will perform multiple tasks: 966*902647e6SAndreas Gohr * 967*902647e6SAndreas Gohr * - clean the given page id 968*902647e6SAndreas Gohr * - disallow an empty page id 969*902647e6SAndreas Gohr * - check if the page exists (unless disabled) 970*902647e6SAndreas Gohr * - check if the user has the required access level (pass AUTH_NONE to skip) 971dd87735dSAndreas Gohr * 972dd87735dSAndreas Gohr * @param string $id page id 973*902647e6SAndreas Gohr * @return string the cleaned page id 974*902647e6SAndreas Gohr * @throws RemoteException 975*902647e6SAndreas Gohr * @throws AccessDeniedException 976dd87735dSAndreas Gohr */ 977*902647e6SAndreas Gohr private function checkPage($id, $existCheck = true, $minAccess = AUTH_READ) 978dd87735dSAndreas Gohr { 979dd87735dSAndreas Gohr $id = cleanID($id); 980*902647e6SAndreas Gohr if ($id === '') { 981*902647e6SAndreas Gohr throw new RemoteException('Empty or invalid page ID given', 131); // FIXME check code 982dd87735dSAndreas Gohr } 983*902647e6SAndreas Gohr 984*902647e6SAndreas Gohr if ($existCheck && !page_exists($id)) { 985*902647e6SAndreas Gohr throw new RemoteException('The requested page does not exist', 121); // FIXME check code 986*902647e6SAndreas Gohr } 987*902647e6SAndreas Gohr 988*902647e6SAndreas Gohr if ($minAccess && auth_quickaclcheck($id) < $minAccess) { 989*902647e6SAndreas Gohr throw new AccessDeniedException('You are not allowed to read this page', 111); // FIXME check code 990*902647e6SAndreas Gohr } 991*902647e6SAndreas Gohr 992dd87735dSAndreas Gohr return $id; 993dd87735dSAndreas Gohr } 994dd87735dSAndreas Gohr} 995