# FILE: posting.pl
# DESCRIPTION: Posting of Messages and uploads of images
#-------------------------------------------------------------------------------
# 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);

###
### posting_control
###
### Controls posting, "Create New Conversation" functionality, previews,
### message queue, profanity checking, etc.
###

sub posting_control {
	my ($forceform, $FORMref, $cookie_str) = @_;
	$FORMref = parse_form($ENV{'QUERY_STRING'}, $ENV{'CONTENT_LENGTH'}, $forceform) if ! defined $FORMref;
	dreq("template");
	if ($FORMref->{action} eq "posting_upload_form" && $DCONF->{pro}) {
		dreq("upload-PRO"); upload_pro_control($FORMref);
	}
	post_upload_control($FORMref) if $FORMref->{action} eq "imageupload";
	my $topic = 0; my $page = 0;
	if ($FORMref->{HTTP_REFERER} =~ m|(\d+)/(\d+)|) {
		$topic = $1; $page = $2;
	} elsif ($FORMref->{topic} =~ m|^(\d+)$| && $FORMref->{page} =~ m|^(\d+)$|) {
		$topic = $FORMref->{topic}; $page = $FORMref->{page};
	} else {
		my @k = map { join("", $_, "=&gt;&#39;", form_escape($FORMref->{$_}), "&#39;") } keys %{$FORMref};
		my $k = join("\n", @k);
		error_message("Invalid Referer Error", "<p>Form Referer Not Found.  Found form variables were:</p><pre style=\"font: 10pt courier new\">$k</pre><p>This indicates a problem with the board skin. Please report this to the board administrator.</p>", 0, 1);
	}
	if ($FORMref->{action} eq "form") { ## Create New Conversation
		undef my $subst;
		my $get_page = GetPage($topic, $page);
		if ($get_page->{head}->{param} !~ /Create/) {
			error_message(read_language()->{BPCREATEERROR}, read_language()->{BP_CREATE_NOT_SUPPORTED_HERE}, 0, 1);
		}
		$subst->{head} = $get_page->{head};
		$subst->{levels} = $subst->{head}->{levels};
		$subst->{general}->{create_new_conversation} = 1;
		$subst->{addmessage} = determine_addmessage($topic);
		$subst->{general}->{secure} = -e "$DCONF->{admin_dir}/secure/$topic";
		screen_out("*$get_page->{head}->{topic_number}newconv", $subst);
	}
	if ($FORMref->{action} eq "") { ## Preview or post
		return post_incoming_handler($FORMref, { topic => $topic, page => $page }, $cookie_str);
	}
	error_message("Fell through posting control");
}

###
### post_upload_control
###
### Controls image/attachment uploading features
###

sub post_upload_control {
	my ($FORMref, $dir_override) = @_;
	dreq("fcn-mtp");
	multipart_post_upload_control($FORMref, $dir_override);
}

###
### post_check_message_size
###
### Checks both maximum and minimum message size
###

sub post_check_message_size {
	my ($self, $vpr) = @_;
	return $self if $vpr->{type}->{is_superuser};
	return $self if $self->{preview}->{error};
	my $max = -1;
	$max = $GLOBAL_OPTIONS->{public_msgsize} if ($vpr->{public} || $vpr->{special});
	$max = $GLOBAL_OPTIONS->{registered_msgsize} if $vpr->{user}->{account};
	$max = $GLOBAL_OPTIONS->{registered_mod_msgsize} if $vpr->{moderator}->{account};
	if ($max != 0) {
		if ($max == -1 || length($self->{message}->{text}) > (1000*$max)) {
			my $l = read_language()->{BP_MESSAGE_EXCEEDED_MAXLENGTH};
			my $s = $max == -1 ? 0 : $max;
			my $m = int(length($self->{message}->{text})/100)/10;
			$l =~ s/\%maxsize/$s/g;
			$l =~ s/\%yoursize/$m/g;
			$self->{preview}->{errormsg} = $l;
			$self->{preview}->{error} = 1;
			return $self;
		}
	}
	my $min = 0;
	$min = $GLOBAL_OPTIONS->{public_min_msgsize} if ($vpr->{public} || $vpr->{special});
	$min = $GLOBAL_OPTIONS->{registered_min_msgsize} if $vpr->{user}->{account};
	$min = $GLOBAL_OPTIONS->{registered_mod_min_msgsize} if $vpr->{moderator}->{account};
	return $self if $min == 0;
	my $k = $self->{message}->{text};
	$k =~ s/<.*?>//g;
	my @w = split(/\s+/, $k);
	@w = grep(length($_) >= 2, @w);
	my $w = scalar(@w);
	if ($w < $min) {
		my $l = read_language()->{BP_MESSAGE_TOO_SHORT};
		$l =~ s/\%wordcount/$min/g;
		$l =~ s/\%yourcount/$w/g;
		$self->{preview}->{errormsg} = $l;
		$self->{preview}->{error} = 1;
		return $self;
	}
}

###
### post_profanity_error
###
### Displays error screen if profanity is detected in a post
###

sub post_profanity_error {
	my ($message, $argument, $words) = @_;
	my $subst = $argument->{subst};
	my $privcache = $argument->{privcache};
	my $error_message = read_language()->{PROFANITY_DETECTED_MESSAGE};
	my $l = join("<li>", sort keys(%{ $words })); $l = "<li>$l";
	$error_message =~ s/\%wordlist/$l/g;
	$subst->{preview}->{errormsg} = $error_message;
	post_there_was_an_error($subst, $privcache);
}

###
### post_incoming_handler
###
### Handles an incoming post, processing input and checking everything.  If it
### looks good, it's passed to "post_message" to be posted.
###

