xref: /plugin/simplenavi/syntax.php (revision ba73b2c809339e391de8345cbdbaf3d46c7b09fd)
11169a1acSAndreas Gohr<?php
2d8ce5486SAndreas Gohr
3d418c031SAndreas Gohruse dokuwiki\Extension\SyntaxPlugin;
4d8ce5486SAndreas Gohruse dokuwiki\File\PageResolver;
5*ba73b2c8SAndreas Gohruse dokuwiki\TreeBuilder\Node\AbstractNode;
6*ba73b2c8SAndreas Gohruse dokuwiki\TreeBuilder\Node\WikiNamespace;
7*ba73b2c8SAndreas Gohruse dokuwiki\TreeBuilder\Node\WikiStartpage;
8*ba73b2c8SAndreas Gohruse dokuwiki\TreeBuilder\PageTreeBuilder;
9*ba73b2c8SAndreas Gohruse dokuwiki\TreeBuilder\TreeSort;
10d8ce5486SAndreas Gohr
111169a1acSAndreas Gohr/**
121169a1acSAndreas Gohr * DokuWiki Plugin simplenavi (Syntax Component)
131169a1acSAndreas Gohr *
141169a1acSAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
151169a1acSAndreas Gohr * @author  Andreas Gohr <gohr@cosmocode.de>
161169a1acSAndreas Gohr */
17d418c031SAndreas Gohrclass syntax_plugin_simplenavi extends SyntaxPlugin
18d8ce5486SAndreas Gohr{
19d8ce5486SAndreas Gohr    private $startpages = [];
201169a1acSAndreas Gohr
21d8ce5486SAndreas Gohr    /** @inheritdoc */
22d8ce5486SAndreas Gohr    public function getType()
23d8ce5486SAndreas Gohr    {
241169a1acSAndreas Gohr        return 'substition';
251169a1acSAndreas Gohr    }
261169a1acSAndreas Gohr
27d8ce5486SAndreas Gohr    /** @inheritdoc */
28d8ce5486SAndreas Gohr    public function getPType()
29d8ce5486SAndreas Gohr    {
301169a1acSAndreas Gohr        return 'block';
311169a1acSAndreas Gohr    }
321169a1acSAndreas Gohr
33d8ce5486SAndreas Gohr    /** @inheritdoc */
34d8ce5486SAndreas Gohr    public function getSort()
35d8ce5486SAndreas Gohr    {
361169a1acSAndreas Gohr        return 155;
371169a1acSAndreas Gohr    }
381169a1acSAndreas Gohr
39d8ce5486SAndreas Gohr    /** @inheritdoc */
40d8ce5486SAndreas Gohr    public function connectTo($mode)
41d8ce5486SAndreas Gohr    {
421169a1acSAndreas Gohr        $this->Lexer->addSpecialPattern('{{simplenavi>[^}]*}}', $mode, 'plugin_simplenavi');
431169a1acSAndreas Gohr    }
441169a1acSAndreas Gohr
45d8ce5486SAndreas Gohr    /** @inheritdoc */
46d8ce5486SAndreas Gohr    public function handle($match, $state, $pos, Doku_Handler $handler)
47d8ce5486SAndreas Gohr    {
485655937aSAndreas Gohr        return explode(' ', substr($match, 13, -2));
491169a1acSAndreas Gohr    }
501169a1acSAndreas Gohr
51d8ce5486SAndreas Gohr    /** @inheritdoc */
52d8ce5486SAndreas Gohr    public function render($format, Doku_Renderer $renderer, $data)
53d8ce5486SAndreas Gohr    {
54d8ce5486SAndreas Gohr        if ($format != 'xhtml') return false;
551169a1acSAndreas Gohr
561169a1acSAndreas Gohr        global $INFO;
57b3e02951SAndreas Gohr        $renderer->nocache();
581169a1acSAndreas Gohr
59b3e02951SAndreas Gohr        // first data is namespace, rest is options
605655937aSAndreas Gohr        $ns = array_shift($data);
615655937aSAndreas Gohr        if ($ns && $ns[0] === '.') {
625655937aSAndreas Gohr            // resolve relative to current page
635655937aSAndreas Gohr            $ns = getNS((new PageResolver($INFO['id']))->resolveId("$ns:xxx"));
645655937aSAndreas Gohr        } else {
655655937aSAndreas Gohr            $ns = cleanID($ns);
665655937aSAndreas Gohr        }
67b3e02951SAndreas Gohr
68*ba73b2c8SAndreas Gohr        $tree = $this->getTree($ns);
69*ba73b2c8SAndreas Gohr        $this->renderTree($renderer, $tree->getTop());
701169a1acSAndreas Gohr
711169a1acSAndreas Gohr        return true;
721169a1acSAndreas Gohr    }
731169a1acSAndreas Gohr
74d8ce5486SAndreas Gohr    /**
75*ba73b2c8SAndreas Gohr     * Create the tree
76e75a33bfSAndreas Gohr     *
77*ba73b2c8SAndreas Gohr     * @param string $ns
78*ba73b2c8SAndreas Gohr     * @return PageTreeBuilder
79e75a33bfSAndreas Gohr     */
80*ba73b2c8SAndreas Gohr    protected function getTree(string $ns): PageTreeBuilder
81e75a33bfSAndreas Gohr    {
82*ba73b2c8SAndreas Gohr        $tree = new PageTreeBuilder($ns);
83*ba73b2c8SAndreas Gohr        $tree->addFlag(PageTreeBuilder::FLAG_NS_AS_STARTPAGE);
84*ba73b2c8SAndreas Gohr        $tree->addFlag(PageTreeBuilder::FLAG_SELF_TOP);
85*ba73b2c8SAndreas Gohr        $tree->setRecursionDecision(\Closure::fromCallable([$this, 'treeRecursionDecision']));
86*ba73b2c8SAndreas Gohr        $tree->setNodeProcessor(\Closure::fromCallable([$this, 'treeNodeProcessor']));
87*ba73b2c8SAndreas Gohr        $tree->generate();
88*ba73b2c8SAndreas Gohr        $tree->sort(TreeSort::SORT_BY_NS_FIRST_THEN_TITLE);
89*ba73b2c8SAndreas Gohr        return $tree;
90e75a33bfSAndreas Gohr    }
91e75a33bfSAndreas Gohr
92e75a33bfSAndreas Gohr
93e75a33bfSAndreas Gohr    /**
94*ba73b2c8SAndreas Gohr     * Callback for the PageTreeBuilder to decide if we want to recurse into a node
95d8ce5486SAndreas Gohr     *
96*ba73b2c8SAndreas Gohr     * @param AbstractNode $node
97*ba73b2c8SAndreas Gohr     * @param int $depth
98*ba73b2c8SAndreas Gohr     * @return bool
99d8ce5486SAndreas Gohr     */
100*ba73b2c8SAndreas Gohr    protected function treeRecursionDecision(AbstractNode $node, int $depth): bool
101d8ce5486SAndreas Gohr    {
102492ddc4eSAndreas Gohr        global $INFO;
103492ddc4eSAndreas Gohr
104*ba73b2c8SAndreas Gohr        if ($node instanceof WikiStartpage) {
105*ba73b2c8SAndreas Gohr            $id = $node->getNs(); // use the namespace for startpages
106492ddc4eSAndreas Gohr        } else {
107*ba73b2c8SAndreas Gohr            $id = $node->getId();
108492ddc4eSAndreas Gohr        }
109*ba73b2c8SAndreas Gohr
110*ba73b2c8SAndreas Gohr        $is_current = $this->isParent($INFO['id'], $id);
111*ba73b2c8SAndreas Gohr        $node->setProperty('is_current', $is_current);
112*ba73b2c8SAndreas Gohr
113*ba73b2c8SAndreas Gohr        // always recurse into the current page path
114*ba73b2c8SAndreas Gohr        if ($is_current) return true;
115*ba73b2c8SAndreas Gohr
116*ba73b2c8SAndreas Gohr        // FIXME for deep peek, we want to recurse until level is reached
117*ba73b2c8SAndreas Gohr
118*ba73b2c8SAndreas Gohr        return false;
1191169a1acSAndreas Gohr    }
1201169a1acSAndreas Gohr
121d8ce5486SAndreas Gohr    /**
122*ba73b2c8SAndreas Gohr     * Callback for the PageTreeBuilder to process a node
123d8ce5486SAndreas Gohr     *
124*ba73b2c8SAndreas Gohr     * @param AbstractNode $node
125*ba73b2c8SAndreas Gohr     * @return AbstractNode|null
126d8ce5486SAndreas Gohr     */
127*ba73b2c8SAndreas Gohr    protected function treeNodeProcessor(AbstractNode $node): ?AbstractNode
128d8ce5486SAndreas Gohr    {
129*ba73b2c8SAndreas Gohr        $usetitle = $this->getConf('usetitle');
130*ba73b2c8SAndreas Gohr
131*ba73b2c8SAndreas Gohr        $perm = auth_quickaclcheck($node->getId());
132*ba73b2c8SAndreas Gohr        $node->setProperty('permission', $perm);
133*ba73b2c8SAndreas Gohr        $node->setTitle($this->getTitle($node->getId(), $usetitle));
134*ba73b2c8SAndreas Gohr
135*ba73b2c8SAndreas Gohr
136*ba73b2c8SAndreas Gohr        if ($node->hasChildren()) {
137*ba73b2c8SAndreas Gohr            // this node has children, we add it to the tree regardless of the permission
138*ba73b2c8SAndreas Gohr            // permissions are checked again when rendering
139*ba73b2c8SAndreas Gohr            return $node;
1401169a1acSAndreas Gohr        }
141*ba73b2c8SAndreas Gohr
142*ba73b2c8SAndreas Gohr        if ($perm < AUTH_READ) {
143*ba73b2c8SAndreas Gohr            // no children, no permission. No need to add it to the tree
144*ba73b2c8SAndreas Gohr            return null;
145*ba73b2c8SAndreas Gohr        }
146*ba73b2c8SAndreas Gohr
147*ba73b2c8SAndreas Gohr        return $node;
148*ba73b2c8SAndreas Gohr    }
149*ba73b2c8SAndreas Gohr
150*ba73b2c8SAndreas Gohr
151*ba73b2c8SAndreas Gohr    /**
152*ba73b2c8SAndreas Gohr     * Example on how to render a TreeBuilder tree
153*ba73b2c8SAndreas Gohr     *
154*ba73b2c8SAndreas Gohr     * @param Doku_Renderer $R The current renderer
155*ba73b2c8SAndreas Gohr     * @param AbstractNode $top The top node of the tree (use getTop() to get it)
156*ba73b2c8SAndreas Gohr     * @param int $level current nesting level, starting at 1
157*ba73b2c8SAndreas Gohr     * @return void
158*ba73b2c8SAndreas Gohr     */
159*ba73b2c8SAndreas Gohr    protected function renderTree(Doku_Renderer $R, AbstractNode $top, $level = 1)
160*ba73b2c8SAndreas Gohr    {
161*ba73b2c8SAndreas Gohr        $R->listu_open();
162*ba73b2c8SAndreas Gohr        foreach ($top->getChildren() as $node) {
163*ba73b2c8SAndreas Gohr            $isfolder = $node instanceof WikiNamespace;
164*ba73b2c8SAndreas Gohr            $incurrent = $node->getProperty('is_current', false);
165*ba73b2c8SAndreas Gohr
166*ba73b2c8SAndreas Gohr            $R->listitem_open(1, $isfolder);
167*ba73b2c8SAndreas Gohr            $R->listcontent_open();
168*ba73b2c8SAndreas Gohr            if ($incurrent) $R->strong_open();
169*ba73b2c8SAndreas Gohr
170*ba73b2c8SAndreas Gohr            if (((int)$node->getProperty('permission', 0)) < AUTH_READ) {
171*ba73b2c8SAndreas Gohr                $R->cdata($node->getTitle());
172*ba73b2c8SAndreas Gohr            } else {
173*ba73b2c8SAndreas Gohr                $R->internallink($node->getId(), $node->getTitle(), null, false, 'navigation');
174*ba73b2c8SAndreas Gohr            }
175*ba73b2c8SAndreas Gohr
176*ba73b2c8SAndreas Gohr            if ($incurrent) $R->strong_close();
177*ba73b2c8SAndreas Gohr            $R->listcontent_close();
178*ba73b2c8SAndreas Gohr            if ($node->hasChildren()) {
179*ba73b2c8SAndreas Gohr                $this->renderTree($R, $node, $level + 1);
180*ba73b2c8SAndreas Gohr            }
181*ba73b2c8SAndreas Gohr            $R->listitem_close();
182*ba73b2c8SAndreas Gohr        }
183*ba73b2c8SAndreas Gohr        $R->listu_close();
1841169a1acSAndreas Gohr    }
1851169a1acSAndreas Gohr
186d8ce5486SAndreas Gohr    /**
187*ba73b2c8SAndreas Gohr     * Check if the given parent ID is a parent of the child ID
188d8ce5486SAndreas Gohr     *
189*ba73b2c8SAndreas Gohr     * @param string $child
190*ba73b2c8SAndreas Gohr     * @param string $parent
191d8ce5486SAndreas Gohr     * @return bool
192d8ce5486SAndreas Gohr     */
193*ba73b2c8SAndreas Gohr    protected function isParent(string $child, string $parent)
194d8ce5486SAndreas Gohr    {
195*ba73b2c8SAndreas Gohr        $child = explode(':', $child);
196*ba73b2c8SAndreas Gohr        $parent = explode(':', $parent);
197*ba73b2c8SAndreas Gohr        return array_slice($child, 0, count($parent)) === $parent;
1981169a1acSAndreas Gohr    }
1991169a1acSAndreas Gohr
200d418c031SAndreas Gohr
201d418c031SAndreas Gohr    /**
202d8ce5486SAndreas Gohr     * Get the title for the given page ID
203d8ce5486SAndreas Gohr     *
204d8ce5486SAndreas Gohr     * @param string $id
205e75a33bfSAndreas Gohr     * @param bool $usetitle - use the first heading as title
206d8ce5486SAndreas Gohr     * @return string
207d8ce5486SAndreas Gohr     */
208e75a33bfSAndreas Gohr    protected function getTitle($id, $usetitle)
209d8ce5486SAndreas Gohr    {
210e306992cSAndreas Gohr        global $conf;
211e306992cSAndreas Gohr
212e75a33bfSAndreas Gohr        if ($usetitle) {
213e306992cSAndreas Gohr            $p = p_get_first_heading($id);
214303e1405SMichael Große            if (!empty($p)) return $p;
215e75a33bfSAndreas Gohr        }
216e306992cSAndreas Gohr
217e306992cSAndreas Gohr        $p = noNS($id);
218d8ce5486SAndreas Gohr        if ($p == $conf['start'] || !$p) {
219e306992cSAndreas Gohr            $p = noNS(getNS($id));
220d8ce5486SAndreas Gohr            if (!$p) {
221e306992cSAndreas Gohr                return $conf['start'];
222e306992cSAndreas Gohr            }
223e306992cSAndreas Gohr        }
224e306992cSAndreas Gohr        return $p;
225e306992cSAndreas Gohr    }
2261169a1acSAndreas Gohr}
227