#!/usr/bin/perl
##############################################################################
# You must modify the the location of PERL in the first line above to match  #
# the location on your server.                                               #
# You must also modify the following variables to match your preferences.    #
##############################################################################

$AbsolutePath = ''; # Absolute or relative path to the directory where iSay.cgi is in. There's no need to fill this out unless you encounter problems.
$crcfg = 'crcfg.dat'; # Absolute or relative path to "crcfg.dat". Leave the filename intact.
$newsdat = '/home/ziomax/public_html/news/newsdat.txt'; # Absolute or relative path to "newsdat.txt". Leave the filename intact.
$SettingsFile = 'iSay_Config.dat'; # Absolute or relative path to "iSay_Config.dat". Leave the filename intact.

$IIS = 0; # Set to 1 if you're using IIS and are having problems with cookies, 0 otherwise.
$UseFlock = 1; # Set to 1 to enable file locking if you know your server supports it, 0 to disable.
$EnableRawPerl = 1; # Set to 1 to enable PerlCode in template designs, 0 to disable.

##############################################################################
# No changes need to be made after these lines                               #
##############################################################################

#use strict;
#use warnings;
#use diagnostics;

$iSay_Version = '0.16';
$iSay_Build = 4;

eval {
	$URL = 'http://'.$ENV{HTTP_HOST}.$ENV{SCRIPT_NAME};
	push @INC,$AbsolutePath if $AbsolutePath;
	my $FileHandle = OpenFile($SettingsFile);
	@Settings = <$FileHandle>;
	close $FileHandle;
	for $i (@Settings) {
		chop $i if $i =~ /\n$/;
		my ($Name,@Values) = split /``x/,$i;
		(my $Value = join '``x',@Values) =~ s/\\n/\n/g;
		$Setting{$Name} = $Value;
	}
	FatalError('iSay settings have not yet been configured in Coranto.') unless $Setting{NoSetup};
	FatalError('iSay settings have not yet been configured in Coranto after version upgrade.') unless $Setting{iSay_Version} eq $iSay_Version;
	FatalError('The commenting system is currently disabled.') if $Setting{iSayStatus} eq 'Disabled';
	read(STDIN,$Buffer,$ENV{CONTENT_LENGTH});
	push @QueryPairs,split /&/,$Buffer;
	push @QueryPairs,split /&/,$ENV{QUERY_STRING};
	for $i (@QueryPairs) {
		$i =~ tr/+/ /;
		$i =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
		my ($Name,@Values) = split /=/,$i;
		my $Value = join '=',@Values;
		$Query{$Name} = $Value;
	}
	for $i (split /; /,$ENV{HTTP_COOKIE}) {
		my ($Name,$Value) = split /=/,$i;
		$i =~ tr/+/ /;
		$i =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
		$Cookie{$Name} = $Value;
	}
	for $i (keys %Setting) {
		next unless $i =~ /^User_(.+)$/;
		for $j (split /\|x\|/,$Setting{$i}) {
			my ($Name,$Value) = split /!x!/,$j;
			$User{$1}->{$Name} = $Value;
		}
	}
	for $i (split /\|x\|/,$Setting{GuestPermissions}) {
		my ($Name,$Value) = split /!x!/,$i;
		$GuestPermission{$Name} = $Value;
	}
	if ($User{$Cookie{iSay_Username}} and $Cookie{iSay_Password} eq $User{$Cookie{iSay_Username}}->{Password} and !$User{$Query{Username}}->{Disabled}) {
		Main(1);
	} else {
		Main();
	}

} unless $NoEval;
FatalError($@) if $@;

