#!/usr/bin/perl
use lib '/home/erealms/ethereal/mgmt/perl';

################################################################################
# Created       : Martin Foster
# Modified      : 22-Apr-2006
################################################################################
#
# Board - Messages/discussion board functionality for Ethereal Realms
# Copyright (C) 2000-2006  Martin Foster
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
# Author of this script can be contacted at the following:
#       E-Mail  : martin@ethereal-realms.org
#       Address : 3456 Wolfe Cres
#                 Halifax, Nova Scotia 
#                 B3L 3S2
#
#################################################################################

use CGI;							# Common gateway interface
use CGI::Carp qw(fatalsToBrowser);				# CGI Error logs
use Text::Wrap;							# Text wrapping
use strict;							# Strict variable enforcement

use Ethereal::Audit;						# Auditing handler
use Ethereal::Database;						# Database handler
use Ethereal::Filter;						# Filter processing
use Ethereal::Login;						# Login functionality
use Ethereal::Mail;						# Mail handler
use Ethereal::Param;						# Parameter control
use Ethereal::Template;						# Template handler

#################################################################################
# Gobal override
#################################################################################
$CGI::POST_MAX=1024 * 50;					# Maximum posts
$CGI::DISABLE_UPLOADS = 1;					# Disable uploads

################################################################################
# Data Members
################################################################################
my $cgi;							# Common gateway interface handle
my $database;							# Database handle
my $login;							# Login handle
my $param;							# Parameter handle
my $tmpl;							# Template

my $ban;							# Banned status
my $ready   = 'False';						# Ready status to proceed
my $sname;							# Script name
my $sparam;							# Script parameter

my %board;							# Board hash
my %system;							# System hash


################################################################################
# Program Area
################################################################################

	# Initial handles
	$cgi      = new CGI;
	$database = new Ethereal::Database();

	# Connect and fetch
	$database->Connect($cgi);

	# Parameter handling
	$param = new Ethereal::Param($database, $cgi);
	$param->GetParam();

	# Retreive hash
	$database->GetHashBoard(\%board);
	$database->GetHashSystem(\%system);

	# Login handle
	$login    = new Ethereal::Login($database, $cgi, $param);
	$tmpl     = new Ethereal::Template(\%board);


	# Alternate addressing
	# Pull addresses
	$sname  = $cgi->url(-full=>1);
	$sparam = $cgi->url(-path_info=>1);

	# Escape characters
	$sname  = quotemeta($sname);

	# Needed addition
	$sparam = ($sparam =~ /\/$/) ? $sparam : "$sparam/";

	# Truncate
	$sparam =~ s/^$sname\///;
	$sparam =~ s/\/$//;

	# Unescape
	$sparam = $cgi->unescape($sparam);


 	# Determine state of readiness
	if (defined($param->{'USER'}))
	{
		# Authenticate
		if ($login->GetVerificationNormal())
		{
			# Assign
			$ready = 'True';
		}
	}

	else
	{
		# Change state
		$ready = 'True';
	}

	# Bypass if necessary
	if ($ready eq 'True')
	{
		# Disable independance and header generation
		$login->{'AUTOMATE'} = 0;
		$login->{'NODEPEND'} = 0;


		# Warp config
		if (length($sparam) > 3)
		{
			# Retrieve information
			my ($realm, $public, $alias) = $database->DataGetRealmExistence($sparam);

			# Only if needed
			if ((defined($realm))
			 && (!defined($alias))
			 && (($public eq 'yes')
			  || ($public eq 'hybrid')))
			{
				# Set realm name to whatever was specified
				$param->{'ROOM'} = $realm;
			}
		}

		# Handle headers
		HandleAlternate($database, $login, $cgi, $param);

		# Document header
		print $database->DocumentGetHeader(), "\n";


		# Determin need to select a realm
		if (RealmSelect($database, $cgi, $param, $tmpl, \%board))
		{
			# Logic handling
			# Writer
			PostWrite($database, $login, $cgi, $param, \%board, \%system)
			  if (defined($param->{'WRITE'})); 

			# Removal
			PostPurge($database, $login, $cgi, $param)
			  if (defined($param->{'PURGE'}));


			# Composer
			if ((defined($param->{'REPLY'}))
			 || (defined($param->{'POST'})))
			{
				# Composition
 				PostComposer($database, $login, $cgi, $param, $tmpl, \%board); 
			}

			# Posts viewing
			elsif (!defined($param->{'PASSGO'}))
			{ 
				# Adjust title
				# Standard behaviour
				if ($param->{'ROOM'} !~ $system{'SetBoardName'})
				{ 
					# Display realm name
					$tmpl->Show('TmplHead', 
						MTITLE => $param->{'ROOM'},
						MTOPIC => ucfirst($param->{'TOPIC'})
					 );
				}

				# System board
				else
				{ 
					# Display system specific name
					$tmpl->Show('TmplHead', 
						MTITLE => $system{'SetBoardName'},
						MTOPIC => ucfirst($param->{'TOPIC'})
					 );
				}

				# Handling of viewing
				if (defined($param->{'ZOOM'}))
				{
					# Lists all children of a specific post
					PostDetails($database, $cgi, $param, \%board, \%system);
				}
				else
				{
					# Listing of parents only
					PostOverview($database, $cgi, $param, \%board, \%system); 
				}
			}
		}

		# Document footer
		print $database->DocumentGetFooter(), "\n";	
	}

