package MOJO::List::PlainText; 

use lib qw (./ ../ ../../); 
 
use MOJO::Config; 
use Fcntl qw(
O_WRONLY 
O_TRUNC 
O_CREAT 
O_CREAT 
O_RDWR
O_RDONLY
LOCK_EX
LOCK_SH 
LOCK_NB); 

use MOJO::Guts;
use MOJO::Log;
my $log = new MOJO::Log;
use strict; 

sub new {
	my $class = shift;
	my %args = (-List => undef, 
				-Path => $FILES, 
				@_); 
	   my $self = {};			
       bless $self, $class;
	   $self->_init(%args); 
	   return $self;
}

sub _init  { 
    my $self = shift; 
    my %args = @_; 
    $self->{fields} = {%args}; 
}



#######################################################################



=pod

=head1 NAME List::PlainText

=head1 DESCRIPTION

This is the Plain Text version of Mojo Mail's subscriber database.

=head1 SYNOPSIS

my $lh = List::Plaintext->new(-List => $list); 

=head2 to $lh->open_email_list(-Path => $path, -Type='list');

returns an array of all e-mail addresses for $list. It can also return only a reference 
you say -As_Ref => 1, 

this is used mostly for the black list functions, as its painfully clear that loading up 
100,000 email addreses into memory is a Bad Thing. 

=cut

# note. BAD to do on large lists. Bad Bad Bad

sub open_email_list { 

	my $self = shift; 	
	my %args = (-Type      => 'list',
				-As_Ref    => 0, 
				-Sorted    => 1,
				@_);
	
	my $FILES       = $self->{fields}->{-Path};
	my $list        = $self->{fields}->{-List} || undef; 
	my $file_ending = $args{-Type}; 
	my $want_ref    = $args{-As_Ref}; 
	   
	
	if($list){
	
	my @list     = (); 
	my @bad_list = (); 
	
	#untaint 
	$list = make_safer($list); 
	$list =~ /(.*)/; 
	$list = $1; 
	
	#untaint 
	$file_ending = make_safer($file_ending); 
	$file_ending =~ /(.*)/; 
	$file_ending = $1; 
	
	my $list_name = "$list.$file_ending";
	sysopen(LIST, "$FILES/$list_name", O_RDWR|O_CREAT, $FILE_CHMOD)or die "couldn't open $FILES/$list_name for reading: $!\n";
	 
	flock(LIST, 1);
	  @bad_list = <LIST>;
	close (LIST);

	foreach(@bad_list) { 
	 $_  =~  s/^\s+|\s+$//o;
	}

	foreach(@bad_list) { 
		if ($_ ne ""){ 
			push(@list, $_); 
		}
	}

	 @list = sort(@list); 

	if($want_ref eq "1"){ 
		return \@list; 
	}else{ 
		return @list; 
	}

	}else{ 
		return undef;
	}
}


=pod

=head2 open_list_handle(-List => $list) 

This function will open the email list file with a handle of LIST, 
it also does a whole bunch of error checking, taint checking, etc, that I'm 
too lazy to do everytime I want a list open. 

=cut 


sub open_list_handle { 

	my $self = shift; 
	my %args = (-Type => 'list',@_,); 
	my $the_lists = $self->{fields}->{-Path};
	
	#untaint 
	my $list = $self->{fields}->{-List} || undef; 
	if($list){ 
	
	   $list = make_safer($list); 
	   $list =~ /(.*)/; 
	   $list = $1; 
	
	#untaint 
	my $file_ending = make_safer($args{-Type}); 
	   $file_ending =~ /(.*)/; 
	   $file_ending = $1; 
	my $list_name = "$list.$file_ending";
	
	
	sysopen(LIST, "$the_lists/$list_name", O_RDWR|O_CREAT, $FILE_CHMOD) or die "couldn't open $FILES/$list_name for reading: $!\n";
		   flock(LIST, LOCK_SH);
	}

}



# note. BAD to do on large lists. Bad Bad Bad
sub sort_email_list { 	
	my $self = shift; 
	my %args = (-Type  =>  'list', 
				  @_);
	
	$self->open_list_handle(-Type => $args{-Type});
						 
		my @list = <LIST>; 
		   @list = sort(@list);            
		close(LIST); 
	$self->add_to_email_list(%args,
						  -Email_Ref => [@list],
						  -Mode=> 'writeover'); 
}


