# FILE: adm-user.pl
# DESCRIPTION: Administration functions dealing with user management
#-------------------------------------------------------------------------------
# DISCUS COPYRIGHT NOTICE
#
# Discus is copyright (c) 2002 by DiscusWare, LLC, all rights reserved.
# The use of Discus is governed by the Discus License Agreement which is
# available from the Discus WWW site at:
#    http://www.discusware.com/discus/license
#
# Pursuant to the Discus License Agreement, this copyright notice may not be
# removed or altered in any way.
#-------------------------------------------------------------------------------

use strict;
use vars qw($GLOBAL_OPTIONS $DCONF $PARAMS);

###
### USER_admin
###
### Controls all User Manager functions
###

sub USER_admin {
	my ($FORMref) = @_;
	my $result = check_password($FORMref->{username}, undef, { type_required => 'moderator' }, $FORMref->{'COOKIE'});
	my $stuff = {};
	bad_login({ username => 1 }) if ! scalar(@{ $result });
	dreq("fcn-grp");
	user_mgr_1($FORMref) if $FORMref->{action} eq "user_mgr_1";
	check_group_authorization($FORMref->{username}, $FORMref->{group}) || bad_login({ group_privilege => 1 });
	if ($FORMref->{action} eq "user_copy") {
		if ($FORMref->{newgroup} ne "") {
			check_group_authorization($FORMref->{username}, $FORMref->{newgroup}) || bad_login({ group_privilege => 1 });
			dreq("fcn-acct");
			$stuff = user_copy(quotemeta($FORMref->{group}), quotemeta($FORMref->{newgroup}), $FORMref->{users}, $FORMref->{move});
			$FORMref->{menu} = 0 if $FORMref->{users} ne "*";
		} else {
			$stuff->{failed_copy} = 1;
		}
		$FORMref->{action} = "user_mgr_2";
	}
	user_form_handler($FORMref, $result) if $FORMref->{SELECTION_action} ne "";
	if ($FORMref->{action} eq "user_mgr_2" && $FORMref->{group} eq " ") {
		bad_login( { superuser_required => 1 } ) if $result->[0]->{user} ne $DCONF->{superuser};
		user_mgr_no_group($FORMref, $result, $stuff);
	}
	user_mgr_2($FORMref, $result, $stuff) if $FORMref->{action} eq "user_mgr_2";
	user_self_reg_save($FORMref, $result) if $FORMref->{action} eq "user_selfreg";
	if ($FORMref->{action} eq "user_edit_save") {
		my $r = user_edit_save_someone_else($FORMref, $result);
		user_edit_someone_else($FORMref, $result, undef, undef, {editsave => 1}, $r);
	}
	if ($FORMref->{action} eq "user_promote" && $result->[0]->{user} eq $DCONF->{superuser}) {
		my %x = map { $_, 1 } grep { /\S/ } split(/,/, $FORMref->{mod});
		dreq("promote-PRO");
		promote_user_manager_action(\%x, $FORMref, $result);
	}
	if ($FORMref->{action} eq "user_mgr_suspend" && $DCONF->{pro}) {
		dreq("suspend-PRO"); user_manager_suspend_action($FORMref, $result);
	}
	if ($FORMref->{action} =~ /user_list_/) {
		dreq("usr-list"); userlist_admin_main($FORMref, $result);
	}
	user_manager_user_add($FORMref, $result) if $FORMref->{action} eq "um-add_user";

}

###
### user_manager_user_add
###
### Adds a single user through the User Manager
###

