Failed Conditions
Push — stable ( 017e16...b83837 )
by
unknown
07:54 queued 02:55
created

Doku_Renderer_xhtml   F

Complexity

Total Complexity 296

Size/Duplication

Total Lines 1984
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
dl 0
loc 1984
rs 0.8
c 0
b 0
f 0
wmc 296
lcom 1
cbo 5

92 Methods

Rating   Name   Duplication   Size   Complexity  
A acronym() 0 13 2
A smiley() 0 9 2
A entity() 0 7 2
A multiplyentity() 0 3 1
A singlequoteopening() 0 4 1
A singlequoteclosing() 0 4 1
A apostrophe() 0 4 1
A doublequoteopening() 0 4 1
A doublequoteclosing() 0 4 1
A camelcaselink() 0 7 2
A locallink() 0 16 2
A startSectionEdit() 0 21 5
A finishSectionEdit() 0 12 5
A getFormat() 0 3 1
A document_start() 0 4 1
C document_end() 0 57 12
A toc_additem() 0 8 3
B header() 0 40 8
A section_open() 0 3 1
A section_close() 0 3 1
A cdata() 0 3 1
A p_open() 0 3 1
A p_close() 0 3 1
A linebreak() 0 3 1
A hr() 0 3 1
A strong_open() 0 3 1
A strong_close() 0 3 1
A emphasis_open() 0 3 1
A emphasis_close() 0 3 1
A underline_open() 0 3 1
A underline_close() 0 3 1
A monospace_open() 0 3 1
A monospace_close() 0 3 1
A subscript_open() 0 3 1
A subscript_close() 0 3 1
A superscript_open() 0 3 1
A superscript_close() 0 3 1
A deleted_open() 0 3 1
A deleted_close() 0 3 1
A footnote_open() 0 6 1
A footnote_close() 0 25 2
A listu_open() 0 8 3
A listu_close() 0 3 1
A listo_open() 0 8 3
A listo_close() 0 3 1
A listitem_open() 0 4 2
A listitem_close() 0 3 1
A listcontent_open() 0 3 1
A listcontent_close() 0 3 1
A unformatted() 0 3 1
A php() 0 12 2
A phpblock() 0 3 1
A html() 0 9 2
A htmlblock() 0 3 1
A quote_open() 0 3 1
A quote_close() 0 3 1
A preformatted() 0 3 1
A file() 0 3 1
A code() 0 3 1
B _highlight() 0 54 8
F internallink() 0 77 12
B externallink() 0 51 9
B interwikilink() 0 48 11
A windowssharelink() 0 29 3
A emaillink() 0 38 5
D internalmedia() 0 58 16
F externalmedia() 0 46 17
D rss() 0 83 18
A table_open() 0 19 4
A table_close() 0 6 2
A tablethead_open() 0 3 1
A tablethead_close() 0 3 1
A tabletbody_open() 0 3 1
A tabletbody_close() 0 3 1
A tabletfoot_open() 0 3 1
A tabletfoot_close() 0 3 1
A tablerow_open() 0 10 3
A tablerow_close() 0 3 1
B tableheader_open() 0 20 6
A tableheader_close() 0 3 1
B tablecell_open() 0 20 6
A tablecell_close() 0 3 1
A getLastlevel() 0 3 1
B _formatLink() 0 29 8
F _media() 0 110 27
A _xmlEntities() 0 3 1
B _getLinkTitle() 0 17 7
A _imageTitle() 0 19 2
A _getMediaLinkConf() 0 16 2
F _video() 0 73 11
B _audio() 0 49 5
A _getLastMediaRevisionAt() 0 5 3

How to fix   Complexity   

Complex Class

Complex classes like Doku_Renderer_xhtml often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Doku_Renderer_xhtml, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
use dokuwiki\ChangeLog\MediaChangeLog;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, MediaChangeLog.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
4
5
/**
6
 * Renderer for XHTML output
7
 *
8
 * This is DokuWiki's main renderer used to display page content in the wiki
9
 *
10
 * @author Harry Fuecks <[email protected]>
11
 * @author Andreas Gohr <[email protected]>
12
 *
13
 */
