1<?php
2
3namespace dokuwiki\TreeBuilder;
4
5use dokuwiki\File\PageResolver;
6use dokuwiki\TreeBuilder\Node\AbstractNode;
7use dokuwiki\TreeBuilder\Node\ExternalLink;
8use dokuwiki\TreeBuilder\Node\Top;
9use dokuwiki\TreeBuilder\Node\WikiPage;
10
11/**
12 * A tree builder that generates a tree from a control page
13 *
14 * A control page is a wiki page containing a nested list of external and internal links. This builder
15 * parses the control page and generates a tree of nodes representing the links.
16 */
17class ControlPageBuilder extends AbstractBuilder
18{
19    /** @var int do not include internal links */
20    public const FLAG_NOINTERNAL = 1;
21    /** @var int do not include external links */
22    public const FLAG_NOEXTERNAL = 2;
23
24    /** @var string */
25    protected string $controlPage;
26    /** @var int */
27    protected int $flags = 0;
28
29    /**
30     * Parse the control page
31     *
32     * Check the flag constants on how to influence the behaviour
33     *
34     * @param string $controlPage
35     * @param int $flags
36     */
37    public function __construct(string $controlPage)
38    {
39        $this->controlPage = $controlPage;
40    }
41
42    /**
43     * @inheritdoc
44     * @todo theoretically it should be possible to also take the recursionDecision into account, even though
45     *       we don't recurse here. Passing the depth would be easy, but actually aborting going deeper is difficult.
46     */
47    public function generate(): void
48    {
49        $this->top = new Top();
50        $instructions = p_cached_instructions(wikiFN($this->controlPage));
51        if (!$instructions) {
52            throw new \RuntimeException('No instructions for control page found');
53        }
54
55        $parents = [
56            0 => $this->top
57        ];
58        $level = 0;
59
60        $resolver = new PageResolver($this->controlPage);
61
62        foreach ($instructions as $instruction) {
63            switch ($instruction[0]) {
64                case 'listu_open':
65                    $level++; // new list level
66                    break;
67                case 'listu_close':
68                    // if we had a node on this level, remove it from the parents
69                    if (isset($parents[$level])) {
70                        unset($parents[$level]);
71                    }
72                    $level--; // close list level
73                    break;
74                case 'internallink':
75                case 'externallink':
76                    if ($instruction[0] == 'internallink') {
77                        if ($this->flags & self::FLAG_NOINTERNAL) break;
78
79                        $newpage = new WikiPage(
80                            $resolver->resolveId($instruction[1][0]),
81                            $instruction[1][1]
82                        );
83                    } else {
84                        if ($this->flags & self::FLAG_NOEXTERNAL) break;
85
86                        $newpage = new ExternalLink(
87                            $instruction[1][0],
88                            $instruction[1][1]
89                        );
90                    }
91
92                    if ($level) {
93                        // remember this page as the parent for this level
94                        $parents[$level] = $newpage;
95                        // parent is the last page on the previous level
96                        // levels may not be evenly distributed, so we need to check the count
97                        $parent = $parents[count($parents) - 2];
98                    } else {
99                        // not in a list, so parent is always the top
100                        $parent = $this->top;
101                    }
102
103                    $newpage->setParent($parent);
104                    $newpage = $this->applyNodeProcessor($newpage);
105                    if ($newpage instanceof AbstractNode) {
106                        $parent->addChild($newpage);
107                    }
108                    break;
109            }
110        }
111
112        $this->generated = true;
113    }
114}
115