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 9*4cadd4f8SNickeauuse ComboStrap\ExceptionCombo; 105f891b7eSNickeauuse ComboStrap\SnippetManager; 11*4cadd4f8SNickeauuse ComboStrap\MarkupRef; 120356c661Sgerardnicouse ComboStrap\PluginUtility; 130356c661Sgerardnico 140356c661Sgerardnicoif (!defined('DOKU_INC')) die(); 150356c661Sgerardnico 160356c661Sgerardnico 170356c661Sgerardnicoclass syntax_plugin_combo_minimap extends DokuWiki_Syntax_Plugin 180356c661Sgerardnico{ 190356c661Sgerardnico 200356c661Sgerardnico const MINIMAP_TAG_NAME = 'minimap'; 210356c661Sgerardnico const INCLUDE_DIRECTORY_PARAMETERS = 'includedirectory'; 220356c661Sgerardnico const SHOW_HEADER = 'showheader'; 230356c661Sgerardnico const NAMESPACE_KEY_ATT = 'namespace'; 240356c661Sgerardnico 250356c661Sgerardnico 260356c661Sgerardnico function connectTo($aMode) 270356c661Sgerardnico { 280356c661Sgerardnico $pattern = '<' . self::MINIMAP_TAG_NAME . '[^>]*>'; 299337a630SNickeau $this->Lexer->addSpecialPattern($pattern, $aMode, PluginUtility::getModeFromTag($this->getPluginComponent())); 300356c661Sgerardnico } 310356c661Sgerardnico 320356c661Sgerardnico function getSort() 330356c661Sgerardnico { 340356c661Sgerardnico /** 350356c661Sgerardnico * One less than the old one 360356c661Sgerardnico */ 370356c661Sgerardnico return 149; 380356c661Sgerardnico } 390356c661Sgerardnico 400356c661Sgerardnico /** 410356c661Sgerardnico * No p element please 420356c661Sgerardnico * @return string 430356c661Sgerardnico */ 440356c661Sgerardnico function getPType() 450356c661Sgerardnico { 460356c661Sgerardnico return 'block'; 470356c661Sgerardnico } 480356c661Sgerardnico 490356c661Sgerardnico function getType() 500356c661Sgerardnico { 510356c661Sgerardnico // The spelling is wrong but this is a correct value 520356c661Sgerardnico // https://www.dokuwiki.org/devel:syntax_plugins#syntax_types 530356c661Sgerardnico return 'substition'; 540356c661Sgerardnico } 550356c661Sgerardnico 560356c661Sgerardnico /** 570356c661Sgerardnico * 580356c661Sgerardnico * The handle function goal is to parse the matched syntax through the pattern function 590356c661Sgerardnico * and to return the result for use in the renderer 600356c661Sgerardnico * This result is always cached until the page is modified. 610356c661Sgerardnico * @param string $match 620356c661Sgerardnico * @param int $state 630356c661Sgerardnico * @param int $pos 640356c661Sgerardnico * @param Doku_Handler $handler 650356c661Sgerardnico * @return array|bool 660356c661Sgerardnico * @see DokuWiki_Syntax_Plugin::handle() 670356c661Sgerardnico * 680356c661Sgerardnico */ 690356c661Sgerardnico function handle($match, $state, $pos, Doku_Handler $handler) 700356c661Sgerardnico { 710356c661Sgerardnico 720356c661Sgerardnico switch ($state) { 730356c661Sgerardnico 740356c661Sgerardnico // As there is only one call to connect to in order to a add a pattern, 750356c661Sgerardnico // there is only one state entering the function 760356c661Sgerardnico // but I leave it for better understanding of the process flow 770356c661Sgerardnico case DOKU_LEXER_SPECIAL : 780356c661Sgerardnico 790356c661Sgerardnico // Parse the parameters 800356c661Sgerardnico $match = substr($match, 8, -1); //9 = strlen("<minimap") 810356c661Sgerardnico 820356c661Sgerardnico // Init 830356c661Sgerardnico $parameters = array(); 840356c661Sgerardnico $parameters['substr'] = 1; 850356c661Sgerardnico $parameters[self::INCLUDE_DIRECTORY_PARAMETERS] = $this->getConf(self::INCLUDE_DIRECTORY_PARAMETERS); 860356c661Sgerardnico $parameters[self::SHOW_HEADER] = $this->getConf(self::SHOW_HEADER); 870356c661Sgerardnico 880356c661Sgerardnico 890356c661Sgerardnico // /i not case sensitive 900356c661Sgerardnico $attributePattern = "\\s*(\w+)\\s*=\\s*[\'\"]{1}([^\`\"]*)[\'\"]{1}\\s*"; 910356c661Sgerardnico $result = preg_match_all('/' . $attributePattern . '/i', $match, $matches); 920356c661Sgerardnico if ($result != 0) { 930356c661Sgerardnico foreach ($matches[1] as $key => $parameterKey) { 940356c661Sgerardnico $parameter = strtolower($parameterKey); 950356c661Sgerardnico $value = $matches[2][$key]; 960356c661Sgerardnico if (in_array($parameter, [self::SHOW_HEADER, self::INCLUDE_DIRECTORY_PARAMETERS])) { 970356c661Sgerardnico $value = filter_var($value, FILTER_VALIDATE_BOOLEAN); 980356c661Sgerardnico } 990356c661Sgerardnico $parameters[$parameter] = $value; 1000356c661Sgerardnico } 1010356c661Sgerardnico } 1020356c661Sgerardnico // Cache the values 10332b85071SNickeau return array( 10432b85071SNickeau PluginUtility::STATE => $state, 10532b85071SNickeau PluginUtility::ATTRIBUTES => $parameters 10632b85071SNickeau ); 1070356c661Sgerardnico 1080356c661Sgerardnico } 1090356c661Sgerardnico 1100356c661Sgerardnico return false; 1110356c661Sgerardnico } 1120356c661Sgerardnico 1130356c661Sgerardnico 1140356c661Sgerardnico function render($mode, Doku_Renderer $renderer, $data) 1150356c661Sgerardnico { 1160356c661Sgerardnico 1170356c661Sgerardnico // The $data variable comes from the handle() function 1180356c661Sgerardnico // 1190356c661Sgerardnico // $mode = 'xhtml' means that we output html 1200356c661Sgerardnico // There is other mode such as metadata where you can output data for the headers (Not 100% sure) 1210356c661Sgerardnico if ($mode == 'xhtml') { 1220356c661Sgerardnico 1230356c661Sgerardnico /** @var Doku_Renderer_xhtml $renderer */ 1240356c661Sgerardnico 12532b85071SNickeau 12632b85071SNickeau $state = $data[PluginUtility::STATE]; 1270356c661Sgerardnico 1280356c661Sgerardnico // As there is only one call to connect to in order to a add a pattern, 1290356c661Sgerardnico // there is only one state entering the function 1300356c661Sgerardnico // but I leave it for better understanding of the process flow 1310356c661Sgerardnico switch ($state) { 1320356c661Sgerardnico 1330356c661Sgerardnico case DOKU_LEXER_SPECIAL : 1340356c661Sgerardnico 1355f891b7eSNickeau 136*4cadd4f8SNickeau PluginUtility::getSnippetManager()->attachCssInternalStyleSheetForSlot(self::MINIMAP_TAG_NAME); 1375f891b7eSNickeau 1380356c661Sgerardnico 1390356c661Sgerardnico global $ID; 1400356c661Sgerardnico global $INFO; 1410356c661Sgerardnico $callingId = $ID; 1420356c661Sgerardnico // If mini-map is in a sidebar, we don't want the ID of the sidebar 1430356c661Sgerardnico // but the ID of the page. 1440356c661Sgerardnico if ($INFO != null) { 1450356c661Sgerardnico $callingId = $INFO['id']; 1460356c661Sgerardnico } 1470356c661Sgerardnico 14832b85071SNickeau $attributes = $data[PluginUtility::ATTRIBUTES]; 1490356c661Sgerardnico $nameSpacePath = getNS($callingId); // The complete path to the directory 15032b85071SNickeau if (array_key_exists(self::NAMESPACE_KEY_ATT, $attributes)) { 15132b85071SNickeau $nameSpacePath = $attributes[self::NAMESPACE_KEY_ATT]; 1520356c661Sgerardnico } 1530356c661Sgerardnico $currentNameSpace = curNS($callingId); // The name of the container directory 15432b85071SNickeau $includeDirectory = $attributes[self::INCLUDE_DIRECTORY_PARAMETERS]; 1550356c661Sgerardnico $pagesOfNamespace = $this->getNamespaceChildren($nameSpacePath, $sort = 'natural', $listdirs = $includeDirectory); 1560356c661Sgerardnico 1570356c661Sgerardnico // Set the two possible home page for the namespace ie: 1580356c661Sgerardnico // - the name of the containing map ($homePageWithContainingMapName) 1590356c661Sgerardnico // - the start conf parameters ($homePageWithStartConf) 1600356c661Sgerardnico global $conf; 1610356c661Sgerardnico $parts = explode(':', $nameSpacePath); 1620356c661Sgerardnico $lastContainingNameSpace = $parts[count($parts) - 1]; 1630356c661Sgerardnico $homePageWithContainingMapName = $nameSpacePath . ':' . $lastContainingNameSpace; 1640356c661Sgerardnico $startConf = $conf['start']; 1650356c661Sgerardnico $homePageWithStartConf = $nameSpacePath . ':' . $startConf; 1660356c661Sgerardnico 1670356c661Sgerardnico // Build the list of page 1680356c661Sgerardnico $miniMapList = '<ul class="list-group">'; 1690356c661Sgerardnico $pageNum = 0; 1700356c661Sgerardnico $startPageFound = false; 1710356c661Sgerardnico $homePageFound = false; 1720356c661Sgerardnico //$pagesCount = count($pagesOfNamespace); // number of pages in the namespace 1730356c661Sgerardnico foreach ($pagesOfNamespace as $pageArray) { 1740356c661Sgerardnico 1750356c661Sgerardnico // The title of the page 1760356c661Sgerardnico $title = ''; 1770356c661Sgerardnico 1780356c661Sgerardnico // If it's a directory 1790356c661Sgerardnico if ($pageArray['type'] == "d") { 1800356c661Sgerardnico 1810356c661Sgerardnico $pageId = $this->getNamespaceStartId($pageArray['id']); 1820356c661Sgerardnico 1830356c661Sgerardnico } else { 1840356c661Sgerardnico 1850356c661Sgerardnico $pageNum++; 1860356c661Sgerardnico $pageId = $pageArray['id']; 1870356c661Sgerardnico 1880356c661Sgerardnico } 189*4cadd4f8SNickeau $markupRef = new MarkupRef($pageId); 190*4cadd4f8SNickeau 1910356c661Sgerardnico 1920356c661Sgerardnico 1930356c661Sgerardnico /** 194*4cadd4f8SNickeau * Label 1950356c661Sgerardnico */ 196*4cadd4f8SNickeau $label = $markupRef->getLabel(); 1970356c661Sgerardnico // Suppress the parts in the name with the regexp defines in the 'suppress' params 19832b85071SNickeau if ($attributes['suppress']) { 19932b85071SNickeau $substrPattern = '/' . $attributes['suppress'] . '/i'; 2000356c661Sgerardnico $replacement = ''; 201*4cadd4f8SNickeau $label = preg_replace($substrPattern, $replacement, $label); 202*4cadd4f8SNickeau } 203*4cadd4f8SNickeau // If debug mode 204*4cadd4f8SNickeau if ($attributes['debug']) { 205*4cadd4f8SNickeau $label .= ' (' . $pageId . '|' . $pageNum . ')'; 2060356c661Sgerardnico } 2070356c661Sgerardnico 208*4cadd4f8SNickeau /** 209*4cadd4f8SNickeau * Link attributes 210*4cadd4f8SNickeau */ 211*4cadd4f8SNickeau try { 212*4cadd4f8SNickeau $linkAttribute = $markupRef->toAttributes(); 213*4cadd4f8SNickeau } catch (ExceptionCombo $e) { 214*4cadd4f8SNickeau $miniMapList .= \ComboStrap\LogUtility::wrapInRedForHtml("Error. {$e->getMessage()}"); 215*4cadd4f8SNickeau continue; 216*4cadd4f8SNickeau } 2170356c661Sgerardnico // See in which page we are 2180356c661Sgerardnico // The style will then change 2190356c661Sgerardnico $active = ''; 2200356c661Sgerardnico if ($callingId == $pageId) { 221*4cadd4f8SNickeau $linkAttribute->addEmptyOutputAttributeValue('active'); 2220356c661Sgerardnico } 2230356c661Sgerardnico 2240356c661Sgerardnico // Not all page are printed 2250356c661Sgerardnico // sidebar are not for instance 2260356c661Sgerardnico 2270356c661Sgerardnico // Are we in the root ? 2280356c661Sgerardnico if ($pageArray['ns']) { 2290356c661Sgerardnico $nameSpacePathPrefix = $pageArray['ns'] . ':'; 2300356c661Sgerardnico } else { 2310356c661Sgerardnico $nameSpacePathPrefix = ''; 2320356c661Sgerardnico } 2330356c661Sgerardnico $print = true; 2340356c661Sgerardnico if ($pageArray['id'] == $nameSpacePathPrefix . $currentNameSpace) { 2350356c661Sgerardnico // If the start page exists, the page with the same name 2360356c661Sgerardnico // than the namespace must be shown 2370356c661Sgerardnico if (page_exists($nameSpacePathPrefix . $startConf)) { 2380356c661Sgerardnico $print = true; 2390356c661Sgerardnico } else { 2400356c661Sgerardnico $print = false; 2410356c661Sgerardnico } 2420356c661Sgerardnico $homePageFound = true; 2430356c661Sgerardnico } else if ($pageArray['id'] == $nameSpacePathPrefix . $startConf) { 2440356c661Sgerardnico $print = false; 2450356c661Sgerardnico $startPageFound = true; 2460356c661Sgerardnico } else if ($pageArray['id'] == $nameSpacePathPrefix . $conf['sidebar']) { 2470356c661Sgerardnico $pageNum -= 1; 2480356c661Sgerardnico $print = false; 2490356c661Sgerardnico }; 2500356c661Sgerardnico 2510356c661Sgerardnico 2520356c661Sgerardnico // If the page must be printed, build the link 2530356c661Sgerardnico if ($print) { 2540356c661Sgerardnico 2550356c661Sgerardnico // Open the item tag 2560356c661Sgerardnico $miniMapList .= "<li class=\"list-group-item " . $active . "\">"; 2570356c661Sgerardnico 2580356c661Sgerardnico // Add a glyphicon if it's a directory 2590356c661Sgerardnico if ($pageArray['type'] == "d") { 2605f891b7eSNickeau $miniMapList .= "<span class=\"nicon_folder_open\" aria-hidden=\"true\"></span> "; 2610356c661Sgerardnico } 2620356c661Sgerardnico 263*4cadd4f8SNickeau $miniMapList .= $linkAttribute->toHtmlEnterTag("a"); 264*4cadd4f8SNickeau $miniMapList .= $label; 265*4cadd4f8SNickeau $miniMapList .= "</a>"; 2660356c661Sgerardnico 2670356c661Sgerardnico 2680356c661Sgerardnico // Close the item 2690356c661Sgerardnico $miniMapList .= "</li>"; 2700356c661Sgerardnico 2710356c661Sgerardnico } 2720356c661Sgerardnico 2730356c661Sgerardnico } 2740356c661Sgerardnico $miniMapList .= '</ul>'; // End list-group 2750356c661Sgerardnico 2760356c661Sgerardnico 2770356c661Sgerardnico // Build the panel header 2780356c661Sgerardnico $miniMapHeader = ""; 2790356c661Sgerardnico $startId = ""; 2800356c661Sgerardnico if ($startPageFound) { 2810356c661Sgerardnico $startId = $homePageWithStartConf; 2820356c661Sgerardnico } else { 2830356c661Sgerardnico if ($homePageFound) { 2840356c661Sgerardnico $startId = $homePageWithContainingMapName; 2850356c661Sgerardnico } 2860356c661Sgerardnico } 2870356c661Sgerardnico 2880356c661Sgerardnico $panelHeaderContent = ""; 2890356c661Sgerardnico if ($startId == "") { 29032b85071SNickeau if ($attributes[self::SHOW_HEADER] == true) { 2910356c661Sgerardnico $panelHeaderContent = 'No Home Page found'; 2920356c661Sgerardnico } 2930356c661Sgerardnico } else { 294*4cadd4f8SNickeau $startLink = new MarkupRef($startId); 295*4cadd4f8SNickeau try { 296*4cadd4f8SNickeau $panelHeaderContent = $startLink->toAttributes()->toHtmlEnterTag("a"); 297*4cadd4f8SNickeau $panelHeaderContent .= $startLink->getLabel(); 298*4cadd4f8SNickeau $panelHeaderContent .= "</a>"; 299*4cadd4f8SNickeau } catch (ExceptionCombo $e) { 300*4cadd4f8SNickeau $panelHeaderContent = "Error: {$e->getMessage()}"; 301*4cadd4f8SNickeau } 3020356c661Sgerardnico // We are not counting the header page 3030356c661Sgerardnico $pageNum--; 3040356c661Sgerardnico } 3050356c661Sgerardnico 3060356c661Sgerardnico if ($panelHeaderContent != "") { 3070356c661Sgerardnico $miniMapHeader .= '<div class="panel-heading">' . $panelHeaderContent . ' <span class="label label-primary">' . $pageNum . ' pages</span></div>'; 3080356c661Sgerardnico } 3090356c661Sgerardnico 31032b85071SNickeau if ($attributes['debug']) { 3110356c661Sgerardnico $miniMapHeader .= '<div class="panel-body">' . 3120356c661Sgerardnico '<B>Debug Information:</B><BR>' . 3130356c661Sgerardnico 'CallingId: (' . $callingId . ')<BR>' . 31432b85071SNickeau 'Suppress Option: (' . $attributes['suppress'] . ')<BR>' . 3150356c661Sgerardnico '</div>'; 3160356c661Sgerardnico } 3170356c661Sgerardnico 3180356c661Sgerardnico // Header + list 3190356c661Sgerardnico $renderer->doc .= '<div id="minimap__plugin"><div class="panel panel-default">' 3200356c661Sgerardnico . $miniMapHeader 3210356c661Sgerardnico . $miniMapList 3220356c661Sgerardnico . '</div></div>'; 3230356c661Sgerardnico break; 3240356c661Sgerardnico } 3250356c661Sgerardnico 3260356c661Sgerardnico return true; 3270356c661Sgerardnico } 3280356c661Sgerardnico return false; 3290356c661Sgerardnico 3300356c661Sgerardnico } 3310356c661Sgerardnico 3320356c661Sgerardnico /** 3330356c661Sgerardnico * Return all pages and/of sub-namespaces (subdirectory) of a namespace (ie directory) 3340356c661Sgerardnico * Adapted from feed.php 3350356c661Sgerardnico * 3360356c661Sgerardnico * @param $namespace The container of the pages 3370356c661Sgerardnico * @param string $sort 'natural' to use natural order sorting (default); 'date' to sort by filemtime 3380356c661Sgerardnico * @param $listdirs - Add the directory to the list of files 3390356c661Sgerardnico * @return array An array of the pages for the namespace 3400356c661Sgerardnico */ 3410356c661Sgerardnico function getNamespaceChildren($namespace, $sort = 'natural', $listdirs = false) 3420356c661Sgerardnico { 3430356c661Sgerardnico require_once(DOKU_INC . 'inc/search.php'); 3440356c661Sgerardnico global $conf; 3450356c661Sgerardnico 3460356c661Sgerardnico $ns = ':' . cleanID($namespace); 3470356c661Sgerardnico // ns as a path 3480356c661Sgerardnico $ns = utf8_encodeFN(str_replace(':', '/', $ns)); 3490356c661Sgerardnico 3500356c661Sgerardnico $data = array(); 3510356c661Sgerardnico 3520356c661Sgerardnico // Options of the callback function search_universal 3530356c661Sgerardnico // in the search.php file 3540356c661Sgerardnico $search_opts = array( 3550356c661Sgerardnico 'depth' => 1, 3560356c661Sgerardnico 'pagesonly' => true, 3570356c661Sgerardnico 'listfiles' => true, 3580356c661Sgerardnico 'listdirs' => $listdirs, 3590356c661Sgerardnico 'firsthead' => true 3600356c661Sgerardnico ); 3610356c661Sgerardnico // search_universal is a function in inc/search.php that accepts the $search_opts parameters 3620356c661Sgerardnico search($data, $conf['datadir'], 'search_universal', $search_opts, $ns, $lvl = 1, $sort); 3630356c661Sgerardnico 3640356c661Sgerardnico return $data; 3650356c661Sgerardnico } 3660356c661Sgerardnico 3670356c661Sgerardnico /** 3680356c661Sgerardnico * Return the id of the start page of a namespace 3690356c661Sgerardnico * 3700356c661Sgerardnico * @param $id an id of a namespace (directory) 3710356c661Sgerardnico * @return string the id of the home page 3720356c661Sgerardnico */ 3730356c661Sgerardnico function getNamespaceStartId($id) 3740356c661Sgerardnico { 3750356c661Sgerardnico 3760356c661Sgerardnico global $conf; 3770356c661Sgerardnico 3780356c661Sgerardnico $id = $id . ":"; 3790356c661Sgerardnico 3800356c661Sgerardnico if (page_exists($id . $conf['start'])) { 3810356c661Sgerardnico // start page inside namespace 3820356c661Sgerardnico $homePageId = $id . $conf['start']; 3830356c661Sgerardnico } elseif (page_exists($id . noNS(cleanID($id)))) { 3840356c661Sgerardnico // page named like the NS inside the NS 3850356c661Sgerardnico $homePageId = $id . noNS(cleanID($id)); 3860356c661Sgerardnico } elseif (page_exists($id)) { 3870356c661Sgerardnico // page like namespace exists 3880356c661Sgerardnico $homePageId = substr($id, 0, -1); 3890356c661Sgerardnico } else { 3900356c661Sgerardnico // fall back to default 3910356c661Sgerardnico $homePageId = $id . $conf['start']; 3920356c661Sgerardnico } 3930356c661Sgerardnico return $homePageId; 3940356c661Sgerardnico } 3950356c661Sgerardnico 3960356c661Sgerardnico /** 3970356c661Sgerardnico * @param $get_called_class 3980356c661Sgerardnico * @return string 3990356c661Sgerardnico */ 4000356c661Sgerardnico public static function getTagName($get_called_class) 4010356c661Sgerardnico { 4020356c661Sgerardnico list(/* $t */, /* $p */, $c) = explode('_', $get_called_class, 3); 4030356c661Sgerardnico return (isset($c) ? $c : ''); 4040356c661Sgerardnico } 4050356c661Sgerardnico 4060356c661Sgerardnico /** 4070356c661Sgerardnico * @return string - the tag 4080356c661Sgerardnico */ 4090356c661Sgerardnico public static function getTag() 4100356c661Sgerardnico { 4110356c661Sgerardnico return self::getTagName(get_called_class()); 4120356c661Sgerardnico } 4130356c661Sgerardnico 4140356c661Sgerardnico 4150356c661Sgerardnico} 416