Failed Conditions
Push — psr2-config ( c6639e )
by Andreas
06:39 queued 03:33
created

ApiCore::getVersion()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace dokuwiki\Remote;
4
5
use Doku_Renderer_xhtml;
6
use DokuWiki_Auth_Plugin;
7
use MediaChangeLog;
8
use PageChangeLog;
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
            ), 'wiki.getPage' => array(
81
                'args' => array('string'),
82
                'return' => 'string',
83
                'doc' => 'Get the raw Wiki text of page, latest version.',
84
                'name' => 'rawPage',
85
            ), 'wiki.getPageVersion' => array(
86
                'args' => array('string', 'int'),
87
                'name' => 'rawPage',
88
                'return' => 'string',
89
                'doc' => 'Return a raw wiki page'
90
            ), 'wiki.getPageHTML' => array(
91
                'args' => array('string'),
92
                'return' => 'string',
93
                'doc' => 'Return page in rendered HTML, latest version.',
94
                'name' => 'htmlPage'
95
            ), 'wiki.getPageHTMLVersion' => array(
96
                'args' => array('string', 'int'),
97
                'return' => 'string',
98
                'doc' => 'Return page in rendered HTML.',
99
                'name' => 'htmlPage'
100
            ), 'wiki.getAllPages' => array(
101
                'args' => array(),
102
                'return' => 'array',
103
                'doc' => 'Returns a list of all pages. The result is an array of utf8 pagenames.',
104
                'name' => 'listPages'
105
            ), 'wiki.getAttachments' => array(
106
                'args' => array('string', 'array'),
107
                'return' => 'array',
108
                'doc' => 'Returns a list of all media files.',
109
                'name' => 'listAttachments'
110
            ), 'wiki.getBackLinks' => array(
111
                'args' => array('string'),
112
                'return' => 'array',
113
                'doc' => 'Returns the pages that link to this page.',
114
                'name' => 'listBackLinks'
115
            ), 'wiki.getPageInfo' => array(
116
                'args' => array('string'),
117
                'return' => 'array',
118
                'doc' => 'Returns a struct with info about the page, latest version.',
119
                'name' => 'pageInfo'
120
            ), 'wiki.getPageInfoVersion' => array(
121
                'args' => array('string', 'int'),
122
                'return' => 'array',
123
                'doc' => 'Returns a struct with info about the page.',
124
                'name' => 'pageInfo'
125
            ), 'wiki.getPageVersions' => array(
126
                'args' => array('string', 'int'),
127
                'return' => 'array',
128
                'doc' => 'Returns the available revisions of the page.',
129
                'name' => 'pageVersions'
130
            ), 'wiki.putPage' => array(
131
                'args' => array('string', 'string', 'array'),
132
                'return' => 'bool',
133
                'doc' => 'Saves a wiki page.'
134
            ), 'wiki.listLinks' => array(
135
                'args' => array('string'),
136
                'return' => 'array',
137
                'doc' => 'Lists all links contained in a wiki page.'
138
            ), 'wiki.getRecentChanges' => array(
139
                'args' => array('int'),
140
                'return' => 'array',
141
                'Returns a struct about all recent changes since given timestamp.'
142
            ), 'wiki.getRecentMediaChanges' => array(
143
                'args' => array('int'),
144
                'return' => 'array',
145
                'Returns a struct about all recent media changes since given timestamp.'
146
            ), 'wiki.aclCheck' => array(
147
                'args' => array('string', 'string', 'array'),
148
                'return' => 'int',
149
                'doc' => 'Returns the permissions of a given wiki page. By default, for current user/groups'
150
            ), 'wiki.putAttachment' => array(
151
                'args' => array('string', 'file', 'array'),
152
                'return' => 'array',
153
                'doc' => 'Upload a file to the wiki.'
154
            ), 'wiki.deleteAttachment' => array(
155
                'args' => array('string'),
156
                'return' => 'int',
157
                'doc' => 'Delete a file from the wiki.'
158
            ), 'wiki.getAttachment' => array(
159
                'args' => array('string'),
160
                'doc' => 'Return a media file',
161
                'return' => 'file',
162
                'name' => 'getAttachment',
163
            ), 'wiki.getAttachmentInfo' => array(
164
                'args' => array('string'),
165
                'return' => 'array',
166
                'doc' => 'Returns a struct with info about the attachment.'
167
            ), 'dokuwiki.getXMLRPCAPIVersion' => array(
168
                'args' => array(),
169
                'name' => 'getAPIVersion',
170
                'return' => 'int',
171
                'doc' => 'Returns the XMLRPC API version.',
172
                'public' => '1',
173
            ), 'wiki.getRPCVersionSupported' => array(
174
                'args' => array(),
175
                'name' => 'wikiRpcVersion',
176
                'return' => 'int',
177
                'doc' => 'Returns 2 with the supported RPC API version.',
178
                'public' => '1'
179
            ),
