#!/usr/bin/perl -w
# This code is a part of Slash, and is released under the GPL.
# Copyright 1997-2001 by Open Source Development Network. See README
# and COPYING for more information, or see http://slashcode.com/.
# $Id: users.pl,v 1.27.2.143 2002/01/05 18:47:05 pudge Exp $

use strict;
use Date::Manip qw(UnixDate DateCalc);
use Digest::MD5 'md5_hex';
use Slash;
use Slash::Display;
use Slash::Utility;

#################################################################
sub main {
	my $slashdb = getCurrentDB();
	my $constants = getCurrentStatic();
	my $user = getCurrentUser();
	my $form = getCurrentForm();
	my $formname = $0;
	$formname =~ s/.*\/(\w+)\.pl/$1/;

	my $error_flag = 0;
	my $formkey = $form->{formkey};

	my $suadmin_flag = $user->{seclev} >= 10000 ? 1 : 0 ;
	my $postflag = $user->{state}{post};
	my $op = lc($form->{op});

	# savepasswd is a special case, because once it's called, you
	# have to reload the form, and you don't want to do any checks if
	# you've just saved.
	my $savepass_flag = $op eq 'savepasswd' ? 1 : 0 ;

	# my $note = [ split /\n+/, $form->{note} ] if defined $form->{note};

	my $note;

	my $ops = {
		admin		=>  {
			function 	=> \&adminDispatch,
			seclev		=> 100,
			formname	=> $formname,
			checks		=> [],
		},
		userlogin	=>  {
			function	=> \&showInfo,
			seclev		=> 1,
			formname	=> $formname,
			checks		=> [],
		},
		userinfo	=>  {
			function	=> \&showInfo,
			#I made this change, not all sites are going to care. -Brian
			seclev		=> $constants->{users_show_info_seclev},
			formname	=> $formname,
			checks		=> [],
		},
		display	=>  {
			function	=> \&showInfo,
			#I made this change, not all sites are going to care. -Brian
			seclev		=> $constants->{users_show_info_seclev},
			formname	=> $formname,
			checks		=> [],
		},
		savepasswd	=> {
			function	=> \&savePasswd,
			seclev		=> 1,
			post		=> 1,
			formname	=> $formname,
			checks		=>
			[ qw (max_post_check valid_check
				formkey_check regen_formkey) ],
		},
		saveuseradmin	=> {
			function	=> \&saveUserAdmin,
			seclev		=> 10000,
			post		=> 1,
			formname	=> $formname,
			checks		=> [],
		},
		savehome	=> {
			function	=> \&saveHome,
			seclev		=> 1,
			post		=> 1,
			formname	=> $formname,
			checks		=>
			[ qw (valid_check
				formkey_check regen_formkey) ],
		},
		savecomm	=> {
			function	=> \&saveComm,
			seclev		=> 1,
			post		=> 1,
			formname	=> $formname,
			checks		=>
			[ qw (valid_check
				formkey_check regen_formkey) ],
		},
		saveuser	=> {
			function	=> \&saveUser,
			seclev		=> 1,
			post		=> 1,
			formname	=> $formname,
			checks		=>
			[ qw (valid_check
				formkey_check regen_formkey) ],
		},
		changepasswd	=> {
			function	=> \&changePasswd,
			seclev		=> 1,
			formname	=> $formname,
			checks		=> $savepass_flag ? [] :
			[ qw (generate_formkey) ],
		},
		editmiscopts	=> {
			function	=> \&editMiscOpts,
			seclev		=> 1,
			formname	=> $formname,
			checks		=> [ ],
		},
		savemiscopts	=> {
			function	=> \&saveMiscOpts,
			seclev		=> 1,
			formname	=> $formname,
			checks		=> [ ],
		},
		edituser	=> {
			function	=> \&editUser,
			seclev		=> 1,
			formname	=> $formname,
			checks		=>
			[ qw (generate_formkey) ],
		},
		authoredit	=> {
			function	=> \&editUser,
			seclev		=> 10000,
			formname	=> $formname,
			checks		=> [],
		},
		edithome	=> {
			function	=> \&editHome,
			seclev		=> 1,
			formname	=> $formname,
			checks		=>
			[ qw (generate_formkey) ],
		},
		editcomm	=> {
			function	=> \&editComm,
			seclev		=> 1,
			formname	=> $formname,
			checks		=>
			[ qw (generate_formkey) ],
		},
		newuser		=> {
			function	=> \&newUser,
			seclev		=> 0,
			formname	=> $formname,
			checks		=>
			[ qw (max_post_check valid_check
				formkey_check regen_formkey) ],
		},
		newuseradmin	=> {
			function	=> \&newUserForm,
			seclev		=> 10000,
			formname	=> $formname,
			checks		=> [],
		},
		previewbox	=> {
			function	=> \&previewSlashbox,
			seclev		=> 0,
			formname	=> $formname,
			checks		=> [],
		},
		mailpasswd	=> {
			function	=> \&mailPasswd,
			seclev		=> 0,
			formname	=> $formname,
			checks		=>
			[ qw (max_post_check valid_check
				interval_check formkey_check ) ],
		},
		validateuser	=> {
			function	=> \&validateUser,
			seclev		=> 1,
			formname	=> $formname,
			checks		=> ['regen_formkey'],
		},
		userclose	=>  {
			function	=> \&displayForm,
			seclev		=> 0,
			formname	=> $formname,
			checks		=> [],
		},
		newuserform	=> {
			function	=> \&displayForm,
			seclev		=> 0,
			formname	=> $formname,
			checks		=>
			[ qw (max_post_check generate_formkey) ],
		},
		mailpasswdform 	=> {
			function	=> \&displayForm,
			seclev		=> 0,
			formname	=> $formname,
			checks		=>
			[ qw (max_post_check generate_formkey) ],
		},
		displayform	=> {
			function	=> \&displayForm,
			seclev		=> 0,
			formname	=> $formname,
			checks		=>
			[ qw (generate_formkey) ],
		},
		listreadonly => {
			function	=> \&listReadOnly,
			seclev		=> 100,
			formname	=> $formname,
			checks		=> [],
		},
		listbanned => {
			function	=> \&listBanned,
			seclev		=> 100,
			formname	=> $formname,
			checks		=> [],
		},
		topabusers 	=> {
			function	=> \&topAbusers,
			seclev		=> 100,
			formname	=> $formname,
			checks		=> [],
		},
		listabuses 	=> {
			function	=> \&listAbuses,
			seclev		=> 100,
			formname	=> $formname,
			checks		=> [],
		},
	} ;
	$ops->{default} = $ops->{displayform};

	if ($form->{op} && ! defined $ops->{$op}) {
		$note .= getError('bad_op', { op => $form->{op}}, 0, 1);
		$op = isAnon($user->{uid}) ? 'userlogin' : 'userinfo'; 
	}

	if ($op eq 'userlogin' && ! isAnon($user->{uid})) {
		# why disable "returnto" ?  What's going on? -- pudge
		# no one responded, so i am changing it back
# 		my $refer = URI->new_abs($constants->{rootdir},
		my $refer = URI->new_abs($form->{returnto} || $constants->{rootdir},
			$constants->{absolutedir});
		# Tolerate redirection with or without a "www.", this is a little
		# sloppy but it may help avoid a subtle misbehavior someday. -- anonymous
		# What misbehavior? It looks to me like it could break a site.
		# www.foo.com is not necessarily the same as foo.com.  Please explain. -- pudge
		my $site_domain = $constants->{basedomain};
		$site_domain =~ s/^www\.//;
		$site_domain =~ s/:.+$//;	# strip port, if available

		my $refer_host = $refer->host();
		$refer_host =~ s/^www\.//;

		if ($site_domain eq $refer_host) {
			# Cool, it goes to our site.  Send the user there.
			$refer = $refer->as_string;
		} else {
			# Bogus, it goes to another site.  op=userlogin is not a
			# URL redirection service, sorry.
			$refer = $constants->{rootdir};
		}
		redirect($refer);
		return;

	} elsif ($op eq 'savepasswd') {
		my $error_flag = 0;
		if ($user->{seclev} < 100) {
			for my $check (@{$ops->{savepasswd}{checks}}) {
				# the only way to save the error message is to pass by ref
				# $note and add the message to note (you can't print it out
				#  before header is called)
				$error_flag = formkeyHandler($check, $formname, $formkey, \$note);
				last if $error_flag;
			}
		}

		if (! $error_flag) {
			$error_flag = savePasswd(\$note) ;
		}
		# change op to edituser and let fall through;
		# we need to have savePasswd set the cookie before
		# header() is called -- pudge
		if ($user->{seclev} < 100 && ! $error_flag) {
			$slashdb->updateFormkey($formkey, length($ENV{QUERY_STRING}));
		}
		$op = $error_flag ? 'changepasswd' : 'userinfo';
		$form->{userfield} = $user->{uid};
	}

	header(getMessage('user_header'), $form->{section});
	print getMessage('note', { note => $note }) if defined $note;
	print createMenu($formname) if ! $user->{is_anon};

	$op = 'userinfo' if (! $form->{op} && ($form->{uid} || $form->{nick}));
	$op ||= isAnon($user->{uid}) ? 'userlogin' : 'userinfo';

	if ($user->{is_anon} && $ops->{$op}{seclev} > 0) {
		$op = 'default';
	} elsif ($user->{seclev} < $ops->{$op}{seclev}) {
		$op = 'userinfo';
	}

	if ($ops->{$op}{post} && !$postflag) {
		$op = isAnon($user->{uid}) ? 'default' : 'userinfo';
	}

	if ($user->{seclev} < 100) {
		for my $check (@{$ops->{$op}{checks}}) {
			last if $op eq 'savepasswd';
			$error_flag = formkeyHandler($check, $formname, $formkey);
			$ops->{$op}{update_formkey} = 1 if $check eq 'formkey_check';
			last if $error_flag;
		}
	}

	errorLog("users.pl error_flag '$error_flag'") if $error_flag;

	# call the method
	my $retval = $ops->{$op}{function}->() if ! $error_flag;
	if ($op eq 'mailpasswd' && $retval) {
		$ops->{$op}{update_formkey} = 0;
	}

	if ($ops->{$op}{update_formkey} && $user->{seclev} < 100 && ! $error_flag) {
		# successful save action, no formkey errors, update existing formkey
		# why assign to an unused variable? -- pudge
		my $updated = $slashdb->updateFormkey($formkey, length($ENV{QUERY_STRING}));
	}
	# if there were legit error levels returned from the save methods
	# I would have it clear the formkey in case of an error, but that
	# needs to be sorted out later
	# else { resetFormkey($formkey); }

	writeLog($user->{nickname});
	footer();
}

