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