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()->upsertCssSnippetForBar(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 . ')'); 198 } 199 200 // Add the page number in the URL title 201 $link->setTitle($link->getTitle() .' (' . $pageNum . ')'); 202 203 // Suppress the parts in the name with the regexp defines in the 'suppress' params 204 if ($attributes['suppress']) { 205 $substrPattern = '/' . $attributes['suppress'] . '/i'; 206 $replacement = ''; 207 $name = preg_replace($substrPattern, $replacement, $link->getName()); 208 $link->setName($name); 209 } 210 211 // See in which page we are 212 // The style will then change 213 $active = ''; 214 if ($callingId == $pageId) { 215 $active = 'active'; 216 } 217 218 // Not all page are printed 219 // sidebar are not for instance 220 221 // Are we in the root ? 222 if ($pageArray['ns']) { 223 $nameSpacePathPrefix = $pageArray['ns'] . ':'; 224 } else { 225 $nameSpacePathPrefix = ''; 226 } 227 $print = true; 228 if ($pageArray['id'] == $nameSpacePathPrefix . $currentNameSpace) { 229 // If the start page exists, the page with the same name 230 // than the namespace must be shown 231 if (page_exists($nameSpacePathPrefix . $startConf)) { 232 $print = true; 233 } else { 234 $print = false; 235 } 236 $homePageFound = true; 237 } else if ($pageArray['id'] == $nameSpacePathPrefix . $startConf) { 238 $print = false; 239 $startPageFound = true; 240 } else if ($pageArray['id'] == $nameSpacePathPrefix . $conf['sidebar']) { 241 $pageNum -= 1; 242 $print = false; 243 }; 244 245 246 // If the page must be printed, build the link 247 if ($print) { 248 249 // Open the item tag 250 $miniMapList .= "<li class=\"list-group-item " . $active . "\">"; 251 252 // Add a glyphicon if it's a directory 253 if ($pageArray['type'] == "d") { 254 $miniMapList .= "<span class=\"nicon_folder_open\" aria-hidden=\"true\"></span> "; 255 } 256 257 $miniMapList .= $link->renderOpenTag($renderer); 258 $miniMapList .= $link->getName(); 259 $miniMapList .= $link->renderClosingTag(); 260 261 262 // Close the item 263 $miniMapList .= "</li>"; 264 265 } 266 267 } 268 $miniMapList .= '</ul>'; // End list-group 269 270 271 // Build the panel header 272 $miniMapHeader = ""; 273 $startId = ""; 274 if ($startPageFound) { 275 $startId = $homePageWithStartConf; 276 } else { 277 if ($homePageFound) { 278 $startId = $homePageWithContainingMapName; 279 } 280 } 281 282 $panelHeaderContent = ""; 283 if ($startId == "") { 284 if ($attributes[self::SHOW_HEADER] == true) { 285 $panelHeaderContent = 'No Home Page found'; 286 } 287 } else { 288 $startLink = new LinkUtility($startId); 289 $panelHeaderContent = $startLink->renderOpenTag($renderer); 290 $panelHeaderContent .= $startLink->getName(); 291 $panelHeaderContent .= $startLink->renderClosingTag(); 292 // We are not counting the header page 293 $pageNum--; 294 } 295 296 if ($panelHeaderContent != "") { 297 $miniMapHeader .= '<div class="panel-heading">' . $panelHeaderContent . ' <span class="label label-primary">' . $pageNum . ' pages</span></div>'; 298 } 299 300 if ($attributes['debug']) { 301 $miniMapHeader .= '<div class="panel-body">' . 302 '<B>Debug Information:</B><BR>' . 303 'CallingId: (' . $callingId . ')<BR>' . 304 'Suppress Option: (' . $attributes['suppress'] . ')<BR>' . 305 '</div>'; 306 } 307 308 // Header + list 309 $renderer->doc .= '<div id="minimap__plugin"><div class="panel panel-default">' 310 . $miniMapHeader 311 . $miniMapList 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