#################################################################
sub checkList {
	my $string = shift;
	$string = substr($string, 0, -1);

	$string =~ s/[^\w,-]//g;
	my @e = split m/,/, $string;
	$string = sprintf "'%s'", join "','", @e;

	if (length($string) > 254) {
		print getError('checklist_err');
		$string = substr($string, 0, 255);
		$string =~ s/,'??\w*?$//g;
	} elsif (length $string < 3) {
		$string = '';
	}

	return $string;
}

#################################################################
sub previewSlashbox {
	my $slashdb = getCurrentDB();
	my $constants = getCurrentStatic();
	my $user = getCurrentUser();
	my $form = getCurrentForm();

	my $block = $slashdb->getBlock($form->{bid}, ['title', 'block', 'url']);
	my $is_editable = $user->{seclev} >= 1000;

	my $title = getTitle('previewslashbox_title', { blocktitle => $block->{title} });
	slashDisplay('previewSlashbox', {
		width		=> '100%',
		title		=> $title,
		block 		=> $block,
		is_editable	=> $is_editable,
	});

	print portalbox($constants->{fancyboxwidth}, $block->{title},
		$block->{block}, '', $block->{url});
}

#################################################################
sub newUserForm {
	my $user = getCurrentUser();
	my $suadmin_flag = $user->{seclev} >= 10000;
	my $title = getTitle('newUserForm_title');
	slashDisplay('newUserForm', { title => $title, suadmin_flag => $suadmin_flag})
}

#################################################################
sub newUser {
	my $slashdb = getCurrentDB();
	my $form = getCurrentForm();
	my $user = getCurrentUser();
	my $title;
	my $suadmin_flag = $user->{seclev} >= 10000 ? 1 : 0;

	# Check if User Exists
	$form->{newusernick} = fixNickname($form->{newusernick});
	(my $matchname = lc $form->{newusernick}) =~ s/[^a-zA-Z0-9]//g;

	if (!$form->{email} || $form->{email} !~ /\@/ || $slashdb->existsEmail($form->{email})) {
		print getError('emailexists_err', 0, 1);
		return;

	} elsif ($matchname ne '' && $form->{newusernick} ne '') {
		my $uid;
		my $rootdir = getCurrentStatic('rootdir', 'value');

		if ($uid = $slashdb->createUser($matchname, $form->{email}, $form->{newusernick})) {

			$title = getTitle('newUser_title');

			$form->{pubkey} = strip_nohtml($form->{pubkey}, 1);
			print getMessage('newuser_msg', { 
				suadmin_flag => $suadmin_flag, 
				title => $title, 
				uid => $uid });

			mailPasswd($uid);

			return;
		} else {
			$slashdb->resetFormkey($form->{formkey});	
			print getError('duplicate_user', { nick => $form->{usernick} });
			return;
		}

	} else {
		print getError('duplicate_user', { nick => $form->{usernick} });
			return;
	}
}

#################################################################
sub mailPasswd {
	my($uid) = @_;

	my $slashdb = getCurrentDB();
	my $form = getCurrentForm();

	if (! $uid) {
		if ($form->{unickname} =~ /\@/) {
			$uid = $slashdb->getUserEmail($form->{unickname});

		} elsif ($form->{unickname} =~ /^\d+$/) {
			my $tmpuser = $slashdb->getUser($form->{unickname}, ['uid']);
			$uid = $tmpuser->{uid};

		} else {
			$uid = $slashdb->getUserUID($form->{unickname});
		}
	}

	unless ($uid) {
		print getError('mailpasswd_notmailed_err');
		$slashdb->resetFormkey($form->{formkey});	
		$form->{op} = 'mailpasswdform';
		displayForm();
		return(1);
	}
	my $user_edit = $slashdb->getUser($uid, ['nickname', 'realemail']);
	my $newpasswd = $slashdb->getNewPasswd($uid);
	my $tempnick = fixparam($user_edit->{nickname});

	my $emailtitle = getTitle('mailPassword_email_title', {
		nickname	=> $user_edit->{nickname}
	}, 1);

	my $msg = getMessage('mailpasswd_msg', {
		newpasswd	=> $newpasswd,
		tempnick	=> $tempnick
	}, 1);

	doEmail($uid, $emailtitle, $msg) if $user_edit->{nickname};
	print getMessage('mailpasswd_mailed_msg', { name => $user_edit->{nickname} });
}

