# FILE: upgr31.pl
# DESCRIPTION: Upgrade procedure from 3.10 to 4.00
#-------------------------------------------------------------------------------
# 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);

###
### upgrade_version_310
###
### Manager for the upgrade from Discus 3.10 to 4.00+
###

sub upgrade_version_310 {
	my ($FORMref, $result, $cookie) = @_;
	dreq("authpass", "filter31");
	if (! defined $result || ref $result ne 'ARRAY' || scalar(@{$result}) == 0) {
		$result = check_password($FORMref->{username}, undef, { type_required => 'moderator' }, $FORMref->{'COOKIE'});	
	}
	bad_login( { username => 1 } ) if scalar(@{ $result }) == 0;
	upgrade_reserved_for_superuser() if $result->[0]->{user} ne $DCONF->{superuser};
	if (! $FORMref->{upgrtf}) {
		my $tempfile = join("", time, $$); $tempfile =~ s/\W//g;
		upgrade_version_310_A();
		upgrade_version_310_gauge($tempfile, $result, { descr => 402, posi => 2 }, $cookie);
	}
	my $tempfile = $FORMref->{upgrtf}; $tempfile =~ s/\W//g;
	upgrade_version_310_B($tempfile, $FORMref, $result) if $FORMref->{posi} <= 2;
	upgrade_version_310_C($tempfile, $FORMref, $result) if $FORMref->{posi} <= 3;
	upgrade_version_310_D($tempfile, $FORMref, $result) if $FORMref->{posi} <= 4;
	upgrade_version_310_E($tempfile, $FORMref, $result) if $FORMref->{posi} <= 5;
	upgrade_version_310_F($tempfile, $FORMref, $result) if $FORMref->{posi} <= 6;
	upgrade_version_310_G($tempfile, $FORMref, $result) if $FORMref->{posi} <= 7;
	upgrade_version_310_H($tempfile, $FORMref, $result) if $FORMref->{posi} <= 8;
	upgrade_version_310_I($tempfile, $FORMref, $result) if $FORMref->{posi} <= 9;
	upgrade_version_310_J($tempfile, $FORMref, $result) if $FORMref->{posi} <= 10;
	upgrade_version_310_K($tempfile, $FORMref, $result) if $FORMref->{posi} <= 11;
	upgrade_version_310_L($tempfile, $FORMref, $result) if $FORMref->{posi} <= 12;
	upgrade_version_310_M($tempfile, $FORMref, $result);
}

###
### upgrade_reserved_for_superuser
###
### Error message if an upgrade needs to be done and an ordinary moderator logs in
###

sub upgrade_reserved_for_superuser {
	my $subst = {};
	$subst->{general}->{upgrade} = 2;
	screen_out("badlogin", $subst);	
}

###
### upgrade_version_310_A
###
### Upgrades administration files (posting.txt & security.txt => topicprv.txt)
###