sub Main {
	$LoggedIn = shift;
	if ($Query{ID}) {
		$ID = $Query{ID};
		if ($Query{Page} eq 'Comments') {
			my @NewsData = LoadNewsData(1);
			for $i (@NewsData) {
				SplitDataFile($i);
				next unless $newsid eq $ID;
				my $IDMatch = 1;
				my $Content;
				unless ($Setting{ShowOriginalPost} eq 'Disabled') {
					DateTime($newstime);
					($Content = $Setting{Design_OriginalPost}) =~ s/<Insert:\s?([^>]+)>/${$1}/gi;
				}
				if ($LoggedIn or $GuestPermission{'Read'}) {
					my @Comments = LoadComments($ID);
					@Comments = reverse @Comments if $Setting{SortOlderFirst} eq 'Disabled';
					$Comments = @Comments;
					for $j (@Comments) {
						chop $j if $j =~ /\n$/;
						if ($Setting{SortOlderFirst} eq 'Disabled') {
							$Number = $Comments - $Loop;
						} else {
							$Number = $Loop + 1;
						}
						($DateTime,$User,$IP,$Email,@Comment) = split /<<>>/,$j;
						$Comment = join '<<>>',@Comment;
						DateTime($DateTime,1);
						if ($User{$User}) {
							$Level = $User{$User}->{Level};
							$Posts = $User{$User}->{Posts};
							$Email = NoHTML($User{$User}->{Email});
						} else {
							$Level = 'Guest';
							$Posts = 'N/A';
							$Email = NoHTML($Email);
						}
						$User = NoHTML($User);
						$Comment = NoHTML($Comment) if $Setting{HTML} eq 'Disabled';
						$Comment = Filter($Comment) if $Setting{WordFilter} eq 'Enabled';

						$Content .= $Setting{Design_Comment};
						eval PerlCode('$Content',$Content);
						FatalError($@) if $@;
						$Content =~ s/<Insert:\s?([^>]+)>/${$1}/gi;
						$Content =~ s/\\n/<br \/>/gi;
						$Loop++;
					}
					unless ($Setting{ShowCommentSummary} eq 'Disabled') {
						$Content .= $Setting{Design_CommentSummary};
					}
					unless ($Setting{ShowPostForm} eq 'Disabled') {
						if ($Cookie{iSay_Username}) {
							$Query{Username} = qq~$Cookie{iSay_Username}" readonly="readonly~;
							$Query{Email} = qq~$User{$Cookie{iSay_Username}}->{Email}" readonly="readonly~;
						}
						$Content .= $Setting{Design_PostForm};
					}
				} else {
					$Content .= '<b>Error:</b> You must be logged in to read comments.';
				}
				Output($Content);
				last;
			}
			Output('<b>Error:</b> Specified ID does not exist.') unless $IDMatch;
		} elsif ($Query{Page} eq 'Post') {
			my $DisplayForm;
			if ($LoggedIn or $GuestPermission{'Post'}) {
				$DisplayForm = 1;
				if ($Query{Action} eq 'Save') {
					push @Errors,qq~The "<b>Username</b>" field must be filled in.~ unless $Query{Username};
					push @Errors,qq~The "<b>Username</b>" you provided is already registered. If you are the owner of that username, you must login before you can submit a comment.~ if ((!$LoggedIn and $User{$Query{Username}}) and $Cookie{iSay_Username} ne $Query{Username});
					push @Errors,qq~The "<b>Email</b>" field must be filled in.~ unless $Query{Email};
					push @Errors,qq~The "<b>Email</b>" you provided is invalid.~ unless isValidEmail($Query{Email});
					push @Errors,qq~The "<b>Comment</b>" field must be filled in.~ unless $Query{Comment};
					for $i (split /\|x\|/,$Setting{BannedUsers}) {
						($IP,$Reason) = split /!x!/,$i;
						$IP =~ s/\*/\(\\d\+\)/g;
						next unless $ENV{REMOTE_ADDR} =~ m/$IP/;
						if ($Reason) {
							push @Errors,"Sorry, this IP has been banned from posting comments for the following reason: $Reason";
						} else {
							push @Errors,'Sorry, this IP has been banned from posting comments.';
						}
						last;
					}
					push @Errors,qq~Sorry, this account has been disabled.~ if $User{$Query{Username}}->{Disabled};
					if (@Errors) {
						$Error = join '<br />',@Errors;
					} else {
						$DisplayForm = 0;
						if ($LoggedIn) {
							$Query{Username} = $Cookie{iSay_Username};
							$Query{Email} = $User{$Cookie{iSay_Username}}->{Email};
							$User{$Query{Username}}->{Posts} = $User{$Query{Username}}->{Posts} + 1;
							$User{$Query{Username}}->{LastPost} = time;
							$User{$Query{Username}}->{LastIP} = $ENV{REMOTE_ADDR};
							SaveUsers();
							SaveSettings();
						}
						$Query{Comment} =~ s/\015?\012/\\n/g;
						$DateTime = time;
						AddComment($ID,$DateTime,$Query{Username},$ENV{REMOTE_ADDR},$Query{Email},$Query{Comment});
						AddRecentList($ID,$DateTime) if $Setting{RecentCommentsFile};
						UpdateCounter($ID) if $Setting{CommentCounterPath};
						if ($IIS) {
							$Content = 'Comment added.';
						} else {
							PrintBasicHeader(0,"$URL?Page=Comments&ID=$ID",@Cookies);
						}
					}
				}
				if ($DisplayForm) {
					if ($Cookie{iSay_Username}) {
						$Query{Username} = qq~$Cookie{iSay_Username}" readonly="readonly~;
						$Query{Email} = qq~$User{$Cookie{iSay_Username}}->{Email}" readonly="readonly~;
					}
					$Content = $Setting{Design_PostForm};
				}
			} else {
				$Content = 'Guests are not allowed to post comments.';
			}
			Output($Content);
		} elsif ($Query{Page} eq 'Register') {
			my $DisplayForm;
			if ($LoggedIn) {
				$DisplayForm = 0;
				$Content = "You are logged in as user $Cookie{iSay_Username}. If you're not $Cookie{iSay_Username},<br />logout and login with your own username.";
			} else {
				$DisplayForm = 1;
				if ($Query{Action} eq 'Save') {
					push @Errors,qq~The "<b>Username</b>" field must be filled in.~ unless $Query{Username};
					push @Errors,qq~The "<b>Username</b>" you entered is already registered.~ if $User{$Query{Username}};
					push @Errors,qq~The "<b>Email</b>" field must be filled in.~ unless $Query{Email};
					push @Errors,qq~The "<b>Email</b>" you entered is invalid.~ unless isValidEmail($Query{Email});
					push @Errors,qq~The "<b>Password</b>" field must be filled in.~ unless $Query{Password};
					push @Errors,qq~The "<b>Repeat Password</b>" field must be filled in.~ unless $Query{RepeatPassword};
					push @Errors,'The passwords you entered do not match.' unless $Query{Password} eq $Query{RepeatPassword};
					if (@Errors) {
						$Error = join '<br />',@Errors;
					} else {
						$DisplayForm = 0;
						$User{$Query{Username}}->{Password} = crypt($Query{Password},substr($Query{Username},0,1));
						$User{$Query{Username}}->{Level} = 'Standard';
						$User{$Query{Username}}->{Posts} = 0;
						$User{$Query{Username}}->{Email} = $Query{Email};
						$User{$Query{Username}}->{DateJoined} = time;
						SaveUsers();
						SaveSettings();
						$Content = 'Your account has been successfully created.';
					}
				}
			}
			if ($DisplayForm) {
				$Content = $Setting{Design_RegistrationForm};
			}
			Output($Content);
		} elsif ($Query{Page} eq 'Login') {
			my $DisplayForm;
			if ($LoggedIn) {
				$DisplayForm = 0;
				$Content = "You are logged in as user $Cookie{iSay_Username}. If you're not $Cookie{iSay_Username},<br />logout and login with your own username.";
			} else {
				$DisplayForm = 1;
				if ($Query{Action} eq 'Save') {
					if ($User{$Query{Username}}->{Disabled}) {
						$Error = 'Sorry, this account has been disabled.';
					} elsif ($User{$Query{Username}} and crypt($Query{Password},substr($Query{Username},0,1)) eq $User{$Query{Username}}->{Password}) {
						$DisplayForm = 0;
						$Expiry = qq~;expires=~.GetCookieTime() if $Query{RememberMe} eq 'Yes';
						@Cookies = ("iSay_Username=$Query{Username}$Expiry","iSay_Password=$User{$Query{Username}}->{Password}$Expiry");
						if ($IIS) {
							PrintBasicHeader(0,0,@Cookies);
							$LoggedIn = 1;
							$Cookie{iSay_Username} = $Query{Username};
							$Content = "You are logged in as user $Cookie{iSay_Username}.";
						} else {
							PrintBasicHeader(0,"$URL?Page=Comments&ID=$ID",@Cookies);
						}
					} else {
						$Error = 'The information you provided is invalid.';
					}
				}
			}
			if ($DisplayForm) {
				$Content = $Setting{Design_LoginForm};
			}
			Output($Content);
		} elsif ($Query{Page} eq 'Logout') {
			@Cookies = ('iSay_Username=0','iSay_Password=0');
			PrintBasicHeader(0,0,@Cookies);
			Output('You have been logged out.');
		} elsif ($Query{Page} eq 'ResetPass') {
			my $DisplayForm;
			if ($LoggedIn) {
				$DisplayForm = 0;
				$Content = "You are logged in as user $Cookie{iSay_Username}. If you're not $Cookie{iSay_Username},<br />logout and login with your own username.";
			} else {
				$DisplayForm = 1;
				if ($Query{Action} eq 'Save') {
					if ($User{$Query{Username}}->{Disabled}) {
						$Error = 'Sorry, this account has been disabled.';
					} elsif ($User{$Query{Username}} and $Query{Email} eq $User{$Query{Username}}->{Email}) {
						$DisplayForm = 0;
						# Send Email code here.
						$User = $Query{Username};
						$RecipientEmail	= $Query{Email};
						$RecipientName	= $Query{Username};
						$SenderEmail	= $Setting{iSayEmail};
						$SenderName		= $Setting{iSayEmailName};
						$Subject		= 'Reset Password Instructions.';
						$ResetKey		= $User{$Query{Username}}->{Password};
						($Content = $Setting{Design_ResetPassEmail}) =~ s/<Insert:\s?([^>]+)>/${$1}/gi;
						SendMail($RecipientEmail,$RecipientName,$SenderEmail,$SenderName,$Subject,$Content);
						$Content = 'Please check your e-mail for details on how to change your password.'
					} else {
						$Error = 'The information you provided is invalid.';
					}
				}
			}
			if ($DisplayForm) {
				$Content = $Setting{Design_ResetPassForm};
			}
			Output($Content);
		} elsif ($Query{Page} eq 'ModifyProfile') {
			my $DisplayForm;
			if ($Query{Action} eq 'ResetPass') {
				if ($User{$Query{Username}}->{Disabled}) {
					$Error = 'Sorry, this account has been disabled.';
				} elsif ($User{$Query{Username}} and $Query{ResetKey} eq $User{$Query{Username}}->{Password}) {
					$DisplayForm = 1;
					@Cookies = ("iSay_Username=$Query{Username}","iSay_Password=$User{$Query{Username}}->{Password}");
					PrintBasicHeader(0,0,@Cookies);
					$LoggedIn = 1;
					$Cookie{iSay_Username} = $Query{Username};
				} else {
					$Error = 'The information you provided is invalid.';
				}
			}
			if ($LoggedIn) {
				$DisplayForm = 1;
				if ($Query{Action} eq 'Save') {
					my $ModifyPassword = 0;
					push @Errors,qq~The "<b>Email</b>" field must be filled in.~ unless $Query{Email};
					push @Errors,qq~The "<b>Email</b>" you provided is invalid.~ unless isValidEmail($Query{Email});
					if ($Query{NewPassword} or $Query{RepeatPassword}) {
						push @Errors,qq~The "<b>New Password</b>" field must be filled in.~ unless $Query{NewPassword};
						push @Errors,qq~The "<b>Repeat Password</b>" field must be filled in.~ unless $Query{RepeatPassword};
						push @Errors,'The passwords you entered do not match.' unless $Query{NewPassword} eq $Query{RepeatPassword};
						$ModifyPassword = 1;
					}
					if (@Errors) {
						$Error = join '<br />',@Errors;
					} else {
						$DisplayForm = 0;
						$Query{Username} = $Cookie{iSay_Username};
						$User{$Query{Username}}->{Password} = crypt($Query{NewPassword},substr($Query{Username},0,1)) if $ModifyPassword;
						$User{$Query{Username}}->{Email} = $Query{Email};
						SaveUsers();
						SaveSettings();
						$Content = 'Your profile has been successfully updated.';
					}
				}
			} else {
				$DisplayForm = 0;
				$Content = "You are not logged in.";
			}
			if ($DisplayForm) {
				$Query{Email} = $User{$Cookie{iSay_Username}}->{Email} unless $Query{Email};
				$Content = $Setting{Design_ModifyProfileForm};
			}
			Output($Content);
		} else {
			Output('Invalid page requested.');
		}
	} else {
		Output('<b>Error:</b> ID not specified.');
	}
}

