Subversion Repositories VORC

Rev

Rev 138 | Rev 199 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

#!/usr/bin/perl

# Redirect error messages to a log of my choosing. (it's annoying to filter for errors in the shared env)
my $error_log_path = $ENV{SERVER_NAME} eq "volunteers.rollercon.com" ? "/home3/rollerco/logs/" : "/tmp/";
close STDERR;
open STDERR, '>>', $error_log_path.'vorc_error.log' or warn "Failed to open redirected logfile ($0): $!";
#warn "Redirecting errors to ${error_log_path}vorc_error.log";

#if ($ENV{SHELL}) { die "This script shouldn't be executed from the command line!\n"; }

#use strict;
use cPanelUserConfig;
use CGI qw/param cookie header start_html url/;
use HTML::Tiny;
use tableViewer;
use RollerCon;
use DateTime;
#use DateTime::Duration;
my $now = DateTime->now (time_zone => 'America/Los_Angeles');
our $h = HTML::Tiny->new( mode => 'html' );

my $cookie_string;
our ($EML, $PWD, $LVL);
my $user;
my $username;
my $RCid;
my $RCAUTH_cookie;
my $YEAR = 1900 + (localtime)[5];

$cookie_string = authenticate(1) || die;
($EML, $PWD, $LVL) = split /&/, $cookie_string;
$user = getUser($EML);
$user->{department} = convertDepartments $user->{department};
$username = $h->a ({ href=>"/schedule/view_user.pl?submit=View&RCid=$user->{RCid}" }, $user->{derby_name});
$RCid = $user->{RCid};
$RCAUTH_cookie = CGI::Cookie->new(-name=>'RCAUTH',-value=>"$cookie_string",-expires=>"+30m");

if (!$user->{MVPid} and $LVL < RollerCon::ADMIN and $user->{department}->{MVP} < RollerCon::USER and $user->{department}->{COA} < RollerCon::USER) {
        print header(-cookie=>$RCAUTH_cookie);
        printRCHeader("Unauthorized Page");
        print $h->div ({ class=>"error" }, "No Access");
        print $h->div ("Your user account is not registered to sign up for MVP Classes, so you can't see this page.  It's possible that your access is still being reviewed.  Please be patient.");
        print $h->a ({ href=>"/schedule/" }, "[Go Home]");
        print $h->close ("html");
        exit;   
}

my $pageTitle = "MVP Classes";
my $prefscookie = "allnewmvpclasses";
our $DBTABLE = 'v_class';
my %COLUMNS = (
# colname   =>  [qw(DisplayName       N    type     status)],   status ->  static | default | <blank>
        name                            => [qw(Class         5    text     static )],
        date                            => [qw(Date         10    date              )],
        dayofweek               => [qw(Day          15    select    default )],
        start_time      => [qw(StartTime    20    text      )],
        end_time                => [qw(EndTime      25    text       )],
        time                            => [qw(Time         30    text      default )],
        location                => [qw(Track        35    select    default )],
        level                   => [qw(Level        37    select    default )],
        coach                           => [qw(Coach        40    select    default )]
);

if ($LVL >= RollerCon::ADMIN) {
  $COLUMNS{id}       = [qw(Admin         1    none        default )];
        $COLUMNS{available}= [qw(SignUp       45    text       )]
}

if ($LVL >= RollerCon::ADMIN or $user->{department}->{MVP} >= RollerCon::VOLUNTEER or $user->{department}->{COA} >= RollerCon::USER) {
        $COLUMNS{note}       = [qw(Notes         60    text         )],
        $COLUMNS{capacity}   = [qw(Capacity      65    number        default )],
        $COLUMNS{count}      = [qw(Count         70    number      default )],
        $COLUMNS{stars}      = [qw(Stars         75    number      default )],  
        $COLUMNS{responses}  = [qw(Responses     80    number      default )],  
}

if ($user->{MVPid} and $LVL < RollerCon::ADMIN) {
        $COLUMNS{available}= [qw(SignUp       45    text       static)]
}


my $stylesheet = "/style.css";
my $homeURL = '/schedule/';
my @pagelimitoptions = ("All", 5, 10, 25);

# Set any custom "where" DB filters here...
my @whereClause;

# If we need to modify line item values, create a subroutine named "modify_$columnname"
#    It will receive a hashref to the object lineitem

#use WebDB;
#my $dbh = WebDB::connect;
my $dbh = getRCDBH;