sub user_manager_user_add {
	my ($FORMref, $result) = @_;
	dreq("fcn-acct", "authpass");
	my $ah = {};
	$ah->{user} = $FORMref->{newuser};
	$ah->{pass1} = prepare_userpass_p($FORMref->{pass_1});
	$ah->{pass2} = prepare_userpass_p($FORMref->{pass_2});
	error_message("Add User Error", "The passwords in the &quot;Password:&quot; and &quot;Retype Password:&quot; boxes did not match.  When you use your browser's back button, re-enter the passwords so they do match.", 0, 1) if $ah->{pass1} ne $ah->{pass2};
	$ah->{email} = $FORMref->{email};
	$ah->{fullname} = $FORMref->{fullname};
	$ah->{groups} = "/$FORMref->{group}/";
	$ah->{forcechange} = 1 if $FORMref->{forcechange};
	my ($success, $failure) = add_account($ah, { database => "users" });
	if (ref $failure eq 'ARRAY' && scalar @{$failure} == 1 && $DCONF->{'pro'}) {
		if ($failure->[0]->{'error_code'} == 1) {
			dreq("userfilt-PRO");
			my $code = username_conflict_resolver($ah->{'user'}, $ah->{'pass1'}, $FORMref->{'username'}, $ah->{'groups'});
			if ($code == 1) {
				$success = \@{$failure};
				$failure = [];
			}
		}
	}	
	if (! scalar(@{ $success })) {
		my $errcode = undef;
		$errcode = $failure->[0]->{error_code} if $failure->[0]->{error_code};
		$errcode = $failure->[0]->{FAILURE} if $failure->[0]->{FAILURE};
		error_message("Add User Error", "The username \U$ah->{user}\E is already taken.", 0, 1) if $errcode == 1;
		error_message("Add User Error", "The username \U$ah->{user}\E must be between 1 and 100 characters.", 0, 1) if $errcode == 2;
		error_message("Add User Error", "The two passwords you entered did not match.", 0, 1) if $errcode == 3;
		error_message("Add User Error", "You did not specify a password for this account.", 0, 1) if $errcode == 4;
		if (defined $failure->[0]->{FAILURE_MESSAGE}) {
			error_message("Add User Error", $failure->[0]->{FAILURE_MESSAGE}, 0, 1);
		}
		error_message("Add User Error", "Other error ($errcode)", 0, 1);
	}
	if ($FORMref->{emailpw} && $DCONF->{pro}) {
		dreq("fcn-user-PRO");
		user_mail_passwords($success, undef);
	}
	user_mgr_2($FORMref, $result, { added => "\U$success->[0]->{user}\E" });
}

###
### user_mgr_1
###
### First page of the User Manager interface.  Displays the groups and the
### user count, and you click which group you want to edit.
###

sub user_mgr_1 {
	my ($FORMref) = @_;
	my $groupfile = read_group_file();
	my ($gr, $bad) = user_mgr_get_auth_groups($FORMref->{username}, $groupfile, undef, 1);
	my @w = sort {$a->{group} cmp $b->{group}} @{$gr};
	my $subst = {};
	$subst->{general}->{username} = $FORMref->{username};
	$subst->{grp_arr} = \@w;
	$subst->{general}->{tot_grp} = scalar(keys %{$groupfile->{by_grp}});
	if ($DCONF->{pro}) {
		dreq("selfreg-PRO");
		$subst->{queue} = count_queued_users($FORMref->{username});
	}
	if (ref $bad eq 'ARRAY' && scalar @{$bad} > 0) {
		$subst->{general}->{nocount} = scalar @{$bad};
	}
	screen_out("usermgr1", $subst);
}

###
### user_mgr_2
###
### Second page of the User Manager -- the main script behind the display of
### the main User Manager interface.
###

