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( 104 PluginUtility::STATE => $state, 105 PluginUtility::ATTRIBUTES=> $parameters 106 ); 107 108 } 109 110 return false; 111 } 112 113 114 function render($mode, Doku_Renderer $renderer, $data) 115 { 116 117 // The $data variable comes from the handle() function 118 // 119 // $mode = 'xhtml' means that we output html 120 // There is other mode such as metadata where you can output data for the headers (Not 100% sure) 121 if ($mode == 'xhtml') { 122 123 /** @var Doku_Renderer_xhtml $renderer */ 124 125 126 $state=$data[PluginUtility::STATE]; 127 128 // As there is only one call to connect to in order to a add a pattern, 129 // there is only one state entering the function 130 // but I leave it for better understanding of the process flow 131 switch ($state) { 132 133 case DOKU_LEXER_SPECIAL : 134 135 136 PluginUtility::getSnippetManager()->attachCssSnippetForBar(self::MINIMAP_TAG_NAME); 137 138 139 global $ID; 140 global $INFO; 141 $callingId = $ID; 142 // If mini-map is in a sidebar, we don't want the ID of the sidebar 143 // but the ID of the page. 144 if ($INFO != null) { 145 $callingId = $INFO['id']; 146 } 147 148 $attributes = $data[PluginUtility::ATTRIBUTES]; 149 $nameSpacePath = getNS($callingId); // The complete path to the directory 150 if (array_key_exists(self::NAMESPACE_KEY_ATT, $attributes)) { 151 $nameSpacePath = $attributes[self::NAMESPACE_KEY_ATT]; 152 } 153 $currentNameSpace = curNS($callingId); // The name of the container directory 154 $includeDirectory = $attributes[self::INCLUDE_DIRECTORY_PARAMETERS]; 155 $pagesOfNamespace = $this->getNamespaceChildren($nameSpacePath, $sort = 'natural', $listdirs = $includeDirectory); 156 157 // Set the two possible home page for the namespace ie: 158 // - the name of the containing map ($homePageWithContainingMapName) 159 // - the start conf parameters ($homePageWithStartConf) 160 global $conf; 161 $parts = explode(':', $nameSpacePath); 162 $lastContainingNameSpace = $parts[count($parts) - 1]; 163 $homePageWithContainingMapName = $nameSpacePath . ':' . $lastContainingNameSpace; 164 $startConf = $conf['start']; 165 $homePageWithStartConf = $nameSpacePath . ':' . $startConf; 166 167 // Build the list of page 168 $miniMapList = '<ul class="list-group">'; 169 $pageNum = 0; 170 $startPageFound = false; 171 $homePageFound = false; 172 //$pagesCount = count($pagesOfNamespace); // number of pages in the namespace 173 foreach ($pagesOfNamespace as $pageArray) { 174 175 // The title of the page 176 $title = ''; 177 178 // If it's a directory 179 if ($pageArray['type'] == "d") { 180 181 $pageId = $this->getNamespaceStartId($pageArray['id']); 182 183 } else { 184 185 $pageNum++; 186 $pageId = $pageArray['id']; 187 188 } 189 $link = new LinkUtility($pageId); 190 191 192 /** 193 * Set special name and title 194 */ 195 // If debug mode 196 if ($attributes['debug']) { 197 $link->setTitle($link->getTitle().' (' . $pageId . '|' . $pageNum . ')'); 198 } 199 200 // Suppress the parts in the name with the regexp defines in the 'suppress' params 201 if ($attributes['suppress']) { 202 $substrPattern = '/' . $attributes['suppress'] . '/i'; 203 $replacement = ''; 204 $name = preg_replace($substrPattern, $replacement, $link->getName()); 205 $link->setName($name); 206 } 207 208 // See in which page we are 209 // The style will then change 210 $active = ''; 211 if ($callingId == $pageId) { 212 $active = 'active'; 213 } 214 215 // Not all page are printed 216 // sidebar are not for instance 217 218 // Are we in the root ? 219 if ($pageArray['ns']) { 220 $nameSpacePathPrefix = $pageArray['ns'] . ':'; 221 } else { 222 $nameSpacePathPrefix = ''; 223 } 224 $print = true; 225 if ($pageArray['id'] == $nameSpacePathPrefix . $currentNameSpace) { 226 // If the start page exists, the page with the same name 227 // than the namespace must be shown 228 if (page_exists($nameSpacePathPrefix . $startConf)) { 229 $print = true; 230 } else { 231 $print = false; 232 } 233 $homePageFound = true; 234 } else if ($pageArray['id'] == $nameSpacePathPrefix . $startConf) { 235 $print = false; 236 $startPageFound = true; 237 } else if ($pageArray['id'] == $nameSpacePathPrefix . $conf['sidebar']) { 238 $pageNum -= 1; 239 $print = false; 240 }; 241 242 243 // If the page must be printed, build the link 244 if ($print) { 245 246 // Open the item tag 247 $miniMapList .= "<li class=\"list-group-item " . $active . "\">"; 248 249 // Add a glyphicon if it's a directory 250 if ($pageArray['type'] == "d") { 251 $miniMapList .= "<span class=\"nicon_folder_open\" aria-hidden=\"true\"></span> "; 252 } 253 254 $miniMapList .= $link->renderOpenTag($renderer); 255 $miniMapList .= $link->getName(); 256 $miniMapList .= $link->renderClosingTag(); 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 ($attributes[self::SHOW_HEADER] == true) { 282 $panelHeaderContent = 'No Home Page found'; 283 } 284 } else { 285 $startLink = new LinkUtility($startId); 286 $panelHeaderContent = $startLink->renderOpenTag($renderer); 287 $panelHeaderContent .= $startLink->getName(); 288 $panelHeaderContent .= $startLink->renderClosingTag(); 289 // We are not counting the header page 290 $pageNum--; 291 } 292 293 if ($panelHeaderContent != "") { 294 $miniMapHeader .= '<div class="panel-heading">' . $panelHeaderContent . ' <span class="label label-primary">' . $pageNum . ' pages</span></div>'; 295 } 296 297 if ($attributes['debug']) { 298 $miniMapHeader .= '<div class="panel-body">' . 299 '<B>Debug Information:</B><BR>' . 300 'CallingId: (' . $callingId . ')<BR>' . 301 'Suppress Option: (' . $attributes['suppress'] . ')<BR>' . 302 '</div>'; 303 } 304 305 // Header + list 306 $renderer->doc .= '<div id="minimap__plugin"><div class="panel panel-default">' 307 . $miniMapHeader 308 . $miniMapList 309 . '</div></div>'; 310 break; 311 } 312 313 return true; 314 } 315 return false; 316 317 } 318 319 /** 320 * Return all pages and/of sub-namespaces (subdirectory) of a namespace (ie directory) 321 * Adapted from feed.php 322 * 323 * @param $namespace The container of the pages 324 * @param string $sort 'natural' to use natural order sorting (default); 'date' to sort by filemtime 325 * @param $listdirs - Add the directory to the list of files 326 * @return array An array of the pages for the namespace 327 */ 328 function getNamespaceChildren($namespace, $sort = 'natural', $listdirs = false) 329 { 330 require_once(DOKU_INC . 'inc/search.php'); 331 global $conf; 332 333 $ns = ':' . cleanID($namespace); 334 // ns as a path 335 $ns = utf8_encodeFN(str_replace(':', '/', $ns)); 336 337 $data = array(); 338 339 // Options of the callback function search_universal 340 // in the search.php file 341 $search_opts = array( 342 'depth' => 1, 343 'pagesonly' => true, 344 'listfiles' => true, 345 'listdirs' => $listdirs, 346 'firsthead' => true 347 ); 348 // search_universal is a function in inc/search.php that accepts the $search_opts parameters 349 search($data, $conf['datadir'], 'search_universal', $search_opts, $ns, $lvl = 1, $sort); 350 351 return $data; 352 } 353 354 /** 355 * Return the id of the start page of a namespace 356 * 357 * @param $id an id of a namespace (directory) 358 * @return string the id of the home page 359 */ 360 function getNamespaceStartId($id) 361 { 362 363 global $conf; 364 365 $id = $id . ":"; 366 367 if (page_exists($id . $conf['start'])) { 368 // start page inside namespace 369 $homePageId = $id . $conf['start']; 370 } elseif (page_exists($id . noNS(cleanID($id)))) { 371 // page named like the NS inside the NS 372 $homePageId = $id . noNS(cleanID($id)); 373 } elseif (page_exists($id)) { 374 // page like namespace exists 375 $homePageId = substr($id, 0, -1); 376 } else { 377 // fall back to default 378 $homePageId = $id . $conf['start']; 379 } 380 return $homePageId; 381 } 382 383 /** 384 * @param $get_called_class 385 * @return string 386 */ 387 public static function getTagName($get_called_class) 388 { 389 list(/* $t */, /* $p */, $c) = explode('_', $get_called_class, 3); 390 return (isset($c) ? $c : ''); 391 } 392 393 /** 394 * @return string - the tag 395 */ 396 public static function getTag() 397 { 398 return self::getTagName(get_called_class()); 399 } 400 401 402} 403