# FILE: adm-appr.pl
# DESCRIPTION: Appearance Manager Interfaces
#-------------------------------------------------------------------------------
# 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);

###
### APPEAR_admin
###
### Run the Appearance Manager
###

sub APPEAR_admin {
	my ($FORMref) = @_;
	my $subst = {};
	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};
	$subst->{'general'}->{'username'} = $result->[0]->{'user'};
	appear_mgr($FORMref, $result, $subst) if $FORMref->{action} eq "appear_mgr";
	appear_select_skin($FORMref, $result) if $FORMref->{action} eq "appear_skinsel";
	appear_preview_skin($FORMref, $result) if $FORMref->{action} eq "appear_prevskin";
	appear_file_editor($FORMref, $result) if $FORMref->{action} eq "appear_ted";
	appear_skin_editor($FORMref, $result) if $FORMref->{action} eq "appear_sked";
	appear_file_validator($FORMref, $result) if $FORMref->{action} eq "appear_val";
	appear_receive_direct_upload($FORMref, $result) if $FORMref->{action} eq "appear_skinupload";
	if ($FORMref->{action} eq "appear_saveskin") {
		appear_save_skin($FORMref);
		$FORMref->{menu} = 5;
		$subst->{stuff}->{regen_recommended} = 1;
	}
	if ($FORMref->{action} eq "appear_bigtitle") {
		dreq("webtags");
		my ($messages, $formatted) = webtags($FORMref->{msg}, 1, 1, 1, 1);
		error_message(read_language()->{FORMATTINGERROR}, $formatted, 0, 1) if $messages eq "!Error";
		dreq("adm-opts");
		options_save({ title_words => $formatted});
		$FORMref->{menu} = 0;
	}
	if ($FORMref->{action} eq "appear_regen") {
		$FORMref->{menu} = 5;
		if ($FORMref->{topic} eq "TPC") {
			dreq("topic-pg");
			regenerate_topic_page();
		} else {
			dreq("fcn-regn");
			my $u = REGEN_admin($FORMref);
			$subst->{stuff}->{regen_taskman} = $u * $GLOBAL_OPTIONS->{discus_taskman} * $GLOBAL_OPTIONS->{taskman_regen};
		}
	}
	if ($FORMref->{action} eq "appear_altcolors") {
		my $ar1 = {};
		for (my $i = 1; $i <= 20; $i++) { ## ALT_COLOR_MAX
			$ar1->{"alt_color_$i"} = trim($FORMref->{"alt_color_$i"});
		}
		dreq("adm-opts");
		options_save($ar1);
		$FORMref->{'menu'} = 5;
		$subst->{stuff}->{regen_recommended} = 1;
	}
	if ($FORMref->{'action'} eq "appear_savecolors") {
		my $ar1 = {};
		foreach my $key ('bgcolor', 'text', 'link', 'alink', 'vlink', 'image', 'face', 'size', 'image') {
			$ar1->{"COLOR_$key"} = trim($FORMref->{$key});
		}
		dreq("adm-opts");
		options_save($ar1);
		$FORMref->{'menu'} = 5;
		$subst->{stuff}->{regen_recommended} = 1;
	}
	if ($FORMref->{'action'} eq "appear_colors") {
		foreach my $key ('bgcolor', 'text', 'link', 'alink', 'vlink', 'image', 'face', 'size', 'image') {
			$subst->{'color'}->{$key} = trim($FORMref->{$key});
		}
		$subst->{'color_override'} = 1;
		$subst->{'FORMref'} = $FORMref;
		screen_out("chbrdclr", $subst);
	}
	if ($FORMref->{'action'} eq "appear_interface") {
		dreq("adm-opts");
		my $v = $FORMref->{ui_topics_from} == 0 ? 0 : 1;
		options_save( { use_static_ui_documents => 0, use_static_topic_document => $v });
		$subst->{stuff}->{saved} = 1;
	}
	if ($FORMref->{'action'} eq "appear_frames") {
		dreq("adm-opts");
		my $u = ( $FORMref->{ui_from} == 0 ? 0 : 1 );
		my $o = $GLOBAL_OPTIONS->{use_no_frames};
		options_save( { use_no_frames => $u });
		$subst->{stuff}->{saved} = 2;
		if ($u != $o) {
			$FORMref->{menu} = 5;
			$subst->{stuff}->{regen_recommended} = 1;
		}
	}
	if ($FORMref->{action} eq "appear_skindel") {
		my $skins = $FORMref->{dels} eq "*" ? $FORMref->{delskin} : $FORMref->{dels};
		my @s = grep { /^[\w\-]+$/ && $_ ne $GLOBAL_OPTIONS->{skinchoice} && -e "$DCONF->{admin_dir}/skins/$_.tmpl" } split(/,/, $skins);
		error_message("Skin Delete Error", "No valid skins were selected for removal", 0, 1) if scalar @s == 0;
		my $ctr = 0;
		foreach my $skindel (@s) {
			my $skin = join("/", $DCONF->{admin_dir}, "skins", "$skindel.tmpl");
			my $icon = join("/", $DCONF->{html_dir}, $DCONF->{icon_dir}, $skindel);
			if (unlink $skin) {
				if (-e $icon) {
					if (opendir(ICONDIR, $icon)) {
						my @fd = map { join("/", $icon, $_) } grep { ! /^\.+$/ } readdir(ICONDIR);
						closedir(ICONDIR);
						foreach my $fd (@fd) {
							unlink $fd || log_error("adm-appr.pl", "APPEAR_admin", "Could not remove icon $fd: $!");;
						}
					}
					rmdir $icon || log_error("adm-appr.pl", "APPEAR_admin", "Could not remove directory $icon: $!");
				}
				$ctr++;
			}
		}
		$FORMref->{menu} = 3;
		$subst->{stuff}->{skinsdel} = $ctr;
		appear_mgr($FORMref, $result, $subst);
	}
	if ($FORMref->{'action'} eq "appear_title" || $FORMref->{'action'} eq "appear_main") {
		$subst->{'general'}->{'location'} = 2 if $FORMref->{'action'} eq "appear_title";
		$subst->{'general'}->{'location'} = 3 if $FORMref->{'action'} eq "appear_main";
		dreq("webtags");
		$FORMref->{msg} = $FORMref->{message} if ! defined $FORMref->{msg};
		my ($messages, $formatted) = &webtags($FORMref->{msg}, 1, 1, 1, 1);
		if ($messages eq "!Error") {
			$FORMref->{preview} = 1;
			$subst->{'preview'}->{'error'} = 1;
		}
		if ($FORMref->{preview} == 1 || $subst->{'preview'} == 1) {
			$subst->{'FORMref'} = $FORMref;
			my $input = $FORMref->{msg};
			$input =~ s/&/&amp;/g;
			$input =~ s/</&lt;/g;
			$input =~ s/>/&gt;/g;
			$input =~ s/"/&quot;/g;
			$subst->{'general'}->{'source'} = $input;
			$subst->{'general'}->{'message_text'} = $formatted;
			$subst->{'general'}->{'hr'} = $FORMref->{hr};
			$subst->{'general'}->{'cancel'} = "$PARAMS->{cgiurl}?username=$FORMref->{username}&action=appear_mgr&menu=0";
			screen_out("adminmsg", $subst);
		}
		my $param_ref = {};
		$param_ref->{'text'} = $formatted;
		$param_ref->{'hr'} = $FORMref->{hr};
		set_title_message($param_ref) if $FORMref->{'action'} eq "appear_title";
		set_main_message($param_ref) if $FORMref->{'action'} eq "appear_main";
		$FORMref->{'action'} = "appear_mgr";
	}
	appear_mgr($FORMref, $result, $subst);
}

