1<?php 2/** 3 * TocTweak plugin for DokuWiki; helper component 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author Satoshi Sahara <sahara.satoshi@gmail.com> 7 */ 8if(!defined('DOKU_INC')) die(); 9 10class helper_plugin_toctweak extends DokuWiki_Plugin { 11 12 /** 13 * overwrite Hash data defined in plugin.info.txt 14 */ 15 function getPluginInfo(array $arrHash) { 16 $pluginInfoTxt = DOKU_PLUGIN.$this->getPluginName().'/plugin.info.txt'; 17 return array_merge(confToHash($pluginInfoTxt), $arrHash); 18 } 19 20 /** 21 * syntax parser 22 */ 23 function parse($param) { 24 25 // Ex: {{METATOC 2-4 width18 toc_hierarchical >id#section | title}} 26 27 // get tocTitle 28 if (strpos($param, '|') !== false) { 29 list($param, $tocTitle) = explode('|', $param); 30 // empty tocTitle will remove h3 'Table of Contents' headline 31 $tocTitle = trim($tocTitle); 32 } else { 33 $tocTitle = null; 34 } 35 36 // get id#section 37 list($param, $id) = explode('>', $param, 2); 38 list($id, $hash) = array_map('trim', explode('#', $id, 2)); 39 $id = cleanID($id).($hash ? '#'.$hash : ''); 40 41 // get other parameters 42 $params = explode(' ', $param); 43 foreach ($params as $token) { 44 if (empty($token)) continue; 45 46 // get TOC generation parameters, like "toptocleevl"-"maxtoclevel" 47 if (preg_match('/^(?:(\d+)-(\d+)|^(\-?\d+))$/', $token, $matches)) { 48 if (count($matches) == 4) { 49 if (strpos($matches[3], '-') !== false) { 50 $maxLv = abs($matches[3]); 51 } else { 52 $topLv = $matches[3]; 53 } 54 } else { 55 $topLv = $matches[1]; 56 $maxLv = $matches[2]; 57 } 58 59 if (isset($topLv)) { 60 $topLv = ($topLv < 1) ? 1 : $topLv; 61 $topLv = ($topLv > 5) ? 5 : $topLv; 62 } else { 63 $topLv = $this->getConf('toptoclevel'); 64 } 65 66 if (isset($maxLv)) { 67 $maxLv = ($maxLv > 5) ? 5 : $maxLv; 68 } else { 69 $maxLv = $this->getConf('maxtoclevel'); 70 } 71 continue; 72 } 73 74 // get class name for TOC box, ensure excluded any malcious character 75 if (!preg_match('/[^ A-Za-z0-9_-]/', $token)) { 76 $classes[] = $token; 77 } 78 } 79 if (!empty($classes)) { 80 $tocClass = implode(' ', $classes); 81 } else { 82 $tocClass = null; 83 } 84 85 return array($topLv, $maxLv, $tocClass, $tocTitle, $id); 86 } 87 88 /** 89 * Get customized toc array using metadata of the page 90 */ 91 function get_metatoc($id, $topLv=null, $maxLv=null, $headline='') { 92 global $ID, $INFO; 93 94 // retrieve TableOfContents from metadata 95 if ($id == $INFO['id']) { 96 $toc = $INFO['meta']['description']['tableofcontents']; 97 } else { 98 $toc = p_get_metadata($id,'description tableofcontents'); 99 } 100 if ($toc == null) return array(); 101 102 // get interested headline items 103 $toc = $this->_toc($toc, $topLv, $maxLv, $headline); 104 105 // modify toc array items directly within loop by reference 106 foreach ($toc as &$item) { 107 // add properties for toc of that is not current page 108 if ($id != $ID) { 109 // headlines should be found in other wiki page 110 $item['page'] = $id; 111 $item['url'] = wl($id).'#'.$item['hid']; 112 $item['class'] = 'wikilink1'; 113 } else { 114 // headlines in current page (internal link) 115 $item['url'] = '#'.$item['hid']; 116 } 117 } // end of foreach 118 unset($item); // break the reference with the last item 119 return $toc; 120 } 121 122 /** 123 * toc array filter 124 */ 125 function _toc(array $toc, $topLv=null, $maxLv=null, $headline='') { 126 global $conf; 127 $topLv = isset($topLv) ? $topLv : $this->getConf('toptoclevel'); 128 $maxLv = isset($maxLv) ? $maxLv : $this->getConf('maxtoclevel'); 129 130 $headline_matched = empty($headline); 131 $headline_level = null; 132 $items = array(); 133 134 foreach ($toc as $item) { 135 // skip non-interested toc entries 136 if ($headline) { 137 if (!$headline_matched) { 138 if ($item['hid'] == $headline) { 139 $headline_matched = true; 140 $headline_level = $item['level']; 141 } else { 142 continue; 143 } 144 } else { 145 if ($item['level'] <= $headline_level) { 146 $headline_matched = false; 147 $headline_level = null; 148 continue; 149 } 150 } 151 } 152 153 // get headline level in real page 154 $Lv = $item['level'] + $conf['toptoclevel'] -1; 155 156 // exclude out-of-range item based on headline level 157 if (($Lv < $topLv)||($Lv > $maxLv)) { 158 continue; 159 } else { // interested toc entry 160 $item['level'] = $Lv - $topLv +1; 161 } 162 $items[] = $item; 163 } 164 return $items; 165 } 166 167 /** 168 * convert auto-toc array to XHTML tailored with class attibute 169 */ 170 function html_toc(array $toc, ...$params) { 171 global $INFO; 172 $meta =& $INFO['meta']['toc']; 173 174 if ($this->getConf('tocminheads') == 0) return ''; 175 176 if (count($params)) { 177 // apply toc array filter 178 list($topLv, $maxLv, $headline) = $params; 179 $toc = $this->_toc($toc, $topLv, $maxLv, $headline); 180 } 181 182 if (count($toc) < $this->getConf('tocminheads')) { 183 $html = ''; 184 } else { 185 $html = html_TOC($toc); // use function in inc/html.php 186 if ($html && isset($meta['class'])) { 187 $search = '<div id="dw__toc"'; 188 $replace = $search.' class="'.hsc($meta['class']).'"'; 189 $html = str_replace($search, $replace, $html); 190 } 191 } 192 return $html; 193 } 194 195} 196 197