=pod

=head2 my $matches = search_email_list(-Method => $method, -Keyword=> $keyword); 

searching through emails happens here, we can search using three methods, 'domain', where we search 
for '.com', '.edu' etc, 'service' where we look for things like 'altavista', 'yahoo', 'hotmail' adn things like that. 
the results will be printed out from here, the function returns how many results have been found. 
 

=cut 

sub search_email_list { 

	my $self = shift; 
	
	my %args = (
	-Keyword  => undef,
	-Method   => undef, 
	-Type     => 'list',
	@_
	);
	my @matches; 
	my $domain_regex = "(.*)\@(.*)"; 
	my $found = 0; 
	
	my $method  = $args{-Method} || "standard"; 
	my $keyword = $args{-Keyword}; 
	   $keyword = quotemeta($keyword); 
	   
	if($self->{fields}->{-List}){ 
	
	
	#open the list and grab what we can, 
	$self->open_list_handle(-Type => $args{-Type});
	
	my $email; 
	
	if(defined($keyword) && $keyword ne ""){
	if($method eq "domain"){ 
		while(defined($email = <LIST>)){ 
			if($email =~ m/$domain_regex$keyword$/i){ 
				 print"<input type=\"checkbox\" name=\"address\" value=\"$email\">&nbsp;&nbsp; $email<br>\n";
				
				
			$found++;
			}
		}
	}elsif($method eq "service"){ 
		while(defined($email = <LIST>)){ 
			if($email =~ m/\@$keyword$/i){ 
				
				#push(@matches, $email); 
				print"<input type=\"checkbox\" name=\"address\" value=\"$email\">&nbsp;&nbsp; $email<br>\n";
				
			$found++;  
			}	   
		}  
	}else{ 
		while(defined($email = <LIST>)){ 
			if($email =~ m/$keyword/i){ 
				print"<input type=\"checkbox\" name=\"address\" value=\"$email\">&nbsp;&nbsp; $email<br>\n";
				
				$found++;
				}
			}
		}
	}
	
	return($found); 
	
	close(LIST); 
	
	}else{ 
	
	return undef;
	}

}


=pod


=head2 my $count = print_out_list(-List => $list);

This function will print out a list. 
Thats it. 


=cut

=pod

=head2 get_black_list_match(\@black_list, \@list); 


zooms through the black list and does regex matches on email addresses to see 
of they match. 




=cut
 
sub get_black_list_match{ 

my $self = shift; 

	my @got_black; 
	my @got_white; 

	my $black_list = shift; 
	my $try_this = shift; 

if($black_list and $try_this){ 

	my $black; 

	foreach $black(@$black_list){ 
			
		my $try; 

		foreach $try(@$try_this){ 
			my $qm_try = $try; 
			if($qm_try =~ m/$black/i){ 
				push(@got_black, $try); 
			}
		}
	}
	return (\@got_black)
}else{ 

return 0;
}

}




sub print_out_list { 

	my $self = shift;
	 	
	my %args = (-Type => 'list',
				-FH  => \*STDOUT,
				@_); 
	my $fh = $args{-FH};
	my $email; 
	my $count; 
	if($self->{fields}->{-List}){ 
		$self->open_list_handle(-Type => $args{-Type}); 
		while(defined($email = <LIST>)){ 
			#	chomp($email);
			print $fh $email; 
			$count++; 
			
		}
		close (LIST);            
		return $count; 
	}

}






=pod

=head2 check_for_double_email(-Path => $path, -List => $list, -Email => $email); 

checks to see if an e-mail is already in $list. returns 0 is it aint, 1 if it is. 

=cut

sub check_for_double_email {
	my $self = shift; 
	
	my %args = ( 
	-Email          => undef,
	-Type           => 'list', 
	@_
	);



	
	if($self->{fields}->{-List} and $args{-Email}){ 
		$self->open_list_handle(-Type => $args{-Type});	 
		my $check_this; 
		my $email = $args{-Email}; 
		my $in_list =0; 



						 
	while(defined($check_this = <LIST>)){ 
		chomp($check_this);
		if($args{-Type} eq "black_list"){ 
				if(cased($email) =~ m/$check_this/i){
					 $in_list=1;
					 last;
				}
			}else{
			
				if(cased($check_this) eq cased($email)){
					$in_list=1;
					last;
				}
			}
		}
		close (LIST);
		return $in_list; 
	}else{ 	
		return undef;
	}

}



