1# custom-lib.pl 2# Functions for storing custom commands 3 4BEGIN { push(@INC, ".."); }; 5use WebminCore; 6&init_config(); 7%access = &get_module_acl(); 8 9# list_commands() 10# Returns a list of all custom commands 11sub list_commands 12{ 13local (@rv, $f); 14local $mcd = $module_info{'usermin'} ? $config{'webmin_config'} 15 : $module_config_directory; 16opendir(DIR, $mcd); 17while($f = readdir(DIR)) { 18 local %cmd; 19 if ($f =~ /^(\d+)\.cmd$/) { 20 # Read custom-command file 21 $cmd{'file'} = "$mcd/$f"; 22 $cmd{'id'} = $1; 23 open(FILE, "<".$cmd{'file'}); 24 chop($cmd{'cmd'} = <FILE>); 25 chop($cmd{'desc'} = <FILE>); 26 local @o = split(/\s+/, <FILE>); 27 $cmd{'user'} = $o[0]; 28 $cmd{'raw'} = int($o[1]); 29 $cmd{'su'} = int($o[2]); 30 $cmd{'order'} = int($o[3]); 31 $cmd{'noshow'} = int($o[4]); 32 $cmd{'usermin'} = int($o[5]); 33 $cmd{'timeout'} = int($o[6]); 34 $cmd{'clear'} = int($o[7]); 35 $cmd{'format'} = $o[8] eq '-' ? undef : $o[8]; 36 } 37 elsif ($f =~ /^(\d+)\.edit$/) { 38 # Read file-editor file 39 $cmd{'file'} = "$mcd/$f"; 40 $cmd{'id'} = $1; 41 open(FILE, "<".$cmd{'file'}); 42 chop($cmd{'edit'} = <FILE>); 43 chop($cmd{'desc'} = <FILE>); 44 chop($cmd{'user'} = <FILE>); 45 chop($cmd{'group'} = <FILE>); 46 chop($cmd{'perms'} = <FILE>); 47 chop($cmd{'before'} = <FILE>); 48 chop($cmd{'after'} = <FILE>); 49 chop($cmd{'order'} = <FILE>); 50 $cmd{'order'} = int($cmd{'order'}); 51 chop($cmd{'usermin'} = <FILE>); 52 chop($cmd{'envs'} = <FILE>); 53 chop($cmd{'beforeedit'} = <FILE>); 54 } 55 elsif ($f =~ /^(\d+)\.sql$/) { 56 # Read SQL file 57 $cmd{'file'} = "$mcd/$f"; 58 $cmd{'id'} = $1; 59 open(FILE, "<".$cmd{'file'}); 60 chop($cmd{'desc'} = <FILE>); 61 chop($cmd{'type'} = <FILE>); 62 chop($cmd{'db'} = <FILE>); 63 chop($cmd{'user'} = <FILE>); 64 chop($cmd{'pass'} = <FILE>); 65 chop($cmd{'host'} = <FILE>); 66 chop($cmd{'sql'} = <FILE>); 67 $cmd{'sql'} =~ s/\t/\n/g; 68 chop($cmd{'order'} = <FILE>); 69 } 70 if (%cmd) { 71 # Read common stuff 72 while(<FILE>) { 73 s/\r|\n//g; 74 local @a = split(/:/, $_, 5); 75 local ($quote, $must) = split(/,/, $a[3]); 76 push(@{$cmd{'args'}}, { 'name' => $a[0], 77 'type' => $a[1], 78 'opts' => $a[2], 79 'quote' => int($quote), 80 'must' => int($must), 81 'desc' => $a[4] }); 82 } 83 close(FILE); 84 $cmd{'index'} = scalar(@rv); 85 open(HTML, "<$mcd/$cmd{'id'}.html"); 86 while(<HTML>) { 87 $cmd{'html'} .= $_; 88 } 89 close(HTML); 90 91 # Read cluster hosts file 92 open(CLUSTER, "<$mcd/$cmd{'id'}.hosts"); 93 while(<CLUSTER>) { 94 s/\r|\n//g; 95 push(@{$cmd{'hosts'}}, $_); 96 } 97 close(CLUSTER); 98 99 push(@rv, \%cmd); 100 } 101 } 102closedir(DIR); 103return @rv; 104} 105 106# sort_commands(&command, ...) 107# Sorts a list of custom commands by the user-defined order 108sub sort_commands 109{ 110local @cust = @_; 111if ($config{'sort'}) { 112 @cust = sort { lc($a->{$config{'sort'}}) cmp 113 lc($b->{$config{'sort'}}) } @cust; 114 } 115else { 116 @cust = sort { local $o = $b->{'order'} <=> $a->{'order'}; 117 $o ? $o : $a->{'id'} <=> $b->{'id'} } @cust; 118 } 119return @cust; 120} 121 122# get_command(id) 123# Returns the command with some ID 124sub get_command 125{ 126local ($id, $idx) = @_; 127local @cmds = &list_commands(); 128local $cmd; 129if ($id) { 130 ($cmd) = grep { $_->{'id'} eq $id } &list_commands(); 131 } 132else { 133 $cmd = $cmds[$idx]; 134 } 135return $cmd; 136} 137 138# save_command(&command) 139sub save_command 140{ 141local $c = $_[0]; 142if ($c->{'edit'}) { 143 # Save a file editor 144 &open_lock_tempfile(FILE, ">$module_config_directory/$c->{'id'}.edit"); 145 &print_tempfile(FILE, $c->{'edit'},"\n"); 146 &print_tempfile(FILE, $c->{'desc'},"\n"); 147 &print_tempfile(FILE, $c->{'user'},"\n"); 148 &print_tempfile(FILE, $c->{'group'},"\n"); 149 &print_tempfile(FILE, $c->{'perms'},"\n"); 150 &print_tempfile(FILE, $c->{'before'},"\n"); 151 &print_tempfile(FILE, $c->{'after'},"\n"); 152 &print_tempfile(FILE, $c->{'order'},"\n"); 153 &print_tempfile(FILE, $c->{'usermin'},"\n"); 154 &print_tempfile(FILE, $c->{'envs'},"\n"); 155 &print_tempfile(FILE, $c->{'beforeedit'},"\n"); 156 } 157elsif ($c->{'sql'}) { 158 # Save an SQL command 159 &open_lock_tempfile(FILE, ">$module_config_directory/$c->{'id'}.sql"); 160 &print_tempfile(FILE, $c->{'desc'},"\n"); 161 &print_tempfile(FILE, $c->{'type'},"\n"); 162 &print_tempfile(FILE, $c->{'db'},"\n"); 163 &print_tempfile(FILE, $c->{'user'},"\n"); 164 &print_tempfile(FILE, $c->{'pass'},"\n"); 165 &print_tempfile(FILE, $c->{'host'},"\n"); 166 local $sql = $c->{'sql'}; 167 $sql =~ s/\n/\t/g; 168 &print_tempfile(FILE, $sql,"\n"); 169 &print_tempfile(FILE, $c->{'order'},"\n"); 170 } 171else { 172 # Save a custom command 173 &open_lock_tempfile(FILE, ">$module_config_directory/$c->{'id'}.cmd"); 174 &print_tempfile(FILE, $c->{'cmd'},"\n"); 175 &print_tempfile(FILE, $c->{'desc'},"\n"); 176 &print_tempfile(FILE, 177 $c->{'user'}," ",int($c->{'raw'})," ",int($c->{'su'})," ", 178 int($c->{'order'})," ",int($c->{'noshow'})," ", 179 int($c->{'usermin'})," ",int($c->{'timeout'})," ", 180 int($c->{'clear'})," ",($c->{'format'} || "-"),"\n"); 181 } 182 183 184# Save parameters 185foreach $a (@{$c->{'args'}}) { 186 &print_tempfile(FILE, $a->{'name'},":",$a->{'type'},":", 187 $a->{'opts'},":",int($a->{'quote'}),",",int($a->{'must'}),":", 188 $a->{'desc'},"\n"); 189 } 190&close_tempfile(FILE); 191 192# Save HTML description file 193&lock_file("$module_config_directory/$c->{'id'}.html"); 194if ($cmd->{'html'}) { 195 &open_tempfile(HTML, ">$module_config_directory/$c->{'id'}.html"); 196 &print_tempfile(HTML, $cmd->{'html'}); 197 &close_tempfile(HTML); 198 } 199else { 200 unlink("$module_config_directory/$c->{'id'}.html"); 201 } 202&unlock_file("$module_config_directory/$c->{'id'}.html"); 203 204# Save cluster hosts 205&lock_file("$module_config_directory/$c->{'id'}.hosts"); 206if (@{$cmd->{'hosts'}}) { 207 &open_tempfile(CLUSTER, ">$module_config_directory/$c->{'id'}.hosts"); 208 foreach my $h (@{$cmd->{'hosts'}}) { 209 &print_tempfile(CLUSTER, "$h\n"); 210 } 211 &close_tempfile(CLUSTER); 212 } 213else { 214 unlink("$module_config_directory/$c->{'id'}.hosts"); 215 } 216&unlock_file("$module_config_directory/$c->{'id'}.hosts"); 217} 218 219# delete_command(&command) 220sub delete_command 221{ 222local $f = "$module_config_directory/$_[0]->{'id'}". 223 ($_[0]->{'edit'} ? ".edit" : $_[0]->{'sql'} ? ".sql" : ".cmd"); 224&lock_file($f); 225unlink($f); 226&unlock_file($f); 227 228# Delete HTML file 229local $hf = "$module_config_directory/$_[0]->{'id'}.html"; 230if (-r $hf) { 231 &lock_file($hf); 232 unlink($hf); 233 &unlock_file($hf); 234 } 235 236# Delete cluster file 237local $cf = "$module_config_directory/$_[0]->{'id'}.hosts"; 238if (-r $cf) { 239 &lock_file($cf); 240 unlink($cf); 241 &unlock_file($cf); 242 } 243} 244 245sub can_run_command 246{ 247if ($module_info{'usermin'}) { 248 # Only modules marked as for Usermin are considered 249 return 0 if (!$_[0]->{'usermin'}); 250 251 # Check detailed access control list (if any) 252 return 1 if (!$config{'access'}); 253 local @uinfo = @remote_user_info; 254 @uinfo = getpwnam($remote_user) if (!@uinfo); 255 local $l; 256 foreach $l (split(/\t/, $config{'access'})) { 257 if ($l =~ /^(\S+):\s*(.*)$/) { 258 local ($user, $ids) = ($1, $2); 259 local $applies; 260 if ($user =~ /^\@(.*)$/) { 261 # Check if user is in group 262 local @ginfo = getgrnam($1); 263 $applies++ 264 if (@ginfo && ($ginfo[2] == $uinfo[3] || 265 &indexof($remote_user, 266 split(/\s+/, $ginfo[3])) >= 0)); 267 } 268 elsif ($user eq $remote_user || $user eq "*") { 269 $applies++; 270 } 271 if ($applies) { 272 # Rule is for this user - check list 273 local @ids = split(/\s+/, $ids); 274 local $d; 275 foreach $d (@ids) { 276 return 1 if ($d eq '*' || 277 $_[0]->{'id'} eq $d); 278 return 0 if ("!".$_[0]->{'id'} eq $d); 279 } 280 return 0; 281 } 282 } 283 } 284 return 0; 285 } 286else { 287 # Just use Webmin user's list of databases 288 local $c; 289 local $found; 290 return 1 if ($access{'cmds'} eq '*'); 291 local @cmds = split(/\s+/, $access{'cmds'}); 292 foreach $c (@cmds) { 293 $found++ if ($c eq $_[0]->{'id'}); 294 } 295 return $cmds[0] eq '!' ? !$found : $found; 296 } 297} 298 299# read_opts_file(file) 300# Read the file containing possible menu options for a command 301sub read_opts_file 302{ 303local @rv; 304local $file = $_[0]; 305if ($file !~ /^\// && $file !~ /\|\s*$/) { 306 local @uinfo = getpwnam($remote_user); 307 if (@uinfo) { 308 $file = "$uinfo[7]/$file"; 309 } 310 } 311my $h; 312$h = "<" if ($file =~ /^\// && $file !~ /\|\s*$/); 313open(FILE, "$h".$file); 314while(<FILE>) { 315 s/\r|\n//g; 316 next if (/^#/); 317 if (/^"([^"]*)"\s+"([^"]*)"$/) { 318 push(@rv, [ $1, $2 ]); 319 } 320 elsif (/^"([^"]*)"$/) { 321 push(@rv, [ $1, $1 ]); 322 } 323 elsif (/^(\S+)\s+(\S.*)/) { 324 push(@rv, [ $1, $2 ]); 325 } 326 else { 327 push(@rv, [ $_, $_ ]); 328 } 329 } 330close(FILE); 331return @rv; 332} 333 334# show_params_inputs(&command, no-quote, editor-mode) 335sub show_params_inputs 336{ 337local ($cmd, $noquote, $editor) = @_; 338 339local $ptable = &ui_columns_start([ 340 $text{'edit_name'}, $text{'edit_desc'}, $text{'edit_type'}, 341 $noquote ? ( ) : ( $text{'edit_quote'} ), 342 $text{'edit_must'}, 343 ], 100, 0, undef, undef); 344local @a = (@{$cmd->{'args'}}, { }); 345for(my $i=0; $i<@a; $i++) { 346 local @cols; 347 push(@cols, &ui_textbox("name_$i", $a[$i]->{'name'}, 10)); 348 push(@cols, &ui_textbox("desc_$i", $a[$i]->{'desc'}, 40)); 349 local @opts; 350 for(my $j=0; $text{"edit_type$j"}; $j++) { 351 next if ($editor && 352 ($j == 7 || $j == 8 || $j == 10 || $j == 11)); 353 push(@opts, [ $j, $text{"edit_type$j"} ]); 354 } 355 push(@cols, &ui_select("type_$i", $a[$i]->{'type'}, \@opts)." ". 356 &ui_textbox("opts_$i", $a[$i]->{'opts'}, 40)); 357 if (!$noquote) { 358 push(@cols, &ui_yesno_radio("quote_$i", 359 int($a[$i]->{'quote'}))); 360 } 361 push(@cols, &ui_yesno_radio("must_$i", 362 int($a[$i]->{'must'}))); 363 $ptable .= &ui_columns_row(\@cols); 364 } 365 366$ptable .= &ui_columns_end(); 367print $ptable; 368} 369 370# parse_params_inputs(&command) 371sub parse_params_inputs 372{ 373local ($cmd) = @_; 374$cmd->{'args'} = [ ]; 375my ($i, $name); 376for($i=0; defined($name = $in{"name_$i"}); $i++) { 377 if ($name) { 378 if ($in{"type_$i"} == 9 || $in{"type_$i"} == 12 || 379 $in{"type_$i"} == 13 || $in{"type_$i"} == 14) { 380 $in{"opts_$i"} =~ /\|$/ || -r $in{"opts_$i"} || 381 &error(&text('save_eopts', $i+1)); 382 } 383 $in{"opts_$i"} =~ /:/ && &error(&text('save_eopts2', $i+1)); 384 push(@{$cmd->{'args'}}, { 'name' => $name, 385 'desc' => $in{"desc_$i"}, 386 'type' => $in{"type_$i"}, 387 'quote' => int($in{"quote_$i"}), 388 'must' => int($in{"must_$i"}), 389 'opts' => $in{"opts_$i"} }); 390 } 391 } 392} 393 394# set_parameter_envs(&command, command-str, &uinfo, [set-in], [skip-menu-check]) 395# Sets $ENV variables based on parameter inputs, and returns the list of 396# environment variable commands, the export commands, the command string, 397# and the command string to display. 398sub set_parameter_envs 399{ 400local ($cmd, $str, $uinfo, $setin, $skipfound) = @_; 401$setin ||= \%in; 402local $displaystr = $str; 403local ($env, $export, @vals); 404foreach my $a (@{$cmd->{'args'}}) { 405 my $n = $a->{'name'}; 406 my $rv; 407 if ($a->{'type'} == 0 || $a->{'type'} == 5 || 408 $a->{'type'} == 6 || $a->{'type'} == 8) { 409 $rv = $setin->{$n}; 410 } 411 elsif ($a->{'type'} == 11) { 412 $rv = $setin->{$n}; 413 $rv =~ s/\r//g; 414 $rv =~ s/\n/ /g; 415 } 416 elsif ($a->{'type'} == 1 || $a->{'type'} == 2) { 417 (@u = getpwnam($setin->{$n})) || &error($text{'run_euser'}); 418 $rv = $a->{'type'} == 1 ? $setin->{$n} : $u[2]; 419 } 420 elsif ($a->{'type'} == 3 || $a->{'type'} == 4) { 421 (@g = getgrnam($setin->{$n})) || &error($text{'run_egroup'}); 422 $rv = $a->{'type'} == 3 ? $setin->{$n} : $g[2]; 423 } 424 elsif ($a->{'type'} == 7) { 425 $rv = $setin->{$n} ? $a->{'opts'} : ""; 426 } 427 elsif ($a->{'type'} == 9) { 428 local $found; 429 foreach my $l (&read_opts_file($a->{'opts'})) { 430 $found++ if ($l->[0] eq $setin->{$n}); 431 } 432 $found || $skipfound || &error($text{'run_eopt'}); 433 $rv = $setin->{$n}; 434 } 435 elsif ($a->{'type'} == 10) { 436 if ($setin->{$n}) { 437 if ($setin->{$n."_filename"} =~ /([^\/\\]+$)/ && $1) { 438 $rv = &transname("$1"); 439 } 440 else { 441 $rv = &transname(); 442 } 443 &open_tempfile(TEMP, ">$rv"); 444 &print_tempfile(TEMP, $setin->{$n}); 445 &close_tempfile(TEMP); 446 chown($uinfo->[2], $uinfo->[3], $rv); 447 push(@unlink, $rv); 448 } 449 else { 450 $a->{'must'} && &error($text{'run_eupload'}); 451 $rv = undef; 452 } 453 } 454 elsif ($a->{'type'} == 12 || $a->{'type'} == 13 || $a->{'type'} == 14) { 455 local @vals; 456 if ($a->{'type'} == 14) { 457 @vals = split(/\r?\n/, $setin->{$n}); 458 } 459 else { 460 @vals = split(/\0/, $setin->{$n}); 461 } 462 local @opts = &read_opts_file($a->{'opts'}); 463 foreach my $v (@vals) { 464 local $found; 465 foreach my $l (@opts) { 466 $found++ if ($l->[0] eq $v); 467 } 468 $found || $skipfound || &error($text{'run_eopt'}); 469 } 470 $rv = join(" ", @vals); 471 } 472 elsif ($a->{'type'} == 15) { 473 $rv = $setin->{$n."_year"}."-". 474 $setin->{$n."_month"}."-". 475 $setin->{$n."_day"}; 476 } 477 elsif ($a->{'type'} == 16) { 478 $rv = $setin->{$n} ? 1 : 0; 479 } 480 if ($rv eq '' && $a->{'must'} && $a->{'type'} != 7) { 481 &error(&text('run_emust', $a->{'desc'})); 482 } 483 $ENV{$n} = $rv; 484 $env .= "$n='$rv'\n"; 485 $export .= " $n"; 486 if ($a->{'quote'}) { 487 $str =~ s/\$$n/"\$$n"/g; 488 $displaystr =~ s/\$$n/"$rv"/g; 489 } 490 else { 491 $displaystr =~ s/\$$n/$rv/g; 492 } 493 push(@vals, $rv); 494 } 495return ($env, $export, $str, $displaystr, \@vals); 496} 497 498# list_dbi_drivers() 499# Returns a list of DBI driver details, which are actually installed 500sub list_dbi_drivers 501{ 502local @rv = ( { 'name' => 'MySQL', 503 'driver' => 'mysql', 504 'dbparam' => 'database' }, 505 { 'name' => 'PostgreSQL', 506 'driver' => 'Pg', 507 'dbparam' => 'dbname' }, 508 ); 509@rv = grep { eval "use DBD::$_->{'driver'}"; !$@ } @rv; 510return @rv; 511} 512 513# list_servers() 514# Returns a list of servers that a command can run on 515sub list_servers 516{ 517if (&foreign_installed("servers")) { 518 &foreign_require("servers", "servers-lib.pl"); 519 @servers = grep { $_->{'user'} } &servers::list_servers(); 520 if (@servers) { 521 return ( { 'id' => 0, 'desc' => $text{'edit_this'} }, @servers); 522 } 523 } 524return ( { 'id' => 0, 'desc' => $text{'edit_this'} } ); 525} 526 527# execute_custom_command(&command, environment, exports, string, print-output) 528# Runs some command, and returns the bytes, output and timeout flag 529sub execute_custom_command 530{ 531local ($cmd, $env, $export, $str, $print) = @_; 532&foreign_require("proc", "proc-lib.pl"); 533 534&clean_environment() if ($cmd->{'clear'}); 535local $got; 536local $outtemp = &transname(); 537open(OUTTEMP, ">$outtemp"); 538local $fh = $print ? STDOUT : *OUTTEMP; 539if ($cmd->{'su'}) { 540 local $temp = &transname(); 541 &open_tempfile(TEMP, ">$temp"); 542 &print_tempfile(TEMP, "#!/bin/sh\n"); 543 &print_tempfile(TEMP, $env); 544 &print_tempfile(TEMP, "export $export\n") if ($export); 545 &print_tempfile(TEMP, "$str\n"); 546 &close_tempfile(TEMP); 547 chmod(0755, $temp); 548 $got = &proc::safe_process_exec( 549 &command_as_user($user, 1, $temp), 0, 0, 550 $fh, undef, !$cmd->{'raw'} && !$cmd->{'format'}, 0, 551 $cmd->{'timeout'}); 552 unlink($temp); 553 } 554else { 555 $got = &proc::safe_process_exec( 556 $str, $user_info[2], undef, $fh, undef, 557 !$cmd->{'raw'} && !$cmd->{'format'}, 0, 558 $cmd->{'timeout'}); 559 } 560local $ex = $?; 561&reset_environment() if ($cmd->{'clear'}); 562close(OUTTEMP); 563local $rv = &read_file_contents($outtemp); 564unlink($outtemp); 565return ($got, $rv, $proc::safe_process_exec_timeout ? 1 : 0, $ex); 566} 567 568# show_parameter_input(&arg, formno) 569# Returns HTML for a parameter input 570sub show_parameter_input 571{ 572local ($a, $form) = @_; 573local $n = $a->{'name'}; 574local $v = $a->{'opts'}; 575if ($a->{'type'} != 9 && $a->{'type'} != 12 && 576 $a->{'type'} != 13 && $a->{'type'} != 14) { 577 if ($v =~ /^"(.*)"$/ || $v =~ /^'(.*)'$/) { 578 # Quoted default 579 $v = $1; 580 } 581 elsif ($v =~ /^(.*)\s*\|$/ && $config{'params_cmd'}) { 582 # Command to run 583 $v = &backquote_command("$1 2>/dev/null </dev/null"); 584 if ($a->{'type'} != 11) { 585 $v =~ s/[\r\n]+$//; 586 } 587 } 588 elsif ($v =~ /^\// && $config{'params_file'}) { 589 # File to read 590 $v = &read_file_contents($v); 591 if ($a->{'type'} != 11) { 592 $v =~ s/[\r\n]+$//; 593 } 594 } 595 } 596if ($a->{'type'} == 0) { 597 return &ui_textbox($n, $v, 30); 598 } 599elsif ($a->{'type'} == 1 || $a->{'type'} == 2) { 600 return &ui_user_textbox($n, $v, $form); 601 } 602elsif ($a->{'type'} == 3 || $a->{'type'} == 4) { 603 return &ui_group_textbox($n, $v, $form); 604 } 605elsif ($a->{'type'} == 5 || $a->{'type'} == 6) { 606 return &ui_textbox($n, $v, 30)." ". 607 &file_chooser_button($n, $a->{'type'}-5, $form); 608 } 609elsif ($a->{'type'} == 7) { 610 return &ui_yesno_radio($n, $v =~ /true|yes|1/ ? 1 : 0); 611 } 612elsif ($a->{'type'} == 8) { 613 return &ui_password($n, $v, 30); 614 } 615elsif ($a->{'type'} == 9) { 616 return &ui_select($n, undef, [ &read_opts_file($a->{'opts'}) ]); 617 } 618elsif ($a->{'type'} == 10) { 619 return &ui_upload($n, 30); 620 } 621elsif ($a->{'type'} == 11) { 622 return &ui_textarea($n, $v, 4, 30); 623 } 624elsif ($a->{'type'} == 12) { 625 return &ui_select($n, undef, [ &read_opts_file($a->{'opts'}) ], 626 5, 1); 627 } 628elsif ($a->{'type'} == 13) { 629 my @opts = &read_opts_file($a->{'opts'}); 630 return &ui_select($n, undef, \@opts, scalar(@opts), 1); 631 } 632elsif ($a->{'type'} == 14) { 633 my @opts = &read_opts_file($a->{'opts'}); 634 return &ui_multi_select($n, [ ], \@opts, 5); 635 } 636elsif ($a->{'type'} == 15) { 637 my ($year, $month, $day) = split(/\-/, $v); 638 return &ui_date_input($day, $month, $year, 639 $n."_day", $n."_month", $n."_year")." ". 640 &date_chooser_button($n."_day", $n."_month", $n."_year"); 641 } 642elsif ($a->{'type'} == 16) { 643 return &ui_submit($v || $a->{'name'}, $a->{'name'}); 644 } 645else { 646 return "Unknown parameter type $a->{'type'}"; 647 } 648} 649 650 6511; 652