sub post_incoming_handler {
	my ($FORMref, $param, $cookie_str) = @_;
	dreq("webtags", "fcn-usrp", "profane");
	my $subst = {};
	bless $subst;
	$subst->{topic} = $param->{topic};
	my ($am, $privcache) = determine_addmessage($param->{topic}, undef, undef, 1);
	my $get_page = GetPage($param->{topic}, $param->{page});
	$subst->{addmessage} = $am;
	$subst->{head} = $get_page->{head};
	$subst->{levels} = $subst->{head}->{levels};
	$subst->{preview}->{new_conversation} = 1 if $FORMref->{'new_conversation'} == 1;
	$subst->{general}->{create_new_conversation} = $subst->{preview}->{new_conversation};
	$subst->{preview}->{username} = $FORMref->{username};
	$subst->{preview}->{password} = $FORMref->{passwd};
	$subst->{preview}->{email} = $FORMref->{email};
	$subst->{preview}->{html} = 0 + $FORMref->{html} if defined $FORMref->{html_a};
	$subst->{preview}->{active_links} = 0 + $FORMref->{active_links} if defined $FORMref->{active_links_a};
	foreach my $fk (keys(%{ $FORMref })) {
		if ($fk =~ m|^field_|) {
			$subst->{preview}->{$fk} = $FORMref->{$fk};
		}
	}
	my ($vpr, $usr, $mod, $susp) = check_postread_priv( { no_suspend => 1, type => 'p', topic => $param->{topic}, username => $FORMref->{username}, password => $FORMref->{passwd}, cookie => $FORMref->{COOKIE} }, $privcache, 1);
	if ($DCONF->{pro} && defined $vpr->{profile_hash} && $vpr->{profile_hash}->{user} ne "") {
		my $prefstr = join("", grep { length($_) == 1 } keys %{$vpr->{profile_hash}->{enhanced}->{pref}});
		if ($prefstr =~ /[ab]/i) {
			dreq("authwrap-PRO");
			$cookie_str .= create_user_access_cookies($FORMref, {}, [$vpr->{profile_hash}], $prefstr);
		}
	}
	if ($susp && $DCONF->{pro}) {
		dreq("suspend-PRO");
		my $L = suspended_account_preserve_post($FORMref);
		suspended_account($vpr->{profile_hash}, $FORMref->{password}, 0, $L);
	}
	if ($GLOBAL_OPTIONS->{maintenance}) {
		maintenance_mode_error() if ! $vpr->{type}->{is_superuser};
	}
	if (! $GLOBAL_OPTIONS->{posting} && ! $vpr->{type}->{is_superuser}) {
		my $x = read_language()->{BPPOSTINGDISABLEDDESCR};
		$x =~ s|%aopen|<a href="$DCONF->{script_url}/board-contact.$DCONF->{cgi_extension}">|gi;
		$x =~ s|%aclose|</a>|gi;
		error_message(read_language()->{BPPOSTINGDISABLEDTITLE}, $x, 0, 1);
	}
	if ($FORMref->{ADMIN_SCREEN} && ! $vpr->{moderator}->{account}) {
		return error_message(read_language()->{BPADDMSGERROR}, read_language()->{BP_ADD_NOT_SUPPORTED_HERE}, 0, 1);
	}
	if (! $subst->{general}->{create_new_conversation} && $get_page->{head}->{param} !~ /Add/ && ! $FORMref->{ADMIN_SCREEN}) {
		return error_message(read_language()->{BPADDMSGERROR}, read_language()->{BP_ADD_NOT_SUPPORTED_HERE}, 0, 1);
	}
	if ($FORMref->{ADMIN_SCREEN}) {
		$vpr->{profile_hash}->{enhanced}->{pref}->{d} = 0;
		$subst->{general}->{admin_screen} = 1;
	}
	return post_authentication_error($subst, $FORMref, $privcache, $param) if $vpr->{authorized} == 0;
	return post_authentication_error($subst, $FORMref, $privcache, $param) if ($FORMref->{passwd} ne "" && ($vpr->{special} + $vpr->{user}->{account} + $vpr->{moderator}->{account} == 0));
	if (! $vpr->{user}->{account} && ! $vpr->{moderator}->{account} && $GLOBAL_OPTIONS->{public_name_limit} && $GLOBAL_OPTIONS->{public_name_limit_val} > 0) {
		if (length($subst->{preview}->{username}) > $GLOBAL_OPTIONS->{public_name_limit_val}) {
			my $l = read_language()->{BP_NAME_IS_TOO_LONG};
			$l =~ s/\%maxchar/$GLOBAL_OPTIONS->{public_name_limit_val}/;
			$subst->{preview}->{errormsg} = $l;
			$subst->post_new_subject_handler($FORMref, $vpr) if $subst->{preview}->{new_conversation};
			$subst->post_prepare_message($FORMref, $vpr);
			return post_there_was_an_error($subst);
		}
	}
	if ($DCONF->{pro} && $GLOBAL_OPTIONS->{ppmu} > 0 && ($vpr->{public} || $vpr->{special}) && ! $vpr->{user}->{account} && ! $vpr->{moderator}->{account}) {
		dreq("postname-PRO");
		my $un = prepare_userpass($subst->{preview}->{username});
		if (check_restricted_postname($un, $vpr)) {
			$subst->{general}->{restricted_name} = 1;
			return post_authentication_error($subst, $FORMref, $privcache);
		}
	}
	$subst->post_new_subject_handler($FORMref, $vpr, $privcache) if $subst->{preview}->{new_conversation};
	$subst->post_prepare_message($FORMref, $vpr, $privcache);
	$subst->post_prepare_author_name($FORMref, $vpr, $privcache);
	$subst->post_check_message_size($vpr, $privcache);
	if ($DCONF->{pro} && $subst->{message}->{text} =~ m|\+\+\+|) {
		dreq("upload-PRO");
		my ($AA, $text, $source, $sizer, $bad_dim) = scan_upload_attachments_remove_too_big($subst->{message}->{text}, $vpr, $subst->{preview}->{source});
		if ($AA) {
			$subst->{message}->{text} = $text;
			my $sizer2 = int($sizer/100) / 10;
			$subst->{preview}->{source} = $source;
			$subst->{preview}->{error} = 1;
			post_upload_size_error($subst, $FORMref, $privcache, $param, $sizer2);
		} elsif (scalar keys %{ $bad_dim } > 0) {
			$subst->{message}->{text} = $text;
			$subst->{preview}->{source} = $source;
			$subst->{preview}->{error} = 1;
			post_upload_size_error_2($subst, $FORMref, $privcache, $param, $bad_dim->{width}, $bad_dim->{height});
		} else {
			$subst->{preview}->{source} = $source;
		}
		$subst->{message}->{text} = convert_wrapped_attachments($text);
	}
	if (($FORMref->{preview} == 0 || $vpr->{profile_hash}->{enhanced}->{pref}->{d} == 1) && $subst->{preview}->{error} == 0) {
		return post_message_helper($subst, $param, $FORMref, $privcache, $vpr, $get_page);
	} else {
		if ($DCONF->{pro} && $GLOBAL_OPTIONS->{spell_check} && $vpr->{profile_hash}->{enhanced}->{pref}->{i}) {
			dreq("speller-PRO");
			($subst->{message}->{text}, $subst->{message}->{misspelled_words}) = spell_check_message($subst->{message}->{text});
			$subst->{message}->{misspelled_words} = scalar(keys(%{ $subst->{message}->{misspelled_words} }));
		}
		$subst->{message}->{text} =~ s%<(/?)form(.*?)>%<!--$1 form $2 -->%g;
		$subst->{general}->{secure} = -e "$DCONF->{admin_dir}/secure/$param->{topic}";
		$subst->{general}->{no_message_icons_shown} = 1;
		$subst->{message}->{fields} = [];
		return screen_out("*$subst->{head}->{topic_number}preview", $subst, $cookie_str);
	}
}

###
### post_message_helper
###
### Takes a minimal number of arguments and posts a message using the most
### common procedure and options.  In particular:
###  * Double post protection
###  * Enhanced user profile stuff (++ number of posts, status)
###  * Checks whether or not to queue
###  * Performs automatic archiving or pruning
###  * Performs active reordering
###  * Updates post counts/last modified/etc. on parent subtopics
###
### Note:
###  $A is prepared text or options ($subst)
###  $B is parameters from input ($param)
###  $C is parameters directly from the user ($FORMref)
###

