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