###
### appear_receive_other_data
###
### Parses other data
###

sub appear_receive_other_data {
	my ($data) = @_;
	my @out = ();
	my @fi = split(/&/, $data);
	foreach my $F (@fi) {
		my ($name, $checksum, $data) = split(/=/, $F);
		my $data_orig = $data;
		my $content = "";
		while ($data =~ /^([0-9a-f][0-9a-f])/) {
			$data = $'; $content .= pack("C", hex($1));
		}
		my $checksum2 = unpack("%16C*", $data_orig);
		template_error("Uploaded data file ($name) had wrong checksum!  Try uploading again.", $checksum, $checksum2) if $checksum != $checksum2;
		push @out, { file => $name, checksum => $checksum, data => $content };
	}
	return \@out;
}


###
### appear_receive_direct_upload
###
### Receives a skin from the DiscusWare skin gallery
###

sub appear_receive_direct_upload {
	my ($FORMref, $result) = @_;
	$FORMref->{skin} = $` if $FORMref->{skin} =~ /\.tmpl\s*$/;
	error_message("Invalid Skin Name", "Skin name is invalid!  It may contain only alphanumeric characters and '-'.", 0, 1) if $FORMref->{skin} !~ /^[\w\-]+$/;
	my $content = unescape($FORMref->{content});
	if (length $content != $FORMref->{slength}) {
		my $len = length $content;
		template_error("Uploaded skin had the wrong size (received size: $len; expected size: $FORMref->{slength})!  Try uploading again.", 0, 1);
	}
	if (unpack("%16C*", $content) != $FORMref->{checksum}) {
		template_error("Uploaded skin had the wrong checksum!  Try uploading again.", 0, 1);
	}
	my $subst = {};
	my $exist_flag = -e "$DCONF->{admin_dir}/skins/$FORMref->{skin}.tmpl";
	$exist_flag = 0 if $FORMref->{overwrite} == 1;
	if ($FORMref->{confirm} == 1 && ! $exist_flag) {
		if (-e "$DCONF->{admin_dir}/skins/$FORMref->{skin}.tmpl") {
			chmod(oct($DCONF->{perms0666}), "$DCONF->{admin_dir}/skins/$FORMref->{skin}.tmpl");
			if (-w "$DCONF->{admin_dir}/skins/$FORMref->{skin}.tmpl") {
				my $ic_dir = "$DCONF->{html_dir}/$DCONF->{icon_dir}/$FORMref->{skin}";
				if (-e $ic_dir) {
					chmod(oct($DCONF->{perms0777}), $ic_dir);
					if (opendir(DIR, $ic_dir)) {
						my @dir = map { join("/", $ic_dir, $_) } grep { ! /^\.+$/ } readdir(DIR);
						closedir(DIR);
						foreach my $file (@dir) {
							if (! unlink($file)) {
								log_error("adm-appr.pl", "appear_receive_direct_upload", "Could not remove icon file $file: $!");
							}
						}
					} else {
						log_error("adm-appr.pl", "appear_receive_direct_upload", "Could not read icon directory: $!");
					}
					rmdir($ic_dir) || log_error("adm-appr.pl", "appear_receive_direct_upload", "Could not remove icon directory: $!");
				}
			}
		}
		if (open (NEWSKIN, "> $DCONF->{admin_dir}/skins/$FORMref->{skin}.tmpl")) {
			binmode(NEWSKIN);
			print NEWSKIN $content;
			close (NEWSKIN);
			chmod(oct($DCONF->{perms0666}), "$DCONF->{admin_dir}/skins/$FORMref->{skin}.tmpl");
			if (-s "$DCONF->{admin_dir}/skins/$FORMref->{skin}.tmpl" == length($content)) {
				$subst->{general}->{completed} = 1;
			} else {
				$subst->{general}->{error_size} = 1;
				unlink "$DCONF->{admin_dir}/skins/$FORMref->{skin}.tmpl";
			}
			my $ic_dir = "$DCONF->{html_dir}/$DCONF->{icon_dir}/$FORMref->{skin}";
			if (! -e "$DCONF->{html_dir}/$DCONF->{icon_dir}/$FORMref->{skin}") {
				mkdir($ic_dir, oct($DCONF->{perms0777}));
				chmod(oct($DCONF->{perms0777}), $ic_dir);
			}
			my $files = appear_receive_other_data($FORMref->{other_data});
			foreach my $file (@{ $files }) {
				next if $file->{file} eq "";
				next if -e "$ic_dir/$file->{file}";
				open (ICON, "> $ic_dir/$file->{file}");
				binmode ICON;
				print ICON $file->{data};
				close (ICON);
				chmod(oct($DCONF->{perms0666}), "$ic_dir/$file->{file}");
			}
		} else {
			$subst->{general}->{error_permissions} = 1;
		}
	} elsif (-e "$DCONF->{admin_dir}/skins/$FORMref->{skin}.tmpl") {
		$subst->{general}->{needs_new_filename} = 1;
	}
	$subst->{general}->{skin} = $FORMref->{skin};
	$subst->{general}->{dumpstr} = escape($content);
	$subst->{general}->{checksum} = unpack("%16C*", $content);
	$subst->{general}->{slength} = length($content);
	$subst->{general}->{username} = $result->[0]->{user};
	$subst->{general}->{other_data} = $FORMref->{other_data};
	screen_out("newskin", $subst);
}

###
### appear_file_validator
###
### Validates file to check for proper use of formatting language
###

sub appear_file_validator {
	my ($FORMref, $result) = @_;
	my $filename = $FORMref->{file};
	my $dir = ""; my $file = "";
	if ($filename =~ m%^(\w+)/([\w\-]+)$%) {
		$dir = $1; $file = join(".", $2, "tmpl");
	} elsif ($filename =~ m%^(\w+)/(\w+)/([\w\-]+)$%) {
		$dir = join("/", $1, $2); $file = join(".", $3, "tmpl");
	} else {
		error_message("File Edit Error", "File $filename is not valid!", 0, 1);
	}
	my $path = join("/", $DCONF->{admin_dir}, $dir, $file); $path =~ s%/+%/%g;
	error_message("File Edit Error", "Selected file '$path' does not exist!", 0, 1) if ! -f $path;
	dreq("fcn-val");
	my $subst = template_validator($path);
	screen_out("validate", $subst);
}

###
### appear_file_editor
###
### Handler for editing of files
###

sub appear_file_editor {
	my ($FORMref, $result) = @_;
	my $filename = $FORMref->{file};
	my $dir = ""; my $file = "";
	if ($filename =~ m%^(\w+)/([\w\-]+)$%) {
		$dir = $1; $file = join(".", $2, "tmpl");
	} elsif ($filename =~ m%^(\w+)/(\w+)/([\w\-]+)$%) {
		$dir = join("/", $1, $2); $file = join(".", $3, "tmpl");
	} elsif ($filename =~ m%^([\w\-]+)\.conf$%) {
		$file = join(".", $1, "conf");
	} else {
		error_message("File Edit Error", "File $filename is not valid!", 0, 1);
	}
	my $path = join("/", $DCONF->{admin_dir}, $dir, $file); $path =~ s%/+%/%g;
	error_message("File Edit Error", "Selected file '$path' does not exist!", 0, 1) if ! -f $path;
	my $Z = readfile($path, "appear_file_editor", { zero_ok => 1 });
	my $c = 0;
	my $subst = {};
	if (defined $FORMref->{text}) {
		my $bufile = join("/", $DCONF->{admin_dir}, 'backups', $file);
		$c++ while (-f join(".", $bufile, sprintf("%02d", $c)) && $c <= 1000);
		if (-f join(".", $bufile, sprintf("%02d", $c))) {
			unlink map { join(".", $bufile, sprintf("%02d", $_)) } ( 0 .. 90 );
			$c = 0;
		}
		$bufile .= join("", ".", sprintf("%02d", $c));
		my $k = readfile($path, "appear_file_editor", { no_unlock => 1, zero_ok => 1 });
		writefile($bufile, $k, "appear_file_editor", { zero_ok => 1, no_lock => 1, no_unlock => 1 });
		my @x = map { join("", $_, "\n") } split(/\n/, $FORMref->{text});
		writefile($path, \@x, "appear_file_editor", { zero_ok => 1, no_lock => 1 });
		$subst->{editor}->{source} = $FORMref->{text};
		chmod(oct($DCONF->{perms0666}), $path);
		chmod(oct($DCONF->{perms0666}), $bufile);
		$subst->{general}->{saved} = 1;
	} else {
		$subst->{editor}->{source} = join("", @{$Z});
	}
	$subst->{editor}->{cols} = $GLOBAL_OPTIONS->{'templ_box_cols'} ? $GLOBAL_OPTIONS->{'templ_box_cols'} : 75;
	$subst->{editor}->{rows} = $GLOBAL_OPTIONS->{'templ_box_rows'} ? $GLOBAL_OPTIONS->{'templ_box_rows'} : 20;
	$subst->{general}->{filename} = $file;
	$subst->{general}->{username} = $result->[0]->{user};
	$subst->{general}->{path} = $filename;
	screen_out("fileedit", $subst);
}

###
### appear_skin_editor
###
### Handler for editing of skins
###

sub appear_skin_editor {
	my ($FORMref, $result) = @_;
	my $filename = $FORMref->{file};
	my $dir = ""; my $file = "";
	if ($filename =~ m%^(\w+)/([\w\-]+)$%) {
		$dir = 'skins'; $file = join(".", $2, "tmpl");
	} else {
		error_message("File Edit Error", "File $filename is not valid!", 0, 1);
	}
	my $path = join("/", $DCONF->{admin_dir}, $dir, $file); $path =~ s%/+%/%g;
	error_message("File Edit Error", "Selected file '$path' does not exist!", 0, 1) if ! -f $path;
	my $subst = {};
	if (defined $FORMref->{partid}) {
		my $bufile = join("/", $DCONF->{admin_dir}, 'backups', $file);
		my $c = 0;
		$c++ while (-f join(".", $bufile, sprintf("%02d", $c)) && $c <= 1000);
		if (-f join(".", $bufile, sprintf("%02d", $c))) {
			unlink map { join(".", $bufile, sprintf("%02d", $_)) } ( 0 .. 90 );
			$c = 0;
		}
		$bufile .= join("", ".", sprintf("%02d", $c));
		my $k = readfile($path, "appear_file_editor", { no_unlock => 1, zero_ok => 1 });
		writefile($bufile, $k, "appear_file_editor", { zero_ok => 1, no_lock => 1, no_unlock => 1 });
		my @x = map { join("", "# ", $_, "\n") } ("User-edited skin", scalar localtime(time), $DCONF->{html_url});
		foreach my $party (split(/,/, $FORMref->{partid})) {
			next if $party !~ /\//;
			my ($part, $parthold) = ($`, $');
			push @x, "<#part \"$part\"#>\n";
			push @x, map { join("", $_, "\n") } split(/\n/, $FORMref->{"text$parthold"});
			push @x, "<#/part#>\n";
		}
		push @x, "\n";
		writefile($path, \@x, "appear_file_editor", { zero_ok => 1, no_lock => 1 });
		chmod(oct($DCONF->{perms0666}), $path);
		chmod(oct($DCONF->{perms0666}), $bufile);
		$subst->{general}->{saved} = 1;
	}
	my $Z = readfile($path, "appear_file_editor", { zero_ok => 1 });
	$subst->{parts} = [];
	my $flag = "";
	my @cur = ();
	my $ctr = 0;
	foreach my $line (@{$Z}) {
		if ($line =~ m|^\s*<#\s*part\s*"(.*?)"\s*#>\s*$|) {
			$flag = $1;
		} elsif ($line =~ m|^\s*<#\s*/\s*part\s*#>\s*$|) {
			$ctr++;
			push @{$subst->{parts}}, { parthold => $ctr, part => $flag, source => join("", @cur) };
			$flag = ""; @cur = ();
		} elsif ($flag) {
			push @cur, $line;
		}
	}
	$subst->{editor}->{cols} = $GLOBAL_OPTIONS->{'templ_box_cols'} ? $GLOBAL_OPTIONS->{'templ_box_cols'} : 75;
	$subst->{editor}->{rows} = $GLOBAL_OPTIONS->{'templ_box_rows'} ? $GLOBAL_OPTIONS->{'templ_box_rows'} : 20;
	$subst->{general}->{filename} = $file;
	$subst->{general}->{username} = $result->[0]->{user};
	$subst->{general}->{path} = $filename;
	screen_out("skinedit", $subst);
}

