# ================================================================== # Gossamer Forum - Advanced web community # # Website : http://gossamer-threads.com/ # Support : http://gossamer-threads.com/scripts/support/ # Revision : $Id: Search.pm,v 1.29.2.8 2004/03/08 19:47:55 jagerman Exp $ # # Copyright (c) 2003 Gossamer Threads Inc. All Rights Reserved. # Redistribution in part or in whole strictly prohibited. Please # see LICENSE file for full details. # ================================================================== # package GForum::Search; use strict; use GForum qw/:user :forum $DB $IN $CFG $USER $SESSION/; use GForum::Forum; use GT::AutoLoader; $COMPILE{err} = __LINE__ . <<'END_OF_SUB'; sub err { my $error_message = shift; my $search_query = shift; @GForum::Template::VARS{"error", keys %$search_query} = ($error_message, values %$search_query); GForum::do_func('search'); } END_OF_SUB $COMPILE{search} = __LINE__ . <<'END_OF_SUB'; sub search { shift; my ($do, $func) = @_; my $page = $func->{page}; my $everything = GForum::Forum::tpl_list('all_user')->{tpl_list}; # The following two for loops are to take out any categories that do not have forums (including forums in subcats) my (%cats_with_forums, @this_cat); for (@$everything) { if ($_->{forum_id}) { while (@this_cat and $this_cat[-1] != $_->{cat_id}) { pop @this_cat; } if ($_->{forum_permission} >= FORUM_PERM_READ) { for (@this_cat) { $cats_with_forums{$_}++; } } } elsif ($_->{cat_id_fk} == $this_cat[-1]) { push @this_cat, $_->{cat_id}; } else { while (@this_cat and $this_cat[-1] != $_->{cat_id_fk}) { pop @this_cat; } push @this_cat, $_->{cat_id}; } } for (my $i = 0; $i < @$everything; $i++) { my $item = $everything->[$i]; splice @$everything, $i--, 1 if $item->{forum_id} ? $item->{forum_permission} < FORUM_PERM_READ : !$cats_with_forums{$item->{cat_id}}; } my @search_forum = $IN->param('search_forum') =~ /(cat|forum)_(\d+)/; return( $page->{search}, { everything => $everything, ($USER && $SESSION && $SESSION->{info}->{session_data}->{prior_last_seen} ? (show_last => 1) : ()), (@search_forum ? (search_forum_type => $search_forum[0], search_forum_id => $search_forum[1]) : ()) } ); } END_OF_SUB # If setting a post hook, results returns a hash ref containing - num_results, search_words, and possibly limited_by_user $COMPILE{results} = __LINE__ . <<'END_OF_SUB'; sub results { my ($nothing, $do, $func) = @_; GT::Plugins->dispatch($CFG->{admin_root_path} . '/Plugins/GForum', "search_results", sub { return _plg_results(@_) }, $nothing, $do, $func); } sub _plg_results { shift; my ($do, $func) = @_; my $page = $func->{page}; my %search_query; my $forum = $IN->param('search_forum'); my @forums; if ($forum =~ /cat_(\d+)/) { my $cat_id = $1; my @categories = $cat_id; my @new = $cat_id; my $Cat = $DB->table('Category'); while (@new = $Cat->select(cat_id => { cat_id_fk => \@new })->fetchall_list) { push @categories, @new; } @forums = $DB->table('Forum')->select('forum_id', { cat_id_fk => \@categories })->fetchall_list; } elsif ($forum =~ /forum_(\d+)/) { @forums = $1; } elsif ($forum eq 'all' or not $forum) { @forums = $DB->table('Forum')->select('forum_id')->fetchall_list; } # verify() takes out any forums that we aren't allowed to view verify(\@forums); my $forums = '(' . join(",", @forums) . ')'; my $type = $IN->param('search_type'); $type eq 'AND' or $type eq 'OR' or $type eq 'PHRASE' or $type = 'PHRASE'; my $search_time = $IN->param('search_time'); my $time_limit; if ($search_time) { my ($length, $type) = $search_time =~ /(\d+)(h|d|w|m|y)/; if ($length and $type) { if ($type eq 'h') { $time_limit = time - 60 * 60 * $length } elsif ($type eq 'd') { $time_limit = time - 24 * 60 * 60 * $length } elsif ($type eq 'w') { $time_limit = time - 7 * 24 * 60 * 60 * $length } elsif ($type eq 'm' or $type eq 'y') { require GT::Date; GT::Date->import(qw/timelocal/); my @lt = localtime; $lt[$type eq 'm' ? 4 : 5] -= $length; if ($type eq 'm' and $lt[4] < 0) { $lt[4] += 12; $lt[5]--; } $time_limit = timelocal(@lt); } else { $time_limit = 0; } } elsif ($search_time eq 'last' and $USER and $SESSION) { $time_limit = $SESSION->{info}->{session_data}->{prior_last_seen} || 0; } else { $time_limit = 0; } } my @fields; my $fields = $IN->param('search_fields') || 'sb'; if (index($fields, 's') >= 0) { push @fields, 'post_subject'; } if (index($fields, 'b') >= 0) { push @fields, 'post_message'; } if (index($fields, 'u') >= 0) { push @fields, 'post_username'; } @fields or @fields = ('post_subject', 'post_message'); my $indexed = index($fields, 's') >= 0 && index($fields, 'b') >= 0; my $master_cond = new GT::SQL::Condition; $master_cond->bool('AND'); $master_cond->add(forum_id_fk => IN => \$forums); $master_cond->add(post_moved => IS => undef); $master_cond->add(post_deleted => '=' => 0) unless $USER and $USER->{user_status} == ADMINISTRATOR; $master_cond->add(post_time => '>' => $time_limit) if $time_limit; my $term = $IN->param('search_string'); my $poster = $IN->param('search_user_username'); my ($just_poster, $just_time); if ($poster and (not defined $term or $term !~ /\S/)) { @fields = 'post_username'; $indexed = 0; $term = $poster; $poster = undef; $just_poster = 1; } elsif ($time_limit and (not defined $term or $term !~ /\S/)) { @fields = (); $indexed = 0; $term = undef; $poster = undef; $just_time = 1; } elsif (index($fields, 'u') == -1 and $poster and defined $term and length $term) { $master_cond->add(post_username => $poster); } if ($just_poster) { $search_query{search_user_username} = $term; $search_query{search_string} = ''; } else { $search_query{search_user_username} = $poster; $search_query{search_string} = $term; } $search_query{search_forum} = $forum; $search_query{search_type} = $type; $search_query{search_time} = $search_time; $search_query{search_fields} = $fields; @search_query{'search_forum_type', 'search_forum_id'} = split /_/, $forum, 2; # If not enough information was passed in, go back to the search page with an error message return err(GForum::language('SEARCH_NO_TERM'), \%search_query) unless $term =~ /\S/ or $just_time; return err(GForum::language('SEARCH_NO_FORUMS'), \%search_query) if $forums eq '()'; my $search_query = join ";", map "$_=" . GT::CGI::escape($search_query{$_}), keys %search_query; my $mh = $IN->param('mh'); $mh = ($USER and $USER->{user_default_mh_search} or $CFG->{default_mh_search}) if not $mh or $mh =~ /\D/; my $pg = $IN->param('page'); $pg = 1 if not $pg or $pg =~ /\D/ or $pg < 1; my $sb = $IN->param('sb') || ($indexed ? 'score' : 'post_time'); my $so; if ($IN->param('so')) { $so = uc $IN->param('so'); $so = undef unless $so eq 'ASC' or $so eq 'DESC'; } elsif ($sb =~ s/\s+(asc|desc)$//i) { $so = uc $1; } $sb = 'post_username' if $sb eq 'user_username'; $sb = 'post_time' unless $sb eq 'score' or exists $DB->table('Post')->{schema}->{cols}->{$sb}; $so ||= (($sb eq 'post_time' or $sb eq 'score') ? 'DESC' : 'ASC'); $sb = 'post_time' if $sb eq 'score' and not $indexed; my $sth; my $hits; if ($indexed) { # The indexed search happens when searching both subject and body, which hopefully is the most common if (lc $type eq 'phrase') { $term =~ s/^"//; $term =~ s/"$//; $term = qq!"$term"!; } my $p = $DB->table('Post'); $sth = $p->query_sth( query => $term, filter => $master_cond, mh => $mh, nh => $pg, sb => $sb, so => $so, bool => (lc $type eq 'or') ? 'OR' : 'AND' ); $hits = $p->hits(); } elsif ($just_time) { # if $just_time is set then $master_cond already has enough in it. my $pu = $DB->table('Post' => 'User'); $pu->select_options("ORDER BY $sb $so"); $pu->select_options(sprintf "LIMIT %d, $mh", ($pg > 0 ? ($pg - 1) * $mh : 0)); $sth = $pu->select(left_join => $master_cond) or die $GT::SQL::error; if ((my $rows = $sth->rows) < $mh) { $hits = $mh * ($pg - 1) + $rows; } else { $hits = $DB->table('Post')->count($master_cond); } } else { $master_cond->add(my $field_cond = new GT::SQL::Condition); $field_cond->bool('OR'); my @words; @words = split ' ', $term unless $type eq 'PHRASE'; for (@fields) { if ($_ eq 'post_username' and @fields == 1 and $just_poster) { # If searching for just a username, don't make it a 'LIKE' $field_cond->add($_ => '=' => $term); } elsif ($type eq 'PHRASE') { $field_cond->add($_ => LIKE => "%$term%"); } elsif ($type eq 'AND') { if (@words > 1) { my $value_cond = new GT::SQL::Condition; $value_cond->bool('AND'); for my $word (@words) { $value_cond->add($_ => LIKE => "%$word%"); } $field_cond->add($value_cond); } else { $field_cond->add($_ => LIKE => "%@words%"); } } else { # $type eq 'OR' if (@words > 1) { my $value_cond = new GT::SQL::Condition; $value_cond->bool('OR'); for my $word (@words) { $value_cond->add($_ => LIKE => "%$word%"); } $field_cond->add($value_cond); } else { $field_cond->add($_ => LIKE => "%@words%"); } } } my $pu = $DB->table('Post' => 'User'); $pu->select_options("ORDER BY $sb $so"); $pu->select_options(sprintf "LIMIT %d, $mh", ($pg ? ($pg - 1) * $mh : 0)); $sth = $pu->select(left_join => $master_cond) or die $GT::SQL::error; if ((my $rows = $sth->rows) < $mh) { $hits = $mh * ($pg - 1) + $rows; } else { $hits = $DB->table('Post')->count($master_cond); } } my $fc = $DB->table('Forum' => 'Category'); if (!$hits) { return err(GForum::language('SEARCH_NO_RESULTS'), \%search_query); } my $result_loop = []; my %forums; while (my $post = $sth->fetchrow_hashref) { if ($indexed) { if ($post->{user_id_fk}) { my $user = $DB->table('User')->get($post->{user_id_fk}); @$post{keys %$user} = values %$user if $user; } } $forums{$post->{forum_id_fk}} = 1; push @$result_loop, $post; } require GForum::Post; GForum::Post::normalize($result_loop); if (keys %forums) { my $forum_sth = $fc->select(GT::SQL::Condition->new(forum_id => IN => \("(" . join(',', keys %forums) . ")"))) or die $GT::SQL::error; while (my $forum = $forum_sth->fetchrow_hashref) { $forums{$forum->{forum_id}} = $forum; } } for (@$result_loop) { @$_{keys %{$forums{$_->{forum_id_fk}}}} = values %{$forums{$_->{forum_id_fk}}}; } return( $page->{results} => { ($poster ? (limited_by_user => $poster) : ()), search_words => $term, results => $result_loop, num_results => $hits, mh => $mh, this_page => $pg, so => $so, sb => $sb, search_query => $search_query, %search_query } ); } sub verify { my $forums = shift; for (my $i = 0; $i < @$forums; $i++) { my $perm = GForum::Authenticate::auth('forum_permission', $forums->[$i]); splice @$forums, $i--, 1 unless $perm >= FORUM_PERM_READ; } } END_OF_SUB 1;