#################################################################
# arhgghgh. I love torture. I love pain. This subroutine satisfies
# these needs of mine
sub showInfo {
	my($id) = @_;

	my $slashdb = getCurrentDB();
	my $form = getCurrentForm();
	my $constants = getCurrentStatic();
	my $user = getCurrentUser();

	my $admin_flag = ($user->{is_admin}) ? 1 : 0;
	my($title, $admin_block, $fieldkey) = ('', '', '');
	my $comments = undef;
	my $commentcount = 0;
	my $commentstruct = [];
	my $requested_user = {};

	my($points, $lastgranted, $nickmatch_flag, $uid, $nick);
	my($mod_flag, $karma_flag, $n) = (0, 0, 0);

	if (! $id && ! $form->{userfield}) {
		if ($form->{uid} && ! $id) {
			$fieldkey = 'uid';
			($uid, $id) = ($form->{uid}, $form->{uid});
			$requested_user = isAnon($uid) ? $user : $slashdb->getUser($id);
			$nick = $requested_user->{nickname};
			$form->{userfield} = $nick if $admin_flag;

		} elsif ($form->{nick} && ! $id) {
			$fieldkey = 'nickname';
			($nick, $id) = ($form->{nick}, $form->{nick});
			$uid = $slashdb->getUserUID($id);
			if (isAnon($uid)) {
				$requested_user = $user;
				($nick, $uid, $id) = @{$user}{qw(nickname uid nickname)};
			} else {
				$requested_user = $slashdb->getUser($uid);
			}
			$form->{userfield} = $uid if $admin_flag;

		} else {
			$fieldkey = 'uid';
			($id, $uid) = ($user->{uid}, $user->{uid});
			$requested_user = $slashdb->getUser($uid);
			$form->{userfield} = $uid if $admin_flag;
		}

		# no can do boss-man
		if (isAnon($uid)) {
			return displayForm();
		}

	} elsif ($user->{is_admin}) {
		$id ||= $form->{userfield} ? $form->{userfield} : $user->{uid};
		if ($id =~ /^\d+$/) {
			$fieldkey = 'uid';
			$requested_user = $slashdb->getUser($id);
			$uid = $requested_user->{uid};
			$nick = $requested_user->{nickname};
			if ((my $conflict_id = $slashdb->getUserUID($id)) && $form->{userinfo}) {
				slashDisplay('showInfoConflict', { op => 'userinfo', id => $uid, nick => $nick, conflict_id => $conflict_id});
				return(1);
			}

		} elsif (length($id) == 32) {
			$fieldkey = 'md5id';
			$requested_user->{nonuid} = 1;
			$requested_user->{md5id} = $id;

		} elsif ($id =~ /^(\d{1,3}\.\d{1,3}.\d{1,3}\.0)$/ 
				|| $id =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3})\.?$/) {
			$fieldkey = 'subnetid';
			$requested_user->{subnetid} = $1; 
			$requested_user->{subnetid} .= '.0' if $requested_user->{subnetid} =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}$/; 
			$requested_user->{nonuid} = 1;
			$requested_user->{subnetid} = md5_hex($requested_user->{subnetid});

		} elsif ($id =~ /^([\d+\.]+)$/) {
			$fieldkey = 'ipid';
			$requested_user->{nonuid} = 1;
			$id ||= $1;
			$requested_user->{ipid} = md5_hex($1);

		} else {  # go by nickname, but make it by uid
			$fieldkey = 'uid';
			$id = $uid = $slashdb->getUserUID($id);
			$requested_user = $slashdb->getUser($uid);
			$nick = $requested_user->{nickname};
		}
		
	} else {
		$fieldkey = 'uid';
		($id, $uid) = ($user->{uid}, $user->{uid});
		$requested_user = $slashdb->getUser($uid);
	}

	my $comments_wanted = $user->{show_comments_num}
		|| $constants->{user_comment_display_default};
	my $min_comment = $form->{min_comment} || 0;
	# haven't decided whether ordinary users get this yet
	$min_comment = 0 unless $admin_flag;

	if ($requested_user->{nonuid}) {
		$requested_user->{fg} = $user->{fg};
		$requested_user->{bg} = $user->{bg};

		my $netid;
		if ($requested_user->{ipid}) {
			$netid = $requested_user->{ipid} ;

		} elsif ($requested_user->{md5id}) {
			$netid = $requested_user->{md5id} ;

		} else {
			$netid = $requested_user->{subnetid} ;
		}
		my $netid_vis = $netid;
		$netid_vis = substr($netid, 0, $constants->{id_md5_vislength})
			if $constants->{id_md5_vislength};

		$title = getTitle('user_netID_user_title', {
			id => $id,
			md5id => $netid,
			md5id_vis => $netid_vis,
		});

		
		$admin_block = getUserAdmin($netid, $fieldkey, 1, 0) if $admin_flag;

		if ($form->{fieldname}) {
			if ($form->{fieldname} eq 'ipid') {
				$commentcount = $slashdb->countCommentsByIPID(
					$netid, $comments_wanted, $min_comment);
				$comments = $slashdb->getCommentsByIPID(
					$netid, $comments_wanted, $min_comment);
			} elsif ($form->{fieldname} eq 'subnetid') {
				$commentcount = $slashdb->countCommentsBySubnetID(
					$netid, $comments_wanted, $min_comment);
				$comments = $slashdb->getCommentsBySubnetID(
					$netid, $comments_wanted, $min_comment);
			}
		}
		if (!defined($comments)) {
			# Last resort; here for backwards compatibility mostly.
			$commentcount = $slashdb->countCommentsByIPIDOrSubnetID(
				$netid, $comments_wanted, $min_comment);
			$comments = $slashdb->getCommentsByIPIDOrSubnetID(
				$netid, $comments_wanted, $min_comment
			);
		}

	} else {
		$admin_block = getUserAdmin($id, $fieldkey, 1, 1) if $admin_flag;

		$commentcount =
			$slashdb->countCommentsByUID($requested_user->{uid});
		$comments = $slashdb->getCommentsByUID(
			$requested_user->{uid}, $comments_wanted, $min_comment
		) if $commentcount;
	}

	for (@$comments) {
		my($pid, $sid, $cid, $subj, $cdate, $pts, $uid) = @$_;
		$uid ||= 0;

		my $type;
		# This works since $sid is numeric.
		my $replies = $slashdb->countCommentsBySidPid($sid, $cid);

		# This is ok, since with all luck we will not be hitting the DB
		# ...however, the "sid" parameter here must be the string
		# based SID from either the "stories" table or from
		# pollquestions.
		my($discussion) = $slashdb->getDiscussion($sid);

		if ($discussion->{url} =~ /journal/i) {
			$type = 'journal';
		} elsif ($discussion->{url} =~ /poll/i) {
			$type = 'poll';
		} else {
			$type = 'story';
		}

		push @$commentstruct, {
			pid 		=> $pid,
			url		=> $discussion->{url},
			type 		=> $type,
			disc_title	=> $discussion->{title},
			sid 		=> $sid,
			cid 		=> $cid,
			subj		=> $subj,
			cdate		=> $cdate,
			pts		=> $pts,
			uid		=> $uid,
			replies		=> $replies,
		};
	}
	my $storycount =
		$slashdb->countStoriesBySubmitter($requested_user->{uid})
	unless $requested_user->{nonuid};
	my $stories = $slashdb->getStoriesBySubmitter(
		$requested_user->{uid},
		$constants->{user_submitter_display_default}
	) unless !$storycount || $requested_user->{nonuid};

	if ($requested_user->{nonuid}) {
		slashDisplay('netIDInfo', {
			title			=> $title,
			id			=> $id,
			user			=> $requested_user,
			commentstruct		=> $commentstruct || [],
			commentcount		=> $commentcount,
			min_comment		=> $min_comment,
			admin_flag		=> $admin_flag,
			admin_block		=> $admin_block,
		});

	} else {
		#if (! defined $uid && defined $nick && ! $requested_user->{nonuid}) {
		if (! $requested_user->{uid}) {
			
			print getError('userinfo_idnf_err', { id => $id, fieldkey => $fieldkey});
			# print getError('userinfo_nicknf_err', { nick => $nick });
			return;
		}

		$karma_flag = 1 if $admin_flag;
		my $nick_plain = $nick ||= $requested_user->{nickname};
		$nick = strip_literal($nick);

		if ($requested_user->{uid} == $user->{uid}) {
			$karma_flag = 1;
			$nickmatch_flag = 1;
			$points = $requested_user->{points};

			$mod_flag = 1 if $points > 0;

			if ($points) {
				$mod_flag = 1;
				$lastgranted = $slashdb->getUser($uid, 'lastgranted');
				if ($lastgranted) {
					$lastgranted = timeCalc(
						UnixDate(DateCalc($lastgranted,
							'+ ' . ($constants->{stir}+1) . ' days'
						), "%C"), '%Y-%m-%d'
					);
				}
			}

			$title = getTitle('userInfo_main_title', { nick => $nick, uid => $uid });

		} else {
			$title = getTitle('userInfo_user_title', { nick => $nick, uid => $uid });
		}

		slashDisplay('userInfo', {
			title			=> $title,
			nick_plain		=> $nick_plain,
			nick			=> $nick,
			useredit		=> $requested_user,
			points			=> $points,
			lastgranted		=> $lastgranted,
			commentstruct		=> $commentstruct || [],
			commentcount		=> $commentcount,
			min_comment		=> $min_comment,
			nickmatch_flag		=> $nickmatch_flag,
			mod_flag		=> $mod_flag,
			karma_flag		=> $karma_flag,
			admin_block		=> $admin_block,
			admin_flag 		=> $admin_flag,
			stories 		=> $stories,
			storycount 		=> $storycount,
		});
	}
}

