# FILE: adm-tpc.pl
# DESCRIPTION: Topic Manager Interface 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);

###
### TOPIC_admin
###
### Handles all Topic Manager actions
###

sub TOPIC_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};
	my $subst = {};
	if ($FORMref->{'action'} eq "topic_prop") {
		my $arg = topic_properties_handle(board_topics(), $FORMref);
		topic_management_handler($FORMref, $result->[0]->{user}, "p", { what => ( set_topic_properties($arg) == 1 ? 1 : 0 ) }, $arg->{number});
		topic_manager($FORMref, $result->[0]->{user}, { what => 6 });
	}
	if ($FORMref->{'action'} eq "topic_catprop") {
		my $arg = topic_category_properties_handle(board_topics(), $FORMref);
		topic_management_handler($FORMref, $result->[0]->{user}, "p", { what => ( defined set_category_properties($arg) ? 2 : 0 ) }, $arg->{number});
	}
	if ($FORMref->{'action'} eq "topic_mgmt") {
		topic_management_handler($FORMref, $result->[0]->{user}, $FORMref->{'SELECTION'}, undef);
	}
	if ($FORMref->{'action'} eq "topic_mod") {
		topic_privileges_save($FORMref->{'affect'}, $FORMref->{'mod'});
		topic_manager($FORMref, $result->[0]->{user}, { what => 2 });
	}
	if ($FORMref->{'action'} eq "topic_mgr") {
		topic_manager($FORMref, $result->[0]->{user}, undef);
	}
	if ($FORMref->{'action'} eq "topic_add") {
		topic_add_topic_handler($FORMref, $result->[0]->{user});
	}
	if ($FORMref->{'action'} eq "topic_cat_add") {
		topic_add_category_handler($FORMref, $result->[0]->{user});
	}
	if ($FORMref->{'action'} eq "topic_rename") {
		topic_rename_handler($FORMref, $result->[0]->{user});
		topic_manager($FORMref, $result->[0]->{user}, { what => 7 });
	}
	if ($FORMref->{'action'} eq "topic_meta") {
		topic_meta_tag_handler($FORMref, $result->[0]->{user});
	}
	error_message("Fell through topic admin subroutine");
}

###
### add_topic
###
### Creates a topic and underlying data structure with these inputs:
###     number =>		Topic number to use (if 0, gets unique #)
###		name =>			Topic name
###		descr =>		Topic description (optional)
###		postpriv =>		Posting privileges (0=public, 1=user+moderator, 2=moderator, 3=off)
###		readpriv =>		Reading privileges (same arguments as for posting)
###		content =>		Content (0=private index,1=public index)
###		place =>		Placement (0=bottom, 1=top)
###