sub modify_id {
  my $hr = shift;
  my $clicky = $hr->{count} ? "event.stopPropagation(); if (confirm('WARNING!\\nYou are modifying a class that someone has signed up for.')==true) {return true;} else {return false;}" : "return true;";
  my $extrawarning = $hr->{count} ? "\\nWARNING! It appears someone is signed up for it." : "";
  return join "&nbsp;", #$hr->{id},
         $h->a ({ href=>"view_class.pl?id=$hr->{id}&choice=Update", onClick=>$clicky }, "[Edit]"),
         $h->a ({ href=>"view_class.pl?id=$hr->{id}&choice=Copy" }, "[Copy]"),
         $h->a ({ href=>"view_class.pl?id=$hr->{id}&choice=Delete", onClick=>"event.stopPropagation(); if (confirm('Are you sure you want to DELETE this class?$extrawarning')==true) {return true;} else {return false;}" }, "[Delete]")
  ;
};

sub modify_available {
        my $t = shift;
        
        my ($yyyy, $mm, $dd) = split /\-/, $t->{date};
        my $cutoff = DateTime->new(
        year => $yyyy,
        month => $mm,
        day => $dd,
        hour => 5,
        minute => 0,
        second => 0,
        time_zone => 'America/Los_Angeles'
  );
  
  return "CLOSED" unless $now < $cutoff;

        my $classkey = join '|', $t->{date}, $t->{start_time}, $t->{location};
  
        ($t->{signedup}) = $dbh->selectrow_array ("select role from v_class_signup where RCid = ? and id = ?", undef, $RCid, $t->{id} );
        my $droplink = $h->a ({ onClick=>"if (confirm('Really? You want to drop this class?')==true) { window.open('make_shift_change.pl?change=del&RCid=$RCid&id=$t->{id}&role=$t->{signedup}','Confirm Change','resizable,height=260,width=370'); return false; }" }, "[DROP]");
        if (!$t->{available}) {
          my $full = "FULL";
          $full .= " | ".$droplink unless !$t->{signedup};
          return $full;
  }
  
        $t->{available} .= " Open";     
  $t->{available} .= " | ".$droplink unless !$t->{signedup};
  if (findConflict ($ORCUSER->{RCid}, $t->{id}, "class")) {
    $t->{available} .= " | *schedule conflict*" unless $t->{signedup};
  } elsif (signUpEligible ($ORCUSER, $t, "class")) {
                # SIGN UP
                $t->{available} .= " | ".$h->a ({ onClick=>"event.stopPropagation(); window.open('make_shift_change.pl?change=add&RCid=$RCid&id=$classkey','Confirm Class Change','resizable,height=260,width=370'); return false;" }, "[SIGN UP]");
        }
        if ($LVL > 4 or $user->{department}->{VCI} >= 2) {
                # ADD USER
                $t->{available} ? $t->{available} .= " | " : {};
                $t->{available} .= $h->a ({ onClick=>"event.stopPropagation(); window.open('make_shift_change.pl?change=lookup&RCid=$RCid&id=$classkey','Confirm Class Change','resizable,height=260,width=370'); return false;" }, "[ADD USER]");
        }

        return $t->{available};
}

sub filter_available {
  my $colName = shift;
        my $filter = shift;
        
        if (defined $filter)    {
                if ($filter eq "Full") {
                        return "$colName = 0";
                }
                return "$colName > 0";
        }       else {
                my $thing = "filter-${colName}";          
                my $Options = "<OPTION></OPTION>"."<OPTION>Available</OPTION>"."<OPTION>Full</OPTION>";

                $Options =~ s/>($FORM{$thing})/ selected>$1/;
                return "<SELECT name=filter-${colName} $onChange>$Options</SELECT>";
        }
}

sub modify_time {
  my $t = shift;
  return convertTime $t->{time};
}

sub modify_start_time {
  my $t = shift;
  return convertTime $t->{start_time};
}

sub modify_end_time {
  my $t = shift;
  return convertTime $t->{end_time};
}

sub modify_stars {
  my $t = shift;
  if ($t->{coach} eq $user->{derby_name} or $LVL >= RollerCon::ADMIN or $user->{department}->{MVP} >= RollerCon::MANAGER) {
    $t->{stars} = $t->{stars};
  } else {
    $t->{stars} = '&nbsp;';
  }
}