sub OpenFile {
	my $File = shift;
	my $Handle = local *FH;
	my ($Mode,$Action);
	if ($File =~ /^>([^>]+)$/) {
		$File = $1;
		$Mode = '>';
		$Action = 'writing';
	} elsif ($File =~ /^>>(.+)$/) {
		$File = $1;
		$Mode = '>>';
		$Action = 'appending';
	} else {
		$Mode = '<';
		$Action = 'reading';
	}
	unless (-e $File) {
		open(CREATE,">$File") or FatalError(qq~The file "<b>$File</b>" does not exist and the script was unable to create it. Perl reported the following error: $!~);
		close(CREATE);
	}
	open($Handle,"$Mode$File") or FatalError(qq~Unable to open the file "<b>$File</b>" for $Action. Perl reported the following error: $!~);
	if ($UseFlock) {
		flock($Handle,2) or die FatalError(qq~Unable to flock the file "<b>$File</b>". Perl reported the following error: $!~);
	}
	return $Handle;
}

sub LoadNewsData {
	my $FullLoad = shift;
	my $FileHandle = OpenFile($newsdat);
	my @NewsData = <$FileHandle>;
	close $FileHandle;
	if ($FullLoad) {
		eval { require $crcfg; };
		Output(qq~Could not load "<b>$crcfg</b>": $@~) if $@;
	}
	return @NewsData;
}

