ApiCore   F
last analyzed

Complexity

Total Complexity 109

Size/Duplication

Total Lines 1011
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Importance

Changes 0
Metric Value
wmc 109
lcom 1
cbo 10
dl 0
loc 1011
rs 1.556
c 0
b 0
f 0

31 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
B __getRemoteInfo() 0 149 1
A getVersion() 0 4 1
A getTime() 0 4 1
A rawPage() 0 13 3
A getAttachment() 0 15 3
A getAttachmentInfo() 0 25 4
A htmlPage() 0 8 2
A listPages() 0 22 3
A readNamespace() 0 13 2
A search() 0 30 4
A getTitle() 0 5 1
A listAttachments() 0 28 5
A listBackLinks() 0 4 1
B pageInfo() 0 30 6
C putPage() 0 53 12
A appendPage() 0 8 2
A deleteUsers() 0 9 2
A putAttachment() 0 24 3
A deleteAttachment() 0 15 4
A aclCheck() 0 20 4
B listLinks() 0 43 6
A getRecentChanges() 0 28 4
A getRecentMediaChanges() 0 27 4
C pageVersions() 0 54 11
A wikiRpcVersion() 0 4 1
B setLocks() 0 33 7
A getAPIVersion() 0 4 1
A login() 0 27 5
A logoff() 0 11 3
A resolvePageId() 0 9 2

How to fix   Complexity   

Complex Class

Complex classes like ApiCore often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ApiCore, and based on these observations, apply Extract Interface, too.

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