###
### appear_mgr
###
### Generates the appearance manager screen
###

sub appear_mgr {
	my ($FORMref, $result, $subst) = @_;
	$subst->{general}->{menu} = $FORMref->{'menu'};
	$subst->{general}->{url} = "$PARAMS->{cgiurl}?username=$result->[0]->{user}&action=appear_mgr";
	if ($FORMref->{'menu'} == 0) {
		dreq("topic-pg");
		my $toppage = read_topic_page( { lock => 1, unlock => 1 } );
		dreq("webtags");
		$subst->{'texts'}->{'title'} = inverse_webtags($toppage->{titlemsg}->{text}, undef);
		$subst->{'texts'}->{'title_words'} = inverse_webtags($GLOBAL_OPTIONS->{title_words}, undef);
		$subst->{'texts'}->{'main'} = inverse_webtags($toppage->{mainmsg}->{text}, undef);
		$subst->{'hr'}->{'title'} = $toppage->{titlemsg}->{hr};
		$subst->{'hr'}->{'main'} = $toppage->{mainmsg}->{hr};
	}
	if ($FORMref->{menu} == 1) {
		undef $subst->{'altcolor_array'};
		for (my $i = 1; $i <= 20; $i++) {
			undef my $hashref;
			$hashref->{number} = $i;
			$hashref->{color} = $GLOBAL_OPTIONS->{"alt_color_$i"};
			push (@{ $subst->{'altcolor_array'} }, $hashref);
		}
		$subst->{'skin'} = get_skin_info($GLOBAL_OPTIONS->{skinchoice});
	}
	if ($FORMref->{menu} == 3 || $FORMref->{menu} == 4) {
		my $rows = $GLOBAL_OPTIONS->{'templ_box_rows'} ? $GLOBAL_OPTIONS->{'templ_box_rows'} : 20;
		my $cols = $GLOBAL_OPTIONS->{'templ_box_cols'} ? $GLOBAL_OPTIONS->{'templ_box_cols'} : 75;
		$subst->{'general'}->{'width'} = int((600/75)*$cols) + 75;
		$subst->{'general'}->{'height'} = int(16*$rows) + 200;
		my @skin = map { get_skin_info((split(/\./, $_, 2))[0]) } sort { $a cmp $b } @{directory_list("$DCONF->{admin_dir}/skins", '.tmpl$', 1)};
		$subst->{'skin'} = \@skin;
		$subst->{'skin2'} = \@skin;
	}
	if ($FORMref->{menu} == 4) {
		my @tmpl = ();
		push @tmpl, @{directory_list("$DCONF->{admin_dir}/template/custom", '.tmpl')};
		push @tmpl, @{directory_list("$DCONF->{admin_dir}/template/pro", '.tmpl')};
		push @tmpl, @{directory_list("$DCONF->{admin_dir}/template/ui", '.tmpl')};
		push @tmpl, @{directory_list("$DCONF->{admin_dir}/template/admin", '.tmpl')};
		push @tmpl, @{directory_list("$DCONF->{admin_dir}/template/mailmesg", '.tmpl')};
		my %u = undef;
		my @out = ();
		foreach my $file (@tmpl) {
			if ($file =~ m|/(\w+)/([\w\-]+)\.tmpl$|) {
				my ($path, $file) = ($1, $2);
				next if $u{$file};
				$u{$file} = 1;
				push @out, { dir => $path, file => $file, descr => get_template_info($path, $file) };
			}
		}
		@out = sort { my $j = $a->{dir} cmp $b->{dir}; return $j if $j; return $a->{file} cmp $b->{file} } @out;
		$subst->{templates} = \@out;
	}
	if ($FORMref->{menu} == 5) {
		$subst->{topics} = board_topics();
	}
	screen_out("appr-mgr", $subst);
}


