#!/usr/bin/perl

# name:    database.cgi
# purpose: Store a phone book in a flat text file

# Define Your Database Fields Here Like So
#	Field Name => [Number, 'Readable Name', 'type of field, two options are text or textarea'
%fields = (
	ID =>		[0,		'Link ID:',			'text'],
	Name =>		[1,		'Name:',			'text'],
	Email =>	[2,		'E-Mail Address:',		'text'],
	Phone =>	[3,		'Phone Number:',		'text'],
	Address =>	[4,		'Street Address:',		'textarea']
);

# IF you are not using ID as your key, specify the name here.
$db_key = 'ID';
$delimeter = "|";
$file = "./file.cgi";
$numberfile = "./num.cgi";


@db_fields = (); # Holds Specific Field Information
foreach $field (sort { $fields{$a}[0] <=> $fields{$b}[0] } keys %fields) {
$db_id{$field} = $fields{$field}[0];
$db_name{$field} = $fields{$field}[1];
$db_type{$field} = $fields{$field}[2];
push @db_fields, $field;
}

%form = &parse;

&print_headers;


if ($form{'action'} eq "addrecord") {
	&add_record;
}
elsif ($form{'action'} eq "add_record_two") {
	&add_record_two;
}
elsif ($form{'action'} eq "edit") {
	&edit;
}
elsif ($form{'action'} eq "edit_two") {
	&edit_two;
}
elsif ($form{'action'} eq "edit_three") {
	&edit_three;
}
elsif ($form{'action'} eq "edit_four") {
	&edit_four;
}
elsif ($form{'action'} eq "delete") {
	&delete_record;
}
elsif ($form{'action'} eq "delete_two") {
	&delete_two;
}
elsif ($form{'action'} eq "delete_three") {
	&delete_three;
}
elsif ($form{'action'} eq "search") {
	&header("Search For Something");
	&search_form("search_two");
	&footer;
}
elsif ($form{'action'} eq "search_two") {
	&search_two;
}
else {
&header("Simple Perl Database");
&footer;
}


sub header {
$title = shift;
print qq~
<HTML>
<HEAD>
<TITLE> $title </TITLE>
<basefont face="Trebuchet MS,Arial,Helvetica" size="2">
</HEAD>
<BODY bgcolor="#FFFFFF">
<CENTER>
~;
}

sub footer {
print qq~
<TABLE border=0 cellspacing=3 cellpadding=3>
<TR>
	<TD><a href="database.cgi?action=addrecord"><FONT COLOR="#000000" size=-1>Add A Record</a></TD>
	<TD><a href="database.cgi?action=edit"><FONT COLOR="#000000" size=-1>Modify a Record</TD>
	<TD><a href="database.cgi?action=delete"><FONT COLOR="#000000" size=-1>Delete a Record</TD>
	<TD><a href="database.cgi?action=search"><FONT COLOR="#000000" size=-1>Search</TD>
</TR>
</TABLE>
</CENTER>
</BODY>
</HTML>
~;
}


sub build_record_page {
my (%record) = @_;
my ($val) = "";
my ($html) = qq~<TABLE border=1 bgcolor="#FFFFFF" cellspacing=0 cellpadding=4>
	<TR bgcolor="#C0C0C0">
		<TD colspan=2><CENTER><font size=-1> Record </CENTER></TD>
	</TR>~;
	foreach $obj (@db_fields) {
		if ($obj eq $db_key) {
			next; # ID's are dynamically made, so why let the user do it?
		}
		$html .= qq~<TR bgcolor="#DDDDDD"><TD><FONT SIZE=-1>$db_name{$obj}</TD><TD>~;
		if ($db_type{$obj} eq "text") {  # Makes the text box
			if ($record{$obj}) { $val = qq~ VALUE="$record{$obj}"~; }
			else { $val = ""; }
			$html .= qq~<INPUT TYPE="text" NAME="$obj" size="20" $val>~;
		}
		elsif ($db_type{$obj} eq "textarea") { # Makes the textarea
			if ($record{$obj}) { $val = qq~$record{$obj}~; }
			else { $val = ""; }
			$html .= qq~<TEXTAREA NAME="$obj" ROWS="4" COLS="40">$val</TEXTAREA>~;
		}
		$html .= qq~</TD></TR>~;
	}
	$html .= "</TABLE><P>";
print $html;
}