#################################################################################
# Sub-Routines
#################################################################################

#####################
# Handle Alternates
#
# Will handle how alternate boards are displayed.  Essentially, this will simply
# create a redirect header sending it to the alternate board if such a thing
# was specified.

sub HandleAlternate
{
	#####################
	# Data members
	my $database = shift; 					# Database handle
	my $login    = shift;					# Login handle
	my $cgi      = shift;					# CGI handle
	my $param    = shift;					# Parameter list

	my $public;						# Public status
	my $altboard;						# Alternate message board
	

	#####################
	# Program area

	# Verification of acceptance
	if (defined($param->{'ROOM'}))
	{
		# Retreive public status
		($public) = $database->DataGetRealmPublic($param->{'ROOM'});

	
		# Verify existence and nature	
		if ((defined($public)) && ($public ne 'no'))
		{
			# Retreive alternate
			($altboard) = $database->DataGetRealmAltBoard($param->{'ROOM'});		
			

			# Exitence of alternate header
			if (defined($altboard))
			{
				# Retriect header
				print $cgi->redirect($altboard);

				# End script
				exit;
			}
		}
		
		# Private or nonexistent	
		else
		{
			# Dispose of value
			$param->Cleanup('ROOM');
		}
	}

	# Default header
	$login->WriteHeader();
}


#####################
# Realm Selection 
#
# Realms are needed in order to complete operations for the message board.  This
# is for the primary reason that the board grants one topic area for each specific
# realm.  Without a public realm, the board topic will not exist.

