xref: /plugin/simplenavi/syntax.php (revision e58e2f72dd917848130cf0286238cd1317212579)
11169a1acSAndreas Gohr<?php
2d8ce5486SAndreas Gohr
3d8ce5486SAndreas Gohruse dokuwiki\File\PageResolver;
4e75a33bfSAndreas Gohruse dokuwiki\Utf8\Sort;
5d8ce5486SAndreas Gohr
61169a1acSAndreas Gohr/**
71169a1acSAndreas Gohr * DokuWiki Plugin simplenavi (Syntax Component)
81169a1acSAndreas Gohr *
91169a1acSAndreas Gohr * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
101169a1acSAndreas Gohr * @author  Andreas Gohr <gohr@cosmocode.de>
111169a1acSAndreas Gohr */
12d8ce5486SAndreas Gohrclass syntax_plugin_simplenavi extends DokuWiki_Syntax_Plugin
13d8ce5486SAndreas Gohr{
14d8ce5486SAndreas Gohr    private $startpages = [];
151169a1acSAndreas Gohr
16d8ce5486SAndreas Gohr    /** @inheritdoc */
17d8ce5486SAndreas Gohr    public function getType()
18d8ce5486SAndreas Gohr    {
191169a1acSAndreas Gohr        return 'substition';
201169a1acSAndreas Gohr    }
211169a1acSAndreas Gohr
22d8ce5486SAndreas Gohr    /** @inheritdoc */
23d8ce5486SAndreas Gohr    public function getPType()
24d8ce5486SAndreas Gohr    {
251169a1acSAndreas Gohr        return 'block';
261169a1acSAndreas Gohr    }
271169a1acSAndreas Gohr
28d8ce5486SAndreas Gohr    /** @inheritdoc */
29d8ce5486SAndreas Gohr    public function getSort()
30d8ce5486SAndreas Gohr    {
311169a1acSAndreas Gohr        return 155;
321169a1acSAndreas Gohr    }
331169a1acSAndreas Gohr
34d8ce5486SAndreas Gohr    /** @inheritdoc */
35d8ce5486SAndreas Gohr    public function connectTo($mode)
36d8ce5486SAndreas Gohr    {
371169a1acSAndreas Gohr        $this->Lexer->addSpecialPattern('{{simplenavi>[^}]*}}', $mode, 'plugin_simplenavi');
381169a1acSAndreas Gohr    }
391169a1acSAndreas Gohr
40d8ce5486SAndreas Gohr    /** @inheritdoc */
41d8ce5486SAndreas Gohr    public function handle($match, $state, $pos, Doku_Handler $handler)
42d8ce5486SAndreas Gohr    {
435655937aSAndreas Gohr        return explode(' ', substr($match, 13, -2));
441169a1acSAndreas Gohr    }
451169a1acSAndreas Gohr
46d8ce5486SAndreas Gohr    /** @inheritdoc */
47d8ce5486SAndreas Gohr    public function render($format, Doku_Renderer $renderer, $data)
48d8ce5486SAndreas Gohr    {
49d8ce5486SAndreas Gohr        if ($format != 'xhtml') return false;
501169a1acSAndreas Gohr
511169a1acSAndreas Gohr        global $INFO;
52b3e02951SAndreas Gohr        $renderer->nocache();
531169a1acSAndreas Gohr
54b3e02951SAndreas Gohr        // first data is namespace, rest is options
555655937aSAndreas Gohr        $ns = array_shift($data);
565655937aSAndreas Gohr        if ($ns && $ns[0] === '.') {
575655937aSAndreas Gohr            // resolve relative to current page
585655937aSAndreas Gohr            $ns = getNS((new PageResolver($INFO['id']))->resolveId("$ns:xxx"));
595655937aSAndreas Gohr        } else {
605655937aSAndreas Gohr            $ns = cleanID($ns);
615655937aSAndreas Gohr        }
625655937aSAndreas Gohr        // convert to path
635655937aSAndreas Gohr        $ns = utf8_encodeFN(str_replace(':', '/', $ns));
64b3e02951SAndreas Gohr
65e75a33bfSAndreas Gohr        $items = $this->getSortedItems(
66e75a33bfSAndreas Gohr            $ns,
67e75a33bfSAndreas Gohr            $INFO['id'],
68e75a33bfSAndreas Gohr            $this->getConf('usetitle'),
69*e58e2f72SAndreas Gohr            $this->getConf('natsort'),
70*e58e2f72SAndreas Gohr            $this->getConf('nsfirst')
71e75a33bfSAndreas Gohr        );
721169a1acSAndreas Gohr
73b3e02951SAndreas Gohr        $class = 'plugin__simplenavi';
74b3e02951SAndreas Gohr        if (in_array('filter', $data)) $class .= ' plugin__simplenavi_filter';
75b3e02951SAndreas Gohr
76b3e02951SAndreas Gohr        $renderer->doc .= '<div class="' . $class . '">';
77d8ce5486SAndreas Gohr        $renderer->doc .= html_buildlist($items, 'idx', [$this, 'cbList'], [$this, 'cbListItem']);
78d8ce5486SAndreas Gohr        $renderer->doc .= '</div>';
791169a1acSAndreas Gohr
801169a1acSAndreas Gohr        return true;
811169a1acSAndreas Gohr    }
821169a1acSAndreas Gohr
83d8ce5486SAndreas Gohr    /**
84e75a33bfSAndreas Gohr     * Fetch the items to display
85e75a33bfSAndreas Gohr     *
86e75a33bfSAndreas Gohr     * This returns a flat list suitable for html_buildlist()
87e75a33bfSAndreas Gohr     *
88e75a33bfSAndreas Gohr     * @param string $ns the namespace to search in
89e75a33bfSAndreas Gohr     * @param string $current the current page, the tree will be expanded to this
90e75a33bfSAndreas Gohr     * @param bool $useTitle Sort by the title instead of the ID?
91e75a33bfSAndreas Gohr     * @param bool $useNatSort Use natural sorting or just sort by ASCII?
92e75a33bfSAndreas Gohr     * @return array
93e75a33bfSAndreas Gohr     */
94*e58e2f72SAndreas Gohr    public function getSortedItems($ns, $current, $useTitle, $useNatSort, $nsFirst)
95e75a33bfSAndreas Gohr    {
96e75a33bfSAndreas Gohr        global $conf;
97e75a33bfSAndreas Gohr
98e75a33bfSAndreas Gohr        // execute search using our own callback
99e75a33bfSAndreas Gohr        $items = [];
100e75a33bfSAndreas Gohr        search(
101e75a33bfSAndreas Gohr            $items,
102e75a33bfSAndreas Gohr            $conf['datadir'],
103e75a33bfSAndreas Gohr            [$this, 'cbSearch'],
104e75a33bfSAndreas Gohr            [
105e75a33bfSAndreas Gohr                'currentID' => $current,
106e75a33bfSAndreas Gohr                'usetitle' => $useTitle,
107e75a33bfSAndreas Gohr            ],
108e75a33bfSAndreas Gohr            $ns,
109e75a33bfSAndreas Gohr            1,
110e75a33bfSAndreas Gohr            '' // no sorting, we do ourselves
111e75a33bfSAndreas Gohr        );
112e75a33bfSAndreas Gohr
113e75a33bfSAndreas Gohr        // split into separate levels
114e75a33bfSAndreas Gohr        $current = 1;
115e75a33bfSAndreas Gohr        $parents = [];
116e75a33bfSAndreas Gohr        $levels = [];
117e75a33bfSAndreas Gohr        foreach ($items as $idx => $item) {
118e75a33bfSAndreas Gohr            if ($current < $item['level']) {
119e75a33bfSAndreas Gohr                // previous item was the parent
120e75a33bfSAndreas Gohr                $parents[] = array_key_last($levels[$current]);
121e75a33bfSAndreas Gohr            }
122e75a33bfSAndreas Gohr            $current = $item['level'];
123e75a33bfSAndreas Gohr            $levels[$item['level']][$idx] = $item;
124e75a33bfSAndreas Gohr        }
125e75a33bfSAndreas Gohr
126e75a33bfSAndreas Gohr        // sort each level separately
127e75a33bfSAndreas Gohr        foreach ($levels as $level => $items) {
128*e58e2f72SAndreas Gohr            uasort($items, function ($a, $b) use ($useNatSort, $nsFirst) {
129*e58e2f72SAndreas Gohr                return $this->itemComparator($a, $b, $useNatSort, $nsFirst);
130e75a33bfSAndreas Gohr            });
131e75a33bfSAndreas Gohr            $levels[$level] = $items;
132e75a33bfSAndreas Gohr        }
133e75a33bfSAndreas Gohr
134e75a33bfSAndreas Gohr        // merge levels into a flat list again
135e75a33bfSAndreas Gohr        $levels = array_reverse($levels, true);
136e75a33bfSAndreas Gohr        foreach ($levels as $level => $items) {
137e75a33bfSAndreas Gohr            if ($level == 1) break;
138e75a33bfSAndreas Gohr
139e75a33bfSAndreas Gohr            $parent = array_pop($parents);
140e75a33bfSAndreas Gohr            $pos = array_search($parent, array_keys($levels[$level - 1])) + 1;
141e75a33bfSAndreas Gohr
142*e58e2f72SAndreas Gohr            /** @noinspection PhpArrayAccessCanBeReplacedWithForeachValueInspection */
143e75a33bfSAndreas Gohr            $levels[$level - 1] = array_slice($levels[$level - 1], 0, $pos, true) +
144e75a33bfSAndreas Gohr                $levels[$level] +
145e75a33bfSAndreas Gohr                array_slice($levels[$level - 1], $pos, null, true);
146e75a33bfSAndreas Gohr        }
147e75a33bfSAndreas Gohr
148*e58e2f72SAndreas Gohr        return $levels[1];
149e75a33bfSAndreas Gohr    }
150e75a33bfSAndreas Gohr
151e75a33bfSAndreas Gohr    /**
152e75a33bfSAndreas Gohr     * Compare two items
153e75a33bfSAndreas Gohr     *
154e75a33bfSAndreas Gohr     * @param array $a
155e75a33bfSAndreas Gohr     * @param array $b
156e75a33bfSAndreas Gohr     * @param bool $useNatSort
157*e58e2f72SAndreas Gohr     * @param bool $nsFirst
158e75a33bfSAndreas Gohr     * @return int
159e75a33bfSAndreas Gohr     */
160*e58e2f72SAndreas Gohr    public function itemComparator($a, $b, $useNatSort, $nsFirst)
161e75a33bfSAndreas Gohr    {
162*e58e2f72SAndreas Gohr        if ($nsFirst && $a['type'] != $b['type']) {
163*e58e2f72SAndreas Gohr            return $a['type'] == 'd' ? -1 : 1;
164*e58e2f72SAndreas Gohr        }
165*e58e2f72SAndreas Gohr
166e75a33bfSAndreas Gohr        if ($useNatSort) {
167e75a33bfSAndreas Gohr            return Sort::strcmp($a['title'], $b['title']);
168e75a33bfSAndreas Gohr        } else {
169e75a33bfSAndreas Gohr            return strcmp($a['title'], $b['title']);
170e75a33bfSAndreas Gohr        }
171e75a33bfSAndreas Gohr    }
172e75a33bfSAndreas Gohr
173e75a33bfSAndreas Gohr
174e75a33bfSAndreas Gohr    /**
175d8ce5486SAndreas Gohr     * Create a list openening
176d8ce5486SAndreas Gohr     *
177d8ce5486SAndreas Gohr     * @param array $item
178d8ce5486SAndreas Gohr     * @return string
179d8ce5486SAndreas Gohr     * @see html_buildlist()
180d8ce5486SAndreas Gohr     */
181d8ce5486SAndreas Gohr    public function cbList($item)
182d8ce5486SAndreas Gohr    {
183492ddc4eSAndreas Gohr        global $INFO;
184492ddc4eSAndreas Gohr
185492ddc4eSAndreas Gohr        if (($item['type'] == 'd' && $item['open']) || $INFO['id'] == $item['id']) {
186e75a33bfSAndreas Gohr            return '<strong>' . html_wikilink(':' . $item['id'], $item['title']) . '</strong>';
187492ddc4eSAndreas Gohr        } else {
188e75a33bfSAndreas Gohr            return html_wikilink(':' . $item['id'], $item['title']);
189492ddc4eSAndreas Gohr        }
1901169a1acSAndreas Gohr
1911169a1acSAndreas Gohr    }
1921169a1acSAndreas Gohr
193d8ce5486SAndreas Gohr    /**
194d8ce5486SAndreas Gohr     * Create a list item
195d8ce5486SAndreas Gohr     *
196d8ce5486SAndreas Gohr     * @param array $item
197d8ce5486SAndreas Gohr     * @return string
198d8ce5486SAndreas Gohr     * @see html_buildlist()
199d8ce5486SAndreas Gohr     */
200d8ce5486SAndreas Gohr    public function cbListItem($item)
201d8ce5486SAndreas Gohr    {
2021169a1acSAndreas Gohr        if ($item['type'] == "f") {
2031169a1acSAndreas Gohr            return '<li class="level' . $item['level'] . '">';
2041169a1acSAndreas Gohr        } elseif ($item['open']) {
2051169a1acSAndreas Gohr            return '<li class="open">';
2061169a1acSAndreas Gohr        } else {
2071169a1acSAndreas Gohr            return '<li class="closed">';
2081169a1acSAndreas Gohr        }
2091169a1acSAndreas Gohr    }
2101169a1acSAndreas Gohr
211d8ce5486SAndreas Gohr    /**
212d8ce5486SAndreas Gohr     * Custom search callback
213d8ce5486SAndreas Gohr     *
214d8ce5486SAndreas Gohr     * @param $data
215d8ce5486SAndreas Gohr     * @param $base
216d8ce5486SAndreas Gohr     * @param $file
217d8ce5486SAndreas Gohr     * @param $type
218d8ce5486SAndreas Gohr     * @param $lvl
219e75a33bfSAndreas Gohr     * @param array $opts - currentID is the currently shown page
220d8ce5486SAndreas Gohr     * @return bool
221d8ce5486SAndreas Gohr     */
222d8ce5486SAndreas Gohr    public function cbSearch(&$data, $base, $file, $type, $lvl, $opts)
223d8ce5486SAndreas Gohr    {
2241169a1acSAndreas Gohr        global $conf;
2251169a1acSAndreas Gohr        $return = true;
2261169a1acSAndreas Gohr
2271169a1acSAndreas Gohr        $id = pathID($file);
2281169a1acSAndreas Gohr
2291169a1acSAndreas Gohr        if ($type == 'd' && !(
230e75a33bfSAndreas Gohr                preg_match('#^' . $id . '(:|$)#', $opts['currentID']) ||
231e75a33bfSAndreas Gohr                preg_match('#^' . $id . '(:|$)#', getNS($opts['currentID']))
2321169a1acSAndreas Gohr
2331169a1acSAndreas Gohr            )) {
2341169a1acSAndreas Gohr            //add but don't recurse
2351169a1acSAndreas Gohr            $return = false;
236303e1405SMichael Große        } elseif ($type == 'f' && (!empty($opts['nofiles']) || substr($file, -4) != '.txt')) {
2371169a1acSAndreas Gohr            //don't add
2381169a1acSAndreas Gohr            return false;
2391169a1acSAndreas Gohr        }
2401169a1acSAndreas Gohr
241660b56c3SAndreas Gohr        // for sneaky index, check access to the namespace's start page
242660b56c3SAndreas Gohr        if ($type == 'd' && $conf['sneaky_index']) {
243660b56c3SAndreas Gohr            $sp = (new PageResolver(''))->resolveId($id . ':');
244660b56c3SAndreas Gohr            if (auth_quickaclcheck($sp) < AUTH_READ) {
2451169a1acSAndreas Gohr                return false;
2461169a1acSAndreas Gohr            }
247660b56c3SAndreas Gohr        }
2481169a1acSAndreas Gohr
2491169a1acSAndreas Gohr        if ($type == 'd') {
2501169a1acSAndreas Gohr            // link directories to their start pages
251e75a33bfSAndreas Gohr            $original = $id;
2521169a1acSAndreas Gohr            $id = "$id:";
253d8ce5486SAndreas Gohr            $id = (new PageResolver(''))->resolveId($id);
2541169a1acSAndreas Gohr            $this->startpages[$id] = 1;
255e75a33bfSAndreas Gohr
256e75a33bfSAndreas Gohr            // if the resolve id is in the same namespace as the original it's a start page named like the dir
257e75a33bfSAndreas Gohr            if (getNS($original) == getNS($id)) {
258e75a33bfSAndreas Gohr                $useNS = $original;
259e75a33bfSAndreas Gohr            }
260e75a33bfSAndreas Gohr
261303e1405SMichael Große        } elseif (!empty($this->startpages[$id])) {
2621169a1acSAndreas Gohr            // skip already shown start pages
2631169a1acSAndreas Gohr            return false;
2641169a1acSAndreas Gohr        } elseif (noNS($id) == $conf['start']) {
2651169a1acSAndreas Gohr            // skip the main start page
2661169a1acSAndreas Gohr            return false;
2671169a1acSAndreas Gohr        }
2681169a1acSAndreas Gohr
2691169a1acSAndreas Gohr        //check hidden
2701169a1acSAndreas Gohr        if (isHiddenPage($id)) {
2711169a1acSAndreas Gohr            return false;
2721169a1acSAndreas Gohr        }
2731169a1acSAndreas Gohr
2741169a1acSAndreas Gohr        //check ACL
2751169a1acSAndreas Gohr        if ($type == 'f' && auth_quickaclcheck($id) < AUTH_READ) {
2761169a1acSAndreas Gohr            return false;
2771169a1acSAndreas Gohr        }
2781169a1acSAndreas Gohr
279e75a33bfSAndreas Gohr        $data[$id] = [
280d8ce5486SAndreas Gohr            'id' => $id,
2811169a1acSAndreas Gohr            'type' => $type,
2821169a1acSAndreas Gohr            'level' => $lvl,
283d8ce5486SAndreas Gohr            'open' => $return,
284e75a33bfSAndreas Gohr            'title' => $this->getTitle($id, $opts['usetitle']),
285e75a33bfSAndreas Gohr            'ns' => $useNS ?? (string)getNS($id),
286e75a33bfSAndreas Gohr        ];
287e75a33bfSAndreas Gohr
2881169a1acSAndreas Gohr        return $return;
2891169a1acSAndreas Gohr    }
2901169a1acSAndreas Gohr
291d8ce5486SAndreas Gohr    /**
292d8ce5486SAndreas Gohr     * Get the title for the given page ID
293d8ce5486SAndreas Gohr     *
294d8ce5486SAndreas Gohr     * @param string $id
295e75a33bfSAndreas Gohr     * @param bool $usetitle - use the first heading as title
296d8ce5486SAndreas Gohr     * @return string
297d8ce5486SAndreas Gohr     */
298e75a33bfSAndreas Gohr    protected function getTitle($id, $usetitle)
299d8ce5486SAndreas Gohr    {
300e306992cSAndreas Gohr        global $conf;
301e306992cSAndreas Gohr
302e75a33bfSAndreas Gohr        if ($usetitle) {
303e306992cSAndreas Gohr            $p = p_get_first_heading($id);
304303e1405SMichael Große            if (!empty($p)) return $p;
305e75a33bfSAndreas Gohr        }
306e306992cSAndreas Gohr
307e306992cSAndreas Gohr        $p = noNS($id);
308d8ce5486SAndreas Gohr        if ($p == $conf['start'] || !$p) {
309e306992cSAndreas Gohr            $p = noNS(getNS($id));
310d8ce5486SAndreas Gohr            if (!$p) {
311e306992cSAndreas Gohr                return $conf['start'];
312e306992cSAndreas Gohr            }
313e306992cSAndreas Gohr        }
314e306992cSAndreas Gohr        return $p;
315e306992cSAndreas Gohr    }
3161169a1acSAndreas Gohr}
317