sub add_topic {
	my ($arg, $FORMref) = @_;
	if (! -e "$DCONF->{admin_dir}/data/serial.txt") {
		open (FILE, "> $DCONF->{admin_dir}/data/serial.txt");
		print FILE join(".", $PARAMS->{'serial'}, time), "\n";
		close (FILE);
	}
	undef my $param;
	$param->{number} = ( defined($arg->{number}) ? $arg->{number} : get_number() );
	$param->{name} = ( defined($arg->{name}) ? $arg->{name} : "Untitled Topic" );
	$param->{descr} = ( defined($arg->{descr}) ? $arg->{descr} : "");
	$param->{postpriv} = ( $arg->{postpriv} > 0 ? $arg->{postpriv} : 0);
	$param->{readpriv} = ( $arg->{readpriv} > 0 ? $arg->{readpriv} : 0);
	$param->{content} = ( $arg->{content} == 1 ? 1 : 0 );
	$param->{place} = ( defined($arg->{place}) ? $arg->{place} : 0 );
	$param->{param} = ( $param->{content} == 1 ? "Sublist" : "SublistCreate" );
	$param->{icon} = ( $param->{readpriv} >= 1 ? "tree_o" : "tree_n" );
	if (my $x = topic_error_touch("$DCONF->{admin_dir}/msg_index/$param->{number}-log.txt")) {
		error_message("Create Topic Error", "Could not create log file for topic: $x", 0, 1);
	}
	if (my $x = topic_error_touch("$DCONF->{admin_dir}/msg_index/$param->{number}-search.txt")) {
		error_message("Create Topic Error", "Could not create search file for topic: $x", 0, 1);
	}
	dreq("fcn-logs");
	create_blank_mini_logs($param->{number});
	undef my $tree_hash;
	$tree_hash->{'topic'} = $param->{number};
	$tree_hash->{'page'} = $param->{number};
	$tree_hash->{'name'} = $param->{name};
	$tree_hash->{'param'} = $param->{param};
	$tree_hash->{'subs'} = 1;
	$tree_hash->{'properties'} = "icon=$param->{icon}";
	my ($tree_line, $tree_h) = generate_tree_line($tree_hash);
	if (open(FILE, "> $DCONF->{admin_dir}/msg_index/$param->{number}-tree.txt")) {
		print FILE $tree_line;
		close (FILE);
	} else {
		error_message("Create Topic Error", "Could not create tree file for topic!");
	}
	chmod(oct($DCONF->{'perms0666'}), "$DCONF->{admin_dir}/msg_index/$param->{number}-tree.txt");
	my $array = readfile("$DCONF->{admin_dir}/tree.txt", "add_topic", { ok_zero => 1, no_unlock => 1 });
	unshift(@{$array}, $tree_line) if $param->{place} == 1;
	push(@{$array}, $tree_line) if $param->{place} != 1;
	writefile("$DCONF->{admin_dir}/tree.txt", $array, "add_topic",	{ no_lock => 1 });
	undef my $dir;
	$dir = ( $arg->{readpriv} == 0 ? "$DCONF->{message_dir}/$param->{number}" : "$DCONF->{secdir}/$param->{number}" );
	if (! mkdir($dir,oct($DCONF->{'perms0777'})) ) {
		error_message("Create Directory Error", "Could not create directory for topic [$param->{number}]!");
	}
	chmod (oct($DCONF->{'perms0777'}), $dir);
	topic_error_touch("$dir/index.html");
	undef my $newhash;
	$newhash->{number} = $param->{number};
	$newhash->{name} = $param->{name};
	$newhash->{descr} = $param->{descr};
	$newhash->{subs} = 1;
	$newhash->{posts} = 0;
	$newhash->{originator} = "&nbsp;";
	$newhash->{last_poster} = "&nbsp;";
	$newhash->{lastmod} = time;
	$newhash->{url} = join("", ( $param->{readpriv} == 1 ? "$DCONF->{authorize_reader}?file=" : "$DCONF->{message_url}" ), "/$param->{number}/$param->{number}.$DCONF->{ext}");
	$newhash->{url} .= "?$newhash->{lastmod}" if ($DCONF->{noqm} == 0 && $param->{readpriv} == 0);
	$newhash->{url} .= "&lm=$newhash->{lastmod}" if $param->{readpriv} == 1;
	$newhash->{type} = 1;
	$newhash->{icon} = "$param->{icon}";
	dreq("topic-pg");
	my $tref = read_topic_page( { lock => 1 } );
	unshift (@{ $tref->{'topics'} }, $newhash) if $param->{place} == 1;
	push (@{ $tref->{'topics'} }, $newhash) if $param->{place} != 1;
	$tref->{unlock} = 1;
	write_topic_page( $tref );
	undef my $newpage;
	$newpage->{me_name} = $param->{name};
	$newpage->{me_number} = $param->{number};
	$newpage->{topic_name} = $param->{name};
	$newpage->{topic_number} = $param->{number};
	$newpage->{param} = $param->{param};
	$newpage->{parent} = $param->{number};
	dreq("fcn-pgad");
	my $subst = create_new_page( $newpage );
	@{ $subst->{head}->{levels} } = ();
	SetPage($subst);
	undef my @actions;
	if ($DCONF->{pro}) { # Reading Privileges
		my $hashref = { action => "add_topic", topic => $param->{number}, type => "r" };
		if ($arg->{readpriv} == 0) {
			$hashref->{ip} = "*";
		} elsif ($arg->{readpriv} == 1) {
			$hashref->{moderator} = "*"; $hashref->{user} = "*";
		} elsif ($arg->{readpriv} == 2) {
			$hashref->{moderator} = "*";
		}
		push (@actions, $hashref);
		if ($DCONF->{pro} && $arg->{readpriv} <= 2 && defined $FORMref) {
			dreq("authwrap-PRO");
			acs_add_additional_topics_to_token(undef, $param->{number}, $FORMref);
		}
	}
	{ # Posting Privileges
		my $hashref = { action => "add_topic", topic => $param->{number}, type => "p" };
		if ($arg->{postpriv} == 0) {
			$hashref->{ip} = "*";
		} elsif ($arg->{postpriv} == 1) {
			$hashref->{moderator} = "*"; $hashref->{user} = "*";
		} elsif ($arg->{postpriv} == 2) {
			$hashref->{moderator} = "*";
		}
		push (@actions, $hashref);
	}
	{ # Post Options
		my $hashref = { action => "add_topic", topic => $param->{number}, type => "o" };
		push (@actions, $hashref);
	}
	if ($DCONF->{pro}) { # Queue Privileges
		my $hashref = { action => "add_topic", topic => $param->{number}, type => "q" };
		push (@actions, $hashref);
	}
	dreq("fcn-usrp"); write_topic_privilege_file(\@actions);
	dreq("fcn-priv"); write_privilege_file([ { action => 'add_topic', topic => $param->{number}, moderator => $DCONF->{superuser} }]);
	return $param->{number};
}

###
### add_category
###
### Creates a category with these inputs:
###		text =>			Category text (essentially its name)
###		descr =>		Category description (optional)
###		bgcolor =>		Background color (if skin supports it)
###		place =>		Placement (0=bottom, 1=top)
###

