xref: /dokuwiki/inc/html.php (revision 316e3ee67cce340deac79a8c6f89d881b178d094)
1<?php
2/**
3 * HTML output functions
4 *
5 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author     Andreas Gohr <andi@splitbrain.org>
7 */
8use dokuwiki\Ui\MediaRevisions;
9use dokuwiki\Form\Form;
10use dokuwiki\Action\Denied;
11use dokuwiki\Action\Locked;
12use dokuwiki\ChangeLog\PageChangeLog;
13use dokuwiki\Extension\AuthPlugin;
14use dokuwiki\Extension\Event;
15use dokuwiki\Ui\Backlinks;
16use dokuwiki\Ui\Editor;
17use dokuwiki\Ui\Index;
18use dokuwiki\Ui\Login;
19use dokuwiki\Ui\PageConflict;
20use dokuwiki\Ui\PageDiff;
21use dokuwiki\Ui\PageDraft;
22use dokuwiki\Ui\PageRevisions;
23use dokuwiki\Ui\PageView;
24use dokuwiki\Ui\Recent;
25use dokuwiki\Ui\UserProfile;
26use dokuwiki\Ui\UserRegister;
27use dokuwiki\Ui\UserResendPwd;
28use dokuwiki\Utf8\Clean;
29
30if (!defined('SEC_EDIT_PATTERN')) {
31    define('SEC_EDIT_PATTERN', '#<!-- EDIT({.*?}) -->#');
32}
33
34
35/**
36 * Convenience function to quickly build a wikilink
37 *
38 * @author Andreas Gohr <andi@splitbrain.org>
39 * @param string  $id      id of the target page
40 * @param string  $name    the name of the link, i.e. the text that is displayed
41 * @param string|array  $search  search string(s) that shall be highlighted in the target page
42 * @return string the HTML code of the link
43 */
44function html_wikilink($id, $name = null, $search = '')
45{
46    /** @var Doku_Renderer_xhtml $xhtml_renderer */
47    static $xhtml_renderer = null;
48    if (is_null($xhtml_renderer)) {
49        $xhtml_renderer = p_get_renderer('xhtml');
50    }
51
52    return $xhtml_renderer->internallink($id, $name, $search, true, 'navigation');
53}
54
55/**
56 * The loginform
57 *
58 * @author   Andreas Gohr <andi@splitbrain.org>
59 *
60 * @param bool $svg Whether to show svg icons in the register and resendpwd links or not
61 * @deprecated 2020-07-18
62 */
63function html_login($svg = false)
64{
65    dbg_deprecated(Login::class .'::show()');
66    (new Login($svg))->show();
67}
68
69
70/**
71 * Denied page content
72 *
73 * @deprecated 2020-07-18 not called anymore, see inc/Action/Denied::tplContent()
74 */
75function html_denied()
76{
77    dbg_deprecated(Denied::class .'::showBanner()');
78    (new Denied())->showBanner();
79}
80
81/**
82 * inserts section edit buttons if wanted or removes the markers
83 *
84 * @author Andreas Gohr <andi@splitbrain.org>
85 *
86 * @param string $text
87 * @param bool   $show show section edit buttons?
88 * @return string
89 */
90function html_secedit($text, $show = true)
91{
92    global $INFO;
93
94    if ((isset($INFO) && !$INFO['writable']) || !$show || (isset($INFO) && $INFO['rev'])) {
95        return preg_replace(SEC_EDIT_PATTERN, '', $text);
96    }
97
98    return preg_replace_callback(SEC_EDIT_PATTERN,
99                'html_secedit_button', $text);
100}
101
102/**
103 * prepares section edit button data for event triggering
104 * used as a callback in html_secedit
105 *
106 * @author Andreas Gohr <andi@splitbrain.org>
107 *
108 * @param array $matches matches with regexp
109 * @return string
110 * @triggers HTML_SECEDIT_BUTTON
111 */
112function html_secedit_button($matches)
113{
114    $json = htmlspecialchars_decode($matches[1], ENT_QUOTES);
115    $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
116    if ($data === null) {
117        return '';
118    }
119    $data['target'] = strtolower($data['target']);
120    $data['hid'] = strtolower($data['hid'] ?? '');
121
122    return Event::createAndTrigger(
123        'HTML_SECEDIT_BUTTON',
124        $data,
125        'html_secedit_get_button'
126    );
127}
128
129/**
130 * prints a section editing button
131 * used as default action form HTML_SECEDIT_BUTTON
132 *
133 * @author Adrian Lang <lang@cosmocode.de>
134 *
135 * @param array $data name, section id and target
136 * @return string html
137 */
138function html_secedit_get_button($data)
139{
140    global $ID;
141    global $INFO;
142
143    if (!isset($data['name']) || $data['name'] === '') return '';
144
145    $name = $data['name'];
146    unset($data['name']);
147
148    $secid = $data['secid'];
149    unset($data['secid']);
150
151    $params = array_merge(
152           ['do'  => 'edit', 'rev' => $INFO['lastmod'], 'summary' => '['.$name.'] '],
153           $data
154    );
155
156    $html = '<div class="secedit editbutton_'.$data['target'] .' editbutton_'.$secid .'">';
157    $html.= html_btn('secedit', $ID, '', $params, 'post', $name);
158    $html.= '</div>';
159    return $html;
160}
161
162/**
163 * Just the back to top button (in its own form)
164 *
165 * @author Andreas Gohr <andi@splitbrain.org>
166 *
167 * @return string html
168 */
169function html_topbtn()
170{
171    global $lang;
172
173    return '<a class="nolink" href="#dokuwiki__top">'
174        .'<button class="button" onclick="window.scrollTo(0, 0)" title="'. $lang['btn_top'] .'">'
175        . $lang['btn_top']
176        .'</button></a>';
177}
178
179/**
180 * Displays a button (using its own form)
181 * If tooltip exists, the access key tooltip is replaced.
182 *
183 * @author Andreas Gohr <andi@splitbrain.org>
184 *
185 * @param string         $name
186 * @param string         $id
187 * @param string         $akey   access key
188 * @param string[]       $params key-value pairs added as hidden inputs
189 * @param string         $method
190 * @param string         $tooltip
191 * @param bool|string    $label  label text, false: lookup btn_$name in localization
192 * @param string         $svg (optional) svg code, inserted into the button
193 * @return string
194 */
195function html_btn($name, $id, $akey, $params, $method = 'get', $tooltip = '', $label = false, $svg = null)
196{
197    global $conf;
198    global $lang;
199
200    if (!$label)
201        $label = $lang['btn_'.$name];
202
203    //filter id (without urlencoding)
204    $id = idfilter($id, false);
205
206    //make nice URLs even for buttons
207    if ($conf['userewrite'] == 2) {
208        $script = DOKU_BASE.DOKU_SCRIPT.'/'.$id;
209    } elseif ($conf['userewrite']) {
210        $script = DOKU_BASE.$id;
211    } else {
212        $script = DOKU_BASE.DOKU_SCRIPT;
213        $params['id'] = $id;
214    }
215
216    $html = '<form class="button btn_'.$name.'" method="'.$method.'" action="'.$script.'"><div class="no">';
217
218    if (is_array($params)) {
219        foreach ($params as $key => $val) {
220            $html .= '<input type="hidden" name="'.$key.'" value="'.hsc($val).'" />';
221        }
222    }
223
224    $tip = empty($tooltip) ? hsc($label) : hsc($tooltip);
225
226    $html .= '<button type="submit" ';
227    if ($akey) {
228        $tip  .= ' ['.strtoupper($akey).']';
229        $html .= 'accesskey="'.$akey.'" ';
230    }
231    $html .= 'title="'.$tip.'">';
232    if ($svg) {
233        $html .= '<span>'. hsc($label) .'</span>'. inlineSVG($svg);
234    } else {
235        $html .= hsc($label);
236    }
237    $html .= '</button>';
238    $html .= '</div></form>';
239
240    return $html;
241}
242/**
243 * show a revision warning
244 *
245 * @author Szymon Olewniczak <dokuwiki@imz.re>
246 * @deprecated 2020-07-18
247 */
248function html_showrev()
249{
250    dbg_deprecated(PageView::class .'::showrev()');
251}
252
253/**
254 * Show a wiki page
255 *
256 * @author Andreas Gohr <andi@splitbrain.org>
257 *
258 * @param null|string $txt wiki text or null for showing $ID
259 * @deprecated 2020-07-18
260 */
261function html_show($txt = null)
262{
263    dbg_deprecated(PageView::class .'::show()');
264    (new PageView($txt))->show();
265}
266
267/**
268 * ask the user about how to handle an exisiting draft
269 *
270 * @author Andreas Gohr <andi@splitbrain.org>
271 * @deprecated 2020-07-18
272 */
273function html_draft()
274{
275    dbg_deprecated(PageDraft::class .'::show()');
276    (new PageDraft)->show();
277}
278
279/**
280 * Highlights searchqueries in HTML code
281 *
282 * @author Andreas Gohr <andi@splitbrain.org>
283 * @author Harry Fuecks <hfuecks@gmail.com>
284 *
285 * @param string $html
286 * @param array|string $phrases
287 * @return string html
288 */
289function html_hilight($html, $phrases)
290{
291    $phrases = (array) $phrases;
292    $phrases = array_map('preg_quote_cb', $phrases);
293    $phrases = array_map('ft_snippet_re_preprocess', $phrases);
294    $phrases = array_filter($phrases);
295
296    $regex = implode('|', $phrases);
297
298    if ($regex === '') return $html;
299    if (!Clean::isUtf8($regex)) return $html;
300
301    return @preg_replace_callback("/((<[^>]*)|$regex)/ui", function ($match) {
302        $hlight = unslash($match[0]);
303        if (!isset($match[2])) {
304            $hlight = '<span class="search_hit">'.$hlight.'</span>';
305        }
306        return $hlight;
307    }, $html);
308}
309
310/**
311 * Display error on locked pages
312 *
313 * @author Andreas Gohr <andi@splitbrain.org>
314 * @deprecated 2020-07-18 not called anymore, see inc/Action/Locked::tplContent()
315 */
316function html_locked()
317{
318    dbg_deprecated(Locked::class .'::showBanner()');
319    (new Locked())->showBanner();
320}
321
322/**
323 * list old revisions
324 *
325 * @author Andreas Gohr <andi@splitbrain.org>
326 * @author Ben Coburn <btcoburn@silicodon.net>
327 * @author Kate Arzamastseva <pshns@ukr.net>
328 *
329 * @param int $first skip the first n changelog lines
330 * @param string $media_id id of media, or empty for current page
331 * @deprecated 2020-07-18
332 */
333function html_revisions($first = -1, $media_id = '')
334{
335    dbg_deprecated(PageRevisions::class .'::show()');
336    if ($media_id) {
337        (new MediaRevisions($media_id))->show($first);
338    } else {
339        global $INFO;
340        (new PageRevisions($INFO['id']))->show($first);
341    }
342}
343
344/**
345 * display recent changes
346 *
347 * @author Andreas Gohr <andi@splitbrain.org>
348 * @author Matthias Grimm <matthiasgrimm@users.sourceforge.net>
349 * @author Ben Coburn <btcoburn@silicodon.net>
350 * @author Kate Arzamastseva <pshns@ukr.net>
351 *
352 * @param int $first
353 * @param string $show_changes
354 * @deprecated 2020-07-18
355 */
356function html_recent($first = 0, $show_changes = 'both')
357{
358    dbg_deprecated(Recent::class .'::show()');
359    (new Recent($first, $show_changes))->show();
360}
361
362/**
363 * Display page index
364 *
365 * @author Andreas Gohr <andi@splitbrain.org>
366 *
367 * @param string $ns
368 * @deprecated 2020-07-18
369 */
370function html_index($ns)
371{
372    dbg_deprecated(Index::class .'::show()');
373    (new Index($ns))->show();
374}
375
376/**
377 * Index tree item formatter for html_buildlist()
378 *
379 * User function for html_buildlist()
380 *
381 * @author Andreas Gohr <andi@splitbrain.org>
382 *
383 * @param array $item
384 * @return string
385 * @deprecated 2020-07-18
386 */
387function html_list_index($item)
388{
389    dbg_deprecated(Index::class .'::formatListItem()');
390    return (new Index)->formatListItem($item);
391}
392
393/**
394 * Index list item formatter for html_buildlist()
395 *
396 * This user function is used in html_buildlist to build the
397 * <li> tags for namespaces when displaying the page index
398 * it gives different classes to opened or closed "folders"
399 *
400 * @author Andreas Gohr <andi@splitbrain.org>
401 *
402 * @param array $item
403 * @return string html
404 * @deprecated 2020-07-18
405 */
406function html_li_index($item)
407{
408    dbg_deprecated(Index::class .'::tagListItem()');
409    return (new Index)->tagListItem($item);
410}
411
412/**
413 * Default list item formatter for html_buildlist()
414 *
415 * @author Andreas Gohr <andi@splitbrain.org>
416 *
417 * @param array $item
418 * @return string html
419 * @deprecated 2020-07-18
420 */
421function html_li_default($item)
422{
423    return '<li class="level'.$item['level'].'">';
424}
425
426/**
427 * Build an unordered list
428 *
429 * Build an unordered list from the given $data array
430 * Each item in the array has to have a 'level' property
431 * the item itself gets printed by the given $func user
432 * function. The second and optional function is used to
433 * print the <li> tag. Both user function need to accept
434 * a single item.
435 *
436 * Both user functions can be given as array to point to
437 * a member of an object.
438 *
439 * @author Andreas Gohr <andi@splitbrain.org>
440 *
441 * @param array    $data  array with item arrays
442 * @param string   $class class of ul wrapper
443 * @param callable $func  callback to print an list item
444 * @param callable $lifunc (optional) callback to the opening li tag
445 * @param bool     $forcewrapper (optional) Trigger building a wrapper ul if the first level is
446 *                               0 (we have a root object) or 1 (just the root content)
447 * @return string html of an unordered list
448 */
449function html_buildlist($data, $class, $func, $lifunc = null, $forcewrapper = false)
450{
451    if ($data === []) {
452        return '';
453    }
454
455    $firstElement = reset($data);
456    $start_level = $firstElement['level'];
457    $level = $start_level;
458    $html  = '';
459    $open  = 0;
460
461    // set callback function to build the <li> tag, formerly defined as html_li_default()
462    if (!is_callable($lifunc)) {
463       $lifunc = static fn($item) => '<li class="level'.$item['level'].'">';
464    }
465
466    foreach ($data as $item) {
467        if ($item['level'] > $level) {
468            //open new list
469            for ($i = 0; $i < ($item['level'] - $level); $i++) {
470                if ($i) $html .= '<li class="clear">';
471                $html .= "\n".'<ul class="'.$class.'">'."\n";
472                $open++;
473            }
474            $level = $item['level'];
475
476        } elseif ($item['level'] < $level) {
477            //close last item
478            $html .= '</li>'."\n";
479            while ($level > $item['level'] && $open > 0 ) {
480                //close higher lists
481                $html .= '</ul>'."\n".'</li>'."\n";
482                $level--;
483                $open--;
484            }
485        } elseif ($html !== '') {
486            //close previous item
487            $html .= '</li>'."\n";
488        }
489
490        //print item
491        $html .= call_user_func($lifunc, $item);
492        $html .= '<div class="li">';
493
494        $html .= call_user_func($func, $item);
495        $html .= '</div>';
496    }
497
498    //close remaining items and lists
499    $html .= '</li>'."\n";
500    while ($open-- > 0) {
501        $html .= '</ul></li>'."\n";
502    }
503
504    if ($forcewrapper || $start_level < 2) {
505        // Trigger building a wrapper ul if the first level is
506        // 0 (we have a root object) or 1 (just the root content)
507        $html = "\n".'<ul class="'.$class.'">'."\n".$html.'</ul>'."\n";
508    }
509
510    return $html;
511}
512
513/**
514 * display backlinks
515 *
516 * @author Andreas Gohr <andi@splitbrain.org>
517 * @author Michael Klier <chi@chimeric.de>
518 * @deprecated 2020-07-18
519 */
520function html_backlinks()
521{
522    dbg_deprecated(Backlinks::class .'::show()');
523    (new Backlinks)->show();
524}
525
526/**
527 * Get header of diff HTML
528 *
529 * @param string $l_rev   Left revisions
530 * @param string $r_rev   Right revision
531 * @param string $id      Page id, if null $ID is used
532 * @param bool   $media   If it is for media files
533 * @param bool   $inline  Return the header on a single line
534 * @return string[] HTML snippets for diff header
535 * @deprecated 2020-07-18
536 */
537function html_diff_head($l_rev, $r_rev, $id = null, $media = false, $inline = false)
538{
539    dbg_deprecated('see '. PageDiff::class .'::buildDiffHead()');
540    return ['', '', '', ''];
541}
542
543/**
544 * Show diff
545 * between current page version and provided $text
546 * or between the revisions provided via GET or POST
547 *
548 * @author Andreas Gohr <andi@splitbrain.org>
549 * @param  string $text  when non-empty: compare with this text with most current version
550 * @param  bool   $intro display the intro text
551 * @param  string $type  type of the diff (inline or sidebyside)
552 * @deprecated 2020-07-18
553 */
554function html_diff($text = '', $intro = true, $type = null)
555{
556    dbg_deprecated(PageDiff::class .'::show()');
557    global $INFO;
558    (new PageDiff($INFO['id']))->compareWith($text)->preference([
559        'showIntro' => $intro,
560        'difftype'  => $type,
561    ])->show();
562}
563
564/**
565 * Create html for revision navigation
566 *
567 * @param PageChangeLog $pagelog changelog object of current page
568 * @param string        $type    inline vs sidebyside
569 * @param int           $l_rev   left revision timestamp
570 * @param int           $r_rev   right revision timestamp
571 * @return string[] html of left and right navigation elements
572 * @deprecated 2020-07-18
573 */
574function html_diff_navigation($pagelog, $type, $l_rev, $r_rev)
575{
576    dbg_deprecated('see '. PageDiff::class .'::buildRevisionsNavigation()');
577    return ['', ''];
578}
579
580/**
581 * Create html link to a diff defined by two revisions
582 *
583 * @param string $difftype display type
584 * @param string $linktype
585 * @param int $lrev oldest revision
586 * @param int $rrev newest revision or null for diff with current revision
587 * @return string html of link to a diff
588 * @deprecated 2020-07-18
589 */
590function html_diff_navigationlink($difftype, $linktype, $lrev, $rrev = null)
591{
592    dbg_deprecated('see '. PageDiff::class .'::diffViewlink()');
593    return '';
594}
595
596/**
597 * Insert soft breaks in diff html
598 *
599 * @param string $diffhtml
600 * @return string
601 * @deprecated 2020-07-18
602 */
603function html_insert_softbreaks($diffhtml)
604{
605    dbg_deprecated(PageDiff::class .'::insertSoftbreaks()');
606    return (new PageDiff)->insertSoftbreaks($diffhtml);
607}
608
609/**
610 * show warning on conflict detection
611 *
612 * @author Andreas Gohr <andi@splitbrain.org>
613 *
614 * @param string $text
615 * @param string $summary
616 * @deprecated 2020-07-18
617 */
618function html_conflict($text, $summary)
619{
620    dbg_deprecated(PageConflict::class .'::show()');
621    (new PageConflict($text, $summary))->show();
622}
623
624/**
625 * Prints the global message array
626 *
627 * @author Andreas Gohr <andi@splitbrain.org>
628 */
629function html_msgarea()
630{
631    global $MSG, $MSG_shown;
632    /** @var array $MSG */
633    // store if the global $MSG has already been shown and thus HTML output has been started
634    $MSG_shown = true;
635
636    if (!isset($MSG)) return;
637
638    $shown = [];
639    foreach ($MSG as $msg) {
640        $hash = md5($msg['msg']);
641        if (isset($shown[$hash])) continue; // skip double messages
642        if (info_msg_allowed($msg)) {
643            print '<div class="'.$msg['lvl'].'">';
644            print $msg['msg'];
645            print '</div>';
646        }
647        $shown[$hash] = 1;
648    }
649
650    unset($GLOBALS['MSG']);
651}
652
653/**
654 * Prints the registration form
655 *
656 * @author Andreas Gohr <andi@splitbrain.org>
657 * @deprecated 2020-07-18
658 */
659function html_register()
660{
661    dbg_deprecated(UserRegister::class .'::show()');
662    (new UserRegister)->show();
663}
664
665/**
666 * Print the update profile form
667 *
668 * @author Christopher Smith <chris@jalakai.co.uk>
669 * @author Andreas Gohr <andi@splitbrain.org>
670 * @deprecated 2020-07-18
671 */
672function html_updateprofile()
673{
674    dbg_deprecated(UserProfile::class .'::show()');
675    (new UserProfile)->show();
676}
677
678/**
679 * Preprocess edit form data
680 *
681 * @author   Andreas Gohr <andi@splitbrain.org>
682 *
683 * @deprecated 2020-07-18
684 */
685function html_edit()
686{
687    dbg_deprecated(Editor::class .'::show()');
688    (new Editor)->show();
689}
690
691/**
692 * Display the default edit form
693 *
694 * Is the default action for HTML_EDIT_FORMSELECTION.
695 *
696 * @param array $param
697 * @deprecated 2020-07-18
698 */
699function html_edit_form($param)
700{
701    dbg_deprecated(Editor::class .'::addTextarea()');
702    (new Editor)->addTextarea($param);
703}
704
705/**
706 * prints some debug info
707 *
708 * @author Andreas Gohr <andi@splitbrain.org>
709 */
710function html_debug()
711{
712    global $conf;
713    global $lang;
714    /** @var AuthPlugin $auth */
715    global $auth;
716    global $INFO;
717
718    //remove sensitive data
719    $cnf = $conf;
720    debug_guard($cnf);
721    $nfo = $INFO;
722    debug_guard($nfo);
723    $ses = $_SESSION;
724    debug_guard($ses);
725
726    print '<html><body>';
727
728    print '<p>When reporting bugs please send all the following ';
729    print 'output as a mail to andi@splitbrain.org ';
730    print 'The best way to do this is to save this page in your browser</p>';
731
732    print '<b>$INFO:</b><pre>';
733    print_r($nfo);
734    print '</pre>';
735
736    print '<b>$_SERVER:</b><pre>';
737    print_r($_SERVER);
738    print '</pre>';
739
740    print '<b>$conf:</b><pre>';
741    print_r($cnf);
742    print '</pre>';
743
744    print '<b>DOKU_BASE:</b><pre>';
745    print DOKU_BASE;
746    print '</pre>';
747
748    print '<b>abs DOKU_BASE:</b><pre>';
749    print DOKU_URL;
750    print '</pre>';
751
752    print '<b>rel DOKU_BASE:</b><pre>';
753    print dirname($_SERVER['PHP_SELF']).'/';
754    print '</pre>';
755
756    print '<b>PHP Version:</b><pre>';
757    print phpversion();
758    print '</pre>';
759
760    print '<b>locale:</b><pre>';
761    print setlocale(LC_ALL, 0);
762    print '</pre>';
763
764    print '<b>encoding:</b><pre>';
765    print $lang['encoding'];
766    print '</pre>';
767
768    if ($auth) {
769        print '<b>Auth backend capabilities:</b><pre>';
770        foreach ($auth->getCapabilities() as $cando) {
771            print '   '.str_pad($cando, 16) .' => '. (int)$auth->canDo($cando) . DOKU_LF;
772        }
773        print '</pre>';
774    }
775
776    print '<b>$_SESSION:</b><pre>';
777    print_r($ses);
778    print '</pre>';
779
780    print '<b>Environment:</b><pre>';
781    print_r($_ENV);
782    print '</pre>';
783
784    print '<b>PHP settings:</b><pre>';
785    $inis = ini_get_all();
786    print_r($inis);
787    print '</pre>';
788
789    if (function_exists('apache_get_version')) {
790        $apache = [];
791        $apache['version'] = apache_get_version();
792
793        if (function_exists('apache_get_modules')) {
794            $apache['modules'] = apache_get_modules();
795        }
796        print '<b>Apache</b><pre>';
797        print_r($apache);
798        print '</pre>';
799    }
800
801    print '</body></html>';
802}
803
804/**
805 * Form to request a new password for an existing account
806 *
807 * @author Benoit Chesneau <benoit@bchesneau.info>
808 * @author Andreas Gohr <gohr@cosmocode.de>
809 * @deprecated 2020-07-18
810 */
811function html_resendpwd()
812{
813    dbg_deprecated(UserResendPwd::class .'::show()');
814    (new UserResendPwd)->show();
815}
816
817/**
818 * Return the TOC rendered to XHTML
819 *
820 * @author Andreas Gohr <andi@splitbrain.org>
821 *
822 * @param array $toc
823 * @return string html
824 */
825function html_TOC($toc)
826{
827    if ($toc === []) return '';
828    global $lang;
829    $out  = '<!-- TOC START -->'.DOKU_LF;
830    $out .= '<div id="dw__toc" class="dw__toc">'.DOKU_LF;
831    $out .= '<h3 class="toggle">';
832    $out .= $lang['toc'];
833    $out .= '</h3>'.DOKU_LF;
834    $out .= '<div>'.DOKU_LF;
835    $out .= html_buildlist($toc, 'toc', 'html_list_toc', null, true);
836    $out .= '</div>'.DOKU_LF.'</div>'.DOKU_LF;
837    $out .= '<!-- TOC END -->'.DOKU_LF;
838    return $out;
839}
840
841/**
842 * Callback for html_buildlist
843 *
844 * @param array $item
845 * @return string html
846 */
847function html_list_toc($item)
848{
849    if (isset($item['hid'])){
850        $link = '#'.$item['hid'];
851    } else {
852        $link = $item['link'];
853    }
854
855    return '<a href="'.$link.'">'.hsc($item['title']).'</a>';
856}
857
858/**
859 * Helper function to build TOC items
860 *
861 * Returns an array ready to be added to a TOC array
862 *
863 * @param string $link  - where to link (if $hash set to '#' it's a local anchor)
864 * @param string $text  - what to display in the TOC
865 * @param int    $level - nesting level
866 * @param string $hash  - is prepended to the given $link, set blank if you want full links
867 * @return array the toc item
868 */
869function html_mktocitem($link, $text, $level, $hash = '#')
870{
871    return  [
872        'link'  => $hash.$link,
873        'title' => $text,
874        'type'  => 'ul',
875        'level' => $level
876    ];
877}
878
879/**
880 * Output a Doku_Form object.
881 * Triggers an event with the form name: HTML_{$name}FORM_OUTPUT
882 *
883 * @author Tom N Harris <tnharris@whoopdedo.org>
884 *
885 * @param string     $name The name of the form
886 * @param Doku_Form  $form The form
887 * @return void
888 * @deprecated 2020-07-18
889 */
890function html_form($name, $form)
891{
892    dbg_deprecated('use dokuwiki\Form\Form instead of Doku_Form');
893    // Safety check in case the caller forgets.
894    $form->endFieldset();
895    Event::createAndTrigger('HTML_'.strtoupper($name).'FORM_OUTPUT', $form, 'html_form_output', false);
896}
897
898/**
899 * Form print function.
900 * Just calls printForm() on the form object.
901 *
902 * @param Doku_Form $form The form
903 * @return void
904 * @deprecated 2020-07-18
905 */
906function html_form_output($form)
907{
908    dbg_deprecated('use ' . Form::class . '::toHTML()');
909    $form->printForm();
910}
911
912/**
913 * Embed a flash object in HTML
914 *
915 * This will create the needed HTML to embed a flash movie in a cross browser
916 * compatble way using valid XHTML
917 *
918 * The parameters $params, $flashvars and $atts need to be associative arrays.
919 * No escaping needs to be done for them. The alternative content *has* to be
920 * escaped because it is used as is. If no alternative content is given
921 * $lang['noflash'] is used.
922 *
923 * @author Andreas Gohr <andi@splitbrain.org>
924 * @link   http://latrine.dgx.cz/how-to-correctly-insert-a-flash-into-xhtml
925 *
926 * @param string $swf      - the SWF movie to embed
927 * @param int $width       - width of the flash movie in pixels
928 * @param int $height      - height of the flash movie in pixels
929 * @param array $params    - additional parameters (<param>)
930 * @param array $flashvars - parameters to be passed in the flashvar parameter
931 * @param array $atts      - additional attributes for the <object> tag
932 * @param string $alt      - alternative content (is NOT automatically escaped!)
933 * @return string         - the XHTML markup
934 */
935function html_flashobject($swf, $width, $height, $params = null, $flashvars = null, $atts = null, $alt = '')
936{
937    global $lang;
938
939    $out = '';
940
941    // prepare the object attributes
942    if(is_null($atts)) $atts = [];
943    $atts['width']  = (int) $width;
944    $atts['height'] = (int) $height;
945    if(!$atts['width'])  $atts['width']  = 425;
946    if(!$atts['height']) $atts['height'] = 350;
947
948    // add object attributes for standard compliant browsers
949    $std = $atts;
950    $std['type'] = 'application/x-shockwave-flash';
951    $std['data'] = $swf;
952
953    // add object attributes for IE
954    $ie  = $atts;
955    $ie['classid'] = 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000';
956
957    // open object (with conditional comments)
958    $out .= '<!--[if !IE]> -->'.NL;
959    $out .= '<object '.buildAttributes($std).'>'.NL;
960    $out .= '<!-- <![endif]-->'.NL;
961    $out .= '<!--[if IE]>'.NL;
962    $out .= '<object '.buildAttributes($ie).'>'.NL;
963    $out .= '    <param name="movie" value="'.hsc($swf).'" />'.NL;
964    $out .= '<!--><!-- -->'.NL;
965
966    // print params
967    if(is_array($params)) foreach($params as $key => $val){
968        $out .= '  <param name="'.hsc($key).'" value="'.hsc($val).'" />'.NL;
969    }
970
971    // add flashvars
972    if(is_array($flashvars)){
973        $out .= '  <param name="FlashVars" value="'.buildURLparams($flashvars).'" />'.NL;
974    }
975
976    // alternative content
977    if($alt){
978        $out .= $alt.NL;
979    }else{
980        $out .= $lang['noflash'].NL;
981    }
982
983    // finish
984    $out .= '</object>'.NL;
985    $out .= '<!-- <![endif]-->'.NL;
986
987    return $out;
988}
989
990/**
991 * Prints HTML code for the given tab structure
992 *
993 * @param array  $tabs        tab structure
994 * @param string $current_tab the current tab id
995 * @return void
996 */
997function html_tabs($tabs, $current_tab = null)
998{
999    echo '<ul class="tabs">'.NL;
1000
1001    foreach ($tabs as $id => $tab) {
1002        html_tab($tab['href'], $tab['caption'], $id === $current_tab);
1003    }
1004
1005    echo '</ul>'.NL;
1006}
1007
1008/**
1009 * Prints a single tab
1010 *
1011 * @author Kate Arzamastseva <pshns@ukr.net>
1012 * @author Adrian Lang <mail@adrianlang.de>
1013 *
1014 * @param string $href - tab href
1015 * @param string $caption - tab caption
1016 * @param boolean $selected - is tab selected
1017 * @return void
1018 */
1019
1020function html_tab($href, $caption, $selected = false)
1021{
1022    $tab = '<li>';
1023    if ($selected) {
1024        $tab .= '<strong>';
1025    } else {
1026        $tab .= '<a href="' . hsc($href) . '">';
1027    }
1028    $tab .= hsc($caption)
1029         .  '</' . ($selected ? 'strong' : 'a') . '>'
1030         .  '</li>'.NL;
1031    echo $tab;
1032}
1033
1034/**
1035 * Display size change
1036 *
1037 * @param int $sizechange - size of change in Bytes
1038 * @param Doku_Form $form - (optional) form to add elements to
1039 * @return void|string
1040 */
1041function html_sizechange($sizechange, $form = null)
1042{
1043    if (isset($sizechange)) {
1044        $class = 'sizechange';
1045        $value = filesize_h(abs($sizechange));
1046        if ($sizechange > 0) {
1047            $class .= ' positive';
1048            $value = '+' . $value;
1049        } elseif ($sizechange < 0) {
1050            $class .= ' negative';
1051            $value = '-' . $value;
1052        } else {
1053            $value = '±' . $value;
1054        }
1055        if (!isset($form)) {
1056            return '<span class="'.$class.'">'.$value.'</span>';
1057        } else { // Doku_Form
1058            $form->addElement(form_makeOpenTag('span', ['class' => $class]));
1059            $form->addElement($value);
1060            $form->addElement(form_makeCloseTag('span'));
1061        }
1062    }
1063}
1064