sub user_mgr_2 {
	my ($FORMref, $result, $stuff) = @_;
	my $subst = {};
	$subst->{general}->{url} = "$PARAMS->{cgiurl}?action=user_mgr_2&username=$result->[0]->{user}&group=$FORMref->{group}";
	$subst->{general}->{menu} = $FORMref->{menu};
	($subst->{authgroups}) = user_mgr_get_auth_groups($result->[0]->{user});
	$subst->{general}->{group} = $FORMref->{group};
	$subst->{general}->{username} = $result->[0]->{user};
	$subst->{general}->{users} = $subst->{authgroups}->[0]->{usrcnt};
	if ($FORMref->{menu} == 4 && $DCONF->{pro}) {
		dreq("selfreg-PRO", "webtags", "fcn-user-PRO");
		my $r = read_selfreg_file();
		my $g = $FORMref->{group};
		$subst->{general}->{param} = $r->{param}->{$g}->{param};
		$subst->{general}->{groupname} = inverse_webtags($r->{param}->{$g}->{groupname});
		$subst->{general}->{descr} = inverse_webtags($r->{param}->{$g}->{descr});
		$subst->{general}->{defcheck} = $r->{param}->{$g}->{defcheck};
		my $x = new_registrations_email_moderators_config_read();
		$subst->{notify}->{here} = $x->{$result->[0]->{user}}->{groups}->{$g};
	} elsif ($FORMref->{menu} == 1) {
		dreq("fcn-acct"); $subst->{general}->{randpass} = pick_random_password(1);
	} elsif ($FORMref->{menu} == 0) {
		my $results_per_screen = 25;
		my $u = read_account_file("users", undef, { condition => { group => { group => $FORMref->{group}} }, returnformat => 'single_array' });
		dreq("userfilt-PRO") if $DCONF->{pro};
		if (! $FORMref->{clear}) {
			$FORMref->{"l10"} = defined $FORMref->{"f10"} ? $FORMref->{"l10"} : 1;
		} else {
			$FORMref->{"l10"} = 1;
			$FORMref->{"f10"} = 1;
		}
		foreach my $f (keys(%{ $FORMref })) {
			if ($f =~ m|^l(\d+)|) {
				if ($FORMref->{clear} == 1 && $1 ne "10") {
					$FORMref->{"l$1"} = undef;
					$FORMref->{"f$1"} = undef;
				}
				$subst->{filter}->{"l$1"} = $FORMref->{"l$1"};
				$subst->{filter}->{"f$1"} = $FORMref->{"f$1"};
			}
		}
		$subst->{general}->{size_u} = 0;
		my @u = (); my $grp = quotemeta($FORMref->{group});
		foreach my $us (@{ $u }) {
			$subst->{general}->{size_u}++;
			next if ($DCONF->{pro} && is_user_filtered_pro($FORMref, $us));
			next if (! $DCONF->{pro} && is_user_filtered_freeware($FORMref, $us));
			if ($us->{pass} =~ m%^\*.*?\|\|\*\*$%) {
				$us->{suspend_ban} = 1;
			} elsif ($us->{pass} =~ m%^\*.*\|\|\*$%) {
				$us->{suspend_cancel} = 1;
			} elsif ($us->{pass} =~ m%^\*.*\|\|\*Q$%) {
				$us->{suspend_queue} = 1;
			} elsif ($us->{pass} =~ m%^\*.*\|\|\*\%%) {
				$us->{suspend_acceptance} = 1;
			} elsif ($us->{pass} =~ m%^\*.*\|\|\*\!%) {
				$us->{suspend_pwchange} = 1;
			} elsif ($us->{pass} =~ m%^\*.*\|\|%) {
				$us->{suspend_email} = 1;
			}
			$us->{suspend} = 1 if $us->{pass} =~ m%^\*%;
			$us->{ctime} = "0" if $us->{ctime} =~ /\D/;
			$us->{atime} = "0" if $us->{atime} =~ /\D/;
			$us->{stime} = "0" if $us->{stime} =~ /\D/;
			push (@u, $us);
		}
		@u = sort {
			return 1 if $a->{user} gt $b->{user};
			return -1 if $a->{user} lt $b->{user};
			return 0;
		} @u;
		my $c = 0;
		my @l = ();
		foreach my $ue (@u) {
			$ue->{ident} = ++$c;
			push @l, $ue->{user};
		}
		$subst->{general}->{mark_all_string} = join(",", @l);
		$subst->{general}->{match} = scalar(@u);
		if (scalar(@u) > $results_per_screen) {
			$subst->{general}->{s} = $FORMref->{s};
			my $s = 0 + $FORMref->{s};
			my @t = ();
			for (my $i = 0; $i < scalar(@u); $i += $results_per_screen) {
				my $o = {};
				$o->{firstuser} = $u[$i]->{user};
				my $luid = ($i + $results_per_screen) > scalar(@u) ? scalar(@u) : ($i + $results_per_screen);
				$o->{lastuser} = $u[$luid-1]->{user};
				$o->{ident} = 1+$i;
				push @t, $o;
			}
			$s = 1 if ($s < 1 || $s != int($s) || $s > scalar(@u));
			my @z = ();
			if ($FORMref->{s} eq "*") {
				@z = @u;
			} else {
				my @u2 = @u;
				@z = splice @u2, $s-1, $results_per_screen;
				undef @u2;
			}
			$subst->{users} = \@z;
			$subst->{t} = \@t;
			$subst->{first_user} = $z[0];
			$subst->{last_user} = $z[$#z];
			$subst->{general}->{result_per_page} = $results_per_screen;
			$subst->{general}->{screen_up} = $s - $results_per_screen;
			$subst->{general}->{screen_up} = 1 if $subst->{general}->{screen_up} <= 1;
			$subst->{general}->{screen_down} = $s + $results_per_screen;
			$subst->{general}->{screen_down} = $s if $subst->{general}->{screen_down} > scalar(@u);
			$subst->{general}->{screen_last} = 1+$results_per_screen * int(($u[$#u]->{ident}-1) / $results_per_screen);
		} else {
			$subst->{users} = \@u;
		}
	}
	$subst->{stuff} = $stuff;
	screen_out("usermgr2", $subst);
}

###
### user_mgr_get_auth_groups
###
### Returns the groups a moderator is permitted to edit
###

sub user_mgr_get_auth_groups {
	my ($moderator, $groupfile, $groupmatch, $count) = @_;
	$groupfile = read_group_file() if ! defined $groupfile;
	my @garr = ();
	my @bad = ();
	my $groupcount = {};
	if ($count) {
		my $ct = 0;
		if ($DCONF->{pro} && $GLOBAL_OPTIONS->{database}) {
			dreq("sql-acct-PRO");
			($groupcount, $ct) = sql_count_users($moderator);
		} else {
			my $rg = read_account_file("users", undef, { return_fields => { user => 1, groups => 1, pass => 1 }, returnformat => "single_array" });
			foreach my $u (@{ $rg }) {
				my @g = grep { /\S/ } split(/\//, $u->{groups});
				if (scalar @g == 0 && $moderator eq $DCONF->{superuser}) {
					push @bad, $u;
				} else {
					foreach my $l (@g) {
						$groupcount->{$l} += 1;
					}
				}
				$ct++;
			}
		}
		dreq("fcn-acct");
		update_registered_users_file($ct);
	}
	foreach my $q (@{ $groupfile->{group_list} }) {
		if ($moderator ne $DCONF->{superuser}) {
			next if ! check_group_authorization($moderator, $q, $groupfile);
		}
		my $r = {};
		$r->{group} = $q;
		$r->{usrcnt} = 0 + $groupcount->{$q};
		push (@garr, $r) if (! defined $groupmatch || $groupmatch eq "" || $groupmatch eq $q);
	}
	return (\@garr, \@bad);
}

###
### is_user_filtered_freeware
###
### Basic filtering for user list (Discus Pro contains a more advanced
### filter in addition to this one).
###

sub is_user_filtered_freeware {
	my ($FORMref, $userref) = @_;
	return 1 if is_user_filtered_compare(1, "user", $FORMref, $userref);
	return 1 if is_user_filtered_compare(2, "email", $FORMref, $userref);
	return 1 if is_user_filtered_compare(3, "fullname", $FORMref, $userref);
	return 0;
}

###
### is_user_filtered_compare
###
### Compares a given field
###

sub is_user_filtered_compare {
	my ($number, $reference, $FORMref, $userref) = @_;
	my $fn = "f$number"; my $ln = "l$number";
	return 0 if $FORMref->{$ln} eq "";
	my $q = quotemeta(trim($FORMref->{$ln}));
	return 1 if $FORMref->{$fn} == 1 && $userref->{$reference} !~ m|$q|i;
	return 1 if $FORMref->{$fn} == 2 && $userref->{$reference} =~ m|$q|i;
	return 1 if $FORMref->{$fn} == 7 && $userref->{$reference} !~ m|^$q$|i;
	return 0;
}

###
### user_self_reg_save
###
### Saves user self-registration settings
###

sub user_self_reg_save {
	my ($FORMref, $result) = @_;
	dreq("selfreg-PRO", "webtags", "fcn-user-PRO");
	user_mgr_save_settings($FORMref, $result);
	user_mgr_2($FORMref, $result, { selfreg => 1 });
}

###
### user_form_handler
###
### Handles delete, copy, edit clicks from User Manager
###

sub user_form_handler {
	my ($FORMref, $result) = @_;
	my $w = {};
	if ($FORMref->{SELECTION_who} eq "*") {
		my $j = $FORMref->{markall} == 1 ? $FORMref->{MARKALL} : $FORMref->{MARK};
		foreach my $l (split(/\,/, $j)) {
			$w->{$l} = 1;
		}
	} else {
		$w->{$FORMref->{SELECTION_who}} = 1;
	}
	if ($FORMref->{SELECTION_action} eq "d") { ## Delete user(s)
		dreq("fcn-acct");
		return delete_account($w, undef, "users");
	}
	if ($FORMref->{SELECTION_action} eq "r") { ## Remove user(s) from group
		dreq("fcn-acct");
		return remove_account_group($w, undef, "users", $FORMref->{group});
	}
	if ($FORMref->{SELECTION_action} eq "s") { ## Suspend user(s)
		dreq("suspend-PRO");
		user_manager_suspend($FORMref, $result, $w);
	}
	if ($FORMref->{SELECTION_action} eq "e") { ## Edit user
		user_edit_someone_else($FORMref, $result, $w, undef);
	}
	if ($FORMref->{SELECTION_action} eq "u") { ## Promote user to moderator
		dreq("promote-PRO");
		promote_user_manager($w, $FORMref, $result);
	}
	if ($FORMref->{SELECTION_action} =~ /^[cm]$/) { ## Copy/move user
		my $subst = {};
		$subst->{general}->{username} = $result->[0]->{user};
		$subst->{general}->{menu} = 3;
		$subst->{general}->{move} = 1 if $FORMref->{SELECTION_action} eq "m";
		$subst->{general}->{users} = join(",", keys(%{$w}));
		$subst->{general}->{group} = $FORMref->{group};
		($subst->{authgroups}) = user_mgr_get_auth_groups($result->[0]->{user});
		$subst->{general}->{url} = "$PARAMS->{cgiurl}?action=user_mgr_2&username=$result->[0]->{user}&group=$FORMref->{group}";
		$subst->{general}->{skip_menu} = 1;
		$subst->{general}->{show_copy} = 1;
		screen_out("usermgr2", $subst, undef);
	}
	if ($FORMref->{SELECTION_action} eq "l") { ## Create a mailing list
		dreq("authpass");
		my $Z = read_account_file("users", $w, { returnformat => "single_array" });
		my %f = map { $_->{email}, 1 } @{$Z};
		my $subst = {};
		my @z = map { { email => $_ } } sort keys %f;
		$subst->{addresses} = \@z;
		screen_out("emlist", $subst);
	}
}

###
### user_edit_save_someone_else
###
### Saves changes you make to someone else's profile
###

sub user_edit_save_someone_else {
	my ($FORMref, $result, $database) = @_;
	$database = "users" if $database eq "";
	dreq("fcn-acct", "ui-prfle");
	my $s = {};
	$s->{user} = $FORMref->{user};
	$s->{fullname} = $GLOBAL_OPTIONS->{name_length_limit} ? substr($FORMref->{fullname}, 0, $GLOBAL_OPTIONS->{name_length_limit_number}) : $FORMref->{fullname};
	if ($FORMref->{new_pass_1} ne "") {
		my ($pw1, $pw2) = prepare_userpass_p($FORMref->{new_pass_1}, $FORMref->{new_pass_2});
		if ($pw1 ne $pw2 || length($pw1) < 1) {
			error_message(read_language()->{PROFILE_CHPASS_ERROR}, read_language()->{PROFILE_CHPASS_ERROR_MATCH});
		}
		$s->{new_password} = $pw1;
	}
	$s->{email} = trim($FORMref->{email});
	$s->{edit} = $FORMref->{edit_true_false};
	if ($FORMref->{email_included} == 1) {
		my $h = {}; my $rr = {};
		my @r = split(/,/, $FORMref->{bysub});
		foreach my $_r (@r) {
			if ($_r =~ m|^(\d+):(.*)|) {
				my ($topic, $splits) = ($1, $2);
				my @sp = split(/&/, $splits);
				foreach my $sp (@sp) {
					$rr->{$topic}->{$sp} = 1;
				}
			}
		}
		my @s = split(/,/, $FORMref->{notify});
		foreach my $s (@s) {
			if ($s =~ m|^0+$|) {
				$h->{own_post} = 1 if $s eq "0";
				$h->{reply_post} = 1 if $s eq "00";
				$h->{format_html} = 1 if $s eq "000";
			} elsif (defined $rr->{$s}) {
				$h->{$s} = $rr->{$s};
			} else {
				$h->{$s}->{'*'} = 1 if ! defined $rr->{$s};
			}
		}
		if ($DCONF->{pro}) {
#			dreq("fcn-prfl-PRO");
#			($h) = notification_simple_prune($h, $FORMref, undef, $result);
		}
		$s->{notify} = email_notification_hash_to_line($h);
	}
	if (defined $FORMref->{cmonth}) {
		my $k = eval '
			use Time::Local;
			my $x = timelocal(0,0,12,$FORMref->{cday},$FORMref->{cmonth},$FORMref->{cyear});
			$x;
		';
		if ($@ eq "" && $k > 0) {
			$s->{ctime} = $k;
		}
	}
	my ($success, $failure) = update_account($s, $database, undef);
	if ($DCONF->{pro} && scalar(@{$success})) {
		dreq("fcn-prfl-PRO");
		my $mt = $database eq "passwd" ? 10 : 8;
		$mt = 10 if $result->[0]->{user} eq $DCONF->{superuser};
		my $q = {};
		$q->{status} = $FORMref->{status} if $FORMref->{status} >= 3 && $FORMref->{status} <= $mt;
		$q->{posts} = $FORMref->{posts} if $FORMref->{posts} >= 0;
		enhanced_profile_update($FORMref, $success->[0], $q);
	}
	my $r = undef;
	$r = $success->[0] if ref $success eq "ARRAY" && ref $success->[0] eq "HASH";
	return [$r];
}

###
### user_edit_someone_else
###
### Edit someone else's user (or moderator) account
###

sub user_edit_someone_else {
	my ($FORMref, $result, $w, $subst_in, $stuff, $res, $file) = @_;
	dreq("authpass", "template", "ui-prfle");
	$file = "users" if $file eq "";
	$res = read_account_file($file, $w, { returnformat => 'single_array'}) if ! defined $res;
	my $subst = defined $subst_in ? $subst_in : {};
	error_message("Edit User Error", "The selected user does not exist.", 0, 1) if ref $res ne 'ARRAY';
	my $h = $res->[0];
	my @m = ();
	$subst->{messages} = \@m;
	$subst->{general}->{spell_checking_enabled} = (-e "$DCONF->{admin_dir}/msg_index/wordlist.txt") * $GLOBAL_OPTIONS->{spell_check};
	$subst->{stuff} = $stuff;
	my $current = undef;
	if ($DCONF->{pro}) {
		dreq("fcn-prfl-PRO", "authwrap-PRO");
		($subst) = profile_editor_variables($subst, $FORMref, undef, $current, $h, $result, $FORMref->{username});
	} else {
		($subst) = profile_editor_email_notification_freeware($subst, $FORMref, undef, $current, $h);
	}
	$subst->{general}->{group} = $FORMref->{group};
	$subst->{general}->{editing} = $file eq "passwd" ? 2 : 1;
	$subst->{general}->{self} = $result->[0]->{user};
	$subst->{general}->{username} = $h->{user};
	$subst->{general}->{action} = $file eq "passwd" ? "mod_edit_save" : "user_edit_save";
	$subst->{general}->{action_url} = $PARAMS->{cgiurl};
	$subst->{ffield}->{fullname} = $h->{fullname};
	$subst->{ffield}->{email} = $h->{email};
	$subst->{ffield}->{edit} = $h->{edit};
	$subst->{general}->{skip_top_options_bar} = 1;
	$subst->{general}->{skip_no_frames_buttons} = 1;
	if ($GLOBAL_OPTIONS->{email}) {
		if ($GLOBAL_OPTIONS->{send_mail_only_admins} == 0) {
			$subst->{general}->{email_notification} = 1;
		} else {
			foreach my $r (@{ $result }) {
				$subst->{general}->{email_notification} = 1 if $r->{database} eq "passwd";
			}
		}
	}
	my @l = localtime($h->{ctime} + $GLOBAL_OPTIONS->{'timezone'}*3600);
	$subst->{creation}->{month} = $l[4];
	$subst->{creation}->{day} = $l[3];
	$subst->{creation}->{year} = 1900 + $l[5];
	$subst->{creation}->{curryear} = 1900 + (localtime(time))[5];
	return $subst if defined $subst_in;
	screen_out("profile", $subst);
}

1;