sub add_to_email_list { 

	my $self = shift; 
	
	my %args = (-Email_Ref => undef, 
				-Type      => "list",
				-Mode      => 'append',
				@_);
	
	my $address = $args{-Email_Ref} || undef;
	my $email_count = 0;
	my $ending = $args{-Type}; 
	my $write_list = $self->{fields}->{-List} || undef; 
	
	
	if($write_list and $address){
	
	$write_list =~ s/ /_/i; 
	#untaint 
	$write_list = make_safer($write_list); 
	$write_list =~ /(.*)/; 
	$write_list = $1; 
	
	
	#untaint 
	$ending = make_safer($ending); 
	$ending =~ /(.*)/; 
	$ending = $1; 
	
	
	if($args{-Mode} eq 'writeover'){ 
	
	
	sysopen(LIST, "$FILES/$write_list.$ending",  O_WRONLY|O_TRUNC|O_CREAT,  $FILE_CHMOD) or 
				die "couldn't open $FILES/$write_list.$ending for writing: $!\n";
	}else{ 
	
	open (LIST, ">>$FILES/$write_list.$ending") or 
		die "couldn't open $FILES/$write_list.$ending for reading: $!\n";
	}	
	
	
	flock(LIST, 2);
	foreach(@$address){
		chomp($_);
		$_ = strip($_); 
		print LIST "$_\n";
		$email_count++;
		# js - log it
		$log->mj_log($self->{fields}->{-List},"Subscribed to $write_list.$ending", $_) if (($LOG{subscriptions}) && ($args{-Mode} ne 'writeover')); 
	}
		close(LIST);
		return $email_count; 
	}else{ 
		warn('Mojo Mail Error: No list, or list ref was given in add_email_list() in Plain_Text.pm');
		return undef;
	}
}



=pod

=head2 my $count = remove_from_list(-Email_List => \@array);


This is used to remove addesses from a list. It creates a temp file and 
writes only the addresses that AREN'T in the array reference. it then deletes
list file and renames the temp file the list file's name. List files are named 
$list.list where list is the list's name, with underscores instead of spaces. 
The list is formatted simply as one email per line. 



=cut
 

