dokuwiki-indexmenu-plugin – Rev 3
?pathlinks?
<?php
/**
* Info Indexmenu: Show a customizable and sortable index for a namespace.
*
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author Samuele Tognini <samuele@samuele.netsons.org>
*
*/
if(!defined('DOKU_INC')) die();
if(!defined('INDEXMENU_IMG_ABSDIR')) define('INDEXMENU_IMG_ABSDIR', DOKU_PLUGIN."indexmenu/images");
require_once(DOKU_INC.'inc/search.php');
/**
* All DokuWiki plugins to extend the parser/rendering mechanism
* need to inherit from this class
*/
class syntax_plugin_indexmenu_indexmenu extends DokuWiki_Syntax_Plugin {
var $sort = false;
var $msort = false;
var $rsort = false;
var $nsort = false;
var $hsort = false;
/**
* What kind of syntax are we?
*/
public function getType() {
return 'substition';
}
/**
* Behavior regarding the paragraph
*/
public function getPType() {
return 'block';
}
/**
* Where to sort in?
*/
public function getSort() {
return 138;
}
/**
* Connect pattern to lexer
*/
public function connectTo($mode) {
$this->Lexer->addSpecialPattern('{{indexmenu>.+?}}', $mode, 'plugin_indexmenu_indexmenu');
}
/**
* Handler to prepare matched data for the rendering process
*
* @param string $match The text matched by the patterns
* @param int $state The lexer state for the match
* @param int $pos The character position of the matched text
* @param Doku_Handler $handler The Doku_Handler object
* @return array Return an array with all data you want to use in render
*/
public function handle($match, $state, $pos, Doku_Handler $handler) {
$theme = 'default';
$level = -1;
$gen_id = 'random';
$maxjs = 0;
$max = 0;
$jsajax = '';
$nss = array();
$skipns = array();
$skipfile = array();
$defaultsstr = $this->getConf('defaultoptions');
$defaults = explode(' ', $defaultsstr);
$match = substr($match, 12, -2);
//split namespace,level,theme
list($nsstr, $optsstr) = explode('|', $match, 2);
//split options
$opts = explode(' ', $optsstr);
//Context option
$context = $this->hasOption($defaults, $opts, 'context');
//split optional namespaces
$nss_temp = preg_split("/ /u", $nsstr, -1, PREG_SPLIT_NO_EMPTY);
//Array optional namespace => level
for($i = 1; $i < count($nss_temp); $i++) {
$nsss = preg_split("/#/u", $nss_temp[$i]);
if(!$context) {
$nsss[0] = $this->_parse_ns($nsss[0]);
}
$nss[] = array($nsss[0], (is_numeric($nsss[1])) ? $nsss[1] : $level);
}
//split main requested namespace
if(preg_match('/(.*)#(\S*)/u', $nss_temp[0], $ns_opt)) {
//split level
$ns = $ns_opt[1];
if(is_numeric($ns_opt[2])) $level = $ns_opt[2];
} else {
$ns = $nss_temp[0];
}
if(!$context) {
$ns = $this->_parse_ns($ns);
}
//nocookie option (disable for uncached pages)
$nocookie = $context || $this->hasOption($defaults, $opts, 'nocookie');
//noscroll option
$noscroll = $this->hasOption($defaults, $opts, 'noscroll');
//Open at current namespace option
$navbar = $this->hasOption($defaults, $opts, 'navbar');
//no namespaces options
$nons = $this->hasOption($defaults, $opts, 'nons');
//no pages option
$nopg = $this->hasOption($defaults, $opts, 'nopg');
//disable toc preview
$notoc = $this->hasOption($defaults, $opts, 'notoc');
//disable underscore to space
$scorespace = $this->hasOption($defaults, $opts, 'scorespace');
//disable the right context menu
$nomenu = $this->hasOption($defaults, $opts, 'nomenu');
//Main sort method
$tsort = $this->hasOption($defaults, $opts, 'tsort');
$dsort = $this->hasOption($defaults, $opts, 'dsort');
if($tsort) {
$sort = 't';
} elseif($dsort) {
$sort = 'd';
} else $sort = 0;
//sort directories in the same way as files
$nsort = $this->hasOption($defaults, $opts, 'nsort');
//sort headpages up
$hsort = $this->hasOption($defaults, $opts, 'hsort');
//Metadata sort method
if($msort = $this->hasOption($defaults, $opts, 'msort')) {
$msort = 'indexmenu_n';
} elseif($value = $this->getOption($defaultsstr, $optsstr, '/msort#(\S+)/u')) {
$msort = str_replace(':', ' ', $value);
}
//reverse sort
$rsort = $this->hasOption($defaults, $opts, 'rsort');
if($sort) $jsajax .= "&sort=" . $sort;
if($msort) $jsajax .= "&msort=" . $msort;
if($rsort) $jsajax .= "&rsort=1";
if($nsort) $jsajax .= "&nsort=1";
if($hsort) $jsajax .= "&hsort=1";
if($nopg) $jsajax .= "&nopg=1";
//javascript option
$dir = '';
//check defaults for js,js#theme, #theme
if(!$js = in_array('js', $defaults)) {
if(preg_match('/(?:^|\s)(js)?#(\S*)/u', $defaultsstr, $match_djs) > 0) {
if(!empty($match_djs[1])) $js = true;
if(isset($match_djs[2])) $dir = $match_djs[2];
}
}
//check opts for nojs,#theme or js,js#theme
if($js) {
if(in_array('nojs', $opts)) {
$js = false;
} else {
if(preg_match('/(?:^|\s)(?:js)?#(\S*)/u', $optsstr, $match_ojs) > 0) {
if(isset($match_ojs[1])) $dir = $match_ojs[1];
}
}
} else {
if($js = in_array('js', $opts)) {
//use theme from the defaults
} else {
if(preg_match('/(?:^|\s)js#(\S*)/u', $optsstr, $match_ojs) > 0) {
$js = true;
if(isset($match_ojs[1])) $dir = $match_ojs[1];
}
}
}
if($js) {
//exist theme?
if(!empty($dir) && is_dir(INDEXMENU_IMG_ABSDIR . "/" . $dir)) {
$theme = $dir;
}
//id generation method
$gen_id = $this->getOption($defaultsstr, $optsstr, '/id#(\S+)/u');
//max option
if($maxmatches = $this->getOption($defaultsstr, $optsstr, '/max#(\d+)($|\s+|#(\d+))/u', true)) {
$max = $maxmatches[1];
if($maxmatches[3]) {
$jsajax .= "&max=" . $maxmatches[3];
}
//disable cookie to avoid javascript errors
$nocookie = true;
} else {
$max = 0;
}
//max js option
if($maxjsvalue = $this->getOption($defaultsstr, $optsstr, '/maxjs#(\d+)/u')) {
$maxjs = $maxjsvalue;
}
}
if(is_numeric($gen_id)) {
$identifier = $gen_id;
} elseif($gen_id == 'ns') {
$identifier = sprintf("%u", crc32($ns));
} else {
$identifier = uniqid(rand());
}
//skip namespaces in index
$skipns[] = $this->getConf('skip_index');
if(preg_match('/skipns[\+=](\S+)/u', $optsstr, $sns) > 0) {
//first sign is: '+' (parallel to conf) or '=' (replace conf)
$action = $sns[0][6];
$index = 0;
if($action == '+') {
$index = 1;
}
$skipns[$index] = $sns[1];
$jsajax .= "&skipns=" . utf8_encodeFN(($action == '+' ? '+' : '=') . $sns[1]);
}
//skip file
$skipfile[] = $this->getConf('skip_file');
if(preg_match('/skipfile[\+=](\S+)/u', $optsstr, $sf) > 0) {
//first sign is: '+' (parallel to conf) or '=' (replace conf)
$action = $sf[0][8];
$index = 0;
if($action == '+') {
$index = 1;
}
$skipfile[$index] = $sf[1];
$jsajax .= "&skipfile=" . utf8_encodeFN(($action == '+' ? '+' : '=') . $sf[1]);
}
//js options
$js_opts = compact('theme', 'identifier', 'nocookie', 'navbar', 'noscroll', 'maxjs', 'notoc', 'scorespace', 'jsajax', 'context', 'nomenu');
return array(
$ns,
$js_opts,
$sort,
$msort,
$rsort,
$nsort,
array(
'level' => $level,
'nons' => $nons,
'nopg' => $nopg,
'nss' => $nss,
'max' => $max,
'js' => $js,
'skip_index' => $skipns,
'skip_file' => $skipfile,
'headpage' => $this->getConf('headpage'),
'hide_headpage' => $this->getConf('hide_headpage')
),
$hsort
);
}
/**
* Looks if the default options and syntax options has the requested option
*
* @param array $defaultsopts array of default options
* @param array $opts array of options provided via syntax
* @param string $optionname name of requested option
* @return bool has optionname?
*/
private function hasOption($defaultsopts, $opts, $optionname) {
$name = $optionname;
if(substr($optionname, 0, 2) == 'no') {
$inversename = substr($optionname, 2);
} else {
$inversename = 'no' . $optionname;
}
if(in_array($name, $defaultsopts)) {
return !in_array($inversename, $opts);
} else {
return in_array($name, $opts);
}
}
/**
* Looks for the value of the requested option in the default options and syntax options
*
* @param string $defaultsstr default options string
* @param string $optsstr syntax options string
* @param string $matchpattern pattern to search for
* @param bool $multiplematches if multiple returns array, otherwise the first match
* @return string|array
*/
private function getOption($defaultsstr, $optsstr, $matchpattern, $multiplematches = false) {
if(preg_match($matchpattern, $optsstr, $match_o) > 0) {
if($multiplematches) {
return $match_o;
} else {
return $match_o[1];
}
} elseif(preg_match($matchpattern, $defaultsstr, $match_d) > 0) {
if($multiplematches) {
return $match_d;
} else {
return $match_d[1];
}
}
return false;
}
/**
* Handles the actual output creation.
*
* @param $mode string output format being rendered
* @param $renderer Doku_Renderer the current renderer object
* @param $data array data created by handler()
* @return boolean rendered correctly?
*/
public function render($mode, Doku_Renderer $renderer, $data) {
global $ACT;
global $conf;
global $INFO;
if($mode == 'xhtml') {
/** @var Doku_Renderer_xhtml $renderer */
if($ACT == 'preview') {
//Check user permission to display indexmenu in a preview page
if($this->getConf('only_admins') &&
$conf['useacl'] &&
$INFO['perm'] < AUTH_ADMIN
)
return false;
//disable cookies
$data[1]['nocookie'] = true;
}
//Navbar with nojs
if($data[1]['navbar'] && !$data[6]['js']) {
if(!isset($data[0])) $data[0] = '..';
$data[6]['nss'][] = array(getNS($INFO['id']));
$renderer->info['cache'] = FALSE;
}
if($data[1]['context']) {
//resolve current id relative namespaces
$data[0] = $this->_parse_ns($data[0], $INFO['id']);
foreach($data[6]['nss'] as $key=> $value) {
$data[6]['nss'][$key][0] = $this->_parse_ns($value[0], $INFO['id']);
}
$renderer->info['cache'] = FALSE;
}
$n = $this->_indexmenu($data);
if(!@$n) {
$n = $this->getConf('empty_msg');
$n = str_replace('{{ns}}', cleanID($data[0]), $n);
$n = p_render('xhtml', p_get_instructions($n), $info);
}
$renderer->doc .= $n;
return true;
} else if($mode == 'metadata') {
/** @var Doku_Renderer_metadata $renderer */
if(!($data[1]['navbar'] && !$data[6]['js']) && !$data[1]['context']) {
//this is an indexmenu page that needs the PARSER_CACHE_USE event trigger;
$renderer->meta['indexmenu'] = TRUE;
}
$renderer->doc .= ((empty($data[0])) ? $conf['title'] : nons($data[0]))." index\n\n";
unset($renderer->persistent['indexmenu']);
return true;
} else {
return false;
}
}
/**
* Return the index
*
* @author Samuele Tognini <samuele@samuele.netsons.org>
*
* This function is a simple hack of Dokuwiki @see html_index($ns)
* @author Andreas Gohr <andi@splitbrain.org>
*
* @param array $myns the options for indexmenu
* @return bool|string return html for a nojs index and when enabled the js rendered index, otherwise false
*/
private function _indexmenu($myns) {
global $conf;
$ns = $myns[0];
$js_opts = $myns[1]; //theme, identifier, nocookie, navbar, noscroll, maxjs, notoc, scorespace, jsajax, context, nomenu
$this->sort = $myns[2];
$this->msort = $myns[3];
$this->rsort = $myns[4];
$this->nsort = $myns[5];
$opts = $myns[6]; //level, nons, nopg, nss, max, js, skip_index, skip_file, headpage, hide_headpage
$this->hsort = $myns[7];
$data = array();
$js_name = "indexmenu_".$js_opts['identifier'];
$fsdir = "/".utf8_encodeFN(str_replace(':', '/', $ns));
if($this->sort || $this->msort || $this->rsort || $this->hsort) {
$this->_search($data, $conf['datadir'], array($this, '_search_index'), $opts, $fsdir);
} else {
search($data, $conf['datadir'], array($this, '_search_index'), $opts, $fsdir);
}
if(!$data) return false;
// javascript index
$output_tmp = "";
if($opts['js']) {
$ns = str_replace('/', ':', $ns);
$output_tmp = $this->_jstree($data, $ns, $js_opts, $js_name, $opts['max']);
//remove unwanted nodes from standard index
$this->_clean_data($data);
}
// Nojs dokuwiki index
// extra div needed when index is first element in sidebar of dokuwiki template, template uses this to toggle sidebar
// the toggle interacts with hide needed for js option.
$output = "\n";
$output .= '<div><div id="nojs_'.$js_name.'" data-jsajax="'.utf8_encodeFN($js_opts['jsajax']).'" class="indexmenu_nojs">'."\n";
$output .= html_buildlist($data, 'idx', array($this, "_html_list_index"), "html_li_index");
$output .= "</div></div>\n";
$output .= $output_tmp;
return $output;
}
/**
* Build the browsable index of pages using javascript
*
* @author Samuele Tognini <samuele@samuele.netsons.org>
* @author Rene Hadler
*
* @param array $data array with items of the tree
* @param string $ns requested namespace
* @param array $js_opts options for javascript renderer
* @param string $js_name identifier for this index
* @param int $max the node at $max level will retrieve all its child nodes through the AJAX mechanism
* @return bool|string returns inline javascript or false
*/
private function _jstree($data, $ns, $js_opts, $js_name, $max) {
global $conf;
$hns = false;
if(empty($data)) return false;
//Render requested ns as root
$headpage = $this->getConf('headpage');
//if rootnamespace and headpage, then add startpage as headpage - TODO seems not logic, when desired use $conf[headpage]=:start: ??
if(empty($ns) && !empty($headpage)) $headpage .= ','.$conf['start'];
$title = $this->_getTitle($ns, $headpage, $hns);
if(empty($title)) {
if(empty($ns)){
$title = htmlspecialchars($conf['title'], ENT_QUOTES);
} else{
$title = $ns;
}
}
// inline javascript
$out = "<script type='text/javascript' charset='utf-8'>\n";
$out .= "<!--//--><![CDATA[//><!--\n";
$out .= "var $js_name = new dTree('".$js_name."','".$js_opts['theme']."');\n";
//javascript config options
$sepchar = idfilter(':', false);
$out .= "$js_name.config.urlbase='".substr(wl(":"), 0, -1)."';\n";
$out .= "$js_name.config.sepchar='".$sepchar."';\n";
if($js_opts['notoc']) $out .= "$js_name.config.toc=false;\n";
if($js_opts['scorespace']) $out .= "$js_name.config.scorespace=true;\n";
if($js_opts['nocookie']) $out .= "$js_name.config.useCookies=false;\n";
if($js_opts['noscroll']) $out .= "$js_name.config.scroll=false;\n";
if($js_opts['maxjs'] > 0) $out .= "$js_name.config.maxjs=".$js_opts['maxjs'].";\n";
if(!empty($js_opts['jsajax'])) $out .= "$js_name.config.jsajax='".utf8_encodeFN($js_opts['jsajax'])."';\n";
//add root node
$json = new JSON();
$out .= $js_name.".add('".idfilter(cleanID($ns), false)."',0,-1,".$json->encode($title);
if($hns) $out .= ",'".idfilter(cleanID($hns), false)."'";
$out .= ");\n";
//add nodes
$anodes = $this->_jsnodes($data, $js_name);
$out .= $anodes[0];
//write to document
$out .= "document.write(".$js_name.");\n";
//initialize index
$out .= "jQuery(function(){".$js_name.".init(";
$out .= (int) is_file(INDEXMENU_IMG_ABSDIR.'/'.$js_opts['theme'].'/style.css').",";
$out .= (int) $js_opts['nocookie'].",";
$out .= '"'.$anodes[1].'",';
$out .= (int) $js_opts['navbar'].",";
$out .= (int) $max;
if($js_opts['nomenu']) $out .= ",1";
$out .= ");});\n";
$out .= "//--><!]]>\n";
$out .= "</script>\n";
return $out;
}
/**
* Return array of javascript nodes and nodes to open.
*
* @author Samuele Tognini <samuele@samuele.netsons.org>
* @param array $data array with items of the tree
* @param string $js_name identifier for this index
* @param int $noajax return as inline js (=1) or array for ajax response (=0)
* @return array|bool returns array with
* - a string of the javascript nodes
* - and a string of space separated numbers of the opened nodes
* or false when no data provided
*/
public function _jsnodes($data, $js_name, $noajax = 1) {
if(empty($data)) return false;
//Array of nodes to check
$q = array('0');
//Current open node
$node = 0;
$out = '';
$extra = '';
if($noajax) {
$jscmd = $js_name.".add";
$separator = ";\n";
} else {
$jscmd = "new Array ";
$separator = ",";
}
$json = new JSON();
foreach($data as $i=> $item) {
$i++;
//Remove already processed nodes (greater level = lower level)
while($item['level'] <= $data[end($q) - 1]['level']) {
array_pop($q);
}
//till i found its father node
if($item['level'] == 1) {
//root node
$father = '0';
} else {
//Father node
$father = end($q);
}
//add node and its options
if($item['type'] == 'd') {
//Search the lowest open node of a tree branch in order to open it.
if($item['open']) ($item['level'] < $data[$node]['level']) ? $node = $i : $extra .= "$i ";
//insert node in last position
array_push($q, $i);
}
$out .= $jscmd."('".idfilter($item['id'], false)."',$i,".$father.",".$json->encode($item['title']);
//hns
($item['hns']) ? $out .= ",'".idfilter($item['hns'], false)."'" : $out .= ",0";
($item['type'] == 'd' || $item['type'] == 'l') ? $out .= ",1" : $out .= ",0";
//MAX option
($item['type'] == 'l') ? $out .= ",1" : $out .= ",0";
$out .= ")".$separator;
}
$extra = rtrim($extra, ' ');
return array($out, $extra);
}
/**
* Get namespace title, checking for headpages
*
* @author Samuele Tognini <samuele@samuele.netsons.org>
* @param string $ns namespace
* @param string $headpage commaseparated headpages options and headpages
* @param string $hns reference pageid of headpage, false when not existing
* @return string when headpage & heading on: title of headpage, otherwise: namespace name
*/
private function _getTitle($ns, $headpage, &$hns) {
global $conf;
$hns = false;
$title = noNS($ns);
if(empty($headpage)) return $title;
$ahp = explode(",", $headpage);
foreach($ahp as $hp) {
switch($hp) {
case ":inside:":
$page = $ns.":".noNS($ns);
break;
case ":same:":
$page = $ns;
break;
//it's an inside start
case ":start:":
$page = ltrim($ns.":".$conf['start'], ":");
break;
//inside pages
default:
$page = $ns.":".$hp;
}
//check headpage
if(@file_exists(wikiFN($page)) && auth_quickaclcheck($page) >= AUTH_READ) {
if($conf['useheading'] == 1 || $conf['useheading'] === 'navigation') {
$title_tmp = p_get_first_heading($page, FALSE);
if(!is_null($title_tmp)) $title = $title_tmp;
}
$title = htmlspecialchars($title, ENT_QUOTES);
$hns = $page;
//headpage found, exit for
break;
}
}
return $title;
}
/**
* Parse namespace request
*
* @author Samuele Tognini <samuele@samuele.netsons.org>
* @param string $ns namespaceid
* @param bool $id page id to resolve $ns relative to.
* @return string id of namespace
*/
public function _parse_ns($ns, $id = FALSE) {
if(!$id) {
global $ID;
$id = $ID;
}
//Just for old reelases compatibility
if(empty($ns) || $ns == '..') $ns = ":..";
return resolve_id(getNS($id), $ns);
}
/**
* Clean index data from unwanted nodes in nojs mode.
*
* @author Samuele Tognini <samuele@samuele.netsons.org>
* @param array $data nodes of the tree
* @return void
*/
private function _clean_data(&$data) {
foreach($data as $i=> $item) {
//closed node
if($item['type'] == "d" && !$item['open']) {
$a = $i + 1;
$level = $data[$i]['level'];
//search and remove every lower and closed nodes
while($data[$a]['level'] > $level && !$data[$a]['open']) {
unset($data[$a]);
$a++;
}
}
}
}
/**
* Callback that adds an item of namespace/page to the browsable index, if it fits in the specified options
*
* $opts['skip_index'] string regexp matching namespaceids to skip
* $opts['skip_file'] string regexp matching pageids to skip
* $opts['headpage'] string headpages options or pageids
* $opts['level'] int desired depth of main namespace, -1 = all levels
* $opts['nss'] array with entries: array(namespaceid,level) specifying namespaces with their own level
* $opts['nons'] bool exclude namespace nodes
* $opts['max'] int If initially closed, the node at max level will retrieve all its child nodes through the AJAX mechanism
* $opts['nopg'] bool exclude page nodes
* $opts['hide_headpage'] int don't hide (0) or hide (1)
* $opts['js'] bool use js-render
*
* @author Andreas Gohr <andi@splitbrain.org>
* modified by Samuele Tognini <samuele@samuele.netsons.org>
* @param array $data Already collected nodes
* @param string $base Where to start the search, usually this is $conf['datadir']
* @param string $file Current file or directory relative to $base
* @param string $type Type either 'd' for directory or 'f' for file
* @param int $lvl Current recursion depht
* @param array $opts Option array as given to search(), see above.
* @return bool if this directory should be traversed (true) or not (false)
*/
public function _search_index(&$data, $base, $file, $type, $lvl, $opts) {
global $conf;
$hns = false;
$isopen = false;
$title = null;
$skip_index = $opts['skip_index'];
$skip_file = $opts['skip_file'];
$headpage = $opts['headpage'];
$id = pathID($file);
if($type == 'd') {
// Skip folders in plugin conf
foreach($skip_index as $skipi) {
if(!empty($skipi) && preg_match($skipi, $id))
return false;
}
//check ACL (for sneaky_index namespaces too).
if($conf['sneaky_index'] && auth_quickaclcheck($id.':') < AUTH_READ) return false;
//Open requested level
if($opts['level'] > $lvl || $opts['level'] == -1) $isopen = true;
//Search optional namespaces
if(!empty($opts['nss'])) {
$nss = $opts['nss'];
for($a = 0; $a < count($nss); $a++) {
if(preg_match("/^".$id."($|:.+)/i", $nss[$a][0], $match)) {
//It contains an optional namespace
$isopen = true;
} elseif(preg_match("/^".$nss[$a][0]."(:.*)/i", $id, $match)) {
//It's inside an optional namespace
if($nss[$a][1] == -1 || substr_count($match[1], ":") < $nss[$a][1]) {
$isopen = true;
} else {
$isopen = false;
}
}
}
}
if($opts['nons']) {
return $isopen;
} elseif($opts['max'] > 0 && !$isopen && $lvl >= $opts['max']) {
$isopen = false;
//Stop recursive searching
$return = false;
//change type
$type = "l";
} elseif($opts['js']) {
$return = true;
} else {
$return = $isopen;
}
//Set title and headpage
$title = $this->_getTitle($id, $headpage, $hns);
//link namespace nodes to start pages when excluding page nodes
if(!$hns && $opts['nopg']) $hns = $id.":".$conf['start'];
} else {
//Nopg.Dont show pages
if($opts['nopg']) return false;
$return = true;
//Nons.Set all pages at first level
if($opts['nons']) $lvl = 1;
//don't add
if(substr($file, -4) != '.txt') return false;
//check hiddens and acl
if(isHiddenPage($id) || auth_quickaclcheck($id) < AUTH_READ) return false;
//Skip files in plugin conf
foreach($skip_file as $skipf) {
if(!empty($skipf) && preg_match($skipf, $id))
return false;
}
//Skip headpages to hide
if(!$opts['nons'] && !empty($headpage) && $opts['hide_headpage']) {
//start page is in root
if($id == $conf['start']) return false;
$ahp = explode(",", $headpage);
foreach($ahp as $hp) {
switch($hp) {
case ":inside:":
if(noNS($id) == noNS(getNS($id))) return false;
break;
case ":same:":
if(@is_dir(dirname(wikiFN($id))."/".utf8_encodeFN(noNS($id)))) return false;
break;
//it' s an inside start
case ":start:":
if(noNS($id) == $conf['start']) return false;
break;
default:
if(noNS($id) == cleanID($hp)) return false;
}
}
}
//Set title
if($conf['useheading'] == 1 || $conf['useheading'] === 'navigation') {
$title = p_get_first_heading($id, FALSE);
}
if(is_null($title)) $title = noNS($id);
$title = htmlspecialchars($title, ENT_QUOTES);
}
$item = array(
'id' => $id,
'type' => $type,
'level' => $lvl,
'open' => $isopen,
'title' => $title,
'hns' => $hns,
'file' => $file,
'return' => $return
);
$item['sort'] = $this->_setorder($item);
$data[] = $item;
return $return;
}
/**
* Callback Index item formatter
*
* User function for @see html_buildlist()
*
* @author Andreas Gohr <andi@splitbrain.org>
* @author Samuele Tognini <samuele@samuele.netsons.org>
* @author Rik Blok
*
* @param array $item item described by array with at least the entries
* - id page id/namespace id
* - type 'd', 'l'(directory which is not yet opened) or 'f'
* - open is node open
* - title title of link
* - hns page id of headpage of the namespace or false
* @return string html of the content of a list item
*/
public function _html_list_index($item) {
global $INFO;
$ret = '';
//namespace
if($item['type'] == 'd' || $item['type'] == 'l') {
$markCurrentPage = false;
$link = $item['id'];
$more = 'idx='.$item['id'];
//namespace link
if($item['hns']) {
$link = $item['hns'];
$tagid = "indexmenu_idx_head";
$more = '';
//current page is shown?
$markCurrentPage = $this->getConf('hide_headpage') && $item['hns'] == $INFO['id'];
} else {
//namespace without headpage
$tagid = "indexmenu_idx";
if($item['open']) $tagid .= ' open';
}
if($markCurrentPage) $ret .= '<span class="curid">';
$ret .= '<a href="'.wl($link, $more).'" class="'.$tagid.'">';
$ret .= $item['title'];
$ret .= '</a>';
if($markCurrentPage) $ret .= '</span>';
} else {
//page link
$ret .= html_wikilink(':'.$item['id']);
}
return $ret;
}
/**
* callback that recurse directory
*
* This function recurses into a given base directory
* and calls the supplied function for each file and directory
*
* Similar to search() of inc/search.php, but has extended sorting options
*
* @param array $data The results of the search are stored here
* @param string $base Where to start the search
* @param callback $func Callback (function name or array with object,method)
* @param array $opts List of indexmenu options
* @param string $dir Current directory beyond $base
* @param int $lvl Recursion Level
*
* @author Andreas Gohr <andi@splitbrain.org>
* @author modified by Samuele Tognini <samuele@samuele.netsons.org>
*/
public function _search(&$data, $base, $func, $opts, $dir = '', $lvl = 1) {
$dirs = array();
$files = array();
$files_tmp = array();
$dirs_tmp = array();
$count = count($data);
//read in directories and files
$dh = @opendir($base.'/'.$dir);
if(!$dh) return;
while(($file = readdir($dh)) !== false) {
//skip hidden files and upper dirs
if(preg_match('/^[\._]/', $file)) continue;
if(is_dir($base.'/'.$dir.'/'.$file)) {
$dirs[] = $dir.'/'.$file;
continue;
}
$files[] = $dir.'/'.$file;
}
closedir($dh);
//Collect and sort dirs
if($this->nsort) {
//collect the wanted directories in dirs_tmp
foreach($dirs as $dir) {
call_user_func_array($func, array(&$dirs_tmp, $base, $dir, 'd', $lvl, $opts));
}
//sort directories
usort($dirs_tmp, array($this, "_cmp"));
//add and search each directory
foreach($dirs_tmp as $dir) {
$data[] = $dir;
if($dir['return']) {
$this->_search($data, $base, $func, $opts, $dir['file'], $lvl + 1);
}
}
} else {
//sort by page name
sort($dirs);
//collect directories
foreach($dirs as $dir) {
if(call_user_func_array($func, array(&$data, $base, $dir, 'd', $lvl, $opts))) {
$this->_search($data, $base, $func, $opts, $dir, $lvl + 1);
}
}
}
//Collect and sort files
foreach($files as $file) {
call_user_func_array($func, array(&$files_tmp, $base, $file, 'f', $lvl, $opts));
}
usort($files_tmp, array($this, "_cmp"));
//count added items
$added = count($data) - $count;
if($added === 0 && empty($files_tmp)) {
//remove empty directory again, only if it has not a headpage associated
$v = end($data);
if(!$v['hns']) array_pop($data);
} else {
//add files to index
$data = array_merge($data, $files_tmp);
}
}
/**
* callback that sorts nodes
*
* @param array $a first node as array with 'sort' entry
* @param array $b second node as array with 'sort' entry
* @return int if less than zero 1st node is less than 2nd, otherwise equal respectively larger
*/
private function _cmp($a, $b) {
if($this->rsort) {
return strnatcasecmp($b['sort'], $a['sort']);
} else {
return strnatcasecmp($a['sort'], $b['sort']);
}
}
/**
* Add sort information to item.
*
* @author Samuele Tognini <samuele@samuele.netsons.org>
*
* @param array $item
* @return bool|int|mixed|string
*/
private function _setorder($item) {
global $conf;
$sort = false;
$page = false;
if($item['type'] == 'd' || $item['type'] == 'l') {
//Fake order info when nsort is not requested
($this->nsort) ? $page = $item['hns'] : $sort = 0;
}
if($item['type'] == 'f') $page = $item['id'];
if($page) {
if($this->hsort && noNS($item['id']) == $conf['start']) $sort = 1;
if($this->msort) $sort = p_get_metadata($page, $this->msort);
if(!$sort && $this->sort) {
switch($this->sort) {
case 't':
$sort = $item['title'];
break;
case 'd':
$sort = @filectime(wikiFN($page));
break;
}
}
}
if($sort === false) $sort = noNS($item['id']);
return $sort;
}
} //Indexmenu class end