sub upgrade_version_310_A {
	my @sd = map { join("/", $DCONF->{admin_dir}, $_) } ('backups', 'data', 'locks', 'msg_index', 'profiles', 'queue', 'secure', 'skins');
	push @sd, map { join("/", $DCONF->{admin_dir}, 'data', $_) } ('debug', 'dtaskman', 'newusers', 'pop3_in', 'tokens', 'updates');
	push @sd, map { join("/", $DCONF->{admin_dir}, 'msg_index', $_) } ('lim-accs', 'searches', 'temp', 'uploads');
	push @sd, join("/", $DCONF->{message_dir}, "thumbnails");
	map { _upgrade_version_310_A_mkdir($_) } @sd;
	touch_createfile("$DCONF->{admin_dir}/data/db.txt");
	if (! -e "$DCONF->{admin_dir}/data/topicprv.txt") {
		dreq("fcn-usrp");
		write_topic_privilege_file(import_posting_reading_privileges(), { create => 1 });
	}
	if (! -e "$DCONF->{admin_dir}/skins/$GLOBAL_OPTIONS->{skinchoice}.tmpl") {
		my $scorig = $GLOBAL_OPTIONS->{skinchoice};
		my $scnew = $scorig;
		$scnew = $` if $scnew =~ /\//;
		my @avail_skins = ('tables2', 'classic');
		unshift @avail_skins, 'classic' if $scorig =~ /^classic/;
		unshift @avail_skins, 'classic' if $scorig =~ /^updated/;
		unshift @avail_skins, 'tables2' if $scorig =~ /^tables/;
		unshift @avail_skins, 'tables2' if $scorig =~ /^supportforum/;
WI:		while (! -e "$DCONF->{admin_dir}/skins/$scnew.tmpl") {
			if ($scnew = shift @avail_skins) {
				next WI if -e "$DCONF->{admin_dir}/skins/$scnew.tmpl";
			} else {
				template_error("Restore Error", "Cannot find a skin to use for this board");
			}
		}
		my $JH = {};
		$JH->{skinchoice} = $scnew if $scorig ne $scnew;
		$JH->{alt_color_1} = "#0000a0" if length($GLOBAL_OPTIONS->{alt_color_1}) < 3;
		$JH->{alt_color_2} = "#f7f7f7" if length($GLOBAL_OPTIONS->{alt_color_2}) < 3;
		for (my $i = 3; $i <= 10; $i++) {
			my $l = join("_", "alt", "color", $i);
			next if $GLOBAL_OPTIONS->{$l} ne "" && $GLOBAL_OPTIONS->{$l} ne "0";
			$JH->{$l} = $i % 2 ? "#cccccc" : "#f7f7f7";
		}
		if (scalar(keys %{$JH})) {
			dreq("adm-opts");
			options_save($JH);
		}
	}	
}

sub _upgrade_version_310_A_mkdir {
	my ($dir) = @_;
	return if -d $dir;
	unlink "$dir";
	mkdir("$dir", oct($DCONF->{perms0777}));
	chmod(oct($DCONF->{perms0777}), "$dir");
}

###
### upgrade_version_310_B
###
### Delete unnecessary files (most, but not all) and some miscellaneous upgrades
###

sub upgrade_version_310_B {
	my ($tempfile, $FORMref, $result) = @_;
	
	# Old message source dirs (replaced by inverse_webtags function)
	
	if (opendir(DIR, "$DCONF->{admin_dir}/msg_index")) {
		my @dir = map { $_ } readdir(DIR);
		closedir(DIR);
		foreach my $dir (grep { /^\d+$/ } @dir) {
			if (opendir(DIR, "$DCONF->{admin_dir}/msg_index/$dir")) {
				my @f = map { "$DCONF->{admin_dir}/msg_index/$dir/$_" } readdir(DIR);
				closedir(DIR);
				unlink grep (-f $_, @f);
				rmdir("$DCONF->{admin_dir}/msg_index/$dir");
			}
		}
		if (my @x = grep(/^\-/, @dir)) {
			unlink map { "$DCONF->{admin_dir}/msg_index/$_" } @x;
		}
		unlink "$DCONF->{admin_dir}/msg_index/attach-mgr.txt";
	}
	
	# Old src-board-subs-* files (replaced by much nicer *.pl files)
	
	if (opendir(DIR, "$DCONF->{admin_dir}/source")) {
		my @dir = map { "$DCONF->{admin_dir}/source/$_" } readdir(DIR);
		closedir(DIR);
		@dir = grep (/\/src-board-subs/, @dir);
		push @dir, "$DCONF->{admin_dir}/source/dep.txt";
		unlink @dir;
	}	
	
	# Old file locks, just for fun	
	
	if (opendir(DIR, "$DCONF->{admin_dir}/locks")) {
		my @dir = map { "$DCONF->{admin_dir}/locks/$_" } grep { ! /^\./ } readdir(DIR);
		closedir(DIR);
		unlink @dir;
	}
	
	# Various remnants in administration directory
	
	my @files = map { join("/", $DCONF->{admin_dir}, $_) } ('posting.txt', 'frontpage_conf.txt', 'security.txt', 'queue.txt', 'upgrade.txt');
	unlink @files;	
	
	# If you're running Discus Pro, make sure necessary files/directories exist
	
	if ($DCONF->{pro}) {
		dreq("pro-init-PRO");
		create_discus_pro_files();
		convert_selfreg_file();
	}
	
	# Convert email.txt
	
	my $emt = readfile("$DCONF->{admin_dir}/email.txt", "upgrade_version_310_B", { no_lock => 1, no_unlock => 1, zero_ok => 1 });
	my @f = ('to', 'from', 'subject', 'bcc', 'replyto', 'user');
	@f = map { join("", $_, "address") } @f;
	my %f = map { $_, 1 } @f;
	foreach my $line (@{ $emt }) {
		if ($line =~ m|^(.*?)=(.*)|) {
			my ($var, $val) = ($1, $2);
			if ($f{$var} == 1) {
				if ($val == 0) {
					$val = 1;
				} elsif ($val == 1) {
					$val = 0;
				}
				$line = join("=", $var, join("", $val, "\n"));
			}
		}
	}
	writefile("$DCONF->{admin_dir}/email.txt", $emt, "upgrade_version_310_B", { no_lock => 1, no_unlock => 1, zero_ok => 1 });

	# Done
	
	upgrade_version_310_gauge($tempfile, $result, { descr => 403, posi => 3 });
}

###
### upgrade_version_310_C
###
### Calculate moderator editing privileges
###

sub upgrade_version_310_C {
	my ($tempfile, $FORMref, $result) = @_;
	dreq("fcn-priv", "fcn-grp");
	my $Z = read_privilege_file(1);
	my $T = board_topics();
	my $G = read_group_file();
	my @actions = ();
	foreach my $t (@{$T}) {
		next if $t->{type} != 1;
		next if defined $Z->{by_topic}->{$t->{number}};
		my $PG = GetPage($t->{number}, $t->{number}, { no_error => 1 });
		if ($PG->{head}->{topic_number} == $t->{number} && $PG->{head}->{owner} ne "") {
			my $X = $G->{by_grp}->{$PG->{head}->{owner}};
			if (ref $X ne 'HASH' || scalar(keys(%{$X})) == 0) {
				push @actions, { topic => $t->{number}, action => 'add_topic', moderator => $DCONF->{superuser} };
			} else {
				$X->{$DCONF->{superuser}} = 1;
				my $mod_str = join(",", sort keys %{$X});
				push @actions, { topic => $t->{number}, action => 'add_topic', moderator => $mod_str };
			}
		} else {
			push @actions, { topic => $t->{number}, action => 'add_topic', moderator => $DCONF->{superuser} };
		}	
	}
	touch_createfile("$DCONF->{admin_dir}/privs.txt");
	write_privilege_file(\@actions, 1) if scalar(@actions);	
	upgrade_version_310_gauge($tempfile, $result, { descr => 403, posi => 4 });
}

###
### upgrade_version_310_D
###
### Convert version 3.1 enhanced user profile fields file
###

sub upgrade_version_310_D {
	my ($tempfile, $FORMref, $result) = @_;
	return if ! $DCONF->{pro};
	dreq("fcn-prfl-PRO");
	my $Z = read_profile_fields();
	if (ref $Z eq 'ARRAY' && scalar @{$Z} > 0) {
		upgrade_version_310_gauge($tempfile, $result, { descr => 404, posi => 5 });
	}
	my $old = readfile("$DCONF->{admin_dir}/profiles/config.txt", "upgrade_version_310_D", { create => 1 });
	if (scalar(@{$old})) {
		my @newfld = ();
		foreach my $line (@{$old}) {
			chomp $line;
			my ($var, $descr, $disp, $type, $size, $req) = split(/:/, $line);
			my $k = join(":", 1+scalar(@newfld), $descr, $disp, $type, $size, $req, 1+scalar(@newfld));
			push @newfld, join("", $k, "\n");
			last if scalar(@newfld) >= 20;
		}
		writefile("$DCONF->{admin_dir}/data/fields.txt", \@newfld, "upgrade_version_310_D");
	}
	upgrade_version_310_gauge($tempfile, $result, { descr => 404, posi => 5 });
}

###
### upgrade_version_310_E
###
### From posting logs, determine creation times of various users & count posts
###

sub upgrade_version_310_E {
	my ($tempfile, $FORMref, $result) = @_;
	my $tfile1 = join("/", $DCONF->{admin_dir}, "backups", join(".", $tempfile, "31E"));
	my $tfile2 = join("/", $DCONF->{admin_dir}, "backups", join(".", $tempfile, "PLG"));
	my $tfile3 = join("/", $DCONF->{admin_dir}, "backups", join(".", $tempfile, "PCT"));
	if (! -e $tfile1) {
		touch_createfile($tfile2);
		touch_createfile($tfile3);
		my $l = directory_list("$DCONF->{admin_dir}/msg_index", '^\d+-log\.txt$', 1);
		return if scalar(@{$l}) == 0;
		touch_createfile($tfile1);
		my @l = map { join("", $_, "\n") } @{$l};
		writefile($tfile1, \@l, "upgrade_version_310_E", { no_lock => 1, no_unlock => 1 });
		upgrade_version_310_gauge($tempfile, $result, { descr => 404, posi => 5, total => scalar(@l) });
	} else {
		my $TSF1 = readfile($tfile2, "upgrade_version_310_E", { no_lock => 1, no_unlock => 1, zero_ok => 1 });
		my %J = map { chomp $_; split(/\t/, $_, 2); } @{$TSF1};
		my $TSF2 = readfile($tfile3, "upgrade_version_310_E", { no_lock => 1, no_unlock => 1, zero_ok => 1 });
		my %PC = map { chomp $_; split(/\t/, $_, 2); } @{$TSF2};
		undef $TSF1;
		undef $TSF2;
		my $T1 = readfile($tfile1, "upgrade_version_310_E", { no_lock => 1, no_unlock => 1 });
		my $lg = shift @{$T1};
		if (open (LOG, "< $DCONF->{admin_dir}/msg_index/$lg")) {
			while (<LOG>) {
				my $z = _upgrade_version_310_E_line_to_hash($_);
				next if ref $z ne 'HASH';
				$PC{$z->{uid}}++;
				next if $J{$z->{uid}} < $z->{ptime} && $J{$z->{uid}} != 0;
				$J{$z->{uid}} = $z->{ptime};
			}
			close (LOG);
		}
		my @TSF = map { join("", $_, "\t", $J{$_}, "\n") } keys %J;
		my @PCT = map { join("", $_, "\t", $PC{$_}, "\n") } keys %PC;
		writefile($tfile2, \@TSF, "upgrade_version_310_E", { no_lock => 1, no_unlock => 1, zero_ok => 1 });
		writefile($tfile3, \@PCT, "upgrade_version_310_E", { no_lock => 1, no_unlock => 1, zero_ok => 1 });
		if (ref $T1 ne 'ARRAY' || scalar @{$T1} == 0) {
			unlink $tfile1;
			upgrade_version_310_gauge($tempfile, $result, { descr => 406, posi => 6 });
		} else {
			writefile($tfile1, $T1, "upgrade_version_310_E", { no_lock => 1, no_unlock => 1, zero_ok => 1 });
			upgrade_version_310_gauge($tempfile, $result, { descr => 405, posi => 5, total => $FORMref->{total}, done => 1 + $FORMref->{done} });
		}
	}	
}

sub _upgrade_version_310_E_line_to_hash {
	my ($line) = @_;
	my ($post, $uid, $ptime) = split(/;/, $line, 4);
	my ($username, $group) = split(/:/, $uid);
	return undef if $group eq "PUBLIC";
	return undef if $group eq "";
	return undef if $ptime == 0;
	return { uid => join(":", $username, 'MODERATOR'), ptime => $ptime } if $group eq "MODERATOR";	
	return { uid => $username, ptime => $ptime };
}

###
### upgrade_version_310_F
###
### Update user database
###

sub upgrade_version_310_F {
	my ($tempfile, $FORMref, $result) = @_;
	my $tfile1 = join("/", $DCONF->{admin_dir}, "backups", join(".", $tempfile, "users.bak"));
	my $tfile2 = join("/", $DCONF->{admin_dir}, "backups", join(".", $tempfile, "passwd.bak"));
	if (! -e $tfile1) {
		foreach my $fr ("users.txt/$tfile1", "passwd.txt/$tfile2") {
			my ($file, $tmpfile) = split(/\//, $fr, 2);
			if (open(SRC, "< $DCONF->{admin_dir}/$file")) {
				if (open(DEST, "> $tmpfile")) {
					while (<SRC>) {
						print DEST;
					}
					close (DEST);
					close (SRC);
				} else {
					error_message("File Creation Error", "Could not write to $tmpfile");
				}
			} else {
				error_message("File Creation Error", "Could not open [admin_dir]/$file");
			}
		}
		upgrade_version_310_gauge($tempfile, $result, { descr => 406, posi => 6, total => 3, done => 1 });
	}	
	my $tfile3 = join("/", $DCONF->{admin_dir}, "backups", join(".", $tempfile, "PLG"));
	my $tfile4 = join("/", $DCONF->{admin_dir}, "backups", join(".", $tempfile, "DUP"));
	my $TSF = readfile($tfile3, "upgrade_version_310_F", { no_lock => 1, no_unlock => 1, zero_ok => 1 });
	my %J = map { chomp $_; split(/\s+/, $_, 2); } @{$TSF};
	undef $TSF;
	my $src = $FORMref->{done} == 1 ? "users.txt" : "passwd.txt";
	my $dest = $FORMref->{done} == 1 ? $tfile1 : $tfile2;
	my $timecache = time;
	my $seen = {};
	open (SRC, "< $dest");
	while (<SRC>) {
		my @s = split(/:/, $_);
		my $q = user_line_as_hash($_);
		if (scalar(@s) < 10) {
			$q = v31_user_line_as_hash($_);
			my $uid = $q->{user};
			$uid .= ":MODERATOR" if $src eq "passwd.txt";
			$q->{ctime} = $J{$uid} if $J{$uid} > 0;
			$q->{ctime} = $q->{atime} if ($q->{ctime} == 0 || $q->{atime} < $q->{ctime}) && $q->{atime} > 0;
			$q->{ctime} = $q->{stime} if ($q->{ctime} == 0 || $q->{stime} < $q->{ctime}) && $q->{stime} > 0;
			$q->{ctime} = $timecache if $q->{ctime} == 0;
			$q->{ctime} = $' if $q->{ctime} =~ /.*\D/;
		}
		push @{ $seen->{$q->{user}}->{$q->{pass}} }, $q;
	}
	close (SRC);
	my @dupe = ();
	open (DEST, "> $DCONF->{admin_dir}/$src");
	foreach my $user (keys %{$seen}) {
		my $Z1 = $seen->{$user};
		if (scalar keys(%{$Z1}) == 1) {
			my $Z2 = $Z1->{(keys(%{$Z1}))[0]};
			my $q = $Z2->[0];
			my @g = map { $_->{groups} } @{$Z2};
			$q->{groups} = join("/", "", @g, "");
			$q->{groups} =~ s%/+%/%g;
			print DEST generate_user_line($q);
		} else {
			my @P = keys %{$Z1};
			my $c = 0;
			my $p = $Z1->{shift @P};
			my $q = $p->[0];
			my @g = map { $_->{groups} } @{$p};
			$q->{groups} = join("/", "", @g, "");
			print DEST generate_user_line($q);
			foreach my $j (@P) {
				my $qcpy = $Z1->{$j}->[0];
				$c++ until ! defined $seen->{join("_", $user, $c)};
				$qcpy->{user} = join("_", $user, $c);
				$seen->{$user} = { used => 1 };
				my @g = map { $_->{groups} } @{$Z1->{$j}};
				$qcpy->{groups} = join("/", "", @g, "");
				$qcpy->{groups} =~ s%/+%/%g;
				print DEST generate_user_line($qcpy);
				push @dupe, { old => $user, new => $qcpy, groups => $g[0] };
			}			
		}
	}	
	close (DEST);
	if (scalar @dupe) {
		open (TFILE, "> $tfile4");
		foreach my $d (@dupe) {
			print TFILE join("", join(":", $d->{old}, $d->{new}->{user}, $d->{new}->{email}, $d->{new}->{fullname}, $d->{groups}), "\n");
		}
		close (TFILE);
	}
	if ($FORMref->{done} == 1) {	
		upgrade_version_310_gauge($tempfile, $result, { descr => 406, posi => 6, total => 3, done => 2 });
	} else {
		unlink $tfile3;
		upgrade_version_310_gauge($tempfile, $result, { descr => 407, posi => 7 });
	}	
}

###
### upgrade_version_310_G
###
### Import user profiles
###

sub upgrade_version_310_G {
	my ($tempfile, $FORMref, $result) = @_;
	if (! $DCONF->{pro}) {
		unlink join("/", $DCONF->{admin_dir}, "backups", join(".", $tempfile, "PCT"));
		return;
	}
	dreq("fcn-prfl-PRO");
	my $tfile1 = join("/", $DCONF->{admin_dir}, "backups", join(".", $tempfile, "PRL"));
	if (! -e $tfile1) {
		my $dl = directory_list("$DCONF->{admin_dir}/profiles", '^\w+\-', 1);
		my @dl = sort map { join("", $_, "\n") } @{$dl};
		return if scalar(@dl) == 0;
		writefile($tfile1, \@dl, "upgrade_version_310_G", { no_lock => 1, no_unlock => 1, zero_ok => 1 });
		upgrade_version_310_gauge($tempfile, $result, { descr => 407, posi => 7, total => scalar(@dl) });
	}
	my $tfile4 = join("/", $DCONF->{admin_dir}, "backups", join(".", $tempfile, "DUP"));
	my $tfile5 = join("/", $DCONF->{admin_dir}, "backups", join(".", $tempfile, "PCT"));
	my $plist = readfile($tfile1, "upgrade_version_310_G", { no_lock => 1, no_unlock => 1, zero_ok => 1 });
	my $dlist = readfile($tfile4, "upgrade_version_310_G", { no_lock => 1, no_unlock => 1, zero_ok => 1, create => 1 });
	my $clist = readfile($tfile5, "upgrade_version_310_G", { no_lock => 1, no_unlock => 1, zero_ok => 1 });
	my $oldconf = readfile("$DCONF->{admin_dir}/profiles/config.txt", "upgrade_version_310_G", { no_lock => 1, no_unlock => 1, create => 1 });
	my $ctr = 0; my $oldconfhash = {};
	foreach my $line (@{$oldconf}) {
		chomp $line;
		my ($var, $descr, $disp, $type, $size, $req) = split(/:/, $line);
		$oldconfhash->{$var} = $ctr;
		$ctr++;
	}
	my %q = undef;
	my %m = undef;
	foreach my $line (@{$plist}) {
		$line =~ s/\.txt\s*$//;
		my ($user, $group) = split(/-/, $line);
		push @{ $q{$user} }, $group if $group ne 'MODERATOR';
		$m{$user} = 1 if $group eq 'MODERATOR';
	}
	my %dupe = undef;
	foreach my $line (@{$dlist}) {
		chomp $line;
		my ($olduser, $newuser, $email, $fullname, $group) = split(/:/, $line);
		$dupe{$olduser} = { newuser => $newuser, group => $group };		
	}
	my %posts = undef;
	foreach my $line (@{$clist}) {
		chomp $line;
		my ($uid, $posts) = split(/\t/, $line);
		$posts{$uid} = $posts;
	}
	my %o1 = undef;
	my %o2 = undef;
	my $done = 0;
	while ($done <= 15 && scalar(keys %q)) {
		my $k = (keys %q)[0]; $done++;
		$o1{$k} = {};
		foreach my $grp (@{ $q{$k} }) {
			my $j = readfile("$DCONF->{admin_dir}/profiles/$k-$grp.txt", "upgrade_version_310_G", { no_lock => 1, no_unlock => 1, create => 1, zero_ok => 1 });
			if (ref $j ne 'ARRAY' || scalar(@{$j}) == 0) {
				delete $q{$k};
				next;
			}
			$FORMref->{done}++;
			my $L = enhanced_user_profile_3031($j, $k, $oldconfhash);
			$L->{posts} = $posts{$k};
			if ($dupe{$k}->{group} eq $grp) {
				$L->{username} = $dupe{$k}->{newuser};
				hash_merge($o1{$dupe{$k}->{newuser}}, $L, 1);
			} else {
				$L->{username} = $k;
				hash_merge($o1{$k}, $L, 1);
			}
			unlink "$DCONF->{admin_dir}/profiles/$k-$grp.txt";			
		}
		delete $q{$k};
		$done++;
	}
	while ($done <= 15 && scalar(keys %m)) {
		my $k = (keys %m)[0]; $done++;
		my $j = readfile("$DCONF->{admin_dir}/profiles/$k-MODERATOR.txt", "upgrade_version_310_G", { no_lock => 1, no_unlock => 1, create => 1, zero_ok => 1 });
		if (ref $j ne 'ARRAY' || scalar(@{$j}) == 0) {
			delete $m{$k};
			next;
		}
		my $L = enhanced_user_profile_3031($j, $k, $oldconfhash);
		$L->{posts} = $posts{"$k:MODERATOR"};
		$L->{username} = $k;
		$o2{$k} = $L;
		unlink "$DCONF->{admin_dir}/profiles/$k-MODERATOR.txt";			
		delete $m{$k};
		$done++;
		$FORMref->{done}++;
	}
	if (scalar(keys %o1) + scalar(keys %o2)) {
		if (scalar(keys %o1)) {
			open (USERS, ">> $DCONF->{admin_dir}/data/us-accts.txt") || error_message("File Append Error", "Could not append [admin_dir]/data/us-accts.txt");
			foreach my $k (keys %o1) {
				print USERS enhanced_profile_hash_to_line($o1{$k}, 0, 0) if $k ne "";
			}
			close (USERS);
		}
		if (scalar(keys %o2)) {
			open (MODS, ">> $DCONF->{admin_dir}/data/md-accts.txt") || error_message("File Append Error", "Could not append [admin_dir]/data/md-accts.txt");
			foreach my $k (keys %o2) {
				my $su = $DCONF->{superuser} eq $k ? 1 : 0;
				print MODS enhanced_profile_hash_to_line($o2{$k}, 1, $su) if $k ne "";
			}
			close (MODS);
		}
	}
	if (scalar(keys %q) + scalar(keys %m)) {
		my @d = ();
		foreach my $k (keys %q) {
			foreach my $g (@{$q{$k}}) {
				push @d, join("", join("-", $k, $g), ".txt\n");
			}
		}
		foreach my $k (keys %m) {
			push @d, join("", join("-", $k, "MODERATOR"), ".txt\n");
		}
		writefile($tfile1, \@d, "upgrade_version_310_G", { no_lock => 1, no_unlock => 1, zero_ok => 1 });
		upgrade_version_310_gauge($tempfile, $result, { descr => 407, posi => 7, total => $FORMref->{total}, done => $FORMref->{done} });
	} else {
		unlink $tfile1;
		unlink "$DCONF->{admin_dir}/profiles/config.txt";
		unlink $tfile5;
		upgrade_version_310_gauge($tempfile, $result, { descr => 408, posi => 8 });
	}				
}

###
### upgrade_version_310_H
###
### Combined reindexing & build search index
###

sub upgrade_version_310_H {
	my ($tempfile, $FORMref, $result) = @_;
	dreq("fcn-regn", "fcn-indx", "fcn-logs");
	if (! -e "$DCONF->{admin_dir}/backups/$tempfile.TMP") {
		my $d = directory_list("$DCONF->{admin_dir}/msg_index", '^\d+-(tree|search)\.TMP$', 1);
		unlink @{$d} if ref $d eq "ARRAY" && scalar(@{$d});
		my $out = incremental({ operation => 1, tempfile => $tempfile });
		upgrade_version_310_gauge($tempfile, $result, { total => $out->{count}, descr => 408, posi => 8 });
	}
	my $tempfileR = incremental({ operation => "read", tempfile => $tempfile });
	my $timer = time;
	my $topic = 0;
	my $fileopen = 0;
	my $ctr = 0;	
	while (1) {
		my $file = shift(@{ $tempfileR->{data} }) or last;
		my ($tn, $pn) = split(/\D/, $file);
		next if $tn == 0;
		if ($topic != 0 && $topic != $tn) {
			unshift(@{ $tempfileR->{data} }, $file);
			last;
		}
		if ($topic == 0) {
			open (TREETMP, ">> $DCONF->{admin_dir}/msg_index/$tn-tree.TMP") || error_message("File Write Error", "Could not create temporary tree index file in 'msg_index' directory!");
			open (SRCHTMP, ">> $DCONF->{admin_dir}/msg_index/$tn-search.TMP") || error_message("File Write Error", "Could not create temporary tree index file in 'msg_index' directory!");
			$topic = $tn;
			$fileopen = 1;
		}
		my $get = GetPage($tn, $pn, { no_lock => 1, no_unlock => 1, no_error => 1 });
		next if $get->{head}->{me_number} ne $pn;
		my ($pdata, $subs) = build_page_data($tn, $pn, $get);
		my $pdta1 = shift(@{ $pdata });
		chomp $pdta1;
		if (scalar(@{ $subs })) {
			print TREETMP $pdta1, "\t", join(",", @{ $subs }), "\n";
		} else {
			print TREETMP $pdta1, "\tX\n";
		}
		if (scalar(@{ $pdata })) {
			print TREETMP @{ $pdata };
		}
		foreach my $m (@{ $get->{messages} }) {
			$m->{postindex} = $m->{number};
			$m->{text} = search_stop($m->{text});
			print SRCHTMP search_hash_to_line($m);
		}
		$ctr++;
		$FORMref->{done}++;
		if ($ctr % 10 == 0) {
			last if time > ($timer+2+$GLOBAL_OPTIONS->{reindex_secs});
		}
	}
	close (TREETMP) if $fileopen;
	close (SRCHTMP) if $fileopen;
	my $res = incremental({ operation => "write", data => $tempfileR->{data}, tempfile => $tempfile });
	if ($res->{'continue'}) {
		upgrade_version_310_gauge($tempfile, $result, { done => $FORMref->{done}, total => $FORMref->{total}, descr => 408, posi => 8 });
	} else {
		upgrade_version_310_gauge($tempfile, $result, { descr => 409, posi => 9 });
	}
}

###
### upgrade_version_310_I
###
### Conclude Reindexing
###

sub upgrade_version_310_I {
	my ($tempfile, $FORMref, $result) = @_;
	dreq("fcn-indx");
	my $d = directory_list("$DCONF->{admin_dir}/msg_index", '^\d+-tree\.TMP$', 1);
	$FORMref->{total} = scalar(@{$d}) if ! $FORMref->{total};
	if (scalar(@{$d}) == 0) {
		_upgrade_version_310_I_maintree();
		upgrade_version_310_gauge($tempfile, $result, { descr => 410, posi => 10 });
	}
	my $k = pop @{$d};
	if ($k =~ m|^(\d+)-tree\.TMP$|) {
		sorter($1);
	} else {
		unlink "$DCONF->{admin_dir}/msg_index/$k";
	}
	upgrade_version_310_gauge($tempfile, $result, { descr => 409, posi => 9, total => $FORMref->{total}, done => 1 + $FORMref->{done} });
}

sub _upgrade_version_310_I_maintree {
	my $J = board_topics();
	my @t = grep($_->{type} == 1, @{$J});
	my @M = ();
	foreach my $T (@t) {
		my $k = read_tree($T->{number}, { no_lock => 1, no_unlock => 1, zero_ok => 1 });
		push @M, $k->[0] if ref $k eq 'ARRAY' && defined $k->[0]->{topic};
	}
	write_tree(0, \@M, { no_lock => 1 });
}

###
### upgrade_version_310_J
###
### Conclude Search Log Indexing
###

sub upgrade_version_310_J {
	my ($tempfile, $FORMref, $result) = @_;
	dreq("fcn-indx");
	my $d = directory_list("$DCONF->{admin_dir}/msg_index", '^\d+-search\.TMP$', 1);
	$FORMref->{total} = scalar(@{$d}) if ! $FORMref->{total};
	if (scalar(@{$d}) == 0) {
		upgrade_version_310_gauge($tempfile, $result, { descr => 411, posi => 11 });
	}
	my $k = pop @{$d};
	if ($k =~ m|^(\d+)-search\.TMP$|) {
		open (SRC, "< $DCONF->{admin_dir}/msg_index/$k");
		open (DEST, "> $DCONF->{admin_dir}/msg_index/$1-search.txt");
		while (<SRC>) { print DEST; }
		close (DEST);
		close (SRC);
		unlink "$DCONF->{admin_dir}/msg_index/$k";
	} else {
		unlink "$DCONF->{admin_dir}/msg_index/$k";
	}
	upgrade_version_310_gauge($tempfile, $result, { descr => 410, posi => 10, total => $FORMref->{total}, done => 1 + $FORMref->{done} });
}

###
### upgrade_version_310_K
###
### Regenenerate Board, fully upgrading files
###

sub upgrade_version_310_K {
	my ($tempfile, $FORMref, $result) = @_;
	dreq("fcn-regn");
	if (! -e "$DCONF->{admin_dir}/backups/$tempfile.TMP") {
		my $out = incremental({ operation => 1, tempfile => $tempfile });
		upgrade_version_310_gauge($tempfile, $result, { total => $out->{count}, descr => 411, posi => 11 });
	}
	my $i = incremental({ tempfile => $tempfile, operation => "read" });
	my $R = regeneration_resource_init();
	my $last_topic_number = 0;
	my $tree = undef;
	lock("*");
	while (my $f = shift @{ $i->{data} }) {
		chomp $f;
		my ($topic, $page) = ($f =~ m|^(\d+)/(\d+)|);
		next if $page == 0;
		my $dirpath = get_message_path($topic);
		next if ! -e "$dirpath/$topic.$DCONF->{ext}";
		if ($last_topic_number == 0) {
			$last_topic_number = $topic;
			$tree = read_tree($topic, { no_unlock => 1, no_lock => 1 });
		} elsif ($last_topic_number != $topic) {
			unshift(@{ $i->{data} }, "$f\n");
			last;
		}
		my $pinfo = GetPage($topic, $page);
		$pinfo->{general}->{subtopic_raw} = 0;
		$pinfo->{general}->{messages_raw} = 0;
		$pinfo->{head}->{owner} = undef;
		$pinfo->{sublist} = expand_sublist($pinfo->{sublist}, $topic, $tree) if scalar(@{ $pinfo->{sublist} });
		SetPage($pinfo);
		$R = regeneration_resource_estimate($R, 1, $pinfo->{general}->{'length'});
		last if regeneration_reset_trigger($R);
		$FORMref->{done}++;
	}
	unlock("*");
	my $iw = incremental({ operation => 3, data => $i->{data}, tempfile => $tempfile });
	if ($iw->{'continue'}) {
		upgrade_version_310_gauge($tempfile, $result, { total => $FORMref->{total}, done => $FORMref->{done}, descr => 411, posi => 11 });
	}
	upgrade_version_310_gauge($tempfile, $result, { descr => 412, posi => 12 });
}

###
### upgrade_version_310_L
###
### Generate Posting Mini-Logs
###

sub upgrade_version_310_L {
	my ($tempfile, $FORMref, $result) = @_;
	dreq("adm-dr");
	my $tfmini = join("", $tempfile, "MINI");
	if (! -e "$DCONF->{admin_dir}/backups/$tfmini.TMP") {
		my $ttl = log_recovery_3({ tempfile => $tfmini }, undef, 1);
		upgrade_version_310_gauge($tempfile, $result, { total => $ttl, done => 0, descr => 412, posi => 12 }) if $ttl > 0;
		upgrade_version_310_M($tempfile, $FORMref, $result);
	} else {
		my ($frdone, $isdone) = log_recovery_4( { tempfile => $tfmini, done => 0 }, undef, 1, 1);
		upgrade_version_310_M($tempfile, $FORMref, $result) if $isdone == 1;
		upgrade_version_310_gauge($tempfile, $result, { done => 1 + $FORMref->{done}, total => $FORMref->{total}, descr => 412, posi => 12 });
	}	
}

###
### upgrade_version_310_M
###
### Finish upgrade (prompt re-registration, etc.)
###

sub upgrade_version_310_M {
	my ($tempfile, $FORMref, $result) = @_;
	dreq("adm-menu", "adm-opts", "topic-pg");
	check_first_install_serial();
	my $tfile = join("/", $DCONF->{admin_dir}, "backups", join(".", $tempfile, "DUP"));
	my @J = ();
	if (-e $tfile) {
		my $X = readfile($tfile, "upgrade_version_310_L", { no_lock => 1, no_unlock => 1 });
		unlink $tfile;
		foreach my $x (@{$X}) {
			chomp $x;
			my ($old, $new, $email, $fullname, $group) = split(/:/, $x);
			push @J, { old_username => $old, new_username => $new, email => $email, fullname => $fullname, group => $group };
		}
		@J = sort { $a->{old_username} cmp $b->{old_username} } @J;
	}
	options_save( { maintenance => 0 }) if $GLOBAL_OPTIONS->{maintenance} == 2;
	regenerate_topic_page();
	my $subst = {};
	$subst->{'changed'} = \@J;
	$subst->{'general'}->{'skipreg'} = 0;
	$subst->{'general'}->{'email'} = $result->[0]->{email};
	$subst->{'general'}->{'name'} =  $result->[0]->{fullname};
	$subst->{'general'}->{'recent_upgrade'} = 1;
	screen_out("register", $subst);
}

###
### upgrade_version_310_gauge
###
### Gauge for the upgrade of version 3.10 to 4.00+
###

sub upgrade_version_310_gauge {
	my ($tempfile, $result, $hash, $cookie) = @_;
	my $subst = {};
	my $total = defined $hash->{total} ? $hash->{total} : 0;
	my $done = defined $hash->{done} ? $hash->{done} : 0;
	$subst->{'gauge'}->{'refresh_url'} = "$PARAMS->{cgiurl}?action=upgrade-act&username=$result->[0]->{user}&upgrtf=$tempfile&total=$total&done=$done&posi=$hash->{posi}";
	$subst->{'gauge'}->{'operation'} = 4;
	$subst->{'gauge'}->{'description'} = defined $hash->{descr} ? $hash->{descr} : 499;
	$subst->{'gauge'}->{'total'} = $total;
	$subst->{'gauge'}->{'done'} = $done;
	$subst->{'gauge'}->{'percent'} = $total > 0 ? (int(100*$done/$total)) : 0;
	screen_out("gauge", $subst, $cookie);
}

1;