14
class Doku_Renderer_xhtml extends Doku_Renderer {
15
    /** @var array store the table of contents */
16
    public $toc = array();
17
18
    /** @var array A stack of section edit data */
19
    protected $sectionedits = array();
20
21
    /** @var string|int link pages and media against this revision */
22
    public $date_at = '';
23
24
    /** @var int last section edit id, used by startSectionEdit */
25
    protected $lastsecid = 0;
26
27
    /** @var array a list of footnotes, list starts at 1! */
28
    protected $footnotes = array();
29
30
    /** @var int current section level */
31
    protected $lastlevel = 0;
32
    /** @var array section node tracker */
33
    protected $node = array(0, 0, 0, 0, 0);
34
35
    /** @var string temporary $doc store */
36
    protected $store = '';
37
38
    /** @var array global counter, for table classes etc. */
39
    protected $_counter = array(); //
40
41
    /** @var int counts the code and file blocks, used to provide download links */
42
    protected $_codeblock = 0;
43
44
    /** @var array list of allowed URL schemes */
45
    protected $schemes = null;
46
47
    /**
48
     * Register a new edit section range
49
     *
50
     * @param int    $start  The byte position for the edit start
51
     * @param array  $data   Associative array with section data:
52
     *                       Key 'name': the section name/title
53
     *                       Key 'target': the target for the section edit,
54
     *                                     e.g. 'section' or 'table'
55
     *                       Key 'hid': header id
56
     *                       Key 'codeblockOffset': actual code block index
57
     *                       Key 'start': set in startSectionEdit(),
58
     *                                    do not set yourself
59
     *                       Key 'range': calculated from 'start' and
60
     *                                    $key in finishSectionEdit(),
61
     *                                    do not set yourself
62
     * @return string  A marker class for the starting HTML element
63
     *
64
     * @author Adrian Lang <[email protected]>
65
     */
66
    public function startSectionEdit($start, $data) {
67
        if (!is_array($data)) {
68
            msg(
69
                sprintf(
70
                    'startSectionEdit: $data "%s" is NOT an array! One of your plugins needs an update.',
71
                    hsc((string) $data)
72
                ), -1
73
            );
74
75
            // @deprecated 2018-04-14, backward compatibility
76
            $args = func_get_args();
77
            $data = array();
78
            if(isset($args[1])) $data['target'] = $args[1];
79
            if(isset($args[2])) $data['name'] = $args[2];
80
            if(isset($args[3])) $data['hid'] = $args[3];
81
        }
82
        $data['secid'] = ++$this->lastsecid;
83
        $data['start'] = $start;
84
        $this->sectionedits[] = $data;
85
        return 'sectionedit'.$data['secid'];
86
    }
87
88
    /**
89
     * Finish an edit section range
90
     *
91
     * @param int  $end     The byte position for the edit end; null for the rest of the page
92
     *
93
     * @author Adrian Lang <[email protected]>
94
     */
95
    public function finishSectionEdit($end = null, $hid = null) {
96
        $data = array_pop($this->sectionedits);
97
        if(!is_null($end) && $end <= $data['start']) {
98
            return;
99
        }
100
        if(!is_null($hid)) {
101
            $data['hid'] .= $hid;
102
        }
103
        $data['range'] = $data['start'].'-'.(is_null($end) ? '' : $end);
104
        unset($data['start']);
105
        $this->doc .= '<!-- EDIT'.hsc(json_encode ($data)).' -->';
106
    }
107
108
    /**
109
     * Returns the format produced by this renderer.
110
     *
111
     * @return string always 'xhtml'
112
     */
113
    public function getFormat() {
114
        return 'xhtml';
115
    }
116
117
    /**
118
     * Initialize the document
119
     */
120
    public function document_start() {
121
        //reset some internals
122
        $this->toc     = array();
123
    }
124
125
    /**
126
     * Finalize the document
127
     */
128
    public function document_end() {
129
        // Finish open section edits.
130
        while(count($this->sectionedits) > 0) {
131
            if($this->sectionedits[count($this->sectionedits) - 1]['start'] <= 1) {
132
                // If there is only one section, do not write a section edit
133
                // marker.
134
                array_pop($this->sectionedits);
135
            } else {
136
                $this->finishSectionEdit();
137
            }
138
        }
139
140
        if(count($this->footnotes) > 0) {
141
            $this->doc .= '<div class="footnotes">'.DOKU_LF;
142
143
            foreach($this->footnotes as $id => $footnote) {
144
                // check its not a placeholder that indicates actual footnote text is elsewhere
145
                if(substr($footnote, 0, 5) != "@@FNT") {
146
147
                    // open the footnote and set the anchor and backlink
148
                    $this->doc .= '<div class="fn">';
149
                    $this->doc .= '<sup><a href="#fnt__'.$id.'" id="fn__'.$id.'" class="fn_bot">';
150
                    $this->doc .= $id.')</a></sup> '.DOKU_LF;
151
152
                    // get any other footnotes that use the same markup
153
                    $alt = array_keys($this->footnotes, "@@FNT$id");
154
155
                    if(count($alt)) {
156
                        foreach($alt as $ref) {
157
                            // set anchor and backlink for the other footnotes
158
                            $this->doc .= ', <sup><a href="#fnt__'.($ref).'" id="fn__'.($ref).'" class="fn_bot">';
159
                            $this->doc .= ($ref).')</a></sup> '.DOKU_LF;
160
                        }
161
                    }
162
163
                    // add footnote markup and close this footnote
164
                    $this->doc .= '<div class="content">'.$footnote.'</div>';
165
                    $this->doc .= '</div>'.DOKU_LF;
166
                }
167
            }
168
            $this->doc .= '</div>'.DOKU_LF;
169
        }
170
171
        // Prepare the TOC
172
        global $conf;
173
        if(
174
            $this->info['toc'] &&
175
            is_array($this->toc) &&
176
            $conf['tocminheads'] && count($this->toc) >= $conf['tocminheads']
177
        ) {
178
            global $TOC;
179
            $TOC = $this->toc;
180
        }
181
182
        // make sure there are no empty paragraphs
183
        $this->doc = preg_replace('#<p>\s*</p>#', '', $this->doc);
184
    }
185
186
    /**
187
     * Add an item to the TOC
188
     *
189
     * @param string $id       the hash link
190
     * @param string $text     the text to display
191
     * @param int    $level    the nesting level
192
     */
193
    public function toc_additem($id, $text, $level) {
194
        global $conf;
195
196
        //handle TOC
197
        if($level >= $conf['toptoclevel'] && $level <= $conf['maxtoclevel']) {
198
            $this->toc[] = html_mktocitem($id, $text, $level - $conf['toptoclevel'] + 1);
199
        }
200
    }
201
202
    /**
203
     * Render a heading
204
     *
205
     * @param string $text  the text to display
206
     * @param int    $level header level
207
     * @param int    $pos   byte position in the original source
208
     */
209
    public function header($text, $level, $pos) {
210
        global $conf;
211
212
        if(blank($text)) return; //skip empty headlines
213
214
        $hid = $this->_headerToLink($text, true);
215
216
        //only add items within configured levels
217
        $this->toc_additem($hid, $text, $level);
218
219
        // adjust $node to reflect hierarchy of levels
220
        $this->node[$level - 1]++;
221
        if($level < $this->lastlevel) {
222
            for($i = 0; $i < $this->lastlevel - $level; $i++) {
223
                $this->node[$this->lastlevel - $i - 1] = 0;
224
            }
225
        }
226
        $this->lastlevel = $level;
227
228
        if($level <= $conf['maxseclevel'] &&
229
            count($this->sectionedits) > 0 &&
230
            $this->sectionedits[count($this->sectionedits) - 1]['target'] === 'section'
231
        ) {
232
            $this->finishSectionEdit($pos - 1);
233
        }
234
235
        // write the header
236
        $this->doc .= DOKU_LF.'<h'.$level;
237
        if($level <= $conf['maxseclevel']) {
238
            $data = array();
239
            $data['target'] = 'section';
240
            $data['name'] = $text;
241
            $data['hid'] = $hid;
242
            $data['codeblockOffset'] = $this->_codeblock;
243
            $this->doc .= ' class="'.$this->startSectionEdit($pos, $data).'"';
244
        }
245
        $this->doc .= ' id="'.$hid.'">';
246
        $this->doc .= $this->_xmlEntities($text);
247
        $this->doc .= "</h$level>".DOKU_LF;
248
    }
249
250
    /**
251
     * Open a new section
252
     *
253
     * @param int $level section level (as determined by the previous header)
254
     */
255
    public function section_open($level) {
256
        $this->doc .= '<div class="level'.$level.'">'.DOKU_LF;
257
    }
258
259
    /**
260
     * Close the current section
261
     */
262
    public function section_close() {
263
        $this->doc .= DOKU_LF.'</div>'.DOKU_LF;
264
    }
265
266
    /**
267
     * Render plain text data
268
     *
269
     * @param $text
270
     */
271
    public function cdata($text) {
272
        $this->doc .= $this->_xmlEntities($text);
273
    }
274
275
    /**
276
     * Open a paragraph
277
     */
278
    public function p_open() {
279
        $this->doc .= DOKU_LF.'<p>'.DOKU_LF;
280
    }
281
282
    /**
283
     * Close a paragraph
284
     */
285
    public function p_close() {
286
        $this->doc .= DOKU_LF.'</p>'.DOKU_LF;
287
    }
288
289
    /**
290
     * Create a line break
291
     */
292
    public function linebreak() {
293
        $this->doc .= '<br/>'.DOKU_LF;
294
    }
295
296
    /**
297
     * Create a horizontal line
298
     */
299
    public function hr() {
300
        $this->doc .= '<hr />'.DOKU_LF;
301
    }
302
303
    /**
304
     * Start strong (bold) formatting
305
     */
306
    public function strong_open() {
307
        $this->doc .= '<strong>';
308
    }
309
310
    /**
311
     * Stop strong (bold) formatting
312
     */
313
    public function strong_close() {
314
        $this->doc .= '</strong>';
315
    }
316
317
    /**
318
     * Start emphasis (italics) formatting
319
     */
320
    public function emphasis_open() {
321
        $this->doc .= '<em>';
322
    }
323
324
    /**
325
     * Stop emphasis (italics) formatting
326
     */
327
    public function emphasis_close() {
328
        $this->doc .= '</em>';
329
    }
330
331
    /**
332
     * Start underline formatting
333
     */
334
    public function underline_open() {
335
        $this->doc .= '<em class="u">';
336
    }
337
338
    /**
339
     * Stop underline formatting
340
     */
341
    public function underline_close() {
342
        $this->doc .= '</em>';
343
    }
344
345
    /**
346
     * Start monospace formatting
347
     */
348
    public function monospace_open() {
349
        $this->doc .= '<code>';
350
    }
351
352
    /**
353
     * Stop monospace formatting
354
     */
355
    public function monospace_close() {
356
        $this->doc .= '</code>';
357
    }
358
359
    /**
360
     * Start a subscript
361
     */
362
    public function subscript_open() {
363
        $this->doc .= '<sub>';
364
    }
365
366
    /**
367
     * Stop a subscript
368
     */
369
    public function subscript_close() {
370
        $this->doc .= '</sub>';
371
    }
372
373
    /**
374
     * Start a superscript
375
     */
376
    public function superscript_open() {
377
        $this->doc .= '<sup>';
378
    }
379
380
    /**
381
     * Stop a superscript
382
     */
383
    public function superscript_close() {
384
        $this->doc .= '</sup>';
385
    }
386
387
    /**
388
     * Start deleted (strike-through) formatting
389
     */
390
    public function deleted_open() {
391
        $this->doc .= '<del>';
392
    }
393
394
    /**
395
     * Stop deleted (strike-through) formatting
396
     */
397
    public function deleted_close() {
398
        $this->doc .= '</del>';
399
    }
400
401
    /**
402
     * Callback for footnote start syntax
403
     *
404
     * All following content will go to the footnote instead of
405
     * the document. To achieve this the previous rendered content
406
     * is moved to $store and $doc is cleared
407
     *
408
     * @author Andreas Gohr <[email protected]>
409
     */
410
    public function footnote_open() {
411
412
        // move current content to store and record footnote
413
        $this->store = $this->doc;
414
        $this->doc   = '';
415
    }
416
417
    /**
418
     * Callback for footnote end syntax
419
     *
420
     * All rendered content is moved to the $footnotes array and the old
421
     * content is restored from $store again
422
     *
423
     * @author Andreas Gohr
424
     */
425
    public function footnote_close() {
426
        /** @var $fnid int takes track of seen footnotes, assures they are unique even across multiple docs FS#2841 */
427
        static $fnid = 0;
428
        // assign new footnote id (we start at 1)
429
        $fnid++;
430
431
        // recover footnote into the stack and restore old content
432
        $footnote    = $this->doc;
433
        $this->doc   = $this->store;
434
        $this->store = '';
435
436
        // check to see if this footnote has been seen before
437
        $i = array_search($footnote, $this->footnotes);
438
439
        if($i === false) {
440
            // its a new footnote, add it to the $footnotes array
441
            $this->footnotes[$fnid] = $footnote;
442
        } else {
443
            // seen this one before, save a placeholder
444
            $this->footnotes[$fnid] = "@@FNT".($i);
445
        }
446
447
        // output the footnote reference and link
448
        $this->doc .= '<sup><a href="#fn__'.$fnid.'" id="fnt__'.$fnid.'" class="fn_top">'.$fnid.')</a></sup>';
449
    }
450
451
    /**
452
     * Open an unordered list
453
     *
454
     * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input
455
     */
456
    public function listu_open($classes = null) {
457
        $class = '';
458
        if($classes !== null) {
459
            if(is_array($classes)) $classes = join(' ', $classes);
460
            $class = " class=\"$classes\"";
461
        }
462
        $this->doc .= "<ul$class>".DOKU_LF;
463
    }
464
465
    /**
466
     * Close an unordered list
467
     */
468
    public function listu_close() {
469
        $this->doc .= '</ul>'.DOKU_LF;
470
    }
471
472
    /**
473
     * Open an ordered list
474
     *
475
     * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input
476
     */
477
    public function listo_open($classes = null) {
478
        $class = '';
479
        if($classes !== null) {
480
            if(is_array($classes)) $classes = join(' ', $classes);
481
            $class = " class=\"$classes\"";
482
        }
483
        $this->doc .= "<ol$class>".DOKU_LF;
484
    }
485
486
    /**
487
     * Close an ordered list
488
     */
489
    public function listo_close() {
490
        $this->doc .= '</ol>'.DOKU_LF;
491
    }
492
493
    /**
494
     * Open a list item
495
     *
496
     * @param int $level the nesting level
497
     * @param bool $node true when a node; false when a leaf
498
     */
499
    public function listitem_open($level, $node=false) {
500
        $branching = $node ? ' node' : '';
501
        $this->doc .= '<li class="level'.$level.$branching.'">';
502
    }
503
504
    /**
505
     * Close a list item
506
     */
507
    public function listitem_close() {
508
        $this->doc .= '</li>'.DOKU_LF;
509
    }
510
511
    /**
512
     * Start the content of a list item
513
     */
514
    public function listcontent_open() {
515
        $this->doc .= '<div class="li">';
516
    }
517
518
    /**
519
     * Stop the content of a list item
520
     */
521
    public function listcontent_close() {
522
        $this->doc .= '</div>'.DOKU_LF;
523
    }
524
525
    /**
526
     * Output unformatted $text
527
     *
528
     * Defaults to $this->cdata()
529
     *
530
     * @param string $text
531
     */
532
    public function unformatted($text) {
533
        $this->doc .= $this->_xmlEntities($text);
534
    }
535
536
    /**
537
     * Execute PHP code if allowed
538
     *
539
     * @param  string $text      PHP code that is either executed or printed
540
     * @param  string $wrapper   html element to wrap result if $conf['phpok'] is okff
541
     *
542
     * @author Andreas Gohr <[email protected]>
543
     */
544
    public function php($text, $wrapper = 'code') {
545
        global $conf;
546
547
        if($conf['phpok']) {
548
            ob_start();
549
            eval($text);
550
            $this->doc .= ob_get_contents();
551
            ob_end_clean();
552
        } else {
553
            $this->doc .= p_xhtml_cached_geshi($text, 'php', $wrapper);
554
        }
555
    }
556
557
    /**
558
     * Output block level PHP code
559
     *
560
     * If $conf['phpok'] is true this should evaluate the given code and append the result
561
     * to $doc
562
     *
563
     * @param string $text The PHP code
564
     */
565
    public function phpblock($text) {
566
        $this->php($text, 'pre');
567
    }
568
569
    /**
570
     * Insert HTML if allowed
571
     *
572
     * @param  string $text      html text
573
     * @param  string $wrapper   html element to wrap result if $conf['htmlok'] is okff
574
     *
575
     * @author Andreas Gohr <[email protected]>
576
     */
577
    public function html($text, $wrapper = 'code') {
578
        global $conf;
579
580
        if($conf['htmlok']) {
581
            $this->doc .= $text;
582
        } else {
583
            $this->doc .= p_xhtml_cached_geshi($text, 'html4strict', $wrapper);
584
        }
585
    }
586
587
    /**
588
     * Output raw block-level HTML
589
     *
590
     * If $conf['htmlok'] is true this should add the code as is to $doc
591
     *
592
     * @param string $text The HTML
593
     */
594
    public function htmlblock($text) {
595
        $this->html($text, 'pre');
596
    }
597
598
    /**
599
     * Start a block quote
600
     */
601
    public function quote_open() {
602
        $this->doc .= '<blockquote><div class="no">'.DOKU_LF;
603
    }
604
605
    /**
606
     * Stop a block quote
607
     */
608
    public function quote_close() {
609
        $this->doc .= '</div></blockquote>'.DOKU_LF;
610
    }
611
612
    /**
613
     * Output preformatted text
614
     *
615
     * @param string $text
616
     */
617
    public function preformatted($text) {
618
        $this->doc .= '<pre class="code">'.trim($this->_xmlEntities($text), "\n\r").'</pre>'.DOKU_LF;
619
    }
620
621
    /**
622
     * Display text as file content, optionally syntax highlighted
623
     *
624
     * @param string $text     text to show
625
     * @param string $language programming language to use for syntax highlighting
626
     * @param string $filename file path label
627
     * @param array  $options  assoziative array with additional geshi options
628
     */
629
    public function file($text, $language = null, $filename = null, $options=null) {
630
        $this->_highlight('file', $text, $language, $filename, $options);
631
    }
632
633
    /**
634
     * Display text as code content, optionally syntax highlighted
635
     *
636
     * @param string $text     text to show
637
     * @param string $language programming language to use for syntax highlighting
638
     * @param string $filename file path label
639
     * @param array  $options  assoziative array with additional geshi options
640
     */
641
    public function code($text, $language = null, $filename = null, $options=null) {
642
        $this->_highlight('code', $text, $language, $filename, $options);
643
    }
644
645
    /**
646
     * Use GeSHi to highlight language syntax in code and file blocks
647
     *
648
     * @author Andreas Gohr <[email protected]>
649
     * @param string $type     code|file
650
     * @param string $text     text to show
651
     * @param string $language programming language to use for syntax highlighting
652
     * @param string $filename file path label
653
     * @param array  $options  assoziative array with additional geshi options
654
     */
655
    public function _highlight($type, $text, $language = null, $filename = null, $options = null) {
656
        global $ID;
657
        global $lang;
658
        global $INPUT;
659
660
        $language = preg_replace(PREG_PATTERN_VALID_LANGUAGE, '', $language);
661
662
        $language = preg_replace(PREG_PATTERN_VALID_LANGUAGE, '', $language);
663
664
        if($filename) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $filename of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
665
            // add icon
666
            list($ext) = mimetype($filename, false);
667
            $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext);
668
            $class = 'mediafile mf_'.$class;
669
670
            $offset = 0;
671
            if ($INPUT->has('codeblockOffset')) {
672
                $offset = $INPUT->str('codeblockOffset');
673
            }
674
            $this->doc .= '<dl class="'.$type.'">'.DOKU_LF;
675
            $this->doc .= '<dt><a href="' .
676
                exportlink(
677
                    $ID,
678
                    'code',
679
                    array('codeblock' => $offset + $this->_codeblock)
680
                ) . '" title="' . $lang['download'] . '" class="' . $class . '">';
681
            $this->doc .= hsc($filename);
682
            $this->doc .= '</a></dt>'.DOKU_LF.'<dd>';
683
        }