#####################################################################
sub validateUser {
	my $user = getCurrentUser();
	my $form = getCurrentForm();
	my $slashdb = getCurrentDB();
	my $constants = getCurrentStatic();

	# If we aren't expiring accounts in some way, we don't belong here.
	if (! allowExpiry()) {
		displayForm();
		return;
	}

	# Since we are here, if the minimum values for the comment trigger and
	# the day trigger are -1, then they should be reset to 1.
	$constants->{min_expiry_comm} = $constants->{min_expiry_days} = 1
		if $constants->{min_expiry_comm} <= 0 ||
		   $constants->{min_expiry_days} <= 0;

	if ($user->{is_anon} || $user->{registered}) {
		if ($user->{is_anon}) {
			print getError('anon_validation_attempt');
			displayForm();
			return;
		} else {
			print getMessage('no_registration_needed')
				if !$user->{reg_id};
			showInfo($user->{uid});
			return;
		}
	# Maybe this should be taken care of in a more centralized location?
	} elsif ($user->{reg_id} eq $form->{id}) {
		# We have a user and the registration IDs match. We are happy!
		my($maxComm, $maxDays) = ($constants->{max_expiry_comm},
					  $constants->{max_expiry_days});
		my($userComm, $userDays) =
			($user->{user_expiry_comm}, $user->{user_expiry_days});

		# Ensure both $userComm and $userDays aren't -1 (expiry has
		# just been turned on).
		$userComm = $constants->{min_expiry_comm}
			if $userComm < $constants->{min_expiry_comm};
		$userDays = $constants->{min_expiry_days}
			if $userDays < $constants->{min_expiry_days};

		my $exp = $constants->{expiry_exponent};

		# Increment only the trigger that was used.
		my $new_comment_expiry = ($maxComm > 0 && $userComm > $maxComm)
			? $maxComm
			: $userComm * (($user->{expiry_comm} < 0)
				? $exp
				: 1
		);
		my $new_days_expiry = ($maxDays > 0 && $userDays > $maxDays)
			? $maxDays
			: $userDays * (($user->{expiry_days} < 0)
				? $exp
				: 1
		);

		# Reset re-registration triggers for user.
		$slashdb->setUser($user->{uid}, {
			'expiry_comm'		=> $new_comment_expiry,
			'expiry_days'		=> $new_days_expiry,
			'user_expiry_comm'	=> $new_comment_expiry,
			'user_expiry_days'	=> $new_days_expiry,
		});

		# Handles rest of re-registration process.
		setUserExpired($user->{uid}, 0);
	}

	slashDisplay('regResult');
}

#################################################################
sub editKey {
	my($uid) = @_;

	my $slashdb = getCurrentDB();

	my $pubkey = $slashdb->getUser($uid, 'pubkey');
	my $editkey = slashDisplay('editKey', { pubkey => $pubkey }, 1);
	return $editkey;
}

#################################################################
sub adminDispatch {
	my $form = getCurrentForm();

	if ($form->{op} eq 'authoredit') {
		editUser($form->{authoruid});

	} elsif ($form->{saveuseradmin}) {
		saveUserAdmin();

	} elsif ($form->{userinfo}) {
		showInfo();

	} elsif ($form->{userfield}) {
		if ($form->{edituser}) {
			editUser();

		} elsif ($form->{edithome}) {
			editHome();

		} elsif ($form->{editcomm}) {
			editComm();

		} elsif ($form->{changepasswd}) {
			changePasswd();
		}

	} else {
		showInfo();
	}
}

#################################################################
sub tildeEd {
	my($extid, $exsect, $exaid, $exboxes, $userspace) = @_;

	my $slashdb = getCurrentDB();
	my $constants = getCurrentStatic();
	my($aidref, $tidref, $sectionref, $section_descref, $tilde_ed, $tilded_msg_box);

	# users_tilded_title
	my $title = getTitle('tildeEd_title');

	# Customizable Authors Thingee
	my $aids = $slashdb->getDescriptions('all-authors'); #$slashdb->getAuthorNames();
	my $n = 0;
	for my $aid (keys %$aids) { #(@$aids) {
		$aidref->{$aid}{checked}  = ($exaid =~ /'\Q$aid\E'/) ? ' CHECKED' : '';
		$aidref->{$aid}{nickname} = $aids->{$aid};
	}

	my $topics = $slashdb->getDescriptions('topics');
	while (my($tid, $alttext) = each %$topics) {
		$tidref->{$tid}{checked} = ($extid =~ /'\Q$tid\E'/) ?
			' CHECKED' : '';
		$tidref->{$tid}{alttext} = $alttext;
	}

	my $sections = $slashdb->getDescriptions('sections');
	while (my($section, $title) = each %$sections) {
		next if !$section;
		$sectionref->{$section}{checked} =
			($exsect =~ /'\Q$section\E'/) ? ' CHECKED' : '';
		$sectionref->{$section}{title} = $title;
	}

	my $customize_title = getTitle('tildeEd_customize_title');

	my $tilded_customize_msg = getMessage('users_tilded_customize_msg',
		{ userspace => $userspace });

	my $sections_description = $slashdb->getSectionBlocks();

	# repeated from above?
	$customize_title = getTitle('tildeEd_customize_title');

	for (sort { lc $b->[1] cmp lc $a->[1]} @$sections_description) {
		my($bid, $title, $boldflag) = @$_;

		$section_descref->{$bid}{checked} = ($exboxes =~ /'$bid'/) ?
			' CHECKED' : '';
		$section_descref->{$bid}{boldflag} = $boldflag > 0;
		$title =~ s/<(.*?)>//g;
		$section_descref->{$bid}{title} = $title;
	}

	my $tilded_box_msg = getMessage('tilded_box_msg');
	$tilde_ed = slashDisplay('tildeEd', {
		title			=> $title,
		tilded_box_msg		=> $tilded_box_msg,
		aidref			=> $aidref,
		tidref			=> $tidref,
		sectionref		=> $sectionref,
		section_descref		=> $section_descref,
		userspace		=> $userspace,
		customize_title		=> $customize_title,
	}, 1);

	return($tilde_ed);
}

#################################################################
sub changePasswd {
	my($id) = @_;

	my $form = getCurrentForm();
	my $slashdb = getCurrentDB();
	my $user = getCurrentUser();
	my $constants = getCurrentStatic();

	# return if (! $user->{is_admin} && $id != $user->{uid});

	my $user_edit = {};
	my $title ;
	my $suadmin_flag = ($user->{seclev} >= 10000) ? 1 : 0;

	if ($form->{userfield}) {
		$id ||= $form->{userfield};
		if ($id =~ /^\d+$/) {
			$user_edit = $slashdb->getUser($id);
		} else {
			$user_edit = $slashdb->getUser($slashdb->getUserUID($id));
		}
	} else {
		$user_edit = $id eq '' ? $user : $slashdb->getUser($id);
		$id = $user_edit->{uid};
	}

	# print getMessage('note', { note => $form->{note}}) if $form->{note};

	$title = getTitle('changePasswd_title', { user_edit => $user_edit });

	my $session = $slashdb->getDescriptions('session_login');
	my $session_select = createSelect('session_login', $session, $user_edit->{session_login}, 1);

	slashDisplay('changePasswd', {
		useredit 		=> $user_edit,
		admin_flag		=> $suadmin_flag,
		title			=> $title,
		session 		=> $session_select,
	});
}

#################################################################
sub editUser {
	my($id) = @_;

	my $form = getCurrentForm();
	my $slashdb = getCurrentDB();
	my $user = getCurrentUser();
	my $constants = getCurrentStatic();

	my $user_edit = {};
	my($admin_block, $title);
	my $admin_flag = ($user->{is_admin}) ? 1 : 0;
	my $fieldkey;

	if ($form->{userfield}) {
		$id ||= $form->{userfield};
		if ($form->{userfield} =~ /^\d+$/) {
			$user_edit = $slashdb->getUser($id);
			$fieldkey = 'uid';
		} else {
			$user_edit = $slashdb->getUser($slashdb->getUserUID($id));
			$fieldkey = 'nickname';
		}
	} else {
		$user_edit = $id eq '' ? $user : $slashdb->getUser($id);
		$fieldkey = 'uid';
		$id = $user_edit->{uid};
	}
	return if isAnon($user_edit->{uid}) && ! $admin_flag;

	$admin_block = getUserAdmin($id, $fieldkey, 1, 1) if $admin_flag;
	$user_edit->{homepage} ||= "http://";

	# Remove domain tags, they'll be added back in, in saveUser.
	for my $dat (@{$user_edit}{qw(sig bio)}) {
		$dat = parseDomainTags($dat, 0, 1);
	}

	$title = getTitle('editUser_title', { user_edit => $user_edit});

	slashDisplay('editUser', {
		useredit 		=> $user_edit,
		admin_flag		=> $admin_flag,
		title			=> $title,
		editkey 		=> editKey($user_edit->{uid}),
		admin_block		=> $admin_block
	});
}

