# ================================================================== # Gossamer Forum - Advanced web community # # Website : http://gossamer-threads.com/ # Support : http://gossamer-threads.com/scripts/support/ # Revision : $Id: View.pm,v 1.25 2002/03/26 00:57:42 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 viewing a post/thread. # package GForum::Post::View; use strict; use GForum qw/:user :forum $DB $IN $CFG $USER $GUEST $SESSION/; use GForum::Post; use GForum::Authenticate; sub view { shift; # Discard package name my $meth; my $requested = lc $IN->param('mode'); if ($requested) { if ($requested eq 'threaded') { $meth = 'view_threaded'; } elsif ($requested eq 'flat') { $meth = 'view_flat'; } } elsif ($USER) { $meth = "view_" . ('threaded', 'flat')[$USER->{user_default_post_display}]; } $meth ||= $CFG->{post_display_default}; $meth =~ s/^post_//; $meth = "view_threaded" unless $meth eq 'view_threaded' or $meth eq 'view_flat'; # A fallback default - happens only if the configuration is broken, in which case you probably have bigger problems than this... $meth = "post_$meth"; GForum::do_func($meth); } # Displays a thread in printable mode. The entire thread is displayed without # any of the excess images/header/footer/etc. sub view_printable { shift; my ($do, $func) = @_; my $page = $func->{page}; # The easiest way to do this is to just fake a view_flat call ;-) $IN->param(mh => -1); # -1 is unlimited - in other words, view the whole thread. # page is the only argument in the function information that view_flat cares about. # Just pass in view_printable's $page ;-) view_flat('GForum::Post::View', 'post_view_flat', { page => $page }); } sub view_flat { shift; my ($do, $func) = @_; my $page = $func->{page}; my $post_id = $IN->param('post'); my $pu = $DB->table('Post' => 'User'); my $literal = $IN->param('literal'); if ($post_id =~ s/^last-(?=\d+$)//) { $IN->param(page => 'last'); # We can't set the anchor (#last) but at least we'll be on the right page. } $post_id and my $post = $pu->select('left_join', { post_id => $post_id })->fetchrow_hashref or return($page->{no_such_post}, { error => GForum::language('POST_DOES_NOT_EXIST') }); my $root = ($post->{post_root_id} ? $pu->select('left_join', { post_id => $post->{post_root_id} })->fetchrow_hashref : $post ); # page and mh are for the flat post display my ($pg, $mh); $mh = $IN->param('mh'); $mh = ($USER and $USER->{user_default_mh_flatpost} or $CFG->{default_mh_post}) if not $mh or $mh !~ /^-?\d+$/; if ($mh >= 0) { $pg = $IN->param('page') || 1; if ($pg eq 'last') { my $num_results = 1 + $pu->count({ post_root_id => $root->{post_id} }); $pg = int($num_results / $mh); ++$pg if $num_results % $mh; } elsif ($pg eq 'unread') { # We have to figure out what page the first unread post is on. my $Post = $DB->table('Post'); $Post->select_options('ORDER BY post_time ASC'); my $posts = $Post->select('post_id', 'post_time', 'forum_id_fk', 'user_id_fk' => { post_root_id => $root->{post_id} })->fetchall_hashref; # $posts contains the children, add the root (but only the things that are needed) unshift @$posts, { post_id => $root->{post_id}, post_time => $root->{post_time}, forum_id_fk => $root->{forum_id_fk}, user_id_fk => $root->{user_id_fk} }; GForum::Post::_calc_new($posts); $pg = 1; # If we don't find any new, show the first page. for (my $i = 0; $i < @$posts; $i++) { if ($posts->[$i]->{post_new}) { $pg = int(($i+1) / $mh); ++$pg if ($i+1) % $mh; last; } } } else { $pg = 1 if $pg =~ /\D/; } } # The "sb" and "so" are for the forum list, not the post. my ($sb, $so); $sb = $IN->param('sb') || 'post_latest_reply'; $sb = 'post_latest_reply' unless exists $pu->{tables}->{$DB->prefix . "Post"}->{schema}->{cols}->{$sb}; $so = uc($IN->param('so') || ''); $so = 'ASC' unless $so eq 'ASC' or $so eq 'DESC'; my $forum_id = $post->{forum_id_fk}; my $forum = $DB->table('Forum', 'Category')->select({ forum_id => $forum_id })->fetchrow_hashref; require GForum::Forum; GForum::Forum::normalize($forum); if ($USER) { if ($USER->{user_forum_permission} >= FORUM_PERM_MODERATOR) { $post->{user_perm_moderator} = 1; $post->{user_perm_edit} = 1; $post->{user_perm_delete} = 1; $root->{user_perm_moderator} = 1; $root->{user_perm_edit} = 1; $root->{user_perm_delete} = 1; } else { if ($USER->{user_id} == $post->{user_id_fk} and $forum->{forum_allow_user_edit} and (!$forum->{forum_edit_timeout} or ($post->{post_time} + $forum->{forum_edit_timeout} * 60) > time)) { $post->{user_perm_edit} = 1 if $forum->{forum_allow_user_edit} % 2; # 1 or 3 $post->{user_perm_delete} = 1 if $forum->{forum_allow_user_edit} >= 2; } # Set the permissions for the root post as well if ($USER->{user_id} == $root->{user_id_fk} and $root->{post_id} != $post->{post_id} and $forum->{forum_allow_user_edit} and (!$forum->{forum_edit_timeout} or ($root->{post_time} + $forum->{forum_edit_timeout} * 60) > time)) { $root->{user_perm_edit} = 1 if $forum->{forum_allow_user_edit} % 2; # 1 or 3 $root->{user_perm_delete} = 1 if $forum->{forum_allow_user_edit} >= 2; } } } my $is_moderator = 1 if $USER and $USER->{user_forum_permission} >= FORUM_PERM_MODERATOR; my $num_hits; # Not the results - the results will be limited my @post_loop; my $add_root = 0; { # This block will be redo()ne if the page requested lies is outside of the range. $pu->select_options("ORDER BY post_time ASC"); my $post_num = 1; if ($mh > 0) { # Don't limit if the maxhits is negative - negative means infinite. my $limit = $mh; if (not $IN->param('page') and $post->{post_root_id}) { # Page wasn't provided, so calculate the right page number. my $preceeding = 1 + $DB->table('Post')->count(GT::SQL::Condition->new(post_root_id => '=' => $post->{post_root_id}, post_time => '<' => $post->{post_time})); $pg = 1 + int($preceeding / $mh); } my $offset = $mh * ($pg - 1); $post_num += $offset; if (not $offset) { $limit--; # We have to tack the root post on the front $add_root++; $post_num++; } else { $offset--; # The select won't be returning the root post, so make up for that. } $pu->select_options("LIMIT $offset, $limit"); } else { $add_root++; $post_num++; } my $sth = $pu->select(left_join => { post_root_id => $root->{post_id} }); $num_hits = 1 + $DB->table('Post')->count({ post_root_id => $root->{post_id} }); while (my $rec = $sth->fetchrow_hashref) { $rec->{post_style} = 0 if $literal; $rec->{post_num} = $post_num++; $rec->{post_locked} = 1 if $root->{post_locked}; if ($USER) { if ($USER->{user_forum_permission} >= FORUM_PERM_MODERATOR) { $rec->{user_perm_moderator} = 1; $rec->{user_perm_edit} = 1; $rec->{user_perm_delete} = 1; } else { if ($USER->{user_id} == $rec->{user_id_fk}) { if ($forum->{forum_allow_user_edit} and (!$forum->{forum_edit_timeout} or ($rec->{post_time} + $forum->{forum_edit_timeout} * 60) > time)) { $rec->{user_perm_edit} = 1 if $forum->{forum_allow_user_edit} % 2; # 1 or 3 $rec->{user_perm_delete} = 1 if $forum->{forum_allow_user_edit} >= 2; } } } } push @post_loop, $rec; } if (not @post_loop and $pg > 1) { $pg = 1; redo; } } if ($add_root) { $root->{post_style} = 0 if $literal; $root->{post_num} = 1; if ($USER) { if ($USER->{user_forum_permission} >= FORUM_PERM_MODERATOR) { $root->{user_perm_moderator} = 1; $root->{user_perm_edit} = 1; $root->{user_perm_delete} = 1; } else { if ($USER->{user_id} == $root->{user_id_fk}) { if ($forum->{forum_allow_user_edit} and (!$forum->{forum_edit_timeout} or ($root->{post_time} + $forum->{forum_edit_timeout} * 60) > time)) { $root->{user_perm_edit} = 1 if $forum->{forum_allow_user_edit} % 2; # 1 or 3 $root->{user_perm_delete} = 1 if $forum->{forum_allow_user_edit} >= 2; } } } } unshift @post_loop, $root; } else { # The root will be normalized if we are adding it, but if we aren't, normalize it. GForum::Post::normalize($root); } $DB->table('PostView')->update({ post_thread_views => \'post_thread_views + 1' }, { post_id_fk => $root->{post_id} }); my $str = "(" . (join ",", map $_->{post_id}, @post_loop) . ")"; $DB->table('PostView')->update({ post_views => \'post_views + 1' }, GT::SQL::Condition->new(post_id_fk => 'IN' => \$str)); GForum::Post::normalize(\@post_loop); my (%on_page, $need_save, $first_new); for (@post_loop) { # This is always ordered # Set the "post_father_on_page" for posts so that they can link to #father_id instead of gforum.cgi?do=... $on_page{$_->{post_id}} = 1; $_->{post_father_on_page} = 1 if $_->{post_father_id} and $on_page{$_->{post_father_id}}; # Update the session if needed to make posts not new anymore # This has to be done after normalizing. if ($SESSION and $_->{post_new}) { # If it's new, update the session so that it won't be new anymore. A post that $USER made will never be new. $_->{first_new} = 1 if not $first_new++; my $data = $SESSION->data(); $data->{posts}->{$_->{forum_id_fk}}->{$_->{post_id}} = time; $data->{roots}->{$_->{forum_id_fk}}->{$root->{post_id}} = time unless $root->{user_id_fk} == $USER->{user_id}; $need_save++; } } if ($SESSION) { my $data = $SESSION->data(); my $forum_last = $data->{usernew}->{$forum_id} || 0; my @insertions; # [$user_id, $post_id, $forum_id] for (@post_loop) { if ($_->{post_new}) { push @insertions, [$USER->{user_id}, $_->{post_id}, $_->{forum_id_fk}, $_->{post_root_id} || $_->{post_id}]; } } $DB->table('PostNew')->insert_multiple(['user_id_fk', 'post_id_fk', 'forum_id_fk', 'root_id_fk'], @insertions) if @insertions; } $SESSION->save() if $need_save; my ($next, $prev) = next_prev($root->{post_id}, $root->{$sb}, $root->{forum_id_fk}, $sb, $so); my %root; @root{map "root_$_", keys %$root} = values %$root; return( $page->{view} => { root_id => $root->{post_id}, requested_post_id => $post_id, %$forum, post_loop => \@post_loop, next => $next, prev => $prev, num_posts => $num_hits, this_page => $pg, sb => $sb, so => $so, mh => $mh, forum_view => scalar $IN->param('forum_view'), %root } ); } sub view_threaded { shift; # Discard the package name my ($do, $func) = @_; my $page = $func->{page}; my $post_id = $IN->param('post'); if ($post_id =~ s/^last-(?=\d+$)//) { my $pt = $DB->table('Post'); $pt->select_options("ORDER BY post_time DESC"); $pt->select_options("LIMIT 1"); my $pid = $pt->select(post_id => { post_root_id => $post_id })->fetchrow; $post_id = $pid if $pid; # This won't be set if you're asking for the last when there is only the root } my $pu = $DB->table('Post' => 'User'); $post_id and my $post = $pu->select('left_join', { post_id => $post_id })->fetchrow_hashref or return($page->{no_such_post}, { error => GForum::language('POST_DOES_NOT_EXIST') }); my $root = ($post->{post_root_id} ? $pu->select('left_join', { post_id => $post->{post_root_id} })->fetchrow_hashref : $post ); $post->{post_locked} = 1 if $root->{post_locked}; my ($sb, $so); $sb = $IN->param('sb') || 'post_latest_reply'; $sb = 'post_latest_reply' unless exists $pu->{tables}->{$DB->prefix . "Post"}->{schema}->{cols}->{$sb}; $so = uc($IN->param('so') || ''); $so = 'ASC' unless $so eq 'ASC' or $so eq 'DESC'; my $forum_id = $post->{forum_id_fk}; my $forum = $DB->table('Forum', 'Category')->select({ forum_id => $forum_id })->fetchrow_hashref; require GForum::Forum; GForum::Forum::normalize($forum); my $user; unless ($post->{user_id}) { require GForum::User; my $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; } my $literal = $IN->param('literal'); $post->{post_style} = 0 if $literal; my $pv = $DB->table('PostView'); if ($post->{post_root_id}) { # It has a root post and therefore is not the root post, which means two queries are needed. $pv->update({ post_thread_views => \'post_thread_views + 1' }, { post_id_fk => $post->{post_root_id} }); $pv->update({ post_views => \'post_views + 1' }, { post_id_fk => $post->{post_id} }); } else { # It's the root post - one update to update both fields will suffice. $pv->update({ post_thread_views => \'post_thread_views + 1', post_views => \'post_views + 1' }, { post_id_fk => $post->{post_id} }); } GForum::Authenticate::auth('forum_permission', $forum_id); if ($USER) { if ($USER->{user_forum_permission} >= FORUM_PERM_MODERATOR) { $post->{user_perm_moderator} = 1; $post->{user_perm_edit} = 1; $post->{user_perm_delete} = 1; $root->{user_perm_moderator} = 1; $root->{user_perm_edit} = 1; $root->{user_perm_delete} = 1; } else { if ($USER->{user_id} == $post->{user_id_fk}) { if ($forum->{forum_allow_user_edit} and (!$forum->{forum_edit_timeout} or ($post->{post_time} + $forum->{forum_edit_timeout} * 60) > time)) { $post->{user_perm_edit} = 1 if $forum->{forum_allow_user_edit} % 2; # 1 or 3 $post->{user_perm_delete} = 1 if $forum->{forum_allow_user_edit} >= 2; } } } } my $tree = $DB->table('Post')->tree; my $post_loop = [$root, @{$tree->children(id => $root->{post_id}, roots_only => 1, select_from => $DB->table('Post', 'User'), sort_col => "post_time")}]; for (@$post_loop) { $_->{this_post} = $_->{post_id} == $post_id; } GForum::Post::normalize($post == $root ? $post_loop : [$post, @$post_loop]); my ($next, $prev) = next_prev($root->{post_id}, $root->{$sb}, $root->{forum_id_fk}, $sb, $so); if ($SESSION) { if ($post->{post_new}) { my $data = $SESSION->data(); $data->{posts}->{$post->{forum_id_fk}}->{$post->{post_id}} = time; $data->{roots}->{$post->{forum_id_fk}}->{$post->{post_root_id} || $post->{post_id}} = time; $SESSION->save(); # Insert into PostNew if the post was made since the last forum view my $forum_last = $data->{usernew}->{$forum_id} || 0; if ($post->{post_time} > $forum_last) { $DB->table('PostNew')->insert({ user_id_fk => $USER->{user_id}, post_id_fk => $post->{post_id}, root_id_fk => $post->{post_root_id} || $post->{post_id}, forum_id_fk => $post->{forum_id_fk} }); } } } return($page->{view} => { %$post, threads => $post_loop, %$forum, next => $next, prev => $prev, sb => $sb, so => $so, forum_view => scalar $IN->param('forum_view'), root_subject => $root->{post_subject} }); } # sub preview { } is in GForum::Post::Write, since it's used when writing # Takes 5 arguments: root post ID, value, forum ID, sort-by column, sort order (ASC or DESC). # "value" should be the value of sort-by column of the _ROOT_ post. # Returns two numbers - the ID's of the next post (first) and previous post (second). # "Next" and "Previous" are defined as the posts coming next or before in the list, respectively. # # You can pass two additional optional arguments - both true or false. The first, if true, # indicates that the "next" ID should be returned. The second, if true, indicates that the "previous" # ID should be returned. So, calling next_prev($value,$forum_id,$sb,$so,0,1) would return just the # previous ID, while next_prev($value,$forum_id,$sb,$so) would return the default - both IDs. sub next_prev { my ($post_id, $value, $forum_id, $sb, $so) = splice @_, 0, 5; my ($want_next, $want_prev) = (1, 1); $want_next = shift if @_; $want_prev = shift if @_; my $table = $DB->table('Post'); $sb = "post_username" if $sb eq "user_username"; $value = 2_000_000_000 - $value if $sb eq 'post_latest_reply'; my $equals = $table->select(post_id => { $sb => $value, post_root_id => 0, forum_id_fk => $forum_id })->fetchall_arrayref; my ($found, $prev, $next); for (@$equals) { if ($_->[0] == $post_id) { $found = 1; $want_next ? next : last; } if ($found) { $next = $_->[0]; last; } $prev = $_->[0] if $want_prev; } $prev = undef if not $found; if ($want_prev and not $prev) { # Prev was not found in the list equal to the current term my $cond = new GT::SQL::Condition; $cond->add($sb => ($so eq 'DESC' ? '>' : '<') => $value); $cond->add(post_root_id => 0); $cond->add(forum_id_fk => $forum_id); my $prev_val = $table->select( ($so eq 'DESC' ? 'MIN' : 'MAX') . "($sb)", $cond )->fetchrow; $cond = new GT::SQL::Condition; $cond->add(post_root_id => 0); $cond->add(forum_id_fk => $forum_id); $cond->add($sb => $prev_val); my $aref = $table->select(post_id => $cond)->fetchall_arrayref; if (@$aref) { $prev = $aref->[-1][0]; } } if ($want_next and not $next) { # Next was not found in the list equal to the current term my $cond = new GT::SQL::Condition; $cond->add($sb => ($so eq 'DESC' ? '<' : '>') => $value); $cond->add(post_root_id => 0); $cond->add(forum_id_fk => $forum_id); my ($next_val, $deleted); my ($val) = $table->select( ($so eq 'DESC' ? 'MAX' : 'MIN') . "($sb)", $cond )->fetchrow; $next_val = $val; $cond = new GT::SQL::Condition; $cond->add(post_root_id => 0); $cond->add(forum_id_fk => $forum_id); $cond->add($sb => $next_val); my $aref = $table->select(post_id => $cond)->fetchall_arrayref; if (@$aref) { $next = $aref->[0][0]; } } return (($want_next && $want_prev) ? ($next, $prev) : $want_next ? $next : $want_prev ? $prev : ()); } sub watch_thread { shift; # Discard the package name my ($do, $func) = @_; my $page = $func->{page}; my $thread_id = $IN->param('thread'); $thread_id and my $root = $DB->table('Post', 'User')->select(left_join => { post_id => $thread_id, post_root_id => 0 })->fetchrow_hashref or return( $page->{no_such_thread} => { error => GForum::language('THREAD_DOES_NOT_EXIST') } ); my $forum = $DB->table('Forum', 'Category')->select({ forum_id => $root->{forum_id_fk} })->fetchrow_hashref; require GForum::Forum; GForum::Forum::normalize($forum); @$root{keys %$forum} = values %$forum; my $tw = $DB->table('ThreadWatch'); unless ($tw->count({ user_id_fk => $USER->{user_id}, thread_id_fk => $root->{post_id} })) { $tw->insert({ user_id_fk => $USER->{user_id}, thread_id_fk => $root->{post_id}, tw_last_mail => 0 }); } return($page->{watch_thread} => $root); } sub download_attachment { shift; # Discard the package name my ($do, $func) = @_; my $page = $func->{page}; my $postatt_id; $postatt_id = $IN->param('postatt_id') and my $attachment = $DB->table('PostAttachment')->get($postatt_id) or return( $page->{no_such_post_attachment} => { error => GForum::language('ATTACHMENT_DOES_NOT_EXIST') } ); my $file = \do { local *FH; *FH }; my $dir = $attachment->{postatt_id} % 10; my $filename = "$CFG->{post_attachment_directory}/$dir/$attachment->{postatt_id}"; open $file, "<$filename" or die "Unable to open file: $!"; my $read; binmode $file; binmode STDOUT; require GT::MIMETypes; my $mime_type = GT::MIMETypes->guess_type ($attachment->{postatt_filename}); print $IN->header( -type => $mime_type, "Content-Disposition" => \("inline; filename=" . $IN->escape($attachment->{postatt_filename}) . "; size=$attachment->{postatt_size}"), "Content-Length" => $attachment->{postatt_size} ); { local $\; while ($read = read($file, my $chunk, 4096)) { print $chunk; } } return; } # Called from the templates with a post ID, this returns the post. sub get { my $post_id = shift; my $post = $DB->table('Post' => 'User')->select(left_join => { post_id => $post_id })->fetchrow_hashref or return; GForum::Post::normalize($post); my $forum = $DB->table('Forum' => 'Category')->select({ forum_id => $post->{forum_id_fk} })->fetchrow_hashref; require GForum::Forum; GForum::Forum::normalize($forum); return { %$post, %$forum }; } sub editlog { shift; my ($do, $func) = @_; my $page = $func->{page}; my $post_id = $IN->param('post') or return GForum::Post->view('post_view', $CFG->{functions}->{post_view}, 'POST_BAD_POST'); my $post = $DB->table('Post', 'Forum', 'Category')->select({ post_id => $post_id })->fetchrow_hashref or return GForum::Post->view('post_view', $CFG->{functions}->{post_view}, 'POST_BAD_POST'); require GForum::Forum; GForum::Forum::normalize($post); my $User = $DB->table('User'); if ($post->{user_id_fk}) { my $user = $User->select({ user_id => $post->{user_id_fk} })->fetchrow_hashref; @$post{keys %$user} = values %$user; } else { require GForum::User; my $user = GForum::User::blank_user({ user_username => $post->{post_username}, user_signature => $post->{post_signature_deleted}, user_title => \GForum::language('USER_DELETED') }); @$post{keys %$user} = values %$user; } GForum::Post::normalize($post); my $et = $DB->table('EditLog'); $et->select_options("ORDER BY edit_time ASC"); my $sth = $et->select({ post_id_fk => $post_id }); my @edits; while (my $edit = $sth->fetchrow_hashref) { $edit->{edit_date} = GForum::date($edit->{edit_time}); if (my $uid = $edit->{user_id_fk}) { my $user = $User->get($uid); GForum::User::normalize($user); @$edit{keys %$user} = values %$user; } else { require GForum::User; my $user = GForum::User::blank_user({ user_username => $post->{post_username}, user_title => \GForum::language('USER_DELETED'), user_signature => $post->{post_signature_deleted} }); @$edit{keys %$user} = values %$user; } push @edits, $edit; } return( $page->{editlog} => { editlog => \@edits, num_edits => scalar @edits, %$post } ); } 1;