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