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