180
181
        );
182
    }
183
184
    /**
185
     * @return string
186
     */
187
    public function getVersion()
188
    {
189
        return getVersion();
190
    }
191
192
    /**
193
     * @return int unix timestamp
194
     */
195
    public function getTime()
196
    {
197
        return time();
198
    }
199
200
    /**
201
     * Return a raw wiki page
202
     *
203
     * @param string $id wiki page id
204
     * @param int|string $rev revision timestamp of the page or empty string
205
     * @return string page text.
206
     * @throws AccessDeniedException if no permission for page
207
     */
208
    public function rawPage($id, $rev = '')
209
    {
210
        $id = $this->resolvePageId($id);
211
        if (auth_quickaclcheck($id) < AUTH_READ) {
212
            throw new AccessDeniedException('You are not allowed to read this file', 111);
213
        }
214
        $text = rawWiki($id, $rev);
215
        if (!$text) {
216
            return pageTemplate($id);
217
        } else {
218
            return $text;
219
        }
220
    }
221
222
    /**
223
     * Return a media file
224
     *
225
     * @author Gina Haeussge <[email protected]>
226
     *
227
     * @param string $id file id
228
     * @return mixed media file
229
     * @throws AccessDeniedException no permission for media
230
     * @throws RemoteException not exist
231
     */
232
    public function getAttachment($id)
233
    {
234
        $id = cleanID($id);
235
        if (auth_quickaclcheck(getNS($id) . ':*') < AUTH_READ) {
236
            throw new AccessDeniedException('You are not allowed to read this file', 211);
237
        }
238
239
        $file = mediaFN($id);
240
        if (!@ file_exists($file)) {
241
            throw new RemoteException('The requested file does not exist', 221);
242
        }
243
244
        $data = io_readFile($file, false);
245
        return $this->api->toFile($data);
246
    }
247
248
    /**
249
     * Return info about a media file
250
     *
251
     * @author Gina Haeussge <[email protected]>
252
     *
253
     * @param string $id page id
254
     * @return array
255
     */
256
    public function getAttachmentInfo($id)
257
    {
258
        $id = cleanID($id);
259
        $info = array(
260
            'lastModified' => $this->api->toDate(0),
261
            'size' => 0,
262
        );
263
264
        $file = mediaFN($id);
265
        if (auth_quickaclcheck(getNS($id) . ':*') >= AUTH_READ) {
266
            if (file_exists($file)) {
267
                $info['lastModified'] = $this->api->toDate(filemtime($file));
268
                $info['size'] = filesize($file);
269
            } else {
270
                //Is it deleted media with changelog?
271
                $medialog = new MediaChangeLog($id);
272
                $revisions = $medialog->getRevisions(0, 1);
273
                if (!empty($revisions)) {
274
                    $info['lastModified'] = $this->api->toDate($revisions[0]);
275
                }
276
            }
277
        }
278
279
        return $info;
280
    }
281
282
    /**
283
     * Return a wiki page rendered to html
284
     *
285
     * @param string $id page id
286
     * @param string|int $rev revision timestamp or empty string
287
     * @return null|string html
288
     * @throws AccessDeniedException no access to page
289
     */
290
    public function htmlPage($id, $rev = '')
291
    {
292
        $id = $this->resolvePageId($id);
293
        if (auth_quickaclcheck($id) < AUTH_READ) {
294
            throw new AccessDeniedException('You are not allowed to read this page', 111);
295
        }
296
        return p_wiki_xhtml($id, $rev, false);
297
    }
298
299
    /**
300
     * List all pages - we use the indexer list here
301
     *
302
     * @return array
303
     */
304
    public function listPages()
