xref: /plugin/simplenavi/syntax.php (revision 4c5e7fe53a086b07303f9962d11819df61d28d18)
11169a1acSAndreas Gohr<?php
2d8ce5486SAndreas Gohr
3d418c031SAndreas Gohruse dokuwiki\Extension\SyntaxPlugin;
4d8ce5486SAndreas Gohruse dokuwiki\File\PageResolver;
5ba73b2c8SAndreas Gohruse dokuwiki\TreeBuilder\Node\AbstractNode;
6ba73b2c8SAndreas Gohruse dokuwiki\TreeBuilder\Node\WikiNamespace;
7ba73b2c8SAndreas Gohruse dokuwiki\TreeBuilder\Node\WikiStartpage;
8ba73b2c8SAndreas Gohruse dokuwiki\TreeBuilder\PageTreeBuilder;
9ba73b2c8SAndreas 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{
19*4c5e7fe5SAndreas Gohr    protected string $ns;
20*4c5e7fe5SAndreas Gohr    protected string $currentID;
21*4c5e7fe5SAndreas Gohr    protected bool $usetitle;
22*4c5e7fe5SAndreas Gohr    protected string $sort;
23*4c5e7fe5SAndreas Gohr    protected bool $home;
241169a1acSAndreas Gohr
25d8ce5486SAndreas Gohr    /** @inheritdoc */
26d8ce5486SAndreas Gohr    public function getType()
27d8ce5486SAndreas Gohr    {
281169a1acSAndreas Gohr        return 'substition';
291169a1acSAndreas Gohr    }
301169a1acSAndreas Gohr
31d8ce5486SAndreas Gohr    /** @inheritdoc */
32d8ce5486SAndreas Gohr    public function getPType()
33d8ce5486SAndreas Gohr    {
341169a1acSAndreas Gohr        return 'block';
351169a1acSAndreas Gohr    }
361169a1acSAndreas Gohr
37d8ce5486SAndreas Gohr    /** @inheritdoc */
38d8ce5486SAndreas Gohr    public function getSort()
39d8ce5486SAndreas Gohr    {
401169a1acSAndreas Gohr        return 155;
411169a1acSAndreas Gohr    }
421169a1acSAndreas Gohr
43d8ce5486SAndreas Gohr    /** @inheritdoc */
44d8ce5486SAndreas Gohr    public function connectTo($mode)
45d8ce5486SAndreas Gohr    {
461169a1acSAndreas Gohr        $this->Lexer->addSpecialPattern('{{simplenavi>[^}]*}}', $mode, 'plugin_simplenavi');
471169a1acSAndreas Gohr    }
481169a1acSAndreas Gohr
49d8ce5486SAndreas Gohr    /** @inheritdoc */
50d8ce5486SAndreas Gohr    public function handle($match, $state, $pos, Doku_Handler $handler)
51d8ce5486SAndreas Gohr    {
525655937aSAndreas Gohr        return explode(' ', substr($match, 13, -2));
531169a1acSAndreas Gohr    }
541169a1acSAndreas Gohr
55d8ce5486SAndreas Gohr    /** @inheritdoc */
56d8ce5486SAndreas Gohr    public function render($format, Doku_Renderer $renderer, $data)
57d8ce5486SAndreas Gohr    {
58d8ce5486SAndreas Gohr        if ($format != 'xhtml') return false;
59*4c5e7fe5SAndreas Gohr        $renderer->nocache();
601169a1acSAndreas Gohr
611169a1acSAndreas Gohr        global $INFO;
621169a1acSAndreas Gohr
63b3e02951SAndreas Gohr        // first data is namespace, rest is options
645655937aSAndreas Gohr        $ns = array_shift($data);
655655937aSAndreas Gohr        if ($ns && $ns[0] === '.') {
665655937aSAndreas Gohr            // resolve relative to current page
675655937aSAndreas Gohr            $ns = getNS((new PageResolver($INFO['id']))->resolveId("$ns:xxx"));
685655937aSAndreas Gohr        } else {
695655937aSAndreas Gohr            $ns = cleanID($ns);
705655937aSAndreas Gohr        }
71b3e02951SAndreas Gohr
72*4c5e7fe5SAndreas Gohr        $this->initState(
73*4c5e7fe5SAndreas Gohr            $ns,
74*4c5e7fe5SAndreas Gohr            $INFO['id'],
75*4c5e7fe5SAndreas Gohr            (bool)$this->getConf('usetitle'),
76*4c5e7fe5SAndreas Gohr            $this->getConf('sort'),
77*4c5e7fe5SAndreas Gohr            in_array('home', $data)
78*4c5e7fe5SAndreas Gohr        );
79*4c5e7fe5SAndreas Gohr
80ba73b2c8SAndreas Gohr        $tree = $this->getTree($ns);
81ba73b2c8SAndreas Gohr        $this->renderTree($renderer, $tree->getTop());
821169a1acSAndreas Gohr
831169a1acSAndreas Gohr        return true;
841169a1acSAndreas Gohr    }
851169a1acSAndreas Gohr
86d8ce5486SAndreas Gohr    /**
87*4c5e7fe5SAndreas Gohr     * Initialize the configuration state of the plugin
88*4c5e7fe5SAndreas Gohr     *
89*4c5e7fe5SAndreas Gohr     * Also used in testing
90e75a33bfSAndreas Gohr     *
91ba73b2c8SAndreas Gohr     * @param string $ns
92*4c5e7fe5SAndreas Gohr     * @param string $currentID
93*4c5e7fe5SAndreas Gohr     * @param bool $usetitle
94*4c5e7fe5SAndreas Gohr     */
95*4c5e7fe5SAndreas Gohr    public function initState(
96*4c5e7fe5SAndreas Gohr        string $ns,
97*4c5e7fe5SAndreas Gohr        string $currentID,
98*4c5e7fe5SAndreas Gohr        bool   $usetitle,
99*4c5e7fe5SAndreas Gohr        string $sort,
100*4c5e7fe5SAndreas Gohr        bool   $home
101*4c5e7fe5SAndreas Gohr    )
102*4c5e7fe5SAndreas Gohr    {
103*4c5e7fe5SAndreas Gohr        $this->ns = $ns;
104*4c5e7fe5SAndreas Gohr        $this->currentID = $currentID;
105*4c5e7fe5SAndreas Gohr        $this->usetitle = $usetitle;
106*4c5e7fe5SAndreas Gohr        $this->sort = $sort;
107*4c5e7fe5SAndreas Gohr        $this->home = $home;
108*4c5e7fe5SAndreas Gohr    }
109*4c5e7fe5SAndreas Gohr
110*4c5e7fe5SAndreas Gohr    /**
111*4c5e7fe5SAndreas Gohr     * Create the tree
112*4c5e7fe5SAndreas Gohr     *
113ba73b2c8SAndreas Gohr     * @return PageTreeBuilder
114e75a33bfSAndreas Gohr     */
115*4c5e7fe5SAndreas Gohr    protected function getTree(): PageTreeBuilder
116e75a33bfSAndreas Gohr    {
117*4c5e7fe5SAndreas Gohr        $tree = new PageTreeBuilder($this->ns);
118ba73b2c8SAndreas Gohr        $tree->addFlag(PageTreeBuilder::FLAG_NS_AS_STARTPAGE);
119*4c5e7fe5SAndreas Gohr        if ($this->home) $tree->addFlag(PageTreeBuilder::FLAG_SELF_TOP);
120ba73b2c8SAndreas Gohr        $tree->setRecursionDecision(\Closure::fromCallable([$this, 'treeRecursionDecision']));
121ba73b2c8SAndreas Gohr        $tree->setNodeProcessor(\Closure::fromCallable([$this, 'treeNodeProcessor']));
122ba73b2c8SAndreas Gohr        $tree->generate();
123*4c5e7fe5SAndreas Gohr
124*4c5e7fe5SAndreas Gohr        switch ($this->sort) {
125*4c5e7fe5SAndreas Gohr            case 'id':
126*4c5e7fe5SAndreas Gohr                $tree->sort(TreeSort::SORT_BY_ID);
127*4c5e7fe5SAndreas Gohr                break;
128*4c5e7fe5SAndreas Gohr            case 'title':
129*4c5e7fe5SAndreas Gohr                $tree->sort(TreeSort::SORT_BY_TITLE);
130*4c5e7fe5SAndreas Gohr                break;
131*4c5e7fe5SAndreas Gohr            case 'ns_id':
132*4c5e7fe5SAndreas Gohr                $tree->sort(TreeSort::SORT_BY_NS_FIRST_THEN_ID);
133*4c5e7fe5SAndreas Gohr                break;
134*4c5e7fe5SAndreas Gohr            default:
135ba73b2c8SAndreas Gohr                $tree->sort(TreeSort::SORT_BY_NS_FIRST_THEN_TITLE);
136*4c5e7fe5SAndreas Gohr                break;
137*4c5e7fe5SAndreas Gohr        }
138*4c5e7fe5SAndreas Gohr
139ba73b2c8SAndreas Gohr        return $tree;
140e75a33bfSAndreas Gohr    }
141e75a33bfSAndreas Gohr
142e75a33bfSAndreas Gohr
143e75a33bfSAndreas Gohr    /**
144ba73b2c8SAndreas Gohr     * Callback for the PageTreeBuilder to decide if we want to recurse into a node
145d8ce5486SAndreas Gohr     *
146ba73b2c8SAndreas Gohr     * @param AbstractNode $node
147ba73b2c8SAndreas Gohr     * @param int $depth
148ba73b2c8SAndreas Gohr     * @return bool
149d8ce5486SAndreas Gohr     */
150ba73b2c8SAndreas Gohr    protected function treeRecursionDecision(AbstractNode $node, int $depth): bool
151d8ce5486SAndreas Gohr    {
152ba73b2c8SAndreas Gohr        if ($node instanceof WikiStartpage) {
153ba73b2c8SAndreas Gohr            $id = $node->getNs(); // use the namespace for startpages
154492ddc4eSAndreas Gohr        } else {
155ba73b2c8SAndreas Gohr            $id = $node->getId();
156492ddc4eSAndreas Gohr        }
157ba73b2c8SAndreas Gohr
158*4c5e7fe5SAndreas Gohr        $is_current = $this->isParent($this->currentID, $id);
159ba73b2c8SAndreas Gohr        $node->setProperty('is_current', $is_current);
160ba73b2c8SAndreas Gohr
161ba73b2c8SAndreas Gohr        // always recurse into the current page path
162ba73b2c8SAndreas Gohr        if ($is_current) return true;
163ba73b2c8SAndreas Gohr
164ba73b2c8SAndreas Gohr        // FIXME for deep peek, we want to recurse until level is reached
165ba73b2c8SAndreas Gohr
166ba73b2c8SAndreas Gohr        return false;
1671169a1acSAndreas Gohr    }
1681169a1acSAndreas Gohr
169d8ce5486SAndreas Gohr    /**
170ba73b2c8SAndreas Gohr     * Callback for the PageTreeBuilder to process a node
171d8ce5486SAndreas Gohr     *
172ba73b2c8SAndreas Gohr     * @param AbstractNode $node
173ba73b2c8SAndreas Gohr     * @return AbstractNode|null
174d8ce5486SAndreas Gohr     */
175ba73b2c8SAndreas Gohr    protected function treeNodeProcessor(AbstractNode $node): ?AbstractNode
176d8ce5486SAndreas Gohr    {
177ba73b2c8SAndreas Gohr        $perm = auth_quickaclcheck($node->getId());
178ba73b2c8SAndreas Gohr        $node->setProperty('permission', $perm);
179*4c5e7fe5SAndreas Gohr        $node->setTitle($this->getTitle($node->getId()));
180ba73b2c8SAndreas Gohr
181ba73b2c8SAndreas Gohr
182ba73b2c8SAndreas Gohr        if ($node->hasChildren()) {
183ba73b2c8SAndreas Gohr            // this node has children, we add it to the tree regardless of the permission
184ba73b2c8SAndreas Gohr            // permissions are checked again when rendering
185ba73b2c8SAndreas Gohr            return $node;
1861169a1acSAndreas Gohr        }
187ba73b2c8SAndreas Gohr
188ba73b2c8SAndreas Gohr        if ($perm < AUTH_READ) {
189ba73b2c8SAndreas Gohr            // no children, no permission. No need to add it to the tree
190ba73b2c8SAndreas Gohr            return null;
191ba73b2c8SAndreas Gohr        }
192ba73b2c8SAndreas Gohr
193ba73b2c8SAndreas Gohr        return $node;
194ba73b2c8SAndreas Gohr    }
195ba73b2c8SAndreas Gohr
196ba73b2c8SAndreas Gohr
197ba73b2c8SAndreas Gohr    /**
198ba73b2c8SAndreas Gohr     * Example on how to render a TreeBuilder tree
199ba73b2c8SAndreas Gohr     *
200ba73b2c8SAndreas Gohr     * @param Doku_Renderer $R The current renderer
201ba73b2c8SAndreas Gohr     * @param AbstractNode $top The top node of the tree (use getTop() to get it)
202ba73b2c8SAndreas Gohr     * @param int $level current nesting level, starting at 1
203ba73b2c8SAndreas Gohr     * @return void
204ba73b2c8SAndreas Gohr     */
205ba73b2c8SAndreas Gohr    protected function renderTree(Doku_Renderer $R, AbstractNode $top, $level = 1)
206ba73b2c8SAndreas Gohr    {
207ba73b2c8SAndreas Gohr        $R->listu_open();
208ba73b2c8SAndreas Gohr        foreach ($top->getChildren() as $node) {
209ba73b2c8SAndreas Gohr            $isfolder = $node instanceof WikiNamespace;
210ba73b2c8SAndreas Gohr            $incurrent = $node->getProperty('is_current', false);
211ba73b2c8SAndreas Gohr
212ba73b2c8SAndreas Gohr            $R->listitem_open(1, $isfolder);
213ba73b2c8SAndreas Gohr            $R->listcontent_open();
214ba73b2c8SAndreas Gohr            if ($incurrent) $R->strong_open();
215ba73b2c8SAndreas Gohr
216ba73b2c8SAndreas Gohr            if (((int)$node->getProperty('permission', 0)) < AUTH_READ) {
217ba73b2c8SAndreas Gohr                $R->cdata($node->getTitle());
218ba73b2c8SAndreas Gohr            } else {
219ba73b2c8SAndreas Gohr                $R->internallink($node->getId(), $node->getTitle(), null, false, 'navigation');
220ba73b2c8SAndreas Gohr            }
221ba73b2c8SAndreas Gohr
222ba73b2c8SAndreas Gohr            if ($incurrent) $R->strong_close();
223ba73b2c8SAndreas Gohr            $R->listcontent_close();
224ba73b2c8SAndreas Gohr            if ($node->hasChildren()) {
225ba73b2c8SAndreas Gohr                $this->renderTree($R, $node, $level + 1);
226ba73b2c8SAndreas Gohr            }
227ba73b2c8SAndreas Gohr            $R->listitem_close();
228ba73b2c8SAndreas Gohr        }
229ba73b2c8SAndreas Gohr        $R->listu_close();
2301169a1acSAndreas Gohr    }
2311169a1acSAndreas Gohr
232d8ce5486SAndreas Gohr    /**
233ba73b2c8SAndreas Gohr     * Check if the given parent ID is a parent of the child ID
234d8ce5486SAndreas Gohr     *
235ba73b2c8SAndreas Gohr     * @param string $child
236ba73b2c8SAndreas Gohr     * @param string $parent
237d8ce5486SAndreas Gohr     * @return bool
238d8ce5486SAndreas Gohr     */
239ba73b2c8SAndreas Gohr    protected function isParent(string $child, string $parent)
240d8ce5486SAndreas Gohr    {
241ba73b2c8SAndreas Gohr        $child = explode(':', $child);
242ba73b2c8SAndreas Gohr        $parent = explode(':', $parent);
243ba73b2c8SAndreas Gohr        return array_slice($child, 0, count($parent)) === $parent;
2441169a1acSAndreas Gohr    }
2451169a1acSAndreas Gohr
246d418c031SAndreas Gohr
247d418c031SAndreas Gohr    /**
248d8ce5486SAndreas Gohr     * Get the title for the given page ID
249d8ce5486SAndreas Gohr     *
250d8ce5486SAndreas Gohr     * @param string $id
251d8ce5486SAndreas Gohr     * @return string
252d8ce5486SAndreas Gohr     */
253*4c5e7fe5SAndreas Gohr    protected function getTitle($id)
254d8ce5486SAndreas Gohr    {
255e306992cSAndreas Gohr        global $conf;
256e306992cSAndreas Gohr
257*4c5e7fe5SAndreas Gohr        if ($this->usetitle) {
258e306992cSAndreas Gohr            $p = p_get_first_heading($id);
259303e1405SMichael Große            if (!empty($p)) return $p;
260e75a33bfSAndreas Gohr        }
261e306992cSAndreas Gohr
262e306992cSAndreas Gohr        $p = noNS($id);
263d8ce5486SAndreas Gohr        if ($p == $conf['start'] || !$p) {
264e306992cSAndreas Gohr            $p = noNS(getNS($id));
265d8ce5486SAndreas Gohr            if (!$p) {
266e306992cSAndreas Gohr                return $conf['start'];
267e306992cSAndreas Gohr            }
268e306992cSAndreas Gohr        }
269e306992cSAndreas Gohr        return $p;
270e306992cSAndreas Gohr    }
2711169a1acSAndreas Gohr}
272