1<?php
2if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../../').'/');
3  if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
4  require_once(DOKU_PLUGIN.'action.php');
5
6/** Clickable index.
7 	Based on Thierry Legras's idea.
8    Overrides the index command to provide a clickable index.
9    Based on aq3tree (http://www.kryogenix.org/code/browser/aqlists/)
10    License: GPL
11    */
12
13
14class action_plugin_fullindex extends DokuWiki_Action_Plugin {
15	//store the namespaces for sorting
16	var $sortIndex = array();
17
18	/**
19	 * Constructor
20	 */
21	function action_plugin_fullindex(){
22		$this->setupLocale();
23	}
24
25	function getInfo() {
26		return array('author' => 'Martin Tschofen',
27					 'email'  => 'mtbrains@comcast.net',
28					 'date'   => '2007-02-04',
29					 'name'   => 'fullindex',
30					 'desc'   => 'Collapsable Index with alternate page names and numbers',
31					 'url'    => 'https://www.dokuwiki.org/plugin:fullindex');
32	}
33
34  function register(&$controller) {
35    $controller->register_hook('TPL_ACT_RENDER', 'BEFORE', $this, 'html_index_clickable');
36  }
37
38  function html_index_clickable(&$event){
39	global $conf;
40	global $ID;
41    if ($event->data != 'index') return;
42		require_once(DOKU_INC.'inc/search.php');
43		$dir = $conf['datadir'];
44		$ns  = cleanID($ns);
45		#fixme use appropriate function
46		if(empty($ns)){
47			$ns = dirname(str_replace(':','/',$ID));
48			if($ns == '.') $ns ='';
49		}
50		$ns  = utf8_encodeFN(str_replace(':','/',$ns));
51		print $this->plugin_locale_xhtml('intro');
52
53		$data = array();
54		search($data,$conf['datadir'],array(&$this,'search_fullindex'),array('ns' => $ns));
55		usort($data, "_strnatSort");
56
57		print '<span class="aqLbl">'.$this->getLang('collapse_header').'</span><ul id="aqNav">';
58		foreach ($this->getLang('collapse') as $key => $item) {
59			print '<li id="aqli'.$key.'"><a href="#" onclick="aq_show('.$key.', this)">'.$item.'</a></li>';
60		}
61		print '</ul>';
62		//if conf is not set to titles
63		if($conf['plugin']['fullindex']['link_names'] == 0){
64			print $this->html_build_full_list($data,'idx','html_list_index','html_li_index');
65		} else { //alternative titles
66			print $this->html_build_full_list($data,'idx',array(&$this,'_html_title_index'),'html_li_index');
67		}
68
69		// prevent Dokuwiki normal processing of $ACT (it would clean the variable and destroy our 'index' value.
70		$event->preventDefault();
71		// index command belongs to us, there is no need to hold up Dokuwiki letting other plugins see if its for them
72		$event->stopPropagation();
73  }
74
75	/**
76	* Build an unordered list
77	* Based on inc/search.php - @author Andreas Gohr <andi@splitbrain.org>
78	*/
79	function html_build_full_list($data,$class,$func,$lifunc='html_li_default'){
80		$level = 0;
81		$opens = 0;
82		$ret   = '';
83
84		foreach ($data as $item){
85			if( $item['level'] > $level ){
86			  //open new list
87			  for($i=0; $i<($item['level'] - $level); $i++){
88				if ($i) $ret .= "<li class=\"clear\">\n";
89				if ($level > 0)
90				  $ret .= "\n<ul class=\"$class\">\n";
91				else
92				  $ret .= "\n<ul class=\"aqtree3clickable\">\n";
93			  }
94			}elseif( $item['level'] < $level ){
95			  //close last item
96			  $ret .= "</li>\n";
97			  for ($i=0; $i<($level - $item['level']); $i++){
98				//close higher lists
99				$ret .= "</ul>\n</li>\n";
100			  }
101			}else{
102			  //close last item
103			  $ret .= "</li>\n";
104			}
105
106			//remember current level
107			$level = $item['level'];
108
109			//print item
110			if(is_array($lifunc)){
111			  $ret .= $lifunc[0]->$lifunc[1]($item); //user object method
112			}else{
113			  $ret .= $lifunc($item); //user function
114			}
115
116			if(is_array($func)){
117				$ret .= $func[0]->$func[1]($item); //user object method
118			} else {
119				$ret .= $func($item); //user function
120			}
121		}
122
123		//close remaining items and lists
124		for ($i=0; $i < $level; $i++){
125			$ret .= "</li></ul>\n";
126		}
127
128		return $ret;
129	}
130
131	/**
132	 * find all items and collect necessary information
133	 */
134
135	function search_fullindex(&$data,$base,$file,$type,$lvl,$opts){
136		global $conf;
137
138		$return = true;
139
140		$item = array();
141
142		if($type == 'd' && !preg_match('#^'.$file.'(/|$)#','/'.$opts['ns'])){
143			//always true - only difference to the inc/search.php function
144			$return = true;
145		}elseif($type == 'f' && !preg_match('#\.txt$#',$file)){
146			//don't add
147			return false;
148		}
149
150		$id = pathID($file);
151
152		//check hidden
153		if(isHiddenPage($id)){
154			return false;
155		}
156
157		//check ACL
158		if($type=='f' && auth_quickaclcheck($id) < AUTH_READ){
159			return false;
160		}
161
162		//don't display any namespace's index file -- displayed as the namespace instead
163		if($type == 'f' && preg_match('#\:'.$conf['start'].'$#', $id)) { return false;}
164
165		//setup the sort string
166		if($type=='d'){
167			$num = $this->_getMetaTag($id.":".$conf['start'], 'identifier');
168			$title = $this->_getMetaTag($id.":".$conf['start'], 'alternative');
169		} else {
170			$num = $this->_getMetaTag($id, 'identifier');
171			$title = $this->_getMetaTag($id, 'alternative');
172		}
173
174
175		$data[]=array('id'    => $id,
176					 'type'  => $type,
177					 'level' => $lvl,
178					 'num'   => $num,
179					 'title' => $title,
180					 'open'  => $return,
181					 'sort'  => $this->_setSortIndex($id, $lvl, $type, $num, $title));
182		return $return;
183	}
184
185	/*
186	 * Create sort index for each item
187	 */
188
189	function _setSortIndex($id, $lvl, $type, $num, $title) {
190		global $conf;
191
192		//directories
193		if ($type == 'd') {
194			//if same add dir to array
195			if(count($this->sortIndex) + 1 == $lvl) {
196				//add to the sortIndex
197				$this->_addToSortIndex($id, $num, $title);
198			} else if (count($this->sortIndex) + 1 > $lvl) {
199				$this->_removeFromSortIndex($lvl);
200				//and add new index
201				$this->_addToSortIndex($id, $num, $title);
202			} else {
203				//remove from index
204				array_pop($this->sortIndex);
205			}
206				$sortIndex = "";
207		//files
208		} else {
209			if(count($this->sortIndex) + 1 > $lvl) {
210				$this->_removeFromSortIndex($lvl);
211			}
212			$temp = trim($num.$title);
213			if(empty($temp)) {
214				$sortIdx = $id;
215			} else {
216				$sortIdx = $this->_cleanForSort($num)." ".$title; //space required for natsearch fix to work!
217			}
218		}
219		/*debug
220		print_r($type);
221		print_r(": count=");
222		print_r(count($this->sortIndex) + 1);
223		print_r('---lvl='.$lvl.'<br>');
224		print_r("id=");
225		print_r($id);
226		print_r(" <i>");
227		print_r($this->sortIndex);
228		print_r("</i><br>");
229		*/
230		return implode("",$this->sortIndex).$sortIdx;
231	}
232
233	/**
234	 * add an namespace to the sortIndex array
235	 */
236	 function _addToSortIndex($id, $num, $title){
237		$newIndex = trim($num.$title);
238		if ($newIndex == "") {
239			$newIndex = strrchr($id, ":");
240			//what if it was a root ns?
241			if ($newIndex == "") {
242				$newIndex = $id;
243			}
244		}
245		$this->sortIndex[] = $newIndex;
246	}
247	/**
248	 * remove any number of namespaces from the sortIndex array
249	 */
250
251	 function _removeFromSortIndex($lvl) {
252	 	$diff = count($this->sortIndex) + 1 - $lvl;
253		while($diff > 0){
254			//backed out of namespace
255			array_pop($this->sortIndex);
256			$diff = $diff - 1;
257		}
258	}
259	/**
260	 * build the individual items
261	 */
262
263	function _html_title_index($item){
264	  	$ret = '';
265		$base = ':'.$item['id'];
266		$base = substr($base,strrpos($base,':')+1);
267		if($item['type']=='d'){
268			$name = $item['num']." ".$item['title'];
269			$name = trim($name);
270			if($name == ''){
271				$name = $item['id'];}
272			$ret .= html_wikilink(':'.$item['id'].":", $name);
273
274			//remove link if namespace/globalstart page doesn't exist
275			if(preg_match('#wikilink2#', $ret)) {
276				//should return somekind of meta info for the namespace instead -- only if NS sorting?
277				$ret = '<span>'.noNS($item['id']).'</span>';
278			}
279		} else {
280			$name = $item['num']." ".$item['title'];
281			$name = trim($name);
282			if($name == ''){
283				$ret .= html_wikilink(':'.$item['id']);
284			} else {
285				$ret .= html_wikilink(':'.$item['id'], $name);
286			}
287		}
288		return $ret;
289	}
290
291	function _getMetaTag($page, $term) {
292		/*if(!$page){
293			return "";
294		}*/
295		//return the found meta tag
296		$data = p_get_metadata($page);
297		if (array_key_exists($term, $data)){
298			return $data[$term];
299		} else {
300			return "";
301		}
302	}
303
304	/**
305	 * fix the natural sort, if a number has characters imbedded
306	 */
307
308	function _cleanForSort($num) {
309		return preg_replace('/([a-zA-Z])/', '*$1', $num);
310	}
311
312} //end of action class
313
314//utilities: natural sort
315function _strnatSort($a, $b) {
316	return strnatcasecmp($a['sort'], $b['sort']);
317}