305
    {
306
        $list = array();
307
        $pages = idx_get_indexer()->getPages();
308
        $pages = array_filter(array_filter($pages, 'isVisiblePage'), 'page_exists');
309
310
        foreach (array_keys($pages) as $idx) {
311
            $perm = auth_quickaclcheck($pages[$idx]);
312
            if ($perm < AUTH_READ) {
313
                continue;
314
            }
315
            $page = array();
316
            $page['id'] = trim($pages[$idx]);
317
            $page['perms'] = $perm;
318
            $page['size'] = @filesize(wikiFN($pages[$idx]));
319
            $page['lastModified'] = $this->api->toDate(@filemtime(wikiFN($pages[$idx])));
320
            $list[] = $page;
321
        }
322
323
        return $list;
324
    }
325
326
    /**
327
     * List all pages in the given namespace (and below)
328
     *
329
     * @param string $ns
330
     * @param array $opts
331
     *    $opts['depth']   recursion level, 0 for all
332
     *    $opts['hash']    do md5 sum of content?
333
     * @return array
334
     */
335
    public function readNamespace($ns, $opts)
336
    {
337
        global $conf;
338
339
        if (!is_array($opts)) $opts = array();
340
341
        $ns = cleanID($ns);
342
        $dir = utf8_encodeFN(str_replace(':', '/', $ns));
343
        $data = array();
344
        $opts['skipacl'] = 0; // no ACL skipping for XMLRPC
345
        search($data, $conf['datadir'], 'search_allpages', $opts, $dir);
346
        return $data;
347
    }
348
349
    /**
350
     * List all pages in the given namespace (and below)
351
     *
352
     * @param string $query
353
     * @return array
354
     */
355
    public function search($query)
356
    {
357
        $regex = array();
358
        $data = ft_pageSearch($query, $regex);
359
        $pages = array();
360
361
        // prepare additional data
362
        $idx = 0;
363
        foreach ($data as $id => $score) {
364
            $file = wikiFN($id);
365
366
            if ($idx < FT_SNIPPET_NUMBER) {
367
                $snippet = ft_snippet($id, $regex);
368
                $idx++;
369
            } else {
370
                $snippet = '';
371
            }
372
373
            $pages[] = array(
374
                'id' => $id,
375
                'score' => intval($score),
376
                'rev' => filemtime($file),
377
                'mtime' => filemtime($file),
378
                'size' => filesize($file),
379
                'snippet' => $snippet,
380
                'title' => useHeading('navigation') ? p_get_first_heading($id) : $id
381
            );
382
        }
383
        return $pages;
384
    }
385
386
    /**
387
     * Returns the wiki title.
388
     *
389
     * @return string
390
     */
391
    public function getTitle()
392
    {
393
        global $conf;
394
        return $conf['title'];
395
    }
396
397
    /**
398
     * List all media files.
399
     *
400
     * Available options are 'recursive' for also including the subnamespaces
401
     * in the listing, and 'pattern' for filtering the returned files against
402
     * a regular expression matching their name.
403
     *
404
     * @author Gina Haeussge <[email protected]>
405
     *
406
     * @param string $ns
407
     * @param array $options
408
     *   $options['depth']     recursion level, 0 for all
409
     *   $options['showmsg']   shows message if invalid media id is used
410
     *   $options['pattern']   check given pattern
411
     *   $options['hash']      add hashes to result list
412
     * @return array
413
     * @throws AccessDeniedException no access to the media files
414
     */
415
    public function listAttachments($ns, $options = array())
416
    {
417
        global $conf;
418
419
        $ns = cleanID($ns);
420
421
        if (!is_array($options)) $options = array();
422
        $options['skipacl'] = 0; // no ACL skipping for XMLRPC
423
424
        if (auth_quickaclcheck($ns . ':*') >= AUTH_READ) {
425
            $dir = utf8_encodeFN(str_replace(':', '/', $ns));
426
427
            $data = array();
428
            search($data, $conf['mediadir'], 'search_media', $options, $dir);
429
            $len = count($data);
430
            if (!$len) return array();
431
432
            for ($i = 0; $i < $len; $i++) {
433
                unset($data[$i]['meta']);
434
                $data[$i]['perms'] = $data[$i]['perm'];
435
                unset($data[$i]['perm']);
436
                $data[$i]['lastModified'] = $this->api->toDate($data[$i]['mtime']);
437
            }
438
            return $data;
439
        } else {
440
            throw new AccessDeniedException('You are not allowed to list media files.', 215);
441
        }
442
    }
