1<?php 2/** 3 * DokuWiki Plugin editsections (Action Component) 4 * 5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6 * @author Christophe Drevet <dr4ke@dr4ke.net> 7 */ 8 9// must be run within Dokuwiki 10if (!defined('DOKU_INC')) die(); 11 12if (!defined('DOKU_LF')) define('DOKU_LF', "\n"); 13if (!defined('DOKU_TAB')) define('DOKU_TAB', "\t"); 14if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); 15 16require_once DOKU_PLUGIN.'action.php'; 17include_once DOKU_INC.'inc/infoutils.php'; 18 19class action_plugin_editsections_es extends DokuWiki_Action_Plugin { 20 21 var $sections; 22 23 function register(&$controller) { 24 $doku_version = getVersionData(); 25 if ( preg_match('/201.-/', $doku_version['date']) > 0 ) { 26 // 2010 or later version 27 $controller->register_hook('PARSER_HANDLER_DONE', 'BEFORE', $this, 'rewrite_sections'); 28 $controller->register_hook('HTML_SECEDIT_BUTTON', 'BEFORE', $this, '_editbutton'); 29 } else { 30 // 2009 or earlier version 31 $controller->register_hook('PARSER_HANDLER_DONE', 'BEFORE', $this, 'rewrite_sections_legacy'); 32 } 33 if ($this->getConf('cache') === 'disabled') { 34 $controller->register_hook('PARSER_CACHE_USE', 'BEFORE', $this, '_cache_use'); 35 } 36 $controller->register_hook('DOKUWIKI_STARTED', 'AFTER', $this, '_addconf'); 37 } 38 39 function _addconf(&$event, $ags) { 40 // add conf to JSINFO exported variable 41 global $JSINFO; 42 $doku_version = getVersionData(); 43 if ( preg_match('/201.-/', $doku_version['date']) > 0 and $this->getConf('cache') === 'enabled') { 44 // dokuwiki >= 2010-11-07 and cache not disabled 45 $JSINFO['es_order_type'] = 'flat'; 46 } else { 47 $JSINFO['es_order_type'] = $this->getConf('order_type'); 48 } 49 } 50 51 function _editbutton(&$event, $param) { 52 dbglog('HTML_SECEDIT_BUTTON hook', 'editsections plugin'); 53 $order = $this->getConf('order_type'); 54 if (count($this->sections) === 0) { 55 dbglog('cache in use, reset edit section name'); 56 // When the page is in cache, the sections are not preprocessed and 57 // existing section names are useless: they reference the next section 58 // so, we replace them by a section number 59 if ( preg_match('/[0-9]$/', $event->data['range']) > 0 ) { 60 $event->data['name'] = 'section '.$event->data['secid']; 61 } else { 62 $event->data['name'] = ''; 63 } 64 return; 65 } 66 if ($event->data['target'] === 'section') { 67 $ind = $event->data['secid']; 68 // Compute new values 69 $last_ind = count($this->sections) - 1; 70 $start = $this->sections[$ind]['start']; 71 $event->data['name'] = $this->sections[$ind]['name']; 72 if ( $order === 'flat') { 73 // flat editing 74 $event->data['range'] = strval($start).'-'.strval($this->sections[$ind]['end']); 75 } elseif ( $order === 'nested' ) { 76 // search end of nested section editing 77 $end_ind = $ind; 78 while ( ($end_ind + 1 <= $last_ind) and ($this->sections[$end_ind + 1]['level'] > $this->sections[$ind]['level']) ) { 79 $end_ind++; 80 } 81 $event->data['range'] = strval($start).'-'.strval($this->sections[$end_ind]['end']); 82 if ($end_ind > $ind) { 83 $event->data['name'] .= ' -> '.$this->sections[$end_ind]['name']; 84 } 85 } else { 86 dbglog('ERROR (plugin editsections): section editing type unknown ('.$order.')'); 87 } 88 } 89 } 90 91 function _cache_use(&$event, $ags) { 92 dbglog('PARSER_CACHE_USE hook', 'editsections plugin'); 93 global $ID; 94 if ( auth_quickaclcheck($ID) >= AUTH_EDIT ) { 95 // disable cache only for writers 96 $event->_default = 0; 97 } 98 } 99 function rewrite_sections(&$event, $ags) { 100 dbglog('PARSER_HANDLER_DONE hook', 'editsections plugin'); 101 // get the instructions list from the handler 102 $calls =& $event->data->calls; 103 $edits = array(); 104 $order = $this->getConf('order_type'); 105 106 // fake section inserted in first position in order to have an edit button before the first section 107 $fakesection = array( array( 'header', // header entry 108 array ( $calls[0][1][0], // Reuse original header name because Dokuwiki 109 // may use the first heading for the page name. 110 0, // level 0 since this is not a real header 111 1), // start : will be overwritten in the following loop 112 1), // start : will be overwritten in the following loop 113 array ( 'section_open', // section_open entry 114 array(0), // level 115 1), // start : will be overwritten in the following loop 116 array ( 'section_close', // section_close entry 117 array(), // 118 1) // end : will be overwritten in the following loop 119 ); 120 $calls = array_merge($fakesection, $calls); 121 // store all sections in a separate array to compute their start, end... 122 $this->sections = array(); 123 $count = 0; 124 foreach( $calls as $index => $value ) { 125 if ($value[0] === 'header') { 126 $count += 1; 127 $this->sections[] = array( 'level' => $value[1][1], 128 'start' => $value[2], 129 'name' => $value[1][0], 130 'header' => $index ); 131 } 132 if ($value[0] === 'section_open') { 133 if ($value[1][0] !== $this->sections[$count - 1]['level']) { 134 } 135 if ($value[2] !== $this->sections[$count - 1]['start']) { 136 } 137 $this->sections[$count - 1]['open'] = $index; 138 } 139 if ($value[0] === 'section_close') { 140 $this->sections[$count - 1]['end'] = $value[2]; 141 $this->sections[$count - 1]['close'] = $index; 142 } 143 } 144 // Compute new values 145 $h_ind = -1; // header index 146 $o_ind = -1; // open section index 147 $c_ind = -1; // close section index 148 $last_ind = count($this->sections) - 1; 149 foreach( $this->sections as $index => $value ) { 150 // set values in previous header 151 if ( $h_ind >= 0 ) { 152 // set start of section 153 $calls[$h_ind][1][2] = $value['start']; 154 $calls[$h_ind][2] = $value['start']; 155 } 156 // set values in previous section_open 157 if ( $o_ind >= 0 ) { 158 // set start of section 159 $calls[$o_ind][2] = $value['start']; 160 } 161 // set values in previous section_close 162 if ( $c_ind >= 0 ) { 163 // set end of section 164 $calls[$c_ind][2] = $value['end']; 165 } 166 // store indexes 167 $h_ind = $value['header']; 168 $o_ind = $value['open']; 169 $c_ind = $value['close']; 170 } 171 // Now, set values for the last section start = end = last byte of the page 172 // If not set, the last edit button disappear and the last section can't be edited 173 // without editing entire page 174 if ( $h_ind >= 0 ) { 175 // set start of section 176 $calls[$h_ind][1][2] = $this->sections[$last_ind][end]; 177 $calls[$h_ind][2] = $this->sections[$last_ind][end]; 178 } 179 if ( $o_ind >= 0 ) { 180 // set start of section 181 $calls[$o_ind][2] = $this->sections[$last_ind][end]; 182 } 183 if ( $c_ind >= 0 ) { 184 // set end of section 185 $calls[$c_ind][2] = $this->sections[$last_ind][end]; 186 } 187 } 188 189 function rewrite_sections_legacy(&$event, $ags) { 190 // get the instructions list from the handler 191 $calls =& $event->data->calls; 192 $edits = array(); 193 $order = $this->getConf('order_type'); 194 195 // scan instructions for edit sections 196 $size = count($calls); 197 for ($i=0; $i<$size; $i++) { 198 if ($calls[$i][0]=='section_edit') { 199 $edits[] =& $calls[$i]; 200 } 201 } 202 203 // rewrite edit section instructions 204 $last = max(count($edits)-1,0); 205 for ($i=0; $i<=$last; $i++) { 206 $end = 0; 207 // get data to move 208 $start = $edits[min($i+1,$last)][1][0]; 209 $level = $edits[min($i+1,$last)][1][2]; 210 $name = $edits[min($i+1,$last)][1][3]; 211 // find the section end point 212 if ($order === 'nested') { 213 $finger = $i+2; 214 while (isset($edits[$finger]) && $edits[$finger][1][2]>$level) { 215 $finger++; 216 } 217 if (isset($edits[$finger])) { 218 $end = $edits[$finger][1][0]-1; 219 } 220 } else { 221 $end = $edits[min($i+1,$last)][1][1]; 222 } 223 // put the data back where it belongs 224 $edits[$i][1][0] = $start; 225 $edits[$i][1][1] = $end; 226 $edits[$i][1][2] = $level; 227 $edits[$i][1][3] = $name; 228 } 229 $edits[max($last-1,0)][1][1] = 0; // set new last section 230 $edits[$last][1][0] = -1; // hide old last section 231 } 232 233} 234 235// vim:ts=4:sw=4:et:enc=utf-8: 236