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