Use new module
authorJamie Cameron <jcameron@webmin.com>
Thu, 5 Mar 2009 21:42:37 +0000 (21:42 +0000)
committerJamie Cameron <jcameron@webmin.com>
Thu, 5 Mar 2009 21:42:37 +0000 (21:42 +0000)
42 files changed:
Webmin/All.pm [new file with mode: 0644]
Webmin/Button.pm [new file with mode: 0644]
Webmin/Checkbox.pm [new file with mode: 0644]
Webmin/Checkboxes.pm [new file with mode: 0644]
Webmin/Columns.pm [new file with mode: 0644]
Webmin/ConfirmPage.pm [new file with mode: 0644]
Webmin/Date.pm [new file with mode: 0644]
Webmin/DynamicBar.pm [new file with mode: 0644]
Webmin/DynamicHTML.pm [new file with mode: 0644]
Webmin/DynamicText.pm [new file with mode: 0644]
Webmin/DynamicWait.pm [new file with mode: 0644]
Webmin/ErrorPage.pm [new file with mode: 0644]
Webmin/File.pm [new file with mode: 0644]
Webmin/Form.pm [new file with mode: 0644]
Webmin/Group.pm [new file with mode: 0644]
Webmin/Icon.pm [new file with mode: 0644]
Webmin/Input.pm [new file with mode: 0644]
Webmin/InputTable.pm [new file with mode: 0644]
Webmin/JavascriptButton.pm [new file with mode: 0644]
Webmin/LinkTable.pm [new file with mode: 0644]
Webmin/Menu.pm [new file with mode: 0644]
Webmin/Multiline.pm [new file with mode: 0644]
Webmin/OptTextarea.pm [new file with mode: 0644]
Webmin/OptTextbox.pm [new file with mode: 0644]
Webmin/Page.pm [new file with mode: 0644]
Webmin/Password.pm [new file with mode: 0644]
Webmin/PlainText.pm [new file with mode: 0644]
Webmin/Properties.pm [new file with mode: 0644]
Webmin/Radios.pm [new file with mode: 0644]
Webmin/ResultPage.pm [new file with mode: 0644]
Webmin/Section.pm [new file with mode: 0644]
Webmin/Select.pm [new file with mode: 0644]
Webmin/Submit.pm [new file with mode: 0644]
Webmin/Table.pm [new file with mode: 0644]
Webmin/TableAction.pm [new file with mode: 0644]
Webmin/Tabs.pm [new file with mode: 0644]
Webmin/Textarea.pm [new file with mode: 0644]
Webmin/Textbox.pm [new file with mode: 0644]
Webmin/Time.pm [new file with mode: 0644]
Webmin/TitleList.pm [new file with mode: 0644]
Webmin/Upload.pm [new file with mode: 0644]
Webmin/User.pm [new file with mode: 0644]

