# ==================================================================
# Gossamer Forum - Advanced web community
#
# Website : http://gossamer-threads.com/
# Support : http://gossamer-threads.com/scripts/support/
# Revision : $Id: Forum.pm,v 1.73.2.1 2003/01/30 22:08:44 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.
# ==================================================================
#
package GForum::Forum;
use strict;
use GForum qw/:user :forum $IN $DB $CFG $USER $GUEST $SESSION/;
use GT::Date;
use GT::AutoLoader;
$COMPILE{view} = __LINE__ . <<'END_OF_SUB';
sub view {
if ($USER and $USER->{user_forum_view}) {
GForum::do_func('forum_view_expanded');
}
else {
GForum::do_func('forum_view_collapsed');
}
}
END_OF_SUB
sub view_collapsed {
shift; # Discard the package name
my ($do, $func) = @_;
my $page = $func->{page};
my $forum_id = $IN->param('forum');
my $forum = $DB->table('Forum', 'Category')->select({ forum_id => $forum_id })->fetchrow_hashref
or return(
$page->{no_such_forum} => {
error => GForum::language('FORUM_DOES_NOT_EXIST')
}
);
normalize($forum);
$forum->{forum_style} = GForum::language((qw/FORUM_STYLE_PLAIN FORUM_STYLE_MARKUP FORUM_STYLE_HTML FORUM_STYLE_BOTH/)[$forum->{forum_style}]);
my $PostUser = $DB->table('Post', 'User');
my ($so, $sb, $pg, $mh);
$sb = $IN->param('sb') || 'post_latest_reply';
$sb = 'post_latest_reply' unless exists $PostUser->{tables}->{$DB->prefix . "Post"}->{schema}->{cols}->{$sb};
$so = uc($IN->param('so') || '');
$so = 'ASC' unless $so eq 'ASC' or $so eq 'DESC';
$pg = $IN->param('page') || 1;
$pg = 1 if $pg =~ /\D/;
$mh = $IN->param('mh');
$mh = ($USER and $USER->{user_default_mh_forum} or $CFG->{default_mh_forum}) if not $mh or $mh =~ /\D/;
$PostUser->select_options("ORDER BY $sb $so");
$PostUser->select_options("LIMIT " . ($mh * ($pg - 1)) . ", $mh");
my $sth;
if (uc $PostUser->{connect}->{driver} eq 'MYSQL') {
local $PostUser->{tables_ord}->[0] = "$PostUser->{tables_ord}->[0] /*!32312 USE INDEX (p_rfl) */"; # MySQL hack to use the intended index
$sth = $PostUser->select(left_join => { forum_id_fk => $forum_id, post_root_id => 0 });
}
else {
$sth = $PostUser->select(left_join => { forum_id_fk => $forum_id, post_root_id => 0 });
}
if ($USER) {
my $UserNew = $DB->table('UserNew');
my $now = time;
if ($UserNew->count({ forum_id_fk => $forum_id, user_id_fk => $USER->{user_id} })) {
$UserNew->update({ usernew_last => $now }, { forum_id_fk => $forum_id, user_id_fk => $USER->{user_id} });
}
else {
$UserNew->insert({ forum_id_fk => $forum_id, user_id_fk => $USER->{user_id}, usernew_last => $now });
}
if ($SESSION) {
$SESSION->{info}->{session_data}->{usernew}->{$forum_id} = $now;
$SESSION->save();
}
# Delete any "read" posts from the PostNew table in this forum
$DB->table('PostNew')->delete({ user_id_fk => $USER->{user_id}, forum_id_fk => $forum_id });
}
my $post_loop;
require GForum::Post;
while (my $ret = $sth->fetchrow_hashref) {
$ret->{post_subject} = GForum::language('POST_DELETED') if $ret->{post_deleted};
# The number of replies to this root post.
if ($USER) {
if ($USER->{user_forum_permission} >= FORUM_PERM_MODERATOR) {
$ret->{user_perm_moderator} = 1;
$ret->{user_perm_edit} = 1;
$ret->{user_perm_delete} = 1;
}
else {
if ($USER->{user_id} == $ret->{user_id_fk}) {
if ($forum->{forum_allow_user_edit} and (!$forum->{forum_edit_timeout} or ($ret->{post_time} + $forum->{forum_edit_timeout}) > time)) {
$ret->{user_perm_edit} = 1 if $forum->{forum_allow_user_edit} % 2; # 1 or 3
$ret->{user_perm_delete} = 1 if $forum->{forum_allow_user_edit} >= 2;
}
}
}
}
push @$post_loop, $ret;
}
GForum::Post::normalize($post_loop);
return(
$page->{view},
{
%$forum,
post_loop => $post_loop,
so => $so,
sb => $sb,
this_page => $pg,
mh => $mh,
}
);
}
$COMPILE{view_expandable} = __LINE__ . <<'END_OF_SUB';
sub view_expandable {
shift; # Discard the package name
my ($do, $func) = @_;
my $page = $func->{page};
my $forum_id = $IN->param('forum');
my $forum = $DB->table('Forum', 'Category')->select({ forum_id => $forum_id })->fetchrow_hashref
or return(
$page->{no_such_forum} => {
error => GForum::language('FORUM_DOES_NOT_EXIST')
}
);
normalize($forum);
my %hot; # root_id => 1 for hot threads
my %locked; # root_id => 1 for locked threads
# Look for and process expand/collapse instructions.
my $Expanded = $DB->table('Expanded');
my @whoami = $USER ? (user_id_fk => $USER->{user_id}) : (guest_id_fk => $GUEST->{guest_id});
my $sth = $Expanded->select('thread_id_fk', 'post_id_fk' => {@whoami});
my (%expanded_roots, %expanded_posts);
while (my $row = $sth->fetchrow_arrayref) {
if (my $pid = $row->[0]) { # This thread is expanded
$expanded_roots{$pid} = 1;
}
elsif ($pid = $row->[1]) { # This post is expanded
$expanded_posts{$pid} = 1;
}
}
if (my $explode = $IN->param('explode')) {
# Exploding a thread means converting each point into it's own row, instead
# of just remembering that the thread is to be fully expanded. This is usually
# used when choosing to collapse a thread at a certain point after the thread
# has been fully expanded.
if ($DB->table('Post')->count({ post_id => $explode })) {
my @ids = $explode;
my $cond = new GT::SQL::Condition(post_root_id => '=' => $explode, post_replies => '>=' => 1);
push @ids, $DB->table('Post')->select(post_id => $cond)->fetchall_list;
my @mult_cols = ($whoami[0], 'post_id_fk');
my @expand;
for (@ids) {
push @expand, [$whoami[1], $_] unless $expanded_posts{$_}++;
}
$Expanded->insert_multiple(\@mult_cols, @expand);
$Expanded->delete({ @whoami, thread_id_fk => $explode });
delete $expanded_roots{$explode};
}
}
if (my $expand = $IN->param('expand')) {
$Expanded->insert({ @whoami, post_id_fk => $expand }) unless $expanded_posts{$expand}++;
}
if (my @expand_thread = $IN->param('et')) {
my @expand;
for (@expand_thread) {
for (split ',', $_) { # et=1,2,3,4 will work
push @expand, [$whoami[1], $_] unless $expanded_roots{$_}++;
}
}
$Expanded->insert_multiple([$whoami[0] => 'thread_id_fk'], @expand);
}
elsif (my $expand_thread = $IN->param('expand_thread')) {
$Expanded->insert({ @whoami, thread_id_fk => $expand_thread }) unless $expanded_roots{$expand_thread}++;
}
if (my $collapse = $IN->param('collapse')) {
my $cond = GT::SQL::Condition->new($whoami[0] => '=' => $whoami[1]);
my $new_cond = GT::SQL::Condition->new(post_id_fk => '=' => $collapse, thread_id_fk => '=' => $collapse, 'OR');
$cond->add($new_cond);
$Expanded->delete($cond);
delete $expanded_roots{$collapse};
delete $expanded_posts{$collapse};
}
$forum->{forum_style} = GForum::language((qw/FORUM_STYLE_PLAIN FORUM_STYLE_MARKUP FORUM_STYLE_HTML FORUM_STYLE_BOTH/)[$forum->{forum_style}]);
$forum->{forum_last_date} = $forum->{forum_last} ? GForum::date($forum->{forum_last}) : 0;
my $Post = $DB->table('Post');
my ($so, $sb, $pg, $mh);
$sb = $IN->param('sb') || 'post_latest_reply';
$sb = 'post_latest_reply' unless exists $Post->{schema}->{cols}->{$sb};
$so = uc($IN->param('so') || '');
$so = 'ASC' unless $so eq 'ASC' or $so eq 'DESC';
$pg = $IN->param('page') || 1;
$pg = 1 if $pg =~ /\D/;
$mh = $IN->param('mh');
$mh = ($USER and $USER->{user_default_mh_forum} or $CFG->{default_mh_forum}) if not $mh or $mh =~ /\D/;
my $tree = $DB->table('Post')->tree();
my $PostUser = $DB->table('Post', 'User');
my $condition = GT::SQL::Condition->new(forum_id_fk => '=' => $forum_id);
$PostUser->select_options("ORDER BY $sb $so");
$PostUser->select_options("LIMIT " . ($mh * ($pg - 1)) . ", $mh");
if (uc $PostUser->{connect}->{driver} eq 'MYSQL') {
local $PostUser->{tables_ord}->[0] = "$PostUser->{tables_ord}->[0] /*!32312 USE INDEX (p_rfl) */"; # MySQL hack to use the intended index
$sth = $PostUser->select(left_join => { forum_id_fk => $forum_id, post_root_id => 0 });
}
else {
$sth = $PostUser->select(left_join => { forum_id_fk => $forum_id, post_root_id => 0 });
}
my (@post_loop, %get_threads);
while (my $post = $sth->fetchrow_hashref) {
push @post_loop, $post;
if ($expanded_roots{$post->{post_id}} or $expanded_posts{$post->{post_id}}) {
$get_threads{$post->{post_id}} = @post_loop; # The value is the splice position for the children
}
}
if (keys %get_threads) {
my $children = $tree->children(
id => [keys %get_threads],
select_from => $PostUser,
left_join => 1,
sort_col => 'post_time',
roots_only => 1
);
my $offset = 0; # The above splice position changes each time more are put in; this keeps track of the difference.
for (sort { $get_threads{$a} <=> $get_threads{$b} } keys %get_threads) {
if (my $num_children = @{$children->{$_}}) {
splice @post_loop, $get_threads{$_} + $offset, 0, @{$children->{$_}};
$offset += $num_children;
}
}
}
my %on_page;
for (my $i = 0; $i < @post_loop; $i++) {
my $p = $post_loop[$i];
if ($p->{post_root_id}) { # Not a root
if (not $expanded_roots{$p->{post_root_id}} and
not ($expanded_posts{$p->{post_father_id}} and $on_page{$p->{post_father_id}})) {
splice @post_loop, $i--, 1;
}
else {
$on_page{$p->{post_id}}++;
if ($expanded_roots{$p->{post_root_id}}) {
$p->{post_expanded} = $p->{post_thread_expanded} = 1;
}
elsif ($expanded_posts{$p->{post_id}}) {
$p->{post_expanded} = 1;
}
}
}
else { # A root
$on_page{$p->{post_id}}++;
if ($expanded_roots{$p->{post_id}}) {
$p->{post_expanded} = $p->{post_thread_expanded} = 1;
}
elsif ($expanded_posts{$p->{post_id}}) {
$p->{post_expanded} = 1;
}
}
}
require GForum::Post;
GForum::Post::normalize(\@post_loop);
if ($USER) {
my $UserNew = $DB->table('UserNew');
my $now = time;
if ($UserNew->count({ forum_id_fk => $forum_id, user_id_fk => $USER->{user_id} })) {
$UserNew->update({ usernew_last => $now }, { forum_id_fk => $forum_id, user_id_fk => $USER->{user_id} });
}
else {
$UserNew->insert({ forum_id_fk => $forum_id, user_id_fk => $USER->{user_id}, usernew_last => $now });
}
if ($SESSION) {
$SESSION->{info}->{session_data}->{usernew}->{$forum_id} = $now;
$SESSION->save();
}
# Delete any "new" posts from the PostNew table in this forum
$DB->table('PostNew')->delete({ user_id_fk => $USER->{user_id}, forum_id_fk => $forum_id });
}
for my $ret (@post_loop) {
$ret->{thread_hot} = 1 if $hot{$ret->{post_root_id} || $ret->{post_id}};
$ret->{post_locked} = 1 if $ret->{post_depth} and $locked{$ret->{post_root_id}};
$locked{$ret->{post_id}} = 1 if $ret->{post_depth} == 0 and $ret->{post_locked};
if ($USER) {
if ($USER->{user_forum_permission} >= FORUM_PERM_MODERATOR) {
$ret->{user_perm_moderator} = 1;
$ret->{user_perm_edit} = 1;
$ret->{user_perm_delete} = 1;
}
else {
if ($USER->{user_id} == $ret->{user_id_fk}) {
if ($forum->{forum_allow_user_edit} and (!$forum->{forum_edit_timeout} or ($ret->{post_time} + $forum->{forum_edit_timeout}) > time)) {
$ret->{user_perm_edit} = 1 if $forum->{forum_allow_user_edit} % 2; # 1 or 3
$ret->{user_perm_delete} = 1 if $forum->{forum_allow_user_edit} >= 2;
}
}
}
}
}
return(
$page->{view},
{
%$forum,
post_loop => \@post_loop,
so => $so,
sb => $sb,
this_page => $pg,
mh => $mh,
}
)
}
END_OF_SUB
$COMPILE{view_expanded} = __LINE__ . <<'END_OF_SUB';
sub view_expanded {
shift; # Discard the package name
my ($do, $func) = @_;
my $page = $func->{page};
my $forum_id = $IN->param('forum');
my $forum = $DB->table('Forum', 'Category')->select({ forum_id => $forum_id })->fetchrow_hashref
or return(
$page->{no_such_forum} => {
error => GForum::language('FORUM_DOES_NOT_EXIST')
}
);
normalize($forum);
my %hot; # root_id => 1 for hot threads
my %locked; #root_id => 1 for locked threads
$forum->{forum_style} = GForum::language((qw/FORUM_STYLE_PLAIN FORUM_STYLE_MARKUP FORUM_STYLE_HTML FORUM_STYLE_BOTH/)[$forum->{forum_style}]);
$forum->{forum_last_date} = $forum->{forum_last} ? GForum::date($forum->{forum_last}) : 0;
my $Post = $DB->table('Post');
my ($so, $sb, $pg, $mh);
$sb = $IN->param('sb') || 'post_latest_reply';
$sb = 'post_latest_reply' unless exists $Post->{schema}->{cols}->{$sb};
$so = uc($IN->param('so') || '');
$so = 'ASC' unless $so eq 'ASC' or $so eq 'DESC';
$pg = $IN->param('page') || 1;
$pg = 1 if $pg =~ /\D/;
$mh = $IN->param('mh');
$mh = ($USER and $USER->{user_default_mh_forum} or $CFG->{default_mh_forum}) if not $mh or $mh =~ /\D/;
my $cond = GT::SQL::Condition->new(forum_id_fk => '=' => $forum_id);
my $tree = $Post->tree;
my $children;
{
my $PostUser = $DB->table('Post', 'User');
my $save;
if (uc $PostUser->{connect}->{driver} eq 'MYSQL') {
$save = $PostUser->{tables_ord}->[0];
$PostUser->{tables_ord}->[0] = "$PostUser->{tables_ord}->[0] /*!32312 USE INDEX (p_rfl) */"; # MySQL hack to use the intended index
}
$children = $tree->children(
id => 0,
select_from => $PostUser,
left_join => 1,
condition => $cond,
sort_col => 'post_time',
roots_order_by => "$sb $so",
limit => ($mh * ($pg - 1)) . ", $mh"
);
if (uc $PostUser->{connect}->{driver} eq 'MYSQL') {
$PostUser->{tables_ord}->[0] = $save;
}
}
my @post_loop = @$children if $children;
my @root_ids;
for (@post_loop) {
push @root_ids, $_->{post_id} unless $_->{post_root_id};
$_->{post_expanded} = $_->{post_thread_expanded} = 1;
}
my %other_roots; # This is messy, but needed for correct partial collapsing!
for my $root_id (@root_ids) {
$other_roots{$root_id} = "et=" . join ",", grep $_ != $root_id, @root_ids;
}
require GForum::Post;
GForum::Post::normalize(\@post_loop) if @post_loop;
if ($USER) {
my $UserNew = $DB->table('UserNew');
my $now = time;
if ($UserNew->count({ forum_id_fk => $forum_id, user_id_fk => $USER->{user_id} })) {
$UserNew->update({ usernew_last => $now }, { forum_id_fk => $forum_id, user_id_fk => $USER->{user_id} });
}
else {
$UserNew->insert({ forum_id_fk => $forum_id, user_id_fk => $USER->{user_id}, usernew_last => $now });
}
if ($SESSION) {
$SESSION->{info}->{session_data}->{usernew}->{$forum_id} = $now;
$SESSION->save();
}
# Delete any "new" posts from the PostNew table in this forum
$DB->table('PostNew')->delete({ user_id_fk => $USER->{user_id}, forum_id_fk => $forum_id });
}
for my $ret (@post_loop) {
$locked{$ret->{post_id}} = 1 if $ret->{post_depth} == 0 and $ret->{post_locked};
$ret->{thread_hot} = 1 if $hot{$ret->{post_root_id} || $ret->{post_id}};
$ret->{post_locked} = 1 if $locked{$ret->{post_root_id} || $ret->{post_id}};
$ret->{other_expand_roots} = $other_roots{$ret->{post_root_id} || $ret->{post_id}};
if ($USER) {
if ($USER->{user_forum_permission} >= FORUM_PERM_MODERATOR) {
$ret->{user_perm_moderator} = 1;
$ret->{user_perm_edit} = 1;
$ret->{user_perm_delete} = 1;
}
else {
if ($USER->{user_id} == $ret->{user_id_fk}) {
if ($forum->{forum_allow_user_edit} and (!$forum->{forum_edit_timeout} or ($ret->{post_time} + $forum->{forum_edit_timeout}) > time)) {
$ret->{user_perm_edit} = 1 if $forum->{forum_allow_user_edit} % 2; # 1 or 3
$ret->{user_perm_delete} = 1 if $forum->{forum_allow_user_edit} >= 2;
}
}
}
}
}
return(
$page->{view},
{
%$forum,
post_loop => \@post_loop,
so => $so,
sb => $sb,
this_page => $pg,
mh => $mh,
}, { compress => 1 }
)
}
END_OF_SUB
# This marks all posts in a forum as read. GForum::Post::mark_all_new marks all
# posts on the site as read.
$COMPILE{mark_all_read} = __LINE__ . <<'END_OF_SUB';
sub mark_all_read {
if ($USER) {
my $UserNew = $DB->table('UserNew');
my $now = time;
my $forum_id = $IN->param('forum');
if ($UserNew->count({ forum_id_fk => $forum_id, user_id_fk => $USER->{user_id} })) {
$UserNew->update({ usernew_last => $now }, { forum_id_fk => $forum_id, user_id_fk => $USER->{user_id} });
}
else {
$UserNew->insert({ forum_id_fk => $forum_id, user_id_fk => $USER->{user_id}, usernew_last => $now });
}
if ($SESSION) {
$SESSION->{info}->{session_data}->{usernew}->{$forum_id} = $now;
$SESSION->{info}->{session_data}->{userlast}->{$forum_id} = $now;
$SESSION->save();
}
# Delete any "new" posts from the PostNew table.
$DB->table('PostNew')->delete({ user_id_fk => $USER->{user_id}, forum_id_fk => $forum_id });
}
GForum::do_func($IN->param('redo'));
}
END_OF_SUB
# Returns a made-for-templates hashref or "tpl_list" and "num_root_cats".
#
# Each element of tpl_list is either a category, or a forum/category
# join. The first element will always be a category. Following
# each category will be a list of all categories and forums within
# that category, sorted by their repective sort ranks.
#
# Call with 'admin' as an argument and the permission checking
# won't be done. Obviously, only do this from the admin panel.
# All categories and forums will be returned.
#
# You can call with 'user' as the argument. If you do so, everything
# will happen as above, but all permissions will be checked (so forums
# that the user does not have access to will not be shown).
# Additionally, the 'show forums/subcats' option will be looked for.
# Any closed categories will not be recursed into. A list of categories
# can be provided after the 'user' argument - only those categories
# will be shown. Note that any subcategories specified will also have
# their parent category in the list, however only the subcategory will
# show - not the parent category's other elements. Confused yet?
#
# Just to make it even more confusing, 'user' can be specified as two other
# things to change the behaviour: 'hide_user', to hide categories the user has
# hidden (via $USER->{user_hidden_cats}), or 'all_user', to show all categories
# regardless of the 'show forums/subcats' setting. This differs from 'admin'
# in that permissions are still checked.
#
$COMPILE{tpl_list} = __LINE__ . <<'END_OF_SUB';
sub tpl_list {
my $loop = [];
my @args = @_;
@args = 'user' if not @args;
my ($admin, $user, @show, %hide, @hide);
if ($args[0] eq 'admin') {
$admin = 1;
}
else { # user, hide_user, or all_user
$user = shift(@args) || 'user';
@show = @args;
}
my %show = map { ($_ => 1) } @show if @show;
if ($user eq 'hide_user' and $USER and $USER->{user_hidden_cats}) {
@hide = split /,/, $USER->{user_hidden_cats};
%hide = map { ($_ => 1) } @hide;
}
my $Category = $DB->table('Category');
my $ForumCat = $DB->table('Forum', 'Category');
$Category->select_options("ORDER BY cat_sort_rank ASC");
my $cats = $Category->select()->fetchall_hashref;
$ForumCat->select_options("ORDER BY forum_sort_rank ASC");
my $forums = $ForumCat->select()->fetchall_hashref;
@$forums = grep {
my $permission = GForum::Authenticate::auth('forum_permission', $_->{forum_id});
$_->{forum_permission} = $permission;
$permission
} @$forums unless $admin;
# Now we have to sort out $categories and $forums, and put them into $loop in the correct order.
# Sorted categories look like this:
# lowest root cat
# lowest subcat
# lowest subcat
# next lowest subcat
# ...
# next lowest root cat
# lowest subcat
# ...
# etc.
#
# Then all the forums have to be mixed in. cat_sort_rank for a subcat competes with forum_sort_rank.
#
my %cats; # This will hold all the categories, for later reference
for (sort { $a->{cat_depth} <=> $b->{cat_depth} } @$cats) {
if ($user eq 'all_user') {
push @show, $_->{cat_id};
$show{$_->{cat_id}} = 1;
}
$_->{cat_full_name} = $_->{cat_depth}
? "$cats{$_->{cat_id_fk}}->{cat_full_name}: $_->{cat_name}"
: $_->{cat_name};
$cats{$_->{cat_id}} = $_;
}
for (@$forums) {
$_->{cat_full_name} = $cats{$_->{cat_id}}->{cat_full_name};
}
normalize($forums);
for (my $i = 0; $i < @$cats; $i++) {
push @$loop, splice @$cats, $i--, 1 if $cats->[$i]->{cat_depth} == 0;
}
my $roots = @$loop; # Right now, there are only root categories in it
for (my $i = 0; $i < @$loop; $i++) {
next if $loop->[$i]->{forum_id};
my $id = $loop->[$i]->{cat_id};
my (@c, @f);
for (my $c = 0; $c < @$cats; $c++) {
push @c, splice @$cats, $c--, 1 if $cats->[$c]->{cat_id_fk} == $id;
}
for (my $f = 0; $f < @$forums; $f++) {
push @f, splice @$forums, $f--, 1 if $forums->[$f]->{cat_id} == $id;
}
if (@c or @f) {
# Now mix the subcategories and forums together, sorting by their respective sort ranks:
my @add = sort { ($a->{forum_id} ? $a->{forum_sort_rank} : $a->{cat_sort_rank}) <=> ($b->{forum_id} ? $b->{forum_sort_rank} : $b->{cat_sort_rank}) } @c, @f;
splice @$loop, $i + 1, 0, @add;
}
$loop->[$i]->{num_forums} = scalar @f; # This way you can tell how many forums are in a category.
$loop->[$i]->{num_subcats} = scalar @c; # This lets you know how many subcategories there are in this category.
}
# If from the admin, where we need everything, we're done.
if ($admin) {
return {
tpl_list => $loop,
num_root_cats => $roots
};
}
# Now strip out anything unwanted.
# First build a tree so we can follow all categories all the way back to their root.
my %tree; # (cat_id => [$root, ..., $father], ...)
for (@$loop) {
next if $_->{forum_id};
$tree{$_->{cat_id}} = [], next if not $_->{cat_id_fk}; # Ignore root categories
$tree{$_->{cat_id}} = [@{$tree{$_->{cat_id_fk}}}, $cats{$_->{cat_id_fk}}];
}
my (%required, %shown); # (cat_id => 1, ...) for both
my $skip = 0; # 0 -> normal, 1 -> skipping until cat_depth <= $depth
my $depth;
# Build up a list of categories that will be shown.
for my $cat (@$loop) {
next if $cat->{forum_id}; # Ignore forums
if ($skip) {
if ($show{$cat->{cat_id}} or $cat->{cat_depth} <= $depth) {
$skip = 0;
}
else {
next;
}
}
my ($shown, $required) = (0,0);
if (@show and $show{$cat->{cat_id}}) {
$shown = $required = 1
}
elsif ($cat->{cat_depth} and ($shown{$cat->{cat_id_fk}} and ($cats{$cat->{cat_id_fk}}->{cat_show_forums} or $show{$cat->{cat_id_fk}}))) {
$required = 1;
if ($cat->{cat_show_forums}) {
if (@hide and $hide{$cat->{cat_id}}) {
$cat->{cat_hidden} = 1;
}
else {
$shown = 1;
}
}
}
elsif (not @show and not $cat->{cat_depth}) {
if (@hide and $hide{$cat->{cat_id}}) {
$cat->{cat_hidden} = 1;
}
else {
$shown = 1 if $cat->{cat_show_forums};
}
$required = 1;
}
if ($required) {
$shown{$cat->{cat_id}} = $shown;
$required{$cat->{cat_id}} = 1;
for (@{$tree{$cat->{cat_id}}}) {
$required{$_->{cat_id}} = 1;
}
}
else {
$skip = 1;
$depth = $cat->{cat_depth};
}
}
# Forums are shown if their category is shown. Categories are shown if their parent is shown.
@$loop = grep { $_->{forum_id} ? $shown{$_->{cat_id}} : $required{$_->{cat_id}} } @$loop;
{
my ($depth, $cat, %num_forums, %num_subcats) = (0);
for (my $i = 0; $i < @$loop; $i++) {
my $item = $loop->[$i];
if ($item->{forum_id}) {
$num_forums{$item->{cat_id}}++;
}
elsif ($item->{cat_depth}) {
$num_subcats{$item->{cat_id_fk}}++;
}
if ($i) {
if ($loop->[$i - 1]->{forum_id}) {
$item->{previous_was_forum} = 1;
}
else {
$item->{previous_was_cat} = 1;
}
}
}
for my $cat_id (keys %num_forums) {
$cats{$cat_id}->{num_forums} = $num_forums{$cat_id};
}
for my $cat_id (keys %num_subcats) {
$cats{$cat_id}->{num_subcats} = $num_subcats{$cat_id};
}
}
return {
tpl_list => $loop,
num_root_cats => $roots
};
}
END_OF_SUB
# Returns a template variable 'moderator_tpl_options'. If the variable is
# blank, it means there are no forums the user is a moderator on. Otherwise, it
# will be a string consisting of a number of };
}
}
}
return { moderator_tpl_options => \$options };
}
END_OF_SUB
# Just like moderator_tpl_options, but returns all forums and doesn't do
# permission checks.
$COMPILE{admin_tpl_options} = __LINE__ . <<'END_OF_SUB';
sub admin_tpl_options {
my %input = @_;
my $list = tpl_list('admin')->{tpl_list};
my $options = '';
for (@$list) {
if ($_->{forum_id}) {
$options .= qq{};
}
}
return { admin_tpl_options => \$options };
}
END_OF_SUB
$COMPILE{set_sort_order} = __LINE__ . <<'END_OF_SUB';
sub set_sort_order {
my $Forum = $DB->table('Forum');
for ($IN->param) {
my ($fid) = /^forum(\d+)$/ or next;
my $new_order = $IN->param($_);
$Forum->update({ forum_sort_rank => $new_order }, { forum_id => $fid });
}
return;
}
END_OF_SUB
# Called from templates, this returns the category and forum tags for the passed
# in forum id.
$COMPILE{get} = __LINE__ . <<'END_OF_SUB';
sub get {
my $forum_id = shift;
my $forum = $DB->table('Forum' => 'Category')->select({ forum_id => $forum_id })->fetchrow_hashref or return;
normalize($forum);
$forum;
}
END_OF_SUB
# Returns the permission for the current user in the forum.
# Pass it a forum ID.
sub permission {
my $forum = shift;
my $permission;
$permission = ($USER and $USER->{user_status} == ADMINISTRATOR) ? FORUM_PERM_MODERATOR : FORUM_PERM_NONE;
unless ($permission) {
my %group_ids; # (group_id => true) for all groups for which the user is a member
# Give guest access, whether or not this is a user.
$group_ids{$CFG->{id_group_guest}}++;
if ($USER) {
# Get all groups to which the user belongs
my $groups = $DB->table('UserGroup')->select({ user_id_fk => $USER->{user_id} }, ['group_id_fk']);
while (my $gid = $groups->fetchrow_array) {
$group_ids{$gid}++;
}
}
if (my @groups = keys %group_ids) {
my $fg_sth = $DB->table('ForumGroup')->select(forum_perms => { group_id_fk => \@groups, forum_id_fk => $forum });
while (my $perm = $fg_sth->fetchrow_array and $permission < FORUM_PERM_MODERATOR) {
$permission = $perm if $permission < $perm;
}
}
}
$permission;
}
sub normalize {
my $f = shift;
GT::Plugins->dispatch($CFG->{admin_root_path} . '/Plugins/GForum', "forum_normalize", sub { return _plg_normalize(@_) }, $f);
}
# Can take either a single forum (hash ref) or an array ref of multiple forums.
# Each hash ref should be a Forum => Category join.
sub _plg_normalize {
my $forums = ref $_[0] eq 'ARRAY' ? shift : [shift];
for my $forum (@$forums) {
$forum->{forum_desc} ||= '';
$forum->{forum_desc} = \"$forum->{forum_desc}";
$forum->{forum_time_passed} = (time - $forum->{forum_last}) / 60;
$forum->{forum_last_date} = $forum->{forum_last} ? GForum::date($forum->{forum_last}) : '';
if (!$forum->{cat_full_name}) {
require GForum::Category;
$forum->{cat_full_name} = GForum::Category::full_name($forum->{cat_id}, ': ');
}
}
$forums;
}
# Called from the templates, this returns two variables:
# forum_num_moderators (a number) and forum_moderators (a template loop).
# The loop goes one user at a time and has all the user information available.
$COMPILE{moderators} = __LINE__ . <<'END_OF_SUB';
sub moderators {
my $forum = shift;
my $fmu = $DB->table('ForumModerator' => 'User');
$fmu->select_options('ORDER BY user_username ASC');
my $mod_sth = $fmu->select('User.*' => { forum_id_fk => $forum }) or return;
my $moderators = $mod_sth->fetchall_hashref;
require GForum::User;
GForum::User::normalize($moderators);
return { forum_num_moderators => scalar @$moderators, forum_moderators => $moderators };
}
END_OF_SUB
sub jump_list {
my $selected = shift;
my $tpl_list = tpl_list('all_user')->{tpl_list};
my $return = '';
for (@$tpl_list) {
if ($_->{forum_id} and $_->{forum_permission} >= FORUM_PERM_VIEW_POSTS) {
$return .= qq{\n};
}
elsif (!$_->{forum_id} and $_->{num_forums} || $_->{num_subcats}) { # A Category
$return .= qq{\n};
}
}
return \$return;
}
1;