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

################################################################################
# Created       : Martin Foster
# Modified      : 01-Apr-2007
################################################################################
#
# Social Network - Script part of Ethereal Realms, designed to display/list
#                  social profiles and allow for searches.
# Copyright (C) 2000-2007  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 : 4-3526 Wolfe Cres
#                 Halifax, Nova Soctia
#                 B3L 3S2
#
#################################################################################

use CGI qw(-no_debug -nosticky);				# 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::Login;						# Login functionality
use Ethereal::Menu;						# Consistent menu
use Ethereal::Param;						# Parameter control
use Ethereal::Template;						# Template handler

require Ethereal::Filter;					# Filter handling
require Ethereal::Geo;						# Geographical handling
require Ethereal::Option;					# Option handling

#################################################################################
# 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 $menu;							# Menu handle
my $param;							# Parameter hash
my $tmpl;							# Template handle

my $sparam;							# Scripted parameter string

my @pass;							# Passage of variables

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

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

	# Initial handles
	$cgi      = new CGI;
	$database = new Ethereal::Database();
	
	# Connect and fetch
	$database->Connect($cgi);

	# Kill switch
	exit if $database->{'COMP_VANITY'} eq 'false';

	# Set title
	$database->{'TITLE'} = $database->{'SYS'}{'TitSocial'};

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

	# Link with hash
	$database->GetHashVanity(\%vanity);

	# Create instances
	$tmpl  = new Ethereal::Template(\%vanity);
	$menu  = new Ethereal::Menu($database, $param);
	$login = new Ethereal::Login($database, $cgi, $param, $menu);


	# Fetch and format
	$sparam =  $cgi->path_info();
	$sparam =~ s/^(\/)(.*)(\/)?$/$2\//;


	# Split things appart and get what is necessary
	# Function, component, region
	($sparam{'FNC'},
 	 $sparam{'CMP'},
 	 $sparam{'REG'}) = split(/\//, $cgi->unescape($sparam));


 	# Unlike galleries
	# Users must be authenticated
	if ($login->GetVerificationNormal())
	{
		# Determine functions
		# Users display
		if ($sparam{'FNC'} eq 'show')
		{
			# Displays a profile
			SocialShow($database, $cgi, $param, $tmpl, $menu, \%sparam, \%vanity);
		}

		# The default
		# User search capabilities
		else
		{
			# Social Search capabilities
			SocialSearch($database, $cgi, $param, $tmpl, $menu, \%sparam, \%vanity);

		}
	}


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

#####################
# 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 $vanity    = shift;					# Vanity hash
	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
	my $pass;						# What is passed
	

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

	# Set values
	$count = 0;
	$nav   = '';
	$pass  = '&nbsp;';
	
	# 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 . '/search/' . $count . $inline;

		# Append
		$nav .= ($offset == $count)
		  ? $tmpl->Pass('TagNavVis', MCOUNT=>$count, MLINK=>$link)
		  : $tmpl->Pass('TagNavDis', MCOUNT=>$count, MLINK=>$link);

	} while ($total > 0);

	# Generate template
	# Only if there is a need
	$pass = $tmpl->Pass('TagNavDis', MCOUNT=>$vanity->{'TxtFirst'}, MLINK=>$url . '/search/' . $frst . $inline)
	     .  $tmpl->Pass('TagNavDis', MCOUNT=>$vanity->{'TxtPrev'},  MLINK=>$url . '/search/' . $prev . $inline)
	     .  $nav
	     .  $tmpl->Pass('TagNavDis', MCOUNT=>$vanity->{'TxtNext'},  MLINK=>$url . '/search/' . $next . $inline)
	     .  $tmpl->Pass('TagNavDis', MCOUNT=>$vanity->{'TxtLast'},  MLINK=>$url . '/search/' . $last . $inline) if ($count > 1);

	# Return completed
	return $pass;
}


	
#####################
# Social Search
#
# As the name implies, this will be used to search for user profiles listed on
# the site.