sub LoadComments {
	my $ID = shift;
	my $FileHandle = OpenFile("$Setting{CommentsPath}/iSay_$ID.txt");
	my @Comments = ();
	while ($i = <$FileHandle>) {
		if ($i =~ /^iSay Database Format (\d\.\d\d?) \((\d\d?)\)\n$/) {
			$DatabaseFormat{$ID} = $2;
			next;
		}
		push @Comments, $i;
	}
	close $FileHandle;
	return @Comments;
}

sub DateTime {
	my ($DateTime,$TimeOffset) = @_;
	$DateTime += $Setting{'TimeOffset'} * 3600 if $TimeOffset;
   	my @Weekdays = qw(Sunday Monday Tuesday Wednesday Thursday Friday Saturday);
	my @Months = qw(January February March April May June July August September October November December);
	($Second,$Minute,$Hour,$Day,$Month,$Year,$Weekday,$DST) = (localtime($DateTime))[0,1,2,3,4,5,6,8];
	$Year += 1900;
	$TwoDigitYear = substr($Year,2,2);
	$MonthName = $Months[$Month];
	$AbbrevMonthName = substr($MonthName,0,3);
	$Month = $Month + 1;
	$TwoDigitMonth = sprintf('%.2d', $Month);
	$Weekday = $Weekdays[$Weekday];
	$TwoDigitDay = sprintf('%.2d', $Day);
	$TwoDigitHour = sprintf('%.2d', $Hour);
	if ($Hour == 0) {
		$TwelveHour = 12;
		$AMPM = 'AM';
	} elsif ($Hour < 12) {
		$TwelveHour = $Hour;
		$AMPM = 'AM';
	} elsif ($Hour == 12) {
		$TwelveHour = $Hour;
		$AMPM = 'PM';
	} else {
		$TwelveHour = $Hour - 12;
		$AMPM = 'PM';
	}
	$TwoDigitTwelveHour = sprintf('%.2d', $TwelveHour);
	$Minute = sprintf('%.2d', $Minute);
	$Second = sprintf('%.2d', $Second);
	if ($DST) {
		$TimeZone = $Setting{DaylightTimeZone};
	} else {
   		$TimeZone = $Setting{StandardTimeZone};
	}
	($Date = $Setting{DateFormat}) =~ s/<Insert:\s?([^>]+)>/${$1}/gi;
	($Time = $Setting{TimeFormat}) =~ s/<Insert:\s?([^>]+)>/${$1}/gi;
}