sub add_category {
	my ($arg) = @_;
	if (! -e "$DCONF->{admin_dir}/data/serial.txt") {
		open (FILE, "> $DCONF->{admin_dir}/data/serial.txt");
		print FILE join(".", $PARAMS->{'serial'}, time), "\n";
		close (FILE);
	}
	dreq("topic-pg");
	undef my $argument;
	my $new_num = get_number();
	my $input = read_topic_page( { lock => 1} );
	my @topics = @{ $input->{topics} };
	undef my $catref;
	$catref->{text} = ( defined($arg->{text}) ? $arg->{text} : "&nbsp;" );
	$catref->{descr} = ( defined($arg->{descr}) ? $arg->{descr} : "" );
	$catref->{bgcolor} = ( defined($arg->{bgcolor}) ? $arg->{bgcolor} : "#ffffcc" );
	$catref->{number} = $new_num;
	my $place = ( defined($arg->{place}) ? $arg->{place} : 0 );
	push (@topics, $catref) if $place == 0;
	unshift (@topics, $catref) if $place == 1;
	$input->{topics} = \@topics;
	$input->{unlock} = 1;
	my $hashref = { action => "set_equal", topic => $new_num, type => "cr" };
	dreq("fcn-usrp");
	my @actions = ($hashref);
	my $m1 = read_topic_privilege_file(1);
	write_topic_privilege_file(\@actions);
	return write_topic_page($input);
}

###
### delete_topic
###
### Deletes one or more topics and their supporting data structures.
###
### Input is $remove_list, a hash reference.  To delete a topic or category, the
### topic or category number is a key and its value is 1.
###

sub delete_topic {
	my ($remove_list) = @_;
	dreq("topic-pg");
	my $topic_page = read_topic_page( { lock => 1 } );
	undef my @new_array;
	undef my @removes;
	while (my $x = shift(@{ $topic_page->{'topics'} })) {
		push (@new_array, $x) if ! $remove_list->{ $x->{number} };
		push (@removes, $x->{number}) if $remove_list->{ $x->{number} } && $x->{type} == 1;
	}
	$topic_page->{'topics'} = \@new_array;
	$topic_page->{'unlock'} = 1;
	write_topic_page($topic_page);
	undef my %nuked;
	dreq("fcn-logs");
	foreach my $key (@removes) {
		undef my $dir;
		if (-e "$DCONF->{message_dir}/$key") {
			$dir = "$DCONF->{message_dir}/$key";
		} else {
			$dir = "$DCONF->{secdir}/$key";
		}
		if (opendir(DIR, $dir)) {
			while (my $f = readdir(DIR)) {
				if ($f !~ m|^(\d+)\.$DCONF->{ext}$|) {
					$nuked{$f} = 1;
				}
				unlink ("$dir/$f");
			}
			closedir(DIR);
			rmdir("$dir") || log_error("adm-tpc.pl", "delete_topic", "Could not remove directory [$dir/$key] : [$!]");
		}
		$dir = "$DCONF->{admin_dir}/msg_index/$key";
		if (-e $dir) {	# Might be here from older versions
			opendir(DIR, "$dir");
			while (my $f = readdir(DIR)) {
				unlink ("$dir/$f");
			}
			closedir(DIR);
			rmdir("$DCONF->{admin_dir}/msg_index/$key");
		}
		unlink("$DCONF->{admin_dir}/msg_index/$key-tree.txt");
		unlink("$DCONF->{admin_dir}/msg_index/$key-search.txt");
	    unlink("$DCONF->{admin_dir}/msg_index/$key-log.txt");
	    my $mini = get_mini_logs();
	    if (ref $mini eq 'ARRAY' && scalar @{$mini}) {
	    	foreach my $m (@{$mini}) {
	    		unlink("$DCONF->{admin_dir}/msg_index/$key-log-$m.txt");
	    	}
	    }
	    if ($GLOBAL_OPTIONS->{'database'} && $DCONF->{pro}) {
	    	dreq("dbint");
	    	my $dbh = db_connect();
	    	db_query("DELETE FROM $PARAMS->{db_prefix}log WHERE posttopic = $key;", $dbh);
	    	db_query("DELETE FROM $PARAMS->{db_prefix}search WHERE posttopic = $key;", $dbh);
	    }
	}
	dreq("fcn-usrp"); write_topic_privilege_file([ { action => "del_topic", type => "p,r,o,q", topic => join(",", keys(%{ $remove_list })) } ]);
	dreq("fcn-priv"); write_privilege_file([ { action => "del_topic", topic => join(",", keys(%{ $remove_list })) } ]);
	undef my @new_tree;
	my $treeref = readfile("$DCONF->{admin_dir}/tree.txt", "delete_topic", { no_unlock => 1, zero_ok => 1 });
	foreach my $treeline (@{$treeref}) {
		my @c = split(/\t/, $treeline, 3);
		if (! $remove_list->{ $c[1] }) {
			push (@new_tree, $treeline);
		}
	}
	writefile("$DCONF->{admin_dir}/tree.txt", \@new_tree, "delete_topic", { no_lock => 1 });
	return 1;
}