sub modify_responses {
  my $t = shift;
  if ($t->{coachRCid} == $user->{RCid} or $LVL >= RollerCon::ADMIN or $user->{department}->{MVP} >= RollerCon::MANAGER) {
    $t->{responses} = $t->{responses} // '0';
  } else {
    $t->{responses} = '&nbsp;';
  }
}


# Ideally, nothing below this comment needs to change
#-------------------------------------------------------------------------------


our %NAME              = map  { $_ => $COLUMNS{$_}->[0] } keys %COLUMNS;
our %colOrderHash      = map  { $_ => $COLUMNS{$_}->[1] } keys %COLUMNS;
our %colFilterTypeHash = map  { $_ => $COLUMNS{$_}->[2] } keys %COLUMNS;
our @staticFields      = sort byfield grep { $COLUMNS{$_}->[3] eq 'static' } keys %COLUMNS;
our @defaultFields     = sort byfield grep { defined $COLUMNS{$_}->[3] } keys %COLUMNS;
#our @defaultFields     = grep { $COLUMNS{$_}->[3] eq 'default' or inArray ($_, \@staticFields) } keys %COLUMNS;

our @allFields = sort byfield keys %NAME;
our @displayFields = ();
our @hideFields = ();
my $QUERY_STRING;

my $pagelimit = param ("limit") // "All"; #$pagelimitoptions[$#pagelimitoptions];
my $curpage = param ("page") // 1;

our %FORM;
my $FILTER;
foreach (param()) {
        if (/^year$/) { #
                $YEAR = param($_);
                next;
        }

        $FORM{$_} = param($_);                          # Retrieve all of the FORM data submitted
        
        if ((/^filter/) and ($FORM{$_} ne '')) {        # Build a set of filters to apply
                my ($filter,$field) = split /-/, $_;            
                $FILTER->{$field} = $FORM{$_} unless notInArray ($field, \@allFields);
        }       elsif ($FORM{$_} eq "true")     {               # Compile list of fields to display
          if ($_ ne "shiftinclude") {
                  push @displayFields, $_;
                }
        }
}

push @whereClause, "year(date) = '$YEAR'";

if (exists $FORM{autoload})     {                       # If the FORM was submitted (i.e. the page is being redisplayed),
                                                                            #   build the data for the cookie that remembers the page setup
        my $disFields = join ":", @displayFields;
        my $fils = join ":", map { "$_=$FILTER->{$_}" } keys %{$FILTER};
                
        $QUERY_STRING = $disFields.'&'.$fils.'&'.$FORM{sortby}.'&'.$FORM{autoload}.'&'.$FORM{shiftinclude};
}


if (!(exists $FORM{autoload}))  {                       # No FORM was submitted...
        if (my $prefs = cookie ($prefscookie) and !defined param ("ignoreCookie"))      { # Check for cookies from previous visits.
                my ($disF, $filts, $sb, $al, $si) = split /&/,$prefs;
                @displayFields = split /:/,$disF;
                
                foreach my $pair (split /:/, $filts)    {
                        my ($key, $value) = split /=/, $pair;
                        $FORM{"filter-$key"} = $value;
                        $FILTER->{$key} = $value;
                }
                
                $FORM{sortby} = $sb;
                $FORM{autoload} = $al;
                $FORM{shiftinclude} = $si;
                $QUERY_STRING = $prefs;
        }       else {
          @displayFields = @defaultFields; # Otherwise suppply a default list of columns.
          $FORM{autoload} = 1;             # And turn aut0load on by default.
                $FORM{sortby} = "note";
        } 
}

# let's just make sure the columns are in the right order (and there aren't any missing)
@displayFields = grep { inArray($_, \@allFields) } sort byfield uniq @displayFields, @staticFields;

# If the field isn't in the displayFields list, then add it to the hideFields list
@hideFields = grep { notInArray ($_, \@displayFields) } @allFields;

# Process any filters provided in the form to pass to the database
push @whereClause, map { filter ($_, $FILTER->{$_}) } grep { defined $FILTER->{$_} } @displayFields;
push @whereClause, "year(date) = '$YEAR'";
#warn join " and ", @whereClause;

                                                        #  Given the fields to display and the where conditions,
                                                        #         "getData" will return a reference to an array of
                                                        #         hash references of the results.
my ($data, $datacount) = getData (\@displayFields, \@whereClause, $DBTABLE, $FORM{sortby}, $curpage, $pagelimit);
my @ProductList = @{ $data };