sub NoHTML {
	my $HTML = shift;
	$HTML =~ s/&/&amp;/g;
	$HTML =~ s/</&lt;/g;
	$HTML =~ s/>/&gt;/g;
	$HTML =~ s/"/&quot;/g;
	return $HTML;
}

sub Filter {
	my $Text = shift;
	my $RegExp = '';
	for $i (split /\|x\|/,$Setting{WordFilterList}) {
		my ($FindText,$ReplaceText,$WholeWords,$FirstInstance,$NoCase) = split /!x!/,$i;
		$FindText =~ s/\//\\\//g;
		$ReplaceText =~ s/\//\\\//g;
		if ($FindText =~ m/\(\#\#\)/) {
			$FindText =~ s/(.*?)\(\#\#\)(.?)/$1.CreateRE($2).$2/ge;
			$FindText = '\Q' . $FindText . '\E';
			$FindText =~ s/\\Q\\E$//;
			$ReplaceText =~ s/\(\#(\d+)\#\)/\$$1/g;

			$RegExp = '$Text =~ s/';
			$RegExp .= $FindText;
			$RegExp .= '/';
			$RegExp .= $ReplaceText;
			$RegExp .= '/';
			$RegExp .= 'g' unless $FirstInstance;
			$RegExp .= 'i' if $NoCase;
			$RegExp .= ';';
		} else {
			$RegExp = '$Text =~ s/';
			if ($WholeWords) {
				$RegExp .= '([^\w\d\<]|\A)\Q';
				$RegExp .= $FindText;
				$RegExp .= '\E([^\w\d\>]|\Z)';
				$RegExp .= '/';
				$RegExp .= '$1' . $ReplaceText . '$2';
			} else {
				$RegExp .= '\Q';
				$RegExp .= $FindText;
				$RegExp .= '\E';
				$RegExp .= '/';
				$RegExp .= $ReplaceText;
			}
			$RegExp .= '/';
			$RegExp .= 'g' unless $FirstInstance;
			$RegExp .= 'i' if $NoCase;
			$RegExp .= ';';
		}
		eval $RegExp;
		FatalError($@) if $@;
	}
	return $Text;
}

sub CreateRE {
	my $Text = shift;
	$Text =~ s/(\A)/$1/;
	$Text = '\Z' if $Text eq '';
	$Text = '\s' if $Text eq ' ';
	$Text =~ s/\A(\W)\Z/\\$1/;
	return '\E([^'.$Text.']+)\Q';
}

sub PerlCode {
	my ($Key,$Code) = @_;

	# Get rid of PerlCode if not allowed
	$Code =~ s/<\/?PerlCode>//gi unless $EnableRawPerl;

	# Escape special perl characters in everything other than perl code.
	$Code =~ s/\G(.*?)(\A|<\/PerlCode>)(.*?)(\Z|<PerlCode>)/$1 . $2 . EscapeCode($3) . $4/gies;

	# Now we allow this code to execute.
	$Code =~ s/<PerlCode>/~;\n/g;
	$Code =~ s/<\/PerlCode>/\n$Key .= qq~/g;
	$Code = "$Key = qq~$Code";


	# <If: Else>
	$Code =~ s/<If: Else>/~;\n} else {\n$Key .= qq~/gi;
	# </If> <If: End>
	$Code =~ s/(<\/If>|<If: End>)/~;\n}\n$Key .= qq~/gi;

	# <If: Name>
	$Code =~ s/<If: ([\w_]+)>/~;\nif (\$$1) {\n$Key .= qq~/gi;
	# <If: Name eq 'Value'>
	$Code =~ s/<If: ([\w_]+) (eq|ne|==|!=) ([\'\"]?)(.*?)\3>/~;\nif (\$$1 $2 $3$4$3) {\n$Key .= qq~/gi;
	# <If: Name{'Key'} eq 'Value'>
	$Code =~ s/<If: ([\w_]+)\{([\'\"]?)([\w_]+)\2\} (eq|ne|==|!=) ([\'\"]?)(.*?)\5>/~;\nif (\$$1\{$2$3$2\} $4 $5$6$5) {\n$Key .= qq~"/gi;
	# <If: Else: Name>
	$Code =~ s/<If: Else: ([\w_]+)>/~;\n} elsif (\$$1) {\n$Key .= qq~/gi;
	# <If: Else: Name eq 'Value'>
	$Code =~ s/<If: Else: ([\w_]+) (eq|ne|==|!=) ([\'\"]?)(.*?)\3>/~;\n} elsif (\$$1 $2 $3$4$3) {\n$Key .= qq~/gi;
	# <If: Else: Name{'Key'} eq 'Value'>
	$Code =~ s/<If: Else: ([\w_]+)\{([\'\"]?)([\w_]+)\2\} (eq|ne|==|!=) ([\'\"])(.*?)\5>/~;\n} elsif (\$$1\{$2$3$2\} $4 $5$6$5) {\n$Key .= qq~"/gi;

	# End magic.
	$Code .= '~;';

	# Now we parse file includes.
	$Code =~ s/<Insert:\s?File=([\'\"]?)(\S+?)\1>/IncludeFile($2)/gie;

	return $Code;
}

sub EscapeCode {
	my $Text = shift;
	$Text =~ s/(\~|\\|\$|\@|\%)/\\$1/g;
	return $Text;
}

sub IncludeFile {
	my $File = shift;
	# Translate windows-style \ slashes to unix-style /
	$File =~ s/\\/\//g;
	# Remove all characters other than: letters, numbers, spaces, and -_:/.
	$File =~ s/[^\w\d \-_:\/\.]//g; 
	my $Content;
	my $FileHandle = OpenFile($File);
	{
		local $/;
		$Content = <$FileHandle>;
	}
	close $FileHandle;
	return $Content;
}

sub isValidEmail {
    my $Email = shift;
    return 0 unless	$Email !~ /(@.*@)|(\.\.)|(@\.)|(\.@)|(^\.)/ and
					$Email =~ /^.+\@(\[?)[\w\-\.]+\.([\w]+)(\]?)$/;
	return 1;
}

sub AddComment {
	my ($ID,@Data) = @_;
	my $FileHandle;
	if (-s "$Setting{CommentsPath}/iSay_$ID.txt" > 0) {
		$FileHandle = OpenFile(">>$Setting{CommentsPath}/iSay_$ID.txt");
	} else {
		$FileHandle = OpenFile(">$Setting{CommentsPath}/iSay_$ID.txt");
		print $FileHandle "iSay Database Format $iSay_Version ($iSay_Build)\n";
	}
	print $FileHandle (join '<<>>',@Data) . "\n";
	close $FileHandle;
	chmod(0666, "$Setting{CommentsPath}/iSay_$ID.txt") or FatalError(qq~Unable to chmod the file "<b>$Setting{CommentsPath}/iSay_$ID.txt</b>". Perl reported the following error: $!~);
}

sub AddRecentList {
	my ($ID,$DateTime) = @_;
	my @RecentList;
	for $i (split /\|x\|/, $Setting{RecentList}) {
		push @RecentList, $i;
	}
	push @RecentList, "$ID!x!$DateTime";
	shift @RecentList while @RecentList > $Setting{RecentComments};
	$Setting{RecentList} = join '|x|',@RecentList;
	SaveSettings();
	my $Content;
	@RecentList = reverse @RecentList if $Setting{SortOlderFirst} eq 'Disabled';
	my @NewsData = LoadNewsData(1);
	for $i (@RecentList) {
		my ($IDMatch,$DateTimeMatch) = split /!x!/,$i;
		for $j (@NewsData) {
			SplitDataFile($j);
			next unless $newsid eq $IDMatch;
			my @Comments = LoadComments($IDMatch);
			$Comments = @Comments;
			for $k (@Comments) {
				chop $k if $k =~ /\n$/;
				($DateTime,$User,$IP,$Email,@Comment) = split /<<>>/,$k;
				$Comment = join '<<>>',@Comment;
				next unless $DateTime eq $DateTimeMatch;
				DateTime($DateTime,1);
				if ($User{$User}) {
					$Level = $User{$User}->{Level};
					$Posts = $User{$User}->{Posts};
					$Email = NoHTML($User{$User}->{Email});
				} else {
					$Level = 'Guest';
					$Posts = 'N/A';
					$Email = NoHTML($Email);
				}
				$User = NoHTML($User);
				$Comment = NoHTML($Comment) if $Setting{HTML} eq 'Disabled';
				$Comment = Filter($Comment) if $Setting{WordFilter} eq 'Enabled';

				$Content .= $Setting{Design_RecentComments};
				eval PerlCode('$Content',$Content);
				FatalError($@) if $@;
				$Content =~ s/<Insert:\s?([^>]+)>/${$1}/gi;
				$Content =~ s/\\n/<br \/>/gi;
			}
		}
	}
	my $FileHandle = OpenFile(">$Setting{RecentCommentsFile}");
	print $FileHandle $Content;
	close $FileHandle;
	chmod(0666, "$Setting{RecentCommentsFile}") or FatalError(qq~Unable to chmod the file "<b>$Setting{RecentCommentsFile}</b>". Perl reported the following error: $!~);
}

sub UpdateCounter {
	my $ID = shift;
	my $CommentCount = 0;
	if (-e "$Setting{CommentsPath}/iSay_$ID.txt") {
		my @Comments = LoadComments($ID);
		$CommentCount = @Comments;
	}
	my $FileHandle = OpenFile(">$Setting{CommentCounterPath}/iSayCommentCount_$ID.txt");
	print $FileHandle $CommentCount;
	close $FileHandle;
	chmod(0666, "$Setting{CommentCounterPath}/iSayCommentCount_$ID.txt") or FatalError(qq~Unable to chmod the file "<b>$Setting{CommentCounterPath}/iSayCommentCount_$ID.txt</b>". Perl reported the following error: $!~);
}

sub SaveUsers {
	for $i (keys %User) {
		$Setting{"User_$i"} =	"Password!x!"	. $User{$i}->{Password}		. "|x|" .
					"Level!x!"	. $User{$i}->{Level}		. "|x|" .
					"Posts!x!"	. $User{$i}->{Posts}		. "|x|" .
					"Email!x!"	. $User{$i}->{Email}		. "|x|" .
					"DateJoined!x!"	. $User{$i}->{DateJoined}	. "|x|" .
					"LastPost!x!"	. $User{$i}->{LastPost}		. "|x|" .
					"LastIP!x!"	. $User{$i}->{LastIP}		. "|x|" .
					"Disabled!x!"	. $User{$i}->{Disabled};
	}
}

sub SaveSettings {
	my $FileHandle = OpenFile(">$SettingsFile");
	for $i (sort keys %Setting) {
		(my $Value = $Setting{$i}) =~ s/\n/\\n/g;
		print $FileHandle "$i``x$Value\n";
	}
	close $FileHandle;
}

sub Output {
	my $Content = shift;
	PrintBasicHeader();
	(my $Output = $Setting{Design_Overall}) =~ s/<Insert:\s?Content>/$Content/gi;
	eval PerlCode('$Output',$Output);
	FatalError($@) if $@;
	if ($LoggedIn and $Query{Page} ne 'Logout') {
		$Output =~ s/<Insert:\s?LoginInfo>/You are logged in as user $Cookie{iSay_Username}/gi;
	} else {
    	$Output =~ s/<Insert:\s?LoginInfo>/You are not logged in/gi;
	}
	$Output =~ s/<Insert:\s?([\w_]+)\{([\w_]+)\}>/${$1}{$2}/gi;
	$Output =~ s/<Insert:\s?([^>]+)>/${$1}/gi;
	print $Output;
	exit;
}

sub SendMail {
	my ($RecipientEmail,$RecipientName,$SenderEmail,$SenderName,$Subject,$Content) = @_;
	open (MAIL,"|$Setting{Sendmail}") or FatalError(qq~Unable to open "<b>$Setting{Sendmail}</b>". Perl reported the following error: $!~);
	print MAIL "MIME-Version: 1.0\n";
	print MAIL "Content-type: text/plain; charset=ISO-8859-1\n";
	print MAIL "Content-transfer-encoding: 8bit\n";
	print MAIL "X-Mailer: iSay\n";
	print MAIL "X-MimeOLE: Produced By iSay $iSay_Version\n";
	print MAIL "Return-Path: $SenderEmail\n";
	print MAIL "To: $RecipientEmail ($RecipientName)\n";
	print MAIL "From: $SenderEmail ($SenderName)\n";
	print MAIL "Reply-to: $SenderEmail\n";
	print MAIL "Subject: $Subject\n\n";
	print MAIL $Content;
	close MAIL;
}

sub GetCookieTime {
	my @CookieDays = qw(Sun Mon Tue Wed Thu Fri Sat);
	my @CookieMonths = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
	my @gmtime = gmtime(time + 2592000);
	my $Format = sprintf("%3s, %02d-%3s-%4d %02d:%02d:%02d GMT",$CookieDays[$gmtime[6]],$gmtime[3],$CookieMonths[$gmtime[4]],$gmtime[5]+1900,$gmtime[2],$gmtime[1],$gmtime[0]);
	return $Format;
}

sub PrintBasicHeader {
	return if $BasicHeaderPrinted;
	my ($Type,$Forward,@Cookies) = @_;
	for $i (@Cookies) {
		print "set-cookie:$i\n";
	}
	if ($Type) {
		print "content-type:$Type\n";
	} else {
		print "content-type:text/html\n"
	}
	if ($Forward) {
		print "location:$Forward\n\n";
	} else {
		print "\n";
	}
	$BasicHeaderPrinted = 1;
}

sub FatalError {
	my $FatalError = shift;
	PrintBasicHeader();
	print qq~<html><head><title>Fatal Error</title></head><body><p align="center"><strong>Fatal Error:</strong> $FatalError</p></body></html>~;
	exit;
}

1;