684
685
        if($text[0] == "\n") {
686
            $text = substr($text, 1);
687
        }
688
        if(substr($text, -1) == "\n") {
689
            $text = substr($text, 0, -1);
690
        }
691
692
        if(empty($language)) { // empty is faster than is_null and can prevent '' string
693
            $this->doc .= '<pre class="'.$type.'">'.$this->_xmlEntities($text).'</pre>'.DOKU_LF;
694
        } else {
695
            $class = 'code'; //we always need the code class to make the syntax highlighting apply
696
            if($type != 'code') $class .= ' '.$type;
697
698
            $this->doc .= "<pre class=\"$class $language\">" .
699
                p_xhtml_cached_geshi($text, $language, '', $options) .
700
                '</pre>' . DOKU_LF;
701
        }
702
703
        if($filename) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $filename of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
704
            $this->doc .= '</dd></dl>'.DOKU_LF;
705
        }
706
707
        $this->_codeblock++;
708
    }
709
710
    /**
711
     * Format an acronym
712
     *
713
     * Uses $this->acronyms
714
     *
715
     * @param string $acronym
716
     */
717
    public function acronym($acronym) {
718
719
        if(array_key_exists($acronym, $this->acronyms)) {
720
721
            $title = $this->_xmlEntities($this->acronyms[$acronym]);
722
723
            $this->doc .= '<abbr title="'.$title
724
                .'">'.$this->_xmlEntities($acronym).'</abbr>';
725
726
        } else {
727
            $this->doc .= $this->_xmlEntities($acronym);
728
        }
729
    }
730
731
    /**
732
     * Format a smiley
733
     *
734
     * Uses $this->smiley
735
     *
736
     * @param string $smiley
737
     */
738
    public function smiley($smiley) {
739
        if(array_key_exists($smiley, $this->smileys)) {
740
            $this->doc .= '<img src="'.DOKU_BASE.'lib/images/smileys/'.$this->smileys[$smiley].
741
                '" class="icon" alt="'.
742
                $this->_xmlEntities($smiley).'" />';
743
        } else {
744
            $this->doc .= $this->_xmlEntities($smiley);
745
        }
746
    }
747
748
    /**
749
     * Format an entity
750
     *
751
     * Entities are basically small text replacements
752
     *
753
     * Uses $this->entities
754
     *
755
     * @param string $entity
756
     */
757
    public function entity($entity) {
758
        if(array_key_exists($entity, $this->entities)) {
759
            $this->doc .= $this->entities[$entity];
760
        } else {
761
            $this->doc .= $this->_xmlEntities($entity);
762
        }
763
    }
764
765
    /**
766
     * Typographically format a multiply sign
767
     *
768
     * Example: ($x=640, $y=480) should result in "640×480"
769
     *
770
     * @param string|int $x first value
771
     * @param string|int $y second value
772
     */
773
    public function multiplyentity($x, $y) {
774
        $this->doc .= "$x&times;$y";
775
    }