#my @ProductList = @{ getData (\@displayFields, \@whereClause, $DBTABLE, $FORM{sortby}, $curpage, $pagelimit) };
my $x = scalar @ProductList; # How many results were returned?

# If the user is trying to download the Excel file, send it to them and then exit out.
if ($FORM{excel}) {
  exportExcel (\@ProductList, "MVP_Class_List");
  exit;
}

my @shifts;
if ($FORM{shiftinclude} eq "true") {
  my @SIWhere; # = ("year(date) = '$YEAR'");
  push @SIWhere, "year(date) = year(now())";
  push @SIWhere, "RCid = $ORCUSER->{RCid}";
  my ($d, $c) = getData (\@displayFields, \@SIWhere, 'v_class_signup', 'date');
  @shifts = @{ $d };
}

my $signedOnAs = $username ? "Welcome, $username. ".$h->a ({ href=>"index.pl?LOGOUT" }, "[Log Out]") : "You are not signed in.";

# Set some cookie stuff...
my $path = `dirname $ENV{SCRIPT_NAME}`; chomp $path; $path .= '/' unless $path eq "/";
my $queryCookie = cookie(-NAME=>$prefscookie,
                        -VALUE=>"$QUERY_STRING",
                        -PATH=>"$path",
                        -EXPIRES=>'+365d');

my $SIChecked;
if ($FORM{shiftinclude}) {
  $SIChecked = $h->input ({ type=>"checkbox", name=>"shiftinclude", value=>"true", checked=>[], onClick=>'submit();' });
} else {
  $SIChecked = $h->input ({ type=>"checkbox", name=>"shiftinclude", value=>"true", onClick=>'submit();' });
}

# Print the header
print header(-cookie=>[$RCAUTH_cookie,$queryCookie]);

#       print "<!-- FORM \n\n";                         # Debug code to dump the FORM to a html comment
#       print "I'm catching updates!!!\n\n";
#       foreach $key (sort (keys %FORM))                #       Must be done after the header is written!
#               { print "\t$key:  $FORM{$key}\n"; }
#       print "--> \n\n";
#       
# 
#       print "<!-- ENV \n\n";                          # Debug code to dump the ENV to a html comment
#       foreach $key (sort (keys %ENV))                 #       Must be done after the header is written!
#               { print "\t$key:  $ENV{$key}\n"; }
#       print "--> \n\n";
# 
#       print "\n\n\n\n<!-- $QUERY_STRING --> \n\n\n\n";


#------------------

# Toggle the autoload fields within the table elements 
our ($onClick, $onChange);   # (also used in scanFunctions)
my ($radiobutton, $refreshbutton, $sortby);
if ($FORM{autoload}) {                                    
        $onClick = "onClick='submit();'";
        $onChange = "onChange='page.value = 1; submit();'";
  $radiobutton = $h->div ({ class=>'autoload' },
    ["Autoload Changes: ",
    $h->input ({ type=>"radio", name=>'autoload', class=>'accent', value=>1, onClick=>'submit();', checked=>[] }), "On ", 
    $h->input ({ type=>"radio", name=>'autoload', class=>'accent', value=>0, onClick=>'submit();' }), "Off ",
    ]);
  $refreshbutton = "";
  $sortby = $h->select ({name=>"sortby", onChange=>'submit();' }, [ map { $FORM{sortby} eq $_ ? $h->option ({ value=>$_, selected=>[] }, $NAME{$_}) : $h->option ({ value=>$_ }, $NAME{$_}) } @displayFields ]);
} else {
  $onClick = "";
        $onChange = "onChange='page.value = 1;'";
  $radiobutton = $h->div ({ class=>'autoload' }, 
    ["Autoload Changes: ",
    $h->input ({ type=>"radio", name=>'autoload', class=>'accent', value=>1, onClick=>'submit();' }), "On ", 
    $h->input ({ type=>"radio", name=>'autoload', class=>'accent', value=>0, onClick=>'submit();', checked=>[] }), "Off ",
    ]);
  $refreshbutton = $h->input ({ type=>"button", value=>"Refresh", onClick=>"submit(); return false;" });
  $sortby = $h->select ({name=>"sortby" }, [ map { $FORM{sortby} eq $_ ? $h->option ({ value=>$_, selected=>[] }, $NAME{$_}) : $h->option ({ value=>$_ }, $NAME{$_}) } @displayFields ]);
}
         
