# FILE: adm-dr.pl
# DESCRIPTION: Data Recovery Interfaces and functions
#-------------------------------------------------------------------------------
# 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);

###
### DR_admin
###
### Controls the Data Recovery utility
###

sub DR_admin {
	my ($FORMref) = @_;
	my $result = check_password($FORMref->{username}, undef, { type_required => 'moderator' }, $FORMref->{'COOKIE'});
	bad_login( { bad_username => 1 } ) if scalar(@{ $result }) == 0;
	bad_login( { superuser_required => 1 } ) if $result->[0]->{user} ne $DCONF->{superuser};
	if ($FORMref->{action} eq "dr_tpc") {
		topic_recovery($FORMref, $result);
		$FORMref->{action} = "data_recovery";
	}
	if ($FORMref->{'action'} eq "dr_reindex") {
		if ($DCONF->{pro} && $DCONF->{tree_as_sql} && $GLOBAL_OPTIONS->{database}) {
			dreq("sql-trem-PRO");
			return sql_reindex_main($FORMref, $result);
		}
		dreq("fcn-indx");
		INDEXING_main({ menuaction => $FORMref->{menu}, position => "start", topic => $FORMref->{topic}, username => $result->[0]->{user} });
	}
	if ($FORMref->{'action'} eq "dr_indexing") {
		dreq("fcn-indx");
		INDEXING_main({ menuaction => $FORMref->{menu}, username => $result->[0]->{user}, position => "continue", tempfile => $FORMref->{tempfile}, counter => $FORMref->{counter} });
	}
	if ($FORMref->{'action'} eq "dr_sorting") {
		dreq("fcn-indx");
		INDEXING_main({ menuaction => $FORMref->{menu}, username => $result->[0]->{user}, position => "sort", counter => $FORMref->{counter} });
		if ($FORMref->{menu} == 1) {
			$FORMref->{spot} = 1;
			page_recovery_master($FORMref, $result);
		}
		$FORMref->{action} = "data_recovery";
		$FORMref->{menu} = 2;
	}
	if ($FORMref->{'action'} eq "dr_sindxfin") {
		dreq("fcn-indx");
		INDEXING_main({ username => $result->[0]->{user}, position => "search", counter => $FORMref->{counter} });
		$FORMref->{action} = "data_recovery";
		$FORMref->{menu} = 3;
	}
	if ($FORMref->{'action'} eq "data_recovery") {
		my $subst = {};
		$subst->{general}->{username} = $result->[0]->{user};
		$subst->{general}->{menu} = defined $FORMref->{menu} ? $FORMref->{menu} : -1;
		$subst->{general}->{url} = "$PARAMS->{cgiurl}?username=$result->[0]->{user}&action=data_recovery";
		$subst->{topics} = board_topics(undef, undef, undef, 1, 1);
		if ($FORMref->{menu} == 1) {
			opendir(DIR, "$DCONF->{admin_dir}/backups");
			my @d = grep { /^\d+MP\.TMP$/ } map { $_ } readdir(DIR);
			closedir(DIR);
			my @x = ();
			foreach my $d (@d) {
				$d =~ /^(\d+)MP\.TMP$/;
				my $i = {};
				$i->{file} = $1;
				my $l = readfile("$DCONF->{admin_dir}/backups/$d", "DR_admin", { no_lock => 1, no_unlock => 1, zero_ok => 1 });
				$i->{unmoved_count} = ref $l eq 'ARRAY' ? scalar @{$l} : 0;
				$i->{date_started} = (stat "$DCONF->{admin_dir}/backups/$d")[9];
				push @x, $i;
			}
			$subst->{movedpages} = \@x;
		}
		if ($FORMref->{menu} == 2 && $FORMref->{topic} != 0) {
			$subst->{general}->{flag_reindex} = 0 + $FORMref->{topic};
		}
		if ($FORMref->{menu} == 5) {
			for (my $i = 2; $i <= 10; $i++) {
				my $x = join("", "status", $i);
				$subst->{status}->{$x} = $GLOBAL_OPTIONS->{$x};
			}
		}
		screen_out("dr_main", $subst);
	}
	if ($FORMref->{action} eq "dr_pgr") {
		page_recovery_master($FORMref, $result);
	}
	if ($FORMref->{action} eq "dr_pgs") {
		specific_page_recovery_master($FORMref, $result);
	}
	if ($FORMref->{action} eq "dr_log") {
		log_recovery_master($FORMref, $result);
	}
	if ($FORMref->{action} eq "dr_thumb") {
		dreq("dr_thumb-PRO");
		thumbnail_recovery_master($FORMref, $result);
	}
	if ($FORMref->{action} eq "dr_profiles") {
		dreq("dr_thumb-PRO");
		profile_recovery_master($FORMref, $result);
	}
	if ($FORMref->{action} eq "dr_posts") {
		dreq("dr_posts-PRO");
		post_counter_master($FORMref, $result);
	}
	if ($FORMref->{action} eq "dr_enh") {
		dreq("dr_posts-PRO");
		enhanced_profile_cleaner($FORMref, $result);
	}
	if ($FORMref->{action} eq "dr_mvp") {
		page_move_recovery_master($FORMref, $result);
	}
	if ($FORMref->{'action'} eq "dr_perm_reset") {
		my $subst = {};
		$subst->{topics} = postread_privilege_reset();
		$subst->{general}->{username} = $result->[0]->{user};
		$subst->{general}->{menu} = $FORMref->{menu};
		$subst->{general}->{url} = "$PARAMS->{cgiurl}?username=$result->[0]->{user}&action=data_recovery";
		screen_out("dr_main", $subst);
	}
}

###
### topic_recovery
###
### Recovers the list of board topics and other information
###

sub topic_recovery {
	my ($FORMref, $result) = @_;
	dreq("topic-pg");
	my $Q = undef;
	my @topics = ();
	my %t = undef;
	my @added = ();
	if ($FORMref->{topic_rec_type} == 2) {
		$Q = read_topic_page();
		@topics = @{ $Q->{topics} };
		%t = map { $_->{number}, 1 } @topics;
	}
	my @d = @{ directory_list($DCONF->{message_dir}, '/\d+$', 0) };
	push @d, @{ directory_list($DCONF->{secdir}, '/\d+$', 0) } if $DCONF->{pro};
	foreach my $d (@d) {
		if ($d =~ m|/(\d+)$|) {
			next if ! -e "$d/$1.$DCONF->{ext}";
			next if $t{$1};
			push @topics, { number => $1, type => 1 };
			push @added, $1;
		}
	}
	$Q->{topics} = \@topics;
	write_topic_page($Q);
	if (scalar(@added)) {
		dreq("fcn-indx");
		INDEXING_main({ menuaction => 2, position => "start", topic => join(",", @added), username => $result->[0]->{user} });
	}
}

###
### log_recovery_master
###
### Operates the log recovery system
###

sub log_recovery_master {
	my ($FORMref, $result) = @_;
	my $subst = {};
	$subst->{general}->{username} = $result->[0]->{user};
	$FORMref->{tempfile} =~ s/[^\w\-]//g;
	$FORMref->{action} = "dr_log";
	$subst->{general}->{tempfile} = $FORMref->{tempfile};
	log_recovery_0($FORMref, $subst) if $FORMref->{spot} == 0;
	log_recovery_1($FORMref, $subst) if $FORMref->{spot} == 1;
	log_recovery_2($FORMref, $subst) if $FORMref->{spot} == 2;
	log_recovery_3($FORMref, $subst) if $FORMref->{spot} == 3;
	log_recovery_4($FORMref, $subst) if $FORMref->{spot} == 4;
	$subst->{general}->{menu} = 5;
	$subst->{general}->{url} = "$PARAMS->{cgiurl}?username=$result->[0]->{user}&action=data_recovery";
	$subst->{topics} = board_topics();
	screen_out("dr_main", $subst);
}

###
### log_recovery_0
###
### Build file lists
###