776
777
    /**
778
     * Render an opening single quote char (language specific)
779
     */
780
    public function singlequoteopening() {
781
        global $lang;
782
        $this->doc .= $lang['singlequoteopening'];
783
    }
784
785
    /**
786
     * Render a closing single quote char (language specific)
787
     */
788
    public function singlequoteclosing() {
789
        global $lang;
790
        $this->doc .= $lang['singlequoteclosing'];
791
    }
792
793
    /**
794
     * Render an apostrophe char (language specific)
795
     */
796
    public function apostrophe() {
797
        global $lang;
798
        $this->doc .= $lang['apostrophe'];
799
    }
800
801
    /**
802
     * Render an opening double quote char (language specific)
803
     */
804
    public function doublequoteopening() {
805
        global $lang;
806
        $this->doc .= $lang['doublequoteopening'];
807
    }
808
809
    /**
810
     * Render an closinging double quote char (language specific)
811
     */
812
    public function doublequoteclosing() {
813
        global $lang;
814
        $this->doc .= $lang['doublequoteclosing'];
815
    }
816
817
    /**
818
     * Render a CamelCase link
819
     *
820
     * @param string $link       The link name
821
     * @param bool   $returnonly whether to return html or write to doc attribute
822
     * @return void|string writes to doc attribute or returns html depends on $returnonly
823
     *
824
     * @see http://en.wikipedia.org/wiki/CamelCase
825
     */
826
    public function camelcaselink($link, $returnonly = false) {
827
        if($returnonly) {
828
          return $this->internallink($link, $link, null, true);
829
        } else {
830
          $this->internallink($link, $link);
831
        }
832
    }
833
834
    /**
835
     * Render a page local link
836
     *
837
     * @param string $hash       hash link identifier
838
     * @param string $name       name for the link
839
     * @param bool   $returnonly whether to return html or write to doc attribute
840
     * @return void|string writes to doc attribute or returns html depends on $returnonly
841
     */
842
    public function locallink($hash, $name = null, $returnonly = false) {
843
        global $ID;
844
        $name  = $this->_getLinkTitle($name, $hash, $isImage);
845
        $hash  = $this->_headerToLink($hash);
846
        $title = $ID.' ↵';
847
848
        $doc = '<a href="#'.$hash.'" title="'.$title.'" class="wikilink1">';
849
        $doc .= $name;
850
        $doc .= '</a>';
851
852
        if($returnonly) {
853
          return $doc;
854
        } else {
855
          $this->doc .= $doc;
856
        }
857
    }
858
859
    /**
860
     * Render an internal Wiki Link
861
     *
862
     * $search,$returnonly & $linktype are not for the renderer but are used
863
     * elsewhere - no need to implement them in other renderers
864
     *
865
     * @author Andreas Gohr <[email protected]>
866
     * @param string      $id         pageid
867
     * @param string|null $name       link name
868
     * @param string|null $search     adds search url param
869
     * @param bool        $returnonly whether to return html or write to doc attribute
870
     * @param string      $linktype   type to set use of headings
871
     * @return void|string writes to doc attribute or returns html depends on $returnonly
872
     */
873
    public function internallink($id, $name = null, $search = null, $returnonly = false, $linktype = 'content') {
874
        global $conf;
875
        global $ID;
876
        global $INFO;
877
878
        $params = '';
879
        $parts  = explode('?', $id, 2);
880
        if(count($parts) === 2) {
881
            $id     = $parts[0];
882
            $params = $parts[1];
883
        }
884
885
        // For empty $id we need to know the current $ID
886
        // We need this check because _simpleTitle needs
887
        // correct $id and resolve_pageid() use cleanID($id)
888
        // (some things could be lost)
889
        if($id === '') {
890
            $id = $ID;
891
        }
892
893
        // default name is based on $id as given
894
        $default = $this->_simpleTitle($id);
895
896
        // now first resolve and clean up the $id
897
        resolve_pageid(getNS($ID), $id, $exists, $this->date_at, true);
0 ignored issues
show
Security Bug introduced by
It seems like getNS($ID) targeting getNS() can also be of type false; however, resolve_pageid() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
898
899
        $link = array();
900
        $name = $this->_getLinkTitle($name, $default, $isImage, $id, $linktype);
901
        if(!$isImage) {
902
            if($exists) {
903
                $class = 'wikilink1';
904
            } else {
905
                $class       = 'wikilink2';
906
                $link['rel'] = 'nofollow';
907
            }
908
        } else {
909
            $class = 'media';
910
        }
911
912
        //keep hash anchor
913
        @list($id, $hash) = explode('#', $id, 2);
914
        if(!empty($hash)) $hash = $this->_headerToLink($hash);
915
916
        //prepare for formating
917
        $link['target'] = $conf['target']['wiki'];
918
        $link['style']  = '';
919
        $link['pre']    = '';
920
        $link['suf']    = '';
921
        $link['more']   = 'data-wiki-id="'.$id.'"'; // id is already cleaned
922
        $link['class']  = $class;
923
        if($this->date_at) {
924
            $params = $params.'&at='.rawurlencode($this->date_at);
925
        }
926
        $link['url']    = wl($id, $params);
927
        $link['name']   = $name;
928
        $link['title']  = $id;
929
        //add search string
930
        if($search) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $search of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
931
            ($conf['userewrite']) ? $link['url'] .= '?' : $link['url'] .= '&amp;';
932
            if(is_array($search)) {
933
                $search = array_map('rawurlencode', $search);
934
                $link['url'] .= 's[]='.join('&amp;s[]=', $search);
935
            } else {
936
                $link['url'] .= 's='.rawurlencode($search);
937
            }
938
        }
939
940
        //keep hash
941
        if($hash) $link['url'] .= '#'.$hash;
942
943
        //output formatted
944
        if($returnonly) {
945
            return $this->_formatLink($link);
946
        } else {
947
            $this->doc .= $this->_formatLink($link);
948
        }
949
    }
950
951
    /**
952
     * Render an external link
953
     *
954
     * @param string       $url        full URL with scheme
955
     * @param string|array $name       name for the link, array for media file
956
     * @param bool         $returnonly whether to return html or write to doc attribute
957
     * @return void|string writes to doc attribute or returns html depends on $returnonly
958
     */
959
    public function externallink($url, $name = null, $returnonly = false) {
960
        global $conf;
961
962
        $name = $this->_getLinkTitle($name, $url, $isImage);
0 ignored issues
show
Bug introduced by
It seems like $name can also be of type null; however, Doku_Renderer_xhtml::_getLinkTitle() does only seem to accept string|array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
963
964
        // url might be an attack vector, only allow registered protocols
965
        if(is_null($this->schemes)) $this->schemes = getSchemes();
966
        list($scheme) = explode('://', $url);
967
        $scheme = strtolower($scheme);
968
        if(!in_array($scheme, $this->schemes)) $url = '';
969
970
        // is there still an URL?
971
        if(!$url) {
972
            if($returnonly) {
973
                return $name;
974
            } else {
975
                $this->doc .= $name;
976
            }
977
            return;
978
        }
979
980
        // set class
981
        if(!$isImage) {
982
            $class = 'urlextern';
983
        } else {
984
            $class = 'media';
985
        }
986
987
        //prepare for formating
988
        $link = array();
989
        $link['target'] = $conf['target']['extern'];
990
        $link['style']  = '';
991
        $link['pre']    = '';
992
        $link['suf']    = '';
993
        $link['more']   = '';
994
        $link['class']  = $class;
995
        $link['url']    = $url;
996
        $link['rel']    = '';
997
998
        $link['name']  = $name;
999
        $link['title'] = $this->_xmlEntities($url);
1000
        if($conf['relnofollow']) $link['rel'] .= ' ugc nofollow';
1001
        if($conf['target']['extern']) $link['rel'] .= ' noopener';
1002
1003
        //output formatted
1004
        if($returnonly) {
1005
            return $this->_formatLink($link);
1006
        } else {
1007
            $this->doc .= $this->_formatLink($link);
1008
        }
1009
    }
1010
1011
    /**
1012
     * Render an interwiki link
1013
     *
1014
     * You may want to use $this->_resolveInterWiki() here
1015
     *
1016
     * @param string       $match      original link - probably not much use
1017
     * @param string|array $name       name for the link, array for media file
1018
     * @param string       $wikiName   indentifier (shortcut) for the remote wiki
1019
     * @param string       $wikiUri    the fragment parsed from the original link
1020
     * @param bool         $returnonly whether to return html or write to doc attribute
1021
     * @return void|string writes to doc attribute or returns html depends on $returnonly
1022
     */