print start_html (-title => $pageTitle, -style => {'src' => $stylesheet} );

print $h->open ('form', { action=>url, method=>'POST', name=>'Req' });
print $h->input ({ type=>"hidden", name=>"excel", value=>0 });
print $h->div ({ class => "accent pageheader" }, [
  $h->h1 ($pageTitle),
  $h->div ({ class=>"sp0" }, [
    $h->div ({ class=>"spLeft" }, [
      $radiobutton
    ]),
    $h->div ({ class=>"spRight" }, [
      $h->input ({ type=>"button", value=>"Home", onClick=>"window.location.href='$homeURL'" }),
      $refreshbutton
    ]),
  ]),
]);

# Print the Hidden fields' check boxes (if there are any)

my $c = 1;
my @hiddencheckboxes;
my @hiddenrows;
foreach my $field (sort { $NAME{$a} cmp $NAME{$b}; } @hideFields) {
  if ($FORM{autoload}) {
    push @hiddencheckboxes, $h->div ({ class=>'rTableCell quarters nowrap', onClick=>"Req.$field.click();" }, [ $h->input ({ type=>'checkbox', class=>'accent', name=>$field, value=>'true', onClick=>"event.stopPropagation(); submit();" }), $NAME{$field} ]);
  } else {
    push @hiddencheckboxes, $h->div ({ class=>'rTableCell quarters nowrap', onClick=>"Req.$field.checked=!Req.$field.checked;" }, [ $h->input ({ type=>'checkbox', class=>'accent', name=>$field, value=>'true', onClick=>"event.stopPropagation();" }), $NAME{$field} ]);
  }
  if ($c++ % 4 == 0) {
    push @hiddenrows, $h->div ({ class=>'rTableRow' }, [ @hiddencheckboxes ]);
    @hiddencheckboxes = [];
  }
}
push @hiddenrows, $h->div ({ class=>'rTableRow' }, [ @hiddencheckboxes ]) unless --$c % 4 == 0;

my @yearoptions;
foreach (@{&getYears()}) {
        push @yearoptions, $YEAR eq $_ ? $h->option ({ selected=>[] }, $_) : $h->option ($_);
}

if (scalar @hideFields) {
  my @topleft;
  push @topleft, $h->div ({ class=>"nowrap" }, "Hidden Columns:");
  push @topleft, $h->div ({ class=>'rTable' }, [ @hiddenrows ]);
  
  print $h->div ({ class=>"sp0" }, [
    $h->div ({ class=>"spLeft"  }, [ @topleft ]),
    $h->div ({ class=>"spRight" }, [
      $signedOnAs, $h->br,
      "Show my classes: ", $SIChecked, $h->br,
      $h->input ({ type=>"button", value=>"Block Personal Time", onClick=>"window.location.href='personal_time.pl'" }),
    ])
  ]);
}

# Print the main table...............................................

print $h->open ('div', { class=>'rTable' });

my @tmptitlerow;
foreach my $f (@displayFields)  {  # Print the Column headings
  if (inArray ($f, \@staticFields)) {
    push @tmptitlerow, $h->div ({ class=>'rTableHead' }, [ $h->input ({ type=>"hidden", name=>$f, value=>"true" }), $NAME{$f} ]);
  } else {
    if ($FORM{autoload}) {
      push @tmptitlerow, $h->div ({ class=>'rTableHead', onClick=>"Req.$f.click();" }, [ $h->input ({ type=>"checkbox", class=>"accent", name=>$f, value=>"true", checked=>[], onClick=>'event.stopPropagation(); submit();' }), $NAME{$f} ]);
    } else {
      push @tmptitlerow, $h->div ({ class=>'rTableHead', onClick=>"Req.$f.checked=!Req.$f.checked;" }, [ $h->input ({ type=>"checkbox", class=>"accent", name=>$f, value=>"true", checked=>[], onClick=>"event.stopPropagation();" }), $NAME{$f} ]);      
    }
  }
}

# Print the filter boxes...
print $h->div ({ class=>'rTableHeading' }, [ @tmptitlerow ], [ map { $h->div ({ class=>'rTableCell filters' }, filter ($_)) } @displayFields ], $h->div ({ class=>"rTableCell" }));