#################################################################
sub editHome {
	my($id) = @_;

	my $slashdb = getCurrentDB();
	my $form = getCurrentForm();
	my $user = getCurrentUser();

	my($formats, $title, $tzformat_select, $tzcode_select);
	my $user_edit = {};
	my $fieldkey;

	my $admin_flag = ($user->{is_admin}) ? 1 : 0;
	my $admin_block = '';

	if ($form->{userfield}) {
		$id ||= $form->{userfield};
		if ($form->{userfield} =~ /^\d+$/) {
			$user_edit = $slashdb->getUser($id);
			$fieldkey = 'uid';
		} else {
			$user_edit = $slashdb->getUser($slashdb->getUserUID($id));
			$fieldkey = 'nickname';
		}
	} else {
		$user_edit = $id eq '' ? $user : $slashdb->getUser($id);
		$fieldkey = 'uid';
	}

	return if isAnon($user_edit->{uid}) && ! $admin_flag;
	$admin_block = getUserAdmin($id, $fieldkey, 1, 1) if $admin_flag;

	$title = getTitle('editHome_title');

	return if $user->{seclev} < 100 && $user_edit->{is_anon};

	$formats = $slashdb->getDescriptions('dateformats');
	$tzformat_select = createSelect('tzformat', $formats, $user_edit->{dfid}, 1);

	$formats = $slashdb->getDescriptions('tzcodes');
	$tzcode_select =
		createSelect('tzcode', [ keys %$formats ], $user_edit->{tzcode}, 1);

	my $l_check = $user_edit->{light}		? ' CHECKED' : '';
	my $b_check = $user_edit->{noboxes}		? ' CHECKED' : '';
	my $i_check = $user_edit->{noicons}		? ' CHECKED' : '';
	my $w_check = $user_edit->{willing}		? ' CHECKED' : '';
	my $s_check = $user_edit->{sectioncollapse}	? ' CHECKED' : '';

	my $tilde_ed = tildeEd(
		$user_edit->{extid}, $user_edit->{exsect},
		$user_edit->{exaid}, $user_edit->{exboxes}, $user_edit->{mylinks}
	);

	slashDisplay('editHome', {
		title			=> $title,
		admin_block		=> $admin_block,
		user_edit		=> $user_edit,
		tzformat_select		=> $tzformat_select,
		tzcode_select		=> $tzcode_select,
		l_check			=> $l_check,
		b_check			=> $b_check,
		i_check			=> $i_check,
		w_check			=> $w_check,
		s_check			=> $s_check,
		tilde_ed		=> $tilde_ed
	});
}

#################################################################
sub editComm {
	my($id) = @_;

	my $slashdb = getCurrentDB();
	my $form = getCurrentForm();
	my $user = getCurrentUser();
	my $user_edit = {};
	my($formats, $commentmodes_select, $commentsort_select, $title,
		$uthreshold_select, $highlightthresh_select, $posttype_select);

	my $admin_block = '';
	my $fieldkey;

	my $admin_flag = $user->{is_admin} ? 1 : 0;

	if ($form->{userfield}) {
		$id ||= $form->{userfield};
		if ($form->{userfield} =~ /^\d+$/) {
			$user_edit = $slashdb->getUser($id);
			$fieldkey = 'uid';
		} else {
			$user_edit = $slashdb->getUser($slashdb->getUserUID($id));
			$fieldkey = 'nickname';
		}
	} else {
		$user_edit = $id eq '' ? $user : $slashdb->getUser($id);
		$fieldkey = 'uid';
	}

	return if isAnon($user_edit->{uid}) && ! $admin_flag;
	$admin_block = getUserAdmin($id, $fieldkey, 1, 1) if $admin_flag;

	$title = getTitle('editComm_title');

	$formats = $slashdb->getDescriptions('commentmodes');
	$commentmodes_select=createSelect('umode', $formats, $user_edit->{mode}, 1);

	$formats = $slashdb->getDescriptions('sortcodes');
	$commentsort_select = createSelect(
		'commentsort', $formats, $user_edit->{commentsort}, 1
	);

	$formats = $slashdb->getDescriptions('threshcodes');
	$uthreshold_select = createSelect(
		'uthreshold', $formats, $user_edit->{threshold}, 1
	);

	$formats = $slashdb->getDescriptions('threshcodes');
	$highlightthresh_select = createSelect(
		'highlightthresh', $formats, $user_edit->{highlightthresh}, 1
	);

	my $h_check = $user_edit->{hardthresh}		? ' CHECKED' : '';
	my $r_check = $user_edit->{reparent}		? ' CHECKED' : '';
	my $n_check = $user_edit->{noscores}		? ' CHECKED' : '';
	my $s_check = $user_edit->{nosigs}		? ' CHECKED' : '';
	my $a_check = $user_edit->{anon_comments}	? ' CHECKED' : '';
	my $d_check = $user_edit->{sigdash}		? ' CHECKED' : '';

	$formats = $slashdb->getDescriptions('postmodes');
	$posttype_select = createSelect(
		'posttype', $formats, $user_edit->{posttype}, 1
	);

	slashDisplay('editComm', {
		title			=> $title,
		admin_block		=> $admin_block,
		user_edit		=> $user_edit,
		h_check			=> $h_check,
		r_check			=> $r_check,
		n_check			=> $n_check,
		s_check			=> $s_check,
		a_check			=> $a_check,
		d_check			=> $d_check,
		commentmodes_select	=> $commentmodes_select,
		commentsort_select	=> $commentsort_select,
		highlightthresh_select	=> $highlightthresh_select,
		uthreshold_select	=> $uthreshold_select,
		posttype_select		=> $posttype_select,
	});
}

#################################################################
sub saveUserAdmin {
	my $slashdb = getCurrentDB();
	my $form = getCurrentForm();
	my $user = getCurrentUser();
	my $constants = getCurrentStatic();

	my($user_edits_table, $user_edit) = ({}, {});
	my $save_success = 0;
	my $author_flag;
	my $note = '';
	my $id;
	my $user_editfield_flag;
	my $banned = 0;
	my $banref;

	if ($form->{uid}) {
		$user_editfield_flag = 'uid';
		$id = $form->{uid};
		$user_edit = $slashdb->getUser($id);

	} elsif ($form->{subnetid}) {
		$user_editfield_flag = 'subnetid';
		$user_edit->{uid} = $constants->{anonymous_coward_uid};
		($id, $user_edit->{subnetid})  = ($form->{subnetid}, $form->{subnetid});
		$user_edit->{nonuid} = 1;

	} elsif ($form->{ipid}) {
		$user_editfield_flag = 'ipid';
		($id, $user_edit->{ipid})  = ($form->{ipid}, $form->{ipid});
		$user_edit->{subnetid} = $1 . "0" ;
		$user_edit->{subnetid} = md5_hex($user_edit->{subnetid});
		$user_edit->{uid} = $constants->{anonymous_coward_uid};
		$user_edit->{nonuid} = 1;

	} elsif ($form->{md5id}) {
		$user_editfield_flag = 'md5id';
		($id, $user_edit->{ipid}, $user_edit->{subnetid})  
			= ($form->{md5id}, $form->{md5id}, $form->{md5id});

	} else { # a bit redundant, I know
		$user_edit = $user;
	}

	for my $formname ('comments', 'submit') {
		my $existing_reason = $slashdb->getAccessListReason($formname, 'readonly', $user_edit);
		my $is_readonly_now = $slashdb->checkReadOnly($formname, $user_edit) ? 1 : 0;

		my $keyname = "readonly_" . $formname;
		my $reason_keyname = $formname . "_ro_reason";
		$form->{$keyname} = $form->{$keyname} eq 'on' ? 1 : 0 ;
		$form->{$reason_keyname} ||= '';

		if ($form->{$keyname} != $is_readonly_now) {
			if ("$existing_reason" ne "$form->{$reason_keyname}") {
				$slashdb->setAccessList($formname, $user_edit, $form->{$keyname}, 'readonly', $form->{$reason_keyname});
			} else {
				$slashdb->setAccessList($formname, $user_edit, $form->{$keyname}, 'readonly');
			}
		} elsif ("$existing_reason" ne "$form->{$reason_keyname}") {
			$slashdb->setAccessList($formname, $user_edit, $form->{$keyname}, 'readonly', $form->{$reason_keyname});
		}

		# $note .= getError('saveuseradmin_notsaved', { field => $user_editfield_flag, id => $id });
	}

	$banref = $slashdb->getBanList(1);
	$banned = $banref->{$id} ? 1 : 0;
	$form->{banned} = $form->{banned} eq 'on' ? 1 : 0 ;
	if ($banned) {
		if ($form->{banned} == 0) {
			$slashdb->setAccessList('', $user_edit, 0, 'isbanned', $form->{banned_reason});
			$slashdb->getBanList(1);
		}
	} else {
		if ($form->{banned} == 1) {
			$slashdb->setAccessList('', $user_edit, $form->{banned}, 'isbanned', $form->{banned_reason});
			$slashdb->getBanList(1);
		}
	}

	$note .= getMessage('saveuseradmin_saved', { field => $user_editfield_flag, id => $id}) if $save_success;

	if ($user->{is_admin} && ($user_editfield_flag eq 'uid' ||
		$user_editfield_flag eq 'nickname')) {

		$user_edits_table->{seclev} = $form->{seclev};
		$user_edits_table->{rtbl} = $form->{rtbl} eq 'on' ? 1 : 0 ;
		$user_edits_table->{rtbl_reason} = $form->{rtbl} eq 'on' ? $form->{rtbl_reason} : '' ;
		$user_edits_table->{author} = $form->{author} ? 1 : 0 ;
		$user_edits_table->{defaultpoints} = $form->{defaultpoints};

		$slashdb->setUser($id, $user_edits_table);
		$note .= getMessage('saveuseradmin_saveduser', { field => $user_editfield_flag, id => $id });
	}

	if (!$user_edit->{nonuid}) {
		if ($form->{expired} eq 'on') {
			$slashdb->setExpired($user_edit->{uid});

		} else {
			$slashdb->setUnexpired($user_edit->{uid});
		}
	}

	print getMessage('note', { note => $note }) if defined $note;

	showInfo($id);
}