sub remove_from_list { 

my $self = shift; 

my %args = ( 
-Email_List => undef, 
-Type       => 'list',
@_,
); 

my $list     = $self->{fields}->{-List}; 
my $path     = $self->{fields}->{-Path};
 
my $type     = $args{-Type}; 
my $deep_six = $args{-Email_List}; 


if($list and $deep_six){ 

	# create the lookup table 
	my %lookup_table; 
	foreach my $going(@$deep_six){
		chomp($going);
		$going = strip($going);
		$lookup_table{$going} = 1;
	}


	# the lookup table holds addresses WE DON'T WANT. SO rememeber that, u.
    my $main_list = "$path/$list.$type";     
	my $message_id = message_id();	
	my $temp_list = "$main_list.tmp-$message_id";
	my $count; 


########################################################################
# this is my big hulking Masterlock that you'll need a shotgun 
# to blow off. This is me being anal retentive.
#sysopen(SAFETYLOCK, "$FILES/$list.lock",  O_RDWR|O_CREAT, 0777); 
	
sysopen(SAFETYLOCK, "$FILES/$list.lock",  O_RDWR|O_CREAT, $FILE_CHMOD) 
	or die "Mojo Mail $VER Error - Cannot open list lock file '$FILES/$list.lock' - $!";

	{
	
	my $sleep_count = 0; 
		{ 
		flock SAFETYLOCK, LOCK_EX | LOCK_NB and last; 
		sleep 1;
		redo if ++$sleep_count < 11; 
		
		# ok, we've waited 'bout 10 seconds... 
		# nothing's happening, let's say  fuck it. 
		
		warn "Mojo Mail $VER Warning: Server is way too busy to unsubscribe people, waited 10 seconds to get access to the list file for $list, giving up: $!\n";
	
		return 'too busy'; 
		exit(0); 
	}
}


# safety lock is set. This should give us a nice big shield to do some file 
# juggling and updating. I think there is a race condition between when the 
# the first time the temp and list file are open, and the second time. 
# This should stop that. wee. 
############################################################################		

    #open the original list
	sysopen(MAIN_LIST, $main_list,  O_RDONLY|O_CREAT, $FILE_CHMOD) or 
		die "Mojo Mail $VER Error: Can't open email list to sort through and make deletions at '$main_list': $!";
	flock(MAIN_LIST, LOCK_SH) or 
		die "Mojo Mail $VER Error: Can't create a exclusive lock to sort through and make deletions at '$main_list': $!";
  
    # open a temporary list
	sysopen(TEMP_LIST, $temp_list,  O_RDWR|O_CREAT, $FILE_CHMOD) or 
		die "Mojo Mail $VER Error: can't create temporary list to sort out deleted e-mails at '$temp_list': $!" ; 
	flock(TEMP_LIST, LOCK_EX) or
		die "Mojo Mail $VER Error: can't create an exculsive lock to sort out deleted e-mails at'$temp_list': $!" ; 	
		
	my $check_this; 

	while(defined($check_this  = <MAIN_LIST>)){ 
	     #lets see, if they pass, send em over. 
	     chomp($check_this); 
	     $check_this = strip($check_this);
	     
	     # unless its in out delete list, 
	      unless(exists($lookup_table{$check_this})){ 
	          # print it into the temporary list
		      print TEMP_LIST $check_this, "\n";

	      }else{
		      #missed the boat! 
		      $count++;
		      
		      # js - log it
				$log->mj_log($self->{fields}->{-List},"Unsubscribed from $list.$type", $check_this) if $LOG{subscriptions}; 
	      }

	}


	close (MAIN_LIST) or 
		die "Mojo Mal $VER Error - did not successfully close file '$main_list': $!"; 
		
	close (TEMP_LIST) or 
		die "Mojo Mal $VER Error - did not successfully close file '$temp_list': $!";

#open the new list, open the old list, copy old to new, done. 


	sysopen(TEMP_LIST, $temp_list,  O_RDONLY|O_CREAT, $FILE_CHMOD) or 
		die "Mojo Mail $VER Error: Can't open temp email list '$temp_list' to copy over to the main list : $!";
	flock(TEMP_LIST, LOCK_SH) or 
		die "Mojo Mail $VER Error: Can't create a exclusive lock to copy over email addresses at  '$temp_list': $!";


	sysopen(MAIN_LIST, $main_list,  O_WRONLY|O_TRUNC|O_CREAT, $FILE_CHMOD) or 
		die "Mojo Mail $VER Error: can't open email list to update '$main_list': $!" ; 
	flock(MAIN_LIST, LOCK_EX) or
		die "Mojo Mail $VER Error: can't create an exclusive lock to update '$main_list': $!" ; 	
		
		
	my $passed_email;	
	while(defined($passed_email  = <TEMP_LIST>)){ 
	     #lets see, if they pass, send em over. 
	     chomp($passed_email); 
		 print MAIN_LIST $passed_email, "\n";
	}
	
		close (MAIN_LIST) or 
		die "Mojo Mal $VER Error - did not sucessfully close file '$main_list': $!"; 
		
		close (TEMP_LIST) or 
		die "Mojo Mal $VER Error - did not sucessfully close file '$temp_list': $!";
		
		unlink($temp_list) or 
	    warn "Mojo Mail  $VER Error: Could not delete temp list file '$temp_list': $!"; 

close(SAFETYLOCK);
unlink("$FILES/$list.lock") or warn "couldn't delete lock file: '$FILES/$list.lock' - $!";

 
return $count; 
	
}else{ 

	warn('Mojo Mail Error: No list information given at Plain_Text.pm for remove_from_list()');
	return ('no list');
}
 			 
}


=pod

=head2 my $listpath = create_bulk_sending_file(-List => $list, -ID => $message_id); 

When sending list messages to the entire list, we make a temp file called $istname.list.$listid
where $listname is the name of your list, and $list_id is an id number, usually made form the list, 
and created form the message ID if we have one. This also creates the pin number for each emal address and 
saves it into this file like so: 

	email::pin 

This can easilly be feed into either the Mail::Bulkmail module or our homebrew batch system. 
after the Lists are send MOJO::Mail.pm should remove this file. 