443
444
    /**
445
     * Return a list of backlinks
446
     *
447
     * @param string $id page id
448
     * @return array
449
     */
450
    public function listBackLinks($id)
451
    {
452
        return ft_backlinks($this->resolvePageId($id));
453
    }
454
455
    /**
456
     * Return some basic data about a page
457
     *
458
     * @param string $id page id
459
     * @param string|int $rev revision timestamp or empty string
460
     * @return array
461
     * @throws AccessDeniedException no access for page
462
     * @throws RemoteException page not exist
463
     */
464
    public function pageInfo($id, $rev = '')
465
    {
466
        $id = $this->resolvePageId($id);
467
        if (auth_quickaclcheck($id) < AUTH_READ) {
468
            throw new AccessDeniedException('You are not allowed to read this page', 111);
469
        }
470
        $file = wikiFN($id, $rev);
471
        $time = @filemtime($file);
472
        if (!$time) {
473
            throw new RemoteException('The requested page does not exist', 121);
474
        }
475
476
        // set revision to current version if empty, use revision otherwise
477
        // as the timestamps of old files are not necessarily correct
478
        if ($rev === '') {
479
            $rev = $time;
480
        }
481
482
        $pagelog = new PageChangeLog($id, 1024);
483
        $info = $pagelog->getRevisionInfo($rev);
484
485
        $data = array(
486
            'name' => $id,
487
            'lastModified' => $this->api->toDate($rev),
488
            'author' => (($info['user']) ? $info['user'] : $info['ip']),
489
            'version' => $rev
490
        );
491
492
        return ($data);
493
    }
494
495
    /**
496
     * Save a wiki page
497
     *
498
     * @author Michael Klier <[email protected]>
499
     *
500
     * @param string $id page id
501
     * @param string $text wiki text
502
     * @param array $params parameters: summary, minor edit
503
     * @return bool
504
     * @throws AccessDeniedException no write access for page
505
     * @throws RemoteException no id, empty new page or locked
506
     */
507
    public function putPage($id, $text, $params)
508
    {
509
        global $TEXT;
510
        global $lang;
511
512
        $id = $this->resolvePageId($id);
513
        $TEXT = cleanText($text);
514
        $sum = $params['sum'];
515
        $minor = $params['minor'];
516
517
        if (empty($id)) {
518
            throw new RemoteException('Empty page ID', 131);
519
        }
520
521
        if (!page_exists($id) && trim($TEXT) == '') {
522
            throw new RemoteException('Refusing to write an empty new wiki page', 132);
523
        }
524
525
        if (auth_quickaclcheck($id) < AUTH_EDIT) {
526
            throw new AccessDeniedException('You are not allowed to edit this page', 112);
527
        }
528
529
        // Check, if page is locked
530
        if (checklock($id)) {
531
            throw new RemoteException('The page is currently locked', 133);
532
        }
533
534
        // SPAM check
535
        if (checkwordblock()) {
536
            throw new RemoteException('Positive wordblock check', 134);
537
        }
538
539
        // autoset summary on new pages
540
        if (!page_exists($id) && empty($sum)) {
541
            $sum = $lang['created'];
542
        }
543
544
        // autoset summary on deleted pages
545
        if (page_exists($id) && empty($TEXT) && empty($sum)) {
546
            $sum = $lang['deleted'];
547
        }
548
549
        lock($id);
550
551
        saveWikiText($id, $TEXT, $sum, $minor);
552
553
        unlock($id);
554
555
        // run the indexer if page wasn't indexed yet
556
        idx_addPage($id);
557
558
        return true;
559
    }
560
561
    /**
562
     * Appends text to a wiki page.
563
     *
564
     * @param string $id page id
565
     * @param string $text wiki text
566
     * @param array $params such as summary,minor
567
     * @return bool|string
568
     * @throws RemoteException
569
     */
570
    public function appendPage($id, $text, $params)
571
    {
572
        $currentpage = $this->rawPage($id);
573
        if (!is_string($currentpage)) {
574
            return $currentpage;
575
        }
576
        return $this->putPage($id, $currentpage . $text, $params);
577
    }
578
579
    /**
580
     * Uploads a file to the wiki.
581
     *
582
     * Michael Klier <[email protected]>
583
     *
584
     * @param string $id page id
585
     * @param string $file
586
     * @param array $params such as overwrite
587
     * @return false|string
588
     * @throws RemoteException
589
     */
