1<?php
2/**
3 * Indexmenu Action Plugin:   Indexmenu Component.
4 *
5 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author     Samuele Tognini <samuele@samuele.netsons.org>
7 */
8
9if(!defined('DOKU_INC')) die();
10
11class action_plugin_indexmenu extends DokuWiki_Action_Plugin {
12
13    /**
14     * plugin should use this method to register its handlers with the dokuwiki's event controller
15     *
16     * @param Doku_Event_Handler $controller DokuWiki's event controller object.
17     */
18    function register(Doku_Event_Handler $controller) {
19        if($this->getConf('only_admins')) $controller->register_hook('IO_WIKIPAGE_WRITE', 'BEFORE', $this, '_checkperm');
20        if($this->getConf('page_index') != '') $controller->register_hook('TPL_ACT_RENDER', 'BEFORE', $this, '_loadindex');
21        $controller->register_hook('DOKUWIKI_STARTED', 'AFTER', $this, '_extendJSINFO');
22        $controller->register_hook('PARSER_CACHE_USE', 'BEFORE', $this, '_purgecache');
23        if($this->getConf('show_sort')) $controller->register_hook('TPL_CONTENT_DISPLAY', 'BEFORE', $this, '_showsort');
24        $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, '_ajax_call');
25    }
26
27    /**
28     * Check if user has permission to insert indexmenu
29     *
30     * @author Samuele Tognini <samuele@samuele.netsons.org>
31     *
32     * @param Doku_Event $event
33     * @param mixed      $param not defined
34     */
35    function _checkperm(&$event, $param) {
36        global $INFO;
37        if(!$INFO['isadmin']) {
38            $event->data[0][1] = preg_replace("/{{indexmenu(|_n)>.+?}}/", "", $event->data[0][1]);
39        }
40    }
41
42    /**
43     * Add additional info to $JSINFO
44     *
45     * @author Samuele Tognini <samuele@samuele.netsons.org>
46     * @author Gerrit Uitslag <klapinklapin@gmail.com>
47     *
48     * @param Doku_Event $event
49     * @param mixed      $param not defined
50     */
51    function _extendJSINFO(&$event, $param) {
52        global $INFO, $JSINFO;
53        $JSINFO['isadmin'] = (int) $INFO['isadmin'];
54        $JSINFO['isauth']  = (int) $INFO['userinfo'];
55    }
56
57    /**
58     * Check for pages changes and eventually purge cache.
59     *
60     * @author Samuele Tognini <samuele@samuele.netsons.org>
61     *
62     * @param Doku_Event $event
63     * @param mixed      $param not defined
64     */
65    function _purgecache(&$event, $param) {
66        global $ID;
67        global $conf;
68        /** @var cache_parser $cache */
69        $cache = &$event->data;
70
71        if(!isset($cache->page)) return;
72        //purge only xhtml cache
73        if($cache->mode != "xhtml") return;
74        //Check if it is an indexmenu page
75        if(!p_get_metadata($ID, 'indexmenu')) return;
76
77        $aclcache = $this->getConf('aclcache');
78        if($conf['useacl']) {
79            $newkey = false;
80            if($aclcache == 'user') {
81                //Cache per user
82                if($_SERVER['REMOTE_USER']) $newkey = $_SERVER['REMOTE_USER'];
83            } else if($aclcache == 'groups') {
84                //Cache per groups
85                global $INFO;
86                if($INFO['userinfo']['grps']) $newkey = implode('#', $INFO['userinfo']['grps']);
87            }
88            if($newkey) {
89                $cache->key .= "#".$newkey;
90                $cache->cache = getCacheName($cache->key, $cache->ext);
91            }
92        }
93        //Check if a page is more recent than purgefile.
94        if(@filemtime($cache->cache) < @filemtime($conf['cachedir'].'/purgefile')) {
95            $event->preventDefault();
96            $event->stopPropagation();
97            $event->result = false;
98        }
99    }
100
101    /**
102     * Render a defined page as index.
103     *
104     * @author Samuele Tognini <samuele@samuele.netsons.org>
105     *
106     * @param Doku_Event $event
107     * @param mixed      $param not defined
108     */
109    function _loadindex(&$event, $param) {
110        if('index' != $event->data) return;
111        if(!file_exists(wikiFN($this->getConf('page_index')))) return;
112        global $lang;
113        print '<h1><a id="index" name="index">'.$lang['btn_index']."</a></h1>\n";
114        print p_wiki_xhtml($this->getConf('page_index'));
115        $event->preventDefault();
116        $event->stopPropagation();
117
118    }
119
120    /**
121     * Display the indexmenu sort number.
122     *
123     * @author Samuele Tognini <samuele@samuele.netsons.org>
124     *
125     * @param Doku_Event $event
126     * @param mixed      $param not defined
127     */
128    function _showsort(&$event, $param) {
129        global $ID, $ACT, $INFO;
130        if($INFO['isadmin'] && $ACT == 'show') {
131            if($n = p_get_metadata($ID, 'indexmenu_n')) {
132                ptln('<div class="info">');
133                ptln($this->getLang('showsort').$n);
134                ptln('</div>');
135            }
136        }
137    }
138
139    /**
140     * Handles ajax requests for indexmenu
141     *
142     * @param Doku_Event $event
143     * @param mixed      $param not defined
144     */
145    function _ajax_call(&$event, $param) {
146        if($event->data !== 'indexmenu') {
147            return;
148        }
149        //no other ajax call handlers needed
150        $event->stopPropagation();
151        $event->preventDefault();
152
153        switch($_REQUEST['req']) {
154            case 'local':
155                //list themes
156                header('Content-Type: application/json');
157
158                $data = $this->_getlocalThemes();
159
160               // require_once DOKU_INC.'inc/JSON.php';
161                $json = new JSON();
162                echo ''.$json->encode($data).'';
163                break;
164
165            case 'toc':
166                //print toc preview
167                if(isset($_REQUEST['id'])) print $this->print_toc($_REQUEST['id']);
168                break;
169
170            case 'index':
171                //print index
172                if(isset($_REQUEST['idx'])) print $this->print_index($_REQUEST['idx']);
173                break;
174        }
175
176    }
177
178    /**
179     * Print a list of local themes
180     *
181     * @author Samuele Tognini <samuele@samuele.netsons.org>
182     * @author Gerrit Uitslag <klapinklapin@gmail.com>
183     */
184    private function _getlocalThemes() {
185        $themebase = 'lib/plugins/indexmenu/images';
186
187        $handle = @opendir(DOKU_INC.$themebase);
188        $themes = array();
189        while(false !== ($file = readdir($handle))) {
190            if(is_dir(DOKU_INC.$themebase.'/'.$file)
191                && $file != "."
192                && $file != ".."
193                && $file != "repository"
194                && $file != "tmp"
195                && $file != ".svn"
196            ) {
197                $themes[] = $file;
198            }
199        }
200        closedir($handle);
201        sort($themes);
202
203        return array(
204            'themebase' => $themebase,
205            'themes'    => $themes
206        );
207
208    }
209
210    /**
211     * Print a toc preview
212     *
213     * @author Samuele Tognini <samuele@samuele.netsons.org>
214     * @author Andreas Gohr <andi@splitbrain.org>
215     */
216    function print_toc($id) {
217        require_once(DOKU_INC.'inc/parser/xhtml.php');
218        $id = cleanID($id);
219        if(auth_quickaclcheck($id) < AUTH_READ) return '';
220
221        $meta = p_get_metadata($id);
222        $toc  = $meta['description']['tableofcontents'];
223
224        if(count($toc) > 1) {
225            //display ToC of two or more headings
226            $out = $this->render_toc($toc);
227        } else {
228            //display page abstract
229            $out = $this->render_abstract($id, $meta);
230        }
231        return $out;
232    }
233
234    /**
235     * Return the TOC rendered to XHTML
236     *
237     * @author Andreas Gohr <andi@splitbrain.org>
238     * @author Gerrit Uitslag <klapinklapin@gmail.com>
239     */
240    function render_toc($toc) {
241        global $lang;
242        $out = '<div class="tocheader">'.DOKU_LF;
243        $out .= $lang['toc'];
244        $out .= '</div>'.DOKU_LF;
245        $out .= '<div class="indexmenu_toc_inside">'.DOKU_LF;
246        $out .= html_buildlist($toc, 'toc', array($this, '_indexmenu_list_toc'), 'html_li_default', true);
247        $out .= '</div>'.DOKU_LF;
248        return $out;
249    }
250
251    /**
252     * Return the page abstract rendered to XHTML
253     */
254    function render_abstract($id, &$meta) {
255        $out = '<div class="tocheader">'.DOKU_LF;
256        $out .= '<a href="'.wl($id).'">';
257        $out .= ($meta['title']) ? htmlspecialchars($meta['title']) : htmlspecialchars(noNS($id));
258        $out .= '</a>'.DOKU_LF;
259        $out .= '</div>'.DOKU_LF;
260        if($meta['description']['abstract']) {
261            $out .= '<div class="indexmenu_toc_inside">'.DOKU_LF;
262            $out .= p_render('xhtml', p_get_instructions($meta['description']['abstract']), $info);
263            $out .= '</div>'.DOKU_LF.'</div>'.DOKU_LF;
264        }
265        return $out;
266    }
267
268    /**
269     * Callback for html_buildlist
270     */
271    function _indexmenu_list_toc($item) {
272        $id = cleanID($_REQUEST['id']);
273
274        if(isset($item['hid'])) {
275            $link = '#'.$item['hid'];
276        } else {
277            $link = $item['link'];
278        }
279
280        //prefix anchers with page id
281        if($link[0] == '#') {
282            $link = wl($id, $link, false, '');
283        }
284        return '<a href="'.$link.'">'.hsc($item['title']).'</a>';
285    }
286
287    /**
288     * Print index nodes
289     *
290     * @author Samuele Tognini <samuele@samuele.netsons.org>
291     * @author Andreas Gohr <andi@splitbrain.org>
292     * @author Rene Hadler <rene.hadler@iteas.at>
293     */
294    function print_index($ns) {
295        require_once(DOKU_PLUGIN.'indexmenu/syntax/indexmenu.php');
296        global $conf;
297        $idxm     = new syntax_plugin_indexmenu_indexmenu();
298        $ns       = $idxm->_parse_ns(rawurldecode($ns));
299        $level    = -1;
300        $max      = 0;
301        $data     = array();
302        $skipfile = array();
303        $skipns   = array();
304
305        if($_REQUEST['max'] > 0) {
306            $max   = $_REQUEST['max'];
307            $level = $max;
308        }
309        $nss         = ($_REQUEST['nss']) ? cleanID($_REQUEST['nss']) : '';
310        $idxm->sort  = $_REQUEST['sort'];
311        $idxm->msort = $_REQUEST['msort'];
312        $idxm->rsort = $_REQUEST['rsort'];
313        $idxm->nsort = $_REQUEST['nsort'];
314        $idxm->hsort = $_REQUEST['hsort'];
315        $fsdir       = "/".utf8_encodeFN(str_replace(':', '/', $ns));
316
317        $skipf = utf8_decodeFN($_REQUEST['skipfile']);
318        $skipfile[] = $this->getConf('skip_file');
319        if(isset($skipf)) {
320            $index = 0;
321            if($skipf[1] == '+') {
322                $index = 1;
323            }
324            $skipfile[$index] = substr($skipf, 1);
325        }
326        $skipn = utf8_decodeFN($_REQUEST['skipns']);
327        $skipns[] = $this->getConf('skip_index');
328        if(isset($skipn)) {
329            $index = 0;
330            if($skipn[1] == '+') {
331                $index = 1;
332            }
333            $skipns[$index] = substr($skipn, 1);
334        }
335
336        $opts = array(
337            'level'         => $level,
338            'nons'          => $_REQUEST['nons'],
339            'nss'           => array(array($nss, 1)),
340            'max'           => $max,
341            'js'            => false,
342            'nopg'          => $_REQUEST['nopg'],
343            'skip_index'    => $skipns,
344            'skip_file'     => $skipfile,
345            'headpage'      => $idxm->getConf('headpage'),
346            'hide_headpage' => $idxm->getConf('hide_headpage')
347        );
348        if($idxm->sort || $idxm->msort || $idxm->rsort || $idxm->hsort) {
349            $idxm->_search($data, $conf['datadir'], array($idxm, '_search_index'), $opts, $fsdir);
350        } else {
351            search($data, $conf['datadir'], array($idxm, '_search_index'), $opts, $fsdir);
352        }
353
354        $out = '';
355        if($_REQUEST['nojs']) {
356            require_once(DOKU_INC.'inc/html.php');
357            $out_tmp = html_buildlist($data, 'idx', array($idxm, "_html_list_index"), "html_li_index");
358            $out .= preg_replace('/<ul class="idx">(.*)<\/ul>/s', "$1", $out_tmp);
359        } else {
360            $nodes = $idxm->_jsnodes($data, '', 0);
361            $out   = "ajxnodes = [";
362            $out .= rtrim($nodes[0], ",");
363            $out .= "];";
364        }
365        return $out;
366    }
367}
368