xref: /plugin/siteexport/syntax/toc.php (revision f3b6399273519478306b68585a3f0b607340b73b)
1<?php
2/**
3 * Search with Scopes
4 *
5 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author     i-net software <tools@inetsoftware.de>
7 * @author     Gerry Weissbach <gweissbach@inetsoftware.de>
8 */
9
10// must be run within Dokuwiki
11if(!defined('DOKU_INC')) die();
12if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
13
14require_once(DOKU_PLUGIN.'syntax.php');
15
16class syntax_plugin_siteexport_toc extends DokuWiki_Syntax_Plugin {
17
18	var $insideToc = false;
19	var $savedToc = array();
20
21	var $mergedPages = array();
22	var $includedPages = array();
23
24	function getType() { return 'protected'; }
25	function getPType() { return 'block'; }
26	function getAllowedTypes() { return array('container'); }
27	function getSort() { return 100; }
28
29	/**
30	 * for backward compatability
31	 * @see inc/DokuWiki_Plugin#getInfo()
32	 */
33    function getInfo(){
34        if ( method_exists(parent, 'getInfo')) {
35            $info = parent::getInfo();
36        }
37        return is_array($info) ? $info : confToHash(dirname(__FILE__).'/../plugin.info.txt');
38    }
39
40	/**
41	 * Connect pattern to lexer
42	 */
43	function connectTo($mode) {
44		$this->Lexer->addEntryPattern('<toc>(?=.*?</toc>)',$mode,'plugin_siteexport_toc');
45		$this->Lexer->addEntryPattern('<toc .+?>(?=.*?</toc>)',$mode,'plugin_siteexport_toc');
46		$this->Lexer->addSpecialPattern("\[\[.+?\]\]",$mode,'plugin_siteexport_toc');
47	}
48
49	function postConnect() {
50		$this->Lexer->addExitPattern('</toc.*?>', 'plugin_siteexport_toc');
51	}
52
53	function handle($match, $state, $pos, &$handler) {
54		global $ID, $INFO;
55
56		switch ($state) {
57			case DOKU_LEXER_ENTER:
58
59				$this->insideToc = true;
60
61				$options = explode(' ', substr($match, 5, -1));
62				return array('start' => true, 'pos' => $pos, 'options' => $options);
63				break;
64
65			case DOKU_LEXER_SPECIAL:
66
67				if ( $this->insideToc ) {
68
69					$link = preg_replace(array('/^\[\[/','/\]\]$/u'),'',$match);
70					// Split title from URL
71					$link = explode('|',$link,2);
72					if ( !isset($link[1]) ) {
73						$link[1] = NULL;
74					} else if ( preg_match('/^\{\{[^\}]+\}\}$/',$link[1]) ) {
75						// If the title is an image, convert it to an array containing the image details
76						$link[1] = Doku_Handler_Parse_Media($link[1]);
77					}
78					$link[0] = trim($link[0]);
79
80					if ( ! (preg_match('/^[a-zA-Z0-9\.]+>{1}.*$/u',$link[0]) ||
81					preg_match('/^\\\\\\\\[\w.:?\-;,]+?\\\\/u',$link[0]) ||
82					preg_match('#^([a-z0-9\-\.+]+?)://#i',$link[0]) ||
83					preg_match('<'.PREG_PATTERN_VALID_EMAIL.'>',$link[0]) ||
84					preg_match('!^#.+!',$link[0]) )
85					) {
86
87						// Get current depth from call stack
88						$depth = 1;
89						if ( $handler->CallWriter instanceof Doku_Handler_List ) {
90
91							$calls = array_reverse($handler->CallWriter->calls);
92							$call = $calls[0];
93							foreach ( $calls as $item ) {
94								if ( in_array( $item[0], array( 'list_item', 'list_open') ) ) { $call = $item; break;}
95							}
96
97							$depth = $handler->CallWriter->interpretSyntax($call[1][0], $listType);
98
99						}
100
101						if ( empty( $link[0] ) ) { break; } // No empty elements. This would lead to problems
102						return array($link[0], $link[1], $depth);
103						break;
104					} else {
105						// use parser! - but with another p
106						$handler->internallink($match, $state, $pos);
107					}
108				} else {
109					// use parser!
110					$handler->internallink($match, $state, $pos);
111				}
112
113				return false;
114			case DOKU_LEXER_UNMATCHED:
115
116				$handler->_addCall('cdata',array($match), $pos);
117				return false;
118				break;
119			case DOKU_LEXER_EXIT:
120
121				$this->insideToc = false;
122				return 'save__meta';
123				break;
124		}
125		return false;
126	}
127
128	function render($mode, &$renderer, $data) {
129		global $ID, $lang, $INFO;
130
131		list( $SID, $NAME, $DEPTH ) = $data;
132
133		resolve_pageid(getNS($ID),$SID,$exists);
134//		$SID = cleanID($SID); // hier kein cleanID, da sonst m�glicherweise der anker verloren geht
135
136        //    Render XHTML and ODT
137		if ($mode == 'xhtml' || $mode == 'odt') {
138
139		    // TOC Title
140			if ( isset($data['start']) ) {
141
142			    if ( is_Array($data['options']) ) {
143                    foreach( $data['options'] as $opt ) {
144    					switch( $opt ) {
145    						case 'description' : $renderer->meta['sitetoc']['showDescription'] = true; break;
146    						case 'notoc' : $renderer->meta['sitetoc']['noTOC'] = true; break;
147    						case 'merge' : $renderer->meta['sitetoc']['mergeDoc'] = true; break;
148    						case 'nohead' : $renderer->meta['sitetoc']['noTocHeader'] = true; break;
149    					}
150    				}
151			    }
152
153				$renderer->section_open("1 sitetoc");
154				if ( $renderer->meta['sitetoc']['noTocHeader'] === false ) {
155					$renderer->header($lang['toc'], 1, $data['pos']);
156				}
157
158				return true;
159			}
160
161			// All Output has been done
162			if ( !is_array($data) && $data == 'save__meta' ) {
163
164				// Close TOC
165				$renderer->section_close();
166
167				if ( $renderer->meta['sitetoc']['noTOC'] === true ) {
168					$renderer->doc = preg_replace("/<div.*?sitetoc.*?$/si", "", $renderer->doc);
169				}
170
171				// If this is not set, we may have it as Metadata
172				if ( !$this->mergedPages && $renderer->meta['sitetoc']['mergeDoc'] ) {
173					$toc = $renderer->meta['sitetoc']['siteexportTOC'];
174					if ( is_array($toc)) {
175						foreach ($toc as $tocItem ) {
176							$this->mergedPages[] = $tocItem['id'];
177						}
178					}
179				}
180
181				// If there is some data to be merged
182				if ( count($this->mergedPages) > 0) {
183
184					$renderer->section_open("1 mergedsite");
185
186					// Prepare lookup Array
187					foreach ( $this->mergedPages as $tocItem ) {
188						$this->includedPages[] = array_shift(explode('#', $tocItem));
189					}
190
191					// Print merged pages
192					foreach ( $this->mergedPages as $tocItem ) {
193						$this->_render_output($renderer,$tocItem, $mode);
194					}
195
196					$renderer->section_close();
197				}
198				return true;
199			}
200
201			// Save the current ID
202			$LNID = $SID;
203
204			// Add ID to flags['mergeDoc']
205			if ( $renderer->meta['sitetoc']['mergeDoc'] === true ) { // || (count($renderer->meta['sitetoc']['siteexportTOC']) > 0 && $renderer->meta['sitetoc']['siteexportMergeDoc'] === true) ) {
206				$this->mergedPages[] = $SID;
207				$default = $renderer->_simpleTitle($SID); $isImage = false;
208				resolve_pageid(getNS($ID),$SID,$exists);
209
210				$NAME = empty($NAME) ? p_get_first_heading($SID,true) : $NAME;
211				$LNID = "$ID#" . sectionID($SID, $check);
212			}
213
214			// Print normal internal link (XHTML odt)
215			$renderer->internallink($LNID, $NAME, null);
216
217			// Display Description underneath
218			if ( $renderer->meta['sitetoc']['showDescription'] === true ) {
219				// $renderer->p_open();
220				$renderer->cdata(p_get_metadata($SID, 'description abstract', true));
221				// $renderer->p_close();
222			}
223
224			// Render Metadata
225		} else if ($mode == 'metadata') {
226			if ( !is_array($data) && $data == 'save__meta' ) {
227				$renderer->meta['sitetoc']['siteexportTOC'] = $this->savedToc;
228
229                foreach ($this->savedToc as $page) {
230                    $renderer->meta['relation']['references'][$page['id']] = $page['exists'];
231                }
232
233				$this->savedToc = array();
234			} else if ( !isset($data['start']) && !isset($data['pos']) ) {
235				$this->savedToc[] = $this->__addTocItem($SID, $NAME, $DEPTH, $renderer);
236			}
237		} else {
238			return false;
239		}
240
241		return true;
242	}
243
244	/*
245	 * pull apart the ID and create an Entry for the TOC
246	 */
247	function __addTocItem($id, $name, $depth, $renderer) {
248		global $conf;
249		global $ID;
250
251		// Render Title
252		$default = $renderer->_simpleTitle($id);
253		$exists = false; $isImage = false; $linktype = null;
254		resolve_pageid(getNS($ID),$id,$exists);
255		$name = $renderer->_getLinkTitle($name, $default, $isImage, $id, $linktype);
256
257		//keep hash anchor
258		list($id,$hash) = explode('#',$id,2);
259		if(!empty($hash)) $hash = $renderer->_headerToLink($hash);
260
261		// Build Sitetoc Item
262		$item = array();
263		$item['id'] = $id;
264		$item['name'] = $name;
265		$item['anchor'] = $hash;
266		$item['depth'] = $depth;
267		$item['exists'] = $exists;
268		if(!$conf['skipacl'] && auth_quickaclcheck($item['id']) < AUTH_READ){
269			return false;
270		}
271
272		return $item;
273	}
274
275	/*
276	 * Render the output of one page
277	 */
278	function _render_output($renderer, $addID, $mode) {
279		global $ID;
280
281		//get data(in instructions format) from $file (dont use cache: false)
282		$file    = wikiFN($addID);
283		$instr   = p_cached_instructions($file, false);
284
285		//page was empty
286		if (empty($instr)) {
287			return;
288		}
289
290		// Convert Link instructions
291		$instr   = $this->_convertInstructions($instr, $addID, $renderer);
292
293
294		// Section IDs
295		$check = null;
296		$addID = sectionID($addID, $check);	//not possible to use a:b:c for id
297
298		if ( $mode == 'xhtml' ) {
299			//--------RENDER
300			//renderer information(TOC build / Cache used)
301			$info = array();
302			$content = p_render($mode, $instr, $info);
303
304			//Remove TOC`s, section edit buttons and tags
305			$content = $this->_cleanXHTML($content);
306
307
308			// embed the included page
309			$renderer->doc .= '<div class="include">';
310			//add an anchor to find start of a inserted page
311			$renderer->doc .= "<a name='$addID' id='$addID'>";
312			$renderer->doc .= $content;
313			$renderer->doc .= '</div>';
314		} else if ( $mode == 'odt') {
315
316			$renderer->doc .= '<text:bookmark text:name="'.$addID.'"/>';
317
318			// Loop through the instructions
319			foreach ( $instr as $instruction ) {
320				// Execute the callback against the Renderer
321				call_user_func_array(array($renderer, $instruction[0]),$instruction[1]);
322			}
323		}
324	}
325
326
327	/*
328	 * Corrects relative internal links and media and
329	 * converts headers of included pages to subheaders of the current page
330	 */
331	function _convertInstructions($instr, $id, &$renderer) {
332		global $ID;
333		global $conf;
334
335		$n = count($instr);
336
337		for ($i = 0; $i < $n; $i++){
338			//internal links(links inside this wiki) an relative links
339			if((substr($instr[$i][0], 0, 12) == 'internallink')){
340				$this->_convert_link($renderer,$instr[$i],$id);
341			}
342			else if((substr($instr[$i][0], 0, 13) == 'internalmedia')){
343				$this->_convert_media($renderer,$instr[$i],$id);
344			}
345		}
346
347		//if its the document start, cut off the first element(document information)
348		if ($instr[0][0] == 'document_start')
349		return array_slice($instr, 1, -1);
350		else
351		return $instr;
352	}
353
354
355	/*
356	 * Convert link of given instruction
357	 */
358	function _convert_link(&$renderer,&$instr,$id) {
359		global $ID;
360
361		$exists = false;
362
363		resolve_pageid(getNS($id),$instr[1][0],$exists);
364		list( $pageID, $pageReference ) = explode("#", $instr[1][0], 2);
365
366		if ( in_array($pageID, $this->includedPages) ) {
367			// Crate new internal Links
368			$check = null;
369
370			// Either get existing reference or create from first heading. If still not there take the alternate ID
371			$pageNameLink = empty( $pageReference ) ? sectionID($pageID,$check) : $pageReference;
372
373			$instr[1][0] = $ID . "#" . $pageNameLink;
374
375		} else {
376			// Convert external Links to plain Text
377
378			$instr = array(
379						"cdata",
380			array($instr[1][1]),
381			$instr[2]
382			);
383		}
384	}
385
386	/*
387	 * Convert internalmedia of given instruction
388	 */
389	function _convert_media(&$renderer,&$instr,$id) {
390		global $ID;
391
392		// Resolvemedia returns the absolute path to media by reference
393		$exists = false;
394		resolve_mediaid(getNS($id),$instr[1][0],$exists);
395	}
396
397	/**
398	 * Remove TOC, section edit buttons and tags
399	 */
400	function _cleanXHTML($xhtml){
401		$replace  = array(
402			'!<div class="toc">.*?(</div>\n</div>)!s' => '', // remove TOCs
403			'#<!-- SECTION \[(\d*-\d*)\] -->#e'       => '', // remove section edit buttons
404			'!<div id="tags">.*?(</div>)!s'           => ''  // remove category tags
405		);
406		$xhtml  = preg_replace(array_keys($replace), array_values($replace), $xhtml);
407		return $xhtml;
408	}
409
410
411	/**
412	 * Allow the plugin to prevent DokuWiki creating a second instance of itself
413	 *
414	 * @return bool   true if the plugin can not be instantiated more than once
415	 */
416	function isSingleton() {
417		return true;
418	}
419}
420// vim:ts=4:sw=4:et:enc=utf-8:
421