#################################################################
sub savePasswd {
	my($note) = @_;

	my $slashdb = getCurrentDB();
	my $form = getCurrentForm();
	my $user = getCurrentUser();
	my $constants = getCurrentStatic();

	my $error_flag = 0;
	my $user_edit = {};
	my $uid;

	my $user_edits_table = {};

	if ($user->{is_admin}) {
		$uid = $form->{uid} ? $form->{uid} : $user->{uid};
	} else {
		$uid = ($user->{uid} == $form->{uid}) ? $form->{uid} : $user->{uid};
	}

	$user_edit = $slashdb->getUser($uid);

	if (!$user_edit->{nickname}) {
		$$note .= getError('cookie_err', { titlebar => 0 }, 0, 1);
		$error_flag++;
	}

	if ($form->{pass1} ne $form->{pass2}) {
		$$note .= getError('saveuser_passnomatch_err', { titlebar => 0 }, 0, 1);
		$error_flag++;
	}

	if (length $form->{pass1} < 6 && $form->{pass1} && $form->{pass1} ne "") {
		$$note .= getError('saveuser_passtooshort_err', { titlebar => 0 }, 0, 1);
		$error_flag++;
	}

	if (! $error_flag) {
		$user_edits_table->{passwd} = $form->{pass1} if $form->{pass1};
		$user_edits_table->{session_login} = $form->{session_login};
		my $pass = bakeUserCookie($uid,
			$user_edits_table->{passwd}
				? encryptPassword($user_edits_table->{passwd})
				: $user_edit->{passwd}
		);

		if ($form->{uid} eq $user->{uid}) {
			setCookie('user', $pass, $user_edits_table->{session_login});
		}

		$slashdb->setUser($uid, $user_edits_table) ;
		$$note .= getMessage('saveuser_passchanged_msg',
			{ nick => $user_edit->{nickname}, uid => $user_edit->{uid} },
		0, 1);
	}

	return $error_flag;
}

#################################################################
sub saveUser {
	my $slashdb = getCurrentDB();
	my $form = getCurrentForm();
	my $user = getCurrentUser();
	my $constants = getCurrentStatic();
	my $uid;
	my $user_editfield_flag;

	$uid = $user->{is_admin} && $form->{uid} ? $form->{uid} : $user->{uid};
	my $user_edit = $slashdb->getUser($uid);

	my($note, $formname);

	$note .= getMessage('savenickname_msg', {
		nickname => $user_edit->{nickname},
	}, 1);

	if (!$user_edit->{nickname}) {
		$note .= getError('cookie_err', 0, 1);
	}

	# Check to ensure that if a user is changing his email address, that
	# it doesn't already exist in the userbase.
	if ($user_edit->{realemail} ne $form->{realemail}) {
		if ($slashdb->existsEmail($form->{realemail})) {
			$note = getError('emailexists_err', 0, 1);
			return $note;
		}
	}

	# The schema is 160 chars but we limit their input to 120.
	# If the sig becomes too long to fit (domain tagging causes
	# string expansion and tag balancing can too), warn the user to
	# use shorter domain names and don't save their change.
	my $sig = chopEntity($form->{sig}, 120);
	my $bio = $form->{bio};

	for my $dat ($sig, $bio) {
		$dat = strip_html($dat);
		$dat = balanceTags($dat, 1); # only 1 nesting tag (UL, OL, BLOCKQUOTE) allowed
		$dat = addDomainTags($dat) if $dat;
	}

	if (defined($sig) && length($sig) > 160) {
		print getError('sig_too_long_err');
		$sig = undef;
	}

	my($err_message);
	# really, comment filters should ignore short length IMO ... oh well.
	if (length($bio) > 1 && ! filterOk('comments', 'postersubj', $bio, \$err_message)) {
		print getError('filter message', {
			err_message	=> $err_message,
			item		=> 'bio',
		});
		$bio = undef;
	} elsif (! compressOk('comments', 'postersubj', $bio)) {
		print getError('compress filter', {
			ratio	=> 'postersubj',
			item		=> 'bio',
		});
		$bio = undef;
	}

	# We should do some conformance checking on a user's pubkey,
	# make sure it looks like one of the known types of public
	# key.  Until then, just make sure it doesn't have HTML.
	$form->{pubkey} = strip_nohtml($form->{pubkey}, 1);

	my $homepage = $form->{homepage};
	$homepage = '' if $homepage eq 'http://';
	$homepage = fudgeurl($homepage);
	$homepage = URI->new_abs($homepage, $constants->{absolutedir})
			->canonical
			->as_string;
	$homepage = substr($homepage, 0, 100) if $homepage;

	# for the users table
	my $user_edits_table = {
		homepage	=> $homepage,
		realname	=> $form->{realname},
		pubkey		=> $form->{pubkey},
		copy		=> $form->{copy},
		quote		=> $form->{quote},
	};
	$user_edits_table->{sig} = $sig if defined $sig;
	$user_edits_table->{bio} = $bio if defined $bio;

	# don't want undef, want to be empty string so they
	# will overwrite the existing record
	for (keys %$user_edits_table) {
		$user_edits_table->{$_} = '' unless defined $user_edits_table->{$_};
	}

	if ($user_edit->{realemail} ne $form->{realemail}) {
		$user_edits_table->{realemail} =
			chopEntity($form->{realemail}, 50);
		my $new_fakeemail = ''; # at emaildisplay 0, don't show any email address
		if ($user->{emaildisplay}) {
			$new_fakeemail = getArmoredEmail($uid, $user_edits_table->{realemail})
				if $user->{emaildisplay} == 1;
			$new_fakeemail = $user_edits_table->{realemail}
				if $user->{emaildisplay} == 2;
		}
		$user_edits_table->{fakeemail} = $new_fakeemail;

		$note .= getMessage('changeemail_msg', {
			realemail => $user_edit->{realemail}
		}, 1);

		my $saveuser_emailtitle = getTitle('saveUser_email_title', {
			nickname  => $user_edit->{nickname},
			realemail => $form->{realemail}
		}, 1);
		my $saveuser_email_msg = getMessage('saveuser_email_msg', {
			nickname  => $user_edit->{nickname},
			realemail => $form->{realemail}
		}, 1);

		sendEmail($form->{realemail}, $saveuser_emailtitle, $saveuser_email_msg);
		doEmail($uid, $saveuser_emailtitle, $saveuser_email_msg);
	}

	$slashdb->setUser($uid, $user_edits_table);

	print getMessage('note', { note => $note}) if $note;

	editUser($uid);
}


