xref: /dokuwiki/inc/Remote/ApiCore.php (revision 0ff4031c084c66f9c78bfc143ba898970844a106)
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']),
47e7323dfbSAndreas Gohr            'dokuwiki.getTime' => (new ApiCall([$this, 'time']))
4842e66c7aSAndreas Gohr                ->setSummary('Returns the current server time')
4942e66c7aSAndreas Gohr                ->setReturnDescription('unix timestamp'),
5042e66c7aSAndreas Gohr            'dokuwiki.setLocks' => new ApiCall([$this, 'setLocks']),
5142e66c7aSAndreas Gohr            'dokuwiki.getTitle' => (new ApiCall([$this, 'getTitle']))
5242e66c7aSAndreas Gohr                ->setPublic(),
5342e66c7aSAndreas Gohr            'dokuwiki.appendPage' => new ApiCall([$this, 'appendPage']),
5442e66c7aSAndreas Gohr            'dokuwiki.createUser' => new ApiCall([$this, 'createUser']),
5542e66c7aSAndreas Gohr            'dokuwiki.deleteUsers' => new ApiCall([$this, 'deleteUsers']),
5642e66c7aSAndreas Gohr            'wiki.getPage' => (new ApiCall([$this, 'rawPage']))
57*0ff4031cSAndreas Gohr                ->limitArgs(['page']),
5842e66c7aSAndreas Gohr            'wiki.getPageVersion' => (new ApiCall([$this, 'rawPage']))
5942e66c7aSAndreas Gohr                ->setSummary('Get a specific revision of a wiki page'),
6042e66c7aSAndreas Gohr            'wiki.getPageHTML' => (new ApiCall([$this, 'htmlPage']))
61*0ff4031cSAndreas Gohr                ->limitArgs(['page']),
6242e66c7aSAndreas Gohr            'wiki.getPageHTMLVersion' => (new ApiCall([$this, 'htmlPage']))
6342e66c7aSAndreas Gohr                ->setSummary('Get the HTML for a specific revision of a wiki page'),
6442e66c7aSAndreas Gohr            'wiki.getAllPages' => new ApiCall([$this, 'listPages']),
6542e66c7aSAndreas Gohr            'wiki.getAttachments' => new ApiCall([$this, 'listAttachments']),
6642e66c7aSAndreas Gohr            'wiki.getBackLinks' => new ApiCall([$this, 'listBackLinks']),
6742e66c7aSAndreas Gohr            'wiki.getPageInfo' => (new ApiCall([$this, 'pageInfo']))
68*0ff4031cSAndreas Gohr                ->limitArgs(['page']),
6942e66c7aSAndreas Gohr            'wiki.getPageInfoVersion' => (new ApiCall([$this, 'pageInfo']))
7042e66c7aSAndreas Gohr                ->setSummary('Get some basic data about a specific revison of a wiki page'),
7142e66c7aSAndreas Gohr            'wiki.getPageVersions' => new ApiCall([$this, 'pageVersions']),
7242e66c7aSAndreas Gohr            'wiki.putPage' => new ApiCall([$this, 'putPage']),
7342e66c7aSAndreas Gohr            'wiki.listLinks' => new ApiCall([$this, 'listLinks']),
7442e66c7aSAndreas Gohr            'wiki.getRecentChanges' => new ApiCall([$this, 'getRecentChanges']),
7542e66c7aSAndreas Gohr            'wiki.getRecentMediaChanges' => new ApiCall([$this, 'getRecentMediaChanges']),
7642e66c7aSAndreas Gohr            'wiki.aclCheck' => new ApiCall([$this, 'aclCheck']),
7742e66c7aSAndreas Gohr            'wiki.putAttachment' => new ApiCall([$this, 'putAttachment']),
7842e66c7aSAndreas Gohr            'wiki.deleteAttachment' => new ApiCall([$this, 'deleteAttachment']),
7942e66c7aSAndreas Gohr            'wiki.getAttachment' => new ApiCall([$this, 'getAttachment']),
8042e66c7aSAndreas Gohr            'wiki.getAttachmentInfo' => new ApiCall([$this, 'getAttachmentInfo']),
8142e66c7aSAndreas Gohr            'dokuwiki.getXMLRPCAPIVersion' => (new ApiCall([$this, 'getAPIVersion']))->setPublic(),
8242e66c7aSAndreas Gohr            'wiki.getRPCVersionSupported' => (new ApiCall([$this, 'wikiRpcVersion']))->setPublic(),
83104a3b7cSAndreas Gohr        ];
84dd87735dSAndreas Gohr    }
85dd87735dSAndreas Gohr
86dd87735dSAndreas Gohr    /**
87e7323dfbSAndreas Gohr     * Return the current server time
88e7323dfbSAndreas Gohr     *
89e7323dfbSAndreas Gohr     * Uses a Unix timestamp (seconds since 1970-01-01 00:00:00 UTC)
90e7323dfbSAndreas Gohr     *
91e7323dfbSAndreas Gohr     * @return int A unix timestamp
92e7323dfbSAndreas Gohr     */
93e7323dfbSAndreas Gohr    public function time() {
94e7323dfbSAndreas Gohr        return time();
95e7323dfbSAndreas Gohr    }
96e7323dfbSAndreas Gohr
97e7323dfbSAndreas Gohr    /**
98dd87735dSAndreas Gohr     * Return a raw wiki page
99dd87735dSAndreas Gohr     *
100*0ff4031cSAndreas Gohr     * @param string $page wiki page id
1018a9282a2SAndreas Gohr     * @param int $rev revision timestamp of the page
1028a9282a2SAndreas Gohr     * @return string the syntax of the page
103dd87735dSAndreas Gohr     * @throws AccessDeniedException if no permission for page
104dd87735dSAndreas Gohr     */
105*0ff4031cSAndreas Gohr    public function rawPage($page, $rev = '')
106dd87735dSAndreas Gohr    {
107*0ff4031cSAndreas Gohr        $page = $this->resolvePageId($page);
108*0ff4031cSAndreas Gohr        if (auth_quickaclcheck($page) < AUTH_READ) {
109dd87735dSAndreas Gohr            throw new AccessDeniedException('You are not allowed to read this file', 111);
110dd87735dSAndreas Gohr        }
111*0ff4031cSAndreas Gohr        $text = rawWiki($page, $rev);
112dd87735dSAndreas Gohr        if (!$text) {
113*0ff4031cSAndreas Gohr            return pageTemplate($page);
114dd87735dSAndreas Gohr        } else {
115dd87735dSAndreas Gohr            return $text;
116dd87735dSAndreas Gohr        }
117dd87735dSAndreas Gohr    }
118dd87735dSAndreas Gohr
119dd87735dSAndreas Gohr    /**
120dd87735dSAndreas Gohr     * Return a media file
121dd87735dSAndreas Gohr     *
122*0ff4031cSAndreas Gohr     * @param string $media file id
123dd87735dSAndreas Gohr     * @return mixed media file
124dd87735dSAndreas Gohr     * @throws AccessDeniedException no permission for media
125dd87735dSAndreas Gohr     * @throws RemoteException not exist
126104a3b7cSAndreas Gohr     * @author Gina Haeussge <osd@foosel.net>
127104a3b7cSAndreas Gohr     *
128dd87735dSAndreas Gohr     */
129*0ff4031cSAndreas Gohr    public function getAttachment($media)
130dd87735dSAndreas Gohr    {
131*0ff4031cSAndreas Gohr        $media = cleanID($media);
132*0ff4031cSAndreas Gohr        if (auth_quickaclcheck(getNS($media) . ':*') < AUTH_READ) {
133dd87735dSAndreas Gohr            throw new AccessDeniedException('You are not allowed to read this file', 211);
134dd87735dSAndreas Gohr        }
135dd87735dSAndreas Gohr
136*0ff4031cSAndreas Gohr        $file = mediaFN($media);
137dd87735dSAndreas Gohr        if (!@ file_exists($file)) {
138dd87735dSAndreas Gohr            throw new RemoteException('The requested file does not exist', 221);
139dd87735dSAndreas Gohr        }
140dd87735dSAndreas Gohr
141dd87735dSAndreas Gohr        $data = io_readFile($file, false);
142dd87735dSAndreas Gohr        return $this->api->toFile($data);
143dd87735dSAndreas Gohr    }
144dd87735dSAndreas Gohr
145dd87735dSAndreas Gohr    /**
146dd87735dSAndreas Gohr     * Return info about a media file
147dd87735dSAndreas Gohr     *
148*0ff4031cSAndreas Gohr     * @param string $media page id
149dd87735dSAndreas Gohr     * @return array
150104a3b7cSAndreas Gohr     * @author Gina Haeussge <osd@foosel.net>
151104a3b7cSAndreas Gohr     *
152dd87735dSAndreas Gohr     */
153*0ff4031cSAndreas Gohr    public function getAttachmentInfo($media)
154dd87735dSAndreas Gohr    {
155*0ff4031cSAndreas Gohr        $media = cleanID($media);
156104a3b7cSAndreas Gohr        $info = ['lastModified' => $this->api->toDate(0), 'size' => 0];
157dd87735dSAndreas Gohr
158*0ff4031cSAndreas Gohr        $file = mediaFN($media);
159*0ff4031cSAndreas Gohr        if (auth_quickaclcheck(getNS($media) . ':*') >= AUTH_READ) {
160dd87735dSAndreas Gohr            if (file_exists($file)) {
161dd87735dSAndreas Gohr                $info['lastModified'] = $this->api->toDate(filemtime($file));
162dd87735dSAndreas Gohr                $info['size'] = filesize($file);
163dd87735dSAndreas Gohr            } else {
164dd87735dSAndreas Gohr                //Is it deleted media with changelog?
165*0ff4031cSAndreas Gohr                $medialog = new MediaChangeLog($media);
166dd87735dSAndreas Gohr                $revisions = $medialog->getRevisions(0, 1);
167dd87735dSAndreas Gohr                if (!empty($revisions)) {
168dd87735dSAndreas Gohr                    $info['lastModified'] = $this->api->toDate($revisions[0]);
169dd87735dSAndreas Gohr                }
170dd87735dSAndreas Gohr            }
171dd87735dSAndreas Gohr        }
172dd87735dSAndreas Gohr
173dd87735dSAndreas Gohr        return $info;
174dd87735dSAndreas Gohr    }
175dd87735dSAndreas Gohr
176dd87735dSAndreas Gohr    /**
1778a9282a2SAndreas Gohr     * Return a wiki page rendered to HTML
178dd87735dSAndreas Gohr     *
179*0ff4031cSAndreas Gohr     * @param string $page page id
1808a9282a2SAndreas Gohr     * @param string $rev revision timestamp
1818a9282a2SAndreas Gohr     * @return string Rendered HTML for the page
182dd87735dSAndreas Gohr     * @throws AccessDeniedException no access to page
183dd87735dSAndreas Gohr     */
184*0ff4031cSAndreas Gohr    public function htmlPage($page, $rev = '')
185dd87735dSAndreas Gohr    {
186*0ff4031cSAndreas Gohr        $page = $this->resolvePageId($page);
187*0ff4031cSAndreas Gohr        if (auth_quickaclcheck($page) < AUTH_READ) {
188dd87735dSAndreas Gohr            throw new AccessDeniedException('You are not allowed to read this page', 111);
189dd87735dSAndreas Gohr        }
190*0ff4031cSAndreas Gohr        return p_wiki_xhtml($page, $rev, false);
191dd87735dSAndreas Gohr    }
192dd87735dSAndreas Gohr
193dd87735dSAndreas Gohr    /**
1948a9282a2SAndreas Gohr     * List all pages
1958a9282a2SAndreas Gohr     *
1968a9282a2SAndreas Gohr     * This use the search index and only returns pages that have been indexed already
197dd87735dSAndreas Gohr     *
198dd87735dSAndreas Gohr     * @return array
199dd87735dSAndreas Gohr     */
200dd87735dSAndreas Gohr    public function listPages()
201dd87735dSAndreas Gohr    {
202104a3b7cSAndreas Gohr        $list = [];
203dd87735dSAndreas Gohr        $pages = idx_get_indexer()->getPages();
204dd87735dSAndreas Gohr        $pages = array_filter(array_filter($pages, 'isVisiblePage'), 'page_exists');
2052d85e841SAndreas Gohr        Sort::ksort($pages);
206dd87735dSAndreas Gohr
207dd87735dSAndreas Gohr        foreach (array_keys($pages) as $idx) {
208dd87735dSAndreas Gohr            $perm = auth_quickaclcheck($pages[$idx]);
209dd87735dSAndreas Gohr            if ($perm < AUTH_READ) {
210dd87735dSAndreas Gohr                continue;
211dd87735dSAndreas Gohr            }
212104a3b7cSAndreas Gohr            $page = [];
213dd87735dSAndreas Gohr            $page['id'] = trim($pages[$idx]);
214dd87735dSAndreas Gohr            $page['perms'] = $perm;
215dd87735dSAndreas Gohr            $page['size'] = @filesize(wikiFN($pages[$idx]));
216dd87735dSAndreas Gohr            $page['lastModified'] = $this->api->toDate(@filemtime(wikiFN($pages[$idx])));
217dd87735dSAndreas Gohr            $list[] = $page;
218dd87735dSAndreas Gohr        }
219dd87735dSAndreas Gohr
220dd87735dSAndreas Gohr        return $list;
221dd87735dSAndreas Gohr    }
222dd87735dSAndreas Gohr
223dd87735dSAndreas Gohr    /**
224dd87735dSAndreas Gohr     * List all pages in the given namespace (and below)
225dd87735dSAndreas Gohr     *
226dd87735dSAndreas Gohr     * @param string $ns
227dd87735dSAndreas Gohr     * @param array $opts
228dd87735dSAndreas Gohr     *    $opts['depth']   recursion level, 0 for all
229dd87735dSAndreas Gohr     *    $opts['hash']    do md5 sum of content?
230dd87735dSAndreas Gohr     * @return array
231dd87735dSAndreas Gohr     */
232104a3b7cSAndreas Gohr    public function readNamespace($ns, $opts = [])
233dd87735dSAndreas Gohr    {
234dd87735dSAndreas Gohr        global $conf;
235dd87735dSAndreas Gohr
236104a3b7cSAndreas Gohr        if (!is_array($opts)) $opts = [];
237dd87735dSAndreas Gohr
238dd87735dSAndreas Gohr        $ns = cleanID($ns);
239dd87735dSAndreas Gohr        $dir = utf8_encodeFN(str_replace(':', '/', $ns));
240104a3b7cSAndreas Gohr        $data = [];
241dd87735dSAndreas Gohr        $opts['skipacl'] = 0; // no ACL skipping for XMLRPC
242dd87735dSAndreas Gohr        search($data, $conf['datadir'], 'search_allpages', $opts, $dir);
243dd87735dSAndreas Gohr        return $data;
244dd87735dSAndreas Gohr    }
245dd87735dSAndreas Gohr
246dd87735dSAndreas Gohr    /**
2478a9282a2SAndreas Gohr     * Do a fulltext search
248dd87735dSAndreas Gohr     *
2498a9282a2SAndreas Gohr     * This executes a full text search and returns the results. Snippets are provided for the first 15 results
2508a9282a2SAndreas Gohr     *
2518a9282a2SAndreas Gohr     * @param string $query The search query as supported by the DokuWiki search
2528a9282a2SAndreas Gohr     * @return array associative array with matching pages.
253dd87735dSAndreas Gohr     */
254dd87735dSAndreas Gohr    public function search($query)
255dd87735dSAndreas Gohr    {
256104a3b7cSAndreas Gohr        $regex = [];
257dd87735dSAndreas Gohr        $data = ft_pageSearch($query, $regex);
258104a3b7cSAndreas Gohr        $pages = [];
259dd87735dSAndreas Gohr
260dd87735dSAndreas Gohr        // prepare additional data
261dd87735dSAndreas Gohr        $idx = 0;
262dd87735dSAndreas Gohr        foreach ($data as $id => $score) {
263dd87735dSAndreas Gohr            $file = wikiFN($id);
264dd87735dSAndreas Gohr
265dd87735dSAndreas Gohr            if ($idx < FT_SNIPPET_NUMBER) {
266dd87735dSAndreas Gohr                $snippet = ft_snippet($id, $regex);
267dd87735dSAndreas Gohr                $idx++;
268dd87735dSAndreas Gohr            } else {
269dd87735dSAndreas Gohr                $snippet = '';
270dd87735dSAndreas Gohr            }
271dd87735dSAndreas Gohr
272104a3b7cSAndreas Gohr            $pages[] = [
273dd87735dSAndreas Gohr                'id' => $id,
274104a3b7cSAndreas Gohr                'score' => (int)$score,
275dd87735dSAndreas Gohr                'rev' => filemtime($file),
276dd87735dSAndreas Gohr                'mtime' => filemtime($file),
277dd87735dSAndreas Gohr                'size' => filesize($file),
278dd87735dSAndreas Gohr                'snippet' => $snippet,
279dd87735dSAndreas Gohr                'title' => useHeading('navigation') ? p_get_first_heading($id) : $id
280104a3b7cSAndreas Gohr            ];
281dd87735dSAndreas Gohr        }
282dd87735dSAndreas Gohr        return $pages;
283dd87735dSAndreas Gohr    }
284dd87735dSAndreas Gohr
285dd87735dSAndreas Gohr    /**
286dd87735dSAndreas Gohr     * Returns the wiki title.
287dd87735dSAndreas Gohr     *
288dd87735dSAndreas Gohr     * @return string
289dd87735dSAndreas Gohr     */
290dd87735dSAndreas Gohr    public function getTitle()
291dd87735dSAndreas Gohr    {
292dd87735dSAndreas Gohr        global $conf;
293dd87735dSAndreas Gohr        return $conf['title'];
294dd87735dSAndreas Gohr    }
295dd87735dSAndreas Gohr
296dd87735dSAndreas Gohr    /**
297dd87735dSAndreas Gohr     * List all media files.
298dd87735dSAndreas Gohr     *
299dd87735dSAndreas Gohr     * Available options are 'recursive' for also including the subnamespaces
300dd87735dSAndreas Gohr     * in the listing, and 'pattern' for filtering the returned files against
301dd87735dSAndreas Gohr     * a regular expression matching their name.
302dd87735dSAndreas Gohr     *
303dd87735dSAndreas Gohr     * @param string $ns
304dd87735dSAndreas Gohr     * @param array $options
305dd87735dSAndreas Gohr     *   $options['depth']     recursion level, 0 for all
306dd87735dSAndreas Gohr     *   $options['showmsg']   shows message if invalid media id is used
307dd87735dSAndreas Gohr     *   $options['pattern']   check given pattern
308dd87735dSAndreas Gohr     *   $options['hash']      add hashes to result list
309dd87735dSAndreas Gohr     * @return array
310dd87735dSAndreas Gohr     * @throws AccessDeniedException no access to the media files
311104a3b7cSAndreas Gohr     * @author Gina Haeussge <osd@foosel.net>
312104a3b7cSAndreas Gohr     *
313dd87735dSAndreas Gohr     */
314104a3b7cSAndreas Gohr    public function listAttachments($ns, $options = [])
315dd87735dSAndreas Gohr    {
316dd87735dSAndreas Gohr        global $conf;
317dd87735dSAndreas Gohr
318dd87735dSAndreas Gohr        $ns = cleanID($ns);
319dd87735dSAndreas Gohr
320104a3b7cSAndreas Gohr        if (!is_array($options)) $options = [];
321dd87735dSAndreas Gohr        $options['skipacl'] = 0; // no ACL skipping for XMLRPC
322dd87735dSAndreas Gohr
323dd87735dSAndreas Gohr        if (auth_quickaclcheck($ns . ':*') >= AUTH_READ) {
324dd87735dSAndreas Gohr            $dir = utf8_encodeFN(str_replace(':', '/', $ns));
325dd87735dSAndreas Gohr
326104a3b7cSAndreas Gohr            $data = [];
327dd87735dSAndreas Gohr            search($data, $conf['mediadir'], 'search_media', $options, $dir);
328dd87735dSAndreas Gohr            $len = count($data);
329104a3b7cSAndreas Gohr            if (!$len) return [];
330dd87735dSAndreas Gohr
331dd87735dSAndreas Gohr            for ($i = 0; $i < $len; $i++) {
332dd87735dSAndreas Gohr                unset($data[$i]['meta']);
333dd87735dSAndreas Gohr                $data[$i]['perms'] = $data[$i]['perm'];
334dd87735dSAndreas Gohr                unset($data[$i]['perm']);
335dd87735dSAndreas Gohr                $data[$i]['lastModified'] = $this->api->toDate($data[$i]['mtime']);
336dd87735dSAndreas Gohr            }
337dd87735dSAndreas Gohr            return $data;
338dd87735dSAndreas Gohr        } else {
339dd87735dSAndreas Gohr            throw new AccessDeniedException('You are not allowed to list media files.', 215);
340dd87735dSAndreas Gohr        }
341dd87735dSAndreas Gohr    }
342dd87735dSAndreas Gohr
343dd87735dSAndreas Gohr    /**
344dd87735dSAndreas Gohr     * Return a list of backlinks
345dd87735dSAndreas Gohr     *
346*0ff4031cSAndreas Gohr     * @param string $page page id
347dd87735dSAndreas Gohr     * @return array
348dd87735dSAndreas Gohr     */
349*0ff4031cSAndreas Gohr    public function listBackLinks($page)
350dd87735dSAndreas Gohr    {
351*0ff4031cSAndreas Gohr        return ft_backlinks($this->resolvePageId($page));
352dd87735dSAndreas Gohr    }
353dd87735dSAndreas Gohr
354dd87735dSAndreas Gohr    /**
355dd87735dSAndreas Gohr     * Return some basic data about a page
356dd87735dSAndreas Gohr     *
357*0ff4031cSAndreas Gohr     * @param string $page page id
358dd87735dSAndreas Gohr     * @param string|int $rev revision timestamp or empty string
359dd87735dSAndreas Gohr     * @return array
360dd87735dSAndreas Gohr     * @throws AccessDeniedException no access for page
361dd87735dSAndreas Gohr     * @throws RemoteException page not exist
362dd87735dSAndreas Gohr     */
363*0ff4031cSAndreas Gohr    public function pageInfo($page, $rev = '')
364dd87735dSAndreas Gohr    {
365*0ff4031cSAndreas Gohr        $page = $this->resolvePageId($page);
366*0ff4031cSAndreas Gohr        if (auth_quickaclcheck($page) < AUTH_READ) {
367dd87735dSAndreas Gohr            throw new AccessDeniedException('You are not allowed to read this page', 111);
368dd87735dSAndreas Gohr        }
369*0ff4031cSAndreas Gohr        $file = wikiFN($page, $rev);
370dd87735dSAndreas Gohr        $time = @filemtime($file);
371dd87735dSAndreas Gohr        if (!$time) {
372dd87735dSAndreas Gohr            throw new RemoteException('The requested page does not exist', 121);
373dd87735dSAndreas Gohr        }
374dd87735dSAndreas Gohr
375dd87735dSAndreas Gohr        // set revision to current version if empty, use revision otherwise
376dd87735dSAndreas Gohr        // as the timestamps of old files are not necessarily correct
377dd87735dSAndreas Gohr        if ($rev === '') {
378dd87735dSAndreas Gohr            $rev = $time;
379dd87735dSAndreas Gohr        }
380dd87735dSAndreas Gohr
381*0ff4031cSAndreas Gohr        $pagelog = new PageChangeLog($page, 1024);
382dd87735dSAndreas Gohr        $info = $pagelog->getRevisionInfo($rev);
383dd87735dSAndreas Gohr
384104a3b7cSAndreas Gohr        $data = [
385*0ff4031cSAndreas Gohr            'name' => $page,
386dd87735dSAndreas Gohr            'lastModified' => $this->api->toDate($rev),
387104a3b7cSAndreas Gohr            'author' => is_array($info) ? ($info['user'] ?: $info['ip']) : null,
388dd87735dSAndreas Gohr            'version' => $rev
389104a3b7cSAndreas Gohr        ];
390dd87735dSAndreas Gohr
391dd87735dSAndreas Gohr        return ($data);
392dd87735dSAndreas Gohr    }
393dd87735dSAndreas Gohr
394dd87735dSAndreas Gohr    /**
395dd87735dSAndreas Gohr     * Save a wiki page
396dd87735dSAndreas Gohr     *
397*0ff4031cSAndreas Gohr     * @param string $page page id
398dd87735dSAndreas Gohr     * @param string $text wiki text
399dd87735dSAndreas Gohr     * @param array $params parameters: summary, minor edit
400dd87735dSAndreas Gohr     * @return bool
401dd87735dSAndreas Gohr     * @throws AccessDeniedException no write access for page
402dd87735dSAndreas Gohr     * @throws RemoteException no id, empty new page or locked
403104a3b7cSAndreas Gohr     * @author Michael Klier <chi@chimeric.de>
404104a3b7cSAndreas Gohr     *
405dd87735dSAndreas Gohr     */
406*0ff4031cSAndreas Gohr    public function putPage($page, $text, $params = [])
407dd87735dSAndreas Gohr    {
408dd87735dSAndreas Gohr        global $TEXT;
409dd87735dSAndreas Gohr        global $lang;
410dd87735dSAndreas Gohr
411*0ff4031cSAndreas Gohr        $page = $this->resolvePageId($page);
412dd87735dSAndreas Gohr        $TEXT = cleanText($text);
41353585189SAndreas Gohr        $sum = $params['sum'] ?? '';
41453585189SAndreas Gohr        $minor = $params['minor'] ?? false;
415dd87735dSAndreas Gohr
416*0ff4031cSAndreas Gohr        if (empty($page)) {
417dd87735dSAndreas Gohr            throw new RemoteException('Empty page ID', 131);
418dd87735dSAndreas Gohr        }
419dd87735dSAndreas Gohr
420*0ff4031cSAndreas Gohr        if (!page_exists($page) && trim($TEXT) == '') {
421dd87735dSAndreas Gohr            throw new RemoteException('Refusing to write an empty new wiki page', 132);
422dd87735dSAndreas Gohr        }
423dd87735dSAndreas Gohr
424*0ff4031cSAndreas Gohr        if (auth_quickaclcheck($page) < AUTH_EDIT) {
425dd87735dSAndreas Gohr            throw new AccessDeniedException('You are not allowed to edit this page', 112);
426dd87735dSAndreas Gohr        }
427dd87735dSAndreas Gohr
428dd87735dSAndreas Gohr        // Check, if page is locked
429*0ff4031cSAndreas Gohr        if (checklock($page)) {
430dd87735dSAndreas Gohr            throw new RemoteException('The page is currently locked', 133);
431dd87735dSAndreas Gohr        }
432dd87735dSAndreas Gohr
433dd87735dSAndreas Gohr        // SPAM check
434dd87735dSAndreas Gohr        if (checkwordblock()) {
435dd87735dSAndreas Gohr            throw new RemoteException('Positive wordblock check', 134);
436dd87735dSAndreas Gohr        }
437dd87735dSAndreas Gohr
438dd87735dSAndreas Gohr        // autoset summary on new pages
439*0ff4031cSAndreas Gohr        if (!page_exists($page) && empty($sum)) {
440dd87735dSAndreas Gohr            $sum = $lang['created'];
441dd87735dSAndreas Gohr        }
442dd87735dSAndreas Gohr
443dd87735dSAndreas Gohr        // autoset summary on deleted pages
444*0ff4031cSAndreas Gohr        if (page_exists($page) && empty($TEXT) && empty($sum)) {
445dd87735dSAndreas Gohr            $sum = $lang['deleted'];
446dd87735dSAndreas Gohr        }
447dd87735dSAndreas Gohr
448*0ff4031cSAndreas Gohr        lock($page);
449dd87735dSAndreas Gohr
450*0ff4031cSAndreas Gohr        saveWikiText($page, $TEXT, $sum, $minor);
451dd87735dSAndreas Gohr
452*0ff4031cSAndreas Gohr        unlock($page);
453dd87735dSAndreas Gohr
454dd87735dSAndreas Gohr        // run the indexer if page wasn't indexed yet
455*0ff4031cSAndreas Gohr        idx_addPage($page);
456dd87735dSAndreas Gohr
457dd87735dSAndreas Gohr        return true;
458dd87735dSAndreas Gohr    }
459dd87735dSAndreas Gohr
460dd87735dSAndreas Gohr    /**
461dd87735dSAndreas Gohr     * Appends text to a wiki page.
462dd87735dSAndreas Gohr     *
463*0ff4031cSAndreas Gohr     * @param string $page page id
464dd87735dSAndreas Gohr     * @param string $text wiki text
465dd87735dSAndreas Gohr     * @param array $params such as summary,minor
466dd87735dSAndreas Gohr     * @return bool|string
467dd87735dSAndreas Gohr     * @throws RemoteException
468dd87735dSAndreas Gohr     */
469*0ff4031cSAndreas Gohr    public function appendPage($page, $text, $params = [])
470dd87735dSAndreas Gohr    {
471*0ff4031cSAndreas Gohr        $currentpage = $this->rawPage($page);
472dd87735dSAndreas Gohr        if (!is_string($currentpage)) {
473dd87735dSAndreas Gohr            return $currentpage;
474dd87735dSAndreas Gohr        }
475*0ff4031cSAndreas Gohr        return $this->putPage($page, $currentpage . $text, $params);
476dd87735dSAndreas Gohr    }
477dd87735dSAndreas Gohr
478dd87735dSAndreas Gohr    /**
4790e0fd3b7SMichael Wegener     * Create one or more users
4800e0fd3b7SMichael Wegener     *
48105438aa9SMichael Wegener     * @param array[] $userStruct User struct
4820e0fd3b7SMichael Wegener     *
48305438aa9SMichael Wegener     * @return boolean Create state
4840e0fd3b7SMichael Wegener     *
4850e0fd3b7SMichael Wegener     * @throws AccessDeniedException
4860e0fd3b7SMichael Wegener     * @throws RemoteException
4870e0fd3b7SMichael Wegener     */
488f0e32bb9SMichael Wegener    public function createUser($userStruct)
4890e0fd3b7SMichael Wegener    {
4900e0fd3b7SMichael Wegener        if (!auth_isadmin()) {
4910e0fd3b7SMichael Wegener            throw new AccessDeniedException('Only admins are allowed to create users', 114);
4920e0fd3b7SMichael Wegener        }
4930e0fd3b7SMichael Wegener
494104a3b7cSAndreas Gohr        /** @var AuthPlugin $auth */
4950e0fd3b7SMichael Wegener        global $auth;
4960e0fd3b7SMichael Wegener
4970e0fd3b7SMichael Wegener        if (!$auth->canDo('addUser')) {
4980e0fd3b7SMichael Wegener            throw new AccessDeniedException(
4990e0fd3b7SMichael Wegener                sprintf('Authentication backend %s can\'t do addUser', $auth->getPluginName()),
5000e0fd3b7SMichael Wegener                114
5010e0fd3b7SMichael Wegener            );
5020e0fd3b7SMichael Wegener        }
5030e0fd3b7SMichael Wegener
504f0e32bb9SMichael Wegener        $user = trim($auth->cleanUser($userStruct['user'] ?? ''));
505f0e32bb9SMichael Wegener        $password = $userStruct['password'] ?? '';
506f0e32bb9SMichael Wegener        $name = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/', '', $userStruct['name'] ?? ''));
507f0e32bb9SMichael Wegener        $mail = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/', '', $userStruct['mail'] ?? ''));
508f0e32bb9SMichael Wegener        $groups = $userStruct['groups'] ?? [];
5090e0fd3b7SMichael Wegener
510d95846aaSAndreas Gohr        $notify = (bool) ($userStruct['notify'] ?? false);
511f0e32bb9SMichael Wegener
512b1d4a667SAndreas Gohr        if ($user === '') throw new RemoteException('empty or invalid user', 401);
513b1d4a667SAndreas Gohr        if ($name === '') throw new RemoteException('empty or invalid user name', 402);
514b1d4a667SAndreas Gohr        if (!mail_isvalid($mail)) throw new RemoteException('empty or invalid mail address', 403);
515f0e32bb9SMichael Wegener
516104a3b7cSAndreas Gohr        if ((string)$password === '') {
517f0e32bb9SMichael Wegener            $password = auth_pwgen($user);
518f0e32bb9SMichael Wegener        }
519f0e32bb9SMichael Wegener
520104a3b7cSAndreas Gohr        if (!is_array($groups) || $groups === []) {
521f0e32bb9SMichael Wegener            $groups = null;
522f0e32bb9SMichael Wegener        }
523f0e32bb9SMichael Wegener
524104a3b7cSAndreas Gohr        $ok = $auth->triggerUserMod('create', [$user, $password, $name, $mail, $groups]);
525f0e32bb9SMichael Wegener
526f0e32bb9SMichael Wegener        if ($ok !== false && $ok !== null) {
527f0e32bb9SMichael Wegener            $ok = true;
528f0e32bb9SMichael Wegener        }
529f0e32bb9SMichael Wegener
5300e0fd3b7SMichael Wegener        if ($ok) {
531f0e32bb9SMichael Wegener            if ($notify) {
532f0e32bb9SMichael Wegener                auth_sendPassword($user, $password);
5330e0fd3b7SMichael Wegener            }
5340e0fd3b7SMichael Wegener        }
535f0e32bb9SMichael Wegener
536f0e32bb9SMichael Wegener        return $ok;
5370e0fd3b7SMichael Wegener    }
5380e0fd3b7SMichael Wegener
5390e0fd3b7SMichael Wegener
5400e0fd3b7SMichael Wegener    /**
5418eb28c6dSAndreas Gohr     * Remove one or more users from the list of registered users
5428eb28c6dSAndreas Gohr     *
5438eb28c6dSAndreas Gohr     * @param string[] $usernames List of usernames to remove
5448eb28c6dSAndreas Gohr     *
5458eb28c6dSAndreas Gohr     * @return bool
5468eb28c6dSAndreas Gohr     *
5478eb28c6dSAndreas Gohr     * @throws AccessDeniedException
5488eb28c6dSAndreas Gohr     */
5498eb28c6dSAndreas Gohr    public function deleteUsers($usernames)
5508eb28c6dSAndreas Gohr    {
5518eb28c6dSAndreas Gohr        if (!auth_isadmin()) {
5528eb28c6dSAndreas Gohr            throw new AccessDeniedException('Only admins are allowed to delete users', 114);
5538eb28c6dSAndreas Gohr        }
554104a3b7cSAndreas Gohr        /** @var AuthPlugin $auth */
5558eb28c6dSAndreas Gohr        global $auth;
556104a3b7cSAndreas Gohr        return (bool)$auth->triggerUserMod('delete', [$usernames]);
5578eb28c6dSAndreas Gohr    }
5588eb28c6dSAndreas Gohr
5598eb28c6dSAndreas Gohr    /**
560dd87735dSAndreas Gohr     * Uploads a file to the wiki.
561dd87735dSAndreas Gohr     *
562dd87735dSAndreas Gohr     * Michael Klier <chi@chimeric.de>
563dd87735dSAndreas Gohr     *
564*0ff4031cSAndreas Gohr     * @param string $media media id
565dd87735dSAndreas Gohr     * @param string $file
566dd87735dSAndreas Gohr     * @param array $params such as overwrite
567dd87735dSAndreas Gohr     * @return false|string
568dd87735dSAndreas Gohr     * @throws RemoteException
569dd87735dSAndreas Gohr     */
570*0ff4031cSAndreas Gohr    public function putAttachment($media, $file, $params = [])
571dd87735dSAndreas Gohr    {
572*0ff4031cSAndreas Gohr        $media = cleanID($media);
573*0ff4031cSAndreas Gohr        $auth = auth_quickaclcheck(getNS($media) . ':*');
574dd87735dSAndreas Gohr
575*0ff4031cSAndreas Gohr        if (!isset($media)) {
576dd87735dSAndreas Gohr            throw new RemoteException('Filename not given.', 231);
577dd87735dSAndreas Gohr        }
578dd87735dSAndreas Gohr
579dd87735dSAndreas Gohr        global $conf;
580dd87735dSAndreas Gohr
581*0ff4031cSAndreas Gohr        $ftmp = $conf['tmpdir'] . '/' . md5($media . clientIP());
582dd87735dSAndreas Gohr
583dd87735dSAndreas Gohr        // save temporary file
584dd87735dSAndreas Gohr        @unlink($ftmp);
585dd87735dSAndreas Gohr        io_saveFile($ftmp, $file);
586dd87735dSAndreas Gohr
587*0ff4031cSAndreas Gohr        $res = media_save(['name' => $ftmp], $media, $params['ow'], $auth, 'rename');
588dd87735dSAndreas Gohr        if (is_array($res)) {
589dd87735dSAndreas Gohr            throw new RemoteException($res[0], -$res[1]);
590dd87735dSAndreas Gohr        } else {
591dd87735dSAndreas Gohr            return $res;
592dd87735dSAndreas Gohr        }
593dd87735dSAndreas Gohr    }
594dd87735dSAndreas Gohr
595dd87735dSAndreas Gohr    /**
596dd87735dSAndreas Gohr     * Deletes a file from the wiki.
597dd87735dSAndreas Gohr     *
598*0ff4031cSAndreas Gohr     * @param string $media media id
599dd87735dSAndreas Gohr     * @return int
600dd87735dSAndreas Gohr     * @throws AccessDeniedException no permissions
601dd87735dSAndreas Gohr     * @throws RemoteException file in use or not deleted
602104a3b7cSAndreas Gohr     * @author Gina Haeussge <osd@foosel.net>
603104a3b7cSAndreas Gohr     *
604dd87735dSAndreas Gohr     */
605*0ff4031cSAndreas Gohr    public function deleteAttachment($media)
606dd87735dSAndreas Gohr    {
607*0ff4031cSAndreas Gohr        $media = cleanID($media);
608*0ff4031cSAndreas Gohr        $auth = auth_quickaclcheck(getNS($media) . ':*');
609*0ff4031cSAndreas Gohr        $res = media_delete($media, $auth);
610dd87735dSAndreas Gohr        if ($res & DOKU_MEDIA_DELETED) {
611dd87735dSAndreas Gohr            return 0;
612dd87735dSAndreas Gohr        } elseif ($res & DOKU_MEDIA_NOT_AUTH) {
613dd87735dSAndreas Gohr            throw new AccessDeniedException('You don\'t have permissions to delete files.', 212);
614dd87735dSAndreas Gohr        } elseif ($res & DOKU_MEDIA_INUSE) {
615dd87735dSAndreas Gohr            throw new RemoteException('File is still referenced', 232);
616dd87735dSAndreas Gohr        } else {
617dd87735dSAndreas Gohr            throw new RemoteException('Could not delete file', 233);
618dd87735dSAndreas Gohr        }
619dd87735dSAndreas Gohr    }
620dd87735dSAndreas Gohr
621dd87735dSAndreas Gohr    /**
622dd87735dSAndreas Gohr     * Returns the permissions of a given wiki page for the current user or another user
623dd87735dSAndreas Gohr     *
624*0ff4031cSAndreas Gohr     * @param string $page page id
625dd87735dSAndreas Gohr     * @param string|null $user username
626dd87735dSAndreas Gohr     * @param array|null $groups array of groups
627dd87735dSAndreas Gohr     * @return int permission level
628dd87735dSAndreas Gohr     */
629*0ff4031cSAndreas Gohr    public function aclCheck($page, $user = null, $groups = null)
630dd87735dSAndreas Gohr    {
631104a3b7cSAndreas Gohr        /** @var AuthPlugin $auth */
632dd87735dSAndreas Gohr        global $auth;
633dd87735dSAndreas Gohr
634*0ff4031cSAndreas Gohr        $page = $this->resolvePageId($page);
635dd87735dSAndreas Gohr        if ($user === null) {
636*0ff4031cSAndreas Gohr            return auth_quickaclcheck($page);
637dd87735dSAndreas Gohr        } else {
638dd87735dSAndreas Gohr            if ($groups === null) {
639dd87735dSAndreas Gohr                $userinfo = $auth->getUserData($user);
640dd87735dSAndreas Gohr                if ($userinfo === false) {
641104a3b7cSAndreas Gohr                    $groups = [];
642dd87735dSAndreas Gohr                } else {
643dd87735dSAndreas Gohr                    $groups = $userinfo['grps'];
644dd87735dSAndreas Gohr                }
645dd87735dSAndreas Gohr            }
646*0ff4031cSAndreas Gohr            return auth_aclcheck($page, $user, $groups);
647dd87735dSAndreas Gohr        }
648dd87735dSAndreas Gohr    }
649dd87735dSAndreas Gohr
650dd87735dSAndreas Gohr    /**
651dd87735dSAndreas Gohr     * Lists all links contained in a wiki page
652dd87735dSAndreas Gohr     *
653*0ff4031cSAndreas Gohr     * @param string $page page id
654dd87735dSAndreas Gohr     * @return array
655dd87735dSAndreas Gohr     * @throws AccessDeniedException  no read access for page
656104a3b7cSAndreas Gohr     * @author Michael Klier <chi@chimeric.de>
657104a3b7cSAndreas Gohr     *
658dd87735dSAndreas Gohr     */
659*0ff4031cSAndreas Gohr    public function listLinks($page)
660dd87735dSAndreas Gohr    {
661*0ff4031cSAndreas Gohr        $page = $this->resolvePageId($page);
662*0ff4031cSAndreas Gohr        if (auth_quickaclcheck($page) < AUTH_READ) {
663dd87735dSAndreas Gohr            throw new AccessDeniedException('You are not allowed to read this page', 111);
664dd87735dSAndreas Gohr        }
665104a3b7cSAndreas Gohr        $links = [];
666dd87735dSAndreas Gohr
667dd87735dSAndreas Gohr        // resolve page instructions
668*0ff4031cSAndreas Gohr        $ins = p_cached_instructions(wikiFN($page));
669dd87735dSAndreas Gohr
670dd87735dSAndreas Gohr        // instantiate new Renderer - needed for interwiki links
671dd87735dSAndreas Gohr        $Renderer = new Doku_Renderer_xhtml();
672dd87735dSAndreas Gohr        $Renderer->interwiki = getInterwiki();
673dd87735dSAndreas Gohr
674dd87735dSAndreas Gohr        // parse parse instructions
675dd87735dSAndreas Gohr        foreach ($ins as $in) {
676104a3b7cSAndreas Gohr            $link = [];
677dd87735dSAndreas Gohr            switch ($in[0]) {
678dd87735dSAndreas Gohr                case 'internallink':
679dd87735dSAndreas Gohr                    $link['type'] = 'local';
680dd87735dSAndreas Gohr                    $link['page'] = $in[1][0];
681dd87735dSAndreas Gohr                    $link['href'] = wl($in[1][0]);
682104a3b7cSAndreas Gohr                    $links[] = $link;
683dd87735dSAndreas Gohr                    break;
684dd87735dSAndreas Gohr                case 'externallink':
685dd87735dSAndreas Gohr                    $link['type'] = 'extern';
686dd87735dSAndreas Gohr                    $link['page'] = $in[1][0];
687dd87735dSAndreas Gohr                    $link['href'] = $in[1][0];
688104a3b7cSAndreas Gohr                    $links[] = $link;
689dd87735dSAndreas Gohr                    break;
690dd87735dSAndreas Gohr                case 'interwikilink':
691dd87735dSAndreas Gohr                    $url = $Renderer->_resolveInterWiki($in[1][2], $in[1][3]);
692dd87735dSAndreas Gohr                    $link['type'] = 'extern';
693dd87735dSAndreas Gohr                    $link['page'] = $url;
694dd87735dSAndreas Gohr                    $link['href'] = $url;
695104a3b7cSAndreas Gohr                    $links[] = $link;
696dd87735dSAndreas Gohr                    break;
697dd87735dSAndreas Gohr            }
698dd87735dSAndreas Gohr        }
699dd87735dSAndreas Gohr
700dd87735dSAndreas Gohr        return ($links);
701dd87735dSAndreas Gohr    }
702dd87735dSAndreas Gohr
703dd87735dSAndreas Gohr    /**
7048a9282a2SAndreas Gohr     * Returns a list of recent changes since given timestamp
705dd87735dSAndreas Gohr     *
706dd87735dSAndreas Gohr     * @param int $timestamp unix timestamp
707dd87735dSAndreas Gohr     * @return array
708dd87735dSAndreas Gohr     * @throws RemoteException no valid timestamp
709104a3b7cSAndreas Gohr     * @author Michael Klier <chi@chimeric.de>
710104a3b7cSAndreas Gohr     *
711104a3b7cSAndreas Gohr     * @author Michael Hamann <michael@content-space.de>
712dd87735dSAndreas Gohr     */
713dd87735dSAndreas Gohr    public function getRecentChanges($timestamp)
714dd87735dSAndreas Gohr    {
715dd87735dSAndreas Gohr        if (strlen($timestamp) != 10) {
716dd87735dSAndreas Gohr            throw new RemoteException('The provided value is not a valid timestamp', 311);
717dd87735dSAndreas Gohr        }
718dd87735dSAndreas Gohr
719dd87735dSAndreas Gohr        $recents = getRecentsSince($timestamp);
720dd87735dSAndreas Gohr
721104a3b7cSAndreas Gohr        $changes = [];
722dd87735dSAndreas Gohr
723dd87735dSAndreas Gohr        foreach ($recents as $recent) {
724104a3b7cSAndreas Gohr            $change = [];
725dd87735dSAndreas Gohr            $change['name'] = $recent['id'];
726dd87735dSAndreas Gohr            $change['lastModified'] = $this->api->toDate($recent['date']);
727dd87735dSAndreas Gohr            $change['author'] = $recent['user'];
728dd87735dSAndreas Gohr            $change['version'] = $recent['date'];
729dd87735dSAndreas Gohr            $change['perms'] = $recent['perms'];
730dd87735dSAndreas Gohr            $change['size'] = @filesize(wikiFN($recent['id']));
731104a3b7cSAndreas Gohr            $changes[] = $change;
732dd87735dSAndreas Gohr        }
733dd87735dSAndreas Gohr
734104a3b7cSAndreas Gohr        if ($changes !== []) {
735dd87735dSAndreas Gohr            return $changes;
736dd87735dSAndreas Gohr        } else {
737dd87735dSAndreas Gohr            // in case we still have nothing at this point
738dd87735dSAndreas Gohr            throw new RemoteException('There are no changes in the specified timeframe', 321);
739dd87735dSAndreas Gohr        }
740dd87735dSAndreas Gohr    }
741dd87735dSAndreas Gohr
742dd87735dSAndreas Gohr    /**
7438a9282a2SAndreas Gohr     * Returns a list of recent media changes since given timestamp
744dd87735dSAndreas Gohr     *
745dd87735dSAndreas Gohr     * @param int $timestamp unix timestamp
746dd87735dSAndreas Gohr     * @return array
747dd87735dSAndreas Gohr     * @throws RemoteException no valid timestamp
748104a3b7cSAndreas Gohr     * @author Michael Klier <chi@chimeric.de>
749104a3b7cSAndreas Gohr     *
750104a3b7cSAndreas Gohr     * @author Michael Hamann <michael@content-space.de>
751dd87735dSAndreas Gohr     */
752dd87735dSAndreas Gohr    public function getRecentMediaChanges($timestamp)
753dd87735dSAndreas Gohr    {
754dd87735dSAndreas Gohr        if (strlen($timestamp) != 10)
755dd87735dSAndreas Gohr            throw new RemoteException('The provided value is not a valid timestamp', 311);
756dd87735dSAndreas Gohr
757dd87735dSAndreas Gohr        $recents = getRecentsSince($timestamp, null, '', RECENTS_MEDIA_CHANGES);
758dd87735dSAndreas Gohr
759104a3b7cSAndreas Gohr        $changes = [];
760dd87735dSAndreas Gohr
761dd87735dSAndreas Gohr        foreach ($recents as $recent) {
762104a3b7cSAndreas Gohr            $change = [];
763dd87735dSAndreas Gohr            $change['name'] = $recent['id'];
764dd87735dSAndreas Gohr            $change['lastModified'] = $this->api->toDate($recent['date']);
765dd87735dSAndreas Gohr            $change['author'] = $recent['user'];
766dd87735dSAndreas Gohr            $change['version'] = $recent['date'];
767dd87735dSAndreas Gohr            $change['perms'] = $recent['perms'];
768dd87735dSAndreas Gohr            $change['size'] = @filesize(mediaFN($recent['id']));
769104a3b7cSAndreas Gohr            $changes[] = $change;
770dd87735dSAndreas Gohr        }
771dd87735dSAndreas Gohr
772104a3b7cSAndreas Gohr        if ($changes !== []) {
773dd87735dSAndreas Gohr            return $changes;
774dd87735dSAndreas Gohr        } else {
775dd87735dSAndreas Gohr            // in case we still have nothing at this point
776dd87735dSAndreas Gohr            throw new RemoteException('There are no changes in the specified timeframe', 321);
777dd87735dSAndreas Gohr        }
778dd87735dSAndreas Gohr    }
779dd87735dSAndreas Gohr
780dd87735dSAndreas Gohr    /**
781dd87735dSAndreas Gohr     * Returns a list of available revisions of a given wiki page
782dd87735dSAndreas Gohr     * Number of returned pages is set by $conf['recent']
783dd87735dSAndreas Gohr     * However not accessible pages are skipped, so less than $conf['recent'] could be returned
784dd87735dSAndreas Gohr     *
785*0ff4031cSAndreas Gohr     * @param string $page page id
786dd87735dSAndreas Gohr     * @param int $first skip the first n changelog lines
787dd87735dSAndreas Gohr     *                      0 = from current(if exists)
788dd87735dSAndreas Gohr     *                      1 = from 1st old rev
789dd87735dSAndreas Gohr     *                      2 = from 2nd old rev, etc
790dd87735dSAndreas Gohr     * @return array
791dd87735dSAndreas Gohr     * @throws AccessDeniedException no read access for page
792dd87735dSAndreas Gohr     * @throws RemoteException empty id
793104a3b7cSAndreas Gohr     * @author Michael Klier <chi@chimeric.de>
794104a3b7cSAndreas Gohr     *
795dd87735dSAndreas Gohr     */
796*0ff4031cSAndreas Gohr    public function pageVersions($page, $first = 0)
797dd87735dSAndreas Gohr    {
798*0ff4031cSAndreas Gohr        $page = $this->resolvePageId($page);
799*0ff4031cSAndreas Gohr        if (auth_quickaclcheck($page) < AUTH_READ) {
800dd87735dSAndreas Gohr            throw new AccessDeniedException('You are not allowed to read this page', 111);
801dd87735dSAndreas Gohr        }
802dd87735dSAndreas Gohr        global $conf;
803dd87735dSAndreas Gohr
804104a3b7cSAndreas Gohr        $versions = [];
805dd87735dSAndreas Gohr
806*0ff4031cSAndreas Gohr        if (empty($page)) {
807dd87735dSAndreas Gohr            throw new RemoteException('Empty page ID', 131);
808dd87735dSAndreas Gohr        }
809dd87735dSAndreas Gohr
810dd87735dSAndreas Gohr        $first = (int)$first;
811dd87735dSAndreas Gohr        $first_rev = $first - 1;
812efc3ac2fSsplitbrain        $first_rev = max(0, $first_rev);
813104a3b7cSAndreas Gohr
814*0ff4031cSAndreas Gohr        $pagelog = new PageChangeLog($page);
815dd87735dSAndreas Gohr        $revisions = $pagelog->getRevisions($first_rev, $conf['recent']);
816dd87735dSAndreas Gohr
817dd87735dSAndreas Gohr        if ($first == 0) {
818dd87735dSAndreas Gohr            array_unshift($revisions, '');  // include current revision
819dd87735dSAndreas Gohr            if (count($revisions) > $conf['recent']) {
820dd87735dSAndreas Gohr                array_pop($revisions);          // remove extra log entry
821dd87735dSAndreas Gohr            }
822dd87735dSAndreas Gohr        }
823dd87735dSAndreas Gohr
824dd87735dSAndreas Gohr        if (!empty($revisions)) {
825dd87735dSAndreas Gohr            foreach ($revisions as $rev) {
826*0ff4031cSAndreas Gohr                $file = wikiFN($page, $rev);
827dd87735dSAndreas Gohr                $time = @filemtime($file);
828dd87735dSAndreas Gohr                // we check if the page actually exists, if this is not the
829dd87735dSAndreas Gohr                // case this can lead to less pages being returned than
830dd87735dSAndreas Gohr                // specified via $conf['recent']
831dd87735dSAndreas Gohr                if ($time) {
832dd87735dSAndreas Gohr                    $pagelog->setChunkSize(1024);
833104a3b7cSAndreas Gohr                    $info = $pagelog->getRevisionInfo($rev ?: $time);
834dd87735dSAndreas Gohr                    if (!empty($info)) {
835104a3b7cSAndreas Gohr                        $data = [];
836dd87735dSAndreas Gohr                        $data['user'] = $info['user'];
837dd87735dSAndreas Gohr                        $data['ip'] = $info['ip'];
838dd87735dSAndreas Gohr                        $data['type'] = $info['type'];
839dd87735dSAndreas Gohr                        $data['sum'] = $info['sum'];
840dd87735dSAndreas Gohr                        $data['modified'] = $this->api->toDate($info['date']);
841dd87735dSAndreas Gohr                        $data['version'] = $info['date'];
842104a3b7cSAndreas Gohr                        $versions[] = $data;
843dd87735dSAndreas Gohr                    }
844dd87735dSAndreas Gohr                }
845dd87735dSAndreas Gohr            }
846dd87735dSAndreas Gohr            return $versions;
847dd87735dSAndreas Gohr        } else {
848104a3b7cSAndreas Gohr            return [];
849dd87735dSAndreas Gohr        }
850dd87735dSAndreas Gohr    }
851dd87735dSAndreas Gohr
852dd87735dSAndreas Gohr    /**
853dd87735dSAndreas Gohr     * The version of Wiki RPC API supported
85442e66c7aSAndreas Gohr     *
85542e66c7aSAndreas Gohr     * This is the version of the Wiki RPC specification implemented. Since that specification
85642e66c7aSAndreas Gohr     * is no longer maintained, this will always return 2
85742e66c7aSAndreas Gohr     *
85842e66c7aSAndreas Gohr     * You probably want to look at dokuwiki.getXMLRPCAPIVersion instead
85942e66c7aSAndreas Gohr     *
86042e66c7aSAndreas Gohr     * @return int
861dd87735dSAndreas Gohr     */
862dd87735dSAndreas Gohr    public function wikiRpcVersion()
863dd87735dSAndreas Gohr    {
864dd87735dSAndreas Gohr        return 2;
865dd87735dSAndreas Gohr    }
866dd87735dSAndreas Gohr
867dd87735dSAndreas Gohr    /**
868dd87735dSAndreas Gohr     * Locks or unlocks a given batch of pages
869dd87735dSAndreas Gohr     *
870dd87735dSAndreas Gohr     * Give an associative array with two keys: lock and unlock. Both should contain a
871dd87735dSAndreas Gohr     * list of pages to lock or unlock
872dd87735dSAndreas Gohr     *
873dd87735dSAndreas Gohr     * Returns an associative array with the keys locked, lockfail, unlocked and
874dd87735dSAndreas Gohr     * unlockfail, each containing lists of pages.
875dd87735dSAndreas Gohr     *
8768a9282a2SAndreas Gohr     * @param array[] $set list pages with ['lock' => [], 'unlock' => []]
8778a9282a2SAndreas Gohr     * @return array[] list of pages with ['locked' => [], 'lockfail' => [], 'unlocked' => [], 'unlockfail' => []]
878dd87735dSAndreas Gohr     */
879dd87735dSAndreas Gohr    public function setLocks($set)
880dd87735dSAndreas Gohr    {
881104a3b7cSAndreas Gohr        $locked = [];
882104a3b7cSAndreas Gohr        $lockfail = [];
883104a3b7cSAndreas Gohr        $unlocked = [];
884104a3b7cSAndreas Gohr        $unlockfail = [];
885dd87735dSAndreas Gohr
886104a3b7cSAndreas Gohr        foreach ($set['lock'] as $id) {
887dd87735dSAndreas Gohr            $id = $this->resolvePageId($id);
888dd87735dSAndreas Gohr            if (auth_quickaclcheck($id) < AUTH_EDIT || checklock($id)) {
889dd87735dSAndreas Gohr                $lockfail[] = $id;
890dd87735dSAndreas Gohr            } else {
891dd87735dSAndreas Gohr                lock($id);
892dd87735dSAndreas Gohr                $locked[] = $id;
893dd87735dSAndreas Gohr            }
894dd87735dSAndreas Gohr        }
895dd87735dSAndreas Gohr
896104a3b7cSAndreas Gohr        foreach ($set['unlock'] as $id) {
897dd87735dSAndreas Gohr            $id = $this->resolvePageId($id);
898dd87735dSAndreas Gohr            if (auth_quickaclcheck($id) < AUTH_EDIT || !unlock($id)) {
899dd87735dSAndreas Gohr                $unlockfail[] = $id;
900dd87735dSAndreas Gohr            } else {
901dd87735dSAndreas Gohr                $unlocked[] = $id;
902dd87735dSAndreas Gohr            }
903dd87735dSAndreas Gohr        }
904dd87735dSAndreas Gohr
905104a3b7cSAndreas Gohr        return [
906dd87735dSAndreas Gohr            'locked' => $locked,
907dd87735dSAndreas Gohr            'lockfail' => $lockfail,
908dd87735dSAndreas Gohr            'unlocked' => $unlocked,
909104a3b7cSAndreas Gohr            'unlockfail' => $unlockfail
910104a3b7cSAndreas Gohr        ];
911dd87735dSAndreas Gohr    }
912dd87735dSAndreas Gohr
913dd87735dSAndreas Gohr    /**
9148a9282a2SAndreas Gohr     * Return the API version
9158a9282a2SAndreas Gohr     *
9168a9282a2SAndreas Gohr     * This is the version of the DokuWiki API. It increases whenever the API definition changes.
9178a9282a2SAndreas Gohr     *
9188a9282a2SAndreas Gohr     * When developing a client, you should check this version and make sure you can handle it.
919dd87735dSAndreas Gohr     *
920dd87735dSAndreas Gohr     * @return int
921dd87735dSAndreas Gohr     */
922dd87735dSAndreas Gohr    public function getAPIVersion()
923dd87735dSAndreas Gohr    {
924dd87735dSAndreas Gohr        return self::API_VERSION;
925dd87735dSAndreas Gohr    }
926dd87735dSAndreas Gohr
927dd87735dSAndreas Gohr    /**
928dd87735dSAndreas Gohr     * Login
929dd87735dSAndreas Gohr     *
9308a9282a2SAndreas Gohr     * This will use the given credentials and attempt to login the user. This will set the
9318a9282a2SAndreas Gohr     * appropriate cookies, which can be used for subsequent requests.
9328a9282a2SAndreas Gohr     *
9338a9282a2SAndreas Gohr     * @param string $user The user name
9348a9282a2SAndreas Gohr     * @param string $pass The password
935dd87735dSAndreas Gohr     * @return int
936dd87735dSAndreas Gohr     */
937dd87735dSAndreas Gohr    public function login($user, $pass)
938dd87735dSAndreas Gohr    {
939dd87735dSAndreas Gohr        global $conf;
940104a3b7cSAndreas Gohr        /** @var AuthPlugin $auth */
941dd87735dSAndreas Gohr        global $auth;
942dd87735dSAndreas Gohr
943dd87735dSAndreas Gohr        if (!$conf['useacl']) return 0;
9446547cfc7SGerrit Uitslag        if (!$auth instanceof AuthPlugin) return 0;
945dd87735dSAndreas Gohr
946dd87735dSAndreas Gohr        @session_start(); // reopen session for login
94781e99965SPhy        $ok = null;
948dd87735dSAndreas Gohr        if ($auth->canDo('external')) {
949dd87735dSAndreas Gohr            $ok = $auth->trustExternal($user, $pass, false);
95081e99965SPhy        }
95181e99965SPhy        if ($ok === null) {
952104a3b7cSAndreas Gohr            $evdata = [
953dd87735dSAndreas Gohr                'user' => $user,
954dd87735dSAndreas Gohr                'password' => $pass,
955dd87735dSAndreas Gohr                'sticky' => false,
956104a3b7cSAndreas Gohr                'silent' => true
957104a3b7cSAndreas Gohr            ];
958cbb44eabSAndreas Gohr            $ok = Event::createAndTrigger('AUTH_LOGIN_CHECK', $evdata, 'auth_login_wrapper');
959dd87735dSAndreas Gohr        }
960dd87735dSAndreas Gohr        session_write_close(); // we're done with the session
961dd87735dSAndreas Gohr
962dd87735dSAndreas Gohr        return $ok;
963dd87735dSAndreas Gohr    }
964dd87735dSAndreas Gohr
965dd87735dSAndreas Gohr    /**
966dd87735dSAndreas Gohr     * Log off
967dd87735dSAndreas Gohr     *
9688a9282a2SAndreas Gohr     * Attempt to log out the current user, deleting the appropriate cookies
9698a9282a2SAndreas Gohr     *
9708a9282a2SAndreas Gohr     * @return int 0 on failure, 1 on success
971dd87735dSAndreas Gohr     */
972dd87735dSAndreas Gohr    public function logoff()
973dd87735dSAndreas Gohr    {
974dd87735dSAndreas Gohr        global $conf;
975dd87735dSAndreas Gohr        global $auth;
976dd87735dSAndreas Gohr        if (!$conf['useacl']) return 0;
9776547cfc7SGerrit Uitslag        if (!$auth instanceof AuthPlugin) return 0;
978dd87735dSAndreas Gohr
979dd87735dSAndreas Gohr        auth_logoff();
980dd87735dSAndreas Gohr
981dd87735dSAndreas Gohr        return 1;
982dd87735dSAndreas Gohr    }
983dd87735dSAndreas Gohr
984dd87735dSAndreas Gohr    /**
985dd87735dSAndreas Gohr     * Resolve page id
986dd87735dSAndreas Gohr     *
987dd87735dSAndreas Gohr     * @param string $id page id
988dd87735dSAndreas Gohr     * @return string
989dd87735dSAndreas Gohr     */
990dd87735dSAndreas Gohr    private function resolvePageId($id)
991dd87735dSAndreas Gohr    {
992dd87735dSAndreas Gohr        $id = cleanID($id);
993dd87735dSAndreas Gohr        if (empty($id)) {
994dd87735dSAndreas Gohr            global $conf;
995dd87735dSAndreas Gohr            $id = cleanID($conf['start']);
996dd87735dSAndreas Gohr        }
997dd87735dSAndreas Gohr        return $id;
998dd87735dSAndreas Gohr    }
999dd87735dSAndreas Gohr}
1000