#!/usr/bin/perl
#
#               -------------
#				   Links
#               -------------
#               Links Manager
#
#         File: search.cgi
#  Description: Searches the database and produces a list of results. If no options are
#               given, the script will produce a search form found in site_html.pl.
#       Author: Alex Krohn
#        Email: alex@gossamer-threads.com
#          Web: http://www.gossamer-threads.com/
#      Version: 1.0
#
# COPYRIGHT NOTICE:
#
# Copyright 1997 Gossamer Threads Inc.  All Rights Reserved.
#
# This program is being distributed as shareware.  It may be used and
# modified free of charge for personal, academic, government or non-profit
# use, so long as this copyright notice and the header above remain intact.
# Any commercial use should be registered.  Please also send me an email,
# and let me know where you are using this script. By using this program 
# you agree to indemnify Gossamer Threads Inc. from any liability.
#
# Selling the code for this program without prior written consent is
# expressly forbidden.  Obtain written permission before redistributing this
# program over the Internet or in any other medium.  In all cases
# copyright and header must remain intact.
#
# Please check the README file for full details on registration.
# =====================================================================
#
#   Form Input: 
#       'type'  : can be either 'keyword' or 'phrase'. 
#       'bool'  : can be either 'and' or 'or'.
#       'mh'    : the maximum number of hits, can be either 10, 25, 50, 100.
#
#   Setup:
#       Make sure the require statement below points to the config file.    

# Required Librariers
# --------------------------------------------------------
eval {
	($0 =~ m,(.*)/[^/]+,)   && unshift (@INC, "$1");	# Get the script location: UNIX /
	($0 =~ m,(.*)\\[^\\]+,) && unshift (@INC, "$1");	# Get the script location: Windows \

    require "admin/links.cfg";				# Change this to full path to links.cfg if you have problems.
    require "$db_lib_path/db.pl";
    require "$db_lib_path/site_html.pl";
};
if ($@) {
    print "Content-type: text/plain\n\n";
    print "Error including libraries: $@\n";
    print "Make sure they exist, permissions are set properly, and paths are set correctly.";
	exit;
}

# ========================================================

eval { &main; };					        # Trap any fatal errors so the program hopefully 
if ($@) { &cgierr("fatal error: $@"); }     # never produces that nasty 500 server error page.
exit;   # There are only two exit calls in the script, here and in in &cgierr. 

