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 
10 use dokuwiki\Extension\Plugin;
11 use dokuwiki\Extension\SyntaxPlugin;
12 
13 /**
14  * Allowed chars in $language for code highlighting
15  * @see GeSHi::set_language()
16  */
17 define('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  */
30 abstract 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     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(
675         $src,
676         $title = null,
677         $align = null,
678         $width = null,
679         $height = null,
680         $cache = null,
681         $linking = null
682     ) {
683     }
684 
685     /**
686      * Render an external media file
687      *
688      * @param string $src full media URL
689      * @param string $title descriptive text
690      * @param string $align left|center|right
691      * @param int $width width of media in pixel
692      * @param int $height height of media in pixel
693      * @param string $cache cache|recache|nocache
694      * @param string $linking linkonly|detail|nolink
695      */
696     public function externalmedia(
697         $src,
698         $title = null,
699         $align = null,
700         $width = null,
701         $height = null,
702         $cache = null,
703         $linking = null
704     ) {
705     }
706 
707     /**
708      * Render a link to an internal media file
709      *
710      * @param string $src media ID
711      * @param string $title descriptive text
712      * @param string $align left|center|right
713      * @param int $width width of media in pixel
714      * @param int $height height of media in pixel
715      * @param string $cache cache|recache|nocache
716      */
717     public function internalmedialink(
718         $src,
719         $title = null,
720         $align = null,
721         $width = null,
722         $height = null,
723         $cache = null
724     ) {
725     }
726 
727     /**
728      * Render a link to an external media file
729      *
730      * @param string $src media ID
731      * @param string $title descriptive text
732      * @param string $align left|center|right
733      * @param int $width width of media in pixel
734      * @param int $height height of media in pixel
735      * @param string $cache cache|recache|nocache
736      */
737     public function externalmedialink(
738         $src,
739         $title = null,
740         $align = null,
741         $width = null,
742         $height = null,
743         $cache = null
744     ) {
745     }
746 
747     /**
748      * Start a table
749      *
750      * @param int $maxcols maximum number of columns
751      * @param int $numrows NOT IMPLEMENTED
752      * @param int $pos byte position in the original source
753      */
754     public function table_open($maxcols = null, $numrows = null, $pos = null)
755     {
756     }
757 
758     /**
759      * Close a table
760      *
761      * @param int $pos byte position in the original source
762      */
763     public function table_close($pos = null)
764     {
765     }
766 
767     /**
768      * Open a table header
769      */
770     public function tablethead_open()
771     {
772     }
773 
774     /**
775      * Close a table header
776      */
777     public function tablethead_close()
778     {
779     }
780 
781     /**
782      * Open a table body
783      */
784     public function tabletbody_open()
785     {
786     }
787 
788     /**
789      * Close a table body
790      */
791     public function tabletbody_close()
792     {
793     }
794 
795     /**
796      * Open a table footer
797      */
798     public function tabletfoot_open()
799     {
800     }
801 
802     /**
803      * Close a table footer
804      */
805     public function tabletfoot_close()
806     {
807     }
808 
809     /**
810      * Open a table row
811      */
812     public function tablerow_open()
813     {
814     }
815 
816     /**
817      * Close a table row
818      */
819     public function tablerow_close()
820     {
821     }
822 
823     /**
824      * Open a table header cell
825      *
826      * @param int $colspan
827      * @param string $align left|center|right
828      * @param int $rowspan
829      */
830     public function tableheader_open($colspan = 1, $align = null, $rowspan = 1)
831     {
832     }
833 
834     /**
835      * Close a table header cell
836      */
837     public function tableheader_close()
838     {
839     }
840 
841     /**
842      * Open a table cell
843      *
844      * @param int $colspan
845      * @param string $align left|center|right
846      * @param int $rowspan
847      */
848     public function tablecell_open($colspan = 1, $align = null, $rowspan = 1)
849     {
850     }
851 
852     /**
853      * Close a table cell
854      */
855     public function tablecell_close()
856     {
857     }
858 
859     #endregion
860 
861     #region util functions, you probably won't need to reimplement them
862 
863     /**
864      * Creates a linkid from a headline
865      *
866      * @param string $title The headline title
867      * @param boolean $create Create a new unique ID?
868      * @return string
869      * @author Andreas Gohr <andi@splitbrain.org>
870      */
871     public function _headerToLink($title, $create = false)
872     {
873         if ($create) {
874             return sectionID($title, $this->headers);
875         } else {
876             $check = false;
877             return sectionID($title, $check);
878         }
879     }
880 
881     /**
882      * Removes any Namespace from the given name but keeps
883      * casing and special chars
884      *
885      * @param string $name
886      * @return string
887      * @author Andreas Gohr <andi@splitbrain.org>
888      *
889      */
890     public function _simpleTitle($name)
891     {
892         global $conf;
893 
894         // remove relative namespace
895         $name = ltrim($name, '~');
896 
897         //if there is a hash we use the ancor name only
898         [$name, $hash] = sexplode('#', $name, 2);
899         if ($hash) return $hash;
900 
901         if ($conf['useslash']) {
902             $name = strtr($name, ';/', ';:');
903         } else {
904             $name = strtr($name, ';', ':');
905         }
906 
907         return noNSorNS($name);
908     }
909 
910     /**
911      * Resolve an interwikilink
912      *
913      * @param string $shortcut identifier for the interwiki link
914      * @param string $reference fragment that refers the content
915      * @param null|bool $exists reference which returns if an internal page exists
916      * @return string interwikilink
917      */
918     public function _resolveInterWiki(&$shortcut, $reference, &$exists = null)
919     {
920         //get interwiki URL
921         if (isset($this->interwiki[$shortcut])) {
922             $url = $this->interwiki[$shortcut];
923         } elseif (isset($this->interwiki['default'])) {
924             $shortcut = 'default';
925             $url = $this->interwiki[$shortcut];
926         } else {
927             // not parsable interwiki outputs '' to make sure string manipluation works
928             $shortcut = '';
929             $url = '';
930         }
931 
932         //split into hash and url part
933         $hash = strrchr($reference, '#');
934         if ($hash) {
935             $reference = substr($reference, 0, -strlen($hash));
936             $hash = substr($hash, 1);
937         }
938 
939         //replace placeholder
940         if (preg_match('#\{(URL|NAME|SCHEME|HOST|PORT|PATH|QUERY)\}#', $url)) {
941             //use placeholders
942             $url = str_replace('{URL}', rawurlencode($reference), $url);
943             //wiki names will be cleaned next, otherwise urlencode unsafe chars
944             $url = str_replace(
945                 '{NAME}',
946                 ($url[0] === ':') ? $reference : preg_replace_callback(
947                     '/[[\\\\\]^`{|}#%]/',
948                     static fn($match) => rawurlencode($match[0]),
949                     $reference
950                 ),
951                 $url
952             );
953             $parsed = parse_url($reference);
954             if (empty($parsed['scheme'])) $parsed['scheme'] = '';
955             if (empty($parsed['host'])) $parsed['host'] = '';
956             if (empty($parsed['port'])) $parsed['port'] = 80;
957             if (empty($parsed['path'])) $parsed['path'] = '';
958             if (empty($parsed['query'])) $parsed['query'] = '';
959             $url = strtr($url, [
960                 '{SCHEME}' => $parsed['scheme'],
961                 '{HOST}' => $parsed['host'],
962                 '{PORT}' => $parsed['port'],
963                 '{PATH}' => $parsed['path'],
964                 '{QUERY}' => $parsed['query'],
965             ]);
966         } elseif ($url != '') {
967             // make sure when no url is defined, we keep it null
968             // default
969             $url .= rawurlencode($reference);
970         }
971         //handle as wiki links
972         if ($url && $url[0] === ':') {
973             $urlparam = '';
974             $id = $url;
975             if (strpos($url, '?') !== false) {
976                 [$id, $urlparam] = sexplode('?', $url, 2, '');
977             }
978             $url = wl(cleanID($id), $urlparam);
979             $exists = page_exists($id);
980         }
981         if ($hash) $url .= '#' . rawurlencode($hash);
982 
983         return $url;
984     }
985 
986     #endregion
987 }
988 
989 
990 //Setup VIM: ex: et ts=4 :
991