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