#################################################################
sub saveComm {
	my $slashdb = getCurrentDB();
	my $user = getCurrentUser();
	my $form = getCurrentForm();
	my($uid, $user_fakeemail);

	if ($user->{is_admin}) {
		$uid = $form->{uid} ? $form->{uid} : $user->{uid};
	} else {
		$uid = ($user->{uid} == $form->{uid}) ?
			$form->{uid} : $user->{uid};
	}

	# Do the right thing with respect to the chosen email display mode
	# and the options that can be displayed.
	my $user_edit = $slashdb->getUser($uid);
	my $new_fakeemail = '';		# at emaildisplay 0, don't show any email address
	if ($form->{emaildisplay}) {
		$new_fakeemail = getArmoredEmail($uid)	if $form->{emaildisplay} == 1;
		$new_fakeemail = $user_edit->{realemail}	if $form->{emaildisplay} == 2;
	}

	my $name = $user->{seclev} && $form->{name} ?
		$form->{name} : $user->{nickname};

	my $savename = getMessage('savename_msg', { name => $name });
	print $savename;

	print getError('cookie_err') if isAnon($uid) || !$name;

	# Take care of the lists
	# Enforce Ranges for variables that need it
	$form->{commentlimit} = 0 if $form->{commentlimit} < 1;
	$form->{commentspill} = 0 if $form->{commentspill} < 1;

	# This has NO BEARING on the table the data goes into now.
	# setUser() does the right thing based on the key name.
	my $users_comments_table = {
		clbig		=> $form->{clbig},
		clsmall		=> $form->{clsmall},
		commentlimit	=> $form->{commentlimit},
		commentsort	=> $form->{commentsort},
		commentspill	=> $form->{commentspill},
		domaintags	=> $form->{domaintags},
		emaildisplay	=> $form->{emaildisplay},
		fakeemail	=> $new_fakeemail,
		highlightthresh	=> $form->{highlightthresh},
		maxcommentsize	=> $form->{maxcommentsize},
		mode		=> $form->{umode},
		posttype	=> $form->{posttype},
		threshold	=> $form->{uthreshold},
		nosigs		=> ($form->{nosigs}     ? 1 : 0),
		reparent	=> ($form->{reparent}   ? 1 : 0),
		noscores	=> ($form->{noscores}   ? 1 : 0),
		hardthresh	=> ($form->{hardthresh} ? 1 : 0),
		anon_comments	=> ($form->{anon_comments} ? 1 : 0),
		sigdash		=> ($form->{sigdash} ? 1 : 0),
	};

	# Update users with the $users_comments_table hash ref
	$slashdb->setUser($uid, $users_comments_table);

	editComm($uid);
}

#################################################################
sub saveHome {
	my $slashdb = getCurrentDB();
	my $user = getCurrentUser();
	my $form = getCurrentForm();
	my $uid;
	my($extid, $exaid, $exsect) = '';

	if ($user->{is_admin}) {
		$uid = $form->{uid} ? $form->{uid} : $user->{uid} ;
	} else {
		$uid = ($user->{uid} == $form->{uid}) ?
			$form->{uid} : $user->{uid};
	}
	my $edit_user = $slashdb->getUser($uid);

	my $name = $user->{seclev} && $form->{name} ?
		$form->{name} : $user->{nickname};

	$name = substr($name, 0, 20);

	# users_cookiemsg
	if (isAnon($uid) || !$name) {
		my $cookiemsg = getError('cookie_err');
		print $cookiemsg;
	}

	my $exboxes = $edit_user->{exboxes};

	$exboxes =~ s/'//g;
	my @b = split m/,/, $exboxes;

	foreach (@b) {
		$_ = '' unless $form->{"exboxes_$_"};
	}

	$exboxes = sprintf "'%s',", join "','", @b;
	$exboxes =~ s/'',//g;

	for my $k (keys %{$form}) {
		if ($k =~ /^extid_(.*)/)	{ $extid  .= "'$1'," }
		if ($k =~ /^exaid_(.*)/)	{ $exaid  .= "'$1'," }
		if ($k =~ /^exsect_(.*)/)	{ $exsect .= "'$1'," }
		if ($k =~ /^exboxes_(.*)/) {
			# Only Append a box if it doesn't exist
			my $box = $1;
			$exboxes .= "'$box'," unless $exboxes =~ /'$box'/;
		}
	}

	$form->{maxstories} = 66 if $form->{maxstories} > 66;
	$form->{maxstories} = 1 if $form->{maxstories} < 1;

	my $users_index_table = {
		extid		=> checkList($extid),
		exaid		=> checkList($exaid),
		exsect		=> checkList($exsect),
		exboxes		=> checkList($exboxes),
		maxstories	=> $form->{maxstories},
		noboxes		=> ($form->{noboxes} ? 1 : 0),
		light		=> ($form->{light} ? 1 : 0),
		noicons		=> ($form->{noicons} ? 1 : 0),
		willing		=> ($form->{willing} ? 1 : 0),
		sectioncollapse	=> ($form->{sectioncollapse} ? 1 : 0),
	};

	if (defined $form->{tzcode} && defined $form->{tzformat}) {
		$users_index_table->{tzcode} = $form->{tzcode};
		$users_index_table->{dfid}   = $form->{tzformat};
	}

	$users_index_table->{mylinks} = $form->{mylinks} || '';

	# If a user is unwilling to moderate, we should cancel all points, lest
	# they be preserved when they shouldn't be.
	my $users_comments = { points => 0 };
	unless (isAnon($uid)) {
		$slashdb->setUser($uid, $users_comments)
			unless $form->{willing};
	}

	# Update users with the $users_index_table thing we've been playing with
	# for this whole damn sub
	$slashdb->setUser($uid, $users_index_table);

	editHome($uid);
}

#################################################################
# A generic way for a site to allow users to edit data about themselves.
# Most useful when your plugin or theme wants to let the user change
# minor settings but you don't want to write a whole new version
# of users.pl to provide a user interface.  The user can save any
# param of the format "opt_foo", as long as "foo" shows up in
# getMiscUserOpts which lists all the misc opts that this user can edit.
# This is *not* protected by formkeys (yet), so assume attackers can make
# users click and accidentally edit their own settings: no really important
# data should be stored in this way.
sub editMiscOpts {
	my $slashdb = getCurrentDB();
	my $user = getCurrentUser(); 
	my $constants = getCurrentStatic();

	return if $user->{is_anon}; # shouldn't be, but can't hurt to check

	my $edit_user = $slashdb->getUser($user->{uid});
	my $title = getTitle('editMiscOpts_title');

	my $opts = $slashdb->getMiscUserOpts();
	for my $opt (@$opts) {
		my $opt_name = "opt_" . $opt->{name};
		$opt->{checked} = $edit_user->{$opt_name} ? 1 : 0;
	}

	slashDisplay('editMiscOpts', {
#		useredit	=> $user_edit,
		title		=> $title,
		opts		=> $opts,
	});
}

#################################################################
#
sub saveMiscOpts {
	my $slashdb = getCurrentDB();
	my $user = getCurrentUser();
	my $form = getCurrentForm();
	my $constants = getCurrentStatic();

	return if $user->{is_anon}; # shouldn't be, but can't hurt to check

	my $edit_user = $slashdb->getUser($user->{uid});
	my %opts_ok_hash = ( );
	my $opts = $slashdb->getMiscUserOpts();
	for my $opt (@$opts) {
		$opts_ok_hash{"opt_$opt->{name}"} = 1;
	}

	my $update = { };
	for my $opt (grep /^opt_/, keys %$form) {
		next unless $opts_ok_hash{$opt};
		$update->{$opt} = $form->{$opt} ? 1 : 0;
	}

	# Make the changes.
	$slashdb->setUser($edit_user->{uid}, $update);

	# Inform the user the change was made.  Since we don't
	# require formkeys, we always want to print a message to
	# make sure the user sees what s/he did.
	print getMessage('savemiscopts_msg', $update);

	editMiscOpts();
}

#################################################################
sub listReadOnly {
	my $slashdb = getCurrentDB();

	my $readonlylist = $slashdb->getAccessList(0, 'readonly');

	slashDisplay('listReadOnly', {
		readonlylist => $readonlylist,
	});

}

#################################################################
sub listBanned {
	my $slashdb = getCurrentDB();

	my $bannedlist = $slashdb->getAccessList(0, 'isbanned');

	slashDisplay('listBanned', {
		bannedlist => $bannedlist,
	});

}

#################################################################
sub topAbusers {
	my $slashdb = getCurrentDB();

	my $topabusers = $slashdb->getTopAbusers();

	slashDisplay('topAbusers', {
		topabusers => $topabusers,
	});
}

#################################################################
sub listAbuses {
	my $user = getCurrentUser();
	my $form = getCurrentForm();
	my $slashdb = getCurrentDB();
	my $constants = getCurrentStatic();

	my $abuses = $slashdb->getAbuses($form->{key}, $form->{abuseid});

	slashDisplay('listAbuses', {
		abuseid	=> $form->{abuseid},
		abuses	=> $abuses,
	});
}

