#!/usr/bin/perl &parsef; open (FOOL, "../footer.lib"); @fool_lib=; $in{'db'}="tutorials"; $in{'db'} ? ($db_setup = $in{'db'}) : &html_message("invalid database"); eval { unshift (@INC, $db_script_path); require 5.003; # We need at least Perl 5.003 unless ($db_setup =~ /^[A-Za-z0-9]+$/) { &html_message("Invalid config file name: $db_setup"); } require "$db_setup.cfg"; # Database Definition File }; $db_script_path = "./"; &findinfo; sub parsef { local (*in) = @_ if @_; local ($i, $key, $val); # Read in text if ($ENV{'REQUEST_METHOD'} eq "GET") { $in = $ENV{'QUERY_STRING'}; } elsif ($ENV{'REQUEST_METHOD'} eq "POST") { read(STDIN,$in,$ENV{'CONTENT_LENGTH'}); } @in = split(/[&;]/,$in); foreach $i (0 .. $#in) { # Convert plus's to spaces $in[$i] =~ s/\+/ /g; # Split into key and value. ($key, $val) = split(/=/,$in[$i],2); # splits on the first =. # Convert %XX from hex numbers to alphanumeric $key =~ s/%(..)/pack("c",hex($1))/ge; $val =~ s/%(..)/pack("c",hex($1))/ge; # Associate key and value $in{$key} .= "\0" if (defined($in{$key})); # \0 is the multiple separator $in{$key} .= $val; } return scalar(@in); } sub query { # -------------------------------------------------------- # First let's get a list of database fields we want to search on and # store it in @search_fields my ($i, $column, @search_fields, @search_gt_fields, @search_lt_fields, $maxhits, $numhits, $nh, $field, @regexp, $line, @values, $key_match, @hits, @sortedhits, $next_url, $next_hit, $prev_hit, $first, $last, $upper, $lower, $left, $right, $restricted); local (%sortby); # First thing we do is find out what we are searching for. We build a list of fields # we want to search on in @search_fields. if ($in{'keyword'}) { # If this is a keyword search, we are searching the same $i = 0; # thing in all fields. Make sure "match any" option is $in{'ma'} = "on"; # on, otherwise this will almost always fail. foreach $column (@db_cols) { if (($db_sort{$column} eq 'date') or &date_to_unix($in{'keyword'})) { $i++; next; } if ($i == $auth_user_field) { $i++; next; } push (@search_fields, $i); # Search every column $in{$column} = $in{'keyword'}; # Fill %in with keyword we are looking for. $i++; } } else { # Otherwise this is a regular search, and we only want records $i = 0; # that match everything the user specified for. foreach $column (@db_cols) { if ($in{$column} =~ /^\>(.+)$/) { ($db_sort{$column} eq 'date') and (&date_to_unix($1) or return "Invalid date format: '$1'"); push (@search_gt_fields, $i); $in{"$column-gt"} = $1; $i++; next; } if ($in{$column} =~ /^\<(.+)$/) { ($db_sort{$column} eq 'date') and (&date_to_unix($1) or return "Invalid date format: '$1'"); push (@search_lt_fields, $i); $in{"$column-lt"} = $1; $i++; next; } if ($in{$column} !~ /^\s*$/) { ($db_sort{$column} eq 'date') and (&date_to_unix($in{$column}) or return "Invalid date format: '$in{$column}'"); push(@search_fields, $i); $i++; next; } if ($in{"$column-gt"} !~ /^\s*$/) { ($db_sort{$column} eq 'date') and (&date_to_unix($in{"$column-gt"}) or return "Invalid date format: '$in{$column}'"); push(@search_gt_fields, $i); } if ($in{"$column-lt"} !~ /^\s*$/) { ($db_sort{$column} eq 'date') and (&date_to_unix($in{"$column-lt"}) or return "Invalid date format: '$in{$column}'"); push(@search_lt_fields, $i); } $i++; } } # If we don't have anything to search on, let's complain. if (!@search_fields and !@search_gt_fields and !@search_lt_fields) { return "no search terms specified"; } # Define the maximum number of hits we will allow, and the next hit counter. $in{'mh'} ? ($maxhits = $in{'mh'}) : ($maxhits = $db_max_hits); $in{'nh'} ? ($nh = $in{'nh'}) : ($nh = 1); $numhits = 0; # Now let's build up all the regexpressions we will use. This saves the program # from having to recompile the same regular expression every time. foreach $field (@search_fields) { my $tmpreg = "$in{$db_cols[$field]}"; (!$in{'re'}) and ($tmpreg = "\Q$tmpreg\E"); ($in{'ww'}) and ($tmpreg = "\\b$tmpreg\\b"); (!$in{'cs'}) and ($tmpreg = "(?i)$tmpreg"); ($in{$db_cols[$field]} eq "*") and ($tmpreg = ".*"); # A "*" matches anything. $regexp_func[$field] = eval "sub { m/$tmpreg/o }"; $regexp_bold[$field] = $tmpreg; } # Now we go through the database and do the actual searching. # First figure out which records we want: $first = ($maxhits * ($nh - 1)); $last = $first + $maxhits - 1; open (DB, "<$db_file_name") or &html_message("error in search. unable to open database: $db_file_name.\nReason: $!"); if ($db_use_flock) { flock(DB, 1); } LINE: while () { (/^#/) and next LINE; # Skip comment Lines. (/^\s*$/) and next LINE; # Skip blank lines. $line = $_; chomp ($line); # Remove trailing new line. @values = &split_decode($line); # Normal searches. $key_match = 0; foreach $field (@search_fields) { $_ = $values[$field]; # Reg function works on $_. $in{'ma'} ? ($key_match = ($key_match or &{$regexp_func[$field]})) : (&{$regexp_func[$field]} or next LINE); } # Greater then searches. foreach $field (@search_gt_fields) { $term = $in{"$db_cols[$field]-gt"}; if ($db_sort{$db_cols[$field]} eq "date") { $in{'ma'} ? ($key_match = ($key_match or (&date_to_unix($values[$field])) > &date_to_unix($term))) : (&date_to_unix($values[$field]) > (&date_to_unix($term)) or next LINE); } elsif ($db_sort{$db_cols[$field]} eq 'alpha') { $in{'ma'} ? ($key_match = ($key_match or ($values[$field] > $term))) : ((lc($values[$field]) gt lc($term)) or next LINE); } else {; $in{'ma'} ? ($key_match = ($key_match or ($values[$field] > $term))) : (($values[$field] > $term) or next LINE); } } # Less then searches. foreach $field (@search_lt_fields) { $term = $in{"$db_cols[$field]-lt"}; if ($db_sort{$db_cols[$field]} eq "date") { $in{'ma'} ? ($key_match = ($key_match or (&date_to_unix($values[$field]) < &date_to_unix($term)))) : (&date_to_unix($values[$field]) < (&date_to_unix($term)) or next LINE); } elsif ($db_sort{$db_cols[$field]} eq 'alpha') { $in{'ma'} ? ($key_match = ($key_match or ($values[$field] < $term))) : ((lc($values[$field]) lt lc($term)) or next LINE); } else { $in{'ma'} ? ($key_match = ($key_match or ($values[$field] < $term))) : (($values[$field] < $term) or next LINE); } } # Did we find a match? We only add the hit to the @hits array if we need it. We can # skip it if we are not sorting and it's not in our first < > last range. if ($key_match || (!($in{'keyword'}) && !($in{'ma'}))) { if (exists $in{'sb'}) { $sortby{(($#hits+1) / ($#db_cols+1))} = $values[$in{'sb'}]; push (@hits, @values); } else { (($numhits >= $first) and ($numhits <= $last)) and push (@hits, @values); } $numhits++; # But we always count it! } } close DB; # Now we've stored all our hits in @hits, and we've got a sorting values stored # in %sortby indexed by their position in @hits. $numhits ? ($db_total_hits = $numhits) : ($db_total_hits = 0); ($db_total_hits == 0) and return ("no matching records."); # Sort the array @hits in order if we are meant to sort. if (exists $in{'sb'}) { # Sort hits on $in{'sb'} field. my ($sort_order, $sort_func); $in{'so'} ? ($sort_order = $in{'so'}) : ($sort_order = "ascend"); $sort_func = "$db_sort{$db_cols[$in{'sb'}]}_$sort_order"; foreach $hit (sort $sort_func (keys %sortby)) { $first = ($hit * $#db_cols) + $hit; $last = ($hit * $#db_cols) + $#db_cols + $hit; push (@sortedhits, @hits[$first .. $last]); } @hits = @sortedhits; } # If we have to many hits, let's build the next toolbar, and return only the hits we want. if ($numhits > $maxhits) { # Remove the nh= from the query string. $next_url = $ENV{'QUERY_STRING'}; $next_url =~ s/\&nh=\d+//; $next_hit = $nh + 1; $prev_hit = $nh - 1; # Build the next hits toolbar. It seems really complicated as we have to do # some number crunching to keep track of where we are on the toolbar, and so # that the toolbar stays centred. # First, set how many pages we have on the left and the right. $left = $nh; $right = int($numhits/$maxhits) - $nh; # Then work out what page number we can go above and below. ($left > 7) ? ($lower = $left - 7) : ($lower = 1); ($right > 7) ? ($upper = $nh + 7) : ($upper = int($numhits/$maxhits) + 1); # Finally, adjust those page numbers if we are near an endpoint. (7 - $nh >= 0) and ($upper = $upper + (8 - $nh)); ($nh > ($numhits/$maxhits - 7)) and ($lower = $lower - ($nh - int($numhits/$maxhits - 7) - 1)); $db_next_hits = ""; # Then let's go through the pages and build the HTML. ($nh > 1) and ($db_next_hits .= qq~[<<] ~); for ($i = 1; $i <= int($numhits/$maxhits) + 1; $i++) { if ($i < $lower) { $db_next_hits .= " ... "; $i = ($lower-1); next; } if ($i > $upper) { $db_next_hits .= " ... "; last; } ($i == $nh) ? ($db_next_hits .= qq~$i ~) : ($db_next_hits .= qq~$i ~); if (($i * $maxhits) >= $numhits) { last; } # Special case if we hit exact. } $db_next_hits .= qq~[>>] ~ unless ($nh == $i); # Slice the @hits to only return the ones we want, only have to do this if the results are sorted. if (exists $in{'sb'}) { $first = ($maxhits * ($nh - 1)) * ($#db_cols+1); $last = $first + (($#db_cols+1) * $maxhits) - 1; $last = $#hits if ($last > $#hits); @hits = @hits[$first .. $last]; } } return ("ok", @hits); } sub date_to_unix { # -------------------------------------------------------- # This routine must take your date format and return the time a la UNIX time(). # Some things to be careful about.. # int your values just in case to remove spaces, etc. # catch the fatal error timelocal will generate if you have a bad date.. # don't forget that the month is indexed from 0! # my ($date) = $_[0]; my (%months) = ("Jan" => 0, "Feb" => 1, "Mar" => 2, "Apr" => 3, "May" => 4, "Jun" => 5, "Jul" => 6, "Aug" => 7, "Sep" => 8, "Oct" => 9, "Nov" => 10,"Dec" => 11); my ($time); my ($day, $mon, $year) = split(/-/, $_[0]); unless ($day and $mon and $year) { return undef; } unless (defined($months{$mon})) { return undef; } use Time::Local; eval { $day = int($day); $year = int($year) - 1900; $time = timelocal(0,0,0,$day, $months{$mon}, $year); }; if ($@) { return undef; } # Could return 0 if you want. return ($time); } sub alpha_ascend { lc($sortby{$a}) cmp lc ($sortby{$b}) } sub alpha_descend { lc($sortby{$b}) cmp lc ($sortby{$a}) } sub numer_ascend { $sortby{$a} <=> $sortby{$b} } sub numer_descend { $sortby{$b} <=> $sortby{$a} } sub date_ascend { &date_to_unix($sortby{$a}) <=> &date_to_unix($sortby{$b}) } sub date_descend { &date_to_unix($sortby{$b}) <=> &date_to_unix($sortby{$a}) } sub findinfo { my ($status, @hits) = &query("view"); if ($status eq "ok") { my (%rec) = &array_to_hash(0, @hits); ($rating,$votes)=split(/ /,$rec{'Rating'}); if ($rating==0) { $rrating="N/A"; } else { $rrating=int($rating/$votes*10)/10; } print "Content-type: text/html\n\n"; if ($rec{'EMail'}) { $authorfield="$rec{'Author'}"; } else { $authorfield=$rec{'Author'}; } print qq| C++ Home - More Info on Tutorial $rec{'ID'} |; require "ssi.lib"; print &Adverts("r"); print qq|
Tutorial Name: $rec{'Name'}
Category: $rec{'Category'} Unique ID: $rec{'ID'}
Author: $authorfield Rating: $rrating ($votes votes)
URL: $rec{'Taken'} Difficulty: $difficulties[$rec{'Difficulty'}]
$rec{'Description'}

Close this window

@fool_lib |; } else { &html_message($status); } } sub split_decode { # -------------------------------------------------------- # Takes one line of the database as input and returns an # array of all the values. It replaces special mark up that # join_encode makes such as replacing the '``' symbol with a # newline and the '~~' symbol with a database delimeter. my ($input) = shift; $input =~ s/\Q$db_delim\E$/$db_delim /o; # Add a space if we have delimiter new line. my (@array) = split (/\Q$db_delim\E/o, $input); for ($i = 0; $i <= $#array; $i++) { $array[$i] =~ s/~~/$db_delim/og; # Retrieve Delimiter.. $array[$i] =~ s/``/\n/g; # Change '' back to newlines.. } return @array; } sub array_to_hash { # -------------------------------------------------------- # Converts an array to a hash using db_cols as the field names. my($hit, @array) = @_; my(%hash); for ($j = 0; $j <= $#db_cols; $j++) { $hash{$db_cols[$j]} = $array[$hit * ($#db_cols+1) + $j]; } return %hash; } sub html_message { my ($message) = shift; print "Content-type: text/html\n\n"; print qq~ $message ~; exit; }