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 95f891b7eSNickeauuse ComboStrap\SnippetManager; 100356c661Sgerardnicouse ComboStrap\LinkUtility; 110356c661Sgerardnicouse ComboStrap\PluginUtility; 120356c661Sgerardnico 130356c661Sgerardnicoif (!defined('DOKU_INC')) die(); 140356c661Sgerardnico 150356c661Sgerardnico 160356c661Sgerardnicoclass syntax_plugin_combo_minimap extends DokuWiki_Syntax_Plugin 170356c661Sgerardnico{ 180356c661Sgerardnico 190356c661Sgerardnico const MINIMAP_TAG_NAME = 'minimap'; 200356c661Sgerardnico const INCLUDE_DIRECTORY_PARAMETERS = 'includedirectory'; 210356c661Sgerardnico const SHOW_HEADER = 'showheader'; 220356c661Sgerardnico const NAMESPACE_KEY_ATT = 'namespace'; 230356c661Sgerardnico 240356c661Sgerardnico 250356c661Sgerardnico 260356c661Sgerardnico function connectTo($aMode) 270356c661Sgerardnico { 280356c661Sgerardnico $pattern = '<' . self::MINIMAP_TAG_NAME . '[^>]*>'; 29*9337a630SNickeau $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 136531e725cSNickeau PluginUtility::getSnippetManager()->attachCssSnippetForBar(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 } 1890356c661Sgerardnico $link = new LinkUtility($pageId); 1900356c661Sgerardnico 1910356c661Sgerardnico 1920356c661Sgerardnico /** 1935f891b7eSNickeau * Set special name and title 1940356c661Sgerardnico */ 1950356c661Sgerardnico // If debug mode 19632b85071SNickeau if ($attributes['debug']) { 197531e725cSNickeau $link->setTitle($link->getTitle().' (' . $pageId . '|' . $pageNum . ')'); 1980356c661Sgerardnico } 1990356c661Sgerardnico 2000356c661Sgerardnico // Suppress the parts in the name with the regexp defines in the 'suppress' params 20132b85071SNickeau if ($attributes['suppress']) { 20232b85071SNickeau $substrPattern = '/' . $attributes['suppress'] . '/i'; 2030356c661Sgerardnico $replacement = ''; 2040356c661Sgerardnico $name = preg_replace($substrPattern, $replacement, $link->getName()); 2050356c661Sgerardnico $link->setName($name); 2060356c661Sgerardnico } 2070356c661Sgerardnico 2080356c661Sgerardnico // See in which page we are 2090356c661Sgerardnico // The style will then change 2100356c661Sgerardnico $active = ''; 2110356c661Sgerardnico if ($callingId == $pageId) { 2120356c661Sgerardnico $active = 'active'; 2130356c661Sgerardnico } 2140356c661Sgerardnico 2150356c661Sgerardnico // Not all page are printed 2160356c661Sgerardnico // sidebar are not for instance 2170356c661Sgerardnico 2180356c661Sgerardnico // Are we in the root ? 2190356c661Sgerardnico if ($pageArray['ns']) { 2200356c661Sgerardnico $nameSpacePathPrefix = $pageArray['ns'] . ':'; 2210356c661Sgerardnico } else { 2220356c661Sgerardnico $nameSpacePathPrefix = ''; 2230356c661Sgerardnico } 2240356c661Sgerardnico $print = true; 2250356c661Sgerardnico if ($pageArray['id'] == $nameSpacePathPrefix . $currentNameSpace) { 2260356c661Sgerardnico // If the start page exists, the page with the same name 2270356c661Sgerardnico // than the namespace must be shown 2280356c661Sgerardnico if (page_exists($nameSpacePathPrefix . $startConf)) { 2290356c661Sgerardnico $print = true; 2300356c661Sgerardnico } else { 2310356c661Sgerardnico $print = false; 2320356c661Sgerardnico } 2330356c661Sgerardnico $homePageFound = true; 2340356c661Sgerardnico } else if ($pageArray['id'] == $nameSpacePathPrefix . $startConf) { 2350356c661Sgerardnico $print = false; 2360356c661Sgerardnico $startPageFound = true; 2370356c661Sgerardnico } else if ($pageArray['id'] == $nameSpacePathPrefix . $conf['sidebar']) { 2380356c661Sgerardnico $pageNum -= 1; 2390356c661Sgerardnico $print = false; 2400356c661Sgerardnico }; 2410356c661Sgerardnico 2420356c661Sgerardnico 2430356c661Sgerardnico // If the page must be printed, build the link 2440356c661Sgerardnico if ($print) { 2450356c661Sgerardnico 2460356c661Sgerardnico // Open the item tag 2470356c661Sgerardnico $miniMapList .= "<li class=\"list-group-item " . $active . "\">"; 2480356c661Sgerardnico 2490356c661Sgerardnico // Add a glyphicon if it's a directory 2500356c661Sgerardnico if ($pageArray['type'] == "d") { 2515f891b7eSNickeau $miniMapList .= "<span class=\"nicon_folder_open\" aria-hidden=\"true\"></span> "; 2520356c661Sgerardnico } 2530356c661Sgerardnico 2545f891b7eSNickeau $miniMapList .= $link->renderOpenTag($renderer); 2555f891b7eSNickeau $miniMapList .= $link->getName(); 2565f891b7eSNickeau $miniMapList .= $link->renderClosingTag(); 2570356c661Sgerardnico 2580356c661Sgerardnico 2590356c661Sgerardnico // Close the item 2600356c661Sgerardnico $miniMapList .= "</li>"; 2610356c661Sgerardnico 2620356c661Sgerardnico } 2630356c661Sgerardnico 2640356c661Sgerardnico } 2650356c661Sgerardnico $miniMapList .= '</ul>'; // End list-group 2660356c661Sgerardnico 2670356c661Sgerardnico 2680356c661Sgerardnico // Build the panel header 2690356c661Sgerardnico $miniMapHeader = ""; 2700356c661Sgerardnico $startId = ""; 2710356c661Sgerardnico if ($startPageFound) { 2720356c661Sgerardnico $startId = $homePageWithStartConf; 2730356c661Sgerardnico } else { 2740356c661Sgerardnico if ($homePageFound) { 2750356c661Sgerardnico $startId = $homePageWithContainingMapName; 2760356c661Sgerardnico } 2770356c661Sgerardnico } 2780356c661Sgerardnico 2790356c661Sgerardnico $panelHeaderContent = ""; 2800356c661Sgerardnico if ($startId == "") { 28132b85071SNickeau if ($attributes[self::SHOW_HEADER] == true) { 2820356c661Sgerardnico $panelHeaderContent = 'No Home Page found'; 2830356c661Sgerardnico } 2840356c661Sgerardnico } else { 2850356c661Sgerardnico $startLink = new LinkUtility($startId); 2865f891b7eSNickeau $panelHeaderContent = $startLink->renderOpenTag($renderer); 2875f891b7eSNickeau $panelHeaderContent .= $startLink->getName(); 2885f891b7eSNickeau $panelHeaderContent .= $startLink->renderClosingTag(); 2890356c661Sgerardnico // We are not counting the header page 2900356c661Sgerardnico $pageNum--; 2910356c661Sgerardnico } 2920356c661Sgerardnico 2930356c661Sgerardnico if ($panelHeaderContent != "") { 2940356c661Sgerardnico $miniMapHeader .= '<div class="panel-heading">' . $panelHeaderContent . ' <span class="label label-primary">' . $pageNum . ' pages</span></div>'; 2950356c661Sgerardnico } 2960356c661Sgerardnico 29732b85071SNickeau if ($attributes['debug']) { 2980356c661Sgerardnico $miniMapHeader .= '<div class="panel-body">' . 2990356c661Sgerardnico '<B>Debug Information:</B><BR>' . 3000356c661Sgerardnico 'CallingId: (' . $callingId . ')<BR>' . 30132b85071SNickeau 'Suppress Option: (' . $attributes['suppress'] . ')<BR>' . 3020356c661Sgerardnico '</div>'; 3030356c661Sgerardnico } 3040356c661Sgerardnico 3050356c661Sgerardnico // Header + list 3060356c661Sgerardnico $renderer->doc .= '<div id="minimap__plugin"><div class="panel panel-default">' 3070356c661Sgerardnico . $miniMapHeader 3080356c661Sgerardnico . $miniMapList 3090356c661Sgerardnico . '</div></div>'; 3100356c661Sgerardnico break; 3110356c661Sgerardnico } 3120356c661Sgerardnico 3130356c661Sgerardnico return true; 3140356c661Sgerardnico } 3150356c661Sgerardnico return false; 3160356c661Sgerardnico 3170356c661Sgerardnico } 3180356c661Sgerardnico 3190356c661Sgerardnico /** 3200356c661Sgerardnico * Return all pages and/of sub-namespaces (subdirectory) of a namespace (ie directory) 3210356c661Sgerardnico * Adapted from feed.php 3220356c661Sgerardnico * 3230356c661Sgerardnico * @param $namespace The container of the pages 3240356c661Sgerardnico * @param string $sort 'natural' to use natural order sorting (default); 'date' to sort by filemtime 3250356c661Sgerardnico * @param $listdirs - Add the directory to the list of files 3260356c661Sgerardnico * @return array An array of the pages for the namespace 3270356c661Sgerardnico */ 3280356c661Sgerardnico function getNamespaceChildren($namespace, $sort = 'natural', $listdirs = false) 3290356c661Sgerardnico { 3300356c661Sgerardnico require_once(DOKU_INC . 'inc/search.php'); 3310356c661Sgerardnico global $conf; 3320356c661Sgerardnico 3330356c661Sgerardnico $ns = ':' . cleanID($namespace); 3340356c661Sgerardnico // ns as a path 3350356c661Sgerardnico $ns = utf8_encodeFN(str_replace(':', '/', $ns)); 3360356c661Sgerardnico 3370356c661Sgerardnico $data = array(); 3380356c661Sgerardnico 3390356c661Sgerardnico // Options of the callback function search_universal 3400356c661Sgerardnico // in the search.php file 3410356c661Sgerardnico $search_opts = array( 3420356c661Sgerardnico 'depth' => 1, 3430356c661Sgerardnico 'pagesonly' => true, 3440356c661Sgerardnico 'listfiles' => true, 3450356c661Sgerardnico 'listdirs' => $listdirs, 3460356c661Sgerardnico 'firsthead' => true 3470356c661Sgerardnico ); 3480356c661Sgerardnico // search_universal is a function in inc/search.php that accepts the $search_opts parameters 3490356c661Sgerardnico search($data, $conf['datadir'], 'search_universal', $search_opts, $ns, $lvl = 1, $sort); 3500356c661Sgerardnico 3510356c661Sgerardnico return $data; 3520356c661Sgerardnico } 3530356c661Sgerardnico 3540356c661Sgerardnico /** 3550356c661Sgerardnico * Return the id of the start page of a namespace 3560356c661Sgerardnico * 3570356c661Sgerardnico * @param $id an id of a namespace (directory) 3580356c661Sgerardnico * @return string the id of the home page 3590356c661Sgerardnico */ 3600356c661Sgerardnico function getNamespaceStartId($id) 3610356c661Sgerardnico { 3620356c661Sgerardnico 3630356c661Sgerardnico global $conf; 3640356c661Sgerardnico 3650356c661Sgerardnico $id = $id . ":"; 3660356c661Sgerardnico 3670356c661Sgerardnico if (page_exists($id . $conf['start'])) { 3680356c661Sgerardnico // start page inside namespace 3690356c661Sgerardnico $homePageId = $id . $conf['start']; 3700356c661Sgerardnico } elseif (page_exists($id . noNS(cleanID($id)))) { 3710356c661Sgerardnico // page named like the NS inside the NS 3720356c661Sgerardnico $homePageId = $id . noNS(cleanID($id)); 3730356c661Sgerardnico } elseif (page_exists($id)) { 3740356c661Sgerardnico // page like namespace exists 3750356c661Sgerardnico $homePageId = substr($id, 0, -1); 3760356c661Sgerardnico } else { 3770356c661Sgerardnico // fall back to default 3780356c661Sgerardnico $homePageId = $id . $conf['start']; 3790356c661Sgerardnico } 3800356c661Sgerardnico return $homePageId; 3810356c661Sgerardnico } 3820356c661Sgerardnico 3830356c661Sgerardnico /** 3840356c661Sgerardnico * @param $get_called_class 3850356c661Sgerardnico * @return string 3860356c661Sgerardnico */ 3870356c661Sgerardnico public static function getTagName($get_called_class) 3880356c661Sgerardnico { 3890356c661Sgerardnico list(/* $t */, /* $p */, $c) = explode('_', $get_called_class, 3); 3900356c661Sgerardnico return (isset($c) ? $c : ''); 3910356c661Sgerardnico } 3920356c661Sgerardnico 3930356c661Sgerardnico /** 3940356c661Sgerardnico * @return string - the tag 3950356c661Sgerardnico */ 3960356c661Sgerardnico public static function getTag() 3970356c661Sgerardnico { 3980356c661Sgerardnico return self::getTagName(get_called_class()); 3990356c661Sgerardnico } 4000356c661Sgerardnico 4010356c661Sgerardnico 4020356c661Sgerardnico} 403