sub print_headers {
# Print out the headers if they haven't already been printed.
	if (!$headers_printed) {		
		print "Content-type: text/html\n\n";
		$headers_printed = 1;
	}
}

sub get_next_id {
	my ($num);
	open (NUM, $numberfile);
		$num = <NUM>;
	close (NUM);
	$num++;
	open (NUM, ">".$numberfile);
	if ($flock) {
		flock(NUM, 2)
	}
	print NUM $num;
	close (NUM);
	return $num;
}


sub process_record {
# changes the data format to something we can use
    my (@array) = @_;
    my (%record);
	my ($num) = 0;
	# map is similar to grep, in that it evaluates each list entry, and returns the new list with changes made.
	# this line looks at the available fields in $db_fields, and causes the array to go back to a usable
	# hash format.
	%record = map { $db_fields[$num] => $array[$num++] } @_;
    return %record;
}


sub grab_data {
# Takes a record, and grabs it into an array
    my ($line) = shift;    
    my (@data) = split (/\Q$delimeter\E/o, $line);
	    foreach (@data) {
        s/``/\n/g;          # Change `` back to newlines..
        s/~~/$delimeter/g;   # get the delimiter back
    }
    return @data;
}

sub get_record {
my ($exist) = 0;
my ($key) = shift;
	open(DATA, $file);
	while (<DATA>) {
		(/^\s*$/) and next; # Looks for blank lines
		chomp $_;
		@record = &grab_data($_);
		%dat = process_record(@record);
		if ($dat{$db_key} eq $key) {
			$exist = 1;
			last;
		}
	}
	close (DATA);
	$exist ? return (%dat) : return;
}


sub make_data {
    my %record = @_;
    my ($rec, $line) = ""; 
    foreach $field (@db_fields) {  # repeats for all of your configured fields             
        $rec = $record{$field};
        $rec =~ s/\r//g;                # Scrap that Windows Line Feed
		# This pattern below compiles once, as we dont want any weird results.
        $rec =~  s/\Q$delimeter\E/~~/og;  # Scraps the delimeter if used, and makes it ~~
        $rec =~ s/\n/``/g;              # Grabs Newlines, and makes them ``
        $line .= $rec.$delimeter;    # Your Record is Being Made
    }
    chop $line; # Whoops... gotta scrap that delimiter at the end (extra one)
    return $line."\n"; # returns the new record, with the nice shiny line feed
}

sub add_record {
	&header("Add a Record");
	print qq~
	<CENTER>
	<FORM METHOD=POST>
	<INPUT TYPE="hidden" NAME="action" VALUE="add_record_two">~;
	&build_record_page;
	print qq~
	<CENTER>
	<INPUT TYPE="submit" value="Enter Into Database"></CENTER>
	<CENTER>
	</FORM>~;
	&footer;
}

sub add_record_two {
	$form{$db_key} = &get_next_id();
	my ($line) = &make_data(%form);
	open (DATABASE, ">>".$file);
		if ($flock) {
			flock(DATABASE, 2)
		}
	print DATABASE $line;
	close (DATABASE);
	&header("Add a Record Successful");
	print qq~<CENTER>Here Is Your Record</CENTER><p>~;
	my (%result) = get_record($form{$db_key});
	if (%result) {
	&build_record_page(%result);
	}
	else {
	print "Error - No Record Added";
	}
	&footer;
}

