1<?php 2/** 3 * Renderer output base class 4 * 5 * @author Harry Fuecks <hfuecks@gmail.com> 6 * @author Andreas Gohr <andi@splitbrain.org> 7 */ 8 9use dokuwiki\Extension\Plugin; 10use dokuwiki\Extension\SyntaxPlugin; 11 12/** 13 * Allowed chars in $language for code highlighting 14 * @see GeSHi::set_language() 15 */ 16define('PREG_PATTERN_VALID_LANGUAGE', '#[^a-zA-Z0-9\-_]#'); 17 18/** 19 * An empty renderer, produces no output 20 * 21 * Inherits from dokuwiki\Plugin\DokuWiki_Plugin for giving additional functions to render plugins 22 * 23 * The renderer transforms the syntax instructions created by the parser and handler into the 24 * desired output format. For each instruction a corresponding method defined in this class will 25 * be called. That method needs to produce the desired output for the instruction and add it to the 26 * $doc field. When all instructions are processed, the $doc field contents will be cached by 27 * DokuWiki and sent to the user. 28 */ 29abstract class Doku_Renderer extends Plugin 30{ 31 /** @var array Settings, control the behavior of the renderer */ 32 public $info = [ 33 'cache' => true, 34 // may the rendered result cached? 35 'toc' => true, 36 ]; 37 38 /** @var array contains the smiley configuration, set in p_render() */ 39 public $smileys = []; 40 /** @var array contains the entity configuration, set in p_render() */ 41 public $entities = []; 42 /** @var array contains the acronym configuration, set in p_render() */ 43 public $acronyms = []; 44 /** @var array contains the interwiki configuration, set in p_render() */ 45 public $interwiki = []; 46 /** @var string|int link pages and media against this revision */ 47 public $date_at = ''; 48 49 /** @var array the list of headers used to create unique link ids */ 50 protected $headers = []; 51 52 /** 53 * @var string the rendered document, this will be cached after the renderer ran through 54 */ 55 public $doc = ''; 56 57 /** 58 * clean out any per-use values 59 * 60 * This is called before each use of the renderer object and should be used to 61 * completely reset the state of the renderer to be reused for a new document 62 */ 63 public function reset() 64 { 65 $this->headers = []; 66 $this->doc = ''; 67 $this->info['cache'] = true; 68 $this->info['toc'] = true; 69 } 70 71 /** 72 * Allow the plugin to prevent DokuWiki from reusing an instance 73 * 74 * Since most renderer plugins fail to implement Doku_Renderer::reset() we default 75 * to reinstantiating the renderer here 76 * 77 * @return bool false if the plugin has to be instantiated 78 */ 79 public function isSingleton() 80 { 81 return false; 82 } 83 84 /** 85 * Returns the format produced by this renderer. 86 * 87 * Has to be overidden by sub classes 88 * 89 * @return string 90 */ 91 abstract public function getFormat(); 92 93 /** 94 * Disable caching of this renderer's output 95 */ 96 public function nocache() 97 { 98 $this->info['cache'] = false; 99 } 100 101 /** 102 * Disable TOC generation for this renderer's output 103 * 104 * This might not be used for certain sub renderer 105 */ 106 public function notoc() 107 { 108 $this->info['toc'] = false; 109 } 110 111 /** 112 * Handle plugin rendering 113 * 114 * Most likely this needs NOT to be overwritten by sub classes 115 * 116 * @param string $name Plugin name 117 * @param mixed $data custom data set by handler 118 * @param string $state matched state if any 119 * @param string $match raw matched syntax 120 */ 121 public function plugin($name, $data, $state = '', $match = '') 122 { 123 /** @var SyntaxPlugin $plugin */ 124 $plugin = plugin_load('syntax', $name); 125 if ($plugin != null) { 126 $plugin->render($this->getFormat(), $this, $data); 127 } 128 } 129 130 /** 131 * handle nested render instructions 132 * this method (and nest_close method) should not be overloaded in actual renderer output classes 133 * 134 * @param array $instructions 135 */ 136 public function nest($instructions) 137 { 138 foreach ($instructions as $instruction) { 139 // execute the callback against ourself 140 if (method_exists($this, $instruction[0])) { 141 call_user_func_array([$this, $instruction[0]], $instruction[1] ?: []); 142 } 143 } 144 } 145 146 /** 147 * dummy closing instruction issued by Doku_Handler_Nest 148 * 149 * normally the syntax mode should override this instruction when instantiating Doku_Handler_Nest - 150 * however plugins will not be able to - as their instructions require data. 151 */ 152 public function nest_close() 153 { 154 } 155 156 #region Syntax modes - sub classes will need to implement them to fill $doc 157 158 /** 159 * Initialize the document 160 */ 161 public function document_start() 162 { 163 } 164 165 /** 166 * Finalize the document 167 */ 168 public function document_end() 169 { 170 } 171 172 /** 173 * Render the Table of Contents 174 * 175 * @return string 176 */ 177 public function render_TOC() 178 { 179 return ''; 180 } 181 182 /** 183 * Add an item to the TOC 184 * 185 * @param string $id the hash link 186 * @param string $text the text to display 187 * @param int $level the nesting level 188 */ 189 public function toc_additem($id, $text, $level) 190 { 191 } 192 193 /** 194 * Render a heading 195 * 196 * @param string $text the text to display 197 * @param int $level header level 198 * @param int $pos byte position in the original source 199 */ 200 public function header($text, $level, $pos) 201 { 202 } 203 204 /** 205 * Open a new section 206 * 207 * @param int $level section level (as determined by the previous header) 208 */ 209 public function section_open($level) 210 { 211 } 212 213 /** 214 * Close the current section 215 */ 216 public function section_close() 217 { 218 } 219 220 /** 221 * Render plain text data 222 * 223 * @param string $text 224 */ 225 public function cdata($text) 226 { 227 } 228 229 /** 230 * Open a paragraph 231 */ 232 public function p_open() 233 { 234 } 235 236 /** 237 * Close a paragraph 238 */ 239 public function p_close() 240 { 241 } 242 243 /** 244 * Create a line break 245 */ 246 public function linebreak() 247 { 248 } 249 250 /** 251 * Create a horizontal line 252 */ 253 public function hr() 254 { 255 } 256 257 /** 258 * Start strong (bold) formatting 259 */ 260 public function strong_open() 261 { 262 } 263 264 /** 265 * Stop strong (bold) formatting 266 */ 267 public function strong_close() 268 { 269 } 270 271 /** 272 * Start emphasis (italics) formatting 273 */ 274 public function emphasis_open() 275 { 276 } 277 278 /** 279 * Stop emphasis (italics) formatting 280 */ 281 public function emphasis_close() 282 { 283 } 284 285 /** 286 * Start underline formatting 287 */ 288 public function underline_open() 289 { 290 } 291 292 /** 293 * Stop underline formatting 294 */ 295 public function underline_close() 296 { 297 } 298 299 /** 300 * Start monospace formatting 301 */ 302 public function monospace_open() 303 { 304 } 305 306 /** 307 * Stop monospace formatting 308 */ 309 public function monospace_close() 310 { 311 } 312 313 /** 314 * Start a subscript 315 */ 316 public function subscript_open() 317 { 318 } 319 320 /** 321 * Stop a subscript 322 */ 323 public function subscript_close() 324 { 325 } 326 327 /** 328 * Start a superscript 329 */ 330 public function superscript_open() 331 { 332 } 333 334 /** 335 * Stop a superscript 336 */ 337 public function superscript_close() 338 { 339 } 340 341 /** 342 * Start deleted (strike-through) formatting 343 */ 344 public function deleted_open() 345 { 346 } 347 348 /** 349 * Stop deleted (strike-through) formatting 350 */ 351 public function deleted_close() 352 { 353 } 354 355 /** 356 * Start a footnote 357 */ 358 public function footnote_open() 359 { 360 } 361 362 /** 363 * Stop a footnote 364 */ 365 public function footnote_close() 366 { 367 } 368 369 /** 370 * Open an unordered list 371 */ 372 public function listu_open() 373 { 374 } 375 376 /** 377 * Close an unordered list 378 */ 379 public function listu_close() 380 { 381 } 382 383 /** 384 * Open an ordered list 385 */ 386 public function listo_open() 387 { 388 } 389 390 /** 391 * Close an ordered list 392 */ 393 public function listo_close() 394 { 395 } 396 397 /** 398 * Open a list item 399 * 400 * @param int $level the nesting level 401 * @param bool $node true when a node; false when a leaf 402 */ 403 public function listitem_open($level, $node = false) 404 { 405 } 406 407 /** 408 * Close a list item 409 */ 410 public function listitem_close() 411 { 412 } 413 414 /** 415 * Start the content of a list item 416 */ 417 public function listcontent_open() 418 { 419 } 420 421 /** 422 * Stop the content of a list item 423 */ 424 public function listcontent_close() 425 { 426 } 427 428 /** 429 * Output unformatted $text 430 * 431 * Defaults to $this->cdata() 432 * 433 * @param string $text 434 */ 435 public function unformatted($text) 436 { 437 $this->cdata($text); 438 } 439 440 /** 441 * Output preformatted text 442 * 443 * @param string $text 444 */ 445 public function preformatted($text) 446 { 447 } 448 449 /** 450 * Start a block quote 451 */ 452 public function quote_open() 453 { 454 } 455 456 /** 457 * Stop a block quote 458 */ 459 public function quote_close() 460 { 461 } 462 463 /** 464 * Display text as file content, optionally syntax highlighted 465 * 466 * @param string $text text to show 467 * @param string $lang programming language to use for syntax highlighting 468 * @param string $file file path label 469 */ 470 public function file($text, $lang = null, $file = null) 471 { 472 } 473 474 /** 475 * Display text as code content, optionally syntax highlighted 476 * 477 * @param string $text text to show 478 * @param string $lang programming language to use for syntax highlighting 479 * @param string $file file path label 480 */ 481 public function code($text, $lang = null, $file = null) 482 { 483 } 484 485 /** 486 * Format an acronym 487 * 488 * Uses $this->acronyms 489 * 490 * @param string $acronym 491 */ 492 public function acronym($acronym) 493 { 494 } 495 496 /** 497 * Format a smiley 498 * 499 * Uses $this->smiley 500 * 501 * @param string $smiley 502 */ 503 public function smiley($smiley) 504 { 505 } 506 507 /** 508 * Format an entity 509 * 510 * Entities are basically small text replacements 511 * 512 * Uses $this->entities 513 * 514 * @param string $entity 515 */ 516 public function entity($entity) 517 { 518 } 519 520 /** 521 * Typographically format a multiply sign 522 * 523 * Example: ($x=640, $y=480) should result in "640×480" 524 * 525 * @param string|int $x first value 526 * @param string|int $y second value 527 */ 528 public function multiplyentity($x, $y) 529 { 530 } 531 532 /** 533 * Render an opening single quote char (language specific) 534 */ 535 public function singlequoteopening() 536 { 537 } 538 539 /** 540 * Render a closing single quote char (language specific) 541 */ 542 public function singlequoteclosing() 543 { 544 } 545 546 /** 547 * Render an apostrophe char (language specific) 548 */ 549 public function apostrophe() 550 { 551 } 552 553 /** 554 * Render an opening double quote char (language specific) 555 */ 556 public function doublequoteopening() 557 { 558 } 559 560 /** 561 * Render an closinging double quote char (language specific) 562 */ 563 public function doublequoteclosing() 564 { 565 } 566 567 /** 568 * Render a CamelCase link 569 * 570 * @param string $link The link name 571 * @see http://en.wikipedia.org/wiki/CamelCase 572 */ 573 public function camelcaselink($link) 574 { 575 } 576 577 /** 578 * Render a page local link 579 * 580 * @param string $hash hash link identifier 581 * @param string $name name for the link 582 */ 583 public function locallink($hash, $name = null) 584 { 585 } 586 587 /** 588 * Render a wiki internal link 589 * 590 * @param string $link page ID to link to. eg. 'wiki:syntax' 591 * @param string|array $title name for the link, array for media file 592 */ 593 public function internallink($link, $title = null) 594 { 595 } 596 597 /** 598 * Render an external link 599 * 600 * @param string $link full URL with scheme 601 * @param string|array $title name for the link, array for media file 602 */ 603 public function externallink($link, $title = null) 604 { 605 } 606 607 /** 608 * Render the output of an RSS feed 609 * 610 * @param string $url URL of the feed 611 * @param array $params Finetuning of the output 612 */ 613 public function rss($url, $params) 614 { 615 } 616 617 /** 618 * Render an interwiki link 619 * 620 * You may want to use $this->_resolveInterWiki() here 621 * 622 * @param string $link original link - probably not much use 623 * @param string|array $title name for the link, array for media file 624 * @param string $wikiName indentifier (shortcut) for the remote wiki 625 * @param string $wikiUri the fragment parsed from the original link 626 */ 627 public function interwikilink($link, $title, $wikiName, $wikiUri) 628 { 629 } 630 631 /** 632 * Link to file on users OS 633 * 634 * @param string $link the link 635 * @param string|array $title name for the link, array for media file 636 */ 637 public function filelink($link, $title = null) 638 { 639 } 640 641 /** 642 * Link to windows share 643 * 644 * @param string $link the link 645 * @param string|array $title name for the link, array for media file 646 */ 647 public function windowssharelink($link, $title = null) 648 { 649 } 650 651 /** 652 * Render a linked E-Mail Address 653 * 654 * Should honor $conf['mailguard'] setting 655 * 656 * @param string $address Email-Address 657 * @param string|array $name name for the link, array for media file 658 */ 659 public function emaillink($address, $name = null) 660 { 661 } 662 663 /** 664 * Render an internal media file 665 * 666 * @param string $src media ID 667 * @param string $title descriptive text 668 * @param string $align left|center|right 669 * @param int $width width of media in pixel 670 * @param int $height height of media in pixel 671 * @param string $cache cache|recache|nocache 672 * @param string $linking linkonly|detail|nolink 673 */ 674 public function internalmedia($src, $title = null, $align = null, $width = null, 675 $height = null, $cache = null, $linking = null) 676 { 677 } 678 679 /** 680 * Render an external media file 681 * 682 * @param string $src full media URL 683 * @param string $title descriptive text 684 * @param string $align left|center|right 685 * @param int $width width of media in pixel 686 * @param int $height height of media in pixel 687 * @param string $cache cache|recache|nocache 688 * @param string $linking linkonly|detail|nolink 689 */ 690 public function externalmedia($src, $title = null, $align = null, $width = null, 691 $height = null, $cache = null, $linking = null) 692 { 693 } 694 695 /** 696 * Render a link to an internal media file 697 * 698 * @param string $src media ID 699 * @param string $title descriptive text 700 * @param string $align left|center|right 701 * @param int $width width of media in pixel 702 * @param int $height height of media in pixel 703 * @param string $cache cache|recache|nocache 704 */ 705 public function internalmedialink($src, $title = null, $align = null, 706 $width = null, $height = null, $cache = null) 707 { 708 } 709 710 /** 711 * Render a link to an external media file 712 * 713 * @param string $src media ID 714 * @param string $title descriptive text 715 * @param string $align left|center|right 716 * @param int $width width of media in pixel 717 * @param int $height height of media in pixel 718 * @param string $cache cache|recache|nocache 719 */ 720 public function externalmedialink($src, $title = null, $align = null, 721 $width = null, $height = null, $cache = null) 722 { 723 } 724 725 /** 726 * Start a table 727 * 728 * @param int $maxcols maximum number of columns 729 * @param int $numrows NOT IMPLEMENTED 730 * @param int $pos byte position in the original source 731 */ 732 public function table_open($maxcols = null, $numrows = null, $pos = null) 733 { 734 } 735 736 /** 737 * Close a table 738 * 739 * @param int $pos byte position in the original source 740 */ 741 public function table_close($pos = null) 742 { 743 } 744 745 /** 746 * Open a table header 747 */ 748 public function tablethead_open() 749 { 750 } 751 752 /** 753 * Close a table header 754 */ 755 public function tablethead_close() 756 { 757 } 758 759 /** 760 * Open a table body 761 */ 762 public function tabletbody_open() 763 { 764 } 765 766 /** 767 * Close a table body 768 */ 769 public function tabletbody_close() 770 { 771 } 772 773 /** 774 * Open a table footer 775 */ 776 public function tabletfoot_open() 777 { 778 } 779 780 /** 781 * Close a table footer 782 */ 783 public function tabletfoot_close() 784 { 785 } 786 787 /** 788 * Open a table row 789 */ 790 public function tablerow_open() 791 { 792 } 793 794 /** 795 * Close a table row 796 */ 797 public function tablerow_close() 798 { 799 } 800 801 /** 802 * Open a table header cell 803 * 804 * @param int $colspan 805 * @param string $align left|center|right 806 * @param int $rowspan 807 */ 808 public function tableheader_open($colspan = 1, $align = null, $rowspan = 1) 809 { 810 } 811 812 /** 813 * Close a table header cell 814 */ 815 public function tableheader_close() 816 { 817 } 818 819 /** 820 * Open a table cell 821 * 822 * @param int $colspan 823 * @param string $align left|center|right 824 * @param int $rowspan 825 */ 826 public function tablecell_open($colspan = 1, $align = null, $rowspan = 1) 827 { 828 } 829 830 /** 831 * Close a table cell 832 */ 833 public function tablecell_close() 834 { 835 } 836 837 #endregion 838 839 #region util functions, you probably won't need to reimplement them 840 841 /** 842 * Creates a linkid from a headline 843 * 844 * @param string $title The headline title 845 * @param boolean $create Create a new unique ID? 846 * @return string 847 * @author Andreas Gohr <andi@splitbrain.org> 848 */ 849 public function _headerToLink($title, $create = false) 850 { 851 if ($create) { 852 return sectionID($title, $this->headers); 853 } else { 854 $check = false; 855 return sectionID($title, $check); 856 } 857 } 858 859 /** 860 * Removes any Namespace from the given name but keeps 861 * casing and special chars 862 * 863 * @param string $name 864 * @return string 865 * @author Andreas Gohr <andi@splitbrain.org> 866 * 867 */ 868 public function _simpleTitle($name) 869 { 870 global $conf; 871 872 //if there is a hash we use the ancor name only 873 [$name, $hash] = sexplode('#', $name, 2); 874 if ($hash) return $hash; 875 876 if ($conf['useslash']) { 877 $name = strtr($name, ';/', ';:'); 878 } else { 879 $name = strtr($name, ';', ':'); 880 } 881 882 return noNSorNS($name); 883 } 884 885 /** 886 * Resolve an interwikilink 887 * 888 * @param string $shortcut identifier for the interwiki link 889 * @param string $reference fragment that refers the content 890 * @param null|bool $exists reference which returns if an internal page exists 891 * @return string interwikilink 892 */ 893 public function _resolveInterWiki(&$shortcut, $reference, &$exists = null) 894 { 895 //get interwiki URL 896 if (isset($this->interwiki[$shortcut])) { 897 $url = $this->interwiki[$shortcut]; 898 } elseif (isset($this->interwiki['default'])) { 899 $shortcut = 'default'; 900 $url = $this->interwiki[$shortcut]; 901 } else { 902 // not parsable interwiki outputs '' to make sure string manipluation works 903 $shortcut = ''; 904 $url = ''; 905 } 906 907 //split into hash and url part 908 $hash = strrchr($reference, '#'); 909 if ($hash) { 910 $reference = substr($reference, 0, -strlen($hash)); 911 $hash = substr($hash, 1); 912 } 913 914 //replace placeholder 915 if (preg_match('#\{(URL|NAME|SCHEME|HOST|PORT|PATH|QUERY)\}#', $url)) { 916 //use placeholders 917 $url = str_replace('{URL}', rawurlencode($reference), $url); 918 //wiki names will be cleaned next, otherwise urlencode unsafe chars 919 $url = str_replace( 920 '{NAME}', 921 ($url[0] === ':') ? $reference : preg_replace_callback( 922 '/[[\\\\\]^`{|}#%]/', static fn($match) => rawurlencode($match[0]), $reference 923 ), 924 $url 925 ); 926 $parsed = parse_url($reference); 927 if (empty($parsed['scheme'])) $parsed['scheme'] = ''; 928 if (empty($parsed['host'])) $parsed['host'] = ''; 929 if (empty($parsed['port'])) $parsed['port'] = 80; 930 if (empty($parsed['path'])) $parsed['path'] = ''; 931 if (empty($parsed['query'])) $parsed['query'] = ''; 932 $url = strtr($url, [ 933 '{SCHEME}' => $parsed['scheme'], 934 '{HOST}' => $parsed['host'], 935 '{PORT}' => $parsed['port'], 936 '{PATH}' => $parsed['path'], 937 '{QUERY}' => $parsed['query'], 938 ]); 939 } elseif ($url != '') { 940 // make sure when no url is defined, we keep it null 941 // default 942 $url .= rawurlencode($reference); 943 } 944 //handle as wiki links 945 if ($url && $url[0] === ':') { 946 $urlparam = ''; 947 $id = $url; 948 if (strpos($url, '?') !== false) { 949 [$id, $urlparam] = sexplode('?', $url, 2, ''); 950 } 951 $url = wl(cleanID($id), $urlparam); 952 $exists = page_exists($id); 953 } 954 if ($hash) $url .= '#' . rawurlencode($hash); 955 956 return $url; 957 } 958 959 #endregion 960} 961 962 963//Setup VIM: ex: et ts=4 : 964