sub main {
# --------------------------------------------------------
    local (%in) = &parse_form;  
    
    if (keys %in > 0) {
        local (%link_results, @category_results);   # Holds our Link and Category Search Results.
        local ($date) = &get_date;				  # Current Date.
        local ($time) = &get_time;				  # Current Time.
        local ($link_hits, $cat_hits, $maxhits, $nh) = 0;
        local ($next, $next_hits, @search_terms, $grand_total);
        my($cat_clean);
    
        if ($in{'nh'}) { $nh = $in{'nh'}; }         # 'nh' controls the search next n results variable.

# Determine the maximum number of hits, can only be 10, 25, 50 or 100. Defaults to 25.
        if ($in{'mh'} && (($in{'mh'} == 10) || ($in{'mh'} == 25) || ($in{'mh'} == 50) || ($in{'mh'} = 100))) {
            $maxhits = $in{'mh'};
        }
        else {
            $maxhits = 25;          # Default Value.
        }

# See whether we are searching for phrases or keywords..
# If it's a phrase, we only have one search term, otherwise we have
# multiple terms.
        if ($in{'type'}) {
            @search_terms = ($in{'query'}) if ($in{'type'} eq 'phrase');
            @search_terms = split (/\s/, $in{'query'}) if ($in{'type'} eq 'keyword');
        }
        else {
            $in{'type'} = 'keyword';
            @search_terms = split (/\s/, $in{'query'}) if ($in{'type'} eq 'keyword');
        }

# Boolean connector defaults to "and".      
        if (!$in{'bool'}) { $in{'bool'} = 'and'; }

# See if we have something to search for.       
        if ($in{'query'}) {
            &search();      # Performs the search and stores results in link_results and category_results.
            if ((keys %link_results > 0) || ($#category_results > 0)) {
# Build the HTML for the category results and store it in "$category_results". 
				foreach $category (@category_results) {
					$cat_hits++;
					$cat_clean = &build_clean($category);
					$linked_title = &build_linked_title ($category);
					$category_results .= qq|<li>$linked_title\n|;
				}

				$cat_hits = 0 if (!$cat_hits);
				$lowrange = $nh * $maxhits + 1;
				$highrange = ($nh+1) * $maxhits;

# Go through each category of links returned, and build the HTML. Store in hash %link_output.
				SETOFLINKS: foreach $setoflinks (sort keys %link_results) {         
					LINK: for ($i = 0; $i < ($#{$link_results{$setoflinks}}+1) / ($#db_cols + 1); $i++) {
					    $link_hits++;
					    if (($link_hits <= $highrange) && ($link_hits >= $lowrange)) {
					        %tmp = &array_to_hash ($i, @{$link_results{$setoflinks}});
					        $link_output{$setoflinks} .= &site_html_link (%tmp) . "\n";
					    }					   
					}
				}
# Go through the hash just built, and build the complete link output. Store in $link_results.               
				foreach $setoflinks (sort keys %link_output) {
					$cat_clean = &build_clean ($setoflinks);
					$title_linked = &build_linked_title ($setoflinks);
					$link_results .= qq|<P>$title_linked\n|;
					$link_results .= $link_output{$setoflinks};
				}
# If we want to bold the search terms...
				if ($search_bold) {
					foreach $term (@search_terms) {
# This reg expression will do the trick, and doesn't bold things inside <> tags such as 
# URL's.
					    $link_results =~ s,(<[^>]+>)|(\Q$term\E),defined($1) ? $1 : "<STRONG>$2</STRONG>",gie;
					    $category_results =~ s,(<[^>]+>)|(\Q$term\E),defined($1) ? $1 : "<STRONG>$2</STRONG>",gie;
					}
				}
# Check to see if we've returned more hits then are maximum allowed..               
				if ($link_hits > ($maxhits * ($nh+1))) {
# If so, we build a $next variable that has the url to get then next n hits.				
					$next = $ENV{'QUERY_STRING'}; 
					if ($in{'nh'}) { 
					    $nh = $in{'nh'} + 1;
					    $next =~ s/&nh=(\d+)/&nh=$nh/; 
					}
					else           { $next .= "&nh=1"; }
					$next = $build_search_url . "?" . $next;
					if (($link_hits - ($maxhits * ($in{'nh'}+1))) <= $maxhits) {
					    $next_hits = ($link_hits - ($maxhits * ($in{'nh'}+1)));
					}
					else {
					    $next_hits = $maxhits;
					}
				}
				else {
					$next = "";
				}				   
# Print out the HTML results.				   
				&site_html_search_results;          
            }
            else {
				&site_html_search_failure ("no matching records");
            }
        }
        else {
            &site_html_search_failure ("no keywords specified");
        }
    }       
    else {
        &site_html_search_form();
    }
}

sub search {
# --------------------------------------------------------
# This routine does the actual search of the database.  

    open (DB, "<$db_file_name") or &cgierr("error in search. unable to open database: $db_file_name. Reason: $!");
        @lines = <DB>;      # Open the Database and read in all the lines.
    close (DB);
    $numhits = 0;
    LINE: foreach $line (@lines) {              # Search through the database
        if ($line =~ /^#/) { next LINE; }       # Skip comment lines.   
        if ($line =~ /^\s*$/) { next LINE; }    # Skip Blank Lines.
        $grand_total++;
        chomp ($line);					          
        @values = &split_decode($line);

        $match = 0; $andmatch = 1;
# Go through each term we have to search, and then check every field
# we have to match against.
        TERM: foreach $term (@search_terms) {
            FIELD: foreach $field (@search_fields) {            
# If it's a boolean OR search, we only need to match one field, and can quit
# if we do match.
				if ($in{'bool'} eq "or") {
					$match = $match || ($values[$field] =~ /\Q$term\E/i);
					last TERM if ($match);
				}
				else {
# Otherwise it's an AND search, so we have to match every term.
					$match = ($values[$field] =~ /\Q$term\E/i);
					last FIELD if ($match);
				}
            }
# This will go to 0 and stay 0 if $match ever equals 0.         
            $andmatch = $andmatch && $match;
        }       
        if (($in{'bool'} eq "or") && $match) {
# We have a match!      
            push (@{$link_results{$values[$db_category]}}, @values);
            $numhits++;
        }       
        elsif ($andmatch) {
# We have a match!      
            push (@{$link_results{$values[$db_category]}}, @values);
            $numhits++;
        }
        else { 
            # No Match.
        }

# Check to see if the category matches. First we check to make sure the
# category is not already in our @category_results.
        if (!(grep $_ eq $values[$db_category], @category_results)) {
            $match=0; $andmatch = 1;
            TERM: foreach $term (@search_terms) {
				if ($in{'bool'} eq "or") {
					$match = $match || ($values[$db_category] =~ /\Q$term\E/i);
					last TERM if ($match);
				}
				else {
					$match = ($values[$db_category] =~ /\Q$term\E/i);
				}
				$andmatch = $andmatch && $match;
            }

            if (($in{'bool'} eq "or") && $match) {
				$numcat++;
				push (@category_results, $values[$db_category]);
            }       
            elsif ($andmatch) {
				$numcat++;
				push (@category_results, $values[$db_category]);
            }
            else { 
				# No Match.
            }
        }
    }

    # Now let's Sort the Results, we borrow the link sort found in nph-build.cgi to
    # sort the links.
    foreach $link ( keys %link_results ) {
        @{$link_results{$link}} = &build_sorthit (@{$link_results{$link}});
    }
    @category_results = sort (@category_results);
}

sub build_linked_title {
# --------------------------------------------------------
# A little different then the one found in nph-build.cgi as it also
# links up the last field as well.

	my ($input) = $_[0];
	my (@dirs, $dir, $output, $path, $last);

	@dirs = split (/\//, $input);

	foreach $dir (@dirs) {
		$path .= "/$dir";
		$dir = &build_clean ($dir);
		$output .= qq|<A HREF="$build_root_url$path/">$dir</A>:|;
	}
	chop ($output);
	
	return $output;
}