###
### topic_rename_handler
###
### Handles form input to rename a topic
###

sub topic_rename_handler {
	my ($FORMref, $username) = @_;
	dreq("webtags");
	my ($l, $n) = webtags($FORMref->{topic_name}, 1, 1, 1);
	error_message("Rename Topic - Problem With Name", $n, 0, 1) if $l eq "!Error";
	my $bt = board_topics();
	error_message("Rename Topic - Error", "Topic does not exist!") if ! grep($_->{number} == $FORMref->{topic}, @{$bt});
	rename_topic($FORMref->{topic}, $n, $username, $bt);

}

###
### rename_topic
###
### Gives a topic a new name that you specify
###

sub rename_topic {
	my ($topic, $new_name, $user, $bt_cache) = @_;
	dreq("fcn-regn", "fcn-tree", "topic-pg");
	my $tree = read_tree($topic, { no_unlock => 1 });
	my $H = tree_record_update($tree, undef, { topic => $topic, page => $topic, name => $new_name }, 1);
	$tree = $H->{tree}; my $cat = $H->{cat};
	write_tree($topic, $tree, { no_lock => 1 });
	topic_tree_to_main($tree);
	regenerate_topic_page();
	my $Q = GetPage($topic, $topic, { lock => 1 });
	$Q->{head}->{topic_name} = $new_name;
	$Q->{head}->{me_name} = $new_name;
	SetPage($Q, { unlock => 1 });
	my $chgfile = regeneration_create_change_process_file("regen_topic_name_change", $new_name);
	my $Z = { $topic => 1 };
	my $hold = regeneration_create_hold({ topic => $topic, action => 'rename' });
	regenerate_board({ topic => $Z, hold => $hold, action => "regen5", username => $user, changefile => $chgfile, operation => 7, description => 701 });
}

###
### topic_meta_tag_handler
###
### Handles form input to set up META tags
###

