xref: /dokuwiki/inc/TreeBuilder/ControlPageBuilder.php (revision a8e9ec06570f392919a588ec98f916aab604b5a1)
178a26510SAndreas Gohr<?php
278a26510SAndreas Gohr
378a26510SAndreas Gohrnamespace dokuwiki\TreeBuilder;
478a26510SAndreas Gohr
578a26510SAndreas Gohruse dokuwiki\File\PageResolver;
678a26510SAndreas Gohruse dokuwiki\TreeBuilder\Node\AbstractNode;
778a26510SAndreas Gohruse dokuwiki\TreeBuilder\Node\ExternalLink;
878a26510SAndreas Gohruse dokuwiki\TreeBuilder\Node\Top;
978a26510SAndreas Gohruse dokuwiki\TreeBuilder\Node\WikiPage;
1078a26510SAndreas Gohr
1178a26510SAndreas Gohr/**
1278a26510SAndreas Gohr * A tree builder that generates a tree from a control page
1378a26510SAndreas Gohr *
1478a26510SAndreas Gohr * A control page is a wiki page containing a nested list of external and internal links. This builder
1578a26510SAndreas Gohr * parses the control page and generates a tree of nodes representing the links.
1678a26510SAndreas Gohr */
1778a26510SAndreas Gohrclass ControlPageBuilder extends AbstractBuilder
1878a26510SAndreas Gohr{
1978a26510SAndreas Gohr    /** @var int do not include internal links */
20*a8e9ec06SAndreas Gohr    public const FLAG_NOINTERNAL = 1;
2178a26510SAndreas Gohr    /** @var int do not include external links */
22*a8e9ec06SAndreas Gohr    public const FLAG_NOEXTERNAL = 2;
2378a26510SAndreas Gohr
2478a26510SAndreas Gohr    /** @var string */
2578a26510SAndreas Gohr    protected string $controlPage;
2678a26510SAndreas Gohr    /** @var int */
2778a26510SAndreas Gohr    protected int $flags = 0;
2878a26510SAndreas Gohr
2978a26510SAndreas Gohr    /**
3078a26510SAndreas Gohr     * Parse the control page
3178a26510SAndreas Gohr     *
3278a26510SAndreas Gohr     * Check the flag constants on how to influence the behaviour
3378a26510SAndreas Gohr     *
3478a26510SAndreas Gohr     * @param string $controlPage
3578a26510SAndreas Gohr     * @param int $flags
3678a26510SAndreas Gohr     */
3778a26510SAndreas Gohr    public function __construct(string $controlPage)
3878a26510SAndreas Gohr    {
3978a26510SAndreas Gohr        $this->controlPage = $controlPage;
4078a26510SAndreas Gohr    }
4178a26510SAndreas Gohr
4278a26510SAndreas Gohr    /**
4378a26510SAndreas Gohr     * @inheritdoc
4478a26510SAndreas Gohr     * @todo theoretically it should be possible to also take the recursionDecision into account, even though
4578a26510SAndreas Gohr     *       we don't recurse here. Passing the depth would be easy, but actually aborting going deeper is difficult.
4678a26510SAndreas Gohr     */
4778a26510SAndreas Gohr    public function generate(): void
4878a26510SAndreas Gohr    {
4978a26510SAndreas Gohr        $this->top = new Top();
5078a26510SAndreas Gohr        $instructions = p_cached_instructions(wikiFN($this->controlPage));
5178a26510SAndreas Gohr        if (!$instructions) {
5278a26510SAndreas Gohr            throw new \RuntimeException('No instructions for control page found');
5378a26510SAndreas Gohr        }
5478a26510SAndreas Gohr
5578a26510SAndreas Gohr        $parents = [
5678a26510SAndreas Gohr            0 => $this->top
5778a26510SAndreas Gohr        ];
5878a26510SAndreas Gohr        $level = 0;
5978a26510SAndreas Gohr
6078a26510SAndreas Gohr        $resolver = new PageResolver($this->controlPage);
6178a26510SAndreas Gohr
6278a26510SAndreas Gohr        foreach ($instructions as $instruction) {
6378a26510SAndreas Gohr            switch ($instruction[0]) {
6478a26510SAndreas Gohr                case 'listu_open':
6578a26510SAndreas Gohr                    $level++; // new list level
6678a26510SAndreas Gohr                    break;
6778a26510SAndreas Gohr                case 'listu_close':
6878a26510SAndreas Gohr                    // if we had a node on this level, remove it from the parents
6978a26510SAndreas Gohr                    if (isset($parents[$level])) {
7078a26510SAndreas Gohr                        unset($parents[$level]);
7178a26510SAndreas Gohr                    }
7278a26510SAndreas Gohr                    $level--; // close list level
7378a26510SAndreas Gohr                    break;
7478a26510SAndreas Gohr                case 'internallink':
7578a26510SAndreas Gohr                case 'externallink':
7678a26510SAndreas Gohr                    if ($instruction[0] == 'internallink') {
7778a26510SAndreas Gohr                        if ($this->flags & self::FLAG_NOINTERNAL) break;
7878a26510SAndreas Gohr
7978a26510SAndreas Gohr                        $newpage = new WikiPage(
8078a26510SAndreas Gohr                            $resolver->resolveId($instruction[1][0]),
8178a26510SAndreas Gohr                            $instruction[1][1]
8278a26510SAndreas Gohr                        );
8378a26510SAndreas Gohr                    } else {
8478a26510SAndreas Gohr                        if ($this->flags & self::FLAG_NOEXTERNAL) break;
8578a26510SAndreas Gohr
8678a26510SAndreas Gohr                        $newpage = new ExternalLink(
8778a26510SAndreas Gohr                            $instruction[1][0],
8878a26510SAndreas Gohr                            $instruction[1][1]
8978a26510SAndreas Gohr                        );
9078a26510SAndreas Gohr                    }
9178a26510SAndreas Gohr
9278a26510SAndreas Gohr                    if ($level) {
9378a26510SAndreas Gohr                        // remember this page as the parent for this level
9478a26510SAndreas Gohr                        $parents[$level] = $newpage;
9578a26510SAndreas Gohr                        // parent is the last page on the previous level
9678a26510SAndreas Gohr                        // levels may not be evenly distributed, so we need to check the count
9778a26510SAndreas Gohr                        $parent = $parents[count($parents) - 2];
9878a26510SAndreas Gohr                    } else {
9978a26510SAndreas Gohr                        // not in a list, so parent is always the top
10078a26510SAndreas Gohr                        $parent = $this->top;
10178a26510SAndreas Gohr                    }
10278a26510SAndreas Gohr
10378a26510SAndreas Gohr                    $newpage->setParent($parent);
10478a26510SAndreas Gohr                    $newpage = $this->applyNodeProcessor($newpage);
10578a26510SAndreas Gohr                    if ($newpage instanceof AbstractNode) {
10678a26510SAndreas Gohr                        $parent->addChild($newpage);
10778a26510SAndreas Gohr                    }
10878a26510SAndreas Gohr                    break;
10978a26510SAndreas Gohr            }
11078a26510SAndreas Gohr        }
11178a26510SAndreas Gohr
11278a26510SAndreas Gohr        $this->generated = true;
11378a26510SAndreas Gohr    }
11478a26510SAndreas Gohr}
115