1use vars qw($theme_no_table $ui_radio_selector_donejs $module_name
2	    $ui_multi_select_donejs, $ui_formcount);
3
4=head1 ui-lib.pl
5
6Common functions for generating HTML for Webmin user interface elements.
7Some example code :
8
9 use WebminCore;
10 init_config();
11 ui_print_header(undef, 'My Module', '');
12
13 print ui_form_start('save.cgi');
14 print ui_table_start('My form', undef, 2);
15
16 print ui_table_row('Enter your name',
17	ui_textbox('name', undef, 40));
18
19 print ui_table_end();
20 print ui_form_end([ [ undef, 'Save' ] ]);
21
22 ui_print_footer('/', 'Webmin index');
23
24=cut
25
26####################### utility functions
27
28=head2 ui_link(href, text, [class], [tags])
29
30Returns HTML for an <a href>.
31
32=item href - Link
33
34=item text - Text to display for link
35
36=item class - Optional additional CSS classes to include
37
38=item tags - Additional HTML attributes for the <a> tag.
39
40=cut
41
42sub ui_link
43{
44return &theme_ui_link(@_) if (defined(&theme_ui_link));
45my ($href, $text, $class, $tags) = @_;
46return ("<a class='ui_link".($class ? " ".$class : "")."' href='$href'".($tags ? " ".$tags : "").">$text</a>");
47}
48
49=head2 ui_help(title)
50
51Returns HTML for help tooltip bubble
52
53=item title - help tooltip title
54
55=cut
56
57sub ui_help
58{
59return &theme_ui_help(@_) if (defined(&theme_ui_help));
60my ($title) = @_;
61return ("<sup class=\"ui_help\" aria-label=\"$title\" data-tooltip><samp>?</samp></sup>");
62}
63
64=head2 ui_img(src, alt, title, [class], [tags])
65
66Returns HTML for an <img src>.
67
68=item src - Image path and filename
69
70=item alt - Alt text for screen readers, etc.
71
72=item title - Element title, and tooltip when user hovers over image
73
74=item class - Optional additional CSS classes to include
75
76=item tags - Additional HTML attributes for the <img> tag
77
78=cut
79
80sub ui_img
81{
82return &theme_ui_img(@_) if (defined(&theme_ui_img));
83my ($src, $alt, $title, $class, $tags) = @_;
84return ("<img src='".$src."' class='ui_img".($class ? " ".$class : "")."' alt='$alt' ".($title ? "title='$title'" : "").($tags ? " ".$tags : "").">");
85}
86
87=head2 ui_link_button(href, text, [target], [tags])
88
89Returns HTML for a button, which opens a URL when clicked. The parameters are :
90
91=item href - Link URL
92
93=item text - Text to display on the button
94
95=item target - Window name to open the link in
96
97=item tags - Additional HTML attributes for the <input> tag.
98
99=cut
100
101sub ui_link_button
102{
103return &theme_ui_link_button(@_) if (defined(&theme_ui_link_button));
104my ($href, $label, $target, $tags) = @_;
105$target ||= "_self";
106return &ui_button($label, undef, 0,
107	"onClick='window.open(\"".&quote_javascript($href)."\", \"$target\")' ".
108	$tags);
109}
110
111####################### table generation functions
112
113=head2 ui_table_start(heading, [tabletags], [cols], [&default-tds], [right-heading])
114
115Returns HTML for the start of a form block into which labelled inputs can
116be placed. By default this is implemented as a table with another table inside
117it, but themes may override this with their own layout.
118
119The parameters are :
120
121=item heading - Text to show at the top of the form.
122
123=item tabletags - HTML attributes to put in the outer <table>, typically something like width=100%.
124
125=item cols - Desired number of columns for labels and fields. Defaults to 4, but can be 2 for forms with lots of wide inputs.
126
127=item default-tds - An optional array reference of HTML attributes for the <td> tags in each row of the table.
128
129=item right-heading - HTML to appear in the heading, aligned to the right.
130
131=cut
132sub ui_table_start
133{
134return &theme_ui_table_start(@_) if (defined(&theme_ui_table_start));
135my ($heading, $tabletags, $cols, $tds, $rightheading) = @_;
136if (defined($main::ui_table_cols)) {
137	# Push on stack, for nested call
138	push(@main::ui_table_cols_stack, $main::ui_table_cols);
139	push(@main::ui_table_pos_stack, $main::ui_table_pos);
140	push(@main::ui_table_default_tds_stack, $main::ui_table_default_tds);
141	}
142my $colspan = 1;
143my $rv;
144$rv .= "<table class='ui_table' border $tabletags>\n";
145if (defined($heading) || defined($rightheading)) {
146	$rv .= "<tr".($tb ? " ".$tb : "")." class='ui_table_head'>";
147	if (defined($heading)) {
148		$rv .= "<td><b>$heading</b></td>"
149		}
150	if (defined($rightheading)) {
151		$rv .= "<td align='right'>$rightheading</td>";
152		$colspan++;
153		}
154	$rv .= "</tr>\n";
155	}
156$rv .= "<tr".($cb ? " ".$cb : "")." class='ui_table_body'> <td colspan='$colspan'>".
157       "<table width='100%'>\n";
158$main::ui_table_cols = $cols || 4;
159$main::ui_table_pos = 0;
160$main::ui_table_default_tds = $tds;
161return $rv;
162}
163
164=head2 ui_table_end
165
166Returns HTML for the end of a block started by ui_table_start.
167
168=cut
169sub ui_table_end
170{
171return &theme_ui_table_end(@_) if (defined(&theme_ui_table_end));
172my $rv;
173if ($main::ui_table_cols == 4 && $main::ui_table_pos) {
174	# Add an empty block to balance the table
175	$rv .= &ui_table_row(" ", " ");
176	}
177if (@main::ui_table_cols_stack) {
178	$main::ui_table_cols = pop(@main::ui_table_cols_stack);
179	$main::ui_table_pos = pop(@main::ui_table_pos_stack);
180	$main::ui_table_default_tds = pop(@main::ui_table_default_tds_stack);
181	}
182else {
183	$main::ui_table_cols = undef;
184	$main::ui_table_pos = undef;
185	$main::ui_table_default_tds = undef;
186	}
187$rv .= "</table></td></tr></table>\n";
188return $rv;
189}
190
191=head2 ui_table_row(label, value, [cols], [&td-tags])
192
193Returns HTML for a row in a table started by ui_table_start, with a 1-column
194label and 1+ column value. The parameters are :
195
196=item label - Label for the input field. If this is undef, no label is displayed.
197
198=item value - HTML for the input part of the row.
199
200=item cols - Number of columns the value should take up, defaulting to 1.
201
202=item td-tags - Array reference of HTML attributes for the <td> tags in this row.
203
204=cut
205sub ui_table_row
206{
207return &theme_ui_table_row(@_) if (defined(&theme_ui_table_row));
208my ($label, $value, $cols, $tds) = @_;
209$cols ||= 1;
210$tds ||= $main::ui_table_default_tds;
211my $rv;
212if ($main::ui_table_pos+$cols+1 > $main::ui_table_cols &&
213    $main::ui_table_pos != 0) {
214	# If the requested number of cols won't fit in the number
215	# remaining, start a new row
216	my $leftover = $main::ui_table_cols - $main::ui_table_pos;
217	$rv .= "<td colspan='$leftover'></td>\n";
218	$rv .= "</tr>\n";
219	$main::ui_table_pos = 0;
220	}
221$rv .= "<tr class='ui_table_row'>\n"
222	if ($main::ui_table_pos%$main::ui_table_cols == 0);
223if (defined($label) &&
224    ($value =~ /id="([^"]+)"/ || $value =~ /id='([^']+)'/ ||
225     $value =~ /id=([^>\s]+)/)) {
226	# Value contains an input with an ID
227	my $id = $1;
228	$label = "<label for=\"".&quote_escape($id)."\">$label</label>";
229	}
230$rv .= "<td valign='top' $tds->[0] class='ui_label'><b>$label</b></td>\n"
231	if (defined($label));
232$rv .= "<td valign='top' colspan='$cols' $tds->[1] class='ui_value'>$value</td>\n";
233$main::ui_table_pos += $cols+(defined($label) ? 1 : 0);
234if ($main::ui_table_pos%$main::ui_table_cols == 0) {
235	$rv .= "</tr>\n";
236	$main::ui_table_pos = 0;
237	}
238return $rv;
239}
240
241=head2 ui_table_hr
242
243Returns HTML for a row in a block started by ui_table_row, with a horizontal
244line inside it to separate sections.
245
246=cut
247sub ui_table_hr
248{
249return &theme_ui_table_hr(@_) if (defined(&theme_ui_table_hr));
250my $rv;
251if ($ui_table_pos) {
252	$rv .= "</tr>\n";
253	$ui_table_pos = 0;
254	}
255$rv .= "<tr class='ui_table_hr'> ".
256       "<td colspan='$main::ui_table_cols'><hr></td> </tr>\n";
257return $rv;
258}
259
260=head2 ui_table_span(text)
261
262Outputs a table row that spans the whole table, and contains the given text.
263
264=cut
265sub ui_table_span
266{
267my ($text) = @_;
268return &theme_ui_table_span(@_) if (defined(&theme_ui_table_span));
269my $rv;
270if ($ui_table_pos) {
271	$rv .= "</tr>\n";
272	$ui_table_pos = 0;
273	}
274$rv .= "<tr class='ui_table_span'> ".
275       "<td colspan='$main::ui_table_cols'>$text</td> </tr>\n";
276return $rv;
277}
278
279=head2 ui_columns_start(&headings, [width-percent], [noborder], [&tdtags], [heading])
280
281Returns HTML for the start of a multi-column table, with the given headings.
282The parameters are :
283
284=item headings - An array reference of headers for the table's columns.
285
286=item width-percent - Desired width as a percentage, or undef to let the browser decide.
287
288=item noborder - Set to 1 if the table should not have a border.
289
290=item tdtags - An optional reference to an array of HTML attributes for the table's <td> tags.
291
292=item heading - An optional heading to put above the table.
293
294=cut
295sub ui_columns_start
296{
297return &theme_ui_columns_start(@_) if (defined(&theme_ui_columns_start));
298my ($heads, $width, $noborder, $tdtags, $title) = @_;
299my $rv;
300$rv .= "<table".($noborder ? "" : " border").
301		(defined($width) ? " width='$width%'" : "")." class='ui_columns'>\n";
302if ($title) {
303	$rv .= "<tr".($tb ? " ".$tb : "")." class='ui_columns_heading'>".
304	       "<td colspan='".scalar(@$heads)."'><b>$title</b></td></tr>\n";
305	}
306$rv .= "<tr".($tb ? " ".$tb : "")." class='ui_columns_heads'>\n";
307my $i;
308for($i=0; $i<@$heads; $i++) {
309	$rv .= "<td ".$tdtags->[$i]."><b>".
310	       ($heads->[$i] eq "" ? "<br>" : $heads->[$i])."</b></td>\n";
311	}
312$rv .= "</tr>\n";
313return $rv;
314}
315
316=head2 ui_columns_row(&columns, &tdtags)
317
318Returns HTML for a row in a multi-column table. The parameters are :
319
320=item columns - Reference to an array containing the HTML to show in the columns for this row.
321
322=item tdtags - An optional array reference containing HTML attributes for the row's <td> tags.
323
324=cut
325sub ui_columns_row
326{
327return &theme_ui_columns_row(@_) if (defined(&theme_ui_columns_row));
328my ($cols, $tdtags) = @_;
329my $rv;
330$rv .= "<tr".($cb ? " ".$cb : "")." class='ui_columns_row'>\n";
331my $i;
332for($i=0; $i<@$cols; $i++) {
333	$rv .= "<td ".$tdtags->[$i].">".
334	       ($cols->[$i] !~ /\S/ ? "<br>" : $cols->[$i])."</td>\n";
335	}
336$rv .= "</tr>\n";
337return $rv;
338}
339
340=head2 ui_columns_header(&columns, &tdtags)
341
342Returns HTML for a row in a multi-column table, styled as a header. Parameters
343are the same as ui_columns_row.
344
345=cut
346sub ui_columns_header
347{
348return &theme_ui_columns_header(@_) if (defined(&theme_ui_columns_header));
349my ($cols, $tdtags) = @_;
350my $rv;
351$rv .= "<tr".($tb ? " ".$tb : "")." class='ui_columns_header'>\n";
352my $i;
353for($i=0; $i<@$cols; $i++) {
354	$rv .= "<td ".$tdtags->[$i]."><b>".
355	       ($cols->[$i] eq "" ? "<br>" : $cols->[$i])."</b></td>\n";
356	}
357$rv .= "</tr>\n";
358return $rv;
359}
360
361=head2 ui_checked_columns_row(&columns, &tdtags, checkname, checkvalue, [checked?], [disabled], [tags])
362
363Returns HTML for a row in a multi-column table, in which the first column
364contains a checkbox. The parameters are :
365
366=item columns - Reference to an array containing the HTML to show in the columns for this row.
367
368=item tdtags - An optional array reference containing HTML attributes for the row's <td> tags.
369
370=item checkname - Name for the checkbox input. Should be the same for all rows.
371
372=item checkvalue - Value for this checkbox input.
373
374=item checked - Set to 1 if it should be checked by default.
375
376=item disabled - Set to 1 if the checkbox should be disabled and thus un-clickable.
377
378=item tags - Extra HTML tags to include in the radio button.
379
380=cut
381sub ui_checked_columns_row
382{
383return &theme_ui_checked_columns_row(@_) if (defined(&theme_ui_checked_columns_row));
384my ($cols, $tdtags, $checkname, $checkvalue, $checked, $disabled, $tags) = @_;
385my $rv;
386$rv .= "<tr".($cb ? " ".$cb : "")." class='ui_checked_columns'>\n";
387$rv .= "<td class='ui_checked_checkbox' ".$tdtags->[0].">".
388       &ui_checkbox($checkname, $checkvalue, undef, $checked, $tags, $disabled).
389       "</td>\n";
390my $i;
391for($i=0; $i<@$cols; $i++) {
392	$rv .= "<td ".$tdtags->[$i+1].">";
393	if ($cols->[$i] !~ /<a\s+href|<input|<select|<textarea/) {
394		$rv .= "<label for=\"".
395			&quote_escape("${checkname}_${checkvalue}")."\">";
396		}
397	$rv .= ($cols->[$i] !~ /\S/ ? "<br>" : $cols->[$i]);
398	if ($cols->[$i] !~ /<a\s+href|<input|<select|<textarea/) {
399		$rv .= "</label>";
400		}
401	$rv .= "</td>\n";
402	}
403$rv .= "</tr>\n";
404return $rv;
405}
406
407=head2 ui_radio_columns_row(&columns, &tdtags, checkname, checkvalue, [checked], [disabled], [tags])
408
409Returns HTML for a row in a multi-column table, in which the first
410column is a radio button. The parameters are :
411
412=item columns - Reference to an array containing the HTML to show in the columns for this row.
413
414=item tdtags - An optional array reference containing HTML attributes for the row's <td> tags.
415
416=item checkname - Name for the radio button input. Should be the same for all rows.
417
418=item checkvalue - Value for this radio button option.
419
420=item checked - Set to 1 if it should be checked by default.
421
422=item disabled - Set to 1 if the radio button should be disabled and thus un-clickable.
423
424=item tags - Extra HTML tags to include in the radio button.
425
426=cut
427sub ui_radio_columns_row
428{
429return &theme_ui_radio_columns_row(@_) if (defined(&theme_ui_radio_columns_row));
430my ($cols, $tdtags, $checkname, $checkvalue, $checked, $dis, $tags) = @_;
431my $rv;
432$rv .= "<tr".($cb ? " ".$cb : "")." class='ui_radio_columns'>\n";
433$rv .= "<td class='ui_radio_radio' ".$tdtags->[0].">".
434    &ui_oneradio($checkname, $checkvalue, "", $checked, undef, $dis)."</td>\n";
435my $i;
436for($i=0; $i<@$cols; $i++) {
437	$rv .= "<td ".$tdtags->[$i+1].">";
438	if ($cols->[$i] !~ /<a\s+href|<input|<select|<textarea/) {
439		$rv .= "<label for=\"".
440			&quote_escape("${checkname}_${checkvalue}")."\">";
441		}
442	$rv .= ($cols->[$i] !~ /\S/ ? "<br>" : $cols->[$i]);
443	if ($cols->[$i] !~ /<a\s+href|<input|<select|<textarea/) {
444		$rv .= "</label>";
445		}
446	$rv .= "</td>\n";
447	}
448$rv .= "</tr>\n";
449return $rv;
450}
451
452=head2 ui_columns_end
453
454Returns HTML to end a table started by ui_columns_start.
455
456=cut
457sub ui_columns_end
458{
459return &theme_ui_columns_end(@_) if (defined(&theme_ui_columns_end));
460return "</table>\n";
461}
462
463=head2 ui_columns_table(&headings, width-percent, &data, &types, no-sort, title, empty-msg)
464
465Returns HTML for a complete table, typically generated internally by
466ui_columns_start, ui_columns_row and ui_columns_end. The parameters are :
467
468=item headings - An array ref of heading HTML.
469
470=item width-percent - Preferred total width
471
472=item data - A 2x2 array ref of table contents. Each can either be a simple string, or a hash ref like :
473
474  { 'type' => 'group', 'desc' => 'Some section title' }
475  { 'type' => 'string', 'value' => 'Foo', 'colums' => 3,
476    'nowrap' => 1 }
477  { 'type' => 'checkbox', 'name' => 'd', 'value' => 'foo',
478    'label' => 'Yes', 'checked' => 1, 'disabled' => 1 }
479  { 'type' => 'radio', 'name' => 'd', 'value' => 'foo', ... }
480
481=item types - An array ref of data types, such as 'string', 'number', 'bytes' or 'date'
482
483=item no-sort - Set to 1 to disable sorting by theme.
484
485=item title - Text to appear above the table.
486
487=item empty-msg - Message to display if no data.
488
489=cut
490sub ui_columns_table
491{
492return &theme_ui_columns_table(@_) if (defined(&theme_ui_columns_table));
493my ($heads, $width, $data, $types, $nosort, $title, $emptymsg) = @_;
494my $rv;
495
496# Just show empty message if no data
497if ($emptymsg && !@$data) {
498	$rv .= &ui_subheading($title) if ($title);
499	$rv .= "<span class='ui_emptymsg'><b>$emptymsg</b></span><p>\n";
500	return $rv;
501	}
502
503# Are there any checkboxes in each column? If so, make those columns narrow
504my @tds = map { "valign='top'" } @$heads;
505my $maxwidth = 0;
506foreach my $r (@$data) {
507	my $cc = 0;
508	foreach my $c (@$r) {
509		if (ref($c) &&
510		    ($c->{'type'} eq 'checkbox' || $c->{'type'} eq 'radio')) {
511			$tds[$cc] .= " width='5'" if ($tds[$cc] !~ /width=/);
512			}
513		$cc++;
514		}
515	$maxwidth = $cc if ($cc > $maxwidth);
516	}
517$rv .= &ui_columns_start($heads, $width, 0, \@tds, $title);
518
519# Add the data rows
520foreach my $r (@$data) {
521	my $c0;
522	if (ref($r->[0]) && ($r->[0]->{'type'} eq 'checkbox' ||
523			     $r->[0]->{'type'} eq 'radio')) {
524		# First column is special
525		$c0 = $r->[0];
526		$r = [ @$r[1..(@$r-1)] ];
527		}
528	# Turn data into HTML
529	my @rtds = @tds;
530	my @cols;
531	my $cn = 0;
532	$cn++ if ($c0);
533	foreach my $c (@$r) {
534		if (!ref($c)) {
535			# Plain old string
536			push(@cols, $c);
537			}
538		elsif ($c->{'type'} eq 'checkbox') {
539			# Checkbox in non-first column
540			push(@cols, &ui_checkbox($c->{'name'}, $c->{'value'},
541					         $c->{'label'}, $c->{'checked'},
542						 $c->{'tags'},
543						 $c->{'disabled'}));
544			}
545		elsif ($c->{'type'} eq 'radio') {
546			# Radio button in non-first column
547			push(@cols, &ui_oneradio($c->{'name'}, $c->{'value'},
548					         $c->{'label'}, $c->{'checked'},
549						 $c->{'tags'},
550						 $c->{'disabled'}));
551			}
552		elsif ($c->{'type'} eq 'group') {
553			# Header row that spans whole table
554			$rv .= &ui_columns_header([ $c->{'desc'} ],
555						  [ "colspan=$width" ]);
556			next;
557			}
558		elsif ($c->{'type'} eq 'string') {
559			# A string, which might be special
560			push(@cols, $c->{'value'});
561			if ($c->{'columns'} > 1) {
562				splice(@rtds, $cn, $c->{'columns'},
563				       "colspan=".$c->{'columns'});
564				}
565			if ($c->{'nowrap'}) {
566				$rtds[$cn] .= " nowrap";
567				}
568			$rtds[$cn] .= " ".$c->{'td'} if ($c->{'td'});
569			}
570		$cn++;
571		}
572	# Add the row
573	if (!$c0) {
574		$rv .= &ui_columns_row(\@cols, \@rtds);
575		}
576	elsif ($c0->{'type'} eq 'checkbox') {
577		$rv .= &ui_checked_columns_row(\@cols, \@rtds, $c0->{'name'},
578					       $c0->{'value'}, $c0->{'checked'},
579					       $c0->{'disabled'},
580					       $c0->{'tags'});
581		}
582	elsif ($c0->{'type'} eq 'radio') {
583		$rv .= &ui_radio_columns_row(\@cols, \@rtds, $c0->{'name'},
584					     $c0->{'value'}, $c0->{'checked'},
585					     $c0->{'disabled'},
586					     $c0->{'tags'});
587		}
588	}
589
590$rv .= &ui_columns_end();
591return $rv;
592}
593
594=head2 ui_form_columns_table(cgi, &buttons, select-all, &otherlinks, &hiddens, &headings, width-percent, &data, &types, no-sort, title, empty-msg, form-no)
595
596Similar to ui_columns_table, but wrapped in a form. Parameters are :
597
598=item cgi - URL to submit the form to.
599
600=item buttons - An array ref of buttons at the end of the form, similar to that taken by ui_form_end.
601
602=item select-all - If set to 1, include select all / invert links.
603
604=item otherslinks - An array ref of other links to put at the top of the table, each of which is a 3-element hash ref of url, text and alignment (left or right).
605
606=item hiddens - An array ref of hidden fields, each of which is a 2-element array ref containing the name and value.
607
608=item formno - Index of this form on the page. Defaults to 0, but should be set if there is more than one form on the page.
609
610All other parameters are the same as ui_columns_table.
611
612=cut
613sub ui_form_columns_table
614{
615return &theme_ui_form_columns_table(@_)
616	if (defined(&theme_ui_form_columns_table));
617my ($cgi, $buttons, $selectall, $others, $hiddens,
618       $heads, $width, $data, $types, $nosort, $title, $emptymsg, $formno) = @_;
619my $rv;
620
621# Build links
622my @leftlinks = map { "<a href='$_->[0]'>$_->[1]</a>" }
623		       grep { $_->[2] ne 'right' } @$others;
624my @rightlinks = map { "<a href='$_->[0]'>$_->[1]</a>" }
625		       grep { $_->[2] eq 'right' } @$others;
626my $links;
627
628# Add select links
629if (@$data) {
630	if ($selectall) {
631		my $cbname;
632		foreach my $r (@$data) {
633			foreach my $c (@$r) {
634				if (ref($c) && $c->{'type'} eq 'checkbox') {
635					$cbname = $c->{'name'};
636					last;
637					}
638				}
639			}
640		if ($cbname) {
641			unshift(@leftlinks, &select_all_link($cbname, $formno),
642				    &select_invert_link($cbname, $formno));
643			}
644		}
645	}
646
647# Turn to HTML
648if (@rightlinks) {
649	$links = &ui_grid_table([ &ui_links_row(\@leftlinks),
650				  &ui_links_row(\@rightlinks) ], 2, 100,
651			        [ undef, "align='right'" ]);
652	}
653elsif (@leftlinks) {
654	$links = &ui_links_row(\@leftlinks);
655	}
656
657# Start the form, if we need one
658if (@$data) {
659	$rv .= &ui_form_start($cgi, "post");
660	foreach my $h (@$hiddens) {
661		$rv .= &ui_hidden(@$h);
662		}
663	$rv .= $links;
664	}
665
666# Add the table
667$rv .= &ui_columns_table($heads, $width, $data, $types, $nosort, $title,
668			 $emptymsg);
669
670# Add form end
671$rv .= $links;
672if (@$data) {
673	$rv .= &ui_form_end($buttons);
674	}
675
676return $rv;
677}
678
679####################### form generation functions
680
681=head2 ui_form_start(script, method, [target], [tags])
682
683Returns HTML for the start of a a form that submits to some script. The
684parameters are :
685
686=item script - CGI script to submit to, like save.cgi.
687
688=item method - HTTP method, which must be one of 'get', 'post' or 'form-data'. If form-data is used, the target CGI must call ReadParseMime to parse parameters.
689
690=item target - Optional target window or frame for the form.
691
692=item tags - Additional HTML attributes for the form tag.
693
694=cut
695sub ui_form_start
696{
697$ui_formcount ||= 0;
698return &theme_ui_form_start(@_) if (defined(&theme_ui_form_start));
699my ($script, $method, $target, $tags) = @_;
700my $rv;
701$rv .= "<form class='ui_form' action='".&html_escape($script)."' ".
702	($method eq "post" ? "method='post'" :
703	 $method eq "form-data" ?
704		"method='post' enctype='multipart/form-data'" :
705		"method='get'").
706	($target ? " target='$target'" : "").
707        ($tags ? " ".$tags : "").">\n";
708return $rv;
709}
710
711=head2 ui_form_end([&buttons], [width], [nojs])
712
713Returns HTML for the end of a form, optionally with a row of submit buttons.
714These are specified by the buttons parameter, which is an array reference
715of array refs, with the following elements :
716
717=item HTML value for the submit input for the button, or undef for none.
718
719=item Text to appear on the button.
720
721=item HTML or other inputs to appear after the button.
722
723=item Set to 1 if the button should be disabled.
724
725=item Additional HTML attributes to appear inside the button's input tag.
726
727=item Don't include generated javascript for ui_opt_textbox
728
729=cut
730sub ui_form_end
731{
732$ui_formcount++;
733return &theme_ui_form_end(@_) if (defined(&theme_ui_form_end));
734my ($buttons, $width, $nojs) = @_;
735my $rv;
736if ($buttons && @$buttons) {
737	$rv .= "<table class='ui_form_end_buttons' ".($width ? " width='$width'" : "")."><tr>\n";
738	my $b;
739	foreach $b (@$buttons) {
740		if (ref($b)) {
741			$rv .= "<td".(!$width ? "" :
742				      $b eq $buttons->[0] ? " align='left'" :
743				      $b eq $buttons->[@$buttons-1] ?
744					" align='right'" : " align='center'").">".
745			       &ui_submit($b->[1], $b->[0], $b->[3], $b->[4]).
746			       ($b->[2] ? " ".$b->[2] : "")."</td>\n";
747			}
748		elsif ($b) {
749			$rv .= "<td>$b</td>\n";
750			}
751		else {
752			$rv .= "<td>&nbsp;&nbsp;</td>\n";
753			}
754		}
755	$rv .= "</tr></table>\n";
756	}
757$rv .= "</form>\n";
758if ( !$nojs ) {
759    # When going back to a form, re-enable any text fields generated by
760    # ui_opt_textbox that aren't in the default state.
761    $rv .= "<script type='text/javascript'>\n";
762    $rv .= "var opts = document.getElementsByClassName('ui_opt_textbox');\n";
763    $rv .= "for(var i=0; i<opts.length; i++) {\n";
764    $rv .= "  opts[i].disabled = document.getElementsByName(opts[i].name+'_def')[0].checked;\n";
765    $rv .= "}\n";
766    $rv .= "</script>\n";
767}
768return $rv;
769}
770
771=head2 ui_textbox(name, value, size, [disabled?], [maxlength], [tags])
772
773Returns HTML for a text input box. The parameters are :
774
775=item name - Name for this input.
776
777=item value - Initial contents for the text box.
778
779=item size - Desired width in characters.
780
781=item disabled - Set to 1 if this text box should be disabled by default.
782
783=item maxlength - Maximum length of the string the user is allowed to input.
784
785=item tags - Additional HTML attributes for the <input> tag.
786
787=cut
788sub ui_textbox
789{
790return &theme_ui_textbox(@_) if (defined(&theme_ui_textbox));
791my ($name, $value, $size, $dis, $max, $tags) = @_;
792$size = &ui_max_text_width($size);
793return "<input class='ui_textbox' type='text' ".
794       "name=\"".&html_escape($name)."\" ".
795       "id=\"".&html_escape($name)."\" ".
796       "value=\"".&html_escape($value)."\" ".
797       "size=$size".($dis ? " disabled='true'" : "").
798       ($max ? " maxlength='$max'" : "").
799       ($tags ? " ".$tags : "").">";
800}
801
802=head2 ui_filebox(name, value, size, [disabled?], [maxlength], [tags], [dir-only])
803
804Returns HTML for a text box for choosing a file. Parameters are the same
805as ui_textbox, except for the extra dir-only option which limits the chooser
806to directories.
807
808=cut
809sub ui_filebox
810{
811return &theme_ui_filebox(@_) if (defined(&theme_ui_filebox));
812my ($name, $value, $size, $dis, $max, $tags, $dironly) = @_;
813return &ui_textbox($name, $value, $size, $dis, $max, $tags)."&nbsp;".
814       &file_chooser_button($name, $dironly);
815}
816
817=head2 ui_bytesbox(name, bytes, [size], [disabled?])
818
819Returns HTML for entering a number of bytes, but with friendly kB/MB/GB
820options. May truncate values to 2 decimal points! The parameters are :
821
822=item name - Name for this input.
823
824=item bytes - Initial number of bytes to show.
825
826=item size - Desired width of the text box part.
827
828=item disabled - Set to 1 if this text box should be disabled by default.
829
830=item tags - Additional HTML attributes for the <input> tag.
831
832=item defaultunits - Units mode selected by default
833
834=cut
835sub ui_bytesbox
836{
837my ($name, $bytes, $size, $dis, $tags, $defaultunits) = @_;
838my $units = 1;
839if ($bytes eq '' && $defaultunits) {
840	$units = $defaultunits;
841	}
842elsif ($bytes >= 10*1024*1024*1024*1024) {
843	$units = 1024*1024*1024*1024;
844	}
845elsif ($bytes >= 10*1024*1024*1024) {
846	$units = 1024*1024*1024;
847	}
848elsif ($bytes >= 10*1024*1024) {
849	$units = 1024*1024;
850	}
851elsif ($bytes >= 10*1024) {
852	$units = 1024;
853	}
854else {
855	$units = 1;
856	}
857if ($bytes ne "") {
858	$bytes = sprintf("%.2f", ($bytes*1.0)/$units);
859	$bytes =~ s/\.00$//;
860	}
861$size = &ui_max_text_width($size || 8);
862return &ui_textbox($name, $bytes, $size, $dis, undef, $tags)." ".
863       &ui_select($name."_units", $units,
864		 [ [ 1, "bytes" ],
865		   [ 1024, "kB" ],
866		   [ 1024*1024, "MB" ],
867		   [ 1024*1024*1024, "GB" ],
868		   [ 1024*1024*1024*1024, "TB" ] ], undef, undef, undef, $dis);
869}
870
871=head2 ui_upload(name, size, [disabled?], [tags])
872
873Returns HTML for a file upload input, for use in a form with the form-data
874method. The parameters are :
875
876=item name - Name for this input.
877
878=item size - Desired width in characters.
879
880=item disabled - Set to 1 if this text box should be disabled by default.
881
882=item tags - Additional HTML attributes for the <input> tag.
883
884=item multiple - Set to 1 to allow uploading of multiple files
885
886=cut
887sub ui_upload
888{
889return &theme_ui_upload(@_) if (defined(&theme_ui_upload));
890my ($name, $size, $dis, $tags, $multiple) = @_;
891$size = &ui_max_text_width($size);
892return "<input class='ui_upload' type='file' ".
893       "name=\"".&quote_escape($name)."\" ".
894       "id=\"".&quote_escape($name)."\" ".
895       "size='$size'".
896       ($dis ? " disabled='true'" : "").
897       ($multiple ? " multiple" : "").
898       ($tags ? " ".$tags : "").">";
899}
900
901=head2 ui_password(name, value, size, [disabled?], [maxlength], [tags])
902
903Returns HTML for a password text input. Parameters are the same as ui_textbox,
904and behaviour is identical except that the user's input is not visible.
905
906=cut
907sub ui_password
908{
909return &theme_ui_password(@_) if (defined(&theme_ui_password));
910my ($name, $value, $size, $dis, $max, $tags) = @_;
911$size = &ui_max_text_width($size);
912return "<input class='ui_password' type='password' ".
913       "name=\"".&quote_escape($name)."\" ".
914       "id=\"".&quote_escape($name)."\" ".
915       ($value ne "" ? "value=\"".&quote_escape($value)."\" " : "").
916       "size='$size'".($dis ? " disabled='true'" : "").
917       ($max ? " maxlength='$max'" : "").
918       ($tags ? " ".$tags : "").">";
919}
920
921=head2 ui_hidden(name, value)
922
923Returns HTML for a hidden field with the given name and value.
924
925=cut
926sub ui_hidden
927{
928return &theme_ui_hidden(@_) if (defined(&theme_ui_hidden));
929my ($name, $value) = @_;
930return "<input class='ui_hidden' type='hidden' ".
931       "name=\"".&quote_escape($name)."\" ".
932       "id=\"".&quote_escape($name)."\" ".
933       "value=\"".&quote_escape($value)."\">\n";
934}
935
936=head2 ui_select(name, value|&values, &options, [size], [multiple], [add-if-missing], [disabled?], [tags])
937
938Returns HTML for a drop-down menu or multiple selection list. The parameters
939are :
940
941=item name - Name for this input.
942
943=item value - Either a single initial value, or an array reference of values if this is a multi-select list.
944
945=item options - An array reference of possible options. Each element can either be a scalar, or a two-element array ref containing a submitted value and displayed text.
946
947=item size - Desired vertical size in rows, which defaults to 1. For multi-select lists, this must be set to something larger.
948
949=item multiple - Set to 1 for a multi-select list, 0 for single.
950
951=item add-if-missing - If set to 1, any value that is not in the list of options will be automatically added (and selected).
952
953=item disabled - Set to 1 to disable this input.
954
955=item tags - Additional HTML attributes for the <select> input.
956
957=cut
958sub ui_select
959{
960return &theme_ui_select(@_) if (defined(&theme_ui_select));
961my ($name, $value, $opts, $size, $multiple, $missing, $dis, $tags) = @_;
962my $rv;
963$rv .= "<select class='ui_select' ".
964       "name=\"".&quote_escape($name)."\" ".
965       "id=\"".&quote_escape($name)."\" ".
966       ($size ? " size='$size'" : "").
967       ($multiple ? " multiple" : "").
968       ($dis ? " disabled=true" : "").($tags ? " ".$tags : "").">\n";
969my ($o, %opt, $s);
970my %sel = ref($value) ? ( map { $_, 1 } @$value ) : ( $value, 1 );
971foreach $o (@$opts) {
972	$o = [ $o ] if (!ref($o));
973	$rv .= "<option value=\"".&quote_escape($o->[0])."\"".
974	       ($sel{$o->[0]} ? " selected" : "").($o->[2] ne '' ? " ".$o->[2] : "").">".
975	       ($o->[1] || $o->[0])."</option>\n";
976	$opt{$o->[0]}++;
977	}
978foreach $s (keys %sel) {
979	if (!$opt{$s} && $missing) {
980		$rv .= "<option value=\"".&quote_escape($s)."\"".
981		       " selected>".($s eq "" ? "&nbsp;" : $s)."</option>\n";
982		}
983	}
984$rv .= "</select>\n";
985return $rv;
986}
987
988=head2 ui_multi_select(name, &values, &options, size, [add-if-missing], [disabled?], [options-title, values-title], [width])
989
990Returns HTML for selecting many of many from a list. By default, this is
991implemented using two <select> lists and Javascript buttons to move elements
992between them. The resulting input value is \n separated.
993
994Parameters are :
995
996=item name - HTML name for this input.
997
998=item values - An array reference of two-element array refs, containing the submitted values and descriptions of items that are selected by default.
999
1000=item options - An array reference of two-element array refs, containing the submitted values and descriptions of items that the user can select from.
1001
1002=item size - Vertical size in rows.
1003
1004=item add-if-missing - If set to 1, any entries that are in values but not in options will be added automatically.
1005
1006=item disabled - Set to 1 to disable this input by default.
1007
1008=item options-title - Optional text to appear above the list of options.
1009
1010=item values-title - Optional text to appear above the list of selected values.
1011
1012=item width - Optional width of the two lists in pixels.
1013
1014=cut
1015sub ui_multi_select
1016{
1017return &theme_ui_multi_select(@_) if (defined(&theme_ui_multi_select));
1018my ($name, $values, $opts, $size, $missing, $dis,
1019       $opts_title, $vals_title, $width) = @_;
1020my $rv;
1021my %already = map { $_->[0], $_ } @$values;
1022my $leftover = [ grep { !$already{$_->[0]} } @$opts ];
1023if ($missing) {
1024	my %optsalready = map { $_->[0], $_ } @$opts;
1025	push(@$opts, grep { !$optsalready{$_->[0]} } @$values);
1026	}
1027if (!defined($width)) {
1028	$width = "200";
1029	}
1030my $wstyle = $width ? "style='width:$width'" : "";
1031
1032if (!$main::ui_multi_select_donejs++) {
1033	$rv .= &ui_multi_select_javascript();
1034	}
1035$rv .= "<table cellpadding=0 cellspacing=0 class='ui_multi_select'>";
1036if (defined($opts_title)) {
1037	$rv .= "<tr class='ui_multi_select_heads'>".
1038	       "<td><b>$opts_title</b></td> ".
1039	       "<td></td><td><b>$vals_title</b></td></tr>";
1040	}
1041$rv .= "<tr class='ui_multi_select_row'>";
1042$rv .= "<td>".&ui_select($name."_opts", [ ], $leftover,
1043			 $size, 1, 0, $dis, $wstyle)."</td>\n";
1044$rv .= "<td>".&ui_button("&#x25B6;", $name."_add", $dis,
1045		 "onClick='multi_select_move(\"$name\", form, 1)'")."<br>".
1046	      &ui_button("&#x25C0;", $name."_remove", $dis,
1047		 "onClick='multi_select_move(\"$name\", form, 0)'")."</td>\n";
1048$rv .= "<td>".&ui_select($name."_vals", [ ], $values,
1049			 $size, 1, 0, $dis, $wstyle)."</td>\n";
1050$rv .= "</tr></table>\n";
1051$rv .= &ui_hidden($name, join("\n", map { $_->[0] } @$values));
1052return $rv;
1053}
1054
1055=head2 ui_multi_select_javascript
1056
1057Returns <script> section for left/right select boxes. For internal use only.
1058
1059=cut
1060sub ui_multi_select_javascript
1061{
1062return &theme_ui_multiselect_javascript()
1063	if (defined(&theme_ui_multiselect_javascript));
1064return <<EOF;
1065<script type='text/javascript'>
1066// Move an element from the options list to the values list, or vice-versa
1067function multi_select_move(name, f, dir)
1068{
1069var opts = f.elements[name+"_opts"];
1070var vals = f.elements[name+"_vals"];
1071var opts_idx = opts.selectedIndex;
1072var vals_idx = vals.selectedIndex;
1073if (dir == 1 && opts_idx >= 0) {
1074	// Moving from options to selected list
1075	for(var i=0; i<opts.options.length; i++) {
1076		var o = opts.options[i];
1077		if (o.selected) {
1078			vals.options[vals.options.length] =
1079				new Option(o.text, o.value);
1080			opts.remove(i);
1081			i--;
1082			}
1083		}
1084	}
1085else if (dir == 0 && vals_idx >= 0) {
1086	// Moving the other way
1087	for(var i=0; i<vals.options.length; i++) {
1088		var o = vals.options[i];
1089		if (o.selected) {
1090			opts.options[opts.options.length] =
1091				new Option(o.text, o.value);
1092			vals.remove(i);
1093			i--;
1094			}
1095		}
1096	}
1097// Fill in hidden field
1098var hid = f.elements[name];
1099if (hid) {
1100	var hv = new Array();
1101	for(var i=0; i<vals.options.length; i++) {
1102		hv.push(vals.options[i].value);
1103		}
1104	hid.value = hv.join("\\n");
1105	}
1106}
1107</script>
1108EOF
1109}
1110
1111=head2 ui_radio(name, value, &options, [disabled?])
1112
1113Returns HTML for a series of radio buttons, of which one can be selected. The
1114parameters are :
1115
1116=item name - HTML name for the radio buttons.
1117
1118=item value - Value of the button that is selected by default.
1119
1120=item options - Array ref of radio button options, each of which is an array ref containing the submitted value and description for each button.
1121
1122=item disabled - Set to 1 to disable all radio buttons by default.
1123
1124=cut
1125sub ui_radio
1126{
1127return &theme_ui_radio(@_) if (defined(&theme_ui_radio));
1128my ($name, $value, $opts, $dis) = @_;
1129my $rv;
1130my $o;
1131foreach $o (@$opts) {
1132	my $id = &quote_escape($name."_".$o->[0]);
1133	my $label = $o->[1] || $o->[0];
1134	my $after;
1135	if ($label =~ /^([\000-\377]*?)((<a\s+href|<input|<select|<textarea|<span|<br|<p)[\000-\377]*)$/i) {
1136		$label = $1;
1137		$after = $2;
1138		}
1139	$rv .= "<input class='ui_radio' type='radio' ".
1140	       "name=\"".&quote_escape($name)."\" ".
1141               "value=\"".&quote_escape($o->[0])."\"".
1142	       ($o->[0] eq $value ? " checked" : "").
1143	       ($dis ? " disabled='true'" : "").
1144	       " id=\"$id\"".
1145	       ($o->[2] ? " ".$o->[2] : "")."> <label for=\"$id\">".
1146	       $label."</label>".$after."\n";
1147	}
1148return $rv;
1149}
1150
1151=head2 ui_yesno_radio(name, value, [yes], [no], [disabled?])
1152
1153Like ui_radio, but always displays just two inputs (yes and no). The parameters
1154are :
1155
1156=item name - HTML name of the inputs.
1157
1158=item value - Option selected by default, typically 1 or 0.
1159
1160=item yes - The value for the yes option, defaulting to 1.
1161
1162=item no - The value for the no option, defaulting to 0.
1163
1164=item disabled - Set to 1 to disable all radio buttons by default.
1165
1166=cut
1167sub ui_yesno_radio
1168{
1169my ($name, $value, $yes, $no, $dis) = @_;
1170return &theme_ui_yesno_radio(@_) if (defined(&theme_ui_yesno_radio));
1171$yes = 1 if (!defined($yes));
1172$no = 0 if (!defined($no));
1173if ( $value =~ /^[0-9,.E]+$/ || !$value) {
1174        $value = int($value);
1175}
1176return &ui_radio($name, $value, [ [ $yes, $text{'yes'} ],
1177				  [ $no, $text{'no'} ] ], $dis);
1178}
1179
1180=head2 ui_checkbox(name, value, label, selected?, [tags], [disabled?])
1181
1182Returns HTML for a single checkbox. Parameters are :
1183
1184=item name - HTML name of the checkbox.
1185
1186=item value - Value that will be submitted if it is checked.
1187
1188=item label - Text to appear next to the checkbox.
1189
1190=item selected - Set to 1 for it to be checked by default.
1191
1192=item tags - Additional HTML attributes for the <input> tag.
1193
1194=item disabled - Set to 1 to disable the checkbox by default.
1195
1196=cut
1197sub ui_checkbox
1198{
1199return &theme_ui_checkbox(@_) if (defined(&theme_ui_checkbox));
1200my ($name, $value, $label, $sel, $tags, $dis) = @_;
1201my $after;
1202if ($label =~ /^([^<]*)(<[\000-\377]*)$/) {
1203	$label = $1;
1204	$after = $2;
1205	}
1206return "<input class='ui_checkbox' type='checkbox' ".
1207       "name=\"".&quote_escape($name)."\" ".
1208       "value=\"".&quote_escape($value)."\" ".
1209       ($sel ? " checked" : "").($dis ? " disabled='true'" : "").
1210       " id=\"".&quote_escape("${name}_${value}")."\"".
1211       ($tags ? " ".$tags : "")."> ".
1212       ($label eq "" ? $after :
1213	 "<label for=\"".&quote_escape("${name}_${value}").
1214	 "\">$label</label>$after")."\n";
1215}
1216
1217=head2 ui_oneradio(name, value, label, selected?, [tags], [disabled?])
1218
1219Returns HTML for a single radio button. The parameters are :
1220
1221=item name - HTML name of the radio button.
1222
1223=item value - Value that will be submitted if it is selected.
1224
1225=item label - Text to appear next to the button.
1226
1227=item selected - Set to 1 for it to be selected by default.
1228
1229=item tags - Additional HTML attributes for the <input> tag.
1230
1231=item disabled - Set to 1 to disable the radio button by default.
1232
1233=cut
1234sub ui_oneradio
1235{
1236return &theme_ui_oneradio(@_) if (defined(&theme_ui_oneradio));
1237my ($name, $value, $label, $sel, $tags, $dis) = @_;
1238my $id = &quote_escape("${name}_${value}");
1239my $after;
1240if ($label =~ /^([^<]*)(<[\000-\377]*)$/) {
1241	$label = $1;
1242	$after = $2;
1243	}
1244my $ret = "<input class='ui_radio' type='radio' name=\"".&quote_escape($name)."\" ".
1245       "value=\"".&quote_escape($value)."\" ".
1246       ($sel ? " checked" : "").($dis ? " disabled='true'" : "").
1247       " id=\"$id\"".
1248       ($tags ? " ".$tags : "").">";
1249    $ret .= " <label for=\"$id\">$label</label>" if ($label ne '');
1250    $ret .= "$after\n";
1251    return $ret;
1252}
1253
1254=head2 ui_textarea(name, value, rows, cols, [wrap], [disabled?], [tags])
1255
1256Returns HTML for a multi-line text input. The function parameters are :
1257
1258=item name - Name for this HTML <textarea>.
1259
1260=item value - Default value. Multiple lines must be separated by \n.
1261
1262=item rows - Number of rows, in lines.
1263
1264=item cols - Number of columns, in characters.
1265
1266=item wrap - Wrapping mode. Can be one of soft, hard or off.
1267
1268=item disabled - Set to 1 to disable this text area by default.
1269
1270=item tags - Additional HTML attributes for the <textarea> tag.
1271
1272=cut
1273sub ui_textarea
1274{
1275return &theme_ui_textarea(@_) if (defined(&theme_ui_textarea));
1276my ($name, $value, $rows, $cols, $wrap, $dis, $tags) = @_;
1277$cols = &ui_max_text_width($cols, 1);
1278return "<textarea class='ui_textarea' ".
1279       "name=\"".&quote_escape($name)."\" ".
1280       "id=\"".&quote_escape($name)."\" ".
1281       "rows='$rows' cols='$cols'".($wrap ? " wrap='$wrap'" : "").
1282       ($dis ? " disabled='true'" : "").
1283       ($tags ? " $tags" : "").">".
1284       &html_escape($value).
1285       "</textarea>";
1286}
1287
1288=head2 ui_user_textbox(name, value, [form], [disabled?], [tags])
1289
1290Returns HTML for an input for selecting a Unix user. Parameters are the
1291same as ui_textbox.
1292
1293=cut
1294sub ui_user_textbox
1295{
1296my ($name, $value, $form, $dis, $tags) = @_;
1297return &theme_ui_user_textbox(@_) if (defined(&theme_ui_user_textbox));
1298return &ui_textbox($name, $value, 13, $dis, undef, $tags)." ".
1299       &user_chooser_button($name, 0, $form);
1300}
1301
1302=head2 ui_users_textbox(name, value, [form], [disabled?], [tags])
1303
1304Returns HTML for an input for selecting multiple Unix users. Parameters are the
1305same as ui_textbox.
1306
1307=cut
1308sub ui_users_textbox
1309{
1310my ($name, $value, $form, $dis, $tags) = @_;
1311return &theme_ui_users_textbox(@_) if (defined(&theme_ui_users_textbox));
1312return &ui_textbox($name, $value, 60, $dis, undef, $tags)." ".
1313       &user_chooser_button($name, 1, $form);
1314}
1315
1316=head2 ui_group_textbox(name, value, [form], [disabled?], [tags])
1317
1318Returns HTML for an input for selecting a Unix group. Parameters are the
1319same as ui_textbox.
1320
1321=cut
1322sub ui_group_textbox
1323{
1324my ($name, $value, $form, $dis, $tags) = @_;
1325return &theme_ui_group_textbox(@_) if (defined(&theme_ui_group_textbox));
1326return &ui_textbox($name, $value, 13, $dis, undef, $tags)." ".
1327       &group_chooser_button($name, 0, $form);
1328}
1329
1330=head2 ui_groups_textbox(name, value, [form], [disabled?], [tags])
1331
1332Returns HTML for an input for selecting Unix groups. Parameters are the
1333same as ui_textbox.
1334
1335=cut
1336sub ui_groups_textbox
1337{
1338my ($name, $value, $form, $dis, $tags) = @_;
1339return &theme_ui_groups_textbox(@_) if (defined(&theme_ui_groups_textbox));
1340return &ui_textbox($name, $value, 60, $dis, undef, $tags)." ".
1341       &group_chooser_button($name, 1, $form);
1342}
1343
1344=head2 ui_opt_textbox(name, value, size, option1, [option2], [disabled?], [&extra-fields], [max])
1345
1346Returns HTML for a text field that is optional, implemented by default as
1347a field with radio buttons next to it. The parameters are :
1348
1349=item name - HTML name for the text box. The radio buttons will have the same name, but with _def appended.
1350
1351=item value - Initial value, or undef if you want the default radio button selected initially.
1352
1353=item size - Width of the text box in characters.
1354
1355=item option1 - Text for the radio button for selecting that no input is being given, such as 'Default'.
1356
1357=item option2 - Text for the radio button for selecting that you will provide input.
1358
1359=item disabled - Set to 1 to disable this input by default.
1360
1361=item extra-fields - An optional array ref of field names that should be disabled by Javascript when this field is disabled.
1362
1363=item max - Optional maximum allowed input length, in characters.
1364
1365=item tags - Additional HTML attributes for the text box
1366
1367=cut
1368sub ui_opt_textbox
1369{
1370return &theme_ui_opt_textbox(@_) if (defined(&theme_ui_opt_textbox));
1371my ($name, $value, $size, $opt1, $opt2, $dis, $extra, $max, $tags) = @_;
1372my $dis1 = &js_disable_inputs([ $name, @$extra ], [ ]);
1373my $dis2 = &js_disable_inputs([ ], [ $name, @$extra ]);
1374my $rv;
1375$size = &ui_max_text_width($size);
1376$rv .= &ui_radio($name."_def", $value eq '' ? 1 : 0,
1377		 [ [ 1, $opt1, "onClick='$dis1'" ],
1378		   [ 0, $opt2 || " ", "onClick='$dis2'" ] ], $dis)."\n";
1379$rv .= "<input class='ui_opt_textbox' type='text' ".
1380       "name=\"".&quote_escape($name)."\" ".
1381       "id=\"".&quote_escape($name)."\" ".
1382       "size=$size value=\"".&quote_escape($value)."\"".
1383       ($dis ? " disabled='true'" : "").
1384       ($max ? " maxlength='$max'" : "").
1385       ($tags ? " ".$tags : "").">";
1386return $rv;
1387}
1388
1389=head2 ui_submit(label, [name], [disabled?], [tags])
1390
1391Returns HTML for a form submit button. Parameters are :
1392
1393=item label - Text to appear on the button.
1394
1395=item name - Optional HTML name for the button. Useful if the CGI it submits to needs to know which of several buttons was clicked.
1396
1397=item disabled - Set to 1 if this button should be disabled by default.
1398
1399=item tags - Additional HTML attributes for the <input> tag.
1400
1401=cut
1402sub ui_submit
1403{
1404return &theme_ui_submit(@_) if (defined(&theme_ui_submit));
1405my ($label, $name, $dis, $tags) = @_;
1406return "<input class='ui_submit' type='submit'".
1407       ($name ne '' ? " name=\"".&quote_escape($name)."\"" : "").
1408       ($name ne '' ? " id=\"".&quote_escape($name)."\"" : "").
1409       " value=\"".&quote_escape($label)."\"".
1410       ($dis ? " disabled='true'" : "").
1411       ($tags ? " ".$tags : "").">\n";
1412}
1413
1414=head2 ui_reset(label, [disabled?], [tags])
1415
1416Returns HTML for a form reset button, which clears all fields when clicked.
1417Parameters are :
1418
1419=item label - Text to appear on the button.
1420
1421=item disabled - Set to 1 if this button should be disabled by default.
1422
1423=item tags - Additional HTML attributes for the <input> tag.
1424
1425=cut
1426sub ui_reset
1427{
1428return &theme_ui_reset(@_) if (defined(&theme_ui_reset));
1429my ($label, $dis, $tags) = @_;
1430return "<input class='ui_reset' type='reset' value=\"".&quote_escape($label)."\"".
1431       ($dis ? " disabled='true'" : "").
1432       ($tags ? " ".$tags : "").">\n";
1433}
1434
1435=head2 ui_button(label, [name], [disabled?], [tags])
1436
1437Returns HTML for a form button, which doesn't do anything when clicked unless
1438you add some Javascript to it. The parameters are :
1439
1440=item label - Text to appear on the button.
1441
1442=item name - HTML name for this input.
1443
1444=item disabled - Set to 1 if this button should be disabled by default.
1445
1446=item tags - Additional HTML attributes for the <input> tag, typically Javascript inside an onClick attribute.
1447
1448=cut
1449sub ui_button
1450{
1451return &theme_ui_button(@_) if (defined(&theme_ui_button));
1452my ($label, $name, $dis, $tags) = @_;
1453return "<input class='ui_button' type='button'".
1454       ($name ne '' ? " name=\"".&quote_escape($name)."\"" : "").
1455       ($name ne '' ? " id=\"".&quote_escape($name)."\"" : "").
1456       " value=\"".&quote_escape($label)."\"".
1457       ($dis ? " disabled='true'" : "").
1458       ($tags ? " ".$tags : "").">\n";
1459}
1460
1461=head2 ui_date_input(day, month, year, day-name, month-name, year-name, [disabled?])
1462
1463Returns HTML for a date-selection field, with day, month and year inputs.
1464The parameters are :
1465
1466=item day - Initial day of the month.
1467
1468=item month - Initial month of the year, indexed from 1.
1469
1470=item year - Initial year, four-digit.
1471
1472=item day-name - Name of the day input field.
1473
1474=item month-name - Name of the month select field.
1475
1476=item year-name - Name of the year input field.
1477
1478=item disabled - Set to 1 to disable all fields by default.
1479
1480=cut
1481sub ui_date_input
1482{
1483return &theme_ui_date_input(@_) if (defined(&theme_ui_date_input));
1484my ($day, $month, $year, $dayname, $monthname, $yearname, $dis) = @_;
1485my $rv;
1486$rv .= "<span class='ui_data'>";
1487$rv .= &ui_textbox($dayname, $day, 3, $dis);
1488$rv .= "/";
1489$rv .= &ui_select($monthname, $month,
1490		  [ map { [ $_, $text{"smonth_$_"} ] } (1 .. 12) ],
1491		  1, 0, 0, $dis);
1492$rv .= "/";
1493$rv .= &ui_textbox($yearname, $year, 5, $dis);
1494$rv .= "</span>";
1495return $rv;
1496}
1497
1498=head2 ui_buttons_start
1499
1500Returns HTML for the start of a block of action buttons with descriptions, as
1501generated by ui_buttons_row. Some example code :
1502
1503  print ui_buttons_start();
1504  print ui_buttons_row('start.cgi', 'Start server',
1505                       'Click this button to start the server process');
1506  print ui_buttons_row('stop.cgi', 'Stop server',
1507                       'Click this button to stop the server process');
1508  print ui_buttons_end();
1509
1510=cut
1511sub ui_buttons_start
1512{
1513return &theme_ui_buttons_start(@_) if (defined(&theme_ui_buttons_start));
1514return "<table width='100%' class='ui_buttons_table'>\n";
1515}
1516
1517=head2 ui_buttons_end
1518
1519Returns HTML for the end of a block started by ui_buttons_start.
1520
1521=cut
1522sub ui_buttons_end
1523{
1524return &theme_ui_buttons_end(@_) if (defined(&theme_ui_buttons_end));
1525return "</table>\n";
1526}
1527
1528=head2 ui_buttons_row(script, button-label, description, [hiddens], [after-submit], [before-submit])
1529
1530Returns HTML for a button with a description next to it, and perhaps other
1531inputs. The parameters are :
1532
1533=item script - CGI script that this button submits to, like start.cgi.
1534
1535=item button-label - Text to appear on the button.
1536
1537=item description - Text to appear next to the button, describing in more detail what it does.
1538
1539=item hiddens - HTML for hidden fields to include in the form this function generates.
1540
1541=item after-submit - HTML for text or inputs to appear after the submit button.
1542
1543=item before-submit - HTML for text or inputs to appear before the submit button.
1544
1545=cut
1546sub ui_buttons_row
1547{
1548return &theme_ui_buttons_row(@_) if (defined(&theme_ui_buttons_row));
1549my ($script, $label, $desc, $hiddens, $after, $before) = @_;
1550if (ref($hiddens)) {
1551	$hiddens = join("\n", map { &ui_hidden(@$_) } @$hiddens);
1552	}
1553return "<form action='$script' class='ui_buttons_form' method='post'>\n".
1554       $hiddens.
1555       "<tr class='ui_buttons_row'> ".
1556       "<td nowrap width='20%' valign='top' class='ui_buttons_label'>".
1557       ($before ? $before." " : "").
1558       &ui_submit($label).($after ? " ".$after : "")."</td>\n".
1559       "<td width='80%' valign='top' class='ui_buttons_value'>".
1560       $desc."</td></tr>\n".
1561       "</form>\n";
1562}
1563
1564=head2 ui_buttons_hr([title])
1565
1566Returns HTML for a separator row, for use inside a ui_buttons_start block.
1567
1568=cut
1569sub ui_buttons_hr
1570{
1571my ($title) = @_;
1572return &theme_ui_buttons_hr(@_) if (defined(&theme_ui_buttons_hr));
1573if ($title) {
1574	return "<tr class='ui_buttons_hr'><td colspan='2'><table cellpadding='0' cellspacing='0' width='100%'><tr><td width='50%'><hr></td><td nowrap>$title</td><td width='50%'><hr></td></tr></table></td></tr>\n";
1575	}
1576else {
1577	return "<tr class='ui_buttons_hr'><td colspan='2'><hr></td></tr>\n";
1578	}
1579}
1580
1581####################### header and footer functions
1582
1583=head2 ui_post_header([subtext])
1584
1585Returns HTML to appear directly after a standard header() call. This is never
1586called directly - instead, ui_print_header calls it. But it can be overridden
1587by themes.
1588
1589=cut
1590sub ui_post_header
1591{
1592return &theme_ui_post_header(@_) if (defined(&theme_ui_post_header));
1593my ($text) = @_;
1594my $rv;
1595$rv .= "<center class='ui_post_header'><font size='+1'>$text</font></center>\n" if (defined($text));
1596if (!$tconfig{'nohr'} && !$tconfig{'notophr'}) {
1597	$rv .= "<hr id='post_header_hr'>\n";
1598	}
1599return $rv;
1600}
1601
1602=head2 ui_pre_footer
1603
1604Returns HTML to appear directly before a standard footer() call. This is never
1605called directly - instead, ui_print_footer calls it. But it can be overridden
1606by themes.
1607
1608=cut
1609sub ui_pre_footer
1610{
1611return &theme_ui_pre_footer(@_) if (defined(&theme_ui_pre_footer));
1612my $rv;
1613if (!$tconfig{'nohr'} && !$tconfig{'nobottomhr'}) {
1614	$rv .= "<hr id='pre_footer_hr'>\n";
1615	}
1616return $rv;
1617}
1618
1619=head2 ui_print_header(subtext, image, [help], [config], [nomodule], [nowebmin], [rightside], [head-stuff], [body-stuff], [below])
1620
1621Print HTML for a header with the post-header line. The args are the same
1622as those passed to header(), defined in web-lib-funcs.pl, with the addition
1623of the subtext parameter :
1624
1625=item subtext - Text to display below the title
1626
1627=item title - The text to show at the top of the page
1628
1629=item image - An image to show instead of the title text. This is typically left blank.
1630
1631=item help - If set, this is the name of a help page that will be linked to in the title.
1632
1633=item config - If set to 1, the title will contain a link to the module's config page.
1634
1635=item nomodule - If set to 1, there will be no link in the title section to the module's index.
1636
1637=item nowebmin - If set to 1, there will be no link in the title section to the Webmin index.
1638
1639=item rightside - HTML to be shown on the right-hand side of the title. Can contain multiple lines, separated by <br>. Typically this is used for links to stop, start or restart servers.
1640
1641=item head-stuff - HTML to be included in the <head> section of the page.
1642
1643=item body-stuff - HTML attributes to be include in the <body> tag.
1644
1645=item below - HTML to be displayed below the title. Typically this is used for application or server version information.
1646
1647
1648
1649=cut
1650sub ui_print_header
1651{
1652&load_theme_library();
1653return &theme_ui_print_header(@_) if (defined(&theme_ui_print_header));
1654my ($text, @args) = @_;
1655&header(@args);
1656print &ui_post_header($text);
1657}
1658
1659=head2 ui_print_unbuffered_header(subtext, args...)
1660
1661Like ui_print_header, but ensures that output for this page is not buffered
1662or contained in a table. This should be called by scripts that are producing
1663output while performing some long-running process.
1664
1665=cut
1666sub ui_print_unbuffered_header
1667{
1668my @args = @_;
1669&load_theme_library();
1670return &theme_ui_print_unbuffered_header(@args)
1671	if (defined(&theme_ui_print_unbuffered_header));
1672$| = 1;
1673$theme_no_table = 1;
1674$args[9] .= " " if ($args[9]);
1675$args[9] .= " data-pagescroll=true";
1676&ui_print_header(@args);
1677}
1678
1679=head2 ui_print_footer(args...)
1680
1681Print HTML for a footer with the pre-footer line. Args are the same as those
1682passed to footer().
1683
1684=cut
1685sub ui_print_footer
1686{
1687return &theme_ui_print_footer(@_) if (defined(&theme_ui_print_footer));
1688my @args = @_;
1689print &ui_pre_footer();
1690&footer(@args);
1691}
1692
1693=head2 ui_config_link(text, &subs)
1694
1695Returns HTML for a module config link. The first non-null sub will be
1696replaced with the appropriate URL for the module's config page.
1697
1698=cut
1699sub ui_config_link
1700{
1701return &theme_ui_config_link(@_) if (defined(&theme_ui_config_link));
1702my ($text, $subs) = @_;
1703my $m = &get_module_name();
1704my @subs = map { $_ || "../config.cgi?$m" }
1705		  ($subs ? @$subs : ( undef ));
1706return "<p>".&text($text, @subs)."<p>\n";
1707}
1708
1709=head2 ui_print_endpage(text)
1710
1711Prints HTML for an error message followed by a page footer with a link to
1712/, then exits. Good for main page error messages.
1713
1714=cut
1715sub ui_print_endpage
1716{
1717return &theme_ui_print_endpage(@_) if (defined(&theme_ui_print_endpage));
1718my ($text) = @_;
1719print $text,"<p class='ui_footer'>\n";
1720print "</p>\n";
1721&ui_print_footer("/", $text{'index'});
1722exit;
1723}
1724
1725=head2 ui_subheading(text, ...)
1726
1727Returns HTML for a section heading whose message is the given text strings.
1728
1729=cut
1730sub ui_subheading
1731{
1732return &theme_ui_subheading(@_) if (defined(&theme_ui_subheading));
1733return "<h3 class='ui_subheading'>".join("", @_)."</h3>\n";
1734}
1735
1736=head2 ui_links_row(&links)
1737
1738Returns HTML for a row of links, like select all / invert selection / add..
1739Each element of the links array ref should be an HTML fragment like :
1740
1741  <a href='user_form.cgi'>Create new user</a>
1742
1743=cut
1744sub ui_links_row
1745{
1746return &theme_ui_links_row(@_) if (defined(&theme_ui_links_row));
1747my ($links) = @_;
1748return @$links ? join("\n|\n", @$links)."<br>\n"
1749	       : "";
1750}
1751
1752########################### collapsible section / tab functions
1753
1754=head2 ui_hidden_javascript
1755
1756Returns <script> and <style> sections for hiding functions and CSS. For
1757internal use only.
1758
1759=cut
1760sub ui_hidden_javascript
1761{
1762return &theme_ui_hidden_javascript(@_)
1763	if (defined(&theme_ui_hidden_javascript));
1764my $rv;
1765my $imgdir = "$gconfig{'webprefix'}/images";
1766my ($jscb, $jstb) = ($cb, $tb);
1767$jscb =~ s/'/\\'/g;
1768$jstb =~ s/'/\\'/g;
1769
1770return <<EOF;
1771<style type='text/css'>
1772.opener_shown {display:inline}
1773.opener_hidden {display:none}
1774</style>
1775<script type='text/javascript'>
1776// Open or close a hidden section
1777function hidden_opener(divid, openerid)
1778{
1779var divobj = document.getElementById(divid);
1780var openerobj = document.getElementById(openerid);
1781if (divobj.className == 'opener_shown') {
1782  divobj.className = 'opener_hidden';
1783  openerobj.innerHTML = '<img border=0 src=$imgdir/closed.gif>';
1784  }
1785else {
1786  divobj.className = 'opener_shown';
1787  openerobj.innerHTML = '<img border=0 src=$imgdir/open.gif>';
1788  }
1789}
1790
1791// Show a tab
1792function select_tab(name, tabname, form)
1793{
1794var tabnames = document[name+'_tabnames'];
1795var tabtitles = document[name+'_tabtitles'];
1796for(var i=0; i<tabnames.length; i++) {
1797  var tabobj = document.getElementById('tab_'+tabnames[i]);
1798  var divobj = document.getElementById('div_'+tabnames[i]);
1799  var title = tabtitles[i];
1800  if (tabnames[i] == tabname) {
1801    // Selected table
1802    tabobj.innerHTML = '<table cellpadding="0" cellspacing="0"><tr>'+
1803		       '<td valign=top $jscb>'+
1804		       '<img src=$imgdir/lc2.gif alt=""></td>'+
1805		       '<td $jscb nowrap>'+
1806		       '&nbsp;<b>'+title+'</b>&nbsp;</td>'+
1807	               '<td valign=top $jscb>'+
1808		       '<img src=$imgdir/rc2.gif alt=""></td>'+
1809		       '</tr></table>';
1810    divobj.className = 'opener_shown';
1811    }
1812  else {
1813    // Non-selected tab
1814    tabobj.innerHTML = '<table cellpadding="0" cellspacing="0"><tr>'+
1815		       '<td valign="top" $jstb>'+
1816		       '<img src="$imgdir/lc1.gif" alt=""></td>'+
1817		       '<td $jstb nowrap>'+
1818                       '&nbsp;<a href=\\'\\' onClick=\\'return select_tab("'+
1819		       name+'", "'+tabnames[i]+'")\\'>'+title+'</a>&nbsp;</td>'+
1820		       '<td valign="top" $jstb>'+
1821    		       '<img src="$imgdir/rc1.gif" alt=""></td>'+
1822		       '</tr></table>';
1823    divobj.className = 'opener_hidden';
1824    }
1825  }
1826if (document.forms[0] && document.forms[0][name]) {
1827  document.forms[0][name].value = tabname;
1828  }
1829return false;
1830}
1831</script>
1832EOF
1833}
1834
1835=head2 ui_hidden_start(title, name, status, thisurl)
1836
1837Returns HTML for the start of a collapsible hidden section, such as for
1838advanced options. When clicked on, the section header will expand to display
1839whatever is between this function and ui_hidden_end. The parameters are :
1840
1841=item title - Text for the start of this hidden section.
1842
1843=item name - A unique name for this section.
1844
1845=item status - 1 if it should be initially open, 0 if not.
1846
1847=item thisurl - URL of the current page. This is used by themes on devices that don't support Javascript to implement the opening and closing.
1848
1849=cut
1850sub ui_hidden_start
1851{
1852return &theme_ui_hidden_start(@_) if (defined(&theme_ui_hidden_start));
1853my ($title, $name, $status, $url) = @_;
1854my $rv;
1855if (!$main::ui_hidden_start_donejs++) {
1856	$rv .= &ui_hidden_javascript();
1857	}
1858my $divid = "hiddendiv_$name";
1859my $openerid = "hiddenopener_$name";
1860my $defimg = $status ? "open.gif" : "closed.gif";
1861my $defclass = $status ? 'opener_shown' : 'opener_hidden';
1862$rv .= "<a href=\"javascript:hidden_opener('$divid', '$openerid')\" id='$openerid'><img border=0 src='$gconfig{'webprefix'}/images/$defimg' alt='*'></a>\n";
1863$rv .= "<a href=\"javascript:hidden_opener('$divid', '$openerid')\">$title</a><br>\n";
1864$rv .= "<div class='$defclass' id='$divid'>\n";
1865return $rv;
1866}
1867
1868=head2 ui_hidden_end(name)
1869
1870Returns HTML for the end of a hidden section, started by ui_hidden_start.
1871
1872=cut
1873sub ui_hidden_end
1874{
1875return &theme_ui_hidden_end(@_) if (defined(&theme_ui_hidden_end));
1876my ($name) = @_;
1877return "</div>\n";
1878}
1879
1880=head2 ui_hidden_table_row_start(title, name, status, thisurl)
1881
1882Similar to ui_hidden_start, but for use within a table started with
1883ui_table_start. I recommend against using this where possible, as it can
1884be difficult for some themes to implement.
1885
1886=cut
1887sub ui_hidden_table_row_start
1888{
1889return &theme_ui_hidden_table_row_start(@_)
1890	if (defined(&theme_ui_hidden_table_row_start));
1891my ($title, $name, $status, $url) = @_;
1892my ($rv, $rrv);
1893if (!$main::ui_hidden_start_donejs++) {
1894	$rv .= &ui_hidden_javascript();
1895	}
1896my $divid = "hiddendiv_$name";
1897my $openerid = "hiddenopener_$name";
1898my $defimg = $status ? "open.gif" : "closed.gif";
1899my $defclass = $status ? 'opener_shown' : 'opener_hidden';
1900if ($title) {
1901	$rrv .= "<a href=\"javascript:hidden_opener('$divid', '$openerid')\" id='$openerid'><img border=0 src='$gconfig{'webprefix'}/images/$defimg'></a>\n";
1902	$rrv .= "<a href=\"javascript:hidden_opener('$divid', '$openerid')\">$title</a><br>\n";
1903	$rv .= &ui_table_row(undef, $rrv, $main::ui_table_cols);
1904	}
1905$rv .= "</table>\n";
1906$rv .= "<div class='$defclass' id='$divid'>\n";
1907$rv .= "<table width='100%'>\n";
1908return $rv;
1909}
1910
1911=head2 ui_hidden_table_row_end(name)
1912
1913Returns HTML to end a block started by ui_hidden_table_start.
1914
1915=cut
1916sub ui_hidden_table_row_end
1917{
1918return &theme_ui_hidden_table_row_end(@_)
1919	if (defined(&theme_ui_hidden_table_row_end));
1920my ($name) = @_;
1921return "</table></div><table width='100%'>\n";
1922}
1923
1924=head2 ui_hidden_table_start(heading, [tabletags], [cols], name, status, [&default-tds], [rightheading])
1925
1926Returns HTML for the start of a form block into which labelled inputs can
1927be placed, which is collapsible by clicking on the header. Basically the same
1928as ui_table_start, and must contain HTML generated by ui_table_row.
1929
1930The parameters are :
1931
1932=item heading - Text to show at the top of the form.
1933
1934=item tabletags - HTML attributes to put in the outer <table>, typically something like width=100%.
1935
1936=item cols - Desired number of columns for labels and fields. Defaults to 4, but can be 2 for forms with lots of wide inputs.
1937
1938=item name - A unique name for this table.
1939
1940=item status - Set to 1 if initially open, 0 if initially closed.
1941
1942=item default-tds - An optional array reference of HTML attributes for the <td> tags in each row of the table.
1943
1944=item right-heading - HTML to appear in the heading, aligned to the right.
1945
1946=cut
1947sub ui_hidden_table_start
1948{
1949return &theme_ui_hidden_table_start(@_)
1950	if (defined(&theme_ui_hidden_table_start));
1951my ($heading, $tabletags, $cols, $name, $status, $tds, $rightheading) = @_;
1952my $rv;
1953if (!$main::ui_hidden_start_donejs++) {
1954	$rv .= &ui_hidden_javascript();
1955	}
1956my $divid = "hiddendiv_$name";
1957my $openerid = "hiddenopener_$name";
1958my $defimg = $status ? "open.gif" : "closed.gif";
1959my $defclass = $status ? 'opener_shown' : 'opener_hidden';
1960my $text = defined($tconfig{'cs_text'}) ? $tconfig{'cs_text'} :
1961	      defined($gconfig{'cs_text'}) ? $gconfig{'cs_text'} : "000000";
1962$rv .= "<table class='ui_table' border $tabletags>\n";
1963my $colspan = 1;
1964if (defined($heading) || defined($rightheading)) {
1965	$rv .= "<tr".($tb ? " ".$tb : "")."><td>";
1966	if (defined($heading)) {
1967		$rv .= "<a href=\"javascript:hidden_opener('$divid', '$openerid')\" id='$openerid'><img border=0 src='$gconfig{'webprefix'}/images/$defimg'></a> <a href=\"javascript:hidden_opener('$divid', '$openerid')\"><b><font color='#$text'>$heading</font></b></a></td>";
1968		}
1969	if (defined($rightheading)) {
1970                $rv .= "<td align='right'>$rightheading</td>";
1971                $colspan++;
1972                }
1973	$rv .= "</td> </tr>\n";
1974	}
1975$rv .= "<tr".($cb ? " ".$cb : "")."><td colspan='$colspan'><div class='$defclass' id='$divid'><table width='100%'>\n";
1976$main::ui_table_cols = $cols || 4;
1977$main::ui_table_pos = 0;
1978$main::ui_table_default_tds = $tds;
1979return $rv;
1980}
1981
1982=head2 ui_hidden_table_end(name)
1983
1984Returns HTML for the end of a form block with hiding, as started by
1985ui_hidden_table_start.
1986
1987=cut
1988sub ui_hidden_table_end
1989{
1990my ($name) = @_;
1991return &theme_ui_hidden_table_end(@_) if (defined(&theme_ui_hidden_table_end));
1992return "</table></div></td></tr></table>\n";
1993}
1994
1995=head2 ui_tabs_start(&tabs, name, selected, show-border)
1996
1997Returns a row of tabs from which one can be selected, displaying HTML
1998associated with that tab. The parameters are :
1999
2000=item tabs - An array reference of array refs, each of which contains the value and user-visible text for a tab.
2001
2002=item name - Name of the HTML field into which the selected tab will be placed.
2003
2004=item selected - Value for the tab selected by default.
2005
2006=item show-border - Set to 1 if there should be a border around the contents of the tabs.
2007
2008Example code :
2009
2010  @tabs = ( [ 'list', 'List services' ],
2011            [ 'install', 'Install new service' ] );
2012  print ui_tabs_start(\@tabs, 'mode', 'list');
2013
2014  print ui_tabs_start_tab('mode', 'list');
2015  generate_service_list();
2016  print ui_tabs_end_tab('mode', 'list');
2017
2018  print ui_tabs_start_tab('mode', 'install');
2019  generate_install_form();
2020  print ui_tabs_end_tab('mode', 'install);
2021
2022  print ui_tabs_end();
2023
2024=cut
2025sub ui_tabs_start
2026{
2027return &theme_ui_tabs_start(@_) if (defined(&theme_ui_tabs_start));
2028my ($tabs, $name, $sel, $border) = @_;
2029my $rv;
2030if (!$main::ui_hidden_start_donejs++) {
2031	$rv .= &ui_hidden_javascript();
2032	}
2033
2034# Build list of tab titles and names
2035my $tabnames = "[".join(",", map { "\"".&quote_escape($_->[0])."\"" } @$tabs)."]";
2036my $tabtitles = "[".join(",", map { "\"".&quote_escape($_->[1])."\"" } @$tabs)."]";
2037$rv .= "<script type='text/javascript'>\n";
2038$rv .= "document.${name}_tabnames = $tabnames;\n";
2039$rv .= "document.${name}_tabtitles = $tabtitles;\n";
2040$rv .= "</script>\n";
2041
2042# Output the tabs
2043my $imgdir = "$gconfig{'webprefix'}/images";
2044$rv .= &ui_hidden($name, $sel)."\n";
2045$rv .= "<table border='0' cellpadding='0' cellspacing='0' class='ui_tabs'>\n";
2046$rv .= "<tr><td bgcolor='#ffffff' colspan='".(scalar(@$tabs)*2+1)."'>";
2047if ($ENV{'HTTP_USER_AGENT'} !~ /msie/i) {
2048	# For some reason, the 1-pixel space above the tabs appears huge on IE!
2049	$rv .= "<img src='$imgdir/1x1.gif'>";
2050	}
2051$rv .= "</td></tr>\n";
2052$rv .= "<tr>\n";
2053$rv .= "<td bgcolor='#ffffff' width='1'><img src='$imgdir/1x1.gif'></td>\n";
2054foreach my $t (@$tabs) {
2055	if ($t ne $$tabs[0]) {
2056		# Spacer
2057		$rv .= "<td width='2' bgcolor='#ffffff' class='ui_tab_spacer'>".
2058		       "<img src='$imgdir/1x1.gif'></td>\n";
2059		}
2060	my $tabid = "tab_".$t->[0];
2061	$rv .= "<td id='${tabid}' class='ui_tab'>";
2062	$rv .= "<table cellpadding='0' cellspacing='0' border='0'><tr>";
2063	if ($t->[0] eq $sel) {
2064		# Selected tab
2065		$rv .= "<td valign='top'".($cb ? " ".$cb : "")." class='selectedTabLeft'>".
2066		       "<img src='$imgdir/lc2.gif' alt=\"\"></td>";
2067		$rv .= "<td".($cb ? " ".$cb : "")." nowrap class='selectedTabMiddle'>".
2068		       "&nbsp;<b>$t->[1]</b>&nbsp;</td>";
2069		$rv .= "<td valign=top".($cb ? " ".$cb : "")." class='selectedTabRight'>".
2070		       "<img src=$imgdir/rc2.gif alt=\"\"></td>";
2071		}
2072	else {
2073		# Other tab (which has a link)
2074		$rv .= "<td valign='top'".($tb ? " ".$tb : "").">".
2075		       "<img src='$imgdir/lc1.gif' alt=\"\"></td>";
2076		$rv .= "<td".($tb ? " ".$tb : "")." nowrap>".
2077		       "&nbsp;<a href='$t->[2]' ".
2078		       "onClick='return select_tab(\"$name\", \"$t->[0]\")'>".
2079		       "$t->[1]</a>&nbsp;</td>";
2080		$rv .= "<td valign='top'".($tb ? " ".$tb : "")." >".
2081		       "<img src='$imgdir/rc1.gif' ".
2082		       "alt=\"\"></td>";
2083		$rv .= "</td>\n";
2084		}
2085	$rv .= "</tr></table>";
2086	$rv .= "</td>\n";
2087	}
2088$rv .= "<td bgcolor='#ffffff' width='1'><img src='$imgdir/1x1.gif'></td>\n";
2089$rv .= "</table>\n";
2090
2091if ($border) {
2092	# All tabs are within a grey box
2093	$rv .= "<table width='100%' cellpadding='0' cellspacing='0' border='0' ".
2094	       "class='ui_tabs_box'>\n";
2095	$rv .= "<tr> <td bgcolor='#ffffff' rowspan='3' width='1'><img src='$imgdir/1x1.gif'></td>\n";
2096	$rv .= "<td".($cb ? " ".$cb : "")." colspan='3' height='2'><img src='$imgdir/1x1.gif'></td> </tr>\n";
2097	$rv .= "<tr> <td".($cb ? " ".$cb : "")." width='2'><img src='$imgdir/1x1.gif'></td>\n";
2098	$rv .= "<td valign='top'>";
2099	}
2100$main::ui_tabs_selected = $sel;
2101return $rv;
2102}
2103
2104=head2 ui_tabs_end(show-border)
2105
2106Returns HTML to end a block started by ui_tabs_start. The show-border parameter
2107must match the parameter with the same name in the start function.
2108
2109=cut
2110sub ui_tabs_end
2111{
2112return &theme_ui_tabs_end(@_) if (defined(&theme_ui_tabs_end));
2113my ($border) = @_;
2114my $rv;
2115my $imgdir = "$gconfig{'webprefix'}/images";
2116if ($border) {
2117	$rv .= "</td>\n";
2118	$rv .= "<td".($cb ? " ".$cb : "")." width='2'><img src='$imgdir/1x1.gif'></td>\n";
2119	$rv .= "</tr>\n";
2120	$rv .= "<tr> <td".($cb ? " ".$cb : "")." colspan='3' height='2'><img src='$imgdir/1x1.gif'></td> </tr>\n";
2121	$rv .= "</table>\n";
2122	}
2123return $rv;
2124}
2125
2126=head2 ui_tabs_start_tab(name, tab)
2127
2128Must be called before outputting the HTML for the named tab, and returns HTML
2129for the required <div> block.
2130
2131=cut
2132sub ui_tabs_start_tab
2133{
2134return &theme_ui_tabs_start_tab(@_) if (defined(&theme_ui_tabs_start_tab));
2135my ($name, $tab) = @_;
2136my $defclass = $tab eq $main::ui_tabs_selected ?
2137			'opener_shown' : 'opener_hidden';
2138my $rv = "<div id='div_$tab' class='$defclass ui_tabs_start'>\n";
2139return $rv;
2140}
2141
2142=head2 ui_tabs_start_tabletab(name, tab)
2143
2144Behaves like ui_tabs_start_tab, but for use within a ui_table_start block.
2145I recommend against using this where possible, as it is difficult for themes
2146to implement.
2147
2148=cut
2149sub ui_tabs_start_tabletab
2150{
2151return &theme_ui_tabs_start_tabletab(@_)
2152	if (defined(&theme_ui_tabs_start_tabletab));
2153my $div = &ui_tabs_start_tab(@_);
2154return "</table>\n".$div."<table width='100%'>\n";
2155}
2156
2157=head2 ui_tabs_end_tab
2158
2159Returns HTML for the end of a block started by ui_tabs_start_tab.
2160
2161=cut
2162sub ui_tabs_end_tab
2163{
2164return &theme_ui_tabs_end_tab(@_) if (defined(&theme_ui_tabs_end_tab));
2165return "</div>\n";
2166}
2167
2168=head2 ui_tabs_end_tabletab
2169
2170Returns HTML for the end of a block started by ui_tabs_start_tabletab.
2171
2172=cut
2173sub ui_tabs_end_tabletab
2174{
2175return &theme_ui_tabs_end_tabletab(@_)
2176	if (defined(&theme_ui_tabs_end_tabletab));
2177return "</table></div><table width='100%'>\n";
2178}
2179
2180=head2 ui_max_text_width(width, [text-area?])
2181
2182Returns a new width for a text field, based on theme settings. For internal
2183use only.
2184
2185=cut
2186sub ui_max_text_width
2187{
2188my ($w, $ta) = @_;
2189my $max = $ta ? $tconfig{'maxareawidth'} : $tconfig{'maxboxwidth'};
2190return $max && $w > $max ? $max : $w;
2191}
2192
2193####################### radio hidden functions
2194
2195=head2 ui_radio_selector(&opts, name, selected, [dropdown-mode])
2196
2197Returns HTML for a set of radio buttons, each of which shows a different
2198block of HTML when selected. The parameters are :
2199
2200=item opts - An array ref to arrays containing [ value, label, html ]
2201
2202=item name - HTML name for the radio buttons
2203
2204=item selected - Value for the initially selected button.
2205
2206=item dropdown - Use a <select> dropdown menu instead of radio buttons
2207
2208=cut
2209sub ui_radio_selector
2210{
2211return &theme_ui_radio_selector(@_) if (defined(&theme_ui_radio_selector));
2212my ($opts, $name, $sel, $dropdown) = @_;
2213my $rv;
2214if (!$main::ui_radio_selector_donejs++) {
2215	$rv .= &ui_radio_selector_javascript();
2216	}
2217my $optnames =
2218	"[".join(",", map { "\"".&html_escape($_->[0])."\"" } @$opts)."]";
2219if ($dropdown) {
2220	$rv .= &ui_select($name, $sel,
2221		[ map { [ $_->[0], $_->[1] ] } @$opts ],
2222		1, 0, 0, 0,
2223		"onChange='selector_show(\"$name\", $name.value, $optnames)'");
2224	}
2225else {
2226	foreach my $o (@$opts) {
2227		$rv .= &ui_oneradio($name, $o->[0], $o->[1], $sel eq $o->[0],
2228		    "onClick='selector_show(\"$name\", \"$o->[0]\", $optnames)'");
2229		}
2230	}
2231$rv .= "<br>\n";
2232foreach my $o (@$opts) {
2233	my $cls = $o->[0] eq $sel ? "selector_shown" : "selector_hidden";
2234	$rv .= "<div id='sel_${name}_$o->[0]' class='$cls'>".$o->[2]."</div>\n";
2235	}
2236return $rv;
2237}
2238
2239sub ui_radio_selector_javascript
2240{
2241return <<EOF;
2242<style type='text/css'>
2243.selector_shown {display:inline}
2244.selector_hidden {display:none}
2245</style>
2246<script type='text/javascript'>
2247function selector_show(name, value, values)
2248{
2249for(var i=0; i<values.length; i++) {
2250	var divobj = document.getElementById('sel_'+name+'_'+values[i]);
2251	divobj.className = value == values[i] ? 'selector_shown'
2252					      : 'selector_hidden';
2253	}
2254}
2255</script>
2256EOF
2257}
2258
2259####################### grid layout functions
2260
2261=head2 ui_grid_table(&elements, columns, [width-percent], [&tds], [tabletags], [title])
2262
2263Given a list of HTML elements, formats them into a table with the given
2264number of columns. However, themes are free to override this to use fewer
2265columns where space is limited. Parameters are :
2266
2267=item elements - An array reference of table elements, each of which can be any HTML you like.
2268
2269=item columns - Desired number of columns in the grid.
2270
2271=item width-percent - Optional desired width as a percentage.
2272
2273=item tds - Array ref of HTML attributes for <td> tags in the tables.
2274
2275=item tabletags - HTML attributes for the <table> tag.
2276
2277=item title - Optional title to add to the top of the grid.
2278
2279=cut
2280sub ui_grid_table
2281{
2282return &theme_ui_grid_table(@_) if (defined(&theme_ui_grid_table));
2283my ($elements, $cols, $width, $tds, $tabletags, $title) = @_;
2284return "" if (!@$elements);
2285my $rv = "<table class='ui_grid_table'".
2286	    ($width ? " width='$width%'" : "").
2287	    ($tabletags ? " ".$tabletags : "").
2288	    ">\n";
2289my $i;
2290for($i=0; $i<@$elements; $i++) {
2291	$rv .= "<tr class='ui_grid_row'>" if ($i%$cols == 0);
2292	$rv .= "<td ".$tds->[$i%$cols]." valign='top' class='ui_grid_cell'>".
2293	       $elements->[$i]."</td>\n";
2294	$rv .= "</tr>" if ($i%$cols == $cols-1);
2295	}
2296if ($i%$cols) {
2297	while($i%$cols) {
2298		$rv .= "<td ".$tds->[$i%$cols]." class='ui_grid_cell'>".
2299		       "<br></td>\n";
2300		$i++;
2301		}
2302	$rv .= "</tr>\n";
2303	}
2304$rv .= "</table>\n";
2305if (defined($title)) {
2306	$rv = "<table class='ui_table border' ".
2307	      ($width ? " width=$width%" : "").">\n".
2308	      ($title ? "<tr".($tb ? " ".$tb : "")."><td><b>$title</b></td></tr>\n" : "").
2309              "<tr".($cb ? " ".$cb : "")."><td>$rv</td></tr>\n".
2310	      "</table>";
2311	}
2312return $rv;
2313}
2314
2315=head2 ui_radio_table(name, selected, &rows, [no-bold])
2316
2317Returns HTML for a table of radio buttons, each of which has a label and
2318some associated inputs to the right. The parameters are :
2319
2320=item name - Unique name for this table, which is also the radio buttons' name.
2321
2322=item selected - Value for the initially selected radio button.
2323
2324=item rows - Array ref of array refs, one per button. The elements of each are the value for this option, a label, and option additional HTML to appear next to it.
2325
2326=item no-bold - When set to 1, labels in the table will not be bolded
2327
2328=cut
2329sub ui_radio_table
2330{
2331return &theme_ui_radio_table(@_) if (defined(&theme_ui_radio_table));
2332my ($name, $sel, $rows, $nobold) = @_;
2333return "" if (!@$rows);
2334my $rv = "<table class='ui_radio_table'>\n";
2335foreach my $r (@$rows) {
2336	$rv .= "<tr>\n";
2337	$rv .= "<td valign='top'".(defined($r->[2]) ? "" : " colspan='2'").">".
2338	       ($nobold ? "" : "<b>").
2339	       &ui_oneradio($name, $r->[0], $r->[1], $r->[0] eq $sel, $r->[3]).
2340	       ($nobold ? "" : "</b>").
2341	       "</td>\n";
2342	if (defined($r->[2])) {
2343		$rv .= "<td valign='top'>".$r->[2]."</td>\n";
2344		}
2345	$rv .= "</tr>\n";
2346	}
2347$rv .= "</table>\n";
2348return $rv;
2349}
2350
2351=head2 ui_up_down_arrows(uplink, downlink, up-show, down-show)
2352
2353Returns HTML for moving some objects in a table up or down. The parameters are :
2354
2355=item uplink - URL for the up-arrow link.
2356
2357=item downlink - URL for the down-arrow link.
2358
2359=item up-show - Set to 1 if the up-arrow should be shown, 0 if not.
2360
2361=item down-show - Set to 1 if the down-arrow should be shown, 0 if not.
2362
2363=item up-icon - Optional path to icon for up link
2364
2365=item down-icon - Optional path to icon for down link
2366
2367=cut
2368sub ui_up_down_arrows
2369{
2370return &theme_ui_up_down_arrows(@_) if (defined(&theme_ui_up_down_arrows));
2371my ($uplink, $downlink, $upshow, $downshow, $upicon, $downicon) = @_;
2372my $mover;
2373my $imgdir = "$gconfig{'webprefix'}/images";
2374$upicon ||= "$imgdir/moveup.gif";
2375$downicon ||= "$imgdir/movedown.gif";
2376if ($downshow) {
2377	$mover .= "<a class='ui_up_down_arrows_down' href='$downlink'>".
2378	  "<img class='ui_up_down_arrows_down' src='$downicon' border='0'></a>";
2379	}
2380else {
2381	$mover .= "<img class='ui_up_down_arrows_gap' src='$imgdir/movegap.gif'>";
2382	}
2383if ($upshow) {
2384	$mover .= "<a class='ui_up_down_arrows_up' href='$uplink'>".
2385	  "<img class='ui_up_down_arrows_up' src='$upicon' border='0'></a>";
2386	}
2387else {
2388	$mover .= "<img class='ui_up_down_arrows_gap' src='$imgdir/movegap.gif'>";
2389	}
2390return $mover;
2391}
2392
2393=head2 ui_hr
2394
2395Returns a horizontal row tag, typically just an <hr>
2396
2397=item tags - Additional HTML attributes for the <hr> tag.
2398
2399=cut
2400sub ui_hr
2401{
2402return &theme_ui_hr(@_) if (defined(&theme_ui_hr));
2403my ($tags) = @_;
2404return "<hr class='ui_hr'".($tags ? " ".$tags : "").">\n";
2405}
2406
2407=head2 ui_nav_link(direction, url, disabled)
2408
2409Returns an arrow icon linking to the provided url.
2410
2411=cut
2412sub ui_nav_link
2413{
2414return &theme_ui_nav_link(@_) if (defined(&theme_ui_nav_link));
2415my ($direction, $url, $disabled) = @_;
2416my $alt = $direction eq "left" ? '<-' : '->';
2417if ($disabled) {
2418	return "<img class='ui_nav_link' alt=\"$alt\" align=\"middle\""
2419	     . "src=\"$gconfig{'webprefix'}/images/$direction-grey.gif\">\n";
2420	}
2421else {
2422	return "<a class='ui_nav_link' href=\"$url\"><img class='ui_nav_link' alt=\"$alt\" align=\"middle\""
2423	     . "src=\"$gconfig{'webprefix'}/images/$direction.gif\"></a>\n";
2424	}
2425}
2426
2427=head2 ui_confirmation_form(cgi, message, &hiddens, [&buttons], [otherinputs], [extra-warning])
2428
2429Returns HTML for a form asking for confirmation before performing some
2430action, such as deleting a user. The parameters are :
2431
2432=item cgi - Script to which the confirmation form submits, like delete.cgi.
2433
2434=item message - Warning message for the user to see.
2435
2436=item hiddens - Array ref of two-element array refs, containing hidden form field names and values.
2437
2438=item buttons - Array ref of two-element array refs, containing form button names and labels.
2439
2440=item otheirinputs - HTML for extra inputs to include in their form.
2441
2442=item extra-warning - An additional separate warning message to show.
2443
2444=cut
2445sub ui_confirmation_form
2446{
2447my ($cgi, $message, $hiddens, $buttons, $others, $warning) = @_;
2448my $rv;
2449$rv .= "<center class='ui_confirmation'>\n";
2450$rv .= &ui_form_start($cgi, "post");
2451foreach my $h (@$hiddens) {
2452	$rv .= &ui_hidden(@$h);
2453	}
2454$rv .= "<b>$message</b><p>\n";
2455if ($warning) {
2456	$rv .= "<b><font color='#ff0000'>$warning</font></b><p>\n";
2457	}
2458if ($others) {
2459	$rv .= $others."<p>\n";
2460	}
2461$rv .= &ui_form_end($buttons);
2462$rv .= "</center>\n";
2463return $rv;
2464}
2465
2466=head2 ui_text_color(text, type)
2467
2468Returns HTML for a text string, with its color determined by $type.
2469
2470=item text - contains any text string
2471
2472=item type - returned text color
2473
2474=cut
2475
2476sub ui_text_color
2477{
2478my ($text, $type) = @_;
2479my ($color);
2480
2481if (defined (&theme_ui_text_color)) {
2482    return &theme_ui_text_color(@_);
2483    }
2484if ($type eq "success") { $color = "#3c763d"; }
2485elsif ($type eq "info") { $color = "#31708f"; }
2486elsif ($type eq "warn") { $color = "#8a6d3b"; }
2487elsif ($type eq "danger") { $color = "#a94442"; }
2488return "<span class=\"ui_text_color text_type_$type\" style=\"color: $color\">$text</span>\n";
2489}
2490
2491=head2 ui_alert_box(msg, type)
2492
2493Returns HTML for an alert box, with background color determined by $type.
2494
2495$msg contains any text or HTML to be contained within the alert box, and
2496can include forms.
2497
2498Type of alert:
2499
2500=item success - green
2501
2502=item info - blue
2503
2504=item warn - yellow
2505
2506=item danger - red
2507
2508=cut
2509
2510sub ui_alert_box
2511{
2512my ($msg, $type) = @_;
2513my ($rv, $color);
2514
2515if (defined (&theme_ui_alert_box)) {
2516    return &theme_ui_alert_box(@_);
2517    }
2518
2519if ($type eq "success") { $color = "DFF0D8"; }
2520elsif ($type eq "info") { $color = "D9EDF7"; }
2521elsif ($type eq "warn") { $color = "FCF8E3"; }
2522elsif ($type eq "danger") { $color = "F2DEDE"; }
2523
2524$rv .= "<table class='ui_alert_box' width='100%'><tr bgcolor='#$color'><td align='center'><p>\n";
2525$rv .= "$msg\n";
2526$rv .= "<p></td></tr></table><p>\n";
2527
2528return $rv;
2529}
2530
2531####################### javascript functions
2532
2533=head2 js_disable_inputs(&disable-inputs, &enable-inputs, [tag])
2534
2535Returns Javascript to disable some form elements and enable others. Mainly
2536for internal use.
2537
2538=cut
2539sub js_disable_inputs
2540{
2541my $rv;
2542my $f;
2543foreach $f (@{$_[0]}) {
2544	$rv .= "e = form.elements[\"$f\"]; e.disabled = true; ";
2545	$rv .= "for(i=0; i<e.length; i++) { e[i].disabled = true; } ";
2546	}
2547foreach $f (@{$_[1]}) {
2548	$rv .= "e = form.elements[\"$f\"]; e.disabled = false; ";
2549	$rv .= "for(i=0; i<e.length; i++) { e[i].disabled = false; } ";
2550	}
2551foreach $f (@{$_[1]}) {
2552	if ($f =~ /^(.*)_def$/ && &indexof($1, @{$_[1]}) >= 0) {
2553		# When enabling both a _def field and its associated text field,
2554		# disable the text if the _def is set to 1
2555		my $tf = $1;
2556		$rv .= "e = form.elements[\"$f\"]; for(i=0; i<e.length; i++) { if (e[i].checked && e[i].value == \"1\") { form.elements[\"$tf\"].disabled = true } } ";
2557		}
2558	}
2559return $_[2] ? "$_[2]='$rv'" : $rv;
2560}
2561
2562=head2 ui_page_flipper(message, [inputs, cgi], left-link, right-link, [far-left-link], [far-right-link], [below])
2563
2564Returns HTML for moving left and right in some large list, such as an inbox
2565or database table. If only 5 parameters are given, no far links are included.
2566If any link is undef, that array will be greyed out. The parameters are :
2567
2568=item message - Text or display between arrows.
2569
2570=item inputs - Additional HTML inputs to show after message.
2571
2572=item cgi - Optional CGI for form wrapping arrows to submit to.
2573
2574=item left-link - Link for left-facing arrow.
2575
2576=item right-link - Link for right-facing arrow.
2577
2578=item far-left-link - Link for far left-facing arrow, optional.
2579
2580=item far-right-link - Link for far right-facing arrow, optional.
2581
2582=item below - HTML to display below the arrows.
2583
2584=cut
2585sub ui_page_flipper
2586{
2587return &theme_ui_page_flipper(@_) if (defined(&theme_ui_page_flipper));
2588my ($msg, $inputs, $cgi, $left, $right, $farleft, $farright, $below) = @_;
2589my $rv = "<center class='ui_page_flipper'>";
2590$rv .= &ui_form_start($cgi) if ($cgi);
2591
2592# Far left link, if needed
2593if (@_ > 5) {
2594	if ($farleft) {
2595		$rv .= "<a href='$farleft'>".
2596		       "<img src='$gconfig{'webprefix'}/images/first.gif' ".
2597		       "border='0' align='middle'></a>\n";
2598		}
2599	else {
2600		$rv .= "<img src='$gconfig{'webprefix'}/images/first-grey.gif' ".
2601		       "border='0' align='middle'></a>\n";
2602		}
2603	}
2604
2605# Left link
2606if ($left) {
2607	$rv .= "<a href='$left'>".
2608	       "<img src=$gconfig{'webprefix'}/images/left.gif ".
2609	       "border='0' align='middle'></a>\n";
2610	}
2611else {
2612	$rv .= "<img src=$gconfig{'webprefix'}/images/left-grey.gif ".
2613	       "border='0' align='middle'></a>\n";
2614	}
2615
2616# Message and inputs
2617$rv .= $msg;
2618$rv .= " ".$inputs if ($inputs);
2619
2620# Right link
2621if ($right) {
2622	$rv .= "<a href='$right'>".
2623	       "<img src='$gconfig{'webprefix'}/images/right.gif' ".
2624	       "border='0' align='middle'></a>\n";
2625	}
2626else {
2627	$rv .= "<img src='$gconfig{'webprefix'}/images/right-grey.gif' ".
2628	       "border='0' align='middle'></a>\n";
2629	}
2630
2631# Far right link, if needed
2632if (@_ > 5) {
2633	if ($farright) {
2634		$rv .= "<a href='$farright'>".
2635		       "<img src='$gconfig{'webprefix'}/images/last.gif' ".
2636		       "border='0' align='middle'></a>\n";
2637		}
2638	else {
2639		$rv .= "<img src='$gconfig{'webprefix'}/images/last-grey.gif' ".
2640		       "border='0' align='middle'></a>\n";
2641		}
2642	}
2643
2644$rv .= "<br>".$below if ($below);
2645$rv .= &ui_form_end() if ($cgi);
2646$rv .= "</center>\n";
2647return $rv;
2648}
2649
2650=head2 js_checkbox_disable(name, &checked-disable, &checked-enable, [tag])
2651
2652For internal use only.
2653
2654=cut
2655sub js_checkbox_disable
2656{
2657my $rv;
2658my $f;
2659foreach $f (@{$_[1]}) {
2660	$rv .= "form.elements[\"$f\"].disabled = $_[0].checked; ";
2661	}
2662foreach $f (@{$_[2]}) {
2663	$rv .= "form.elements[\"$f\"].disabled = !$_[0].checked; ";
2664	}
2665return $_[3] ? "$_[3]='$rv'" : $rv;
2666}
2667
2668=head2 js_redirect(url, [window-object])
2669
2670Returns HTML to trigger a redirect to some URL.
2671
2672=cut
2673sub js_redirect
2674{
2675my ($url, $window) = @_;
2676if (defined(&theme_js_redirect)) {
2677	return &theme_js_redirect(@_);
2678	}
2679$window ||= "window";
2680if ($url =~ /^\//) {
2681	# If the URL is like /foo , add webprefix
2682	$url = $gconfig{'webprefix'}.$url;
2683	}
2684return "<script type='text/javascript'>${window}.location = '".&quote_escape($url)."';</script>\n";
2685}
2686
2687=head2 ui_webmin_link(module, page)
2688
2689Returns the URL for a link to this Webmin instance that can be used in an email
2690
2691=cut
2692sub ui_webmin_link
2693{
2694my ($mod, $page) = @_;
2695if (defined(&theme_ui_webmin_link)) {
2696	return &theme_ui_webmin_link(@_);
2697	}
2698my %miniserv;
2699&get_miniserv_config(\%miniserv);
2700my $proto = $miniserv{'ssl'} ? 'https' : 'http';
2701my $port = $miniserv{'port'};
2702my $host = $ENV{'HTTP_HOST'} || &get_display_hostname();
2703if ($host =~ /^([a-zA-Z0-9\-\_\.]+):(\d+)$/) {
2704	$host = $1;
2705	$port = $2;
2706	}
2707my $rv = $proto."://$host:$port";
2708if ($mod) {
2709	$rv .= "/$mod";
2710	}
2711if ($page) {
2712	$rv .= "/$page";
2713	}
2714return $rv;
2715}
2716
2717=head2 ui_line_break_double()
2718
2719Create double line break, with accessible second break
2720
2721=cut
2722sub ui_line_break_double
2723{
2724if (defined(&theme_ui_line_break_double)) {
2725	return &theme_ui_line_break_double(@_);
2726	}
2727return "<br><br data-x-br>\n";
2728}
2729
2730=head2 ui_details(Config, Opened)
2731
2732Creates a disclosure widget in which information is visible only when
2733the widget is toggled into an "open" state.
2734
2735=cut
2736sub ui_details
2737{
2738my ($c, $o) = @_;
2739if (defined(&theme_ui_details)) {
2740	return &theme_ui_details(@_);
2741	}
2742
2743my $rv;
2744if (!$c->{'html'}) {
2745	$c->{'title'} = &html_escape($c->{'title'});
2746	$c->{'content'} = &html_escape($c->{'content'});
2747	}
2748$c->{'class'} = " class=\"@{[&quote_escape($c->{'class'})]}\"" if($c->{'class'});
2749$o = ' open' if ($o);
2750$rv = "<details$c->{'class'}$o>";
2751$rv .= "<summary>$c->{'title'}</summary>";
2752$rv .= "<span>$c->{'content'}</span>";
2753$rv .= "</details>";
2754return $rv;
2755}
2756
2757=head2 ui_read_file_contents_limit(\%data)
2758
2759Reads file content with options and
2760returns head and/or tail separated with
2761chomped message
2762
2763=cut
2764sub ui_read_file_contents_limit
2765{
2766if (defined(&theme_ui_read_file_contents_limit)) {
2767	return &theme_ui_read_file_contents_limit(@_);
2768	}
2769my ($opts)  = @_;
2770my $binary  = -s $opts->{'file'} >= 128 && -B $opts->{'file'};
2771my $data = &read_file_contents_limit($opts->{'file'}, $opts->{'limit'}, $opts);
2772my $error = $data->{'error'};
2773if ($error) {
2774    return $error;
2775	}
2776my $nonulls = sub {
2777    $_[0] =~ s/[^[:print:]\n\r\t]/\ /g;
2778    return $_[0];
2779	};
2780my $head     = $data->{'head'};
2781my $tail     = $data->{'tail'};
2782my $chomped  = $data->{'chomped'};
2783my $fsize    = $data->{'size'};
2784my $flimit   = $data->{'limit'};
2785my $msg_type = !$head &&  $tail ? '_tail' :
2786                $head && !$tail ? '_head' : undef;
2787my $nlines   = $nslines = $nelines = "\n" x 10;
2788$nslines     = undef if (!$head);
2789$nelines     = undef if (!$tail);
2790my $chomped_msg;
2791$chomped_msg =
2792"${nslines}[--- @{[&text(\"file_truncated_message$msg_type\",
2793                         &nice_size($flimit),
2794                         &nice_size($chomped),
2795                         &nice_size($fsize))]} ---]$nelines"
2796  if ($chomped);
2797
2798# Trim nulls
2799$head = &$nonulls($head)
2800  if ($binary && $head);
2801$tail = &$nonulls($tail)
2802  if ($binary && $tail);
2803
2804# Return data
2805if ($head && $tail) {
2806    return $head . $chomped_msg . $tail;
2807	}
2808if ($tail) {
2809    return $chomped_msg . $tail;
2810	}
2811if ($head) {
2812    return $head . $chomped_msg;
2813	}
2814}
2815
28161;
2817
2818