# ==================================================================
# Gossamer Forum - Advanced web community
#
# Website : http://gossamer-threads.com/
# Support : http://gossamer-threads.com/scripts/support/
# Revision : $Id: Category.pm,v 1.36 2002/03/27 20:25:17 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::Category;
use strict;
use GForum qw/:user :forum $IN $DB $CFG $USER $GUEST $SESSION %CAT_CACHE/;
use GForum::Forum;
use GT::Date;
sub set_sort_order {
my $Cat = $DB->table('Category');
for ($IN->param) {
my ($cid) = /^cat(\d+)$/ or next;
my $new_order = $IN->param($_);
$Cat->update({ cat_sort_rank => $new_order }, { cat_id => $cid });
}
return;
}
# This handles hiding/unhiding categories
sub _hide_cats {
return unless $USER;
$USER->{user_hidden_cats} ||= '';
my $save;
if (my $hide = $IN->param('hide_cat')) {
return if $hide =~ /\D/;
$USER->{user_hidden_cats} =~ s/\b\Q$hide\E(?:,|$)//g;
if ($USER->{user_hidden_cats}) {
$USER->{user_hidden_cats} .= ",$hide";
}
else {
$USER->{user_hidden_cats} = $hide;
}
$save = 1;
}
elsif (my $unhide = $IN->param('unhide_cat')) {
return if $unhide =~ /\D/;
$save = ($USER->{user_hidden_cats} =~ s/\b\Q$unhide\E(?:,|$)//g);
}
if ($save) {
# Save the setting
$DB->table('User')->update({ user_hidden_cats => $USER->{user_hidden_cats} }, { user_id => $USER->{user_id} });
}
}
sub list {
shift; # discard the package
my ($do, $func) = @_;
my $page = $func->{page};
my @show_cats = $IN->param('category');
my $cats_query = join ';', map "category=$_", @show_cats if @show_cats;
_hide_cats() if $USER; # Handle any hide/unhide instructions
my $items = GForum::Forum::tpl_list(hide_user => @show_cats);
my $roots = $items->{num_root_cats};
$items = $items->{tpl_list};
if ($SESSION) {
_calc_new($items);
}
return(
$page->{list},
{
num_root_cats => $roots,
everything => $items,
(@show_cats ? (cats_query => $cats_query) : (main_page => 1)),
}
)
}
# Takes two arguments: a category ID, and a seperator. The full name will be
# returned, with categories/subcategories divided by the seperator.
sub full_name {
my ($id, $sep) = @_;
$sep ||= ': ';
if (!$CAT_CACHE{$id}) {
_full_name_cache($id);
}
my @cats = @{$CAT_CACHE{$id}};
my $cat_name = join $sep, map $_->{cat_name}, @cats;
return $cat_name;
}
# This is called from the templates. It takes a category ID, seperator, prefix, suffix, and a list of variables that will be concatenated together to form the URL (;category=... will be added to it).
# The prefix and suffix will be placed immediately before and after the category
# name(s), INSIDE the links.
# For example, with a prefix of "", suffix of "", and seperator of
# "->", you would get back something like: cat -> cat ...
sub full_name_linked {
my ($id, $sep, $pre, $suf, @url) = @_;
$sep ||= ': ';
$pre ||= '';
$pre =~ s/%/%%/g;
$suf ||= '';
$suf =~ s/%/%%/g;
my $url = join '', @url;
$url ||= 'gforum.cgi?';
$url =~ s/%/%%/;
$url =~ s/[;&?]category=[^;&]*//g;
$url .= ";category=%d";
$url =~ s/"/"/g;
my $category = qq|$pre%s$suf|;
if (!$CAT_CACHE{$id}) {
_full_name_cache($id);
}
my @cats = @{$CAT_CACHE{$id}};
my $cat_name = join $sep, map sprintf($category, $_->{cat_id}, $_->{cat_name}), @cats;
return \$cat_name;
}
# Loads a category and parents into %CAT_CACHE.
sub _full_name_cache {
my $id = shift;
my $Cat = $DB->table('Category');
my $tree = $Cat->tree;
my $cat = $Cat->get({ cat_id => $id }, 'HASH', ['cat_id', 'cat_name']);
my $parents = $tree->parents(id => $id, cols => ['cat_id', 'cat_name']);
$CAT_CACHE{$id} = [@$parents, $cat];
}
# See tpl_list in GForum::Forum. This is the same except that it does not return
# forums, only categories. Also, each category has "cat_full_name" set, with
# ": "'s as seperators between categories. This is used only from the admin.
# You can call with a category/subcategory seperator to be used in
# "cat_full_name" - it defaults to ': '
# Takes a second argument of a category ID. If specified, the category (and any
# subcategories) will not be returned.
sub tpl_list {
my $loop = [];
my $sep = shift || ': ';
my $skip_id = shift;
my $Category = $DB->table('Category');
$Category->select_options("ORDER BY cat_sort_rank ASC");
my $cats = $Category->select()->fetchall_hashref;
# Now we have to sort out $cats, putting 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.
#
my %cats; # This will hold all the categories, for later reference
for (@$cats) {
$cats{$_->{cat_id}} = $_;
}
for (sort { $cats{$a}->{cat_depth} <=> $cats{$b}->{cat_depth} } keys %cats) {
$cats{$_}->{cat_full_name} = $cats{$_}->{cat_depth}
? "$cats{$cats{$_}->{cat_id_fk}}->{cat_full_name}$sep$cats{$_}->{cat_name}"
: $cats{$_}->{cat_name};
}
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++) {
my $id = $loop->[$i]->{cat_id};
my (@c);
for (my $c = 0; $c < @$cats; $c++) {
push @c, splice @$cats, $c--, 1 if $cats->[$c]->{cat_id_fk} == $id;
}
splice @$loop, $i + 1, 0, sort { $a->{cat_sort_rank} <=> $b->{cat_sort_rank} } @c if @c;
$loop->[$i]->{num_subcats} = scalar @c; # This lets you know how many subcategories there are in this category.
}
# Now, if it was requested to leave out a category, splice it and any subcats out of @$loop.
if ($skip_id) {
my ($skip, $depth) = (0, 0);
for (my $i = 0; $i < @$loop; $i++) {
my $cat = $loop->[$i];
if ($skip) {
if ($cat->{cat_depth} <= $depth) {
last; # Since you can only specify one skip category, we're done.
}
else {
splice @$loop, $i--, 1; # Get rid of this category
next;
}
}
elsif ($cat->{cat_id} == $skip_id) {
$skip = 1;
$depth = $cat->{cat_depth};
splice @$loop, $i--, 1;
next
}
}
}
return {
tpl_list => $loop,
num_root_cats => $roots
};
}
sub _calc_new {
my $forums = shift;
return unless $SESSION and $USER;
my $data = $SESSION->data;
# Initialize counts equal to the number of posts for all forums.
my (%need_check, @usernew_delete);
foreach my $forum (@$forums) {
next unless ($forum->{forum_id});
# We include a forum in the check if either $forum->{forum_new_timeout} is not set,
# or the user has been in the forum in the last $forum->{forum_new_timeout} days
if (!$forum->{forum_new_timeout}
or
(
$data->{usernew}->{$forum->{forum_id}} and
$data->{usernew}->{$forum->{forum_id}} > time - $forum->{forum_new_timeout} * (24 * 60 * 60)
)
) {
$need_check{$forum->{forum_id}} = 1;
}
elsif ($forum->{forum_new_timeout} and $data->{usernew}->{$forum->{forum_id}}) {
push @usernew_delete, $forum->{forum_id};
}
$forum->{forum_new} = $forum->{forum_total};
$forum->{forum_new_threads} = $forum->{forum_total_threads};
}
if (@usernew_delete) {
for (@usernew_delete) {
delete $data->{usernew}->{$_};
}
$SESSION->save();
$DB->table('UserNew')->delete({ user_id_fk => $USER->{user_id}, forum_id_fk => \@usernew_delete });
}
my (%new_posts, %new_posts_by_user, %new_posts_seen, %new_threads, %new_threads_by_user, %new_threads_seen, $has_new);
my $Post = $DB->table('Post');
my %seen = ();
foreach my $forum (%{$data->{posts}}) {
my $time = $data->{usernew}->{$forum} or next;
push @{$seen{$forum}}, grep $data->{posts}->{$forum}->{$_} > $time, keys %{$data->{posts}->{$forum}};
}
my $cond;
my @these = ();
$forums = [sort { $data->{usernew}->{$a->{forum_id}} <=> $data->{usernew}->{$b->{forum_id}} } grep $need_check{$_->{forum_id}}, @$forums];
for my $i (0 .. $#$forums) {
my $forum = $forums->[$i];
next unless $data->{usernew}->{$forum->{forum_id}};
push @these, $forum->{forum_id};
$cond ||= new GT::SQL::Condition 'OR';
$cond->add(GT::SQL::Condition->new(
forum_id_fk => '=' => $forum->{forum_id},
post_time => '>' => $data->{usernew}->{$forum->{forum_id}}
));
}
if ($cond) {
my ($np, $nt, $npu, $nps);
# New posts:
$Post->select_options('GROUP BY forum_id_fk');
my $sth = $Post->select('forum_id_fk', 'COUNT(*)' => $cond);
while (my ($fid, $c) = $sth->fetchrow) {
$new_posts{$fid} = $c;
$np++;
}
if ($np) {
# New threads:
my $mycond = new GT::SQL::Condition;
$mycond->add($cond);
$mycond->add(post_root_id => '=' => 0);
$Post->select_options('GROUP BY forum_id_fk');
$sth = $Post->select('forum_id_fk', 'COUNT(*)' => $mycond);
while (my ($fid, $c) = $sth->fetchrow) {
$new_threads{$fid} = $c;
$nt++;
}
# New posts by $USER:
$mycond = new GT::SQL::Condition;
$mycond->add($cond);
$mycond->add(user_id_fk => '=' => $USER->{user_id});
$Post->select_options('GROUP BY forum_id_fk');
$sth = $Post->select('forum_id_fk', 'COUNT(*)' => $mycond);
while (my ($fid, $c) = $sth->fetchrow) {
$new_posts_by_user{$fid} = $c;
$npu++;
}
if ($nt and $npu) {
# New threads by $USER:
$mycond->add(post_root_id => '=' => 0);
my $save;
if (uc $Post->{connect}->{driver} eq 'MYSQL') {
$save = $Post->{driver}->{name};
$Post->{driver}->{name} .= " /*!32312 USE INDEX (p_rfmp) */"; # MySQL hack - MySQL doesn't pick the right index, and will even crash otherwise!
}
$Post->select_options('GROUP BY forum_id_fk');
$sth = $Post->select('forum_id_fk', 'COUNT(*)' => $mycond);
while (my ($fid, $c) = $sth->fetchrow) {
$new_threads_by_user{$fid} = $c;
}
if (uc $Post->{connect}->{driver} eq 'MYSQL') {
$Post->{driver}->{name} = $save;
}
}
# "New" seen posts:
$mycond = new GT::SQL::Condition;
$mycond->add($cond);
$mycond->add(post_id => IN => [map {@{$seen{$_} || []}} @these]);
$Post->select_options('GROUP BY forum_id_fk');
$sth = $Post->select('forum_id_fk', 'COUNT(*)' => $mycond);
while (my ($fid, $c) = $sth->fetchrow) {
$new_posts_seen{$fid} = $c;
$nps++;
}
if ($nt and $nps) {
# "New" seen threads:
$mycond->add(post_root_id => '=' => 0);
$Post->select_options('GROUP BY forum_id_fk');
$sth = $Post->select('forum_id_fk', 'COUNT(*)' => $mycond);
while (my ($fid, $c) = $sth->fetchrow) {
$new_threads_seen{$fid} = $c;
}
}
}
}
# Update our counts.
foreach my $forum (@$forums) {
my $id = $forum->{forum_id} or next;
# New Posts
my $all = $new_posts{$id} || 0;
my $mine = $new_posts_by_user{$id} || 0;
my $seen = $new_posts_seen{$id} || 0;
$forum->{forum_new} = $all - $mine - $seen;
# warn "forum ($id): posts all '$all' mine '$mine' seen '$seen' total '$forum->{forum_new}'\n";
# New Threads
$all = $new_threads{$id} || 0;
$mine = $new_threads_by_user{$id} || 0;
$seen = $new_threads_seen{$id} || 0;
$forum->{forum_new_threads} = $all - $mine - $seen;
# warn "forum ($id): threads all '$all' mine '$mine' seen '$seen' total '$forum->{forum_new_threads}'\n";
}
}
1;