1<?php 2/** 3 * Plugin catlist : Displays a list of the pages of a namespace recursively 4 * 5 * @license MIT 6 * @author Félix Faisant <xcodexif@xif.fr> 7 * 8 */ 9 10if (!defined('DOKU_INC')) die('meh.'); 11 12if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC.'lib/plugins/'); 13require_once(DOKU_PLUGIN.'syntax.php'); 14require_once(DOKU_INC.'inc/search.php'); 15require_once(DOKU_INC.'inc/pageutils.php'); 16require_once(DOKU_INC.'inc/parserutils.php'); 17 18define('CATLIST_DISPLAY_LIST', 1); 19define('CATLIST_DISPLAY_LINE', 2); 20 21define('CATLIST_NSLINK_AUTO', 0); 22define('CATLIST_NSLINK_NONE', 1); 23define('CATLIST_NSLINK_FORCE', 2); 24 25define('CATLIST_INDEX_START', 0); 26define('CATLIST_INDEX_OUTSIDE', 1); 27define('CATLIST_INDEX_INSIDE', 2); 28 29define('CATLIST_SORT_NONE', 0); 30define('CATLIST_SORT_ASCENDING', 1); 31define('CATLIST_SORT_DESCENDING', 2); 32 33class syntax_plugin_catlist extends DokuWiki_Syntax_Plugin { 34 35 function connectTo ($aMode) { 36 $this->Lexer->addSpecialPattern('<catlist[^>]*>', $aMode, 'plugin_catlist'); 37 } 38 39 function getSort () { 40 return 189; 41 } 42 43 function getType () { 44 return 'substition'; 45 } 46 47 /*********************************************************************************************/ 48 /************************************ <catlist> directive ************************************/ 49 50 function _checkOption(&$match, $option, &$varAffected, $valIfFound){ 51 if (preg_match('/-'.$option.' /i', $match, $found)) { 52 $varAffected = $valIfFound; 53 $match = str_replace($found[0], '', $match); 54 } 55 } 56 function _checkOptionParam(&$match, $option, &$varAffected, $varAssoc){ 57 if (preg_match('/-'.$option.':('.implode('|',array_keys($varAssoc)).') /i', $match, $found)) { 58 $varAffected = $varAssoc[$found[1]]; 59 $match = str_replace($found[0], '', $match); 60 } 61 } 62 63 function handle ($match, $state, $pos, Doku_Handler $handler) { 64 global $conf; 65 66 $_default_sort_map = array("none" => CATLIST_SORT_NONE, 67 "ascending" => CATLIST_SORT_ASCENDING, 68 "descending" => CATLIST_SORT_DESCENDING); 69 $_index_priority_map = array("start" => CATLIST_INDEX_START, 70 "outside" => CATLIST_INDEX_OUTSIDE, 71 "inside" => CATLIST_INDEX_INSIDE); 72 73 $data = array('displayType' => CATLIST_DISPLAY_LIST, 'nsInBold' => true, 'expand' => 6, 74 'exclupage' => array(), 'excluns' => array(), 'exclunsall' => array(), 'exclunspages' => array(), 'exclunsns' => array(), 75 'exclutype' => 'id', 76 'createPageButtonNs' => true, 'createPageButtonSubs' => false, 'pagename_sanitize' => (boolean)$this->getConf('pagename_sanitize'), 77 'head' => (boolean)$this->getConf('showhead'), 78 'headTitle' => NULL, 'smallHead' => false, 'linkStartHead' => true, 'hn' => 'h1', 79 'useheading' => (boolean)$this->getConf('useheading'), 80 'nsuseheading' => NULL, 'nsLinks' => CATLIST_NSLINK_AUTO, 81 'columns' => 0, 'maxdepth' => 0, 82 'sort_order' => $_default_sort_map[$this->getConf('default_sort')], 83 'sort_by_title' => false, 'sort_by_type' => false, 'sort_by_date' => false, 'sort_collator' => $this->getConf('sort_collator_locale'), 84 'hide_index' => (boolean)$this->getConf('hide_index'), 85 'index_priority' => array(), 86 'nocache' => (boolean)$this->getConf('nocache'), 87 'hide_nsnotr' => (boolean)$this->getConf('hide_acl_nsnotr'), 'show_pgnoread' => false, 'show_perms' => (boolean)$this->getConf('show_acl'), 88 'show_leading_ns' => (boolean)$this->getConf('show_leading_ns'), 89 'show_notfound_error' => true ); 90 91 $index_priority = explode(',', $this->getConf('index_priority')); 92 foreach ($index_priority as $index_type) { 93 if (!array_key_exists($index_type, $_index_priority_map)) { 94 msg("catlist: invalid index type in index_priority", -1); 95 return false; 96 } 97 $data['index_priority'][] = $_index_priority_map[$index_type]; 98 } 99 $match = utf8_substr($match, 9, -1).' '; 100 101 // Display options 102 $this->_checkOption($match, "displayList", $data['displayType'], CATLIST_DISPLAY_LIST); 103 $this->_checkOption($match, "displayLine", $data['displayType'], CATLIST_DISPLAY_LINE); 104 $this->_checkOption($match, "noNSInBold", $data['nsInBold'], false); 105 if (preg_match("/-expandButton:([0-9]+)/i", $match, $found)) { 106 $data['expand'] = intval($found[1]); 107 $match = str_replace($found[0], '', $match); 108 } 109 $this->_checkOption($match, "noHeadTitle", $data['useheading'], false); 110 $this->_checkOption($match, "forceHeadTitle", $data['useheading'], true); 111 $data['nsuseheading'] = $data['useheading']; 112 $this->_checkOption($match, "noNSHeadTitle", $data['nsuseheading'], false); 113 $this->_checkOption($match, "hideNotFoundMsg", $data['show_notfound_error'], false); 114 115 // Namespace options 116 $this->_checkOption($match, "forceLinks", $data['nsLinks'], CATLIST_NSLINK_FORCE); // /!\ Deprecated 117 $this->_checkOptionParam($match, "nsLinks", $data['nsLinks'], array( "none" => CATLIST_NSLINK_NONE, 118 "auto" => CATLIST_NSLINK_AUTO, 119 "force" => CATLIST_NSLINK_FORCE )); 120 121 // Exclude options 122 for ($found; preg_match("/-(exclu(page|ns|nsall|nspages|nsns)!?):\"([^\\/\"]+)\" /i", $match, $found); ) { 123 $option = strtolower($found[1]); 124 // is regex negated ? 125 if (substr($option,-1) == "!") { 126 $data[substr($option,0,-1)][] = array('regex' => $found[3], 'neg' => true); 127 } else { 128 $data[$option][] = array('regex' => $found[3], 'neg' => false); 129 } 130 $match = str_replace($found[0], '', $match); 131 } 132 for ($found; preg_match("/-(exclu(page|ns|nsall|nspages|nsns)) /i", $match, $found); ) { 133 $data[strtolower($found[1])] = true; 134 $match = str_replace($found[0], '', $match); 135 } 136 // Exclude type (exclude based on id, name, or title) 137 $this->_checkOption($match, "excludeOnID", $data['exclutype'], 'id'); 138 $this->_checkOption($match, "excludeOnName", $data['exclutype'], 'name'); 139 $this->_checkOption($match, "excludeOnTitle", $data['exclutype'], 'title'); 140 // Exclude page/namespace id list 141 $data['excludelist'] = array(); 142 for ($found; preg_match("/-exclude:\\{([^\\}]*)\\} /", $match, $found); ) { 143 $list = explode(' ', $found[1]); 144 $data['excludelist'] = array_merge($data['excludelist'], $list); 145 $match = str_replace($found[0], '', $match); 146 } 147 148 // Max depth 149 if (preg_match("/-maxDepth:([0-9]+)/i", $match, $found)) { 150 $data['maxdepth'] = intval($found[1]); 151 $match = str_replace($found[0], '', $match); 152 } 153 154 // Columns 155 if (preg_match("/-columns:([0-9]+)/i", $match, $found)) { 156 $data['columns'] = intval($found[1]); 157 $match = str_replace($found[0], '', $match); 158 } 159 160 // Head options 161 $this->_checkOption($match, "noHead", $data['head'], false); 162 $this->_checkOption($match, "showHead", $data['head'], true); 163 $this->_checkOption($match, "smallHead", $data['smallHead'], true); 164 $this->_checkOption($match, "noLinkStartHead", $data['linkStartHead'], false); 165 if (preg_match("/-(h[1-5])/i", $match, $found)) { 166 $data['hn'] = $found[1]; 167 $match = str_replace($found[0], '', $match); 168 } 169 if (preg_match("/-titleHead:\"([^\"]*)\"/i", $match, $found)) { 170 $data['headTitle'] = $found[1]; 171 $match = str_replace($found[0], '', $match); 172 } 173 174 // Create page button options 175 $this->_checkOption($match, "noAddPageButton", $data['createPageButtonNs'], false); 176 $this->_checkOption($match, "addPageButtonEach", $data['createPageButtonSubs'], true); 177 178 // Sorting options 179 $this->_checkOption($match, "sortAscending", $data['sort_order'], CATLIST_SORT_ASCENDING); 180 $this->_checkOption($match, "sortDescending", $data['sort_order'], CATLIST_SORT_DESCENDING); 181 $this->_checkOption($match, "sortByTitle", $data['sort_by_title'], true); 182 $this->_checkOption($match, "sortByType", $data['sort_by_type'], true); 183 $this->_checkOption($match, "sortByCreationDate", $data['sort_by_date'], 'created'); 184 $this->_checkOption($match, "sortByModifDate", $data['sort_by_date'], 'modified'); 185 186 // ACL options 187 $this->_checkOption($match, "ACLshowPage", $data['show_pgnoread'], true); 188 $this->_checkOption($match, "ACLhideNs", $data['hide_nsnotr'], true); 189 190 // Remove other options and warn about 191 for ($found; preg_match("/ (-.*)/", $match, $found); ) { 192 msg(sprintf($this->getLang('unknownoption'), htmlspecialchars($found[1])), -1); 193 $match = str_replace($found[0], '', $match); 194 } 195 196 // Looking for the wanted namespace. Now, only the wanted namespace remains in $match. Then clean the namespace id 197 $ns = trim($match); 198 if ((boolean)$this->getConf('nswildcards')) { 199 global $ID; 200 $parsepagetemplate_data = array('id' => $ID, 'tpl' => $ns, 'doreplace' => true); 201 $ns = parsePageTemplate($parsepagetemplate_data); 202 } 203 if ($ns == '') $ns = '.'; // If there is nothing, we take the current namespace 204 global $ID; 205 if ($ns[0] == '.') $ns = getNS($ID).':'.$ns; // If it start with a '.', it is a relative path 206 $split = explode(':', $ns); 207 for ($i = 0; $i < count($split); $i++) { 208 if ($split[$i] === '' || $split[$i] === '.') { 209 array_splice($split, $i, 1); 210 $i--; 211 } else if ($split[$i] == '..') { 212 if ($i != 0) { 213 array_splice($split, $i-1, 2); 214 $i -= 2; 215 } else break; 216 } 217 } 218 if (count($split) > 0 && $split[0] == '..') { 219 // Path would be outside the 'pages' directory 220 msg($this->getLang('outofpages'), -1); 221 return false; 222 } 223 $data['ns'] = implode(':', $split); 224 return $data; 225 } 226 227 /**************************************************************************************/ 228 /************************************ Tree walking ************************************/ 229 230 /* Utility function to check is a given page/namespace ($item) is excluded 231 * based on the relevant list of blacklisting/whitelisting regexes $arrayRegex 232 * ( array of array('regex'=>the_regex,'neg'=>false/true) ). The exclusion 233 * is based on item title, full id or name ($exclutype). 234 */ 235 function _isExcluded ($item, $exclutype, $arrayRegex) { 236 if ($arrayRegex === true) return true; 237 global $conf; 238 if ((strlen($conf['hidepages']) != 0) && preg_match('/'.$conf['hidepages'].'/i', $item['id'])) return true; 239 foreach($arrayRegex as $regex) { 240 if (!is_array($regex)) // temporary, for transitioning to v2021-07-21 241 $regex = array('regex' => $regex, 'neg' => false); 242 $match = preg_match('/'.$regex['regex'].(($exclutype=='title')?'/':'/i'), $item[$exclutype]); 243 if ($regex['neg']) { 244 if ($match === 0) 245 return true; 246 } else { 247 if ($match === 1) 248 return true; 249 } 250 } 251 return false; 252 } 253 254 function _getStartPage ($index_priority, $parid, $parpath, $name, $force) { 255 $exists = false; 256 if ($parid != '') $parid .= ':'; 257 global $conf; 258 $index_path_map = array( CATLIST_INDEX_START => $parpath.'/'.$name.'/'.$conf['start'].'.txt', 259 CATLIST_INDEX_OUTSIDE => $parpath.'/'.$name.'.txt', 260 CATLIST_INDEX_INSIDE => $parpath.'/'.$name.'/'.$name.'.txt' ); 261 $index_id_map = array( CATLIST_INDEX_START => $parid .$name.':'.$conf['start'], 262 CATLIST_INDEX_OUTSIDE => $parid .$name, 263 CATLIST_INDEX_INSIDE => $parid .$name.':'.$name ); 264 foreach ($index_priority as $index_type) { 265 if (is_file($index_path_map[$index_type])) { 266 $exists = true; 267 return array(true, $index_id_map[$index_type], $index_path_map[$index_type]); 268 } 269 } 270 if ($force && isset($index_priority[0])) 271 return array(false, $index_id_map[0], null); 272 else 273 return array(false, false, null); 274 // returns ($index_exists, $index_id, $index_filepath) 275 } 276 277 function _getMetadata ($id, $filepath) { 278 $meta = p_get_metadata($id, $key='', $render=METADATA_RENDER_USING_SIMPLE_CACHE); 279 if (!isset($meta['date']['modified'])) 280 $meta['date']['modified'] = @filemtime($filepath); 281 if (!isset($meta['contributor'])) 282 $meta['contributor'] = $meta['creator']; 283 return $meta; 284 } 285 286 /* Entry function for tree walking, called in render() 287 * 288 * $data contains the various options initialized and parsed in handle(), and will be passed along 289 * the tree walking. Moreover, $data['tree'] is filled by the pages found by _walk_recurse(), and 290 * will contain the full tree, minus the excluded pages (however, permissions are only evaluated at 291 * rendering time) and up to the max depth. _walk() prepares and start the tree walking. 292 */ 293 function _walk (&$data) { 294 global $conf; 295 296 // Get the directory path from namespace id, and check if it exists 297 $ns = $data['ns']; 298 if($ns == '%%CURRENT_NAMESPACE%%') 299 $ns = getNS(cleanID(getID())); // update namespace to the one currently displayed 300 $path = str_replace(':', '/', $ns); 301 $path = $conf['datadir'].'/'.utf8_encodeFN($path); 302 if (!is_dir($path)) { 303 if ($data['show_notfound_error']) 304 msg(sprintf($this->getLang('dontexist'), $ns), -1); 305 return false; 306 } 307 308 // Info on the main page (the "header" page) 309 $main = array( 'id' => $ns.':', 310 'exist' => false, 311 'title' => NULL ); 312 resolve_pageid('', $main['id'], $main['exist']); 313 if ($data['headTitle'] !== NULL) 314 $main['title'] = $data['headTitle']; 315 else { 316 if ($data['useheading'] && $main['exist']) 317 $main['title'] = p_get_first_heading($main['id'], true); 318 if (is_null($main['title'])) { 319 $ex = explode(':', $ns); 320 $main['title'] = end($ex); 321 } 322 } 323 $data['main'] = $main; 324 325 // Preparing other stuff 326 if (!isset($data['sort_collator']) || $data['sort_collator'] == "") 327 $data['sort_collator'] = NULL; 328 else { 329 $locale = $data['sort_collator']; 330 $coll = collator_create($locale); 331 if (!isset($coll)) { 332 msg("catlist sorting: can't create Collator object: ".intl_get_error_message(), -1); 333 $data['sort_collator'] = NULL; 334 } else { 335 $coll->setAttribute(Collator::CASE_FIRST, Collator::UPPER_FIRST); 336 $coll->setAttribute(Collator::NUMERIC_COLLATION, Collator::ON); 337 $data['sort_collator'] = $coll; 338 } 339 } 340 341 // Start the recursion 342 if (!isset($data['excludelist'])) // temporary, for transitioning to v2021-07-21 343 $data['excludelist'] = array(); 344 $data['tree'] = array(); 345 $data['index_pages'] = array( $main['id'] ); 346 $this->_walk_recurse($data, $path, $ns, "", false, false, 1/*root depth is 1*/, $data['tree']/*root*/); 347 return true; 348 } 349 350 /* Recursive function for tree walking. 351 * 352 * Scans the current namespace by looking directly at the filesystem directory 353 * for .txt files (pages) and sub-directories (namespaces). Excludes pages/namespaces 354 * based on the various exclusion options. The current/local directory path, namesapce 355 * ID and relative namespace ID are respectively $path, $ns and $relns. 356 * $data is described above. $data['tree'] is not modified directly, but only through 357 * $_TREE which is the *local* tree view (ie. a reference of a $data['tree'] node) and 358 * where found children are added. Optionally sorts this list of children. 359 * The local tree depth is $depth. $excluPages, $excluNS are flags indicates if the 360 * sub-pages/namespaces should be excluded. Fills $data['index_pages'] with all 361 * namespace IDs where an index has been found. 362 */ 363 function _walk_recurse (&$data, $path, $ns, $relns, $excluPages, $excluNS, $depth, &$_TREE) { 364 $scanDirs = @scandir($path, SCANDIR_SORT_NONE); 365 if ($scanDirs === false) { 366 msg("catlist: can't open directory of namespace ".$ns, -1); 367 return; 368 } 369 foreach ($scanDirs as $file) { 370 if ($file[0] == '.' || $file[0] == '_') continue; 371 $name = utf8_decodeFN(str_replace('.txt', '', $file)); 372 $id = ($ns == '') ? $name : $ns.':'.$name; 373 $rel_id = ($relns == '') ? $name : $relns.':'.$name; 374 $item = array('id' => $id, 'rel_id' => $rel_id, 'name' => $name, 'title' => NULL); 375 376 // ID exclusion 377 if (in_array($rel_id, $data['excludelist'])) continue; 378 379 // It's a namespace 380 if (is_dir($path.'/'.$file)) { 381 // Index page of the namespace 382 list($index_exists, $index_id, $index_filepath) = $this->_getStartPage($data['index_priority'], $ns, $path, $name, ($data['nsLinks']==CATLIST_NSLINK_FORCE)); 383 if ($index_exists) 384 $data['index_pages'][] = $index_id; 385 // Exclusion 386 if ($excluNS) continue; 387 if ($this->_isExcluded($item, $data['exclutype'], $data['excluns'])) continue; 388 // Namespace 389 if ($index_exists) { 390 $item['metadata'] = $this->_getMetadata($index_id, $index_filepath); 391 if ($data['nsuseheading'] && isset($item['metadata']['title'])) 392 $item['title'] = $item['metadata']['title']; 393 } 394 if (is_null($item['title'])) 395 $item['title'] = $name; 396 $item['linkdisp'] = ($index_exists && ($data['nsLinks']==CATLIST_NSLINK_AUTO)) || ($data['nsLinks']==CATLIST_NSLINK_FORCE); 397 $item['linkid'] = $index_id; 398 // Button 399 $item['buttonid'] = $data['createPageButtonSubs'] ? $id.':' : NULL; 400 // Recursion if wanted 401 $item['_'] = array(); 402 $okdepth = ($depth < $data['maxdepth']) || ($data['maxdepth'] == 0); 403 $exclude_content = $this->_isExcluded($item, $data['exclutype'], $data['exclunsall']) 404 || in_array($rel_id.':', $data['excludelist']); 405 if (!$exclude_content && $okdepth) { 406 $exclunspages = $this->_isExcluded($item, $data['exclutype'], $data['exclunspages']); 407 $exclunsns = $this->_isExcluded($item, $data['exclutype'], $data['exclunsns']); 408 $this->_walk_recurse($data, $path.'/'.$file, $id, $rel_id, $exclunspages, $exclunsns, $depth+1, $item['_']); 409 } 410 // Tree 411 $_TREE[] = $item; 412 } else 413 414 // It's a page 415 if (!$excluPages) { 416 if (substr($file, -4) != ".txt") continue; 417 // Page title 418 $item['metadata'] = $this->_getMetadata($id, $file); 419 if ($data['useheading'] && isset($item['metadata']['title'])) { 420 $item['title'] = $item['metadata']['title']; 421 } 422 if (is_null($item['title'])) 423 $item['title'] = $name; 424 // Exclusion 425 if ($this->_isExcluded($item, $data['exclutype'], $data['exclupage'])) continue; 426 // Tree 427 $_TREE[] = $item; 428 } 429 430 // Sorting 431 if ($data['sort_order'] != CATLIST_SORT_NONE) { 432 usort($_TREE, function ($a, $b) use ($data) { 433 $a_is_folder = isset($a['_']); 434 $b_is_folder = isset($b['_']); 435 // if one or the other is folder, comparison is done 436 if ($data['sort_by_type'] && ($a_is_folder xor $b_is_folder )) 437 return $b_is_folder; 438 // else, compare date or name 439 if ($data['sort_by_date'] === false) { 440 // by name 441 $a_title = ($data['sort_by_title'] ? $a['title'] : $a['name']); 442 $b_title = ($data['sort_by_title'] ? $b['title'] : $b['name']); 443 if (!is_null($data['sort_collator'])) 444 $r = $data['sort_collator']->compare($a_title, $b_title); 445 else 446 $r = strnatcasecmp($a_title, $b_title); 447 } else { 448 // by date 449 $field = $data['sort_by_date']; 450 $a_date = (isset($a['metadata']['date'][$field]) ? $a['metadata']['date'][$field] : 0); 451 $b_date = (isset($b['metadata']['date'][$field]) ? $b['metadata']['date'][$field] : 0); 452 $r = $a_date <=> $b_date; 453 } 454 if ($data['sort_order'] == CATLIST_SORT_DESCENDING) 455 $r *= -1; 456 return $r; 457 }); 458 } 459 } 460 } 461 462 /***********************************************************************************/ 463 /************************************ Rendering ************************************/ 464 465 function render ($mode, Doku_Renderer $renderer, $data) { 466 if (!is_array($data)) return false; 467 $ns = $data['ns']; 468 469 // Disabling cache 470 if ($data['nocache']) 471 $renderer->nocache(); 472 473 // Walk namespace tree 474 $r = $this->_walk($data); 475 if ($r == false) return false; 476 477 // Write params for the add page button 478 global $conf; 479 if (!isset($data['pagename_sanitize'])) // temporary, for transitioning to v2022-06-25 480 $data['pagename_sanitize'] = true; 481 $renderer->doc .= '<script type="text/javascript"> catlist_baseurl = "'.DOKU_URL.'"; catlist_basescript = "'.DOKU_SCRIPT.'"; catlist_useslash = '.$conf['useslash'].'; catlist_userewrite = '.$conf['userewrite'].'; catlist_sepchar = "'.$conf['sepchar'].'"; catlist_deaccent = '.$conf['deaccent'].'; catlist_pagename_sanitize = '.$data['pagename_sanitize'].'; </script>'; 482 483 // Display headline 484 if ($data['head']) { 485 $html_tag_small = ($data['nsInBold']) ? 'strong' : 'span'; 486 $html_tag = ($data['smallHead']) ? $html_tag_small : $data['hn']; 487 $renderer->doc .= '<'.$html_tag.' class="catlist-head">'; 488 $main = $data['main']; 489 if (($main['exist'] && $data['linkStartHead'] && !($data['nsLinks']==CATLIST_NSLINK_NONE)) || ($data['nsLinks']==CATLIST_NSLINK_FORCE)) 490 $renderer->internallink(':'.$main['id'], $main['title']); 491 else 492 $renderer->doc .= htmlspecialchars($main['title']); 493 $renderer->doc .= '</'.$html_tag.'>'; 494 } 495 496 // Recurse and display 497 $global_ul_attr = ""; 498 if ($data['columns'] != 0) { 499 $global_ul_attr = 'column-count: '.$data['columns'].';'; 500 $global_ul_attr = 'style="-webkit-'.$global_ul_attr.' -moz-'.$global_ul_attr.' '.$global_ul_attr.'" '; 501 $global_ul_attr .= 'class="catlist_columns catlist-nslist" '; 502 } else { 503 $global_ul_attr = 'class="catlist-nslist" '; 504 } 505 if ($data['displayType'] == CATLIST_DISPLAY_LIST) $renderer->doc .= '<ul '.$global_ul_attr.'>'; 506 $this->_recurse($renderer, $data, $data['tree']); 507 $perm_create = $this->_cached_quickaclcheck($ns.':*') >= AUTH_CREATE; 508 $ns_button = ($ns == '') ? '' : $ns.':'; 509 if ($data['createPageButtonNs'] && $perm_create) $this->_displayAddPageButton($renderer, $ns_button, $data['displayType']); 510 if ($data['displayType'] == CATLIST_DISPLAY_LIST) $renderer->doc .= '</ul>'; 511 512 return true; 513 } 514 515 /* Just cache the calls to auth_quickaclcheck, mainly for _any_child_perms */ 516 function _cached_quickaclcheck($id) { 517 static $cache = array(); 518 if (!isset($cache[$id])) 519 $cache[$id] = auth_quickaclcheck($id); 520 return $cache[$id]; 521 } 522 523 /* Walk the tree to see if any page/namespace below this has read access access, for show_leading_ns option */ 524 function _any_child_perms ($data, $_TREE) { 525 foreach ($_TREE as $item) { 526 if (isset($item['_'])) { 527 $perms = $this->_cached_quickaclcheck($item['id'].':*'); 528 if ($perms >= AUTH_READ || $this->_any_child_perms($data, $item['_'])) 529 return true; 530 } else { 531 $perms = $this->_cached_quickaclcheck($item['id']); 532 if ($perms >= AUTH_READ) 533 return true; 534 } 535 } 536 return false; 537 } 538 539 function _recurse (&$renderer, $data, $_TREE) { 540 foreach ($_TREE as $item) { 541 if (isset($item['_'])) { 542 // It's a namespace 543 $perms = $this->_cached_quickaclcheck($item['id'].':*'); 544 $perms_exemption = $data['show_perms']; 545 // If we actually care about not showing the namespace because of permissions : 546 if ($perms < AUTH_READ && !$perms_exemption) { 547 // If show_leading_ns activated, walk the tree below this, see if any page/namespace below this has access 548 if ($data['show_leading_ns'] && $this->_any_child_perms($data, $item['_'])) { 549 $perms_exemption = true; 550 } else { 551 if ($data['hide_nsnotr']) continue; 552 if ($data['show_pgnoread']) 553 $perms_exemption = true; // Add exception if show_pgnoread enabled, but hide_nsnotr prevails 554 } 555 } 556 $linkdisp = $item['linkdisp'] && ($perms >= AUTH_READ); 557 $item['buttonid'] = ($perms >= AUTH_CREATE) ? $item['buttonid'] : NULL; 558 $this->_displayNSBegin($renderer, $data, $item['title'], $linkdisp, $item['linkid'], ($data['show_perms'] ? $perms : NULL)); 559 if ($perms >= AUTH_READ || $perms_exemption) 560 $this->_recurse($renderer, $data, $item['_']); 561 $this->_displayNSEnd($renderer, $data['displayType'], $item['buttonid']); 562 } else { 563 // It's a page 564 $perms = $this->_cached_quickaclcheck($item['id']); 565 if ($perms < AUTH_READ && !$data['show_perms'] && !$data['show_pgnoread']) 566 continue; 567 if ($data['hide_index'] && in_array($item['id'], $data['index_pages'])) 568 continue; 569 $displayLink = $perms >= AUTH_READ || $data['show_perms']; 570 $this->_displayPage($renderer, $item, $data['displayType'], ($data['show_perms'] ? $perms : NULL), $displayLink); 571 } 572 } 573 } 574 575 function _displayNSBegin (&$renderer, $data, $title, $displayLink, $idLink, $perms) { 576 if ($data['displayType'] == CATLIST_DISPLAY_LIST) { 577 $warper_ns = ($data['nsInBold']) ? 'strong' : 'span'; 578 $renderer->doc .= '<li class="catlist-ns"><'.$warper_ns.' class="li catlist-nshead">'; 579 if ($displayLink) $renderer->internallink($idLink, $title); 580 else $renderer->doc .= htmlspecialchars($title); 581 if ($perms !== NULL) $renderer->doc .= ' [ns, perm='.$perms.']'; 582 $renderer->doc .= '</'.$warper_ns.'>'; 583 $renderer->doc .= '<ul class="catlist-nslist">'; 584 } 585 else if ($data['displayType'] == CATLIST_DISPLAY_LINE) { 586 if ($data['nsInBold']) $renderer->doc .= '<strong>'; 587 if ($displayLink) $renderer->internallink($idLink, $title); 588 else $renderer->doc .= htmlspecialchars($title); 589 if ($data['nsInBold']) $renderer->doc .= '</strong>'; 590 $renderer->doc .= '[ '; 591 } 592 } 593 594 function _displayNSEnd (&$renderer, $displayType, $nsAddButton) { 595 if (!is_null($nsAddButton)) $this->_displayAddPageButton($renderer, $nsAddButton, $displayType); 596 if ($displayType == CATLIST_DISPLAY_LIST) $renderer->doc .= '</ul></li>'; 597 else if ($displayType == CATLIST_DISPLAY_LINE) $renderer->doc .= '] '; 598 } 599 600 function _displayPage (&$renderer, $item, $displayType, $perms, $displayLink) { 601 if ($displayType == CATLIST_DISPLAY_LIST) { 602 $renderer->doc .= '<li class="catlist-page">'; 603 if ($displayLink) $renderer->internallink(':'.$item['id'], $item['title']); 604 else $renderer->doc .= htmlspecialchars($item['title']); 605 if ($perms !== NULL) $renderer->doc .= ' [page, perm='.$perms.']'; 606 $renderer->doc .= '</li>'; 607 } else if ($displayType == CATLIST_DISPLAY_LINE) { 608 $renderer->internallink(':'.$item['id'], $item['title']); 609 $renderer->doc .= ' '; 610 } 611 } 612 613 function _displayAddPageButton (&$renderer, $ns, $displayType) { 614 $html = ($displayType == CATLIST_DISPLAY_LIST) ? 'li' : 'span'; 615 $renderer->doc .= '<'.$html.' class="catlist_addpage"><button class="button" onclick="catlist_button_add_page(this,\''.$ns.'\')">'.$this->getLang('addpage').'</button></'.$html.'>'; 616 } 617 618} 619