xref: /plugin/simplenavi/syntax.php (revision ba73b2c809339e391de8345cbdbaf3d46c7b09fd)
1<?php
2
3use dokuwiki\Extension\SyntaxPlugin;
4use dokuwiki\File\PageResolver;
5use dokuwiki\TreeBuilder\Node\AbstractNode;
6use dokuwiki\TreeBuilder\Node\WikiNamespace;
7use dokuwiki\TreeBuilder\Node\WikiStartpage;
8use dokuwiki\TreeBuilder\PageTreeBuilder;
9use dokuwiki\TreeBuilder\TreeSort;
10
11/**
12 * DokuWiki Plugin simplenavi (Syntax Component)
13 *
14 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
15 * @author  Andreas Gohr <gohr@cosmocode.de>
16 */
17class syntax_plugin_simplenavi extends SyntaxPlugin
18{
19    private $startpages = [];
20
21    /** @inheritdoc */
22    public function getType()
23    {
24        return 'substition';
25    }
26
27    /** @inheritdoc */
28    public function getPType()
29    {
30        return 'block';
31    }
32
33    /** @inheritdoc */
34    public function getSort()
35    {
36        return 155;
37    }
38
39    /** @inheritdoc */
40    public function connectTo($mode)
41    {
42        $this->Lexer->addSpecialPattern('{{simplenavi>[^}]*}}', $mode, 'plugin_simplenavi');
43    }
44
45    /** @inheritdoc */
46    public function handle($match, $state, $pos, Doku_Handler $handler)
47    {
48        return explode(' ', substr($match, 13, -2));
49    }
50
51    /** @inheritdoc */
52    public function render($format, Doku_Renderer $renderer, $data)
53    {
54        if ($format != 'xhtml') return false;
55
56        global $INFO;
57        $renderer->nocache();
58
59        // first data is namespace, rest is options
60        $ns = array_shift($data);
61        if ($ns && $ns[0] === '.') {
62            // resolve relative to current page
63            $ns = getNS((new PageResolver($INFO['id']))->resolveId("$ns:xxx"));
64        } else {
65            $ns = cleanID($ns);
66        }
67
68        $tree = $this->getTree($ns);
69        $this->renderTree($renderer, $tree->getTop());
70
71        return true;
72    }
73
74    /**
75     * Create the tree
76     *
77     * @param string $ns
78     * @return PageTreeBuilder
79     */
80    protected function getTree(string $ns): PageTreeBuilder
81    {
82        $tree = new PageTreeBuilder($ns);
83        $tree->addFlag(PageTreeBuilder::FLAG_NS_AS_STARTPAGE);
84        $tree->addFlag(PageTreeBuilder::FLAG_SELF_TOP);
85        $tree->setRecursionDecision(\Closure::fromCallable([$this, 'treeRecursionDecision']));
86        $tree->setNodeProcessor(\Closure::fromCallable([$this, 'treeNodeProcessor']));
87        $tree->generate();
88        $tree->sort(TreeSort::SORT_BY_NS_FIRST_THEN_TITLE);
89        return $tree;
90    }
91
92
93    /**
94     * Callback for the PageTreeBuilder to decide if we want to recurse into a node
95     *
96     * @param AbstractNode $node
97     * @param int $depth
98     * @return bool
99     */
100    protected function treeRecursionDecision(AbstractNode $node, int $depth): bool
101    {
102        global $INFO;
103
104        if ($node instanceof WikiStartpage) {
105            $id = $node->getNs(); // use the namespace for startpages
106        } else {
107            $id = $node->getId();
108        }
109
110        $is_current = $this->isParent($INFO['id'], $id);
111        $node->setProperty('is_current', $is_current);
112
113        // always recurse into the current page path
114        if ($is_current) return true;
115
116        // FIXME for deep peek, we want to recurse until level is reached
117
118        return false;
119    }
120
121    /**
122     * Callback for the PageTreeBuilder to process a node
123     *
124     * @param AbstractNode $node
125     * @return AbstractNode|null
126     */
127    protected function treeNodeProcessor(AbstractNode $node): ?AbstractNode
128    {
129        $usetitle = $this->getConf('usetitle');
130
131        $perm = auth_quickaclcheck($node->getId());
132        $node->setProperty('permission', $perm);
133        $node->setTitle($this->getTitle($node->getId(), $usetitle));
134
135
136        if ($node->hasChildren()) {
137            // this node has children, we add it to the tree regardless of the permission
138            // permissions are checked again when rendering
139            return $node;
140        }
141
142        if ($perm < AUTH_READ) {
143            // no children, no permission. No need to add it to the tree
144            return null;
145        }
146
147        return $node;
148    }
149
150
151    /**
152     * Example on how to render a TreeBuilder tree
153     *
154     * @param Doku_Renderer $R The current renderer
155     * @param AbstractNode $top The top node of the tree (use getTop() to get it)
156     * @param int $level current nesting level, starting at 1
157     * @return void
158     */
159    protected function renderTree(Doku_Renderer $R, AbstractNode $top, $level = 1)
160    {
161        $R->listu_open();
162        foreach ($top->getChildren() as $node) {
163            $isfolder = $node instanceof WikiNamespace;
164            $incurrent = $node->getProperty('is_current', false);
165
166            $R->listitem_open(1, $isfolder);
167            $R->listcontent_open();
168            if ($incurrent) $R->strong_open();
169
170            if (((int)$node->getProperty('permission', 0)) < AUTH_READ) {
171                $R->cdata($node->getTitle());
172            } else {
173                $R->internallink($node->getId(), $node->getTitle(), null, false, 'navigation');
174            }
175
176            if ($incurrent) $R->strong_close();
177            $R->listcontent_close();
178            if ($node->hasChildren()) {
179                $this->renderTree($R, $node, $level + 1);
180            }
181            $R->listitem_close();
182        }
183        $R->listu_close();
184    }
185
186    /**
187     * Check if the given parent ID is a parent of the child ID
188     *
189     * @param string $child
190     * @param string $parent
191     * @return bool
192     */
193    protected function isParent(string $child, string $parent)
194    {
195        $child = explode(':', $child);
196        $parent = explode(':', $parent);
197        return array_slice($child, 0, count($parent)) === $parent;
198    }
199
200
201    /**
202     * Get the title for the given page ID
203     *
204     * @param string $id
205     * @param bool $usetitle - use the first heading as title
206     * @return string
207     */
208    protected function getTitle($id, $usetitle)
209    {
210        global $conf;
211
212        if ($usetitle) {
213            $p = p_get_first_heading($id);
214            if (!empty($p)) return $p;
215        }
216
217        $p = noNS($id);
218        if ($p == $conf['start'] || !$p) {
219            $p = noNS(getNS($id));
220            if (!$p) {
221                return $conf['start'];
222            }
223        }
224        return $p;
225    }
226}
227