###############################################################################
# System.pl                                                                   #
###############################################################################
# YaBB: Yet another Bulletin Board                                            #
# Open-Source Community Software for Webmasters                               #
# Version:        YaBB 2.2.3                                                  #
# Packaged:       July 12, 2008                                               #
# Distributed by: http://www.yabbforum.com                                    #
# =========================================================================== #
# Copyright (c) 2000-2008 YaBB (www.yabbforum.com) - All Rights Reserved.     #
# Software by:  The YaBB Development Team                                     #
#               with assistance from the YaBB community.                      #
# Sponsored by: Xnull Internet Media, Inc. - http://www.ximinc.com            #
#               Your source for web hosting, web design, and domains.         #
###############################################################################

$systemplver = 'YaBB 2.2.3 $Revision: 1.37.2.13 $';

sub BoardTotals {
	my ($job, @updateboards) = @_;
	my ($testboard, $line, @lines, $updateboard, @boardvars, $tag, $cnt);
	if (!@updateboards) { @updateboards = @allboards; }
	if ($job eq "convert") {
		fopen(FORUMTOTALS, ">>$boardsdir/forum.totals") || &fatal_error('cannot_open', "$boardsdir/forum.totals", 1);
		foreach $testboard (@allboards) {
			chomp $testboard;
			if (-e "$boardsdir/$testboard.ttl") {
				fopen(BOARDTTL, "$boardsdir/$testboard.ttl") || &fatal_error('cannot_open', "$boardsdir/$testboard.ttl", 1);
				$line = <BOARDTTL>;
				fclose(BOARDTTL);
				chomp $line;
				print FORUMTOTALS "$testboard|$line|\n";
				#unlink "$boardsdir/$testboard.ttl";
			}
		}
		fclose(FORUMTOTALS);
	}
	if (@updateboards) {
		if ($job eq "load") {
			fopen(FORUMTOTALS, "$boardsdir/forum.totals") || &fatal_error('cannot_open', "$boardsdir/forum.totals", 1);
			@lines = <FORUMTOTALS>;
			fclose(FORUMTOTALS);
			my @tags = qw(board threadcount messagecount lastposttime lastposter lastpostid lastreply lastsubject lasticon lasttopicstate lasttmp);
			foreach $line (@lines) {
				chomp $line;
				(@boardvars) = split(/\|/, $line);
				foreach $updateboard (@updateboards) {
					chomp $updateboard;
					if ($boardvars[0] eq $updateboard && exists($board{ $boardvars[0] })) {
						$loadedboards++;
						for ($cnt = 1; $cnt < $#tags; $cnt++) {
							${$uid.$updateboard}{ $tags[$cnt] } = $boardvars[$cnt];
						}
					}
				}
			}
		}
		if ($job eq "update") {
			fopen(FORUMTOTALS, "+<$boardsdir/forum.totals") || &fatal_error('cannot_open', "$boardsdir/forum.totals", 1);
			seek FORUMTOTALS, 0, 0;
			@lines = <FORUMTOTALS>;
			truncate FORUMTOTALS, 0;
			seek FORUMTOTALS, 0, 0;
			my @tags = qw(board threadcount messagecount lastposttime lastposter lastpostid lastreply lastsubject lasticon lasttopicstate lasttmp);
			print FORUMTOTALS "$updateboards[0]|";
			for ($cnt = 1; $cnt <= $#tags; $cnt++) {
				print FORUMTOTALS ${$uid.$updateboards[0]}{ $tags[$cnt] };
				if ($cnt < $#tags) { print FORUMTOTALS "|"; }
			}
			print FORUMTOTALS "\n";
			foreach $line (@lines) {
				chomp $line;
				(@boardvars) = split(/\|/, $line);
				if ($boardvars[0] ne $updateboards[0] && exists($board{ $boardvars[0] })) {
					print FORUMTOTALS "$line\n";
					$loadedboards++;
				}
			}
			fclose(FORUMTOTALS);
		}
		if ($job eq "delete") {
			fopen(FORUMTOTALS, "$boardsdir/forum.totals") || &fatal_error('cannot_open', "$boardsdir/forum.totasl", 1);
			seek FORUMTOTALS, 0, 0;
			@lines = <FORUMTOTALS>;
			truncate FORUMTOTALS, 0;
			seek FORUMTOTALS, 0, 0;
			foreach $line (@lines) {
				chomp $line;
				(@boardvars) = split(/\|/, $line);
				if ($boardvars[0] ne $updateboards[0] && exists($board{ $boardvars[0] })) {
					print FORUMTOTALS "$line\n";
					$loadedboards++;
				}
			}
			fclose(FORUMTOTALS);
		}
		if ($job eq "add") {
			fopen(FORUMTOTALS, "$boardsdir/forum.totals") || &fatal_error('cannot_open', "$boardsdir/forum.totals", 1);
			seek FORUMTOTALS, 0, 0;
			@lines = <FORUMTOTALS>;
			truncate FORUMTOTALS, 0;
			seek FORUMTOTALS, 0, 0;
			print FORUMTOTALS "$updateboards[0]|0|0|N/A|N/A||||\n";
			print FORUMTOTALS @lines;
			fclose(FORUMTOTALS);
		}
	}
}


sub BoardCountTotals {
	my $cntboard = $_[0];
	unless ($cntboard) { return undef; }
	my (@threads, $threadcount, $messagecount, $i, $threadline);

	fopen(BOARD, "$boardsdir/$cntboard.txt") || &fatal_error('cannot_open', "$boardsdir/$cntboard.txt", 1);
	@threads = <BOARD>;
	fclose(BOARD);
	$threadcount  = @threads;
	$messagecount = $threadcount;
	if ($threadcount) {
		for ($i = 0; $i < @threads; $i++) {
			@threadline = split(/\|/, $threads[$i]);
			$messagecount += $threadline[5];
		}
	}
	&BoardTotals("load", $cntboard);
	${$uid.$cntboard}{'threadcount'}  = $threadcount;
	${$uid.$cntboard}{'messagecount'} = $messagecount;
	&BoardTotals("update", $cntboard);
	&BoardSetLastInfo($cntboard);
}

sub BoardSetLastInfo {
	my $setboard = $_[0];
	unless ($setboard) { return undef; }
	my ($lastthread, $lastthreadid, @lastthreadmessages, @lastmessage, $lastmessage);

	fopen(BOARD, "$boardsdir/$setboard.txt") || &fatal_error('cannot_open', "$boardsdir/$setboard.txt", 1);
	$lastthread = <BOARD>;
	fclose(BOARD);
	chomp $lastthread;
	if ($lastthread) {
		$lastthreadid = (split /\|/, $lastthread)[0];
		$lastthreadstate = (split /\|/, $lastthread)[8];
		fopen(FILE, "$datadir/$lastthreadid.txt") || &fatal_error("cannot_open","$datadir/$lastthreadid.txt", 1);
		@lastthreadmessages = <FILE>;
		fclose(FILE);
		@lastmessage = split(/\|/, $lastthreadmessages[$#lastthreadmessages]);
		&MessageTotals("load", $lastthreadid);
	}
	&BoardTotals("load", $setboard);
	${$uid.$setboard}{'lastposttime'} = $lastthread ? $lastmessage[3]                : 'N/A';
	${$uid.$setboard}{'lastposter'}   = $lastthread ? ${$lastthreadid}{'lastposter'} : 'N/A';
	${$uid.$setboard}{'lastpostid'}   = $lastthread ? $lastthreadid                  : '';
	${$uid.$setboard}{'lastreply'}    = $lastthread ? ${$lastthreadid}{'replies'}    : '';
	${$uid.$setboard}{'lastsubject'}  = $lastthread ? $lastmessage[0]                : '';
	${$uid.$setboard}{'lasttopicstate'} = $lastthread ? $lastthreadstate : 0;
	${$uid.$setboard}{'lasticon'}     = $lastthread ? $lastmessage[5]                : '';
	&BoardTotals("update", $setboard);
}

#### THREAD MANAGEMENT ####

sub MessageTotals {
	# usage: &MessageTotals("task",<threadid>)
	# tasks: load, update, recover
	($job, $updatethread) = @_;
	my @tag = qw(board replies views lastposter lastpostdate threadstatus repliers);
	my ($line, $views, $dummy, @msgs, @ctb, $data, $cnt);
	chomp $updatethread;
	if ($job eq "convert") {
		opendir(MSGDIR, $datadir);
		@msglist = grep { /\.data$/ } readdir(MSGDIR);
		closedir(MSGDIR);
		foreach $line (@msglist) {
			chomp $file;
			$line = substr($line, 0, length($file) - 5);
			push(@msgs, $line);
		}
		foreach $line (@msgs) {
			chomp $line;
			fopen(CTB, "$datadir/$line.ctb") || &fatal_error('cannot_open', "$datadir/$line.ctb", 1);
			@ctb = <CTB>;
			fclose(CTB);
			fopen(DATA, "$datadir/$line.data") || &fatal_error('cannot_open', "$datadir/$line.data", 1);
			$data = <DATA>;
			fclose(DATA);
			($views, $lastposter) = split(/\|/, $data);
			chomp $lastposter;
			${$line}{'board'} 		= $ctb[0];
			${$line}{'replies'} 		= $ctb[1];
			${$line}{'views'} 		= $views;
			${$line}{'lastposter'} 		= $lastposter;
			${$line}{'lastpostdate'} 	= $lastpostdate;
			${$line}{'threadstatus'} 	= 0;
			${$line}{'repliers'} 		= "";
			&update_ctb($line);
			unlink "$datadir/$line.data";
		}
	}
	if ($updatethread ne "") {
		if ($job eq "update") {
			if (${$updatethread}{'board'} eq ""){ &MessageTotals("load",$updatethread); }	## load if the variable is not already filled
			${$updatethread}{'repliers'} = join(",", @repliers);
			&update_ctb;
		}
		if ($job eq "load") {
			if (${$updatethread}{'board'} ne ""){ return; }	## skip load if the variable is already filled
			fopen(CTB, "$datadir/$updatethread.ctb",1);
			@ctb = <CTB>;
			fclose(CTB);
			## Determine if the format is old or new ##
			if ($ctb[0] =~ /###/) {						## load new format ##
				foreach $ctbline (@ctb) {
					chomp $ctbline;
					unless (length($ctbline) == 0 || $ctbline =~ /###/) {
						$ctbline =~ s/\'(.*?)\'\,\"(.*?)\"//ig;
						my $tag   = $1;
						my $value = $2;
						${$updatethread}{$tag} = $value;
					}
				}
				@repliers = ();
				@repliers = split(",", ${$updatethread}{'repliers'});
			} else {							## load old format and convert to new ##
				for ($cnt = 0; $cnt <= $#tag; $cnt++) {
					chomp $ctb[$cnt];
					# Remove Win linebreaks in case we've moved from win->*nix servers
					$ctb[$cnt] =~ s~\r~~g;
					${$updatethread}{ $tag[$cnt] } = $ctb[$cnt];
				}
				$repstart = $#tag + 1;
				$reps     = 0;
				@repliers = ();
				for ($repcnt = $repstart; $repcnt < @ctb; $repcnt++) {
					chomp $ctb[$repcnt];
					$repliers[$reps] = $ctb[$repcnt];
					$reps++;
				}
				${$updatethread}{'repliers'} = join(",", @repliers);
				&update_ctb;
			}
		}
		if ($job eq "incview") {
			${$updatethread}{'repliers'} = join(",", @repliers);
			${$updatethread}{'views'}++;
			&update_ctb;
		}
		if ($job eq "incpost") {
			${$updatethread}{'repliers'} = join(",", @repliers);
			${$updatethread}{'replies'}++;
			&update_ctb;
		}
		if ($job eq "decpost") {
			${$updatethread}{'repliers'} = join(",", @repliers);
			${$updatethread}{'replies'}--;
			&update_ctb;
		}
		if ($job eq 'recover') {
			# storing thread status
			my $threadstatus;
			my $openboard = ${$updatethread}{'board'};
			fopen(TESTBOARD, "$boardsdir/$openboard.txt") || &fatal_error('cannot_open', "$openboard/$openboard.txt", 1);
			while ($ThreadLine = <TESTBOARD>) {
				chomp $ThreadLine;
				if ($updatethread == (split /\|/, $ThreadLine)[0]) { $threadstatus =( split /\|/, $ThreadLine)[8]; }
			}
			fclose(TESTBOARD);
			# storing thread other info
			fopen(MSG, "$datadir/$updatethread.txt") || &fatal_error('cannot_open', "$datadir/$updatethread.txt", 1);
			my @threaddata = <MSG>;
			fclose(MSG);
			my (@lastinfo) = split(/\|/, $threaddata[$#threaddata]);
			my $lastpostdate = sprintf("%010d", $lastinfo[3]);
			my $lastposter = $lastinfo[4] eq 'Guest' ? qq~Guest-$lastinfo[1]~ : $lastinfo[4];
			# rewrite/create a correct thread.ctb
			${$updatethread}{'replies'} = $#threaddata;
			${$updatethread}{'views'} = ${$updatethread}{'views'} || 0;
			${$updatethread}{'lastposter'} = $lastposter;
			${$updatethread}{'lastpostdate'} = $lastpostdate;
			${$updatethread}{'threadstatus'} = $threadstatus || ${$updatethread}{'threadstatus'} || 0;
			${$updatethread}{'repliers'} = '';
			&update_ctb;
		}
	}
	sub update_ctb {
		my $format = 'SDT, DD MM YYYY HH:mm:ss zzz'; # The format
		# Save their old format
		my $timeformat = ${$uid.$username}{'timeformat'};
		my $timeselect = ${$uid.$username}{'timeselect'};
		# Override their settings
		${$uid.$username}{'timeformat'} = $format;
		${$uid.$username}{'timeselect'} = 7;
		# Do the work
		my $newtime = &timeformat($date, 1,"rfc");
		# And restore their settings
		${$uid.$username}{'timeformat'} = $timeformat;
		${$uid.$username}{'timeselect'} = $timeselect;

		## trap writing false ctb files on forged num= actions ##
		if (!-e "$datadir/$updatethread.txt"){ return; }

		fopen(UPDATE_CTB, ">$datadir/$updatethread.ctb",1) || &fatal_error('cannot_open', "$datadir/$updatethread.ctb", 1);
		print UPDATE_CTB qq~### ThreadID: $updatethread, LastModified: $newtime ###\n\n~;
		for (my $cnt = 0; $cnt < @tag; $cnt++) {
			print UPDATE_CTB "\'$tag[$cnt]\'\,\"${$updatethread}{$tag[$cnt]}\"\n";
		}
		fclose(UPDATE_CTB);
	}
}


sub ctb_check {
	my @check = @_;
	my @testtag = qw(board replies views lastposter lastpostdate threadstatus);
	foreach (@testtag){
		chomp $_;
		if ($ctb[$_] eq ""){&fatal_error("error_occurred","Houston...we have a server problem !! We lost essential variables again so updating CTB is aborted !!"); }
	}
}

# NOBODY expects the Spanish Inquisition!
# - Monty Python

#### USER AND MEMBERSHIP MANAGEMENT ####

sub UserAccount {
	
	my ($user, $action, $pars) = @_;
	@labels = split(/\+/, $pars);
	if (!${$uid.$user}{'password'}) { return 0; }
	if (!${$uid.$user}{'timeformat'}) { ${$uid.$user}{'timeformat'} = qq~MM D+ YYYY @ HH:mm:ss*~; }
	if ($action eq "update") {
		foreach $label (@labels) {
			chomp $label;
			${$uid.$user}{$label} = $date;
		}
		$userext = "vars";
	}
	if ($action eq "approve") {
		$userext = "wait";
	}
	if ($action eq "preregister") {
		$userext = "pre";
	}
	if ($action eq "register") {
		$userext = "vars";
	}
	if ($action eq "delete") {
		if (-e "$memberdir/$user.vars") { unlink "$memberdir/$user.vars"; }
		return 0;
	}
	if (!$userext) { $userext = "vars"; }

	# using sequential tag writing as hashes do not sort the way we like them to
	my @tags = qw(password realname email regdate regreason webtitle weburl spamcount spamtime signature postcount position addgroups icq aim yim skype gender usertext userpic regtime location bday timeselect timeoffset timeformat hidemail msn gtalk template language lastonline lastpost lastim im_ignorelist im_notify im_popup im_imspop cathide postlayout session sesquest sesanswer favorites dsttimeoffset pageindex lastips onlinealert pmactprev pmmessprev pmviewMess buddylist offlinestatus awaysubj awayreply awayreplysent stealth);
	fopen(UPDATEUSER, ">$memberdir/$user.$userext",1) || &fatal_error('cannot_open', "$memberdir/$user.$userext", 1);
	print UPDATEUSER "### User variables for ID: $user ###\n\n";
	for (my $cnt = 0; $cnt < @tags; $cnt++) {
		print UPDATEUSER "\'$tags[$cnt]\'\,\"${$uid.$user}{$tags[$cnt]}\"\n";
	}
	fclose(UPDATEUSER);
	if (-e "$memberdir/$user.dat") { unlink "$memberdir/$user.dat"; }
}

## just load variable needed for user
sub UserCheck {
	# usage:  &UserCheck($var1+$var2+$var3) returns $usercheck{$tag}
	my ($user, $pars) = @_;
	%usercheck = "";
	$found     = 0;
	$pars =~ s/ //g;

	# Convert user to new file if dat file still exists
	if (-e "$memberdir/$user.dat") {
		&LoadUser($user);
		&UserAccount($user, "update");
	}
	@labels = split(/\+/, $pars);
	if (!-e "$memberdir/$user.vars") { return; }
	fopen(CHECKUSER, "$memberdir/$user.vars") || &fatal_error('cannot_open', "$memberdir/$user.vars", 1);
	my @settings = <CHECKUSER>;
	fclose(CHECKUSER);
	foreach $label (@labels) {
		chomp $label;
		foreach my $setting (@settings) {
			chomp $setting;
			$setting =~ m/\'(.+?)\'\,\"(.+?)\"/;
			my $tag   = $1;
			my $value = $2;
			if ($label eq $tag) { $usercheck{$tag} = $value; last; }
			if ($label eq "all") { $usercheck{$tag} = $value; }
		}
	}
}

sub MemberIndex {
	my ($memaction, $user) = @_;
	if ($memaction eq "add" && -e "$memberdir/$user.vars") {
		&UserCheck($user, "realname+email+regdate+position+postcount");
		$theregdate = &stringtotime($usercheck{'regdate'});
		$theregdate = sprintf("%010d", $theregdate);
		if (!$usercheck{'postcount'}) { $usercheck{'postcount'} = 0; }
		if (!$usercheck{'position'})  { $usercheck{'position'}  = &MemberPostGroup($usercheck{'postcount'}); }
		&ManageMemberlist("add", $user, $theregdate);
		&ManageMemberinfo("add", $user, $usercheck{'realname'}, $usercheck{'email'}, $usercheck{'position'}, $usercheck{'postcount'});

		fopen(TTL, "$memberdir/members.ttl") || &fatal_error('cannot_open', "$memberdir/members.ttl", 1);
		$buffer = <TTL>;
		fclose(TTL);

		($membershiptotal, undef) = split(/\|/, $buffer);
		$membershiptotal++;

		fopen(TTL, ">$memberdir/members.ttl") || &fatal_error('cannot_open', "$memberdir/members.ttl", 1);
		print TTL qq~$membershiptotal|$user~;
		fclose(TTL);
		return 0;

	} elsif ($memaction eq "remove" && $user) {
		&ManageMemberlist("delete", $user);
		&ManageMemberinfo("delete", $user);

		require "$sourcedir/Notify.pl";
		&removeNotifications($user);

		fopen(MEMLIST, "$memberdir/memberlist.txt") || &fatal_error('cannot_open', "$memberdir/memberlist.txt", 1);
		@memberlt = <MEMLIST>;
		fclose(MEMLIST);

		my $membershiptotal = @memberlt;
		my ($lastuser, undef) = split(/\t/, $memberlt[$#memberlt], 2);

		fopen(TTL, ">$memberdir/members.ttl") || &fatal_error('cannot_open', "$memberdir/members.ttl", 1);
		print TTL qq~$membershiptotal|$lastuser~;
		fclose(TTL);
		return 0;

	} elsif ($memaction eq "check_exist" && $user) {
		&ManageMemberinfo("load");
		while (($curmemb, $value) = each(%memberinf)) {
			($curname, $curmail, $curposition, $curpostcnt) = split(/\|/, $value);
			if    (lc $user eq lc $curmemb) { undef %memberinf; return $curmemb; }
			elsif (lc $user eq lc $curmail) { undef %memberinf; return $curmail; }
			elsif (lc $user eq lc $curname) { undef %memberinf; return $curname; }
		}

	} elsif ($memaction eq "who_is" && $user) {
		&ManageMemberinfo("load");
		while (($curmemb, $value) = each(%memberinf)) {
			($curname, $curmail, $curposition, $curpostcnt) = split(/\|/, $value);
			if    (lc $user eq lc $curmail) { undef %memberinf; return $curmemb; }
			elsif (lc $user eq lc $curname) { undef %memberinf; return $curmemb; }
		}
	}
	# if ($memaction eq "rebuild") { ... Deleted! Don't rebuild
	# member list here, or you run into browser/server timeout
	# with xx-large forums!!! Use Admin.pl -> sub RebuildMemList instead!
}

sub MemberPostGroup {
	$userpostcnt = $_[0];
	$grtitle     = "";
	foreach $postamount (sort { $b <=> $a } keys %Post) {
		if ($userpostcnt >= $postamount) {
			($grtitle, undef) = split(/\|/, $Post{$postamount}, 2);
			last;
		}
	}
	return $grtitle;
}

sub MembershipCountTotal {
	fopen(MEMBERLISTREAD, "$memberdir/memberlist.txt") || &fatal_error('cannot_open', "$memberdir/memberlist.txt", 1);
	my @num = <MEMBERLISTREAD>;
	fclose(MEMBERLISTREAD);
	($latestmember, $meminfo) = split(/\t/, $num[$#num]);
	my $membertotal = @num;
	undef @num;

	fopen(MEMTTL, ">$memberdir/members.ttl") || &fatal_error('cannot_open', "$memberdir/members.ttl", 1);
	print MEMTTL qq~$membertotal|$latestmember~;
	fclose(MEMTTL);

	if (wantarray()) {
		&ManageMemberinfo("load");
		($latestrealname, undef) = split(/\|/, $memberinf{$latestmember}, 2);
		undef %memberinf;
		return ($membertotal, $latestmember, $latestrealname);
	} else {
		return $membertotal;
	}
}

sub RegApprovalCheck {
	## alert admins and gmods of waiting users for approval
	if ($regtype == 1 && ($iamadmin || ($iamgmod && $allow_gmod_admin eq "on" && $gmod_access{'view_reglog'} eq "on"))) {
		opendir(MEM,"$memberdir"); 
		my @approval = (grep /.wait$/i, readdir(MEM));
		closedir(MEM);
		my $app_waiting = $#approval+1;
		if ($app_waiting == 1) {
			$yyadmin_alert = qq~<div class="editbg">$reg_txt{'admin_alert_start_one'} $app_waiting $reg_txt{'admin_alert_one'} <a href="$boardurl/AdminIndex.$yyaext?action=view_reglog">$reg_txt{'admin_alert_end'}</a></div>~;
		} elsif ($app_waiting > 1) {
			$yyadmin_alert = qq~<div class="editbg">$reg_txt{'admin_alert_start_more'} $app_waiting $reg_txt{'admin_alert_more'} <a href="$boardurl/AdminIndex.$yyaext?action=view_reglog">$reg_txt{'admin_alert_end_more'}</a></div>~;
		}
	}
	## alert admins and gmods of waiting users for validations
	if (($regtype == 1 || $regtype == 2) && ($iamadmin || ($iamgmod && $allow_gmod_admin eq "on" && $gmod_access{'view_reglog'} eq "on"))) {
		opendir(MEM,"$memberdir"); 
		my @preregged = (grep /.pre$/i, readdir(MEM));
		closedir(MEM);
		my $preregged_waiting = $#preregged+1;
		if ($preregged_waiting == 1) {
			$yyadmin_alert .= qq~<div class="editbg">$reg_txt{'admin_alert_start_one'} $preregged_waiting $reg_txt{'admin_alert_act_one'} <a href="$boardurl/AdminIndex.$yyaext?action=view_reglog">$reg_txt{'admin_alert_act_end'}</a></div>~;
		} elsif ($preregged_waiting > 1) {
			$yyadmin_alert .= qq~<div class="editbg">$reg_txt{'admin_alert_start_more'} $preregged_waiting $reg_txt{'admin_alert_act_more'} <a href="$boardurl/AdminIndex.$yyaext?action=view_reglog">$reg_txt{'admin_alert_act_end_more'}</a></div>~;
		}
	}
}

#### TEMPLATE MANAGEMENT ####

sub UpdateTemplates {
	my ($tempelement, $tempjob) = @_;
	unless ($templatesloaded == 1) {
		require "$vardir/template.cfg";
	}
	if ($tempjob eq "new") {
		require "$templatesdir/$tempelement/$tempelement.cfg";
		if ($template_name !~ m^\A[0-9a-zA-Z_\´\ \.\#\%\-\:\+\?\$\&\~\.\,\@/]+\Z^ || $template_name eq "") { $template_name = "Invalid Name in $tempelement.cfg"; }
		$testname = $template_name;
		$i        = 1;
		while (($curtemplate, $value) = each(%templateset)) {
			if (lc $curtemplate eq lc $testname) {
				$testname = qq~$template_name ($i)~;
				$i++;
			}
		}
		if ($template_css) { $templateset{"$testname"} = "$tempelement"; }
		else { $templateset{"$testname"} = "default"; }
		if ($template_images) { $templateset{"$testname"} .= "|$tempelement"; }
		else { $templateset{"$testname"} .= "|default"; }
		if ($template_head) { $templateset{"$testname"} .= "|$tempelement"; }
		else { $templateset{"$testname"} .= "|default"; }
		if ($template_board) { $templateset{"$testname"} .= "|$tempelement"; }
		else { $templateset{"$testname"} .= "|default"; }
		if ($template_message) { $templateset{"$testname"} .= "|$tempelement"; }
		else { $templateset{"$testname"} .= "|default"; }
		if ($template_display) { $templateset{"$testname"} .= "|$tempelement"; }
		else { $templateset{"$testname"} .= "|default"; }
		if ($template_mycenter) { $templateset{"$testname"} .= "|$tempelement"; }
		else { $templateset{"$testname"} .= "|default"; }
		if ($template_menutype) { $templateset{"$testname"} .= "|$tempelement"; }
		else { $templateset{"$testname"} .= "|"; }
		fopen(UPDATETEMPLATE, ">$vardir/template.cfg") || &fatal_error('cannot_open', "$vardir/template.cfg", 1);
		print UPDATETEMPLATE "\$templatesloaded = 1;\n";

		while (($key, $value) = each(%templateset)) {
			print UPDATETEMPLATE qq~\$templateset{'$key'} = "$value";\n~;
		}
		fclose(UPDATETEMPLATE);
		unlink "$templatesdir/$tempelement/$tempelement.cfg";
	}
	if ($tempjob eq "save") {
		$template_name = $tempelement;
		$templateset{"$template_name"} = "$template_css";
		$templateset{"$template_name"} .= "|$template_images";
		$templateset{"$template_name"} .= "|$template_head";
		$templateset{"$template_name"} .= "|$template_board";
		$templateset{"$template_name"} .= "|$template_message";
		$templateset{"$template_name"} .= "|$template_display";
		$templateset{"$template_name"} .= "|$template_mycenter";
		$templateset{"$template_name"} .= "|$template_menutype";
		fopen(UPDATETEMPLATE, ">$vardir/template.cfg") || &fatal_error('cannot_open', "$vardir/template.cfg", 1);
		print UPDATETEMPLATE "\$templatesloaded = 1;\n";

		while (($key, $value) = each(%templateset)) {
			print UPDATETEMPLATE qq~\$templateset{'$key'} = "$value";\n~;
		}
		fclose(UPDATETEMPLATE);
	}
	if ($tempjob eq "delete") {
		fopen(UPDATETEMPLATE, ">$vardir/template.cfg") || &fatal_error('cannot_open', "$vardir/template.cfg", 1);
		print UPDATETEMPLATE "\$templatesloaded = 1;\n";
		while (($key, $value) = each(%templateset)) {
			if ($key ne $tempelement) { print UPDATETEMPLATE qq~\$templateset{'$key'} = "$value";\n~; }
		}
		fclose(UPDATETEMPLATE);
	}
	$templatesloaded = 0;
}

sub CheckNewTemplates {
	opendir(TMPLDIR, "$templatesdir");
	@configs = readdir(TMPLDIR);
	closedir(TMPLDIR);
	foreach $file (@configs) {
		if (-e "$templatesdir/$file/$file.cfg") {
			&UpdateTemplates($file, "new");
		}
	}
}

sub MakeStealthURL {
	# Usage is simple - just call MakeStealthURL with any url, and it will stealthify it.
	# if stealth urls are turned off, it just gives you the same value back
	$theurl = $_[0];

	if ($stealthurl) {
		$theurl =~ s~([^\w\"\=\[\]]|[\n\b]|\A)\\*(\w+://[\w\~\.\;\:\,\$\-\+\!\*\?/\=\&\@\#\%]+\.[\w\~\;\:\$\-\+\!\*\?/\=\&\@\#\%]+[\w\~\;\:\$\-\+\!\*\?/\=\&\@\#\%])~$boardurl/YaBB.$yyext?action=dereferer;url=$2~isg;
		$theurl =~ s~([^\"\=\[\]/\:\.(\://\w+)]|[\n\b]|\A)\\*(www\.[^\.][\w\~\.\;\:\,\$\-\+\!\*\?/\=\&\@\#\%]+\.[\w\~\;\:\$\-\+\!\*\?/\=\&\@\#\%]+[\w\~\;\:\$\-\+\!\*\?/\=\&\@\#\%])~$boardurl/YaBB.$yyext?action=dereferer;url=http://$2~isg;
	}

	return $theurl;
}

sub arraysort {
	# usage: &arraysort(1,"|","R",@array_to_sort);

	my ($sortfield, $delimiter, $reverse, @in) = @_;
	my (@sk, @out, @sortkey, %newline, $oldline, $n);
	foreach $oldline (@in) {
		@sk = split(/$delimiter/, $oldline);
		$sk[$sortfield] = "$sk[$sortfield]-$n";    ## make sure that identical keys are avoided ##
		$n++;
		$newline{ $sk[$sortfield] } = $oldline;
	}
	@sortkey = sort keys %newline;
	if ($reverse) {
		@sortkey = reverse @sortkey;
	}
	foreach (@sortkey) {
		push(@out, $newline{$_});
	}
	return @out;
}

sub keygen {
	## length = output length, type = A (All), U (Uppercase), L (lowercase) ##
	my ($length, $type) = @_;
	if ($length <= 0 || $length > 10000 || !$length) { return; }
	$type = uc($type);
	if ($type ne "A" && $type ne "U" && $type ne "L") { $type = "A"; }

	# generate random ID for password reset or other purposes.
	@chararray = qw(0 1 2 3 4 5 6 7 8 9 a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z);
	my $randid;
	for (my $i; $i < $length; $i++) {
		$randid .= $chararray[int(rand(61))];
	}
	if ($type eq "U") { return uc $randid; } 
	elsif ($type eq "L") { return lc $randid; }
	else { return $randid; }
}

1;