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 14use dokuwiki\File\PageResolver; 15 16class syntax_plugin_siteexport_toc extends DokuWiki_Syntax_Plugin { 17 18 private $insideToc = false; 19 private $savedToc = array(); 20 private $options = array(); 21 22 private $mergedPages = array(); 23 private $includedPages = array(); 24 private $merghintIds = array(); 25 private $mergeHints = array(); 26 27 public function getType() { return 'protected'; } 28 public function getPType() { return 'block'; } 29 public function getAllowedTypes() { return array('container'); } 30 public function getSort() { return 100; } 31 32 /** 33 * Connect pattern to lexer 34 */ 35 public function connectTo($mode) { 36 $this->Lexer->addEntryPattern('<toc>(?=.*?</toc>)', $mode, 'plugin_siteexport_toc'); 37 $this->Lexer->addEntryPattern('<toc .+?>(?=.*?</toc>)', $mode, 'plugin_siteexport_toc'); 38 $this->Lexer->addSpecialPattern("\[\[.+?\]\]", $mode, 'plugin_siteexport_toc'); 39 } 40 41 public function postConnect() { 42 $this->Lexer->addExitPattern('</toc.*?>', 'plugin_siteexport_toc'); 43 } 44 45 public function handle($match, $state, $pos, Doku_Handler $handler) { 46 global $ID, $INFO; 47 48 switch ($state) { 49 case DOKU_LEXER_ENTER: 50 51 $this->insideToc = true; 52 $this->options = explode(' ', substr($match, 5, -1)?:""); 53 return array('start' => true, 'pos' => $pos, 'options' => $this->options); 54 55 case DOKU_LEXER_SPECIAL: 56 57 if ($this->insideToc) { 58 59 $link = preg_replace(array('/^\[\[/', '/\]\]$/u'), '', $match); 60 // Split title from URL 61 $link = explode('|', $link, 2); 62 if (!isset($link[1])) { 63 $link[1] = NULL; 64 } else if (preg_match('/^\{\{[^\}]+\}\}$/', $link[1])) { 65 // If the title is an image, convert it to an array containing the image details 66 $link[1] = Doku_Handler_Parse_Media($link[1]); 67 } 68 $link[0] = trim($link[0]); 69 70 if (!(preg_match('/^[a-zA-Z0-9\.]+>{1}.*$/u', $link[0]) || 71 preg_match('/^\\\\\\\\[\w.:?\-;,]+?\\\\/u', $link[0]) || 72 preg_match('#^([a-z0-9\-\.+]+?)://#i', $link[0]) || 73 preg_match('<' . PREG_PATTERN_VALID_EMAIL . '>', $link[0]) || 74 preg_match('!^#.+!', $link[0])) 75 ) { 76 77 // Get current depth from call stack 78 $depth = 1; 79 if ( isset( $handler->CallWriter ) && $handler->CallWriter instanceof Doku_Handler_List ) { 80 81 $calls = array_reverse($handler->CallWriter->calls); 82 $call = $calls[0]; 83 foreach ($calls as $item) { 84 if (in_array($item[0], array('list_item', 'list_open'))) { $call = $item; break; } 85 } 86 87 $listType = null; 88 $depth = $handler->CallWriter->interpretSyntax($call[1][0], $listType)-1; // Minus one because of plus one inside the interpret function 89 } 90 91 if (empty($link[0])) { break; } // No empty elements. This would lead to problems 92 return array($link[0], $link[1], $depth); 93 } else { 94 // use parser! - but with another p 95 $handler->internallink($match, $state, $pos); 96 } 97 } else { 98 // use parser! 99 $handler->internallink($match, $state, $pos); 100 } 101 102 return false; 103 case DOKU_LEXER_UNMATCHED: 104 105 $handler->addCall('cdata', array($match), $pos); 106 107 return false; 108 case DOKU_LEXER_EXIT: 109 110 $this->insideToc = false; 111 return 'save__meta'; 112 } 113 return false; 114 } 115 116 public function render($mode, Doku_Renderer $renderer, $data) { 117 global $ID, $lang, $INFO; 118 119 list($SID, $NAME, $DEPTH) = $data; 120 121 $exists = page_exists( (new PageResolver( $ID ))->resolveId($SID) ); 122 123// $SID = cleanID($SID); // hier kein cleanID, da sonst moeglicherweise der anker verloren geht 124 125 // Render XHTML and ODT 126 if ($mode != 'metadata' ) { 127 128 // TOC Title 129 if (is_array($data) && $data['start'] == true) { 130 131 if (is_Array($data['options'])) { 132 foreach ($data['options'] as $opt) { 133 switch ($opt) { 134 case 'description' : $renderer->meta['sitetoc']['showDescription'] = true; break; 135 case 'notoc' : $renderer->meta['sitetoc']['noTOC'] = true; break; 136 case 'merge' : $renderer->meta['sitetoc']['mergeDoc'] = true; break; 137 case 'nohead' : $renderer->meta['sitetoc']['noTocHeader'] = true; break; 138 case 'mergeheader' : $renderer->meta['sitetoc']['mergeHeader'] = true; break; 139 case 'pagebreak' : $renderer->meta['sitetoc']['pagebreak'] = true; break; 140 case 'mergehint' : $renderer->meta['sitetoc']['mergehint'] = true; break; 141 } 142 } 143 } 144 145 $renderer->section_open("1 sitetoc"); 146 if ($renderer->meta['sitetoc']['noTocHeader'] === false) { 147 $renderer->header($lang['toc'], 1, $data['pos']); 148 } 149 150 return true; 151 } else 152 153 // All Output has been done 154 if (!is_array($data) && $data == 'save__meta') { 155 156 // Close TOC 157 $renderer->section_close(); 158 159 if ($renderer->meta['sitetoc']['noTOC'] === true) { 160 $renderer->doc = preg_replace("/<div.*?sitetoc.*?$/si", "", $renderer->doc); 161 } 162 163 // If this is not set, we may have it as Metadata 164 if (empty($this->mergedPages) && $renderer->meta['sitetoc']['mergeDoc']) { 165 $toc = $renderer->meta['sitetoc']['siteexportTOC']; 166 167 if (is_array($toc)) { 168 foreach ($toc as $tocItem) { 169 $this->mergedPages[] = array($tocItem['id'], $tocItem['depth']); 170 } 171 } 172 173 } 174 175 // If there is some data to be merged 176 if (count($this->mergedPages) > 0) { 177 178 $renderer->doc = ''; // Start fresh! 179 180 $renderer->section_open("1 mergedsite" . ($renderer->meta['sitetoc']['mergehint'] && count($this->mergedPages) > 1 ? ' mergehint' : '')); 181 182 // Prepare lookup Array 183 foreach ($this->mergedPages as $tocItem) { 184 list($this->includedPages[]) = explode('#', $tocItem[0]); 185 } 186 187 // Load the instructions 188 $instr = array(); 189 foreach ($this->mergedPages as $tocElement) { 190 191 list($tocItem, $depth) = $tocElement; 192 $file = wikiFN($tocItem); 193 194 if (@file_exists($file)) { 195 $instructions = p_cached_instructions($file, false, $tocItem); 196 } else { 197 $instructions = p_get_instructions(io_readWikiPage($file, $tocItem)); 198 } 199 200 // Convert Link and header instructions 201 $instructions = $this->_convertInstructions($instructions, $addID = null, $renderer, $depth); 202 203 if ($renderer->meta['sitetoc']['mergeHeader'] && count($this->mergedPages) > 1) { 204 // get a hint for merged pages 205 if ($renderer->meta['sitetoc']['mergehint']) { 206 // only if the first section is already there 207 $mergeHint = p_get_metadata($tocItem, 'mergehint', METADATA_RENDER_USING_SIMPLE_CACHE); 208 if (empty($mergeHint)) { $mergeHint = p_get_metadata($tocItem, 'thema', METADATA_RENDER_USING_SIMPLE_CACHE); } 209 if (empty($mergeHint)) { $mergeHint = tpl_pagetitle($tocItem, true); } 210 $instructions = $this->_mergeWithHeaders($this->_initialHeaderStructure($instructions), $instructions, 1, $mergeHint); 211 } 212 // Merge 213 $instr = $this->_mergeWithHeaders($instr, $instructions, 1); 214 215 } else 216 if ($renderer->meta['sitetoc']['pagebreak']) { 217 $sitepagebreak = array(array( 218 'plugin', 219 array( 220 'siteexport_toctools', 221 array( 222 'pagebreak', 223 null, 224 null 225 ) 226 ) 227 )); 228 $instr = array_merge($instr, $instructions, $sitepagebreak); 229 } else { 230 // Concat 231 $instr = array_merge($instr, $instructions); 232 } 233 } 234 235 if (!empty($instr)) { 236 if ( $this->_cleanAllInstructions($instr, true) ) { 237 // There are no toc elements, remove the mergesite mergehint 238 $renderer->doc = preg_replace( '/(class=".*?\s)mergedsite/', '\1', $renderer->doc ); 239 $renderer->doc = preg_replace( '/(class=".*?\s)mergehint/', '\1', $renderer->doc ); 240 } 241 242 // print "<pre>"; print_r($instr); print "</pre>"; 243 $this->_render_output($renderer, $mode, $instr); 244 } 245 246 $renderer->section_close(); 247 } 248 return true; 249 } 250 251 // Save the current ID 252 $LNID = $SID; 253 254 // Add ID to flags['mergeDoc'] 255 if ($renderer->meta['sitetoc']['mergeDoc'] === true) { // || (count($renderer->meta['sitetoc']['siteexportTOC']) > 0 && $renderer->meta['sitetoc']['siteexportMergeDoc'] === true) ) { 256 $this->mergedPages[] = array($SID, $DEPTH); 257 $SID = (new PageResolver( $ID ))->resolveId($SID); 258 $exists = page_exists( $SID ); 259 } else { 260 // // print normal internal link (XHTML odt) 261 $renderer->internallink($LNID, $NAME, null); 262 263 // Display Description underneath 264 if ($renderer->meta['sitetoc']['showDescription'] === true) { 265 $renderer->cdata(p_get_metadata($SID, 'description abstract', true)); 266 } 267 } 268 269 // Render Metadata 270 } else if ($mode == 'metadata') { 271 if (!is_array($data) && $data == 'save__meta') { 272 $renderer->meta['sitetoc']['siteexportTOC'] = $this->savedToc; 273 274 foreach ($this->savedToc as $page) { 275 $renderer->meta['relation']['references'][$page['id']] = $page['exists']; 276 } 277 278 $this->savedToc = array(); 279 } else if (!isset($data['start']) && !isset($data['pos'])) { 280 $this->savedToc[] = $this->__addTocItem($SID, $NAME, $DEPTH, $renderer); 281 } 282 } 283 284 return true; 285 } 286 287 /* 288 * pull apart the ID and create an Entry for the TOC 289 */ 290 private function __addTocItem($id, $name, $depth, $renderer) { 291 global $conf; 292 global $ID; 293 294 $exists = false; $isImage = false; $linktype = null; 295 $id = (new PageResolver( $ID ) )->resolveId($id); 296 $exists = page_exists( $id ); 297 // Keep explicit link title (e.g. [[.:configuration|configuration]]) so tocitem target stays lowercased 298 $default = $renderer->_simpleTitle($id); 299 if ($name === null || $name === '') { 300 $name = $renderer->_getLinkTitle($name, $default, $isImage, $id, $linktype); 301 } 302 303 //keep hash anchor 304 list($id, $hash) = explode('#', $id, 2); 305 if (!empty($hash)) $hash = $renderer->_headerToLink($hash); 306 307 // Build Sitetoc Item 308 $item = array(); 309 $item['id'] = $id; 310 $item['name'] = $name; 311 $item['anchor'] = $hash; 312 $item['depth'] = $depth; 313 $item['exists'] = $exists; 314 if (!$conf['skipacl'] && auth_quickaclcheck($item['id']) < AUTH_READ) { 315 return false; 316 } 317 318 return $item; 319 } 320 321 /* 322 * Render the output of one page 323 */ 324 private function _render_output($renderer, $mode, $instr) { 325 global $ID; 326 327 // Section IDs 328 // $addID = sectionID($addID, $check); //not possible to use a:b:c for id 329 330 if ($mode == 'xhtml') { 331 332 //--------RENDER 333 //renderer information(TOC build / Cache used) 334 $info = array(); 335 $content = p_render($mode, $instr, $info); 336 337 //Remove TOC`s, section edit buttons and tags 338 $content = $this->_cleanXHTML($content); 339 340 // embed the included page 341 // $renderer->doc .= '<div class="include">'; 342 //add an anchor to find start of a inserted page 343 // $renderer->doc .= "<a name='$addID' id='$addID'>"; 344 $renderer->doc .= $content; 345 // $renderer->doc .= '</div>'; 346 } else { 347 348 // Loop through the instructions 349 foreach ($instr as $instruction) { 350 // Execute the callback against the Renderer 351 call_user_func_array(array($renderer, $instruction[0]), $instruction[1]); 352 } 353 } 354 } 355 356 /* 357 * Corrects relative internal links and media and 358 * converts headers of included pages to subheaders of the current page 359 */ 360 private function _convertInstructions($instr, $id, &$renderer, $depth = 1) { 361 global $ID; 362 global $conf; 363 364 $n = count($instr); 365 366 for ($i = 0; $i < $n; $i++) { 367 //internal links(links inside this wiki) an relative links 368 if ((substr($instr[$i][0], 0, 12) == 'internallink')) { 369 $this->_convert_link($renderer, $instr[$i], $id); 370 } 371 else if ((substr($instr[$i][0], 0, 13) == 'internalmedia')) { 372 $this->_convert_media($renderer, $instr[$i], $id); 373 } 374 else if ((substr($instr[$i][0], 0, 6) == 'header')) { 375 $this->_convert_header($renderer, $instr[$i], $depth-1); // -1 because the depth starts at 1 376 } 377 else if ((substr($instr[$i][0], 0, 12) == 'section_open')) { 378 $this->_convert_section($renderer, $instr[$i], $depth-1); // -1 because the depth starts at 1 379 } 380 } 381 382 //if its the document start, cut off the first element(document information) 383 if ($instr[0][0] == 'document_start') 384 return array_slice($instr, 1, -1); 385 else 386 return $instr; 387 } 388 389 /* 390 * Convert link of given instruction 391 */ 392 private function _convert_link(&$renderer, &$instr, $id) { 393 global $ID; 394 395 $exists = false; 396 397 resolve_pageid(getNS($id), $instr[1][0], $exists); 398 list($pageID, $pageReference) = explode("#", $instr[1][0], 2); 399 400 if (in_array($pageID, $this->includedPages)) { 401 // Crate new internal Links 402 $check = null; 403 404 // Either get existing reference or create from first heading. If still not there take the alternate ID 405 $pageNameLink = empty($pageReference) ? sectionID($pageID, $check) : $pageReference; 406 407 $instr[1][0] = $ID . "#" . $pageNameLink; 408 409 } else { 410 // Convert external Links to plain Text 411 412 $instr = array( 413 "cdata", 414 array($instr[1][1]), 415 $instr[2] 416 ); 417 } 418 } 419 420 /* 421 * Convert internalmedia of given instruction 422 */ 423 private function _convert_media(&$renderer, &$instr, $id) { 424 global $ID; 425 426 // Resolvemedia returns the absolute path to media by reference 427 $exists = false; 428 resolve_mediaid(getNS($id), $instr[1][0], $exists); 429 } 430 431 /** 432 * @param integer $depth 433 */ 434 private function _convert_header(&$renderer, &$instr, $depth) { 435 // More Depth! 436 $instr[1][1] += $depth; 437 } 438 439 /** 440 * @param integer $depth 441 */ 442 private function _convert_section(&$renderer, &$instr, $depth) { 443 // More Depth! 444 $instr[1][0] += $depth; 445 } 446 447 private function _mergeWithHeaders($existing, $newInstructions, $level = 1, $mergeHint = array()) { 448 449 $returnInstructions = array(); 450 $preparedInstructions = array(); 451 $existingStart = $existingEnd = 0; 452 $firstRun = true; 453 454 while ($this->_findNextHeaderSection($existing, $level, $existingStart, $existingEnd)) { 455 456 if ($firstRun) { 457 $returnInstructions = array_merge($returnInstructions, array_slice($existing, 0, $existingStart)); 458 $firstRun = false; 459 } 460 461 $currentSlice = array_slice($existing, $existingStart, $existingEnd-$existingStart); 462 463 // Find matching part with headername 464 $newStart = $newEnd = 0; 465 if ($this->_findNextHeaderSection($newInstructions, $level, $newStart, $newEnd, $currentSlice[0][1][0])) { 466 467 $newSlice = array_slice($newInstructions, $newStart, $newEnd-$newStart); 468 if ($newSlice[0][0] == 'header') 469 array_shift($newSlice); // Remove Heading 470 471 // merge found parts on next level. 472 $returnedInstructions = $this->_mergeWithHeaders($currentSlice, $newSlice, $level+1, $mergeHint); 473 474 // Put them at the end! 475 $preparedInstructions = array_merge($preparedInstructions, $returnedInstructions); 476 477 // Remove from input 478 array_splice($newInstructions, $newStart, $newEnd-$newStart); 479 } else { 480 // Nothing else found 481 $preparedInstructions = array_merge($preparedInstructions, $currentSlice); 482 } 483 484 $existingStart = $existingEnd; 485 } 486 487 // Append the rest 488 $returnInstructions = array_merge($returnInstructions, array_slice($existing, $existingStart)); 489 490 // Check for section close inconsistencies and put one at the very end ... 491 $section_postpend = array(); 492 if ( 493 ( 494 ($tmp1 = array_slice($newInstructions, -1)) 495 && ($tmp1[0][0] == 'section_close') 496 ) 497 && 498 ( 499 ($tmp2 = array_slice($newInstructions, -2)) 500 && ($tmp2[0][0] == 'section_close') 501 ) 502 ) { 503 $section_postpend = array_splice($newInstructions, -1); 504 } 505 if ( 506 ( 507 ($tmp3 = array_slice($returnInstructions, -1)) 508 && ($tmp3[0][0] == 'section_close') 509 ) 510 && 511 ( 512 ($tmp4 = array_slice($returnInstructions, -2)) 513 && ($tmp4[0][0] == 'section_close') 514 ) 515 ) { 516 $section_postpend = array_merge($section_postpend, array_splice($returnInstructions, -1)); 517 } 518 519 // What if there are headings left inside the $newInstructions????? 520 // Find matching part with headername 521 $newStart = $newEnd = 0; 522 $section_prepend = array(); 523 if ($this->_findNextHeaderSection($newInstructions, $level, $newStart, $newEnd)) { 524 // If there are header in here, build a prepend and have the rest at the end 525 $section_prepend = array_splice($newInstructions, 0, $newStart); 526 } else { 527 // If not, prepend all of it. 528 $section_prepend = $newInstructions; 529 $newInstructions = array(); 530 } 531 532 $this->_insertMergeHint($section_prepend, $mergeHint); 533 534 $returnInstructions = array_merge($returnInstructions, $section_prepend, $preparedInstructions, $newInstructions, $section_postpend); 535 536 return $returnInstructions; 537 } 538 539 /** 540 * @param integer $level 541 */ 542 private function _findNextHeaderSection($section, $level, &$start, &$end, $headerName = null) { 543 544 $inCount = count($section); 545 $currentSlice = -1; 546 547 // Find Level 1 Header that matches. 548 for ($i = $start; $i < $inCount; $i++) { 549 550 $instruction = $section[$i]; 551 $end = $i; // Or it will be lost and a section close will be missing. 552 553 // First Level Header 554 if ($instruction[0] == 'header' && $instruction[1][1] == $level) { 555 556 if ($currentSlice > 0) { 557 return true; 558 } 559 560 if ($headerName == null || ($headerName == $instruction[1][0])) { 561 // Begin of new slice ... 562 $start = $currentSlice = $i; 563 } 564 } 565 } 566 567 // Nothing found 568 $end = $i; // Or it will be lost and a section close will be missing. 569 return $currentSlice > 0; 570 } 571 572 private function _cleanAllInstructions(&$instr, $advanced=false) { 573 $this->_cleanInstructions($instr, '/p_(close|open)/'); 574 $this->_cleanInstructions($instr, '/section_(close|open)/'); 575 $this->_cleanInstructions($instr, '/listu_(close|open)/'); 576 $this->_cleanInstructions($instr, '/listo_(close|open)/'); 577 578 if ( !$advanced ) { 579 return false; 580 } 581 582 $currentMergeHint = null; 583 $listOfMergeHintNames= []; 584 585 for( $i=0; $i<count($instr); $i++ ) { 586 587 $hasMoreEntries = count($instr)-1 > $i; 588 589 if ( $instr[$i][0] == 'header' ) { 590 // reset after header 591 $currentMergeHint = null; 592 } 593 594 if ( $instr[$i][1][0] == 'siteexport_toctools' && $instr[$i][1][0][0] != 'pagebreak' ) { 595 if ( $currentMergeHint != null && $instr[$i][1][1][2] == $currentMergeHint[1][1][2] ) { 596 597 if ( $instr[$i][1][1][1] == 'end' ) { 598 // look ahead, if the next hint is also the same ID, if so: remove this ending hint. 599 $shouldSpliceAway = false; 600 for( $ii=$i+1; $ii<count($instr); $ii++ ) { 601 if ( $instr[$ii][0] == 'header' ) { 602 // Jumping over a section now ... we have to leave the last entry 603 break; 604 } else if ( $instr[$ii][1][0] == 'siteexport_toctools' && $instr[$ii][1][0][0] != 'pagebreak' ) { 605 if ( $instr[$ii][1][1][2] == $currentMergeHint[1][1][2] && $instr[$ii][1][1][1] == 'start' ) { 606 // Found another one, that is identicall - so this will be removed. 607 // also remove the current ending element 608 $shouldSpliceAway = true; 609 } 610 611 // Okay, this was a toctools whatever ... but maybe not a start of the same type. 612 // we're done. 613 break; 614 } 615 } 616 617 if ( !$shouldSpliceAway ) { 618 // print "<pre>NOT Splicing away ". print_r($instr[$i], true) . "</pre>"; 619 continue; 620 } 621 // print "<pre>Splicing away ". print_r($instr[$i], true) . "</pre>"; 622 } 623 624 // print "<p>Removing 'mergehint' in between </p>"; 625 array_splice($instr, $i--, 1); 626 } else { 627 // print "<p>Resetting Mergehint '" . $instr[$i][1][1][2] . "' == '" . $currentMergeHint[1][1][2] . "'</p>"; 628 $currentMergeHint = $instr[$i]; 629 $listOfMergeHintNames[] = $instr[$i][1][1][2]; 630 } 631 } 632 } 633 634/* 635 print "<pre>" . print_r($instr, 1) . "</pre>"; 636 637//*/ 638 639 // There is only ONE distinct mergehint -> remove all 640 $listOfMergeHintNames = array_unique($listOfMergeHintNames); 641 if ( count($listOfMergeHintNames) == 1 ) { 642 for( $i=0; $i<count($instr); $i++ ) { 643 if ( $instr[$i][1][0] == 'siteexport_toctools' && $instr[$i][1][0][0] != 'pagebreak' ) { 644 array_splice($instr, $i--, 1); 645 } 646 } 647 } 648 649 return count($listOfMergeHintNames) == 1; 650 } 651 652 /** 653 * @param string $tag 654 */ 655 private function _cleanInstructions(&$instructions, $tag) { 656 657 658/* 659 print "<pre>"; 660 print "$tag ->\n"; 661 print_r($instructions); 662 print "</pre>"; 663//*/ 664 $inCount = count($instructions); 665 for ($i = 0; $i < $inCount; $i++) { 666 667 // Last instruction 668 if ($i == $inCount-1) { 669 break; 670 } 671 672 if (preg_match($tag, $instructions[$i][0]) && preg_match($tag, $instructions[$i+1][0]) && $instructions[$i][0] != $instructions[$i+1][0]) { 673/* 674 print "<pre>"; 675 print "Removed ->\n"; 676 print_r($instructions[$i-1]); 677 print "---\n"; 678 print_r($instructions[$i]); 679 print_r($instructions[$i+1]); 680 print "---\n"; 681 print_r($instructions[$i+2]); 682 print "</pre>"; 683//*/ 684 685 // found different tags, but both match the expression and follow each other - so they can be elliminated 686 array_splice($instructions, $i, 2); 687 $inCount -= 2; 688 $i--; 689 } 690 } 691/* 692 print "<pre>"; 693 print "$tag ->\n"; 694 print_r($instructions); 695 print "</pre>"; 696//*/ 697 } 698 699 /** 700 * Strip everything except for the headers 701 */ 702 private function _initialHeaderStructure($instructions) { 703 $inCount = count($instructions); 704 for ($i = 0; $i < $inCount; $i++) { 705 706 // Last instruction 707 if ($i == $inCount-1) { 708 break; 709 } 710 711 if (!in_array($instructions[$i][0], array('header', 'section_open', 'section_close', 'p_open', 'p_close'))) { 712 // found non-matching 713 array_splice($instructions, $i, 1); 714 $inCount--; 715 $i--; 716 } 717 } 718 return $instructions; 719 } 720 721 private function _insertMergeHint(&$instructions, $mergeHint) { 722 723 // Surround new slice with a mergehint 724 if (empty($mergeHint)) { return; } 725 726 // No emtpy insruction sets. 727 $this->_cleanAllInstructions($instructions); 728 729 if (empty($instructions)) { return; } 730 731 $mergeHintPrepend = $this->_toctoolPrepends( $instructions ); 732 733 // only section content should be surrounded. 734 if ($instructions[0][0] != 'section_open') { return; } 735 736 // save for later use 737 $mergeHintId = sectionid($mergeHint, $this->mergeHints); 738 $this->merghintIds[$mergeHintId] = $mergeHint; 739 740 // Insert section information 741 array_push( $mergeHintPrepend, array( 742 'plugin', 743 array( 744 'siteexport_toctools', 745 array( 746 'mergehint', 747 'start', 748 $mergeHint, 749 $mergeHintId 750 ) 751 ) 752 ) ); 753 754 $mergeHintPostpend = array(array( 755 'plugin', 756 array( 757 'siteexport_toctools', 758 array( 759 'mergehint', 760 'end', 761 $mergeHint 762 ) 763 ) 764 )); 765 766 $instructions = array_merge($mergeHintPrepend, $instructions, $mergeHintPostpend); 767/* 768 print "<pre>"; print_r($instructions); print "</pre>"; 769//*/ 770 } 771 772 private function _toctoolPrepends( &$instructions ) { 773 774 $mergeHintPrependPrepend = array(); 775 776 // 2021-01-14 This did no good - if a merged page had two mergehints, the first was stripped. 777/* 778 if ( $instructions[0][0] == 'plugin' && $instructions[0][1][0] == 'siteexport_toctools' && $instructions[0][1][1][1] == 'start' ) { 779 780 // This is already section merge hint ... but it will have a section at its end ... hopefully 781 do { 782 $_instructions = array_shift( $instructions ); 783 array_push( $mergeHintPrependPrepend, $_instructions); 784 } while( !($_instructions[0] == 'plugin' && $_instructions[1][0] == 'siteexport_toctools' && $_instructions[1][1][1] == 'end' ) ) ; 785 array_splice($mergeHintPrepend, 0, 0, $mergeHintPrependPrepend); 786 } 787//*/ 788/* 789 print "<pre>"; print_r($instructions); print "</pre>"; 790//*/ 791 return $mergeHintPrependPrepend; 792 } 793 794 /** 795 * Remove TOC, section edit buttons and tags 796 */ 797 private function _cleanXHTML($xhtml) { 798 $replace = array( 799 '!<div class="toc">.*?(</div>\n</div>)!s' => '', // remove TOCs 800 '#<!-- SECTION \[(\d*-\d*)\] -->#s' => '', // remove section edit buttons 801 '!<div id="tags">.*?(</div>)!s' => '' // remove category tags 802 ); 803 $xhtml = preg_replace(array_keys($replace), array_values($replace), $xhtml); 804 return $xhtml; 805 } 806 807 /** 808 * Allow the plugin to prevent DokuWiki creating a second instance of itself 809 * 810 * @return bool true if the plugin can not be instantiated more than once 811 */ 812 public function isSingleton() { 813 return true; 814 } 815} 816// vim:ts=4:sw=4:et:enc=utf-8: 817