# ================================================================== # 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;