1<?php
2
3// must be run within Dokuwiki
4if (!defined('DOKU_INC')) die();
5
6if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC.'lib/plugins/');
7
8class helper_plugin_explorertree extends DokuWiki_Plugin {
9
10	private $routes = array();		// array of routes (registered classes)
11	private $memcache = false;		// memcache
12
13	private $options = array(
14		'callbacks' =>array(
15			'page_selected_cb' => null,
16			'ns_selected_cb' => null,
17			'page_selected_js' => null,
18			'ns_selected_js' => null,
19		),
20		'vars'=>array(
21			'id'=> null,
22			'class' => 'explorertree',
23		),
24		'init_plugin' => null,
25	);
26
27	function cache(){
28		if ($this->memcache === false){
29			// we will use memcache even it's emulated, as we building a tree requires to search filesystem (multiple fs I/O) and emulated cache only graps one compressed wile from disk (one optimized fs I/O)
30			$this->memcache = plugin_load('helper','memcache');
31		}
32		return $this->memcahce;
33	}
34
35
36    function getMethods() {
37        $result = array();
38		$result[] = array(
39                'name'   => 'registerRoute',
40                'desc'   => 'registers a route: the tree will be created by the options and ajax will be routed to the caller class.',
41				'parameters' => array(
42					'name' => "string unique name, usually the registerer plugin's name (with suffix, if the plugin uses more trees).",
43					'options' => "array of options, that replace the original options (see getOptions)",
44					)
45                );
46		$result[] = array(
47				'name' => 'getOptions',
48				'desc' => 'returns a registered route options or the default options',
49				'parameters' => array(
50					'name' => "string unique name for the registered options. if omitted, the default options are returned."
51					),
52				'return' => array('options'=>'array of options or null if the name does not exists.'),
53				);
54        $result[] = array(
55                'name'   => 'getTree',
56                'desc'   => 'gets the tree of NS and pages ',
57				'parameters' => array(
58					'folder' => 'string a folder of the current namespace',
59					),
60				'return' => array('tree'=>'array'),
61                );
62        $result[] = array(
63                'name'   => 'htmlExplorer',
64                'desc'   => 'gets html explorer of the whole wiki.',
65				'parameters' => array(
66					'name' => 'string unique name of a callback/data store.',
67					'base' => 'string ID of the root node',
68					'current' => 'string ID of the current node',
69					),
70				'return' => array('tree'=>'array'),
71                );
72        return $result;
73    }
74
75	function registerRoute($name,array $options){
76		$this->routes[$name] = array_replace_recursive ($this->options,$options);
77	}
78	function getOptions($name = null){
79		if (!$name) return $this->options;
80		return @$this->routes[$name][$options];
81	}
82
83	function loadRoute($name,array $reg = null){
84		if (!$name) return $this->options;
85		if ((! @$this->routes[$name]) && $reg){
86			if (($p = plugin_load($reg['type'],$reg['plugin'])) && $met = $reg['method']){
87				call_user_func(array($p,$met),array());
88			}
89		}
90		return @$this->routes[$name];
91	}
92
93
94    /**
95     * get a list of namespace / page files
96     *
97     * @param string $folder an already converted filesystem folder of the current namespace
98     */
99    function getTree($folder=':'){
100        global $conf;
101		global $ID;
102        // read tree structure from pages and media
103		$ofolder = $folder;
104		if ($folder == '*' || $folder == '') $folder = ':';
105		if ($folder[0] != ':') $folder = resolve_id($folder,$ID);
106		$dir = strtr(cleanID($folder),':','/');
107		if (!($this->cache() && is_array($data = $this->cache()->get('explorertree_cache_'.$dir)))){
108			$data = array();
109			search($data,$conf['datadir'],'search_index',array('ns'=>getNS($ID)),$dir,$dir == '' ? 1 : count(explode('/',$dir))+1);
110			$count = count($data);
111			if($count>0) for($i=1; $i<$count; $i++){
112				if($data[$i-1]['id'] == $data[$i]['id'] && $data[$i-1]['type'] == $data[$i]['type']) {
113					unset($data[$i]);
114					$i++;  // duplicate found, next $i can't be a duplicate, so skip forward one
115				}
116			}
117			if ($this->cache()) {
118				$this->cache()->set($cache_id = 'explorertree_cache_'.$dir,$data,60);	// store the data itself (cache for one minute)
119			}
120		}
121        return $data;
122    }
123
124    /**
125     * Display a tree menu to select a page or namespace
126     *
127     */
128    function htmlExplorer($name,$base = '',$current = null){
129        global $lang;
130		if ($base == '' || $base == '*') $base = ':';
131        if (!($o = $this->loadRoute($name))){
132			return "<div>Invalid explorertree route!</div>";	//TODO: replace with lang...
133		}
134        $data = $this->getTree($base);
135        // wrap a list with the root level around the other namespaces
136        if ($base == ':'){
137			array_unshift($data, array( 'level' => 0, 'id' => ':', 'type' => 'd',
138                   'open' =>'true', 'label' => '['.$lang['mediaroot'].']'));
139		}
140        $list = html_buildlist($data,
141							$class = $o['vars']['class'],
142							array($this,'_html_list_tree'),
143							array($this,'_html_li_tree')
144							);
145		if (strncasecmp(trim($list),'<ul ',4)){
146			$list = "<ul class='{$class}' >".$list."</ul>";
147		}
148		if (!($id = $o['vars']['id'])){
149			$id = "explorertree_{$name}";
150		}
151        if ($base == ':'){
152			return "<div class='{$class}_root' id='{$id}'>".$list."</div>"
153			."<script type='text/javascript'>jQuery(document).ready(function(){jQuery('#{$id}').explorerTree(".$this->_treeOpts($name,$current === null ? $base : $current).")});</script>";
154		}
155		return $list;
156
157    }
158
159	function _treeOpts($name,$current){
160		$opts = $this->loadRoute($name);
161		$o = array(
162			'route' => $name,
163			'classname' => $opts['vars']['class'],
164			'loader' => $opts['init_plugin'],
165			'current' => ':'.ltrim(strtr($current,'/',':'),':'),
166			'onselectpage' => (bool)$opts['callbacks']['page_selected_cb'],
167			'onselectns' => (bool)$opts['callbacks']['ns_selected_cb'],
168			'onselectnsjs' => null,
169			'onselectpagejs' => null,
170			'token' => getSecurityToken(),
171		);
172		$json = json_encode($o);
173		$json = preg_replace_callback('~("onselect(ns|page)js"\s*:\s*)null\s*,~',function($m) use ($opts){
174			if (is_string($x = $opts['callbacks'][$m[2].'_selected_js']) && strlen($x) > 0){
175				return $m[1].$x.'||null,';
176			}
177			return $m[0];
178		},$json);
179		return $json;
180
181	}
182
183
184
185    /**
186     * Item formatter for the tree view
187     *
188     * User function for html_buildlist()
189     *
190     * @author Andreas Gohr <andi@splitbrain.org>
191     */
192    function _html_list_tree($item){
193        $ret = '';
194        // what to display
195        if(!empty($item['label'])){
196            $base = $item['label'];
197        }else{
198            $base = ':'.$item['id'];
199            $base = substr($base,strrpos($base,':')+1);
200        }
201
202        // highlight?
203        if( ($item['type']== $this->current_item['type'] && $item['id'] == $this->current_item['id'])) {
204            $cl = ' cur';
205        } else {
206            $cl = '';
207        }
208
209        // namespace or page?
210        if($item['type']=='d'){
211            $ret .= '<a href="#" class="idx_dir'.$cl.' " data-itemid="'.$item["id"].'">';
212            $ret .= $base;
213            $ret .= '</a>';
214        }else{
215            $ret .= '<a href="#" class="wikilink1'.$cl.' " data-itemid="'.$item["id"].'">';
216            $ret .= noNS($item['id']);
217            $ret .= '</a>';
218        }
219        return $ret;
220    }
221
222
223    function _html_li_tree($item){
224        return '<li class="level' . $item['level'] . ' ' .($item["type"] == 'd' ? 'folder ':' ').
225               ($item['open'] ? 'open' : 'closed') . '" data-itemid="'.$item["id"].'">';
226    }
227
228
229}
230// vim:ts=4:sw=4:et:
231