1023
    public function interwikilink($match, $name, $wikiName, $wikiUri, $returnonly = false) {
1024
        global $conf;
1025
1026
        $link           = array();
1027
        $link['target'] = $conf['target']['interwiki'];
1028
        $link['pre']    = '';
1029
        $link['suf']    = '';
1030
        $link['more']   = '';
1031
        $link['name']   = $this->_getLinkTitle($name, $wikiUri, $isImage);
1032
        $link['rel']    = '';
1033
1034
        //get interwiki URL
1035
        $exists = null;
1036
        $url    = $this->_resolveInterWiki($wikiName, $wikiUri, $exists);
1037
1038
        if(!$isImage) {
1039
            $class         = preg_replace('/[^_\-a-z0-9]+/i', '_', $wikiName);
1040
            $link['class'] = "interwiki iw_$class";
1041
        } else {
1042
            $link['class'] = 'media';
1043
        }
1044
1045
        //do we stay at the same server? Use local target
1046
        if(strpos($url, DOKU_URL) === 0 OR strpos($url, DOKU_BASE) === 0) {
1047
            $link['target'] = $conf['target']['wiki'];
1048
        }
1049
        if($exists !== null && !$isImage) {
1050
            if($exists) {
1051
                $link['class'] .= ' wikilink1';
1052
            } else {
1053
                $link['class'] .= ' wikilink2';
1054
                $link['rel'] .= ' nofollow';
1055
            }
1056
        }
1057
        if($conf['target']['interwiki']) $link['rel'] .= ' noopener';
1058
1059
        $link['url']   = $url;
1060
        $link['title'] = htmlspecialchars($link['url']);
1061
1062
        // output formatted
1063
        if($returnonly) {
1064
            if($url == '') return $link['name'];
1065
            return $this->_formatLink($link);
1066
        } else {
1067
            if($url == '') $this->doc .= $link['name'];
1068
            else $this->doc .= $this->_formatLink($link);
1069
        }
1070
    }
1071
1072
    /**
1073
     * Link to windows share
1074
     *
1075
     * @param string       $url        the link
1076
     * @param string|array $name       name for the link, array for media file
1077
     * @param bool         $returnonly whether to return html or write to doc attribute
1078
     * @return void|string writes to doc attribute or returns html depends on $returnonly
1079
     */
1080
    public function windowssharelink($url, $name = null, $returnonly = false) {
1081
        global $conf;
1082
1083
        //simple setup
1084
        $link = array();
1085
        $link['target'] = $conf['target']['windows'];
1086
        $link['pre']    = '';
1087
        $link['suf']    = '';
1088
        $link['style']  = '';
1089
1090
        $link['name'] = $this->_getLinkTitle($name, $url, $isImage);
0 ignored issues
show
Bug introduced by
It seems like $name defined by parameter $name on line 1080 can also be of type null; however, Doku_Renderer_xhtml::_getLinkTitle() does only seem to accept string|array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1091
        if(!$isImage) {
1092
            $link['class'] = 'windows';
1093
        } else {
1094
            $link['class'] = 'media';
1095
        }
1096
1097
        $link['title'] = $this->_xmlEntities($url);
1098
        $url           = str_replace('\\', '/', $url);
1099
        $url           = 'file:///'.$url;
1100
        $link['url']   = $url;
1101
1102
        //output formatted
1103
        if($returnonly) {
1104
            return $this->_formatLink($link);
1105
        } else {
1106
            $this->doc .= $this->_formatLink($link);
1107
        }
1108
    }
1109
1110
    /**
1111
     * Render a linked E-Mail Address
1112
     *
1113
     * Honors $conf['mailguard'] setting
1114
     *
1115
     * @param string       $address    Email-Address
1116
     * @param string|array $name       name for the link, array for media file
1117
     * @param bool         $returnonly whether to return html or write to doc attribute
1118
     * @return void|string writes to doc attribute or returns html depends on $returnonly
1119
     */
1120
    public function emaillink($address, $name = null, $returnonly = false) {
1121
        global $conf;
1122
        //simple setup
1123
        $link           = array();
1124
        $link['target'] = '';
1125
        $link['pre']    = '';
1126
        $link['suf']    = '';
1127
        $link['style']  = '';
1128
        $link['more']   = '';
1129
1130
        $name = $this->_getLinkTitle($name, '', $isImage);
0 ignored issues
show
Bug introduced by
It seems like $name can also be of type null; however, Doku_Renderer_xhtml::_getLinkTitle() does only seem to accept string|array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1131
        if(!$isImage) {
1132
            $link['class'] = 'mail';
1133
        } else {
1134
            $link['class'] = 'media';
1135
        }
1136
1137
        $address = $this->_xmlEntities($address);
1138
        $address = obfuscate($address);
1139
        $title   = $address;
1140
1141
        if(empty($name)) {
1142
            $name = $address;
1143
        }
1144
1145
        if($conf['mailguard'] == 'visible') $address = rawurlencode($address);
1146
1147
        $link['url']   = 'mailto:'.$address;
1148
        $link['name']  = $name;
1149
        $link['title'] = $title;
1150
1151
        //output formatted
1152
        if($returnonly) {
1153
            return $this->_formatLink($link);
1154
        } else {
1155
            $this->doc .= $this->_formatLink($link);
1156
        }
1157
    }
1158
1159
    /**
1160
     * Render an internal media file
1161
     *
1162
     * @param string $src       media ID
1163
     * @param string $title     descriptive text
1164
     * @param string $align     left|center|right
1165
     * @param int    $width     width of media in pixel
1166
     * @param int    $height    height of media in pixel
1167
     * @param string $cache     cache|recache|nocache
1168
     * @param string $linking   linkonly|detail|nolink
1169
     * @param bool   $return    return HTML instead of adding to $doc
1170
     * @return void|string writes to doc attribute or returns html depends on $return
1171
     */
1172
    public function internalmedia($src, $title = null, $align = null, $width = null,
1173
                           $height = null, $cache = null, $linking = null, $return = false) {
1174
        global $ID;
1175
        if (strpos($src, '#') !== false) {
1176
            list($src, $hash) = explode('#', $src, 2);
1177
        }
1178
        resolve_mediaid(getNS($ID), $src, $exists, $this->date_at, true);
0 ignored issues
show
Security Bug introduced by
It seems like getNS($ID) targeting getNS() can also be of type false; however, resolve_mediaid() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
1179
1180
        $noLink = false;
1181
        $render = ($linking == 'linkonly') ? false : true;
1182
        $link   = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render);
1183
1184
        list($ext, $mime) = mimetype($src, false);
1185
        if(substr($mime, 0, 5) == 'image' && $render) {
1186
            $link['url'] = ml(
1187
                $src,
1188
                array(
1189
                    'id' => $ID,
1190
                    'cache' => $cache,
1191
                    'rev' => $this->_getLastMediaRevisionAt($src)
1192
                ),
1193
                ($linking == 'direct')
1194
            );
1195
        } elseif(($mime == 'application/x-shockwave-flash' || media_supportedav($mime)) && $render) {
1196
            // don't link movies
1197
            $noLink = true;
1198
        } else {
1199
            // add file icons
1200
            $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext);
1201
            $link['class'] .= ' mediafile mf_'.$class;
1202
            $link['url'] = ml(
1203
                $src,
1204
                array(
1205
                    'id' => $ID,
1206
                    'cache' => $cache,
1207
                    'rev' => $this->_getLastMediaRevisionAt($src)
1208
                ),
1209
                true
1210
            );
1211
            if($exists) $link['title'] .= ' ('.filesize_h(filesize(mediaFN($src))).')';
1212
        }
1213
1214
        if (!empty($hash)) $link['url'] .= '#'.$hash;
1215
1216
        //markup non existing files
1217
        if(!$exists) {
1218
            $link['class'] .= ' wikilink2';
1219
        }
1220
1221
        //output formatted
1222
        if($return) {
1223
            if($linking == 'nolink' || $noLink) return $link['name'];
1224
            else return $this->_formatLink($link);
1225
        } else {
1226
            if($linking == 'nolink' || $noLink) $this->doc .= $link['name'];
1227
            else $this->doc .= $this->_formatLink($link);
1228
        }
1229
    }
1230
1231
    /**
1232
     * Render an external media file
1233
     *
1234
     * @param string $src     full media URL
1235
     * @param string $title   descriptive text
1236
     * @param string $align   left|center|right
1237
     * @param int    $width   width of media in pixel
1238
     * @param int    $height  height of media in pixel
1239
     * @param string $cache   cache|recache|nocache
1240
     * @param string $linking linkonly|detail|nolink
1241
     * @param bool   $return  return HTML instead of adding to $doc
1242
     * @return void|string writes to doc attribute or returns html depends on $return
1243
     */
1244
    public function externalmedia($src, $title = null, $align = null, $width = null,
1245
                           $height = null, $cache = null, $linking = null, $return = false) {
1246
        if(link_isinterwiki($src)){
1247
            list($shortcut, $reference) = explode('>', $src, 2);
1248
            $exists = null;
1249
            $src = $this->_resolveInterWiki($shortcut, $reference, $exists);
1250
            if($src == '' && empty($title)){
1251
                // make sure at least something will be shown in this case
1252
                $title = $reference;
1253
            }
1254
        }
1255
        list($src, $hash) = explode('#', $src, 2);
1256
        $noLink = false;
1257
        if($src == '') {
1258
            // only output plaintext without link if there is no src
1259
            $noLink = true;
1260
        }
1261
        $render = ($linking == 'linkonly') ? false : true;
1262
        $link   = $this->_getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render);
1263
1264
        $link['url'] = ml($src, array('cache' => $cache));
1265
1266
        list($ext, $mime) = mimetype($src, false);