590
    public function putAttachment($id, $file, $params)
591
    {
592
        $id = cleanID($id);
593
        $auth = auth_quickaclcheck(getNS($id) . ':*');
594
595
        if (!isset($id)) {
596
            throw new RemoteException('Filename not given.', 231);
597
        }
598
599
        global $conf;
600
601
        $ftmp = $conf['tmpdir'] . '/' . md5($id . clientIP());
602
603
        // save temporary file
604
        @unlink($ftmp);
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
605
        io_saveFile($ftmp, $file);
606
607
        $res = media_save(array('name' => $ftmp), $id, $params['ow'], $auth, 'rename');
608
        if (is_array($res)) {
609
            throw new RemoteException($res[0], -$res[1]);
610
        } else {
611
            return $res;
612
        }
613
    }
614
615
    /**
616
     * Deletes a file from the wiki.
617
     *
618
     * @author Gina Haeussge <[email protected]>
619
     *
620
     * @param string $id page id
621
     * @return int
622
     * @throws AccessDeniedException no permissions
623
     * @throws RemoteException file in use or not deleted
624
     */
625
    public function deleteAttachment($id)
626
    {
627
        $id = cleanID($id);
628
        $auth = auth_quickaclcheck(getNS($id) . ':*');
629
        $res = media_delete($id, $auth);
630
        if ($res & DOKU_MEDIA_DELETED) {
631
            return 0;
632
        } elseif ($res & DOKU_MEDIA_NOT_AUTH) {
633
            throw new AccessDeniedException('You don\'t have permissions to delete files.', 212);
634
        } elseif ($res & DOKU_MEDIA_INUSE) {
635
            throw new RemoteException('File is still referenced', 232);
636
        } else {
637
            throw new RemoteException('Could not delete file', 233);
638
        }
639
    }
640
641
    /**
642
     * Returns the permissions of a given wiki page for the current user or another user
643
     *
644
     * @param string $id page id
645
     * @param string|null $user username
646
     * @param array|null $groups array of groups
647
     * @return int permission level
648
     */
649
    public function aclCheck($id, $user = null, $groups = null)
650
    {
651
        /** @var DokuWiki_Auth_Plugin $auth */
652
        global $auth;
653
654
        $id = $this->resolvePageId($id);
655
        if ($user === null) {
656
            return auth_quickaclcheck($id);
657
        } else {
658
            if ($groups === null) {
659
                $userinfo = $auth->getUserData($user);
660
                if ($userinfo === false) {
661
                    $groups = array();
662
                } else {
663
                    $groups = $userinfo['grps'];
664
                }
665
            }
666
            return auth_aclcheck($id, $user, $groups);
667
        }
668
    }
669
670
    /**
671
     * Lists all links contained in a wiki page
672
     *
673
     * @author Michael Klier <[email protected]>
674
     *
675
     * @param string $id page id
676
     * @return array
677
     * @throws AccessDeniedException  no read access for page
678
     */
679
    public function listLinks($id)
680
    {
681
        $id = $this->resolvePageId($id);
682
        if (auth_quickaclcheck($id) < AUTH_READ) {
683
            throw new AccessDeniedException('You are not allowed to read this page', 111);
684
        }
685
        $links = array();
686
687
        // resolve page instructions
688
        $ins = p_cached_instructions(wikiFN($id));
689
690
        // instantiate new Renderer - needed for interwiki links
691
        $Renderer = new Doku_Renderer_xhtml();
692
        $Renderer->interwiki = getInterwiki();
693
694
        // parse parse instructions
695
        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...
696
            $link = array();
697
            switch ($in[0]) {
698
                case 'internallink':
699
                    $link['type'] = 'local';
700
                    $link['page'] = $in[1][0];
701
                    $link['href'] = wl($in[1][0]);
702
                    array_push($links, $link);
703
                    break;
704
                case 'externallink':
705
                    $link['type'] = 'extern';
706
                    $link['page'] = $in[1][0];
707
                    $link['href'] = $in[1][0];
708
                    array_push($links, $link);
709
                    break;
710
                case 'interwikilink':
711
                    $url = $Renderer->_resolveInterWiki($in[1][2], $in[1][3]);
712
                    $link['type'] = 'extern';
713
                    $link['page'] = $url;
714
                    $link['href'] = $url;
715
                    array_push($links, $link);
716
                    break;
717
            }
718
        }
719
720
        return ($links);
