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()->upsertCssSnippetForBar(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