1<?php
2/**
3 * Info tIndexmenu: Displays the index of a specified namespace.
4 *
5 * @license     GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author      Samuele Tognini <samuele@netsons.org>
7 * @author      Rene Hadler <rene.hadler@iteas.at>
8 *
9 */
10
11if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
12if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
13if(!defined('INDEXMENU_IMG_ABSDIR')) define('INDEXMENU_IMG_ABSDIR',DOKU_PLUGIN."tindexmenu/images");
14require_once(DOKU_PLUGIN.'syntax.php');
15require_once(DOKU_INC.'inc/search.php');
16
17/**
18*
19* Wrapper around deprecated search_callback.
20* @deprecated
21*
22*/
23if(!function_exists("search_callback")) {
24	function search_callback($func,&$data,$base,$file,$type,$lvl,$opts) {
25		return call_user_func_array($func, array(&$data,$base,$file,$type,$lvl,$opts));
26	}
27}
28
29/**
30 * All DokuWiki plugins to extend the parser/rendering mechanism
31 * need to inherit from this class
32 */
33class syntax_plugin_indexmenu_indexmenu extends DokuWiki_Syntax_Plugin {
34
35	var $sort=false;
36	var $msort=false;
37	var $rsort=false;
38	var $nsort=false;
39
40	/**
41	 * return some info
42	 */
43	function getInfo(){
44		return array(
45				'author' => 'Samuele Tognini mod. by Rene Hadler',
46				'email'  => 'samuele@netsons.org, rene.hadler@iteas.at',
47				'date'   => rtrim(io_readFile(DOKU_PLUGIN.'tindexmenu/VERSION.txt')),
48				'name'   => 'tIndexmenu',
49				'desc'   => 'Insert the index of a specified namespace.',
50				'url'    => 'http://wiki.splitbrain.org/plugin:tindexmenu'
51		);
52	}
53
54	/**
55	 * What kind of syntax are we?
56	 */
57	function getType(){
58		return 'substition';
59	}
60
61	function getPType(){
62		return 'block';
63	}
64
65	/**
66	 * Where to sort in?
67	 */
68	function getSort(){
69		return 138;
70	}
71
72	/**
73	 * Connect pattern to lexer
74	 */
75	function connectTo($mode) {
76		$this->Lexer->addSpecialPattern('{{indexmenu>.+?}}',$mode,'plugin_indexmenu_indexmenu');
77	}
78
79	/**
80	 * Handle the match
81	 */
82	function handle($match, $state, $pos, &$handler){
83		$theme="default";
84		$ns=".";
85		$level = -1;
86		$nons = true;
87		$gen_id='random';
88		$maxjs=0;
89		$max=0;
90		$jsajax='';
91		$nss=array();
92		$match = substr($match,12,-2);
93		//split namespace,level,theme
94		$match = preg_split('/\|/u', $match, 2);
95		//split options
96		$opts=preg_split('/ /u',$match[1]);
97		//Context option
98		$context = in_array('context',$opts);
99		//split optional namespaces
100		$nss_temp=preg_split("/ /u",$match[0],-1,PREG_SPLIT_NO_EMPTY);
101		//Array optional namespace => level
102		for ($i = 1; $i < count($nss_temp); $i++) {
103			$nsss=preg_split("/#/u",$nss_temp[$i]);
104			if (!$context) {
105				$nsss[0] = $this->_parse_ns($nsss[0]);
106			}
107			$nss[]=array($nsss[0],(is_numeric($nsss[1])) ? $nsss[1] : $level);
108		}
109		//split main requested namespace
110		if (preg_match('/(.*)#(\S*)/u',$nss_temp[0],$ns_opt)) {
111			//split level
112			$ns = $ns_opt[1];
113			if (is_numeric($ns_opt[2])) $level=$ns_opt[2];
114		} else {
115			$ns = $nss_temp[0];
116		}
117		if (!$context) {
118			$ns = $this->_parse_ns($ns);
119		}
120		//nocookie option (disable for uncached pages)
121		$nocookie=$context||in_array('nocookie',$opts);
122		//noscroll option
123		$noscroll=in_array('noscroll',$opts);
124		//Open at current namespace option
125		$navbar=in_array('navbar',$opts);
126		//no namespaces  options
127		$nons = in_array('nons',$opts);
128		//no pages option
129		$nopg = in_array('nopg',$opts);
130		//disable toc preview
131		$notoc = in_array('notoc',$opts);
132		//Main sort method
133		if (in_array('tsort',$opts)) {
134			$sort='t';
135		} elseif (in_array('dsort',$opts)) {
136			$sort='d';
137		} else $sort=0;
138		//Directory sort
139		$nsort=in_array('nsort',$opts);
140		//Metadata sort method
141		if ($msort = in_array('msort',$opts)) {
142			$msort='indexmenu_n';
143		} elseif (preg_match('/msort#(\S+)/u',$match[1],$msort_tmp) >0) $msort=str_replace(':',' ',$msort_tmp[1]);
144		//reverse sort
145		$rsort=in_array('rsort',$opts);
146		//javascript option
147		if (!$js= in_array('js',$opts)) {
148			//split theme
149			if (preg_match('/js#(\S*)/u',$match[1],$tmp_theme) > 0) {
150				if (is_dir(INDEXMENU_IMG_ABSDIR."/".$tmp_theme[1])) {
151					$theme=$tmp_theme[1];
152				}
153				$js=true;
154			}
155		}
156		//id generation method
157		if (preg_match('/id#(\S+)/u',$match[1],$id) >0) $gen_id=$id[1];
158		//max option
159		if (preg_match('/max#(\d+)($|\s+|#(\d+))/u',$match[1],$maxtmp) >0) {
160			$max=$maxtmp[1];
161			if ($maxtmp[3]) $jsajax = "&max=".$maxtmp[3];
162			//disable cookie to avoid javascript errors
163			$nocookie=true;
164		}
165		if ($sort) $jsajax .= "&sort=".$sort;
166		if ($msort) $jsajax .= "&msort=".$msort;
167		if ($rsort) $jsajax .= "&rsort=1";
168		if ($nsort) $jsajax .= "&nsort=1";
169		if ($nopg) $jsajax .= "&nopg=1";
170		//max js option
171		if (preg_match('/maxjs#(\d+)/u',$match[1],$maxtmp) >0) $maxjs=$maxtmp[1];
172		//js options
173		$js_opts=compact('theme','gen_id','nocookie','navbar','noscroll','maxjs','notoc','jsajax','context');
174		return array($ns,
175				$js_opts,
176				$sort,
177				$msort,
178				$rsort,
179				$nsort,
180				array('level' => $level,
181						'nons' => $nons,
182						'nopg' => $nopg,
183						'nss' => $nss,
184						'max' => $max,
185						'js' => $js,
186						'skip_index' => $this->getConf('skip_index'),
187						'skip_file' => $this->getConf('skip_file'),
188						'headpage' => $this->getConf('headpage'),
189						'hide_headpage' => $this->getConf('hide_headpage')
190				)
191		);
192	}
193
194	/**
195	 * Render output
196	 */
197	function render($mode, &$renderer, $data) {
198		global $ACT;
199		global $conf;
200		global $INFO;
201		if($mode == 'xhtml'){
202			if ($ACT == 'preview') {
203				//Check user permission to display indexmenu in a preview page
204				if( $this->getConf('only_admins') &&
205						$conf['useacl'] &&
206						$INFO['perm'] < AUTH_ADMIN)
207					return false;
208				//disable cookies
209				$data[1]['nocookie']=true;
210			}
211			//Navbar with nojs
212			if ($data[1]['navbar'] && !$data[6]['js']) {
213				if (!isset($data[0])) $data[0]='..';
214				$data[6]['nss'][]=array(getNS($INFO['id']));
215				$renderer->info['cache'] = FALSE;
216			}
217
218			if ($data[1]['context']) {
219				//resolve current id relative namespaces
220				$data[0]=$this->_parse_ns($data[0],$INFO['id']);
221				foreach ($data[6]['nss'] as $key=>$value) {
222					$data[6]['nss'][$key][0] = $this->_parse_ns($value[0],$INFO['id']);
223				}
224				$renderer->info['cache'] = FALSE;
225			}
226			$n = $this->_indexmenu($data);
227			if (!@$n) {
228				$n = $this->getConf('empty_msg');
229				$n = str_replace('{{ns}}',cleanID($data[0]),$n);
230				$n = p_render('xhtml',p_get_instructions($n),$info);
231			}
232			$renderer->doc .= $n;
233			return true;
234		} else if ($mode == 'metadata') {
235			if (!($data[1]['navbar'] && !$data[6]['js']) && !$data[1]['context']) {
236				//this is an indexmenu page that needs the PARSER_CACHE_USE event trigger;
237				$renderer->meta['indexmenu'] = TRUE;
238			}
239			$renderer->doc .= ((empty($data[0])) ? $conf['title'] : nons($data[0])) ." index\n\n";
240			unset($renderer->persistent['indexmenu']);
241			return true;
242		} else {
243			return false;
244		}
245	}
246
247	/**
248	 * Return the index
249	 * @author Samuele Tognini <samuele@netsons.org>
250	 *
251	 * This function is a simple hack of Dokuwiki html_index($ns)
252	 * @author Andreas Gohr <andi@splitbrain.org>
253	 */
254	function _indexmenu($myns) {
255		global $conf;
256		$ns = $myns[0];
257		$js_opts=$myns[1];
258		$this->sort = $myns[2];
259		$this->msort = $myns[3];
260		$this->rsort = $myns[4];
261		$this->nsort = $myns[5];
262		$opts = $myns[6];
263		$output=false;
264		$data = array();
265		$js_name="indexmenu_";
266		$fsdir="/".utf8_encodeFN(str_replace(':','/',$ns));
267		if ($this->sort || $this->msort || $this->rsort) {
268			$custsrch=$this->_search($data,$conf['datadir'],array($this,'_search_index'),$opts,$fsdir);
269		} else {
270			search($data,$conf['datadir'],array($this,'_search_index'),$opts,$fsdir);
271		}
272		if (!$data) return false;
273
274		// Id generation method
275		if (is_numeric($js_opts['gen_id'])) {
276			$js_name .= $js_opts['gen_id'];
277		} elseif ($js_opts['gen_id'] == 'ns') {
278			$js_name .= sprintf("%u",crc32($ns));
279		} else {
280			$js_name .= uniqid(rand());
281		}
282
283		//javascript index
284		if ($opts['js']) {
285			$ns = str_replace('/',':',$ns);
286			$output_tmp=$this->_jstree($data,$ns,$js_opts,$js_name,$opts['max']);
287			//remove unwanted nodes from standard index
288			$this->_clean_data($data);
289		} else {
290			//Nojs dokuwiki index
291			$output .= "<script type='text/javascript' charset='utf-8'>\n";
292			$output .= "<!--//--><![CDATA[//><!--\n";
293			$output .= "indexmenu_nojsqueue.push(new Array('".$js_name."','".utf8_encodeFN($js_opts['jsajax'])."'));\n";
294			$output .= "jQuery(function(){indexmenu_loadJs(DOKU_BASE+'lib/plugins/tindexmenu/nojsindex.js');});\n";
295			$output .= "//--><!]]>\n";
296			$output .= "</script>\n";
297			$output.="\n".'<div id="nojs_'.$js_name.'" class="indexmenu_nojs"';
298			$output.=">\n";
299			$output.=html_buildlist($data,'idx',array($this,"_html_list_index"),"html_li_index");
300			$output.="</div>\n";
301		}
302		$output.=$output_tmp;
303		return $output;
304	}
305
306	/**
307	 * Build the browsable index of pages using javascript
308	 *
309	 * @author  Samuele Tognini <samuele@netsons.org> mod. by Rene Hadler
310	 */
311	function _jstree($data,$ns,$js_opts,$js_name,$max) {
312		global $conf;
313		$hns=false;
314		if (empty($data)) return false;
315		//Render requested ns as root
316		$headpage=$this->getConf('headpage');
317		if (empty($ns) && !empty($headpage)) $headpage.=','.$conf['start'];
318		$title=$this->_getTitle($ns,$headpage,$hns);
319		if (empty($title)) {
320			(empty($ns)) ? $title = htmlspecialchars($conf['title'],ENT_QUOTES) : $title=$ns;
321		}
322		$out = "<script type='text/javascript' charset='utf-8'>\n";
323		$out .= "<!--//--><![CDATA[//><!--\n";
324		$out .= "var $js_name = new dTree('".$js_name."','".$js_opts['theme']."');\n";
325		$sepchar = idfilter(':');
326		$out .= "$js_name.config.urlbase='".substr(wl(":"), 0, -1)."';\n";
327		$out .= "$js_name.config.sepchar='".$sepchar."';\n";
328		if ($js_opts['notoc']) $out .="$js_name.config.toc=false;\n";
329		if ($js_opts['nocookie']) $out .="$js_name.config.useCookies=false;\n";
330		if ($js_opts['noscroll']) $out .="$js_name.config.scroll=false;\n";
331		if ($js_opts['maxjs'] > 0)  $out .= "$js_name.config.maxjs=".$js_opts['maxjs'].";\n";
332		if (!empty($js_opts['jsajax'])) $out .= "$js_name.config.jsajax='".utf8_encodeFN($js_opts['jsajax'])."';\n";
333		$out .= $js_name.".add('".idfilter(cleanID($ns))."',0,-1,'".$title."'";
334		if ($hns) $out .= ",'".idfilter(cleanID($hns))."'";
335		$out .= ");\n";
336		$anodes = $this->_jsnodes($data,$js_name);
337		$out .= $anodes[0];
338		$out .= "document.write(".$js_name.");\n";
339		$out .= $js_name.".init(";
340		$out .= (int) is_file(INDEXMENU_IMG_ABSDIR.'/'.$js_opts['theme'].'/style.css').",";
341		$out .= (int) $js_opts['nocookie'].",";
342		$out .= '"'.$anodes[1].'",';
343		$out .= (int) $js_opts['navbar'].",$max";
344		$out .= ");\n";
345		$out .= "//--><!]]>\n";
346		$out .= "</script>\n";
347		return $out;
348	}
349
350	/**
351	 * Return array of javascript nodes and nodes to open.
352	 *
353	 * @author  Samuele Tognini <samuele@netsons.org>
354	 */
355	function _jsnodes($data,$js_name,$noajax=1) {
356		if (empty($data)) return false;
357		//Array of nodes to check
358		$q=array('0');
359		//Current open node
360		$node=0;
361		$out='';
362		$extra='';
363		if ($noajax) {
364			$jscmd=$js_name.".add";
365			$com=";\n";
366		} else {
367			$jscmd="new Array ";
368			$com=",";
369		}
370		foreach ($data as $i=>$item){
371			$i++;
372			//Remove already processed nodes (greater level = lower level)
373			while ($item['level'] <= $data[end($q)-1]['level']) {
374				array_pop($q);
375			}
376
377			//till i found its father node
378			if ($item['level']==1) {
379				//root node
380				$father='0';
381			} else {
382				//Father node
383				$father=end($q);
384			}
385			//add node and its options
386			if ($item['type'] == 'd' ) {
387				//Search the lowest open node of a tree branch in order to open it.
388				if ($item['open']) ($item['level'] < $data[$node]['level']) ? $node=$i : $extra .= "$i ";
389				//insert node in last position
390				array_push($q,$i);
391			}
392			$out .= $jscmd."('".idfilter($item['id'])."',$i,".$father.",'".$item['title']."'";
393			//hns
394			($item['hns']) ? $out .= ",'".idfilter($item['hns'])."'" : $out .= ",0";
395			($item['type'] == 'd' || $item['type']=='l') ? $out .= ",1" : $out .= ",0";
396			//MAX option
397			($item['type']=='l') ? $out .= ",1" : $out .= ",0";
398			$out .= ")".$com;
399		}
400		$extra=rtrim($extra,' ');
401		return array($out,$extra);
402	}
403	/**
404	 * Get page title, checking for headpages
405	 *
406	 * @author  Samuele Tognini <samuele@netsons.org>
407	 */
408	function _getTitle ($ns,$headpage,&$hns) {
409		global $conf;
410		$hns=false;
411		$title=noNS($ns);
412		if (empty($headpage)) return $title;
413		$ahp=explode(",",$headpage);
414		foreach ($ahp as $hp) {
415			switch ($hp) {
416				case ":inside:":
417					$page=$ns.":".noNS($ns);
418					break;
419				case ":same:":
420					$page=$ns;
421					break;
422					//it's an inside start
423				case ":start:":
424					$page=ltrim($ns.":".$conf['start'],":");
425					break;
426					//inside pages
427				default:
428					$page=$ns.":".$hp;
429			}
430			//check headpage
431			if (@file_exists(wikiFN($page)) && auth_quickaclcheck($page) >= AUTH_READ) {
432				if ($conf['useheading'] && $title_tmp=p_get_first_heading($page,FALSE)) $title=$title_tmp;
433				$title=htmlspecialchars($title,ENT_QUOTES);
434				$hns=$page;
435				//headpage found, exit for
436				break;
437			}
438		}
439		return $title;
440	}
441
442	/**
443	 * Parse namespace request
444	 *
445	 * @author  Samuele Tognini <samuele@netsons.org>
446	 */
447	function _parse_ns ($ns,$id=FALSE) {
448		if (!$id) {
449			global $ID;
450			$id = $ID;
451		}
452		//Just for old reelases compatibility
453		if (empty($ns) || $ns == '..') $ns=":..";
454		return resolve_id(getNS($id),$ns);
455	}
456
457	/**
458	 * Clean index data from unwanted nodes in nojs mode.
459	 *
460	 * @author  Samuele Tognini <samuele@netsons.org>
461	 */
462	function _clean_data(&$data) {
463		foreach ($data as $i=>$item) {
464			//closed node
465			if ($item['type'] == "d" && !$item['open']) {
466				$a=$i+1;
467				$level=$data[$i]['level'];
468				//search and remove every lower and closed nodes
469				while ($data[$a]['level'] > $level && !$data[$a]['open']) {
470					unset($data[$a]);
471					$a++;
472				}
473			}
474			$i++;
475		}
476	}
477
478	/**
479	 * Build the browsable index of pages
480	 *
481	 * $opts['ns'] is the current namespace
482	 *
483	 * @author  Andreas Gohr <andi@splitbrain.org>
484	 * modified by Samuele Tognini <samuele@netsons.org>
485	 */
486	function _search_index(&$data,$base,$file,$type,$lvl,$opts){
487		global $conf;
488		$hns=false;
489		$return=false;
490		$isopen=false;
491		$skip_index=$opts['skip_index'];
492		$skip_file=$opts['skip_file'];
493		$headpage=$opts['headpage'];
494		$id = pathID($file);
495		if($type == 'd'){
496			// Skip folders in plugin conf
497			if (!empty($skip_index) &&
498					preg_match($skip_index, $id))
499				return false;
500			//check ACL (for sneaky_index namespaces too).
501			if ($this->getConf('sneaky_index') && auth_quickaclcheck($id.':') < AUTH_READ) return false;
502			//Open requested level
503			if ($opts['level'] > $lvl || $opts['level'] == -1) $isopen=true;
504			//Search optional namespaces
505			if (!empty($opts['nss'])){
506				$nss=$opts['nss'];
507				for ($a=0; $a<count($nss);$a++) {
508					if (preg_match("/^".$id."($|:.+)/i",$nss[$a][0],$match)) {
509						//It contains an optional namespace
510						$isopen=true;
511					} elseif (preg_match("/^".$nss[$a][0]."(:.*)/i",$id,$match)) {
512						//It's inside an optional namespace
513						if ($nss[$a][1] == -1 || substr_count($match[1],":") < $nss[$a][1]) {
514	      $isopen=true;
515						} else {
516	      $isopen=false;
517						}
518					}
519				}
520			}
521			if ($opts['nons']) {
522				return $isopen;
523			} elseif ($opts['max'] >0 && !$isopen && $lvl >= $opts['max']) {
524				$isopen=false;
525				//Stop recursive searching
526				$return=false;
527				//change type
528				$type="l";
529			} elseif ($opts['js']) {
530				$return=true;
531			} else {
532				$return=$isopen;
533			}
534			//Set title and headpage
535			$title=$this->_getTitle($id,$headpage,$hns);
536			if (!$hns && $opts['nopg']) $hns=$id.":".$conf['start'];
537		} else {
538			//Nopg.Dont show pages
539			if ($opts['nopg']) return false;
540			$return=true;
541			//Nons.Set all pages at first level
542			if ($opts['nons']) $lvl=1;
543			//don't add
544			if (substr($file,-4) != '.txt') return false;
545			//check hiddens and acl
546			if (isHiddenPage($id) || auth_quickaclcheck($id) < AUTH_READ) return false;
547			//Skip files in plugin conf
548			if (!empty($skip_file) &&
549					preg_match($skip_file, $id))
550				return false;
551			//Skip headpages to hide
552			if (!$opts['nons'] &&
553					!empty($headpage) &&
554					$opts['hide_headpage']) {
555				if ($id==$conf['start']) return false;
556				$ahp=explode(",",$headpage);
557				foreach ($ahp as $hp) {
558					switch ($hp) {
559						case ":inside:":
560							if (noNS($id)==noNS(getNS($id)))  return false;
561							break;
562						case ":same:":
563							if (@is_dir(dirname(wikiFN($id))."/".utf8_encodeFN(noNS($id)))) return false;
564							break;
565							//it' s an inside start
566						case ":start:":
567							if (noNS($id)==$conf['start']) return false;
568							break;
569						default:
570							if (noNS($id)==cleanID($hp)) return false;
571					}
572				}
573			}
574			//Set title
575			if (!$conf['useheading'] || !$title=p_get_first_heading($id,FALSE)) $title=noNS($id);
576			$title=htmlspecialchars($title,ENT_QUOTES);
577		}
578
579		$item = array( 'id'    => $id,
580				'type'  => $type,
581				'level' => $lvl,
582				'open'  => $isopen,
583				'title' => $title,
584				'hns'   => $hns,
585				'file' => $file,
586				'return' => $return
587		);
588		$item['sort'] = $this->_setorder($item);
589		$data[] = $item;
590		return $return;
591	}
592
593
594	/**
595	 * Index item formatter
596	 *
597	 * User function for html_buildlist()
598	 *
599	 * @author Andreas Gohr <andi@splitbrain.org>
600	 * modified by Samuele Tognini <samuele@netsons.org>
601	 */
602	function _html_list_index($item){
603		$ret = '';
604		//namespace
605		if($item['type']=='d' || $item['type']=='l'){
606			$link=$item['id'];
607			$more='idx='.$item['id'];
608			//namespace link
609			if ($item['hns']) {
610				$link=$item['hns'];
611				$tagid="indexmenu_idx_head";
612				$more='';
613			} else {
614				//namespace with headpage
615				$tagid="indexmenu_idx";
616				if ($item['open']) $tagid.=' open';
617			}
618			$ret .= '<a href="'.wl($link,$more).'" class="'.$tagid.'">';
619			$ret .= $item['title'];
620			$ret .= '</a>';
621		}else{
622			//page link
623			$ret .= html_wikilink(':'.$item['id']);
624		}
625		return $ret;
626	}
627
628
629	/**
630	 * recurse direcory
631	 *
632	 * This function recurses into a given base directory
633	 * and calls the supplied function for each file and directory
634	 *
635	 * @param   array ref $data The results of the search are stored here
636	 * @param   string    $base Where to start the search
637	 * @param   callback  $func Callback (function name or arayy with object,method)
638	 * @param   string    $dir  Current directory beyond $base
639	 * @param   int       $lvl  Recursion Level
640	 * @author  Andreas Gohr <andi@splitbrain.org>
641	 * modified by Samuele Tognini <samuele@netsons.org>
642	 */
643	function _search(&$data,$base,$func,$opts,$dir='',$lvl=1){
644		$dirs   = array();
645		$files  = array();
646		$files_tmp=array();
647		$dirs_tmp=array();
648
649		//read in directories and files
650		$dh = @opendir($base.'/'.$dir);
651		if(!$dh) return;
652		while(($file = readdir($dh)) !== false){
653			//skip hidden files and upper dirs
654			if(preg_match('/^[\._]/',$file)) continue;
655			if(is_dir($base.'/'.$dir.'/'.$file)){
656				$dirs[] = $dir.'/'.$file;
657				continue;
658			}
659			$files[] = $dir.'/'.$file;
660		}
661		closedir($dh);
662		//Sort dirs
663		if ($this->nsort) {
664			foreach($dirs as $dir){
665				search_callback($func,$dirs_tmp,$base,$dir,'d',$lvl,$opts);
666			}
667			usort($dirs_tmp,array($this,"_cmp"));
668			foreach ($dirs_tmp as $dir) {
669				$data[]=$dir;
670				if ($dir['return']) $this->_search($data,$base,$func,$opts,$dir['file'],$lvl+1);
671			}
672		} else {
673			sort($dirs);
674			foreach($dirs as $dir){
675				if (search_callback($func,$data,$base,$dir,'d',$lvl,$opts)) $this->_search($data,$base,$func,$opts,$dir,$lvl+1);
676			}
677		}
678		//Sort files
679		foreach($files as $file){
680			search_callback($func,$files_tmp,$base,$file,'f',$lvl,$opts);
681		}
682		usort($files_tmp,array($this,"_cmp"));
683		if (empty($dirs) && empty($files_tmp)) {
684			$v=end($data);
685			if (!$v['hns']) array_pop($data);
686		} else {
687			$data=array_merge($data,$files_tmp);
688		}
689		return true;
690	}
691
692	/**
693	 * Sort nodes
694	 *
695	 */
696	function _cmp($a, $b) {
697		if ($this->rsort) {
698			return strnatcasecmp($b['sort'], $a['sort']);
699		} else {
700			return strnatcasecmp($a['sort'], $b['sort']);
701		}
702	}
703
704
705	/**
706	 * Add sort information to item.
707	 *
708	 * @author  Samuele Tognini <samuele@netsons.org>
709	 */
710	function _setorder($item) {
711		$sort=false;
712		if ($item['type']=='d') {
713			//Fake order info when nsort is not requested
714			($this->nsort) ? $page=$item['hns'] : $sort=0;
715		}
716		if ($item['type']=='f') $page=$item['id'];
717		if ($page) {
718			if ($this->msort) $sort=p_get_metadata($page,$this->msort);
719			if (!$sort && $this->sort) {
720				switch ($this->sort) {
721					case 't':
722						$sort=$item['title'];
723						break;
724					case 'd':
725						$sort=@filectime(wikiFN($page));
726						break;
727				}
728			}
729		}
730		if ($sort===false) $sort=noNS($item['id']);
731		return $sort;
732	}
733} //Indexmenu class end
734