721
    }
722
723
    /**
724
     * Returns a list of recent changes since give timestamp
725
     *
726
     * @author Michael Hamann <[email protected]>
727
     * @author Michael Klier <[email protected]>
728
     *
729
     * @param int $timestamp unix timestamp
730
     * @return array
731
     * @throws RemoteException no valid timestamp
732
     */
733
    public function getRecentChanges($timestamp)
734
    {
735
        if (strlen($timestamp) != 10) {
736
            throw new RemoteException('The provided value is not a valid timestamp', 311);
737
        }
738
739
        $recents = getRecentsSince($timestamp);
740
741
        $changes = array();
742
743
        foreach ($recents as $recent) {
744
            $change = array();
745
            $change['name'] = $recent['id'];
746
            $change['lastModified'] = $this->api->toDate($recent['date']);
747
            $change['author'] = $recent['user'];
748
            $change['version'] = $recent['date'];
749
            $change['perms'] = $recent['perms'];
750
            $change['size'] = @filesize(wikiFN($recent['id']));
751
            array_push($changes, $change);
752
        }
753
754
        if (!empty($changes)) {
755
            return $changes;
756
        } else {
757
            // in case we still have nothing at this point
758
            throw new RemoteException('There are no changes in the specified timeframe', 321);
759
        }
760
    }
761
762
    /**
763
     * Returns a list of recent media changes since give timestamp
764
     *
765
     * @author Michael Hamann <[email protected]>
766
     * @author Michael Klier <[email protected]>
767
     *
768
     * @param int $timestamp unix timestamp
769
     * @return array
770
     * @throws RemoteException no valid timestamp
771
     */
772
    public function getRecentMediaChanges($timestamp)
773
    {
774
        if (strlen($timestamp) != 10)
775
            throw new RemoteException('The provided value is not a valid timestamp', 311);
776
777
        $recents = getRecentsSince($timestamp, null, '', RECENTS_MEDIA_CHANGES);
778
779
        $changes = array();
780
781
        foreach ($recents as $recent) {
782
            $change = array();
783
            $change['name'] = $recent['id'];
784
            $change['lastModified'] = $this->api->toDate($recent['date']);
785
            $change['author'] = $recent['user'];
786
            $change['version'] = $recent['date'];
787
            $change['perms'] = $recent['perms'];
788
            $change['size'] = @filesize(mediaFN($recent['id']));
789
            array_push($changes, $change);
790
        }
791
792
        if (!empty($changes)) {
793
            return $changes;
794
        } else {
795
            // in case we still have nothing at this point
796
            throw new RemoteException('There are no changes in the specified timeframe', 321);
797
        }
798
    }
799
800
    /**
801
     * Returns a list of available revisions of a given wiki page
802
     * Number of returned pages is set by $conf['recent']
803
     * However not accessible pages are skipped, so less than $conf['recent'] could be returned
804
     *
805
     * @author Michael Klier <[email protected]>
806
     *
807
     * @param string $id page id
808
     * @param int $first skip the first n changelog lines
809
     *                      0 = from current(if exists)
810
     *                      1 = from 1st old rev
811
     *                      2 = from 2nd old rev, etc
812
     * @return array
813
     * @throws AccessDeniedException no read access for page
814
     * @throws RemoteException empty id
815
     */
816
    public function pageVersions($id, $first)
817
    {
818
        $id = $this->resolvePageId($id);
819
        if (auth_quickaclcheck($id) < AUTH_READ) {
820
            throw new AccessDeniedException('You are not allowed to read this page', 111);
821
        }
822
        global $conf;
823
824
        $versions = array();
825
826
        if (empty($id)) {
827
            throw new RemoteException('Empty page ID', 131);
828
        }
829
830
        $first = (int) $first;
831
        $first_rev = $first - 1;
832
        $first_rev = $first_rev < 0 ? 0 : $first_rev;
833
        $pagelog = new PageChangeLog($id);
834
        $revisions = $pagelog->getRevisions($first_rev, $conf['recent']);
835
836
        if ($first == 0) {
837
            array_unshift($revisions, '');  // include current revision
838
            if (count($revisions) > $conf['recent']) {
839
                array_pop($revisions);          // remove extra log entry
840
            }
841
        }
842
843
        if (!empty($revisions)) {
844
            foreach ($revisions as $rev) {
845
                $file = wikiFN($id, $rev);
846
                $time = @filemtime($file);
847
                // we check if the page actually exists, if this is not the
848
                // case this can lead to less pages being returned than
849
                // specified via $conf['recent']
850
                if ($time) {
851
                    $pagelog->setChunkSize(1024);
852
                    $info = $pagelog->getRevisionInfo($rev ? $rev : $time);
853
                    if (!empty($info)) {
854
                        $data = array();
855
                        $data['user'] = $info['user'];
856
                        $data['ip'] = $info['ip'];
857
                        $data['type'] = $info['type'];
858
                        $data['sum'] = $info['sum'];
859
                        $data['modified'] = $this->api->toDate($info['date']);
860
                        $data['version'] = $info['date'];
861
                        array_push($versions, $data);
862
                    }
863
                }
864
            }
865
            return $versions;
866
        } else {
867
            return array();
868
        }
869
    }