sub SocialSearch
{
	#####################
	# Data members

	my $database = shift;					# Database handler
	my $cgi      = shift;					# Common gateway interface
	my $param    = shift;					# Paramater handler
	my $tmpl     = shift;					# Template handle
	my $menu     = shift;					# Menu handler
	my $sparam   = shift;					# Parameter line hash
	my $vanity   = shift;					# Gallery hash

	my $option;						# Option handler
	my $geo;						# Geographical information

	my $res;						# Results handler
	my $statement;						# Query statement handler
	my $append;						# Ammended text

	my $count;						# Number of entries
	my $offset;						# Where to start

	my $url      = $cgi->url(-absolute=>1);			# Self referencing link
	my $inline;						# Inline parameters
	
	my $rng_def;						# Range defaults 
	my $rng_wig;						# Range wigets
	my $res_def;						# Restriction defaults 
	my $res_wig;						# Restriction wigets

	my $lst_int;						# Interest list match
	my $lst_van;						# Vanity listing
	my $tpl_search;						# Search bar
	my $tpl_nav;						# Navigation options
	my $tpl_res;						# Results list

	my $intro;						# Safe intros
	my $descr;						# Safe descriptions
	my $rlat;						# Random details
	my $rlon;						# Random details
	my $toggle;						# Link toggle

	my $gender;						# Users gender
	my $looking;						# Looking for
	my $from;						# From age
	my $to;							# To age
	my $city;						# City restriction
	my $region;						# Region restriction
	my $country;						# Country restriction
	my $interest;						# Singular interests
	my $lat;						# Latitude
	my $lon;						# Longitude
	my $restrict;						# Restrictions
	
	my @append;						# Coordinates
	my @interests;						# Array of interests
	my @ready;						# Ready made interests
	my @range;						# Range based searching
	my @restrict;						# Restriction of search


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

	# Check
	# Get preferences only if profile exists
	($gender,
	 $looking,
	 $from,
	 $to,
	 $city,
	 $region,
	 $country,
	 $lat,
	 $lon) = $database->DataGetSearchPrefs($param->{'USER'});

 	# Only if necessary
	# Profiled users only
	if (defined($gender))
	{
		# Create a new instance
		$option = new Ethereal::Option();
		$geo    = new Ethereal::Geo($database);

		#
		# Search bar
		# Get appropriate values
		$option->Split(\@range,    $vanity->{'OptSearchDistance'});
		$option->Split(\@restrict, $vanity->{'OptSearchRestrict'});
	
		# Set defaults
		$rng_def  = (defined($param->{'SRNG'})) ? $param->{'SRNG'} : $vanity->{'TxtSearchDef'};
		$res_def  = (defined($param->{'SRES'})) ? $param->{'SRES'} : $restrict[0];

		# Restrictions
		$restrict = $option->SocialRestrictRev($vanity->{'OptSearchRestrict'}, $res_def);

		# Generate widgets
		$rng_wig  = $cgi->popup_menu('SRNG',  \@range,    $rng_def);
		$res_wig  = $cgi->radio_group('SRES', \@restrict, $res_def, 'true');

		# Assign for safety
		$param->{'SRNG'} = $rng_def;
		$param->{'SRES'} = $res_def;


		# Generate template
		$tpl_search = $tmpl->Pass('TmplSocialSearchBar',
			WSRNG   => $rng_wig,
			WSRES   => $res_wig,
			WSEARCH	=> $cgi->submit($vanity->{'TxtSearch'})
		 );


		#
		# Geo aspects
		@append = $geo->Distance($rng_def, $lat, $lon);

		# Quick check
		# Initialize
		$append = '';
		
		# Region
		if ($restrict eq 'region')
		{
			# Push into coords
			push(@append, $region);

			# Append
			$append = 'AND LocateRegion=? ';
		}

		# City restriction
		elsif ($restrict eq 'city')
		{
			# Push into coords
			push(@append, $city);

			# Append
			$append = 'AND LocateCity= ? ';
		}
	
		#
		# Generate query
		# Contact restriction
		if (defined($param->{'SNEW'}))
		{
			# Add more onto append
			$append .= 'AND NOT EXISTS (SELECT Relation.RelationTarget
				FROM Relation
				WHERE Relation.RelationOwner=?
				  AND Relation.RelationTarget=Network.PuppetName)';

			# Push another record
			push(@append, $param->{'USER'});
		}



		#
		# Define how things are shown
		# This is here to search results
		unless ($sparam->{'FNC'} eq 'geo')
		{
			# Initialize
			$tpl_res = '';
		
			# Use the ingore feature already in place
			($count) = $database->DataGet("SELECT COUNT(*)
			  FROM Network
			 WHERE PuppeteerLogin <> ?
			   AND NOT EXISTS (SELECT PuppetIgnore.PuppetLogin
				FROM PuppetIgnore
				WHERE PuppetIgnore.PuppetIgnore='global'
				  AND PuppetIgnore.PuppeteerLogin=?
				  AND PuppetIgnore.PuppetLogin=Network.PuppeteerLogin)
			   AND NOT EXISTS (SELECT PuppetIgnore.PuppetName
				FROM PuppetIgnore
				WHERE PuppetIgnore.PuppetIgnore='single'
				  AND PuppetIgnore.PuppeteerLogin=?
				  AND PuppetIgnore.PuppetName=Network.PuppetName)
			   AND PhysicalGender = ?
			   AND SocialSeeking  = ?
			   AND PhysicalAge >= ?
			   AND PhysicalAge <= ?
			   AND LocateLat  > ?
			   AND LocateLong > ?
			   AND LocateLat  < ?
			   AND LocateLong < ? $append",

			 $param->{'USER'},
			 $param->{'USER'},
			 $param->{'USER'},
			 $looking,					# Inversed for search
			 $gender,					# Inversed for search
			 $from,
			 $to,
			 @append					# Complete coordinate list and misc
			);


			# Worth the work
			if ($count > 0)
			{
				# Offset calculation
				$offset = ((defined($sparam->{'CMP'})) && ($sparam->{'CMP'} =~ /^\d+$/))
				  ? ($sparam->{'CMP'} - 1) * $vanity->{'SetDisplay'}
				  : 0;

				
			
				# Nav requirements
				# Generate inline
				$inline = $param->EmbedInline($param->Flat());

				# Get that template
				$tpl_nav = Navigation(
					$tmpl,
					$vanity,
					$vanity->{'SetDisplay'},
					$count,
					$sparam->{'CMP'},
					$url,
					$inline
				 );


				# 	
				# Use the ingore feature already in place
				$database->Pull(\$statement, "SELECT
				  PuppeteerLogin       AS \"PuppeteerLogin\",
				  PuppeteerTimestamp   AS \"PuppeteerTimestamp\",
				  PuppetName           AS \"PuppetName\",
				  PuppetShort          AS \"PuppetShort\",
				  LocateCity           AS \"LocateCity\",
				  LocateRegion         AS \"LocateRegion\",
				  LocateCountry        AS \"LocateCountry\",
				  PhysicalGender       AS \"PhysicalGender\",
				  PhysicalAge          AS \"PhysicalAge\",
				  IntroCatchPhrase     AS \"IntroCatchPhrase\",
				  IntroDescription     AS \"IntroDescription\",
				  SocialLooking        AS \"SocialLooking\",
				  SocialSeeking        AS \"SocialSeeking\",
				  SocialInterests      AS \"SocialInterests\"
				  FROM Network
				 WHERE PuppeteerLogin <> ?
				   AND NOT EXISTS (SELECT PuppetIgnore.PuppetLogin
					FROM PuppetIgnore
					WHERE PuppetIgnore.PuppetIgnore='global'
					  AND PuppetIgnore.PuppeteerLogin=?
					  AND PuppetIgnore.PuppetLogin=Network.PuppeteerLogin)
				   AND NOT EXISTS (SELECT PuppetIgnore.PuppetName
					FROM PuppetIgnore
					WHERE PuppetIgnore.PuppetIgnore='single'
					  AND PuppetIgnore.PuppeteerLogin=?
					  AND PuppetIgnore.PuppetName=Network.PuppetName)
				   AND PhysicalGender = ?
				   AND SocialSeeking  = ?
				   AND PhysicalAge >= ?
				   AND PhysicalAge <= ?
				   AND LocateLat  > ?
				   AND LocateLong > ?
				   AND LocateLat  < ?
				   AND LocateLong < ? $append
				 ORDER BY PuppeteerTimestamp DESC
				 LIMIT $vanity->{SetDisplay}
				 OFFSET $offset",

				 $param->{'USER'},
				 $param->{'USER'},
				 $param->{'USER'},
				 $looking,				# Inversed for search
				 $gender,				# Inversed for search
				 $from,
				 $to,
				 @append				# Complete coordinate list and misc
				);


				# Do a quick check
				if ($res = $statement->fetchrow_hashref())
				{
					# Initialize
					$lst_int = '';


					# Pull list of interests
					($interest) = $database->DataGetSocialInterests($param->{'USER'});
					@interests  = split(/\s*\,\s*/, $interest);
			
					# Loop through
					do
					{
						# Match maker of interests
						# What are the matches
						foreach $interest (sort(@interests))
						{
							# Append to the list
							push(@ready, $tmpl->Pass('TagInterestsList', MNAME=>$interest))
							  if ($res->{'SocialInterests'} =~ /$interest/i);
						}

						# Generate accordingly
						$lst_int = join($vanity->{'TagInterestsSep'}, @ready);
						
						# Clean up
						splice(@ready, 0);
						

						# No go
						$lst_int = $vanity->{'TagInterestsNone'}
						  if ($lst_int eq '');


					  
						# Vanity shots
						# Pull value
						($lst_van) = $database->DataGetVanityDefault($res->{'PuppeteerLogin'});

						# Ensure definition
						$lst_van = (defined($lst_van))
						  ? $tmpl->Pass('TagSocialVanity',
							  MLINK  => $url . '/show/' . $res->{'PuppetShort'} . $inline,
						  	  MIMAGE => $lst_van
						     )
						  : $tmpl->Pass('TagSocialLackofVanity',
							  MLINK  => $url . '/show/' . $res->{'PuppetShort'} . $inline
						     );


						# Safety
						# Should always be assigned
						$descr = (defined($res->{'IntroDescription'}))
						  ? substr(DBI::neat($res->{'IntroDescription'}, 250), 1, -1)
						  : $vanity->{'TxtNoDescr'};
						$intro = ((defined($res->{'IntroCatchPhrase'})) && (length($res->{'IntroCatchPhrase'}) > 3))
						  ? $res->{'IntroCatchPhrase'}
						  : $vanity->{'TxtNoIntro'};


						#
						# Display template
						$tpl_res .= $tmpl->Pass('TmplSocialSearchList',
							LMAIN             => $url . '/show/' . $res->{'PuppetShort'} . $inline,
							MPUPPETNAME       => $res->{'PuppetName'},
							MLOCATECITY       => $res->{'LocateCity'},
							MLOCATEREGION     => $res->{'LocateRegion'},
							MLOCATECOUNTRY    => $res->{'LocateCountry'},
							MPHYSICALGENDER   => $res->{'PhysicalGender'},
							MPHYSICALAGE      => $res->{'PhysicalAge'},
							MSOCIALLOOKING    => $res->{'SocialLooking'},
							MSOCIALSEEKING    => $res->{'SocialSeeking'},
							MINTRODESCRIPTION => $descr,
							MINTROCATCHPHRASE => $intro,
							MVANITY           => $lst_van,
							LSTINTERESTS      => $lst_int
						 );
					
		
					} while ($res = $statement->fetchrow_hashref());
				}

				# Final fail point
				else
				{
					# Null results return
					$tpl_res = $vanity->{'TmplErrorEmpty'};
	
					# Nav bar is a no go
					$tpl_nav = '';
				}
			}

			# Not enough entries
			else
			{
				# Null results return
				$tpl_res = $vanity->{'TmplErrorEmpty'};
	
				# Nav bar is a no go
				$tpl_nav = '';
			}
		  
			#
			# General visual map link
			$toggle = $tmpl->Pass('TagNavDis',
				MLINK  =>  $url . '/geo' . $inline,
				MCOUNT => $vanity->{'TxtVisual'}
			 );
			

			#
			# Display
			print $menu->Rewrite($database->DocumentGetHeader()), "\n";

			# Form information
			print $cgi->start_form(-action=>$url . '/search/');
			print $param->EmbedNormal($param->Flat());
	
			# Display template
			$tmpl->Show('TmplSocialSearch',
				LGEO    => $url . '/geo' . $inline, 
				MSEARCH => $tpl_search,
				MVISUAL => $toggle,
				LSTNAV  => $tpl_nav,
				LSTRES  => $tpl_res
			 );
	
			# Form end
			print $cgi->end_form(), "\n";
		
			# Display footer
			print $database->DocumentGetFooter(), "\n";
		}

		#
		# Display geographical mappings
		else
		{
			# 	
			# Use the ignore feature already in place
			$database->Pull(\$statement, "SELECT
				  PuppeteerLogin       AS \"PuppeteerLogin\",
				  PuppetName           AS \"PuppetName\",
				  PuppetShort          AS \"PuppetShort\",
				  LocateLat            AS \"LocateLat\",
				  LocateLong           AS \"LocateLong\",
				  PhysicalGender       AS \"PhysicalGender\",
				  PhysicalAge          AS \"PhysicalAge\",
				  IntroCatchPhrase     AS \"IntroCatchPhrase\",
				  SocialLooking        AS \"SocialLooking\",
				  SocialSeeking        AS \"SocialSeeking\"
				  FROM Network
				 WHERE PuppeteerLogin <> ?
				   AND NOT EXISTS (SELECT PuppetIgnore.PuppetLogin
					FROM PuppetIgnore
					WHERE PuppetIgnore.PuppetIgnore='global'
					  AND PuppetIgnore.PuppeteerLogin=?
					  AND PuppetIgnore.PuppetLogin=Network.PuppeteerLogin)
				   AND NOT EXISTS (SELECT PuppetIgnore.PuppetName
					FROM PuppetIgnore
					WHERE PuppetIgnore.PuppetIgnore='single'
					  AND PuppetIgnore.PuppeteerLogin=?
					  AND PuppetIgnore.PuppetName=Network.PuppetName)
				   AND PhysicalGender = ?
				   AND SocialSeeking  = ?
				   AND PhysicalAge >= ?
				   AND PhysicalAge <= ?
				   AND LocateLat  > ?
				   AND LocateLong > ?
				   AND LocateLat  < ?
				   AND LocateLong < ? $append
				 ORDER BY PuppeteerTimestamp
				 LIMIT $vanity->{SetDisplayMax}",

			 $param->{'USER'},
			 $param->{'USER'},
			 $param->{'USER'},
			 $looking,						# Inversed for search
			 $gender,						# Inversed for search
			 $from,
			 $to,
			 @append						# Complete coordinate list and misc
			);


			# Generate inline
			$inline = $param->EmbedInline($param->Flat());

			# Loop through
			while ($res = $statement->fetchrow_hashref())
			{
				# Vanity shots
				# Pull value
				($lst_van) = $database->DataGetVanityDefault($res->{'PuppeteerLogin'});
	
				# Ensure definition
				$lst_van = (defined($lst_van))
				  ? $tmpl->Pass('TagGeoVanity',
				  	  MIMAGE => $lst_van
				     )
				  : $vanity->{'TagGeoLackofVanity'};

				# Safety
				# Should always be assigned
				$intro = ((defined($res->{'IntroCatchPhrase'})) && (length($res->{'IntroCatchPhrase'}) > 3))
				  ? $res->{'IntroCatchPhrase'}
				  : $vanity->{'TxtNoIntro'};


				# Privacy
				# Randomize
				$rlat = (int(rand() * 10) / 1000) + $res->{'LocateLat'};
				$rlon = (int(rand() * 10) / 1000) + $res->{'LocateLong'};
				     

				#
				# Display template
				$tpl_res .= $tmpl->Pass('TmplSocialGeoList',
					MLINK             => $url . '/show/' . $res->{'PuppetShort'} . $inline,
					MLAT              => $rlat,
					MLON              => $rlon,
					MPUPPETNAME       => $res->{'PuppetName'},
					MPHYSICALGENDER   => $res->{'PhysicalGender'},
					MPHYSICALAGE      => $res->{'PhysicalAge'},
					MSOCIALLOOKING    => $res->{'SocialLooking'},
					MSOCIALSEEKING    => $res->{'SocialSeeking'},
					MINTROCATCHPHRASE => $intro,
					MVANITY           => $lst_van
				 );
			}


			#
			# Generate button
			$toggle = $tmpl->Pass('TagNavDis',
				MLINK  => $url . '/search' . $inline,
				MCOUNT => $vanity->{'TxtReturn'}
			 );
				
			# Display template
			print $menu->Rewrite(
			  $tmpl->Pass('TmplSocialGeo',
				MGMKEY  => $database->{'SYS'}{'SetGoogleMaps'}, 
				MSLAT   => $lat,
				MSLON   => $lon,
				LSTRES  => $tpl_res,
				MLINKS  => $toggle,
			 ));
		}
	}

	# Warn user
	else
	{
		# Display header
		print $menu->Rewrite($database->DocumentGetHeader()), "\n";

		# Display error message
		$tmpl->Show('TmplErrorProfile');
		
		# Display footer
		print $database->DocumentGetFooter(), "\n";
	}
}


