#!/usr/bin/perl
#
# Catch22 (llama@osoal.org.nz)
# One Short Of A Llama [http://web.osoal.org.nz]

# create table comments
# (
#    pid      int unsigned not null auto_increment primary key,
#    thread   int unsigned,
#    replies  int unsigned,
#    nickname varchar(30),
#    uid      int  unsigned default 0,
#    address  varchar(50),
#    browser  varchar(50),
#    time     int unsigned,
#    subject  varchar(80),
#    email    varchar(50),
#    comment  longtext
# );

#
use strict;
use OneShortOfALlama;
use DBI;
use POSIX;
use Captcha::noCAPTCHA;

#
my($comments, $threading, @HTML);

#
$comments  = 10;
$threading = 1;
@HTML      = ('b', 'i', 'u', 'strike', 'a href=("|\'|)?.+\1', 'pre',
			  'ul', 'ol', 'li', 'small', 'code', 'tt', 'em', 'blockquote');

#
sub print_comments
{
	my($dbh, $gid, $thread, $mode, $load, $i, $sth, $pid, $replies, $nickname,
	   $uid, $address, $browser, $time, $subject, $email, $comment, $queued);

	#
	foreach $i ($dbh, $gid, $thread, $mode, $queued, $load)
	{
		$i = shift(@_);
	}

	#
	$sth = $dbh->prepare('select pid, replies, nickname, uid, address, browser,
                          from_unixtime(time, "%W %D of %M %l:%i %p, %Y"), subject,
                          email, comment from osoal.comments
                          where (thread = ' . $thread . ') order by pid desc' . ($queued > -1 ? " limit $queued" : ''));
	$sth->execute();

	#
	if ($sth->rows())
	{
		$sth->bind_columns(undef(), \$pid, \$replies, \$nickname, \$uid, \$address,
						            \$browser, \$time, \$subject, \$email, \$comment);
		while ($sth->fetch())
		{
			unless ($queued--)
			{
				$sth->finish();
				return (0);
			}
			
			print('<a name="', $pid, '">
                   <b>', $nickname, '</b>');

			if (!$uid || $gid == 1)
			{
				print(' <small>', $address, '</small>');
			}
			
			print(' ', $browser, '<br>
                   <em>', $subject, '</em> <small>on ', $time, '</small>');
			
			if ($email)
			{
				print(' (<a href="mailto:', $email, '">', $email, '</a>)');
			}

			print('<br>');

			#
			for $i (split("\n", $comment))
			{
				print("<br>$i\n");
			}

			#
			print('<br>
                   <br>
                   [ <a href="', $ENV{'SCRIPT_NAME'}, '?select=Reply&pid=', $pid, '&mode=', $mode,
				  '&load=', $load, '"><small><b>Post Reply</b></small></a>');

			#
			if ($mode == 1)
			{
				if ($replies)
				{
					print(' | <a href="', $ENV{'SCRIPT_NAME'}, '?select=Expand&pid=', $pid, '&mode=2&load=-1',
						  '"><small><b>Expand</b></small></a>');
				}
			}

			#
			if ($uid)
			{
				print(' | <a href="/login.pl?select=Whois&uid=', $uid, '"><small><b>Whois</b></small></a>');
			}

			#
			if ($gid == 1)
			{
				print(' | <a href="', $ENV{'SCRIPT_NAME'}, '?select=Modify&pid=', $pid, '&mode=', $mode,
					  '&load=', $load, '"><small><b>Modify</b></small></a>',
					  ' | <a href="', $ENV{'SCRIPT_NAME'}, '?select=Delete&pid=', $pid, '&mode=', $mode,
					  '&load=', $load, '"><small><b>Delete</b></small></a>');
			}

			print(' ]<br><br>', "\n\n");
			
			# post seperator in flat mode
			unless ($replies && $mode)
			{
				print('<hr noshade><br>', "\n\n");
			}

			if ($replies)
			{
				if ($mode)
				{
					if ($mode == 1)
					{
						# post seperator in threaded mode
						print_threads($dbh, $pid, $load);
						print('<hr noshade><br>', "\n\n");
					}
					else
					{
						print('<hr noshade><br>
                               <ul>', "\n");
						print_comments($dbh, $gid, $pid, $mode, -1, $load);
						print('</ul>', "\n");
					}
				}
				else
				{
					$queued = print_comments($dbh, $gid, $pid, $mode, $queued, $load);
				}
			}
		}
	}

	return ($queued);
}

sub print_threads
{
	my($sth, $thread, $dbh, $load,
	   $pid, $replies, $nickname, $subject, $time);

	#
	$dbh    = shift(@_);
	$thread = shift(@_);
	$load   = shift(@_);

	#
	$sth = $dbh->prepare('select pid, replies, nickname, subject, from_unixtime(time, "%W %D of %M %l:%i %p, %Y")
                          from osoal.comments where (thread = ' . $thread . ') order by pid desc');
	$sth->execute();

	unless ($sth->rows())
	{
		return ();
	}

	$sth->bind_columns(undef(), \$pid, \$replies, \$nickname, \$subject, \$time);
	print('<ul>', "\n");

	while ($sth->fetch())
	{
		print('<a href="', $ENV{'SCRIPT_NAME'}, '?pid=', $pid, '&mode=1&load=', $load,
			  '#', $pid, '"><b>', $nickname, '</b></a> <small><em>', $subject, '</em> on ', $time, '</small><br>', "\n");

		#
		if ($replies)
		{
			print_threads($dbh, $pid);
		}
	}

	print('</ul>', "\n");
}

sub input_comment
{
	my($dbh, $i, $nickname, $email, $subject, $comment, $uid, $param);

	#
	foreach $i ($dbh, $uid, $param)
	{
		$i = shift(@_);
	}

	#
	print('<form method="POST" action="', $ENV{'SCRIPT_NAME'}, '">', "\n\n");

	#
	if ($param->{'select'} eq 'Modify')
	{
		($nickname, $email, $subject, $comment)
			= $dbh->selectrow_array('select nickname, email, subject, comment from osoal.comments
                                     where (pid = ' . $param->{'pid'} . ')');

			print('<input type="hidden" name="pid" value="', $param->{'pid'}, '">');
	}
	else
	{
		if ($param->{'select'} eq 'Reply')
		{
			$subject = $dbh->selectrow_array('select subject from osoal.comments
                                              where (pid = ' . $param->{'pid'} . ')');
		}

		if ($uid)
		{
			($nickname, $email) = $dbh->selectrow_array("select nickname, fakeemail from osoal.users
                                                         where (uid = $uid)");
		}
	}

	#
	print('<input type="hidden" name="pid" value="', $param->{'pid'}, '">
           <input type="hidden" name="mode" value="', $param->{'mode'}, '">
           <input type="hidden" name="load" value="', $param->{'load'}, '">

           <table align="center">
           <tr>
             <p><small>If you are posting a link to increase your google pagerank, please stop to consider that our <a href=/robots.txt>robots.txt</a> is configured to prevent google ( or any other search engine from indexing the comments section.</small></p>
           </tr>
           <tr>
           <td align="left">Nickname</td>
           <td align="right">
               <input type="text" name="nickname" value="', $nickname, '" size="40"></td>
           </tr>

           <tr>
           <td align="left">Password</td>
           <td align="right">
               <input type="password" name="passwd" value="" size="40"></td>
           </tr>

           <tr>
           <td align="left">Email</td>
           <td align="right">
               <input type="text" name="email" value="', $email, '" size="40"></td>
           </tr>

           <tr>
           <td align="left">Subject</td>
           <td align="right">
               <input type="text" name="subject" value="', $subject, '" size="40"></td>
           </tr>

           <tr>
           <td colspan="2"> 
               <textarea name="comment" rows="20" cols="52" wrap="virtual">', $comment, '</textarea></td>
           </tr>

           <tr>

           <td colspan="2">
               <table width="100%" border="0" cellpadding="0" cellspacing="0">
               <tr>
                   <td align="left">
                   <input type="submit" name="select" value="');

	if ($param->{'select'} eq 'Comment' || $param->{'select'} eq 'Reply')
	{
		print('Submit');
	}
	elsif ($param->{'select'} eq 'Modify')
	{
		print('Apply');
	}

	print('"> <input type="submit" name="select" value="Preview"></td>
              <td align="right"><img src="/pics/icons/oneshortofallama.png" alt=""></td>
              </tr>

              <div class="g-recaptcha" data-sitekey="6LdFkAgTAAAAAAU2bg8RxjXfSe1MxLfNAE3k4XLM" style="position: relative; right: -132px;"></div>

              </table>

           </td>
           </tr>

           </table>
           </form>', "\n\n");
}

sub delete_comment
{
	my($dbh, $pid, $replies, $thread);

	#
	$dbh = shift(@_);
	$pid = shift(@_);

	#
	($thread, $replies) = $dbh->selectrow_array("select thread, replies from osoal.comments
                                                 where (pid = $pid)");

	if ($replies)
	{
		delete_thread($dbh, $pid);
	}

	$dbh->do("delete from osoal.comments where (pid = $pid)");
	$replies = $dbh->selectrow_array("select replies from osoal.comments where (pid = $thread)");

	#
	if (--$replies)
	{
		# keep focus in same thread
		$pid = $dbh->selectrow_array("select pid from osoal.comments where (thread = $thread)");
	}
	else
	{
		# go to parent thread
		$pid = $thread;
	}

	if ($pid > 0)
	{
		$dbh->do("update osoal.comments set replies = $replies where (pid = $pid)");
	}

	return ($pid);
}

sub delete_thread
{
	my($sth, $thread, $pid, $replies, $dbh);

	#
	$dbh    = shift(@_);
	$thread = shift(@_);

	#
	$sth = $dbh->prepare("select pid, replies from osoal.comments where (thread = $thread)");
	$sth->execute();
	$sth->bind_columns(undef(), \$pid, \$replies);

	while ($sth->fetch())
	{
		$dbh->do("delete from osoal.comments where (pid = $pid)");
		
		if ($replies)
		{
			delete_thread($dbh, $pid);
		}
	}
}

sub submit_comment
{
	my($dbh, $uid, $gid, $param, $i, $sth, $replies, $time, $browser, $address);

	#
	foreach $i ($dbh, $uid, $gid, $param)
	{
		$i = shift(@_);
	}

	#
	unless ($param->{'pid'} =~ m/^\d+$/)
	{
		print("Mangled PID");
		return;
	}

	if ($param->{'pid'} > 0)
	{
		$replies = $dbh->selectrow_array('select replies from osoal.comments
                                          where (pid = ' . $param->{'pid'} . ')');

		unless (defined($replies))
		{
			print("Orphaned Comment; Bad/Unknown Parent");
			return ();
		}
	}

	#
	if ($param->{'passwd'})
	{
		($uid, $gid)
			= authenticate($param->{'nickname'}, crypt($param->{'passwd'}, $param->{'nickname'}));
	} else {
	    print ('You must authenticate to post');
	    return();
	}

	#
	unless (parse_comment($param, $gid))
	{
		print('Insufficent aruments to build comment');
		return ();
	}

	#
	$i = $dbh->selectrow_array('select uid from osoal.users
                                where (nickname = ' . $dbh->quote($param->{'nickname'}) . ')');

	unless ($i == $uid)
	{
		print('You may not post comments using that nickname');
		return ();
	}

	my $c = Captcha::noCAPTCHA->new({site_key => "6LdFkAgTAAAAAAU2bg8RxjXfSe1MxLfNAE3k4XLM", 
					 secret_key => "6LdFkAgTAAAAAOjTTz9wNSSVBi6L6y0gSU3hc38H"});

	unless ($c->verify($param->{'g-recaptcha-response'}, $ENV{'REMOTE_ADDR'}) ) 
	{
		print('You must not be a robot');
		return ();
	}


	#
	if ($replies)
	{
		$i = $dbh->selectrow_array('select concat(nickname, " ", comment) from osoal.comments
                                    where (thread = ' . $param->{'pid'} . ') limit 1');

		if ($i eq join(' ', $param->{'nickname'}, $param->{'comment'}))
		{
			print('Reposting is not allowed');
			return ();
		}
	}

	#
	$address = quote(nslookup($ENV{'REMOTE_ADDR'}));
	$browser = quote($ENV{'HTTP_USER_AGENT'});
					 
	#
#	$sth = $dbh->prepare('insert into osoal.comments 
#                                 (thread, nickname, uid, address, browser, time, subject, email, comment)
#                          values (?, ?, ?, ?, ?, unix_timestamp(), ?, ?, ?)');

	$sth->execute($param->{'pid'}, $param->{'nickname'}, $uid, $address,
				  $browser, $param->{'subject'}, $param->{'email'}, $param->{'comment'});

	#
	if ($param->{'pid'} > 0)
	{
		$dbh->do('update osoal.comments set replies = replies + 1
                  where (pid = ' . $param->{'pid'} . ')');
	}

	#
	$param->{'pid'} = $sth->{'mysql_insertid'};


}

sub preview_comment
{
	my($dbh, $i, $param, $gid, $param, $browser, $time);

	#
	foreach $i ($dbh, $gid, $param)
	{
		$i = shift(@_);
	}

	#
	unless (parse_comment($param, $gid))
	{
		print('Insufficent arguments to preview comment');
		return ();
	}

	#
	$browser = quote($ENV{'HTTP_USER_AGENT'});
	$time    = $dbh->selectrow_array('select from_unixtime(unix_timestamp(), "%W %D of %M %l:%i %p, %Y")');

	#
	print('<br>
           <b>', $param->{'nickname'}, '</b> ', $browser, '<br>
           <em>', $param->{'subject'}, '</em> <small>on ', $time, '</small>');

	if ($param->{'email'})
	{
		print(' (<a href="mailto:', $param->{'email'}, '">', $param->{'email'}, '</a>)');
	}

	print('<br>');

	for $i (split("\n", $param->{'comment'}))
	{
		print("<br>$i\n");
	}		

	print('<br>
               <br>
           <hr noshade>', "\n\n");
}

sub apply_comment
{
	my($dbh, $param, $i, $address, $browser);

	#
	$dbh   = shift(@_);
	$param = shift(@_);

	#
	unless (parse_comment($param, 1))
	{
		print('Insufficent arguments to apply comment');
		return ();
	}

	$dbh->do(sprintf('update osoal.comments
                          set nickname = %s, email = %s, subject = %s, comment = %s where (pid = %d)',
			 $dbh->quote($param->{'nickname'}),
			 $dbh->quote($param->{'email'}),
			 $dbh->quote($param->{'subject'}),
			 $dbh->quote($param->{'comment'}),
			 $param->{'pid'}));
}

sub parse_comment
{
	my(@comment, $i, $length, $tag, $param, $gid);

	#
	$param = shift(@_);
	$gid   = shift(@_);

	#
	for $i (qw/nickname subject email/)
	{
		$param->{$i} =~ s/\n//g;
		$param->{$i} =~ s/^\s*(.*?)\s*$/$1/;

		unless ($gid == 1)
		{
			$param->{$i} = quote($param->{$i});
		}
	}
	
	unless ($param->{'subject'})
	{
		$param->{'subject'} = 'no subject';
	}

	#
	$param->{'comment'} =~ s/\r//g;
	$param->{'comment'} =~ s/^\s*(.+?)\s*$/$1/s;
	$param->{'comment'} =~ s/(\S{200})/$1 /g;

	#
	unless ($param->{'nickname'} && $param->{'comment'})
	{
		return (0);
	}

	#
	if ($gid == 1)
	{
		return (1);
	}
	
	#
	@comment = split(//, $param->{'comment'});
	$length  = scalar(@comment);
	$tag     = -1;

	#
	for ($i = 0; $i < $length; $i++)
	{
		if ($comment[$i] eq '<')
		{
			if ($tag > -1)
			{
				$comment[$i] = '&lt;';
				$tag = -1;
			}
			else
			{
				$tag = $i;
			}
		}

		elsif ($comment[$i] eq '>')
		{
			if ($tag > -1)
			{
				unless (allowed($tag, $i, \$param->{'comment'}))
				{
					$comment[$tag] = '&lt;';
					$comment[$i]   = '&gt;';
				}
				
				$tag = -1;
			}
			else
			{
				$comment[$i] = '&gt;';
			}
		}

		elsif ($comment[$i] eq "\n")
		{
			if ($tag > -1)
			{
				$comment[$tag] = '&lt;';
				$tag = -1;
			}
		}
	}
	
	$param->{'comment'} = join('', @comment);
	return (1);
}


sub allowed
{
	my($tag, $start, $end, $i, $delim, $comment);

	$start   = shift(@_) + 1;
	$end     = shift(@_);
	$comment = shift(@_);

	if (substr($$comment, $start, 1) eq '/')
	{
		$start++;
		$delim  = 0;
	}
	else
	{
		$delim = 1;
	}
	
	$tag = substr($$comment, $start, $end - $start);
	
	for $i (@HTML)
	{
	    if ($delim)
		{
			unless ($tag =~ m/^$i$/i)
			{
				next;
			}
		}

		if (($start = index($i, ' ')) > -1)
		{
			$i = substr($i, 0, $start);
		}

		if ($delim)
		{
			unless (substr($$comment, $end) =~ m/<\/$i>/i)
			{
				next;
			}
		}
		else
		{
			unless ($tag =~ m/^$i$/i)
			{
				next;
			}
		}
		
		return (1);
	}

	return (0);
}

sub parent_load
{
	my($parentload, $thread, $pid, $load, $dbh);

	#
	$dbh  = shift(@_);
	$pid  = shift(@_);
	$load = shift(@_);

	#
	$thread = $dbh->selectrow_array("select thread from osoal.comments where (pid = $pid)");
	$parentload = $dbh->selectrow_array("select count(*) from osoal.comments where (thread = $thread && pid >= $pid)");
	
	#
	if ($load < $parentload)
	{
		$load = ($parentload > $comments) ? $parentload : $comments;
	}

	return ($load);
}

sub comment_preferences
{
	my($load, $mode, $dbh, $param, $uid);

	$uid   = shift(@_);
	$dbh   = shift(@_);
	$param = shift(@_);

	if ($uid)
	{
		($load, $mode) = $dbh->selectrow_array("select comments, threads from osoal.users
                                                where (uid = $uid)");
	}
	else
	{
		$load = $comments;
		$mode = $threading;
	}

	unless ($param->{'mode'} =~ m/^[012]$/)
	{
		$param->{'mode'} = $mode;
	}

	unless ($param->{'load'} =~ m/^\d+$/ || $param->{'load'} == -1)
	{
		$param->{'load'} = $load;
	}
}

#
#
#

sub main
{
	my($param, $cookie, $uid, $gid, $dbh, $parent);

	#
	$param       = variables();
	$cookie      = cookie('osoal');
	($uid, $gid) = authenticate($cookie->{'nickname'}, $cookie->{'passwd'});

	#
	header('Comments', 'comments.png', '/');
	print('<div align="center">
           <table width="600" cellpadding="0" cellspacing="0" border="0">
           <tr>
           <td>', "\n\n");

	#
	if ($dbh = DBI->connect("dbi:mysql:$MySQLserver;database=osoal",
				$MySQLuser, $MySQLpasswd))
	{
		if ($param->{'select'} eq 'Comment' || $param->{'select'} eq 'Reply' || $param->{'select'} eq 'Modify')
		{
			input_comment($dbh, $uid, $param);
		}

		elsif ($param->{'select'} eq 'Preview')
		{
			preview_comment($dbh, $gid, $param);
		}

		else
		{
			if ($param->{'select'} eq 'Delete')
			{
				if ($gid == 1)
				{
					$param->{'pid'} = delete_comment($dbh, $param->{'pid'});
				}
			}

			elsif ($param->{'select'} eq 'Apply')
			{
				if ($gid == 1)
				{
					apply_comment($dbh, $param);
				}
			}

			elsif ($param->{'select'} eq 'Submit')
			{
				submit_comment($dbh, $uid, $gid, $param);
			}

			# pid / parent
			if ($param->{'pid'} =~ m/^\d+$/ && $param->{'pid'} > 0)
			{
				# this whole bit is a complete mess
				if ($param->{'select'} eq 'Expand')
				{
					$parent = $param->{'pid'};
					$param->{'pid'} = $dbh->selectrow_array('select pid from osoal.comments
                                                             where (thread = ' . $parent . ')');
				}
				else
				{
					$parent = $dbh->selectrow_array('select thread from osoal.comments
                                                     where (pid = ' . $param->{'pid'} . ')');
				}
			}
			else
			{
				$param->{'pid'} = $parent = 0;
			}

			# mode / load
			unless (($param->{'load'} =~ m/^\d+$/ || $param->{'load'} =~ -1) && $param->{'mode'} =~ m/^[012]$/)
			{
				comment_preferences($uid, $dbh, $param);
			}

			# post
			print('<form method="GET" action="', $ENV{'SCRIPT_NAME'}, '">

                   <input type="hidden" name="pid" value="', $param->{'pid'}, '">
                   <input type="hidden" name="mode" value="', $param->{'mode'}, '">

                   <div align="center">
                   [ <a href="', $ENV{'SCRIPT_NAME'}, '?select=Comment&mode=', $param->{'mode'},
				  '&pid=', $parent, '&load=', $param->{'load'}, '"><b>Post Comment</b></a>');

			# mode
			if ($param->{'mode'} != 0)
			{
				# threaded
				print(' | <a href="', $ENV{'SCRIPT_NAME'}, '?pid=', $param->{'pid'},
					  '&mode=0&load=', $param->{'load'}, '"><b>Flattened</b></a>');
			}

			if ($param->{'mode'} != 1)
			{
				# flat
				print(' | <a href="', $ENV{'SCRIPT_NAME'}, '?pid=', $param->{'pid'},
					  '&mode=1&load=', $param->{'load'}, '"><b>Threaded</b></a>');
			}

			if ($param->{'mode'} != 2)
			{
				# nested
				print(' | <a href="', $ENV{'SCRIPT_NAME'}, '?pid=', $param->{'pid'},
					  '&mode=2&load=', $param->{'load'}, '"><b>Nested</b></a>');
			}

			# parent
			if ($parent)
			{
				print(' | <a href="', $ENV{'SCRIPT_NAME'}, '?pid=', $parent, '&mode=1',
					  '&load=', parent_load($dbh, $parent, $param->{'load'}), '#', $parent,
					  '"><b>Parent</b></a>');
			}

			# load
			print(' | <b>Display</b> <input type="text" name="load" value="', $param->{'load'},
				  '" size="3"> <b>Comments</b>');

			# search
			print(' | <a href="/search.pl?location=Comments"><b>Search</b></a> ]
                   </div>
                   </form>', "\n\n");
			
			#
			if (defined($parent))
			{
				print_comments($dbh, $gid, $parent, $param->{'mode'}, $param->{'load'}, $param->{'load'});
			}
		}
	
		$dbh->disconnect();
	}

	print('</td>
           </tr>
           </table>
           </div>', "\n\n");

	footer();
}

#
main();