if ($FORM{shiftinclude}) {  # Include all of the user's shifts at the top
  foreach my $t (@shifts)       {
          print $h->div ({ class=>'rTableRow highlighted' }, [ map { $h->div ({ class=>'rTableCell' }, exists &{"modify_".$_} ? &{"modify_".$_} ($t) : $t->{$_} ? $t->{$_} : "") } @displayFields ]);
  }
  print $h->hr ({ width=>"500%" });
}

# Print the things
foreach my $t (@ProductList)    {
  my @display = map { $h->div ({ class=>'rTableCell' }, exists &{"modify_".$_} ? &{"modify_".$_} ($t) : $t->{$_}) } @displayFields;
  if ($LVL >= RollerCon::ADMIN or $user->{department}->{MVP} >= RollerCon::VOLUNTEER or $user->{department}->{COA} >= RollerCon::VOLUNTEER) {
    print $t->{signedup} ? $h->div ({ class=>'rTableRow highlighted', onclick=>"location.href='view_class.pl?id=$t->{id}&choice=View'" }, [ @display ])
                         : $h->div ({ class=>'rTableRow shaded',      onclick=>"location.href='view_class.pl?id=$t->{id}&choice=View'" }, [ @display ]);
  } else {
    print $t->{signedup} ? $h->div ({ class=>'rTableRow highlighted' }, [ @display ])
                         : $h->div ({ class=>'rTableRow shaded' },      [ @display ]);  
  }
}

print $h->close ('div');

# close things out................................................

my $pages = $pagelimit eq "All" ? 1 : int( $datacount / $pagelimit + 0.99 );
if ($curpage > $pages) { $curpage = $pages; }

my @pagerange;
if ($pages <= 5 ) {
  @pagerange = 1 .. $pages;
} else {  
  if ($curpage <= 3) {
    @pagerange = (1, 2, 3, 4, ">>");
  } elsif ($curpage >= $pages - 2) {
    @pagerange = ("<<", $pages-3, $pages-2, $pages-1, $pages);
  } else {
    @pagerange = ("<<", $curpage-1, $curpage, $curpage+1, ">>");
  }
}

my @excelcode;
push @excelcode, $h->br;
push @excelcode, $h->a ({ href=>"", target=>"_new", onClick=>"window.document.Req.excel.value=1; window.document.Req.submit(); window.document.Req.excel.value=0; return false;" }, "[Export Displayed Data as an Excel Document.]");

print $h->br; # print $h->br;
print $h->div ({ class=>"sp0" }, [
    $h->div ({ class=>"spLeft" }, [
      $h->div ({ class=>"footer" }, [
        "To bookmark, save, or send this exact view, use the ",
        $h->a ({ href=>'', onClick=>"window.document.Req.method = 'GET'; Req.submit(); return false;" }, "[Full URL]"),
        $h->br,
        "If this page is displaying oddly, ", $h->a ({ href=>url ()."?ignoreCookie=1" }, "[Reset Your View]"),
        @excelcode,
        $h->br,
        "This page was displayed on ", currentTime (),
        $h->br,
        "Please direct questions, problems, and concerns to $SYSTEM_EMAIL",
        $h->br,
        "Displaying: ", $h->select ({ name=>"year", onchange=>"Req.submit();" }, [ @yearoptions ])
      ])
    ]),
    $h->div ({ class=>"spRight" }, [
      $h->h5 ([
               "$x of $datacount Record". ($x == 1 ? "" : "s") ." Displayed", $h->br,               
               "Sorted by ", $sortby, $h->br,
               "Displaying ", $h->select ({ name=>"limit", onChange=>"page.value = 1; submit();" }, [ map { $pagelimit == $_ ? $h->option ({ selected=>[] }, $_) : $h->option ($_) } @pagelimitoptions ]), " Per Page", $h->br,
               ( $pages > 1 ? ( join " ", map { $_ == $curpage ? "<B>$_</b>" :
                                                $_ eq "<<"     ? $h->a ({ onClick=>qq{Req.page.value=1; Req.submit();} }, "$_") :
                                                $_ eq ">>"     ? $h->a ({ onClick=>qq{Req.page.value=$pages; Req.submit();} }, "$_") :
                                                                 $h->a ({ onClick=>qq{Req.page.value=$_; Req.submit();} }, "[$_]") } @pagerange ) : "" ), $h->br,
               $h->input ({ type=>"hidden", name=>"page", value=>$curpage })
      ])
    ]),
]);

print $h->close('form');
print $h->close('html');