870
871
    /**
872
     * The version of Wiki RPC API supported
873
     */
874
    public function wikiRpcVersion()
875
    {
876
        return 2;
877
    }
878
879
    /**
880
     * Locks or unlocks a given batch of pages
881
     *
882
     * Give an associative array with two keys: lock and unlock. Both should contain a
883
     * list of pages to lock or unlock
884
     *
885
     * Returns an associative array with the keys locked, lockfail, unlocked and
886
     * unlockfail, each containing lists of pages.
887
     *
888
     * @param array[] $set list pages with array('lock' => array, 'unlock' => array)
889
     * @return array
890
     */
891
    public function setLocks($set)
892
    {
893
        $locked = array();
894
        $lockfail = array();
895
        $unlocked = array();
896
        $unlockfail = array();
897
898
        foreach ((array) $set['lock'] as $id) {
899
            $id = $this->resolvePageId($id);
900
            if (auth_quickaclcheck($id) < AUTH_EDIT || checklock($id)) {
901
                $lockfail[] = $id;
902
            } else {
903
                lock($id);
904
                $locked[] = $id;
905
            }
906
        }
907
908
        foreach ((array) $set['unlock'] as $id) {
909
            $id = $this->resolvePageId($id);
910
            if (auth_quickaclcheck($id) < AUTH_EDIT || !unlock($id)) {
911
                $unlockfail[] = $id;
912
            } else {
913
                $unlocked[] = $id;
914
            }
915
        }
916
917
        return array(
918
            'locked' => $locked,
919
            'lockfail' => $lockfail,
920
            'unlocked' => $unlocked,
921
            'unlockfail' => $unlockfail,
922
        );
923
    }
924
925
    /**
926
     * Return API version
927
     *
928
     * @return int
929
     */
930
    public function getAPIVersion()
931
    {
932
        return self::API_VERSION;
933
    }
934
935
    /**
936
     * Login
937
     *
938
     * @param string $user
939
     * @param string $pass
940
     * @return int
941
     */
942
    public function login($user, $pass)
943
    {
944
        global $conf;
945
        /** @var DokuWiki_Auth_Plugin $auth */
946
        global $auth;
947
948
        if (!$conf['useacl']) return 0;
949
        if (!$auth) return 0;
950
951
        @session_start(); // reopen session for login
1 ignored issue
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
952
        if ($auth->canDo('external')) {
953
            $ok = $auth->trustExternal($user, $pass, false);
954
        } else {
955
            $evdata = array(
956
                'user' => $user,
957
                'password' => $pass,
958
                'sticky' => false,
959
                'silent' => true,
960
            );
961
            $ok = trigger_event('AUTH_LOGIN_CHECK', $evdata, 'auth_login_wrapper');
962
        }
963
        session_write_close(); // we're done with the session
964
965
        return $ok;
966
    }
967
968
    /**
969
     * Log off
970
     *
971
     * @return int
972
     */
973
    public function logoff()
974
    {
975
        global $conf;
976
        global $auth;
977
        if (!$conf['useacl']) return 0;
978
        if (!$auth) return 0;
979
980
        auth_logoff();
981
982
        return 1;
983
    }
984
985
    /**
986
     * Resolve page id
987
     *
988
     * @param string $id page id
989
     * @return string
990
     */
991
    private function resolvePageId($id)
992
    {
993
        $id = cleanID($id);
994
        if (empty($id)) {
995
            global $conf;
996
            $id = cleanID($conf['start']);
997
        }
998
        return $id;
999
    }
1000
}
1001