aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/inc/TreeBuilder/ControlPageBuilder.php
blob: 22598986853832ec8da0ec439345e2f0b9335309 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
<?php

namespace dokuwiki\TreeBuilder;

use dokuwiki\File\PageResolver;
use dokuwiki\TreeBuilder\Node\AbstractNode;
use dokuwiki\TreeBuilder\Node\ExternalLink;
use dokuwiki\TreeBuilder\Node\Top;
use dokuwiki\TreeBuilder\Node\WikiPage;

/**
 * A tree builder that generates a tree from a control page
 *
 * A control page is a wiki page containing a nested list of external and internal links. This builder
 * parses the control page and generates a tree of nodes representing the links.
 */
class ControlPageBuilder extends AbstractBuilder
{
    /** @var int do not include internal links */
    const FLAG_NOINTERNAL = 1;
    /** @var int do not include external links */
    const FLAG_NOEXTERNAL = 2;

    /** @var string */
    protected string $controlPage;
    /** @var int */
    protected int $flags = 0;

    /**
     * Parse the control page
     *
     * Check the flag constants on how to influence the behaviour
     *
     * @param string $controlPage
     * @param int $flags
     */
    public function __construct(string $controlPage)
    {
        $this->controlPage = $controlPage;
    }

    /**
     * @inheritdoc
     * @todo theoretically it should be possible to also take the recursionDecision into account, even though
     *       we don't recurse here. Passing the depth would be easy, but actually aborting going deeper is difficult.
     */
    public function generate(): void
    {
        $this->top = new Top();
        $instructions = p_cached_instructions(wikiFN($this->controlPage));
        if (!$instructions) {
            throw new \RuntimeException('No instructions for control page found');
        }

        $parents = [
            0 => $this->top
        ];
        $level = 0;

        $resolver = new PageResolver($this->controlPage);

        foreach ($instructions as $instruction) {
            switch ($instruction[0]) {
                case 'listu_open':
                    $level++; // new list level
                    break;
                case 'listu_close':
                    // if we had a node on this level, remove it from the parents
                    if(isset($parents[$level])) {
                        unset($parents[$level]);
                    }
                    $level--; // close list level
                    break;
                case 'internallink':
                case 'externallink':
                    if ($instruction[0] == 'internallink') {
                        if ($this->flags & self::FLAG_NOINTERNAL) break;

                        $newpage = new WikiPage(
                            $resolver->resolveId($instruction[1][0]),
                            $instruction[1][1]
                        );
                    } else {
                        if ($this->flags & self::FLAG_NOEXTERNAL) break;

                        $newpage = new ExternalLink(
                            $instruction[1][0],
                            $instruction[1][1]
                        );
                    }

                    if($level) {
                        // remember this page as the parent for this level
                        $parents[$level] = $newpage;
                        // parent is the last page on the previous level
                        // levels may not be evenly distributed, so we need to check the count
                        $parent = $parents[count($parents) - 2];
                    } else {
                        // not in a list, so parent is always the top
                        $parent = $this->top;
                    }

                    $newpage->setParent($parent);
                    $newpage = $this->applyNodeProcessor($newpage);
                    if($newpage instanceof AbstractNode) {
                        $parent->addChild($newpage);
                    }
                    break;
            }
        }

        $this->generated = true;
    }
}