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