sub post_message_helper {
	my ($A, $B, $C, $privcache, $vpr, $get_page) = @_;
	my $args = $A->{message};
	$args->{emoticon} = $A->{new_conv}->{emoticon};
	$args->{topic_number} = $B->{topic};
	$args->{me_number} = $B->{page};
	$args->{subject} = $A->{new_conv}->{subject};
	$args->{real_author} = $A->{message}->{real_author};
	$args->{is_private} = -e "$DCONF->{message_dir}/$B->{topic}" ? 0 : 1;
	$args->{is_page_mgr} = defined $C->{pagemgr} ? 1 : 0;
	$args->{username} = $C->{username};
	$args->{remote_addr} = $ENV{'REMOTE_ADDR'};
	$args->{remote_host} = $ENV{'REMOTE_HOST'};
	$args->{source} = $A->{message}->{source};
	$args->{entered_uname} = $A->{message}->{field_entered_uname};
	$args->{uname} = $A->{message}->{field_uname};
	$args->{group} = $vpr->{user}->{account} ? "user" : "MODERATOR";
	$args->{group} = "PUBLIC" if $args->{group} eq "MODERATOR" && ! $vpr->{moderator}->{account};
	$args->{personal_fields} = $vpr->{profile_hash}->{enhanced}->{personal} if $DCONF->{pro};
	foreach my $l (keys(%{ $args })) {
		if ($l =~ m|^field_|) {
			$args->{starthash}->{$l} = $args->{$l} if $l ne "ip_address";
		}
	}
	post_double_post_error($args) if post_check_double_post($args, $get_page);
	my $att = post_scan_attachments($A->{message}->{source});
	$args->{author_status} = 3 if ! $DCONF->{pro} && defined $vpr->{user}->{account};
	$args->{author_status} = 9 if ! $DCONF->{pro} && defined $vpr->{moderator}->{account};
	$args->{author_status} = 10 if ! $DCONF->{pro} && defined $vpr->{moderator}->{account} && $vpr->{profile_hash}->{user} eq $DCONF->{superuser};
	my $d = (defined $att ? post_prepare_attachment_tempfile($args, $att) : undef);
	if ($DCONF->{pro}) {
		dreq("queue1-PRO");
		if (queue_must_we_queue($vpr, $att)) {
			$args->{queued} = 1;
			my ($postnum, $pagenum) = queue_enter_message($args, $vpr);
			if ($GLOBAL_OPTIONS->{email} == 1) {
				dreq("notify");
				my $pd = queue_fudge_page_data($args);
				notify_handler($postnum, $pagenum, $args, $pd, { queue => 1 });
			}
			post_upload_form($postnum, $pagenum, $args, $att, $d) if defined $att;
			queue_submitted_message($args);
		}
	}
	if ($DCONF->{pro} && ! $A->{message}->{anonymous}) {
		dreq("fcn-prfl-PRO"); increment_number_of_posts($vpr->{profile_hash}, 1, $args);
	}
	$args->{privcache} = $privcache;
	my @locks = ();
	push @locks, "$DCONF->{message_dir}/$B->{topic}/$B->{page}.$DCONF->{ext}";
	push @locks, "$DCONF->{admin_dir}/msg_index/$B->{topic}-tree.txt";
	lock("post_message_helper", @locks);
	my $PAGECACHE = GetPage($B->{topic}, $B->{page});
	my $TREECACHE = read_tree($B->{topic}, { no_unlock => 1, no_lock => 1 });
	my $presult = post_message($args, $PAGECACHE, $TREECACHE, undef, $privcache);
	post_message_helper_after($args, $presult, $privcache);
	post_upload_form($presult->{postnum}, $presult->{pageinfo}->{head}->{me_number}, $args, $att, $d) if defined $att;
	return seturl(post_get_result_url($args));
}

###
### post_message_helper_after
###
### Performs functions after post_message is called
###

sub post_message_helper_after {
	my ($args, $presult, $privcache) = @_;
	my $PAGECACHE = $presult->{pagecache};
	my $TREECACHE = $presult->{treecache};
	my $TREECAT = $presult->{treecat};
	if ($DCONF->{pro}) {
		dreq("fcn-arch-PRO");
		my $z = $args->{new_conversation} ? 2 : 1;
		my $aa = auto_archive_check($args->{topic_number}, $args->{me_number}, $z, $PAGECACHE, $TREECACHE, $TREECAT);
		if (ref $aa eq "HASH") {
			$PAGECACHE = $aa->{oldfile} if defined $aa->{oldfile};
			$TREECACHE = $aa->{old_topic_tree} if defined $aa->{old_topic_tree};
			$TREECAT = $aa->{old_topic_cat} if defined $aa->{old_topic_cat};
			$TREECACHE = $aa->{tree} if defined $aa->{tree};
			$TREECAT = $aa->{cat} if defined $aa->{cat};
		}
	}
	SetPage($PAGECACHE, { privcache => $privcache, unlock => 1 });
	my $topic_number = $args->{topic_number};
	my $active_reorder_self = (grep(/^$topic_number$/, split(/,/, $GLOBAL_OPTIONS->{'active_subs'})) ? 1 : 0 );
	my $active_reorder_parent = (grep(/^$topic_number$/, split(/,/, $GLOBAL_OPTIONS->{'active_subs_parents'})) ? 1 : 0 );
	if ($args->{new_conversation} == 0 && ($active_reorder_self + $active_reorder_parent > 0)) {
		my @children = ();
		undef my $parent_arrays;
		undef my $is_level;
		foreach my $oo (@{ $PAGECACHE->{head}->{levels} }) {
			$is_level->{ $oo->{level_number} } = 1;
		}
		$is_level->{ $PAGECACHE->{head}->{topic_number} } = 1;
		$is_level->{ 0 } = 1;
		foreach my $refr (@{ $TREECACHE }) {
			next if $refr->{page} == 0;
			push (@{ $parent_arrays->{ $refr->{parent} } }, $refr) if $refr->{level} > 0;
			push (@{ $parent_arrays->{ 0 } }, $refr) if $refr->{level} == 0;
		}
		if ($PAGECACHE->{head}->{topic_number} == $PAGECACHE->{head}->{me_number}) {
			$PAGECACHE->{head}->{parent} = 0;
		}
		if ($active_reorder_self && defined $PAGECACHE->{head}->{parent}) {
			@{$parent_arrays->{ $PAGECACHE->{head}->{parent} } } = sort {$b->{lastmod} <=> $a->{lastmod}} @{$parent_arrays->{ $PAGECACHE->{head}->{parent} } };
			@{$parent_arrays->{ $PAGECACHE->{head}->{parent} } } = sort sort_archive_method @{$parent_arrays->{ $PAGECACHE->{head}->{parent} } } if $GLOBAL_OPTIONS->{sort_archive_method_on};
		}
		if ($active_reorder_parent && defined $PAGECACHE->{head}->{parent}) {
			foreach my $y (keys(%{ $is_level })) {
				next if $y == $PAGECACHE->{head}->{parent};
				next if ! defined $parent_arrays->{$y};
				@{$parent_arrays->{ $y } } = sort {$b->{lastmod} <=> $a->{lastmod}} @{$parent_arrays->{ $y } };
				@{$parent_arrays->{ $y } } = sort sort_archive_method @{$parent_arrays->{ $y } } if $GLOBAL_OPTIONS->{sort_archive_method_on};
			}
		}
		$TREECACHE = buildtree($parent_arrays, 0);
		write_tree($args->{topic_number}, $TREECACHE, { no_lock => 1 });
		foreach my $page (keys(%{ $is_level })) {
			next if $page == 0;
			next if $page == $PAGECACHE->{head}->{me_number};
			my $rescode = lock("--REGENERATE-SUBTOPICS--", "$DCONF->{message_dir}/$topic_number/$page.$DCONF->{ext}");
			if ($rescode == 1) {
				my $inp = GetPage($args->{topic_number}, $page);
				$inp->{sublist} = expand_sublist($inp->{sublist}, $topic_number, $TREECACHE);
				if ($active_reorder_self && $page == $PAGECACHE->{head}->{parent}) {
					@{ $inp->{sublist} } = sort {$b->{lastmod} <=> $a->{lastmod} } @{ $inp->{sublist} };
				}
				if ($active_reorder_parent && $page != $PAGECACHE->{head}->{parent}) {
					@{ $inp->{sublist} } = sort {$b->{lastmod} <=> $a->{lastmod} } @{ $inp->{sublist} };
				}
				@{ $inp->{sublist} } = sort sort_archive_method @{ $inp->{sublist} } if $GLOBAL_OPTIONS->{sort_archive_method_on};
				$inp->{general}->{subtopic_raw} = 0;
				SetPage($inp, { privcache => $privcache, unlock => 1 });
			}
		}
	} else {
		write_tree($args->{topic_number}, $TREECACHE, { no_lock => 1 });
		pages_upward_update($TREECACHE, $TREECAT, [ $args->{me_number} ]);
	}
	$PARAMS->{"lookup:$PAGECACHE->{head}->{topic_number}"} = undef;
	if ($GLOBAL_OPTIONS->{top_page_noregen} != 1) {
		my $ttm = topic_tree_to_main($TREECACHE);
		dreq("topic-pg"); regenerate_topic_page(undef, $ttm);
	}
	if ($GLOBAL_OPTIONS->{email} == 1) {
		dreq("notify");
		notify_handler($presult->{postnum}, $presult->{pageinfo}->{head}->{me_number}, $args, $presult->{pageinfo}, { queue => 0 });
	}
}