1267
        if(substr($mime, 0, 5) == 'image' && $render) {
1268
            // link only jpeg images
1269
            // if ($ext != 'jpg' && $ext != 'jpeg') $noLink = true;
1270
        } elseif(($mime == 'application/x-shockwave-flash' || media_supportedav($mime)) && $render) {
1271
            // don't link movies
1272
            $noLink = true;
1273
        } else {
1274
            // add file icons
1275
            $class = preg_replace('/[^_\-a-z0-9]+/i', '_', $ext);
1276
            $link['class'] .= ' mediafile mf_'.$class;
1277
        }
1278
1279
        if($hash) $link['url'] .= '#'.$hash;
1280
1281
        //output formatted
1282
        if($return) {
1283
            if($linking == 'nolink' || $noLink) return $link['name'];
1284
            else return $this->_formatLink($link);
1285
        } else {
1286
            if($linking == 'nolink' || $noLink) $this->doc .= $link['name'];
1287
            else $this->doc .= $this->_formatLink($link);
1288
        }
1289
    }
1290
1291
    /**
1292
     * Renders an RSS feed
1293
     *
1294
     * @param string $url    URL of the feed
1295
     * @param array  $params Finetuning of the output
1296
     *
1297
     * @author Andreas Gohr <[email protected]>
1298
     */
1299
    public function rss($url, $params) {
1300
        global $lang;
1301
        global $conf;
1302
1303
        require_once(DOKU_INC.'inc/FeedParser.php');
1304
        $feed = new FeedParser();
1305
        $feed->set_feed_url($url);
1306
1307
        //disable warning while fetching
1308
        if(!defined('DOKU_E_LEVEL')) {
1309
            $elvl = error_reporting(E_ERROR);
1310
        }
1311
        $rc = $feed->init();
1312
        if(isset($elvl)) {
1313
            error_reporting($elvl);
1314
        }
1315
1316
        if($params['nosort']) $feed->enable_order_by_date(false);
1317
1318
        //decide on start and end
1319
        if($params['reverse']) {
1320
            $mod   = -1;
1321
            $start = $feed->get_item_quantity() - 1;
1322
            $end   = $start - ($params['max']);
1323
            $end   = ($end < -1) ? -1 : $end;
1324
        } else {
1325
            $mod   = 1;
1326
            $start = 0;
1327
            $end   = $feed->get_item_quantity();
1328
            $end   = ($end > $params['max']) ? $params['max'] : $end;
1329
        }
1330
1331
        $this->doc .= '<ul class="rss">';
1332
        if($rc) {
1333
            for($x = $start; $x != $end; $x += $mod) {
1334
                $item = $feed->get_item($x);
1335
                $this->doc .= '<li><div class="li">';
1336
                // support feeds without links
1337
                $lnkurl = $item->get_permalink();
1338
                if($lnkurl) {
1339
                    // title is escaped by SimplePie, we unescape here because it
1340
                    // is escaped again in externallink() FS#1705
1341
                    $this->externallink(
1342
                        $item->get_permalink(),
1343
                        html_entity_decode($item->get_title(), ENT_QUOTES, 'UTF-8')
1344
                    );
1345
                } else {
1346
                    $this->doc .= ' '.$item->get_title();
1347
                }
1348
                if($params['author']) {
1349
                    $author = $item->get_author(0);
1350
                    if($author) {
1351
                        $name = $author->get_name();
1352
                        if(!$name) $name = $author->get_email();
1353
                        if($name) $this->doc .= ' '.$lang['by'].' '.hsc($name);
1354
                    }
1355
                }
1356
                if($params['date']) {
1357
                    $this->doc .= ' ('.$item->get_local_date($conf['dformat']).')';
1358
                }
1359
                if($params['details']) {
1360
                    $this->doc .= '<div class="detail">';
1361
                    if($conf['htmlok']) {
1362
                        $this->doc .= $item->get_description();
1363
                    } else {
1364
                        $this->doc .= strip_tags($item->get_description());
1365
                    }
1366
                    $this->doc .= '</div>';
1367
                }
1368
1369
                $this->doc .= '</div></li>';
1370
            }
1371
        } else {
1372
            $this->doc .= '<li><div class="li">';
1373
            $this->doc .= '<em>'.$lang['rssfailed'].'</em>';
1374
            $this->externallink($url);
1375
            if($conf['allowdebug']) {
1376
                $this->doc .= '<!--'.hsc($feed->error).'-->';
1377
            }
1378
            $this->doc .= '</div></li>';
1379
        }
1380
        $this->doc .= '</ul>';
1381
    }
1382
1383
    /**
1384
     * Start a table
1385
     *
1386
     * @param int $maxcols maximum number of columns
1387
     * @param int $numrows NOT IMPLEMENTED
1388
     * @param int $pos byte position in the original source
1389
     * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input
1390
     */
1391
    public function table_open($maxcols = null, $numrows = null, $pos = null, $classes = null) {
1392
        // initialize the row counter used for classes
1393
        $this->_counter['row_counter'] = 0;
1394
        $class                         = 'table';
1395
        if($classes !== null) {
1396
            if(is_array($classes)) $classes = join(' ', $classes);
1397
            $class .= ' ' . $classes;
1398
        }
1399
        if($pos !== null) {
1400
            $hid = $this->_headerToLink($class, true);
1401
            $data = array();
1402
            $data['target'] = 'table';
1403
            $data['name'] = '';
1404
            $data['hid'] = $hid;
1405
            $class .= ' '.$this->startSectionEdit($pos, $data);
1406
        }
1407
        $this->doc .= '<div class="'.$class.'"><table class="inline">'.
1408
            DOKU_LF;
1409
    }
1410
1411
    /**
1412
     * Close a table
1413
     *
1414
     * @param int $pos byte position in the original source
1415
     */
1416
    public function table_close($pos = null) {
1417
        $this->doc .= '</table></div>'.DOKU_LF;
1418
        if($pos !== null) {
1419
            $this->finishSectionEdit($pos);
1420
        }
1421
    }
1422
1423
    /**
1424
     * Open a table header
1425
     */
1426
    public function tablethead_open() {
1427
        $this->doc .= DOKU_TAB.'<thead>'.DOKU_LF;
1428
    }
1429
1430
    /**
1431
     * Close a table header
1432
     */
1433
    public function tablethead_close() {
1434
        $this->doc .= DOKU_TAB.'</thead>'.DOKU_LF;
1435
    }
1436
1437
    /**
1438
     * Open a table body
1439
     */
1440
    public function tabletbody_open() {
1441
        $this->doc .= DOKU_TAB.'<tbody>'.DOKU_LF;
1442
    }
1443
1444
    /**
1445
     * Close a table body
1446
     */
1447
    public function tabletbody_close() {
1448
        $this->doc .= DOKU_TAB.'</tbody>'.DOKU_LF;
1449
    }
1450
1451
    /**
1452
     * Open a table footer
1453
     */
1454
    public function tabletfoot_open() {
1455
        $this->doc .= DOKU_TAB.'<tfoot>'.DOKU_LF;
1456
    }
1457
1458
    /**
1459
     * Close a table footer
1460
     */
1461
    public function tabletfoot_close() {
1462
        $this->doc .= DOKU_TAB.'</tfoot>'.DOKU_LF;
1463
    }
1464
1465
    /**
1466
     * Open a table row
1467
     *
1468
     * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input
1469
     */
1470
    public function tablerow_open($classes = null) {
1471
        // initialize the cell counter used for classes
1472
        $this->_counter['cell_counter'] = 0;
1473
        $class                          = 'row'.$this->_counter['row_counter']++;
1474
        if($classes !== null) {
1475
            if(is_array($classes)) $classes = join(' ', $classes);
1476
            $class .= ' ' . $classes;
1477
        }
1478
        $this->doc .= DOKU_TAB.'<tr class="'.$class.'">'.DOKU_LF.DOKU_TAB.DOKU_TAB;
1479
    }
1480
1481
    /**
1482
     * Close a table row
1483
     */
1484
    public function tablerow_close() {
1485
        $this->doc .= DOKU_LF.DOKU_TAB.'</tr>'.DOKU_LF;
1486
    }
1487
1488
    /**
1489
     * Open a table header cell
1490
     *
1491
     * @param int    $colspan
1492
     * @param string $align left|center|right
1493
     * @param int    $rowspan
1494
     * @param string|string[] $classes css classes - have to be valid, do not pass unfiltered user input
1495
     */
1496
    public function tableheader_open($colspan = 1, $align = null, $rowspan = 1, $classes = null) {
1497
        $class = 'class="col'.$this->_counter['cell_counter']++;
1498
        if(!is_null($align)) {
1499
            $class .= ' '.$align.'align';
1500
        }
1501
        if($classes !== null) {
1502
            if(is_array($classes)) $classes = join(' ', $classes);
1503
            $class .= ' ' . $classes;
1504
        }
1505
        $class .= '"';
1506
        $this->doc .= '<th '.$class;
1507
        if($colspan > 1) {
1508
            $this->_counter['cell_counter'] += $colspan - 1;
1509
            $this->doc .= ' colspan="'.$colspan.'"';
1510
        }
1511
        if($rowspan > 1) {
1512
            $this->doc .= ' rowspan="'.$rowspan.'"';
1513
        }
1514
        $this->doc .= '>';
1515
    }
1516
1517
    /**
1518
     * Close a table header cell
1519
     */