This function can also pass a reference to an array of addresses that shouldn't get sent the lsit message, 
you could theoretically pass the black list, or for mojo_send.pl, the mail alias address you set up. 

				-Ban => [$address_on, $address_two], 
				
=cut


=pod

=head2 my ($count, \%domains,\%SERVICES) = list_option_form 

this is brand new shiny backend for the 'View List' Control Panel.
It does a whole bunch of things, 
first off, it prints each email address in an option tag, for a select box, like this: 

 <option value=$email>$email</option>

It will also count how many email addresses match each 'top level domains' and 'services' 
these are specifies in the Config.pm file in the @DOMAINS array  and the %SERVICES hash.  You can also turn these off 
in the Config.pm by setting $SHOW_DOMAIN_TABLE, $SHOW_SERVICES_TABLE and $SHOW_EMAIL_LIST to 0, respectively. 
 

=cut

sub list_option_form { 

my $self = shift; 

my %args = (
-Type           =>  'list', 
-In_Order       =>  $LIST_IN_ORDER, 
@_,
);


if(defined($self->{fields}->{-List})){ 

#make the 'Show Domains' Hash
my %domains; 
foreach(@DOMAINS){ $domains{$_} = 0; }
$domains{Other} = 0;

#make the 'Show Services' Hash
my %SERVICES; 
foreach(@SERVICES){ $SERVICES{$_} = 0; }


	my $email;
	my $count = 0; 
#
my $test = 0;
   $test = $SHOW_DOMAIN_TABLE + $SHOW_SERVICES_TABLE + $SHOW_EMAIL_LIST; 
  
unless ($test == 0){

#this is a stupid, dumb, idiotic idea, but, people want it. 
if($args{-In_Order} == 1){
	$self->sort_email_list(%args);
}

$self->open_list_handle(-Type => $args{-Type}); 


			while(defined($email = <LIST>)){ 
				chomp($email);
				
				if($SHOW_EMAIL_LIST ==1){
					print "<option value=",$email,">",$email,"</option>\n";
				}
                
                
                
                # this is the 'Show Domains' Hash Generator
				
				if($SHOW_DOMAIN_TABLE ==1){
					my $domain_email = $email;
					#delete everything before the first . after the @. 
					$domain_email =~ s/(^.*)@//; 
					#lowercase the ending..
					$domain_email = lc($domain_email); 
					#strip everything before the last . 
					$domain_email  =~ s/(^.*)\.//;

					# hey, if it matches, recorde it. 
					if(exists($domains{$domain_email})){ 
					 $domains{$domain_email}++; 
					 }else{ 
					 $domains{Other}++;
					 } 
                 }
        
				
				
				
				
				
				# Here be the 'Show Services' Hash Generator; 
			    if($SHOW_SERVICES_TABLE == 1){ 
				    my $services_email = $email;
					#delete everything before the first . after the @. 
	                $services_email =~ s/(^.*)@//; 
	                #lowercase the ending..
	                $services_email = lc($services_email); 

	                # hey, if it matches, record it. 
	                if(exists($SERVICES{$services_email})){ 
	                $SERVICES{$services_email}++ ;
	                }else{ 
	                $SERVICES{Other}++;
	                }
                }
 				$count++;
				
				
			}
	close (LIST);
}	
	return($count, \%domains,\%SERVICES);
	
}else{ 
	return 0; 
}

}