###
### get_template_info
###
### Gets information from 'Description' comment in template file
###

sub get_template_info {
	my ($dir, $file) = @_;
	return "" if ! open(FILE, "$DCONF->{admin_dir}/template/$dir/$file.tmpl");
	while (<FILE>) {
		if (m|^\s*#\s*DESCRIPTION:\s*(.*)|) {
			close (FILE);
			return $1;
		}
	}
	close (FILE);
	return "";
}

###
### appear_select_skin
###
### Confirms user's selection of a skin
###

sub appear_select_skin {
	my ($FORMref) = @_;
	my $skinname = $FORMref->{skname};
	error_message("Skin Selection Error", "That skin name is invalid.  Skin file names must contain only alphanumeric characters.", 0, 1) if $skinname !~ m|^[\w\-\.]+$|;
	my $subst = {};
	$subst->{general}->{username} = $FORMref->{username};
	$subst->{skin} = get_skin_info($skinname);
	$subst->{opt} = $subst->{skin}->{opt};
	my @i = ();
	for (my $i = 1; $i <= 20; $i++) { ## ALT_COLOR_MAX
		my $u = {};
		$u->{number} = $i;
		$u->{used_for} = $subst->{skin}->{"_alt_color_$i"};
		$u->{value} = $subst->{opt}->{"alt_color_$i"};
		push @i, $u;
	}
	$subst->{altcols} = \@i;
	if (ref $subst->{skin}->{optf} eq 'HASH') {
		$subst->{skin}->{has_forced_options} = 1;
		my @k = ();
		foreach my $k (keys %{ $subst->{skin}->{optf} }) {
			push @k, { name => $k, value => $subst->{skin}->{optf}->{$k}->{value}, explanation => $subst->{skin}->{optf}->{$k}->{explanation} };
		}
		$subst->{forced} = \@k;
	}
	screen_out("appr-sel", $subst);
}