sub log_recovery_0 {
	my ($FORMref, $subst) = @_;
	dreq("fcn-regn");
	my $X = incremental({ operation => 'build', suffix => 'logr' });
	$subst->{general}->{tempfile} = $X->{tempfile};
	$subst->{'gauge'}->{'refresh_url'} = "$PARAMS->{cgiurl}?&action=$FORMref->{action}&username=$subst->{general}->{username}&tempfile=$X->{tempfile}&spot=1&done=0&total=$X->{count}";
	$subst->{'gauge'}->{'operation'} = 16;
	$subst->{'gauge'}->{'description'} = 1601;
	$subst->{'gauge'}->{'percent'} = 0;
	screen_out("gauge", $subst);
}

###
### log_recovery_1
###
### Build all posts into a summary file
###

sub log_recovery_1 {
	my ($FORMref, $subst) = @_;
	dreq("fcn-regn");
	my $i = incremental({ tempfile => $FORMref->{tempfile}, operation => "read" });
	my $R = regeneration_resource_init();
	my $out = join("/", $DCONF->{admin_dir}, "backups", "$FORMref->{tempfile}OUT");
	my $old_topic = 0;
	my $preview_storage = defined $DCONF->{log_preview_length} ? $DCONF->{log_preview_length} : 25;
	lock("*");
	while (my $f = shift @{ $i->{data} }) {
		chomp $f;
		my ($topic, $page) = ($f =~ m|^(\d+)/(\d+)|);
		if ($old_topic == 0) {
			open (OUT, ">> $out-$topic.TMP");
			$old_topic = $topic;
		} elsif ($topic != $old_topic) {
			unshift @{ $i->{data} }, "$f\n";
			last;
		}
		my $pinfo = GetPage($topic, $page, { no_error => 1 });
		next if $pinfo->{head}->{me_number} != $page;
		next if ref $pinfo->{messages} ne 'ARRAY';
		next if scalar @{ $pinfo->{messages} } == 0;
		foreach my $message (@{ $pinfo->{messages} }) {
			my $firstfew = escape(substr(remove_html($message->{text}), 0, $preview_storage));
			my $postby = escape($message->{author});
			my $uid = $message->{email} =~ m|profile=(.*?)-(\w+)| ? join(":", $1, $2) : "";
			print OUT join("\t", $topic, $page, $message->{number}, $message->{'time'}, $uid, $postby, "$firstfew\n");
		}
		$R = regeneration_resource_estimate($R, 1, $pinfo->{general}->{'length'});
		$FORMref->{done}++;
		last if regeneration_reset_trigger($R);
	}
	unlock("*");
	close (OUT);
	my $iw = incremental({ operation => 3, data => $i->{data}, tempfile => $FORMref->{tempfile} });
	if ($iw->{'continue'}) {
		$subst->{'gauge'}->{'refresh_url'} = "$PARAMS->{cgiurl}?&action=$FORMref->{action}&username=$subst->{general}->{username}&tempfile=$FORMref->{tempfile}&spot=1&done=$FORMref->{done}&total=$FORMref->{total}";
		$subst->{'gauge'}->{'operation'} = 16;
		$subst->{'gauge'}->{'description'} = 1602;
		$subst->{'gauge'}->{'percent'} = $FORMref->{total} > 0 ? (int(100*$FORMref->{done}/$FORMref->{total})) : 0;
		screen_out("gauge", $subst);
	}
	unlink "$DCONF->{admin_dir}/backups/$FORMref->{tempfile}.TMP";
	$subst->{'gauge'}->{'refresh_url'} = "$PARAMS->{cgiurl}?&action=$FORMref->{action}&username=$subst->{general}->{username}&tempfile=$FORMref->{tempfile}&spot=2";
	$subst->{'gauge'}->{'operation'} = 16;
	$subst->{'gauge'}->{'description'} = 1603;
	$subst->{'gauge'}->{'percent'} = 0;
	screen_out("gauge", $subst);
}

###
### log_recovery_2
###
### Compare existing posts to logs
###

sub log_recovery_2 {
	my ($FORMref, $subst) = @_;
	opendir(DIR, "$DCONF->{admin_dir}/backups");
	my @u = grep { /^$FORMref->{tempfile}OUT-(\d+)\.TMP$/ } readdir(DIR);
	closedir(DIR);
	$FORMref->{total} = scalar(@u) if ! $FORMref->{total};
	if ($u[0] ne "") {
		if ($DCONF->{pro} && $GLOBAL_OPTIONS->{database}) {
			dreq("sql-logr-PRO");
			sql_log_recovery_2($FORMref, $subst, $u[0]);
		} else {
			dreq("fcn-logs");
			my $inlog = {};
			my $repair = {};
			my $postchars = {};
			my $topic_save = 0;
			open (FILEU, "< $DCONF->{admin_dir}/backups/$u[0]");
			while (my $z = <FILEU>) {
				chomp $z;
				my ($topic, $page, $postnum, $posttime, $uid, $author, $chars) = split(/\t/, $z);
				if ($postnum > 0) {
					$topic_save = $topic;
					if (! scalar keys %{$inlog}) {
						$inlog->{fileread} = 1;
						open (LOG, "< $DCONF->{admin_dir}/msg_index/$topic-log.txt");
						while (my $j = <LOG>) {
							my ($post, $who, $when, $where, $ip, $host, $chars, $author) = split(/;/, $j, 8);
							$inlog->{post}->{$post} = 1;
							$postchars->{$post} = $chars;
						}
						close (LOG);
					}
					if ($inlog->{post}->{$postnum} && ! $inlog->{done}->{$postnum}) {
						$inlog->{done}->{$postnum} = 1;
						if ($postchars->{$postnum} ne $chars) {
							$repair->{$postnum}->{type} = "fix";
							$repair->{$postnum}->{postindex} = $postnum;
							$repair->{$postnum}->{topic} = $topic;
							$repair->{$postnum}->{page} = $page;
							$repair->{$postnum}->{firstchars} = $chars;
						}
					} elsif ($inlog->{post}->{$postnum} && $inlog->{done}->{$postnum}) {
						dreq("posting");
						my $m = get_postindex();
						$repair->{$postnum}->{type} = "change";
						$repair->{$postnum}->{postindex} = $m;
						$repair->{$postnum}->{topic} = $topic;
						$repair->{$postnum}->{page} = $page;
						$repair->{$postnum}->{firstchars} = $chars;
					} else {
						$repair->{$postnum}->{type} = "add";
						$repair->{$postnum}->{where} = join("/", $topic, $page);
						$repair->{$postnum}->{'time'} = $posttime;
						$repair->{$postnum}->{'username'} = $uid;
						$repair->{$postnum}->{'remote_addr'} = "";
						$repair->{$postnum}->{'remote_host'} = "";
						$repair->{$postnum}->{'firstchars'} = $chars;
						$repair->{$postnum}->{'poststr'} = $author;
						$repair->{$postnum}->{'postindex'} = $postnum;
					}
					$inlog->{done}->{$postnum} = 1;
				}
			}
			close (FILEU);
			foreach my $x (keys %{$inlog->{post}}) {
				$repair->{$x}->{type} = "delete" if ! $inlog->{done}->{$x};
			}
			unlink "$DCONF->{admin_dir}/msg_index/$topic_save-log.out";
			if (scalar keys %{$repair}) {
				my $dp = {};
				my $seen = {};
				my $size = 0;
				open (LOG_IN, "< $DCONF->{admin_dir}/msg_index/$topic_save-log.txt");
				open (LOG_OUT, "> $DCONF->{admin_dir}/msg_index/$topic_save-log.out");
				while (<LOG_IN>) {
					my ($x) = split(/;/, $_, 2);
					if (ref $repair->{$x} ne 'HASH' || $repair->{$x} eq "") {
						$size += length($_);
						print LOG_OUT $_;
						next;
					}
					if ($repair->{$x}->{type} eq "fix") {
						my $hh = log_line_to_hash($_);
						$hh->{postindex} = $repair->{$x}->{postindex};
						$hh->{firstchars} = $repair->{$x}->{firstchars} if defined $repair->{$x}->{firstchars};
						my $line = log_hash_to_line($hh);
						print LOG_OUT $line;
						$size += length($line);
					}
					if ($repair->{$x}->{type} eq "change") {
						if (! $seen->{$x}) {
							$seen->{$x} = 1;
							print LOG_OUT $_;
							$size += length($_);
							next;
						}
						my $hh = log_line_to_hash($_);
						$hh->{postindex} = $repair->{$x}->{postindex};
						$hh->{firstchars} = $repair->{$x}->{firstchars} if defined $repair->{$x}->{firstchars};
						my $line = log_hash_to_line($hh);
						print LOG_OUT $line;
						$size += length($line);
						next if $dp->{$repair->{$x}->{page}};
						$dp->{$repair->{$x}->{page}} = 1;
						my $p = GetPage($repair->{$x}->{topic}, $repair->{$x}->{page});
						foreach my $m (@{ $p->{messages} }) {
							if (defined $repair->{$m->{number}}->{postindex}) {
								$m->{number} = $repair->{$m->{number}}->{postindex};
							}
						}
						SetPage($p);
					}
				}
				foreach my $x (keys %{$repair}) {
					next if $repair->{$x}->{type} ne "add";
					my $line = log_hash_to_line($repair->{$x});
					print LOG_OUT $line;
					$size += length($line);
				}
				close (LOG_IN);
				close (LOG_OUT);
				rename_file("$DCONF->{admin_dir}/msg_index/$topic_save-log.out", $size, "$DCONF->{admin_dir}/msg_index/$topic_save-log.txt");
			}
		}
		unlink "$DCONF->{admin_dir}/backups/$u[0]";
		$FORMref->{done}++;
	}
	if ($u[0] ne "" && $u[1] ne "") {
		$subst->{'gauge'}->{'refresh_url'} = "$PARAMS->{cgiurl}?&action=$FORMref->{action}&username=$subst->{general}->{username}&tempfile=$FORMref->{tempfile}&spot=2&done=$FORMref->{done}&total=$FORMref->{total}";
		$subst->{'gauge'}->{'operation'} = 16;
		$subst->{'gauge'}->{'description'} = 1603;
		$subst->{'gauge'}->{'percent'} = $FORMref->{total} > 0 ? (int(100*$FORMref->{done}/$FORMref->{total})) : 0;
		screen_out("gauge", $subst);
	}
	if ($DCONF->{pro} && $GLOBAL_OPTIONS->{database}) {
		return 0;
	}
	$subst->{'gauge'}->{'refresh_url'} = "$PARAMS->{cgiurl}?&action=$FORMref->{action}&username=$subst->{general}->{username}&tempfile=$FORMref->{tempfile}&spot=3";
	$subst->{'gauge'}->{'operation'} = 16;
	$subst->{'gauge'}->{'description'} = 1604;
	$subst->{'gauge'}->{'percent'} = 0;
	screen_out("gauge", $subst);
}

