1<?php 2 3/** 4 * Renderer output base class 5 * 6 * @author Harry Fuecks <hfuecks@gmail.com> 7 * @author Andreas Gohr <andi@splitbrain.org> 8 */ 9 10use dokuwiki\Extension\Plugin; 11use dokuwiki\Extension\SyntaxPlugin; 12 13/** 14 * Allowed chars in $language for code highlighting 15 * @see GeSHi::set_language() 16 */ 17define('PREG_PATTERN_VALID_LANGUAGE', '#[^a-zA-Z0-9\-_]#'); 18 19/** 20 * An empty renderer, produces no output 21 * 22 * Inherits from dokuwiki\Extension\Plugin for giving additional functions to render plugins 23 * 24 * The renderer transforms the syntax instructions created by the parser and handler into the 25 * desired output format. For each instruction a corresponding method defined in this class will 26 * be called. That method needs to produce the desired output for the instruction and add it to the 27 * $doc field. When all instructions are processed, the $doc field contents will be cached by 28 * DokuWiki and sent to the user. 29 */ 30abstract class Doku_Renderer extends Plugin 31{ 32 /** @var array Settings, control the behavior of the renderer */ 33 public $info = [ 34 'cache' => true, // may the rendered result cached? 35 'toc' => true, // render the TOC? 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 * @param string|string[]|null $classes Optional CSS classes 387 * @param int $start Starting number for the list (default 1) 388 */ 389 public function listo_open($classes = null, $start = 1) 390 { 391 } 392 393 /** 394 * Close an ordered list 395 */ 396 public function listo_close() 397 { 398 } 399 400 /** 401 * Open a list item 402 * 403 * @param int $level the nesting level 404 * @param bool $node true when a node; false when a leaf 405 */ 406 public function listitem_open($level, $node = false) 407 { 408 } 409 410 /** 411 * Close a list item 412 */ 413 public function listitem_close() 414 { 415 } 416 417 /** 418 * Start the content of a list item 419 */ 420 public function listcontent_open() 421 { 422 } 423 424 /** 425 * Stop the content of a list item 426 */ 427 public function listcontent_close() 428 { 429 } 430 431 /** 432 * Output unformatted $text 433 * 434 * Defaults to $this->cdata() 435 * 436 * @param string $text 437 */ 438 public function unformatted($text) 439 { 440 $this->cdata($text); 441 } 442 443 /** 444 * Output preformatted text 445 * 446 * @param string $text 447 */ 448 public function preformatted($text) 449 { 450 } 451 452 /** 453 * Start a block quote 454 */ 455 public function quote_open() 456 { 457 } 458 459 /** 460 * Stop a block quote 461 */ 462 public function quote_close() 463 { 464 } 465 466 /** 467 * Display text as file content, optionally syntax highlighted 468 * 469 * @param string $text text to show 470 * @param string $lang programming language to use for syntax highlighting 471 * @param string $file file path label 472 */ 473 public function file($text, $lang = null, $file = null) 474 { 475 } 476 477 /** 478 * Display text as code content, optionally syntax highlighted 479 * 480 * @param string $text text to show 481 * @param string $lang programming language to use for syntax highlighting 482 * @param string $file file path label 483 */ 484 public function code($text, $lang = null, $file = null) 485 { 486 } 487 488 /** 489 * Format an acronym 490 * 491 * Uses $this->acronyms 492 * 493 * @param string $acronym 494 */ 495 public function acronym($acronym) 496 { 497 } 498 499 /** 500 * Format a smiley 501 * 502 * Uses $this->smiley 503 * 504 * @param string $smiley 505 */ 506 public function smiley($smiley) 507 { 508 } 509 510 /** 511 * Format an entity 512 * 513 * Entities are basically small text replacements 514 * 515 * Uses $this->entities 516 * 517 * @param string $entity 518 */ 519 public function entity($entity) 520 { 521 } 522 523 /** 524 * Typographically format a multiply sign 525 * 526 * Example: ($x=640, $y=480) should result in "640×480" 527 * 528 * @param string|int $x first value 529 * @param string|int $y second value 530 */ 531 public function multiplyentity($x, $y) 532 { 533 } 534 535 /** 536 * Render an opening single quote char (language specific) 537 */ 538 public function singlequoteopening() 539 { 540 } 541 542 /** 543 * Render a closing single quote char (language specific) 544 */ 545 public function singlequoteclosing() 546 { 547 } 548 549 /** 550 * Render an apostrophe char (language specific) 551 */ 552 public function apostrophe() 553 { 554 } 555 556 /** 557 * Render an opening double quote char (language specific) 558 */ 559 public function doublequoteopening() 560 { 561 } 562 563 /** 564 * Render an closinging double quote char (language specific) 565 */ 566 public function doublequoteclosing() 567 { 568 } 569 570 /** 571 * Render a CamelCase link 572 * 573 * @param string $link The link name 574 * @see http://en.wikipedia.org/wiki/CamelCase 575 */ 576 public function camelcaselink($link) 577 { 578 } 579 580 /** 581 * Render a page local link 582 * 583 * @param string $hash hash link identifier 584 * @param string $name name for the link 585 */ 586 public function locallink($hash, $name = null) 587 { 588 } 589 590 /** 591 * Render a wiki internal link 592 * 593 * @param string $link page ID to link to. eg. 'wiki:syntax' 594 * @param string|array $title name for the link, array for media file 595 */ 596 public function internallink($link, $title = null) 597 { 598 } 599 600 /** 601 * Render an external link 602 * 603 * @param string $link full URL with scheme 604 * @param string|array $title name for the link, array for media file 605 */ 606 public function externallink($link, $title = null) 607 { 608 } 609 610 /** 611 * Render the output of an RSS feed 612 * 613 * @param string $url URL of the feed 614 * @param array $params Finetuning of the output 615 */ 616 public function rss($url, $params) 617 { 618 } 619 620 /** 621 * Render an interwiki link 622 * 623 * You may want to use $this->_resolveInterWiki() here 624 * 625 * @param string $link original link - probably not much use 626 * @param string|array $title name for the link, array for media file 627 * @param string $wikiName indentifier (shortcut) for the remote wiki 628 * @param string $wikiUri the fragment parsed from the original link 629 */ 630 public function interwikilink($link, $title, $wikiName, $wikiUri) 631 { 632 } 633 634 /** 635 * Link to file on users OS 636 * 637 * @param string $link the link 638 * @param string|array $title name for the link, array for media file 639 */ 640 public function filelink($link, $title = null) 641 { 642 } 643 644 /** 645 * Link to windows share 646 * 647 * @param string $link the link 648 * @param string|array $title name for the link, array for media file 649 */ 650 public function windowssharelink($link, $title = null) 651 { 652 } 653 654 /** 655 * Render a linked E-Mail Address 656 * 657 * Should honor $conf['mailguard'] setting 658 * 659 * @param string $address Email-Address 660 * @param string|array $name name for the link, array for media file 661 */ 662 public function emaillink($address, $name = null) 663 { 664 } 665 666 /** 667 * Render an internal media file 668 * 669 * @param string $src media ID 670 * @param string $title descriptive text 671 * @param string $align left|center|right 672 * @param int $width width of media in pixel 673 * @param int $height height of media in pixel 674 * @param string $cache cache|recache|nocache 675 * @param string $linking linkonly|detail|nolink 676 */ 677 public function internalmedia( 678 $src, 679 $title = null, 680 $align = null, 681 $width = null, 682 $height = null, 683 $cache = null, 684 $linking = null 685 ) { 686 } 687 688 /** 689 * Render an external media file 690 * 691 * @param string $src full media URL 692 * @param string $title descriptive text 693 * @param string $align left|center|right 694 * @param int $width width of media in pixel 695 * @param int $height height of media in pixel 696 * @param string $cache cache|recache|nocache 697 * @param string $linking linkonly|detail|nolink 698 */ 699 public function externalmedia( 700 $src, 701 $title = null, 702 $align = null, 703 $width = null, 704 $height = null, 705 $cache = null, 706 $linking = null 707 ) { 708 } 709 710 /** 711 * Render a link to an internal 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 internalmedialink( 721 $src, 722 $title = null, 723 $align = null, 724 $width = null, 725 $height = null, 726 $cache = null 727 ) { 728 } 729 730 /** 731 * Render a link to an external media file 732 * 733 * @param string $src media ID 734 * @param string $title descriptive text 735 * @param string $align left|center|right 736 * @param int $width width of media in pixel 737 * @param int $height height of media in pixel 738 * @param string $cache cache|recache|nocache 739 */ 740 public function externalmedialink( 741 $src, 742 $title = null, 743 $align = null, 744 $width = null, 745 $height = null, 746 $cache = null 747 ) { 748 } 749 750 /** 751 * Start a table 752 * 753 * @param int $maxcols maximum number of columns 754 * @param int $numrows NOT IMPLEMENTED 755 * @param int $pos byte position in the original source 756 */ 757 public function table_open($maxcols = null, $numrows = null, $pos = null) 758 { 759 } 760 761 /** 762 * Close a table 763 * 764 * @param int $pos byte position in the original source 765 */ 766 public function table_close($pos = null) 767 { 768 } 769 770 /** 771 * Open a table header 772 */ 773 public function tablethead_open() 774 { 775 } 776 777 /** 778 * Close a table header 779 */ 780 public function tablethead_close() 781 { 782 } 783 784 /** 785 * Open a table body 786 */ 787 public function tabletbody_open() 788 { 789 } 790 791 /** 792 * Close a table body 793 */ 794 public function tabletbody_close() 795 { 796 } 797 798 /** 799 * Open a table footer 800 */ 801 public function tabletfoot_open() 802 { 803 } 804 805 /** 806 * Close a table footer 807 */ 808 public function tabletfoot_close() 809 { 810 } 811 812 /** 813 * Open a table row 814 */ 815 public function tablerow_open() 816 { 817 } 818 819 /** 820 * Close a table row 821 */ 822 public function tablerow_close() 823 { 824 } 825 826 /** 827 * Open a table header cell 828 * 829 * @param int $colspan 830 * @param string $align left|center|right 831 * @param int $rowspan 832 */ 833 public function tableheader_open($colspan = 1, $align = null, $rowspan = 1) 834 { 835 } 836 837 /** 838 * Close a table header cell 839 */ 840 public function tableheader_close() 841 { 842 } 843 844 /** 845 * Open a table cell 846 * 847 * @param int $colspan 848 * @param string $align left|center|right 849 * @param int $rowspan 850 */ 851 public function tablecell_open($colspan = 1, $align = null, $rowspan = 1) 852 { 853 } 854 855 /** 856 * Close a table cell 857 */ 858 public function tablecell_close() 859 { 860 } 861 862 #endregion 863 864 #region util functions, you probably won't need to reimplement them 865 866 /** 867 * Creates a linkid from a headline 868 * 869 * @param string $title The headline title 870 * @param boolean $create Create a new unique ID? 871 * @return string 872 * @author Andreas Gohr <andi@splitbrain.org> 873 */ 874 public function _headerToLink($title, $create = false) 875 { 876 if ($create) { 877 return sectionID($title, $this->headers); 878 } else { 879 $check = false; 880 return sectionID($title, $check); 881 } 882 } 883 884 /** 885 * Removes any Namespace from the given name but keeps 886 * casing and special chars 887 * 888 * @param string $name 889 * @return string 890 * @author Andreas Gohr <andi@splitbrain.org> 891 * 892 */ 893 public function _simpleTitle($name) 894 { 895 global $conf; 896 897 // remove relative namespace 898 $name = ltrim($name, '~'); 899 900 //if there is a hash we use the ancor name only 901 [$name, $hash] = sexplode('#', $name, 2); 902 if ($hash) return $hash; 903 904 if ($conf['useslash']) { 905 $name = strtr($name, ';/', ';:'); 906 } else { 907 $name = strtr($name, ';', ':'); 908 } 909 910 return noNSorNS($name); 911 } 912 913 /** 914 * Resolve an interwikilink 915 * 916 * @param string $shortcut identifier for the interwiki link 917 * @param string $reference fragment that refers the content 918 * @param null|bool $exists reference which returns if an internal page exists 919 * @return string interwikilink 920 */ 921 public function _resolveInterWiki(&$shortcut, $reference, &$exists = null) 922 { 923 //get interwiki URL 924 if (isset($this->interwiki[$shortcut])) { 925 $url = $this->interwiki[$shortcut]; 926 } elseif (isset($this->interwiki['default'])) { 927 $shortcut = 'default'; 928 $url = $this->interwiki[$shortcut]; 929 } else { 930 // not parsable interwiki outputs '' to make sure string manipluation works 931 $shortcut = ''; 932 $url = ''; 933 } 934 935 //split into hash and url part 936 $hash = strrchr($reference, '#'); 937 if ($hash) { 938 $reference = substr($reference, 0, -strlen($hash)); 939 $hash = substr($hash, 1); 940 } 941 942 //replace placeholder 943 if (preg_match('#\{(URL|NAME|SCHEME|HOST|PORT|PATH|QUERY)\}#', $url)) { 944 //use placeholders 945 $url = str_replace('{URL}', rawurlencode($reference), $url); 946 //wiki names will be cleaned next, otherwise urlencode unsafe chars 947 $url = str_replace( 948 '{NAME}', 949 ($url[0] === ':') ? $reference : preg_replace_callback( 950 '/[[\\\\\]^`{|}#%]/', 951 static fn($match) => rawurlencode($match[0]), 952 $reference 953 ), 954 $url 955 ); 956 $parsed = parse_url($reference); 957 if (empty($parsed['scheme'])) $parsed['scheme'] = ''; 958 if (empty($parsed['host'])) $parsed['host'] = ''; 959 if (empty($parsed['port'])) $parsed['port'] = 80; 960 if (empty($parsed['path'])) $parsed['path'] = ''; 961 if (empty($parsed['query'])) $parsed['query'] = ''; 962 $url = strtr($url, [ 963 '{SCHEME}' => $parsed['scheme'], 964 '{HOST}' => $parsed['host'], 965 '{PORT}' => $parsed['port'], 966 '{PATH}' => $parsed['path'], 967 '{QUERY}' => $parsed['query'], 968 ]); 969 } elseif ($url != '') { 970 // make sure when no url is defined, we keep it null 971 // default 972 $url .= rawurlencode($reference); 973 } 974 //handle as wiki links 975 if ($url && $url[0] === ':') { 976 $urlparam = ''; 977 $id = $url; 978 if (str_contains($url, '?')) { 979 [$id, $urlparam] = sexplode('?', $url, 2, ''); 980 } 981 $url = wl(cleanID($id), $urlparam); 982 $exists = page_exists($id); 983 } 984 if ($hash) $url .= '#' . rawurlencode($hash); 985 986 return $url; 987 } 988 989 #endregion 990} 991 992 993//Setup VIM: ex: et ts=4 : 994