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

################################################################################
# Created       : Martin Foster
# Modified      : 17-Feb-2005
################################################################################
#
# Gallery View - Script part of Ethereal Realms, designed to display and list
#                galleries and associated works
# Copyright (C) 2000-2005  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 : 29-26503 TWP RD 511
#                 Spruce Grove, Alberta
#                 T7Y 1G4
#
#################################################################################

use Calendar::Simple;						# Perl extension to create simple calendars
use CGI;							# Common gateway interface
use CGI::Carp qw(fatalsToBrowser);				# CGI Error logs
use Number::Format qw(:subs);					# Number formatting library
use strict;							# Strict variable enforcement

use Ethereal::Database;						# Database handler
use Ethereal::Filter;						# Filter handling
use Ethereal::Login;						# Login functionality
use Ethereal::Mail;						# Mail handler
use Ethereal::Option;						# Option handler
use Ethereal::Param;						# Parameter control
use Ethereal::Table;						# Table handler
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 hash
my $tmpl;							# Template handle

my $ready   = 'False';						# Ready for processing
my $sname;							# Simply the script name
my $sparam;							# Scripted parameter string

my %gal;							# Gallery hash
my %sparam;							# Scripted parameters

################################################################################
# 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();

	# Link with hash
	$database->GetHashGallery(\%gal);

	# Initial hash
	$tmpl  = new Ethereal::Template(\%gal);


	# Authenthication instance
	$login = new Ethereal::Login($database, $cgi, $param, \%gal);

	# Enable custom header and footer
	$login->{'HEADER'} = $database->DocumentGetGalleryHeader();
	$login->{'FOOTER'} = $database->DocumentGetGalleryFooter();


	# Pull script name
	$sname = $cgi->url(-full=>1);
	$sparam = $cgi->url(-path_info=>1);


	# Determine need
	# Escape
	$sname = quotemeta($sname);

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

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

	# Retreive parameters
	($sparam{'ACT'},
	 $sparam{'GAL'},
	 $sparam{'WRK'},
	 $sparam{'POS'}) = split(/\//, $cgi->unescape($sparam));

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

	else
	{
		# Require a header
		print $cgi->header();

		# Change state
		$ready = 'True';
	}

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

		# Page header
		HeaderRewrite($database, \%sparam, \%gal);


		# Action decisions
		# Specific work/gallery views
		if ((defined($sparam{'ACT'}))
		 && ($sparam{'ACT'} eq 'view')) 
		{
			# Speficied gallery
			if ((defined($sparam{'GAL'}))
			 && ($sparam{'GAL'} =~ /^\d+$/))
			{
				# Specific work
				if ((defined($sparam{'WRK'}))
				 && (($sparam{'WRK'} =~ /^\d+$/)
				  || ($sparam{'WRK'} =~ /^new$/)))
				{
					# Fetch proper number
					# If necessary
					if ($sparam{'WRK'} =~ /^new$/)
					{
						# Need exists to fetch newest work
						($sparam{'WRK'}) = $database->DataGet("SELECT WorkID
							FROM Work
							WHERE GalleryID=?
							ORDER BY WorkTimestamp DESC LIMIT 1", $sparam{'GAL'});
					}
				
					# Work specific display
					WorkDisplay($database, $login, $cgi, $param, $tmpl, \%sparam, \%gal);
				}

				# General gallery
				else
				{
					# Display gallery
					GalleryDisplay($database, $login, $cgi, $param, $tmpl, \%sparam, \%gal);
				}
			}

			# Invalid entry	
			else
			{
				# Display appropriate error message
				$tmpl->Show('TmplMsg', MMESSAGE=>$gal{'MsgParam'});
			}
		}

		# Comment viewer
		elsif ((defined($sparam{'ACT'}))
		  && ($sparam{'ACT'} eq 'comment'))
		{
			# Basic check
			if ((defined($sparam{'GAL'}))
			 && (defined($sparam{'WRK'})))
			{	
				# Verify for operation
				if ((defined($sparam{'POS'}))
				 && ($sparam{'POS'} =~ /^\d+$/))
				{
					# Remove comment
					CommentRemove($database, $login, $cgi, $param, $tmpl, \%sparam, \%gal);
				}
	
				elsif ((defined($sparam{'POS'}))
				 && ($sparam{'POS'} eq 'new'))
				{
					# Create comments
					CommentWrite($database, $login, $cgi, $param, $tmpl, \%sparam, \%gal);
				}
				else
				{
					# Display comments
					CommentDisplay($database, $login, $cgi, $param, $tmpl, \%sparam, \%gal);	
				}
			}

			# Invalid entry	
			else
			{
				# Display appropriate error message
				$tmpl->Show('TmplMsg', MMESSAGE=>$gal{'MsgParam'});
			}
		}

		# Work search
		elsif ((defined($sparam{'ACT'}))
		  && ($sparam{'ACT'} eq 'search'))
		{
			# Simply one window
			WorkSearch($database, $login, $cgi, $param, $tmpl, \%sparam, \%gal);
		}

		# Display behaviour
		else
		{
			# List galleries
			GalleryList($database, $login, $cgi, $param, $tmpl, \%sparam, \%gal);
		}
	
		# Document footer
		print $database->DocumentGetGalleryFooter(), "\n";
	}


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

#####################
# Check Authorization
#
# In certain causes, realms will choose to carry an adult rating.  In such a
# case, there is a need to prevent people from using the system when such
# a circumstance takes place.

sub CheckAuth
{
	#####################
	# Data members
	my $database = shift;					# Database handler
	my $login    = shift;					# Authenthication handler
	my $param    = shift;					# Paramater handler
	my $sparam   = shift;					# Parameter line hash
	my $tmpl     = shift;					# Template handle
	my $gal      = shift;					# Gallery hash
	my $choice   = shift;					# Choice of the matter

	my $rating;						# Rating to realm
	my $verify;						# Verified access


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

	# Retreive information
	($rating) = $database->DataGet("SELECT GalleryRating
		FROM Gallery
		WHERE GalleryID=?",

	 $sparam->{'GAL'}
	);


	# Reasons to check
	if ((defined($param->{'USER'}))
	 || ($rating eq $gal->{'SetRestricted'})
	 || (defined($choice)))
	{
		# Authenthicate
		if ($login->GetVerificationNormal())
		{
			# More checks
			if ($rating eq $gal->{'SetRestricted'})
			{
				# Retreive verification
				($verify) = $database->DataGetVerified($param->{'USER'});

				# Invalid
				if ($verify eq 'no')
				{
					# Display error
					$tmpl->Show('TmplMsg', MMESSAGE=>$gal->{'MsgRestricted'});
					# Flase
					return 0;
				}
			}

			# Return true
			return 1;
		}


		# Return false
		return 0;
	} 

	# Always return true
	return 1;
}


#####################
# Comment Display
#
# If one has the capability of composing messages, then one will also need to
# be able to view them.   This is a general, all purpose method that allows
# for attached and semi-independant use.

sub CommentDisplay
{
	#####################
	# Data members
	my $database = shift;					# Database handler
	my $login    = shift;					# Authenthication handler
	my $cgi      = shift;					# CGI interface
	my $param    = shift;					# Paramater handler
	my $tmpl     = shift;					# Template handler
	my $sparam   = shift;					# Parameter line hash
	my $gal      = shift;					# Gallery hash
	my $limit    = shift;					# Retreival limit

	my $res;						# Results
	my $rsth;						# Query handle

	my $comment;						# Comment settings
	my $name;						# Name of work
	my $access;						# Username

	my $rate;						# Rating
	my $vote;						# Votes

	my $ibio;						# Biographical line
	my $idelete;						# Delete link
	my $inline;						# Inline parameters
	my $uri;						# Secondary name
	my $url;						# Self referencing link


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

	# Make sure values are sane
	if (($sparam->{'WRK'} =~ /^\d+$/)
	 && ($sparam->{'GAL'} =~ /^\d+$/))
	{
		# Things are in order
		($name) = $database->DataGetWorkName($sparam->{'WRK'}, $sparam->{'GAL'});

		# Continue as planned
		if (defined($name))
		{
			# Force authenthication reguardless
			return 0 unless (CheckAuth($database, $login, $param, $sparam, $tmpl, $gal));

			# Retreive values
			($comment) = $database->DataGetGalleryComment($sparam->{'GAL'});
			($access)  = $database->DataGetGalleryJanitor($sparam->{'GAL'}, $param->{'USER'}) if (defined($param->{'USER'}));


			# Only display if needed
			# Comments
			if (($comment eq 'comment')
			 || ($comment eq 'combo'))
			{
				# Determine limit
				$limit = (defined($limit)) ? "LIMIT $limit" : "";


				# Prepare query
				# Set handle if need be
				$database->Pull(\$rsth, "SELECT
					  CommentID      AS \"CommentID\",
					  PuppetName     AS \"PuppetName\",
					  PuppeteerLogin AS \"PuppeteerLogin\",
					  CommentPost    AS \"CommentPost\",
					  getDate(CommentTimestamp) AS \"CommentTimestamp\"
					FROM Comment
					WHERE WorkID=?
					  AND GalleryID=?
					  AND CommentPost IS NOT NULL
					ORDER BY CommentTimestamp DESC $limit",

				 $sparam->{'WRK'},
				 $sparam->{'GAL'}
				);


				# Self referencing links
				# Capture link
				$url = $cgi->url(-absolute=>1);


				# Inline parameters general
				$inline = (defined($param->{'USER'}))
				 ? $param->EmbedInline(USER=>$param->{'USER'}, CRYPT=>$param->{'CRYPT'})
				 : "";

				# Generate appropriate
				# Standard
				if ($sparam->{'WRK'} > 1)
				{
					# Work based on specific works
					$uri = $url . '/view/' . $sparam->{'GAL'} . '/' . $sparam->{'WRK'};
					$url = $url . '/comment/' . $sparam->{'GAL'} . '/' . $sparam->{'WRK'};
				}

				# Higher level comments
				else
				{
					# Work based on gallery or news
					$uri = $url . '/view/' . $sparam->{'GAL'};
					$url = $url . '/comment/' . $sparam->{'GAL'} . '/' . $sparam->{'WRK'};
				}

				# Display title
				$tmpl->Show('TmplComments', MNAME =>$name);
	
				# Initial acid test
				if ($res = $rsth->fetchrow_hashref())
				{
					# Display table header
				 	$tmpl->Show('TmplTblTop');	


					# First row
					# Generate link
					$ibio = (defined($param->{'USER'}))
					 ? $param->EmbedInline(
						 USER  => $param->{'USER'},
						 CRYPT => $param->{'CRYPT'},
						 BCHAR => $res->{'PuppetName'},
						 BPOOL => 'system')
					 : $param->Crypt(
						 BCHAR => $res->{'PuppetName'},
						 BPOOL => 'system');

					# Deletion handle
					$idelete = ((defined($param->{'USER'})) && (defined($access)))
					  ? "<A HREF=\"$url/$res->{CommentID}$inline\"><B>[$gal->{TxtDelete}]</B></A>"
					  : "";

					# First row
					$tmpl->Show('TmplTblMid',
						MCMNT => $res->{'CommentPost'},
						MDATE => $res->{'CommentTimestamp'},
						MDELE => $idelete,
						MFROM => $res->{'PuppetName'},
						MLINK => $gal->{'LnkBio'} . $ibio
					 );

					# Second and so forth
					# Loop and retrieve
					while ($res = $rsth->fetchrow_hashref())
					{
						# Biographical link
						$ibio = (defined($param->{'USER'}))
						 ? $param->EmbedInline(
							 USER  => $param->{'USER'},
							 CRYPT => $param->{'CRYPT'},
							 BCHAR => $res->{'PuppetName'},
							 BPOOL => 'system')
							 : $param->Crypt(
							 BCHAR => $res->{'PuppetName'},
							 BPOOL => 'system');

						# Deletion handle
						$idelete = ((defined($param->{'USER'})) && (defined($access)))
						  ? "<A HREF=\"$url/$res->{CommentID}$inline\"><B>[$gal->{TxtDelete}]</B></A>"
						  : "";
	
						# Subsequent rows
						$tmpl->Show('TmplTblMid',
							MCMNT => $res->{'CommentPost'},
							MDATE => $res->{'CommentTimestamp'},
							MDELE => $idelete,
							MFROM => $res->{'PuppetName'},
							MLINK => "$gal->{LnkBio}$ibio"
						 );
					}

					# Close table
				 	$tmpl->Show('TmplTblBottom');	
				}
	
				# Display options
				print "<DIV ALIGN=\"CENTER\">";
				print "<A HREF=\"$url$inline\">$gal->{'TagNavComplete'}</A> " if ($limit =~ /^LIMIT/);
				print "<A HREF=\"$uri$inline\">$gal->{'TagNavReturn'}</A> "  if ($limit !~ /^LIMIT/);
				print "<A HREF=\"$url/new$inline\">$gal->{'TagNavComment'}</A> " if ($sparam->{'WRK'} != 1);
				print "</DIV>";

				# Finish
				$rsth->finish();
			}

			# Rating
			if (($comment eq 'rating')
			 || ($comment eq 'combo'))
			{
				# Retrieve values
				($rate, $vote) = $database->DataGetWorkAvg($sparam->{'GAL'}, $sparam->{'WRK'});
	
				# Display
				$tmpl->Show('TmplVotes',
					MITENAME => $name,
					MOLVOTES => $vote,
					MRATINGS => $rate
				 );
			}
		}
	}
}


#####################
# Comment Remove
#
# In order to cope with double posts, annoying messages or things that the artists
# simply cannot agree upon, the comments can be managed by owner.

sub CommentRemove
{
	#####################
	# Data members
	my $database = shift;					# Database handler
	my $login    = shift;					# Authenthication handler
	my $cgi      = shift;					# CGI interface
	my $param    = shift;					# Paramater handler
	my $tmpl     = shift;					# Template handler
	my $sparam   = shift;					# Parameter line hash
	my $gal      = shift;					# Gallery hash

	my $comment;						# Comment settings
	my $access;						# Access privs


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

	# Force authenthication reguardless
	return 0 unless (CheckAuth($database, $login, $param, $sparam, $tmpl, $gal, 'true'));

	# Retreive values
	($comment) = $database->DataGetGalleryComment($sparam->{'GAL'});
	($access)  = $database->DataGetGalleryJanitor($sparam->{'GAL'}, $param->{'USER'}) if (defined($param->{'USER'}));

	# Only display if needed
	if (($comment eq 'comment')
	 || ($comment eq 'combo'))
	{
		# User matches
		if (defined($access))
		{
			# Remove entry
			$database->Quick("DELETE FROM Comment
				WHERE CommentID=?
				  AND WorkID=?
				  AND GalleryID=?",

			  $sparam->{'POS'},
			  $sparam->{'WRK'},
			  $sparam->{'GAL'}
			);
		}
	}

	# Give them light
	if ($sparam->{'WRK'} > 1)
	{
		# Standard entrance
		CommentDisplay($database, $login, $cgi, $param, $tmpl, $sparam, $gal);
	}

	else
	{
		# Pass only what is necessary
		%{$sparam} = (GAL=>$sparam->{'GAL'});
						
		# Global and news items
		GalleryDisplay($database, $login, $cgi, $param, $tmpl, $sparam, $gal);
	}
}


#####################
# Comment Write
#
# Part of the system, as to allow for rating and commenting of works themselves. 

sub CommentWrite
{
	#####################
	# Data members
	my $database = shift;					# Database handler
	my $login    = shift;					# Authenthication handler
	my $cgi      = shift;					# CGI interface
	my $param    = shift;					# Paramater handler
	my $tmpl     = shift;					# Template handler
	my $sparam   = shift;					# Parameter line hash
	my $gal      = shift;					# Gallery hash
	
	my $filter;						# Filter handler
	my $send;						# Mail sender
	my $table;						# Table handler

	my $comment;						# Comment settings
	my $name;						# Name of work
	my $puppet;						# Puppet default

	my $mail;						# Mail comment
	my $post;						# Post
	my $rate;						# Rating

	my $wpuppet;						# Puppet popup
	my $wrating;						# Puppet popup
	my $wsubmit;						# Post submit

	my @puppet;						# List of puppets
	my @rating   =  qw(1 2 3 4 5 6 7 8 9 10);		# Rating scale

	my %system;						# System hash


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

	# Things are in order
	($name) = $database->DataGetWorkName($sparam->{'WRK'}, $sparam->{'GAL'});

	# Continue as planned
	if (defined($name))
	{
		# Force authenthication reguardless
		return 0 unless (CheckAuth($database, $login, $param, $sparam, $tmpl, $gal, 'true'));

		# Retreive gallery comment
		($comment) = $database->DataGetGalleryComment($sparam->{'GAL'});


		# Proceed with comment generation
		if ($comment ne 'none')
		{
			# Write post
			if (defined($param->{'POST'}))
			{
				# Evaluate change
				eval
				{
					# Post handling
					if (($comment eq 'comment')
					 || ($comment eq 'combo'))
					{
						# Create instance
						$filter = new Ethereal::Filter();

						# Safety
						$post = $filter->StripHTML($param->{'CPOST'});
						$mail = $post;

						# Verify
						if (length($post) > 3)
						{
							# Post handling
							$post =~ s/$/\n/;

							# Truncate Length
							$post =  substr(DBI::neat($post, 1026), 1, -1);


							# Hyperlinks
							$post =~ s/(http:\/\/.*?)(\s|$)/<A HREF="$1" TARGET="_blank">$gal->{TagHyperlink}<\/A> /gm;
							$post =~ s/([\w\.\-]*?\@[\w\.\-]*?\..*?)(\s|$)/<A HREF="mailto:$1">$gal->{TagEmail}<\/A> /gm;


							# Human readability
							$post =~ s/\n/<BR>/gs;
						}
						else
						{
							# Undefine as to not see
							$post = undef;
						}
					}

					# Basic ranting handling
					$rate = ((defined($param->{'CRATING'})) && ($param->{'CRATING'} =~ /^\d+$/))
					 ? $param->{'CRATING'}
					 : 0;
				

					# Insert row
					$database->Write("INSERT INTO Comment
						(WorkID,
						 GalleryID,
						 PuppetName,
						 PuppeteerLogin,
						 CommentPost,
						 CommentRating)
						VALUES(?,?,?,?,?,?)",

					 $sparam->{'WRK'},
					 $sparam->{'GAL'},
					 $param->{'CPUPPET'},
					 $param->{'USER'},
					 $post,
					 $rate);
				 
				
					# Rating update
					if (($comment eq 'rate')
					 || ($comment eq 'combo'))
					{
						# Calculate average
						($rate) = $database->DataGetWorkAvg($sparam->{'GAL'}, $sparam->{'WRK'});


						# Update
						$database->Write("UPDATE Work
							SET WorkRating=?
							WHERE WorkID=?
							  AND GalleryID=?",

						 $rate,
						 $sparam->{'WRK'},
						 $sparam->{'GAL'});
					}

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


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

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

				else
				{
					# Mail notification to user
					if ((defined($mail))
				  	 && (length($mail) > 3))
					{
						# Postage handling
						# Link with hash
						$database->GetHashSystem(\%system);

						# Retreive notice message and contact
						my ($notice) = $database->DocumentGetGalleryNotice();
						my ($user)   = $database->DataGetGalleryOwner($sparam->{'GAL'});

						# Full contact
						my ($email)  = $database->DataGetPuppeteerEmail($user);
						my ($fname)  = $database->DataGetPuppeteerName($user);

						# Create instance
						$send = tie(*MAIL, 'Ethereal::Mail', $database);

						# Initial setup
						$send->SetSubject($system{'SetGalleryPrefix'} . $name);

						# Search and replace
						$send->SetSearch(
							MDATE => $database->DataGetDate(),
							MFROM => $param->{'CPUPPET'},
							MMESG => $mail,
							MMAIL => "$system{SetInfoContactName}\n$system{SetInfoContactAddress}",
							MWORK => $name
							 );

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

						# Add message
						print MAIL $notice;

						# Close and send
						close(MAIL);
					}

					# Comment display
					if ($sparam->{'WRK'} > 1)
					{
						# Normal works
						WorkDisplay($database, $login, $cgi, $param, $tmpl, $sparam, $gal);
					}

					# Return to gallery
					else
					{
						# Pass only what is necessary
						%{$sparam} = (GAL=>$sparam->{'GAL'});
						
						# Global and news items
						GalleryDisplay($database, $login, $cgi, $param, $tmpl, $sparam, $gal);
					}
				}
			}


			# Display form
			else
			{
				# Create instance
				$table = new Ethereal::Table($cgi);


				# Puppet names
				$database->GetListPuppetYours(\@puppet, $param->{'USER'});

				# Puppet default
				($puppet) = $database->DataGetDefault($param->{'USER'});


				# Append abstaining
				unshift(@rating, $gal->{'TxtAbstain'});


				# Generate widget
				$wpuppet = $cgi->popup_menu('CPUPPET', \@puppet, $puppet);
				$wrating = $cgi->popup_menu('CRATING', \@rating, $rating[0]);

				# Submit button
				$wsubmit = ($comment eq 'rating') ? $gal->{'TxtRate'} : $gal->{'TxtPost'};
	

				# Title
				$tmpl->ShowMain($gal->{'TagPostTitle'} . $name);
			

				# Display information
				# Form
				print $cgi->start_form();
				print $param->EmbedNormal($param->Flat(), POST=>'True');

				# Table creation
				$table->MakeTop();

				# Puppet selection
				$table->MakeValid(
					$tmpl->PassSub($gal->{'TagPostPuppet'}),
					$wpuppet
				 );

				# Rating indicator
				if (($comment eq 'rating')
				 || ($comment eq 'combo'))
				{
					# Rating widget and title
					$table->MakeValid(
						$tmpl->PassSub($gal->{'TagPostRating'}),
						$wrating
					 );
				}
				
				# Comments
				if (($comment eq 'comment')
				 || ($comment eq 'combo'))
				{
					# Spacer
					$table->MakeBlank();

					# Widget and Title
					$table->MakeSingle(
						$tmpl->PassTitle($gal->{'TagPostComment'})
					 );
					$table->MakeSingle(
						$cgi->textarea(
							-name    => 'CPOST',
							-rows    => 3,
							-columns => 75,
							-wrap    => 'SOFT')
					 );
				}
				
				# Spacer
				$table->MakeBlank();

				# Display submit
				$table->MakeSingle($cgi->submit($wsubmit));


				# End of table
				$table->MakeBottom();

				# Form
				print $cgi->end_form(), "\n";
			}
		}

		# Warn author has disabled comments
		else
		{
			# Warn users comments are disabled
			$tmpl->Show('TmplMsg', MMESSAGE=>$gal->{'MsgComment'});	
		}
	}

	# Invalid entry	
	else
	{
		# Warn that things are off
		$tmpl->Show('TmplMsg', MMESSAGE=>$gal{'MsgParam'});
	}
}


#####################
# Gallery Display
#
# Gallery Display will allow casual viewers to see an overview of every
# work available.   This includes a naivgational system, and an overview of
# the more recent comments made on works.

sub GalleryDisplay
{
	#####################
	# Data members
	my $database = shift;					# Database handler
	my $login    = shift;					# Authenthication handler
	my $cgi      = shift;					# CGI interface
	my $param    = shift;					# Paramater handler
	my $tmpl     = shift;					# Template handler
	my $sparam   = shift;					# Parameter line hash
	my $gal      = shift;					# Gallery hash

	my $filter;						# Filtering options
	my $option;						# Option handler
	
	my $gsth;						# Gallery query
	my $lsth;						# Link query
	my $nsth;						# News query
	my $wsth;						# Works query
	my $gres;						# Information on gallery
	my $lres;						# Link results page
	my $nres;						# Information on news items
	my $wres;						# Information on works

	my $check;						# Check value
	my $descr;						# Description
	my $genres;						# Formatted genres
	my $length;						# Text length
	my $rating;						# Rating

	my $expand;						# Expansion handling
	my $modulus;						# Remainder of values
	my $newness;						# Newness factor
	my $offset;						# Where to start from
	my $order;						# Searching order
	my $page;						# Navigational page
	my $pos;						# Position
	my $publish;						# Publishing wizard
	my $size;						# Size of array
	my $total;						# Entries to look out for
	my $user;						# Username of owner
	my $watch;						# Watch gallery

	my $inline;						# Inline parameters
	my $idelete;						# Deletion and removal
	my $iwatch;						# Puppet watch option
	my $uri;						# Direct link
	my $url;						# Self referencing link

	my @genres;						# Multiple genres
	
	my %pass;						# Temporary passthrough for parameters
	my %system;						# System hash


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

	# Things are proper
	return 0 unless (CheckAuth($database, $login, $param, $sparam, $tmpl, $gal));


	# Gallery information
	$database->Pull(\$gsth, "SELECT
		  PuppeteerLogin     AS \"PuppeteerLogin\",
		  GalleryName        AS \"GalleryName\",
		  GalleryRating      AS \"GalleryRating\",
		  GalleryDescription AS \"GalleryDescription\",
		  GallerySort        AS \"GallerySort\",
		  GalleryType        AS \"GalleryType\",
		  GalleryComments    AS \"GalleryComments\",
		  GalleryDisplay     AS \"GalleryDisplay\",
		  getDate(GalleryCreated) AS \"GalleryCreated\",
		  getDate(GalleryUpdated) AS \"GalleryUpdated\"
		FROM Gallery
		WHERE GalleryID=?",

	 $sparam->{'GAL'}
	);
	
	
	# Retreive
	if ($gres = $gsth->fetchrow_hashref())
	{
		# New instance
		$option = new Ethereal::Option();


		# Assign username
		$user = $gres->{'PuppeteerLogin'};

		
		# Genre searching
		$database->GetListEstablished(\@genres, $sparam->{'GAL'});

		# Format genre line
		$genres = join("/", @genres);

		
		# Ordering type
		$order = $option->GallerySortReal($gres->{'GallerySort'});


		# Linking
		# Fetch link
		$uri = $cgi->url(-absolute=>1);
		$url = $cgi->url(-absolute=>1) . '/view/' . $sparam->{'GAL'};

		# Inline parameters general
		$inline  = (defined($param->{'USER'}))
		 ? $param->EmbedInline(USER=>$param->{'USER'}, CRYPT=>$param->{'CRYPT'})
		 : '';

		# Starting point
		$sparam->{'POS'} = (defined($sparam->{'POS'}))
		  ? $sparam->{'POS'} 
		  : 1;	

		# Check
		$check = 1;

		# Offset
		$offset = ($sparam->{'POS'} - 1) * $gres->{'GalleryDisplay'};


		# Watchful processing
		if (defined($param->{'USER'}))
		{
			# Determine watch
			($watch) = $database->DataGetGalleryWatch($sparam->{'GAL'}, $param->{'USER'});


			# Toggle watch
			if (defined($param->{'WATCH'}))
			{
				# Change standing behaviour
				if (defined($watch))
				{
					# Remove database entry
					$database->Quick("DELETE FROM GalleryNotify
						WHERE GalleryID=?
						  AND PuppeteerLogin=?",
				  
					 $sparam->{'GAL'}, 
					 $param->{'USER'}
				 	);

					# Change logic
					$watch = undef;
				}

				else
				{
					# Create entry
					$database->Quick("INSERT INTO GalleryNotify
						(GalleryID,
						 PuppeteerLogin)
						VALUES (?,?)",
				  
					 $sparam->{'GAL'}, 
					 $param->{'USER'}
					);

					# Change logic
					$watch = $param->{'USER'};
				}

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

			# Generate inline
			$iwatch = $param->EmbedInline(
				USER  => $param->{'USER'},
				CRYPT => $param->{'CRYPT'},
				WATCH => 'True'
			 );

			# Change langauge
			$watch  = (defined($watch)) 
			 ? $tmpl->Pass('TagGalIgnore', MLINK=>"$url$iwatch")
			 : $tmpl->Pass('TagGalWatch',  MLINK=>"$url$iwatch");
		}

		# Non-registered users
		else
		{
			# Generate widget
			$watch  = $tmpl->Pass('TagGalWatch', MLINK=>"$url\?USER\=\&WATCH\=True");
		}


		# Printing
		$publish = (-f "$gal->{SetRoot}/$sparam->{GAL}/novel.pdf")
	       	 ? $tmpl->Pass('TagGalView', MLINK=>"$gal->{SetUrl}/$sparam->{GAL}/novel.pdf")
		 : "";

			

		# Information display
		# Intro and summary
		$tmpl->Show('TmplIntro',
			MDESCR   => $gres->{'GalleryDescription'},
			MGENRE   => $genres,
			MMAIN    => $uri . '/list/' . ucfirst($gres->{'GalleryType'}) . '/' . $genres[0] . $inline,
			MPUBLISH => $publish,
			MPUPPET  => PenNames($database, $param, $tmpl, $gal, $sparam->{'GAL'}),
			MRATING  => $gres->{'GalleryRating'},
			MTITLE   => $gres->{'GalleryName'},
			MCREATED => $gres->{'GalleryCreated'},
			MUPDATED => $gres->{'GalleryUpdated'},
			MWATCH   => $watch
		 );


		# Image galleries
		if ($gres->{'GalleryType'} eq 'image')
		{
			# Link with hash
			$database->GetHashSystem(\%system);

			# Work information
			# Amount of works
			($total) = $database->DataGet("SELECT COUNT(*)
				FROM Work
				WHERE GalleryID=?
				  AND WorkImage IS NOT NULL",

			 $sparam->{'GAL'}
			);

			# Capture nav line
			$page = Navigation($tmpl, $gal, 'TagNav', $gres->{'GalleryDisplay'}, $total, $sparam->{'POS'}, $url . '/', $inline);

			
			# Proper entries
			$database->Pull(\$wsth, "SELECT
				  WorkID             AS \"WorkID\",
				  WorkName           AS \"WorkName\",
				  WorkDescription    AS \"WorkDescription\",
				  WorkRating         AS \"WorkRating\",
				  WorkImageThumbnail AS \"WorkImageThumbnail\",
				  WorkImageHeight    AS \"WorkImageHeight\",
				  WorkImageWidth     AS \"WorkImageWidth\",
				  WorkImageSize      AS \"WorkImageSize\",
				  getDate(WorkTimestamp) AS \"WorkTimestamp\",
				  getNew(WorkTimestamp)  AS \"WorkNewness\"
				FROM Work
				WHERE GalleryID=?
				  AND WorkImage IS NOT NULL
				ORDER BY \"WorkNewness\" DESC, $order
				LIMIT $gres->{GalleryDisplay}
				OFFSET $offset",

			 $sparam->{'GAL'}
			);
			
			# Test the waters
			if ($wres = $wsth->fetchrow_hashref())
			{
				# Create table information if need be
				print "<TABLE WIDTH=\"100%\">\n <TR>\n  <TD VALIGN=\"TOP\">\n" if ($sparam->{'POS'} == 1);

				# Display entry
				$tmpl->Show('TagNavBar', 
					MNAV   => $page,
				       	MCOUNT => $total,
				       	MENTRY => $gres->{'GalleryDisplay'}
				 ) unless ($total <= $gres->{'GalleryDisplay'});


				# Cycle through
				do
				{
					# Newness tag
					$newness = ($wres->{'WorkNewness'} != 0) ? $gal->{'TagNewness'} : '';
					
					# If need exists
					if (($gres->{'GalleryComments'} eq 'comment')
					 || ($gres->{'GalleryComments'} eq 'combo'))
					{
						# Caculate
						$rating = $wres->{'WorkRating'};
					}
					else
					{
						# Assign nil
						$rating = $gal->{'SetNil'};
					}


					# Display
					$tmpl->Show('TmplDisImage',
				 		MNAME    => $wres->{'WorkName'},
						MDESCR   => $wres->{'WorkDescription'},
						MHEIGHT  => $wres->{'WorkImageHeight'},
						MIMAGE   => $wres->{'WorkImageThumbnail'},
						MLINK    => $url . '/' . $wres->{'WorkID'} . $inline,
						MRATING  => $rating,
						MSIZE    => $wres->{'WorkImageSize'},
						MTHUMB   => $system{'SetThumbnail'},
						MWIDTH   => $wres->{'WorkImageWidth'},
						MUPDATED => $wres->{'WorkTimestamp'},
						MNEWNESS => $newness
					);

				} while ($wres = $wsth->fetchrow_hashref());
			}
		}

		# Text based galleries
		elsif ($gres->{'GalleryType'} eq 'text')
		{
			# Work information
			# Calculate totals
			($total) = $database->DataGet("SELECT COUNT(*)
				FROM Work
				WHERE GalleryID=?
				  AND WorkText IS NOT NULL",

			 $sparam->{'GAL'}
			);
			
			# Capture nav line
			$page = Navigation($tmpl, $gal, 'TagNav', $gres->{'GalleryDisplay'}, $total, $sparam->{'POS'}, $url . '/', $inline);

			# Detailed information
			$database->Pull(\$wsth, "SELECT
				  WorkID          AS \"WorkID\",
				  WorkName        AS \"WorkName\",
				  WorkDescription AS \"WorkDescription\",
				  WorkRating      AS \"WorkRating\",
				  WorkText        AS \"WorkText\",
				  getDate(WorkTimestamp) AS \"WorkTimestamp\",
				  getNew(WorkTimestamp)  AS \"WorkNewness\"
				FROM Work
				WHERE GalleryID=?
				  AND WorkText IS NOT NULL
				ORDER BY \"WorkNewness\" DESC, $order
				LIMIT $gres->{GalleryDisplay}
				OFFSET $offset",

			 $sparam->{'GAL'}
			);


			# Safety check
			if ($wres = $wsth->fetchrow_hashref())
			{
				# Create instance
				$filter = new Ethereal::Filter();


				# Create table information if need be
				print "<TABLE WIDTH=\"100%\">\n <TR>\n  <TD VALIGN=\"TOP\">\n" if ($sparam->{'POS'} == 1);

				# Display entry
				$tmpl->Show('TagNavBar', 
					MNAV   => $page,
				       	MCOUNT => $total,
				       	MENTRY => $gres->{'GalleryDisplay'}
				 ) unless ($total <= $gres->{'GalleryDisplay'});


				# Continue through
				do
				{
					# Newness tag
					$newness = ($wres->{'WorkNewness'} != 0) ? $gal->{'TagNewness'} : '';

					# If need exists
					if (($gres->{'GalleryComments'} eq 'comment')
					 || ($gres->{'GalleryComments'} eq 'combo'))
					{
						# Calculate
						$rating = $wres->{'WorkRating'};
					}
					else
					{
						# Assign nil
						$rating = $gal->{'SetNil'};
					}

					# Description
					$descr  = $wres->{'WorkDescription'};
					$descr  = (defined($descr))
				       	  ? $descr
				       	  : $filter->StripHTML(substr(DBI::neat($wres->{'WorkText'}, 252), 1, -1));

					# Length
					$length = length($wres->{'WorkText'});


					# Display
					$tmpl->Show('TmplDisText',
				 		MNAME    => $wres->{'WorkName'},
						MDESCR   => $descr,
						MLENGTH  => format_number($length, 0),
						MLINK    => $url . '/' . $wres->{'WorkID'} . $inline,
						MRATING  => $rating,
						MUPDATED => $wres->{'WorkTimestamp'},
						MNEWNESS => $newness
					);
				} while ($wres = $wsth->fetchrow_hashref());
			}
		}

		# Lower navigational bar
		if (($gres->{'GalleryType'} eq 'image')
		 || ($gres->{'GalleryType'} eq 'text'))
		{
			# Display entry
			$tmpl->Show('TagNavBar', 
				MNAV   => $page,
			       	MCOUNT => $total,
			       	MENTRY => $gres->{'GalleryDisplay'}
			 ) unless ($total <= $gres->{'GalleryDisplay'});
		}


		# Live Journal and Web Gallery
		if (($gres->{'GalleryType'} eq 'comic')
		 || ($gres->{'GalleryType'} eq 'journal'))
	 	{
			# Specific data members
			my $count;						# Counter
			my $curr;						# Specific month
			my $cday;						# Current day
			my $next;						# Month after
			my $futr;						# Future year
			my $wday;						# Week day	
			my $week;						# Selected week
			my $year;						# Specific year

			my @cal;						# Current calendar
			my @months;						# Available months
			my @nice;						# Nice month formatting
			my @wdays;						# Weekdays
			my @years;						# Available years

			
			# Create instances
			$option = new Ethereal::Option();

			# Split months and weekdays
			$option->Split(\@nice,  $gal->{'OptMonths'});
			$option->Split(\@wdays, $gal->{'OptWeekdays'});

			# Pull list of years
			$database->GetList(\@years, "SELECT
				 EXTRACT(YEAR FROM WorkTimestamp) AS \"Year\"
				FROM Work
				WHERE GalleryID=?
				  AND (WorkText IS NOT NULL
				   OR WorkImage IS NOT NULL)
				GROUP BY \"Year\"
				ORDER BY \"Year\"",
			       
			 $sparam->{'GAL'}
		 	);

			# Safety
			$sparam->{'POS'} = ((defined($sparam->{'POS'})) && ($sparam->{'POS'} =~ /^20\d\d$/))
		       		? $sparam->{'POS'}
			       	: $years[-1];
			

			# Assignment of year and check
			$year  = $sparam->{'POS'};
			$check = $sparam->{'POS'};
			
			# Pull list of months for year
			$database->GetList(\@months, "SELECT
				 EXTRACT(MONTH FROM WorkTimestamp) AS \"Month\"
				FROM Work
				WHERE GalleryID=?
				  AND (WorkText IS NOT NULL
				   OR WorkImage IS NOT NULL)
				  AND WorkTimestamp >= '$year-01-01 00:00'::TIMESTAMP WITHOUT TIME ZONE
				  AND WorkTimestamp <= '$year-12-31 23:59'::TIMESTAMP WITHOUT TIME ZONE
				GROUP BY \"Month\"
				ORDER BY \"Month\"", 

			 $sparam->{'GAL'}
		 	);


			# Display available years
			# Center material
			print "<CENTER>";

			# Loop
			foreach $year (@years)
			{
				# Which to display
				if ($year == $sparam->{'POS'})
				{
					# Show years
					$tmpl->Show('TagSelected', 
						MLINK => "$url/p/$year$inline",
						MNAME => $year
					 );
				}

				# Selected
				else
				{
					# Show years
					$tmpl->Show('TagTabbed', 
						MLINK => "$url/p/$year$inline",
						MNAME => $year
					 );
				}
			}

			# Close off code
			print "</CENTER>\n<BR>\n<TABLE WIDTH=\"100%\">";


			# Count set
			$count=0;

			# Display calendars
			foreach $curr (@months)
			{
				# Increment
				$count++;

				# Reset if necessary
				if ($count > 3)
				{
					# Reset counter
					$count = 1;
					
					# Close of row
					print "  </TD>\n </TR>\n <TR>\n  <TD COLSPAN=\"3\" CLASS\"CALSPCE\">&nbsp;</TD>\n </TR>\n";
				}

				# Multiple row formatting
				# Initial row
				if ($count == 1)
				{ print " <TR>\n  <TD>"; }

				# Second row
				elsif ($count == 2)
				{ print "  </TD>\n  <TD>"; }
				
				# Third and final
				elsif ($count == 3)
				{ print "  </TD>\n  <TD>"; }
				

				# Pull month
				@cal = calendar($curr, $year); 

				
				# Month calculations
				$next += $curr + 1;

				# Safety
				# Month out of bounds
				if ($next > 12)
				{
					# Set to the next year
					$next = 1;
					$futr = $year + 1;
				}

				# Month within limits
				else
				{
					# Set to match
					$futr = $year;
				}

				
				# Work information
				# Prepare and execute
				$database->Pull(\$wsth, "SELECT
					  WorkID          AS \"WorkID\",
					  EXTRACT(DAY FROM WorkTimestamp)   AS \"WorkDay\"
					FROM Work
					WHERE GalleryID=?
					  AND (WorkText IS NOT NULL
					   OR WorkImage IS NOT NULL)
				  	  AND WorkTimestamp >= '$year-$curr-01 00:00'::TIMESTAMP WITHOUT TIME ZONE
				  	  AND WorkTimestamp <= '$futr-$next-01 00:00'::TIMESTAMP WITHOUT TIME ZONE
					ORDER BY WorkTimestamp",

				 $sparam->{'GAL'}
				);
		

				# Start table
				print "   <TABLE CLASS=\"CAL\">\n    <TR>\n     <TD CLASS=\"CALMON\" COLSPAN=\"7\">$nice[$curr-1]</TD>\n </TR>\n";
				print "    <TR>\n";
				
				# Weekdays
				for ($wday=0; $wday < 7; $wday++)
				{
					# Display month header
					print "     <TD CLASS=\"CALWDAY\">$wdays[$wday]</TD>\n";
					
				}

				# Close row
				print "    </TR>\n";

				
				# Loop to retrieve all values
				if ($wres = $wsth->fetchrow_hashref())
				{
					# Weekdays
					for ($week=0; $week < 6; $week++)
					{
						# Security
						last unless (defined($cal[$week]));


						# Start row
						print "    <TR>\n";

						
						# Scroll through dates
						for ($cday=0; $cday < 7; $cday++)
						{
							# Blank entries
							unless (defined($cal[$week]->[$cday]))
							{
								# Blanks
								print "     <TD CLASS=\"CALBLNK\">&nbsp;</TD>\n"; 
							}

							# Valid gallery entry
							elsif ((defined($cal[$week]->[$cday]))
							  && ($cal[$week]->[$cday] == $wres->{'WorkDay'}))
						  	{
								# Print row
								print "     <TD CLASS=\"CALACTV\"><A HREF=\"$url/$wres->{WorkID}$inline\">$cal[$week]->[$cday]</TD>\n"; 

								# Next entry
								$wres = $wsth->fetchrow_hashref();
							}

							else
							{
								# Basic display
								print "     <TD CLASS=\"CALNORM\">$cal[$week]->[$cday]</TD>\n"; 
							}
						}

						# End row
						print "    </TR>\n";
					}
				}

				# Close table
				print "   </TABLE>\n";
			}

			# Table closure
			# Second row
			if ($count == 1)
			{ print "  </TD>\n   <TD COLSPAN=\"2\">&nbsp;</TD>\n </TR>\n</TABLE>"; }

			# Third row
			elsif ($count == 2)
			{ print "  </TD>\n   <TD>&nbsp;</TD>\n </TR>\n</TABLE>"; }

			# Simple close
			else
			{ print " </TR>\n</TABLE>"; }
		}


		# End query
		$wsth->finish() if (defined($wsth));
		$gsth->finish();


		# Additional features
		if ($sparam->{'POS'} == $check)
		{
			# Gallery Comments
			# Passing of temporary values
			%pass = (%{$sparam}, WRK=>0); 

			# List comments
			CommentDisplay($database, $login, $cgi, $param, $tmpl, \%pass, $gal, $gal->{'SetRetrieve'});


			# Next column please
			print "  </TD>\n  <TD VALIGN=\"TOP\">\n";


			# News Items
			# Expansion handling
			$expand = (defined($param->{'EXPAND'})) ? '' : 'LIMIT 5';
			
			# Prepare query
			$database->Pull(\$nsth, "SELECT
				 CommentID		   AS \"CommentID\",
				 CommentPost               AS \"CommentPost\",
				 getDate(CommentTimestamp) AS \"CommentTimestamp\"
				FROM Comment
				WHERE GalleryID=?
				  AND WorkID=1
				ORDER BY CommentTimestamp DESC $expand", $sparam->{'GAL'});

			# Pull information
			# Verify need
			if ($nres = $nsth->fetchrow_hashref())
			{	
				# Expansion code
				$expand = $param->EmbedInline($param->Flat(), EXPAND=>'True');
				$expand = $url . $expand;


				# Self referencing links
				$url = $cgi->url(-absolute=>1) . '/comment/' . $sparam->{'GAL'} . '/' . 1;
				
				# Inline parameters general
				$inline = (defined($param->{'USER'}))
				 ? $param->EmbedInline(USER=>$param->{'USER'}, CRYPT=>$param->{'CRYPT'})
				 : "";


				# Display footer
				$tmpl->Show('TmplNewsHead');

				# Loop and display
				do
				{
					# Deletion handle
					$idelete = ((defined($param->{'USER'})) && ($user eq $param->{'USER'}))
					  ? "<A HREF=\"$url/$nres->{CommentID}$inline\"><B>[$gal->{TxtDelete}]</B></A>"
					  : "";

					# Display
					$tmpl->Show('TmplNewsBody',
				 		MDATE => $nres->{'CommentTimestamp'},
						MTEXT => $nres->{'CommentPost'},
						WDELL => $idelete
				 	 );
				} while ($nres = $nsth->fetchrow_hashref());
	

				# Display footer
				$tmpl->Show('TmplNewsFoot',
					LNEXP => $expand
				 );
			}

			# Finish query
			$nsth->finish();


			# Spacer
			print "<BR>\n<BR>\n";


			# Quick Links
			# Prepare retreival
			$database->Pull(\$lsth, "SELECT
				 LinkName    AS \"LinkName\",
				 LinkAddress AS \"LinkAddress\"
				FROM Link
				WHERE GalleryID=?
				ORDER BY LinkName",

			 $sparam->{'GAL'}
			);

			# Check and initial setup
			if ($lres = $lsth->fetchrow_hashref())
			{
				# Display footer
				$tmpl->Show('TmplLinksHead');

				# Loop and populate main body
				do
				{
					# Display
					$tmpl->Show('TmplLinksBody',
						MLINK => $lres->{'LinkAddress'},
						MNAME => $lres->{'LinkName'}
				 	 );
				} while ($lres = $lsth->fetchrow_hashref());


				# Create footer
				$tmpl->Show('TmplLinksFoot');
			}

			# Finish query
			$lsth->finish();

			
			# Close table code
			print "  </TD>\n </TR>\n</TABLE>\n";
		}
	}
}


#####################
# Gallery List
#
# Provides the front end to the gallery system itself.   Will allow the user
# to select from a list of galleries and move on from there.

sub GalleryList
{
	#####################
	# Data members
	my $database = shift;					# Database handler
	my $login    = shift;					# Authenthication handler
	my $cgi      = shift;					# CGI interface
	my $param    = shift;					# Paramater handler
	my $tmpl     = shift;					# Template handler
	my $sparam   = shift;					# Parameter line hash
	my $gal      = shift;					# Gallery hash
	
	my $option;						# Option handler

	my $sections;						# Section handling
	my $statement;						# Query statement
	my $res;						# Query results

	my $gen_inline;						# Generic inline
	my $gen_url;						# Generic url
	my $inline;						# Standard inline
	my $page;						# Nav page
	my $url;						# Link

	my $default;						# Default list
	my $genre;						# Gallery genre
	my $newness;						# Newness factor
	my $type;						# Gallery type

	my $count;						# Counter
	my $offset;						# Starting position
	my $total;						# Amount of entries

	my $section;						# Section generation
	my $sort;						# Sort type

	my @sections;						# Selectable sections
	my @sorts;						# Sorting types


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

	# Option handling
	$option = new Ethereal::Option();


	# Give more meaniful names
	$sparam->{'TYP'} = $sparam->{'GAL'} if defined($sparam->{'GAL'});
	$sparam->{'GRE'} = $sparam->{'WRK'} if defined($sparam->{'WRK'});

	# Determine type
	if ((defined($param->{'LSELECT'}))
	 && ($param->{'LSELECT'} =~ /(\w+)\s\-\s([\w\s]+)\s\(/))
	{
		# Set values	
		$sparam->{'TYP'} = $1;
		$sparam->{'GRE'} = $2;
	}

	# Retreive sections list
	# Prepare and execute
	$database->Pull(\$sections, "SELECT
		 GalleryType  AS \"GalleryType\",
		 GalleryGenre AS \"GalleryGenre\",
		 COUNT(*)     AS \"GalleryCount\" 
		FROM Exhibition 
		GROUP BY GalleryType, GalleryGenre
		ORDER BY GalleryType, GalleryGenre");


	# Loop and gather
	while ($res = $sections->fetchrow_hashref())
	{
		# Determine type
		$type  = $option->GalleryTypeRev($gal->{'OptListType'}, $res->{'GalleryType'});

		# Determine rest
		$genre = $res->{'GalleryGenre'};
		$count = $res->{'GalleryCount'};


		# Append
		push(@sections, "$type - $genre ($count)");


		# Check for match
		if ((defined($sparam->{'TYP'}))
		 && (defined($sparam->{'GRE'}))
		 && ($sparam->{'TYP'} eq $type)
		 && ($sparam->{'GRE'} eq $genre))
		{
			# Assign default value
			$default = "$type - $genre ($count)";
		}
		
	}
	$sections->finish();

	# Sort list
	$option->Split(\@sorts, $gal->{'OptSrchSort'});

	# Defaults
	$default = (defined($default)) ? $default : $sections[0];
	$param->{'LSORT'} = (defined($param->{'LSORT'})) ? $param->{'LSORT'} : $sorts[1];


	# Generate widget
	$section = $cgi->popup_menu('LSELECT', \@sections, $default);
	$sort    = $cgi->popup_menu('LSORT',   \@sorts,    $param->{'LSORT'});


	# Self referencing link
	$gen_url    = $cgi->url(-absolute=>1) . '/view';
	$gen_inline = (defined($param->{'USER'}))
		? $param->EmbedInline(USER=>$param->{'USER'}, CRYPT=>$param->{'CRYPT'}) : '';


	# Display
	# Title
	$tmpl->ShowMain($gal->{'TagListing'});

	
	# From generation
	print $cgi->start_form(-action=>$cgi->url(-absolute=>1) . '/list');
	print $param->EmbedNormal(
		USER  => $param->{'USER'},
		CRYPT => $param->{'CRYPT'}) if (defined($param->{'USER'}));
	
	# Display nav
	$tmpl->Show('TmplGalSelect', 
		WGENRE   => $section,
		WSORTS   => $sort,
		WSELCT   => $cgi->submit($gal->{'TxtSelect'}),
		GENPARAM => $gen_inline
	 );

	# End of form
	print $cgi->end_form();


	# Information display
	# Something to do
	if ((defined($sparam->{'TYP'}))
	 && (defined($sparam->{'GRE'})))
	{
		# Determine type
		$type = $option->GalleryType($gal->{'OptListType'}, $sparam->{'TYP'});

		# Determine sort
		$sort = $option->GallerySort($gal->{'OptSrchSort'}, $param->{'LSORT'});

		# Discover
		if    ($sort eq 'date')   { $sort = 'GalleryUpdated DESC'; }
		elsif ($sort eq 'rating') { $sort = 'GalleryRating ASC, GalleryName ASC'; }
		else  { $sort = '"GalleryNewness" DESC, GalleryName ASC' }


		# Quickly grab total entries
		($total) = $database->DataGet("SELECT COUNT(*)
			FROM Exhibition 
			WHERE GalleryType=?
			  AND GalleryGenre=?", 

		 $type,
		 $sparam->{'GRE'}
		);

		# Determine position
		$sparam->{'POS'} = ((defined($sparam->{'POS'})) && ($sparam->{'POS'} =~ /^\d+$/))
		 ? $sparam->{'POS'} : 1;

		# Determine offset
		$offset = ($sparam->{'POS'} - 1) * $gal->{'SetRetrieve'};


		# Main display
		$database->Pull(\$statement, "SELECT
			  GalleryID               AS \"GalleryID\",
			  PuppeteerLogin          AS \"PuppeteerLogin\",
			  GalleryName             AS \"GalleryName\",
			  GalleryRating           AS \"GalleryRating\",
			  GalleryDescription      AS \"GalleryDescription\",
			  getDate(GalleryCreated) AS \"GalleryCreated\",
			  getDate(GalleryUpdated) AS \"GalleryUpdated\",
			  getNew(GalleryUpdated)  AS \"GalleryNewness\",
			  WorkCount               AS \"WorkCount\"
			FROM Exhibition 
			WHERE GalleryType=?
			  AND GalleryGenre=?
			ORDER BY $sort LIMIT $gal->{SetRetrieve} OFFSET $offset",

		 $type,
		 $sparam->{'GRE'}
		);

		# Quick verification
		if ($res = $statement->fetchrow_hashref())
		{
			# Navigational menu
			$type  = $cgi->escape($sparam->{'TYP'});
			$genre = $cgi->escape($sparam->{'GRE'}); 

			# Self referencing link
			$url    = $cgi->url(-absolute=>1) . '/list/' . $type . '/' . $genre;
			$inline = (defined($param->{'USER'}))
			 ? $param->EmbedInline($param->Flat())
			 : '?LSORT=' . $param->{'LSORT'};

			# Capture nav line
			$page = Navigation($tmpl, $gal, 'TagNav', $gal->{'SetRetrieve'}, $total, $sparam->{'POS'}, $url, $inline);

			# Display nav
			$tmpl->Show('TagNavBar', 
				MNAV   => $page,
				MCOUNT => $total,
				MENTRY => $gal->{'SetRetrieve'}
			 ) unless ($total <= $gal->{'SetRetrieve'});


			# Continue 
			do
			{
				# Display information
				$tmpl->Show('TmplDisGallery',
					MAUTHOR  => PenNames($database, $param, $tmpl, $gal, $res->{'GalleryID'}),
					MCREATED => $res->{'GalleryCreated'},
					MDESCR   => $res->{'GalleryDescription'},
					MLINK    => "$gen_url/$res->{GalleryID}/$gen_inline",
					MNAME    => $res->{'GalleryName'},
					MCOUNT   => $res->{'WorkCount'},
					MRATING  => $res->{'GalleryRating'},
					MUPDATED => $res->{'GalleryUpdated'},
					MNEWNESS => $newness
				 );

			} while ($res = $statement->fetchrow_hashref());

			# Display nav
			$tmpl->Show('TagNavBar', 
				MNAV   => $page,
				MCOUNT => $total,
				MENTRY => $gal->{'SetRetrieve'}
			 ) unless ($total <= $gal->{'SetRetrieve'});
		}

		else
		{
			# Display error message
			$tmpl->Show('TmplSrch', MMESSAGE=>$gal->{'MsgListing'});
		}


		# End statement
		$statement->finish();
	}
}


#####################
# Pen Names
#
# Will take a list of all contributors and make it in such a way that it 
# can display all of the contributors on one line.

sub PenNames
{
	#####################
	# Data members
	my $database = shift;					# Database handler
	my $param    = shift;					# Parameter list
	my $tmpl     = shift;					# Template handler
	my $gal      = shift;					# Gallery parameter
	my $id       = shift;					# Gallery ID
	my $owner    = shift;					# Rightful owner

	my $count;						# Basic counter
	my $entries;						# Number entries
	
	my $consol;						# Consolidated lines
	my $inline;						# Inline handling
	my $spacer;						# Employed spacer

	my @args;						# Arguments to send
	my @contribs;						# Complete list of contributors
	
	#####################
	# Program area

	# Owner of gallery
	# Owner specified
	if (defined($owner))
	{
		# Pass arguments
		push(@args, $id, $owner);

		# Throw in additional formatting
		$owner = 'AND PuppeteerLogin=? ';
	}

	# Normal dual entry
	else
	{
		# Normal passage
		push(@args, $id);

		# Nothing to see
		$owner = '';
	}
	
	# Pull list of all contributors
	$database->GetList(\@contribs, "SELECT PuppetName
		FROM GalleryAccess
		WHERE GalleryID=? $owner
		ORDER BY AccessOwner DESC, PuppetName", @args);


	# Amount of entries
	$entries = @contribs;

	# Loop and search
	for ($count=0; $count < $entries; $count++)
	{
		# Generate biographical link
		$inline = (defined($param->{'USER'}))
		 ? $param->EmbedInline(
			 USER  => $param->{'USER'},
			 CRYPT => $param->{'CRYPT'},
			 BCHAR => $contribs[$count],
			 BPOOL => 'system')
		 : $param->Crypt(
			 BCHAR => $contribs[$count],
			 BPOOL => 'system');


		# Information handling
		# Initialize
		if ($count == 0)
		{
			# Set authors information first
			$consol = $tmpl->Pass('TagPenName',
				MNAME => $contribs[$count],
				MLINK => $gal->{'LnkBio'} . $inline
			);
		}

		# Appending
		else
		{
			# Spacer
			$spacer = ($count != ($entries-1)) ? $gal->{'TagPenNameSpacer'} : $gal->{'TagPenNameFinalize'}; 

			# Additional contributors
			$consol = $consol . $spacer . $tmpl->Pass('TagPenName',
				MNAME => $contribs[$count],
				MLINK => $gal->{'LnkBio'} . $inline
			 );
		}
	}

	# Safety measure
	$consol = '' unless (defined($consol));
	
	# Return consolidated line
	return $consol;
}

#####################
# Header rewrite
#
# Used to rewrite the HTML header as to properly list the Authors name
# and gallery name in the title field.

sub HeaderRewrite
{
	#####################
	# Data members
	my $database = shift;					# Database handler
	my $sparam   = shift;					# Parameter line hash
	my $gal      = shift;					# Gallery parameter

	my $author;						# Author of gallery
	my $id;							# Gallery ID
	my $name;						# Gallery/Work name
	my $header;						# HTML Header

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

	# Retrieve header
	($header) = $database->DocumentGet('GalleryHeader');


	# Check for rewrite
	if ((defined($sparam->{'GAL'}))
	 && ($sparam->{'GAL'} =~ /^\d+$/))
 	{
		# Works
		if ((defined($sparam->{'WRK'}))
		 && ($sparam->{'WRK'} =~ /^\d+$/))
		{
			# Pull details
			($id, $author, $name) = $database->DataGet("SELECT
				 g.GalleryID,
			 	 g.GalleryPenName,
				 w.WorkName
				FROM Gallery g, Work w
				WHERE w.GalleryID=g.GalleryID
				  AND g.GalleryID=?
				  AND w.WorkID=?",
	
			 $sparam->{'GAL'},
			 $sparam->{'WRK'}
			);

			# Replace if need be
			if (defined($name))
			{
				# Search and replace
				# Look for instances of the title field
				$header =~ s/(<TITLE.*?>).+(<\/TITLE.*?>)/${1}${name} by ${author}${2}/gsi;	
			}
		}
	
		# Galleries
		else
		{
			# Pull details
			($id, $author, $name) = $database->DataGet("SELECT
				 GalleryID,
				 GalleryPenName,
				 GalleryName
				FROM Gallery
				WHERE GalleryID=?", $sparam->{'GAL'});

			# Replace if need be
			if (defined($name))
			{
				# Search and replace
				# Look for instances of the title field
				$header =~ s/(<TITLE.*?>).+(<\/TITLE.*?>)/${1}${author}: ${name}${2}/gsi;	
			}
		}

		# CSS Override
		if ((defined($id))
		 && (-f "$gal->{SetRoot}/$id/gal.css"))
		{
			# Search and replace CSS override
			$header =~ s/(<LINK.+HREF=\").+?(\".+TITLE=\"ETHEREAL\".+>)/${1}$gal->{SetUrl}\/${id}\/gal.css${2}/gsi;	
		}
	}

	# Print out modified header
	print $header;
}


#####################
# Navigation
#
# Used to consolidate navigational methods on the site employing OFFSET and LIMIT
# which are available on PostgreSQL.

sub Navigation
{
	#####################
	# Data members
	my $tmpl      = shift;					# Template handler
	my $gal       = shift;					# Gallery hash
	my $template  = shift;					# Template to use
	my $display   = shift;					# Entries to show off
	my $total     = shift;					# Total entries
	my $offset    = shift;					# Current position
	my $url       = shift;					# Link to display
	my $inline    = shift;					# Inline parameters

	my $count;						# Basic counter
	my $frst;						# First entry
	my $last;						# Last entry
	my $next;						# Next entry
	my $prev;						# Prev entry
	
	my $link;						# Generated link
	my $nav;						# Completed navigational page
	

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

	# Set values
	$count = 0;
	$nav   = '';
	
	# Calculate next and previous
	$frst = 1;
	$last = (($total - ($total % $display)) / $display) + 1;
	$prev = (($offset - 1) > 0) ? $offset - 1 : 1;
	$next = (($offset + 1) <= $last) ? $offset + 1 : $last;
	
	# Loop
	do
	{
		# Remove mount to verify spacing
		$total -= $display;

		# Increment
		$count++;

		# Assign
		$link  = $url . '/' . $count . $inline;

		# Append
		$nav .= $tmpl->Pass($template,
			MNAME => $count,
			MLINK => $link
		 );

	} while ($total > 0);


	# Return completed
	return  $tmpl->Pass($template, MNAME=>$gal->{'TxtFirst'}, MLINK=>$url . '/' . $frst . $inline)
	     .  $tmpl->Pass($template, MNAME=>$gal->{'TxtPrev'},  MLINK=>$url . '/' . $prev . $inline)
	     .  $nav
	     .  $tmpl->Pass($template, MNAME=>$gal->{'TxtNext'},  MLINK=>$url . '/' . $next . $inline)
	     .  $tmpl->Pass($template, MNAME=>$gal->{'TxtLast'},  MLINK=>$url . '/' . $last . $inline);
}

#####################
# Work Display
#
# Work display allows a user to view the works themselves in full
# detail.    

sub WorkDisplay
{
	#####################
	# Data members
	my $database = shift;					# Database handler
	my $login    = shift;					# Authenthication handler
	my $cgi      = shift;					# CGI interface
	my $param    = shift;					# Paramater handler
	my $tmpl     = shift;					# Template handler
	my $sparam   = shift;					# Parameter line hash
	my $gal      = shift;					# Gallery hash

	my $option;						# Option handler
	
	my $res;						# Results
	my $revisions;						# Query for revisions
	my $statement;						# Query statement

	my $cmnt;						# Comment preferences
	my $name;						# Verification check
	my $type;						# Gallery type

	my $order;						# Sort order
	my $sort;						# Sort type

	my $next = 0;						# Next sequence
	my $curr = 0;						# Current position
	my $prev = 0;						# Previous record
	my $totl = 0;						# Total amount of works

	my $rate;						# Rating
	my $user;						# Username
	my $vote;						# Votes

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

	my @seq;						# Logical squence


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

	# Things are in order
	($name) = $database->DataGetWorkName($sparam->{'WRK'}, $sparam->{'GAL'});

	# Continue as planned
	if (defined($name))
	{
		# Things are proper
		return 0 unless (CheckAuth($database, $login, $param, $sparam, $tmpl, $gal));

		# Create instance
		$option = new Ethereal::Option;


		# Information gathering
		# Retreive gallery basics
		($sort, $cmnt, $type, $user) = $database->DataGet("SELECT
			  GallerySort,
			  GalleryComments,
			  GalleryType,
			  PuppeteerLogin
			FROM Gallery
			WHERE GalleryID=?",

		 $sparam->{'GAL'}
		);


		# Ordering type
		$order = $option->GallerySortReal($sort);
			
		# Retrieve next of kin
		$database->GetList(\@seq, "SELECT WorkID
			FROM Work
			WHERE GalleryID=?
			  AND WorkID > 1
			ORDER BY $order",

		 $sparam->{'GAL'}
		);

		# Total
		$totl = @seq;


		# Where we are
		for ($curr=0; $curr < $totl; $curr++)
		{
			# Find and leave loop
			last unless ($seq[$curr] != $sparam->{'WRK'});
		}

		# Position of cousins
		$next = ($curr+1 >= $totl) ? $seq[0] : $seq[$curr+1];
		$prev = $seq[$curr-1];
		

		# Linking
		# Fetch link
		$url = $cgi->url(-absolute=>1) . '/view/' . $sparam->{'GAL'};

		# Inline parameters general
		$inline  = (defined($param->{'USER'}))
		 ? $param->EmbedInline(USER=>$param->{'USER'}, CRYPT=>$param->{'CRYPT'})
		 : '';

		# Works of art
		# Retrieve rating
		($rate, $vote) = $database->DataGetWorkAvg($sparam->{'WRK'}, $sparam->{'GAL'});


		# Prepare and execute
		$database->Pull(\$statement, "SELECT
			  PuppeteerLogin     AS \"WorkOwner\",
			  WorkName           AS \"WorkName\",
			  WorkDescription    AS \"WorkDescription\",
			  WorkSort           AS \"WorkSort\",
			  WorkImage          AS \"WorkImage\",
			  WorkImageThumbnail AS \"WorkImageThumbnail\",
			  WorkImageHeight    AS \"WorkImageHeight\",
			  WorkImageWidth     AS \"WorkImageWidth\",
			  WorkImageSize      AS \"WorkImageSize\",
			  WorkText           AS \"WorkText\",
			  WorkTextPreserve   AS \"WorkTextPreserve\",
			  getDate(WorkTimestamp) AS \"WorkTimestamp\"
			FROM Work
			WHERE WorkID=?
			  AND GalleryID=?",

		 $sparam->{'WRK'},
		 $sparam->{'GAL'}
		);
	
		# Make available
		if ($res = $statement->fetchrow_hashref())
		{
			# Common display
			# Header
			$tmpl->Show('TmplWrkHeader',
				MDISAUTH => PenNames($database, $param, $tmpl, $gal, $sparam->{'GAL'}, $res->{'WorkOwner'}),
				MDISCURR => $curr+1,
				MDISDESC => $res->{'WorkDescription'},
				MDISNAME => $res->{'WorkName'},
				MDISTOTL => $totl,
				MLNKGTOP => "$url/$inline",
				MLNKNEXT => "$url/$next$inline",
				MLNKPREV => "$url/$prev$inline"
			 );
			

			# Fork for gallery styles
			# Image gallery
			if (($type eq 'image')
			 || ($type eq 'comic'))
			{
				# Display image specific
				$tmpl->Show('TmplWrkImage',
					MDISIMAG => $res->{'WorkImage'},
					MDISHGHT => $res->{'WorkImageHeight'},
					MDISWDTH => $res->{'WorkImageWidth'},
					MDISNAME => $res->{'WorkName'}
				 );
			}

			# Text based galleries
			elsif (($type eq 'text')
			  || ($type eq 'journal'))
			{
				# Local variables
				my ($rdate, $rstamp);

				# Initial assignment
				my $print = '';
				my $revs  = '';
				my $post  = $res->{'WorkText'};


				# Make use of revision instead
				if ((defined($sparam->{'POS'}))
				 && ($sparam->{'POS'} =~ /^\w{30,}/))
				{
					# Make use of revision
					($post, $rdate) = $database->DataGet("SELECT
						  RevisionText,
						  getDate(RevisionTimestamp)
						FROM Revision
						WHERE WorkID=?
						  AND GalleryID=?
						  AND RevisionTimestamp=?",

					  $sparam->{'WRK'},
					  $sparam->{'GAL'},
					  pack('H*', $sparam->{'POS'})
					 );

					 # Append notice
					 $post .= $tmpl->Pass('TagRevNotice', MDATE=>$rdate);
				}

				
 				# Rewriting
				# Basic replacements
				$post =~ s/$/\n/;
					
				# Maintain spacing
				$post =~ s/\n/<BR>/gs;					
 

				# Revision control
				if ($type eq 'text')
				{
					
					# Pull new list
					$database->Pull(\$revisions, "SELECT
						  RevisionTimestamp          AS \"Timestamp\",
						  getDate(RevisionTimestamp) AS \"Date\"
						FROM Revision
						WHERE WorkID=?
						  AND GalleryID=?",
						  
					 $sparam->{'WRK'},
					 $sparam->{'GAL'}
					);

					# Verification
					if (($rstamp, $rdate) = $revisions->fetchrow())
					{	
						# Initialize template
						$revs = $gal->{'TagRevTitle'};

						# Initial link
						$revs .= $tmpl->Pass('TagRevLink',
							MLINK => $url . '/' . $sparam->{'WRK'} . $inline,
							MNAME => $gal->{'TxtRecent'}
						 );

						
						# Check first
						do
						{
							# Append additional links
							$revs .= $tmpl->Pass('TagRevLink',
								MLINK => $url . '/' . $sparam->{'WRK'} . '/' . unpack('H*', $rstamp) . $inline,
								MNAME => $rdate
							 );

						} while (($rstamp, $rdate) = $revisions->fetchrow());

						# Close off
						$revs .= $gal->{'TagRevClose'}; 

						# Close handle
						$revisions->finish();
					}
				}

				
				# Printing option
				if (-f "$gal->{SetRoot}/$sparam->{GAL}/$sparam->{WRK}/work.pdf")
				{
					# Show print option
					$print = $tmpl->Pass('TagPrint', 
						MLINK => "$gal->{SetUrl}/$sparam->{GAL}/$sparam->{WRK}/work.pdf"
					 );
				}
				

				# Display image specific
				$tmpl->Show('TmplWrkText',
					MDISTEXT  => $post,
					MDISREVS  => $revs,
					MDISPRINT => $print
				 );
			}

		}

		# Finish query
		$statement->finish();

		# List comments
		CommentDisplay($database, $login, $cgi, $param, $tmpl, $sparam, $gal, $gal->{'SetRetrieve'});
	}

	# Signal error
	else
	{
		# Invalid entry
		$tmpl->Show('TmplMsg', MMESSAGE=>$gal{'MsgParam'});
	}
}


#####################
# Work Search
#
# Allows for searchign through works for specific words and phrases
# reguardless of standard word search of boolean searches.

sub WorkSearch
{
	#####################
	# Data members
	my $database = shift;					# Database handler
	my $login    = shift;					# Authenthication handler
	my $cgi      = shift;					# CGI interface
	my $param    = shift;					# Paramater handler
	my $tmpl     = shift;					# Template handler
	my $sparam   = shift;					# Parameter line hash
	my $gal      = shift;					# Gallery hash
	
	my $option;						# Option handler
	my $res;						# Results
	my $statement;						# Query statement

	my $newness;						# Newness factor
	my $page;						# Page number
	my $panel;						# Search panel

	my $count;						# Counter
	my $length;						# Length of string
	my $number;						# Number
	my $offset;						# Where to begin
	my $total;						# Total amount of entries

	my $query;						# Main query
	my $where;						# Where clause

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

	my $descr;						# Description
	my $field;						# Search field
	my $genre;						# Genre sort
	my $sort;						# Sort type
	my $text;						# Text type
	my $type;						# Search type
	my $page;						# Template

	my $sfild;						# Search field
	my $sgnre;						# Search genre
	my $ssort;						# Search sort
	my $ssmbt;						# Submit
	my $stext;						# Text handler
	my $stype;						# Search type

	my @field;						# Option search field
	my @genre;						# Option for genres
	my @sort;						# Option for sort type
	my @type;						# Option search type

	my %system;						# System hash
	my %val;						# Value hash

	#####################
	# Data members

	# Create instances
	$option = new Ethereal::Option();

	# Pull genre lists
	$database->GetList(\@genre, "SELECT GenreName
		FROM Genre
		WHERE GenreOrder IN ('image','text')");

	# Options lists
	$option->Split(\@field, $gal->{'OptSrchField'});
	$option->Split(\@sort,  $gal->{'OptSrchSort'});
	$option->Split(\@type,  $gal->{'OptSrchType'});
	
	# Append
	unshift(@genre, '------');


	# Definition	
	$param->{'SFILD'} = (defined($param->{'SFILD'})) ? $param->{'SFILD'} : $field[0];
	$param->{'SGNRE'} = (defined($param->{'SGNRE'})) ? $param->{'SGNRE'} : $genre[0];
	$param->{'SSORT'} = (defined($param->{'SSORT'})) ? $param->{'SSORT'} : $sort[0];
	$param->{'STYPE'} = (defined($param->{'STYPE'})) ? $param->{'STYPE'} : $type[0];
	$param->{'STEXT'} = (defined($param->{'STEXT'})) ? $param->{'STEXT'} : '';

	# Generate widgets
	$sfild = $cgi->popup_menu('SFILD', \@field, $param->{'SFILD'});
	$sgnre = $cgi->popup_menu('SGNRE', \@genre, $param->{'SGNRE'});
	$ssort = $cgi->popup_menu('SSORT', \@sort,  $param->{'SSORT'});
	$stype = $cgi->popup_menu('STYPE', \@type,  $param->{'STYPE'});

	$ssmbt = $cgi->submit($gal->{'TxtSearch'});
	$stext = $cgi->textfield('STEXT', $param->{'STEXT'}, 50, 75);


	# Ensure definition
	$sparam->{'POS'} = (defined($sparam->{'GAL'})) ? $sparam->{'GAL'} : 1;

	# New query
	if (defined($param->{'SNEW'}))
	{
		# Reset for new query
		$sparam->{'GAL'} = undef;
		$sparam->{'WRK'} = undef;

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


	# Display
	$tmpl->PassTitle($gal->{'TagSrch'});

	# Search panel
	($panel) = $database->DocumentGetGallerySearch();

	# Search and replace
	$panel =~ s/MSRCHRESL/&nbsp;/gs;
	$panel =~ s/WSRCHFILD/$sfild/gs;
	$panel =~ s/WSRCHGNRE/$sgnre/gs;
	$panel =~ s/WSRCHSORT/$ssort/gs;
	$panel =~ s/WSRCHSMBT/$ssmbt/gs;
	$panel =~ s/WSRCHTEXT/$stext/gs;
	$panel =~ s/WSRCHTYPE/$stype/gs;

	# Forms
	print $cgi->start_form(-action=>$cgi->url(-absolute=>1) . '/search');
	print $cgi->hidden('SNEW', 'True'), "\n";
	print $param->EmbedNormal($param->Flat()) if (defined($param->{'USER'}));

	# Display
	print "$panel\n";

	# End form
	print $cgi->end_form(), "\n";

	
	# Need exists
	if (length($param->{'STEXT'}) > 2)
	{
		# Determine useable
		$field = $option->GalleryField($gal->{'OptSrchField'}, $param->{'SFILD'});
		$type  = $database->{'HANDLE'}->quote($option->GalleryType($gal->{'OptSrchType'}, $param->{'STYPE'}));


		# Sorting options
		$sort  = $option->GallerySort($gal->{'OptSrchSort'},   $param->{'SSORT'});


		# Discover
		#if    ($sort eq 'date')   { $sort = 'wrk.WorkTimestamp DESC'; }
		#lsif ($sort eq 'rating') { $sort = 'wrk.WorkRating DESC, wrk.WorkName ASC'}
		#else  { $sort = 'wrk.WorkName ASC' }
		$sort = 'wrk.WorkName ASC';


		# Genre
		$genre = ($param->{'SGNRE'} =~ /^\w+/) ? "AND gre.GenreName=\'$param->{SGNRE}\'" : "";


		# Pull text
		$text  = $param->{'STEXT'};

		# Determine where statement
		$where = $database->Query($text, $field);
		

		# Pull total list
		($total) = $database->DataGet("SELECT COUNT(*)
			FROM Gallery gal,
			     GalleryGenre gre,
			     Work wrk
			WHERE gal.GalleryID=wrk.GalleryID
			  AND gal.GalleryID=gre.GalleryID
			  AND gal.GalleryType=$type $genre AND ($where)");

			  
		# Prepare query
		if ($total > 0)
		{
			# Navigational
			# Self referencing link
			$url    = $cgi->url(-absolute=>1) . '/search';
			$inline = (defined($param->{'USER'}))
			 ? $param->EmbedInline($param->Flat())
			 : $param->Crypt($param->Flat());

			# Generate template
			$page = Navigation($tmpl, $gal, 'TagNav', $gal->{'SetRetrieve'}, $total, $sparam->{'POS'}, $url, $inline);

			# Display nav
			$tmpl->Show('TagNavBar', 
				MNAV   => $page,
				MCOUNT => $total,
				MENTRY => $gal->{'SetRetrieve'}
			 ) unless ($total <= $gal->{'SetRetrieve'});

			# Calculate offset
			$offset = ($sparam->{'POS'} - 1) * $gal->{'SetRetrieve'};


			# Pull full list
			$database->Pull(\$statement, "SELECT DISTINCT ON (wrk.WorkName)
				  wrk.GalleryID              AS \"GalleryID\",
				  wrk.WorkID                 AS \"WorkID\",
				  wrk.WorkName               AS \"WorkName\",
				  wrk.WorkDescription        AS \"WorkDescription\",
				  wrk.WorkRating             AS \"WorkRating\",
				  wrk.WorkImage              AS \"WorkImage\",
				  wrk.WorkImageHeight        AS \"WorkImageHeight\",
				  wrk.WorkImageWidth         AS \"WorkImageWidth\",
				  wrk.WorkImageSize          AS \"WorkImageSize\",
				  wrk.WorkImageThumbnail     AS \"WorkImageThumbnail\",
				  wrk.WorkText               AS \"WorkText\",
				  getDate(wrk.WorkTimestamp) AS \"WorkTimestamp\",
				  getNew(wrk.WorkTimestamp)  AS \"WorkNewness\"
				FROM Gallery gal,
				     GalleryGenre gre,
				     Work wrk
				WHERE gal.GalleryID=wrk.GalleryID
				  AND gal.GalleryID=gre.GalleryID
				  AND gal.GalleryType=$type $genre AND ($where)
				  ORDER BY $sort LIMIT $gal->{SetRetrieve} OFFSET $offset");
			
			# Pull all records
			if ($res = $statement->fetchrow_hashref())
			{
				# Self referencing link
				$url    = $cgi->url(-absolute=>1) . '/view';

				# Inline parameters
				$inline = (defined($param->{'USER'}))
				 ? $param->EmbedInline(
					USER=>$param->{'USER'},
					CRYPT=>$param->{'CRYPT'})
				 : '';


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

				do
				{
					# Determine newness
					$newness = ($res->{'WorkNewness'} != 0) ? $gal->{'TagNewness'} : '';

						
					# Display
					# Image gallery
					if ($type =~ /image/)
					{
						# Display
						$tmpl->Show('TmplDisImage',
					 		MNAME    => $res->{'WorkName'},
							MDESCR   => $res->{'WorkDescription'},
							MHEIGHT  => $res->{'WorkImageHeight'},
							MIMAGE   => $res->{'WorkImageThumbnail'},
							MLINK    => $url . '/' . $res->{'GalleryID'} . '/' .$res->{'WorkID'} . '/' .$inline,
							MRATING  => $res->{'WorkRating'},
							MSIZE    => $res->{'WorkImageSize'},
							MTHUMB   => $system{'SetThumbnail'},
							MWIDTH   => $res->{'WorkImageWidth'},
							MUPDATED => $res->{'WorkTimestamp'},
							MNEWNESS => $newness
						);
					}

					# Text gallery
					elsif ($type =~ /text/)
					{
						# Description
						$descr  = $res->{'WorkDescription'};
						$descr  = (defined($descr)) ? $descr : substr(DBI::neat($res->{'WorkText'}, 252), 1, -1);

						# Length
						$length = length($res->{'WorkText'});


						# Display information
						$tmpl->Show('TmplDisText',
					 		MNAME    => $res->{'WorkName'},
							MDESCR   => $descr,
							MLENGTH  => $length,
							MLINK    => $url . '/' . $res->{'GalleryID'} . '/' .$res->{'WorkID'} . '/' .$inline,
							MRATING  => $res->{'WorkRating'},
							MUPDATED => $res->{'WorkTimestamp'},
							MNEWNESS => $newness
						);
					}

				} while ($res = $statement->fetchrow_hashref());

				# Display bottom nav
				$tmpl->Show('TagNavBar', 
					MNAV   => $page,
					MCOUNT => $total,
					MENTRY => $gal->{'SetRetrieve'}
				 ) unless ($total <= $gal->{'SetRetrieve'});
			}

			else
			{
				# No results
				$tmpl->Show('TmplSrch',
					MMESSAGE=>$gal->{'MsgSrchResults'}
				 );
			}

			# End execution
			$statement->finish();
		}

		else
		{
			# Error in execution
			$tmpl->Show('TmplSrch',
				MMESSAGE=>$gal->{'MsgSrchError'}
			 );
		}
	}
}