diff --git a/Webmin/All.pm b/Webmin/All.pm
new file mode 100644 (file)
index 0000000..529a542
--- /dev/null
@@ -0,0 +1,44 @@
+use Webmin::Page;
+use Webmin::ResultPage;
+use Webmin::ErrorPage;
+use Webmin::ConfirmPage;
+use Webmin::Form;
+use Webmin::Section;
+use Webmin::Textbox;
+use Webmin::OptTextbox;
+use Webmin::OptTextarea;
+use Webmin::Submit;
+use Webmin::Password;
+use Webmin::Checkbox;
+use Webmin::Select;
+use Webmin::Radios;
+use Webmin::Checkboxes;
+use Webmin::Table;
+use Webmin::Menu;
+use Webmin::LinkTable;
+use Webmin::Tabs;
+use Webmin::Textarea;
+use Webmin::Upload;
+use Webmin::DynamicText;
+use Webmin::DynamicBar;
+use Webmin::DynamicWait;
+use Webmin::DynamicHTML;
+use Webmin::Properties;
+use Webmin::User;
+use Webmin::Group;
+use Webmin::File;
+use Webmin::Button;
+use Webmin::JavascriptButton;
+use Webmin::PlainText;
+use Webmin::Multiline;
+use Webmin::Date;
+use Webmin::Time;
+use Webmin::TitleList;
+use Webmin::Columns;
+use Webmin::Icon;
+use Webmin::TableAction;
+use Webmin::InputTable;
+use WebminCore;
+
+1;
+
diff --git a/Webmin/Button.pm b/Webmin/Button.pm
new file mode 100644 (file)
index 0000000..2b2c739
--- /dev/null
@@ -0,0 +1,61 @@
+package Webmin::Button;
+use Webmin::Input;
+use WebminCore;
+@ISA = ( "Webmin::Input" );
+
+=head2 new Webmin::Button(cgi, label, [name])
+Creates a button that when clicked will link to some other page
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::Button::new) &&
+    caller() !~ /Webmin::Theme::Button/) {
+        return new Webmin::Theme::Button(@_[1..$#_]);
+        }
+my ($self, $cgi, $value, $name) = @_;
+$self = { };
+bless($self);
+$self->set_cgi($cgi);
+$self->set_value($value);
+$self->set_name($name) if ($name);
+return $self;
+}
+
+=head2 html()
+Returns HTML for this button
+=cut
+sub html
+{
+my ($self) = @_;
+my $rv = "<form action=".$self->get_cgi().">";
+foreach my $h (@{$self->{'hiddens'}}) {
+       $rv .= &ui_hidden($h->[0], $h->[1])."\n";
+       }
+$rv .= &ui_submit($self->get_value(), $self->get_name(),
+                       $self->get_disabled())."</form>";
+return $rv;
+}
+
+sub set_cgi
+{
+my ($self, $cgi) = @_;
+$self->{'cgi'} = $cgi;
+}
+
+sub get_cgi
+{
+my ($self) = @_;
+return $self->{'cgi'};
+}
+
+=head2 add_hidden(name, value)
+Adds some hidden input to this button, for passing to the CGI
+=cut
+sub add_hidden
+{
+my ($self, $name, $value) = @_;
+push(@{$self->{'hiddens'}}, [ $name, $value ]);
+}
+
+1;
+
diff --git a/Webmin/Checkbox.pm b/Webmin/Checkbox.pm
new file mode 100644 (file)
index 0000000..f1191e3
--- /dev/null
@@ -0,0 +1,64 @@
+package Webmin::Checkbox;
+use Webmin::Input;
+use WebminCore;
+@ISA = ( "Webmin::Input" );
+
+=head2 new Webmin::Checkbox(name, return, label, checked, [disabled])
+Create a single checkbox field
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::Checkbox::new)) {
+        return new Webmin::Theme::Checkbox(@_[1..$#_]);
+        }
+my ($self, $name, $return, $label, $checked, $disabled) = @_;
+$self = { };
+bless($self);
+$self->set_name($name);
+$self->set_return($return);
+$self->set_label($label);
+$self->set_value($checked);
+$self->set_disabled($disabled);
+return $self;
+}
+
+=head2 html()
+Returns the HTML for this single checkbox
+=cut
+sub html
+{
+my ($self) = @_;
+my $dis = $self->{'form'}->get_changefunc($self);
+return &ui_checkbox($self->get_name(), $self->get_return(),
+                         $self->get_label(), $self->get_value(),
+                         $dis ? "onClick='$dis'" : undef,
+                         $self->get_disabled()).
+       &ui_hidden("ui_exists_".$self->get_name(), 1);
+}
+
+sub set_return
+{
+my ($self, $return) = @_;
+$self->{'return'} = $return;
+}
+
+sub set_label
+{
+my ($self, $label) = @_;
+$self->{'label'} = $label;
+}
+
+sub get_return
+{
+my ($self) = @_;
+return $self->{'return'};
+}
+
+sub get_label
+{
+my ($self) = @_;
+return $self->{'label'};
+}
+
+1;
+
diff --git a/Webmin/Checkboxes.pm b/Webmin/Checkboxes.pm
new file mode 100644 (file)
index 0000000..1cebd82
--- /dev/null
@@ -0,0 +1,119 @@
+package Webmin::Checkboxes;
+use Webmin::Input;
+use WebminCore;
+@ISA = ( "Webmin::Input" );
+
+=head2 new Webmin::Checkboxes(name, value|&values, &options, [disabled])
+Create a list of checkboxes, of which zero or more may be selected
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::Checkboxes::new)) {
+        return new Webmin::Theme::Checkboxes(@_[1..$#_]);
+        }
+my ($self, $name, $value, $options, $disabled) = @_;
+$self = { };
+bless($self);
+$self->set_name($name);
+$self->set_value($value);
+$self->set_options($options);
+$self->set_disabled($disabled);
+return $self;
+}
+
+=head2 add_option(name, [label])
+=cut
+sub add_option
+{
+my ($self, $name, $label) = @_;
+push(@{$self->{'options'}}, [ $name, $label ]);
+}
+
+=head2 html()
+Returns the HTML for all the checkboxes, one after the other
+=cut
+sub html
+{
+my ($self) = @_;
+my $rv;
+for(my $i=0; $i<@{$self->{'options'}}; $i++) {
+       $rv .= $self->one_html($i)."\n";
+       }
+return $rv;
+}
+
+=head2 one_html(number)
+Returns the HTML for a single one of the checkboxes
+=cut
+sub one_html
+{
+my ($self, $num) = @_;
+my $opt = $self->{'options'}->[$num];
+my $value = $self->get_value();
+my %sel = map { $_, 1 } (ref($value) ? @$value : ( $value ));
+return &ui_checkbox($self->get_name(), $opt->[0],
+                         defined($opt->[1]) ? $opt->[1] : $opt->[0],
+                         $sel{$opt->[0]}, undef, $self->get_disabled()).
+       ($num == 0 ? &ui_hidden("ui_exists_".$self->get_name(), 1) : "");
+}
+
+=head2 get_value()
+Returns a hash ref of all selected values
+=cut
+sub get_value
+{
+my ($self) = @_;
+my $in = $self->{'form'} ? $self->{'form'}->{'in'} : undef;
+if ($in && (defined($in->{$self->{'name'}}) ||
+           defined($in->{"ui_exists_".$self->{'name'}}))) {
+       return [ split(/\0/, $in->{$self->{'name'}}) ];
+       }
+elsif ($in && defined($in->{"ui_value_".$self->{'name'}})) {
+       return [ split(/\0/, $in->{"ui_value_".$self->{'name'}}) ];
+       }
+else {
+       return $self->{'value'};
+       }
+}
+
+sub set_options
+{
+my ($self, $options) = @_;
+$self->{'options'} = $options;
+}
+
+sub get_options
+{
+my ($self) = @_;
+return $self->{'options'};
+}
+
+=head2 validate()
+Returns a list of error messages for this field
+=cut
+sub validate
+{
+my ($self) = @_;
+my $value = $self->get_value();
+if ($self->{'mandatory'} && !@$value) {
+       return ( $self->{'mandmesg'} || $text{'ui_checkmandatory'} );
+       }
+return ( );
+}
+
+=head2 get_input_names()
+Returns the actual names of all HTML elements that make up this input
+=cut
+sub get_input_names
+{
+my ($self) = @_;
+my @rv;
+for(my $i=0; $i<@{$self->{'options'}}; $i++) {
+       push(@rv, $self->{'name'}."[".$i."]");
+       }
+return @rv;
+}
+
+1;
+
+
diff --git a/Webmin/Columns.pm b/Webmin/Columns.pm
new file mode 100644 (file)
index 0000000..b2a26c4
--- /dev/null
@@ -0,0 +1,82 @@
+package Webmin::Columns;
+use WebminCore;
+
+=head2 new Webmin::Columns(cols)
+Displays some page elements in a multi-column table
+=cut
+sub new
+{
+my ($self, $cols) = @_;
+if (defined(&Webmin::Theme::Columns::new)) {
+        return new Webmin::Theme::Columns(@_[1..$#_]);
+        }
+$self = { 'columns' => 2 };
+bless($self);
+$self->set_columns($cols) if (defined($cols));
+return $self;
+}
+
+=head2 html()
+Returns HTML for the objects, arranged in columns
+=cut
+sub html
+{
+my ($self) = @_;
+my $rv;
+my $n = scalar(@{$self->{'contents'}});
+$rv .= "<table width=100% cellpadding=4><tr>\n";
+my $h = int($n / $self->{'columns'})+1;
+my $i = 0;
+my $pc = int(100/$self->{'columns'});
+foreach my $c (@{$self->{'contents'}}) {
+       if ($i%$h == 0) {
+               $rv .= "<td valign=top width=$pc%>";
+               }
+       $rv .= $c->html()."<p>\n";
+       $i++;
+       if ($i%$h == 0) {
+               $rv .= "</td>\n";
+               }
+       }
+$rv .= "</tr></table>\n";
+return $rv;
+}
+
+=head2 add(object)
+Adds some Webmin:: object to this list
+=cut
+sub add
+{
+my ($self, $object) = @_;
+push(@{$self->{'contents'}}, $object);
+if ($self->{'page'}) {
+       $object->set_page($self->{'page'});
+       }
+}
+
+sub set_columns
+{
+my ($self, $columns) = @_;
+$self->{'columns'} = $columns;
+}
+
+sub get_columns
+{
+my ($self) = @_;
+return $self->{'columns'};
+}
+
+=head2 set_page(Webmin::Page)
+Called when this menu is added to a page
+=cut
+sub set_page
+{
+my ($self, $page) = @_;
+$self->{'page'} = $page;
+foreach my $c (@{$self->{'contents'}}) {
+       $c->set_page($page);
+       }
+}
+
+1;
+
diff --git a/Webmin/ConfirmPage.pm b/Webmin/ConfirmPage.pm
new file mode 100644 (file)
index 0000000..8e90af1
--- /dev/null
@@ -0,0 +1,47 @@
+package Webmin::ConfirmPage;
+use Webmin::Page;
+use WebminCore;
+@ISA = ( "Webmin::Page" );
+
+=head2 new Webmin::ConfirmPage(subheading, title, message, cgi, &in, [ok-message],
+                              [cancel-message], [help-name])
+Create a new page object that asks if the user is sure if he wants to
+do something or not.
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::ConfirmPage::new)) {
+       return new Webmin::Theme::ConfirmPage(@_[1..$#_]);
+       }
+my ($self, $subheading, $title, $message, $cgi, $in, $ok, $cancel, $help) = @_;
+$self = new Webmin::Page($subheading, $title, $help);
+$self->{'in'} = $in;
+$self->add_message($message);
+my $form = new Webmin::Form($cgi, "get");
+$form->set_input($in);
+$self->add_form($form);
+foreach my $i (keys %$in) {
+       foreach my $v (split(/\0/, $in->{$i})) {
+               $form->add_hidden($i, $v);
+               }
+       }
+$form->add_button(new Webmin::Submit($ok || "OK", "ui_confirm"));
+$form->add_button(new Webmin::Submit($cancel || $text{'cancel'}, "ui_cancel"));
+bless($self);
+return $self;
+}
+
+sub get_confirm
+{
+my ($self) = @_;
+return $self->{'in'}->{'ui_confirm'} ? 1 : 0;
+}
+
+sub get_cancel
+{
+my ($self) = @_;
+return $self->{'in'}->{'ui_cancel'} ? 1 : 0;
+}
+
+1;
+
diff --git a/Webmin/Date.pm b/Webmin/Date.pm
new file mode 100644 (file)
index 0000000..8c1c6af
--- /dev/null
@@ -0,0 +1,96 @@
+package Webmin::Date;
+use Webmin::Input;
+use Time::Local;
+use WebminCore;
+@ISA = ( "Webmin::Input" );
+
+=head2 new Webmin::Date(name, time, [disabled])
+Create a new field for selecting a date
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::Date::new)) {
+        return new Webmin::Theme::Date(@_[1..$#_]);
+        }
+my ($self, $name, $value, $disabled) = @_;
+bless($self = { });
+$self->set_name($name);
+$self->set_value($value);
+$self->set_disabled($disabled) if (defined($disabled));
+return $self;
+}
+
+=head2 html()
+Returns the HTML for the date chooser
+=cut
+sub html
+{
+my ($self) = @_;
+my $rv;
+my @tm = localtime($self->get_value());
+my $name = $self->get_name();
+$rv .= &ui_date_input($tm[3], $tm[4]+1, $tm[5]+1900,
+                           "day_".$name, "month_".$name, "year_".$name,
+                           $self->get_disabled())." ".
+       &date_chooser_button("day_".$name, "month_".$name, "year_".$name);
+return $rv;
+}
+
+=head2 get_value()
+Returns the date as a Unix time number (for zero o'clock)
+=cut
+sub get_value
+{
+my ($self) = @_;
+my $in = $self->{'form'} ? $self->{'form'}->{'in'} : undef;
+if ($in && defined($in->{"day_".$self->{'name'}})) {
+       my $rv = $self->to_time($in);
+       return defined($rv) ? $rv : $self->{'value'};
+       }
+elsif ($in && defined($in->{"ui_value_".$self->{'name'}})) {
+       return $in->{"ui_value_".$self->{'name'}};
+       }
+else {
+       return $self->{'value'};
+       }
+}
+
+sub to_time
+{
+my ($self, $in) = @_;
+my $day = $in->{"day_".$self->{'name'}};
+return undef if ($day !~ /^\d+$/);
+my $month = $in->{"month_".$self->{'name'}}-1;
+my $year = $in->{"year_".$self->{'name'}}-1900;
+return undef if ($year !~ /^\d+$/);
+my $rv = eval { timelocal(0, 0, 0, $day, $month, $year) };
+return $@ ? undef : $rv;
+}
+
+sub set_validation_func
+{
+my ($self, $func) = @_;
+$self->{'validation_func'} = $func;
+}
+
+=head2 validate()
+Ensures that the date is valid
+=cut
+sub validate
+{
+my ($self) = @_;
+my $tm = $self->to_time($self->{'form'}->{'in'});
+if (!defined($tm)) {
+       return ( $text{'ui_edate'} );
+       }
+if ($self->{'validation_func'}) {
+       my $err = &{$self->{'validation_func'}}($self->get_value(),
+                                               $self->{'name'},
+                                               $self->{'form'});
+       return ( $err ) if ($err);
+       }
+return ( );
+}
+
+1;
+
diff --git a/Webmin/DynamicBar.pm b/Webmin/DynamicBar.pm
new file mode 100644 (file)
index 0000000..850bbaf
--- /dev/null
@@ -0,0 +1,119 @@
+package Webmin::DynamicBar;
+use WebminCore;
+
+=head2 new Webmin::DynamicBar(&start-function, max)
+A page element for displaying progress towards some goal, like the download of
+a file.
+=cut
+sub new
+{
+my ($self, $func, $max) = @_;
+$self = { 'func' => $func,
+         'name' => "dynamic".++$dynamic_count,
+         'width' => 80,
+         'max' => $max };
+bless($self);
+return $self;
+}
+
+=head2 set_message(text)
+Sets the text describing what we are waiting for
+=cut
+sub set_message
+{
+my ($self, $message) = @_;
+$self->{'message'} = $message;
+}
+
+sub get_message
+{
+my ($self) = @_;
+return $self->{'message'};
+}
+
+=head2 html()
+Returns the HTML for the text field
+=cut
+sub html
+{
+my ($self) = @_;
+my $rv;
+if ($self->get_message()) {
+       $rv .= $self->get_message()."<p>\n";
+       }
+$rv .= "<form name=form_$self->{'name'}>";
+$rv .= "<input name=bar_$self->{'name'} size=$self->{'width'} disabled=true style='font-family: courier'>";
+$rv .= "&nbsp;";
+$rv .= "<input name=pc_$self->{'name'} size=3 disabled=true style='font-family: courier'>%";
+$rv .= "</form>";
+return $rv;
+}
+
+=head2 start()
+Called by the page to begin the progress
+=cut
+sub start
+{
+my ($self) = @_;
+&{$self->{'func'}}($self);
+}
+
+=head2 update(pos)
+Called by the function to update the position of the bar.
+=cut
+sub update
+{
+my ($self, $pos) = @_;
+my $pc = int(100*$pos/$self->{'max'});
+if ($pc != $self->{'lastpc'}) {
+       my $xn = int($self->{'width'}*$pos/$self->{'max'});
+       my $xes = "X" x $xn;
+       print "<script>window.document.forms[\"form_$self->{'name'}\"].pc_$self->{'name'}.value = \"$pc\";</script>\n";
+       print "<script>window.document.forms[\"form_$self->{'name'}\"].bar_$self->{'name'}.value = \"$xes\";</script>\n";
+       $self->{'lastpc'} = $pc;
+       }
+}
+
+=head2 set_wait(wait)
+If called with a non-zero arg, generation of the page should wait until this
+the progress is complete. Otherwise, the page will be generated completely before
+the start function is called
+=cut
+sub set_wait
+{
+my ($self, $wait) = @_;
+$self->{'wait'} = $wait;
+}
+
+sub get_wait
+{
+my ($self) = @_;
+return $self->{'wait'};
+}
+
+=head2 set_page(Webmin::Page)
+Called when this dynamic text box is added to a page
+=cut
+sub set_page
+{
+my ($self, $page) = @_;
+$self->{'page'} = $page;
+}
+
+sub set_width
+{
+my ($self, $width) = @_;
+$self->{'width'} = $width;
+}
+
+=head2 needs_unbuffered()
+Must return 1 if the page needs to be in un-buffered and no-table mode
+=cut
+sub needs_unbuffered
+{
+return 0;
+}
+
+
+1;
+
diff --git a/Webmin/DynamicHTML.pm b/Webmin/DynamicHTML.pm
new file mode 100644 (file)
index 0000000..80952e5
--- /dev/null
@@ -0,0 +1,76 @@
+package Webmin::DynamicHTML;
+use WebminCore;
+
+=head2 new Webmin::DynamicHTML(&function, &args, [before])
+When the page is being rendered, executes the given function and prints any
+text that it returns.
+=cut
+sub new
+{
+my ($self, $func, $args, $before) = @_;
+$self = { 'func' => $func,
+         'args' => $args,
+         'before' => $before };
+bless($self);
+return $self;
+}
+
+=head2 set_before(text)
+Sets the text describing what we are waiting for
+=cut
+sub set_before
+{
+my ($self, $before) = @_;
+$self->{'before'} = $before;
+}
+
+sub get_before
+{
+my ($self) = @_;
+return $self->{'before'};
+}
+
+sub html
+{
+my ($self) = @_;
+my $rv;
+if ($self->get_before()) {
+       $rv .= $self->get_before()."<p>\n";
+       }
+return $rv;
+}
+
+=head2 start()
+Called by the page to begin the dynamic output.
+=cut
+sub start
+{
+my ($self) = @_;
+&{$self->{'func'}}($self, @$args);
+}
+
+sub get_wait
+{
+my ($self) = @_;
+return 1;
+}
+
+=head2 needs_unbuffered()
+Must return 1 if the page needs to be in un-buffered and no-table mode
+=cut
+sub needs_unbuffered
+{
+return 1;
+}
+
+=head2 set_page(Webmin::Page)
+Called when this dynamic HTML element is added to a page
+=cut
+sub set_page
+{
+my ($self, $page) = @_;
+$self->{'page'} = $page;
+}
+
+1;
+
diff --git a/Webmin/DynamicText.pm b/Webmin/DynamicText.pm
new file mode 100644 (file)
index 0000000..279d28b
--- /dev/null
@@ -0,0 +1,123 @@
+# XXX should support non-Javascript mode?
+package Webmin::DynamicText;
+use WebminCore;
+
+=head2 new Webmin::DynamicText(&start-function, &args)
+A page element for displaying text that takes time to generate, such as from
+a long-running script. Uses a non-editable text box, updated via Javascript.
+The function will be called when it is time to start producing output, with this
+object as a parameter. It must call the add_line function on the object for each
+new line to be added.
+=cut
+sub new
+{
+my ($self, $func, $args) = @_;
+$self = { 'func' => $func,
+         'args' => $args,
+         'name' => "dynamic".++$dynamic_count,
+         'rows' => 20,
+         'cols' => 80 };
+bless($self);
+return $self;
+}
+
+=head2 set_message(text)
+Sets the text describing what we are waiting for
+=cut
+sub set_message
+{
+my ($self, $message) = @_;
+$self->{'message'} = $message;
+}
+
+sub get_message
+{
+my ($self) = @_;
+return $self->{'message'};
+}
+
+=head2 html()
+Returns the HTML for the text box
+=cut
+sub html
+{
+my ($self) = @_;
+my $rv;
+if ($self->get_message()) {
+       $rv .= $self->get_message()."<p>\n";
+       }
+$rv .= "<form name=form_$self->{'name'}>";
+$rv .= "<textarea name=$self->{'name'} rows=$self->{'rows'} cols=$self->{'cols'} wrap=off disabled=true>";
+$rv .= "</textarea>\n";
+$rv .= "</form>";
+return $rv;
+}
+
+=head2 start()
+Called by the page to begin the dynamic output.
+=cut
+sub start
+{
+my ($self) = @_;
+&{$self->{'func'}}($self, @$args);
+}
+
+=head2 add_line(line)
+Called by the function to add a line of text to this output
+=cut
+sub add_line
+{
+my ($self, $line) = @_;
+$line =~ s/\r|\n//g;
+$line = &quote_escape($line);
+print "<script>window.document.forms[\"form_$self->{'name'}\"].$self->{'name'}.value += \"$line\"+\"\\n\";</script>\n";
+}
+
+=head2 set_wait(wait)
+If called with a non-zero arg, generation of the page should wait until this
+text box is complete. Otherwise, the page will be generated completely before the
+start function is called
+=cut
+sub set_wait
+{
+my ($self, $wait) = @_;
+$self->{'wait'} = $wait;
+}
+
+sub get_wait
+{
+my ($self) = @_;
+return $self->{'wait'};
+}
+
+=head2 set_page(Webmin::Page)
+Called when this dynamic text box is added to a page
+=cut
+sub set_page
+{
+my ($self, $page) = @_;
+$self->{'page'} = $page;
+}
+
+sub set_rows
+{
+my ($self, $rows) = @_;
+$self->{'rows'} = $rows;
+}
+
+sub set_cols
+{
+my ($self, $cols) = @_;
+$self->{'cols'} = $cols;
+}
+
+=head2 needs_unbuffered()
+Must return 1 if the page needs to be in un-buffered and no-table mode
+=cut
+sub needs_unbuffered
+{
+return 0;
+}
+
+1;
+
diff --git a/Webmin/DynamicWait.pm b/Webmin/DynamicWait.pm
new file mode 100644 (file)
index 0000000..e725e7c
--- /dev/null
@@ -0,0 +1,129 @@
+package Webmin::DynamicWait;
+use WebminCore;
+
+=head2 new Webmin::DynamicWait(&start-function, [&args])
+A page element indicating that something is happening.
+=cut
+sub new
+{
+my ($self, $func, $args) = @_;
+$self = { 'func' => $func,
+         'args' => $args,
+         'name' => "dynamic".++$dynamic_count,
+         'width' => 80,
+         'delay' => 20 };
+bless($self);
+return $self;
+}
+
+=head2 set_message(text)
+Sets the text describing what we are waiting for
+=cut
+sub set_message
+{
+my ($self, $message) = @_;
+$self->{'message'} = $message;
+}
+
+sub get_message
+{
+my ($self) = @_;
+return $self->{'message'};
+}
+
+=head2 html()
+Returns the HTML for the text field used to indicate progress
+=cut
+sub html
+{
+my ($self) = @_;
+my $rv;
+if ($self->get_message()) {
+       $rv .= $self->get_message()."<p>\n";
+       }
+$rv .= "<form name=form_$self->{'name'}>";
+$rv .= "<input name=$self->{'name'} size=$self->{'width'} disabled=true style='font-family: courier'>";
+$rv .= "</form>";
+return $rv;
+}
+
+=head2 start()
+Called by the page to begin the progress. Also starts a process to update the
+Javascript text box
+=cut
+sub start
+{
+my ($self) = @_;
+$self->{'pid'} = fork();
+if (!$self->{'pid'}) {
+       my $pos = 0;
+       while(1) {
+               select(undef, undef, undef, $self->{'delay'}/1000.0);
+               my $str = (" " x $pos) . ("x" x 10);
+               print "<script>window.document.forms[\"form_$self->{'name'}\"].$self->{'name'}.value = \"$str\";</script>\n";
+               $pos++;
+               $pos = 0 if ($pos == $self->{'width'});
+               }
+       exit;
+       }
+&{$self->{'func'}}($self, @{$self->{'args'}});
+}
+
+=head2 stop()
+Called back by the function when whatever we were waiting for is done
+=cut
+sub stop
+{
+my ($self) = @_;
+if ($self->{'pid'}) {
+       kill('TERM', $self->{'pid'});
+       }
+my $str = (" " x ($self->{'width'}/2 - 2)) . "DONE";
+print "<script>window.document.forms[\"form_$self->{'name'}\"].$self->{'name'}.value = \"$str\";</script>\n";
+}
+
+=head2 set_wait(wait)
+If called with a non-zero arg, generation of the page should wait until this
+the progress is complete. Otherwise, the page will be generated completely before
+the start function is called
+=cut
+sub set_wait
+{
+my ($self, $wait) = @_;
+$self->{'wait'} = $wait;
+}
+
+sub get_wait
+{
+my ($self) = @_;
+return $self->{'wait'};
+}
+
+=head2 set_page(Webmin::Page)
+Called when this dynamic text box is added to a page
+=cut
+sub set_page
+{
+my ($self, $page) = @_;
+$self->{'page'} = $page;
+}
+
+sub set_width
+{
+my ($self, $width) = @_;
+$self->{'width'} = $width;
+}
+
+=head2 needs_unbuffered()
+Must return 1 if the page needs to be in un-buffered and no-table mode
+=cut
+sub needs_unbuffered
+{
+return 0;
+}
+
+
+
+
+1;
+
diff --git a/Webmin/ErrorPage.pm b/Webmin/ErrorPage.pm
new file mode 100644 (file)
index 0000000..7f37565
--- /dev/null
@@ -0,0 +1,20 @@
+package Webmin::ErrorPage;
+use WebminCore;
+
+=head2 new Webmin::ErrorPage(subheading, title, message, [program-output], [help-name])
+Create a new page object for showing an error of some kind
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::ErrorPage::new)) {
+       return new Webmin::Theme::ErrorPage(@_[1..$#_]);
+       }
+my ($self, $subheading, $title, $message, $output, $help) = @_;
+$self = new Webmin::Page($subheading, $title, $help);
+$self->add_message("<b>",$text{'error'}," : ",$message,"</b>");
+$self->add_message("<pre>",$output,"</pre>");
+return $self;
+}
+
+1;
+
diff --git a/Webmin/File.pm b/Webmin/File.pm
new file mode 100644 (file)
index 0000000..92f1417
--- /dev/null
@@ -0,0 +1,70 @@
+package Webmin::File;
+use Webmin::Textbox;
+use WebminCore;
+@ISA = ( "Webmin::Textbox" );
+
+=head2 new Webmin::File(name, value, size, [directory], [disabled])
+A text box for selecting a file
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::File::new)) {
+        return new Webmin::Theme::File(@_[1..$#_]);
+        }
+my ($self, $name, $value, $size, $directory, $disabled) = @_;
+$self = new Webmin::Textbox($name, $value, $size, $disabled);
+bless($self);
+$self->set_directory($directory);
+return $self;
+}
+
+=head2 html()
+Returns the HTML for this file input
+=cut
+sub html
+{
+my ($self) = @_;
+my $rv = Webmin::Textbox::html($self);
+my $name = $self->get_name();
+my $directory = $self->get_directory();
+my $add = 0;
+my $chroot = $self->get_chroot();
+$rv .= "<input type=button name=${name}_button onClick='ifield = form.$name; chooser = window.open(\"$gconfig{'webprefix'}/chooser.cgi?add=$add&type=$directory&chroot=$chroot&file=\"+escape(ifield.value), \"chooser\", \"toolbar=no,menubar=no,scrollbar=no,width=400,height=300\"); chooser.ifield = ifield; window.ifield = ifield' value=\"...\">\n";
+return $rv;
+}
+
+sub set_directory
+{
+my ($self, $directory) = @_;
+$self->{'directory'} = $directory;
+}
+
+sub get_directory
+{
+my ($self) = @_;
+return $self->{'directory'};
+}
+
+sub set_chroot
+{
+my ($self, $chroot) = @_;
+$self->{'chroot'} = $chroot;
+}
+
+sub get_chroot
+{
+my ($self) = @_;
+return $self->{'chroot'};
+}
+
+=head2 get_input_names()
+Returns the actual names of all HTML elements that make up this input
+=cut
+sub get_input_names
+{
+my ($self) = @_;
+return ( $self->{'name'}, $self->{'name'}."_button" );
+}
+
+1;
+
diff --git a/Webmin/Form.pm b/Webmin/Form.pm
new file mode 100644 (file)
index 0000000..d85dbc9
--- /dev/null
@@ -0,0 +1,405 @@
+package Webmin::Form;
+use WebminCore;
+
+=head2 new Webmin::Form(cgi, [method])
+Creates a new form, which submits to the given CGI
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::Form::new)) {
+        return new Webmin::Theme::Form(@_[1..$#_]);
+        }
+my ($self, $program, $method) = @_;
+$self = { 'method' => 'get',
+         'name' => "form".++$form_count };
+bless($self);
+$self->set_program($program);
+$self->set_method($method) if ($method);
+return $self;
+}
+
+=head2 html()
+Returns the HTML that makes up this form
+=cut
+sub html
+{
+my ($self) = @_;
+my $rv;
+if ($self->get_align()) {
+       $rv .= "<div align=".$self->get_align().">\n";
+       }
+$rv .= $self->form_start();
+if ($self->get_heading()) {
+       if (defined(&ui_subheading)) {
+               $rv .= &ui_subheading($self->get_heading());
+               }
+       else {
+               $rv .= "<h3>".$self->get_heading()."</h3>\n";
+               }
+       }
+
+# Add the sections
+foreach my $h (@{$self->{'hiddens'}}) {
+       $rv .= &ui_hidden($h->[0], $h->[1])."\n";
+       }
+foreach my $s (@{$self->{'sections'}}) {
+       $rv .= $s->html();
+       }
+
+# Check if we have any inputs that need disabling
+my @dis = $self->list_disable_inputs();
+if (@dis) {
+       # Yes .. generate a function for them
+       $rv .= "<script>\n";
+       $rv .= "function ui_disable_".$self->{'name'}."(form) {\n";
+       foreach my $i (@dis) {
+               foreach my $n ($i->get_input_names()) {
+                       $rv .= "    form.".$n.".disabled = (".
+                                     $i->get_disable_code().");\n";
+                       }
+               }
+       $rv .= "}\n";
+       $rv .= "</script>\n";
+       }
+
+# Add the buttons at the end of the form
+my @buttonargs;
+foreach my $b (@{$self->{'buttons'}}) {
+       if (ref($b)) {
+               # An array of inputs
+               my $ihtml = join(" ", map { $_->html() } @$b);
+               push(@buttonargs, $ihtml);
+               }
+       else {
+               # A spacer
+               push(@buttonargs, "");
+               }
+       }
+$rv .= &ui_form_end(\@buttonargs);
+
+if ($self->get_align()) {
+       $rv .= "</div>\n";
+       }
+
+# Call the Javascript disable function
+if (@dis) {
+       $rv .= "<script>\n";
+       $rv .= "ui_disable_".$self->{'name'}."(window.document.forms[\"$self->{'name'}\"]);\n";
+       $rv .= "</script>\n";
+       }
+
+return $rv;
+}
+
+sub form_start
+{
+my ($self) = @_;
+return "<form action='$self->{'program'}' ".
+               ($self->{'method'} eq "post" ? "method=post" :
+                $self->{'method'} eq "form-data" ?
+                       "method=post enctype=multipart/form-data" :
+                       "method=get")." name=$self->{'name'}>\n";
+}
+
+=head2 add_section(section)
+Adds a Webmin::Section object to this form
+=cut
+sub add_section
+{
+my ($self, $section) = @_;
+push(@{$self->{'sections'}}, $section);
+$section->set_form($self);
+}
+
+=head2 get_section(idx)
+=cut
+sub get_section
+{
+my ($self, $idx) = @_;
+return $self->{'sections'}->[$idx];
+}
+
+=head2 add_button(button, [beside, ...])
+Adds a Webmin::Submit object to this form, for display at the bottom
+=cut
+sub add_button
+{
+my ($self, $button, @beside) = @_;
+push(@{$self->{'buttons'}}, [ $button, @beside ]);
+}
+
+=head2 add_button_spacer()
+Adds a gap between buttons, for grouping
+=cut
+sub add_button_spacer
+{
+my ($self, $spacer) = @_;
+push(@{$self->{'buttons'}}, $spacer);
+}
+
+=head2 add_hidden(name, value)
+Adds some hidden input to this form, for passing to the CGI
+=cut
+sub add_hidden
+{
+my ($self, $name, $value) = @_;
+push(@{$self->{'hiddens'}}, [ $name, $value ]);
+}
+
+=head2 validate()
+Validates all form inputs, based on the current CGI input hash. Returns a list
+of errors, each of which is field name and error message.
+=cut
+sub validate
+{
+my ($self) = @_;
+my @errs;
+foreach my $s (@{$self->{'sections'}}) {
+       push(@errs, $s->validate($self->{'in'}));
+       }
+return @errs;
+}
+
+=head2 validate_redirect(page, [&extra-errors])
+Validates the form, and if any errors are found re-directs to the given page
+with the errors, so that they can be displayed.
+=cut
+sub validate_redirect
+{
+my ($self, $page, $extras) = @_;
+if ($self->{'in'}->{'ui_redirecting'}) {
+       # If this page is displayed as part of a redirect, no need to validate!
+       return;
+       }
+my @errs = $self->validate();
+push(@errs, @$extras);
+if (@errs) {
+       my (@errlist, @vallist);
+       foreach my $e (@errs) {
+               push(@errlist, &urlize("ui_error_".$e->[0])."=".
+                              &urlize($e->[1]));
+               }
+       foreach my $i ($self->list_inputs()) {
+               my $v = $i->get_value();
+               my @vals = ref($v) ? @$v : ( $v );
+               @vals = ( undef ) if (!@vals);
+               foreach $v (@vals) {
+                       push(@vallist,
+                           &urlize("ui_value_".$i->get_name())."=".
+                           &urlize($v));
+                       }
+               }
+       foreach my $h (@{$self->{'hiddens'}}) {
+               push(@vallist,
+                   &urlize($h->[0])."=".&urlize($h->[1]));
+               }
+       if ($page =~ /\?/) { $page .= "&"; }
+       else { $page .= "?"; }
+       &redirect($page.join("&", "ui_redirecting=1", @errlist, @vallist));
+       exit(0);
+       }
+}
+
+=head2 validate_error(whatfailed)
+Validates the form, and if any errors are found displays an error page.
+=cut
+sub validate_error
+{
+my ($self, $whatfailed) = @_;
+my @errs = $self->validate();
+&error_setup($whatfailed);
+if (@errs == 1) {
+       &error($errs[0]->[2] ? "$errs[0]->[2] : $errs[0]->[1]"
+                                  : $errs[0]->[1]);
+       }
+elsif (@errs > 1) {
+       my $msg = $text{'ui_errors'}."<br>";
+       foreach my $e (@errs) {
+               $msg .= $e->[2] ? "$e->[2] : $e->[1]<br>\n"
+                               : "$e->[1]<br>\n";
+               }
+       &error($msg);
+       }
+}
+
+=head2 field_errors(name)
+Returns a list of error messages associated with the field of some name, from
+the input passed to set_input
+=cut
+sub field_errors
+{
+my ($self, $name) = @_;
+my @errs;
+my $in = $self->{'in'};
+foreach my $i (keys %$in) {
+       if ($i eq "ui_error_".$name) {
+               push(@errs, split(/\0/, $in->{$i}));
+               }
+       }
+return @errs;
+}
+
+=head2 set_input(&input)
+Passes the form input hash to this form object, for use by the validate
+functions and for displaying errors next to fields.
+=cut
+sub set_input
+{
+my ($self, $in) = @_;
+$self->{'in'} = $in;
+}
+
+=head2 get_value(input-name)
+Returns the value of the input with the given name.
+=cut
+sub get_value
+{
+my ($self, $name) = @_;
+foreach my $s (@{$self->{'sections'}}) {
+       my $rv = $s->get_value($name);
+       return $rv if (defined($rv));
+       }
+return $self->{'in'}->{$name};
+}
+
+=head2 get_input(name)
+Returns the input with the given name
+=cut
+sub get_input
+{
+my ($self, $name) = @_;
+foreach my $i ($self->list_inputs()) {
+       return $i if ($i->get_name() eq $name);
+       }
+return undef;
+}
+
+sub set_program
+{
+my ($self, $program) = @_;
+$self->{'program'} = $program;
+}
+
+sub set_method
+{
+my ($self, $method) = @_;
+$self->{'method'} = $method;
+}
+
+=head2 list_inputs()
+Returns all inputs in all form sections
+=cut
+sub list_inputs
+{
+my ($self) = @_;
+my @rv;
+foreach my $s (@{$self->{'sections'}}) {
+       push(@rv, $s->list_inputs());
+       }
+return @rv;
+}
+
+=head2 list_disable_inputs()
+Returns a list of inputs that have disable functions
+=cut
+sub list_disable_inputs
+{
+my ($self) = @_;
+my @dis;
+foreach my $i ($self->list_inputs()) {
+       push(@dis, $i) if ($i->get_disable_code());
+       }
+return @dis;
+}
+
+=head2 set_page(Webmin::Page)
+Called when this form is added to a page
+=cut
+sub set_page
+{
+my ($self, $page) = @_;
+$self->{'page'} = $page;
+}
+
+=head2 get_changefunc(&input)
+Called by some input, to return the Javascript that should be called when this
+input changes it's value.
+=cut
+sub get_changefunc
+{
+my ($self, $input) = @_;
+my @dis = $self->list_disable_inputs();
+if (@dis) {
+       return "ui_disable_".$self->{'name'}."(form)";
+       }
+return undef;
+}
+
+=head2 set_heading(text)
+Sets the heading to be displayed above the form
+=cut
+sub set_heading
+{
+my ($self, $heading) = @_;
+$self->{'heading'} = $heading;
+}
+
+sub get_heading
+{
+my ($self) = @_;
+return $self->{'heading'};
+}
+
+=head2 get_formno()
+Returns the index of this form on the page
+=cut
+sub get_formno
+{
+my ($self) = @_;
+my $n = 0;
+foreach my $f (@{$self->{'page'}->{'contents'}}) {
+       if ($f eq $self) {
+               return $n;
+               }
+       elsif (ref($f) =~ /Form/) {
+               $n++;
+               }
+       }
+return undef;
+}
+
+=head2 add_onload(code)
+Adds some Javascript code for inclusion in the onLoad tag
+=cut
+sub add_onload
+{
+my ($self, $code) = @_;
+push(@{$self->{'onloads'}}, $code);
+}
+
+=head2 add_script(code)
+Adds some Javascript code for putting in the <head> section
+=cut
+sub add_script
+{
+my ($self, $script) = @_;
+push(@{$self->{'scripts'}}, $script);
+}
+
+=head2 set_align(align)
+Sets the alignment on the page (left, center, right)
+=cut
+sub set_align
+{
+my ($self, $align) = @_;
+$self->{'align'} = $align;
+}
+
+sub get_align
+{
+my ($self) = @_;
+return $self->{'align'};
+}
+
+1;
+
diff --git a/Webmin/Group.pm b/Webmin/Group.pm
new file mode 100644 (file)
index 0000000..cd45640
--- /dev/null
@@ -0,0 +1,57 @@
+package Webmin::Group;
+use Webmin::Textbox;
+use WebminCore;
+@ISA = ( "Webmin::Textbox" );
+
+=head2 new Webmin::Group(name, value, [multiple], [disabled])
+A text box for entering or selecting one or many Unix groupnames
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::Group::new)) {
+        return new Webmin::Theme::Group(@_[1..$#_]);
+        }
+my ($self, $name, $value, $multiple, $disabled) = @_;
+$self = new Webmin::Textbox($name, $value, $multiple ? 40 : 15, $disabled);
+bless($self);
+$self->set_multiple($multiple);
+return $self;
+}
+
+=head2 html()
+Returns the HTML for this group input
+=cut
+sub html
+{
+my ($self) = @_;
+my $rv = Webmin::Textbox::html($self);
+my $name = $self->get_name();
+my $multiple = $self->get_multiple();
+local $w = $multiple ? 500 : 300;
+$rv .= "&nbsp;<input type=button name=${name}_button onClick='ifield = form.$name; chooser = window.open(\"$gconfig{'webprefix'}/group_chooser.cgi?multi=$multiple&group=\"+escape(ifield.value), \"chooser\", \"toolbar=no,menubar=no,scrollbars=yes,width=$w,height=200\"); chooser.ifield = ifield; window.ifield = ifield' value=\"...\">\n";
+return $rv;
+}
+
+sub set_multiple
+{
+my ($self, $multiple) = @_;
+$self->{'multiple'} = $multiple;
+}
+
+sub get_multiple
+{
+my ($self) = @_;
+return $self->{'multiple'};
+}
+
+=head2 get_input_names()
+Returns the actual names of all HTML elements that make up this input
+=cut
+sub get_input_names
+{
+my ($self) = @_;
+return ( $self->{'name'}, $self->{'name'}."_button" );
+}
+
+1;
+
diff --git a/Webmin/Icon.pm b/Webmin/Icon.pm
new file mode 100644 (file)
index 0000000..d1d460f
--- /dev/null
@@ -0,0 +1,64 @@
+package Webmin::Icon;
+use WebminCore;
+
+=head2 Webmin::Icon(type, [message])
+This object generates an icon indicating some status. Possible types are :
+ok - OK
+critial - A serious problem
+major - A relatively serious problem
+minor - A small problem
+Can be used inside tables and property lists
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::Icon::new) && caller() !~ /Webmin::Theme::Icon/) {
+        return new Webmin::Theme::Icon(@_[1..$#_]);
+        }
+my ($self, $type, $message) = @_;
+$self = { };
+bless($self);
+$self->set_type($type);
+$self->set_message($message) if (defined($message));
+return $self;
+}
+
+=head2 html()
+Returns HTML for the icon
+=cut
+sub html
+{
+my ($self) = @_;
+my $rv;
+$rv .= "<img src=/images/".$self->get_type().".gif align=middle>";
+if ($self->get_message()) {
+       $rv .= "&nbsp;".$self->get_message();
+       }
+return $rv;
+}
+
+sub set_type
+{
+my ($self, $type) = @_;
+$self->{'type'} = $type;
+}
+
+sub get_type
+{
+my ($self) = @_;
+return $self->{'type'};
+}
+
+sub set_message
+{
+my ($self, $message) = @_;
+$self->{'message'} = $message;
+}
+
+sub get_message
+{
+my ($self) = @_;
+return $self->{'message'};
+}
+
+1;
+
diff --git a/Webmin/Input.pm b/Webmin/Input.pm
new file mode 100644 (file)
index 0000000..5f55048
--- /dev/null
@@ -0,0 +1,134 @@
+package Webmin::Input;
+use WebminCore;
+
+sub set_form
+{
+my ($self, $form) = @_;
+$self->{'form'} = $form;
+}
+
+sub set_name
+{
+my ($self, $name) = @_;
+$self->{'name'} = $name;
+}
+
+sub get_name
+{
+my ($self) = @_;
+return $self->{'name'};
+}
+
+sub set_disabled
+{
+my ($self, $disabled) = @_;
+$self->{'disabled'} = $disabled;
+}
+
+sub get_disabled
+{
+my ($self) = @_;
+return $self->{'disabled'};
+}
+
+=head2 validate()
+No validation is done by default
+=cut
+sub validate
+{
+return ( );
+}
+
+sub set_value
+{
+my ($self, $value) = @_;
+$self->{'value'} = $value;
+}
+
+=head2 get_value()
+Returns the current value for this field as entered by the user, the value
+set when the form is re-displayed due to an error, or the initial value.
+=cut
+sub get_value
+{
+my ($self) = @_;
+my $in = $self->{'form'} ? $self->{'form'}->{'in'} : undef;
+if ($in && (defined($in->{$self->{'name'}}) ||
+           defined($in->{"ui_exists_".$self->{'name'}}))) {
+       return $in->{$self->{'name'}};
+       }
+elsif ($in && defined($in->{"ui_value_".$self->{'name'}})) {
+       return $in->{"ui_value_".$self->{'name'}};
+       }
+else {
+       return $self->{'value'};
+       }
+}
+
+=head2 set_disable_code(javascript)
+Must be provided with a Javascript expression that will return true when this
+input should be disabled. May refer to other fields, via the variable 'form'.
+ie. form.mode.value = "0"
+Will be called every time any field's value changes.
+=cut
+sub set_disable_code
+{
+my ($self, $code) = @_;
+$self->{'disablecode'} = $code;
+}
+
+sub get_disable_code
+{
+my ($self) = @_;
+return $self->{'disablecode'};
+}
+
+=head2 get_input_names()
+Returns the actual names of all HTML elements that make up this input
+=cut
+sub get_input_names
+{
+my ($self) = @_;
+return ( $self->{'name'} );
+}
+
+=head2 set_label(text)
+Sets HTML to be displayed before this field
+=cut
+sub set_label
+{
+my ($self, $label) = @_;
+$self->{'label'} = $label;
+}
+
+sub get_label
+{
+my ($self) = @_;
+return $self->{'label'};
+}
+
+sub set_mandatory
+{
+my ($self, $mandatory, $mandmesg) = @_;
+$self->{'mandatory'} = $mandatory;
+$self->{'mandmesg'} = $mandmesg if (defined($mandmesg));
+}
+
+sub get_mandatory
+{
+my ($self) = @_;
+return $self->{'mandatory'};
+}
+
+=head2 get_errors()
+Returns a list of errors associated with this field
+=cut
+sub get_errors
+{
+my ($self) = @_;
+return $self->{'form'} ? $self->{'form'}->field_errors($self->get_name())
+                      : ( );
+}
+
+1;
+
diff --git a/Webmin/InputTable.pm b/Webmin/InputTable.pm
new file mode 100644 (file)
index 0000000..020fa9e
--- /dev/null
@@ -0,0 +1,137 @@
+package Webmin::InputTable;
+use Webmin::Table;
+use WebminCore;
+@ISA = ( "Webmin::Table" );
+
+=head2 new Webmin::InputTable(&headings, [width], [name], [heading])
+A table containing multiple rows of inputs, each of which is the same
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::InputTable::new) &&
+    caller() !~ /Webmin::Theme::InputTable/) {
+        return new Webmin::Theme::InputTable(@_[1..$#_]);
+        }
+my $self = defined(&Webmin::Theme::Table::new) ? Webmin::Theme::Table::new(@_)
+                                              : Webmin::Table::new(@_);
+bless($self);
+$self->{'rowcount'} = 0;
+return $self;
+}
+
+=head2 set_inputs(&inputs)
+Sets the objects to be used for each row
+=cut
+sub set_inputs
+{
+my ($self, $classes) = @_;
+$self->{'classes'} = $classes;
+}
+
+=head2 add_values(&values)
+Adds a row of inputs, with the given values
+=cut
+sub add_values
+{
+my ($self, $values) = @_;
+my @row;
+for(my $i=0; $i<@$values; $i++) {
+       my $cls = $self->{'classes'}->[$i];
+       my $newin = { %$cls };
+       bless($newin, ref($cls));
+       $newin->set_value($values->[$i]);
+       $newin->set_name($newin->get_name()."_".$self->{'rowcount'});
+       $newin->set_form($self->{'form'}) if ($self->{'form'});
+       push(@row, $newin);
+       }
+$self->add_row(\@row);
+$self->{'rowcount'}++;
+}
+
+=head2 get_values(row)
+Returns the values of the inputs in the given row
+=cut
+sub get_values
+{
+my ($self, $row) = @_;
+my @rv;
+foreach my $i (@{$self->{'rows'}->[$row]}) {
+       if (ref($i) && $i->isa("Webmin::Input")) {
+               push(@rv, $i->get_value());
+               }
+       }
+return @rv;
+}
+
+=head2 list_inputs()
+=cut
+sub list_inputs
+{
+my ($self) = @_;
+my @rv = Webmin::Table::list_inputs($self);
+foreach my $r (@{$self->{'rows'}}) {
+       foreach my $i (@$r) {
+               if ($i && ref($i) && $i->isa("Webmin::Input")) {
+                       push(@rv, $i);
+                       }
+               }
+       }
+return @rv;
+}
+
+sub get_rowcount
+{
+my ($self) = @_;
+return $self->{'rowcount'};
+}
+
+=head2 validate()
+Validates all inputs, and returns a list of error messages
+=cut
+sub validate
+{
+my ($self) = @_;
+my $seli = $self->{'selectinput'};
+my @errs;
+if ($seli) {
+       push(@errs, map { [ $seli->get_name(), $_ ] } $seli->validate());
+       }
+foreach my $i (@{$self->{'inputs'}}) {
+       foreach my $e ($i->validate()) {
+               push(@errs, [ $i->get_name(), $e ]);
+               }
+       }
+my $k = 1;
+foreach my $r (@{$self->{'rows'}}) {
+       my $j = 0;
+       my $skip;
+       if (defined($self->{'control'})) {
+               if ($r->[$self->{'control'}]->get_value() eq "") {
+                       $skip = 1;
+                       }
+               }
+       foreach my $i (@$r) {
+               if ($i && ref($i) && $i->isa("Webmin::Input") && !$skip) {
+                       my $label = &text('ui_rowlabel', $k, $self->{'headings'}->[$j]);
+                       foreach my $e ($i->validate()) {
+                               push(@errs, [ $i->get_name(), $label." ".$e ]);
+                               }
+                       }
+               $j++;
+               }
+       $k++;
+       }
+return @errs;
+}
+
+=head2 set_control(column)
+Sets the column for which an empty value means no validation should be done
+=cut
+sub set_control
+{
+my ($self, $control) = @_;
+$self->{'control'} = $control;
+}
+
+1;
+
diff --git a/Webmin/JavascriptButton.pm b/Webmin/JavascriptButton.pm
new file mode 100644 (file)
index 0000000..f09d780
--- /dev/null
@@ -0,0 +1,47 @@
+package Webmin::JavascriptButton;
+use Webmin::Input;
+use WebminCore;
+@ISA = ( "Webmin::Input" );
+
+=head2 new Webmin::JavascriptButton(label, script, [disabled])
+Create a button that runs some Javascript when clicked
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::JavascriptButton::new) &&
+    caller() !~ /Webmin::Theme::JavascriptButton/) {
+        return new Webmin::Theme::JavascriptButton(@_[1..$#_]);
+        }
+my ($self, $value, $script, $disabled) = @_;
+$self = { };
+bless($self);
+$self->set_value($value);
+$self->set_script($script);
+$self->set_disabled($disabled) if ($disabled);
+return $self;
+}
+
+=head2 html()
+Returns the HTML for this text input
+=cut
+sub html
+{
+my ($self) = @_;
+return "<input type=button value=\"".&quote_escape($self->get_value())."\" ".
+       "onClick=\"".$self->get_script()."\">";
+}
+
+sub set_script
+{
+my ($self, $script) = @_;
+$self->{'script'} = $script;
+}
+
+sub get_script
+{
+my ($self) = @_;
+return $self->{'script'};
+}
+
+1;
+
diff --git a/Webmin/LinkTable.pm b/Webmin/LinkTable.pm
new file mode 100644 (file)
index 0000000..4acc42d
--- /dev/null
@@ -0,0 +1,314 @@
+package Webmin::LinkTable;
+use Webmin::Table;
+use WebminCore;
+
+=head2 new Webmin::LinkTable(heading, [columns], [width], [name])
+Creates a new table that just displays links, like in the Users and Groups module
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::LinkTable::new) &&
+    caller() !~ /Webmin::Theme::LinkTable/) {
+        return new Webmin::Theme::LinkTable(@_[1..$#_]);
+        }
+my ($self, $heading, $columns, $width, $name) = @_;
+$self = { 'sorter' => \&Webmin::Table::default_sorter,
+         'columns' => 4,
+         'sortable' => 1 };
+bless($self);
+$self->set_heading($heading);
+$self->set_name($name) if (defined($name));
+$self->set_width($width) if (defined($width));
+$self->set_columns($columns) if (defined($columns));
+return $self;
+}
+
+=head2 add_entry(name, link)
+Adds one item to appear in the table
+=cut
+sub add_entry
+{
+my ($self, $name, $link) = @_;
+push(@{$self->{'entries'}}, [ $name, $link ]);
+}
+
+=head2 html()
+Returns the HTML for this table.
+=cut
+sub html
+{
+my ($self) = @_;
+
+# Prepare the selector
+my @srows = @{$self->{'entries'}};
+my %selmap;
+if (defined($self->{'selectinput'})) {
+       my $i = 0;
+       foreach my $r (@srows) {
+               $selmap{$r} = $self->{'selectinput'}->one_html($i);
+               $i++;
+               }
+       }
+
+# Sort the entries
+my $sortdir = $self->get_sortdir();
+if (defined($sortdir)) {
+       my $func = $self->{'sorter'};
+       @srows = sort { my $so = &$func($a->[0], $b->[0]);
+                       $sortdir ? -$so : $so } @srows;
+       }
+
+# Build the sorter
+my $head;
+my $thisurl = $self->{'form'}->{'page'}->get_myurl();
+$thisurl .= $thisurl =~ /\?/ ? "&" : "?";
+my $name = $self->get_name();
+if ($self->get_sortable()) {
+       $head = "<table cellpadding=0 cellspacing=0 width=100%><tr>";
+       $head .= "<td><b>".$self->get_heading()."</b></td> <td align=right>";
+       if (!defined($sortdir)) {
+               # Not sorting .. show grey button
+               $head .= "<a href='${thisurl}ui_sortdir_${name}=0'>".
+                        "<img src=/images/nosort.gif border=0></a>";
+               }
+       else {
+               # Sorting .. show button to switch mode
+               my $notsort = !$sortdir;
+               $head .= "<a href='${thisurl}ui_sortdir_${name}=$notsort'>".
+                        "<img src=/images/sort.gif border=0></a>";
+               }
+       $head .= "</td></tr></table>";
+       }
+else {
+       $head = $self->get_heading();
+       }
+
+# Find any errors
+my $rv;
+if ($self->{'selectinput'}) {
+       # Get any errors for inputs
+       my @errs = $self->{'form'}->field_errors(
+                       $self->{'selectinput'}->get_name());
+       if (@errs) {
+               foreach my $e (@errs) {
+                       $rv .= "<font color=#ff0000>$e</font><br>\n";
+                       }
+               }
+       }
+
+# Create the actual table
+$rv .= &ui_table_start($head,
+                            defined($self->{'width'}) ? "width=$self->{'width'}"
+                                                      : undef, 1);
+$rv .= "<td colspan=2><table width=100%>";
+my $i = 0;
+my $cols = $self->get_columns();
+my $pc = 100/$cols;
+foreach my $r (@srows) {
+       $rv .= "<tr>\n" if ($i%$cols == 0);
+       $rv .= "<td width=$pc%>".$selmap{$r}."<a href='$r->[1]'>".
+              &html_escape($r->[0])."</a></td>\n";
+       $rv .= "<tr>\n" if ($i%$cols == $cols-1);
+       $i++;
+       }
+if ($i%$cols) {
+       # Finish off row
+       while($i++%$cols != $cols-1) {
+               $rv .= "<td width=$pc%></td>\n";
+               }
+       $rv .= "</tr>\n";
+       }
+$rv .= "</table></td>";
+$rv .= &ui_table_end();
+return $rv;
+}
+
+=head2 set_heading(text)
+Sets the heading text to appear above the table
+=cut
+sub set_heading
+{
+my ($self, $heading) = @_;
+$self->{'heading'} = $heading;
+}
+
+sub get_heading
+{
+my ($self) = @_;
+return $self->{'heading'};
+}
+
+sub set_name
+{
+my ($self, $name) = @_;
+$self->{'name'} = $name;
+}
+
+=head2 get_name()
+Returns the name for indentifying this table in HTML
+=cut
+sub get_name
+{
+my ($self) = @_;
+if (defined($self->{'name'})) {
+       return $self->{'name'};
+       }
+elsif ($self->{'form'}) {
+       my $secs = $self->{'form'}->{'sections'};
+       for(my $i=0; $i<@$secs; $i++) {
+               return "table".$i if ($secs->[$i] eq $self);
+               }
+       }
+return "table";
+}
+
+=head2 set_sorter(function)
+Sets a function used for sorting fields. Will be called with two values to compare
+=cut
+sub set_sorter
+{
+my ($self, $func) = @_;
+$self->{'sorter'} = $func;
+}
+
+=head2 default_sorter(value1, value2)
+=cut
+sub default_sorter
+{
+my ($value1, $value2, $col) = @_;
+return lc($value1) cmp lc($value2);
+}
+
+=head2 set_sortable(sortable?)
+Tells the table if sorting is allowed or not. By default, it is.
+=cut
+sub set_sortable
+{
+my ($self, $sortable) = @_;
+$self->{'sortable'} = $sortable;
+}
+
+sub get_sortable
+{
+my ($self) = @_;
+return $self->{'sortable'};
+}
+
+=head2 get_sortdir()
+Returns the order to sort in (1 for descending)
+=cut
+sub get_sortdir
+{
+my ($self) = @_;
+my $in = $self->{'form'} ? $self->{'form'}->{'in'} : undef;
+my $name = $self->get_name();
+if ($in && defined($in->{"ui_sortdir_".$name})) {
+       return ( $in->{"ui_sortdir_".$name} );
+       }
+else {
+       return ( $self->{'sortdir'} );
+       }
+}
+
+=head2 set_sortdir(descending?)
+Sets the default sort direction, unless overridden by the user.
+=cut
+sub set_sortcolumn
+{
+my ($self, $desc) = @_;
+$self->{'sortdir'} = $desc;
+}
+
+=head2 set_width([number|number%])
+Sets the width of this entire table. Can be called with 100%, 500 or undef to use
+the minimum possible width.
+=cut
+sub set_width
+{
+my ($self, $width) = @_;
+$self->{'width'} = $width;
+}
+
+=head2 set_columns(cols)
+Sets the number of columns to display
+=cut
+sub set_columns
+{
+my ($self, $columns) = @_;
+$self->{'columns'} = $columns;
+}
+
+sub get_columns
+{
+my ($self) = @_;
+return $self->{'columns'};
+}
+
+=head2 set_form(form)
+Called by the Webmin::Form object when this table is added to it
+=cut
+sub set_form
+{
+my ($self, $form) = @_;
+$self->{'form'} = $form;
+if ($self->{'selectinput'}) {
+       $self->{'selectinput'}->set_form($form);
+       }
+}
+
+=head2 set_selector(input)
+Takes a Webmin::Checkboxes or Webmin::Radios object, and uses it to add checkboxes
+to all the entries
+=cut
+sub set_selector
+{
+my ($self, $input) = @_;
+$self->{'selectinput'} = $input;
+$input->set_form($form);
+}
+
+=head2 get_selector()
+Returns the UI element used for selecting rows
+=cut
+sub get_selector
+{
+my ($self) = @_;
+return $self->{'selectinput'};
+}
+
+=head2 validate()
+Validates the selector input
+=cut
+sub validate
+{
+my ($self) = @_;
+my $seli = $self->{'selectinput'};
+if ($seli) {
+       return map { [ $seli->get_name(), $_ ] } $seli->validate();
+       }
+return ( );
+}
+
+=head2 get_value(input-name)
+Returns the value of the input with the given name.
+=cut
+sub get_value
+{
+my ($self, $name) = @_;
+if ($self->{'selectinput'} && $self->{'selectinput'}->get_name() eq $name) {
+       return $self->{'selectinput'}->get_value();
+       }
+return undef;
+}
+
+=head2 list_inputs()
+Returns all inputs in all form sections
+=cut
+sub list_inputs
+{
+my ($self) = @_;
+return $self->{'selectinput'} ? ( $self->{'selectinput'} ) : ( );
+}
+
+1;
+
diff --git a/Webmin/Menu.pm b/Webmin/Menu.pm
new file mode 100644 (file)
index 0000000..2f20503
--- /dev/null
@@ -0,0 +1,86 @@
+package Webmin::Menu;
+use WebminCore;
+
+=head2 new Webmin::Menu(&options, [columns])
+Generates a menu of options, typically using icons.
+=cut
+sub new
+{
+my ($self, $options, $columns) = @_;
+if (defined(&Webmin::Theme::Menu::new)) {
+        return new Webmin::Theme::Menu(@_[1..$#_]);
+        }
+$self = { 'columns' => 4 };
+bless($self);
+$self->set_options($options);
+$self->set_columns($columns) if (defined($columns));
+return $self;
+}
+
+=head2 html()
+Returns the HTML for the table
+=cut
+sub html
+{
+my ($self) = @_;
+my (@links, @titles, @icons, @hrefs);
+foreach my $o (@{$self->{'options'}}) {
+       push(@links, $o->{'link'});
+       if ($o->{'link2'}) {
+               push(@titles, "$o->{'title'}</a> <a href='$o->{'link2'}'>$o->{'title2'}");
+               }
+       else {
+               push(@titles, $o->{'title'});
+               }
+       push(@icons, $o->{'icon'});
+       push(@hrefs, $o->{'href'});
+       }
+my $rv = &capture_function_output(\&icons_table,
+               \@links, \@titles, \@icons, $self->get_columns(),
+               \@hrefs);
+return $rv;
+}
+
+=head2 add_option(&option)
+=cut
+sub add_option
+{
+my ($self, $option) = @_;
+push(@{$self->{'options'}}, $option);
+}
+
+sub set_options
+{
+my ($self, $options) = @_;
+$self->{'options'} = $options;
+}
+
+sub get_options
+{
+my ($self) = @_;
+return $self->{'options'};
+}
+
+sub set_columns
+{
+my ($self, $columns) = @_;
+$self->{'columns'} = $columns;
+}
+
+sub get_columns
+{
+my ($self) = @_;
+return $self->{'columns'};
+}
+
+=head2 set_page(Webmin::Page)
+Called when this menu is added to a page
+=cut
+sub set_page
+{
+my ($self, $page) = @_;
+$self->{'page'} = $page;
+}
+
+1;
+
diff --git a/Webmin/Multiline.pm b/Webmin/Multiline.pm
new file mode 100644 (file)
index 0000000..863c510
--- /dev/null
@@ -0,0 +1,40 @@
+package Webmin::Multiline;
+use Webmin::Textarea;
+use WebminCore;
+@ISA = ( "Webmin::Textarea" );
+
+=head2 new Webmin::Multiline(name, &lines, rows, cols, [disabled])
+Create a new input for entering multiple text entries. By default, just uses
+a textbox
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::Multiline::new)) {
+        return new Webmin::Theme::Multiline(@_[1..$#_]);
+        }
+my ($self, $name, $lines, $rows, $cols, $wrap, $disabled) = @_;
+$self = new Webmin::Textarea($name, join("\n", @$lines), $rows, $cols, undef, $disabled);
+bless($self);
+return $self;
+}
+
+=head2 set_lines(&lines)
+Sets the lines to display
+=cut
+sub set_lines
+{
+my ($self, $lines) = @_;
+$self->set_value(join("\n", @$lines));
+}
+
+=head2 get_lines()
+Returns an array ref of lines to display
+=cut
+sub get_lines
+{
+my ($self) = @_;
+return [ split(/[\r|\n]+/, $self->get_value()) ];
+}
+
+1;
+
diff --git a/Webmin/OptTextarea.pm b/Webmin/OptTextarea.pm
new file mode 100644 (file)
index 0000000..25e11bf
--- /dev/null
@@ -0,0 +1,120 @@
+package Webmin::OptTextarea;
+use Webmin::Textarea;
+use WebminCore;
+@ISA = ( "Webmin::Textarea" );
+
+=head2 new Webmin::OptTextarea(name, value, rows, cols, [default-msg], [other-msg])
+Create a text area whose value is optional.
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::OptTextarea::new)) {
+        return new Webmin::Theme::OptTextarea(@_[1..$#_]);
+        }
+my ($self, $name, $value, $rows, $cols, $default, $other) = @_;
+$self = new Webmin::Textarea($name, $value, $rows, $cols);
+bless($self);
+$self->set_default($default || $text{'default'});
+$self->set_other($other) if ($other);
+return $self;
+}
+
+=head2 html()
+Returns the HTML for this optional text input
+=cut
+sub html
+{
+my ($self) = @_;
+my $rv;
+my $name = $self->get_name();
+my $value = $self->get_value();
+my $dis = $self->get_disabled();
+my $rows = $self->get_rows();
+my $columns = $self->get_cols();
+my $dis1 = &js_disable_inputs([ $name ], [ ]);
+my $dis2 = &js_disable_inputs([ ], [ $name ]);
+my $opt1 = $self->get_default();
+my $opt2 = $self->get_other();
+$rv .= "<input type=radio name=\"".&quote_escape($name."_def")."\" ".
+       "value=1 ".($value ne '' ? "" : "checked").
+       ($dis ? " disabled=true" : "")." onClick='$dis1'> ".$opt1."\n";
+$rv .= "<input type=radio name=\"".&quote_escape($name."_def")."\" ".
+       "value=0 ".($value ne '' ? "checked" : "").
+       ($dis ? " disabled=true" : "")." onClick='$dis2'> ".$opt2."<br>\n";
+$rv .= "<textarea name=\"".&quote_escape($name)."\" ".
+       ($value eq "" || $dis ? " disabled=true" : "").
+       "rows=$rows columns=$columns>".&html_escape($value)."</textarea>\n";
+return $rv;
+
+}
+
+=head2 validate(&inputs)
+=cut
+sub validate
+{
+my ($self, $in) = @_;
+if (defined($self->get_value())) {
+       if ($self->get_value() eq "") {
+               return ( $text{'ui_nothing'} );
+               }
+       return Webmin::Textbox::validate($self);
+       }
+return ( );
+}
+
+sub set_default
+{
+my ($self, $default) = @_;
+$self->{'default'} = $default;
+}
+
+sub get_default
+{
+my ($self) = @_;
+return $self->{'default'};
+}
+
+sub set_other
+{
+my ($self, $other) = @_;
+$self->{'other'} = $other;
+}
+
+sub get_other
+{
+my ($self) = @_;
+return $self->{'other'};
+}
+
+=head2 get_value()
+Returns the specified initial value for this field, or the value set when the
+form is re-displayed due to an error.
+=cut
+sub get_value
+{
+my ($self) = @_;
+my $in = $self->{'form'} ? $self->{'form'}->{'in'} : undef;
+if ($in && (defined($in->{$self->{'name'}}) ||
+           defined($in->{$self->{'name'}.'_def'}))) {
+       return $in->{$self->{'name'}.'_def'} ? undef : $in->{$self->{'name'}};
+       }
+elsif ($in && defined($in->{"ui_value_".$self->{'name'}})) {
+       return $in->{"ui_value_".$self->{'name'}};
+       }
+else {
+       return $self->{'value'};
+       }
+}
+
+=head2 get_input_names()
+Returns the actual names of all HTML elements that make up this input
+=cut
+sub get_input_names
+{
+my ($self) = @_;
+return ( $self->{'name'}, $self->{'name'}."_def[0]",
+                         $self->{'name'}."_def[1]" );
+}
+
+1;
+
diff --git a/Webmin/OptTextbox.pm b/Webmin/OptTextbox.pm
new file mode 100644 (file)
index 0000000..dd2d95f
--- /dev/null
@@ -0,0 +1,90 @@
+package Webmin::OptTextbox;
+use Webmin::Textbox;
+use WebminCore;
+@ISA = ( "Webmin::Textbox" );
+
+=head2 new Webmin::OptTextbox(name, value, size, [default-msg], [other-msg])
+Create a text field whose value is optional.
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::OptTextbox::new)) {
+        return new Webmin::Theme::OptTextbox(@_[1..$#_]);
+        }
+my ($self, $name, $value, $size, $default, $other) = @_;
+$self = new Webmin::Textbox($name, $value, $size);
+bless($self);
+$self->set_default($default || $text{'default'});
+$self->set_other($other) if ($other);
+return $self;
+}
+
+=head2 html()
+Returns the HTML for this optional text input
+=cut
+sub html
+{
+my ($self) = @_;
+return &ui_opt_textbox($self->get_name(), $self->get_value(),
+                            $self->{'size'}, $self->{'default'},
+                            $self->{'other'});
+}
+
+=head2 validate(&inputs)
+=cut
+sub validate
+{
+my ($self, $in) = @_;
+if (defined($self->get_value())) {
+       if ($self->get_value() eq "") {
+               return ( $text{'ui_nothing'} );
+               }
+       return Webmin::Textbox::validate($self);
+       }
+return ( );
+}
+
+sub set_default
+{
+my ($self, $default) = @_;
+$self->{'default'} = $default;
+}
+
+sub set_other
+{
+my ($self, $other) = @_;
+$self->{'other'} = $other;
+}
+
+=head2 get_value()
+Returns the specified initial value for this field, or the value set when the
+form is re-displayed due to an error.
+=cut
+sub get_value
+{
+my ($self) = @_;
+my $in = $self->{'form'} ? $self->{'form'}->{'in'} : undef;
+if ($in && (defined($in->{$self->{'name'}}) ||
+           defined($in->{$self->{'name'}.'_def'}))) {
+       return $in->{$self->{'name'}.'_def'} ? undef : $in->{$self->{'name'}};
+       }
+elsif ($in && defined($in->{"ui_value_".$self->{'name'}})) {
+       return $in->{"ui_value_".$self->{'name'}};
+       }
+else {
+       return $self->{'value'};
+       }
+}
+
+=head2 get_input_names()
+Returns the actual names of all HTML elements that make up this input
+=cut
+sub get_input_names
+{
+my ($self) = @_;
+return ( $self->{'name'}, $self->{'name'}."_def[0]",
+                         $self->{'name'}."_def[1]" );
+}
+
+1;
+
diff --git a/Webmin/Page.pm b/Webmin/Page.pm
new file mode 100644 (file)
index 0000000..4ee217b
--- /dev/null
@@ -0,0 +1,426 @@
+package Webmin::Page;
+use WebminCore;
+use WebminCore;
+
+=head2 new Webmin::Page(subheading, title, [help-name], [show-config],
+                     [no-module-index], [no-webmin-index], [rightside],
+                     [header], [body-tags], [below-text])
+Create a new page object, with the given heading and other details
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::Page::new) && caller() !~ /Webmin::Theme::Page/) {
+        return new Webmin::Theme::Page(@_[1..$#_]);
+        }
+my ($self, $subheading, $title, $help, $config, $noindex, $nowebmin, $right,
+    $header, $body, $below) = @_;
+$self = { 'index' => 1, 'webmin' => 1, 'image' => "" };
+bless($self);
+$self->set_subheading($subheading);
+$self->set_title($title);
+$self->set_help($help);
+$self->set_config($config);
+$self->set_index(!$noindex);
+$self->set_webmin(!$nowebmin);
+$self->set_right($right);
+$self->set_header($header);
+$self->set_body($body);
+$self->set_below($below);
+return $self;
+}
+
+=head2 print()
+Actually outputs this page
+=cut
+sub print
+{
+my ($self) = @_;
+my $rv;
+
+# Work out if we need buffering/table
+foreach my $c (@{$self->{'contents'}}) {
+       if (ref($c) =~ /Dynamic/) {
+               $| = 1;
+               if ($c->needs_unbuffered()) {
+                       $self->{'unbuffered'} = 1;
+                       }
+               }
+       }
+
+# Show the page header
+my $func = $self->{'unbuffered'} ? \&ui_print_unbuffered_header
+                                : \&ui_print_header;
+my $scripts;
+foreach my $s (@{$self->{'scripts'}},
+              (map { @{$_->{'scripts'}} } @{$self->{'contents'}})) {
+       $scripts .= "<script>\n".$s."\n</script>\n";
+       }
+my $onload;
+my @onloads = ( @{$self->{'onloads'}},
+               map { @{$_->{'onloads'}} } @{$self->{'contents'}} );
+if (@onloads) {
+       $onload = "onLoad='".join(" ", @onloads)."'";
+       }
+my @args =  ( $self->{'subheading'}, $self->{'title'}, $self->{'image'},
+             $self->{'help'}, $self->{'config'}, $self->{'index'} ? undef : 1,
+             $self->{'webmin'} ? undef : 1, $self->{'right'},
+             $self->{'header'}.$scripts, $self->{'body'}.$onload,
+             $self->{'below'} );
+while(!defined($args[$#args])) {
+       pop(@args);
+       }
+if ($self->get_refresh()) {
+       print "Refresh: ",$self->get_refresh(),"\r\n";
+       }
+&ui_print_header(@args);
+
+# Add the tab top
+if ($self->{'tabs'}) {
+       print $self->{'tabs'}->top_html();
+       }
+
+# Add any pre-content stuff
+print $self->pre_content();
+
+if ($self->{'errormsg'}) {
+       # Show the error only
+       print $self->get_errormsg_html();
+       }
+else {
+       # Generate the forms and other stuff
+       foreach my $c (@{$self->{'contents'}}) {
+               if (!ref($c)) {
+                       # Just a message
+                       print "$c<p>\n";
+                       }
+               else {
+                       # Convert to HTML
+                       eval { print $c->html(); };
+                       if ($@) {
+                               print "<pre>$@</pre>";
+                               }
+                       if (ref($c) =~ /Dynamic/ && $c->get_wait()) {
+                               # Dynamic object .. execute now
+                               $c->start();
+                               }
+                       }
+               }
+
+       # Generate buttons row
+       if ($self->{'buttons'}) {
+               print "<hr>\n";
+               print &ui_buttons_start();
+               foreach my $b (@{$self->{'buttons'}}) {
+                       print &ui_buttons_row(@$b);
+                       }
+               print &ui_buttons_end();
+               }
+       }
+
+# Add any post-content stuff
+print $self->post_content();
+
+# End of the tabs
+if ($self->{'tabs'}) {
+       print $self->{'tabs'}->bottom_html();
+       }
+
+# Print the footer
+my @footerargs;
+foreach my $f (@{$self->{'footers'}}) {
+       push(@footerargs, $f->[0], $f->[1]);
+       }
+&ui_print_footer(@footerargs);
+
+# Start any dynamic objects
+foreach my $c (@{$self->{'contents'}}) {
+       if (ref($c) =~ /Dynamic/ && !$c->get_wait()) {
+               $c->start();
+               }
+       }
+}
+
+=head2 add_footer(link, title)
+Adds a return link, typically for display at the end of the page.
+=cut
+sub add_footer
+{
+my ($self, $link, $title) = @_;
+push(@{$self->{'footers'}}, [ $link, $title ]);
+}
+
+=head2 get_footer(index)
+Returns the link for the numbered footer
+=cut
+sub get_footer
+{
+my ($self, $num) = @_;
+return $self->{'footers'}->[$num]->[0];
+}
+
+=head2 add_message(text, ...)
+Adds a text message, to appear at this point on the page
+=cut
+sub add_message
+{
+my ($self, @message) = @_;
+push(@{$self->{'contents'}}, join("", @message));
+}
+
+=head2 add_error(text, [command-output])
+Adds a an error message, possible accompanied by the command output
+=cut
+sub add_error
+{
+my ($self, $message, $out) = @_;
+$message = "<font color=#ff0000>$message</font>";
+if ($out) {
+       $message .= "<pre>$out</pre>";
+       }
+push(@{$self->{'contents'}}, $message);
+}
+
+=head2 add_message_after(&object, text, ...)
+Adds a message after some existing object
+=cut
+sub add_message_after
+{
+my ($self, $object, @message) = @_;
+splice(@{$self->{'contents'}}, $self->position_of($object)+1, 0,
+       join("", @message));
+}
+
+=head2 add_error_after(&object, text, [command-output])
+Adds an error message after some existing object
+=cut
+sub add_error_after
+{
+my ($self, $object, $message, $out) = @_;
+$message = "<font color=#ff0000>$message</font>";
+if ($out) {
+       $message .= "<pre>$out</pre>";
+       }
+splice(@{$self->{'contents'}}, $self->position_of($object)+1, 0,
+       $message);
+}
+
+sub position_of
+{
+my ($self, $object) = @_;
+for(my $i=0; $i<@{$self->{'contents'}}; $i++) {
+       if ($self->{'contents'}->[$i] eq $object) {
+               return $i;
+               }
+       }
+print STDERR "Could not find $object in ",join(" ",@{$self->{'contents'}}),"\n";
+return scalar(@{$self->{'contents'}});
+}
+
+=head2 add_form(Webmin::Form)
+Adds a form to be displayed on this page
+=cut
+sub add_form
+{
+my ($self, $form) = @_;
+push(@{$self->{'contents'}}, $form);
+$form->set_page($self);
+}
+
+=head2 add_separator()
+Adds some kind of separation between parts of this page, like an <hr>
+=cut
+sub add_separator
+{
+my ($self, $message) = @_;
+push(@{$self->{'contents'}}, "<hr>");
+}
+
+=head2 add_button(cgi, label, description, [&hiddens], [before-button],
+                 [after-button])
+Adds an action button associated with this page, typically for display at the end
+=cut
+sub add_button
+{
+my ($self, $cgi, $label, $desc, $hiddens, $before, $after) = @_;
+push(@{$self->{'buttons'}}, [ $cgi, $label, $desc, join(" ", @$hiddens),
+                             $before, $after ]);
+}
+
+=head2 add_tabs(Webmin::Tags)
+Tells the page to display the given set of tabs at the top
+=cut
+sub add_tabs
+{
+my ($self, $tabs) = @_;
+$self->{'tabs'} = $tabs;
+}
+
+=head2 add_dynamic(Webmin::DynamicText|Webmin::DynamicProgress)
+Adds an object that is dynamically generated, such as a text box or progress bar.
+=cut
+sub add_dynamic
+{
+my ($self, $dyn) = @_;
+push(@{$self->{'contents'}}, $dyn);
+$dyn->set_page($self);
+}
+
+sub set_subheading
+{
+my ($self, $subheading) = @_;
+$self->{'subheading'} = $subheading;
+}
+
+sub set_title
+{
+my ($self, $title) = @_;
+$self->{'title'} = $title;
+}
+
+sub set_help
+{
+my ($self, $help) = @_;
+$self->{'help'} = $help;
+}
+
+sub set_config
+{
+my ($self, $config) = @_;
+$self->{'config'} = $config;
+}
+
+sub set_index
+{
+my ($self, $index) = @_;
+$self->{'index'} = $index;
+}
+
+sub set_webmin
+{
+my ($self, $webmin) = @_;
+$self->{'webmin'} = $webmin;
+}
+
+sub set_right
+{
+my ($self, $right) = @_;
+$self->{'right'} = $right;
+}
+
+sub set_header
+{
+my ($self, $header) = @_;
+$self->{'header'} = $header;
+}
+
+sub set_body
+{
+my ($self, $body) = @_;
+$self->{'body'} = $body;
+}
+
+sub set_below
+{
+my ($self, $below) = @_;
+$self->{'below'} = $below;
+}
+
+sub set_unbuffered
+{
+my ($self, $unbuffered) = @_;
+$self->{'unbuffered'} = $unbuffered;
+}
+
+=head2 set_popup(popup?)
+If set to 1, then this is a popup window
+=cut
+sub set_popup
+{
+my ($self, $popup) = @_;
+$self->{'popup'} = $popup;
+}
+
+=head2 get_myurl()
+Returns the path part of the URL for this page, like /foo/bar.cgi
+=cut
+sub get_myurl
+{
+my @args;
+if ($ENV{'QUERY_STRING'} && $ENV{'REQUEST_METHOD'} ne 'POST') {
+       my %in;
+       &ReadParse(\%in);
+       foreach my $i (keys %in) {
+               if ($i !~ /^ui_/) {
+                       foreach my $v (split(/\0/, $in{$i})) {
+                               push(@args, &urlize($i)."=".
+                                           &urlize($v));
+                               }
+                       }
+               }
+       }
+return @args ? $ENV{'SCRIPT_NAME'}."?".join("&", @args)
+            : $ENV{'SCRIPT_NAME'};
+}
+
+=head2 set_refresh(seconds)
+Sets the number of seconds between automatic page refreshes
+=cut
+sub set_refresh
+{
+my ($self, $refresh) = @_;
+$self->{'refresh'} = $refresh;
+}
+
+sub get_refresh
+{
+my ($self) = @_;
+return $self->{'refresh'};
+}
+
+=head2 add_onload(code)
+Adds some Javascript code for inclusion in the onLoad tag
+=cut
+sub add_onload
+{
+my ($self, $code) = @_;
+push(@{$self->{'onloads'}}, $code);
+}
+
+=head2 add_script(code)
+Adds some Javascript code for putting in the <head> section
+=cut
+sub add_script
+{
+my ($self, $script) = @_;
+push(@{$self->{'scripts'}}, $script);
+}
+
+sub pre_content
+{
+my ($self) = @_;
+return undef;
+}
+
+sub post_content
+{
+my ($self) = @_;
+return undef;
+}
+
+=head2 set_errormsg(message)
+Sets an error message to be displayed instead of the page contents
+=cut
+sub set_errormsg
+{
+my ($self, $errormsg) = @_;
+$self->{'errormsg'} = $errormsg;
+}
+
+sub get_errormsg_html
+{
+my ($self) = @_;
+return $self->{'errormsg'}."<p>\n";
+}
+
+1;
+
diff --git a/Webmin/Password.pm b/Webmin/Password.pm
new file mode 100644 (file)
index 0000000..e3d5731
--- /dev/null
@@ -0,0 +1,32 @@
+package Webmin::Password;
+@ISA = ( "Webmin::Textbox" );
+use Webmin::Textbox;
+use WebminCore;
+
+=head2 new Webmin::Password(name, value, [size])
+Create a new text input field, for a password
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::Password::new)) {
+       return new Webmin::Theme::Password(@_[1..$#_]);
+       }
+my ($self, $name, $value, $size) = @_;
+$self = new Webmin::Textbox($name, $value, $size);
+bless($self);
+return $self;
+}
+
+=head2 html()
+Returns the HTML for this password input
+=cut
+sub html
+{
+my ($self) = @_;
+return &ui_password($self->get_name(), $self->get_value(),
+                         $self->{'size'},
+                         $self->{'$disabled'});
+}
+
+
+
diff --git a/Webmin/PlainText.pm b/Webmin/PlainText.pm
new file mode 100644 (file)
index 0000000..43c5ae6
--- /dev/null
@@ -0,0 +1,97 @@
+package Webmin::PlainText;
+use WebminCore;
+
+=head2 new Webmin::PlainText(text, columns)
+Displays a block of plain fixed-width text, within a page or form.
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::PlainText::new) &&
+    caller() !~ /Webmin::Theme::PlainText/) {
+        return new Webmin::Theme::PlainText(@_[1..$#_]);
+        }
+my ($self, $text, $columns) = @_;
+$self = { 'columns' => 80 };
+bless($self);
+$self->set_text($text);
+$self->set_columns($columns) if (defined($columns));
+return $self;
+}
+
+=head2 html()
+=cut
+sub html
+{
+my ($self) = @_;
+my $rv;
+$rv .= "<table border><tr $cb><td><pre>";
+foreach my $l (&wrap_lines($self->get_text(), $self->get_columns())) {
+       if (length($l) < $self->get_columns()) {
+               $l .= (" " x $self->get_columns() - length($l));
+               }
+       $rv .= &html_escape($l)."\n";
+       }
+if (!$self->get_text()) {
+       print (" " x $self->get_columns()),"\n";
+       }
+$rv .= "</pre></td></tr></table>\n";
+return $rv;
+}
+
+sub set_text
+{
+my ($self, $text) = @_;
+$self->{'text'} = $text;
+}
+
+sub get_text
+{
+my ($self) = @_;
+return $self->{'text'};
+}
+
+sub set_columns
+{
+my ($self, $columns) = @_;
+$self->{'columns'} = $columns;
+}
+
+sub get_columns
+{
+my ($self) = @_;
+return $self->{'columns'};
+}
+
+# wrap_lines(text, width)
+# Given a multi-line string, return an array of lines wrapped to
+# the given width
+sub wrap_lines
+{
+local @rv;
+local $w = $_[1];
+foreach $rest (split(/\n/, $_[0])) {
+       if ($rest =~ /\S/) {
+               while($rest =~ /^(.{1,$w}\S*)\s*([\0-\377]*)$/) {
+                       push(@rv, $1);
+                       $rest = $2;
+                       }
+               }
+       else {
+               # Empty line .. keep as it is
+               push(@rv, $rest);
+               }
+       }
+return @rv;
+}
+
+=head2 set_page(Webmin::Page)
+Called when this form is added to a page
+=cut
+sub set_page
+{
+my ($self, $page) = @_;
+$self->{'page'} = $page;
+}
+
+1;
+
diff --git a/Webmin/Properties.pm b/Webmin/Properties.pm
new file mode 100644 (file)
index 0000000..5f084b9
--- /dev/null
@@ -0,0 +1,132 @@
+package Webmin::Properties;
+use WebminCore;
+
+=head2 new Webmin::Properties([heading], [columns], [width])
+Creates a read-only properties list
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::Properties::new) &&
+    caller() !~ /Webmin::Theme::Properties/) {
+        return new Webmin::Theme::Properties(@_[1..$#_]);
+        }
+my ($self, $heading, $columns, $width) = @_;
+$self = { 'columns' => 2 };
+bless($self);
+$self->set_heading($heading) if (defined($heading));
+$self->set_columns($columns) if (defined($columns));
+$self->set_width($width) if (defined($width));
+return $self;
+}
+
+=head2 add_row(label, value, ...)
+Adds one row to the properties table
+=cut
+sub add_row
+{
+my ($self, @row) = @_;
+push(@{$self->{'rows'}}, \@row);
+}
+
+=head2 set_heading_row(head1, head2, ...)
+Adds a row of headings
+=cut
+sub set_heading_row
+{
+my ($self, @row) = @_;
+$self->{'heading_row'} = \@row;
+}
+
+=head2 html()
+Returns the HTML for this properties list
+=cut
+sub html
+{
+my ($self) = @_;
+my $rv;
+my $width = $self->get_width();
+$rv .= "<table border ".($width ? "width=$width" : "").">\n";
+$rv .= "<tr><td><table width=100% cellspacing=0 cellpadding=3>\n";
+my $cols = $self->get_columns();
+if ($self->get_heading()) {
+       $rv .= "<tr $tb><td colspan=$cols><b>".
+              $self->get_heading()."</b></td> </tr>\n";
+       }
+if ($self->{'heading_row'}) {
+       $rv .= "<tr $tb>\n";
+       foreach my $r (@{$self->{'heading_row'}}) {
+               $rv .= "<td><b>$r</b></td>\n";
+               }
+       $rv .= "</tr>\n";
+       }
+foreach my $r (@{$self->{'rows'}}) {
+       $rv .= "<tr $cb>\n";
+       $rv .= "<td><b>$r->[0]</b></td>\n";
+       for(my $i=1; $i<@$r || $i<$cols; $i++) {
+               $rv .= "<td>".(ref($r->[$i]) ? $r->[$i]->html()
+                                           : $r->[$i])."</td>\n";
+               }
+       $rv .= "</tr>\n";
+       }
+$rv .= "</table></td></tr></table>\n";
+return $rv;
+}
+
+=head2 set_width([number|number%])
+Sets the width of this section. Can be called with 100%, 500, or undef to use
+the minimum possible width.
+=cut
+sub set_width
+{
+my ($self, $width) = @_;
+$self->{'width'} = $width;
+}
+
+sub get_width
+{
+my ($self) = @_;
+return $self->{'width'};
+}
+
+=head2 set_columns(number)
+Sets the number of columns in the properties table, including the title column
+=cut
+sub set_columns
+{
+my ($self, $columns) = @_;
+$self->{'columns'} = $columns;
+}
+
+sub get_columns
+{
+my ($self) = @_;
+return $self->{'columns'};
+}
+
+=head2 set_heading(number)
+Sets the heading to appear above the properties list
+=cut
+sub set_heading
+{
+my ($self, $heading) = @_;
+$self->{'heading'} = $heading;
+}
+
+sub get_heading
+{
+my ($self) = @_;
+return $self->{'heading'};
+}
+
+
+=head2 set_page(Webmin::Page)
+Called when this form is added to a page
+=cut
+sub set_page
+{
+my ($self, $page) = @_;
+$self->{'page'} = $page;
+}
+
+1;
+
diff --git a/Webmin/Radios.pm b/Webmin/Radios.pm
new file mode 100644 (file)
index 0000000..fed6106
--- /dev/null
@@ -0,0 +1,77 @@
+package Webmin::Radios;
+use Webmin::Input;
+use WebminCore;
+@ISA = ( "Webmin::Input" );
+
+=head2 new Webmin::Radios(name, value, &options, [disabled])
+Create a list of radio buttons, of which one may be selected
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::Radios::new)) {
+        return new Webmin::Theme::Radios(@_[1..$#_]);
+        }
+my ($self, $name, $value, $options, $disabled) = @_;
+$self = { };
+bless($self);
+$self->set_name($name);
+$self->set_value($value);
+$self->set_options($options);
+$self->set_disabled($disabled);
+return $self;
+}
+
+=head2 add_option(name, [label])
+=cut
+sub add_option
+{
+my ($self, $name, $label) = @_;
+push(@{$self->{'options'}}, [ $name, $label ]);
+}
+
+=head2 html()
+Returns the HTML for all the radio buttons, one after the other
+=cut
+sub html
+{
+my ($self) = @_;
+my $dis = $self->{'form'}->get_changefunc($self);
+my $opts = $self->get_options();
+if ($dis) {
+       foreach my $o (@$opts) {
+               $o->[2] = "onClick='$dis'";
+               }
+       }
+return &ui_radio($self->get_name(), $self->get_value(),
+                      $opts, $self->get_disabled());
+}
+
+=head2 one_html(number)
+Returns the HTML for a single one of the radio buttons
+=cut
+sub one_html
+{
+my ($self, $num) = @_;
+my $opt = $self->{'options'}->[$num];
+my $dis = $self->{'form'}->get_changefunc($self);
+return &ui_oneradio($self->get_name(), $opt->[0],
+                         defined($opt->[1]) ? $opt->[1] : $opt->[0],
+                         $self->get_value() eq $opt->[0],
+                         $dis ? "onClick='$dis'" : undef,
+                         $self->get_disabled());
+}
+
+sub set_options
+{
+my ($self, $options) = @_;
+$self->{'options'} = $options;
+}
+
+sub get_options
+{
+my ($self) = @_;
+return $self->{'options'};
+}
+
+1;
+
diff --git a/Webmin/ResultPage.pm b/Webmin/ResultPage.pm
new file mode 100644 (file)
index 0000000..127dae3
--- /dev/null
@@ -0,0 +1,20 @@
+package Webmin::ResultPage;
+use WebminCore;
+
+=head2 new Webmin::ResultPage(subheading, title, message, [help-name])
+Create a new page object for showing some success message.
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::ResultPage::new) &&
+    caller() !~ /Webmin::Theme::ResultPage/) {
+       return new Webmin::Theme::ResultPage(@_[1..$#_]);
+       }
+my ($self, $subheading, $title, $message, $help) = @_;
+$self = new Webmin::Page($subheading, $title, $help);
+$self->add_message("<b>$message</b>");
+return $self;
+}
+
+1;
+
diff --git a/Webmin/Section.pm b/Webmin/Section.pm
new file mode 100644 (file)
index 0000000..244d187
--- /dev/null
@@ -0,0 +1,172 @@
+package Webmin::Section;
+use WebminCore;
+
+=head2 new Webmin::Section(header, [columns], [title], [width])
+Create a new form section, which has a header and contains some inputs
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::Section::new) &&
+    caller() !~ /Webmin::Theme::Section/) {
+        return new Webmin::Theme::Section(@_[1..$#_]);
+        }
+my ($self, $header, $columns, $title, $width) = @_;
+$self = { 'columns' => 4 };
+bless($self);
+$self->set_header($header);
+$self->set_columns($columns) if (defined($columns));
+$self->set_title($title) if (defined($title));
+$self->set_width($width) if (defined($width));
+return $self;
+}
+
+=head2 html()
+Returns the HTML for this form section
+=cut
+sub html
+{
+my ($self) = @_;
+my $rv;
+$rv .= &ui_table_start($self->{'header'},
+                    $self->{'width'} ? "width=$self->{'width'}" : undef,
+                    $self->{'columns'});
+foreach my $i (@{$self->{'inputs'}}) {
+       if (is_input($i->[1])) {
+               my $errs;
+               my @errs = $self->{'form'}->field_errors($i->[1]->get_name());
+               if (@errs) {
+                       foreach my $e (@errs) {
+                               $errs .= "<br><font color=#ff0000>$e</font>\n";
+                               }
+                       }
+               $rv .= &ui_table_row($i->[0], $i->[1]->html().$errs,
+                                          $i->[2]);
+               }
+       else {
+               $rv .= &ui_table_row($i->[0],
+                       ref($i->[1]) ? $i->[1]->html() : $i->[1], $i->[2]);
+               }
+       }
+$rv .= &ui_table_end();
+return $rv;
+}
+
+=head2 add_input(label, input, [columns])
+Adds some Webmin::Input object to this form section
+=cut
+sub add_input
+{
+my ($self, $label, $input, $cols) = @_;
+push(@{$self->{'inputs'}}, [ $label, $input, $cols ]);
+$input->set_form($self->{'form'});
+}
+
+=head2 add_row(label, text, [columns])
+Adds a non-editable row to this form section
+=cut
+sub add_row
+{
+my ($self, $label, $text, $cols) = @_;
+push(@{$self->{'inputs'}}, [ $label, $text, $cols ]);
+}
+
+=head2 add_separator()
+Adds some kind of separator at this point in the section
+=cut
+sub add_separator
+{
+my ($self) = @_;
+push(@{$self->{'inputs'}}, [ undef, "<hr>", $self->{'columns'} ]);
+}
+
+sub set_header
+{
+my ($self, $header) = @_;
+$self->{'header'} = $header;
+}
+
+sub set_columns
+{
+my ($self, $columns) = @_;
+$self->{'columns'} = $columns;
+}
+
+sub set_title
+{
+my ($self, $title) = @_;
+$self->{'title'} = $title;
+}
+
+=head2 set_width([number|number%])
+Sets the width of this section. Can be called with 100%, 500, or undef to use
+the minimum possible width.
+=cut
+sub set_width
+{
+my ($self, $width) = @_;
+$self->{'width'} = $width;
+}
+
+=head2 validate()
+Validates all form inputs, based on the given CGI input hash. Returns a list
+of errors, each of which is field name, error message and field label.
+=cut
+sub validate
+{
+my ($self) = @_;
+my @errs;
+foreach my $i (@{$self->{'inputs'}}) {
+       if (is_input($i->[1])) {
+               foreach my $e ($i->[1]->validate()) {
+                       push(@errs, [ $i->[1]->get_name(), $e, $i->[0] ]);
+                       }
+               }
+       }
+return @errs;
+}
+
+=head2 get_value(input-name)
+Returns the value of the input with the given name.
+=cut
+sub get_value
+{
+my ($self, $name) = @_;
+foreach my $i (@{$self->{'inputs'}}) {
+       if (is_input($i->[1]) && $i->[1]->get_name() eq $name) {
+               return $i->[1]->get_value();
+               }
+       }
+return undef;
+}
+
+=head2 set_form(form)
+Called by the Webmin::Form object when this section is added to it
+=cut
+sub set_form
+{
+my ($self, $form) = @_;
+$self->{'form'} = $form;
+foreach my $i (@{$self->{'inputs'}}) {
+       if (is_input($i->[1])) {
+               $i->[1]->set_form($form);
+               }
+       }
+}
+
+sub list_inputs
+{
+my ($self) = @_;
+return map { $_->[1] } grep { is_input($_->[1]) } @{$self->{'inputs'}};
+}
+
+=head2 is_input(object)
+=cut
+sub is_input
+{
+my ($object) = @_;
+return ref($object) && ref($object) =~ /::/ &&
+       $object->isa("Webmin::Input");
+}
+
+1;
+
diff --git a/Webmin/Select.pm b/Webmin/Select.pm
new file mode 100644 (file)
index 0000000..f1d5844
--- /dev/null
@@ -0,0 +1,135 @@
+package Webmin::Select;
+use Webmin::Input;
+use WebminCore;
+@ISA = ( "Webmin::Input" );
+
+=head2 new Webmin::Select(name, value|&values, &options, [multiple-size],
+                         [add-missing], [disabled])
+Create a menu or multiple-selection field
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::Select::new)) {
+        return new Webmin::Theme::Select(@_[1..$#_]);
+        }
+my ($self, $name, $value, $options, $size, $missing, $disabled) = @_;
+$self = { 'size' => 1 };
+bless($self);
+$self->set_name($name);
+$self->set_value($value);
+$self->set_options($options);
+$self->set_size($size) if (defined($size));
+$self->set_missing($missing);
+$self->set_disabled($disabled);
+return $self;
+}
+
+=head2 add_option(name, [label])
+=cut
+sub add_option
+{
+my ($self, $name, $label) = @_;
+push(@{$self->{'options'}}, [ $name, $label ]);
+}
+
+=head2 html()
+Returns the HTML for this menu or multi-select input
+=cut
+sub html
+{
+my ($self) = @_;
+my $dis = $self->{'form'}->get_changefunc($self);
+return &ui_select($self->get_name(), $self->get_value(),
+                       $self->get_options(), 
+                       $self->get_size() > 1 ? $self->get_size() : undef,
+                       $self->get_size() > 1 ? 1 : 0,
+                       undef,
+                       $self->get_disabled(),
+                       $dis ? "onChange='$dis'" : undef).
+       ($self->get_size() > 1 ? 
+              &ui_hidden("ui_exists_".$self->get_name(), 1) : "");
+}
+
+=head2 get_value()
+For a multi-select field, returns an array ref of all values. For a menu,
+return just the one value.
+=cut
+sub get_value
+{
+my ($self) = @_;
+my $in = $self->{'form'} ? $self->{'form'}->{'in'} : undef;
+if ($in && (defined($in->{$self->{'name'}}) ||
+           defined($in->{"ui_exists_".$self->{'name'}}))) {
+       if ($self->get_size() > 1) {
+               return [ split(/\0/, $in->{$self->{'name'}}) ];
+               }
+       else {
+               return $in->{$self->{'name'}};
+               }
+       }
+elsif ($in && defined($in->{"ui_value_".$self->{'name'}})) {
+       if ($self->get_size() > 1) {
+               return [ split(/\0/, $in->{"ui_value_".$self->{'name'}}) ];
+               }
+       else {
+               return $in->{"ui_value_".$self->{'name'}};
+               }
+       }
+else {
+       return $self->{'value'};
+       }
+}
+
+sub set_options
+{
+my ($self, $options) = @_;
+$self->{'options'} = $options;
+}
+
+sub set_size
+{
+my ($self, $size) = @_;
+$self->{'size'} = $size;
+}
+
+sub set_missing
+{
+my ($self, $missing) = @_;
+$self->{'missing'} = $missing;
+}
+
+sub get_options
+{
+my ($self) = @_;
+return $self->{'options'};
+}
+
+sub get_size
+{
+my ($self) = @_;
+return $self->{'size'};
+}
+
+sub get_missing
+{
+my ($self) = @_;
+return $self->{'missing'};
+}
+
+=head2 validate()
+Returns a list of error messages for this field
+=cut
+sub validate
+{
+my ($self) = @_;
+if ($self->{'size'} > 1) {
+       my $value = $self->get_value();
+       if ($self->{'mandatory'} && !@$value) {
+               return ( $self->{'mandatorymsg'} || $text{'ui_mandatory'} );
+               }
+       }
+return ( );
+}
+
+1;
+
diff --git a/Webmin/Submit.pm b/Webmin/Submit.pm
new file mode 100644 (file)
index 0000000..68f4520
--- /dev/null
@@ -0,0 +1,41 @@
+package Webmin::Submit;
+use Webmin::Input;
+use WebminCore;
+@ISA = ( "Webmin::Input" );
+
+=head2 new Webmin::Submit(label, [name], [disabled])
+Create a form submit button
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::Submit::new) &&
+    caller() !~ /Webmin::Theme::Submit/) {
+        return new Webmin::Theme::Submit(@_[1..$#_]);
+        }
+my ($self, $value, $name, $disabled) = @_;
+$self = { };
+bless($self);
+$self->set_value($value);
+$self->set_name($name) if ($name);
+$self->set_disabled($disabled) if ($disabled);
+return $self;
+}
+
+=head2 html()
+Returns the HTML for this form submit button
+=cut
+sub html
+{
+my ($self) = @_;
+return &ui_submit($self->get_value(), $self->get_name(),
+                       $self->get_disabled());
+}
+
+sub get_value
+{
+my ($self) = @_;
+return $self->{'value'};
+}
+
+1;
+
diff --git a/Webmin/Table.pm b/Webmin/Table.pm
new file mode 100644 (file)
index 0000000..e35ef57
--- /dev/null
@@ -0,0 +1,660 @@
+package Webmin::Table;
+use Webmin::JavascriptButton;
+use WebminCore;
+
+=head2 new Webmin::Table(&headings, [width], [name], [heading])
+Create a multi-column table, with support for sorting, paging and so on
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::Table::new) &&
+    caller() !~ /Webmin::Theme::Table/) {
+        return new Webmin::Theme::Table(@_[1..$#_]);
+        }
+my ($self, $headings, $width, $name, $heading) = @_;
+$self = { 'sorter' => [ map { \&default_sorter } @$headings ] };
+bless($self);
+$self->set_headings($headings);
+$self->set_name($name) if (defined($name));
+$self->set_width($width) if (defined($width));
+$self->set_heading($heading) if (defined($heading));
+$self->set_all_sortable(1);
+$self->set_paging(1);
+return $self;
+}
+
+=head2 add_row(&fields)
+Adds a row to the table. Each element in the row can be either an input of some
+kind, or a piece of text.
+=cut
+sub add_row
+{
+my ($self, $fields) = @_;
+push(@{$self->{'rows'}}, $fields);
+}
+
+=head2 html()
+Returns the HTML for this table. The actual ordering may depend upon sort headers
+clicked by the user. The rows to display may be limited by the page size.
+=cut
+sub html
+{
+my ($self) = @_;
+my @srows = @{$self->{'rows'}};
+my $thisurl = $self->{'form'}->{'page'}->get_myurl();
+my $name = $self->get_name();
+my $rv;
+
+# Add the heading
+if ($self->get_heading()) {
+       $rv .= &ui_subheading($self->get_heading())."\n";
+       }
+
+my $sm = $self->get_searchmax();
+if (defined($sm) && @srows > $sm) {
+       # Too many rows to show .. add a search form. This will need to close
+       # the parent form, and then re-open it after the search form, as nested
+       # forms aren't allowed!
+       if ($self->get_searchmsg()) {
+               $rv .= $self->get_searchmsg()."<br>\n";
+               }
+
+       my $form = new Webmin::Form($thisurl, "get");
+       $form->set_input($self->{'form'}->{'in'});
+       my $section = new Webmin::Section(undef, 2);
+       $form->add_section($section);
+
+       my $col = new Webmin::Select("ui_searchcol_".$name, undef);
+       my $i = 0;
+       foreach my $h (@{$self->get_headings()}) {
+               if ($self->{'sortable'}->[$i]) {
+                       $col->add_option($i, $h);
+                       }
+               $i++;
+               }
+       $section->add_input($text{'ui_searchcol'}, $col);
+
+       my $for = new Webmin::Textbox("ui_searchfor_".$name, undef, 30);
+       $section->add_input($text{'ui_searchfor'}, $for);
+
+       $rv .= $section->html();
+       my $url = $self->make_url(undef, undef, undef, undef, 1);
+       my $jsb = new Webmin::JavascriptButton($text{'ui_searchok'},
+                       "window.location = '$url'+'&'+'ui_searchfor_${name}'+'='+escape(form.ui_searchfor_${name}.value)+'&'+'ui_searchcol_${name}'+'='+escape(form.ui_searchcol_${name}.selectedIndex)");
+       $rv .= $jsb->html();
+       $rv .= "<br>\n";
+
+       # Limit records to current search
+       if (defined($col->get_value())) {
+               my $sf = $for->get_value();
+               @srows = grep { $_->[$col->get_value()] =~ /\Q$sf\E/i } @srows;
+               }
+       else {
+               @srows = ( );
+               }
+       }
+
+# Prepare the selector
+my $selc = $self->{'selectcolumn'};
+my $seli = $self->{'selectinput'};
+my %selmap;
+if (defined($selc)) {
+       my $i = 0;
+       foreach my $r (@srows) {
+               $selmap{$r,$selc} = $seli->one_html($i);
+               $i++;
+               }
+       }
+
+# Sort the rows
+my ($sortcol, $sortdir) = $self->get_sortcolumn();
+if (defined($sortcol)) {
+       my $func = $self->{'sorter'}->[$sortcol];
+       @srows = sort { my $so = &$func($a->[$sortcol], $b->[$sortcol], $sortcol);
+                       $sortdir ? -$so : $so } @srows;
+       }
+
+# Build the td attributes
+my @tds = map { "valign=top" } @{$self->{'headings'}};
+if ($self->{'widths'}) {
+       my $i = 0;
+       foreach my $w (@{$self->{'widths'}}) {
+               $tds[$i++] .= " width=$w";
+               }
+       }
+if ($self->{'aligns'}) {
+       my $i = 0;
+       foreach my $a (@{$self->{'aligns'}}) {
+               $tds[$i++] .= " align=$a";
+               }
+       }
+
+# Find the page we want
+my $page = $self->get_pagepos();
+my ($start, $end, $origsize);
+if ($self->get_paging() && $self->get_pagesize()) {
+       # Restrict view to rows within some page
+       $start = $self->get_pagesize()*$page;
+       $end = $self->get_pagesize()*($page+1) - 1;
+       if ($start >= @srows) {
+               # Gone off end!
+               $start = 0;
+               $end = $self->get_pagesize()-1;
+               }
+       if ($end >= @srows) {
+               # End is too far
+               $end = @srows-1;
+               }
+       $origsize = scalar(@srows);
+       @srows = @srows[$start..$end];
+       }
+
+# Generate the headings, with sorters
+$thisurl .= $thisurl =~ /\?/ ? "&" : "?";
+my @sheadings;
+my $i = 0;
+foreach my $h (@{$self->get_headings()}) {
+       if ($self->{'sortable'}->[$i]) {
+               # Column can be sorted!
+               my $hh = "<table cellpadding=0 cellspacing=0 width=100%><tr>";
+               $hh .= "<td><b>$h</b></td> <td align=right>";
+               if (!defined($sortcol) || $sortcol != $i) {
+                       # Not sorting on this column .. show grey button
+                       my $url = $self->make_url($i, 0, undef, undef);
+                       $hh .= "<a href='$url'>".
+                              "<img src=/images/nosort.gif border=0></a>";
+                       }
+               else {
+                       # Sorting .. show button to switch mode
+                       my $notsort = !$sortdir;
+                       my $url = $self->make_url($i, $sortdir ? 0 : 1, undef, undef);
+                       $hh .= "<a href='$url'>".
+                              "<img src=/images/sort.gif border=0></a>";
+                       }
+               $hh .= "</td></tr></table>";
+               push(@sheadings, $hh);
+               }
+       else {
+               push(@sheadings, $h);
+               }
+       $i++;
+       }
+
+# Get any errors for inputs
+my @errs = map { $_->get_errors() } $self->list_inputs();
+if (@errs) {
+       foreach my $e (@errs) {
+               $rv .= "<font color=#ff0000>$e</font><br>\n";
+               }
+       }
+
+# Build links for top and bottom
+my $links;
+if (ref($seli) =~ /Checkboxes/) {
+       # Add select all/none links
+       my $formno = $self->{'form'}->get_formno();
+       $links .= &select_all_link($seli->get_name(), $formno,
+                                        $text{'ui_selall'})."\n";
+       $links .= &select_invert_link($seli->get_name(), $formno,
+                                           $text{'ui_selinv'})."\n";
+       $links .= "&nbsp;\n";
+       }
+foreach my $l (@{$self->{'links'}}) {
+       $links .= "<a href='$l->[0]'>$l->[1]</a>\n";
+       }
+$links .= "<br>" if ($links);
+
+# Build list of inputs for bottom
+my $inputs;
+foreach my $i (@{$self->{'inputs'}}) {
+       $inputs .= $i->html()."\n";
+       }
+$inputs .= "<br>" if ($inputs);
+
+# Create the pager
+if ($self->get_paging() && $origsize) {
+       my $lastpage = int(($origsize-1)/$self->get_pagesize());
+       $rv .= "<center>";
+       if ($page != 0) {
+               # Add start and left arrows
+               my $surl = $self->make_url(undef, undef, undef, 0);
+               $rv .= "<a href='$surl'><img src=/images/first.gif border=0 align=middle></a>\n";
+               my $lurl = $self->make_url(undef, undef, undef, $page-1);
+               $rv .= "<a href='$lurl'><img src=/images/left.gif border=0 align=middle></a>\n";
+               }
+       else {
+               # Start and left are disabled
+               $rv .= "<img src=/images/first-grey.gif border=0 align=middle>\n";
+               $rv .= "<img src=/images/left-grey.gif border=0 align=middle>\n";
+               }
+       $rv .= &text('ui_paging', $start+1, $end+1, $origsize);
+       if ($end < $origsize-1) {
+               # Add right and end arrows
+               my $rurl = $self->make_url(undef, undef, undef, $page+1);
+               $rv .= "<a href='$rurl'><img src=/images/right.gif border=0 align=middle></a>\n";
+               my $eurl = $self->make_url(undef, undef, undef, $lastpage);
+               $rv .= "<a href='$eurl'><img src=/images/last.gif border=0 align=middle></a>\n";
+               }
+       else {
+               # Right and end are disabled
+               $rv .= "<img src=/images/right-grey.gif border=0 align=middle>\n";
+               $rv .= "<img src=/images/last-grey.gif border=0 align=middle>\n";
+               }
+       $rv .= "</center>\n";
+       }
+
+# Create actual table
+if (@srows) {
+       $rv .= $links;
+       $rv .= &ui_columns_start(\@sheadings, $self->{'width'}, 0, \@tds);
+       foreach my $r (@srows) {
+               my @row;
+               for(my $i=0; $i<@$r || $i<@sheadings; $i++) {
+                       if (ref($r->[$i]) eq "ARRAY") {
+                               my $j = $r->[$i]->[0] &&
+                                       $r->[$i]->[0]->isa("Webmin::TableAction")
+                                       ? "&nbsp;|&nbsp;" : "&nbsp;";
+                               $row[$i] = $selmap{$r,$i}.
+                                 join($j, map { ref($_) ? $_->html() : $_ }
+                                                    @{$r->[$i]});
+                               }
+                       elsif (ref($r->[$i])) {
+                               $row[$i] = $selmap{$r,$i}.$r->[$i]->html();
+                               }
+                       else {
+                               $row[$i] = $selmap{$r,$i}.$r->[$i];
+                               }
+                       }
+               $rv .= &ui_columns_row(\@row, \@tds);
+               }
+       $rv .= &ui_columns_end();
+       }
+elsif ($self->{'emptymsg'}) {
+       $rv .= $self->{'emptymsg'}."<p>\n";
+       }
+$rv .= $links;
+$rv .= $inputs;
+return $rv;
+}
+
+=head2 set_form(form)
+Called by the Webmin::Form object when this table is added to it
+=cut
+sub set_form
+{
+my ($self, $form) = @_;
+$self->{'form'} = $form;
+foreach my $i ($self->list_inputs()) {
+       $i->set_form($form);
+       }
+}
+
+=head2 set_sorter(function, [column])
+Sets a function used for sorting fields. Will be called with two field values to
+compare, and a column number.
+=cut
+sub set_sorter
+{
+my ($self, $func, $col) = @_;
+if (defined($col)) {
+       $self->{'sorter'}->[$col] = $func;
+       }
+else {
+       $self->{'sorter'} = [ map { $func } @{$self->{'headings'}} ];
+       }
+}
+
+=head2 default_sorter(value1, value2, col)
+=cut
+sub default_sorter
+{
+my ($value1, $value2, $col) = @_;
+if (ref($value1) && $value1->isa("Webmin::TableAction")) {
+       $value1 = $value1->get_value();
+       $value2 = $value2->get_value();
+       }
+return lc($value1) cmp lc($value2);
+}
+
+=head2 numeric_sorter(value1, value2, col)
+=cut
+sub numeric_sorter
+{
+my ($value1, $value2, $col) = @_;
+return $value1 <=> $value2;
+}
+
+=head2 set_sortable(column, sortable?)
+Tells the table if some column should allow sorting or not. By default, all are.
+=cut
+sub set_sortable
+{
+my ($self, $col, $sortable) = @_;
+$self->{'sortable'}->[$col] = $sortable;
+}
+
+=head2 set_all_sortable(sortable?)
+Enabled or disables sorting for all columns
+=cut
+sub set_all_sortable
+{
+my ($self, $sortable) = @_;
+$self->{'sortable'} = [ map { $sortable } @{$self->{'headings'}} ];
+}
+
+=head2 get_sortcolumn()
+Returns the column to sort on and the order (1 for descending), or undef for none
+=cut
+sub get_sortcolumn
+{
+my ($self) = @_;
+my $in = $self->{'form'} ? $self->{'form'}->{'in'} : undef;
+my $name = $self->get_name();
+if ($in && defined($in->{"ui_sortcolumn_".$name})) {
+       return ( $in->{"ui_sortcolumn_".$name},
+                $in->{"ui_sortdir_".$name} );
+       }
+else {
+       return ( $self->{'sortcolumn'}, $self->{'sortdir'} );
+       }
+}
+
+=head2 set_sortcolumn(num, descending?)
+Sets the default column on which sorting will be done, unless overridden by
+the user.
+=cut
+sub set_sortcolumn
+{
+my ($self, $col, $desc) = @_;
+$self->{'sortcolumn'} = $col;
+$self->{'sortdir'} = $desc;
+}
+
+=head2 get_paging()
+Returns 1 if page-by-page display should be used
+=cut
+sub get_paging
+{
+my ($self) = @_;
+my $in = $self->{'form'} ? $self->{'form'}->{'in'} : undef;
+my $name = $self->get_name();
+if ($in && defined($in->{"ui_paging_".$name})) {
+       return ( $in->{"ui_paging_".$name} );
+       }
+else {
+       return ( $self->{'paging'} );
+       }
+}
+
+=head2 set_paging(paging?)
+Turns page-by-page display of the table on or off
+=cut
+sub set_paging
+{
+my ($self, $paging) = @_;
+$self->{'paging'} = $paging;
+}
+
+sub set_name
+{
+my ($self, $name) = @_;
+$self->{'name'} = $name;
+}
+
+=head2 get_name()
+Returns the name for indentifying this table in HTML
+=cut
+sub get_name
+{
+my ($self) = @_;
+if (defined($self->{'name'})) {
+       return $self->{'name'};
+       }
+elsif ($self->{'form'}) {
+       my $secs = $self->{'form'}->{'sections'};
+       for(my $i=0; $i<@$secs; $i++) {
+               return "table".$i if ($secs->[$i] eq $self);
+               }
+       }
+return "table";
+}
+
+sub set_headings
+{
+my ($self, $headings) = @_;
+$self->{'headings'} = $headings;
+}
+
+sub get_headings
+{
+my ($self) = @_;
+return $self->{'headings'};
+}
+
+=head2 set_selector(column, input)
+Takes a Webmin::Checkboxes or Webmin::Radios object, and uses it to add checkboxes
+in the specified column.
+=cut
+sub set_selector
+{
+my ($self, $col, $input) = @_;
+$self->{'selectcolumn'} = $col;
+$self->{'selectinput'} = $input;
+$input->set_form($form);
+}
+
+=head2 get_selector()
+Returns the UI element used for selecting rows
+=cut
+sub get_selector
+{
+my ($self) = @_;
+return wantarray ? ( $self->{'selectinput'}, $self->{'selectcolumn'} )
+                : $self->{'selectinput'};
+}
+
+=head2 set_widths(&widths)
+Given an array reference of widths (like 50 or 20%), uses them for the columns
+in the table.
+=cut
+sub set_widths
+{
+my ($self, $widths) = @_;
+$self->{'widths'} = $widths;
+}
+
+=head2 set_width([number|number%])
+Sets the width of this entire table. Can be called with 100%, 500 or undef to use
+the minimum possible width.
+=cut
+sub set_width
+{
+my ($self, $width) = @_;
+$self->{'width'} = $width;
+}
+
+=head2 set_aligns(&aligns)
+Given an array reference of horizontal alignments (like left or right), uses them
+for the columns in the table.
+=cut
+sub set_aligns
+{
+my ($self, $aligns) = @_;
+$self->{'aligns'} = $aligns;
+}
+
+=head2 validate()
+Validates all inputs, and returns a list of error messages
+=cut
+sub validate
+{
+my ($self) = @_;
+my $seli = $self->{'selectinput'};
+my @errs;
+if ($seli) {
+       push(@errs, map { [ $seli->get_name(), $_ ] } $seli->validate());
+       }
+foreach my $i ($self->list_inputs()) {
+       foreach my $e ($i->validate()) {
+               push(@errs, [ $i->get_name(), $e ]);
+               }
+       }
+return @errs;
+}
+
+=head2 get_value(input-name)
+Returns the value of the input with the given name.
+=cut
+sub get_value
+{
+my ($self, $name) = @_;
+if ($self->{'selectinput'} && $self->{'selectinput'}->get_name() eq $name) {
+       return $self->{'selectinput'}->get_value();
+       }
+foreach my $i ($self->list_inputs()) {
+       if ($i->get_name() eq $name) {
+               return $i->get_value();
+               }
+       }
+return undef;
+}
+
+=head2 list_inputs()
+Returns all inputs in all form sections
+=cut
+sub list_inputs
+{
+my ($self) = @_;
+my @rv = @{$self->{'inputs'}};
+push(@rv, $self->{'selectinput'}) if ($self->{'selectinput'});
+return @rv;
+}
+
+=head2 set_searchmax(num, [message])
+Sets the maximum number of table rows to display before showing a search form
+=cut
+sub set_searchmax
+{
+my ($self, $searchmax, $searchmsg) = @_;
+$self->{'searchmax'} = $searchmax;
+$self->{'searchmsg'} = $searchmsg;
+}
+
+sub get_searchmax
+{
+my ($self) = @_;
+return $self->{'searchmax'};
+}
+
+sub get_searchmsg
+{
+my ($self) = @_;
+return $self->{'searchmsg'};
+}
+
+=head2 add_link(link, message)
+Adds a link to the table, for example for adding a new entry
+=cut
+sub add_link
+{
+my ($self, $link, $msg) = @_;
+push(@{$self->{'links'}}, [ $link, $msg ]);
+}
+
+=head2 add_input(input)
+Adds some input to be displayed at the bottom of the table
+=cut
+sub add_input
+{
+my ($self, $input) = @_;
+push(@{$self->{'inputs'}}, $input);
+$input->set_form($self->{'form'});
+}
+
+=head2 set_emptymsg(message)
+Sets the message to display when the table is empty
+=cut
+sub set_emptymsg
+{
+my ($self, $emptymsg) = @_;
+$self->{'emptymsg'} = $emptymsg;
+}
+
+=head2 set_heading(text)
+Sets the heading text to appear above the table
+=cut
+sub set_heading
+{
+my ($self, $heading) = @_;
+$self->{'heading'} = $heading;
+}
+
+sub get_heading
+{
+my ($self) = @_;
+return $self->{'heading'};
+}
+
+=head2 set_pagesize(pagesize)
+Sets the size of a page. Set to 0 to turn off completely.
+=cut
+sub set_pagesize
+{
+my ($self, $pagesize) = @_;
+$self->{'pagesize'} = $pagesize;
+}
+
+=head2 get_pagesize()
+Returns the size of a page, or 0 if paging is turned off totally
+=cut
+sub get_pagesize
+{
+my ($self) = @_;
+return $self->{'pagesize'};
+}
+
+sub get_pagepos
+{
+my ($self) = @_;
+my $in = $self->{'form'} ? $self->{'form'}->{'in'} : undef;
+my $name = $self->get_name();
+if ($in && defined($in->{"ui_pagepos_".$name})) {
+       return ( $in->{"ui_pagepos_".$name} );
+       }
+else {
+       return ( $self->{'pagepos'} );
+       }
+}
+
+=head2 make_url(sortcol, sortdir, paging, page, [no-searchargs], [no-pagearg])
+Returns a link to this table's page, with the defaults for the various state
+fields overriden by the parameters (where defined)
+=cut
+sub make_url
+{
+my ($self, $newsortcol, $newsortdir, $newpaging, $newpagepos,
+    $nosearch, $nopage) = @_;
+my ($sortcol, $sortdir) = $self->get_sortcolumn();
+$sortcol = $newsortcol if (defined($newsortcol));
+$sortdir = $newsortdir if (defined($newsortdir));
+my $paging = $self->get_paging();
+$paging = $newpaging if (defined($newpaging));
+my $pagepos = $self->get_pagepos();
+$pagepos = $newpagepos if (defined($newpagepos));
+
+my $thisurl = $self->{'form'}->{'page'}->get_myurl();
+my $name = $self->get_name();
+$thisurl .= $thisurl =~ /\?/ ? "&" : "?";
+my $in = $self->{'form'}->{'in'};
+return "${thisurl}ui_sortcolumn_${name}=$sortcol".
+       "&ui_sortdir_${name}=$sortdir".
+       "&ui_paging_${name}=$paging".
+       ($nopage ? "" : "&ui_pagepos_${name}=$pagepos").
+       ($nosearch ? "" : "&ui_searchfor_${name}=".
+                        &urlize($in->{"ui_searchfor_${name}"}).
+                        "&ui_searchcol_${name}=".
+                        &urlize($in->{"ui_searchcol_${name}"}));
+}
+
+1;
+
diff --git a/Webmin/TableAction.pm b/Webmin/TableAction.pm
new file mode 100644 (file)
index 0000000..8c9d471
--- /dev/null
@@ -0,0 +1,92 @@
+package Webmin::TableAction;
+use WebminCore;
+
+=head2 new Webmin::TableAction(cgi, label, &args, disabled)
+An object of this class can be added to a table or properties object to create
+a link or action button of some kind.
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::TableAction::new) &&
+    caller() !~ /Webmin::Theme::TableAction/) {
+        return new Webmin::Theme::TableAction(@_[1..$#_]);
+        }
+my ($self, $cgi, $value, $args, $disabled) = @_;
+$self = { };
+bless($self);
+$self->set_value($value);
+$self->set_cgi($cgi);
+$self->set_args($args) if (defined($args));
+$self->set_disabled($disabled) if (defined($disabled));
+return $self;
+}
+
+sub html
+{
+my ($self) = @_;
+my $rv;
+if ($self->get_disabled()) {
+       $rv .= "<u><i>".$self->get_value()."</i></u>";
+       }
+else {
+       my $link = $self->get_cgi();
+       my $i = 0;
+       foreach my $a (@{$self->get_args()}) {
+               $link .= ($i++ ? "&" : "?");
+               $link .= &urlize($a->[0])."=".&urlize($a->[1]);
+               }
+       $rv .= "<a href='$link'>".$self->get_value()."</a>";
+       }
+return $rv;
+}
+
+sub set_value
+{
+my ($self, $value) = @_;
+$self->{'value'} = $value;
+}
+
+sub get_value
+{
+my ($self) = @_;
+return $self->{'value'};
+}
+
+sub set_cgi
+{
+my ($self, $cgi) = @_;
+$self->{'cgi'} = $cgi;
+}
+
+sub get_cgi
+{
+my ($self) = @_;
+return $self->{'cgi'};
+}
+
+sub set_args
+{
+my ($self, $args) = @_;
+$self->{'args'} = $args;
+}
+
+sub get_args
+{
+my ($self) = @_;
+return $self->{'args'};
+}
+
+sub set_disabled
+{
+my ($self, $disabled) = @_;
+$self->{'disabled'} = $disabled;
+}
+
+sub get_disabled
+{
+my ($self) = @_;
+return $self->{'disabled'};
+}
+
+1;
+
diff --git a/Webmin/Tabs.pm b/Webmin/Tabs.pm
new file mode 100644 (file)
index 0000000..01fb29a
--- /dev/null
@@ -0,0 +1,142 @@
+package Webmin::Tabs;
+use WebminCore;
+
+=head2 new Webmin::Tabs([&tabs])
+Displayed at the top of a page, to allow selection of various pages
+=cut
+sub new
+{
+my ($self, $tabs) = @_;
+if (defined(&Webmin::Theme::Tabs::new)) {
+        return new Webmin::Theme::Tabs(@_[1..$#_]);
+        }
+$self = { 'tabs' => [ ],
+         'tab' => 0 };
+bless($self);
+$self->set_tabs($tabs) if (defined($tabs));
+return $self;
+}
+
+=head2 add_tab(name, link)
+=cut
+sub add_tab
+{
+my ($self, $name, $link) = @_;
+push(@{$self->{'tabs'}}, [ $name, $link ]);
+}
+
+=head2 html()
+Returns the HTML for the top of the page
+=cut
+sub top_html
+{
+my ($self) = @_;
+my $rv;
+$rv .= "<table border=0 cellpadding=0 cellspacing=0 width=100% height=20><tr>";
+$rv .= "<td valign=bottom>";
+$rv .= "<table border=0 cellpadding=0 cellspacing=0 height=20><tr>";
+my $i = 0;
+my ($high, $low) = ("#cccccc", "#9999ff");
+my ($lowlc, $lowrc) = ( "/images/lc1.gif", "/images/rc1.gif" );
+my ($highlc, $highrc) = ( "/images/lc2.gif", "/images/rc2.gif" );
+foreach my $t (@{$self->get_tabs()}) {
+       if ($i == $self->get_tab()) {
+               # This is the selected tab
+               $rv .= "<td valign=top bgcolor=$high>".
+                      "<img src=$highlc alt=\"\"></td>";
+               if ($self->get_link()) {
+                       # Link
+                       $rv .= "<td bgcolor=$high>&nbsp;".
+                              "<a href=$t->[1]><b>$t->[0]</b></a>&nbsp;</td>";
+                       }
+               else {
+                       # Don't link
+                       $rv .= "<td bgcolor=$high>&nbsp;<b>$t->[0]</b>&nbsp;</td>";
+                       }
+               $rv .= "<td valign=top bgcolor=$high>".
+                      "<img src=$highrc alt=\"\"></td>\n";
+               }
+       else {
+               # Not selected
+               $rv .= "<td valign=top bgcolor=$low>".
+                      "<img src=$lowlc alt=\"\"></td>";
+               $rv .= "<td bgcolor=$low>&nbsp;".
+                      "<a href=$t->[1]><b>$t->[0]</b></a>&nbsp;</td>";
+               $rv .= "<td valign=top bgcolor=$low>".
+                      "<img src=$lowrc alt=\"\"></td>\n";
+               }
+       $i++;
+       if ($self->{'wrap'} && $i%$self->{'wrap'} == 0) {
+               # New row
+               $rv .= "</tr><tr>";
+               }
+       }
+$rv .= "</tr></table></td>\n";
+$rv .= "</tr></table>\n";
+$rv .= "<table border=1 cellpadding=10 cellspacing=0 width=100%><tr><td>\n";
+return $rv;
+}
+
+=head2 bottom_html()
+Returns the HTML for the bottom of the page
+=cut
+sub bottom_html
+{
+my ($self) = @_;
+my $rv = "</td></tr></table>\n";
+return $rv;
+}
+
+=head2 set_tab(number|link)
+Sets the tab that is currently highlighted
+=cut
+sub set_tab
+{
+my ($self, $tab) = @_;
+if ($tab =~ /^\d+$/) {
+       $self->{'tab'} = $tab;
+       }
+else {
+       for(my $i=0; $i<@{$self->{'tabs'}}; $i++) {
+               if ($self->{'tabs'}->[$i]->[1] eq $tab) {
+                       $self->{'tab'} = $i;
+                       }
+               }
+       }
+}
+
+sub get_tab
+{
+my ($self) = @_;
+return $self->{'tab'};
+}
+
+=head2 set_link(link)
+If called with a non-zero parameter, even the highlighted tab will be a link
+=cut
+sub set_link
+{
+my ($self, $link) = @_;
+$self->{'link'} = $link;
+}
+
+sub get_link
+{
+my ($self) = @_;
+return $self->{'link'};
+}
+
+sub set_tabs
+{
+my ($self, $tabs) = @_;
+$self->{'tabs'} = $tabs;
+}
+
+sub get_tabs
+{
+my ($self) = @_;
+return $self->{'tabs'};
+}
+
+1;
+
diff --git a/Webmin/Textarea.pm b/Webmin/Textarea.pm
new file mode 100644 (file)
index 0000000..31af918
--- /dev/null
@@ -0,0 +1,122 @@
+package Webmin::Textarea;
+use Webmin::Input;
+use WebminCore;
+@ISA = ( "Webmin::Input" );
+
+=head2 new Webmin::Textarea(name, value, rows, cols, [wrap], [disabled])
+Create a new text box, with the given size
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::Textarea::new)) {
+        return new Webmin::Theme::Textarea(@_[1..$#_]);
+        }
+my ($self, $name, $value, $rows, $cols, $wrap, $disabled) = @_;
+$self = { };
+bless($self);
+$self->set_name($name);
+$self->set_value($value);
+$self->set_rows($rows);
+$self->set_cols($cols);
+$self->set_disabled($disabled);
+return $self;
+}
+
+=head2 html()
+Returns the HTML for this text area
+=cut
+sub html
+{
+my ($self) = @_;
+return &ui_textarea($self->get_name(), $self->get_value(),
+                         $self->get_rows(), $self->get_cols(),
+                         $self->get_wrap(), $self->get_disabled());
+}
+
+sub set_rows
+{
+my ($self, $rows) = @_;
+$self->{'rows'} = $rows;
+}
+
+sub get_rows
+{
+my ($self) = @_;
+return $self->{'rows'};
+}
+
+sub set_cols
+{
+my ($self, $cols) = @_;
+$self->{'cols'} = $cols;
+}
+
+sub get_cols
+{
+my ($self) = @_;
+return $self->{'cols'};
+}
+
+sub set_wrap
+{
+my ($self, $wrap) = @_;
+$self->{'wrap'} = $wrap;
+}
+
+sub get_wrap
+{
+my ($self) = @_;
+return $self->{'wrap'};
+}
+
+sub set_validation_func
+{
+my ($self, $func) = @_;
+$self->{'validation_func'} = $func;
+}
+
+=head2 set_validation_regexp(regexp, message)
+=cut
+sub set_validation_regexp
+{
+my ($self, $regexp, $message) = @_;
+$self->{'validation_regexp'} = $regexp;
+$self->{'validation_message'} = $message;
+}
+
+=head2 validate()
+Returns a list of error messages for this field
+=cut
+sub validate
+{
+my ($self) = @_;
+my $value = $self->get_value();
+if ($self->{'mandatory'} && $value eq '') {
+       return ( $self->{'mandmesg'} || $text{'ui_mandatory'} );
+       }
+if ($self->{'validation_func'}) {
+       my $err = &{$self->{'validation_func'}}($value, $self->{'name'},
+                                               $self->{'form'});
+       return ( $err ) if ($err);
+       }
+if ($self->{'validation_regexp'}) {
+       if ($value !~ /$self->{'validation_regexp'}/) {
+               return ( $self->{'validation_message'} );
+               }
+       }
+return ( );
+}
+
+=head2 get_value()
+Returns the value, without any \r characters
+=cut
+sub get_value
+{
+my ($self) = @_;
+my $rv = Webmin::Input::get_value($self);
+$rv =~ s/\r//g;
+return $rv;
+}
+
+1;
+
diff --git a/Webmin/Textbox.pm b/Webmin/Textbox.pm
new file mode 100644 (file)
index 0000000..55c1fb2
--- /dev/null
@@ -0,0 +1,80 @@
+package Webmin::Textbox;
+use Webmin::Input;
+use WebminCore;
+@ISA = ( "Webmin::Input" );
+
+=head2 new Webmin::Textbox(name, value, [size], [disabled])
+Create a new text input field
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::Textbox::new)) {
+        return new Webmin::Theme::Textbox(@_[1..$#_]);
+        }
+my ($self, $name, $value, $size, $disabled) = @_;
+$self = { 'size' => 30 };
+bless($self);
+$self->{'name'} = $name;
+$self->{'value'} = $value;
+$self->{'size'} = $size if ($size);
+$self->set_disabled($disabled) if (defined($disabled));
+return $self;
+}
+
+=head2 html()
+Returns the HTML for this text input
+=cut
+sub html
+{
+my ($self) = @_;
+return &ui_textbox($self->get_name(), $self->get_value(),
+                        $self->{'size'},
+                        $self->{'$disabled'});
+}
+
+sub set_size
+{
+my ($self, $size) = @_;
+$self->{'size'} = $size;
+}
+
+sub set_validation_func
+{
+my ($self, $func) = @_;
+$self->{'validation_func'} = $func;
+}
+
+=head2 set_validation_regexp(regexp, message)
+=cut
+sub set_validation_regexp
+{
+my ($self, $regexp, $message) = @_;
+$self->{'validation_regexp'} = $regexp;
+$self->{'validation_message'} = $message;
+}
+
+=head2 validate()
+Returns a list of error messages for this field
+=cut
+sub validate
+{
+my ($self) = @_;
+my $value = $self->get_value();
+if ($self->{'mandatory'} && $value eq '') {
+       return ( $self->{'mandmesg'} || $text{'ui_mandatory'} );
+       }
+if ($self->{'validation_func'}) {
+       my $err = &{$self->{'validation_func'}}($value, $self->{'name'},
+                                               $self->{'form'});
+       return ( $err ) if ($err);
+       }
+if ($self->{'validation_regexp'}) {
+       if ($value !~ /$self->{'validation_regexp'}/) {
+               return ( $self->{'validation_message'} );
+               }
+       }
+return ( );
+}
+
+1;
+
diff --git a/Webmin/Time.pm b/Webmin/Time.pm
new file mode 100644 (file)
index 0000000..b299b49
--- /dev/null
@@ -0,0 +1,168 @@
+package Webmin::Time;
+use Webmin::Input;
+use Time::Local;
+use WebminCore;
+@ISA = ( "Webmin::Input" );
+
+=head2 new Webmin::Time(name, time, [disabled])
+Create a new field for selecting a time
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::Time::new)) {
+        return new Webmin::Theme::Time(@_[1..$#_]);
+        }
+my ($self, $name, $value, $disabled) = @_;
+bless($self = { });
+$self->set_name($name);
+$self->set_value($value);
+$self->set_disabled($disabled) if (defined($disabled));
+return $self;
+}
+
+=head2 html()
+Returns the HTML for the time chooser
+=cut
+sub html
+{
+my ($self) = @_;
+my $rv;
+my $val = $self->get_value();
+my $hour = ($val/3600) % 24;
+my $min = ($val/60) % 60;
+my $sec = ($val/1) % 60;
+my $name = $self->get_name();
+$rv .= &ui_textbox("hour_".$name, pad2($hour), 2,$self->get_disabled()).":".
+       &ui_textbox("min_".$name, pad2($min), 2, $self->get_disabled()).":".
+       &ui_textbox("sec_".$name, pad2($sec), 2, $self->get_disabled());
+return $rv;
+}
+
+sub pad2
+{
+return $_[0] < 10 ? "0".$_[0] : $_[0];
+}
+
+sub set_value
+{
+my ($self, $value) = @_;
+$self->{'value'} = timegm(localtime($value));
+}
+
+=head2 get_value()
+Returns the date as a Unix time number (for 1st jan 1970)
+=cut
+sub get_value
+{
+my ($self) = @_;
+my $in = $self->{'form'} ? $self->{'form'}->{'in'} : undef;
+if ($in && defined($in->{"hour_".$self->{'name'}})) {
+       my $rv = $self->to_time($in);
+       return defined($rv) ? $rv : $self->{'value'};
+       }
+elsif ($in && defined($in->{"ui_value_".$self->{'name'}})) {
+       return $in->{"ui_value_".$self->{'name'}};
+       }
+else {
+       return $self->{'value'};
+       }
+}
+
+sub to_time
+{
+my ($self, $in) = @_;
+my $hour = $in->{"hour_".$self->{'name'}};
+return undef if ($hour !~ /^\d+$/ || $hour < 0 || $hour > 23);
+my $min = $in->{"min_".$self->{'name'}};
+return undef if ($min !~ /^\d+$/ || $min < 0 || $min > 59);
+my $sec = $in->{"sec_".$self->{'name'}};
+return undef if ($sec !~ /^\d+$/ || $sec < 0 || $sec > 59);
+return $hour*60*60 + $min*60 + $sec;
+}
+
+sub set_validation_func
+{
+my ($self, $func) = @_;
+$self->{'validation_func'} = $func;
+}
+
+=head2 validate()
+Ensures that the date is valid
+=cut
+sub validate
+{
+my ($self) = @_;
+my $tm = $self->to_time($self->{'form'}->{'in'});
+if (!defined($tm)) {
+       return ( $text{'ui_etime'} );
+       }
+if ($self->{'validation_func'}) {
+       my $err = &{$self->{'validation_func'}}($self->get_value(),
+                                               $self->{'name'},
+                                               $self->{'form'});
+       return ( $err ) if ($err);
+       }
+return ( );
+}
+
+=head2 set_auto(auto?)
+If set to 1, the time will be automatically incremented by Javascript
+=cut
+sub set_auto
+{
+my ($self, $auto) = @_;
+$self->{'auto'} = $auto;
+if ($auto) {
+       # XXX incorrect!!
+       my $formno = $self->{'form'}->get_formno();
+       $self->{'form'}->add_onload("F=[0]; timeInit(F); setTimeout(\"timeUpdate(F)\", 5000)");
+       my $as = $autoscript;
+       $as =~ s/NAME/$self->{'name'}/g;
+       $self->{'form'}->add_script($as);
+       }
+}
+
+$autoscript = <<EOF;
+function timeInit(F) {
+        secs = new Array();
+        mins = new Array();
+        hours = new Array();
+        for(i=0; i<F.length; i++){
+                secs[i]  = document.forms[F[i]].sec_NAME;
+                mins[i]  = document.forms[F[i]].min_NAME;
+                hours[i] = document.forms[F[i]].hour_NAME;
+               }
+}
+function timeUpdate(F) {
+        for(i=0; i<F.length; i++){
+                s = parseInt(secs[i].value);
+                s = s ? s : 0;
+                s = s+5;
+                if( s>59 ){
+                        s -= 60;
+                        m = parseInt(mins[i].value);
+                        m= m ? m : 0;
+                        m+=1;
+                        if( m>59 ){
+                                m -= 60;
+                                h = parseInt(hours[i].value);
+                                h = h ? h : 0;
+                                h+=1;
+                                if( h>23 ){
+                                        h -= 24;
+                                }
+                                hours[i].value  = packNum(h);
+                        }
+                        mins[i].value  = packNum(m);
+                }
+        secs[i].value = packNum(s);
+       }
+        setTimeout('timeUpdate(F)', 5000);
+}
+function packNum(t) {
+        return (t < 10 ? '0'+t : t);
+}
+EOF
+
+1;
+
diff --git a/Webmin/TitleList.pm b/Webmin/TitleList.pm
new file mode 100644 (file)
index 0000000..f6409bc
--- /dev/null
@@ -0,0 +1,101 @@
+package Webmin::TitleList;
+use WebminCore;
+
+=head2 new Webmin::TitleList(title, &links, [alt-text])
+Generates a title with a list of links under it
+=cut
+sub new
+{
+my ($self, $title, $links, $alt) = @_;
+if (defined(&Webmin::Theme::TitleList::new)) {
+        return new Webmin::Theme::TitleList(@_[1..$#_]);
+        }
+$self = { };
+bless($self);
+$self->set_title($title);
+$self->set_links($links);
+$self->set_alt($alt) if (defined($alt));
+return $self;
+}
+
+=head2 html()
+Returns the list
+=cut
+sub html
+{
+my ($self) = @_;
+my $rv;
+if (defined(&ui_subheading)) {
+       $rv .= &ui_subheading($self->get_title());
+       }
+else {
+       $rv .= "<h3>".$self->get_title()."</h3>\n";
+       }
+$rv .= "<hr>\n";
+foreach my $l (@{$self->get_links()}) {
+       if ($l->[1]) {
+               $rv .= "<a href='$l->[1]'>$l->[0]</a><br>\n";
+               }
+       else {
+               $rv .= $l->[0]."<br>\n";
+               }
+       }
+return $rv;
+}
+
+sub set_title
+{
+my ($self, $title) = @_;
+$self->{'title'} = $title;
+}
+
+sub get_title
+{
+my ($self) = @_;
+return $self->{'title'};
+}
+
+sub set_links
+{
+my ($self, $links) = @_;
+$self->{'links'} = $links;
+}
+
+sub get_links
+{
+my ($self) = @_;
+return $self->{'links'};
+}
+
+sub set_alt
+{
+my ($self, $alt) = @_;
+$self->{'alt'} = $alt;
+}
+
+sub get_alt
+{
+my ($self) = @_;
+return $self->{'alt'};
+}
+
+=head2 add_link(name, link)
+Adds a link to be displayed in the list
+=cut
+sub add_link
+{
+my ($self, $name, $link) = @_;
+push(@{$self->{'links'}}, [ $name, $link ]);
+}
+
+=head2 set_page(Webmin::Page)
+Called when this menu is added to a page
+=cut
+sub set_page
+{
+my ($self, $page) = @_;
+$self->{'page'} = $page;
+}
+
+1;
+
diff --git a/Webmin/Upload.pm b/Webmin/Upload.pm
new file mode 100644 (file)
index 0000000..87c019d
--- /dev/null
@@ -0,0 +1,77 @@
+package Webmin::Upload;
+use Webmin::Input;
+use WebminCore;
+@ISA = ( "Webmin::Input" );
+
+=head2 new Webmin::Upload(name, [size])
+Create a new file upload field
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::Upload::new)) {
+        return new Webmin::Theme::Upload(@_[1..$#_]);
+        }
+my ($self, $name, $size) = @_;
+$self = { 'size' => 30 };
+bless($self);
+$self->{'name'} = $name;
+$self->{'size'} = $size if ($size);
+return $self;
+}
+
+=head2 html()
+Returns the HTML for this text input
+=cut
+sub html
+{
+my ($self) = @_;
+return &ui_upload($self->get_name(), $self->{'size'},
+                       $self->{'$disabled'});
+}
+
+sub set_size
+{
+my ($self, $size) = @_;
+$self->{'size'} = $size;
+}
+
+sub set_validation_func
+{
+my ($self, $func) = @_;
+$self->{'validation_func'} = $func;
+}
+
+=head2 set_validation_regexp(regexp, message)
+=cut
+sub set_validation_regexp
+{
+my ($self, $regexp, $message) = @_;
+$self->{'validation_regexp'} = $regexp;
+$self->{'validation_message'} = $message;
+}
+
+=head2 validate()
+Returns a list of error messages for this field
+=cut
+sub validate
+{
+my ($self) = @_;
+my $value = $self->get_value();
+if ($self->{'mandatory'} && $value eq '') {
+       return ( $self->{'mandmesg'} || $text{'ui_mandatory'} );
+       }
+if ($self->{'validation_func'}) {
+       my $err = &{$self->{'validation_func'}}($value, $self->{'name'},
+                                               $self->{'in'});
+       return ( $err ) if ($err);
+       }
+if ($self->{'validation_regexp'}) {
+       if ($value !~ /$self->{'validation_regexp'}/) {
+               return ( $self->{'validation_message'} );
+               }
+       }
+return ( );
+}
+
+1;
+
diff --git a/Webmin/User.pm b/Webmin/User.pm
new file mode 100644 (file)
index 0000000..d9b63c9
--- /dev/null
@@ -0,0 +1,57 @@
+package Webmin::User;
+use Webmin::Textbox;
+use WebminCore;
+@ISA = ( "Webmin::Textbox" );
+
+=head2 new Webmin::User(name, value, [multiple], [disabled])
+A text box for entering or selecting one or many Unix usernames
+=cut
+sub new
+{
+if (defined(&Webmin::Theme::User::new)) {
+        return new Webmin::Theme::User(@_[1..$#_]);
+        }
+my ($self, $name, $value, $multiple, $disabled) = @_;
+$self = new Webmin::Textbox($name, $value, $multiple ? 40 : 15, $disabled);
+bless($self);
+$self->set_multiple($multiple);
+return $self;
+}
+
+=head2 html()
+Returns the HTML for this user input
+=cut
+sub html
+{
+my ($self) = @_;
+my $rv = Webmin::Textbox::html($self);
+my $name = $self->get_name();
+my $multiple = $self->get_multiple();
+local $w = $multiple ? 500 : 300;
+$rv .= "&nbsp;<input type=button name=${name}_button onClick='ifield = form.$name; chooser = window.open(\"$gconfig{'webprefix'}/user_chooser.cgi?multi=$multiple&user=\"+escape(ifield.value), \"chooser\", \"toolbar=no,menubar=no,scrollbars=yes,width=$w,height=200\"); chooser.ifield = ifield; window.ifield = ifield' value=\"...\">\n";
+return $rv;
+}
+
+sub set_multiple
+{
+my ($self, $multiple) = @_;
+$self->{'multiple'} = $multiple;
+}
+
+sub get_multiple
+{
+my ($self) = @_;
+return $self->{'multiple'};
+}
+
+=head2 get_input_names()
+Returns the actual names of all HTML elements that make up this input
+=cut
+sub get_input_names
+{
+my ($self) = @_;
+return ( $self->{'name'}, $self->{'name'}."_button" );
+}
+
+1;
+