sub create_bulk_sending_file { 

	my $self = shift; 
	
	my %args = ( 
	-Type      => 'list', 
	-Pin       =>  1,
	-ID        =>  undef, 
	-Ban       =>  undef, 
	-Bulk_Test =>  0,
	@_,
	); 
	
	my $list = $self->{fields}->{-List}; 
	my $path = $TMP; 
	
	my $type = $args{-Type}; 
	
	$list =~ s/ /_/g;
	
	my $message_id = message_id();
	#use the message ID, If we have one. 
	my $letter_id = $args{'-ID'} ||  $message_id;
	
	
	my %banned_list; 
	
	if($args{-Ban}){ 
		my $banned_list = $args{-Ban};
		foreach(@$banned_list){$banned_list{$_} = 1} 
	}
	
	
	if($list){ 
	
		my $list_file    = $FILES . '/' . $list . '.' . $type; 
		
		my $sending_file = $TMP   . '/' . $list . '.' . $type . '.' .$letter_id; 
		
		#open one file, write to the other. 
		my $email; 
		
		
		my %list_info = open_database(-List=>$list); 
		
		sysopen(LISTFILE, "$list_file",  O_RDONLY|O_CREAT, $FILE_CHMOD) or 
			die "Mojo Mail $VER Error: Cannot open email list for copying, in preparation to send out bulk message: $! "; 
		flock(LISTFILE, LOCK_SH); 
			
		sysopen (SENDINGFILE, "$sending_file",  O_RDWR|O_CREAT, $FILE_CHMOD) or
			die "Mojo Mail $VER Error: Cannot create temporary email list file for sending out bulk message: $!"; 
		flock(SENDINGFILE, LOCK_EX); 	
		
		my $to_pin = make_pin(-Email => $list_info{mojo_email});
		print SENDINGFILE "$list_info{mojo_email}\:\:$to_pin\n";
		
		my $test_test = $args{'-Bulk_Test'};
		chomp($test_test);
		unless($test_test == 1){ 
			while(defined($email  = <LISTFILE>)){ 
				chomp($email); 
				unless(exists($banned_list{$email})){
					my $pin = make_pin(-Email => $email); 
					print SENDINGFILE "$email\:\:$pin\n";
				}
			}
		}


		close(LISTFILE) 
			or die ("$PROGRAM_NAME $VER Error - could not close list file '$list_file'  successfully"); 
		
		close(SENDINGFILE) 
			or die ("$PROGRAM_NAME $VER Error - could not close temporary sending  file '$sending_file' successfully"); 
		
		return $sending_file; 
	}else{ 
	warn('Mojo Mail Error: No list information given for Plain_Text.pm at create_bulk_sending_file()');
	return undef;
	}
}


=pod

=head2 my ($unique, $duplicate) = unique_and_duplicate(-New_List => \@new_list); 

This is used to mass add and remove email addresses from a list, it takes a 
array ref full of new email addresses, and see if they are already in the list. 

my ($unique_ref, $duplicate_ref) = weed_out_subscribers(-List     => $list, 
														-Path     => $FILES, 
														-Type     => 'list', 
														-New_List => \@addresses, 
														);

=cut

 
sub unique_and_duplicate { 

my $self = shift; 

my %args = ( 
-New_List      => undef, 
-Type          => 'list', 
@_,
); 


# first thing we got to do is to make a lookup hash. 
my %lookup_table; 
my $address_ref = $args{-New_List}; 
my $list        = $self->{fields}->{-List};

if($list and $address_ref){

foreach(@$address_ref){$lookup_table{$_} = 0}

# easy enough, now, we'll open up the list, and 
# keep them as 0 if they're all unique, and 
# flag them as 1 if they're in the list. 

$self->open_list_handle(-Type => $args{-Type});
				 
my $email; 

#let us go.. 
	while(defined($email = <LIST>)){ 
	chomp($email);
    $lookup_table{$email} = 1 if(exists($lookup_table{$email})); 
    #nabbed it, 
    }
    close (LIST); 
    
    
#lets lookie and see what we gots.     
my @unique; 
my @double; 
my $value; 



foreach(keys %lookup_table){
	$value = $lookup_table{$_}; 
	if($value == 1){ 
		push(@double, $_)
	}else{ 
		push(@unique, $_) 
	}
}

#again, harmony is restored to the force.
return(\@unique, \@double); 
}else{ 

warn('Mojo Mail Error: No list name or list reference provided at Plain_Text.pm');
return undef;
}

}

sub find_unique_elements { 

my $self = shift; 

my $A = shift || undef; 
my $B = shift || undef; 
if($A and $B){ 


#lookup table
my %seen = ();     

# we'lls tore unique things in here            
my @unique = ();

#we'll store what we already got in here
my @already_in = ();                 

# build lookup table
foreach my $item (@$B) { $seen{$item} = 1 }

# find only elements in @$A and not in @$B
foreach my $item (@$A) {
    unless ($seen{$item}) {
        # it's not in %seen, so add to @aonly
        push(@unique, $item);
    }else{
        push(@already_in, $item);
        }
}




return (\@unique, \@already_in); 
}else{ 

return 0; 

}
}
sub remove_this_listype{my $self = shift;}
sub DESTROY {}

1;