###
### appear_save_skin
###
### Saves the skin in the options file
###

sub appear_save_skin {
	my ($FORMref) = @_;
	my $skinname = $FORMref->{choice};
	error_message("Skin Selection Error", "That skin name is invalid.  Skin file names must contain only alphanumeric characters.", 0, 1) if $skinname !~ m|^[\w\-\.]+$|;
	dreq("adm-opts");
	my $new_opt = {};
	$new_opt->{skinchoice} = $skinname;
	my $o = get_skin_info($skinname);
	if ($FORMref->{colors}) {
		foreach my $t (keys(%{ $o->{opt} })) {
			$new_opt->{$t} = $o->{opt}->{$t};
		}
	}
	if ($FORMref->{fa} ne "") {
		my %u = map { $_, 1 } split(/,/, $FORMref->{fa});
		foreach my $key (keys %{ $o->{optf} }) {
			$new_opt->{$key} = $o->{optf}->{$key}->{value} if $u{$key};
		}
	}
	options_save($new_opt);
}


###
### appear_preview_skin
###
### Preview the appearance of a skin that is not as of yet applied
###

sub appear_preview_skin {
	my ($FORMref) = @_;
	my $sc = $FORMref->{'template'};
	$sc =~ s/[^\w\-]//g;
	$GLOBAL_OPTIONS->{'skinchoice'} = $sc;
	$GLOBAL_OPTIONS->{'tcache'} = 0;
	hash_merge($GLOBAL_OPTIONS, get_skin_info($sc)->{opt}, 1);
	my $s = undef;
	if ($FORMref->{page} =~ m|^(\d+)/(\d+)$|) {
		my $y = GetPage($1, $2);
		$y->{general}->{secure} = 0;
		$y->{general}->{subtopic_raw} = 0;
		$y->{general}->{messages_raw} = 0;
		$y->{sublist} = expand_sublist($y->{sublist}, $1, undef);
		if ($DCONF->{pro}) {
			dreq("authwrap-PRO");
			$y->{messages} = auth_wrap_attachments($y->{messages});
		}
		$s = SetPage($y, { return_val => 1 });
	} elsif ($DCONF->{pro} && $FORMref->{file} =~ m|^(\d+)/([\w\-\.]+)\.(\w+)$|) {
		dreq("authwrap-PRO");
		auth_wrapped_attachment($1, $2, $3, $FORMref->{content});
	} else {
		dreq("topic-pg");
		my $f = read_topic_page();
		$f->{return_val} = 1;
		$s = write_topic_page($f);
	}
	my $murl = quotemeta($DCONF->{message_url});
	my $cgi = quotemeta($DCONF->{cgi_extension});
	my $surl = quotemeta($DCONF->{script_url});
	my $btf = quotemeta($DCONF->{board_topics_file});
	my $ext = quotemeta($DCONF->{ext});
	my $bdauth = quotemeta($DCONF->{authorize_reader});

	$s =~ s%$murl/(\d+)/(\d+)\.$ext\??\d*%$DCONF->{script_url}/board-admin\.$DCONF->{cgi_extension}\?action=appear_prevskin&template=$FORMref->{template}&username=$FORMref->{username}&page=$1/$2%g;
	$s =~ s%$murl/$btf%$DCONF->{script_url}/board-admin\.$DCONF->{cgi_extension}\?action=appear_prevskin&template=$FORMref->{template}&username=$FORMref->{username}%g;
	$s =~ s%$bdauth\?file=/(\d+)/(\d+)\.$DCONF->{ext}(&lm=\d+)?%$DCONF->{script_url}/board-admin\.$DCONF->{cgi_extension}\?action=appear_prevskin&template=$FORMref->{template}&username=$FORMref->{username}&page=$1/$2%g;
	$s =~ s%$surl/discus\.$cgi\?pg=topics%$DCONF->{script_url}/board-admin\.$DCONF->{cgi_extension}\?action=appear_prevskin&template=$FORMref->{template}&username=$FORMref->{username}%g;
	$s =~ s%$surl/board-post\.$cgi\??([^"\s]*)%%g;

	header();
	print $s;
	program_exit(0);
}

###
### set_title_message
###
### Sets the "title message" on the topics page
###

sub set_title_message {
	my ($param_ref) = @_;
	$param_ref->{message} = "titlemsg";
	return set_topic_message($param_ref);
}

###
### set_main_message
###
### Sets the "main message" on the topics page
###

sub set_main_message {
	my ($param_ref) = @_;
	$param_ref->{message} = "mainmsg";
	return set_topic_message($param_ref);
}

###
### set_topic_message
###
### Sets either the title message or the main message
###

sub set_topic_message {
	my ($param_ref) = @_;
	return 0 if $param_ref->{'message'} eq "";
	dreq("topic-pg");
	my $arg = read_topic_page({ lock => 1});
	$arg->{ $param_ref->{message} }->{text} = $param_ref->{text};
	$arg->{ $param_ref->{message} }->{hr} = ( $param_ref->{hr} == 1 ? 1 : 0 );
	write_topic_page($arg, { unlock => 1 });
	return 1;
}

###
### get_skin_info
###
### Reads parameters from a skin
###

sub get_skin_info {
	my ($tfile) = @_;
	my $file = readfile("$DCONF->{admin_dir}/skins/$tfile.tmpl", "APPEAR_admin", { no_lock => 1, no_unlock => 1 } );
	my $file_as_string = join("", @{$file});
	if ($file_as_string =~ m%\n\s*<#part "info"#>\s+(.*?)\s*\n\s*<#/part#>%is) {
		my @split = split(/\n/, $1);
		my $output = {};
		foreach my $x (@split) {
			next if $x =~ m|^#|;
			next if $x !~ m|\S|;
			$x = $` if $x =~ m|##|;
			my ($name, $text) = split(/\s+/, $x, 2);
			$output->{$name} = $text;
			if ($name =~ m|^\*|) {
				$output->{opt}->{$'} = $text;
			} elsif ($name =~ m|^setoption_(\w+)|) {
				$output->{optf}->{$1}->{value} = trim($text);
			} elsif ($name =~ m|^explainoption_(\w+)|) {
				$output->{optf}->{$1}->{explanation} = trim($text);
			}
		}
		$output->{skinfile} = $tfile;
		$output->{has_color_scheme} = (defined $output->{opt} ? 1 : 0);
		return $output;
	} else {
		error_message("Error Reading Skin File", "Skin file [$tfile] is not valid, as it does not contain an information section.", 0, 1);
	}
}

1;
