1<?php
2/**
3 * DokuWiki Plugin DocNavigation (Syntax Component)
4 *
5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6 * @author  Gerrit Uitslag <klapinklapin@gmail.com>
7 */
8
9use dokuwiki\Extension\SyntaxPlugin;
10
11/**
12 * Handles document navigation syntax
13 */
14class syntax_plugin_docnavigation_pagenav extends SyntaxPlugin
15{
16
17    /**
18     * Stores data of navigation per page (for preview)
19     *
20     * @var array with entries:
21     *   '<pageid>' => [
22     *      'previous' => [
23     *          'link' => string,
24     *          'title'  => null|string,
25     *          'rawlink' => string
26     *      ],
27     *      'toc' => [...],
28     *      'next' => [...]
29     *   ]
30     */
31    public array $data = [];
32
33    /**
34     * Syntax Type
35     *
36     * Needs to return one of the mode types defined in $PARSER_MODES in parser.php
37     *
38     * @return string
39     */
40    public function getType()
41    {
42        return 'substition';
43    }
44
45    /**
46     * Paragraph Type
47     *
48     * Defines how this syntax is handled regarding paragraphs. This is important
49     * for correct XHTML nesting. Should return one of the following:
50     *
51     * 'normal' - The plugin can be used inside paragraphs
52     * 'block'  - Open paragraphs need to be closed before plugin output
53     * 'stack'  - Special case. Plugin wraps other paragraphs.
54     *
55     * @return string
56     * @see \dokuwiki\Parsing\Handler\Block
57     *
58     */
59    public function getPType()
60    {
61        return 'block';
62    }
63
64    /**
65     * Sort for applying this mode
66     *
67     * @return int
68     */
69    public function getSort()
70    {
71        return 150;
72    }
73
74    /**
75     * @param string $mode
76     */
77    public function connectTo($mode)
78    {
79        $this->Lexer->addSpecialPattern('<-[^\n]*\^[^\n]*\^[^\n]*->', $mode, 'plugin_docnavigation_pagenav');
80        $this->Lexer->addSpecialPattern('<<[^\n]*\^[^\n]*\^[^\n]*>>', $mode, 'plugin_docnavigation_pagenav');
81    }
82
83    /**
84     * Handler to prepare matched data for the rendering process
85     *
86     * Usually you should only need the $match param.
87     *
88     * @param string $match The text matched by the patterns
89     * @param int $state The lexer state for the match
90     * @param int $pos The character position of the matched text
91     * @param Doku_Handler $handler The Doku_Handler object
92     * @return  array Return an array with all data you want to use in render, false don't add an instruction
93     */
94    public function handle($match, $state, $pos, Doku_Handler $handler)
95    {
96        global $conf, $ID;
97
98        // links are: 0=previous, 1=toc, 2=next
99        $linkstrs = explode("^", substr($match, 2, -2), 3);
100        $links = [];
101        foreach ($linkstrs as $index => $linkstr) {
102            // Split title from URL
103            [$link, $title] = array_pad(explode('|', $linkstr, 2), 2, null);
104            if (isset($title) && preg_match('/^\{\{[^}]+}}$/', $title)) {
105                // If the title is an image, convert it to an array containing the image details
106                $title = Doku_Handler_Parse_Media($title);
107            }
108
109            $link = trim($link);
110
111            //look for an existing headpage when toc is empty
112            if ($index == 1 && empty($link)) {
113                $ns = getNS($ID);
114                if (page_exists($ns . ':' . $conf['start'])) {
115                    // start page inside namespace
116                    $link = $ns . ':' . $conf['start'];
117                } elseif (page_exists($ns . ':' . noNS($ns))) {
118                    // page named like the NS inside the NS
119                    $link = $ns . ':' . noNS($ns);
120                } elseif (page_exists($ns)) {
121                    // page like namespace exists
122                    $link = (!getNS($ns) ? ':' : '') . $ns;
123                }
124            }
125            //store original link with special chars and upper cases
126            $rawlink = $link;
127
128            // resolve and clean up the $id
129            // Igor and later
130            if (class_exists('dokuwiki\File\PageResolver')) {
131                $resolver = new dokuwiki\File\PageResolver($ID);
132                $link = $resolver->resolveId($link);
133            } else {
134                // Compatibility with older releases
135                resolve_pageid(getNS($ID), $link, $exists);
136            }
137            //ignore hash
138            [$link,$hash] = array_pad(explode('#', $link, 2), 2, '');
139
140            //previous or next should not point to itself
141            if ($index !== 1 && $link == $ID) {
142                $link = '';
143            }
144
145            $links[] = [
146                'link' => $link,
147                'title' => $title,
148                'rawlink' => $rawlink,
149                'hash' => $hash
150            ];
151        }
152
153        $data = [
154            'previous' => $links[0],
155            'toc' => $links[1],
156            'next' => $links[2]
157        ];
158
159        // store data for preview
160        $this->data[$ID] = $data;
161
162        // return instruction data for renderers
163        return $data;
164    }
165
166    /**
167     * Handles the actual output creation.
168     *
169     * @param string $format output format being rendered
170     * @param Doku_Renderer $renderer the current renderer object
171     * @param array $data data created by handler()
172     * @return  boolean                 rendered correctly? (however, returned value is not used at the moment)
173     */
174    public function render($format, Doku_Renderer $renderer, $data)
175    {
176        if ($format == 'metadata') {
177            /** @var Doku_Renderer_metadata $renderer */
178            $renderer->meta['docnavigation'] = $data;
179
180            foreach ($data as $url) {
181                if ($url) {
182                    if ($url['title'] === null) {
183                        $defaulttitle = $renderer->_simpleTitle($url['rawlink']);
184                        $url['title'] = $renderer->_getLinkTitle(null, $defaulttitle, $url['link']);
185                    }
186                    $renderer->internallink($url['link'], $url['title']);
187                }
188            }
189            return true;
190        }
191
192        return false;
193    }
194
195    /**
196     * Get data for a pageid
197     *
198     * @param string $pageId
199     * @return array|null
200     */
201    public function getPageData(string $pageId): ?array
202    {
203        return $this->data[$pageId] ?? null;
204    }
205}
206