1520
    public function tableheader_close() {
1521
        $this->doc .= '</th>';
1522
    }
1523
1524
    /**
1525
     * Open a table cell
1526
     *
1527
     * @param int       $colspan
1528
     * @param string    $align left|center|right
1529
     * @param int       $rowspan
1530
     * @param string|string[]    $classes css classes - have to be valid, do not pass unfiltered user input
1531
     */
1532
    public function tablecell_open($colspan = 1, $align = null, $rowspan = 1, $classes = null) {
1533
        $class = 'class="col'.$this->_counter['cell_counter']++;
1534
        if(!is_null($align)) {
1535
            $class .= ' '.$align.'align';
1536
        }
1537
        if($classes !== null) {
1538
            if(is_array($classes)) $classes = join(' ', $classes);
1539
            $class .= ' ' . $classes;
1540
        }
1541
        $class .= '"';
1542
        $this->doc .= '<td '.$class;
1543
        if($colspan > 1) {
1544
            $this->_counter['cell_counter'] += $colspan - 1;
1545
            $this->doc .= ' colspan="'.$colspan.'"';
1546
        }
1547
        if($rowspan > 1) {
1548
            $this->doc .= ' rowspan="'.$rowspan.'"';
1549
        }
1550
        $this->doc .= '>';
1551
    }
1552
1553
    /**
1554
     * Close a table cell
1555
     */
1556
    public function tablecell_close() {
1557
        $this->doc .= '</td>';
1558
    }
1559
1560
    /**
1561
     * Returns the current header level.
1562
     * (required e.g. by the filelist plugin)
1563
     *
1564
     * @return int The current header level
1565
     */
1566
    public function getLastlevel() {
1567
        return $this->lastlevel;
1568
    }
1569
1570
    #region Utility functions
1571
1572
    /**
1573
     * Build a link
1574
     *
1575
     * Assembles all parts defined in $link returns HTML for the link
1576
     *
1577
     * @param array $link attributes of a link
1578
     * @return string
1579
     *
1580
     * @author Andreas Gohr <[email protected]>
1581
     */
1582
    public function _formatLink($link) {
1583
        //make sure the url is XHTML compliant (skip mailto)
1584
        if(substr($link['url'], 0, 7) != 'mailto:') {
1585
            $link['url'] = str_replace('&', '&amp;', $link['url']);
1586
            $link['url'] = str_replace('&amp;amp;', '&amp;', $link['url']);
1587
        }
1588
        //remove double encodings in titles
1589
        $link['title'] = str_replace('&amp;amp;', '&amp;', $link['title']);
1590
1591
        // be sure there are no bad chars in url or title
1592
        // (we can't do this for name because it can contain an img tag)
1593
        $link['url']   = strtr($link['url'], array('>' => '%3E', '<' => '%3C', '"' => '%22'));
1594
        $link['title'] = strtr($link['title'], array('>' => '&gt;', '<' => '&lt;', '"' => '&quot;'));
1595
1596
        $ret = '';
1597
        $ret .= $link['pre'];
1598
        $ret .= '<a href="'.$link['url'].'"';
1599
        if(!empty($link['class'])) $ret .= ' class="'.$link['class'].'"';
1600
        if(!empty($link['target'])) $ret .= ' target="'.$link['target'].'"';
1601
        if(!empty($link['title'])) $ret .= ' title="'.$link['title'].'"';
1602
        if(!empty($link['style'])) $ret .= ' style="'.$link['style'].'"';
1603
        if(!empty($link['rel'])) $ret .= ' rel="'.trim($link['rel']).'"';
1604
        if(!empty($link['more'])) $ret .= ' '.$link['more'];
1605
        $ret .= '>';
1606
        $ret .= $link['name'];
1607
        $ret .= '</a>';
1608
        $ret .= $link['suf'];
1609
        return $ret;
1610
    }
1611
1612
    /**
1613
     * Renders internal and external media
1614
     *
1615
     * @author Andreas Gohr <[email protected]>
1616
     * @param string $src       media ID
1617
     * @param string $title     descriptive text
1618
     * @param string $align     left|center|right
1619
     * @param int    $width     width of media in pixel
1620
     * @param int    $height    height of media in pixel
1621
     * @param string $cache     cache|recache|nocache
1622
     * @param bool   $render    should the media be embedded inline or just linked
1623
     * @return string
1624
     */
1625
    public function _media($src, $title = null, $align = null, $width = null,
1626
                    $height = null, $cache = null, $render = true) {
1627
1628
        $ret = '';
1629
1630
        list($ext, $mime) = mimetype($src);
1631
        if(substr($mime, 0, 5) == 'image') {
1632
            // first get the $title
1633
            if(!is_null($title)) {
1634
                $title = $this->_xmlEntities($title);
1635
            } elseif($ext == 'jpg' || $ext == 'jpeg') {
1636
                //try to use the caption from IPTC/EXIF
1637
                require_once(DOKU_INC.'inc/JpegMeta.php');
1638
                $jpeg = new JpegMeta(mediaFN($src));
1639
                if($jpeg !== false) $cap = $jpeg->getTitle();
1640
                if(!empty($cap)) {
1641
                    $title = $this->_xmlEntities($cap);
1642
                }
1643
            }
1644
            if(!$render) {
1645
                // if the picture is not supposed to be rendered
1646
                // return the title of the picture
1647
                if($title === null || $title === "") {
1648
                    // just show the sourcename
1649
                    $title = $this->_xmlEntities(\dokuwiki\Utf8\PhpString::basename(noNS($src)));
1650
                }
1651
                return $title;
1652
            }
1653
            //add image tag
1654
            $ret .= '<img src="' . ml(
1655
                    $src,
1656
                    array(
1657
                        'w' => $width, 'h' => $height,
1658
                        'cache' => $cache,
1659
                        'rev' => $this->_getLastMediaRevisionAt($src)
1660
                    )
1661
                ) . '"';
1662
            $ret .= ' class="media'.$align.'"';
1663
1664
            if($title) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $title of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1665
                $ret .= ' title="'.$title.'"';
1666
                $ret .= ' alt="'.$title.'"';
1667
            } else {
1668
                $ret .= ' alt=""';
1669
            }
1670
1671
            if(!is_null($width))
1672
                $ret .= ' width="'.$this->_xmlEntities($width).'"';
1673
1674
            if(!is_null($height))
1675
                $ret .= ' height="'.$this->_xmlEntities($height).'"';
1676
1677
            $ret .= ' />';
1678
1679
        } elseif(media_supportedav($mime, 'video') || media_supportedav($mime, 'audio')) {
1680
            // first get the $title
1681
            $title = !is_null($title) ? $title : false;
1682
            if(!$render) {
1683
                // if the file is not supposed to be rendered
1684
                // return the title of the file (just the sourcename if there is no title)
1685
                return $this->_xmlEntities($title ? $title : \dokuwiki\Utf8\PhpString::basename(noNS($src)));
1686
            }
1687
1688
            $att          = array();
1689
            $att['class'] = "media$align";
1690
            if($title) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $title of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1691
                $att['title'] = $title;
1692
            }
1693
1694
            if(media_supportedav($mime, 'video')) {
1695
                //add video
1696
                $ret .= $this->_video($src, $width, $height, $att);
1697
            }
1698
            if(media_supportedav($mime, 'audio')) {
1699
                //add audio
1700
                $ret .= $this->_audio($src, $att);
1701
            }
1702
1703
        } elseif($mime == 'application/x-shockwave-flash') {
1704
            if(!$render) {
1705
                // if the flash is not supposed to be rendered
1706
                // return the title of the flash
1707
                if(!$title) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $title of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1708
                    // just show the sourcename
1709
                    $title = \dokuwiki\Utf8\PhpString::basename(noNS($src));
1710
                }
1711
                return $this->_xmlEntities($title);
1712
            }
1713
1714
            $att          = array();
1715
            $att['class'] = "media$align";
1716
            if($align == 'right') $att['align'] = 'right';
1717
            if($align == 'left') $att['align'] = 'left';
1718
            $ret .= html_flashobject(
1719
                ml($src, array('cache' => $cache), true, '&'), $width, $height,
1720
                array('quality' => 'high'),
1721
                null,
1722
                $att,
1723
                $this->_xmlEntities($title)
1724
            );
1725
        } elseif($title) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $title of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1726
            // well at least we have a title to display
1727
            $ret .= $this->_xmlEntities($title);
1728
        } else {
1729
            // just show the sourcename
1730
            $ret .= $this->_xmlEntities(\dokuwiki\Utf8\PhpString::basename(noNS($src)));
1731
        }
1732
1733
        return $ret;
1734
    }
1735
1736
    /**
1737
     * Escape string for output
1738
     *
1739
     * @param $string
1740
     * @return string
1741
     */
1742
    public function _xmlEntities($string) {
1743
        return htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
1744
    }
1745
1746
1747
1748
    /**
1749
     * Construct a title and handle images in titles
1750
     *
1751
     * @author Harry Fuecks <[email protected]>
1752
     * @param string|array $title    either string title or media array
1753
     * @param string       $default  default title if nothing else is found
1754
     * @param bool         $isImage  will be set to true if it's a media file
1755
     * @param null|string  $id       linked page id (used to extract title from first heading)
1756
     * @param string       $linktype content|navigation
1757
     * @return string      HTML of the title, might be full image tag or just escaped text
1758
     */
