Failed Conditions
Push — stable ( 017e16...b83837 )
by
unknown
10:45 queued 07:58
created

ApiCore::putAttachment()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 3
dl 0
loc 24
rs 9.536
c 0
b 0
f 0
1
<?php
2
3
namespace dokuwiki\Remote;
4
5
use Doku_Renderer_xhtml;
6
use dokuwiki\ChangeLog\MediaChangeLog;
7
use dokuwiki\ChangeLog\PageChangeLog;
8
use dokuwiki\Extension\Event;
9
10
define('DOKU_API_VERSION', 10);
11
12
/**
13
 * Provides the core methods for the remote API.
14
 * The methods are ordered in 'wiki.<method>' and 'dokuwiki.<method>' namespaces
15
 */
16
class ApiCore
17
{
18
    /** @var int Increased whenever the API is changed */
19
    const API_VERSION = 10;
20
21
22
    /** @var Api */
23
    private $api;
24
25
    /**
26
     * @param Api $api
27
     */
28
    public function __construct(Api $api)
29
    {
30
        $this->api = $api;
31
    }
32
33
    /**
34
     * Returns details about the core methods
35
     *
36
     * @return array
37
     */
38
    public function __getRemoteInfo()
39
    {
40
        return array(
41
            'dokuwiki.getVersion' => array(
42
                'args' => array(),
43
                'return' => 'string',
44
                'doc' => 'Returns the running DokuWiki version.'
45
            ), 'dokuwiki.login' => array(
46
                'args' => array('string', 'string'),
47
                'return' => 'int',
48
                'doc' => 'Tries to login with the given credentials and sets auth cookies.',
49
                'public' => '1'
50
            ), 'dokuwiki.logoff' => array(
51
                'args' => array(),
52
                'return' => 'int',
53
                'doc' => 'Tries to logoff by expiring auth cookies and the associated PHP session.'
54
            ), 'dokuwiki.getPagelist' => array(
55
                'args' => array('string', 'array'),
56
                'return' => 'array',
57
                'doc' => 'List all pages within the given namespace.',
58
                'name' => 'readNamespace'
59
            ), 'dokuwiki.search' => array(
60
                'args' => array('string'),
61
                'return' => 'array',
62
                'doc' => 'Perform a fulltext search and return a list of matching pages'
63
            ), 'dokuwiki.getTime' => array(
64
                'args' => array(),
65
                'return' => 'int',
66
                'doc' => 'Returns the current time at the remote wiki server as Unix timestamp.',
67
            ), 'dokuwiki.setLocks' => array(
68
                'args' => array('array'),
69
                'return' => 'array',
70
                'doc' => 'Lock or unlock pages.'
71
            ), 'dokuwiki.getTitle' => array(
72
                'args' => array(),
73
                'return' => 'string',
74
                'doc' => 'Returns the wiki title.',
75
                'public' => '1'
76
            ), 'dokuwiki.appendPage' => array(
77
                'args' => array('string', 'string', 'array'),
78
                'return' => 'bool',
79
                'doc' => 'Append text to a wiki page.'
80
            ), 'dokuwiki.deleteUsers' => array(
81
                'args' => array('array'),
82
                'return' => 'bool',
83
                'doc' => 'Remove one or more users from the list of registered users.'
84
            ),  'wiki.getPage' => array(
85
                'args' => array('string'),
86
                'return' => 'string',
87
                'doc' => 'Get the raw Wiki text of page, latest version.',
88
                'name' => 'rawPage',
89
            ), 'wiki.getPageVersion' => array(
90
                'args' => array('string', 'int'),
91
                'name' => 'rawPage',
92
                'return' => 'string',
93
                'doc' => 'Return a raw wiki page'
94
            ), 'wiki.getPageHTML' => array(
95
                'args' => array('string'),
96
                'return' => 'string',
97
                'doc' => 'Return page in rendered HTML, latest version.',
98
                'name' => 'htmlPage'
99
            ), 'wiki.getPageHTMLVersion' => array(
100
                'args' => array('string', 'int'),
101
                'return' => 'string',
102
                'doc' => 'Return page in rendered HTML.',
103
                'name' => 'htmlPage'
104
            ), 'wiki.getAllPages' => array(
105
                'args' => array(),
106
                'return' => 'array',
107
                'doc' => 'Returns a list of all pages. The result is an array of utf8 pagenames.',
108
                'name' => 'listPages'
109
            ), 'wiki.getAttachments' => array(
110
                'args' => array('string', 'array'),
111
                'return' => 'array',
112
                'doc' => 'Returns a list of all media files.',
113
                'name' => 'listAttachments'
114
            ), 'wiki.getBackLinks' => array(
115
                'args' => array('string'),
116
                'return' => 'array',
117
                'doc' => 'Returns the pages that link to this page.',
118
                'name' => 'listBackLinks'
119
            ), 'wiki.getPageInfo' => array(
120
                'args' => array('string'),
121
                'return' => 'array',
122
                'doc' => 'Returns a struct with info about the page, latest version.',
123
                'name' => 'pageInfo'
124
            ), 'wiki.getPageInfoVersion' => array(
125
                'args' => array('string', 'int'),
126
                'return' => 'array',
127
                'doc' => 'Returns a struct with info about the page.',
128
                'name' => 'pageInfo'
129
            ), 'wiki.getPageVersions' => array(
130
                'args' => array('string', 'int'),
131
                'return' => 'array',
132
                'doc' => 'Returns the available revisions of the page.',
133
                'name' => 'pageVersions'
134
            ), 'wiki.putPage' => array(
135
                'args' => array('string', 'string', 'array'),
136
                'return' => 'bool',
137
                'doc' => 'Saves a wiki page.'
138
            ), 'wiki.listLinks' => array(
139
                'args' => array('string'),
140
                'return' => 'array',
141
                'doc' => 'Lists all links contained in a wiki page.'
142
            ), 'wiki.getRecentChanges' => array(
143
                'args' => array('int'),
144
                'return' => 'array',
145
                'Returns a struct about all recent changes since given timestamp.'
146
            ), 'wiki.getRecentMediaChanges' => array(
147
                'args' => array('int'),
148
                'return' => 'array',
149
                'Returns a struct about all recent media changes since given timestamp.'
150
            ), 'wiki.aclCheck' => array(
151
                'args' => array('string', 'string', 'array'),
152
                'return' => 'int',
153
                'doc' => 'Returns the permissions of a given wiki page. By default, for current user/groups'
154
            ), 'wiki.putAttachment' => array(
155
                'args' => array('string', 'file', 'array'),
156
                'return' => 'array',
157
                'doc' => 'Upload a file to the wiki.'
158
            ), 'wiki.deleteAttachment' => array(
159
                'args' => array('string'),
160
                'return' => 'int',
161
                'doc' => 'Delete a file from the wiki.'
162
            ), 'wiki.getAttachment' => array(
163
                'args' => array('string'),
164
                'doc' => 'Return a media file',
165
                'return' => 'file',
166
                'name' => 'getAttachment',
167
            ), 'wiki.getAttachmentInfo' => array(
168
                'args' => array('string'),
169
                'return' => 'array',
170
                'doc' => 'Returns a struct with info about the attachment.'
171
            ), 'dokuwiki.getXMLRPCAPIVersion' => array(
172
                'args' => array(),
173
                'name' => 'getAPIVersion',
174
                'return' => 'int',
175
                'doc' => 'Returns the XMLRPC API version.',
176
                'public' => '1',
177
            ), 'wiki.getRPCVersionSupported' => array(
178
                'args' => array(),
179
                'name' => 'wikiRpcVersion',
180
                'return' => 'int',
181
                'doc' => 'Returns 2 with the supported RPC API version.',
182
                'public' => '1'
183
            ),
184
185
        );
186
    }
187
188
    /**
189
     * @return string
190
     */
191
    public function getVersion()
192
    {
193
        return getVersion();
194
    }
195
196
    /**
197
     * @return int unix timestamp
198
     */
199
    public function getTime()
200
    {
201
        return time();
202
    }
203
204
    /**
205
     * Return a raw wiki page
206
     *
207
     * @param string $id wiki page id
208
     * @param int|string $rev revision timestamp of the page or empty string
209
     * @return string page text.
210
     * @throws AccessDeniedException if no permission for page
211
     */
212
    public function rawPage($id, $rev = '')
213
    {
214
        $id = $this->resolvePageId($id);
215
        if (auth_quickaclcheck($id) < AUTH_READ) {
216
            throw new AccessDeniedException('You are not allowed to read this file', 111);
217
        }
218
        $text = rawWiki($id, $rev);
219
        if (!$text) {
220
            return pageTemplate($id);
221
        } else {
222
            return $text;
223
        }
224
    }
225
226
    /**
227
     * Return a media file
228
     *
229
     * @author Gina Haeussge <[email protected]>
230
     *
231
     * @param string $id file id
232
     * @return mixed media file
233
     * @throws AccessDeniedException no permission for media
234
     * @throws RemoteException not exist
235
     */
236
    public function getAttachment($id)
237
    {
238
        $id = cleanID($id);
239
        if (auth_quickaclcheck(getNS($id) . ':*') < AUTH_READ) {
240
            throw new AccessDeniedException('You are not allowed to read this file', 211);
241
        }
242
243
        $file = mediaFN($id);
244
        if (!@ file_exists($file)) {
245
            throw new RemoteException('The requested file does not exist', 221);
246
        }
247
248
        $data = io_readFile($file, false);
249
        return $this->api->toFile($data);
250
    }
251
252
    /**
253
     * Return info about a media file
254
     *
255
     * @author Gina Haeussge <[email protected]>
256
     *
257
     * @param string $id page id
258
     * @return array
259
     */
260
    public function getAttachmentInfo($id)
261
    {
262
        $id = cleanID($id);
263
        $info = array(
264
            'lastModified' => $this->api->toDate(0),
265
            'size' => 0,
266
        );
267
268
        $file = mediaFN($id);
269
        if (auth_quickaclcheck(getNS($id) . ':*') >= AUTH_READ) {
270
            if (file_exists($file)) {
271
                $info['lastModified'] = $this->api->toDate(filemtime($file));
272
                $info['size'] = filesize($file);
273
            } else {
274
                //Is it deleted media with changelog?
275
                $medialog = new MediaChangeLog($id);
276
                $revisions = $medialog->getRevisions(0, 1);
277
                if (!empty($revisions)) {
278
                    $info['lastModified'] = $this->api->toDate($revisions[0]);
279
                }
280
            }
281
        }
282
283
        return $info;
284
    }
285
286
    /**
287
     * Return a wiki page rendered to html
288
     *
289
     * @param string $id page id
290
     * @param string|int $rev revision timestamp or empty string
291
     * @return null|string html
292
     * @throws AccessDeniedException no access to page
293
     */
294
    public function htmlPage($id, $rev = '')
295
    {
296
        $id = $this->resolvePageId($id);
297
        if (auth_quickaclcheck($id) < AUTH_READ) {
298
            throw new AccessDeniedException('You are not allowed to read this page', 111);
299
        }
300
        return p_wiki_xhtml($id, $rev, false);
301
    }
302
303
    /**
304
     * List all pages - we use the indexer list here
305
     *
306
     * @return array
307
     */
308
    public function listPages()
309
    {
310
        $list = array();
311
        $pages = idx_get_indexer()->getPages();
312
        $pages = array_filter(array_filter($pages, 'isVisiblePage'), 'page_exists');
313
314
        foreach (array_keys($pages) as $idx) {
315
            $perm = auth_quickaclcheck($pages[$idx]);
316
            if ($perm < AUTH_READ) {
317
                continue;
318
            }
319
            $page = array();
320
            $page['id'] = trim($pages[$idx]);
321
            $page['perms'] = $perm;
322
            $page['size'] = @filesize(wikiFN($pages[$idx]));
323
            $page['lastModified'] = $this->api->toDate(@filemtime(wikiFN($pages[$idx])));
324
            $list[] = $page;
325
        }
326
327
        return $list;
328
    }
329
330
    /**
331
     * List all pages in the given namespace (and below)
332
     *
333
     * @param string $ns
334
     * @param array $opts
335
     *    $opts['depth']   recursion level, 0 for all
336
     *    $opts['hash']    do md5 sum of content?
337
     * @return array
338
     */
339
    public function readNamespace($ns, $opts = array())
340
    {
341
        global $conf;
342
343
        if (!is_array($opts)) $opts = array();
344
345
        $ns = cleanID($ns);
346
        $dir = utf8_encodeFN(str_replace(':', '/', $ns));
347
        $data = array();
348
        $opts['skipacl'] = 0; // no ACL skipping for XMLRPC
349
        search($data, $conf['datadir'], 'search_allpages', $opts, $dir);
350
        return $data;
351
    }
352
353
    /**
354
     * List all pages in the given namespace (and below)
355
     *
356
     * @param string $query
357
     * @return array
358
     */
359
    public function search($query)
360
    {
361
        $regex = array();
362
        $data = ft_pageSearch($query, $regex);
363
        $pages = array();
364
365
        // prepare additional data
366
        $idx = 0;
367
        foreach ($data as $id => $score) {
368
            $file = wikiFN($id);
369
370
            if ($idx < FT_SNIPPET_NUMBER) {
371
                $snippet = ft_snippet($id, $regex);
372
                $idx++;
373
            } else {
374
                $snippet = '';
375
            }
376
377
            $pages[] = array(
378
                'id' => $id,
379
                'score' => intval($score),
380
                'rev' => filemtime($file),
381
                'mtime' => filemtime($file),
382
                'size' => filesize($file),
383
                'snippet' => $snippet,
384
                'title' => useHeading('navigation') ? p_get_first_heading($id) : $id
385
            );
386
        }
387
        return $pages;
388
    }
389
390
    /**
391
     * Returns the wiki title.
392
     *
393
     * @return string
394
     */
395
    public function getTitle()
396
    {
397
        global $conf;
398
        return $conf['title'];
399
    }
400
401
    /**
402
     * List all media files.
403
     *
404
     * Available options are 'recursive' for also including the subnamespaces
405
     * in the listing, and 'pattern' for filtering the returned files against
406
     * a regular expression matching their name.
407
     *
408
     * @author Gina Haeussge <[email protected]>
409
     *
410
     * @param string $ns
411
     * @param array $options
412
     *   $options['depth']     recursion level, 0 for all
413
     *   $options['showmsg']   shows message if invalid media id is used
414
     *   $options['pattern']   check given pattern
415
     *   $options['hash']      add hashes to result list
416
     * @return array
417
     * @throws AccessDeniedException no access to the media files
418
     */
419
    public function listAttachments($ns, $options = array())
420
    {
421
        global $conf;
422
423
        $ns = cleanID($ns);
424
425
        if (!is_array($options)) $options = array();
426
        $options['skipacl'] = 0; // no ACL skipping for XMLRPC
427
428
        if (auth_quickaclcheck($ns . ':*') >= AUTH_READ) {
429
            $dir = utf8_encodeFN(str_replace(':', '/', $ns));
430
431
            $data = array();
432
            search($data, $conf['mediadir'], 'search_media', $options, $dir);
433
            $len = count($data);
434
            if (!$len) return array();
435
436
            for ($i = 0; $i < $len; $i++) {
437
                unset($data[$i]['meta']);
438
                $data[$i]['perms'] = $data[$i]['perm'];
439
                unset($data[$i]['perm']);
440
                $data[$i]['lastModified'] = $this->api->toDate($data[$i]['mtime']);
441
            }
442
            return $data;
443
        } else {
444
            throw new AccessDeniedException('You are not allowed to list media files.', 215);
445
        }
446
    }
447
448
    /**
449
     * Return a list of backlinks
450
     *
451
     * @param string $id page id
452
     * @return array
453
     */
454
    public function listBackLinks($id)
455
    {
456
        return ft_backlinks($this->resolvePageId($id));
457
    }
458
459
    /**
460
     * Return some basic data about a page
461
     *
462
     * @param string $id page id
463
     * @param string|int $rev revision timestamp or empty string
464
     * @return array
465
     * @throws AccessDeniedException no access for page
466
     * @throws RemoteException page not exist
467
     */
468
    public function pageInfo($id, $rev = '')
469
    {
470
        $id = $this->resolvePageId($id);
471
        if (auth_quickaclcheck($id) < AUTH_READ) {
472
            throw new AccessDeniedException('You are not allowed to read this page', 111);
473
        }
474
        $file = wikiFN($id, $rev);
475
        $time = @filemtime($file);
476
        if (!$time) {
477
            throw new RemoteException('The requested page does not exist', 121);
478
        }
479
480
        // set revision to current version if empty, use revision otherwise
481
        // as the timestamps of old files are not necessarily correct
482
        if ($rev === '') {
483
            $rev = $time;
484
        }
485
486
        $pagelog = new PageChangeLog($id, 1024);
487
        $info = $pagelog->getRevisionInfo($rev);
488
489
        $data = array(
490
            'name' => $id,
491
            'lastModified' => $this->api->toDate($rev),
492
            'author' => is_array($info) ? (($info['user']) ? $info['user'] : $info['ip']) : null,
493
            'version' => $rev
494
        );
495
496
        return ($data);
497
    }
498
499
    /**
500
     * Save a wiki page
501
     *
502
     * @author Michael Klier <[email protected]>
503
     *
504
     * @param string $id page id
505
     * @param string $text wiki text
506
     * @param array $params parameters: summary, minor edit
507
     * @return bool
508
     * @throws AccessDeniedException no write access for page
509
     * @throws RemoteException no id, empty new page or locked
510
     */
511
    public function putPage($id, $text, $params = array())
512
    {
513
        global $TEXT;
514
        global $lang;
515
516
        $id = $this->resolvePageId($id);
517
        $TEXT = cleanText($text);
518
        $sum = $params['sum'];
519
        $minor = $params['minor'];
520
521
        if (empty($id)) {
522
            throw new RemoteException('Empty page ID', 131);
523
        }
524
525
        if (!page_exists($id) && trim($TEXT) == '') {
526
            throw new RemoteException('Refusing to write an empty new wiki page', 132);
527
        }
528
529
        if (auth_quickaclcheck($id) < AUTH_EDIT) {
530
            throw new AccessDeniedException('You are not allowed to edit this page', 112);
531
        }
532
533
        // Check, if page is locked
534
        if (checklock($id)) {
535
            throw new RemoteException('The page is currently locked', 133);
536
        }
537
538
        // SPAM check
539
        if (checkwordblock()) {
540
            throw new RemoteException('Positive wordblock check', 134);
541
        }
542
543
        // autoset summary on new pages
544
        if (!page_exists($id) && empty($sum)) {
545
            $sum = $lang['created'];
546
        }
547
548
        // autoset summary on deleted pages
549
        if (page_exists($id) && empty($TEXT) && empty($sum)) {
550
            $sum = $lang['deleted'];
551
        }
552
553
        lock($id);
554
555
        saveWikiText($id, $TEXT, $sum, $minor);
556
557
        unlock($id);
558
559
        // run the indexer if page wasn't indexed yet
560
        idx_addPage($id);
561
562
        return true;
563
    }
564
565
    /**
566
     * Appends text to a wiki page.
567
     *
568
     * @param string $id page id
569
     * @param string $text wiki text
570
     * @param array $params such as summary,minor
571
     * @return bool|string
572
     * @throws RemoteException
573
     */
574
    public function appendPage($id, $text, $params = array())
575
    {
576
        $currentpage = $this->rawPage($id);
577
        if (!is_string($currentpage)) {
578
            return $currentpage;
579
        }
580
        return $this->putPage($id, $currentpage . $text, $params);
581
    }
582
583
    /**
584
     * Remove one or more users from the list of registered users
585
     *
586
     * @param string[] $usernames List of usernames to remove
587
     *
588
     * @return bool
589
     *
590
     * @throws AccessDeniedException
591
     */
592
    public function deleteUsers($usernames)
593
    {
594
        if (!auth_isadmin()) {
595
            throw new AccessDeniedException('Only admins are allowed to delete users', 114);
596
        }
597
        /** @var \dokuwiki\Extension\AuthPlugin $auth */
598
        global $auth;
599
        return (bool)$auth->triggerUserMod('delete', array($usernames));
600
    }
601
602
    /**
603
     * Uploads a file to the wiki.
604
     *
605
     * Michael Klier <[email protected]>
606
     *
607
     * @param string $id page id
608
     * @param string $file
609
     * @param array $params such as overwrite
610
     * @return false|string
611
     * @throws RemoteException
612
     */
613
    public function putAttachment($id, $file, $params = array())
614
    {
615
        $id = cleanID($id);
616
        $auth = auth_quickaclcheck(getNS($id) . ':*');
617
618
        if (!isset($id)) {
619
            throw new RemoteException('Filename not given.', 231);
620
        }
621
622
        global $conf;
623
624
        $ftmp = $conf['tmpdir'] . '/' . md5($id . clientIP());
625
626
        // save temporary file
627
        @unlink($ftmp);
628
        io_saveFile($ftmp, $file);
629
630
        $res = media_save(array('name' => $ftmp), $id, $params['ow'], $auth, 'rename');
631
        if (is_array($res)) {
632
            throw new RemoteException($res[0], -$res[1]);
633
        } else {
634
            return $res;
635
        }
636
    }
637
638
    /**
639
     * Deletes a file from the wiki.
640
     *
641
     * @author Gina Haeussge <[email protected]>
642
     *
643
     * @param string $id page id
644
     * @return int
645
     * @throws AccessDeniedException no permissions
646
     * @throws RemoteException file in use or not deleted
647
     */
648
    public function deleteAttachment($id)
649
    {
650
        $id = cleanID($id);
651
        $auth = auth_quickaclcheck(getNS($id) . ':*');
652
        $res = media_delete($id, $auth);
653
        if ($res & DOKU_MEDIA_DELETED) {
654
            return 0;
655
        } elseif ($res & DOKU_MEDIA_NOT_AUTH) {
656
            throw new AccessDeniedException('You don\'t have permissions to delete files.', 212);
657
        } elseif ($res & DOKU_MEDIA_INUSE) {
658
            throw new RemoteException('File is still referenced', 232);
659
        } else {
660
            throw new RemoteException('Could not delete file', 233);
661
        }
662
    }
663
664
    /**
665
     * Returns the permissions of a given wiki page for the current user or another user
666
     *
667
     * @param string $id page id
668
     * @param string|null $user username
669
     * @param array|null $groups array of groups
670
     * @return int permission level
671
     */
672
    public function aclCheck($id, $user = null, $groups = null)
673
    {
674
        /** @var \dokuwiki\Extension\AuthPlugin $auth */
675
        global $auth;
676
677
        $id = $this->resolvePageId($id);
678
        if ($user === null) {
679
            return auth_quickaclcheck($id);
680
        } else {
681
            if ($groups === null) {
682
                $userinfo = $auth->getUserData($user);
683
                if ($userinfo === false) {
684
                    $groups = array();
685
                } else {
686
                    $groups = $userinfo['grps'];
687
                }
688
            }
689
            return auth_aclcheck($id, $user, $groups);
690
        }
691
    }
692
693
    /**
694
     * Lists all links contained in a wiki page
695
     *
696
     * @author Michael Klier <[email protected]>
697
     *
698
     * @param string $id page id
699
     * @return array
700
     * @throws AccessDeniedException  no read access for page
701
     */
702
    public function listLinks($id)
703
    {
704
        $id = $this->resolvePageId($id);
705
        if (auth_quickaclcheck($id) < AUTH_READ) {
706
            throw new AccessDeniedException('You are not allowed to read this page', 111);
707
        }
708
        $links = array();
709
710
        // resolve page instructions
711
        $ins = p_cached_instructions(wikiFN($id));
712
713
        // instantiate new Renderer - needed for interwiki links
714
        $Renderer = new Doku_Renderer_xhtml();
715
        $Renderer->interwiki = getInterwiki();
716
717
        // parse parse instructions
718
        foreach ($ins as $in) {
0 ignored issues
show
Bug introduced by
The expression $ins of type array|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
719
            $link = array();
720
            switch ($in[0]) {
721
                case 'internallink':
722
                    $link['type'] = 'local';
723
                    $link['page'] = $in[1][0];
724
                    $link['href'] = wl($in[1][0]);
725
                    array_push($links, $link);
726
                    break;
727
                case 'externallink':
728
                    $link['type'] = 'extern';
729
                    $link['page'] = $in[1][0];
730
                    $link['href'] = $in[1][0];
731
                    array_push($links, $link);
732
                    break;
733
                case 'interwikilink':
734
                    $url = $Renderer->_resolveInterWiki($in[1][2], $in[1][3]);
735
                    $link['type'] = 'extern';
736
                    $link['page'] = $url;
737
                    $link['href'] = $url;
738
                    array_push($links, $link);
739
                    break;
740
            }
741
        }
742
743
        return ($links);
744
    }
745
746
    /**
747
     * Returns a list of recent changes since give timestamp
748
     *
749
     * @author Michael Hamann <[email protected]>
750
     * @author Michael Klier <[email protected]>
751
     *
752
     * @param int $timestamp unix timestamp
753
     * @return array
754
     * @throws RemoteException no valid timestamp
755
     */
756
    public function getRecentChanges($timestamp)
757
    {
758
        if (strlen($timestamp) != 10) {
759
            throw new RemoteException('The provided value is not a valid timestamp', 311);
760
        }
761
762
        $recents = getRecentsSince($timestamp);
763
764
        $changes = array();
765
766
        foreach ($recents as $recent) {
767
            $change = array();
768
            $change['name'] = $recent['id'];
769
            $change['lastModified'] = $this->api->toDate($recent['date']);
770
            $change['author'] = $recent['user'];
771
            $change['version'] = $recent['date'];
772
            $change['perms'] = $recent['perms'];
773
            $change['size'] = @filesize(wikiFN($recent['id']));
774
            array_push($changes, $change);
775
        }
776
777
        if (!empty($changes)) {
778
            return $changes;
779
        } else {
780
            // in case we still have nothing at this point
781
            throw new RemoteException('There are no changes in the specified timeframe', 321);
782
        }
783
    }
784
785
    /**
786
     * Returns a list of recent media changes since give timestamp
787
     *
788
     * @author Michael Hamann <[email protected]>
789
     * @author Michael Klier <[email protected]>
790
     *
791
     * @param int $timestamp unix timestamp
792
     * @return array
793
     * @throws RemoteException no valid timestamp
794
     */
795
    public function getRecentMediaChanges($timestamp)
796
    {
797
        if (strlen($timestamp) != 10)
798
            throw new RemoteException('The provided value is not a valid timestamp', 311);
799
800
        $recents = getRecentsSince($timestamp, null, '', RECENTS_MEDIA_CHANGES);
801
802
        $changes = array();
803
804
        foreach ($recents as $recent) {
805
            $change = array();
806
            $change['name'] = $recent['id'];
807
            $change['lastModified'] = $this->api->toDate($recent['date']);
808
            $change['author'] = $recent['user'];
809
            $change['version'] = $recent['date'];
810
            $change['perms'] = $recent['perms'];
811
            $change['size'] = @filesize(mediaFN($recent['id']));
812
            array_push($changes, $change);
813
        }
814
815
        if (!empty($changes)) {
816
            return $changes;
817
        } else {
818
            // in case we still have nothing at this point
819
            throw new RemoteException('There are no changes in the specified timeframe', 321);
820
        }
821
    }
822
823
    /**
824
     * Returns a list of available revisions of a given wiki page
825
     * Number of returned pages is set by $conf['recent']
826
     * However not accessible pages are skipped, so less than $conf['recent'] could be returned
827
     *
828
     * @author Michael Klier <[email protected]>
829
     *
830
     * @param string $id page id
831
     * @param int $first skip the first n changelog lines
832
     *                      0 = from current(if exists)
833
     *                      1 = from 1st old rev
834
     *                      2 = from 2nd old rev, etc
835
     * @return array
836
     * @throws AccessDeniedException no read access for page
837
     * @throws RemoteException empty id
838
     */
839
    public function pageVersions($id, $first = 0)
840
    {
841
        $id = $this->resolvePageId($id);
842
        if (auth_quickaclcheck($id) < AUTH_READ) {
843
            throw new AccessDeniedException('You are not allowed to read this page', 111);
844
        }
845
        global $conf;
846
847
        $versions = array();
848
849
        if (empty($id)) {
850
            throw new RemoteException('Empty page ID', 131);
851
        }
852
853
        $first = (int) $first;
854
        $first_rev = $first - 1;
855
        $first_rev = $first_rev < 0 ? 0 : $first_rev;
856
        $pagelog = new PageChangeLog($id);
857
        $revisions = $pagelog->getRevisions($first_rev, $conf['recent']);
858
859
        if ($first == 0) {
860
            array_unshift($revisions, '');  // include current revision
861
            if (count($revisions) > $conf['recent']) {
862
                array_pop($revisions);          // remove extra log entry
863
            }
864
        }
865
866
        if (!empty($revisions)) {
867
            foreach ($revisions as $rev) {
868
                $file = wikiFN($id, $rev);
869
                $time = @filemtime($file);
870
                // we check if the page actually exists, if this is not the
871
                // case this can lead to less pages being returned than
872
                // specified via $conf['recent']
873
                if ($time) {
874
                    $pagelog->setChunkSize(1024);
875
                    $info = $pagelog->getRevisionInfo($rev ? $rev : $time);
876
                    if (!empty($info)) {
877
                        $data = array();
878
                        $data['user'] = $info['user'];
879
                        $data['ip'] = $info['ip'];
880
                        $data['type'] = $info['type'];
881
                        $data['sum'] = $info['sum'];
882
                        $data['modified'] = $this->api->toDate($info['date']);
883
                        $data['version'] = $info['date'];
884
                        array_push($versions, $data);
885
                    }
886
                }
887
            }
888
            return $versions;
889
        } else {
890
            return array();
891
        }
892
    }
893
894
    /**
895
     * The version of Wiki RPC API supported
896
     */
897
    public function wikiRpcVersion()
898
    {
899
        return 2;
900
    }
901
902
    /**
903
     * Locks or unlocks a given batch of pages
904
     *
905
     * Give an associative array with two keys: lock and unlock. Both should contain a
906
     * list of pages to lock or unlock
907
     *
908
     * Returns an associative array with the keys locked, lockfail, unlocked and
909
     * unlockfail, each containing lists of pages.
910
     *
911
     * @param array[] $set list pages with array('lock' => array, 'unlock' => array)
912
     * @return array
913
     */
914
    public function setLocks($set)
915
    {
916
        $locked = array();
917
        $lockfail = array();
918
        $unlocked = array();
919
        $unlockfail = array();
920
921
        foreach ((array) $set['lock'] as $id) {
922
            $id = $this->resolvePageId($id);
923
            if (auth_quickaclcheck($id) < AUTH_EDIT || checklock($id)) {
924
                $lockfail[] = $id;
925
            } else {
926
                lock($id);
927
                $locked[] = $id;
928
            }
929
        }
930
931
        foreach ((array) $set['unlock'] as $id) {
932
            $id = $this->resolvePageId($id);
933
            if (auth_quickaclcheck($id) < AUTH_EDIT || !unlock($id)) {
934
                $unlockfail[] = $id;
935
            } else {
936
                $unlocked[] = $id;
937
            }
938
        }
939
940
        return array(
941
            'locked' => $locked,
942
            'lockfail' => $lockfail,
943
            'unlocked' => $unlocked,
944
            'unlockfail' => $unlockfail,
945
        );
946
    }
947
948
    /**
949
     * Return API version
950
     *
951
     * @return int
952
     */
953
    public function getAPIVersion()
954
    {
955
        return self::API_VERSION;
956
    }
957
958
    /**
959
     * Login
960
     *
961
     * @param string $user
962
     * @param string $pass
963
     * @return int
964
     */
965
    public function login($user, $pass)
966
    {
967
        global $conf;
968
        /** @var \dokuwiki\Extension\AuthPlugin $auth */
969
        global $auth;
970
971
        if (!$conf['useacl']) return 0;
972
        if (!$auth) return 0;
973
974
        @session_start(); // reopen session for login
975
        $ok = null;
976
        if ($auth->canDo('external')) {
977
            $ok = $auth->trustExternal($user, $pass, false);
978
        }
979
        if ($ok === null){
980
            $evdata = array(
981
                'user' => $user,
982
                'password' => $pass,
983
                'sticky' => false,
984
                'silent' => true,
985
            );
986
            $ok = Event::createAndTrigger('AUTH_LOGIN_CHECK', $evdata, 'auth_login_wrapper');
987
        }
988
        session_write_close(); // we're done with the session
989
990
        return $ok;
991
    }
992
993
    /**
994
     * Log off
995
     *
996
     * @return int
997
     */
998
    public function logoff()
999
    {
1000
        global $conf;
1001
        global $auth;
1002
        if (!$conf['useacl']) return 0;
1003
        if (!$auth) return 0;
1004
1005
        auth_logoff();
1006
1007
        return 1;
1008
    }
1009
1010
    /**
1011
     * Resolve page id
1012
     *
1013
     * @param string $id page id
1014
     * @return string
1015
     */
1016
    private function resolvePageId($id)
1017
    {
1018
        $id = cleanID($id);
1019
        if (empty($id)) {
1020
            global $conf;
1021
            $id = cleanID($conf['start']);
1022
        }
1023
        return $id;
1024
    }
1025
}
1026