###
### post_new_subject_handler
###
### Handles the name of a new subject
###

sub post_new_subject_handler {
	my ($subst, $FORMref, $vpr, $privcache) = @_;
	$subst->{message}->{new_conversation} = 1;
	$subst->{preview}->{subject} = $FORMref->{subject};
	$subst->{preview}->{emoticon} = $FORMref->{emoticon};
	$subst->{new_conv}->{emoticon} = $FORMref->{emoticon};
	my $subject = profanity_filter($FORMref->{subject}, {privcache => $privcache, subst => $subst, alternate_handler => \&post_profanity_error});
	my ($lint_subj, $subtopic_name) = webtags($subject, 3, 1, $vpr->{type}->{is_moderator}, $vpr->{type}->{is_superuser}, $subst->{head}->{topic_number});
	$subtopic_name = "\u$subtopic_name" if $GLOBAL_OPTIONS->{capitalize} == 1;
	if ($lint_subj eq "!Error") {
		$subst->{preview}->{errormsg} = $subtopic_name;
		$subst->{preview}->{error} = 1;
	} elsif ($GLOBAL_OPTIONS->{new_conv_limit_num} > 0 && $GLOBAL_OPTIONS->{new_conv_limit}) {
		my $z = substrnohtml($subtopic_name, $GLOBAL_OPTIONS->{new_conv_limit_num});
		if ($z ne $subtopic_name) {
			my $l = read_language()->{BP_SUBJECT_TOO_LONG};
			$l =~ s/\%maxchar/$GLOBAL_OPTIONS->{new_conv_limit_num}/g;
			$subst->{preview}->{errormsg} = $l;
			$subst->{preview}->{error} = 1;
		}
	}
	if ($subtopic_name !~ m|\S|) {
		$subst->{preview}->{errormsg} = read_language()->{BPCREATEERRORDESC};
		$subst->{preview}->{error} = 1;
	}
	$subst->{new_conv}->{subject} = $subtopic_name;
	return $subst;
}

###
### post_chop_long_words
###
### Implementation of the 'Limit maximum word length in posts' option
###