sub search {
	my (%dat);
	my ($or_match) = 0;
	my ($findit,$param) = "";
    my @search_terms = ();
	($form{'type'} eq 'phrase') ?
        (@search_terms = ($form{'search_term'})) :
        (@search_terms = split (/\s/, $form{'search_term'}));
	if ($form{'boolean'} eq "or") { $or_match = 1;}
	if ($or_match) { $param = '||' } else { $param = '&&'; }
	foreach $term (@search_terms) {
		next if (length($term) < 2); # skips single letter terms
		if ($form{'field'} eq "everything") {
			$findit .= "/\Q$term\E/oi $param ";
		}
		else {
			$findit .= "\$dat{\$form{'field'}} =~ /\Q$term\E/oi $param ";
		}
	}
chop ($findit); chop ($findit); chop ($findit);
$reg = eval "sub { $findit; }"; 
$@ and print "Error Processing Search" and return;
	open(DATA, $file);
	while (<DATA>) {
		(/^\s*$/) and next; # Looks for blank lines
		chomp $_;
		if ($form{'field'} eq "everything") {
			if (&{$reg}) {	push @search_results,$_;  }
		}
		else {
			@record = &grab_data($_);
			%dat = &process_record(@record);
			if (&{$reg}) { push @search_results,$_;	}
		}
	}
	close (DATA);
@search_results = &search_sorter(@search_results);
return (@search_results);
}

sub search_sorter {
my (@results) = @_;
my(@rec);
my (%temp_rec,$eval_code);
$stop = @db_fields;

foreach $result (@results){
    (@rec) = &grab_data($result);    
	$eval_code ='$temp_rec{$rec[0]} = { $db_key => "$rec[0]", ';

	for($i=1;$i<$stop;$i++){
		  $eval_code .= "\$db_fields[$i] => \"\$rec[$i]\",\n";
    }
    $eval_code .= '};';
     eval $eval_code;
  } 
 $sort_field = $form{'sort_field'};

  @results=();
  foreach $field (sort { lc($a->{$sort_field}) cmp lc($b->{$sort_field}) } values %temp_rec){
    $new_record = "";
    for($i=0;$i<$stop;$i++){
		 # Now We have to make sure all of our fields are encoded again 	so it looks right
		 $field->{$db_fields[$i]} =~  s/\Q$delimeter\E/~~/og;
         $field->{$db_fields[$i]} =~ s/\n/``/g;
         $new_record .= "$field->{$db_fields[$i]}\|";
    } 
    chop $new_record; 
    push @results, $new_record;
  }
  
  return (@results);
}

sub search_two {
	my (@results) = &search;
	$search_num = @results;
	if ($search_num < 1) {  &nomatches; }
	&multi_match_view(@results) if ($search_num > 0);
}

sub nomatches {
		my $colspan = @db_fields;
		&header("No Matches Found For ". $form{'search_term'});
		print qq~
		<CENTER><TABLE border=1 bgcolor="#FFFFFF" cellpadding=3 cellspacing=0>
			<TR bgcolor="#C0C0C0">
			~;
			foreach $field (@db_fields) {
				print qq~<TD><CENTER><FONT size=-1>$field</CENTER></TD>~;
			}
			print qq~</TR>
			<TR bgcolor="#DDDDDD">
			<TD colspan=~.$colspan.qq~><CENTER><FONT size=-1><B>No Matches Found For "$form{'search_term'}"</B></CENTER></TD></TR></TABLE></CENTER>
			~;
		&footer;
}


sub multi_match_view {
my (@results) = @_;
my (%rec);
&header($search_num." Matches Found For ". $form{'search_term'});
print qq~
<CENTER><TABLE border=1 bgcolor="#FFFFFF" cellpadding=3 cellspacing=0>
	<TR bgcolor="#C0C0C0">
~;
foreach $field (@db_fields) {
print qq~<TD><CENTER><FONT size=-1>$field</CENTER></TD>~;
}
print qq~</TR>~;
foreach $result (@results) {
		%rec = &process_record(&grab_data($result));
 		print qq~<TR bgcolor="#DDDDDD">~;
			foreach $field (@db_fields) {
				print qq~<TD><FONT size=-1>~.&nl2br($rec{$field}).qq~</TD>~;
			}
		print qq~</TR>~;
}
print qq~</TABLE></CENTER>~;
&footer;
}

