xref: /plugin/combo/syntax/minimap.php (revision 5f891b7e09648e05e78f5882f3fdde1e9df9b0f1)
1<?php
2/**
3 * Plugin minimap : Displays mini-map for namespace
4 *
5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author  Nicolas GERARD
7 */
8
9use ComboStrap\SnippetManager;
10use ComboStrap\LinkUtility;
11use ComboStrap\PluginUtility;
12
13if (!defined('DOKU_INC')) die();
14
15
16class syntax_plugin_combo_minimap extends DokuWiki_Syntax_Plugin
17{
18
19    const MINIMAP_TAG_NAME = 'minimap';
20    const INCLUDE_DIRECTORY_PARAMETERS = 'includedirectory';
21    const SHOW_HEADER = 'showheader';
22    const NAMESPACE_KEY_ATT = 'namespace';
23
24
25
26    function connectTo($aMode)
27    {
28        $pattern = '<' . self::MINIMAP_TAG_NAME . '[^>]*>';
29        $this->Lexer->addSpecialPattern($pattern, $aMode, PluginUtility::getModeForComponent($this->getPluginComponent()));
30    }
31
32    function getSort()
33    {
34        /**
35         * One less than the old one
36         */
37        return 149;
38    }
39
40    /**
41     * No p element please
42     * @return string
43     */
44    function getPType()
45    {
46        return 'block';
47    }
48
49    function getType()
50    {
51        // The spelling is wrong but this is a correct value
52        // https://www.dokuwiki.org/devel:syntax_plugins#syntax_types
53        return 'substition';
54    }
55
56    /**
57     *
58     * The handle function goal is to parse the matched syntax through the pattern function
59     * and to return the result for use in the renderer
60     * This result is always cached until the page is modified.
61     * @param string $match
62     * @param int $state
63     * @param int $pos
64     * @param Doku_Handler $handler
65     * @return array|bool
66     * @see DokuWiki_Syntax_Plugin::handle()
67     *
68     */
69    function handle($match, $state, $pos, Doku_Handler $handler)
70    {
71
72        switch ($state) {
73
74            // As there is only one call to connect to in order to a add a pattern,
75            // there is only one state entering the function
76            // but I leave it for better understanding of the process flow
77            case DOKU_LEXER_SPECIAL :
78
79                // Parse the parameters
80                $match = substr($match, 8, -1); //9 = strlen("<minimap")
81
82                // Init
83                $parameters = array();
84                $parameters['substr'] = 1;
85                $parameters[self::INCLUDE_DIRECTORY_PARAMETERS] = $this->getConf(self::INCLUDE_DIRECTORY_PARAMETERS);
86                $parameters[self::SHOW_HEADER] = $this->getConf(self::SHOW_HEADER);
87
88
89                // /i not case sensitive
90                $attributePattern = "\\s*(\w+)\\s*=\\s*[\'\"]{1}([^\`\"]*)[\'\"]{1}\\s*";
91                $result = preg_match_all('/' . $attributePattern . '/i', $match, $matches);
92                if ($result != 0) {
93                    foreach ($matches[1] as $key => $parameterKey) {
94                        $parameter = strtolower($parameterKey);
95                        $value = $matches[2][$key];
96                        if (in_array($parameter, [self::SHOW_HEADER, self::INCLUDE_DIRECTORY_PARAMETERS])) {
97                            $value = filter_var($value, FILTER_VALIDATE_BOOLEAN);
98                        }
99                        $parameters[$parameter] = $value;
100                    }
101                }
102                // Cache the values
103                return array($state, $parameters);
104
105        }
106
107        return false;
108    }
109
110
111    function render($mode, Doku_Renderer $renderer, $data)
112    {
113
114        // The $data variable comes from the handle() function
115        //
116        // $mode = 'xhtml' means that we output html
117        // There is other mode such as metadata where you can output data for the headers (Not 100% sure)
118        if ($mode == 'xhtml') {
119
120            /** @var Doku_Renderer_xhtml $renderer */
121
122            // Unfold the $data array in two separates variables
123            list($state, $parameters) = $data;
124
125            // As there is only one call to connect to in order to a add a pattern,
126            // there is only one state entering the function
127            // but I leave it for better understanding of the process flow
128            switch ($state) {
129
130                case DOKU_LEXER_SPECIAL :
131
132
133                    PluginUtility::getSnippetManager()->addCssSnippetOnlyOnce(self::MINIMAP_TAG_NAME);
134
135
136                    global $ID;
137                    global $INFO;
138                    $callingId = $ID;
139                    // If mini-map is in a sidebar, we don't want the ID of the sidebar
140                    // but the ID of the page.
141                    if ($INFO != null) {
142                        $callingId = $INFO['id'];
143                    }
144
145                    $nameSpacePath = getNS($callingId); // The complete path to the directory
146                    if (array_key_exists(self::NAMESPACE_KEY_ATT, $parameters)) {
147                        $nameSpacePath = $parameters[self::NAMESPACE_KEY_ATT];
148                    }
149                    $currentNameSpace = curNS($callingId); // The name of the container directory
150                    $includeDirectory = $parameters[self::INCLUDE_DIRECTORY_PARAMETERS];
151                    $pagesOfNamespace = $this->getNamespaceChildren($nameSpacePath, $sort = 'natural', $listdirs = $includeDirectory);
152
153                    // Set the two possible home page for the namespace ie:
154                    //   - the name of the containing map ($homePageWithContainingMapName)
155                    //   - the start conf parameters ($homePageWithStartConf)
156                    global $conf;
157                    $parts = explode(':', $nameSpacePath);
158                    $lastContainingNameSpace = $parts[count($parts) - 1];
159                    $homePageWithContainingMapName = $nameSpacePath . ':' . $lastContainingNameSpace;
160                    $startConf = $conf['start'];
161                    $homePageWithStartConf = $nameSpacePath . ':' . $startConf;
162
163                    // Build the list of page
164                    $miniMapList = '<ul class="list-group">';
165                    $pageNum = 0;
166                    $startPageFound = false;
167                    $homePageFound = false;
168                    //$pagesCount = count($pagesOfNamespace); // number of pages in the namespace
169                    foreach ($pagesOfNamespace as $pageArray) {
170
171                        // The title of the page
172                        $title = '';
173
174                        // If it's a directory
175                        if ($pageArray['type'] == "d") {
176
177                            $pageId = $this->getNamespaceStartId($pageArray['id']);
178
179                        } else {
180
181                            $pageNum++;
182                            $pageId = $pageArray['id'];
183
184                        }
185                        $link = new LinkUtility($pageId);
186
187
188                        /**
189                         * Set special name and title
190                         */
191                        // If debug mode
192                        if ($parameters['debug']) {
193                            $link->setTitle($link->getTitle().' (' . $pageId . ')');
194                        }
195
196                        // Add the page number in the URL title
197                        $link->setTitle($link->getTitle() .' (' . $pageNum . ')');
198
199                        // Suppress the parts in the name with the regexp defines in the 'suppress' params
200                        if ($parameters['suppress']) {
201                            $substrPattern = '/' . $parameters['suppress'] . '/i';
202                            $replacement = '';
203                            $name = preg_replace($substrPattern, $replacement, $link->getName());
204                            $link->setName($name);
205                        }
206
207                        // See in which page we are
208                        // The style will then change
209                        $active = '';
210                        if ($callingId == $pageId) {
211                            $active = 'active';
212                        }
213
214                        // Not all page are printed
215                        // sidebar are not for instance
216
217                        // Are we in the root ?
218                        if ($pageArray['ns']) {
219                            $nameSpacePathPrefix = $pageArray['ns'] . ':';
220                        } else {
221                            $nameSpacePathPrefix = '';
222                        }
223                        $print = true;
224                        if ($pageArray['id'] == $nameSpacePathPrefix . $currentNameSpace) {
225                            // If the start page exists, the page with the same name
226                            // than the namespace must be shown
227                            if (page_exists($nameSpacePathPrefix . $startConf)) {
228                                $print = true;
229                            } else {
230                                $print = false;
231                            }
232                            $homePageFound = true;
233                        } else if ($pageArray['id'] == $nameSpacePathPrefix . $startConf) {
234                            $print = false;
235                            $startPageFound = true;
236                        } else if ($pageArray['id'] == $nameSpacePathPrefix . $conf['sidebar']) {
237                            $pageNum -= 1;
238                            $print = false;
239                        };
240
241
242                        // If the page must be printed, build the link
243                        if ($print) {
244
245                            // Open the item tag
246                            $miniMapList .= "<li class=\"list-group-item " . $active . "\">";
247
248                            // Add a glyphicon if it's a directory
249                            if ($pageArray['type'] == "d") {
250                                $miniMapList .= "<span class=\"nicon_folder_open\" aria-hidden=\"true\"></span> ";
251                            }
252
253                            $miniMapList .= $link->renderOpenTag($renderer);
254                            $miniMapList .= $link->getName();
255                            $miniMapList .= $link->renderClosingTag();
256
257
258                            // Close the item
259                            $miniMapList .= "</li>";
260
261                        }
262
263                    }
264                    $miniMapList .= '</ul>'; // End list-group
265
266
267                    // Build the panel header
268                    $miniMapHeader = "";
269                    $startId = "";
270                    if ($startPageFound) {
271                        $startId = $homePageWithStartConf;
272                    } else {
273                        if ($homePageFound) {
274                            $startId = $homePageWithContainingMapName;
275                        }
276                    }
277
278                    $panelHeaderContent = "";
279                    if ($startId == "") {
280                        if ($parameters[self::SHOW_HEADER] == true) {
281                            $panelHeaderContent = 'No Home Page found';
282                        }
283                    } else {
284                        $startLink = new LinkUtility($startId);
285                        $panelHeaderContent = $startLink->renderOpenTag($renderer);
286                        $panelHeaderContent .= $startLink->getName();
287                        $panelHeaderContent .= $startLink->renderClosingTag();
288                        // We are not counting the header page
289                        $pageNum--;
290                    }
291
292                    if ($panelHeaderContent != "") {
293                        $miniMapHeader .= '<div class="panel-heading">' . $panelHeaderContent . '  <span class="label label-primary">' . $pageNum . ' pages</span></div>';
294                    }
295
296                    if ($parameters['debug']) {
297                        $miniMapHeader .= '<div class="panel-body">' .
298                            '<B>Debug Information:</B><BR>' .
299                            'CallingId: (' . $callingId . ')<BR>' .
300                            'Suppress Option: (' . $parameters['suppress'] . ')<BR>' .
301                            '</div>';
302                    }
303
304                    // Header + list
305                    $renderer->doc .= '<div id="minimap__plugin"><div class="panel panel-default">'
306                        . $miniMapHeader
307                        . $miniMapList
308                        . '</div></div>';
309                    break;
310            }
311
312            return true;
313        }
314        return false;
315
316    }
317
318    /**
319     * Return all pages and/of sub-namespaces (subdirectory) of a namespace (ie directory)
320     * Adapted from feed.php
321     *
322     * @param $namespace The container of the pages
323     * @param string $sort 'natural' to use natural order sorting (default); 'date' to sort by filemtime
324     * @param $listdirs - Add the directory to the list of files
325     * @return array An array of the pages for the namespace
326     */
327    function getNamespaceChildren($namespace, $sort = 'natural', $listdirs = false)
328    {
329        require_once(DOKU_INC . 'inc/search.php');
330        global $conf;
331
332        $ns = ':' . cleanID($namespace);
333        // ns as a path
334        $ns = utf8_encodeFN(str_replace(':', '/', $ns));
335
336        $data = array();
337
338        // Options of the callback function search_universal
339        // in the search.php file
340        $search_opts = array(
341            'depth' => 1,
342            'pagesonly' => true,
343            'listfiles' => true,
344            'listdirs' => $listdirs,
345            'firsthead' => true
346        );
347        // search_universal is a function in inc/search.php that accepts the $search_opts parameters
348        search($data, $conf['datadir'], 'search_universal', $search_opts, $ns, $lvl = 1, $sort);
349
350        return $data;
351    }
352
353    /**
354     * Return the id of the start page of a namespace
355     *
356     * @param $id an id of a namespace (directory)
357     * @return string the id of the home page
358     */
359    function getNamespaceStartId($id)
360    {
361
362        global $conf;
363
364        $id = $id . ":";
365
366        if (page_exists($id . $conf['start'])) {
367            // start page inside namespace
368            $homePageId = $id . $conf['start'];
369        } elseif (page_exists($id . noNS(cleanID($id)))) {
370            // page named like the NS inside the NS
371            $homePageId = $id . noNS(cleanID($id));
372        } elseif (page_exists($id)) {
373            // page like namespace exists
374            $homePageId = substr($id, 0, -1);
375        } else {
376            // fall back to default
377            $homePageId = $id . $conf['start'];
378        }
379        return $homePageId;
380    }
381
382    /**
383     * @param $get_called_class
384     * @return string
385     */
386    public static function getTagName($get_called_class)
387    {
388        list(/* $t */, /* $p */, $c) = explode('_', $get_called_class, 3);
389        return (isset($c) ? $c : '');
390    }
391
392    /**
393     * @return string - the tag
394     */
395    public static function getTag()
396    {
397        return self::getTagName(get_called_class());
398    }
399
400
401}
402