Failed Conditions
Push — master ( af7ba5...31a58a )
by Andreas
06:52 queued 03:53
created

inc/parser/renderer.php (13 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Renderer output base class
4
 *
5
 * @author Harry Fuecks <[email protected]>
6
 * @author Andreas Gohr <[email protected]>
7
 */
8
9
use dokuwiki\Extension\Plugin;
10
use dokuwiki\Extension\SyntaxPlugin;
11
12
/**
13
 * Allowed chars in $language for code highlighting
14
 * @see GeSHi::set_language()
15
 */
16
define('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
 */
29
abstract class Doku_Renderer extends Plugin {
30
    /** @var array Settings, control the behavior of the renderer */
31
    public $info = array(
32
        'cache' => true, // may the rendered result cached?
33
        'toc'   => true, // render the TOC?
34
    );
35
36
    /** @var array contains the smiley configuration, set in p_render() */
37
    public $smileys = array();
38
    /** @var array contains the entity configuration, set in p_render() */
39
    public $entities = array();
40
    /** @var array contains the acronym configuration, set in p_render() */
41
    public $acronyms = array();
42
    /** @var array contains the interwiki configuration, set in p_render() */
43
    public $interwiki = array();
44
45
    /** @var array the list of headers used to create unique link ids */
46
    protected $headers = array();
47
48
    /**
49
     * @var string the rendered document, this will be cached after the renderer ran through
50
     */
51
    public $doc = '';
52
53
    /**
54
     * clean out any per-use values
55
     *
56
     * This is called before each use of the renderer object and should be used to
57
     * completely reset the state of the renderer to be reused for a new document
58
     */
59
    public function reset(){
60
        $this->headers = array();
61
    }
62
63
    /**
64
     * Allow the plugin to prevent DokuWiki from reusing an instance
65
     *
66
     * Since most renderer plugins fail to implement Doku_Renderer::reset() we default
67
     * to reinstantiating the renderer here
68
     *
69
     * @return bool   false if the plugin has to be instantiated
70
     */
71
    public function isSingleton() {
72
        return false;
73
    }
74
75
    /**
76
     * Returns the format produced by this renderer.
77
     *
78
     * Has to be overidden by sub classes
79
     *
80
     * @return string
81
     */
82
    abstract public function getFormat();
83
84
    /**
85
     * Disable caching of this renderer's output
86
     */
87
    public function nocache() {
88
        $this->info['cache'] = false;
89
    }
90
91
    /**
92
     * Disable TOC generation for this renderer's output
93
     *
94
     * This might not be used for certain sub renderer
95
     */
96
    public function notoc() {
97
        $this->info['toc'] = false;
98
    }
99
100
    /**
101
     * Handle plugin rendering
102
     *
103
     * Most likely this needs NOT to be overwritten by sub classes
104
     *
105
     * @param string $name  Plugin name
106
     * @param mixed  $data  custom data set by handler
107
     * @param string $state matched state if any
108
     * @param string $match raw matched syntax
109
     */
110
    public function plugin($name, $data, $state = '', $match = '') {
0 ignored issues
show
The parameter $state is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $match is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
111
        /** @var SyntaxPlugin $plugin */
112
        $plugin = plugin_load('syntax', $name);
113
        if($plugin != null) {
114
            $plugin->render($this->getFormat(), $this, $data);
115
        }
116
    }
117
118
    /**
119
     * handle nested render instructions
120
     * this method (and nest_close method) should not be overloaded in actual renderer output classes
121
     *
122
     * @param array $instructions
123
     */
124
    public function nest($instructions) {
125
        foreach($instructions as $instruction) {
126
            // execute the callback against ourself
127
            if(method_exists($this, $instruction[0])) {
128
                call_user_func_array(array($this, $instruction[0]), $instruction[1] ? $instruction[1] : array());
129
            }
130
        }
131
    }
132
133
    /**
134
     * dummy closing instruction issued by Doku_Handler_Nest
135
     *
136
     * normally the syntax mode should override this instruction when instantiating Doku_Handler_Nest -
137
     * however plugins will not be able to - as their instructions require data.
138
     */
139
    public function nest_close() {
140
    }
141
142
    #region Syntax modes - sub classes will need to implement them to fill $doc
143
144
    /**
145
     * Initialize the document
146
     */
147
    public function document_start() {
148
    }
149
150
    /**
151
     * Finalize the document
152
     */
153
    public function document_end() {
154
    }
155
156
    /**
157
     * Render the Table of Contents
158
     *
159
     * @return string
160
     */
161
    public function render_TOC() {
162
        return '';
163
    }
164
165
    /**
166
     * Add an item to the TOC
167
     *
168
     * @param string $id       the hash link
169
     * @param string $text     the text to display
170
     * @param int    $level    the nesting level
171
     */
172
    public function toc_additem($id, $text, $level) {
173
    }
174
175
    /**
176
     * Render a heading
177
     *
178
     * @param string $text  the text to display
179
     * @param int    $level header level
180
     * @param int    $pos   byte position in the original source
181
     */
182
    public function header($text, $level, $pos) {
183
    }
184
185
    /**
186
     * Open a new section
187
     *
188
     * @param int $level section level (as determined by the previous header)
189
     */
190
    public function section_open($level) {
191
    }
192
193
    /**
194
     * Close the current section
195
     */
196
    public function section_close() {
197
    }
198
199
    /**
200
     * Render plain text data
201
     *
202
     * @param string $text
203
     */
204
    public function cdata($text) {
205
    }
206
207
    /**
208
     * Open a paragraph
209
     */
210
    public function p_open() {
211
    }
212
213
    /**
214
     * Close a paragraph
215
     */
216
    public function p_close() {
217
    }
218
219
    /**
220
     * Create a line break
221
     */
222
    public function linebreak() {
223
    }
224
225
    /**
226
     * Create a horizontal line
227
     */
228
    public function hr() {
229
    }
230
231
    /**
232
     * Start strong (bold) formatting
233
     */
234
    public function strong_open() {
235
    }
236
237
    /**
238
     * Stop strong (bold) formatting
239
     */
240
    public function strong_close() {
241
    }
242
243
    /**
244
     * Start emphasis (italics) formatting
245
     */
246
    public function emphasis_open() {
247
    }
248
249
    /**
250
     * Stop emphasis (italics) formatting
251
     */
252
    public function emphasis_close() {
253
    }
254
255
    /**
256
     * Start underline formatting
257
     */
258
    public function underline_open() {
259
    }
260
261
    /**
262
     * Stop underline formatting
263
     */
264
    public function underline_close() {
265
    }
266
267
    /**
268
     * Start monospace formatting
269
     */
270
    public function monospace_open() {
271
    }
272
273
    /**
274
     * Stop monospace formatting
275
     */
276
    public function monospace_close() {
277
    }
278
279
    /**
280
     * Start a subscript
281
     */
282
    public function subscript_open() {
283
    }
284
285
    /**
286
     * Stop a subscript
287
     */
288
    public function subscript_close() {
289
    }
290
291
    /**
292
     * Start a superscript
293
     */
294
    public function superscript_open() {
295
    }
296
297
    /**
298
     * Stop a superscript
299
     */
300
    public function superscript_close() {
301
    }
302
303
    /**
304
     * Start deleted (strike-through) formatting
305
     */
306
    public function deleted_open() {
307
    }
308
309
    /**
310
     * Stop deleted (strike-through) formatting
311
     */
312
    public function deleted_close() {
313
    }
314
315
    /**
316
     * Start a footnote
317
     */
318
    public function footnote_open() {
319
    }
320
321
    /**
322
     * Stop a footnote
323
     */
324
    public function footnote_close() {
325
    }
326
327
    /**
328
     * Open an unordered list
329
     */
330
    public function listu_open() {
331
    }
332
333
    /**
334
     * Close an unordered list
335
     */
336
    public function listu_close() {
337
    }
338
339
    /**
340
     * Open an ordered list
341
     */
342
    public function listo_open() {
343
    }
344
345
    /**
346
     * Close an ordered list
347
     */
348
    public function listo_close() {
349
    }
350
351
    /**
352
     * Open a list item
353
     *
354
     * @param int $level the nesting level
355
     * @param bool $node true when a node; false when a leaf
356
     */
357
    public function listitem_open($level,$node=false) {
358
    }
359
360
    /**
361
     * Close a list item
362
     */
363
    public function listitem_close() {
364
    }
365
366
    /**
367
     * Start the content of a list item
368
     */
369
    public function listcontent_open() {
370
    }
371
372
    /**
373
     * Stop the content of a list item
374
     */
375
    public function listcontent_close() {
376
    }
377
378
    /**
379
     * Output unformatted $text
380
     *
381
     * Defaults to $this->cdata()
382
     *
383
     * @param string $text
384
     */
385
    public function unformatted($text) {
386
        $this->cdata($text);
387
    }
388
389
    /**
390
     * Output inline PHP code
391
     *
392
     * If $conf['phpok'] is true this should evaluate the given code and append the result
393
     * to $doc
394
     *
395
     * @param string $text The PHP code
396
     */
397
    public function php($text) {
398
    }
399
400
    /**
401
     * Output block level PHP code
402
     *
403
     * If $conf['phpok'] is true this should evaluate the given code and append the result
404
     * to $doc
405
     *
406
     * @param string $text The PHP code
407
     */
408
    public function phpblock($text) {
409
    }
410
411
    /**
412
     * Output raw inline HTML
413
     *
414
     * If $conf['htmlok'] is true this should add the code as is to $doc
415
     *
416
     * @param string $text The HTML
417
     */
418
    public function html($text) {
419
    }
420
421
    /**
422
     * Output raw block-level HTML
423
     *
424
     * If $conf['htmlok'] is true this should add the code as is to $doc
425
     *
426
     * @param string $text The HTML
427
     */
428
    public function htmlblock($text) {
429
    }
430
431
    /**
432
     * Output preformatted text
433
     *
434
     * @param string $text
435
     */
436
    public function preformatted($text) {
437
    }
438
439
    /**
440
     * Start a block quote
441
     */
442
    public function quote_open() {
443
    }
444
445
    /**
446
     * Stop a block quote
447
     */
448
    public function quote_close() {
449
    }
450
451
    /**
452
     * Display text as file content, optionally syntax highlighted
453
     *
454
     * @param string $text text to show
455
     * @param string $lang programming language to use for syntax highlighting
456
     * @param string $file file path label
457
     */
458
    public function file($text, $lang = null, $file = null) {
459
    }
460
461
    /**
462
     * Display text as code content, optionally syntax highlighted
463
     *
464
     * @param string $text text to show
465
     * @param string $lang programming language to use for syntax highlighting
466
     * @param string $file file path label
467
     */
468
    public function code($text, $lang = null, $file = null) {
469
    }
470
471
    /**
472
     * Format an acronym
473
     *
474
     * Uses $this->acronyms
475
     *
476
     * @param string $acronym
477
     */
478
    public function acronym($acronym) {
479
    }
480
481
    /**
482
     * Format a smiley
483
     *
484
     * Uses $this->smiley
485
     *
486
     * @param string $smiley
487
     */
488
    public function smiley($smiley) {
489
    }
490
491
    /**
492
     * Format an entity
493
     *
494
     * Entities are basically small text replacements
495
     *
496
     * Uses $this->entities
497
     *
498
     * @param string $entity
499
     */
500
    public function entity($entity) {
501
    }
502
503
    /**
504
     * Typographically format a multiply sign
505
     *
506
     * Example: ($x=640, $y=480) should result in "640×480"
507
     *
508
     * @param string|int $x first value
509
     * @param string|int $y second value
510
     */
511
    public function multiplyentity($x, $y) {
512
    }
513
514
    /**
515
     * Render an opening single quote char (language specific)
516
     */
517
    public function singlequoteopening() {
518
    }
519
520
    /**
521
     * Render a closing single quote char (language specific)
522
     */
523
    public function singlequoteclosing() {
524
    }
525
526
    /**
527
     * Render an apostrophe char (language specific)
528
     */
529
    public function apostrophe() {
530
    }
531
532
    /**
533
     * Render an opening double quote char (language specific)
534
     */
535
    public function doublequoteopening() {
536
    }
537
538
    /**
539
     * Render an closinging double quote char (language specific)
540
     */
541
    public function doublequoteclosing() {
542
    }
543
544
    /**
545
     * Render a CamelCase link
546
     *
547
     * @param string $link The link name
548
     * @see http://en.wikipedia.org/wiki/CamelCase
549
     */
550
    public function camelcaselink($link) {
551
    }
552
553
    /**
554
     * Render a page local link
555
     *
556
     * @param string $hash hash link identifier
557
     * @param string $name name for the link
558
     */
559
    public function locallink($hash, $name = null) {
560
    }
561
562
    /**
563
     * Render a wiki internal link
564
     *
565
     * @param string       $link  page ID to link to. eg. 'wiki:syntax'
566
     * @param string|array $title name for the link, array for media file
567
     */
568
    public function internallink($link, $title = null) {
569
    }
570
571
    /**
572
     * Render an external link
573
     *
574
     * @param string       $link  full URL with scheme
575
     * @param string|array $title name for the link, array for media file
576
     */
577
    public function externallink($link, $title = null) {
578
    }
579
580
    /**
581
     * Render the output of an RSS feed
582
     *
583
     * @param string $url    URL of the feed
584
     * @param array  $params Finetuning of the output
585
     */
586
    public function rss($url, $params) {
587
    }
588
589
    /**
590
     * Render an interwiki link
591
     *
592
     * You may want to use $this->_resolveInterWiki() here
593
     *
594
     * @param string       $link     original link - probably not much use
595
     * @param string|array $title    name for the link, array for media file
596
     * @param string       $wikiName indentifier (shortcut) for the remote wiki
597
     * @param string       $wikiUri  the fragment parsed from the original link
598
     */
599
    public function interwikilink($link, $title, $wikiName, $wikiUri) {
0 ignored issues
show
The parameter $link is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
600
    }
601
602
    /**
603
     * Link to file on users OS
604
     *
605
     * @param string       $link  the link
606
     * @param string|array $title name for the link, array for media file
607
     */
608
    public function filelink($link, $title = null) {
0 ignored issues
show
The parameter $link is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $title is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
609
    }
610
611
    /**
612
     * Link to windows share
613
     *
614
     * @param string       $link  the link
615
     * @param string|array $title name for the link, array for media file
616
     */
617
    public function windowssharelink($link, $title = null) {
618
    }
619
620
    /**
621
     * Render a linked E-Mail Address
622
     *
623
     * Should honor $conf['mailguard'] setting
624
     *
625
     * @param string $address Email-Address
626
     * @param string|array $name name for the link, array for media file
627
     */
628
    public function emaillink($address, $name = null) {
629
    }
630
631
    /**
632
     * Render an internal media file
633
     *
634
     * @param string $src     media ID
635
     * @param string $title   descriptive text
636
     * @param string $align   left|center|right
637
     * @param int    $width   width of media in pixel
638
     * @param int    $height  height of media in pixel
639
     * @param string $cache   cache|recache|nocache
640
     * @param string $linking linkonly|detail|nolink
641
     */
642
    public function internalmedia($src, $title = null, $align = null, $width = null,
643
                           $height = null, $cache = null, $linking = null) {
644
    }
645
646
    /**
647
     * Render an external media file
648
     *
649
     * @param string $src     full media URL
650
     * @param string $title   descriptive text
651
     * @param string $align   left|center|right
652
     * @param int    $width   width of media in pixel
653
     * @param int    $height  height of media in pixel
654
     * @param string $cache   cache|recache|nocache
655
     * @param string $linking linkonly|detail|nolink
656
     */
657
    public function externalmedia($src, $title = null, $align = null, $width = null,
658
                           $height = null, $cache = null, $linking = null) {
659
    }
660
661
    /**
662
     * Render a link to an internal media file
663
     *
664
     * @param string $src     media ID
665
     * @param string $title   descriptive text
666
     * @param string $align   left|center|right
667
     * @param int    $width   width of media in pixel
668
     * @param int    $height  height of media in pixel
669
     * @param string $cache   cache|recache|nocache
670
     */
671
    public function internalmedialink($src, $title = null, $align = null,
0 ignored issues
show
The parameter $src is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $title is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $align is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
672
                               $width = null, $height = null, $cache = null) {
673
    }
674
675
    /**
676
     * Render a link to an external media file
677
     *
678
     * @param string $src     media ID
679
     * @param string $title   descriptive text
680
     * @param string $align   left|center|right
681
     * @param int    $width   width of media in pixel
682
     * @param int    $height  height of media in pixel
683
     * @param string $cache   cache|recache|nocache
684
     */
685
    public function externalmedialink($src, $title = null, $align = null,
0 ignored issues
show
The parameter $src is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $title is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $align is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
686
                               $width = null, $height = null, $cache = null) {
687
    }
688
689
    /**
690
     * Start a table
691
     *
692
     * @param int $maxcols maximum number of columns
693
     * @param int $numrows NOT IMPLEMENTED
694
     * @param int $pos     byte position in the original source
695
     */
696
    public function table_open($maxcols = null, $numrows = null, $pos = null) {
0 ignored issues
show
The parameter $maxcols is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $numrows is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
697
    }
698
699
    /**
700
     * Close a table
701
     *
702
     * @param int $pos byte position in the original source
703
     */
704
    public function table_close($pos = null) {
705
    }
706
707
    /**
708
     * Open a table header
709
     */
710
    public function tablethead_open() {
711
    }
712
713
    /**
714
     * Close a table header
715
     */
716
    public function tablethead_close() {
717
    }
718
719
    /**
720
     * Open a table body
721
     */
722
    public function tabletbody_open() {
723
    }
724
725
    /**
726
     * Close a table body
727
     */
728
    public function tabletbody_close() {
729
    }
730
731
    /**
732
     * Open a table footer
733
     */
734
    public function tabletfoot_open() {
735
    }
736
737
    /**
738
     * Close a table footer
739
     */
740
    public function tabletfoot_close() {
741
    }
742
743
    /**
744
     * Open a table row
745
     */
746
    public function tablerow_open() {
747
    }
748
749
    /**
750
     * Close a table row
751
     */
752
    public function tablerow_close() {
753
    }
754
755
    /**
756
     * Open a table header cell
757
     *
758
     * @param int    $colspan
759
     * @param string $align left|center|right
760
     * @param int    $rowspan
761
     */
762
    public function tableheader_open($colspan = 1, $align = null, $rowspan = 1) {
763
    }
764
765
    /**
766
     * Close a table header cell
767
     */
768
    public function tableheader_close() {
769
    }
770
771
    /**
772
     * Open a table cell
773
     *
774
     * @param int    $colspan
775
     * @param string $align left|center|right
776
     * @param int    $rowspan
777
     */
778
    public function tablecell_open($colspan = 1, $align = null, $rowspan = 1) {
779
    }
780
781
    /**
782
     * Close a table cell
783
     */
784
    public function tablecell_close() {
785
    }
786
787
    #endregion
788
789
    #region util functions, you probably won't need to reimplement them
790
791
    /**
792
     * Creates a linkid from a headline
793
     *
794
     * @author Andreas Gohr <[email protected]>
795
     * @param string  $title   The headline title
796
     * @param boolean $create  Create a new unique ID?
797
     * @return string
798
     */
799
    public function _headerToLink($title, $create = false) {
800
        if($create) {
801
            return sectionID($title, $this->headers);
802
        } else {
803
            $check = false;
804
            return sectionID($title, $check);
805
        }
806
    }
807
808
    /**
809
     * Removes any Namespace from the given name but keeps
810
     * casing and special chars
811
     *
812
     * @author Andreas Gohr <[email protected]>
813
     *
814
     * @param string $name
815
     * @return string
816
     */
817
    protected function _simpleTitle($name) {
818
        global $conf;
819
820
        //if there is a hash we use the ancor name only
821
        @list($name, $hash) = explode('#', $name, 2);
822
        if($hash) return $hash;
823
824
        if($conf['useslash']) {
825
            $name = strtr($name, ';/', ';:');
826
        } else {
827
            $name = strtr($name, ';', ':');
828
        }
829
830
        return noNSorNS($name);
831
    }
832
833
    /**
834
     * Resolve an interwikilink
835
     *
836
     * @param string    $shortcut  identifier for the interwiki link
837
     * @param string    $reference fragment that refers the content
838
     * @param null|bool $exists    reference which returns if an internal page exists
839
     * @return string interwikilink
840
     */
841
    public function _resolveInterWiki(&$shortcut, $reference, &$exists = null) {
842
        //get interwiki URL
843
        if(isset($this->interwiki[$shortcut])) {
844
            $url = $this->interwiki[$shortcut];
845
        } else {
846
            // Default to Google I'm feeling lucky
847
            $url      = 'https://www.google.com/search?q={URL}&amp;btnI=lucky';
848
            $shortcut = 'go';
849
        }
850
851
        //split into hash and url part
852
        $hash = strrchr($reference, '#');
853
        if($hash) {
854
            $reference = substr($reference, 0, -strlen($hash));
855
            $hash = substr($hash, 1);
856
        }
857
858
        //replace placeholder
859
        if(preg_match('#\{(URL|NAME|SCHEME|HOST|PORT|PATH|QUERY)\}#', $url)) {
860
            //use placeholders
861
            $url    = str_replace('{URL}', rawurlencode($reference), $url);
862
            //wiki names will be cleaned next, otherwise urlencode unsafe chars
863
            $url    = str_replace('{NAME}', ($url{0} === ':') ? $reference :
864
                                  preg_replace_callback('/[[\\\\\]^`{|}#%]/', function($match) {
865
                                    return rawurlencode($match[0]);
866
                                  }, $reference), $url);
867
            $parsed = parse_url($reference);
868
            if (empty($parsed['scheme'])) $parsed['scheme'] = '';
869
            if (empty($parsed['host'])) $parsed['host'] = '';
870
            if (empty($parsed['port'])) $parsed['port'] = 80;
871
            if (empty($parsed['path'])) $parsed['path'] = '';
872
            if (empty($parsed['query'])) $parsed['query'] = '';
873
            $url = strtr($url,[
874
                '{SCHEME}' => $parsed['scheme'],
875
                '{HOST}' => $parsed['host'],
876
                '{PORT}' => $parsed['port'],
877
                '{PATH}' => $parsed['path'],
878
                '{QUERY}' => $parsed['query'] ,
879
            ]);
880
        } else {
881
            //default
882
            $url = $url.rawurlencode($reference);
883
        }
884
        //handle as wiki links
885
        if($url{0} === ':') {
886
            $urlparam = null;
887
            $id = $url;
888
            if (strpos($url, '?') !== false) {
889
                list($id, $urlparam) = explode('?', $url, 2);
890
            }
891
            $url    = wl(cleanID($id), $urlparam);
892
            $exists = page_exists($id);
893
        }
894
        if($hash) $url .= '#'.rawurlencode($hash);
895
896
        return $url;
897
    }
898
899
    #endregion
900
}
901
902
903
//Setup VIM: ex: et ts=4 :
904