1<?php
2/**
3 * TocTweak plugin for DokuWiki; Action rendertoc
4 * move toc position in the page with optional css class
5 *
6 * developed from TOC plugin revision 1 (2009-09-23) by Andriy Lesyuk
7 * @see also http://projects.andriylesyuk.com/projects/toc
8 *
9 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
10 * @author     Satoshi Sahara <sahara.satoshi@gmail.com>
11 */
12
13if(!defined('DOKU_INC')) die();
14
15class action_plugin_toctweak_rendertoc extends DokuWiki_Action_Plugin {
16
17    const TOC_HERE = '<!-- TOC_HERE -->'.DOKU_LF;
18
19    /**
20     * Register event handlers
21     */
22    function register(Doku_Event_Handler $controller) {
23     // $controller->register_hook('DOKUWIKI_STARTED', 'BEFORE', $this, '_initTocConfig');
24        $controller->register_hook('ACTION_ACT_PREPROCESS','BEFORE', $this, 'handleActPreprocess');
25        $controller->register_hook('PARSER_CACHE_USE', 'BEFORE', $this, 'handleParserCache');
26
27        $controller->register_hook('RENDERER_CONTENT_POSTPROCESS', 'BEFORE', $this, 'handlePostProcess');
28        $controller->register_hook('TPL_ACT_RENDER', 'BEFORE', $this, 'handleActRender');
29     // $controller->register_hook('TPL_TOC_RENDER', 'BEFORE', $this, 'handleTocRender');
30        $controller->register_hook('TPL_CONTENT_DISPLAY', 'BEFORE', $this, 'handleContentDisplay');
31    }
32
33
34    /*
35     * Overwrite toc config parameters to catch up all headings in pages
36     *  toptoclevel : highest headline level which can appear in table of contents
37     *  maxtoclevel : lowest headline level to include in table of contents
38     *  tocminheads : minimum amount of headlines to show table of contents
39     */
40    function _setTocConfig() {
41        global $conf;
42        $active = $this->getConf('tocAllHeads');
43        if ($conf['toptoclevel'] != 1) {
44            $conf['toptoclevel'] = $active ? 1 : $this->getConf('toptoclevel');
45        }
46        if ($conf['maxtoclevel'] != 5) {
47            $conf['maxtoclevel'] = $active ? 5 : $this->getConf('maxtoclevel');
48        }
49        if ($conf['tocminheads'] != $this->getConf('tocminheads')) {
50            $conf['tocminheads'] = $this->getConf('tocminheads');
51        }
52    }
53
54    /**
55     * DOKUWIKI_STARTED
56     */
57    function _initTocConfig(Doku_Event $event, $param) {
58        $this->_setTocConfig();
59    }
60
61    /**
62     * ACTION_ACT_PREPROCESS
63     * catch action mode before dispacher begins to process the $ACT variable
64     */
65    function handleActPreprocess(Doku_Event $event, $param) {
66        global $conf, $ACT, $ID;
67        if ($event->data == 'admin') {
68            // admin plugins such as the Config Manager may have own TOC
69            // that might be depend on global toc parameters?
70            $conf['toptoclevel'] = 1;
71            $conf['maxtoclevel'] = 3;
72            $conf['tocminheads'] = 3;
73        } else {
74            $this->_setTocConfig();
75        }
76        return;
77    }
78
79    /**
80     * PARSER_CACHE_USE
81     * manipulate cache validity (to get correct toc of other page)
82     */
83    function handleParserCache(Doku_Event $event, $param) {
84        global $conf;
85
86        $this->_setTocConfig(); //!! ensure correct toc parameters have set
87
88        $cache =& $event->data;
89
90        if (!$cache->page) return;
91
92        switch ($cache->mode) {
93            case 'i':        // instruction cache
94            case 'metadata': // metadata cache
95                break;
96            case 'xhtml':    // xhtml cache
97                // request check with additional dependent files
98                $depends = p_get_metadata($cache->page, 'relation toctweak');
99                if (!$depends) break;
100                $cache->depends['files'] = ($cache->depends['files'])
101                        ? array_merge($cache->depends['files'], $depends)
102                        : $depends;
103        } // end of switch
104        return;
105    }
106
107    /**
108     * RENDERER_CONTENT_POSTPROCESS
109     * replace placeholder set by autotoc syntax component with html_toc
110     * this must happen only when tocPosition is -1
111     * Note: Do not add/insert html of auto-toc in this event handler
112     * to avoid locale text (such as preview.txt) has unwanted toc box.
113     */
114    function handlePostProcess(Doku_Event $event, $param) {
115        global $ACT, $INFO;
116        $meta =& $INFO['meta']['toc'];
117
118        // Action mode check
119        if (!in_array($ACT, ['show','preview'])) {
120            return;
121        }
122
123        // TOC Position check
124        $tocPosition = @$meta['position'] ?: $this->getConf('tocPosition');
125        if ($ACT == 'preview') {
126            if (strpos($event->data[1], self::TOC_HERE) !== false) {
127                $tocPosition = -1;
128            }
129        }
130        if ($tocPosition != -1) {
131            return;
132        }
133
134        // retrieve toc config parameters from metadata
135        $topLv = @$meta['toptoclevel'] ?: $this->getConf('toptoclevel');
136        $maxLv = @$meta['maxtoclevel'] ?: $this->getConf('maxtoclevel');
137        $headline = '';
138        $toc = @$INFO['meta']['description']['tableofcontents'] ?: array();
139
140        // load helper object
141        isset($tocTweak) || $tocTweak = $this->loadHelper($this->getPluginName());
142
143        // prepare html of table of content
144        $html_toc = $tocTweak->html_toc($toc, $topLv, $maxLv, $headline);
145
146        // replace PLACEHOLDER with html_toc
147        $event->data[1] = str_replace(self::TOC_HERE, $html_toc, $event->data[1], $count);
148        return;
149    }
150
151    /**
152     * TPL_ACT_RENDER
153     * hide auto-toc that is to be rendered in handleContentDisplay()
154     */
155    function handleActRender(Doku_Event $event, $param) {
156        global $ACT, $INFO;
157
158        // Action mode check
159        if (in_array($ACT, ['show','preview'])) {
160            $INFO['prependTOC'] = false;
161        }
162        return;
163    }
164
165    /**
166     * TPL_TOC_RENDER
167     * Pre-/postprocess the TOC array
168     */
169    function handleTocRender(Doku_Event $event, $param) {
170        global $ACT, $INFO;
171        $meta =& $INFO['meta']['toc'];
172
173        // Action mode check
174        if (!in_array($ACT, ['show','preview'])) {
175            return;
176        }
177
178        // retrieve toc config parameters from metadata
179        $topLv = @$meta['toptoclevel'] ?: $this->getConf('toptoclevel');
180        $maxLv = @$meta['maxtoclevel'] ?: $this->getConf('maxtoclevel');
181        $headline = '';
182        $toc = $event->data ?: array(); // data is reference to global $TOC
183
184        // load helper object
185        isset($tocTweak) || $tocTweak = $this->loadHelper($this->getPluginName());
186
187        $event->data = $tocTweak->_toc($toc, $topLv, $maxLv, $headline);
188    }
189
190    /**
191     * TPL_CONTENT_DISPLAY
192     * insert XHTML of auto-toc at tocPosition where
193     *  0: top of the content (default)
194     *  1: after the first level 1 heading
195     *  2: after the first level 2 heading
196     *  6: after the first heading
197     */
198    function handleContentDisplay(Doku_Event $event, $param) {
199        global $ACT, $INFO;
200        $meta =& $INFO['meta']['toc'];
201
202        // Action mode check
203        if (!in_array($ACT, ['show','preview'])) {
204            return;
205        }
206
207        // TOC Position check
208        $tocPosition = @$meta['position'] ?: $this->getConf('tocPosition');
209        if (!in_array($tocPosition, [0,1,2,6])) {
210            return;
211        }
212
213        // retrieve toc config parameters from metadata
214        $topLv = @$meta['toptoclevel'] ?: $this->getConf('toptoclevel');
215        $maxLv = @$meta['maxtoclevel'] ?: $this->getConf('maxtoclevel');
216        $headline = '';
217        $toc = @$INFO['meta']['description']['tableofcontents'] ?: array();
218
219        // load helper object
220        isset($tocTweak) || $tocTweak = $this->loadHelper($this->getPluginName());
221
222        // prepare html of table of content
223        $html_toc = $tocTweak->html_toc($toc, $topLv, $maxLv, $headline);
224
225        // get html content of current page from event data, exclude editor UI
226        if ($ACT == 'preview') {
227            $search = '<div class="preview"><div class="pad">';
228            $offset = strpos($event->data, $search) + strlen($search);
229            $content = substr($event->data, $offset);
230        } else {
231            $content = $event->data;
232        }
233
234        // Step 1: set PLACEHOLDER based on tocPostion config setting
235        if ($tocPosition == 0) {
236            $content = self::TOC_HERE.$content;
237            $count = 1;
238        } else {
239            $search  = '#</(h'.(($tocPosition == 6) ? '[1-6]' : $tocPosition).')>#';
240            $replace = '</$1>'.self::TOC_HERE;
241            $content= preg_replace($search, $replace, $content, 1, $count);
242            if (!$count) {
243                // show toc original position if placeholder replacement failed
244                $content = self::TOC_HERE.$content;
245                $count = 1;
246            }
247            unset($search, $replace);
248        }
249
250        // Step 2: replace PLACEHOLDER with html_toc
251        if ($count > 0) {
252            // try to replace placeholder according to tocPostion
253            $content = str_replace(self::TOC_HERE, $html_toc, $content, $count);
254        }
255
256        // reflect content to event data
257        if ($ACT == 'preview') {
258            $event->data = substr($event->data, 0, $offset).$content;
259        } else {
260            $event->data = $content;
261        }
262        return;
263    }
264
265}
266