1759
    public function _getLinkTitle($title, $default, &$isImage, $id = null, $linktype = 'content') {
1760
        $isImage = false;
1761
        if(is_array($title)) {
1762
            $isImage = true;
1763
            return $this->_imageTitle($title);
1764
        } elseif(is_null($title) || trim($title) == '') {
1765
            if(useHeading($linktype) && $id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $id of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1766
                $heading = p_get_first_heading($id);
1767
                if(!blank($heading)) {
1768
                    return $this->_xmlEntities($heading);
1769
                }
1770
            }
1771
            return $this->_xmlEntities($default);
1772
        } else {
1773
            return $this->_xmlEntities($title);
1774
        }
1775
    }
1776
1777
    /**
1778
     * Returns HTML code for images used in link titles
1779
     *
1780
     * @author Andreas Gohr <[email protected]>
1781
     * @param array $img
1782
     * @return string HTML img tag or similar
1783
     */
1784
    public function _imageTitle($img) {
1785
        global $ID;
1786
1787
        // some fixes on $img['src']
1788
        // see internalmedia() and externalmedia()
1789
        list($img['src']) = explode('#', $img['src'], 2);
1790
        if($img['type'] == 'internalmedia') {
1791
            resolve_mediaid(getNS($ID), $img['src'], $exists ,$this->date_at, true);
0 ignored issues
show
Security Bug introduced by
It seems like getNS($ID) targeting getNS() can also be of type false; however, resolve_mediaid() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
1792
        }
1793
1794
        return $this->_media(
1795
            $img['src'],
1796
            $img['title'],
1797
            $img['align'],
1798
            $img['width'],
1799
            $img['height'],
1800
            $img['cache']
1801
        );
1802
    }
1803
1804
    /**
1805
     * helperfunction to return a basic link to a media
1806
     *
1807
     * used in internalmedia() and externalmedia()
1808
     *
1809
     * @author   Pierre Spring <[email protected]>
1810
     * @param string $src       media ID
1811
     * @param string $title     descriptive text
1812
     * @param string $align     left|center|right
1813
     * @param int    $width     width of media in pixel
1814
     * @param int    $height    height of media in pixel
1815
     * @param string $cache     cache|recache|nocache
1816
     * @param bool   $render    should the media be embedded inline or just linked
1817
     * @return array associative array with link config
1818
     */
1819
    public function _getMediaLinkConf($src, $title, $align, $width, $height, $cache, $render) {
1820
        global $conf;
1821
1822
        $link           = array();
1823
        $link['class']  = 'media';
1824
        $link['style']  = '';
1825
        $link['pre']    = '';
1826
        $link['suf']    = '';
1827
        $link['more']   = '';
1828
        $link['target'] = $conf['target']['media'];
1829
        if($conf['target']['media']) $link['rel'] = 'noopener';
1830
        $link['title']  = $this->_xmlEntities($src);
1831
        $link['name']   = $this->_media($src, $title, $align, $width, $height, $cache, $render);
1832
1833
        return $link;
1834
    }
1835
1836
    /**
1837
     * Embed video(s) in HTML
1838
     *
1839
     * @author Anika Henke <[email protected]>
1840
     * @author Schplurtz le Déboulonné <[email protected]>
1841
     *
1842
     * @param string $src         - ID of video to embed
1843
     * @param int    $width       - width of the video in pixels
1844
     * @param int    $height      - height of the video in pixels
1845
     * @param array  $atts        - additional attributes for the <video> tag
1846
     * @return string
1847
     */
1848
    public function _video($src, $width, $height, $atts = null) {
1849
        // prepare width and height
1850
        if(is_null($atts)) $atts = array();
1851
        $atts['width']  = (int) $width;
1852
        $atts['height'] = (int) $height;
1853
        if(!$atts['width']) $atts['width'] = 320;
1854
        if(!$atts['height']) $atts['height'] = 240;
1855
1856
        $posterUrl = '';
1857
        $files = array();
1858
        $tracks = array();
1859
        $isExternal = media_isexternal($src);
1860
1861
        if ($isExternal) {
1862
            // take direct source for external files
1863
            list(/*ext*/, $srcMime) = mimetype($src);
1864
            $files[$srcMime] = $src;
1865
        } else {
1866
            // prepare alternative formats
1867
            $extensions   = array('webm', 'ogv', 'mp4');
1868
            $files        = media_alternativefiles($src, $extensions);
1869
            $poster       = media_alternativefiles($src, array('jpg', 'png'));
1870
            $tracks       = media_trackfiles($src);
1871
            if(!empty($poster)) {
1872
                $posterUrl = ml(reset($poster), '', true, '&');
1873
            }
1874
        }
1875
1876
        $out = '';
1877
        // open video tag
1878
        $out .= '<video '.buildAttributes($atts).' controls="controls"';
1879
        if($posterUrl) $out .= ' poster="'.hsc($posterUrl).'"';
1880
        $out .= '>'.NL;
1881
        $fallback = '';
1882
1883
        // output source for each alternative video format
1884
        foreach($files as $mime => $file) {
1885
            if ($isExternal) {
1886
                $url = $file;
1887
                $linkType = 'externalmedia';
1888
            } else {
1889
                $url = ml($file, '', true, '&');
1890
                $linkType = 'internalmedia';
1891
            }
1892
            $title = $atts['title'] ? $atts['title'] : $this->_xmlEntities(\dokuwiki\Utf8\PhpString::basename(noNS($file)));
1893
1894
            $out .= '<source src="'.hsc($url).'" type="'.$mime.'" />'.NL;
1895
            // alternative content (just a link to the file)
1896
            $fallback .= $this->$linkType(
1897
                $file,
1898
                $title,
1899
                null,
1900
                null,
1901
                null,
1902
                $cache = null,
1903
                $linking = 'linkonly',
1904
                $return = true
1905
            );
1906
        }
1907
1908
        // output each track if any
1909
        foreach( $tracks as $trackid => $info ) {
1910
            list( $kind, $srclang ) = array_map( 'hsc', $info );
1911
            $out .= "<track kind=\"$kind\" srclang=\"$srclang\" ";
1912
            $out .= "label=\"$srclang\" ";
1913
            $out .= 'src="'.ml($trackid, '', true).'">'.NL;
1914
        }
1915
1916
        // finish
1917
        $out .= $fallback;
1918
        $out .= '</video>'.NL;
1919
        return $out;
1920
    }
1921
1922
    /**
1923
     * Embed audio in HTML
1924
     *
1925
     * @author Anika Henke <[email protected]>
1926
     *
1927
     * @param string $src       - ID of audio to embed
1928
     * @param array  $atts      - additional attributes for the <audio> tag
1929
     * @return string
1930
     */
1931
    public function _audio($src, $atts = array()) {
1932
        $files = array();
1933
        $isExternal = media_isexternal($src);
1934
1935
        if ($isExternal) {
1936
            // take direct source for external files
1937
            list(/*ext*/, $srcMime) = mimetype($src);
1938
            $files[$srcMime] = $src;
1939
        } else {
1940
            // prepare alternative formats
1941
            $extensions   = array('ogg', 'mp3', 'wav');
1942
            $files        = media_alternativefiles($src, $extensions);
1943
        }
1944
1945
        $out = '';
1946
        // open audio tag
1947
        $out .= '<audio '.buildAttributes($atts).' controls="controls">'.NL;
1948
        $fallback = '';
1949
1950
        // output source for each alternative audio format
1951
        foreach($files as $mime => $file) {
1952
            if ($isExternal) {
1953
                $url = $file;
1954
                $linkType = 'externalmedia';
1955
            } else {
1956
                $url = ml($file, '', true, '&');
1957
                $linkType = 'internalmedia';
1958
            }
1959
            $title = $atts['title'] ? $atts['title'] : $this->_xmlEntities(\dokuwiki\Utf8\PhpString::basename(noNS($file)));
1960
1961
            $out .= '<source src="'.hsc($url).'" type="'.$mime.'" />'.NL;
1962
            // alternative content (just a link to the file)
1963
            $fallback .= $this->$linkType(
1964
                $file,
1965
                $title,
1966
                null,
1967
                null,
1968
                null,
1969
                $cache = null,
1970
                $linking = 'linkonly',
1971
                $return = true
1972
            );
1973
        }
1974
1975
        // finish
1976
        $out .= $fallback;
1977
        $out .= '</audio>'.NL;
1978
        return $out;
1979
    }
1980
1981
    /**
1982
     * _getLastMediaRevisionAt is a helperfunction to internalmedia() and _media()
1983
     * which returns an existing media revision less or equal to rev or date_at
1984
     *
1985
     * @author lisps
1986
     * @param string $media_id
1987
     * @access protected
1988
     * @return string revision ('' for current)
1989
     */
1990
    protected function _getLastMediaRevisionAt($media_id){
1991
        if(!$this->date_at || media_isexternal($media_id)) return '';
1992
        $pagelog = new MediaChangeLog($media_id);
1993
        return $pagelog->getLastRevisionAt($this->date_at);
1994
    }
1995
1996
    #endregion
1997
}
1998
1999
//Setup VIM: ex: et ts=4 :
2000