sub RealmSelect
{
	#####################
	# Data members
	my $database = shift; 					# Database handle
	my $cgi      = shift;					# CGI handle
	my $param    = shift;					# Parameter list
	my $tmpl     = shift;					# Template handler
	my $board    = shift;					# Board hash handle 

	my $statement;						# Query statement

	my $choice;						# Choice of the matter
	my $count;						# Topic counter
	my $title;						# Title to display

	my $check;						# See if its really zero
	my $descr;						# Description
	my $posts;						# Posts found
	my $topic;						# Topic of discussion
	my $select;						# Selection template

	my $url;						# Self referencing link
	my $inline;						# Inline parameters

	my $dis;						# Display realms
	my @dis;						# Display selection

	my %topics;						# Topics hash	

	#####################
	# Program area


	# Handle specifics
	# Realm selection
	unless (defined($param->{'ROOM'}))
	{
		# Retreive realm list
		$database->GetListPublicRealms(\@dis);

		# Generate widget
		$dis = $cgi->scrolling_list('ROOM', \@dis, $dis[0], 10);


		# Beginning of form
		print $cgi->start_form();
		print $param->EmbedNormal($param->Flat()) if (defined($param->{'USER'}));

		# Display form
		$tmpl->Show('TmplRealmSel',
			WREALMS => $dis,
			WSELECT => $cgi->submit($board->{'TxtSelect'})
		 );
		
		# Return false
		return 0;
	}

	# Topic selection
	elsif (!defined($param->{'TOPIC'}))
	{
		# Retreive number of topics
		($count) = $database->DataGet("SELECT COUNT(*) AS Topics
			FROM BoardTopic
			WHERE RealmName=?", $param->{'ROOM'});

		# If there is a need
		if ($count > 0)
		{
			# Display templates
			# Prepare and execute
			$database->Pull(\$statement, "SELECT
				 t.BoardTopic       AS \"Topic\",
				 b.BoardTopic       AS \"Board\",
				 COUNT(*)           AS \"Posts\",
				 t.BoardDescription AS \"Descr\"
				FROM ethereal.Board b
				RIGHT OUTER JOIN ethereal.BoardTopic t
				   ON (t.RealmName=b.RealmName AND t.BoardTopic=b.BoardTopic)
				WHERE t.RealmName=?
				GROUP BY t.BoardTopic, b.boardtopic, t.BoardDescription
				ORDER BY b.BoardTopic",

			 $param->{'ROOM'}
			);

			# Something there
			if (($topic, $check, $posts, $descr) = $statement->fetchrow())
			{
				# Capture link
				$url = $cgi->url(-absolute=>1);

				# Selection safety
				$select = '';

				
				# Loop and display
				do
				{
					# Generate link
					$inline = (defined($param->{'USER'}))
					  ? $param->EmbedInline($param->Flat(), TOPIC=>$topic)
					  : $param->Crypt(ROOM=>$param->{ROOM}, TOPIC=>$topic);


					# Description
					# Ensure setting
					$descr = $board->{'TxtDescr'} unless (defined($descr));

					# Carriage returns
					$descr =~ s/\n/<BR>/gs;


					# Check
					$posts = 0 if (!defined($check));


					# Display row
					$select .= $tmpl->Pass('TmplTopicSel',
						MTITLE => $topic,
						MPOSTS => $posts,
						MLINK  => $url . $inline,
						MDESCR => $descr
					 );
				}
				while (($topic, $check, $posts, $descr) = $statement->fetchrow());


				# Display field
				$tmpl->Show('TmplTopic',
					LSTSELECT => $select);	
				

				# Return False
				return 0;
			}
		}

		# Assign the default parameter
		$param->{'TOPIC'} = 'system';
	}

	# True if reached
	return 1;
}

#####################
# Post Details
#
# Designed to view all children of a speicfic post.   Useful for those
# that prefer segregation over actual speed.

sub PostDetails
{
	#####################
	# Data members
	my $database = shift;				# Database handle
	my $cgi      = shift;			 	# CGI handle	
	my $param    = shift;				# Parameter list
	my $board    = shift;				# Board hash
	my $system   = shift;				# System hash

	my $statement;					# Query handle
	my $res;					# Results hash

	
	my $choice;					# Choice of the matter
	my $count;					# Counter
	my $entries;					# Number of entries
	my $kill;					# Kill button
	my $listing;					# Post listing

	my $iback;					# Back link
	my $ibio;					# Biographical link
	my $idirect;					# Direct link to post
	my $inline;					# Inline parameter list
	my $ireply;					# Reply capabilities
	my $isuper;					# Supervisory parameter list
	my $url      = $cgi->url(-absolute=>1);		# Self referencing link

	my %user;					# Authentication arguments
	my %args;					# Basic arguments


	#####################
	# Program area

	# Logged on specific
	if (defined($param->{'USER'}))
	{
		# Determine supervisory
		($choice) = $database->DataGetRoomPrivs($param->{'ROOM'}, $param->{'USER'});

		# Authentication arguments
		%user = (
			USER  => $param->{'USER'},
			CRYPT => $param->{'CRYPT'},
		 );
	}

	# Base arguments
	%args = (
		ROOM  => $param->{'ROOM'},
		TOPIC => $param->{'TOPIC'},
	 );

	
	# Back parameters
	$iback = (defined($param->{'USER'}))
		? $param->EmbedInline(%user, %args)
		: $param->Crypt(%args);
		

	# Parental display
	# Prepare and execute
	$database->Pull(\$statement, "SELECT 
			 BoardIDNumber  AS \"BoardIDNumber\",
			 PuppetName     AS \"PuppetName\",
			 BoardSubject   AS \"BoardSubject\",
			 BoardMessage   AS \"BoardMessage\",
			 getDate(BoardTimestamp) AS \"BoardTimestamp\"
			FROM  Board 
			WHERE BoardIDNumber=?", 

	 $param->{'ZOOM'}
	); 

	# Pull total entries
        ($entries) = $database->DataGet("SELECT COUNT(*)-1
			FROM  Board 
			WHERE LevelZero=?",

	 $param->{'ZOOM'}
	);

	
	# Safety
	$listing = '';
	$entries = (defined($entries)) ? $entries : 0;
	
	
	# Pull and display
	if ($res = $statement->fetchrow_hashref())
	{
		# Inline parameter
		($ibio)   = $param->Crypt(
			BCHAR => $res->{'PuppetName'},
			BPOOL => 'system'
		 );

		# Generate parameters
		$inline = (defined($param->{'USER'}))
			? $param->EmbedInline(%user, %args, ZOOM=>$res->{'BoardIDNumber'})
			: $param->Crypt(%args, ZOOM=>$res->{'BoardIDNumber'});

		# Append entry
		$listing = $tmpl->Pass('TmplParent',
			MBBODY => $res->{'BoardMessage'},
			MBFROM => $res->{'PuppetName'},
			MBRPLY => $entries,
			MBSEQN => $res->{'BoardIDNumber'},
			MBSUBJ => $res->{'BoardSubject'},
			MBTIME => $res->{'BoardTimestamp'},
			MKILL  => '',
			LBBIOL => $board->{'LnkBio'} . $ibio,
			LBDRCT => $url . $idirect,
			LBZOOM => $url . $inline
		 );
	}
	
	# Finish execution
	$statement->finish();


	# Seperator bar to comments
	# Insert additional argument
	$args{'ZOOM'} = $param->{'ZOOM'}; 

	# Generate parameters
	$ireply = (defined($param->{'USER'}))
		? $param->EmbedInline(%user, %args, REPLY=>$res->{'BoardIDNumber'})
		: $param->Crypt(%args, REPLY=>$res->{'BoardIDNumber'});

	# Append Comments section
	$listing = $listing . $tmpl->Pass('TmplComments',
		MCOUNT => $entries,
		LREPLY => $url . $ireply,
		LBACK  => $url . $iback
	 );


	# Children of above post
	# Prepare and execute
	$database->Pull(\$statement, "SELECT 
			 BoardIDNumber  AS \"BoardIDNumber\",
			 PuppetName     AS \"PuppetName\",
			 BoardSubject   AS \"BoardSubject\",
			 BoardMessage   AS \"BoardMessage\",
			 BoardLevel     AS \"BoardLevel\",
			 getDate(BoardTimestamp) AS \"BoardTimestamp\"
			FROM  Board 
			WHERE LevelZero = ?
			  AND BoardLevel <> 0
			ORDER BY LevelZero, LevelOne, LevelTwo, LevelThree, LevelFour, LevelFive",

	 $param->{'ZOOM'},
	); 

	# Check for validity
	if ($res = $statement->fetchrow_hashref())
	{
	
		# Loop and pull data
		do
		{
			# Kill value
			# Default value
			$kill = '';
		
			# Kill button
			if (defined($choice))
			{
				# Supervisory
				$isuper = $param->EmbedInline(%user, %args, PURGE=>$res->{'BoardIDNumber'});

				# Append kill button
				$kill = $tmpl->Pass('TmplKill', LBKILL=>$url . $isuper);
			}

		
			# Inline parameter
			($ibio) = $param->Crypt(BCHAR=>$res->{'PuppetName'}, BPOOL=>'system');

			# Generate parameters
			$inline = (defined($param->{'USER'}))
				? $param->EmbedInline(%user, %args, REPLY=>$res->{'BoardIDNumber'})
				: $param->Crypt(%args, REPLY=>$res->{'BoardIDNumber'});

			# Append spacer
			$listing = $listing . $tmpl->Pass('TmplSpacer') if ($count > 1);
		
			# Append entry
			$listing = $listing . $tmpl->Pass('TmplListing',
				MBBODY => $res->{'BoardMessage'},
				MBFROM => $res->{'PuppetName'},
				MBSEQN => $res->{'BoardIDNumber'},
				MBSUBJ => $res->{'BoardSubject'},
				MBTIME => $res->{'BoardTimestamp'},
				MBLEVL => $res->{'BoardLevel'} * $board->{'SetSpace'},
				MKILL  => $kill,
				LBBIOL => $board->{'LnkBio'} . $ibio,
				LBDRCT => $url . $idirect,
				LBRPLY => $url . $inline
			 );
			
		} while ($res = $statement->fetchrow_hashref());

		# Close query handle
		$statement->finish();
	}

	# Redisplay warning of no children
	else
	{
		# Display empty post
		$listing .= $board->{'TmplEmpty'};
	}

	# Append Comments section
	$listing = $listing . $tmpl->Pass('TmplComments',
		MCOUNT => $entries,
		LREPLY => $url . $ireply,
		LBACK  => $url . $iback
	 );

	# Display gathered information
	print $listing, "\n";
}


#####################
# Post Overviewer 
#
# The viewing sub-routine for the message board.  Of course the specific realm is needed
# which is why that this is called after the ROOM AND TOPIC are determined. 

sub PostOverview 
{
	#####################
	# Data members
	my $database = shift;				# Database handle
	my $cgi      = shift;			 	# CGI handle	
	my $param    = shift;				# Parameter list
	my $board    = shift;				# Board hash
	my $system   = shift;				# System hash

	my $statement;					# Query handle
	my $res;					# Results hash

	my $choice;					# Choice of the matter
	my $stime;					# Search time

	my $count;					# Running total
	my $entries;					# Entries listed
	my $offset;					# Offset
	my $total;					# Total entries

	my $order;					# Order to take on
	my $activity;					# Activity based searching

	my $ibio;					# Biographical link
	my $idirect;					# Direct link to post
	my $inline;					# Inline parameter list
	my $isuper;					# Supervisory parameter list
	my $url      = $cgi->url(-absolute=>1);		# Self referencing link

	my $kill;					# Kill button
	my $listing;					# List of all entries
	my $pages;					# Page support
	
	my %args;					# Basic arguments


	#####################
	# Program area

	# Logged on specific
	if (defined($param->{'USER'}))
	{
		# Determine supervisory
		($choice) = $database->DataGetRoomPrivs($param->{'ROOM'}, $param->{'USER'});

		# Base arguments
		%args = (
			USER  => $param->{'USER'},
			CRYPT => $param->{'CRYPT'},
		 );
	}


	# Generate parameter lines
	# Nav variables
	my ($back, $topc, $post, $actv, $desc, $asc, $day, $week, $mnth, $year);

	# Back button
	if (defined($param->{'USER'})) { $back = $param->EmbedInline(%args); }
	else { $back = ''; }

	# Add additional arguments
	$args{'ROOM'}  = $param->{'ROOM'};


	# Default behaviour
	if (defined($param->{'USER'})) { $topc = $param->EmbedInline(%args); } 
	else { $topc = $param->Crypt(ROOM=>$param->{'ROOM'}); }

	# Add topic as argument
	$args{'TOPIC'} = $param->{'TOPIC'};


	# Default search
	# Activity based search
	if ((!defined($param->{'ORDER'}))
	 && (!defined($param->{'ACTV'}))
	 && (!defined($param->{'TIME'})))
	{
		# Create to cause default
		$param->{'ACTV'} = 'True';

	}

	# Maintain directional state
	$param->{'ORDER'}  = (defined($param->{'ORDER'}))  ? $param->{'ORDER'}  : 'ASC';

	# Maintain offset
	$param->{'OFFSET'} = (defined($param->{'OFFSET'})) ? $param->{'OFFSET'} : 1;


	# Functional
	# User defined
	if (defined($param->{'USER'}))
	{
		$post = $param->EmbedInline(%args, POST=>'True');

		# Activity based
		$actv = $param->EmbedInline(%args, ACTV=>'True');

		# Order
		$desc = $param->EmbedInline(%args, ORDER=>'DESC');
		$asc  = $param->EmbedInline(%args, ORDER=>'ASC');


		# Append order
		$args{'ORDER'} =  $param->{'ORDER'};

		# Time based
		$day  = $param->EmbedInline(%args, TIME=>'1');
		$week = $param->EmbedInline(%args, TIME=>'7');
		$mnth = $param->EmbedInline(%args, TIME=>'31');
		$year = $param->EmbedInline(%args, TIME=>'365');
	}

	# General
	else
	{
		$post = $param->Crypt(%args, POST=>'True');

		# Activity based
		$actv = $param->Crypt(%args, ACTV=>'True');

		# Order
		$desc = $param->Crypt(%args, ORDER=>'DESC');
		$asc  = $param->Crypt(%args, ORDER=>'ASC');


		# Append order
		$args{'ORDER'} =  $param->{'ORDER'};

		# Time based
		$day  = $param->Crypt(%args, TIME=>'1');
		$week = $param->Crypt(%args, TIME=>'7');
		$mnth = $param->Crypt(%args, TIME=>'31');
		$year = $param->Crypt(%args, TIME=>'365');
	}


	# Additional searching
	$stime    = (defined($param->{'TIME'})) ? "getInterval('$param->{TIME} days')" : "'01/01/2000 00:00:00'";
	$activity = (defined($param->{'ACTV'})) ? "BoardUpdate DESC," : "";


	# Generate direct link
	$idirect = $param->Crypt(ROOM=>$param->{'ROOM'}, TOPIC=>$param->{'TOPIC'});


	# Page handling
	# Entries
	($total) = $database->DataGet("SELECT COUNT(*)
			FROM  Board 
			WHERE BoardLevel=0
			  AND RealmName=?
			  AND BoardTopic=?
			  AND BoardUpdate > ($stime)::TIMESTAMP",
	
	 $param->{'ROOM'},
	 $param->{'TOPIC'}
	); 

	# Create offset entries
	$count   = 0;
	$entries = $total;

	# Loop
	do
	{
		# Remove mount to verify spacing
		$entries -= $board->{'SetLimit'};

		# Increment
		$count++;

		# Parameter handling
		$inline = (defined($param->{'USER'}))
			? $param->EmbedInline($param->Flat(), OFFSET=>$count)
			: $param->Crypt($param->Flat(), OFFSET=>$count);

		# Create page entry
		$pages = (defined($pages))
			? $pages . ' ' . $tmpl->Pass('TagPage', MLINK=>$url . $inline, MCOUNT=>$count)
			: $tmpl->Pass('TagPage', MLINK=>$url . $inline, MCOUNT=>$count);

	} while ($entries > 0);
	

	# Create offset
	$offset = ($param->{'OFFSET'} - 1) * $board->{'SetLimit'};


	# Display of parents
	# Prepare query
	$database->Pull(\$statement, "SELECT 
			 BoardIDNumber  AS \"BoardIDNumber\",
			 PuppetName     AS \"PuppetName\",
			 BoardSubject   AS \"BoardSubject\",
			 BoardMessage   AS \"BoardMessage\",
			 getDate(BoardTimestamp) AS \"BoardTimestamp\"
			FROM  Board 
			WHERE BoardLevel=0
			  AND RealmName=?
			  AND BoardTopic=?
			  AND BoardUpdate > ($stime)::TIMESTAMP
			ORDER BY $activity LevelZero $param->{ORDER}
			LIMIT  $board->{SetLimit}
			OFFSET $offset",

	 $param->{'ROOM'},
	 $param->{'TOPIC'}
	); 

	# Safety
	$count   = 0;
	$listing = '';
	
	# Loop and display
	while ($res = $statement->fetchrow_hashref())
	{
		# Kill value
		# Default value
		$kill = '';
		
		# Kill button
		if (defined($choice))
		{
			# Supervisory
			$isuper = $param->EmbedInline($param->Flat(), PURGE=>$res->{'BoardIDNumber'});

			# Append kill button
			$kill = $tmpl->Pass('TmplKill', LBKILL=>$url . $isuper);
		}


		# Increment counter
		$count++;

		# Number of replies
		($entries) = $database->DataGet("SELECT COUNT(*)-1
		        FROM Board	
		 	WHERE LevelZero=?", $res->{'BoardIDNumber'});	 
		
		# Inline parameter
		($ibio)   = $param->Crypt(
			BCHAR => $res->{'PuppetName'},
			BPOOL => 'system'
		 );

		# Generate parameters
		$inline = (defined($param->{'USER'}))
			? $param->EmbedInline($param->Flat(), ZOOM=>$res->{'BoardIDNumber'})
			: $param->Crypt($param->Flat(), ZOOM=>$res->{'BoardIDNumber'});


		# Append spacer
		$listing = $listing . $tmpl->Pass('TmplSpacer') if ($count > 1);
		
		# Append entry
		$listing = $listing . $tmpl->Pass('TmplParent',
			MBBODY => $res->{'BoardMessage'},
			MBFROM => $res->{'PuppetName'},
			MBRPLY => $entries,
			MBSEQN => $res->{'BoardIDNumber'},
			MBSUBJ => $res->{'BoardSubject'},
			MBTIME => $res->{'BoardTimestamp'},
			MKILL  => $kill,
			LBBIOL => $board->{'LnkBio'} . $ibio,
			LBDRCT => $url . $idirect,
			LBZOOM => $url . $inline
		 );
	}

	# System based forums
	$back = $topc if ($param->{'ROOM'} eq $system->{'SetBoardAlias'});


	# Template
	# Display completed template
	$tmpl->Show('TmplOverview',
		LSTMESS => $listing,
		LBPOST  => $url . $post,
		LBACTV  => $url . $actv,
		LBASCO  => $url . $asc,
		LBDSCO  => $url . $desc,
		LBTDAY  => $url . $day,
		LBTWEK  => $url . $week,
		LBTMNT  => $url . $mnth,
		LBTYAR  => $url . $year,
		LBSLCT  => $url . $topc,
		LBBACK  => $url . $back,
		MCOUNT  => $count,
		MPAGES  => $pages,
		MTOTAL  => $total,
	);
		
		
	# Close query handle
	$statement->finish();
}


#####################
# Post Composer  
#
# As the name would imply, this subroutine allows for the addition of posts to the 
# existing set of posts.  Provided that there are posts to reply to, one can
# reply under the parent post.

sub PostComposer
{
	#####################
	# Data members
	my $database = shift; 					# Database handle
	my $login    = shift;					# Login handler
	my $cgi      = shift;					# CGI handle
	my $param    = shift;					# Parameter list
	my $tmpl     = shift;					# Template handler
	my $board    = shift;					# Board hash handle 

	my $acc;						# Realm access
	my $ban;						# User banned status
	my $def;						# Default page
	my $subj;						# Subject of message

	my @puppets;						# List of puppets


	#####################
	# Program area
	
	# User logged on
	if ($login->GetVerificationNormal())
	{
		# Determine ban
		($acc) = $database->DataGetRealmAccess($param->{'ROOM'});
		($ban) = $database->DataGetBannedStatus($param->{'USER'}, $param->{'ROOM'});

		# Default
		($def) = $database->DataGetDefault($param->{'USER'});


		# Allow if not banned
		unless ((defined($ban)) && ($acc eq 'relaxed'))
		{
			# Gather information
 			if (defined($param->{'REPLY'}))
			{
				# Prepare and execute
			 	($subj) = $database->DataGet("SELECT BoardSubject 
					FROM Board 
					WHERE BoardIDNumber=?::INT",

				 $param->{'REPLY'}
				);

				# Adapt subject
				$subj =~ s/(.+)/(RE) $1/ if ($subj !~ /^\(RE\)/);

				# Assign ID for next pass
				$param->{'PID'} = $param->{'REPLY'};
			}

			# Cleanup
			$param->Cleanup('REPLY', 'POST');


			# Retreive puppet list
			$database->GetListPuppetYours(\@puppets, $param->{'USER'});


			# Widget generation 
			# Normal user input
			my $wbfrom = (defined($database->{'DEFAULT'}))
			  ? $cgi->hidden('PFROM', $database->{'DEFAULT'})
			  : $cgi->popup_menu('PFROM', \@puppets, $def);
			my $wbsubj = $cgi->textfield('PSUBJ', $subj, 25, 250);
			my $wbbody = $cgi->textarea(
				-name    => 'PTEXT',
				-rows    => 10,
				-columns => 60,
				-wrap    => 'SOFT'
			 );

			# Buttons
			my $wbpost = $cgi->submit($board->{'TxtPost'});
			my $wbrset = $cgi->reset($board->{'TxtReset'});


			# Display
			# Start of form
			print $cgi->start_form();
			print $param->EmbedNormal($param->Flat(), WRITE=>'True'), "\n";

			# Display template
			$tmpl->Show('TmplCompose',
				WBFROM => $wbfrom,
				WBSUBJ => $wbsubj,
				WBBODY => $wbbody,
				WBPOST => $wbpost,
				WBRSET => $wbrset,
				LBACK  => $cgi->url(-absolute=>1) . $param->EmbedInline($param->Flat())
			 );
			 
			# End form
			print $cgi->end_form(), "\n";
		}


		# Banner users
		else
		{
			# Display generic message
			$tmpl->Show('TmplBanned');
		}
	}
}


#####################
# Post Purge
#
# Handles the purging or killing of post and any children associated with it.   As one can
# see this is useful for giving the message board a more youthful look and cleaning out
# unwanted material.

sub PostPurge
{
	#####################
	# Data members
	my $database = shift;					# Database handle
	my $login    = shift;					# Login handle
	my $cgi	     = shift;					# Common gateway interface
	my $param    = shift;					# Parameter list

	my $super;						# Supervisory access

	#####################
	# Program Area

	# Only if login credentials are valid
	if ($login->GetVerificationNormal())
	{
		# Audit handle
		my $audit = new Ethereal::Audit($database, $cgi, $param);

		# Superisory access
		($super)  = $database->DataGetRoomPrivs($param->{'ROOM'}, $param->{'USER'});

		# Ensure they are supervisors
		if (defined($super)) 
		{
			# Evaluation days
			eval
			{
				# Pruge posts
				$database->Write("DELETE FROM Board
					WHERE RealmName=? 
					  AND BoardTopic=?
					  AND (LevelZero=? 
					   OR LevelOne=?
					   OR LevelTwo=?
					   OR LevelThree=?
					   OR LevelFive=?)",

				 $param->{'ROOM'},
				 $param->{'TOPIC'},
				 $param->{'PURGE'},	
				 $param->{'PURGE'},	
				 $param->{'PURGE'},	
				 $param->{'PURGE'},	
				 $param->{'PURGE'}
				);

				# Auditing table
				$audit->BoardRem($param->{'PURGE'}, $param->{'ROOM'});


				# Commit
				$database->Commit();
			};

			# If block failed
			if ($@)
			{
				# Issue warning
				warn("Transaction aborted: $@");

				# Undo incomplete changes
				$database->Rollback();
			}
		}


		# Cleanup
		$param->Cleanup('PURGE');
	}

	# Kill switch
	else
	{
		# Do not collect 100$
		$param->{'PASSGO'} = 'No';
	}

}


#####################
# Post Writer  
#
# Handles the eventual cleanup and posting of the post to the threads and tree.  This includes
# language filtering and removal of HTML to keep this clean.
	
sub PostWrite	 						# Database, CGI, Parameter list
{
	#####################
	# Data members
	my $database = shift; 					# Database handle
	my $login    = shift;					# Login handle
	my $cgi      = shift;					# CGI handle
	my $param    = shift;					# Parameter list
	my $board    = shift;					# Board hash handle
	my $system   = shift;					# System board

	my $filter;						# Filter handler
	my $send;						# Mail sender

	my $statement;						# Query statement

	my $time;						# Date of handling
	my $email;						# Email address
	my $name;						# Full name
	my $notice;						# Notice
	my $post;						# Pre modified post

	my $puppet;						# Puppet name
	my $puppeteer;						# Puppeteer name

	my $level;						# Level
	my $res;						# Results

	my $id;							# ID insertion function
	my $url = $cgi->url();					# Location to self

	my @levels;						# Levels and depth


	#####################
	# Program area

	# Create instance
	$filter   = new Ethereal::Filter();


	# Link with hash
	$database->GetHashSystem(\%system);

	# Ensure authenthication
	if ($login->GetVerificationNormal())
	{
		# Evaluation
		eval
		{
			# Set wrapping width
			$Text::Wrap::columns = 72;
			
			# Pull insertion id
			($id) = $database->DataGetInsert('seqBoard');

			# Information handling
			# Retreive post information
			if (defined($param->{'PID'}))
			{
				# Prepare and execute
				$database->Pull(\$statement, "SELECT
					 PuppetName AS \"PuppetName\",
					 BoardLevel AS \"BoardLevel\",
					 LevelZero  AS \"LevelZero\",
					 LevelOne   AS \"LevelOne\",
					 LevelTwo   AS \"LevelTwo\",
					 LevelThree AS \"LevelThree\",
					 LevelFour  AS \"LevelFour\",
					 LevelFive  AS \"LevelFive\"
					FROM  Board
					WHERE BoardIDNumber=?::INT",

				 $param->{'PID'}
				);

				# Retreive and adapt
				$res = $statement->fetchrow_hashref();


				# Determine levels
				$levels[0] = $res->{'LevelZero'};
				$levels[1] = ($res->{'LevelOne'}   != $res->{'LevelZero'})  ? $res->{'LevelOne'}   : $id;
				$levels[2] = ($res->{'LevelTwo'}   != $res->{'LevelOne'})   ? $res->{'LevelTwo'}   : $id;
				$levels[3] = ($res->{'LevelThree'} != $res->{'LevelTwo'})   ? $res->{'LevelThree'} : $id;
				$levels[4] = ($res->{'LevelFour'}  != $res->{'LevelThree'}) ? $res->{'LevelFour'}  : $id;
				$levels[5] = ($res->{'LevelFive'}  != $res->{'LevelFour'})  ? $res->{'LevelFive'}  : $id;

				# Determin response level
				$level = $res->{'BoardLevel'} + 1;
				$level = ($level > 5) ? 5 : $level; 

				# Determin puppet name
				$puppet = $res->{'PuppetName'};


				# Finish query
				$statement->finish();
			}
	
			# Parent (root level) posts
			else
			{
				# Assign levels and level for root
				@levels = ($id, $id, $id, $id, $id, $id); 
				$level = 0; 
			}
	
			# Sanity check
			$param->{'PTEXT'} = $filter->MakeSane($param->{'PTEXT'}, $board->{'SetTruncate'});


			# Mold subject and body
			# Strip HTML
			$param->{'PSUBJ'} =~ s/<[^>]*>//gs;
			$param->{'PTEXT'} =~ s/<[^>]*>//gs;

			# Buffer for Emails
			$post =  fill('    ', '    ', $param->{'PTEXT'});

			# Replace carriage returns
			$param->{'PTEXT'} =~ s/\n/<BR>/gs;

			# Create hyperlinks
			$param->{'PTEXT'} =~ s/(http:\/\/.*?)(\s|$)/<A HREF="$1" TARGET="_blank">$board->{TagHyperlink}<\/A> /gm;
			$param->{'PTEXT'} =~ s/([\w\.\-]*?\@[\w\.\-]*?\..*?)(\s|$)/<A HREF="mailto:$1">$board->{TagEmail}<\/A> /gm;

			# Database insertion
			$database->Write("INSERT INTO Board 
				(BoardIDNumber,
				 PuppetName,
				 RealmName,
				 BoardTopic,
				 BoardSubject,
				 BoardMessage,
				 BoardLevel,
				 LevelZero,
				 LevelOne,
				 LevelTwo,
				 LevelThree,
				 LevelFour,
				 LevelFive)
				VALUES
				($id,?,?,?,?,?,?,?,?,?,?,?,?)",

			 $param->{'PFROM'},
			 $param->{'ROOM'},
			 $param->{'TOPIC'},
			 $param->{'PSUBJ'},
			 $param->{'PTEXT'},
			 $level,
			 $levels[0],
			 $levels[1],
			 $levels[2],
			 $levels[3],
			 $levels[4],
			 $levels[5]
			);


			# Bump up thread
			$database->Write("UPDATE Board
				SET BoardUpdate=LOCALTIMESTAMP
				WHERE LevelZero=?", 

			 $levels[0]
			);


			# Administrative message
			($email) = $database->DataGetRealmBoard($param->{'ROOM'});
			($time)  = $database->DataGetDate();


			# Generate
			if (defined($email))
			{
				# Pull
				($notice) = $database->DocumentGetBoardAdmin();


				# Send Mail
				# Handle initiation
				$send = tie(*MAIL, 'Ethereal::Mail', $database);

				# Initial setup
				$send->SetSubject("$system->{SetBoardPrefix} $param->{PSUBJ}");
				$send->SetSearch(
					MDATE => $time,
					MFROM => $param->{'PFROM'},
					MLINK => $url,
					MMAIL => "$system->{SetInfoContactName}\n$system->{SetInfoContactAddress}",
					MMESG => $post,
					MROOM => $param->{'ROOM'},
					MSUBJ => $param->{'PSUBJ'},
					MTOPC => $param->{'TOPIC'}
				 );

				# Recipients
				$send->AddTo("$name <$email>");
	
				# Add message
				print MAIL $notice;

				# Close and send
				close(MAIL);
			}

			# Email notification
			if (defined($puppet))
			{
				# Retreive login
				($puppeteer) = $database->DataGetPuppeteerLogin($puppet);


				# Complete if defined
				if (defined($puppeteer))
				{
					# Retreive name and Email
					($name)   = $database->DataGetPuppeteerName($puppeteer);
					($email)  = $database->DataGetPuppeteerEmail($puppeteer);
					($notice) = $database->DocumentGetBoardNotice();


					if (defined($notice))
					{
						# Send Email
						# Iniate instance
						$send = tie(*MAIL, 'Ethereal::Mail', $database);

						# Initial setup
						$send->SetSubject("You have a reply on $param->{ROOM} message board from: $param->{'PFROM'}");
						$send->SetSearch(
							MDATE => $time,
							MFROM => $param->{'PFROM'},
							MLINK => $url,
							MMAIL => "$system->{SetInfoContactName}\n$system->{SetInfoContactAddress}",
							MMESG => $post,
							MROOM => $param->{'ROOM'},
							MSUBJ => $param->{'PSUBJ'},
							MTOPC => $param->{'TOPIC'}
						 );

						# Recipients
						$send->AddTo("$name <$email>");

						# Message
						print MAIL $notice;

						# Close and send
						close(MAIL);
					}
				}
			}

			# Commit
			$database->Commit();
		};

		# If block failed
		if ($@)
		{
			# Issue warning
			warn("Transaction aborted: $@");

			# Undo incomplete changes
			$database->Rollback();
		}


		# Cleanup
		$param->Cleanup(
			'PID',
			'PFROM',
			'PSUBJ',
			'PTEXT',
			'WRITE'
		 );
	}

	# Kill switch
	else
	{
		# Do not collect 100$
		$param->{'PASSGO'} = 'No';
	}
}