###
### log_recovery_3
###
### Mini-log maintenance/recovery
###

sub log_recovery_3 {
	my ($FORMref, $subst, $kick) = @_;
	my $M = board_topics();
	return if ref $M ne 'ARRAY';
	my @t = grep { $_->{type} == 1 } @{$M};
	return if scalar @t == 0;
	my $tmp = join("", $$, time, "MINI");
	$tmp = $FORMref->{tempfile} if defined $FORMref->{tempfile};
	$tmp =~ s/\W//g;
	open (TF, "> $DCONF->{admin_dir}/backups/$tmp.TMP");
	print TF map { join("", $_->{number}, "\n") } @t;
	close (TF);
	my $ttl = scalar(@t);
	return $ttl if $kick;
	$subst->{'gauge'}->{'refresh_url'} = "$PARAMS->{cgiurl}?&action=$FORMref->{action}&username=$subst->{general}->{username}&tempfile=$tmp&spot=4&total=$ttl";
	$subst->{'gauge'}->{'operation'} = 16;
	$subst->{'gauge'}->{'description'} = 1605;
	$subst->{'gauge'}->{'percent'} = 0;
	screen_out("gauge", $subst);
}

###
### log_recovery_4
###
### Mini-log maintenance/recovery action
###

sub log_recovery_4 {
	my ($FORMref, $subst, $kick, $from_upgrade) = @_;
	my $z = readfile("$DCONF->{admin_dir}/backups/$FORMref->{tempfile}.TMP", "log_recovery_4", { zero_ok => 1, no_lock => 1, no_unlock => 1 });
	if (ref $z eq 'ARRAY' && scalar @{$z}) {
		my $i = shift @{$z};
		dreq("fcn-logs");
		chomp $i;
		create_minilog($i);
		$FORMref->{done}++;
	}
	if (ref $z ne 'ARRAY' || scalar @{$z} == 0) {
		unlink "$DCONF->{admin_dir}/backups/$FORMref->{tempfile}.TMP";
		return (1, 1) if $from_upgrade;
		return undef;
	}
	writefile("$DCONF->{admin_dir}/backups/$FORMref->{tempfile}.TMP", $z, "log_recovery_4", { zero_ok => 1, no_lock => 1, no_unlock => 1 });
	return ($FORMref->{done}, 0) if $from_upgrade;
	return $FORMref->{done} if $kick;
	$subst->{'gauge'}->{'refresh_url'} = "$PARAMS->{cgiurl}?&action=$FORMref->{action}&username=$subst->{general}->{username}&tempfile=$FORMref->{tempfile}&total=$FORMref->{total}&done=$FORMref->{done}&spot=4";
	$subst->{'gauge'}->{'operation'} = 16;
	$subst->{'gauge'}->{'description'} = 1605;
	$subst->{'gauge'}->{'percent'} = $FORMref->{total} > 0 ? (int(100*$FORMref->{done}/$FORMref->{total})) : 0;
	screen_out("gauge", $subst);
}

###
### specific_page_recovery_master
###
### Operates the specific page recovery system
###

sub specific_page_recovery_master {
	my ($FORMref, $result) = @_;
	my $subst = {};
	$subst->{general}->{username} = $result->[0]->{user};
	$FORMref->{tempfile} =~ s/\D//g;
	$FORMref->{action} = "dr_pgs";
	specific_page_recovery_0($FORMref, $subst) if $FORMref->{spot} == 0;
	specific_page_recovery_1($FORMref, $subst) if $FORMref->{spot} == 1;
	specific_page_recovery_2($FORMref, $subst) if $FORMref->{spot} == 2;
	error_message("Undefined Error", "Contact DiscusWare support, code 'specific_page_recovery_master'", 0, 1);
}


###
### page_recovery_master
###
### Operates the page recovery system
###

sub page_recovery_master {
	my ($FORMref, $result) = @_;
	my $subst = {};
	$subst->{general}->{username} = $result->[0]->{user};
	$FORMref->{tempfile} =~ s/\D//g;
	$FORMref->{action} = "dr_pgr";
	page_recovery_0($FORMref, $subst) if $FORMref->{spot} == 0;
	page_recovery_1($FORMref, $subst) if $FORMref->{spot} == 1;
	page_recovery_2($FORMref, $subst) if $FORMref->{spot} == 2;
	page_recovery_3($FORMref, $subst) if $FORMref->{spot} == 3;
	page_recovery_4($FORMref, $subst) if $FORMref->{spot} == 4;
	page_recovery_5($FORMref, $subst) if $FORMref->{spot} == 5;
	page_recovery_6($FORMref, $subst) if $FORMref->{spot} == 6;
	page_recovery_7($FORMref, $subst) if $FORMref->{spot} == 7;
	$subst->{general}->{menu} = 0;
	$subst->{general}->{url} = "$PARAMS->{cgiurl}?username=$result->[0]->{user}&action=data_recovery";
	$subst->{topics} = board_topics();
	screen_out("dr_main", $subst);
}

###
### page_move_recovery_master
###
### Continues a page move recovery operation
###