sub topic_meta_tag_handler {
	my ($FORMref, $username) = @_;
	my $d = defined $FORMref->{meta_description} ? $FORMref->{meta_description} : ""; $d =~ s/["\n]//g;
	my $k = defined $FORMref->{meta_keywords} ? $FORMref->{meta_keywords} : ""; $k =~ s/["\n]//g;
	my $r = "";
	if ($FORMref->{meta_robots} == 1) {
		$r = "NOINDEX,NOFOLLOW";
	} elsif ($FORMref->{meta_robots} == 2) {
		$r = "INDEX,FOLLOW";
	} elsif ($FORMref->{meta_robots} == 3) {
		$r = $FORMref->{meta_robots_text}; $r =~ s/["\n]//g;
	}
	dreq("fcn-regn");
	my $hold = regeneration_create_hold({ topic => $FORMref->{topic}, action => 'meta' });
	save_meta_tags($FORMref->{topic}, $username, $d, $k, $r, $hold);
	my $q = GetPage($FORMref->{topic}, $FORMref->{topic}, { lock => 1 });
	$q->{meta_description} = $d;
	$q->{meta_keywords} = $k;
	$q->{meta_robots} = $r;
	SetPage($q, { unlock => 1 });
	topic_management_handler($FORMref, $username, "p", undef, $FORMref->{topic});
}

###
### save_meta_tags
###
### Sets META tags in the topic
###

sub save_meta_tags {
	my ($topic, $user, $descr, $keyword, $robot, $hold) = @_;
	dreq("fcn-regn");
	my $chgfile = regeneration_create_change_process_file("regen_meta_tag_change", $descr, $keyword, $robot);
	my %z = map { $_ , 1 } split(/,/, $topic);
	regenerate_board({ topic => \%z, hold => $hold, action => "regen5", username => $user, changefile => $chgfile, operation => 8, description => 801 });
}

###
### reorder_topics
###
### Puts the topics in whatever order you specify.
###

sub reorder_topics {
	my ($order) = @_;
	dreq("topic-pg");
	my $topic_page = read_topic_page( { lock => 1 } );
	my @topic_sort = sort {$order->{ $a->{number} } <=> $order->{ $b->{number} } } @{ $topic_page->{topics} };
	$topic_page->{topics} = \@topic_sort;
	$topic_page->{'unlock'} = 1;
	write_topic_page($topic_page);
	undef my @new_tree;
	undef my $tree_line;
	my $treeref = readfile("$DCONF->{admin_dir}/tree.txt", "reorder_topics", { no_unlock => 1 });
	foreach my $treeline (@{$treeref}) {
		my @c = split(/\t/, $treeline, 3);
		$tree_line->{ $c[1] } = $treeline;
	}
	foreach my $ts (@topic_sort) {
		push (@new_tree, $tree_line->{ $ts->{number} });
	}
	writefile("$DCONF->{admin_dir}/tree.txt", \@new_tree, "reorder_topics", { no_lock => 1 });
}

###
### set_category_properties
###
### Saves changes to category properties (name, background color, description).
###

sub set_category_properties {
	my ($arg) = @_;
	dreq("topic-pg");
	my $input = read_topic_page( { lock => 1} );
	my $number = $arg->{number};
	if (defined($arg->{editor})) {
		my $m = (@{ $input->{topics} })[ $arg->{editor} ];
		undef my $hashref;
		$hashref->{'text'} = (defined($arg->{text}) ? $arg->{text} : $m->{'text'});
		$hashref->{'bgcolor'} = (defined($arg->{bgcolor}) ? $arg->{bgcolor} : $m->{'bgcolor'});
		$hashref->{'descr'} = (defined($arg->{descr}) ? $arg->{descr} : $m->{'descr'});
		$hashref->{'number'} = $number;
		$input->{topics}[ $arg->{editor} ] = $hashref;
	} else {
		foreach my $x (@{ $input->{topics} }) {
			if ($x->{number} == $number) {
				$x->{'text'} = (defined($arg->{text}) ? $arg->{text} : $x->{'text'});
				$x->{'bgcolor'} = (defined($arg->{bgcolor}) ? $arg->{bgcolor} : $x->{'bgcolor'});
				$x->{'descr'} = (defined($arg->{descr}) ? $arg->{descr} : $x->{'descr'});
				last;
			}
		}
	}
	$input->{unlock} = 1;
	return write_topic_page($input);
}

###
### set_topic_properties
###
### Saves topic properties (hidden/visible, name, icon, etc.)
###

sub set_topic_properties {
	my ($arg) = @_;
	my %z = map {$_, 1} split(/,/, $arg->{topic_number});
	my $regeneration = 0;
#	if ($arg->{topic_name} ne $arg->{orig}->{topic_name} || $arg->{icon} ne $arg->{orig}->{icon} || $arg->{hidden} != $arg->{orig}->{hidden}) {
	if (1) {
		my %tl = undef;
		my $tree_main = readfile("$DCONF->{admin_dir}/tree.txt", "set_topic_properties", { no_unlock => 1 } );
		my $save_properties = "";
		foreach my $x (@{ $tree_main }) {
			my $y = tree_line_as_hash($x);
			$tl{$y->{topic}} = $y;
			if ($z{$y->{topic}}) {
				$tl{$y->{topic}}->{name} = $arg->{topic_name} if defined $arg->{topic_name};
				$tl{$y->{topic}}->{props} = $arg->{orig}->{props};
				$tl{$y->{topic}}->{props}->{hidden} = 0 + $arg->{hidden};
				$tl{$y->{topic}}->{props}->{icon} = $arg->{icon} if $arg->{icon} ne "";
				undef $tl{$y->{topic}}->{properties};
				$x = (generate_tree_line($tl{$y->{topic}}))[0];
				$save_properties = (split(/\t/, $x))[8];
			}
			$tl{$y->{topic}} = $x;
		}
		writefile("$DCONF->{admin_dir}/tree.txt", $tree_main, "set_topic_properties", { no_lock => 1 } );
		foreach my $num (keys(%z)) {
			my $tree_tpc = readfile("$DCONF->{admin_dir}/msg_index/$num-tree.txt", "set_topic_properties", { no_unlock => 1 } );
			$tree_tpc->[0] = $tl{$num};
			writefile("$DCONF->{admin_dir}/msg_index/$num-tree.txt", $tree_tpc, "set_topic_properties", { no_lock => 1 } );
			my $topic_file = GetPage($num,$num,{lock => 1});
			$topic_file->{pginfo}->{headers_asstring} = "";
			$topic_file->{headers_asstring} = "";
			$topic_file->{head}->{topic_name} = $arg->{topic_name} if defined $arg->{topic_name};
			$topic_file->{head}->{me_name} = $arg->{topic_name} if defined $arg->{topic_name};
			$topic_file->{head}->{properties} = $save_properties;
			SetPage($topic_file, { unlock => 1 });
			$regeneration = 1 if (defined $arg->{topic_name} && scalar(@{ $topic_file->{sublist} }) > 0 && $arg->{topic_name} ne $arg->{orig}->{topic_name});
		}
	}
	dreq("topic-pg");
	my $input = read_topic_page( { lock => 1 } );
	foreach my $x (@{ $input->{topics} }) {
		if ($z{$x->{number}}) {
			$x->{'descr'} = (defined($arg->{descr}) ? $arg->{descr} : $x->{'descr'});
		}
	}
	$input->{unlock} = 1;
	write_topic_page($input);
	return 1;
}

#######################################################################
# INTERNAL FUNCTIONS                                                  #
#                                                                     #
# These functions are used internally by the program and are not      #
# designed to be called independently from any other scripts.         #
#######################################################################

###
### topic_add_category_handler
###
### Handles form input to add a new category
###

sub topic_add_category_handler {
	my ($FORMref, $username) = @_;
	undef my $arg;
	$arg->{'bgcolor'} = $FORMref->{bgcolor};
	dreq("webtags");
	my ($messages, $formatted) = webtags($FORMref->{cat_text}, 3, 1, 1, 1);
	if ($messages eq "!Error") {
		error_message("Add Category Error - Problem with Text", $formatted, 0, 1);
	} else {
		$arg->{'text'} = $formatted;
	}
	if ($FORMref->{cat_descr}) {
		($messages, $formatted) = webtags($FORMref->{cat_descr}, 3, 1, 1, 1);
		if ($messages eq "!Error") {
			error_message("Add Category Error - Problem with Description", $formatted, 0, 1);
		} else {
			$arg->{'descr'} = $formatted;
		}
	} else {
		$arg->{'descr'} = undef;
	}
	$arg->{'bgcolor'} =~ s/[^#\w\s]//g;
	$arg->{'place'} = $FORMref->{place};
	add_category($arg);
	topic_manager($FORMref, $username, { menu => 2, what => 5 });
}

###
### topic_add_topic_handler
###
### Handles form input to add a new topic
###

sub topic_add_topic_handler {
	my ($FORMref, $username) = @_;
	my $arg = {};
	dreq("webtags");
	my ($messages, $formatted) = webtags($FORMref->{topic_name}, 3, 1, 1, 1);
	if ($messages eq "!Error") {
		error_message("Add Topic Error - Problem with Text", $formatted, 0, 1);
	} else {
		$arg->{'name'} = $formatted;
	}
	if ($FORMref->{descr}) {
		($messages, $formatted) = webtags($FORMref->{descr}, 3, 1, 1, 1);
		if ($messages eq "!Error") {
			error_message("Add Category Error - Problem with Description", $formatted, 0, 1);
		} else {
			$arg->{'descr'} = $formatted;
		}
	} else {
		$arg->{'descr'} = undef;
	}
	foreach my $x ('readpriv', 'postpriv', 'content', 'place') {
		$arg->{$x} = 0 + $FORMref->{$x};
	}
	add_topic($arg, $FORMref);
	topic_manager($FORMref, $username, { menu => 1, what => 4 });
}

###
### topic_management_handler
###
### Handles form input from actions on the topics list
###

sub topic_management_handler {
	my ($FORMref, $username, $selection, $stuff, $number) = @_;
	my $subst = {};
	$subst->{topics} = board_topics();
	$subst->{stuff} = $stuff;
	$selection = $FORMref->{SELECTION_action} if $selection eq "";
	$number = $FORMref->{SELECTION_who} if $number eq "";
	my %p = $number eq '*' ? map { $_, 1 } split(/,/, $FORMref->{MARK}) : map { $_, 1 } split(/,/, $number);
	my $type = {};
	foreach my $topic (@{ $subst->{topics} }) {
		$type->{ $topic->{number} } = $topic->{type};
	}
	if ($selection eq "d") {
		my $count = 0;
		undef my $J;
		foreach my $K (keys(%p)) {
			$J->{$K} = 1 if $type->{$K} == 1 || $type->{$K} == 2;
			$subst->{'delete'}->{'error'} = 1 if ! defined($J->{$K});
		}
		$count += delete_topic($J) if defined($J);
		topic_manager($FORMref, $username, { what => 1, count => $count });
	} elsif ($selection eq "a") {
		my @q = grep ($type->{$_} == 1, keys(%p));
		error_message("Topic Privilege Assign Error", "You must select at least one topic to assign privileges (categories don't have privileges).", 0, 1) if scalar(@q) == 0;
		topic_privileges_screen(join(",", @q), $username);
	} elsif ($selection eq "p") {
		my @z = grep ($p{$_->{number}} == 1, @{$subst->{topics}});
		error_message("Property Screen Error", "You did not check any of the topics on the list!", 0, 1) if scalar(@z) == 0;
		if (scalar(@z) > 1) {
			@z = grep ($_->{type} == 1, @z);
			error_message("Property Screen Error", "No properties can be set simultaneously for multiple categories!", 0, 1) if scalar(@z) == 0;
		}
		$subst->{stuff}->{what} = 4 if $FORMref->{renamed};
		$subst->{stuff}->{what} = 3 if $FORMref->{metasave};
		if (scalar(@z) > 1) {
			$subst->{general}->{multiple} = 1;
			$subst->{'seltopic'} = $z[0];
			$subst->{general}->{username} = $username;
			$subst->{general}->{topics} = join(",", map { $_->{number} } @z);
			$subst->{'meta'}->{'meta_description'} = undef;
			$subst->{'meta'}->{'meta_keywords'} = undef;
			$subst->{'meta'}->{'meta_robots'} = undef;
			$subst->{'props'} = {};
			foreach my $q (@z) {
				my $href = GetPage($q->{number}, $q->{number});
				foreach my $mt ('meta_description', 'meta_keywords', 'meta_robots') {
					if (defined $subst->{'meta'}->{$mt} && $href->{$mt} =~ /\S/) {
						if ($href->{$mt} ne $subst->{'meta'}->{$mt}) {
							$subst->{'meta'}->{$mt} = "";
						}
					} elsif ($href->{$mt} =~ /\S/) {
						$subst->{'meta'}->{$mt} = $href->{$mt};
					}
				}
				foreach my $pr ('hidden', 'icon') {
					if (defined $subst->{'props'}->{$pr} && $q->{$pr} =~ /\S/) {
						if ($q->{$pr} ne $subst->{props}->{$pr}) {
							$subst->{'props'}->{$pr} = "";
						}
					} elsif ($q->{$pr} =~ /\S/) {
						$subst->{'props'}->{$pr} = $q->{$pr};
					}
				}
			}
		} else {
			$subst->{'seltopic'} = $z[0];
			$number = $z[0]->{number};
			$subst->{general}->{username} = $username;
			error_message("Property Screen Error", "Topic/Category not found on topics page!", 0, 1) if ! defined $subst->{'seltopic'};
			dreq("webtags");
			if ($subst->{seltopic}->{type} == 2) {
				$subst->{'edit'}->{'cat_text'} = inverse_webtags($subst->{seltopic}->{text}, undef, 1);
				$subst->{'edit'}->{'cat_descr'} = inverse_webtags($subst->{seltopic}->{descr}, undef, 1);
			} else {
				my $href = GetPage($number, $number);
				$subst->{'edit'}->{'topic_name'} = inverse_webtags($subst->{seltopic}->{name}, undef, 1);
				$subst->{'edit'}->{'descr'} = inverse_webtags($subst->{seltopic}->{descr}, undef, 1);
				foreach my $pr ('hidden', 'icon') {
					$subst->{'props'}->{$pr} = $subst->{'seltopic'}->{$pr};
				}
				$subst->{'meta'}->{'meta_description'} = $href->{'meta_description'};
				$subst->{'meta'}->{'meta_keywords'} = $href->{'meta_keywords'};
				$subst->{'meta'}->{'meta_robots'} = $href->{'meta_robots'};
			}
		}
		if (defined $subst->{'meta'}->{'meta_robots'}) {
			my $s = $subst->{'meta'}->{'meta_robots'};
			if ($s !~ /\S/) {
				$subst->{'meta'}->{'meta_robots'} = 0;
			} elsif ($s =~ /^noindex,nofollow/i) {
				$subst->{'meta'}->{'meta_robots'} = 1;
			} elsif ($s =~ /^index,follow/i) {
				$subst->{'meta'}->{'meta_robots'} = 2;
			} else {
				$subst->{'meta'}->{'meta_robots'} = 3;
				$subst->{'meta'}->{'meta_robots_text'} = $s;
			}
		}
		screen_out("tpcprops", $subst);
	} elsif ($selection eq 'r') {
		my $order = {};
		foreach my $x (keys(%{ $FORMref })) {
			next if $x !~ m|^ORDER(\d+)|;
			$order->{$1} = $FORMref->{$x};
		}
		reorder_topics($order);
		topic_manager($FORMref, $username, { what => 3 });
	}
}

###
### topic_manager
###
### Displays the Topic Manager
###

sub topic_manager {
	my ($FORMref, $username, $stuff) = @_;
	my $subst = {};
	$subst->{stuff} = $stuff;
	$subst->{'general'}->{'url'} = "$PARAMS->{cgiurl}?username=$FORMref->{username}&action=topic_mgr";
	$subst->{'general'}->{'menu'} = (defined $stuff->{menu} ? $stuff->{menu} : $FORMref->{menu});
	$subst->{'topics'} = board_topics(undef, undef, undef, undef, 1);
	$subst->{'general'}->{'username'} = $username;
	my @a2z = sort { $a->{name} cmp $b->{name} } @{ $subst->{topics} };
	$subst->{general}->{menu} = 1 if $FORMref->{menu} eq "" && scalar @a2z == 0;
	undef my @a2z_js;
	foreach my $x (@a2z) {
		undef my $hashref;
		$hashref->{text} = "document.tf.ORDER$x->{number}.value";
		$hashref->{numonly} = $x->{number};
		push (@a2z_js, $hashref);
	}
	$subst->{'a2z_js'} = \@a2z_js;
	my @by_date = sort { $b->{lastmod} <=> $a->{lastmod} } @{ $subst->{topics} };
	undef my @by_date_js;
	foreach my $xb (@by_date) {
		undef my $hashref;
		$hashref->{text} = "document.tf.ORDER$xb->{number}.value";
		$hashref->{numonly} = $xb->{number};
		push (@by_date_js, $hashref);
	}
	$subst->{'date_js'} = \@by_date_js;
	header();
	print templ_int("topicmgr", $subst);
	program_exit(0);
}

###
### topic_privileges_save
###
### Saves topic editing privileges
###

sub topic_privileges_save {
	my ($topics, $moderators) = @_;
	dreq("fcn-priv");
	my @actions = ({ topic => $topics, moderator => $moderators, action => 'set_equal' });
	write_privilege_file(\@actions);
}

###
### topic_category_properties_handle
###
### Handle form input to save topic categories
###

sub topic_category_properties_handle {
	my ($topics, $FORMref) = @_;
	my $number = $FORMref->{topic};
	undef my $this_line;
	my $ctr = 0;
	foreach my $x ( @{ $topics } ) {
		if ($x->{number} eq $number) {
			$this_line = $x;
			last;
		}
		$ctr++;
	}
	error_message("Edit Category Error", "Category does not exist!", 0, 1) if $this_line->{type} != 2;
	undef my $arg;
	dreq("webtags");
	my ($messages, $formatted) = webtags($FORMref->{cat_text}, 3, 1, 1, 1);
	if ($messages eq "!Error") {
		error_message("Edit Category Error - Problem with Text", $formatted, 0, 1);
	} else {
		$arg->{'text'} = $formatted;
	}
	if (defined $FORMref->{cat_descr}) {
		($messages, $formatted) = webtags($FORMref->{cat_descr}, 1, 1, 1, 1);
		if ($messages eq "!Error") {
			error_message("Edit Category Error - Problem with Description", $formatted, 0, 1);
		} else {
			$arg->{'descr'} = $formatted;
		}
	} else {
		$arg->{'descr'} = undef;
	}
	$arg->{'editor'} = $ctr;
	$arg->{'bgcolor'} = $FORMref->{'bgcolor'};
	$arg->{'bgcolor'} =~ s/[^#\w\s]//g;
	$arg->{'number'} = $number;
	return $arg;
}

###
### topic_properties_handle
###
### Handle form input to save topic privileges
###

sub topic_properties_handle {
	my ($topics, $FORMref) = @_;
	my @z = grep ($_->{type} == 1, @{$topics});
	my %z = map { $_, 1 } split(/,/, $FORMref->{topic});
	my @t = grep ( $z{ $_->{number} } == 1, @z);
	error_message("Invalid Topic Number", "Requested topic does not exist [tph1]!", 0, 1) if scalar(@t) == 0;
	my $param = {};
	my $tx = {};
	if (scalar(@t) == 1) {
		dreq("webtags");
		if ($FORMref->{topic_name} ne "") {
			my ($messages, $formatted) = webtags($FORMref->{topic_name}, 3, 1, 1, 1);
			if ($messages eq "!Error") {
				error_message("Topic Name Error - Problem with Text", $formatted, 0, 1);
			} else {
				$param->{'topic_name'} = $formatted;
			}
		} else {
			$param->{'topic_name'} = $tx->{name};
		}
		if ($FORMref->{descr} ne "") {
			my ($messages, $formatted) = webtags($FORMref->{descr}, 1, 1, 1, 1);
			if ($messages eq "!Error") {
				error_message("Topic Description Error - Problem with Text", $formatted, 0, 1);
			} else {
				$param->{'descr'} = $formatted;
			}
		} else {
			$param->{'descr'} = "";
		}
	}
	$param->{'hidden'} = 0 + $FORMref->{'hide'};
	$param->{'icon'} = $FORMref->{'icon'};
	$param->{'topic_number'} = $FORMref->{topic};
	$param->{'number'} = $FORMref->{topic};
	$param->{'orig'} = $tx;
	return $param;
}

###
### topic_privileges_screen
###
### Assign moderator editing privilegs
###

sub topic_privileges_screen {
	my ($topics, $username) = @_;
	dreq("fcn-priv");
	my $filecache = read_privilege_file();
	my $subst = {};
	my $mod = read_account_file("passwd", undef, { returnformat => 'single_array' });
	my @mod = ();
	my @gs = split(/,/, $topics);
	foreach my $x (@{ $mod }) {
		undef my $i;
		$i->{user} = $x->{user};
		$i->{name} = $x->{fullname};
		my $c = 0;
		foreach my $g (@gs) {
			$c += check_topic_authorization($x->{user}, $g, $filecache);
		}
		$i->{checked} = ($c >= scalar(@gs) ? 1 : 0);
		push (@mod, $i);
	}
	@mod = sort { $a->{user} cmp $b->{user} } @mod;
	$subst->{moderators} = \@mod;
	$subst->{general}->{mods} = scalar(split(/,/, $topics));
	$subst->{general}->{screen} = 1;
	$subst->{general}->{username} = $username;
	$subst->{general}->{affect} = $topics;
	if ($topics =~ /^\d+$/) {
		my $href = GetPage($topics, $topics);
		$subst->{general}->{thistopic_name} = $href->{head}->{topic_name};
	}
	screen_out("grpmodas", $subst);
}

###
### topic_error_touch
###
### Initializes a file (and returns an error code if it could not)
###

sub topic_error_touch {
	my ($filename) = @_;
	if (open(FILE, "> $filename")) {
		close (FILE);
		chmod(oct($DCONF->{'perms0666'}), $filename);
		return undef;
	} else {
		return $!;
	}
}

1;