#####################
# Social Show
#
# As the name implies, this will show a user profile as seen by the public
# along with IFF (Identify Friend Foe) information.

sub SocialShow
{
	#####################
	# Data members

	my $database = shift;					# Database handler
	my $cgi      = shift;					# Common gateway interface
	my $param    = shift;					# Paramater handler
	my $tmpl     = shift;					# Template handle
	my $menu     = shift;					# Menu handler
	my $sparam   = shift;					# Parameter line hash
	my $vanity   = shift;					# Gallery hash

	my $filter;						# Filter handle
	
	my $s_pro;						# Profile statement handle
	my $r_pro;						# Profile results handle
	my $s_van;						# Vanity statement handle
	my $r_van;						# Vanity results handle

	my $url      = $cgi->url(-absolute=>1);			# Self referencing link
	my $inline;						# Embeded parameters
	my $ibio;						# Embeded biographical link
	my $link;						# Completed link
	
	my $def;						# Default handle
	my $date;						# Data aspect for comparison
	my $int;						# Current interests

	my $key;						# Current key
	my $name;						# New name
	my $val;						# Current value
	
	my $source;						# Source of contact
	my $liason;						# IFF Status
	
	my @interests;						# Interests handling
	
	my %macros;						# Hash of macros and values


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

	# Prepare for query
	# Make use of the Network table
	$database->Pull(\$s_pro, "SELECT
		  PuppeteerLogin       AS \"PuppeteerLogin\",
		  PuppeteerTimestamp   AS \"PuppeteerTimestamp\",
		  PuppetName           AS \"PuppetName\",
		  PuppetShort          AS \"PuppetShort\",
		  PuppetGummy          AS \"PuppetGummy\",
		  LocateCity           AS \"LocateCity\",
		  LocateRegion         AS \"LocateRegion\",
		  LocateCountry        AS \"LocateCountry\",
		  PhysicalGender       AS \"PhysicalGender\",
		  PhysicalAge          AS \"PhysicalAge\",
		  PhysicalBirthDate    AS \"PhysicalBirthDate\",
		  PhysicalBirthDay     AS \"PhysicalBirthDay\",
		  PhysicalBirthMonth   AS \"PhysicalBirthMonth\",
		  PhysicalBirthYear    AS \"PhysicalBirthYear\",
		  PhysicalHeight       AS \"PhysicalHeight\",
		  PhysicalBodyType     AS \"PhysicalBodyType\",
		  PhysicalEyeColour    AS \"PhysicalEyeColour\",
		  PhysicalHairColour   AS \"PhysicalHairColour\",
		  PhysicalEthnicity    AS \"PhysicalEthnicity\",
		  IntroCatchPhrase     AS \"IntroCatchPhrase\",
		  IntroDescription     AS \"IntroDescription\",
		  IntroFirstDate       AS \"IntroFirstDate\",
		  SocialLooking        AS \"SocialLooking\",
		  SocialSeeking        AS \"SocialSeeking\",
		  SocialMarital        AS \"SocialMarital\",
		  SocialChildrenHave   AS \"SocialChildrenHave\",
		  SocialChildrenWant   AS \"SocialChildrenWant\",
		  SocialInterests      AS \"SocialInterests\",
		  SocialReligion       AS \"SocialReligion\",
		  SocialProfession     AS \"SocialProfession\",
		  ViceSmoke            AS \"ViceSmoke\",
		  ViceDrink            AS \"ViceDrink\"
		 FROM Network
		WHERE PuppetShort=?", $sparam->{'CMP'});

	# User must exist
	if ($r_pro = $s_pro->fetchrow_hashref())
	{

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


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

		
		#
		# Identify Friend Foe
		($source, $liason) = Toggle($database, $param, $r_pro);

		# Favs
		($macros{'MLIASONFRIENDS'}) = $database->DataGet("SELECT COUNT(*)
			FROM Liason
			WHERE LiasonTarget=?
			  AND LiasonRelation=?",

		 $r_pro->{'PuppeteerLogin'}, 'friend'
	 	); 


		# Gummy handling
		# Make the gummy its own element
		$macros{'MBIOGUMMY'} = $r_pro->{'PuppetGummy'};

		# Biographical link
		$ibio = '/' . $r_pro->{'PuppetShort'} . '/system' . $inline;
		
		# Biographical link
		$macros{'LBIOLINK'} = $database->{'SYS'}{'LnkIntBio'} . $ibio;
		
		

		# Establish Source macro
		# For tag maintenance issues:
		#   TagLiasonSOwner
		#   TagLiasonSTarget
		#   TagLiasonSNil
		$macros{'MLIASONSOURCE'}    = $vanity->{'TagLiasonS' . ucfirst($source)};

		# Mail link
		$macros{'LLIASONMAIL'}     = $database->{'SYS'}{'LnkIntMail'} . $param->EmbedInline(
			USER   => $param->{'USER'},
			CRYPT  => $param->{'CRYPT'},
			MFROM  => $def,
			MRCPT  => $r_pro->{'PuppetName'},
			MSUBJ  => $vanity->{'TxtSubject'},
			WRITE  => 'true'
		 );


		# Establish IFF macro
		# Generate link
		$link = $url . '/show/' . $r_pro->{'PuppetShort'};
		
		# Generate macro
		$macros{'LLIASONRELATION'} = $link . $param->EmbedInline(
			USER   => $param->{'USER'},
			CRYPT  => $param->{'CRYPT'},
			TOGGLE => 'true'
		 );

		# Generate macro
		# For tag maintenance issues:
		#   TagLiasonRFoe
		#   TagLiasonRFriend
		#   TagLiasonRNeutral
		#   TagLiasonRInitial
		$macros{'MLIASONRELATION'}  = $vanity->{'TagLiasonR' . ucfirst($liason)};


		#
		# Astrology extras
		# Arrange date
		$date = $r_pro->{'PhysicalBirthMonth'} . '-' . $r_pro->{'PhysicalBirthDay'};
		
		# European astrological signs
		($macros{'MASTROSIGN'})    = $database->DataGetSocialAstro($date, $date);

		# Chinese symbols and elements
		($macros{'MASTROANIMAL'},
		 $macros{'MASTROELEMENT'}) = $database->DataGetSocialSign($r_pro->{'PhysicalBirthDate'}, $r_pro->{'PhysicalBirthDate'});

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


		#
		# Interests generation
		# Split appart
		@interests = split(/\s*\,\s*/, $r_pro->{'SocialInterests'});

		# Set accordingly
		$macros{'MSOCIALINTERESTS'} = '';

		# Loop through
		foreach $int (sort(@interests))
		{
			# Generate link
			$link = $url . '/search/interests/' . $int . $inline;
			
			# Append
			$macros{'MSOCIALINTERESTS'} .= $tmpl->Pass('TmplSocialInterests',
				MNAME => $int,
				MLINK => $link
			 );
		}


		#
		# Vanity shots
		# Initialize values accordingly
		$macros{'MVANITYDEF'} = '';
		$macros{'MVANITYSUB'} = '';


		# Time to find
		$database->Pull(\$s_van, "SELECT
			 VanityID      AS \"VanityID\",
			 VanityPath    AS \"VanityPath\",
			 VanityThumb   AS \"VanityThumb\",
			 VanityWidth   AS \"VanityWidth\",
			 VanityHeight  AS \"VanityHeight\",
			 VanityDefault AS \"VanityDefault\"
			FROM Vanity
			WHERE PuppeteerLogin=?
			  AND VanityPublic=?
			ORDER BY VanityID", $r_pro->{'PuppeteerLogin'}, 'yes');

		# Loop through
		while ($r_van = $s_van->fetchrow_hashref())
		{
			# Generate link
			$link = $database->{'SYS'}{'LnkIntVanity'} . $inline . '&VID=' . $r_van->{'VanityID'};

			# Determine type
			# Default
			if ($r_van->{'VanityDefault'} eq 'yes')
			{
				# Set the template accordingly
				$macros{'MVANITYDEF'}  = $tmpl->Pass('TmplSocialVanityDef',
					MLINK   => $link,
					MIMG    => $r_van->{'VanityPath'},
					MWIDTH  => $r_van->{'VanityWidth'},
					MHEIGHT => $r_van->{'VanityHeight'}
				 );
			}

			# Append all new templates
			$macros{'MVANITYSUB'} .= $tmpl->Pass('TmplSocialVanitySub',
				MLINK   => $link,
				MIMG    => $r_van->{'VanityThumb'}
			 );
		}

		# Finish cleanly
		$s_van->finish();


		#
		# Main macro generations
		# Loop through
		foreach $key (sort(keys(%{$r_pro})))
		{
			# Certain exceptions
			next if ($key =~ /^SocialInterests/);

			
			# Generate name
			$name = 'M' . uc($key);

			# Filter if necessary
			$val = ($key =~ /^Intro.*/)
			  ? $filter->Inject($database, $r_pro->{$key})
			  : $r_pro->{$key};

			# This should add in spacers
			$val =~ s/\n/<br>/gs;

			# Assign and move on
			$macros{$name} = $val;
		}


		# Display template
		print $menu->Rewrite(
		  $tmpl->Pass('TmplSocial', %macros)
	 	 );

	}


	# Can't leave a blank screen
	else
	{
		# Display header
		print $menu->Rewrite($database->DocumentGetHeader()), "\n";

		# Display error message
		$tmpl->Show('TmplErrorInvalid');
		
		# Display footer
		print $database->DocumentGetFooter(), "\n";
	}

	
	# Finish things off
	$s_pro->finish();
}


#####################
# Toggle IFF
#
# This component is used to toggle the IFF standing when a user clicks on it and
# can be used to filter searched based on criteria.

sub Toggle
{
	#####################
	# Data members

	my $database = shift;					# Database handler
	my $param    = shift;					# Paramater handler
	my $res      = shift;					# Results handle

	my $liason;						# Dangerous liasons
	my $source;						# Source of contact

	
	#####################
	# Program area
	
	# Fetch
	($source, $liason) = $database->DataGetLiason($param->{'USER'}, $res->{'PuppeteerLogin'});


	# Check for changes
	unless (defined($source))
	{
		# Everything works
		eval
		{
			# No relation in place
			$database->Write("INSERT INTO Liason
				(LiasonOwner,
				 LiasonTarget,
				 LiasonSource,
				 LiasonRelation)
				VALUES(?,?,'nil','neutral')",

			 $param->{'USER'},
			 $res->{'PuppeteerLogin'}
			);


			#
			# Check for equivilant link
			($source, $liason) = $database->DataGetLiason($res->{'PuppeteerLogin'}, $param->{'USER'});
		
			# Another bout of changes
			unless (defined($source))
			{
				# No relation in place
				$database->Write("INSERT INTO Liason
					(LiasonOwner,
					 LiasonTarget,
					 LiasonSource,
					 LiasonRelation)
					VALUES(?,?,'nil','neutral')",

				 $res->{'PuppeteerLogin'},
				 $param->{'USER'}
				);
			}

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

		# Error generated
		if ($@)
		{
			# Rollback
			$database->Rollback();

			# Warn users
			warn("There was an error setting Liasons: ", $@);
		}

		# Return with source and liason
		return 'nil', 'initial';
	}

	# Toggle has been requested
	if (defined($param->{'TOGGLE'}))
	{
		# Check of current status and adapt
		if    ($liason eq 'neutral') { $liason = 'friend'; }
		elsif ($liason eq 'friend')  { $liason = 'foe'; }
		elsif ($liason eq 'foe')     { $liason = 'neutral'; }

		# Update accordingly
		$database->Quick("UPDATE Liason
			  SET LiasonRelation=?
			WHERE LiasonOwner=?
			  AND LiasonTarget=?",

		 $liason,
		 $param->{'USER'},
		 $res->{'PuppeteerLogin'}
		);
	}

	# Return information
	return $source, $liason;
}