sub multi_match {
my ($type,$action,$what,@results) = @_;
my (%rec);
&header($search_num." Matches Found For ". $form{'search_term'});
print qq~
<CENTER><FORM METHOD=POST>
<INPUT TYPE="hidden" name="action" value="$action">
<TABLE border=1 bgcolor="#FFFFFF" cellpadding=3 cellspacing=0>
	<TR bgcolor="#C0C0C0">
	<TD><CENTER><FONT size=-1>Select</CENTER></TD>
~;
foreach $field (@db_fields) {
print qq~<TD><CENTER><FONT size=-1>$field</CENTER></TD>~;
}
print qq~</TR>~;
foreach $result (@results) {
		%rec = &process_record(&grab_data($result));
 		print qq~<TR bgcolor="#DDDDDD"><TD><INPUT TYPE="$type" NAME="key" value="$rec{$db_key}"></TD>~;
			foreach $field (@db_fields) {
				print qq~<TD><FONT size=-1>~.&nl2br($rec{$field}).qq~</TD>~;
			}
		print qq~</TR>~;
}
print qq~</TABLE><p><INPUT TYPE="submit" value="$what Record(s)">
</FORM></CENTER>~;
&footer;
}


sub edit {
&header("Search To Edit");
&search_form("edit_two","To Edit");
&footer;
}

sub edit_two {
	my (@results) = &search;
	$search_num = @results;
	if ($search_num < 1) { &nomatches; }
	elsif ($search_num > 0) {
	&multi_match("radio","edit_three","Edit",@results);
	}
}

sub edit_three {
	&header("Edit Record");
	print qq~
	<CENTER><TABLE border=1 bgcolor="#FFFFFF" cellpadding=3 cellspacing=0>
	<TR bgcolor="#C0C0C0"><TD>
	<CENTER>
	<FORM METHOD=POST>
	<INPUT TYPE="hidden" NAME="action" VALUE="edit_four">
	<INPUT TYPE="hidden" NAME="key" VALUE="$form{'key'}">~;
	my (%result) = get_record($form{'key'});
	if (%result) { &build_record_page(%result); }
	else { print qq~Error --- Record Does Not Exist ~; }

	print qq~
	<CENTER>
	<INPUT TYPE="submit" value="Edit Record"></CENTER>
	<CENTER>
	</FORM></TD></TR></TABLE></CENTER>~;
	&footer;
}

sub edit_four {
	$form{$db_key} = $form{'key'};
	my ($line) = &make_data(%form);
	my ($found) = 0;
	my ($output) = "";
	open (DATABASE, $file);
	while (<DATABASE>) {
		chomp($_);
		(/^\s*$/) and next;
		my (%dat) = &process_record(&grab_data($_));
		if ($dat{$db_key} eq $form{'key'}) {
			$output .= $line;
			$found = 1;
		}
		else {
			$output .= "$_\n";
		}
	}
	close (DATABASE);
	if ($found) {
		open (DATABASE, ">".$file);
		if ($flock) {
			flock(DATABASE, 2);
		}
		print DATABASE $output;
		close (DATABASE);
	}
		&header("Record Edited Successful");
		print qq~<CENTER>Here Is Your Record<p>~;
		my (%result) = get_record($form{'key'});
		if (%result) { &build_record_page(%result); }		
		else {	print "Error - No Record Added";	}
		print qq~</CENTER>~;
		&footer;
}


sub delete_record {
&header("Search To Delete");
&search_form("delete_two","To Delete");
&footer;
}

sub delete_two {
	my (@results) = &search;
	$search_num = @results;
	if ($search_num < 1) { &nomatches; }
	elsif ($search_num > 0) {
	&multi_match("checkbox","delete_three","Delete",@results);
	}
}


