increase API version for new call, remove unused parameter
[dokuwiki.git] / inc / Remote / ApiCore.php
blob0b54f6c328d5cca201712eaeaa20ff3c5447d218
1 <?php
3 namespace dokuwiki\Remote;
5 use Doku_Renderer_xhtml;
6 use dokuwiki\ChangeLog\PageChangeLog;
7 use dokuwiki\Extension\AuthPlugin;
8 use dokuwiki\Extension\Event;
9 use dokuwiki\Remote\Response\Link;
10 use dokuwiki\Remote\Response\Media;
11 use dokuwiki\Remote\Response\MediaChange;
12 use dokuwiki\Remote\Response\Page;
13 use dokuwiki\Remote\Response\PageChange;
14 use dokuwiki\Remote\Response\PageHit;
15 use dokuwiki\Remote\Response\User;
16 use dokuwiki\Utf8\Sort;
18 /**
19 * Provides the core methods for the remote API.
20 * The methods are ordered in 'wiki.<method>' and 'dokuwiki.<method>' namespaces
22 class ApiCore
24 /** @var int Increased whenever the API is changed */
25 public const API_VERSION = 13;
27 /**
28 * Returns details about the core methods
30 * @return array
32 public function getMethods()
34 return [
35 'core.getAPIVersion' => (new ApiCall([$this, 'getAPIVersion'], 'info'))->setPublic(),
37 'core.getWikiVersion' => new ApiCall('getVersion', 'info'),
38 'core.getWikiTitle' => (new ApiCall([$this, 'getWikiTitle'], 'info'))->setPublic(),
39 'core.getWikiTime' => (new ApiCall([$this, 'getWikiTime'], 'info')),
41 'core.login' => (new ApiCall([$this, 'login'], 'user'))->setPublic(),
42 'core.logoff' => new ApiCall([$this, 'logoff'], 'user'),
43 'core.whoAmI' => (new ApiCall([$this, 'whoAmI'], 'user')),
44 'core.aclCheck' => new ApiCall([$this, 'aclCheck'], 'user'),
46 'core.listPages' => new ApiCall([$this, 'listPages'], 'pages'),
47 'core.searchPages' => new ApiCall([$this, 'searchPages'], 'pages'),
48 'core.getRecentPageChanges' => new ApiCall([$this, 'getRecentPageChanges'], 'pages'),
50 'core.getPage' => (new ApiCall([$this, 'getPage'], 'pages')),
51 'core.getPageHTML' => (new ApiCall([$this, 'getPageHTML'], 'pages')),
52 'core.getPageInfo' => (new ApiCall([$this, 'getPageInfo'], 'pages')),
53 'core.getPageHistory' => new ApiCall([$this, 'getPageHistory'], 'pages'),
54 'core.getPageLinks' => new ApiCall([$this, 'getPageLinks'], 'pages'),
55 'core.getPageBackLinks' => new ApiCall([$this, 'getPageBackLinks'], 'pages'),
57 'core.lockPages' => new ApiCall([$this, 'lockPages'], 'pages'),
58 'core.unlockPages' => new ApiCall([$this, 'unlockPages'], 'pages'),
59 'core.savePage' => new ApiCall([$this, 'savePage'], 'pages'),
60 'core.appendPage' => new ApiCall([$this, 'appendPage'], 'pages'),
62 'core.listMedia' => new ApiCall([$this, 'listMedia'], 'media'),
63 'core.getRecentMediaChanges' => new ApiCall([$this, 'getRecentMediaChanges'], 'media'),
65 'core.getMedia' => new ApiCall([$this, 'getMedia'], 'media'),
66 'core.getMediaInfo' => new ApiCall([$this, 'getMediaInfo'], 'media'),
67 'core.getMediaUsage' => new ApiCall([$this, 'getMediaUsage'], 'media'),
68 // todo: implement getMediaHistory
70 'core.saveMedia' => new ApiCall([$this, 'saveMedia'], 'media'),
71 'core.deleteMedia' => new ApiCall([$this, 'deleteMedia'], 'media'),
75 // region info
77 /**
78 * Return the API version
80 * This is the version of the DokuWiki API. It increases whenever the API definition changes.
82 * When developing a client, you should check this version and make sure you can handle it.
84 * @return int
86 public function getAPIVersion()
88 return self::API_VERSION;
91 /**
92 * Returns the wiki title
94 * @link https://www.dokuwiki.org/config:title
95 * @return string
97 public function getWikiTitle()
99 global $conf;
100 return $conf['title'];
104 * Return the current server time
106 * Returns a Unix timestamp (seconds since 1970-01-01 00:00:00 UTC).
108 * You can use this to compensate for differences between your client's time and the
109 * server's time when working with last modified timestamps (revisions).
111 * @return int A unix timestamp
113 public function getWikiTime()
115 return time();
118 // endregion
120 // region user
123 * Login
125 * This will use the given credentials and attempt to login the user. This will set the
126 * appropriate cookies, which can be used for subsequent requests.
128 * Use of this mechanism is discouraged. Using token authentication is preferred.
130 * @param string $user The user name
131 * @param string $pass The password
132 * @return int If the login was successful
134 public function login($user, $pass)
136 global $conf;
137 /** @var AuthPlugin $auth */
138 global $auth;
140 if (!$conf['useacl']) return 0;
141 if (!$auth instanceof AuthPlugin) return 0;
143 @session_start(); // reopen session for login
144 $ok = null;
145 if ($auth->canDo('external')) {
146 $ok = $auth->trustExternal($user, $pass, false);
148 if ($ok === null) {
149 $evdata = [
150 'user' => $user,
151 'password' => $pass,
152 'sticky' => false,
153 'silent' => true
155 $ok = Event::createAndTrigger('AUTH_LOGIN_CHECK', $evdata, 'auth_login_wrapper');
157 session_write_close(); // we're done with the session
159 return $ok;
163 * Log off
165 * Attempt to log out the current user, deleting the appropriate cookies
167 * Use of this mechanism is discouraged. Using token authentication is preferred.
169 * @return int 0 on failure, 1 on success
171 public function logoff()
173 global $conf;
174 global $auth;
175 if (!$conf['useacl']) return 0;
176 if (!$auth instanceof AuthPlugin) return 0;
178 auth_logoff();
180 return 1;
184 * Info about the currently authenticated user
186 * @return User
188 public function whoAmI()
190 return new User();
194 * Check ACL Permissions
196 * This call allows to check the permissions for a given page/media and user/group combination.
197 * If no user/group is given, the current user is used.
199 * Read the link below to learn more about the permission levels.
201 * @link https://www.dokuwiki.org/acl#background_info
202 * @param string $page A page or media ID
203 * @param string $user username
204 * @param string[] $groups array of groups
205 * @return int permission level
206 * @throws RemoteException
208 public function aclCheck($page, $user = '', $groups = [])
210 /** @var AuthPlugin $auth */
211 global $auth;
213 $page = $this->checkPage($page, 0, false, AUTH_NONE);
215 if ($user === '') {
216 return auth_quickaclcheck($page);
217 } else {
218 if ($groups === []) {
219 $userinfo = $auth->getUserData($user);
220 if ($userinfo === false) {
221 $groups = [];
222 } else {
223 $groups = $userinfo['grps'];
226 return auth_aclcheck($page, $user, $groups);
230 // endregion
232 // region pages
235 * List all pages in the given namespace (and below)
237 * Setting the `depth` to `0` and the `namespace` to `""` will return all pages in the wiki.
239 * Note: author information is not available in this call.
241 * @param string $namespace The namespace to search. Empty string for root namespace
242 * @param int $depth How deep to search. 0 for all subnamespaces
243 * @param bool $hash Whether to include a MD5 hash of the page content
244 * @return Page[] A list of matching pages
245 * @todo might be a good idea to replace search_allpages with search_universal
247 public function listPages($namespace = '', $depth = 1, $hash = false)
249 global $conf;
251 $namespace = cleanID($namespace);
253 // shortcut for all pages
254 if ($namespace === '' && $depth === 0) {
255 return $this->getAllPages($hash);
258 // search_allpages handles depth weird, we need to add the given namespace depth
259 if ($depth) {
260 $depth += substr_count($namespace, ':') + 1;
263 // run our search iterator to get the pages
264 $dir = utf8_encodeFN(str_replace(':', '/', $namespace));
265 $data = [];
266 $opts['skipacl'] = 0;
267 $opts['depth'] = $depth;
268 $opts['hash'] = $hash;
269 search($data, $conf['datadir'], 'search_allpages', $opts, $dir);
271 return array_map(static fn($item) => new Page(
272 $item['id'],
273 0, // we're searching current revisions only
274 $item['mtime'],
275 '', // not returned by search_allpages
276 $item['size'],
277 null, // not returned by search_allpages
278 $item['hash'] ?? ''
279 ), $data);
283 * Get all pages at once
285 * This is uses the page index and is quicker than iterating which is done in listPages()
287 * @return Page[] A list of all pages
288 * @see listPages()
290 protected function getAllPages($hash = false)
292 $list = [];
293 $pages = idx_get_indexer()->getPages();
294 Sort::ksort($pages);
296 foreach (array_keys($pages) as $idx) {
297 $perm = auth_quickaclcheck($pages[$idx]);
298 if ($perm < AUTH_READ || isHiddenPage($pages[$idx]) || !page_exists($pages[$idx])) {
299 continue;
302 $page = new Page($pages[$idx], 0, 0, '', null, $perm);
303 if ($hash) $page->calculateHash();
305 $list[] = $page;
308 return $list;
312 * Do a fulltext search
314 * This executes a full text search and returns the results. The query uses the standard
315 * DokuWiki search syntax.
317 * Snippets are provided for the first 15 results only. The title is either the first heading
318 * or the page id depending on the wiki's configuration.
320 * @link https://www.dokuwiki.org/search#syntax
321 * @param string $query The search query as supported by the DokuWiki search
322 * @return PageHit[] A list of matching pages
324 public function searchPages($query)
326 $regex = [];
327 $data = ft_pageSearch($query, $regex);
328 $pages = [];
330 // prepare additional data
331 $idx = 0;
332 foreach ($data as $id => $score) {
333 if ($idx < FT_SNIPPET_NUMBER) {
334 $snippet = ft_snippet($id, $regex);
335 $idx++;
336 } else {
337 $snippet = '';
340 $pages[] = new PageHit(
341 $id,
342 $snippet,
343 $score,
344 useHeading('navigation') ? p_get_first_heading($id) : $id
347 return $pages;
351 * Get recent page changes
353 * Returns a list of recent changes to wiki pages. The results can be limited to changes newer than
354 * a given timestamp.
356 * Only changes within the configured `$conf['recent']` range are returned. This is the default
357 * when no timestamp is given.
359 * @link https://www.dokuwiki.org/config:recent
360 * @param int $timestamp Only show changes newer than this unix timestamp
361 * @return PageChange[]
362 * @author Michael Klier <chi@chimeric.de>
363 * @author Michael Hamann <michael@content-space.de>
365 public function getRecentPageChanges($timestamp = 0)
367 $recents = getRecentsSince($timestamp);
369 $changes = [];
370 foreach ($recents as $recent) {
371 $changes[] = new PageChange(
372 $recent['id'],
373 $recent['date'],
374 $recent['user'],
375 $recent['ip'],
376 $recent['sum'],
377 $recent['type'],
378 $recent['sizechange']
382 return $changes;
386 * Get a wiki page's syntax
388 * Returns the syntax of the given page. When no revision is given, the current revision is returned.
390 * A non-existing page (or revision) will return an empty string usually. For the current revision
391 * a page template will be returned if configured.
393 * Read access is required for the page.
395 * @param string $page wiki page id
396 * @param int $rev Revision timestamp to access an older revision
397 * @return string the syntax of the page
398 * @throws AccessDeniedException
399 * @throws RemoteException
401 public function getPage($page, $rev = 0)
403 $page = $this->checkPage($page, $rev, false);
405 $text = rawWiki($page, $rev);
406 if (!$text && !$rev) {
407 return pageTemplate($page);
408 } else {
409 return $text;
414 * Return a wiki page rendered to HTML
416 * The page is rendered to HTML as it would be in the wiki. The HTML consist only of the data for the page
417 * content itself, no surrounding structural tags, header, footers, sidebars etc are returned.
419 * References in the HTML are relative to the wiki base URL unless the `canonical` configuration is set.
421 * If the page does not exist, an error is returned.
423 * @link https://www.dokuwiki.org/config:canonical
424 * @param string $page page id
425 * @param int $rev revision timestamp
426 * @return string Rendered HTML for the page
427 * @throws AccessDeniedException
428 * @throws RemoteException
430 public function getPageHTML($page, $rev = 0)
432 $page = $this->checkPage($page, $rev);
434 return (string)p_wiki_xhtml($page, $rev, false);
438 * Return some basic data about a page
440 * The call will return an error if the requested page does not exist.
442 * Read access is required for the page.
444 * @param string $page page id
445 * @param int $rev revision timestamp
446 * @param bool $author whether to include the author information
447 * @param bool $hash whether to include the MD5 hash of the page content
448 * @return Page
449 * @throws AccessDeniedException
450 * @throws RemoteException
452 public function getPageInfo($page, $rev = 0, $author = false, $hash = false)
454 $page = $this->checkPage($page, $rev);
456 $result = new Page($page, $rev);
457 if ($author) $result->retrieveAuthor();
458 if ($hash) $result->calculateHash();
460 return $result;
464 * Returns a list of available revisions of a given wiki page
466 * The number of returned pages is set by `$conf['recent']`, but non accessible revisions pages
467 * are skipped, so less than that may be returned.
469 * @link https://www.dokuwiki.org/config:recent
470 * @param string $page page id
471 * @param int $first skip the first n changelog lines, 0 starts at the current revision
472 * @return PageChange[]
473 * @throws AccessDeniedException
474 * @throws RemoteException
475 * @author Michael Klier <chi@chimeric.de>
477 public function getPageHistory($page, $first = 0)
479 global $conf;
481 $page = $this->checkPage($page, 0, false);
483 $pagelog = new PageChangeLog($page);
484 $pagelog->setChunkSize(1024);
485 // old revisions are counted from 0, so we need to subtract 1 for the current one
486 $revisions = $pagelog->getRevisions($first - 1, $conf['recent']);
488 $result = [];
489 foreach ($revisions as $rev) {
490 if (!page_exists($page, $rev)) continue; // skip non-existing revisions
491 $info = $pagelog->getRevisionInfo($rev);
493 $result[] = new PageChange(
494 $page,
495 $rev,
496 $info['user'],
497 $info['ip'],
498 $info['sum'],
499 $info['type'],
500 $info['sizechange']
504 return $result;
508 * Get a page's links
510 * This returns a list of links found in the given page. This includes internal, external and interwiki links
512 * If a link occurs multiple times on the page, it will be returned multiple times.
514 * Read access for the given page is needed and page has to exist.
516 * @param string $page page id
517 * @return Link[] A list of links found on the given page
518 * @throws AccessDeniedException
519 * @throws RemoteException
520 * @todo returning link titles would be a nice addition
521 * @todo hash handling seems not to be correct
522 * @todo maybe return the same link only once?
523 * @author Michael Klier <chi@chimeric.de>
525 public function getPageLinks($page)
527 $page = $this->checkPage($page);
529 // resolve page instructions
530 $ins = p_cached_instructions(wikiFN($page));
532 // instantiate new Renderer - needed for interwiki links
533 $Renderer = new Doku_Renderer_xhtml();
534 $Renderer->interwiki = getInterwiki();
536 // parse instructions
537 $links = [];
538 foreach ($ins as $in) {
539 switch ($in[0]) {
540 case 'internallink':
541 $links[] = new Link('local', $in[1][0], wl($in[1][0]));
542 break;
543 case 'externallink':
544 $links[] = new Link('extern', $in[1][0], $in[1][0]);
545 break;
546 case 'interwikilink':
547 $url = $Renderer->_resolveInterWiki($in[1][2], $in[1][3]);
548 $links[] = new Link('interwiki', $in[1][0], $url);
549 break;
553 return ($links);
557 * Get a page's backlinks
559 * A backlink is a wiki link on another page that links to the given page.
561 * Only links from pages readable by the current user are returned. The page itself
562 * needs to be readable. Otherwise an error is returned.
564 * @param string $page page id
565 * @return string[] A list of pages linking to the given page
566 * @throws AccessDeniedException
567 * @throws RemoteException
569 public function getPageBackLinks($page)
571 $page = $this->checkPage($page, 0, false);
573 return ft_backlinks($page);
577 * Lock the given set of pages
579 * This call will try to lock all given pages. It will return a list of pages that were
580 * successfully locked. If a page could not be locked, eg. because a different user is
581 * currently holding a lock, that page will be missing from the returned list.
583 * You should always ensure that the list of returned pages matches the given list of
584 * pages. It's up to you to decide how to handle failed locking.
586 * Note: you can only lock pages that you have write access for. It is possible to create
587 * a lock for a page that does not exist, yet.
589 * Note: it is not necessary to lock a page before saving it. The `savePage()` call will
590 * automatically lock and unlock the page for you. However if you plan to do related
591 * operations on multiple pages, locking them all at once beforehand can be useful.
593 * @param string[] $pages A list of pages to lock
594 * @return string[] A list of pages that were successfully locked
596 public function lockPages($pages)
598 $locked = [];
600 foreach ($pages as $id) {
601 $id = cleanID($id);
602 if ($id === '') continue;
603 if (auth_quickaclcheck($id) < AUTH_EDIT || checklock($id)) {
604 continue;
606 lock($id);
607 $locked[] = $id;
609 return $locked;
613 * Unlock the given set of pages
615 * This call will try to unlock all given pages. It will return a list of pages that were
616 * successfully unlocked. If a page could not be unlocked, eg. because a different user is
617 * currently holding a lock, that page will be missing from the returned list.
619 * You should always ensure that the list of returned pages matches the given list of
620 * pages. It's up to you to decide how to handle failed unlocking.
622 * Note: you can only unlock pages that you have write access for.
624 * @param string[] $pages A list of pages to unlock
625 * @return string[] A list of pages that were successfully unlocked
627 public function unlockPages($pages)
629 $unlocked = [];
631 foreach ($pages as $id) {
632 $id = cleanID($id);
633 if ($id === '') continue;
634 if (auth_quickaclcheck($id) < AUTH_EDIT || !unlock($id)) {
635 continue;
637 $unlocked[] = $id;
640 return $unlocked;
644 * Save a wiki page
646 * Saves the given wiki text to the given page. If the page does not exist, it will be created.
647 * Just like in the wiki, saving an empty text will delete the page.
649 * You need write permissions for the given page and the page may not be locked by another user.
651 * @param string $page page id
652 * @param string $text wiki text
653 * @param string $summary edit summary
654 * @param bool $isminor whether this is a minor edit
655 * @return bool Returns true on success
656 * @throws AccessDeniedException no write access for page
657 * @throws RemoteException no id, empty new page or locked
658 * @author Michael Klier <chi@chimeric.de>
660 public function savePage($page, $text, $summary = '', $isminor = false)
662 global $TEXT;
663 global $lang;
665 $page = $this->checkPage($page, 0, false, AUTH_EDIT);
666 $TEXT = cleanText($text);
669 if (!page_exists($page) && trim($TEXT) == '') {
670 throw new RemoteException('Refusing to write an empty new wiki page', 132);
673 // Check, if page is locked
674 if (checklock($page)) {
675 throw new RemoteException('The page is currently locked', 133);
678 // SPAM check
679 if (checkwordblock()) {
680 throw new RemoteException('The page content was blocked', 134);
683 // autoset summary on new pages
684 if (!page_exists($page) && empty($summary)) {
685 $summary = $lang['created'];
688 // autoset summary on deleted pages
689 if (page_exists($page) && empty($TEXT) && empty($summary)) {
690 $summary = $lang['deleted'];
693 // FIXME auto set a summary in other cases "API Edit" might be a good idea?
695 lock($page);
696 saveWikiText($page, $TEXT, $summary, $isminor);
697 unlock($page);
699 // run the indexer if page wasn't indexed yet
700 idx_addPage($page);
702 return true;
706 * Appends text to the end of a wiki page
708 * If the page does not exist, it will be created. If a page template for the non-existant
709 * page is configured, the given text will appended to that template.
711 * The call will create a new page revision.
713 * You need write permissions for the given page.
715 * @param string $page page id
716 * @param string $text wiki text
717 * @param string $summary edit summary
718 * @param bool $isminor whether this is a minor edit
719 * @return bool Returns true on success
720 * @throws AccessDeniedException
721 * @throws RemoteException
723 public function appendPage($page, $text, $summary = '', $isminor = false)
725 $currentpage = $this->getPage($page);
726 if (!is_string($currentpage)) {
727 $currentpage = '';
729 return $this->savePage($page, $currentpage . $text, $summary, $isminor);
732 // endregion
734 // region media
737 * List all media files in the given namespace (and below)
739 * Setting the `depth` to `0` and the `namespace` to `""` will return all media files in the wiki.
741 * When `pattern` is given, it needs to be a valid regular expression as understood by PHP's
742 * `preg_match()` including delimiters.
743 * The pattern is matched against the full media ID, including the namespace.
745 * @link https://www.php.net/manual/en/reference.pcre.pattern.syntax.php
746 * @param string $namespace The namespace to search. Empty string for root namespace
747 * @param string $pattern A regular expression to filter the returned files
748 * @param int $depth How deep to search. 0 for all subnamespaces
749 * @param bool $hash Whether to include a MD5 hash of the media content
750 * @return Media[]
751 * @author Gina Haeussge <osd@foosel.net>
753 public function listMedia($namespace = '', $pattern = '', $depth = 1, $hash = false)
755 global $conf;
757 $namespace = cleanID($namespace);
759 $options = [
760 'skipacl' => 0,
761 'depth' => $depth,
762 'hash' => $hash,
763 'pattern' => $pattern,
766 $dir = utf8_encodeFN(str_replace(':', '/', $namespace));
767 $data = [];
768 search($data, $conf['mediadir'], 'search_media', $options, $dir);
769 return array_map(static fn($item) => new Media(
770 $item['id'],
771 0, // we're searching current revisions only
772 $item['mtime'],
773 $item['size'],
774 $item['perm'],
775 $item['isimg'],
776 $item['hash'] ?? ''
777 ), $data);
781 * Get recent media changes
783 * Returns a list of recent changes to media files. The results can be limited to changes newer than
784 * a given timestamp.
786 * Only changes within the configured `$conf['recent']` range are returned. This is the default
787 * when no timestamp is given.
789 * @link https://www.dokuwiki.org/config:recent
790 * @param int $timestamp Only show changes newer than this unix timestamp
791 * @return MediaChange[]
792 * @author Michael Klier <chi@chimeric.de>
793 * @author Michael Hamann <michael@content-space.de>
795 public function getRecentMediaChanges($timestamp = 0)
798 $recents = getRecentsSince($timestamp, null, '', RECENTS_MEDIA_CHANGES);
800 $changes = [];
801 foreach ($recents as $recent) {
802 $changes[] = new MediaChange(
803 $recent['id'],
804 $recent['date'],
805 $recent['user'],
806 $recent['ip'],
807 $recent['sum'],
808 $recent['type'],
809 $recent['sizechange']
813 return $changes;
817 * Get a media file's content
819 * Returns the content of the given media file. When no revision is given, the current revision is returned.
821 * @link https://en.wikipedia.org/wiki/Base64
822 * @param string $media file id
823 * @param int $rev revision timestamp
824 * @return string Base64 encoded media file contents
825 * @throws AccessDeniedException no permission for media
826 * @throws RemoteException not exist
827 * @author Gina Haeussge <osd@foosel.net>
830 public function getMedia($media, $rev = 0)
832 $media = cleanID($media);
833 if (auth_quickaclcheck($media) < AUTH_READ) {
834 throw new AccessDeniedException('You are not allowed to read this media file', 211);
837 $file = mediaFN($media, $rev);
838 if (!@ file_exists($file)) {
839 throw new RemoteException('The requested media file (revision) does not exist', 221);
842 $data = io_readFile($file, false);
843 return base64_encode($data);
847 * Return info about a media file
849 * The call will return an error if the requested media file does not exist.
851 * Read access is required for the media file.
853 * @param string $media file id
854 * @param int $rev revision timestamp
855 * @param bool $author whether to include the author information
856 * @param bool $hash whether to include the MD5 hash of the media content
857 * @return Media
858 * @throws AccessDeniedException no permission for media
859 * @throws RemoteException if not exist
860 * @author Gina Haeussge <osd@foosel.net>
862 public function getMediaInfo($media, $rev = 0, $author = false, $hash = false)
864 $media = cleanID($media);
865 if (auth_quickaclcheck($media) < AUTH_READ) {
866 throw new AccessDeniedException('You are not allowed to read this media file', 211);
868 if (!media_exists($media, $rev)) {
869 throw new RemoteException('The requested media file does not exist', 221);
872 $info = new Media($media, $rev);
873 if ($hash) $info->calculateHash();
874 if ($author) $info->retrieveAuthor();
876 return $info;
880 * Returns the pages that use a given media file
882 * The call will return an error if the requested media file does not exist.
884 * Read access is required for the media file.
886 * Since API Version 13
888 * @param string $media file id
889 * @return string[] A list of pages linking to the given page
890 * @throws AccessDeniedException no permission for media
891 * @throws RemoteException if not exist
893 public function getMediaUsage($media)
895 $media = cleanID($media);
896 if (auth_quickaclcheck($media) < AUTH_READ) {
897 throw new AccessDeniedException('You are not allowed to read this media file', 211);
899 if (!media_exists($media)) {
900 throw new RemoteException('The requested media file does not exist', 221);
903 return ft_mediause($media);
907 * Uploads a file to the wiki
909 * The file data has to be passed as a base64 encoded string.
911 * @link https://en.wikipedia.org/wiki/Base64
912 * @param string $media media id
913 * @param string $base64 Base64 encoded file contents
914 * @param bool $overwrite Should an existing file be overwritten?
915 * @return bool Should always be true
916 * @throws RemoteException
917 * @author Michael Klier <chi@chimeric.de>
919 public function saveMedia($media, $base64, $overwrite = false)
921 $media = cleanID($media);
922 $auth = auth_quickaclcheck(getNS($media) . ':*');
924 if ($media === '') {
925 throw new RemoteException('Empty or invalid media ID given', 231);
928 // clean up base64 encoded data
929 $base64 = strtr($base64, [
930 "\n" => '', // strip newlines
931 "\r" => '', // strip carriage returns
932 '-' => '+', // RFC4648 base64url
933 '_' => '/', // RFC4648 base64url
934 ' ' => '+', // JavaScript data uri
937 $data = base64_decode($base64, true);
938 if ($data === false) {
939 throw new RemoteException('Invalid base64 encoded data', 234);
942 if ($data === '') {
943 throw new RemoteException('Empty file given', 235);
946 // save temporary file
947 global $conf;
948 $ftmp = $conf['tmpdir'] . '/' . md5($media . clientIP());
949 @unlink($ftmp);
950 io_saveFile($ftmp, $data);
952 $res = media_save(['name' => $ftmp], $media, $overwrite, $auth, 'rename');
953 if (is_array($res)) {
954 throw new RemoteException('Failed to save media: ' . $res[0], 236);
956 return (bool)$res; // should always be true at this point
960 * Deletes a file from the wiki
962 * You need to have delete permissions for the file.
964 * @param string $media media id
965 * @return bool Should always be true
966 * @throws AccessDeniedException no permissions
967 * @throws RemoteException file in use or not deleted
968 * @author Gina Haeussge <osd@foosel.net>
971 public function deleteMedia($media)
973 $media = cleanID($media);
975 $auth = auth_quickaclcheck($media);
976 $res = media_delete($media, $auth);
977 if ($res & DOKU_MEDIA_DELETED) {
978 return true;
979 } elseif ($res & DOKU_MEDIA_NOT_AUTH) {
980 throw new AccessDeniedException('You are not allowed to delete this media file', 212);
981 } elseif ($res & DOKU_MEDIA_INUSE) {
982 throw new RemoteException('Media file is still referenced', 232);
983 } elseif (!media_exists($media)) {
984 throw new RemoteException('The media file requested to delete does not exist', 221);
985 } else {
986 throw new RemoteException('Failed to delete media file', 233);
990 // endregion
994 * Convenience method for page checks
996 * This method will perform multiple tasks:
998 * - clean the given page id
999 * - disallow an empty page id
1000 * - check if the page exists (unless disabled)
1001 * - check if the user has the required access level (pass AUTH_NONE to skip)
1003 * @param string $id page id
1004 * @param int $rev page revision
1005 * @param bool $existCheck
1006 * @param int $minAccess
1007 * @return string the cleaned page id
1008 * @throws AccessDeniedException
1009 * @throws RemoteException
1011 private function checkPage($id, $rev = 0, $existCheck = true, $minAccess = AUTH_READ)
1013 $id = cleanID($id);
1014 if ($id === '') {
1015 throw new RemoteException('Empty or invalid page ID given', 131);
1018 if ($existCheck && !page_exists($id, $rev)) {
1019 throw new RemoteException('The requested page (revision) does not exist', 121);
1022 if ($minAccess && auth_quickaclcheck($id) < $minAccess) {
1023 throw new AccessDeniedException('You are not allowed to read this page', 111);
1026 return $id;