# ==================================================================
# Gossamer Forum - Advanced web community
#
# Website : http://gossamer-threads.com/
# Support : http://gossamer-threads.com/scripts/support/
# Revision : $Id: Write.pm,v 1.54 2002/08/28 05:29:37 jagerman Exp $
#
# Copyright (c) 2001 Gossamer Threads Inc. All Rights Reserved.
# Redistribution in part or in whole strictly prohibited. Please
# see LICENSE file for full details.
# ==================================================================
#
# This file handles everything associated with writing/editing a post.
#
package GForum::Post::Write;
use strict;
use vars qw/$Attachment_Max/;
use GForum qw/:user :forum $DB $IN $CFG $USER $GUEST %HIDDEN/;
use GForum::Post;
use GT::Plugins;
# GForum::Post loads all of the other modules we may need
sub attachment_upload {
my $temp_id = $IN->param('temp_id');
my $file = $IN->param("post_attachment") or return GForum::do_func($IN->param('redo'));
my ($filename, $ext) = $file =~ m{(?:^|[\\/])([^\\//]+?(\.[^\\/.]+)?)$};
require GT::MIMETypes;
my $content_type = GT::MIMETypes->guess_type(".$ext");
my $attach = $DB->table('TempAttachment');
$attach->delete(GT::SQL::Condition->new(tempatt_expiry => '<=' => time));
my $error;
my $forum_id;
my $post_id;
my ($forum, $parent, $post) = (scalar $IN->param('forum'), scalar $IN->param('parent_post_id'), scalar $IN->param('post'));
if ($forum and not $parent and not $post) { # A new post
$forum_id = $forum;
}
elsif ($parent and not $post) { # A reply
$forum_id = $DB->table('Post')->get($parent, 'ARRAY', ['forum_id_fk'])->[0];
}
elsif ($post and not $parent) { # An edit
$forum_id = $DB->table('Post')->get($post, 'ARRAY', ['forum_id_fk'])->[0];
$post_id = $post;
}
else {
$GForum::Template::VARS{attachment_error} = GForum::language('ATTACHMENT_NOT_ALLOWED');
return GForum::do_func($IN->param('redo'));
}
unless ($DB->table('Forum')->count({ forum_id => $forum_id, forum_allow_attachments => 1 })) {
$GForum::Template::VARS{attachment_error} = GForum::language('ATTACHMENT_NOT_ALLOWED');
return GForum::do_func($IN->param('redo'));
}
$attach->insert(Post => {
tempatt_msg_id => $temp_id,
tempatt_filename => $filename,
tempatt_content => $content_type,
tempatt_fh => $file,
forum_id => $forum_id,
($post_id ? (post_id => $post_id) : ())
}) or $error = $attach->{attachment_error};
$GForum::Template::VARS{attachment_error} = $error if $error;
GForum::do_func($IN->param('redo'));
}
sub attachment_delete {
my $att_id = $IN->param('att_id');
my $type = $IN->param('att_type');
if ($type eq 'temp') {
# It's a temporary attachment we need to delete.
$DB->table('TempAttachment')->delete($att_id);
}
elsif ($type eq 'post') {
my $post = $DB->table('PostAttachment' => 'Post')->select({ postatt_id => $att_id })->fetchrow_hashref;
if ($USER and $post and ($USER->{user_id} == $post->{user_id_fk} or GForum::Authenticate::auth('forum_permission', $post->{forum_id_fk}) >= FORUM_PERM_MODERATOR)) {
$DB->table('PostAttachment')->delete($att_id);
}
else {
return GForum::do_func('permission_denied');
}
}
else {
die "Unknown attachment type '$type'";
}
GForum::do_func($IN->param('redo'));
}
sub write {
shift;
my ($do, $func) = splice @_, 0, 2;
my $page = $func->{page};
my $forum_id = $IN->param('forum');
my $forum;
unless ($forum_id and $forum = $DB->table('Forum', 'Category')->select({ forum_id => $forum_id })->fetchrow_hashref) {
return(
$page->{no_such_forum} => {
error => GForum::language('FORUM_DOES_NOT_EXIST')
}
);
}
require GForum::Forum;
GForum::Forum::normalize($forum);
my $style = $IN->param('post_style');
$style = $USER ? $USER->{user_default_post_style} : 3 if not defined $style;
if ($forum->{forum_style} < $style or $forum->{forum_style} == 2 and $style == 1) {
$style = $forum->{forum_style};
}
my $temp_id;
my $attach = [];
if ($temp_id = $IN->param('temp_id')) {
my $sth = $DB->table('TempAttachment')->select({ tempatt_msg_id => $temp_id });
while (my $rec = $sth->fetchrow_hashref) {
for (keys %$rec) {
if (s/^temp//) {
$rec->{$_} = delete $rec->{"temp$_"};
}
}
$rec->{att_type} = "temp";
push @$attach, $rec;
}
}
else {
$temp_id = generate_temp_id();
}
my (@anonymous, $guest_username);
if (!$USER) {
@anonymous = @{$DB->table('User')->select({ user_status => ANONYMOUS, user_enabled => 1 })->fetchall_hashref};
if (!@anonymous) {
return($page->{anonymous_not_configured} => { error => GForum::language('POST_ANONYMOUS_DISABLED') });
}
elsif (my $anonymous = $IN->param('anon_id')) {
for (@anonymous) {
$_->{selected} = 1, last if $_->{user_id} == $anonymous;
}
}
}
my $post_subject = $IN->param('post_subject') || '';
my $post_message = $IN->param('post_message') || '';
my $orig_post_message;
my ($is_ie, $ie_version);
if ($ENV{HTTP_USER_AGENT} and $ENV{HTTP_USER_AGENT} =~ /MSIE (\d+(?:\.\d+)?)/i and $ENV{HTTP_USER_AGENT} !~ /mac/i) {
$is_ie = 1;
$ie_version = $1;
}
if ($post_message and $IN->param('basic_editor_switch')) {
$post_message = GForum::Convert::advanced_editor_convert($post_message);
}
elsif (
($IN->param('advanced_editor_switch') or not $IN->param('advanced_editor')) and (
($post_message and $IN->param('advanced_editor_switch'))
or
($IN->param('advanced_editor') and $is_ie and $ie_version >= 5.5)
or
($USER and $USER->{user_advanced_editor} and not $IN->param('basic_editor') and $is_ie and $ie_version >= 5.5)
)
) {
$orig_post_message = $post_message;
_post_message_html(\$post_message, $style >= 2);
}
my $can_attach = not cant_attach({ forum_id => $forum_id, temp_id => $temp_id });
my $post_append_sig = $IN->param('post_append_signature');
$post_append_sig = 1 if not $post_subject and not $post_message;
my $post_reply_notify = $IN->param('post_reply_notify');
if (not($post_subject or $post_message) and $USER) {
$post_reply_notify = $USER->{user_default_reply_notify};
}
my %page_data = (
temp_id => $temp_id,
%$forum,
forum_style_selected => $style,
post_subject => $post_subject,
orig_post_message => $orig_post_message,
post_message => $post_message,
post_icon => scalar $IN->param('post_icon'),
advanced_editor => scalar $IN->param('advanced_editor'),
basic_editor => scalar $IN->param('basic_editor'),
attachments => $attach,
num_attachments => scalar @$attach,
can_attach => $can_attach,
post_reply_notify => $post_reply_notify,
post_append_signature => $post_append_sig,
($USER
? () : (
anonymous => \@anonymous,
post_anonymous_email => scalar $IN->param('post_anonymous_email'),
guest_username => scalar $IN->param('guest_username')
)
)
);
my $cols = $DB->table('Post')->cols;
for my $col (keys %$cols) {
next if $cols->{$col}->{protect} or exists $page_data{$col} or not defined(my $val = $IN->param($col));
$page_data{$col} = $val;
}
return($page->{write} => \%page_data);
}
sub reply_write {
shift;
my ($do, $func) = splice @_, 0, 2;
my $page = $func->{page};
my $parent;
my $parent_id = $IN->param('parent_post_id') or return die "No post ID entered";
# Get the parent post or print an error page.
my $parent_sth = $DB->table('Post', 'User')->select(left_join => { post_id => $parent_id }) or die $GT::SQL::error;
unless ($parent_id and $parent = $parent_sth->fetchrow_hashref) {
return(
$page->{no_such_post} => {
error => GForum::language('POST_DOES_NOT_EXIST')
}
);
}
elsif ($parent->{post_deleted}) {
return(
$page->{post_deleted} => {
error => GForum::language('POST_IS_DELETED')
}
);
}
if ($USER and $USER->{user_forum_permission} < FORUM_PERM_MODERATOR) {
# If you have moderator privileges you can post whether or not the post is locked, so
# these checks only happen if you don't have moderator privileges.
my $locked;
if ($parent->{post_root_id}) { # The parent isn't the root post
$locked = $DB->table('Post')->get($parent->{post_root_id}, 'ARRAY', ['post_locked'])->[0];
}
else { # If the parent is the root post this saves a select to get the root post
$locked = $parent->{post_locked};
}
if ($locked) {
return($page->{locked}, { error => GForum::language('POST_LOCKED') });
}
}
my $forum = $DB->table('Forum', 'Category')->select({ forum_id => $parent->{forum_id_fk} })->fetchrow_hashref;
require GForum::Forum;
GForum::Forum::normalize($forum);
my $err_code = shift;
my $error = sprintf GForum::language($err_code), @_ if $err_code;
my $style = $IN->param('post_style');
$style = $USER ? $USER->{user_default_post_style} : 3 if not defined $style;
if ($forum->{forum_style} < $style or $forum->{forum_style} == 2 and $style == 1) {
$style = $forum->{forum_style};
}
my $parent_body = $parent->{post_message};
GForum::Post::normalize($parent);
my $cols = $DB->table('Post')->{schema}->{cols};
for (keys %$parent) {
$parent->{"parent_$_"} = delete $parent->{$_} if $cols->{$_};
}
my $subject;
unless ($subject = $IN->param('post_subject')) {
my $re = GForum::language('POST_REGARDING');
if ($CFG->{post_reply_position} eq 'end') {
if ($CFG->{post_reply_subject_username} and $CFG->{post_reply_subject_username} eq 'beginning') {
$subject = $parent->{parent_post_subject};
$subject =~ s/^(?:\s*\[.*?\]\s*)?/[$parent->{parent_post_username}] /;
$subject =~ s/\s*(?:\Q$re\E\s*)?$/$re/;
}
elsif ($CFG->{post_reply_subject_username} and $CFG->{post_reply_subject_username} eq 'after_re') {
($subject = $parent->{parent_post_subject}) =~ s/\s*(?:(?:\s*\[.*?\]\s*)?\s*\Q$re\E)?$/ \[$parent->{parent_post_username}]$re/;
}
elsif ($CFG->{post_reply_subject_username} and $CFG->{post_reply_subject_username} eq 'end') {
($subject = $parent->{parent_post_subject}) =~ s/\s*(?:\Q$re\E\s*)?(?:\[.*?\]\s*)?$/$re [$parent->{parent_post_username}]/;
}
else {
($subject = $parent->{parent_post_subject}) =~ s/\s*(?:\Q$re\E\s*)?$/$re/;
}
}
else {
if ($CFG->{post_reply_subject_username} and $CFG->{post_reply_subject_username} eq 'beginning') {
($subject = $parent->{parent_post_subject}) =~ s/^\s*(?:(?:\[.*?\]\s*)?\Q$re\E)?/[$parent->{parent_post_username}] $re/;
}
elsif ($CFG->{post_reply_subject_username} and $CFG->{post_reply_subject_username} eq 'after_re') {
($subject = $parent->{parent_post_subject}) =~ s/^\s*(?:\Q$re\E(?:\s*\[.*?\]\s*)?)?/$re\[$parent->{parent_post_username}] /;
}
elsif ($CFG->{post_reply_subject_username} and $CFG->{post_reply_subject_username} eq 'end') {
($subject = $parent->{parent_post_subject}) =~ s/^\s*(?:\Q$re\E)?/$re/;
$subject =~ s/\s*(\[.*?\])?\s*$/ [$parent->{parent_post_username}]/;
}
else {
($subject = $parent->{parent_post_subject}) =~ s/^\s*(?:\Q$re\E)?/$re/;
}
}
}
my $temp_id;
my $attach = [];
if ($temp_id = $IN->param('temp_id')) {
my $sth = $DB->table('TempAttachment')->select({ tempatt_msg_id => $temp_id });
while (my $rec = $sth->fetchrow_hashref) {
for (keys %$rec) {
if (s/^temp//) {
$rec->{$_} = delete $rec->{"temp$_"};
}
}
$rec->{att_type} = "temp";
push @$attach, $rec;
}
}
else {
$temp_id = generate_temp_id();
}
my @anonymous;
if (!$USER) {
@anonymous = @{$DB->table('User')->select({ user_status => ANONYMOUS, user_enabled => 1 })->fetchall_hashref};
if (!@anonymous) {
return($page->{anonymous_not_configured} => { error => GForum::language('POST_ANONYMOUS_DISABLED') });
}
elsif (my $anonymous = $IN->param('anon_id')) {
for (@anonymous) {
$_->{selected} = 1 if $_->{user_id} == $anonymous;
}
}
}
my $can_attach = not cant_attach({ forum_id => $forum->{forum_id}, temp_id => $temp_id });
my $post_subject = $IN->param('post_subject') || '';
my $post_message = $IN->param('post_message') || '';
my $orig_post_message;
my $quoted;
if (!$post_message and $IN->param('quote') and $forum->{forum_style} % 2) {
$parent_body =~ s/
/\n/g;
$parent_body =~ s/\n?\[\s*\Q$CFG->{markup_signature_tag}\E\s*\]//g;
if ($parent->{parent_post_style} % 2) {
$style = $parent->{parent_post_style};
}
elsif ($parent->{parent_post_style}) { # HTML-only mode
$style = 3; # Both mode
$parent_body =~ s/\[([^\]]*)\]/[.$1]/g; # Escape markup tags
}
else {
$style = 1; # Markup mode - the parent was a plain text.
$parent_body =~ s/\[([^\]]*)\]/[.$1]/g; # Escape markup tags
}
my $reply_tag = 'reply';
unless ($CFG->{markup_tags}->{reply}) {
if ($CFG->{markup_tags}->{'répondre'}) { # Hack :( - GForum 2 needs a much better translation mechanism
$reply_tag = 'répondre';
}
}
$post_message = "[$reply_tag]$parent_body\[/$reply_tag]";
$quoted = 1;
}
my ($is_ie, $ie_version);
if ($ENV{HTTP_USER_AGENT} and $ENV{HTTP_USER_AGENT} =~ /MSIE (\d+(?:\.\d+)?)/i and $ENV{HTTP_USER_AGENT} !~ /mac/i) {
$is_ie = 1;
$ie_version = $1;
}
if ($post_message and $IN->param('basic_editor_switch')) {
$post_message = GForum::Convert::advanced_editor_convert($post_message);
}
elsif (
($IN->param('advanced_editor_switch') or not $IN->param('advanced_editor')) and (
($post_message and $IN->param('advanced_editor_switch'))
or
($IN->param('advanced_editor') and $is_ie and $ie_version >= 5.5)
or
($USER and $USER->{user_advanced_editor} and not $IN->param('basic_editor') and $is_ie and $ie_version >= 5.5)
)
) {
$orig_post_message = $post_message;
_post_message_html(\$post_message, $style >= 2);
}
my $post_append_sig = $IN->param('post_append_signature');
$post_append_sig = 1 if not $post_subject and not $post_message or $quoted;
my $post_reply_notify = $IN->param('post_reply_notify');
if ((!$post_subject and !$post_message or $quoted) and $USER) {
$post_reply_notify = $USER->{user_default_reply_notify};
}
my %page_data = (
temp_id => $temp_id,
%$parent,
%$forum,
($error ? (error => $error) : ()),
forum_style_selected => $style,
post_subject => $subject,
post_message => $post_message,
orig_post_message => $orig_post_message,
attachments => $attach,
post_icon => scalar $IN->param('post_icon'),
num_attachments => scalar @$attach,
can_attach => $can_attach,
post_append_signature => $post_append_sig,
post_reply_notify => $post_reply_notify,
advanced_editor => scalar $IN->param('advanced_editor'),
basic_editor => scalar $IN->param('basic_editor'),
($USER
? () : (
anonymous => \@anonymous,
post_anonymous_email => scalar $IN->param('post_anonymous_email'),
guest_username => scalar $IN->param('guest_username')
)
)
);
for my $col (keys %$cols) {
next if $cols->{$col}->{protect} or exists $page_data{$col} or not defined(my $val = $IN->param($col));
$page_data{$col} = $val;
}
return($page->{reply_write} => \%page_data);
}
sub edit {
shift;
my ($do, $func) = splice @_, 0, 2;
my $page = $func->{page};
my $post;
my $post_id = $IN->param('post');
# Get the post or print an error page.
unless ($post_id and $post = $DB->table('Post' => 'Forum' => 'Category')->select({ post_id => $post_id })->fetchrow_hashref) {
return(
$page->{no_such_post} => {
error => GForum::language('POST_DOES_NOT_EXIST')
}
);
}
require GForum::Forum;
GForum::Forum::normalize($post);
my $user;
if ($post->{user_id_fk}) {
$user = $DB->table('User')->get($post->{user_id_fk});
}
else {
$user = GForum::User::blank_user({
user_username => $post->{post_username},
user_title => \GForum::language('USER_DELETED'),
user_signature => $post->{post_signature_deleted}
});
}
@$post{keys %$user} = values %$user;
unless ($USER->{user_forum_permission} >= FORUM_PERM_MODERATOR
or ($USER->{user_id} == $post->{user_id_fk}
and
$post->{forum_allow_user_edit} % 2 # 1 is edit, 3 is edit & delete, but 0 and 2 do not allow editing
and
(!$post->{forum_edit_timeout} or ($post->{post_time} + $post->{forum_edit_timeout} * 60) > time)
)
) {
return GForum::do_func('permission_denied');
}
my $err_code = shift;
my $error = sprintf GForum::language($err_code), @_ if $err_code;
my $style = $IN->param('forum_style') || $USER->{user_default_post_style} || 3;
if ($post->{forum_style} < $style or $post->{forum_style} == 2 and $style == 1) {
$style = $post->{forum_style};
}
my $post_message;
$post_message = $IN->param('post_message') || $post->{post_message};
my $orig_post_message;
my ($is_ie, $ie_version);
if ($ENV{HTTP_USER_AGENT} and $ENV{HTTP_USER_AGENT} =~ /MSIE (\d+(?:\.\d+)?)/i and $ENV{HTTP_USER_AGENT} !~ /mac/i) {
$is_ie = 1;
$ie_version = $1;
}
if ($post_message and $IN->param('basic_editor_switch')) {
$post_message = GForum::Convert::advanced_editor_convert($post_message);
}
elsif (
($IN->param('advanced_editor_switch') or not $IN->param('advanced_editor')) and (
($post_message and $IN->param('advanced_editor_switch'))
or
($IN->param('advanced_editor') and $is_ie and $ie_version >= 5.5)
or
($USER->{user_advanced_editor} and not $IN->param('basic_editor') and $is_ie and $ie_version >= 5.5)
)
) {
$orig_post_message = $post_message;
_post_message_html(\$post_message, $style >= 2);
}
my $temp_id;
my $attach = [];
my $p_sth = $DB->table('PostAttachment')->select({ post_id_fk => $post_id });
while (my $rec = $p_sth->fetchrow_hashref) {
for (keys %$rec) {
if (s/^post//) {
$rec->{$_} = delete $rec->{"post$_"};
}
}
$rec->{att_type} = "post";
push @$attach, $rec;
}
my $first_time;
if ($temp_id = $IN->param('temp_id')) {
my $t_sth = $DB->table('TempAttachment')->select({ tempatt_msg_id => $temp_id });
while (my $rec = $t_sth->fetchrow_hashref) {
for (keys %$rec) {
if (s/^temp//) {
$rec->{$_} = delete $rec->{"temp$_"};
}
}
$rec->{att_type} = "temp";
push @$attach, $rec;
}
}
else {
$first_time = 1;
$temp_id = generate_temp_id();
}
my $can_attach = not cant_attach({ forum_id => $post->{forum_id}, temp_id => $temp_id, post_id => $post_id });
my $append_sig;
my $post_reply_notify;
if ($first_time) {
my $sig = '[' . $CFG->{markup_signature_tag} . ']';
if ($post_message =~ s/\n?\Q$sig\E$//) {
$append_sig = 1;
}
$post_reply_notify = $post->{post_reply_notify};
}
else {
$append_sig = $IN->param('post_append_signature');
$post_reply_notify = $IN->param('post_reply_notify');
}
my $post_icon = $IN->param('post_icon');
$post_icon = $post->{post_icon} if not defined $post_icon;
my %page_data = (
temp_id => $temp_id,
($error ? (error => $error) : ()),
forum_style_selected => $style,
post_subject => $IN->param('post_subject') || $post->{post_subject},
post_message => $post_message,
orig_post_message => $orig_post_message,
attachments => $attach,
num_attachments => scalar @$attach,
can_attach => $can_attach,
post_append_signature => $append_sig,
post_reply_notify => $post_reply_notify,
post_icon => $post_icon,
advanced_editor => scalar $IN->param('advanced_editor'),
basic_editor => scalar $IN->param('basic_editor'),
edit_reason => scalar $IN->param('edit_reason')
);
my $cols = $DB->table('Post')->cols;
for my $col (keys %$cols) {
next if $cols->{$col}->{protect} or exists $page_data{$col} or not defined(my $val = $IN->param($col));
$page_data{$col} = $val;
}
for (keys %$post) { $page_data{$_} = $post->{$_} unless exists $page_data{$_} }
for (keys %$user) { $page_data{$_} = $user->{$_} unless exists $page_data{$_} }
return($page->{edit} => \%page_data);
}
sub preview {
my $self = shift;
my ($do, $func) = @_;
my $page = $func->{page};
my $redo = $IN->param('redo')
or die 'GForum::Post::Write->preview called with bad arguments - CGI parameter redo not set';
my $post;
my $pt = $DB->table('Post');
for (grep exists($pt->{schema}->{cols}->{$_}), $IN->param) {
$post->{$_} = $IN->param($_);
}
$post->{post_time} = time;
$post->{post_ip} = $ENV{REMOTE_ADDR};
my $user;
if ($redo eq 'post_edit') {
my ($uid, $d_username, $d_sig) = $pt->select('user_id_fk', 'post_username', 'post_signature_deleted' => { post_id => scalar $IN->param('post') })->fetchrow;
if ($uid) {
$user = $DB->table('User')->get($uid);
}
else {
$user = GForum::User::blank_user({
user_username => $d_username,
user_title => \GForum::language('USER_DELETED'),
user_signature => $d_sig
});
}
}
elsif ($USER) {
$user = $USER;
}
elsif (my $guest_id = $IN->param('anon_id')) {
$user = $DB->table('User')->select({ user_id => $guest_id, user_status => ANONYMOUS })->fetchrow_hashref;
if ($CFG->{post_guest_custom_username} and my $guest = $IN->param('guest_username')) {
$user->{user_username} = $guest;
}
}
@$post{keys %$user} = values %$user if $user;
$post->{post_username} = $user->{user_username};
if ($post->{post_message} and $IN->param('advanced_editor')) {
$post->{post_message} = GForum::Convert::advanced_editor_convert($post->{post_message});
}
$post->{post_message} .= "\n[$CFG->{markup_signature_tag}]" if $IN->param('post_append_signature');
GForum::Post::normalize($post);
$GForum::Template::VARS{preview} = \GForum::Template->parse($page->{preview} => { %$post, preview => 1 });
GForum::do_func($redo);
}
sub post {
shift;
my ($do, $func) = @_;
uc $ENV{REQUEST_METHOD} eq 'POST' or die "Unable to post with a GET method";
my $page = $func->{page};
my $forum_id = $IN->param('forum');
$forum_id or die "Bad forum ID '$forum_id'";
my $forum = $DB->table('Forum', 'Category')->select({ forum_id => $forum_id })->fetchrow_hashref
or die "Bad forum ID '$forum_id'";
my $temp_id = $IN->param('temp_id');
require GForum::Forum;
GForum::Forum::normalize($forum);
my $post_message = $IN->param('post_message');
if ($IN->param('advanced_editor')) {
$post_message = GForum::Convert::advanced_editor_convert($post_message);
}
$post_message =~ s/\s+$//; # Remove all trailing whitespace
$post_message .= "\n[$CFG->{markup_signature_tag}]" if $IN->param('post_append_signature');
my $post_icon = $IN->param('post_icon');
my $good;
for (values %{$CFG->{post_icons}}) {
$good++, last if $_ eq $post_icon;
}
$post_icon = undef if not $good;
my $post_subject = $IN->param('post_subject');
unless ($post_subject =~ /[^\s\xa0]/) { # \xa0 is Alt-0160 - a non-breaking space, in HTML
if ($CFG->{post_require_subject}) {
$GForum::Template::VARS{post_error_no_subject} = 1;
return GForum::do_func('post_write');
}
else {
$post_subject = GForum::language("NO_SUBJECT");
}
}
my $reply_notify = 1 if $IN->param('post_reply_notify');
$reply_notify ||= 0;
my $post_style = $IN->param('post_style') || 0;
if ($post_style > $forum->{forum_style} or $forum->{forum_style} == 2 and $post_style == 1) {
$post_style = $forum->{forum_style};
}
my ($user_id, $user_username, $guest_username);
if ($USER) {
$user_id = $USER->{user_id};
$user_username = $USER->{user_username};
}
else {
unless ($user_id = $IN->param('anon_id') and $DB->table('User')->count({ user_id => $user_id, user_status => ANONYMOUS, user_enabled => 1 })) {
$user_id = $DB->table('User')->select(user_id => { user_status => ANONYMOUS, user_enabled => 1 })->fetchrow;
}
if ($CFG->{post_guest_custom_username} and $guest_username = $IN->param('guest_username')) {
if (!GForum::Authenticate::auth('valid_username', $guest_username)) {
$GForum::Template::VARS{post_error_bad_guest_username} = $GForum::Authenticate::Auth_Error;
return GForum::do_func('post_write');
}
}
my $u = $DB->table('User')->get($user_id, 'ARRAY', ['user_username']);
$user_username = $u->[0] if $u;
}
$user_id or return($page->{anonymous_not_configured} => { error => GForum::language('POST_ANONYMOUS_DISABLED') });
if (my $pid = $DB->table('Post')->select(post_id => { post_unique_id => $temp_id })->fetchrow) {
require GForum::Post::View;
my $post = GForum::Post::View::get($pid);
return($page->{already_posted} => $post);
}
my $Post = $DB->table('Post');
my $insert = {
user_id_fk => $user_id,
post_username => $user_username,
(defined $guest_username ? (guest_username => $guest_username) : ()),
forum_id_fk => $forum_id,
post_unique_id => $temp_id,
post_root_id => 0,
post_father_id => 0,
post_depth => 0,
post_time => time,
post_subject => $post_subject,
post_message => $post_message,
post_ip => $ENV{REMOTE_ADDR},
post_last_edit_username => undef,
post_last_edit_time => undef,
post_anonymous_email => (scalar $IN->param('post_anonymous_email') || undef),
post_locked => 0,
post_style => $post_style,
post_reply_notify => $reply_notify,
post_icon => $post_icon
};
my $cols = $Post->cols;
for my $col (keys %$cols) {
next if $cols->{$col}->{protect} or exists $insert->{$col} or not defined(my $val = $IN->param($col));
$insert->{$col} = $val;
}
my $post_id;
eval { $post_id = ($Post->insert($insert) or die "$GT::SQL::error\n")->insert_id };
if ($@) {
# An error occured while trying to post
$GForum::Template::VARS{post_error_other} = $@;
return GForum::do_func('post_write');
}
my @attach_errors;
my $pa = $DB->table('PostAttachment');
# Get all the temporary attachments associated with this ID so that we can
# transfer the attachments from TempAttachment to PostAttachment
my $sth = $DB->table('TempAttachment')->select('tempatt_id', { tempatt_msg_id => $temp_id });
while (my $tempatt_id = $sth->fetchrow_array) {
$pa->insert({ tempatt_id => $tempatt_id, post_id_fk => $post_id })
or push @attach_errors, delete $pa->{attachment_error};
}
my $function = "post_confirm"; # 'post_confirm' isn't actually an option, it's handled here
if (not @attach_errors and $USER and exists $CFG->{functions}->{$USER->{user_do_after_post}}) {
$function = $USER->{user_do_after_post};
}
unless ($function eq "post_confirm" or $function eq "post_confirm_post") {
if (substr($function, 0, 4) eq 'post') {
$IN->param(post => $post_id); # post actions are going to need the post ID in $IN->param.
$HIDDEN{post} = $post_id;
}
elsif (substr($function, 0, 5) eq 'forum') {
$IN->param(forum => $forum_id);
$HIDDEN{forum} = $forum_id;
}
$HIDDEN{do} = $function;
my $url = $CFG->{cgi_root_url} . "/gforum.cgi?" . ${GForum::hidden('query')};
print $IN->redirect($url); # If using plugin hooks, setting up a pre hook that prints a header will get around this.
return(
undef, {
%$forum,
post_subject => $post_subject,
post_message => $post_message,
post_id => $post_id,
user_username => $USER->{user_username},
user_id => $USER->{user_id},
(@attach_errors ? (attachment_errors => GForum::language('ATTACHMENT_FAILED', join "
\n", @attach_errors)) : ()),
}
);
}
else {
return(
$page->{post} => {
%$forum,
post_subject => $post_subject,
post_message => $post_message,
post_id => $post_id,
user_username => $USER->{user_username},
user_id => $USER->{user_id},
(@attach_errors ? (attachment_errors => GForum::language('ATTACHMENT_FAILED', join "
\n", @attach_errors)) : ()),
}
);
}
}
sub reply_post {
shift;
uc $ENV{REQUEST_METHOD} eq 'POST' or die "Unable to post with a GET method";
my ($do, $func) = @_;
my $page = $func->{page};
my $parent_id = $IN->param('parent_post_id');
my $parent;
my $parent_sth = $DB->table('Post' => 'Forum' => 'Category')->select({ post_id => $parent_id });
unless ($parent_id and $parent = $parent_sth->fetchrow_hashref) {
return(
$page->{no_such_post} => {
error => GForum::language('POST_DOES_NOT_EXIST')
}
);
}
require GForum::Forum;
GForum::Forum::normalize($parent);
my $forum_id = $parent->{forum_id_fk};
my $temp_id = $IN->param('temp_id');
my $post_icon = $IN->param('post_icon');
my $good;
for (values %{$CFG->{post_icons}}) {
if ($_ eq $post_icon) {
$good = 1;
last;
}
}
$post_icon = undef if not $good;
my $post_message = $IN->param('post_message');
if ($post_message and $IN->param('advanced_editor')) {
$post_message = GForum::Convert::advanced_editor_convert($post_message);
}
$post_message =~ s/\s+$//; # Remove all trailing whitespace
$post_message .= "\n[$CFG->{markup_signature_tag}]" if $IN->param('post_append_signature');
my $post_subject = $IN->param('post_subject');
unless ($post_subject =~ /[^\s\xa0]/) { # \xa0 is Alt-0160 - a non-breaking space, in HTML
if ($CFG->{post_require_subject}) {
$GForum::Template::VARS{post_error_no_subject} = 1;
return GForum::do_func('post_reply_write');
}
else {
$post_subject = GForum::language("NO_SUBJECT");
}
}
my $reply_notify = 1 if $IN->param('post_reply_notify');
$reply_notify ||= 0;
my $root_id = $parent->{post_root_id} || $parent->{post_id};
if ($USER and $USER->{user_forum_permission} < FORUM_PERM_MODERATOR) {
# If you have moderator privileges you can post whether ot not the post is locked.
my $locked;
if ($parent->{post_root_id}) { # The parent isn't the root post
$locked = $DB->table('Post')->get($root_id, 'ARRAY', ['post_locked'])->[0];
}
else { # If the parent is the root post this saves a select to get the root post
$locked = $parent->{post_locked};
}
if ($locked) {
return($page->{locked}, { error => GForum::language('POST_LOCKED') });
}
}
my $post_style = $IN->param('post_style') || 0;
if ($post_style > $parent->{forum_style} or $parent->{forum_style} == 2 and $post_style == 1) {
$post_style = $parent->{forum_style};
}
my ($user_id, $user_username, $guest_username);
if ($USER) {
$user_id = $USER->{user_id};
$user_username = $USER->{user_username};
}
else {
unless ($user_id = $IN->param('anon_id') and $DB->table('User')->count({ user_id => $user_id, user_status => ANONYMOUS, user_enabled => 1 })) {
$user_id = $DB->table('User')->select(user_id => { user_status => ANONYMOUS, user_enabled => 1 })->fetchrow;
}
if ($CFG->{post_guest_custom_username} and $guest_username = $IN->param('guest_username')) {
if (!GForum::Authenticate::auth('valid_username', $guest_username)) {
$GForum::Template::VARS{post_error_bad_guest_username} = $GForum::Authenticate::Auth_Error;
return GForum::do_func('post_reply_write');
}
}
my $u = $DB->table('User')->get($user_id, 'ARRAY', ['user_username']);
$user_username = $u->[0] if $u;
}
$user_id or return($page->{anonymous_not_configured} => { error => GForum::language('POST_ANONYMOUS_DISABLED') });
if (my $pid = $DB->table('Post')->select(post_id => { post_unique_id => $temp_id })->fetchrow) {
require GForum::Post::View;
my $post = GForum::Post::View::get($pid);
return($page->{already_posted} => $post);
}
my %data = (
user_id_fk => $user_id,
post_username => $user_username,
(defined $guest_username ? (guest_username => $guest_username) : ()),
forum_id_fk => $forum_id,
post_unique_id => $temp_id,
post_root_id => $root_id,
post_father_id => $parent_id,
post_depth => $parent->{post_depth} + 1,
post_time => time,
post_subject => $post_subject,
post_message => $post_message,
post_style => $post_style,
post_ip => $ENV{REMOTE_ADDR},
post_last_edit_username => undef,
post_last_edit_time => undef,
post_anonymous_email => (scalar $IN->param('post_anonymous_email')),
post_locked => 0,
post_reply_notify => $reply_notify,
post_icon => $post_icon
);
my $Post = $DB->table('Post');
my $cols = $Post->cols;
for my $col (keys %$cols) {
next if $cols->{$col}->{protect} or exists $data{$col} or not defined(my $val = $IN->param($col));
$data{$col} = $val;
}
my $post_id = ($DB->table('Post')->insert(\%data) or die $GT::SQL::error)->insert_id;
my @attach_errors;
my $pa = $DB->table('PostAttachment');
# Get all the temporary attachments associated with this ID so that we can
# transfer the attachments from TempAttachment to PostAttachment
my $sth = $DB->table('TempAttachment')->select('tempatt_id', { tempatt_msg_id => $temp_id });
while (my $tempatt_id = $sth->fetchrow_array) {
$pa->insert({ tempatt_id => $tempatt_id, post_id_fk => $post_id })
or push @attach_errors, delete $pa->{attachment_error};
}
my $notified;
if ($parent->{post_reply_notify} and $parent->{user_id_fk} and not ($USER and $USER->{user_id} == $parent->{user_id_fk})) {
my ($email_addr, $username, $template) = @{$DB->table('User')->get($parent->{user_id_fk}, 'ARRAY', ['user_email', 'user_username', 'user_template']) || []};
if ($email_addr) {
$notified = $parent->{user_id_fk};
my $post = { post_id => $post_id, %data, user_email => $email_addr, user_username => $username };
for (keys %$parent) { # This should add the forum and category columns
$post->{$_} = $parent->{$_} unless exists $post->{$_};
$post->{"parent_$_"} = $parent->{$_};
}
$post->{post_message_text} = $post->{post_message};
GForum::Post::normalize($post);
GForum::Post::plain_text(\$post->{post_message_text}, $post);
require GT::Mail::Editor;
my $email = GT::Mail::Editor->new(dir => "$CFG->{admin_root_path}/templates", template => ($template or $CFG->{default_template_set} or 'default'));
$email->load("reply.eml");
my $headers = $email->headers;
my %head;
while (my ($k, $v) = each %$headers) {
my $val = $v; # Copy it
$val = GForum::Template->parse("string", $post, { string => $val });
$head{$k} = $val;
}
my $body = $email->body;
$body = GForum::Template->parse("string", $post, { string => $body });
$CFG->{smtp_server} or $CFG->{mail_path} or die 'No mail path or SMTP server set!';
$head{To} ||= $email_addr;
$head{From} ||= $CFG->{admin_email};
require GT::Mail;
my $mailer = GT::Mail->new(
%head,
msg => $body,
($CFG->{smtp_server} ? (smtp => $CFG->{smtp_server}) : (sendmail => $CFG->{mail_path}))
);
local $@;
my $sent = eval { $mailer->send };
if (!$sent || $@ and $GForum::DEBUG || $CFG->{debug_level}) {
die $@ || $mailer->error;
}
}
}
# Handle watched threads:
my $condition = GT::SQL::Condition->new(thread_id_fk => '=' => $root_id, tw_last_mail => '<' => \'user_last_seen');
$sth = $DB->table('ThreadWatch', 'User')->select('user_id', 'user_email', 'user_template' => $condition);
my (%tplset, @notified);
while (my $row = $sth->fetchrow_arrayref) {
next if $notified and $notified == $row->[0]; # Skip the user if they received a reply notification
push @{$tplset{$row->[2]}}, $1 if $row->[0] != $user_id and $row->[1] =~ /([^<>"\s]+\@[\.a-zA-Z0-9-]+)/;
push @notified, $row->[0];
}
if (keys %tplset) {
my $PostUser = $DB->table('Post', 'User');
my $post = $PostUser->select(left_join => { post_id => $post_id })->fetchrow_hashref;
{
my $root = $PostUser->select(left_join => { post_id => $root_id })->fetchrow_hashref;
my $forum = $DB->table('Forum', 'Category')->select({ forum_id => $post->{forum_id_fk} })->fetchrow_hashref;
GForum::Forum::normalize($forum);
$post->{post_message_text} = $post->{post_message};
GForum::Post::normalize([$root, $post]);
GForum::Post::plain_text(\$post->{post_message_text}, $post);
for (keys %$root) {
$post->{"root_$_"} = $root->{$_};
}
for (keys %$forum) {
$post->{$_} = $forum->{$_};
}
}
for my $tpl (keys %tplset) {
require GT::Mail::Editor;
my $email = GT::Mail::Editor->new(dir => "$CFG->{admin_root_path}/templates", template => ($tpl || $CFG->{default_template_set} || 'default'));
$email->load("thread_notify.eml");
my $headers = $email->headers;
my %head;
while (my ($k, $v) = each %$headers) {
my $val = $v; # Copy it
$val = GForum::Template->parse("string", $post, { string => $val });
$head{$k} = $val;
}
my $body = GForum::Template->parse("string", $post, { string => $email->body });
$CFG->{smtp_server} or $CFG->{mail_path} or die 'No mail path or SMTP server set!';
$head{From} ||= $CFG->{admin_email};
$head{Bcc} = join ', ', @{$tplset{$tpl}};
require GT::Mail;
my $mailer = GT::Mail->new(
%head,
msg => $body,
($CFG->{smtp_server} ? (smtp => $CFG->{smtp_server}) : (sendmail => $CFG->{mail_path}))
);
local $@;
my $sent = eval { $mailer->send };
if (!$sent || $@ and $GForum::DEBUG || $CFG->{debug_level}) {
die $@ || $mailer->error;
}
}
$DB->table('ThreadWatch')->update({ tw_last_mail => time }, { user_id_fk => \@notified });
}
my $function = "post_confirm"; # 'post_confirm' isn't actually an option, it's handled here
if (not @attach_errors and $USER and exists $CFG->{functions}->{$USER->{user_do_after_post}}) {
$function = $USER->{user_do_after_post};
}
unless ($function eq "post_confirm" or $function eq "post_confirm_post") {
if (substr($function, 0, 4) eq 'post') {
$IN->param(post => $post_id); # post actions are going to need the post ID in $IN->param.
$HIDDEN{post} = $post_id;
}
elsif (substr($function, 0, 5) eq 'forum') {
$IN->param(forum => $forum_id);
$HIDDEN{forum} = $forum_id;
}
$HIDDEN{do} = $function;
my $url = $CFG->{cgi_root_url} . "/gforum.cgi?" . ${GForum::hidden('query')};
print $IN->redirect($url); # If using plugin hooks, setting up a pre hook that prints a header will get around this.
return;
}
else {
return(
$page->{reply_post} => {
forum_name => $parent->{forum_name},
forum_id => $parent->{forum_id},
cat_name => $parent->{cat_name},
cat_full_name => $parent->{cat_full_name},
cat_id => $parent->{cat_id},
post_id => $post_id,
post_subject => $post_subject,
post_message => $post_message,
(@attach_errors ? (attachment_errors => GForum::language('ATTACHMENT_FAILED', join "
\n", @attach_errors)) : ()),
}
);
}
}
sub edit_post {
shift;
uc $ENV{REQUEST_METHOD} eq 'POST' or die "Unable to post with a GET method";
my ($do, $func) = @_;
my $page = $func->{page};
my $post_id = $IN->param('post')
or return GForum::Post->edit('post_edit', $CFG->{functions}->{post_edit}, 'POST_BAD_POST');
my $post = $DB->table('Post' => 'Forum' => 'Category')->select({ post_id => $post_id })->fetchrow_hashref
or return GForum::Post->edit('post_edit', $CFG->{functions}->{post_edit}, 'POST_BAD_POST');
require GForum::Forum;
GForum::Forum::normalize($post);
my $post_icon = $IN->param('post_icon');
my $good;
for (values %{$CFG->{post_icons}}) {
$good++, last if $_ eq $post_icon;
}
$post_icon = undef if not $good;
my $post_message = $IN->param('post_message');
if ($post_message and $IN->param('advanced_editor')) {
$post_message = GForum::Convert::advanced_editor_convert($post_message);
}
$post_message =~ s/\s+$//; # Remove all trailing whitespace
$post_message .= "\n[$CFG->{markup_signature_tag}]" if $IN->param('post_append_signature');
my $post_subject = $IN->param('post_subject');
unless ($post_subject =~ /[^\s\xa0]/) { # \xa0 is Alt-0160 - a non-breaking space, in HTML
if ($CFG->{post_require_subject}) {
$GForum::Template::VARS{post_error_no_subject} = 1;
return GForum::do_func('post_write');
}
else {
$post_subject = GForum::language("NO_SUBJECT");
}
}
unless ($USER->{user_forum_permission} >= FORUM_PERM_MODERATOR
or ($USER->{user_id} == $post->{user_id_fk}
and
$post->{forum_allow_user_edit} % 2 # 1 is edit, 3 is edit & delete, but 0 and 2 do not allow editing
and
(!$post->{forum_edit_timeout} or ($post->{post_time} + $post->{forum_edit_timeout} * 60) > time)
)
) {
$GForum::Template::VARS{permission_denied_reason} = GForum::language('POST_EDIT_TIME_EXPIRED');
return GForum::do_func('permission_denied');
}
my $edit_time = time;
my $post_style = $IN->param('post_style');
if ($post_style > $post->{forum_style} or $post->{forum_style} == 2 and $post_style == 1) {
$post_style = $post->{forum_style};
}
my $post_reply_notify = $IN->param('post_reply_notify') && 1 || undef;
my %data = (
post_subject => $post_subject,
post_message => $post_message,
post_icon => $post_icon,
(defined $post_style ? (post_style => $post_style) : ()),
post_last_edit_username => $USER->{user_username},
post_last_edit_time => $edit_time,
post_reply_notify => $post_reply_notify
);
my $Post = $DB->table('Post');
my $cols = $Post->cols;
for my $col (keys %$cols) {
next if $cols->{$col}->{protect} or exists $data{$col} or not defined(my $val = $IN->param($col));
$data{$col} = $val;
}
$DB->table('Post')->update(
\%data,
{
post_id => $post->{post_id}
}
) or die "Unable to update record: $GT::SQL::error";
# Reselect the post to get the updates (in case a subclass changed more than what the update contained):
$post = $DB->table('Post' => 'Forum' => 'Category')->select({ post_id => $post_id })->fetchrow_hashref;
require GForum::Forum;
GForum::Forum::normalize($post);
$DB->table('EditLog')->insert({
post_id_fk => $post->{post_id},
user_id_fk => $USER->{user_id},
edit_time => $edit_time,
edit_reason => scalar($IN->param('edit_reason')) || ''
});
my @attach_errors;
my $pa = $DB->table('PostAttachment');
# Get all the temporary attachments that we added to this post so that we
# can transfer the attachments from TempAttachment to PostAttachment
my $temp_id = $IN->param('temp_id');
my $sth = $DB->table('TempAttachment')->select('tempatt_id', { tempatt_msg_id => $temp_id });
while (my $tempatt_id = $sth->fetchrow_array) {
$pa->insert({ tempatt_id => $tempatt_id, post_id_fk => $post_id })
or push @attach_errors, delete $pa->{attachment_error};
}
my $function = "post_confirm"; # 'post_confirm' isn't actually an option, it's handled here
if (not @attach_errors and $USER and exists $CFG->{functions}->{$USER->{user_do_after_post}}) {
$function = $USER->{user_do_after_post};
}
unless ($function eq "post_confirm" or $function eq "post_confirm_post") {
if (substr($function, 0, 4) eq 'post') {
$IN->param(post => $post_id); # post actions are going to need the post ID in $IN->param.
$HIDDEN{post} = $post_id;
}
elsif (substr($function, 0, 5) eq 'forum') {
$IN->param(forum => $post->{forum_id_fk});
$HIDDEN{forum} = $post->{forum_id_fk};
}
$HIDDEN{do} = $function;
my $url = $CFG->{cgi_root_url} . "/gforum.cgi?" . ${GForum::hidden('query')};
print $IN->redirect($url); # If using plugin hooks, setting up a pre hook that prints a header will get around this.
return;
}
else {
return(
$page->{edit_post} => $post
);
}
}
# Converts a scalar reference into it's HTML equivelant. Used when switching to
# the advanced editor. Converts markup into HTML. Note, however, that this
# differs in one way: URL's, which markup converts to: gforum.cgi?url=... do not
# forward through gforum.cgi and are kept as the original URL.
sub _post_message_html {
my ($message, $html) = @_;
GForum::Convert::escape_html($$message) unless $html;
my $save = $GForum::Convert::Leave_Dot;
$GForum::Convert::Leave_Dot = 1;
GForum::Convert::convert_markup($message);
$GForum::Convert::Leave_Dot = $save;
# Convert the URL's: has to become . Also, the .... has to be url unescaped.
$$message =~ s//qq||/eg;
$$message =~ s/\r?\n/
/g; # That space keeps IE from condensing multiple
's into 1. It is only needed where you have
, but that regex would slow the conversion down quite a bit.
$$message =~ s/^( +)/' ' x length $1/gem;
$message;
}
# Takes one argument - a hashref consisting of two or three keys - 'forum_id',
# 'post_id' (only required for edit mode), and 'temp_id'.
# The returns true if you can't add attachments to that post for a variety of
# reasons. The value returned is actually the reason that attachments can't be
# attached to the post (or a new post of the forum).
sub cant_attach {
my @args = @_;
GT::Plugins->dispatch($CFG->{admin_root_path} . '/Plugins/GForum', 'post_cant_attach', sub { return _plg_cant_attach(@args) });
}
sub _plg_cant_attach {
$Attachment_Max = undef;
my $args = shift;
my ($forum_id, $temp_id, $post_id) = @$args{qw/forum_id temp_id post_id/};
my $forum = $DB->table('Forum')->get($forum_id)
or die "No such forum ID '$forum_id'";
$USER or $forum->{forum_allow_guest_attachments}
or return GForum::language('ATTACHMENTS_NOT_ALLOWED-GUESTS');
if (!$forum->{forum_allow_attachments}) {
return GForum::language('ATTACHMENTS_NOT_ALLOWED');
}
if ($forum->{forum_attachments}) { # If this is 0 it means unlimited attachments are allowed per post
my $count = $DB->table('TempAttachment')->count({ tempatt_msg_id => $temp_id });
# If this is an edit, we need to add the attachments already posted to the count
if ($post_id) {
$count += $DB->table('PostAttachment')->count({ post_id_fk => $post_id });
}
if ($count >= $forum->{forum_attachments}) {
return GForum::language('ATTACHMENT_COUNT_EXCEEDED');
}
}
if ($CFG->{post_attachments_max_size}) {
my $total_size = $DB->table('PostAttachment')->_attachments_size;
if ($total_size >= $CFG->{post_attachments_max_size}) {
return GForum::language('ATTACHMENT_LIMIT_EXCEEDED');
}
$Attachment_Max = $CFG->{post_attachments_max_size} - $total_size;
$Attachment_Max = $CFG->{post_attachment_max_size} if $CFG->{post_attachment_max_size} < $Attachment_Max;
}
if ($CFG->{post_attachments_max_count}) {
my $total_count = $DB->table('PostAttachment')->count() + $DB->table('TempAttachment')->count({ tempatt_type => 'Post' });
if ($total_count > $CFG->{post_attachments_max_count}) {
return GForum::language('ATTACHMENT_COUNT_EXCEEDED');
}
}
return;
}
sub generate_temp_id {
require GT::MD5;
my $id = GT::MD5::md5_hex(time . $$ . rand(16000));
while ($DB->table('Post')->count(post_unique_id => $id) > 0) {
$id = GT::MD5::md5_hex(time . $$ . rand(16000));
}
return $id;
}
1;