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