xref: /plugin/simplenavi/syntax.php (revision ea3588fbe5ebca4327e03de387457cdfdcedf95a)
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{
194c5e7fe5SAndreas Gohr    protected string $ns;
204c5e7fe5SAndreas Gohr    protected string $currentID;
214c5e7fe5SAndreas Gohr    protected bool $usetitle;
224c5e7fe5SAndreas Gohr    protected string $sort;
234c5e7fe5SAndreas Gohr    protected bool $home;
248a3822efSAndreas Gohr    protected int $peek=0;
251169a1acSAndreas Gohr
26d8ce5486SAndreas Gohr    /** @inheritdoc */
27d8ce5486SAndreas Gohr    public function getType()
28d8ce5486SAndreas Gohr    {
291169a1acSAndreas Gohr        return 'substition';
301169a1acSAndreas Gohr    }
311169a1acSAndreas Gohr
32d8ce5486SAndreas Gohr    /** @inheritdoc */
33d8ce5486SAndreas Gohr    public function getPType()
34d8ce5486SAndreas Gohr    {
351169a1acSAndreas Gohr        return 'block';
361169a1acSAndreas Gohr    }
371169a1acSAndreas Gohr
38d8ce5486SAndreas Gohr    /** @inheritdoc */
39d8ce5486SAndreas Gohr    public function getSort()
40d8ce5486SAndreas Gohr    {
411169a1acSAndreas Gohr        return 155;
421169a1acSAndreas Gohr    }
431169a1acSAndreas Gohr
44d8ce5486SAndreas Gohr    /** @inheritdoc */
45d8ce5486SAndreas Gohr    public function connectTo($mode)
46d8ce5486SAndreas Gohr    {
471169a1acSAndreas Gohr        $this->Lexer->addSpecialPattern('{{simplenavi>[^}]*}}', $mode, 'plugin_simplenavi');
481169a1acSAndreas Gohr    }
491169a1acSAndreas Gohr
50d8ce5486SAndreas Gohr    /** @inheritdoc */
51d8ce5486SAndreas Gohr    public function handle($match, $state, $pos, Doku_Handler $handler)
52d8ce5486SAndreas Gohr    {
535655937aSAndreas Gohr        return explode(' ', substr($match, 13, -2));
541169a1acSAndreas Gohr    }
551169a1acSAndreas Gohr
56d8ce5486SAndreas Gohr    /** @inheritdoc */
57d8ce5486SAndreas Gohr    public function render($format, Doku_Renderer $renderer, $data)
58d8ce5486SAndreas Gohr    {
59d8ce5486SAndreas Gohr        if ($format != 'xhtml') return false;
604c5e7fe5SAndreas Gohr        $renderer->nocache();
611169a1acSAndreas Gohr
621169a1acSAndreas Gohr        global $INFO;
631169a1acSAndreas Gohr
64b3e02951SAndreas Gohr        // first data is namespace, rest is options
655655937aSAndreas Gohr        $ns = array_shift($data);
665655937aSAndreas Gohr        if ($ns && $ns[0] === '.') {
675655937aSAndreas Gohr            // resolve relative to current page
685655937aSAndreas Gohr            $ns = getNS((new PageResolver($INFO['id']))->resolveId("$ns:xxx"));
695655937aSAndreas Gohr        } else {
705655937aSAndreas Gohr            $ns = cleanID($ns);
715655937aSAndreas Gohr        }
72b3e02951SAndreas Gohr
734c5e7fe5SAndreas Gohr        $this->initState(
744c5e7fe5SAndreas Gohr            $ns,
754c5e7fe5SAndreas Gohr            $INFO['id'],
764c5e7fe5SAndreas Gohr            (bool)$this->getConf('usetitle'),
774c5e7fe5SAndreas Gohr            $this->getConf('sort'),
788a3822efSAndreas Gohr            in_array('home', $data),
798a3822efSAndreas Gohr            $this->getConf('peek', 0),
804c5e7fe5SAndreas Gohr        );
814c5e7fe5SAndreas Gohr
828a3822efSAndreas Gohr        $tree = $this->getTree();
83ba73b2c8SAndreas Gohr        $this->renderTree($renderer, $tree->getTop());
841169a1acSAndreas Gohr
851169a1acSAndreas Gohr        return true;
861169a1acSAndreas Gohr    }
871169a1acSAndreas Gohr
88d8ce5486SAndreas Gohr    /**
894c5e7fe5SAndreas Gohr     * Initialize the configuration state of the plugin
904c5e7fe5SAndreas Gohr     *
914c5e7fe5SAndreas Gohr     * Also used in testing
92e75a33bfSAndreas Gohr     *
93ba73b2c8SAndreas Gohr     * @param string $ns
944c5e7fe5SAndreas Gohr     * @param string $currentID
954c5e7fe5SAndreas Gohr     * @param bool $usetitle
964c5e7fe5SAndreas Gohr     */
974c5e7fe5SAndreas Gohr    public function initState(
984c5e7fe5SAndreas Gohr        string $ns,
994c5e7fe5SAndreas Gohr        string $currentID,
1004c5e7fe5SAndreas Gohr        bool   $usetitle,
1014c5e7fe5SAndreas Gohr        string $sort,
1028a3822efSAndreas Gohr        bool   $home,
1038a3822efSAndreas Gohr        int $peek = 0
1044c5e7fe5SAndreas Gohr    )
1054c5e7fe5SAndreas Gohr    {
1064c5e7fe5SAndreas Gohr        $this->ns = $ns;
1074c5e7fe5SAndreas Gohr        $this->currentID = $currentID;
1084c5e7fe5SAndreas Gohr        $this->usetitle = $usetitle;
1094c5e7fe5SAndreas Gohr        $this->sort = $sort;
1104c5e7fe5SAndreas Gohr        $this->home = $home;
1118a3822efSAndreas Gohr        $this->peek = $peek;
1124c5e7fe5SAndreas Gohr    }
1134c5e7fe5SAndreas Gohr
1144c5e7fe5SAndreas Gohr    /**
1154c5e7fe5SAndreas Gohr     * Create the tree
1164c5e7fe5SAndreas Gohr     *
117ba73b2c8SAndreas Gohr     * @return PageTreeBuilder
118e75a33bfSAndreas Gohr     */
1194c5e7fe5SAndreas Gohr    protected function getTree(): PageTreeBuilder
120e75a33bfSAndreas Gohr    {
1214c5e7fe5SAndreas Gohr        $tree = new PageTreeBuilder($this->ns);
122ba73b2c8SAndreas Gohr        $tree->addFlag(PageTreeBuilder::FLAG_NS_AS_STARTPAGE);
1234c5e7fe5SAndreas Gohr        if ($this->home) $tree->addFlag(PageTreeBuilder::FLAG_SELF_TOP);
124ba73b2c8SAndreas Gohr        $tree->setRecursionDecision(\Closure::fromCallable([$this, 'treeRecursionDecision']));
125ba73b2c8SAndreas Gohr        $tree->setNodeProcessor(\Closure::fromCallable([$this, 'treeNodeProcessor']));
126ba73b2c8SAndreas Gohr        $tree->generate();
1274c5e7fe5SAndreas Gohr
1284c5e7fe5SAndreas Gohr        switch ($this->sort) {
1294c5e7fe5SAndreas Gohr            case 'id':
1304c5e7fe5SAndreas Gohr                $tree->sort(TreeSort::SORT_BY_ID);
1314c5e7fe5SAndreas Gohr                break;
1324c5e7fe5SAndreas Gohr            case 'title':
1334c5e7fe5SAndreas Gohr                $tree->sort(TreeSort::SORT_BY_TITLE);
1344c5e7fe5SAndreas Gohr                break;
1354c5e7fe5SAndreas Gohr            case 'ns_id':
1364c5e7fe5SAndreas Gohr                $tree->sort(TreeSort::SORT_BY_NS_FIRST_THEN_ID);
1374c5e7fe5SAndreas Gohr                break;
1384c5e7fe5SAndreas Gohr            default:
139ba73b2c8SAndreas Gohr                $tree->sort(TreeSort::SORT_BY_NS_FIRST_THEN_TITLE);
1404c5e7fe5SAndreas Gohr                break;
1414c5e7fe5SAndreas Gohr        }
1424c5e7fe5SAndreas Gohr
143ba73b2c8SAndreas Gohr        return $tree;
144e75a33bfSAndreas Gohr    }
145e75a33bfSAndreas Gohr
146e75a33bfSAndreas Gohr
147e75a33bfSAndreas Gohr    /**
148ba73b2c8SAndreas Gohr     * Callback for the PageTreeBuilder to decide if we want to recurse into a node
149d8ce5486SAndreas Gohr     *
150ba73b2c8SAndreas Gohr     * @param AbstractNode $node
151ba73b2c8SAndreas Gohr     * @param int $depth
152ba73b2c8SAndreas Gohr     * @return bool
153d8ce5486SAndreas Gohr     */
154ba73b2c8SAndreas Gohr    protected function treeRecursionDecision(AbstractNode $node, int $depth): bool
155d8ce5486SAndreas Gohr    {
156ba73b2c8SAndreas Gohr        if ($node instanceof WikiStartpage) {
157ba73b2c8SAndreas Gohr            $id = $node->getNs(); // use the namespace for startpages
158492ddc4eSAndreas Gohr        } else {
159ba73b2c8SAndreas Gohr            $id = $node->getId();
160492ddc4eSAndreas Gohr        }
161ba73b2c8SAndreas Gohr
1624c5e7fe5SAndreas Gohr        $is_current = $this->isParent($this->currentID, $id);
163ba73b2c8SAndreas Gohr        $node->setProperty('is_current', $is_current);
164ba73b2c8SAndreas Gohr
165ba73b2c8SAndreas Gohr        // always recurse into the current page path
166ba73b2c8SAndreas Gohr        if ($is_current) return true;
167ba73b2c8SAndreas Gohr
1688a3822efSAndreas Gohr        // should we peek deeper to see if there's something readable?
1698a3822efSAndreas Gohr        if($depth < $this->peek && auth_quickaclcheck($node->getId()) < AUTH_READ ) {
1708a3822efSAndreas Gohr            return true;
1718a3822efSAndreas Gohr        }
172ba73b2c8SAndreas Gohr
173ba73b2c8SAndreas Gohr        return false;
1741169a1acSAndreas Gohr    }
1751169a1acSAndreas Gohr
176d8ce5486SAndreas Gohr    /**
177ba73b2c8SAndreas Gohr     * Callback for the PageTreeBuilder to process a node
178d8ce5486SAndreas Gohr     *
179ba73b2c8SAndreas Gohr     * @param AbstractNode $node
180ba73b2c8SAndreas Gohr     * @return AbstractNode|null
181d8ce5486SAndreas Gohr     */
182ba73b2c8SAndreas Gohr    protected function treeNodeProcessor(AbstractNode $node): ?AbstractNode
183d8ce5486SAndreas Gohr    {
184ba73b2c8SAndreas Gohr        $perm = auth_quickaclcheck($node->getId());
185ba73b2c8SAndreas Gohr        $node->setProperty('permission', $perm);
1864c5e7fe5SAndreas Gohr        $node->setTitle($this->getTitle($node->getId()));
187ba73b2c8SAndreas Gohr
188ba73b2c8SAndreas Gohr
189ba73b2c8SAndreas Gohr        if ($node->hasChildren()) {
190ba73b2c8SAndreas Gohr            // this node has children, we add it to the tree regardless of the permission
191ba73b2c8SAndreas Gohr            // permissions are checked again when rendering
192ba73b2c8SAndreas Gohr            return $node;
1931169a1acSAndreas Gohr        }
194ba73b2c8SAndreas Gohr
195ba73b2c8SAndreas Gohr        if ($perm < AUTH_READ) {
196ba73b2c8SAndreas Gohr            // no children, no permission. No need to add it to the tree
197ba73b2c8SAndreas Gohr            return null;
198ba73b2c8SAndreas Gohr        }
199ba73b2c8SAndreas Gohr
200ba73b2c8SAndreas Gohr        return $node;
201ba73b2c8SAndreas Gohr    }
202ba73b2c8SAndreas Gohr
203ba73b2c8SAndreas Gohr
204ba73b2c8SAndreas Gohr    /**
205ba73b2c8SAndreas Gohr     * Example on how to render a TreeBuilder tree
206ba73b2c8SAndreas Gohr     *
207ba73b2c8SAndreas Gohr     * @param Doku_Renderer $R The current renderer
208ba73b2c8SAndreas Gohr     * @param AbstractNode $top The top node of the tree (use getTop() to get it)
209ba73b2c8SAndreas Gohr     * @param int $level current nesting level, starting at 1
210ba73b2c8SAndreas Gohr     * @return void
211ba73b2c8SAndreas Gohr     */
212ba73b2c8SAndreas Gohr    protected function renderTree(Doku_Renderer $R, AbstractNode $top, $level = 1)
213ba73b2c8SAndreas Gohr    {
214ba73b2c8SAndreas Gohr        $R->listu_open();
215ba73b2c8SAndreas Gohr        foreach ($top->getChildren() as $node) {
216ba73b2c8SAndreas Gohr            $isfolder = $node instanceof WikiNamespace;
217ba73b2c8SAndreas Gohr            $incurrent = $node->getProperty('is_current', false);
218ba73b2c8SAndreas Gohr
219ba73b2c8SAndreas Gohr            $R->listitem_open(1, $isfolder);
220ba73b2c8SAndreas Gohr            $R->listcontent_open();
221ba73b2c8SAndreas Gohr            if ($incurrent) $R->strong_open();
222ba73b2c8SAndreas Gohr
223ba73b2c8SAndreas Gohr            if (((int)$node->getProperty('permission', 0)) < AUTH_READ) {
224ba73b2c8SAndreas Gohr                $R->cdata($node->getTitle());
225ba73b2c8SAndreas Gohr            } else {
226ba73b2c8SAndreas Gohr                $R->internallink($node->getId(), $node->getTitle(), null, false, 'navigation');
227ba73b2c8SAndreas Gohr            }
228ba73b2c8SAndreas Gohr
229ba73b2c8SAndreas Gohr            if ($incurrent) $R->strong_close();
230ba73b2c8SAndreas Gohr            $R->listcontent_close();
231ba73b2c8SAndreas Gohr            if ($node->hasChildren()) {
232ba73b2c8SAndreas Gohr                $this->renderTree($R, $node, $level + 1);
233ba73b2c8SAndreas Gohr            }
234ba73b2c8SAndreas Gohr            $R->listitem_close();
235ba73b2c8SAndreas Gohr        }
236ba73b2c8SAndreas Gohr        $R->listu_close();
2371169a1acSAndreas Gohr    }
2381169a1acSAndreas Gohr
239d8ce5486SAndreas Gohr    /**
240ba73b2c8SAndreas Gohr     * Check if the given parent ID is a parent of the child ID
241d8ce5486SAndreas Gohr     *
242ba73b2c8SAndreas Gohr     * @param string $child
243ba73b2c8SAndreas Gohr     * @param string $parent
244d8ce5486SAndreas Gohr     * @return bool
245d8ce5486SAndreas Gohr     */
246ba73b2c8SAndreas Gohr    protected function isParent(string $child, string $parent)
247d8ce5486SAndreas Gohr    {
248*ea3588fbSAndreas Gohr (aider)        // Empty parent is considered a parent of all pages
249*ea3588fbSAndreas Gohr (aider)        if ($parent === '') {
250*ea3588fbSAndreas Gohr (aider)            return true;
251*ea3588fbSAndreas Gohr (aider)        }
252*ea3588fbSAndreas Gohr (aider)
253ba73b2c8SAndreas Gohr        $child = explode(':', $child);
254ba73b2c8SAndreas Gohr        $parent = explode(':', $parent);
255ba73b2c8SAndreas Gohr        return array_slice($child, 0, count($parent)) === $parent;
2561169a1acSAndreas Gohr    }
2571169a1acSAndreas Gohr
258d418c031SAndreas Gohr
259d418c031SAndreas Gohr    /**
260d8ce5486SAndreas Gohr     * Get the title for the given page ID
261d8ce5486SAndreas Gohr     *
262d8ce5486SAndreas Gohr     * @param string $id
263d8ce5486SAndreas Gohr     * @return string
264d8ce5486SAndreas Gohr     */
2654c5e7fe5SAndreas Gohr    protected function getTitle($id)
266d8ce5486SAndreas Gohr    {
267e306992cSAndreas Gohr        global $conf;
268e306992cSAndreas Gohr
2694c5e7fe5SAndreas Gohr        if ($this->usetitle) {
270e306992cSAndreas Gohr            $p = p_get_first_heading($id);
271303e1405SMichael Große            if (!empty($p)) return $p;
272e75a33bfSAndreas Gohr        }
273e306992cSAndreas Gohr
274e306992cSAndreas Gohr        $p = noNS($id);
275d8ce5486SAndreas Gohr        if ($p == $conf['start'] || !$p) {
276e306992cSAndreas Gohr            $p = noNS(getNS($id));
277d8ce5486SAndreas Gohr            if (!$p) {
278e306992cSAndreas Gohr                return $conf['start'];
279e306992cSAndreas Gohr            }
280e306992cSAndreas Gohr        }
281e306992cSAndreas Gohr        return $p;
282e306992cSAndreas Gohr    }
2831169a1acSAndreas Gohr}
284