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