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    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        //if there is a hash we use the ancor name only
895        [$name, $hash] = sexplode('#', $name, 2);
896        if ($hash) return $hash;
897
898        if ($conf['useslash']) {
899            $name = strtr($name, ';/', ';:');
900        } else {
901            $name = strtr($name, ';', ':');
902        }
903
904        return noNSorNS($name);
905    }
906
907    /**
908     * Resolve an interwikilink
909     *
910     * @param string $shortcut identifier for the interwiki link
911     * @param string $reference fragment that refers the content
912     * @param null|bool $exists reference which returns if an internal page exists
913     * @return string interwikilink
914     */
915    public function _resolveInterWiki(&$shortcut, $reference, &$exists = null)
916    {
917        //get interwiki URL
918        if (isset($this->interwiki[$shortcut])) {
919            $url = $this->interwiki[$shortcut];
920        } elseif (isset($this->interwiki['default'])) {
921            $shortcut = 'default';
922            $url = $this->interwiki[$shortcut];
923        } else {
924            // not parsable interwiki outputs '' to make sure string manipluation works
925            $shortcut = '';
926            $url = '';
927        }
928
929        //split into hash and url part
930        $hash = strrchr($reference, '#');
931        if ($hash) {
932            $reference = substr($reference, 0, -strlen($hash));
933            $hash = substr($hash, 1);
934        }
935
936        //replace placeholder
937        if (preg_match('#\{(URL|NAME|SCHEME|HOST|PORT|PATH|QUERY)\}#', $url)) {
938            //use placeholders
939            $url = str_replace('{URL}', rawurlencode($reference), $url);
940            //wiki names will be cleaned next, otherwise urlencode unsafe chars
941            $url = str_replace(
942                '{NAME}',
943                ($url[0] === ':') ? $reference : preg_replace_callback(
944                    '/[[\\\\\]^`{|}#%]/',
945                    static fn($match) => rawurlencode($match[0]),
946                    $reference
947                ),
948                $url
949            );
950            $parsed = parse_url($reference);
951            if (empty($parsed['scheme'])) $parsed['scheme'] = '';
952            if (empty($parsed['host'])) $parsed['host'] = '';
953            if (empty($parsed['port'])) $parsed['port'] = 80;
954            if (empty($parsed['path'])) $parsed['path'] = '';
955            if (empty($parsed['query'])) $parsed['query'] = '';
956            $url = strtr($url, [
957                '{SCHEME}' => $parsed['scheme'],
958                '{HOST}' => $parsed['host'],
959                '{PORT}' => $parsed['port'],
960                '{PATH}' => $parsed['path'],
961                '{QUERY}' => $parsed['query'],
962            ]);
963        } elseif ($url != '') {
964            // make sure when no url is defined, we keep it null
965            // default
966            $url .= rawurlencode($reference);
967        }
968        //handle as wiki links
969        if ($url && $url[0] === ':') {
970            $urlparam = '';
971            $id = $url;
972            if (strpos($url, '?') !== false) {
973                [$id, $urlparam] = sexplode('?', $url, 2, '');
974            }
975            $url = wl(cleanID($id), $urlparam);
976            $exists = page_exists($id);
977        }
978        if ($hash) $url .= '#' . rawurlencode($hash);
979
980        return $url;
981    }
982
983    #endregion
984}
985
986
987//Setup VIM: ex: et ts=4 :
988