sub page_move_recovery_master {
	my ($FORMref, $result) = @_;
	my $subst = {};
	$subst->{general}->{username} = $result->[0]->{user};
	$FORMref->{tempfile} =~ s/\D//g;
	if (-e "$DCONF->{admin_dir}/backups/$FORMref->{tempfile}MP.TMP") {
		my $chgfile = "";
		my $L = readfile("$DCONF->{admin_dir}/backups/$FORMref->{tempfile}MP.TMP", "page_move_recovery_master", { no_lock => 1, no_unlock => 1, zero_ok => 1 });
		my $P = substr($FORMref->{tempfile}, 0, 12);
		opendir(DIR, "$DCONF->{admin_dir}/backups");
		my @d = grep { /^CHG$P\d+\.TMP$/ } map { $_ } readdir(DIR);
		if (scalar @d != 1) {
			error_message("Data Recovery Error", "Could not find change file (1)", 0, 1);
		}
		$chgfile = $` if $d[0] =~ /\W/;
		if ($chgfile eq "") {
			error_message("Data Recovery Error", "Could not find change file (2)", 0, 1);
		}
		dreq("fcn-regn");
		regenerate_board({ esc => 0, operation => 6, description => 601, tempfile => "$FORMref->{tempfile}MP", done => 0, total => scalar(@{$L}), action => "regen7", username => $result->[0]->{user}, changefile => $chgfile });
	}
	$subst->{general}->{menu} = 0;
	$subst->{general}->{url} = "$PARAMS->{cgiurl}?username=$result->[0]->{user}&action=data_recovery";
	$subst->{topics} = board_topics();
	screen_out("dr_main", $subst);
}


###
### page_recovery_0
###
### Handles first input to initiate process
###

sub page_recovery_0 {
	my ($FORMref, $subst) = @_;
	if ($FORMref->{maint}) {
		dreq("adm-opts");
		$GLOBAL_OPTIONS = options_save({ maintenance => 1 });
	}
	if ($FORMref->{reindex}) {
		dreq("fcn-indx");
		my $bt = board_topics();
		my %h = map {$_->{number}, 1} grep($_->{type}==1,@{$bt});
		INDEXING_main({ topic => \%h, menuaction => 1, position => "start", username => $subst->{general}->{username} });
	}
	page_recovery_1($FORMref, $subst);
}

###
### specific_page_recovery_0
###
### Initialize temporary files
###

sub specific_page_recovery_0 {
	my ($FORMref, $subst) = @_;
	my $tcache = time;
	my $topic = $FORMref->{topic}; $topic =~ s/\D//g;
	my $path = get_message_path($topic);
	error_message("Invalid Topic", "Selected topic number is not valid", 0, 1) if ! -e $path;
	my $tfname = join("", $$, substr($tcache, length($tcache)-5, 5), substr($ENV{REMOTE_ADDR}, length($ENV{REMOTE_ADDR})-8, 8));
	$tfname =~ s/\D//g;
	dreq("fcn-regn");
	my $filelist = incremental({ tempfile => $tfname, operation => 1, topic => $topic });
	$subst->{'gauge'}->{'refresh_url'} = "$PARAMS->{cgiurl}?&topic=$topic&page=$FORMref->{page}&action=$FORMref->{action}&username=$subst->{general}->{username}&tempfile=$filelist->{tempfile}&spot=1&done=0&total=$filelist->{count}";
	$subst->{'gauge'}->{'operation'} = 9;
	$subst->{'gauge'}->{'description'} = 950;
	$subst->{'gauge'}->{'percent'} = 0;
	screen_out("gauge", $subst);
}

###
### specific_page_recovery_1
###
### Analyze files to see if they are children of the selected page or not
###

sub specific_page_recovery_1 {
	my ($FORMref, $subst) = @_;
	my $done = $FORMref->{done};
	dreq("fcn-regn");
	my $tempfile = incremental({ operation => "read", tempfile => $FORMref->{tempfile} });
	my $R = regeneration_resource_init();
	open (TFOUT, ">> $DCONF->{admin_dir}/backups/$FORMref->{tempfile}-RES.TMP");
	while (my $f = shift @{ $tempfile->{data} }) {
		chomp $f;
		my ($topic, $page) = ($f =~ m|^(\d+)/(\d+)|);
		$done += 1;
		next if $page == $FORMref->{page};
		next if $topic == $page;
		my $K = GetPage($topic, $page);
		if ($K->{head}->{parent} == $FORMref->{page}) {
			print TFOUT "$page\n";
		}
		$R = regeneration_resource_estimate($R, 1, $K->{general}->{length});
		last if regeneration_reset_trigger($R);
	}
	close (TFOUT);
	if (ref $tempfile->{data} eq 'ARRAY' && scalar @{ $tempfile->{data} } > 0) {
		incremental({ operation => 3, data => $tempfile->{data}, tempfile => $FORMref->{tempfile} });
		$FORMref->{done} = $done;
		$subst->{'gauge'}->{'refresh_url'} = "$PARAMS->{cgiurl}?&topic=$FORMref->{topic}&page=$FORMref->{page}&action=$FORMref->{action}&username=$subst->{general}->{username}&tempfile=$FORMref->{tempfile}&spot=1&done=$done&total=$FORMref->{total}";
		$subst->{'gauge'}->{'operation'} = 9;
		$subst->{'gauge'}->{'description'} = 951;
		$subst->{'gauge'}->{'percent'} = $FORMref->{total} == 0 ? 0 : int(100 * $FORMref->{done} / $FORMref->{total});
		screen_out("gauge", $subst);
	} else {
		unlink "$DCONF->{admin_dir}/backups/$FORMref->{tempfile}.TMP";
		$subst->{'gauge'}->{'refresh_url'} = "$PARAMS->{cgiurl}?&topic=$FORMref->{topic}&page=$FORMref->{page}&action=$FORMref->{action}&username=$subst->{general}->{username}&tempfile=$FORMref->{tempfile}&spot=2";
		$subst->{'gauge'}->{'operation'} = 9;
		$subst->{'gauge'}->{'description'} = 952;
		$subst->{'gauge'}->{'percent'} = 0;
		screen_out("gauge", $subst);
	}
}

###
### specific_page_recovery_2
###
### Analyze a certain page and put the links back onto that page if needed
###

sub specific_page_recovery_2 {
	my ($FORMref, $subst) = @_;
	my $K = readfile("$DCONF->{admin_dir}/backups/$FORMref->{tempfile}-RES.TMP", "specific_page_recovery_2", { zero_ok => 1, no_lock => 1, no_unlock => 1 });
	unlink "$DCONF->{admin_dir}/backups/$FORMref->{tempfile}-RES.TMP";
	my $L = GetPage($FORMref->{topic}, $FORMref->{page}, { zero_ok => 1, lock => 1, no_error => 1 });
	my %z1 = map { $_->{number}, 1 } grep { $_->{islink} == 0 } @{$L->{sublist}};
	my %z2 = map { chomp; $_, 1 } grep { /^\d+/ } @{$K};
	my %z3 = map { $_, 1 } grep { $z1{$_} != 1 } keys %z2;
	my %z4 = map { $_, 1 } grep { $z2{$_} != 1 } keys %z1;
	if (! scalar keys %z3 && ! scalar keys %z4) {
		my ($topic, $page) = (0 + $FORMref->{topic}, 0 + $FORMref->{page});
		$subst->{general}->{change} = 0;
		my $q = get_message_path($topic);
		my $filename = join("/", $q, "$page.$DCONF->{ext}");
		unlock("$filename");
	} else {
		if (! $L->{head}->{topic_number}) {
			$L->{head}->{topic_number} = 0 + $FORMref->{topic};
			$L->{head}->{me_number} = 0 + $FORMref->{page};
			$L->{sublist} = [];
			$L->{head}->{param} = "Sublist";
			$L->{head}->{me_name} = "Recovered Page";
		}
		my @x = @{expand_sublist($L->{sublist}, $L->{head}->{topic_number})};
		@x = grep { $z4{$_->{number}} == 0 } @x;
		foreach my $q (keys %z3) {
			push @x, { type => 0, number => $q, name => 'Recovered', icon => 'tree_k' };
		}
		$L->{sublist} = \@x;
		$L->{general}->{subtopic_raw} = 0;
		SetPage($L, { unlock => 1 });
		$subst->{general}->{count} = 0 + scalar @{$K};
		$subst->{general}->{change} = 1;
		$subst->{general}->{removed} = scalar keys %z4;
		$subst->{general}->{added} = scalar keys %z3;
		$subst->{general}->{topic} = $FORMref->{topic};
	}
	$subst->{general}->{menu} = 100;
	$subst->{general}->{username} = $FORMref->{username};
	$subst->{general}->{url} = "$PARAMS->{cgiurl}?username=$FORMref->{username}&action=data_recovery";
	screen_out("dr_main", $subst);
}

###
### page_recovery_1
###
### Initialize temporary files
###

sub page_recovery_1 {
	my ($FORMref, $subst) = @_;
	my $tcache = time;
	my $tfname = join("", $$, substr($tcache, length($tcache)-5, 5), substr($ENV{REMOTE_ADDR}, length($ENV{REMOTE_ADDR})-8, 8));
	$tfname =~ s/\D//g;
	my $budir = join("/", $DCONF->{admin_dir}, "backups");
	dreq("fcn-regn");
	my $bt = board_topics();
	my %h = map {$_->{number}, 1} grep($_->{type}==1,@{$bt});
	foreach my $T (keys(%h)) {
		touch_createfile("$budir/$tfname-$T.TMP") || error_message("Page Recovery Error", "Could not create temporary file!");
	}
	my $filelist = incremental( { tempfile => $tfname, topic => \%h, operation => "build"} );
	writefile("$budir/$tfname-C2.TMP", $filelist->{data}, "page_recovery_1", { no_lock => 1, no_unlock => 1, create => 1 });
	writefile("$budir/$tfname-FP.TMP", $filelist->{data}, "page_recovery_1", { no_lock => 1, no_unlock => 1, create => 1 });
	writefile("$budir/$tfname-FL.TMP", $filelist->{data}, "page_recovery_1", { no_lock => 1, no_unlock => 1, create => 1 });
	$subst->{'gauge'}->{'refresh_url'} = "$PARAMS->{cgiurl}?&action=$FORMref->{action}&username=$subst->{general}->{username}&tempfile=$filelist->{tempfile}&spot=2&done=0&total=$filelist->{count}";
	$subst->{'gauge'}->{'operation'} = 9;
	$subst->{'gauge'}->{'description'} = 901;
	$subst->{'gauge'}->{'percent'} = 0;
	screen_out("gauge", $subst);
}

###
### page_recovery_2
###
### Check to see if pages link to themselves, parents, etc.
###

sub page_recovery_2 {
	my ($FORMref, $subst) = @_;
	dreq("fcn-regn");
	my $tf = join("-", $FORMref->{tempfile}, "C2");
	if (-e "$DCONF->{admin_dir}/backups/$tf.TMP") {
		my $tempfile = incremental({ operation => "read", tempfile => $tf });
		my $R = regeneration_resource_init();
		lock("*");
		my @d = ();
		my $done = $FORMref->{done};
		while (my $f = shift @{ $tempfile->{data} }) {
			chomp $f;
			my ($topic, $page) = ($f =~ m|^(\d+)/(\d+)|);
			my $K = GetPage($topic, $page);
			push @d, join("", join("\t", $topic, $page, join(",", map {$_->{number}} grep($_->{islink} == 0, @{$K->{sublist}}))), "\n");
			$R = regeneration_resource_estimate($R, 1, 3 * $K->{general}->{length});
			$done++;
			last if regeneration_reset_trigger($R);
		}
		unlock("*");
		appendfile("$DCONF->{admin_dir}/backups/$FORMref->{tempfile}-TD.TMP", \@d);
		my $iw = incremental({ operation => 3, data => $tempfile->{data}, tempfile => $tf });
		my $total = $FORMref->{total};
		$subst->{'gauge'}->{'refresh_url'} = "$PARAMS->{cgiurl}?&action=$FORMref->{action}&username=$subst->{general}->{username}&tempfile=$FORMref->{tempfile}&spot=2&done=$done&total=$total";
		$subst->{'gauge'}->{'operation'} = 9;
		$subst->{'gauge'}->{'description'} = 901;
		$subst->{'gauge'}->{'percent'} = $total > 0 ? int(100 * $done / $total) : 0;
		screen_out("gauge", $subst);
	}
	my $bt = board_topics();
	my %h = map {$_->{number}, 1} grep($_->{type}==1,@{$bt});
	my $Q = readfile("$DCONF->{admin_dir}/backups/$FORMref->{tempfile}-TD.TMP", "page_recovery_2", { no_lock => 1, no_unlock => 1, create => 1 });
	unlink "$DCONF->{admin_dir}/backups/$FORMref->{tempfile}-TD.TMP";
	my $S = {};
	foreach my $line (@{$Q}) {
		my ($topic, $page, $subs) = split(/\s+/, $line);
		foreach my $s (split(/,/, $subs)) {
			$S->{$topic}->{$page}->{$s}++;
		}
	}
	my $seen = {}; my $err = {};
	foreach my $t (keys %h) {
		my ($sn, $er) = _page_recovery_2_dig($t, $t, -1, $S, $seen, $err);
		hash_merge($seen, $sn);
		hash_merge($err, $er);
	}
	if (scalar(keys(%{$err}))) {
		my @k = ();
		foreach my $t (keys(%{$err})) {
			foreach my $p (keys(%{$err->{$t}})) {
				my @q = ();
				foreach my $k (keys(%{$err->{$t}->{$p}})) {
					push @q, "$k" if $err->{$t}->{$p}->{$k} == 1;
				}
				push @k, join("", join("\t", $t, $p, join(",", @q), "\n"));
			}
		}
		writefile("$DCONF->{admin_dir}/backups/$FORMref->{tempfile}-BL.TMP", \@k, "page_recovery_2", { create => 1, no_lock => 1, no_unlock => 1 });
		my $sck = scalar(@k);
		$subst->{'gauge'}->{'refresh_url'} = "$PARAMS->{cgiurl}?&action=$FORMref->{action}&username=$subst->{general}->{username}&tempfile=$FORMref->{tempfile}&spot=3&done=0&total=$sck";
		$subst->{'gauge'}->{'operation'} = 9;
		$subst->{'gauge'}->{'description'} = 902;
		$subst->{'gauge'}->{'percent'} = 0;
		screen_out("gauge", $subst);
	}
	$subst->{'gauge'}->{'refresh_url'} = "$PARAMS->{cgiurl}?&action=$FORMref->{action}&username=$subst->{general}->{username}&tempfile=$FORMref->{tempfile}&spot=4";
	$subst->{'gauge'}->{'operation'} = 9;
	$subst->{'gauge'}->{'description'} = 903;
	$subst->{'gauge'}->{'percent'} = 0;
	screen_out("gauge", $subst);
}

sub _page_recovery_2_dig {
	my ($topic, $page, $par, $S, $seen, $err) = @_;
	$seen->{$topic}->{$page} = $par;
	foreach my $Z (keys %{$S->{$topic}->{$page}}) {
		if ($S->{$topic}->{$page}->{$Z} > 1) {
			$err->{$topic}->{$page}->{$Z} = 2;
		} elsif ($seen->{$topic}->{$Z} != 0) {
			$err->{$topic}->{$page}->{$Z} = 1;
		} else {
			($seen, $err) = _page_recovery_2_dig($topic, $Z, $page, $S, $seen, $err);
		}
	}
	return ($seen, $err);
}

###
### page_recovery_3
###
### Kills bad links (to page itself or to parents or multiple links) within pages
###

sub page_recovery_3 {
	my ($FORMref, $subst) = @_;
	my $bldf = readfile("$DCONF->{admin_dir}/backups/$FORMref->{tempfile}-BL.TMP", "page_recovery_3", { no_lock => 1, no_unlock => 1 });
	my $k = shift @{$bldf};
	my ($topic, $page, $links) = split(/\s+/, $k);
	my $GF = GetPage($topic, $page, { lock => 1 });
	my @u = split(/\n/, $GF->{sublist_raw});
	my %l = map { $_, 1 } split(/,/, $links);
	my @n = ();
	my $flag = 0;
	foreach my $q (@u) {
		if ($q =~ /^<!--Top: (\d+)-->(.*)/) {
			my $num = $1;
			$flag = 0;
			if ($l{$num}) {
				my $t = $2;
				$flag = 1 if $t =~ /^\s*$/;
			} else {
				push @n, $q;
				$l{$num} = 1;
			}
		} elsif ($q =~ m|<!--/Top-->|) {
			push @n, $q if ! $flag;
			$flag = 0;
		} elsif ($q =~ m|<!--URL: (\d+)-->|) {
			push @n, $q;
			$flag = 0;
		} elsif ($q =~ /\S/) {
			push @n, $q;
		}
	}
	$GF->{sublist_raw} = join("\n", @n); $GF->{sublist_raw} .= "\n";
	SetPage($GF, { unlock => 1 });
	if (scalar(@{$bldf}) == 0) {
		unlink "$DCONF->{admin_dir}/backups/$FORMref->{tempfile}-BL.TMP";
		$subst->{'gauge'}->{'refresh_url'} = "$PARAMS->{cgiurl}?&action=$FORMref->{action}&username=$subst->{general}->{username}&tempfile=$FORMref->{tempfile}&spot=4";
		$subst->{'gauge'}->{'operation'} = 9;
		$subst->{'gauge'}->{'description'} = 902;
		$subst->{'gauge'}->{'percent'} = 100;
		screen_out("gauge", $subst);
	}
	writefile("$DCONF->{admin_dir}/backups/$FORMref->{tempfile}-BL.TMP", $bldf, "page_recovery_3", { no_lock => 1, no_unlock => 1 });
	$FORMref->{done}++;
	$subst->{'gauge'}->{'refresh_url'} = "$PARAMS->{cgiurl}?&action=$FORMref->{action}&username=$subst->{general}->{username}&tempfile=$FORMref->{tempfile}&spot=3&total=$FORMref->{total}&done=$FORMref->{done}";
	$subst->{'gauge'}->{'operation'} = 9;
	$subst->{'gauge'}->{'description'} = 902;
	$subst->{'gauge'}->{'percent'} = $FORMref->{total} == 0 ? 0 : int(100 * $FORMref->{done} / $FORMref->{total});
	screen_out("gauge", $subst);
}

###
### page_recovery_4
###
### Check for duplicates in file list
###

sub page_recovery_4 {
	my ($FORMref, $subst) = @_;
	dreq("fcn-regn");
	my $tempfile = incremental({ operation => "read", tempfile => $FORMref->{tempfile} });
	my $budir = join("/", $DCONF->{admin_dir}, "backups");
	my $dupe = {};
	foreach my $entry (@{$tempfile->{data}}) {
		if ($entry =~ /^(\d+)\/(\d+)\./) {
			my ($topic, $page) = ($1, $2);
			push @{ $dupe->{$page} }, $topic;
		}
	}
	my @Z = grep { scalar(@{ $dupe->{$_} }) > 1 } keys(%{$dupe});
	if (scalar(@Z) == 0) {
		$subst->{'gauge'}->{'refresh_url'} = "$PARAMS->{cgiurl}?&action=$FORMref->{action}&username=$subst->{general}->{username}&tempfile=$FORMref->{tempfile}&spot=6";
		$subst->{'gauge'}->{'operation'} = 9;
		$subst->{'gauge'}->{'description'} = 903;
		$subst->{'gauge'}->{'percent'} = 100;
		screen_out("gauge", $subst);
	}
	touch_createfile("$budir/$FORMref->{tempfile}-DUPE.TMP") || error_message("Page Recovery Error", "Could not create temporary file!");
	my @d = ();
	foreach my $z (@Z) {
		push @d, join("", join("\t", $z, join(",", @{$dupe->{$z}})), "\n");
	}
	appendfile("$budir/$FORMref->{tempfile}-DUPE.TMP", \@d, "page_recovery_2", { no_lock => 1, no_unlock => 1 });
	my $td = scalar(@d);
	$subst->{'gauge'}->{'refresh_url'} = "$PARAMS->{cgiurl}?&action=$FORMref->{action}&username=$subst->{general}->{username}&tempfile=$FORMref->{tempfile}&spot=5&total=$td";
	$subst->{'gauge'}->{'operation'} = 9;
	$subst->{'gauge'}->{'description'} = 904;
	$subst->{'gauge'}->{'percent'} = 0;
	screen_out("gauge", $subst);
}

###
### page_recovery_5
###
### Fix all duplicates in file list by removing unwanted files
###

sub page_recovery_5 {
	my ($FORMref, $subst) = @_;
	my $dupefile = readfile("$DCONF->{admin_dir}/backups/$FORMref->{tempfile}-DUPE.TMP", "page_recovery_3", { no_lock => 1, no_unlock => 1 });
	my $U = shift @{$dupefile} or return page_recovery_6($FORMref, $subst);
	my ($pagenum, $topics, $pass) = split(/\s+/, $U);
	my @T = split(/,/, $topics);
	my $pgref = {};
	foreach my $topic (@T) {
		$pgref->{$topic} = GetPage($topic, $pagenum, { no_error => 1 });
	}
	my $R = _page_recovery_5($pgref, \@T, $pagenum, $pass);
	if (ref $R eq 'HASH') {
		my $topic = (keys(%{$R}))[0];
		foreach my $TN (@T) {
			next if $TN == $topic;
			my $tdir = get_message_path($TN);
			my $Z = $R->{$topic};
			unlink "$tdir/$pagenum.$DCONF->{ext}";
			my $pnt = GetPage($TN, $Z->{head}->{parent}, { no_error => 1 });
			if ($pnt->{head}->{topic_number} == $TN) {
				my @u = split(/\n/, $pnt->{sublist_raw});
				my @n = ();
				my $flag = 0;
				foreach my $q (@u) {
					if ($q =~ /^<!--Top: (\d+)-->(.*)/) {
						my ($o, $t) = ($1, $2);
						if ($o == $pagenum) {
							$flag = 1 if $t =~ /^\s*$/;
							next;
						}
						push @n, $q;
					} elsif ($q =~ m%<!--/Top-->%) {
						push @n, $q if $flag == 0;
						$flag = 0;
					} elsif ($q =~ /\S/ && $flag != 0) {
						push @n, $q;
					}
				}
				$pnt->{sublist_raw} = join("\n", @n); $pnt->{sublist_raw} .= "\n";
				SetPage($pnt, { });
			}
		}
		if (scalar(@{$dupefile}) == 0) {
			unlink "$DCONF->{admin_dir}/backups/$FORMref->{tempfile}-DUPE.TMP";
			return page_recovery_6($FORMref, $subst);
		} else {
			foreach my $q (@{$dupefile}) {
				my ($pagenums, $topicsnum, $pass) = split(/\s+/, $q);
				$q = join("", join("\t", $pagenums, $topicsnum), "\n");
				log_error("adm-dr.pl", "page_recovery_5", "Note: duplicated file $pagenums in topics $topicsnum");
			}
			writefile("$DCONF->{admin_dir}/backups/$FORMref->{tempfile}-DUPE.TMP", $dupefile, "page_recovery_5");
			$FORMref->{done}++;
			$subst->{'gauge'}->{'refresh_url'} = "$PARAMS->{cgiurl}?&action=$FORMref->{action}&username=$subst->{general}->{username}&tempfile=$FORMref->{tempfile}&spot=5&total=$FORMref->{total}&done=$FORMref->{done}";
			$subst->{'gauge'}->{'operation'} = 9;
			$subst->{'gauge'}->{'description'} = 904;
			$subst->{'gauge'}->{'percent'} = $FORMref->{total} == 0 ? 0 : int(100 * $FORMref->{done} / $FORMref->{total});
			screen_out("gauge", $subst);
		}
	} else {
		push @{$dupefile}, join("", join("\t", $pagenum, $topics, 1 + $pass), "\n");
		writefile("$DCONF->{admin_dir}/backups/$FORMref->{tempfile}-DUPE.TMP", $dupefile, "page_recovery_5");
		$subst->{'gauge'}->{'refresh_url'} = "$PARAMS->{cgiurl}?&action=$FORMref->{action}&username=$subst->{general}->{username}&tempfile=$FORMref->{tempfile}&spot=5&total=$FORMref->{total}&done=$FORMref->{done}";
		$subst->{'gauge'}->{'operation'} = 9;
		$subst->{'gauge'}->{'description'} = 904 + $pass;
		$subst->{'gauge'}->{'percent'} = $FORMref->{total} == 0 ? 0 : int(100 * $FORMref->{done} / $FORMref->{total});
		screen_out("gauge", $subst);
	}
}

sub _page_recovery_5 {
	my ($pgref, $topicref, $pagenum, $pass) = @_;
	my %pg = %{$pgref}; my @T = @{$topicref};

	# Zap corrupted files - those who do not know who they are
	foreach my $topic (@T) {
		if ($pg{$topic}->{head}->{me_number} != $pagenum) {
			log_error("adm-dr.pl", "_page_recovery_5", "Note: $pagenum in $topic deleted due to corruption");
			delete $pg{$topic};
		}
	}
	return \%pg if scalar(keys(%pg)) <= 1;

	# Zap topics that are in the wrong topic directory
	foreach my $topic (@T) {
		if (grep($_ == $pagenum, @T) && $pagenum != $topic) {
			log_error("adm-dr.pl", "_page_recovery_5", "Note: $pagenum in $topic deleted due to wrong location");
			delete $pg{$topic};
		}
	}
	return \%pg if scalar(keys(%pg)) <= 1;

	# Zap pages in the wrong topic directory, if one is in the right directory
	my %rightdir = {};
	foreach my $topic (@T) {
		$rightdir{$topic} = 1 if $pg{$topic}->{head}->{topic_number} == $topic;
	}
	if (scalar(keys %rightdir) == 1) {
		foreach my $topic (@T) {
			if (! $rightdir{$topic}) {
				log_error("adm-dr.pl", "_page_recovery_5", "Note: $pagenum in $topic deleted due to wrong location when one found in right location");
				delete $pg{$topic};
			}
		}
		return \%pg;
	}

	# Re-calculate the parent page from the headers & read parent pages
	my %parents = {};
	foreach my $topic (@T) {
		my $self = $pg{$topic};
		my @l = @{$self->{head}->{levels}};
		my $parent = $self->{head}->{topic_number};
		$parent = $l[scalar(@l)-1]->{level_number} if scalar(@l) > 1;
		$self->{head}->{parent} = $parent;
		$parents{$topic} = GetPage($topic, $parent, { no_error => 1 });
		$pg{$topic} = $self;
	}

	# See how many of the parent pages exist -- if only 1 we're done
	if (scalar(grep ( $parents{$_}->{head}->{me_number} > 0, keys %parents)) == 1) {
		foreach my $topic (@T) {
			if ($parents{$topic}->{head}->{me_number} <= 0) {
				log_error("adm-dr.pl", "_page_recovery_5", "Note: $pagenum in $topic deleted due to only one parent remaining");
				delete $pg{$topic};
			}
		}
		return \%pg;
	}

	# SECOND PASS -- see if parents claim this page
	if ($pass >= 1) {
		my $claims = {};
		foreach my $topic (@T) {
			$claims->{$topic} = 1 if grep ( $_->{number} == $pagenum , @{$parents{$topic}->{sublist}});
		}
		if (scalar(keys %{$claims}) == 1) {
			foreach my $topic (@T) {
				if (! $claims->{$topic}) {
					log_error("adm-dr.pl", "_page_recovery_5", "Note: $pagenum in $topic deleted due to no claims");
					delete $pg{$topic};
				}
			}
			return \%pg;
		}
	}

	# THIRD PASS -- see if the content is different among the pages
	if ($pass >= 2) {
		my %content = {};
		foreach my $topic (@T) {
			my @q = map {$_->{number}} @{$pg{$topic}->{sublist}};
			push @q, map {$_->{number}} @{$pg{$topic}->{messages}};
			$content{$topic} = join("\t", @q);
		}
		my $same = 0;
		foreach my $t1 (@T) {
			foreach my $t2 (@T) {
				$same++ if $content{$t1} == $content{$t2};
			}
		}
		if ($same < scalar(@T)*scalar(@T) && $pass >= 2) {
			my @O = sort {scalar(@{$pg{$a}->{sublist}}) <=> scalar(@{$pg{$b}->{sublist}}) } @T;
			my @R = @T;
			my $nt = pop @R;
			foreach my $topic (@R) {
				my $d = get_message_path($topic);
				my $G = get_number();
				my $P = GetPage($topic, $pagenum, { no_error => 1 });
				$P->{head}->{me_number} = $G;
				my @l = @{$P->{head}->{levels}};
				my $parent = $P->{head}->{topic_number};
				$parent = $l[scalar(@l)-2]->{level_number} if scalar(@l) > 2;
				$P->{head}->{parent} = $parent;
				$l[$#l]->{level_number} = $G if (scalar(@l) > 0);
				$P->{head}->{levels} = \@l;
				SetPage($P, { });
				unlink "$d/$pagenum.$DCONF->{ext}";
				unlock("$d/$pagenum.$DCONF->{ext}");
				my $PAR = GetPage($topic, $parent, { no_error => 1 });
				if ($PAR->{head}->{me_number} == $parent) {
					$PAR->{general}->{subtopic_raw} = 1;
					$PAR->{sublist_raw} =~ s/\/$topic\/$pagenum\.$DCONF->{ext}/\/$topic\/$G\.$DCONF->{ext}/g;
					$PAR->{sublist_raw} =~ s/<!--Top: $pagenum-->/<!--Top: $G-->/g;
					log_error("adm-dr.pl", "_page_recovery_5", "Note: $pagenum in $topic renumbered; is now $G [a]");
					SetPage($PAR, { });
				} else {
					unlock("$d/$parent.$DCONF->{ext}");
				}
#				foreach my $pg (@{ $P->{sublist} }) {
#					_page_recovery_5_renum($topic, $pg->{number}, scalar(@l), $G, 1, {$parent => 1, $G => 1, $pagenum => 1});
#				}
				delete $pg{$topic};
			}
			return \%pg;
		}
	}

	# FOURTH PASS -- see if these appear in their respective tree files

	if ($pass >= 3) {
		my %ait = {};
O1:		foreach my $topic (@T) {
			my $l = read_tree($topic);
O2:			foreach my $q (@{$l}) {
				if ($q->{page} == $pagenum) {
					$ait{$topic} = 1;
					last O2;
				}
			}
		}
		if (scalar(keys %ait) == 1) {
			foreach my $topic (@T) {
				delete $pg{$topic} if ! $ait{$topic};
			}
			return \%pg;
		}
	}

	# FIFTH PASS -- duplicated pages -- essentially random selection

	if ($pass >= 4) {
		delete $pg{(keys %pg)[0]} while scalar(keys(%pg)) > 1;
		return \%pg;
	}
	return undef;
}

sub _page_recovery_5_renum {
	my ($topic, $page, $level, $new_num, $flag, $done) = @_;
	my $GP = GetPage($topic, $page, { no_error => 1 });
	if ($GP->{head}->{me_number} == $page) {
		$new_num = get_number();
		$GP->{head}->{levels}->[$level]->{level_number} = $new_num;
		$GP->{head}->{parent} = $new_num if $flag;
		SetPage($GP);
		$done->{$page} = 1;
		log_error("adm-dr.pl", "_page_recovery_5_renum", "Note: $page in $topic renumbered; is now $new_num [b]");
		foreach my $p (@{$GP->{sublist}}) {
			next if $done->{$p->{number}};
			_page_recovery_5_renum($topic, $p->{number}, $level, $new_num, 0, $done);
		}
	} else {
		unlock("/$topic/$page.$DCONF->{ext}");
	}
}

###
### page_recovery_6
###
### Fix parent numbers on any pages where they are broken
###

sub page_recovery_6 {
	my ($FORMref, $subst) = @_;
	dreq("fcn-regn");
	my $tf = join("-", $FORMref->{tempfile}, "FP");
	if (-e "$DCONF->{admin_dir}/backups/$tf.TMP") {
		my $tempfile = incremental({ operation => "read", tempfile => $tf });
		$FORMref->{total} = scalar(@{$tempfile->{data}}) if $FORMref->{total} == 0;
		my $R = regeneration_resource_init();
		my $done = $FORMref->{done};
		lock("*");
		while (my $f = shift @{ $tempfile->{data} }) {
			chomp $f;
			my ($topic, $page) = ($f =~ m|^(\d+)/(\d+)|);
			my $K = GetPage($topic, $page, { no_error => 1 });
			next if ref $K->{head}->{levels} ne 'ARRAY';
			my @l = @{$K->{head}->{levels}};
			$done++;
			my $parent = $K->{head}->{topic_number};
			$parent = $l[scalar(@l)-2]->{level_number} if scalar(@l) > 2;
			$parent = 0 if $K->{head}->{topic_number} == $K->{head}->{me_number};
			if ($parent != $K->{head}->{parent}) {
				$K->{head}->{parent} = $parent;
				SetPage($K);
			}
			$R = regeneration_resource_estimate($R, 1, 3 * $K->{general}->{length});
			last if regeneration_reset_trigger($R);
		}
		unlock("*");
		my $iw = incremental({ operation => 3, data => $tempfile->{data}, tempfile => $tf });
		if ($iw->{'continue'}) {
			my $total = $FORMref->{total};
			$subst->{'gauge'}->{'refresh_url'} = "$PARAMS->{cgiurl}?&action=$FORMref->{action}&username=$subst->{general}->{username}&tempfile=$FORMref->{tempfile}&spot=6&done=$done&total=$total";
			$subst->{'gauge'}->{'operation'} = 9;
			$subst->{'gauge'}->{'description'} = 908;
			$subst->{'gauge'}->{'percent'} = $total > 0 ? int(100 * $done / $total) : 0;
			screen_out("gauge", $subst);
		}
	}
	$subst->{'gauge'}->{'refresh_url'} = "$PARAMS->{cgiurl}?&action=$FORMref->{action}&username=$subst->{general}->{username}&tempfile=$FORMref->{tempfile}&spot=7";
	$subst->{'gauge'}->{'operation'} = 9;
	$subst->{'gauge'}->{'description'} = 908;
	$subst->{'gauge'}->{'percent'} = 100;
	screen_out("gauge", $subst);
}

###
### page_recovery_7
###
### Delete links from pages that point to non-existent destinations
###

sub page_recovery_7 {
	my ($FORMref, $subst) = @_;
	dreq("fcn-regn");
	my $tf = join("-", $FORMref->{tempfile}, "FL");
	if (-e "$DCONF->{admin_dir}/backups/$tf.TMP") {
		my $tempfile = incremental({ operation => "read", tempfile => $tf });
		$FORMref->{total} = scalar(@{$tempfile->{data}}) if $FORMref->{total} == 0;
		my $R = regeneration_resource_init();
		my $done = $FORMref->{done};
		lock("*");
		while (my $f = shift @{ $tempfile->{data} }) {
			chomp $f;
			my ($topic, $page) = ($f =~ m|^(\d+)/(\d+)|);
			my $tdir = get_message_path($topic);
			my $K = GetPage($topic, $page, { no_error => 1 });
			next if ref $K->{head}->{levels} ne 'ARRAY';
			$done++;
			my @sln = ();
			my %nu = undef;
			foreach my $sl (@{ $K->{sublist} }) {
				if ($sl->{number} > 0 && ($sl->{islink} || -e "$tdir/$sl->{number}.$DCONF->{ext}")) {
					push @sln, $sl;
				} else {
					log_error("adm-dr.pl", "page_recovery_7", "Deleted bad subtopic $sl->{number} from $topic/$page");
				}
			}
			$K->{sublist} = \@sln;
			$K->{general}->{subtopic_raw} = 0;
			SetPage($K, { unlock => 1 });
			$R = regeneration_resource_estimate($R, 1, 15 * $K->{general}->{length});
			last if regeneration_reset_trigger($R);
		}
		unlock("*");
		my $iw = incremental({ operation => 3, data => $tempfile->{data}, tempfile => $tf });
		if ($iw->{'continue'}) {
			my $total = $FORMref->{total};
			$subst->{'gauge'}->{'refresh_url'} = "$PARAMS->{cgiurl}?&action=$FORMref->{action}&username=$subst->{general}->{username}&tempfile=$FORMref->{tempfile}&spot=7&done=$done&total=$total";
			$subst->{'gauge'}->{'operation'} = 9;
			$subst->{'gauge'}->{'description'} = 909;
			$subst->{'gauge'}->{'percent'} = $total > 0 ? int(100 * $done / $total) : 0;
			screen_out("gauge", $subst);
		}
	}
	$subst->{'gauge'}->{'refresh_url'} = "$PARAMS->{cgiurl}?&action=$FORMref->{action}&username=$subst->{general}->{username}&tempfile=$FORMref->{tempfile}&spot=8";
	$subst->{'gauge'}->{'operation'} = 9;
	$subst->{'gauge'}->{'description'} = 909;
	$subst->{'gauge'}->{'percent'} = 100;
	screen_out("gauge", $subst);
}

###
### postread_privilege_reset
###
### Resets posting and reading privileges if file topicprv.txt has been reset
###

sub postread_privilege_reset {
	my $topics = board_topics();
	dreq("fcn-usrp");
	my @u = ();
	foreach my $topic (@{ $topics }) {
		my $tn = $topic->{number};
		my $ph = {};
		$ph->{action} = "add_topic";
		$ph->{type} = "p";
		$ph->{topic} = $tn;
		$ph->{ip} = "*";
		push @u, $ph;
		if ($DCONF->{pro}) {
			my $rh = {};
			$rh->{action} = "set_equal";
			$rh->{type} = "r";
			$rh->{topic} = $tn;
			if (-e "$DCONF->{message_dir}/$tn") {
				$rh->{ip} = "*";
			} else {
				$rh->{user} = "*";
				$rh->{user} = "*";
			}
			push @u, $rh;
		} else {
			$ph->{type} = "r";
			push @u, $ph;
		}
	}
	write_topic_privilege_file(\@u);
	return $topics;
}

###
### postindex_database_fix
###
### Fixes improper use of postindex.txt and database
###

sub postindex_database_fix {
	my @max = ();
	dreq("dbint");
	my $sth = database_dbh()->prepare("SELECT max(postnum) FROM $PARAMS->{db_prefix}log;");
	$sth->execute();
	push @max, ($sth->fetchrow_array())[0];
	$sth->finish();
	my $fref = readfile("$DCONF->{admin_dir}/postindex.txt", "get_postindex");
	push @max, trim($fref->[0]);
	my $sth2 = database_dbh()->prepare("SELECT value FROM $PARAMS->{db_prefix}counters WHERE ID = 2;");
	$sth2->execute();
	push @max, ($sth2->fetchrow_array())[0];
	$sth2->finish();
	my $sth3 = database_dbh()->prepare("SELECT max(postnum) FROM $PARAMS->{db_prefix}search;");
	$sth3->execute();
	push @max, ($sth3->fetchrow_array())[0];
	$sth3->finish();
	my $maximum = 1 + _max(@max);
	my $sth3 = database_dbh()->prepare("UPDATE $PARAMS->{db_prefix}counters SET value = 1+$maximum WHERE ID = 2;");
	$sth3->execute();
	dreq("adm-opts");
	options_save({postindex_fixed => 1 });
	return 1+$maximum;
}

sub _max {
	my $max = undef;
	foreach my $q (@_) {
		$max = $q if (! defined $max || $q > $max);
	}
	return $max;
}

1;