sub delete_three {
	my (@keys) = split (/,/,$form{'key'});
	my ($output) = "";
	my ($found) = 0;
	open (DATABASE, $file);
	while (<DATABASE>) {
		chomp($_);
		(/^\s*$/) and next;
		my (%dat) = &process_record(&grab_data($_));
		foreach $key (@keys) {
			if ($dat{$db_key} eq $key) {
				$found = 1;
			}
		}
		if ($found) {
			$found = 0;
			next;
		}	
		else {
				$output .= "$_\n";
		}
	}
	close (DATABASE);
	open (DATABASE, ">".$file);
	if ($flock) {
		flock(DATABASE, 2);
	}
	print DATABASE $output;
	close (DATABASE);
	&header("Record Edited Successful");
	print qq~<CENTER><font size=-1>Record(s) Deleted></CENTER>~;
	&footer;
}

sub search_form {
	$action_val = shift;
	$text = shift;
	my ($check) = "";
	print qq~
	<FORM METHOD=post>
	<INPUT TYPE="hidden" NAME="action" VALUE="$action_val">
	<TABLE border=1 bgcolor="#FFFFFF" cellspacing=0 cellpadding=4>
	<TR bgcolor="#C0C0C0">
		<TD colspan=2><CENTER><font size=-1> Search For a Record $text </CENTER></TD>
	</TR>
	<TR bgcolor="#DDDDDD">
		<TD><font size=-1>Search Term	</TD>
		<TD><font size=-1><INPUT TYPE="text" NAME="search_term" size=20>&nbsp;&nbsp;Type <INPUT TYPE="radio" NAME="type" value="phrase">Phrase&nbsp;&nbsp;<INPUT TYPE="radio" NAME="type" value="keywords" CHECKED>Keywords</TD>
	</TR>
	<TR bgcolor="#DDDDDD">
		<TD><font size=-1>Boolean Connector</TD>
		<TD><font size=-1>&nbsp;&nbsp;<INPUT TYPE="radio" NAME="boolean" value="and" CHECKED>AND&nbsp;&nbsp;<INPUT TYPE="radio" NAME="boolean" value="or">OR</TD>
	</TR>
	<TR bgcolor="#DDDDDD">
		<TD><font size=-1>Search Fields</TD>
		<TD>
		<font size=-1>All: <INPUT TYPE="radio" NAME="field" value="everything" CHECKED>~;
	foreach $field (@db_fields) {
		if ($field eq $db_key) { next; }
		print qq~&nbsp;&nbsp;$db_name{$field}<INPUT TYPE="radio" NAME="field" value="$field">
		~;
	}
	print qq~</TD></TR>
	<TR bgcolor="#DDDDDD">
		<TD><font size=-1>Sort By</TD>
		<TD>~;
	foreach $field (@db_fields) {
		if ($field eq "ID") { $val = " CHECKED"; } else { $val = "" };
		print qq~<font size=-1>&nbsp;&nbsp;$db_name{$field}<INPUT TYPE="radio" NAME="sort_field" value="$field"$val>
		~;
	}
	print qq~</TD></TR></TABLE>~;
	print qq~<CENTER><INPUT TYPE="submit" value="Search"></CENTER></FORM>~;
}

sub nl2br {
#changes newlines to <br>'s
	my ($tmp) = shift;
	$tmp =~ s/\n/\n<br>/g;
	return ($tmp);
}

sub parse {
my (%temp);
	(*fval) = @_ if @_ ;
	local ($buf);
	if ($ENV{'REQUEST_METHOD'} eq 'POST') {	read(STDIN,$buf,$ENV{'CONTENT_LENGTH'}); }
	else { $buf=$ENV{'QUERY_STRING'}; }
	if ($buf eq "") { return 0 ; }
	else {
 		@fval=split(/&/,$buf);
		foreach $i (0 .. $#fval) {
			($name,$val)=split (/=/,$fval[$i],2);
			$val=~tr/+/ /;
			$val=~ s/%(..)/pack("c",hex($1))/ge;
			$name=~tr/+/ /;
			$name=~ s/%(..)/pack("c",hex($1))/ge;
			if (!defined($temp{$name})) { $temp{$name}=$val; }
			else {  $temp{$name} .= ",$val"; }
		   }
		}
return (%temp);
}