sub post_chop_long_words {
	my ($message) = @_;
	return $message if $GLOBAL_OPTIONS->{prevent_long_posts} == 0;
	my $thres = 0 + $GLOBAL_OPTIONS->{prevent_long_posts_threshold};
	return $message if $thres <= 0;
	my ($U, $V) = split_u_v($message, '<.*?>');
	foreach my $u (@{$U}) {
		my @v = ();
		while ($u =~ /&#?\w+;/g) { push @v, $& };
		$u =~ s/&#?\w+;/\r/g;
		while ($u =~ /([\S\r]{$thres})([\S\r])/) {
			$u = join("", $`, $1, " ", $2, $');
		}
		while ($u =~ /\r/) {
			$u = join("", $`, shift @v, $');
		}
	}
	return join_u_v($U, $V);
}

###
### post_prepare_message
###
### Handles the incoming message.
###

sub post_prepare_message {
	my ($subst, $FORMref, $vpr, $privcache) = @_;
	$subst->{preview}->{source} = $FORMref->{message};
	my $message = $FORMref->{message};
	$message =~ s/\s+$//;
	$message =~ s/^\s+//;
	if ($GLOBAL_OPTIONS->{html}) {
		dreq("fcn-html"); $message = html_to_webtags($message, $FORMref);
	}
	if ($GLOBAL_OPTIONS->{active_links}) {
		dreq("fcn-html"); $message = activate_links($message, $FORMref);
	}
	$message = profanity_filter($message, {privcache => $privcache, subst => $subst, alternate_handler => \&post_profanity_error, error_report => 1});
	$subst->{message}->{source} = $message;
	my ($lint, $msg) = webtags($message, 0, 1, $vpr->{type}->{is_moderator}, $vpr->{type}->{is_superuser}, $subst->{head}->{topic_number}, 1);
	$msg = post_chop_long_words($msg);
	if ($lint eq "!Error") {
		$subst->{preview}->{errormsg} = $msg;
		post_there_was_an_error($subst, $privcache);
	} elsif ($msg =~ m|<img src="(.*?)"|is && $1 !~ m|^https?://.*$|i) {
		$subst->{preview}->{errormsg} = "Illegal image link: $1 is not permitted";
		post_there_was_an_error($subst, $privcache);
	} else {
		$subst->{message}->{text} = $msg;
		$subst->{preview}->{formatting_tips} = $lint;
		if ($msg !~ m|\S|) {
			$subst->{preview}->{errormsg} = read_language()->{BPADDMSGERRORDESC};
			post_there_was_an_error($subst, $privcache);
		}
		$subst->{preview}->{source} = $FORMref->{message};
	}
	if (defined $vpr->{profile_hash}) {
		if ($DCONF->{pro}) {
			dreq("fcn-prfl-PRO");
			$vpr->{profile_hash} = enhance_result_profile($vpr->{profile_hash})->[0];
		}
		if ($DCONF->{pro} && $vpr->{options_string} !~ m|p| && ($GLOBAL_OPTIONS->{profile_on_post} == 1 || $GLOBAL_OPTIONS->{profile_on_post} eq "")) {
			$subst->{message}->{email} = "$DCONF->{script_url}/board-profile.$DCONF->{cgi_extension}?action=view_profile&profile=$vpr->{profile_hash}->{user}-";
			$subst->{message}->{email} .= $vpr->{type}->{is_moderator} ? "MODERATOR" : "users";
		} elsif ($vpr->{options_string} !~ m|e| && ($GLOBAL_OPTIONS->{email_on_post} == 1 || $GLOBAL_OPTIONS->{email_on_post} eq "")) {
			if ($vpr->{profile_hash}->{enhanced}->{pref}->{c} != 1) {
				$subst->{message}->{email} = "mailto:$vpr->{profile_hash}->{email}" if ($vpr->{profile_hash}->{email} ne "" && $vpr->{profile_hash}->{email} ne "email");
			}
		}
		if ($GLOBAL_OPTIONS->{fullnames} ne "0" && $vpr->{options_string} !~ m|f| && $vpr->{profile_hash}->{fullname} ne "" && $vpr->{profile_hash}->{fullname} ne "fullname") {
			$subst->{message}->{author} = $vpr->{profile_hash}->{fullname};
		} else {
			$subst->{message}->{author} = ($GLOBAL_OPTIONS->{'capitalize_username'} == 1 ? "\u$vpr->{profile_hash}->{user}" : $vpr->{profile_hash}->{user});
		}
		if ($GLOBAL_OPTIONS->{user_paren} == 1 || $GLOBAL_OPTIONS->{user_paren} eq "") {
			$subst->{message}->{author} .= join($GLOBAL_OPTIONS->{'capitalize_username'} == 1 ? "\u$vpr->{profile_hash}->{user}" : $vpr->{profile_hash}->{user}, " (", ")");
		}
		$subst->{message}->{author_registered} = $vpr->{profile_hash}->{ctime};
		if (defined $vpr->{profile_hash}->{enhanced}->{status}) {
			$subst->{message}->{author_status} = $vpr->{profile_hash}->{enhanced}->{status};
		} else {
			$subst->{message}->{author_status} = 3;
			$subst->{message}->{author_status} = 9 if $vpr->{type}->{is_moderator} == 1;
			$subst->{message}->{author_status} = 10 if $vpr->{type}->{is_superuser} == 1;
		}
		if ($GLOBAL_OPTIONS->{'signature_user'} && ! $vpr->{type}->{is_moderator}) {
			$subst->{message}->{field_signature} = $vpr->{profile_hash}->{enhanced}->{signature};
		} elsif ($GLOBAL_OPTIONS->{'signature_mod'} == 1 && $vpr->{type}->{'is_moderator'} == 1) {
			$subst->{message}->{field_signature} = $vpr->{profile_hash}->{enhanced}->{signature};
		}
		if ($GLOBAL_OPTIONS->{thumbnails_enabled_posts} && $vpr->{profile_hash}->{enhanced}->{picture_num}) {
			$subst->{message}->{thumbnail} = $vpr->{profile_hash}->{enhanced}->{picture_num};
			$subst->{message}->{thumbnail} = "" if $vpr->{profile_hash}->{enhanced}->{picture} eq "";
			$subst->{message}->{thumbnail} = "" if ! -e "$DCONF->{admin_dir}/profiles/$vpr->{profile_hash}->{enhanced}->{picture}";
		}
		$subst->{message}->{real_author} = $vpr->{profile_hash}->{user};
		$subst->{message}->{author_posts} = 1 + $vpr->{profile_hash}->{enhanced}->{posts};
		if ($vpr->{options_string} =~ m|i1|) {
			$subst->{message}->{ip_address} = ($ENV{REMOTE_HOST} ne "" ? $ENV{REMOTE_HOST} : $ENV{REMOTE_ADDR} );
		}
	} else {
		$subst->{message}->{author_status} = 2;
		$subst->{message}->{author} = remove_html(profanity_filter($FORMref->{username}, {privcache => $privcache, subst => $subst, alternate_handler => \&post_profanity_error}));
		$subst->{message}->{author} =~ s/[<>]//g;
		$subst->{message}->{author} =~ s/\((.*?)\)/$1/g;
		if ($GLOBAL_OPTIONS->{public_addparen} == 1) {
			$subst->{message}->{author} .= " (" . read_language()->{UNREGISTERED_NOTICE};
			$subst->{message}->{author} .= ($GLOBAL_OPTIONS->{public_addip} == 1 ? " " : ")");
		}
		$FORMref->{email} = trim($FORMref->{email});
		$subst->{message}->{email} = join("", "mailto:", remove_html($FORMref->{email})) if $FORMref->{email} =~ /^([\w\+\-\.]+)\@([\w\+\-\.]+)$/;
		$subst->{message}->{email} = remove_html($FORMref->{email}) if $FORMref->{email} =~ m|^https?://|;
		$subst->{message}->{email} = "" if ($subst->{message}->{email} =~ m|^https?://|i && ! $GLOBAL_OPTIONS->{'allow_email_url'});
		$subst->{message}->{email} =~ s/[<>"]//g;
		$subst->{message}->{real_author} = escape(read_language()->{UNREGISTERED_NOTICE});
		if ($vpr->{options_string} =~ m|i[12]|) {
			$subst->{message}->{ip_address} = ($ENV{REMOTE_HOST} ne "" ? $ENV{REMOTE_HOST} : $ENV{REMOTE_ADDR} );
		}
	}
	if ($FORMref->{'Anon'} ne "" && $GLOBAL_OPTIONS->{'anonymous'} == 1 && $vpr->{options_string} !~ m|a|) {
		$subst->{message}->{email} = "";
		$subst->{message}->{author} = read_language()->{BPANONYMOUS};
		if ($FORMref->{Anon} ne "1" && $GLOBAL_OPTIONS->{anonymous_name}) {
			$subst->{message}->{author} = remove_html(profanity_filter($FORMref->{Anon}, {privcache => $privcache, subst => $subst, alternate_handler => \&post_profanity_error}));
		}
		$subst->{preview}->{anonymous} = 1;
		$subst->{message}->{anonymous} = 1;
		$subst->{message}->{author_status} = 1;
		$subst->{message}->{field_status} = 1;
		$subst->{message}->{field_registered} = "";
		$subst->{message}->{author_registered} = "";
		$subst->{message}->{author_posts} = "";
		$subst->{message}->{field_posts} = "";
		$subst->{message}->{field_signature} = "";
		$subst->{message}->{thumbnail} = "";
		if ($vpr->{options_string} =~ m|j1|) {
			$subst->{message}->{ip_address} = "";
		} elsif ($vpr->{options_string} =~ m|j2|) {
			$subst->{message}->{ip_address} = ($ENV{REMOTE_HOST} ne "" ? $ENV{REMOTE_HOST} : $ENV{REMOTE_ADDR} );
		}
	}
	$subst->{message}->{'number'} = 0;
	$subst->{message}->{'time'} = time;
	$subst->{message}->{'_iteration'} = 1;
	return $subst;
}

###
### post_prepare_author_name
###
### Prepares the author's name and other information in the "Add a Message" box
###

sub post_prepare_author_name {
	my ($subst, $FORMref, $vpr, $privcache) = @_;
	if ($subst->{message}->{author} !~ m|\S|) {
		$subst->{preview}->{errormsg} = read_language()->{BPAUTHERRORNONAME};
		post_there_was_an_error($subst, $privcache);
	} else {
		$subst->{message}->{author} = char_convert($subst->{message}->{author});
	}
	if ($subst->{message}->{text} !~ m|\S|) {
		$subst->{preview}->{errormsg} = read_language()->{BPADDMSGERRORDESC};
		post_there_was_an_error($subst, $privcache);
	}
	$subst->{preview}->{username} = $FORMref->{username};
	$subst->{preview}->{password} = $FORMref->{passwd};
	$subst->{preview}->{email} = $FORMref->{email};
	foreach my $fk (keys(%{ $FORMref })) {
		if ($fk =~ m|^field_|) {
			my $fieldstuff = profanity_filter($FORMref->{$fk}, {error_report => 1, privcache => $privcache, subst => $subst, alternate_handler => \&post_profanity_error});
			my ($lint, $field) = webtags($fieldstuff, 1, 1, $vpr->{type}->{is_moderator}, $vpr->{type}->{is_superuser}, $subst->{head}->{topic_number});
			if ($lint eq "!Error") {
				$subst->{preview}->{errormsg} = $field;
				post_there_was_an_error($subst, $privcache);
			}
			$subst->{message}->{$fk} = $field;
		}
	}
	$subst->{message}->{field_entered_uname} = _prepare_as_entered($FORMref->{username}, $vpr->{profile_hash}->{user});
	$subst->{message}->{entered_uname} = $subst->{message}->{field_entered_uname};
	$subst->{message}->{field_uname} = $vpr->{profile_hash}->{user};
	$subst->{message}->{field_uname} = "\u$subst->{message}->{field_uname}" if $GLOBAL_OPTIONS->{capitalize_username};
	$subst->{message}->{uname} = $subst->{message}->{field_uname};
	$subst->{enhanced_author_data} = $vpr->{profile_hash}->{enhanced};
	$subst->{message}->{groups} = $vpr->{profile_hash}->{groups};
	my %u = map { $_, 1 } grep { /\S/ } split(/\//, $vpr->{profile_hash}->{groups});
	$subst->{message}->{authorgroups} = \%u;
	$subst->{message}->{personal_fields} = $vpr->{profile_hash}->{enhanced}->{personal};
	return $subst;
}

sub _prepare_as_entered {
	my ($entered, $inhash) = @_;
	return "" if $inhash eq "";
	my $z = $entered; $z =~ s/([^\w\s])/join("", "&#", ord($1), ";")/ge;
	return $z;
}

###
### post_message
###
### Posts a message onto a message page, creating the new conversation if necessary.
###

sub post_message {
	my ($argument, $PAGECACHE_in, $TREECACHE_in, $TREECAT_in, $privcache) = @_;
	dreq("fcn-tree", "fcn-logs");
	my $newhash = $argument->{starthash};
	my $TREECAT = defined $TREECAT_in ? $TREECAT_in : undef;
	$newhash->{groups} = $argument->{groups};
	$newhash->{authorgroups} = ref $argument->{authorgroups} eq 'HASH' ? $argument->{authorgroups} : {};
	$newhash->{author} = $argument->{author} eq "" ? read_language()->{BPANONYMOUS} : $argument->{author};
	$newhash->{text} = $argument->{text} eq "" ? "&nbsp;" : $argument->{text};
	$newhash->{email} = $argument->{email} eq "" ? "" : $argument->{email};
	$newhash->{number} = $argument->{number} == 0 ? get_postindex() : $argument->{number};
	$newhash->{'time'} = $argument->{'time'} == 0 ? time : $argument->{'time'};
	$newhash->{author_posts} = ($argument->{'author_posts'} == 0 ? 0 : $argument->{'author_posts'});
	$newhash->{author_registered} = ($argument->{'author_registered'} == 0 ? 0 : $argument->{'author_registered'});
	$newhash->{author_status} = ($argument->{author_status} > 0 ? $argument->{author_status} : 1);
	$newhash->{field_signature} = ($argument->{field_signature} eq "" ? "" : $argument->{field_signature});
	$newhash->{field_ip_address} = ($argument->{ip_address} eq "" ? "" : $argument->{ip_address});
	$newhash->{field_uname} = ($argument->{uname} eq "" ? "" : $argument->{uname});
	$newhash->{thumbnail} = ($argument->{thumbnail} eq "" ? "" : $argument->{thumbnail});
	$newhash->{property_approver} = $argument->{approver} if defined $argument->{approver};
	$newhash->{personal_fields} = $argument->{personal_fields};
	$newhash->{remote_addr} = $argument->{remote_addr} if defined $argument->{remote_addr};
	$newhash->{remote_host} = $argument->{remote_host} if defined $argument->{remote_host};
	undef my $pageinfo;
	my $privcache = defined $privcache ? $privcache : $argument->{privcache};
	my $_author = $newhash->{author};
	$_author = $` if $_author =~ /(?:\(|&#40;)(.*?)(?:\)|&#41;)/;

	if ($argument->{new_conversation} == 1) {
		dreq("fcn-pgad");
	}

	my @locks = ();
	push @locks, "$DCONF->{message_dir}/$argument->{topic_number}/$argument->{me_number}.$DCONF->{ext}" if ! defined $PAGECACHE_in;
	push @locks, "$DCONF->{admin_dir}/msg_index/$argument->{topic_number}-tree.txt" if ! defined $TREECACHE_in;
	lock("post_message", @locks) if scalar @locks;

	my $PAGECACHE = defined $PAGECACHE_in ? $PAGECACHE_in : GetPage($argument->{topic_number}, $argument->{me_number});
	my $TREECACHE = defined $TREECACHE_in ? $TREECACHE_in : read_tree($argument->{topic_number}, { no_unlock => 1, no_lock => 1 });

	if ($argument->{new_conversation} == 1) {
		$newhash->{_iteration} = 1;
		my $topic_number = $PAGECACHE->{head}->{topic_number};
		my $new_conv = {};
		$new_conv->{number} = ($argument->{new_conversation_page} == 0 ? undef : $argument->{new_conversation_page});
		$new_conv->{name} = $argument->{subject};
		$new_conv->{position} = 2 if grep(/^$topic_number$/, split(/,/, $GLOBAL_OPTIONS->{'alphabet_subs'}));
		$new_conv->{position} = 1 if grep(/^$topic_number$/, split(/,/, $GLOBAL_OPTIONS->{'reverse_subs'}));
		$new_conv->{position} = 1 if grep(/^$topic_number$/, split(/,/, $GLOBAL_OPTIONS->{'active_subs'}));
		$new_conv->{position} = 0 if $new_conv->{position} == 0;
		$new_conv->{topic} = $argument->{topic_number};
		$new_conv->{host} = $argument->{me_number};
		$new_conv->{new_post} = 1;
		$new_conv->{msg_count} = 1;
		$new_conv->{last_poster} = $_author;
		$new_conv->{originator} = $_author;
		$new_conv->{post_list} = $newhash->{number};
		$new_conv->{type} = "Messages";
		$new_conv->{type} .= "Add" if ($GLOBAL_OPTIONS->{'new_conv_with_add'} == 1 || $GLOBAL_OPTIONS->{'new_conv_with_add'} eq "");
		$new_conv->{type} = "SublistCreate$new_conv->{type}" if $GLOBAL_OPTIONS->{'new_conv_with_button'};
		$new_conv->{first_post} = $newhash;
		$new_conv->{emoticon} = $argument->{emoticon};
		$new_conv->{tree_ref} = $TREECACHE;
		$new_conv->{cat_ref} = $TREECAT;
		$new_conv->{pagehash} = $PAGECACHE;
		$new_conv->{privcache} = $privcache;
		my $r = add_page($new_conv);
		$pageinfo = $r->{newfile};
		$PAGECACHE = $r->{pghash};
		$TREECACHE = $r->{tree};
		$TREECAT = $r->{cat};
		($TREECACHE, $TREECAT) = tree_upward_update($TREECACHE, undef, $r->{number}, { metoo => 0, posts => '+1', lastmod => $newhash->{'time'}, last_poster => $_author });
	} else {
		my @msg = @{$PAGECACHE->{messages}};
		my $topic_number = $PAGECACHE->{head}->{topic_number};
		if (grep(/^$topic_number$/, split(/,/, $GLOBAL_OPTIONS->{'reverse_msgs'}))) {
			unshift(@msg, $newhash);
			tree_push_post($TREECACHE, $TREECAT, $PAGECACHE->{head}->{me_number}, $newhash->{number}, 1);
		} else {
			push (@msg, $newhash);
			tree_push_post($TREECACHE, $TREECAT, $PAGECACHE->{head}->{me_number}, $newhash->{number}, 0);
		}
		$PAGECACHE->{messages} = \@msg;
		$PAGECACHE->{general}->{messages_raw} = 0;
		($TREECACHE, $TREECAT) = tree_upward_update($TREECACHE, $TREECAT, $argument->{me_number}, { metoo => 1, posts => '+1', lastmod => $newhash->{'time'}, last_poster => $_author });
		SetPage($PAGECACHE, { unlock => 1, privcache => $privcache }) if ! defined $PAGECACHE_in;
		$pageinfo = $PAGECACHE;
	}
	my $topic_number = $pageinfo->{head}->{topic_number};
	my $me_number = $pageinfo->{head}->{me_number};
	my $postindex = $newhash->{number};
	my $time = $newhash->{'time'};
	my $username = $argument->{real_author};
	my $group = $argument->{group};
	$username = "PUBLIC" if $group eq "PUBLIC";
	my $RA = defined $newhash->{remote_addr} ? $newhash->{remote_addr} : $ENV{REMOTE_ADDR};
	my $RH = defined $newhash->{remote_host} ? $newhash->{remote_host} : $ENV{REMOTE_HOST};
	my $poststr = escape($newhash->{author});
	my $teXT = $newhash->{text}; $teXT =~ s/\+\+\+ (\w+) \+\+\+ (\d+) \+\+\+ (.*?) \+\+\+/\[$3\]/g;
	my $preview_storage = defined $DCONF->{log_preview_length} ? $DCONF->{log_preview_length} : 25;
	my $chars = escape(substr(remove_html($teXT), 0, $preview_storage));
	log_push_entry($topic_number, [ { topic => $topic_number, postindex => $postindex, username => join(":", $username, $group), time => $time, page => $me_number, remote_addr => $RA, remote_host => $RH, firstchars => $chars, poststr => $poststr }]);
	my $stxt = search_stop($newhash->{text});
	search_push_entry($topic_number, [ { topic => $topic_number, postindex => $postindex, page => $me_number, time => $time, text => $stxt } ]);
	return {
		pagecache => $PAGECACHE,
		treecache => $TREECACHE,
		cat => $TREECAT,
		pageinfo => $pageinfo,
		postnum => $newhash->{number},
	};
}

###
### buildtree
###
### Builds a "family tree" recursively
###

sub buildtree {
	my ($arr, $parent) = @_;
	undef my @newtree;
	while (my $x = shift(@{ $arr->{$parent} })) {
		push (@newtree, $x);
		push (@newtree, @{ buildtree($arr, $x->{page}) });
	}
	return \@newtree;
}

###
### get_postindex
###
### Obtains and increases by one the post index
###

sub get_postindex {
	if ($GLOBAL_OPTIONS->{database} && $GLOBAL_OPTIONS->{postindex_fixed} == 0) {
		dreq("adm-dr"); return postindex_database_fix();
	}
	return sql_counter_increment("postindex") if $GLOBAL_OPTIONS->{database};
	my $fref = readfile("$DCONF->{admin_dir}/postindex.txt", "get_postindex", { no_unlock => 1 });
	my $num = $fref->[0]; chomp $num; $num += 1;
	my @fref = ( "$num\n" );
	writefile("$DCONF->{admin_dir}/postindex.txt", \@fref, "get_postindex", { no_lock => 1 });
	return $num;
}

###
### post_authentication_error
###
### Present an authentication error on the preview screen if you are
### not authorized to post here.
###

sub post_authentication_error {
	my ($subst, $FORMref, $privcache, $param) = @_;
	$subst->{preview}->{errormsg} = read_language()->{BPAUTHERRORINVALID};
	$subst->{preview}->{errormsg} = read_language()->{BP_MATCHES_REGISTERED_NAME} if $subst->{general}->{restricted_name};
	$subst->{preview}->{subject} = $FORMref->{subject};
	$subst->{preview}->{source} = $FORMref->{message};
	post_there_was_an_error($subst, $privcache, $param);
}

###
### post_there_was_an_error
###
### An easy way to get to the preview screen if there was an error
###

sub post_there_was_an_error {
	my ($subst, $privcache, $param) = @_;
	if (ref $PARAMS->{no_exit} eq 'CODE') {
		return &{ $PARAMS->{no_exit} }($subst->{preview}->{errormsg});
	}
	$subst->{addmessage} = determine_addmessage($subst->{topic}, $privcache);
	$subst->{preview}->{error} = 1;
	$subst->{general}->{secure} = -e "$DCONF->{admin_dir}/secure/$subst->{topic}";
	$subst->{general}->{no_message_icons_shown} = 1;
	screen_out("*$subst->{head}->{topic_number}preview", $subst, undef);
}

###
### post_upload_form
###
### Upload image/attachment form
###

sub post_upload_form {
	my ($postnum, $pagenum, $args, $att, $d) = @_;
	my @data = ("postnum=$postnum\n");
	push @data, "pagenum=$pagenum\n";
	push @data, "queued=1\n" if $args->{queued};
	appendfile("$DCONF->{admin_dir}/msg_index/temp/$d.TMP", \@data, "post_upload_form");
	my $subst = {};
	if ($DCONF->{pro}) {
		dreq("upload-PRO");
		$subst->{attachments} = remove_done_attachments($att->{uploads}, $d);
	} else {
		$subst->{attachments} = $att->{uploads};
	}
	$subst->{general}->{target_script} = "board-post";
	$subst->{general}->{action} = "imageupload";
	$subst->{general}->{tempfile} = $d;
	screen_out("upload", $subst);
}

###
### post_prepare_attachment_tempfile
###
### Prepares a temporary file for uploads
###

sub post_prepare_attachment_tempfile {
	my ($argument, $attachment_ref) = @_;
	my $filename = get_number();
	if (! -d "$DCONF->{admin_dir}/msg_index/temp") {
		mkdir("$DCONF->{admin_dir}/msg_index/temp", oct($DCONF->{perms0777})) || error_message("Create Directory Error", "Could not create 'temp' directory under 'msg_index'.  Post with image/attachments failed.");
		chmod(oct($DCONF->{perms0777}), "$DCONF->{admin_dir}/msg_index/temp");
	}
	my @data = ();
	foreach my $u (keys(%{ $argument })) {
		if (ref $argument->{$u} eq "HASH") {
			my $q = $argument->{$u}->{descr}; $q =~ s/(^|[^\\]),/$1\\,/g;
			push @data, "$u=" . escape($q) . "\n" if $u ne "";
		} else {
			my $q = $argument->{$u}; $q =~ s/(^|[^\\]),/$1\\,/g;
			push @data, "$u=" . escape($q) . "\n" if $u ne "";
		}
	}
	foreach my $f (@{ $attachment_ref->{fragments} }) {
		push @data, "=[f]" . escape($f) . "\n";
	}
	foreach my $f (@{ $attachment_ref->{uploads} }) {
		my $line = "=[u]";
		if ($f->{'descr'} =~ m|^(\d+),| && $f->{'done'}) {
			$f->{file} = $1; $f->{'descr'} = $';
		}
		foreach my $k (keys(%{ $f })) {
			my $q = $f->{$k}; $q =~ s/(^|[^\\]),/$1\\,/g;
			$line .= "&$k=>" . escape($q);
		}
		push @data, $line . "\n";
	}
	push @data, "ip=$ENV{REMOTE_ADDR}\n";
	writefile("$DCONF->{admin_dir}/msg_index/temp/$filename.TMP", \@data, { create => 1 });
	return $filename;
}

###
### post_get_result_url
###
### Gets the URL of the page on which a message was posted
###

sub post_get_result_url {
	my ($argument) = @_;
	return "$DCONF->{script_url}/board-admin.$DCONF->{cgi_extension}?username=$argument->{username}&action=pm-page_editor&HTTP_REFERER=$argument->{topic_number}/$argument->{me_number}&menu=4" if $argument->{is_page_mgr};
	return "$DCONF->{authorize_reader}?file=/$argument->{topic_number}/$argument->{me_number}.$DCONF->{ext}&lm=$argument->{time}" if $argument->{is_private} && ! $DCONF->{noqm};
	return "$DCONF->{authorize_reader}?file=/$argument->{topic_number}/$argument->{me_number}.$DCONF->{ext}" if $argument->{is_private};
	return "$DCONF->{message_url}/$argument->{topic_number}/$argument->{me_number}.$DCONF->{ext}?$argument->{time}" if ! $DCONF->{noqm};
	return "$DCONF->{message_url}/$argument->{topic_number}/$argument->{me_number}.$DCONF->{ext}";
}

###
### post_double_post_error
###
### Double post error message
###

sub post_double_post_error {
	my ($argument) = @_;
	undef my $subst;
	$subst->{general}->{clickthrough} = post_get_result_url($argument);
	screen_out("dblpost", $subst);
}

###
### post_check_double_post
###
### Checks to see if this post is a duplicate
###

sub post_check_double_post {
	my ($argument, $get_page) = @_;
	return 0 if ! $GLOBAL_OPTIONS->{'double_post'};
	my $threshold = ( defined $GLOBAL_OPTIONS->{'double_post_threshold'} ? $GLOBAL_OPTIONS->{'double_post_threshold'} : 120 );
	my $timecache = time;
	if ($argument->{new_conversation} == 1) {
		my $topic_tree = read_tree($argument->{topic_number}, { no_lock => 1, no_unlock => 1});
		foreach my $u (@{ $topic_tree }) {
			if ($u->{parent} == $argument->{me_number} && $argument->{subject} eq $u->{name} && $u->{posts} == 1 && $u->{lastmod} > ($timecache - $threshold)) {
				return 1;
			}
		}
	} else {
		$get_page = GetPage($argument->{topic_number}, $argument->{me_number}) if ! $get_page;
		foreach my $msg (@{ $get_page->{messages} }) {
			return 1 if $msg->{'text'} eq $argument->{text} && $msg->{'time'} > ($timecache - $threshold);
		}
	}
	return 0;
}

###
### post_scan_attachments
###
### Scan a post for attachments and images to upload
###

sub post_scan_attachments {
	my ($message_source) = @_;
	my @img_tags = ("image", read_language()->{IMAGE_TAG_NAME});
	push @img_tags, ("attach", read_language()->{ATTACH_TAG_NAME}, "popgif", "popjpeg", "popattach", "poppng") if $DCONF->{pro};
	@img_tags = grep(/\S/, @img_tags);
	my @result = ();
	my @fragments = ();
	my $s = join("|", @img_tags);
	while ($message_source =~ m%(^|[^\\])\\($s)\{(.*?)\}%i) {
		$message_source = $';
		push @fragments, join("", $`, $1);
		my $u = lc($2);
		my $x = {};
		$x->{type} = "i" if $u eq "image";
		$x->{type} = "i" if $u eq read_language()->{IMAGE_TAG_NAME};
		$x->{type} = "i" if $u eq "popgif";
		$x->{type} = "i" if $u eq "popjpeg";
		$x->{type} = "i" if $u eq "poppng";
		$x->{type} = "a" if $u eq "attach";
		$x->{type} = "a" if $u eq "popattach";
		$x->{type} = "a" if $u eq read_language()->{ATTACH_TAG_NAME};
		$x->{descr} = $3;
		$x->{done} = 1 if $u =~ m|^pop|;
		push @result, $x;
	}
	push @fragments, $message_source;
	return undef if scalar(@result) == 0;
	return { fragments => \@fragments, uploads => \@result };
}

###
### split_u_v
###
### Generates arrays for split processing based on pattern
###

sub split_u_v {
	my ($text, $pat) = @_;
	$pat = '<.*?>' if $pat eq "";
	my @u = split(/$pat/, $text);
	my @v = (); while ($text =~ /($pat)/g) { push @v, $1; }
	return (\@u, \@v);
}

###
### join_u_v
###
### Re-joins arrays after split processing
###

sub join_u_v {
	my @u = @{$_[0]};
	my @v = @{$_[1]};
	my $text = "";
	while (scalar(@u) + scalar(@v)) {
		$text .= shift @u; $text .= shift @v;
	}
	return $text;
}

1;
