xref: /dokuwiki/inc/Remote/ApiCore.php (revision 0e0fd3b7b5152356c1ef84db24eabbae0d18b38b)
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;
8cbb44eabSAndreas Gohruse dokuwiki\Extension\Event;
92d85e841SAndreas Gohruse dokuwiki\Utf8\Sort;
10dd87735dSAndreas Gohr
11dd87735dSAndreas Gohrdefine('DOKU_API_VERSION', 10);
12dd87735dSAndreas Gohr
13dd87735dSAndreas Gohr/**
14dd87735dSAndreas Gohr * Provides the core methods for the remote API.
15dd87735dSAndreas Gohr * The methods are ordered in 'wiki.<method>' and 'dokuwiki.<method>' namespaces
16dd87735dSAndreas Gohr */
17dd87735dSAndreas Gohrclass ApiCore
18dd87735dSAndreas Gohr{
19dd87735dSAndreas Gohr    /** @var int Increased whenever the API is changed */
20dd87735dSAndreas Gohr    const API_VERSION = 10;
21dd87735dSAndreas Gohr
22dd87735dSAndreas Gohr
23dd87735dSAndreas Gohr    /** @var Api */
24dd87735dSAndreas Gohr    private $api;
25dd87735dSAndreas Gohr
26dd87735dSAndreas Gohr    /**
27dd87735dSAndreas Gohr     * @param Api $api
28dd87735dSAndreas Gohr     */
29dd87735dSAndreas Gohr    public function __construct(Api $api)
30dd87735dSAndreas Gohr    {
31dd87735dSAndreas Gohr        $this->api = $api;
32dd87735dSAndreas Gohr    }
33dd87735dSAndreas Gohr
34dd87735dSAndreas Gohr    /**
35dd87735dSAndreas Gohr     * Returns details about the core methods
36dd87735dSAndreas Gohr     *
37dd87735dSAndreas Gohr     * @return array
38dd87735dSAndreas Gohr     */
39e6a9d76fSSyntaxseed    public function getRemoteInfo()
40dd87735dSAndreas Gohr    {
41dd87735dSAndreas Gohr        return array(
42dd87735dSAndreas Gohr            'dokuwiki.getVersion' => array(
43dd87735dSAndreas Gohr                'args' => array(),
44dd87735dSAndreas Gohr                'return' => 'string',
45dd87735dSAndreas Gohr                'doc' => 'Returns the running DokuWiki version.'
46dd87735dSAndreas Gohr            ), 'dokuwiki.login' => array(
47dd87735dSAndreas Gohr                'args' => array('string', 'string'),
48dd87735dSAndreas Gohr                'return' => 'int',
49dd87735dSAndreas Gohr                'doc' => 'Tries to login with the given credentials and sets auth cookies.',
50dd87735dSAndreas Gohr                'public' => '1'
51dd87735dSAndreas Gohr            ), 'dokuwiki.logoff' => array(
52dd87735dSAndreas Gohr                'args' => array(),
53dd87735dSAndreas Gohr                'return' => 'int',
54dd87735dSAndreas Gohr                'doc' => 'Tries to logoff by expiring auth cookies and the associated PHP session.'
55dd87735dSAndreas Gohr            ), 'dokuwiki.getPagelist' => array(
56dd87735dSAndreas Gohr                'args' => array('string', 'array'),
57dd87735dSAndreas Gohr                'return' => 'array',
58dd87735dSAndreas Gohr                'doc' => 'List all pages within the given namespace.',
59dd87735dSAndreas Gohr                'name' => 'readNamespace'
60dd87735dSAndreas Gohr            ), 'dokuwiki.search' => array(
61dd87735dSAndreas Gohr                'args' => array('string'),
62dd87735dSAndreas Gohr                'return' => 'array',
63dd87735dSAndreas Gohr                'doc' => 'Perform a fulltext search and return a list of matching pages'
64dd87735dSAndreas Gohr            ), 'dokuwiki.getTime' => array(
65dd87735dSAndreas Gohr                'args' => array(),
66dd87735dSAndreas Gohr                'return' => 'int',
67dd87735dSAndreas Gohr                'doc' => 'Returns the current time at the remote wiki server as Unix timestamp.',
68dd87735dSAndreas Gohr            ), 'dokuwiki.setLocks' => array(
69dd87735dSAndreas Gohr                'args' => array('array'),
70dd87735dSAndreas Gohr                'return' => 'array',
71dd87735dSAndreas Gohr                'doc' => 'Lock or unlock pages.'
72dd87735dSAndreas Gohr            ), 'dokuwiki.getTitle' => array(
73dd87735dSAndreas Gohr                'args' => array(),
74dd87735dSAndreas Gohr                'return' => 'string',
75dd87735dSAndreas Gohr                'doc' => 'Returns the wiki title.',
76dd87735dSAndreas Gohr                'public' => '1'
77dd87735dSAndreas Gohr            ), 'dokuwiki.appendPage' => array(
78dd87735dSAndreas Gohr                'args' => array('string', 'string', 'array'),
79dd87735dSAndreas Gohr                'return' => 'bool',
80dd87735dSAndreas Gohr                'doc' => 'Append text to a wiki page.'
81*0e0fd3b7SMichael Wegener            ), 'dokuwiki.createUsers' => array(
82*0e0fd3b7SMichael Wegener                'args' => array('array'),
83*0e0fd3b7SMichael Wegener                'return' => 'array',
84*0e0fd3b7SMichael Wegener                'doc' => 'Create one or more users. The result is an array of successfully created users'
858eb28c6dSAndreas Gohr            ),'dokuwiki.deleteUsers' => array(
868eb28c6dSAndreas Gohr                'args' => array('array'),
878eb28c6dSAndreas Gohr                'return' => 'bool',
888eb28c6dSAndreas Gohr                'doc' => 'Remove one or more users from the list of registered users.'
89dd87735dSAndreas Gohr            ),  'wiki.getPage' => array(
90dd87735dSAndreas Gohr                'args' => array('string'),
91dd87735dSAndreas Gohr                'return' => 'string',
92dd87735dSAndreas Gohr                'doc' => 'Get the raw Wiki text of page, latest version.',
93dd87735dSAndreas Gohr                'name' => 'rawPage',
94dd87735dSAndreas Gohr            ), 'wiki.getPageVersion' => array(
95dd87735dSAndreas Gohr                'args' => array('string', 'int'),
96dd87735dSAndreas Gohr                'name' => 'rawPage',
97dd87735dSAndreas Gohr                'return' => 'string',
98dd87735dSAndreas Gohr                'doc' => 'Return a raw wiki page'
99dd87735dSAndreas Gohr            ), 'wiki.getPageHTML' => array(
100dd87735dSAndreas Gohr                'args' => array('string'),
101dd87735dSAndreas Gohr                'return' => 'string',
102dd87735dSAndreas Gohr                'doc' => 'Return page in rendered HTML, latest version.',
103dd87735dSAndreas Gohr                'name' => 'htmlPage'
104dd87735dSAndreas Gohr            ), 'wiki.getPageHTMLVersion' => array(
105dd87735dSAndreas Gohr                'args' => array('string', 'int'),
106dd87735dSAndreas Gohr                'return' => 'string',
107dd87735dSAndreas Gohr                'doc' => 'Return page in rendered HTML.',
108dd87735dSAndreas Gohr                'name' => 'htmlPage'
109dd87735dSAndreas Gohr            ), 'wiki.getAllPages' => array(
110dd87735dSAndreas Gohr                'args' => array(),
111dd87735dSAndreas Gohr                'return' => 'array',
112dd87735dSAndreas Gohr                'doc' => 'Returns a list of all pages. The result is an array of utf8 pagenames.',
113dd87735dSAndreas Gohr                'name' => 'listPages'
114dd87735dSAndreas Gohr            ), 'wiki.getAttachments' => array(
115dd87735dSAndreas Gohr                'args' => array('string', 'array'),
116dd87735dSAndreas Gohr                'return' => 'array',
117dd87735dSAndreas Gohr                'doc' => 'Returns a list of all media files.',
118dd87735dSAndreas Gohr                'name' => 'listAttachments'
119dd87735dSAndreas Gohr            ), 'wiki.getBackLinks' => array(
120dd87735dSAndreas Gohr                'args' => array('string'),
121dd87735dSAndreas Gohr                'return' => 'array',
122dd87735dSAndreas Gohr                'doc' => 'Returns the pages that link to this page.',
123dd87735dSAndreas Gohr                'name' => 'listBackLinks'
124dd87735dSAndreas Gohr            ), 'wiki.getPageInfo' => array(
125dd87735dSAndreas Gohr                'args' => array('string'),
126dd87735dSAndreas Gohr                'return' => 'array',
127dd87735dSAndreas Gohr                'doc' => 'Returns a struct with info about the page, latest version.',
128dd87735dSAndreas Gohr                'name' => 'pageInfo'
129dd87735dSAndreas Gohr            ), 'wiki.getPageInfoVersion' => array(
130dd87735dSAndreas Gohr                'args' => array('string', 'int'),
131dd87735dSAndreas Gohr                'return' => 'array',
132dd87735dSAndreas Gohr                'doc' => 'Returns a struct with info about the page.',
133dd87735dSAndreas Gohr                'name' => 'pageInfo'
134dd87735dSAndreas Gohr            ), 'wiki.getPageVersions' => array(
135dd87735dSAndreas Gohr                'args' => array('string', 'int'),
136dd87735dSAndreas Gohr                'return' => 'array',
137dd87735dSAndreas Gohr                'doc' => 'Returns the available revisions of the page.',
138dd87735dSAndreas Gohr                'name' => 'pageVersions'
139dd87735dSAndreas Gohr            ), 'wiki.putPage' => array(
140dd87735dSAndreas Gohr                'args' => array('string', 'string', 'array'),
141dd87735dSAndreas Gohr                'return' => 'bool',
142dd87735dSAndreas Gohr                'doc' => 'Saves a wiki page.'
143dd87735dSAndreas Gohr            ), 'wiki.listLinks' => array(
144dd87735dSAndreas Gohr                'args' => array('string'),
145dd87735dSAndreas Gohr                'return' => 'array',
146dd87735dSAndreas Gohr                'doc' => 'Lists all links contained in a wiki page.'
147dd87735dSAndreas Gohr            ), 'wiki.getRecentChanges' => array(
148dd87735dSAndreas Gohr                'args' => array('int'),
149dd87735dSAndreas Gohr                'return' => 'array',
15086125ddaSMoisés Braga Ribeiro                'doc' => 'Returns a struct about all recent changes since given timestamp.'
151dd87735dSAndreas Gohr            ), 'wiki.getRecentMediaChanges' => array(
152dd87735dSAndreas Gohr                'args' => array('int'),
153dd87735dSAndreas Gohr                'return' => 'array',
15486125ddaSMoisés Braga Ribeiro                'doc' => 'Returns a struct about all recent media changes since given timestamp.'
155dd87735dSAndreas Gohr            ), 'wiki.aclCheck' => array(
156dd87735dSAndreas Gohr                'args' => array('string', 'string', 'array'),
157dd87735dSAndreas Gohr                'return' => 'int',
158dd87735dSAndreas Gohr                'doc' => 'Returns the permissions of a given wiki page. By default, for current user/groups'
159dd87735dSAndreas Gohr            ), 'wiki.putAttachment' => array(
160dd87735dSAndreas Gohr                'args' => array('string', 'file', 'array'),
161dd87735dSAndreas Gohr                'return' => 'array',
162dd87735dSAndreas Gohr                'doc' => 'Upload a file to the wiki.'
163dd87735dSAndreas Gohr            ), 'wiki.deleteAttachment' => array(
164dd87735dSAndreas Gohr                'args' => array('string'),
165dd87735dSAndreas Gohr                'return' => 'int',
166dd87735dSAndreas Gohr                'doc' => 'Delete a file from the wiki.'
167dd87735dSAndreas Gohr            ), 'wiki.getAttachment' => array(
168dd87735dSAndreas Gohr                'args' => array('string'),
169dd87735dSAndreas Gohr                'doc' => 'Return a media file',
170dd87735dSAndreas Gohr                'return' => 'file',
171dd87735dSAndreas Gohr                'name' => 'getAttachment',
172dd87735dSAndreas Gohr            ), 'wiki.getAttachmentInfo' => array(
173dd87735dSAndreas Gohr                'args' => array('string'),
174dd87735dSAndreas Gohr                'return' => 'array',
175dd87735dSAndreas Gohr                'doc' => 'Returns a struct with info about the attachment.'
176dd87735dSAndreas Gohr            ), 'dokuwiki.getXMLRPCAPIVersion' => array(
177dd87735dSAndreas Gohr                'args' => array(),
178dd87735dSAndreas Gohr                'name' => 'getAPIVersion',
179dd87735dSAndreas Gohr                'return' => 'int',
180dd87735dSAndreas Gohr                'doc' => 'Returns the XMLRPC API version.',
181dd87735dSAndreas Gohr                'public' => '1',
182dd87735dSAndreas Gohr            ), 'wiki.getRPCVersionSupported' => array(
183dd87735dSAndreas Gohr                'args' => array(),
184dd87735dSAndreas Gohr                'name' => 'wikiRpcVersion',
185dd87735dSAndreas Gohr                'return' => 'int',
186dd87735dSAndreas Gohr                'doc' => 'Returns 2 with the supported RPC API version.',
187dd87735dSAndreas Gohr                'public' => '1'
188dd87735dSAndreas Gohr            ),
189dd87735dSAndreas Gohr
190dd87735dSAndreas Gohr        );
191dd87735dSAndreas Gohr    }
192dd87735dSAndreas Gohr
193dd87735dSAndreas Gohr    /**
194dd87735dSAndreas Gohr     * @return string
195dd87735dSAndreas Gohr     */
196dd87735dSAndreas Gohr    public function getVersion()
197dd87735dSAndreas Gohr    {
198dd87735dSAndreas Gohr        return getVersion();
199dd87735dSAndreas Gohr    }
200dd87735dSAndreas Gohr
201dd87735dSAndreas Gohr    /**
202dd87735dSAndreas Gohr     * @return int unix timestamp
203dd87735dSAndreas Gohr     */
204dd87735dSAndreas Gohr    public function getTime()
205dd87735dSAndreas Gohr    {
206dd87735dSAndreas Gohr        return time();
207dd87735dSAndreas Gohr    }
208dd87735dSAndreas Gohr
209dd87735dSAndreas Gohr    /**
210dd87735dSAndreas Gohr     * Return a raw wiki page
211dd87735dSAndreas Gohr     *
212dd87735dSAndreas Gohr     * @param string $id wiki page id
213dd87735dSAndreas Gohr     * @param int|string $rev revision timestamp of the page or empty string
214dd87735dSAndreas Gohr     * @return string page text.
215dd87735dSAndreas Gohr     * @throws AccessDeniedException if no permission for page
216dd87735dSAndreas Gohr     */
217dd87735dSAndreas Gohr    public function rawPage($id, $rev = '')
218dd87735dSAndreas Gohr    {
219dd87735dSAndreas Gohr        $id = $this->resolvePageId($id);
220dd87735dSAndreas Gohr        if (auth_quickaclcheck($id) < AUTH_READ) {
221dd87735dSAndreas Gohr            throw new AccessDeniedException('You are not allowed to read this file', 111);
222dd87735dSAndreas Gohr        }
223dd87735dSAndreas Gohr        $text = rawWiki($id, $rev);
224dd87735dSAndreas Gohr        if (!$text) {
225dd87735dSAndreas Gohr            return pageTemplate($id);
226dd87735dSAndreas Gohr        } else {
227dd87735dSAndreas Gohr            return $text;
228dd87735dSAndreas Gohr        }
229dd87735dSAndreas Gohr    }
230dd87735dSAndreas Gohr
231dd87735dSAndreas Gohr    /**
232dd87735dSAndreas Gohr     * Return a media file
233dd87735dSAndreas Gohr     *
234dd87735dSAndreas Gohr     * @author Gina Haeussge <osd@foosel.net>
235dd87735dSAndreas Gohr     *
236dd87735dSAndreas Gohr     * @param string $id file id
237dd87735dSAndreas Gohr     * @return mixed media file
238dd87735dSAndreas Gohr     * @throws AccessDeniedException no permission for media
239dd87735dSAndreas Gohr     * @throws RemoteException not exist
240dd87735dSAndreas Gohr     */
241dd87735dSAndreas Gohr    public function getAttachment($id)
242dd87735dSAndreas Gohr    {
243dd87735dSAndreas Gohr        $id = cleanID($id);
244dd87735dSAndreas Gohr        if (auth_quickaclcheck(getNS($id) . ':*') < AUTH_READ) {
245dd87735dSAndreas Gohr            throw new AccessDeniedException('You are not allowed to read this file', 211);
246dd87735dSAndreas Gohr        }
247dd87735dSAndreas Gohr
248dd87735dSAndreas Gohr        $file = mediaFN($id);
249dd87735dSAndreas Gohr        if (!@ file_exists($file)) {
250dd87735dSAndreas Gohr            throw new RemoteException('The requested file does not exist', 221);
251dd87735dSAndreas Gohr        }
252dd87735dSAndreas Gohr
253dd87735dSAndreas Gohr        $data = io_readFile($file, false);
254dd87735dSAndreas Gohr        return $this->api->toFile($data);
255dd87735dSAndreas Gohr    }
256dd87735dSAndreas Gohr
257dd87735dSAndreas Gohr    /**
258dd87735dSAndreas Gohr     * Return info about a media file
259dd87735dSAndreas Gohr     *
260dd87735dSAndreas Gohr     * @author Gina Haeussge <osd@foosel.net>
261dd87735dSAndreas Gohr     *
262dd87735dSAndreas Gohr     * @param string $id page id
263dd87735dSAndreas Gohr     * @return array
264dd87735dSAndreas Gohr     */
265dd87735dSAndreas Gohr    public function getAttachmentInfo($id)
266dd87735dSAndreas Gohr    {
267dd87735dSAndreas Gohr        $id = cleanID($id);
268dd87735dSAndreas Gohr        $info = array(
269dd87735dSAndreas Gohr            'lastModified' => $this->api->toDate(0),
270dd87735dSAndreas Gohr            'size' => 0,
271dd87735dSAndreas Gohr        );
272dd87735dSAndreas Gohr
273dd87735dSAndreas Gohr        $file = mediaFN($id);
274dd87735dSAndreas Gohr        if (auth_quickaclcheck(getNS($id) . ':*') >= AUTH_READ) {
275dd87735dSAndreas Gohr            if (file_exists($file)) {
276dd87735dSAndreas Gohr                $info['lastModified'] = $this->api->toDate(filemtime($file));
277dd87735dSAndreas Gohr                $info['size'] = filesize($file);
278dd87735dSAndreas Gohr            } else {
279dd87735dSAndreas Gohr                //Is it deleted media with changelog?
280dd87735dSAndreas Gohr                $medialog = new MediaChangeLog($id);
281dd87735dSAndreas Gohr                $revisions = $medialog->getRevisions(0, 1);
282dd87735dSAndreas Gohr                if (!empty($revisions)) {
283dd87735dSAndreas Gohr                    $info['lastModified'] = $this->api->toDate($revisions[0]);
284dd87735dSAndreas Gohr                }
285dd87735dSAndreas Gohr            }
286dd87735dSAndreas Gohr        }
287dd87735dSAndreas Gohr
288dd87735dSAndreas Gohr        return $info;
289dd87735dSAndreas Gohr    }
290dd87735dSAndreas Gohr
291dd87735dSAndreas Gohr    /**
292dd87735dSAndreas Gohr     * Return a wiki page rendered to html
293dd87735dSAndreas Gohr     *
294dd87735dSAndreas Gohr     * @param string $id page id
295dd87735dSAndreas Gohr     * @param string|int $rev revision timestamp or empty string
296dd87735dSAndreas Gohr     * @return null|string html
297dd87735dSAndreas Gohr     * @throws AccessDeniedException no access to page
298dd87735dSAndreas Gohr     */
299dd87735dSAndreas Gohr    public function htmlPage($id, $rev = '')
300dd87735dSAndreas Gohr    {
301dd87735dSAndreas Gohr        $id = $this->resolvePageId($id);
302dd87735dSAndreas Gohr        if (auth_quickaclcheck($id) < AUTH_READ) {
303dd87735dSAndreas Gohr            throw new AccessDeniedException('You are not allowed to read this page', 111);
304dd87735dSAndreas Gohr        }
305dd87735dSAndreas Gohr        return p_wiki_xhtml($id, $rev, false);
306dd87735dSAndreas Gohr    }
307dd87735dSAndreas Gohr
308dd87735dSAndreas Gohr    /**
309dd87735dSAndreas Gohr     * List all pages - we use the indexer list here
310dd87735dSAndreas Gohr     *
311dd87735dSAndreas Gohr     * @return array
312dd87735dSAndreas Gohr     */
313dd87735dSAndreas Gohr    public function listPages()
314dd87735dSAndreas Gohr    {
315dd87735dSAndreas Gohr        $list = array();
316dd87735dSAndreas Gohr        $pages = idx_get_indexer()->getPages();
317dd87735dSAndreas Gohr        $pages = array_filter(array_filter($pages, 'isVisiblePage'), 'page_exists');
3182d85e841SAndreas Gohr        Sort::ksort($pages);
319dd87735dSAndreas Gohr
320dd87735dSAndreas Gohr        foreach (array_keys($pages) as $idx) {
321dd87735dSAndreas Gohr            $perm = auth_quickaclcheck($pages[$idx]);
322dd87735dSAndreas Gohr            if ($perm < AUTH_READ) {
323dd87735dSAndreas Gohr                continue;
324dd87735dSAndreas Gohr            }
325dd87735dSAndreas Gohr            $page = array();
326dd87735dSAndreas Gohr            $page['id'] = trim($pages[$idx]);
327dd87735dSAndreas Gohr            $page['perms'] = $perm;
328dd87735dSAndreas Gohr            $page['size'] = @filesize(wikiFN($pages[$idx]));
329dd87735dSAndreas Gohr            $page['lastModified'] = $this->api->toDate(@filemtime(wikiFN($pages[$idx])));
330dd87735dSAndreas Gohr            $list[] = $page;
331dd87735dSAndreas Gohr        }
332dd87735dSAndreas Gohr
333dd87735dSAndreas Gohr        return $list;
334dd87735dSAndreas Gohr    }
335dd87735dSAndreas Gohr
336dd87735dSAndreas Gohr    /**
337dd87735dSAndreas Gohr     * List all pages in the given namespace (and below)
338dd87735dSAndreas Gohr     *
339dd87735dSAndreas Gohr     * @param string $ns
340dd87735dSAndreas Gohr     * @param array $opts
341dd87735dSAndreas Gohr     *    $opts['depth']   recursion level, 0 for all
342dd87735dSAndreas Gohr     *    $opts['hash']    do md5 sum of content?
343dd87735dSAndreas Gohr     * @return array
344dd87735dSAndreas Gohr     */
345a9284ce8SPhy    public function readNamespace($ns, $opts = array())
346dd87735dSAndreas Gohr    {
347dd87735dSAndreas Gohr        global $conf;
348dd87735dSAndreas Gohr
349dd87735dSAndreas Gohr        if (!is_array($opts)) $opts = array();
350dd87735dSAndreas Gohr
351dd87735dSAndreas Gohr        $ns = cleanID($ns);
352dd87735dSAndreas Gohr        $dir = utf8_encodeFN(str_replace(':', '/', $ns));
353dd87735dSAndreas Gohr        $data = array();
354dd87735dSAndreas Gohr        $opts['skipacl'] = 0; // no ACL skipping for XMLRPC
355dd87735dSAndreas Gohr        search($data, $conf['datadir'], 'search_allpages', $opts, $dir);
356dd87735dSAndreas Gohr        return $data;
357dd87735dSAndreas Gohr    }
358dd87735dSAndreas Gohr
359dd87735dSAndreas Gohr    /**
360dd87735dSAndreas Gohr     * List all pages in the given namespace (and below)
361dd87735dSAndreas Gohr     *
362dd87735dSAndreas Gohr     * @param string $query
363dd87735dSAndreas Gohr     * @return array
364dd87735dSAndreas Gohr     */
365dd87735dSAndreas Gohr    public function search($query)
366dd87735dSAndreas Gohr    {
367dd87735dSAndreas Gohr        $regex = array();
368dd87735dSAndreas Gohr        $data = ft_pageSearch($query, $regex);
369dd87735dSAndreas Gohr        $pages = array();
370dd87735dSAndreas Gohr
371dd87735dSAndreas Gohr        // prepare additional data
372dd87735dSAndreas Gohr        $idx = 0;
373dd87735dSAndreas Gohr        foreach ($data as $id => $score) {
374dd87735dSAndreas Gohr            $file = wikiFN($id);
375dd87735dSAndreas Gohr
376dd87735dSAndreas Gohr            if ($idx < FT_SNIPPET_NUMBER) {
377dd87735dSAndreas Gohr                $snippet = ft_snippet($id, $regex);
378dd87735dSAndreas Gohr                $idx++;
379dd87735dSAndreas Gohr            } else {
380dd87735dSAndreas Gohr                $snippet = '';
381dd87735dSAndreas Gohr            }
382dd87735dSAndreas Gohr
383dd87735dSAndreas Gohr            $pages[] = array(
384dd87735dSAndreas Gohr                'id' => $id,
385dd87735dSAndreas Gohr                'score' => intval($score),
386dd87735dSAndreas Gohr                'rev' => filemtime($file),
387dd87735dSAndreas Gohr                'mtime' => filemtime($file),
388dd87735dSAndreas Gohr                'size' => filesize($file),
389dd87735dSAndreas Gohr                'snippet' => $snippet,
390dd87735dSAndreas Gohr                'title' => useHeading('navigation') ? p_get_first_heading($id) : $id
391dd87735dSAndreas Gohr            );
392dd87735dSAndreas Gohr        }
393dd87735dSAndreas Gohr        return $pages;
394dd87735dSAndreas Gohr    }
395dd87735dSAndreas Gohr
396dd87735dSAndreas Gohr    /**
397dd87735dSAndreas Gohr     * Returns the wiki title.
398dd87735dSAndreas Gohr     *
399dd87735dSAndreas Gohr     * @return string
400dd87735dSAndreas Gohr     */
401dd87735dSAndreas Gohr    public function getTitle()
402dd87735dSAndreas Gohr    {
403dd87735dSAndreas Gohr        global $conf;
404dd87735dSAndreas Gohr        return $conf['title'];
405dd87735dSAndreas Gohr    }
406dd87735dSAndreas Gohr
407dd87735dSAndreas Gohr    /**
408dd87735dSAndreas Gohr     * List all media files.
409dd87735dSAndreas Gohr     *
410dd87735dSAndreas Gohr     * Available options are 'recursive' for also including the subnamespaces
411dd87735dSAndreas Gohr     * in the listing, and 'pattern' for filtering the returned files against
412dd87735dSAndreas Gohr     * a regular expression matching their name.
413dd87735dSAndreas Gohr     *
414dd87735dSAndreas Gohr     * @author Gina Haeussge <osd@foosel.net>
415dd87735dSAndreas Gohr     *
416dd87735dSAndreas Gohr     * @param string $ns
417dd87735dSAndreas Gohr     * @param array $options
418dd87735dSAndreas Gohr     *   $options['depth']     recursion level, 0 for all
419dd87735dSAndreas Gohr     *   $options['showmsg']   shows message if invalid media id is used
420dd87735dSAndreas Gohr     *   $options['pattern']   check given pattern
421dd87735dSAndreas Gohr     *   $options['hash']      add hashes to result list
422dd87735dSAndreas Gohr     * @return array
423dd87735dSAndreas Gohr     * @throws AccessDeniedException no access to the media files
424dd87735dSAndreas Gohr     */
425dd87735dSAndreas Gohr    public function listAttachments($ns, $options = array())
426dd87735dSAndreas Gohr    {
427dd87735dSAndreas Gohr        global $conf;
428dd87735dSAndreas Gohr
429dd87735dSAndreas Gohr        $ns = cleanID($ns);
430dd87735dSAndreas Gohr
431dd87735dSAndreas Gohr        if (!is_array($options)) $options = array();
432dd87735dSAndreas Gohr        $options['skipacl'] = 0; // no ACL skipping for XMLRPC
433dd87735dSAndreas Gohr
434dd87735dSAndreas Gohr        if (auth_quickaclcheck($ns . ':*') >= AUTH_READ) {
435dd87735dSAndreas Gohr            $dir = utf8_encodeFN(str_replace(':', '/', $ns));
436dd87735dSAndreas Gohr
437dd87735dSAndreas Gohr            $data = array();
438dd87735dSAndreas Gohr            search($data, $conf['mediadir'], 'search_media', $options, $dir);
439dd87735dSAndreas Gohr            $len = count($data);
440dd87735dSAndreas Gohr            if (!$len) return array();
441dd87735dSAndreas Gohr
442dd87735dSAndreas Gohr            for ($i = 0; $i < $len; $i++) {
443dd87735dSAndreas Gohr                unset($data[$i]['meta']);
444dd87735dSAndreas Gohr                $data[$i]['perms'] = $data[$i]['perm'];
445dd87735dSAndreas Gohr                unset($data[$i]['perm']);
446dd87735dSAndreas Gohr                $data[$i]['lastModified'] = $this->api->toDate($data[$i]['mtime']);
447dd87735dSAndreas Gohr            }
448dd87735dSAndreas Gohr            return $data;
449dd87735dSAndreas Gohr        } else {
450dd87735dSAndreas Gohr            throw new AccessDeniedException('You are not allowed to list media files.', 215);
451dd87735dSAndreas Gohr        }
452dd87735dSAndreas Gohr    }
453dd87735dSAndreas Gohr
454dd87735dSAndreas Gohr    /**
455dd87735dSAndreas Gohr     * Return a list of backlinks
456dd87735dSAndreas Gohr     *
457dd87735dSAndreas Gohr     * @param string $id page id
458dd87735dSAndreas Gohr     * @return array
459dd87735dSAndreas Gohr     */
460dd87735dSAndreas Gohr    public function listBackLinks($id)
461dd87735dSAndreas Gohr    {
462dd87735dSAndreas Gohr        return ft_backlinks($this->resolvePageId($id));
463dd87735dSAndreas Gohr    }
464dd87735dSAndreas Gohr
465dd87735dSAndreas Gohr    /**
466dd87735dSAndreas Gohr     * Return some basic data about a page
467dd87735dSAndreas Gohr     *
468dd87735dSAndreas Gohr     * @param string $id page id
469dd87735dSAndreas Gohr     * @param string|int $rev revision timestamp or empty string
470dd87735dSAndreas Gohr     * @return array
471dd87735dSAndreas Gohr     * @throws AccessDeniedException no access for page
472dd87735dSAndreas Gohr     * @throws RemoteException page not exist
473dd87735dSAndreas Gohr     */
474dd87735dSAndreas Gohr    public function pageInfo($id, $rev = '')
475dd87735dSAndreas Gohr    {
476dd87735dSAndreas Gohr        $id = $this->resolvePageId($id);
477dd87735dSAndreas Gohr        if (auth_quickaclcheck($id) < AUTH_READ) {
478dd87735dSAndreas Gohr            throw new AccessDeniedException('You are not allowed to read this page', 111);
479dd87735dSAndreas Gohr        }
480dd87735dSAndreas Gohr        $file = wikiFN($id, $rev);
481dd87735dSAndreas Gohr        $time = @filemtime($file);
482dd87735dSAndreas Gohr        if (!$time) {
483dd87735dSAndreas Gohr            throw new RemoteException('The requested page does not exist', 121);
484dd87735dSAndreas Gohr        }
485dd87735dSAndreas Gohr
486dd87735dSAndreas Gohr        // set revision to current version if empty, use revision otherwise
487dd87735dSAndreas Gohr        // as the timestamps of old files are not necessarily correct
488dd87735dSAndreas Gohr        if ($rev === '') {
489dd87735dSAndreas Gohr            $rev = $time;
490dd87735dSAndreas Gohr        }
491dd87735dSAndreas Gohr
492dd87735dSAndreas Gohr        $pagelog = new PageChangeLog($id, 1024);
493dd87735dSAndreas Gohr        $info = $pagelog->getRevisionInfo($rev);
494dd87735dSAndreas Gohr
495dd87735dSAndreas Gohr        $data = array(
496dd87735dSAndreas Gohr            'name' => $id,
497dd87735dSAndreas Gohr            'lastModified' => $this->api->toDate($rev),
4980a444b5aSPhy            'author' => is_array($info) ? (($info['user']) ? $info['user'] : $info['ip']) : null,
499dd87735dSAndreas Gohr            'version' => $rev
500dd87735dSAndreas Gohr        );
501dd87735dSAndreas Gohr
502dd87735dSAndreas Gohr        return ($data);
503dd87735dSAndreas Gohr    }
504dd87735dSAndreas Gohr
505dd87735dSAndreas Gohr    /**
506dd87735dSAndreas Gohr     * Save a wiki page
507dd87735dSAndreas Gohr     *
508dd87735dSAndreas Gohr     * @author Michael Klier <chi@chimeric.de>
509dd87735dSAndreas Gohr     *
510dd87735dSAndreas Gohr     * @param string $id page id
511dd87735dSAndreas Gohr     * @param string $text wiki text
512dd87735dSAndreas Gohr     * @param array $params parameters: summary, minor edit
513dd87735dSAndreas Gohr     * @return bool
514dd87735dSAndreas Gohr     * @throws AccessDeniedException no write access for page
515dd87735dSAndreas Gohr     * @throws RemoteException no id, empty new page or locked
516dd87735dSAndreas Gohr     */
517a9284ce8SPhy    public function putPage($id, $text, $params = array())
518dd87735dSAndreas Gohr    {
519dd87735dSAndreas Gohr        global $TEXT;
520dd87735dSAndreas Gohr        global $lang;
521dd87735dSAndreas Gohr
522dd87735dSAndreas Gohr        $id = $this->resolvePageId($id);
523dd87735dSAndreas Gohr        $TEXT = cleanText($text);
524dd87735dSAndreas Gohr        $sum = $params['sum'];
525dd87735dSAndreas Gohr        $minor = $params['minor'];
526dd87735dSAndreas Gohr
527dd87735dSAndreas Gohr        if (empty($id)) {
528dd87735dSAndreas Gohr            throw new RemoteException('Empty page ID', 131);
529dd87735dSAndreas Gohr        }
530dd87735dSAndreas Gohr
531dd87735dSAndreas Gohr        if (!page_exists($id) && trim($TEXT) == '') {
532dd87735dSAndreas Gohr            throw new RemoteException('Refusing to write an empty new wiki page', 132);
533dd87735dSAndreas Gohr        }
534dd87735dSAndreas Gohr
535dd87735dSAndreas Gohr        if (auth_quickaclcheck($id) < AUTH_EDIT) {
536dd87735dSAndreas Gohr            throw new AccessDeniedException('You are not allowed to edit this page', 112);
537dd87735dSAndreas Gohr        }
538dd87735dSAndreas Gohr
539dd87735dSAndreas Gohr        // Check, if page is locked
540dd87735dSAndreas Gohr        if (checklock($id)) {
541dd87735dSAndreas Gohr            throw new RemoteException('The page is currently locked', 133);
542dd87735dSAndreas Gohr        }
543dd87735dSAndreas Gohr
544dd87735dSAndreas Gohr        // SPAM check
545dd87735dSAndreas Gohr        if (checkwordblock()) {
546dd87735dSAndreas Gohr            throw new RemoteException('Positive wordblock check', 134);
547dd87735dSAndreas Gohr        }
548dd87735dSAndreas Gohr
549dd87735dSAndreas Gohr        // autoset summary on new pages
550dd87735dSAndreas Gohr        if (!page_exists($id) && empty($sum)) {
551dd87735dSAndreas Gohr            $sum = $lang['created'];
552dd87735dSAndreas Gohr        }
553dd87735dSAndreas Gohr
554dd87735dSAndreas Gohr        // autoset summary on deleted pages
555dd87735dSAndreas Gohr        if (page_exists($id) && empty($TEXT) && empty($sum)) {
556dd87735dSAndreas Gohr            $sum = $lang['deleted'];
557dd87735dSAndreas Gohr        }
558dd87735dSAndreas Gohr
559dd87735dSAndreas Gohr        lock($id);
560dd87735dSAndreas Gohr
561dd87735dSAndreas Gohr        saveWikiText($id, $TEXT, $sum, $minor);
562dd87735dSAndreas Gohr
563dd87735dSAndreas Gohr        unlock($id);
564dd87735dSAndreas Gohr
565dd87735dSAndreas Gohr        // run the indexer if page wasn't indexed yet
566dd87735dSAndreas Gohr        idx_addPage($id);
567dd87735dSAndreas Gohr
568dd87735dSAndreas Gohr        return true;
569dd87735dSAndreas Gohr    }
570dd87735dSAndreas Gohr
571dd87735dSAndreas Gohr    /**
572dd87735dSAndreas Gohr     * Appends text to a wiki page.
573dd87735dSAndreas Gohr     *
574dd87735dSAndreas Gohr     * @param string $id page id
575dd87735dSAndreas Gohr     * @param string $text wiki text
576dd87735dSAndreas Gohr     * @param array $params such as summary,minor
577dd87735dSAndreas Gohr     * @return bool|string
578dd87735dSAndreas Gohr     * @throws RemoteException
579dd87735dSAndreas Gohr     */
580a9284ce8SPhy    public function appendPage($id, $text, $params = array())
581dd87735dSAndreas Gohr    {
582dd87735dSAndreas Gohr        $currentpage = $this->rawPage($id);
583dd87735dSAndreas Gohr        if (!is_string($currentpage)) {
584dd87735dSAndreas Gohr            return $currentpage;
585dd87735dSAndreas Gohr        }
586dd87735dSAndreas Gohr        return $this->putPage($id, $currentpage . $text, $params);
587dd87735dSAndreas Gohr    }
588dd87735dSAndreas Gohr
589dd87735dSAndreas Gohr    /**
590*0e0fd3b7SMichael Wegener     * Create one or more users
591*0e0fd3b7SMichael Wegener     *
592*0e0fd3b7SMichael Wegener     * @param array[] $users List of users to create
593*0e0fd3b7SMichael Wegener     *
594*0e0fd3b7SMichael Wegener     * @return string[] List of created users
595*0e0fd3b7SMichael Wegener     *
596*0e0fd3b7SMichael Wegener     * @throws AccessDeniedException
597*0e0fd3b7SMichael Wegener     * @throws RemoteException
598*0e0fd3b7SMichael Wegener     */
599*0e0fd3b7SMichael Wegener    public function createUsers($users)
600*0e0fd3b7SMichael Wegener    {
601*0e0fd3b7SMichael Wegener        if (!auth_isadmin()) {
602*0e0fd3b7SMichael Wegener            throw new AccessDeniedException('Only admins are allowed to create users', 114);
603*0e0fd3b7SMichael Wegener        }
604*0e0fd3b7SMichael Wegener
605*0e0fd3b7SMichael Wegener        /** @var \dokuwiki\Extension\AuthPlugin $auth */
606*0e0fd3b7SMichael Wegener        global $auth;
607*0e0fd3b7SMichael Wegener
608*0e0fd3b7SMichael Wegener        if(!$auth->canDo('addUser')) {
609*0e0fd3b7SMichael Wegener            throw new AccessDeniedException(
610*0e0fd3b7SMichael Wegener                sprintf('Authentication backend %s can\'t do addUser', $auth->getPluginName()),
611*0e0fd3b7SMichael Wegener                114
612*0e0fd3b7SMichael Wegener            );
613*0e0fd3b7SMichael Wegener        }
614*0e0fd3b7SMichael Wegener
615*0e0fd3b7SMichael Wegener        $validatedUsers = [];
616*0e0fd3b7SMichael Wegener        foreach ($users as $id => $user) {
617*0e0fd3b7SMichael Wegener            $user['user'] = trim($auth->cleanUser($user['user'] ?? ''));
618*0e0fd3b7SMichael Wegener            $user['password'] = $user['password'] ?? '';
619*0e0fd3b7SMichael Wegener            $user['name'] = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/', '', $user['name'] ?? ''));
620*0e0fd3b7SMichael Wegener            $user['mail'] = trim(preg_replace('/[\x00-\x1f:<>&%,;]+/', '', $user['mail'] ?? ''));
621*0e0fd3b7SMichael Wegener            $user['notify'] = (boolean)$user['notify'] ?? false;
622*0e0fd3b7SMichael Wegener
623*0e0fd3b7SMichael Wegener            if(!empty($user['user']) && !empty($user['name']) && mail_isvalid($user['mail'])) {
624*0e0fd3b7SMichael Wegener                $validatedUsers[] = $user;
625*0e0fd3b7SMichael Wegener            } else {
626*0e0fd3b7SMichael Wegener                throw new RemoteException(
627*0e0fd3b7SMichael Wegener                    sprintf('User number %s has invalid data (check user, name and mail)', $id + 1),
628*0e0fd3b7SMichael Wegener                    114
629*0e0fd3b7SMichael Wegener                );
630*0e0fd3b7SMichael Wegener            }
631*0e0fd3b7SMichael Wegener        }
632*0e0fd3b7SMichael Wegener
633*0e0fd3b7SMichael Wegener        $createdUsers = array();
634*0e0fd3b7SMichael Wegener        foreach ($validatedUsers as $user) {
635*0e0fd3b7SMichael Wegener            if(strlen($user['password']) === 0) {
636*0e0fd3b7SMichael Wegener                $user['password'] = auth_pwgen($user);
637*0e0fd3b7SMichael Wegener            }
638*0e0fd3b7SMichael Wegener            $ok = $auth->triggerUserMod('create', array($user['user'], $user['password'], $user['name'], $user['mail'], $user['groups']));
639*0e0fd3b7SMichael Wegener            if($ok) {
640*0e0fd3b7SMichael Wegener                $createdUsers[] = $user['user'];
641*0e0fd3b7SMichael Wegener                if($user['notify']) {
642*0e0fd3b7SMichael Wegener                    auth_sendPassword($user['user'], $user['password']);
643*0e0fd3b7SMichael Wegener                }
644*0e0fd3b7SMichael Wegener            }
645*0e0fd3b7SMichael Wegener        }
646*0e0fd3b7SMichael Wegener        return $createdUsers;
647*0e0fd3b7SMichael Wegener    }
648*0e0fd3b7SMichael Wegener
649*0e0fd3b7SMichael Wegener
650*0e0fd3b7SMichael Wegener    /**
6518eb28c6dSAndreas Gohr     * Remove one or more users from the list of registered users
6528eb28c6dSAndreas Gohr     *
6538eb28c6dSAndreas Gohr     * @param string[] $usernames List of usernames to remove
6548eb28c6dSAndreas Gohr     *
6558eb28c6dSAndreas Gohr     * @return bool
6568eb28c6dSAndreas Gohr     *
6578eb28c6dSAndreas Gohr     * @throws AccessDeniedException
6588eb28c6dSAndreas Gohr     */
6598eb28c6dSAndreas Gohr    public function deleteUsers($usernames)
6608eb28c6dSAndreas Gohr    {
6618eb28c6dSAndreas Gohr        if (!auth_isadmin()) {
6628eb28c6dSAndreas Gohr            throw new AccessDeniedException('Only admins are allowed to delete users', 114);
6638eb28c6dSAndreas Gohr        }
6642a93a6adSAndreas Gohr        /** @var \dokuwiki\Extension\AuthPlugin $auth */
6658eb28c6dSAndreas Gohr        global $auth;
6669ddafcedSAndreas Gohr        return (bool)$auth->triggerUserMod('delete', array($usernames));
6678eb28c6dSAndreas Gohr    }
6688eb28c6dSAndreas Gohr
6698eb28c6dSAndreas Gohr    /**
670dd87735dSAndreas Gohr     * Uploads a file to the wiki.
671dd87735dSAndreas Gohr     *
672dd87735dSAndreas Gohr     * Michael Klier <chi@chimeric.de>
673dd87735dSAndreas Gohr     *
674dd87735dSAndreas Gohr     * @param string $id page id
675dd87735dSAndreas Gohr     * @param string $file
676dd87735dSAndreas Gohr     * @param array $params such as overwrite
677dd87735dSAndreas Gohr     * @return false|string
678dd87735dSAndreas Gohr     * @throws RemoteException
679dd87735dSAndreas Gohr     */
680a9284ce8SPhy    public function putAttachment($id, $file, $params = array())
681dd87735dSAndreas Gohr    {
682dd87735dSAndreas Gohr        $id = cleanID($id);
683dd87735dSAndreas Gohr        $auth = auth_quickaclcheck(getNS($id) . ':*');
684dd87735dSAndreas Gohr
685dd87735dSAndreas Gohr        if (!isset($id)) {
686dd87735dSAndreas Gohr            throw new RemoteException('Filename not given.', 231);
687dd87735dSAndreas Gohr        }
688dd87735dSAndreas Gohr
689dd87735dSAndreas Gohr        global $conf;
690dd87735dSAndreas Gohr
691dd87735dSAndreas Gohr        $ftmp = $conf['tmpdir'] . '/' . md5($id . clientIP());
692dd87735dSAndreas Gohr
693dd87735dSAndreas Gohr        // save temporary file
694dd87735dSAndreas Gohr        @unlink($ftmp);
695dd87735dSAndreas Gohr        io_saveFile($ftmp, $file);
696dd87735dSAndreas Gohr
697dd87735dSAndreas Gohr        $res = media_save(array('name' => $ftmp), $id, $params['ow'], $auth, 'rename');
698dd87735dSAndreas Gohr        if (is_array($res)) {
699dd87735dSAndreas Gohr            throw new RemoteException($res[0], -$res[1]);
700dd87735dSAndreas Gohr        } else {
701dd87735dSAndreas Gohr            return $res;
702dd87735dSAndreas Gohr        }
703dd87735dSAndreas Gohr    }
704dd87735dSAndreas Gohr
705dd87735dSAndreas Gohr    /**
706dd87735dSAndreas Gohr     * Deletes a file from the wiki.
707dd87735dSAndreas Gohr     *
708dd87735dSAndreas Gohr     * @author Gina Haeussge <osd@foosel.net>
709dd87735dSAndreas Gohr     *
710dd87735dSAndreas Gohr     * @param string $id page id
711dd87735dSAndreas Gohr     * @return int
712dd87735dSAndreas Gohr     * @throws AccessDeniedException no permissions
713dd87735dSAndreas Gohr     * @throws RemoteException file in use or not deleted
714dd87735dSAndreas Gohr     */
715dd87735dSAndreas Gohr    public function deleteAttachment($id)
716dd87735dSAndreas Gohr    {
717dd87735dSAndreas Gohr        $id = cleanID($id);
718dd87735dSAndreas Gohr        $auth = auth_quickaclcheck(getNS($id) . ':*');
719dd87735dSAndreas Gohr        $res = media_delete($id, $auth);
720dd87735dSAndreas Gohr        if ($res & DOKU_MEDIA_DELETED) {
721dd87735dSAndreas Gohr            return 0;
722dd87735dSAndreas Gohr        } elseif ($res & DOKU_MEDIA_NOT_AUTH) {
723dd87735dSAndreas Gohr            throw new AccessDeniedException('You don\'t have permissions to delete files.', 212);
724dd87735dSAndreas Gohr        } elseif ($res & DOKU_MEDIA_INUSE) {
725dd87735dSAndreas Gohr            throw new RemoteException('File is still referenced', 232);
726dd87735dSAndreas Gohr        } else {
727dd87735dSAndreas Gohr            throw new RemoteException('Could not delete file', 233);
728dd87735dSAndreas Gohr        }
729dd87735dSAndreas Gohr    }
730dd87735dSAndreas Gohr
731dd87735dSAndreas Gohr    /**
732dd87735dSAndreas Gohr     * Returns the permissions of a given wiki page for the current user or another user
733dd87735dSAndreas Gohr     *
734dd87735dSAndreas Gohr     * @param string $id page id
735dd87735dSAndreas Gohr     * @param string|null $user username
736dd87735dSAndreas Gohr     * @param array|null $groups array of groups
737dd87735dSAndreas Gohr     * @return int permission level
738dd87735dSAndreas Gohr     */
739dd87735dSAndreas Gohr    public function aclCheck($id, $user = null, $groups = null)
740dd87735dSAndreas Gohr    {
7412a93a6adSAndreas Gohr        /** @var \dokuwiki\Extension\AuthPlugin $auth */
742dd87735dSAndreas Gohr        global $auth;
743dd87735dSAndreas Gohr
744dd87735dSAndreas Gohr        $id = $this->resolvePageId($id);
745dd87735dSAndreas Gohr        if ($user === null) {
746dd87735dSAndreas Gohr            return auth_quickaclcheck($id);
747dd87735dSAndreas Gohr        } else {
748dd87735dSAndreas Gohr            if ($groups === null) {
749dd87735dSAndreas Gohr                $userinfo = $auth->getUserData($user);
750dd87735dSAndreas Gohr                if ($userinfo === false) {
751dd87735dSAndreas Gohr                    $groups = array();
752dd87735dSAndreas Gohr                } else {
753dd87735dSAndreas Gohr                    $groups = $userinfo['grps'];
754dd87735dSAndreas Gohr                }
755dd87735dSAndreas Gohr            }
756dd87735dSAndreas Gohr            return auth_aclcheck($id, $user, $groups);
757dd87735dSAndreas Gohr        }
758dd87735dSAndreas Gohr    }
759dd87735dSAndreas Gohr
760dd87735dSAndreas Gohr    /**
761dd87735dSAndreas Gohr     * Lists all links contained in a wiki page
762dd87735dSAndreas Gohr     *
763dd87735dSAndreas Gohr     * @author Michael Klier <chi@chimeric.de>
764dd87735dSAndreas Gohr     *
765dd87735dSAndreas Gohr     * @param string $id page id
766dd87735dSAndreas Gohr     * @return array
767dd87735dSAndreas Gohr     * @throws AccessDeniedException  no read access for page
768dd87735dSAndreas Gohr     */
769dd87735dSAndreas Gohr    public function listLinks($id)
770dd87735dSAndreas Gohr    {
771dd87735dSAndreas Gohr        $id = $this->resolvePageId($id);
772dd87735dSAndreas Gohr        if (auth_quickaclcheck($id) < AUTH_READ) {
773dd87735dSAndreas Gohr            throw new AccessDeniedException('You are not allowed to read this page', 111);
774dd87735dSAndreas Gohr        }
775dd87735dSAndreas Gohr        $links = array();
776dd87735dSAndreas Gohr
777dd87735dSAndreas Gohr        // resolve page instructions
778dd87735dSAndreas Gohr        $ins = p_cached_instructions(wikiFN($id));
779dd87735dSAndreas Gohr
780dd87735dSAndreas Gohr        // instantiate new Renderer - needed for interwiki links
781dd87735dSAndreas Gohr        $Renderer = new Doku_Renderer_xhtml();
782dd87735dSAndreas Gohr        $Renderer->interwiki = getInterwiki();
783dd87735dSAndreas Gohr
784dd87735dSAndreas Gohr        // parse parse instructions
785dd87735dSAndreas Gohr        foreach ($ins as $in) {
786dd87735dSAndreas Gohr            $link = array();
787dd87735dSAndreas Gohr            switch ($in[0]) {
788dd87735dSAndreas Gohr                case 'internallink':
789dd87735dSAndreas Gohr                    $link['type'] = 'local';
790dd87735dSAndreas Gohr                    $link['page'] = $in[1][0];
791dd87735dSAndreas Gohr                    $link['href'] = wl($in[1][0]);
792dd87735dSAndreas Gohr                    array_push($links, $link);
793dd87735dSAndreas Gohr                    break;
794dd87735dSAndreas Gohr                case 'externallink':
795dd87735dSAndreas Gohr                    $link['type'] = 'extern';
796dd87735dSAndreas Gohr                    $link['page'] = $in[1][0];
797dd87735dSAndreas Gohr                    $link['href'] = $in[1][0];
798dd87735dSAndreas Gohr                    array_push($links, $link);
799dd87735dSAndreas Gohr                    break;
800dd87735dSAndreas Gohr                case 'interwikilink':
801dd87735dSAndreas Gohr                    $url = $Renderer->_resolveInterWiki($in[1][2], $in[1][3]);
802dd87735dSAndreas Gohr                    $link['type'] = 'extern';
803dd87735dSAndreas Gohr                    $link['page'] = $url;
804dd87735dSAndreas Gohr                    $link['href'] = $url;
805dd87735dSAndreas Gohr                    array_push($links, $link);
806dd87735dSAndreas Gohr                    break;
807dd87735dSAndreas Gohr            }
808dd87735dSAndreas Gohr        }
809dd87735dSAndreas Gohr
810dd87735dSAndreas Gohr        return ($links);
811dd87735dSAndreas Gohr    }
812dd87735dSAndreas Gohr
813dd87735dSAndreas Gohr    /**
814dd87735dSAndreas Gohr     * Returns a list of recent changes since give timestamp
815dd87735dSAndreas Gohr     *
816dd87735dSAndreas Gohr     * @author Michael Hamann <michael@content-space.de>
817dd87735dSAndreas Gohr     * @author Michael Klier <chi@chimeric.de>
818dd87735dSAndreas Gohr     *
819dd87735dSAndreas Gohr     * @param int $timestamp unix timestamp
820dd87735dSAndreas Gohr     * @return array
821dd87735dSAndreas Gohr     * @throws RemoteException no valid timestamp
822dd87735dSAndreas Gohr     */
823dd87735dSAndreas Gohr    public function getRecentChanges($timestamp)
824dd87735dSAndreas Gohr    {
825dd87735dSAndreas Gohr        if (strlen($timestamp) != 10) {
826dd87735dSAndreas Gohr            throw new RemoteException('The provided value is not a valid timestamp', 311);
827dd87735dSAndreas Gohr        }
828dd87735dSAndreas Gohr
829dd87735dSAndreas Gohr        $recents = getRecentsSince($timestamp);
830dd87735dSAndreas Gohr
831dd87735dSAndreas Gohr        $changes = array();
832dd87735dSAndreas Gohr
833dd87735dSAndreas Gohr        foreach ($recents as $recent) {
834dd87735dSAndreas Gohr            $change = array();
835dd87735dSAndreas Gohr            $change['name'] = $recent['id'];
836dd87735dSAndreas Gohr            $change['lastModified'] = $this->api->toDate($recent['date']);
837dd87735dSAndreas Gohr            $change['author'] = $recent['user'];
838dd87735dSAndreas Gohr            $change['version'] = $recent['date'];
839dd87735dSAndreas Gohr            $change['perms'] = $recent['perms'];
840dd87735dSAndreas Gohr            $change['size'] = @filesize(wikiFN($recent['id']));
841dd87735dSAndreas Gohr            array_push($changes, $change);
842dd87735dSAndreas Gohr        }
843dd87735dSAndreas Gohr
844dd87735dSAndreas Gohr        if (!empty($changes)) {
845dd87735dSAndreas Gohr            return $changes;
846dd87735dSAndreas Gohr        } else {
847dd87735dSAndreas Gohr            // in case we still have nothing at this point
848dd87735dSAndreas Gohr            throw new RemoteException('There are no changes in the specified timeframe', 321);
849dd87735dSAndreas Gohr        }
850dd87735dSAndreas Gohr    }
851dd87735dSAndreas Gohr
852dd87735dSAndreas Gohr    /**
853dd87735dSAndreas Gohr     * Returns a list of recent media changes since give timestamp
854dd87735dSAndreas Gohr     *
855dd87735dSAndreas Gohr     * @author Michael Hamann <michael@content-space.de>
856dd87735dSAndreas Gohr     * @author Michael Klier <chi@chimeric.de>
857dd87735dSAndreas Gohr     *
858dd87735dSAndreas Gohr     * @param int $timestamp unix timestamp
859dd87735dSAndreas Gohr     * @return array
860dd87735dSAndreas Gohr     * @throws RemoteException no valid timestamp
861dd87735dSAndreas Gohr     */
862dd87735dSAndreas Gohr    public function getRecentMediaChanges($timestamp)
863dd87735dSAndreas Gohr    {
864dd87735dSAndreas Gohr        if (strlen($timestamp) != 10)
865dd87735dSAndreas Gohr            throw new RemoteException('The provided value is not a valid timestamp', 311);
866dd87735dSAndreas Gohr
867dd87735dSAndreas Gohr        $recents = getRecentsSince($timestamp, null, '', RECENTS_MEDIA_CHANGES);
868dd87735dSAndreas Gohr
869dd87735dSAndreas Gohr        $changes = array();
870dd87735dSAndreas Gohr
871dd87735dSAndreas Gohr        foreach ($recents as $recent) {
872dd87735dSAndreas Gohr            $change = array();
873dd87735dSAndreas Gohr            $change['name'] = $recent['id'];
874dd87735dSAndreas Gohr            $change['lastModified'] = $this->api->toDate($recent['date']);
875dd87735dSAndreas Gohr            $change['author'] = $recent['user'];
876dd87735dSAndreas Gohr            $change['version'] = $recent['date'];
877dd87735dSAndreas Gohr            $change['perms'] = $recent['perms'];
878dd87735dSAndreas Gohr            $change['size'] = @filesize(mediaFN($recent['id']));
879dd87735dSAndreas Gohr            array_push($changes, $change);
880dd87735dSAndreas Gohr        }
881dd87735dSAndreas Gohr
882dd87735dSAndreas Gohr        if (!empty($changes)) {
883dd87735dSAndreas Gohr            return $changes;
884dd87735dSAndreas Gohr        } else {
885dd87735dSAndreas Gohr            // in case we still have nothing at this point
886dd87735dSAndreas Gohr            throw new RemoteException('There are no changes in the specified timeframe', 321);
887dd87735dSAndreas Gohr        }
888dd87735dSAndreas Gohr    }
889dd87735dSAndreas Gohr
890dd87735dSAndreas Gohr    /**
891dd87735dSAndreas Gohr     * Returns a list of available revisions of a given wiki page
892dd87735dSAndreas Gohr     * Number of returned pages is set by $conf['recent']
893dd87735dSAndreas Gohr     * However not accessible pages are skipped, so less than $conf['recent'] could be returned
894dd87735dSAndreas Gohr     *
895dd87735dSAndreas Gohr     * @author Michael Klier <chi@chimeric.de>
896dd87735dSAndreas Gohr     *
897dd87735dSAndreas Gohr     * @param string $id page id
898dd87735dSAndreas Gohr     * @param int $first skip the first n changelog lines
899dd87735dSAndreas Gohr     *                      0 = from current(if exists)
900dd87735dSAndreas Gohr     *                      1 = from 1st old rev
901dd87735dSAndreas Gohr     *                      2 = from 2nd old rev, etc
902dd87735dSAndreas Gohr     * @return array
903dd87735dSAndreas Gohr     * @throws AccessDeniedException no read access for page
904dd87735dSAndreas Gohr     * @throws RemoteException empty id
905dd87735dSAndreas Gohr     */
906a9284ce8SPhy    public function pageVersions($id, $first = 0)
907dd87735dSAndreas Gohr    {
908dd87735dSAndreas Gohr        $id = $this->resolvePageId($id);
909dd87735dSAndreas Gohr        if (auth_quickaclcheck($id) < AUTH_READ) {
910dd87735dSAndreas Gohr            throw new AccessDeniedException('You are not allowed to read this page', 111);
911dd87735dSAndreas Gohr        }
912dd87735dSAndreas Gohr        global $conf;
913dd87735dSAndreas Gohr
914dd87735dSAndreas Gohr        $versions = array();
915dd87735dSAndreas Gohr
916dd87735dSAndreas Gohr        if (empty($id)) {
917dd87735dSAndreas Gohr            throw new RemoteException('Empty page ID', 131);
918dd87735dSAndreas Gohr        }
919dd87735dSAndreas Gohr
920dd87735dSAndreas Gohr        $first = (int) $first;
921dd87735dSAndreas Gohr        $first_rev = $first - 1;
922dd87735dSAndreas Gohr        $first_rev = $first_rev < 0 ? 0 : $first_rev;
923dd87735dSAndreas Gohr        $pagelog = new PageChangeLog($id);
924dd87735dSAndreas Gohr        $revisions = $pagelog->getRevisions($first_rev, $conf['recent']);
925dd87735dSAndreas Gohr
926dd87735dSAndreas Gohr        if ($first == 0) {
927dd87735dSAndreas Gohr            array_unshift($revisions, '');  // include current revision
928dd87735dSAndreas Gohr            if (count($revisions) > $conf['recent']) {
929dd87735dSAndreas Gohr                array_pop($revisions);          // remove extra log entry
930dd87735dSAndreas Gohr            }
931dd87735dSAndreas Gohr        }
932dd87735dSAndreas Gohr
933dd87735dSAndreas Gohr        if (!empty($revisions)) {
934dd87735dSAndreas Gohr            foreach ($revisions as $rev) {
935dd87735dSAndreas Gohr                $file = wikiFN($id, $rev);
936dd87735dSAndreas Gohr                $time = @filemtime($file);
937dd87735dSAndreas Gohr                // we check if the page actually exists, if this is not the
938dd87735dSAndreas Gohr                // case this can lead to less pages being returned than
939dd87735dSAndreas Gohr                // specified via $conf['recent']
940dd87735dSAndreas Gohr                if ($time) {
941dd87735dSAndreas Gohr                    $pagelog->setChunkSize(1024);
942dd87735dSAndreas Gohr                    $info = $pagelog->getRevisionInfo($rev ? $rev : $time);
943dd87735dSAndreas Gohr                    if (!empty($info)) {
944dd87735dSAndreas Gohr                        $data = array();
945dd87735dSAndreas Gohr                        $data['user'] = $info['user'];
946dd87735dSAndreas Gohr                        $data['ip'] = $info['ip'];
947dd87735dSAndreas Gohr                        $data['type'] = $info['type'];
948dd87735dSAndreas Gohr                        $data['sum'] = $info['sum'];
949dd87735dSAndreas Gohr                        $data['modified'] = $this->api->toDate($info['date']);
950dd87735dSAndreas Gohr                        $data['version'] = $info['date'];
951dd87735dSAndreas Gohr                        array_push($versions, $data);
952dd87735dSAndreas Gohr                    }
953dd87735dSAndreas Gohr                }
954dd87735dSAndreas Gohr            }
955dd87735dSAndreas Gohr            return $versions;
956dd87735dSAndreas Gohr        } else {
957dd87735dSAndreas Gohr            return array();
958dd87735dSAndreas Gohr        }
959dd87735dSAndreas Gohr    }
960dd87735dSAndreas Gohr
961dd87735dSAndreas Gohr    /**
962dd87735dSAndreas Gohr     * The version of Wiki RPC API supported
963dd87735dSAndreas Gohr     */
964dd87735dSAndreas Gohr    public function wikiRpcVersion()
965dd87735dSAndreas Gohr    {
966dd87735dSAndreas Gohr        return 2;
967dd87735dSAndreas Gohr    }
968dd87735dSAndreas Gohr
969dd87735dSAndreas Gohr    /**
970dd87735dSAndreas Gohr     * Locks or unlocks a given batch of pages
971dd87735dSAndreas Gohr     *
972dd87735dSAndreas Gohr     * Give an associative array with two keys: lock and unlock. Both should contain a
973dd87735dSAndreas Gohr     * list of pages to lock or unlock
974dd87735dSAndreas Gohr     *
975dd87735dSAndreas Gohr     * Returns an associative array with the keys locked, lockfail, unlocked and
976dd87735dSAndreas Gohr     * unlockfail, each containing lists of pages.
977dd87735dSAndreas Gohr     *
978dd87735dSAndreas Gohr     * @param array[] $set list pages with array('lock' => array, 'unlock' => array)
979dd87735dSAndreas Gohr     * @return array
980dd87735dSAndreas Gohr     */
981dd87735dSAndreas Gohr    public function setLocks($set)
982dd87735dSAndreas Gohr    {
983dd87735dSAndreas Gohr        $locked = array();
984dd87735dSAndreas Gohr        $lockfail = array();
985dd87735dSAndreas Gohr        $unlocked = array();
986dd87735dSAndreas Gohr        $unlockfail = array();
987dd87735dSAndreas Gohr
988dd87735dSAndreas Gohr        foreach ((array) $set['lock'] as $id) {
989dd87735dSAndreas Gohr            $id = $this->resolvePageId($id);
990dd87735dSAndreas Gohr            if (auth_quickaclcheck($id) < AUTH_EDIT || checklock($id)) {
991dd87735dSAndreas Gohr                $lockfail[] = $id;
992dd87735dSAndreas Gohr            } else {
993dd87735dSAndreas Gohr                lock($id);
994dd87735dSAndreas Gohr                $locked[] = $id;
995dd87735dSAndreas Gohr            }
996dd87735dSAndreas Gohr        }
997dd87735dSAndreas Gohr
998dd87735dSAndreas Gohr        foreach ((array) $set['unlock'] as $id) {
999dd87735dSAndreas Gohr            $id = $this->resolvePageId($id);
1000dd87735dSAndreas Gohr            if (auth_quickaclcheck($id) < AUTH_EDIT || !unlock($id)) {
1001dd87735dSAndreas Gohr                $unlockfail[] = $id;
1002dd87735dSAndreas Gohr            } else {
1003dd87735dSAndreas Gohr                $unlocked[] = $id;
1004dd87735dSAndreas Gohr            }
1005dd87735dSAndreas Gohr        }
1006dd87735dSAndreas Gohr
1007dd87735dSAndreas Gohr        return array(
1008dd87735dSAndreas Gohr            'locked' => $locked,
1009dd87735dSAndreas Gohr            'lockfail' => $lockfail,
1010dd87735dSAndreas Gohr            'unlocked' => $unlocked,
1011dd87735dSAndreas Gohr            'unlockfail' => $unlockfail,
1012dd87735dSAndreas Gohr        );
1013dd87735dSAndreas Gohr    }
1014dd87735dSAndreas Gohr
1015dd87735dSAndreas Gohr    /**
1016dd87735dSAndreas Gohr     * Return API version
1017dd87735dSAndreas Gohr     *
1018dd87735dSAndreas Gohr     * @return int
1019dd87735dSAndreas Gohr     */
1020dd87735dSAndreas Gohr    public function getAPIVersion()
1021dd87735dSAndreas Gohr    {
1022dd87735dSAndreas Gohr        return self::API_VERSION;
1023dd87735dSAndreas Gohr    }
1024dd87735dSAndreas Gohr
1025dd87735dSAndreas Gohr    /**
1026dd87735dSAndreas Gohr     * Login
1027dd87735dSAndreas Gohr     *
1028dd87735dSAndreas Gohr     * @param string $user
1029dd87735dSAndreas Gohr     * @param string $pass
1030dd87735dSAndreas Gohr     * @return int
1031dd87735dSAndreas Gohr     */
1032dd87735dSAndreas Gohr    public function login($user, $pass)
1033dd87735dSAndreas Gohr    {
1034dd87735dSAndreas Gohr        global $conf;
10352a93a6adSAndreas Gohr        /** @var \dokuwiki\Extension\AuthPlugin $auth */
1036dd87735dSAndreas Gohr        global $auth;
1037dd87735dSAndreas Gohr
1038dd87735dSAndreas Gohr        if (!$conf['useacl']) return 0;
1039dd87735dSAndreas Gohr        if (!$auth) return 0;
1040dd87735dSAndreas Gohr
1041dd87735dSAndreas Gohr        @session_start(); // reopen session for login
104281e99965SPhy        $ok = null;
1043dd87735dSAndreas Gohr        if ($auth->canDo('external')) {
1044dd87735dSAndreas Gohr            $ok = $auth->trustExternal($user, $pass, false);
104581e99965SPhy        }
104681e99965SPhy        if ($ok === null){
1047dd87735dSAndreas Gohr            $evdata = array(
1048dd87735dSAndreas Gohr                'user' => $user,
1049dd87735dSAndreas Gohr                'password' => $pass,
1050dd87735dSAndreas Gohr                'sticky' => false,
1051dd87735dSAndreas Gohr                'silent' => true,
1052dd87735dSAndreas Gohr            );
1053cbb44eabSAndreas Gohr            $ok = Event::createAndTrigger('AUTH_LOGIN_CHECK', $evdata, 'auth_login_wrapper');
1054dd87735dSAndreas Gohr        }
1055dd87735dSAndreas Gohr        session_write_close(); // we're done with the session
1056dd87735dSAndreas Gohr
1057dd87735dSAndreas Gohr        return $ok;
1058dd87735dSAndreas Gohr    }
1059dd87735dSAndreas Gohr
1060dd87735dSAndreas Gohr    /**
1061dd87735dSAndreas Gohr     * Log off
1062dd87735dSAndreas Gohr     *
1063dd87735dSAndreas Gohr     * @return int
1064dd87735dSAndreas Gohr     */
1065dd87735dSAndreas Gohr    public function logoff()
1066dd87735dSAndreas Gohr    {
1067dd87735dSAndreas Gohr        global $conf;
1068dd87735dSAndreas Gohr        global $auth;
1069dd87735dSAndreas Gohr        if (!$conf['useacl']) return 0;
1070dd87735dSAndreas Gohr        if (!$auth) return 0;
1071dd87735dSAndreas Gohr
1072dd87735dSAndreas Gohr        auth_logoff();
1073dd87735dSAndreas Gohr
1074dd87735dSAndreas Gohr        return 1;
1075dd87735dSAndreas Gohr    }
1076dd87735dSAndreas Gohr
1077dd87735dSAndreas Gohr    /**
1078dd87735dSAndreas Gohr     * Resolve page id
1079dd87735dSAndreas Gohr     *
1080dd87735dSAndreas Gohr     * @param string $id page id
1081dd87735dSAndreas Gohr     * @return string
1082dd87735dSAndreas Gohr     */
1083dd87735dSAndreas Gohr    private function resolvePageId($id)
1084dd87735dSAndreas Gohr    {
1085dd87735dSAndreas Gohr        $id = cleanID($id);
1086dd87735dSAndreas Gohr        if (empty($id)) {
1087dd87735dSAndreas Gohr            global $conf;
1088dd87735dSAndreas Gohr            $id = cleanID($conf['start']);
1089dd87735dSAndreas Gohr        }
1090dd87735dSAndreas Gohr        return $id;
1091dd87735dSAndreas Gohr    }
1092dd87735dSAndreas Gohr}
1093