1<?php 2/** 3 * EditSections2 Plugin for DokuWiki / action.php 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author Ben Coburn <btcoburn@silicodon.net> 7 * @author Kazutaka Miyasaka <kazmiya@gmail.com> 8 */ 9 10// must be run within DokuWiki 11if (!defined('DOKU_INC')) { 12 die(); 13} 14 15if (!defined('DOKU_PLUGIN')) { 16 define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/'); 17} 18 19require_once(DOKU_PLUGIN . 'action.php'); 20 21class action_plugin_editsections2 extends DokuWiki_Action_Plugin 22{ 23 var $section_levels = null; 24 25 /** 26 * Returns some info 27 */ 28 function getInfo() 29 { 30 return confToHash(DOKU_PLUGIN . '/editsections2/plugin.info.txt'); 31 } 32 33 /** 34 * Registers event handlers 35 */ 36 function register(&$controller) 37 { 38 if (function_exists('html_secedit_get_button')) { 39 $controller->register_hook( 40 'DOKUWIKI_STARTED', 'BEFORE', 41 $this, 'exportToJSINFO' 42 ); 43 44 $controller->register_hook( 45 'PARSER_HANDLER_DONE', 'BEFORE', 46 $this, 'handleInstructions' 47 ); 48 49 $controller->register_hook( 50 'RENDERER_CONTENT_POSTPROCESS', 'BEFORE', 51 $this, 'handleHtmlContent' 52 ); 53 } else { 54 // for DokuWiki Lemming or earlier 55 $controller->register_hook( 56 'PARSER_HANDLER_DONE', 'BEFORE', 57 $this, 'rewriteEditInstructions' 58 ); 59 } 60 } 61 62 /** 63 * Exports configuration settings to $JSINFO 64 */ 65 function exportToJSINFO(&$event) 66 { 67 global $JSINFO; 68 69 $JSINFO['plugin_editsections2'] = array( 70 'highlight_target' => $this->getConf('highlight_target'), 71 ); 72 } 73 74 /** 75 * Prepends special instruction to put dummy section edit marker 76 */ 77 function handleInstructions(&$event, $param) 78 { 79 $calls =& $event->data->calls; 80 81 // prepend plugin's instruction to the beginning of instructions 82 // (this instruction will be processed by render() in syntax.php) 83 array_unshift( 84 $calls, 85 array('plugin', array('editsections2', array(), 0)) 86 ); 87 88 // keeps section levels for hierarchical mode 89 if ($this->getConf('order_type')) { 90 $this->setSectionLevels($calls); 91 } 92 } 93 94 /** 95 * Replaces section edit markers 96 */ 97 function handleHtmlContent(&$event, $param) 98 { 99 if ($event->data[0] !== 'xhtml') { 100 return; 101 } 102 103 $doc =& $event->data[1]; 104 105 $marker_regexp = 106 '/<!-- EDIT(\d+) SECTION "([^"]*)" \[(\-?\d+)-(\d*)\] -->/'; 107 108 if (preg_match_all($marker_regexp, $doc, $matches)) { 109 list($markers, $secids, $titles, $starts, $ends) = $matches; 110 } else { 111 return; 112 } 113 114 $markers_search = array_reverse($markers); 115 116 // build $markers_replace 117 if ($this->getConf('order_type')) { 118 // mode: hierarchical 119 $markers_replace = array(); 120 121 // in case instruction is cached 122 if ($this->section_levels === null) { 123 $this->setSectionLevels(); 124 } 125 126 // calculate nested section ranges and build new markers 127 for ($i = 1, $i_max = count($markers); $i < $i_max; $i++) { 128 $markers_replace[] = sprintf( 129 '<!-- EDIT%s SECTION "%s" [%s-%s] -->', 130 $secids[$i], 131 $titles[$i], 132 $starts[$i], 133 $ends[$this->findNestedSectionEnd($i)] 134 ); 135 } 136 137 $markers_replace = array_merge( 138 array(''), 139 array_reverse($markers_replace) 140 ); 141 } else { 142 // mode: flat (shift button positions only) 143 $markers_replace = array_merge( 144 array(''), 145 array_slice($markers_search, 0, -1) 146 ); 147 } 148 149 // shift edit button positions 150 $doc = str_replace($markers_search, $markers_replace, $doc); 151 } 152 153 /** 154 * Keeps section levels 155 */ 156 function setSectionLevels($calls = null) 157 { 158 global $ID; 159 global $conf; 160 161 if ($calls === null) { 162 $calls = p_cached_instructions(wikiFN($ID), 'cacheonly'); 163 } 164 165 list($handler_name, $instructions) = array(0, 1); 166 $this->section_levels = array('dummy_entry_for_padding'); 167 168 for ($i = 0, $i_max = count($calls); $i < $i_max; $i++) { 169 if ($calls[$i][$handler_name] === 'section_open') { 170 $section_level = $calls[$i][$instructions][0]; 171 172 if ($section_level <= $conf['maxseclevel']) { 173 $this->section_levels[] = $section_level; 174 } 175 } 176 } 177 } 178 179 /** 180 * Finds the end of nested edit sections 181 */ 182 function findNestedSectionEnd($offset_idx) 183 { 184 $offset_level = $this->section_levels[$offset_idx]; 185 $end_idx = $offset_idx; 186 $i_max = count($this->section_levels); 187 188 for ($i = $offset_idx + 1; $i < $i_max; $i++) { 189 if ($this->section_levels[$i] > $offset_level) { 190 $end_idx = $i; 191 } else { 192 break; 193 } 194 } 195 196 return $end_idx; 197 } 198 199 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 200 201 /** 202 * Rewrites instructions (for DokuWiki Lemming or earlier) 203 */ 204 function rewriteEditInstructions(&$event, $param) 205 { 206 // get the instructions list from the handler 207 $calls =& $event->data->calls; 208 209 // index numbers for readability 210 list($handler_name, $instructions) = array(0, 1); 211 list($pos_start, $pos_end, $sec_level) = array(0, 1, 2); 212 213 // scan instructions for edit 214 $sections = array(); 215 216 for ($i = 0, $i_max = count($calls); $i < $i_max; $i++) { 217 if ($calls[$i][$handler_name] === 'section_edit') { 218 $sections[] =& $calls[$i][$instructions]; 219 } 220 } 221 222 $section_count = count($sections); 223 224 // no need to rewrite 225 if ($section_count < 2) { 226 return; 227 } 228 229 // rewrite instructions 230 if ($this->getConf('order_type')) { 231 // mode: hierarchical 232 for ($i = 0, $i_max = $section_count - 1; $i < $i_max; $i++) { 233 // shift instructions 234 $sections[$i] = $sections[$i + 1]; 235 236 // set default pos_end value (end of the wiki page) 237 $sections[$i][$pos_end] = 0; 238 239 // find and set the end point of hierarchical section 240 $level = $sections[$i][$sec_level]; 241 242 for ($j = $i + 2; $j < $section_count; $j++) { 243 if ($level >= $sections[$j][$sec_level]) { 244 $sections[$i][$pos_end] = $sections[$j][$pos_start] - 1; 245 break; 246 } 247 } 248 } 249 } else { 250 // mode: flat (shift instructions only) 251 for ($i = 0, $i_max = $section_count - 1; $i < $i_max; $i++) { 252 $sections[$i] = $sections[$i + 1]; 253 } 254 } 255 256 // hide old last section 257 $sections[$section_count - 1][$pos_start] = -1; 258 } 259} 260