#################################################################
sub displayForm {
	my $user = getCurrentUser();
	my $form = getCurrentForm();
	my $slashdb = getCurrentDB();
	my $constants = getCurrentStatic();

	my $op = $form->{op};
	my $suadmin_flag = $user->{seclev} >= 10000 ? 1 : 0;

	$op ||= 'displayform';

	my $ops = {
		displayform 	=> 'loginForm',
		edithome	=> 'loginForm',
		editicomm	=> 'loginForm',
		edituser	=> 'loginForm',
		mailpasswdform 	=> 'sendPasswdForm',
		newuserform	=> 'newUserForm',
		userclose	=> 'loginForm',
		userlogin	=> 'loginForm',
		editmiscopts	=> 'loginForm',
		savemiscopts	=> 'loginForm',
		default		=> 'loginForm'
	};

	my($title, $title2, $msg1, $msg2) = ('', '', '', '');

	if ($form->{op} eq 'userclose') {
		$title = getMessage('userclose');

	} elsif ($op eq 'displayForm') {
		$title = $form->{unickname}
			? getTitle('displayForm_err_title')
			: getTitle('displayForm_title');
	} elsif ($op eq 'mailpasswdform') {
		$title = getTitle('mailPasswdForm_title');
	} elsif ($op eq 'newuserform') {
		$title = getTitle('newUserForm_title');
	} else {
		$title = getTitle('displayForm_title');
	}

	$form->{unickname} ||= $form->{newusernick};

	if ($form->{newusernick}) {
		$title2 = getTitle('displayForm_dup_title');
	} else {
		$title2 = getTitle('displayForm_new_title');
	}

	$msg1 = getMessage('dispform_new_msg_1');
	if (! $form->{newusernick} && $op eq 'newuserform') {
		$msg2 = getMessage('dispform_new_msg_2');
	} elsif ($op eq 'displayform' || $op eq 'userlogin') {
		$msg2 = getMessage('newuserform_msg');
	}

	slashDisplay($ops->{$op}, {
		newnick		=> fixNickname($form->{newusernick}),
		suadmin_flag 	=> $suadmin_flag,
		title 		=> $title,
		title2 		=> $title2,
		logged_in	=> isAnon($user->{uid}) ? 0 : 1,
		msg1 		=> $msg1,
		msg2 		=> $msg2
	});
}

#################################################################
# this groups all the messages together in
# one template, called "messages;users;default"
sub getMessage {
	my($value, $hashref, $nocomm) = @_;
	$hashref ||= {};
	$hashref->{value} = $value;
	return slashDisplay('messages', $hashref,
		{ Return => 1, Nocomm => $nocomm });
}

#################################################################
# this groups all the errors together in
# one template, called "errors;users;default"
sub getError {
	my($value, $hashref, $nocomm) = @_;
	$hashref ||= {};
	$hashref->{value} = $value;
	return slashDisplay('errors', $hashref,
		{ Return => 1, Nocomm => $nocomm });
}

#################################################################
# this groups all the titles together in
# one template, called "users-titles"
sub getTitle {
	my($value, $hashref, $nocomm) = @_;
	$hashref ||= {};
	$hashref->{value} = $value;
	return slashDisplay('titles', $hashref,
		{ Return => 1, Nocomm => $nocomm });
}

#################################################################
# getUserAdmin - returns a block of text
# containing fields for admin users
sub getUserAdmin {
	my($id, $field, $form_flag, $seclev_field) = @_;

	my $slashdb	= getCurrentDB();
	my $user	= getCurrentUser();
	my $form	= getCurrentForm();
	my $constants	= getCurrentStatic();
	$id ||= $user->{uid};

	my($checked, $uidstruct, $readonly, $readonly_reasons);
	my($user_edit, $user_editfield, $uidlist, $iplist, $authors, $author_flag, $topabusers, $thresh_select);
	my $user_editinfo_flag = ($form->{op} eq 'userinfo' || ! $form->{op} || $form->{userinfo} || $form->{saveuseradmin}) ? 1 : 0;
	my $authoredit_flag = ($user->{seclev} >= 10000) ? 1 : 0;
	my($banned, $banned_reason);

	$field ||= 'uid';
	if ($field eq 'uid') {
		$user_edit = $slashdb->getUser($id);
		$user_editfield = $user_edit->{uid};
		$checked->{expired} = $slashdb->checkExpired($user_edit->{uid}) ? ' CHECKED' : '';
		$iplist = $slashdb->getNetIDList($user_edit->{uid});

	} elsif ($field eq 'nickname') {
		$user_edit = $slashdb->getUser($slashdb->getUserUID($id));
		$user_editfield = $user_edit->{nickname};
		$checked->{expired} = $slashdb->checkExpired($user_edit->{uid}) ? ' CHECKED' : '';
		$iplist = $slashdb->getNetIDList($user_edit->{uid});

	} elsif ($field eq 'md5id') {
		$user_edit->{nonuid} = 1;
		$user_edit->{md5id} = $id;
		if ($form->{fieldname} and $form->{fieldname} =~ /^(ipid|subnetid)$/) {
			$uidlist = $slashdb->getUIDList($form->{fieldname}, $user_edit->{md5id});
		} else {
			$uidlist = $slashdb->getUIDList('md5id', $user_edit->{md5id});
		}

	} elsif ($field eq 'ipid') {
		$user_edit->{nonuid} = 1;
		$user_edit->{ipid} = $id;
		$user_editfield = $id;
		$uidlist = $slashdb->getUIDList('ipid', $user_edit->{ipid});

	} elsif ($field eq 'subnetid') {
		$user_edit->{nonuid} = 1;
		if ($id =~ /^(\d+\.\d+\.\d+\.)\.?\d+?/) {
			$id = $1 . ".0";
			$user_edit->{subnetid} = $id;
		} else {
			$user_edit->{subnetid} = $id;
		}

		$user_editfield = $id;
		$uidlist = $slashdb->getUIDList('subnetid', $user_edit->{subnetid});

	} else {
		$user_edit = $id ? $slashdb->getUser($id) : $user;
		$user_editfield = $user_edit->{uid};
		$iplist = $slashdb->getNetIDList($user_edit->{uid});
	}

	for my $formname ('comments', 'submit') {
		$readonly->{$formname} = $slashdb->checkReadOnly($formname, $user_edit) ? ' CHECKED' : '';
		$readonly_reasons->{$formname} = $slashdb->getAccessListReason($formname, 'readonly', $user_edit) if $readonly->{$formname};
	}
	
	my $banref = $slashdb->getBanList(1);

	$banned = $banref->{$id} ? ' CHECKED' : '';
	$banned_reason = $slashdb->getAccessListReason('', 'isbanned', $user_edit);

	for (@$uidlist) {
		$uidstruct->{$_->[0]} = $slashdb->getUser($_->[0], 'nickname');
	}

	$user_edit->{author} = ($user_edit->{author} == 1) ? ' CHECKED' : '';
	$user_edit->{rtbl} = ($user_edit->{rtbl} == 1) ? ' CHECKED' : '';
	if (! $user->{nonuid}) {
		my $threshcodes = $slashdb->getDescriptions('threshcode_values','',1);
		$thresh_select = createSelect('defaultpoints', $threshcodes, $user_edit->{defaultpoints}, 1);
	}

	if (!ref $iplist or scalar(@$iplist) < 1) {
		undef $iplist;
	}

	return slashDisplay('getUserAdmin', {
		field			=> $field,
		useredit		=> $user_edit,
		banned 			=> $banned,
		banned_reason		=> $banned_reason,
		userinfo_flag		=> $user_editinfo_flag,
		userfield		=> $user_editfield,
		iplist			=> $iplist,
		uidstruct		=> $uidstruct,
		seclev_field		=> $seclev_field,
		checked 		=> $checked,
		topabusers		=> $topabusers,
		form_flag		=> $form_flag,
		readonly		=> $readonly,
		thresh_select		=> $thresh_select,
		readonly_reasons 	=> $readonly_reasons,
		authoredit_flag 	=> $authoredit_flag
	}, 1);
}

#################################################################
sub fixNickname {
	local($_) = @_;
	s/\s+/ /g;
	s/[^ a-zA-Z0-9\$_.+!*'(),-]+//g;
	$_ = substr($_, 0, 20);
	return $_;
}

createEnvironment();
main();

1;
