10356c661Sgerardnico<?php 20356c661Sgerardnico/** 30356c661Sgerardnico * Plugin minimap : Displays mini-map for namespace 40356c661Sgerardnico * 50356c661Sgerardnico * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 60356c661Sgerardnico * @author Nicolas GERARD 70356c661Sgerardnico */ 80356c661Sgerardnico 904fd306cSNickeauuse ComboStrap\ExceptionCompile; 1004fd306cSNickeauuse ComboStrap\SnippetSystem; 1104fd306cSNickeauuse ComboStrap\LinkMarkup; 120356c661Sgerardnicouse ComboStrap\PluginUtility; 130356c661Sgerardnico 1404fd306cSNickeau 150356c661Sgerardnicoclass syntax_plugin_combo_minimap extends DokuWiki_Syntax_Plugin 160356c661Sgerardnico{ 170356c661Sgerardnico 180356c661Sgerardnico const MINIMAP_TAG_NAME = 'minimap'; 190356c661Sgerardnico const INCLUDE_DIRECTORY_PARAMETERS = 'includedirectory'; 200356c661Sgerardnico const SHOW_HEADER = 'showheader'; 210356c661Sgerardnico const NAMESPACE_KEY_ATT = 'namespace'; 220356c661Sgerardnico 230356c661Sgerardnico 240356c661Sgerardnico function connectTo($aMode) 250356c661Sgerardnico { 260356c661Sgerardnico $pattern = '<' . self::MINIMAP_TAG_NAME . '[^>]*>'; 279337a630SNickeau $this->Lexer->addSpecialPattern($pattern, $aMode, PluginUtility::getModeFromTag($this->getPluginComponent())); 280356c661Sgerardnico } 290356c661Sgerardnico 300356c661Sgerardnico function getSort() 310356c661Sgerardnico { 320356c661Sgerardnico /** 330356c661Sgerardnico * One less than the old one 340356c661Sgerardnico */ 350356c661Sgerardnico return 149; 360356c661Sgerardnico } 370356c661Sgerardnico 380356c661Sgerardnico /** 390356c661Sgerardnico * No p element please 400356c661Sgerardnico * @return string 410356c661Sgerardnico */ 420356c661Sgerardnico function getPType() 430356c661Sgerardnico { 440356c661Sgerardnico return 'block'; 450356c661Sgerardnico } 460356c661Sgerardnico 470356c661Sgerardnico function getType() 480356c661Sgerardnico { 490356c661Sgerardnico // The spelling is wrong but this is a correct value 500356c661Sgerardnico // https://www.dokuwiki.org/devel:syntax_plugins#syntax_types 510356c661Sgerardnico return 'substition'; 520356c661Sgerardnico } 530356c661Sgerardnico 540356c661Sgerardnico /** 550356c661Sgerardnico * 560356c661Sgerardnico * The handle function goal is to parse the matched syntax through the pattern function 570356c661Sgerardnico * and to return the result for use in the renderer 580356c661Sgerardnico * This result is always cached until the page is modified. 590356c661Sgerardnico * @param string $match 600356c661Sgerardnico * @param int $state 610356c661Sgerardnico * @param int $pos 620356c661Sgerardnico * @param Doku_Handler $handler 630356c661Sgerardnico * @return array|bool 640356c661Sgerardnico * @see DokuWiki_Syntax_Plugin::handle() 650356c661Sgerardnico * 660356c661Sgerardnico */ 670356c661Sgerardnico function handle($match, $state, $pos, Doku_Handler $handler) 680356c661Sgerardnico { 690356c661Sgerardnico 700356c661Sgerardnico switch ($state) { 710356c661Sgerardnico 720356c661Sgerardnico // As there is only one call to connect to in order to a add a pattern, 730356c661Sgerardnico // there is only one state entering the function 740356c661Sgerardnico // but I leave it for better understanding of the process flow 750356c661Sgerardnico case DOKU_LEXER_SPECIAL : 760356c661Sgerardnico 770356c661Sgerardnico // Parse the parameters 780356c661Sgerardnico $match = substr($match, 8, -1); //9 = strlen("<minimap") 790356c661Sgerardnico 800356c661Sgerardnico // Init 810356c661Sgerardnico $parameters = array(); 820356c661Sgerardnico $parameters['substr'] = 1; 830356c661Sgerardnico $parameters[self::INCLUDE_DIRECTORY_PARAMETERS] = $this->getConf(self::INCLUDE_DIRECTORY_PARAMETERS); 840356c661Sgerardnico $parameters[self::SHOW_HEADER] = $this->getConf(self::SHOW_HEADER); 850356c661Sgerardnico 860356c661Sgerardnico 870356c661Sgerardnico // /i not case sensitive 8804fd306cSNickeau $attributePattern = "\\s*(\w+)\\s*=\\s*['\"]{1}([^`\"]*)['\"]{1}\\s*"; 890356c661Sgerardnico $result = preg_match_all('/' . $attributePattern . '/i', $match, $matches); 900356c661Sgerardnico if ($result != 0) { 910356c661Sgerardnico foreach ($matches[1] as $key => $parameterKey) { 920356c661Sgerardnico $parameter = strtolower($parameterKey); 930356c661Sgerardnico $value = $matches[2][$key]; 940356c661Sgerardnico if (in_array($parameter, [self::SHOW_HEADER, self::INCLUDE_DIRECTORY_PARAMETERS])) { 950356c661Sgerardnico $value = filter_var($value, FILTER_VALIDATE_BOOLEAN); 960356c661Sgerardnico } 970356c661Sgerardnico $parameters[$parameter] = $value; 980356c661Sgerardnico } 990356c661Sgerardnico } 1000356c661Sgerardnico // Cache the values 10132b85071SNickeau return array( 10232b85071SNickeau PluginUtility::STATE => $state, 10332b85071SNickeau PluginUtility::ATTRIBUTES => $parameters 10432b85071SNickeau ); 1050356c661Sgerardnico 1060356c661Sgerardnico } 1070356c661Sgerardnico 1080356c661Sgerardnico return false; 1090356c661Sgerardnico } 1100356c661Sgerardnico 1110356c661Sgerardnico 1120356c661Sgerardnico function render($mode, Doku_Renderer $renderer, $data) 1130356c661Sgerardnico { 1140356c661Sgerardnico 1150356c661Sgerardnico // The $data variable comes from the handle() function 1160356c661Sgerardnico // 1170356c661Sgerardnico // $mode = 'xhtml' means that we output html 1180356c661Sgerardnico // There is other mode such as metadata where you can output data for the headers (Not 100% sure) 1190356c661Sgerardnico if ($mode == 'xhtml') { 1200356c661Sgerardnico 1210356c661Sgerardnico /** @var Doku_Renderer_xhtml $renderer */ 1220356c661Sgerardnico 12332b85071SNickeau 12432b85071SNickeau $state = $data[PluginUtility::STATE]; 1250356c661Sgerardnico 1260356c661Sgerardnico // As there is only one call to connect to in order to a add a pattern, 1270356c661Sgerardnico // there is only one state entering the function 1280356c661Sgerardnico // but I leave it for better understanding of the process flow 1290356c661Sgerardnico switch ($state) { 1300356c661Sgerardnico 1310356c661Sgerardnico case DOKU_LEXER_SPECIAL : 1320356c661Sgerardnico 1335f891b7eSNickeau 13404fd306cSNickeau PluginUtility::getSnippetManager()->attachCssInternalStyleSheet(self::MINIMAP_TAG_NAME); 1355f891b7eSNickeau 1360356c661Sgerardnico 1370356c661Sgerardnico global $ID; 1380356c661Sgerardnico global $INFO; 1390356c661Sgerardnico $callingId = $ID; 1400356c661Sgerardnico // If mini-map is in a sidebar, we don't want the ID of the sidebar 1410356c661Sgerardnico // but the ID of the page. 1420356c661Sgerardnico if ($INFO != null) { 1430356c661Sgerardnico $callingId = $INFO['id']; 1440356c661Sgerardnico } 1450356c661Sgerardnico 14632b85071SNickeau $attributes = $data[PluginUtility::ATTRIBUTES]; 1470356c661Sgerardnico $nameSpacePath = getNS($callingId); // The complete path to the directory 14832b85071SNickeau if (array_key_exists(self::NAMESPACE_KEY_ATT, $attributes)) { 14932b85071SNickeau $nameSpacePath = $attributes[self::NAMESPACE_KEY_ATT]; 1500356c661Sgerardnico } 1510356c661Sgerardnico $currentNameSpace = curNS($callingId); // The name of the container directory 15232b85071SNickeau $includeDirectory = $attributes[self::INCLUDE_DIRECTORY_PARAMETERS]; 1530356c661Sgerardnico $pagesOfNamespace = $this->getNamespaceChildren($nameSpacePath, $sort = 'natural', $listdirs = $includeDirectory); 1540356c661Sgerardnico 1550356c661Sgerardnico // Set the two possible home page for the namespace ie: 1560356c661Sgerardnico // - the name of the containing map ($homePageWithContainingMapName) 1570356c661Sgerardnico // - the start conf parameters ($homePageWithStartConf) 1580356c661Sgerardnico global $conf; 1590356c661Sgerardnico $parts = explode(':', $nameSpacePath); 1600356c661Sgerardnico $lastContainingNameSpace = $parts[count($parts) - 1]; 1610356c661Sgerardnico $homePageWithContainingMapName = $nameSpacePath . ':' . $lastContainingNameSpace; 1620356c661Sgerardnico $startConf = $conf['start']; 1630356c661Sgerardnico $homePageWithStartConf = $nameSpacePath . ':' . $startConf; 1640356c661Sgerardnico 1650356c661Sgerardnico // Build the list of page 1660356c661Sgerardnico $miniMapList = '<ul class="list-group">'; 1670356c661Sgerardnico $pageNum = 0; 1680356c661Sgerardnico $startPageFound = false; 1690356c661Sgerardnico $homePageFound = false; 1700356c661Sgerardnico //$pagesCount = count($pagesOfNamespace); // number of pages in the namespace 1710356c661Sgerardnico foreach ($pagesOfNamespace as $pageArray) { 1720356c661Sgerardnico 1730356c661Sgerardnico // The title of the page 1740356c661Sgerardnico $title = ''; 1750356c661Sgerardnico 1760356c661Sgerardnico // If it's a directory 1770356c661Sgerardnico if ($pageArray['type'] == "d") { 1780356c661Sgerardnico 1790356c661Sgerardnico $pageId = $this->getNamespaceStartId($pageArray['id']); 1800356c661Sgerardnico 1810356c661Sgerardnico } else { 1820356c661Sgerardnico 1830356c661Sgerardnico $pageNum++; 1840356c661Sgerardnico $pageId = $pageArray['id']; 1850356c661Sgerardnico 1860356c661Sgerardnico } 18704fd306cSNickeau $markupRef = new LinkMarkup($pageId); 1884cadd4f8SNickeau 1890356c661Sgerardnico 1900356c661Sgerardnico /** 1914cadd4f8SNickeau * Label 1920356c661Sgerardnico */ 19304fd306cSNickeau $label = $markupRef->getDefaultLabel(); 1940356c661Sgerardnico // Suppress the parts in the name with the regexp defines in the 'suppress' params 195*70bbd7f1Sgerardnico if ($attributes['suppress'] ?? null) { 19632b85071SNickeau $substrPattern = '/' . $attributes['suppress'] . '/i'; 1970356c661Sgerardnico $replacement = ''; 1984cadd4f8SNickeau $label = preg_replace($substrPattern, $replacement, $label); 1994cadd4f8SNickeau } 2004cadd4f8SNickeau // If debug mode 201*70bbd7f1Sgerardnico if ($attributes['debug'] ?? null) { 2024cadd4f8SNickeau $label .= ' (' . $pageId . '|' . $pageNum . ')'; 2030356c661Sgerardnico } 2040356c661Sgerardnico 2054cadd4f8SNickeau /** 2064cadd4f8SNickeau * Link attributes 2074cadd4f8SNickeau */ 2084cadd4f8SNickeau try { 2094cadd4f8SNickeau $linkAttribute = $markupRef->toAttributes(); 21004fd306cSNickeau } catch (ExceptionCompile $e) { 2114cadd4f8SNickeau $miniMapList .= \ComboStrap\LogUtility::wrapInRedForHtml("Error. {$e->getMessage()}"); 2124cadd4f8SNickeau continue; 2134cadd4f8SNickeau } 2140356c661Sgerardnico // See in which page we are 2150356c661Sgerardnico // The style will then change 2160356c661Sgerardnico $active = ''; 2170356c661Sgerardnico if ($callingId == $pageId) { 21804fd306cSNickeau $linkAttribute->addBooleanOutputAttributeValue('active'); 2190356c661Sgerardnico } 2200356c661Sgerardnico 2210356c661Sgerardnico // Not all page are printed 2220356c661Sgerardnico // sidebar are not for instance 2230356c661Sgerardnico 2240356c661Sgerardnico // Are we in the root ? 2250356c661Sgerardnico if ($pageArray['ns']) { 2260356c661Sgerardnico $nameSpacePathPrefix = $pageArray['ns'] . ':'; 2270356c661Sgerardnico } else { 2280356c661Sgerardnico $nameSpacePathPrefix = ''; 2290356c661Sgerardnico } 2300356c661Sgerardnico $print = true; 2310356c661Sgerardnico if ($pageArray['id'] == $nameSpacePathPrefix . $currentNameSpace) { 2320356c661Sgerardnico // If the start page exists, the page with the same name 2330356c661Sgerardnico // than the namespace must be shown 2340356c661Sgerardnico if (page_exists($nameSpacePathPrefix . $startConf)) { 2350356c661Sgerardnico $print = true; 2360356c661Sgerardnico } else { 2370356c661Sgerardnico $print = false; 2380356c661Sgerardnico } 2390356c661Sgerardnico $homePageFound = true; 2400356c661Sgerardnico } else if ($pageArray['id'] == $nameSpacePathPrefix . $startConf) { 2410356c661Sgerardnico $print = false; 2420356c661Sgerardnico $startPageFound = true; 2430356c661Sgerardnico } else if ($pageArray['id'] == $nameSpacePathPrefix . $conf['sidebar']) { 2440356c661Sgerardnico $pageNum -= 1; 2450356c661Sgerardnico $print = false; 2460356c661Sgerardnico }; 2470356c661Sgerardnico 2480356c661Sgerardnico 2490356c661Sgerardnico // If the page must be printed, build the link 2500356c661Sgerardnico if ($print) { 2510356c661Sgerardnico 2520356c661Sgerardnico // Open the item tag 2530356c661Sgerardnico $miniMapList .= "<li class=\"list-group-item " . $active . "\">"; 2540356c661Sgerardnico 2550356c661Sgerardnico // Add a glyphicon if it's a directory 2560356c661Sgerardnico if ($pageArray['type'] == "d") { 2575f891b7eSNickeau $miniMapList .= "<span class=\"nicon_folder_open\" aria-hidden=\"true\"></span> "; 2580356c661Sgerardnico } 2590356c661Sgerardnico 2604cadd4f8SNickeau $miniMapList .= $linkAttribute->toHtmlEnterTag("a"); 2614cadd4f8SNickeau $miniMapList .= $label; 2624cadd4f8SNickeau $miniMapList .= "</a>"; 2630356c661Sgerardnico 2640356c661Sgerardnico 2650356c661Sgerardnico // Close the item 2660356c661Sgerardnico $miniMapList .= "</li>"; 2670356c661Sgerardnico 2680356c661Sgerardnico } 2690356c661Sgerardnico 2700356c661Sgerardnico } 2710356c661Sgerardnico $miniMapList .= '</ul>'; // End list-group 2720356c661Sgerardnico 2730356c661Sgerardnico 2740356c661Sgerardnico // Build the panel header 2750356c661Sgerardnico $miniMapHeader = ""; 2760356c661Sgerardnico $startId = ""; 2770356c661Sgerardnico if ($startPageFound) { 2780356c661Sgerardnico $startId = $homePageWithStartConf; 2790356c661Sgerardnico } else { 2800356c661Sgerardnico if ($homePageFound) { 2810356c661Sgerardnico $startId = $homePageWithContainingMapName; 2820356c661Sgerardnico } 2830356c661Sgerardnico } 2840356c661Sgerardnico 2850356c661Sgerardnico $panelHeaderContent = ""; 2860356c661Sgerardnico if ($startId == "") { 28732b85071SNickeau if ($attributes[self::SHOW_HEADER] == true) { 2880356c661Sgerardnico $panelHeaderContent = 'No Home Page found'; 2890356c661Sgerardnico } 2900356c661Sgerardnico } else { 29104fd306cSNickeau $startLink = new LinkMarkup($startId); 2924cadd4f8SNickeau try { 2934cadd4f8SNickeau $panelHeaderContent = $startLink->toAttributes()->toHtmlEnterTag("a"); 29404fd306cSNickeau $panelHeaderContent .= $startLink->getDefaultLabel(); 2954cadd4f8SNickeau $panelHeaderContent .= "</a>"; 29604fd306cSNickeau } catch (ExceptionCompile $e) { 2974cadd4f8SNickeau $panelHeaderContent = "Error: {$e->getMessage()}"; 2984cadd4f8SNickeau } 2990356c661Sgerardnico // We are not counting the header page 3000356c661Sgerardnico $pageNum--; 3010356c661Sgerardnico } 3020356c661Sgerardnico 3030356c661Sgerardnico if ($panelHeaderContent != "") { 3040356c661Sgerardnico $miniMapHeader .= '<div class="panel-heading">' . $panelHeaderContent . ' <span class="label label-primary">' . $pageNum . ' pages</span></div>'; 3050356c661Sgerardnico } 3060356c661Sgerardnico 307*70bbd7f1Sgerardnico if ($attributes['debug'] ?? null) { 3080356c661Sgerardnico $miniMapHeader .= '<div class="panel-body">' . 3090356c661Sgerardnico '<B>Debug Information:</B><BR>' . 3100356c661Sgerardnico 'CallingId: (' . $callingId . ')<BR>' . 311*70bbd7f1Sgerardnico 'Suppress Option: (' . ($attributes['suppress'] ?? '') . ')<BR>' . 3120356c661Sgerardnico '</div>'; 3130356c661Sgerardnico } 3140356c661Sgerardnico 3150356c661Sgerardnico // Header + list 3160356c661Sgerardnico $renderer->doc .= '<div id="minimap__plugin"><div class="panel panel-default">' 3170356c661Sgerardnico . $miniMapHeader 3180356c661Sgerardnico . $miniMapList 3190356c661Sgerardnico . '</div></div>'; 3200356c661Sgerardnico break; 3210356c661Sgerardnico } 3220356c661Sgerardnico 3230356c661Sgerardnico return true; 3240356c661Sgerardnico } 3250356c661Sgerardnico return false; 3260356c661Sgerardnico 3270356c661Sgerardnico } 3280356c661Sgerardnico 3290356c661Sgerardnico /** 3300356c661Sgerardnico * Return all pages and/of sub-namespaces (subdirectory) of a namespace (ie directory) 3310356c661Sgerardnico * Adapted from feed.php 3320356c661Sgerardnico * 33304fd306cSNickeau * @param string $namespace The container of the pages 3340356c661Sgerardnico * @param string $sort 'natural' to use natural order sorting (default); 'date' to sort by filemtime 3350356c661Sgerardnico * @param $listdirs - Add the directory to the list of files 3360356c661Sgerardnico * @return array An array of the pages for the namespace 3370356c661Sgerardnico */ 3380356c661Sgerardnico function getNamespaceChildren($namespace, $sort = 'natural', $listdirs = false) 3390356c661Sgerardnico { 3400356c661Sgerardnico require_once(DOKU_INC . 'inc/search.php'); 3410356c661Sgerardnico global $conf; 3420356c661Sgerardnico 3430356c661Sgerardnico $ns = ':' . cleanID($namespace); 3440356c661Sgerardnico // ns as a path 3450356c661Sgerardnico $ns = utf8_encodeFN(str_replace(':', '/', $ns)); 3460356c661Sgerardnico 3470356c661Sgerardnico $data = array(); 3480356c661Sgerardnico 3490356c661Sgerardnico // Options of the callback function search_universal 3500356c661Sgerardnico // in the search.php file 3510356c661Sgerardnico $search_opts = array( 3520356c661Sgerardnico 'depth' => 1, 3530356c661Sgerardnico 'pagesonly' => true, 3540356c661Sgerardnico 'listfiles' => true, 3550356c661Sgerardnico 'listdirs' => $listdirs, 3560356c661Sgerardnico 'firsthead' => true 3570356c661Sgerardnico ); 3580356c661Sgerardnico // search_universal is a function in inc/search.php that accepts the $search_opts parameters 3590356c661Sgerardnico search($data, $conf['datadir'], 'search_universal', $search_opts, $ns, $lvl = 1, $sort); 3600356c661Sgerardnico 3610356c661Sgerardnico return $data; 3620356c661Sgerardnico } 3630356c661Sgerardnico 3640356c661Sgerardnico /** 3650356c661Sgerardnico * Return the id of the start page of a namespace 3660356c661Sgerardnico * 36704fd306cSNickeau * @param string $id an id of a namespace (directory) 3680356c661Sgerardnico * @return string the id of the home page 3690356c661Sgerardnico */ 3700356c661Sgerardnico function getNamespaceStartId($id) 3710356c661Sgerardnico { 3720356c661Sgerardnico 3730356c661Sgerardnico global $conf; 3740356c661Sgerardnico 3750356c661Sgerardnico $id = $id . ":"; 3760356c661Sgerardnico 3770356c661Sgerardnico if (page_exists($id . $conf['start'])) { 3780356c661Sgerardnico // start page inside namespace 3790356c661Sgerardnico $homePageId = $id . $conf['start']; 3800356c661Sgerardnico } elseif (page_exists($id . noNS(cleanID($id)))) { 3810356c661Sgerardnico // page named like the NS inside the NS 3820356c661Sgerardnico $homePageId = $id . noNS(cleanID($id)); 3830356c661Sgerardnico } elseif (page_exists($id)) { 3840356c661Sgerardnico // page like namespace exists 3850356c661Sgerardnico $homePageId = substr($id, 0, -1); 3860356c661Sgerardnico } else { 3870356c661Sgerardnico // fall back to default 3880356c661Sgerardnico $homePageId = $id . $conf['start']; 3890356c661Sgerardnico } 3900356c661Sgerardnico return $homePageId; 3910356c661Sgerardnico } 3920356c661Sgerardnico 3930356c661Sgerardnico /** 3940356c661Sgerardnico * @param $get_called_class 3950356c661Sgerardnico * @return string 3960356c661Sgerardnico */ 3970356c661Sgerardnico public static function getTagName($get_called_class) 3980356c661Sgerardnico { 3990356c661Sgerardnico list(/* $t */, /* $p */, $c) = explode('_', $get_called_class, 3); 4000356c661Sgerardnico return (isset($c) ? $c : ''); 4010356c661Sgerardnico } 4020356c661Sgerardnico 4030356c661Sgerardnico /** 4040356c661Sgerardnico * @return string - the tag 4050356c661Sgerardnico */ 4060356c661Sgerardnico public static function getTag() 4070356c661Sgerardnico { 4080356c661Sgerardnico return self::getTagName(get_called_class()); 4090356c661Sgerardnico } 4100356c661Sgerardnico 4110356c661Sgerardnico 4120356c661Sgerardnico} 413