# ================================================================== # Links SQL - enhanced directory management system # # Website : http://gossamer-threads.com/ # Support : http://gossamer-threads.com/scripts/support/ # CVS Info : 087,069,082,087,085 # Revision : $Id: Search.pm,v 1.14 2002/05/13 16:59:50 alex 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 Links::User::Search; # ================================================================== use strict; use Links qw/$DB $IN $USER $CFG/; use Links::SiteHTML; use Links::Build; sub handle { #-------------------------------------------------------------------------------- # Determine whether we are displaying the search form, or doing a # search. # my $db = $Links::DB->table('Links'); my $results = {}; my $args = $IN->get_hash; # Remove search fields we aren't allowed to search on. if ($CFG->{search_blocked}) { foreach my $col (@{$CFG->{search_blocked}}) { $col =~ s/^\s*|\s*$//g; if ($args->{$col}) { delete $args->{$col}; $IN->delete ($col); } delete $args->{"$col-lt"}; $IN->delete ("$col-lt"); delete $args->{"$col-gt"}; $IN->delete ("$col-gt"); } } # Make sure we only search on validated links. $IN->param('isValidated', ['Yes']); # If query is set we know we are searching. defined $args->{'query'} and ($args->{query} =~ /\S/) and return search(); # Otherwise, if we pass in a field name, we can search on that too. foreach (keys %{$db->cols}) { if ((defined $args->{$_} and length $args->{$_}) or (defined $args->{"$_-lt"} and length $args->{"$_-lt"}) or (defined $args->{"$_-gt"} and length $args->{"$_-gt"})) { return search(); } } print $IN->header(); print Links::SiteHTML::display('search'); } sub search { # ------------------------------------------------------------------ # Do the search and print out the results. # my $results = GT::Plugins->dispatch ($CFG->{admin_root_path} . '/Plugins', 'search_results', \&query, {}); if (defined $results->{error}) { print $IN->header(); print Links::SiteHTML::display ('search', $results); } else { print $IN->header(); print Links::SiteHTML::display ('search_results', $results); } if ($CFG->{debug_level} > 1) { print "
", GT::SQL->query_stack_disp , "
"; } } sub query { # ------------------------------------------------------------------ # Query the database. # my $query = $IN->param('query'); my $term = $IN->escape($IN->param('query')); # First get our search options. my $args = $IN->get_hash; $args->{query} =~ s,^\s*|\s*$,,g; $args->{bool} = (defined $args->{bool} and $args->{bool} =~ /^(and|or)$/i) ? uc $1 : $CFG->{search_bool}; $args->{nh} = (defined $args->{nh} and $args->{nh} =~ /^(\d+)$/) ? $1 : 1; $args->{mh} = (defined $args->{mh} and $args->{mh} =~ /^(10|25|50|100)$/) ? $1 : $CFG->{search_maxhits}; $args->{substring} = defined $args->{substring} ? $args->{substring} : $CFG->{search_substring}; $args->{so} = (defined $args->{so} and $args->{so} =~ /^(asc|desc)$/i ? $1 : ''); $args->{sb} and ($args->{sb} =~ /^[\w\s,]+$/ or ($args->{sb} = '')); #Cached_Page add two line my $whole_word = 1 if $args->{substring}=0; #For make summary my @search_words = split('\s+',$args->{query}), # Get our Links/Category db object. my $links = $Links::DB->table('Links'); my $categories = $Links::DB->table('Category'); # We don't do a category search if we only have a filters. my $filter = 0; if (! defined $query or ($query eq '')) { $filter = 1; } $args->{filter} = $filter; # Then do the search. $args->{callback} = \&_cat_search_subcat if ($args->{catid}); my $orig_sb = $args->{sb}; my $orig_so = $args->{so}; $args->{sb} = $CFG->{build_sort_order_search_cat}; $args->{so} = ''; $filter and $args->{sb} =~ s/score//; my $cat_sth = $categories->query_sth ($args) unless ($filter); my $cat_count = $filter ? 0 : $categories->hits(); $args->{callback} = \&_search_subcat if ($args->{catid}); $args->{sb} = $orig_sb ? $orig_sb : $CFG->{build_sort_order_search} || ''; $args->{so} = (defined $orig_so and $orig_so =~ /^(asc|desc)$/i) ? $1 : 'ASC'; $filter and $args->{sb} =~ s/score//; my $link_sth = $links->query_sth ($args); my $link_count = $links->hits; #Cached_Page add # query cached table $args->{field_name}='Page'; $args->{bool} = (defined $args->{bool} and $args->{bool} =~ /^(and|or)$/i) ? uc $1 : $CFG->{search_bool}; $args->{substring} = defined $args->{substring} ? $args->{substring} : $CFG->{search_substring}; my $cache_db = $DB->table ('Cached_Page'); my $cache_sth = $cache_db->query_sth($args); my $cache_count = $cache_db->hits; # Cached_Page end # Return if no results. unless ($link_count or $cat_count or $cache_count) { #add $cache_count to this line return { error => Links::language('SEARCH_NOLINKS'), term => $term }; } #Cached_Page add # First lets get all links matches my $results; my (@link_ids, $seen); # id hit from links table if ($link_count) { $results = $link_sth->fetchall_hashref; @link_ids = map { $_->{ID} } @$results; for my $id (@link_ids) { $seen->{$id} ++; } } # Now get cache page if ($cache_count){ my (@cache_ids, $result_fm_cache); while (my $cache_hit = $cache_sth->fetchrow_hashref) { # make summary from page content for display my $page = _make_summary(\@search_words, $cache_hit->{Page}, $whole_word); push @cache_ids, $cache_hit->{LinkID} unless exists $seen->{$cache_hit->{LinkID}}; # get ids only from cached table $result_fm_cache->{$cache_hit->{LinkID}} = $page; } # Now need to query links table again to get extra hits from cached table if (@cache_ids >=1){ my $cond = GT::SQL::Condition->new('ID', 'IN', \@cache_ids); my $extra_links = $links->select ( {isValidated => 'Yes'}, $cond); my $extra_count = $links->hits; $link_count = $link_count + $extra_count; # add to sum my $newlinks; ## There must be results, so don't need to test $extra_count while (my $newlink = $extra_links->fetchrow_hashref) { # replace description with cached page summary for display $newlink->{Description}= $result_fm_cache->{$newlink->{ID}}; push @{$results}, $newlink; # add extra links to the pool } } } #Cached_Page end # Now format the category results. my $count = 0; my ($category_results, @category_results_loop); if (!$filter and $cat_count) { while (my $cat = $cat_sth->fetchrow_hashref) { last if ($count++ > $args->{mh}); my $title = Links::Build::build ('title_linked', { name => $cat->{Full_Name}, complete => 1, home => 0 }); $category_results .= "
  • $title\n"; $cat->{title_linked} = $title; push @category_results_loop, $cat; } } # And format the link results. my ($link_results, %link_output); if ($link_count) { #Cached_Page remove one line here #my $results = $link_sth->fetchall_hashref; $links->add_reviews ($results); if ($CFG->{build_search_gb}) { #Cached_Page remove one line #my @ids = map { $_->{ID} } @$results; my $catlink = $DB->table('CatLinks','Category'); #my %names = $catlink->select ('LinkID', 'Full_Name', { LinkID => \@ids })->fetchall_list; my %names = $catlink->select ('LinkID', 'Full_Name', { LinkID => \@link_ids })->fetchall_list; foreach my $link (@$results) { push @{$link_output{$names{$link->{ID}}}}, $link; } } else { push @{$link_output{none}}, @$results; } } # Join the link results by category if we are grouping. my @link_results_loop; if ($CFG->{build_search_gb}) { foreach my $cat (sort keys %link_output) { my $title = Links::Build::build ('title_linked', { name => $cat, complete => 1, home => 0 }); $link_results .= "

    $title" . join ("", map { Links::SiteHTML::display('link', $_) } @{$link_output{$cat}}); $link_output{$cat}->[0]->{title_linked} = $title; push @link_results_loop, @{$link_output{$cat}}; } } else { $link_results = join ("", map { Links::SiteHTML::display('link', $_) } @{$link_output{none}}); push @link_results_loop, @{$link_output{none}}; } # Generate a toolbar if requested. my $toolbar; if (($link_count > $args->{mh}) or ($cat_count > $args->{mh})) { my $url = $IN->url ( { query_string => 1 } ); $url =~ s/([;&?]?)nh=(\d+)/($1 and $1 eq '?') ? '?' : ''/eg; $toolbar = Links::Build::build ('search_toolbar', { url => $url, numlinks => $link_count > $cat_count ? $link_count : $cat_count, nh => $args->{nh}, mh => $args->{mh} }); } else { $toolbar = ''; } # If we are bolding the results, let's bold them. if ($CFG->{search_bold}) { my $tempquery = $args->{query}; $tempquery =~ s/[+\-"']//g; my @terms = split /\s/, $tempquery; foreach my $term (@terms) { next unless ($term); $term =~ s/^\s*|\s*$//; if ($term =~ s/(.+)\*(.*)/$1/) { push @terms, $2 if ($2); $term = $1; } $link_results =~ s,(<[^>]+>)|(\Q$term\E),defined($1) ? $1 : "$2",gie if ($link_results); $category_results =~ s,(<[^>]+>)|(\Q$term\E),defined($1) ? $1 : "$2",gie if ($category_results); } } # Print the output. my $results = { link_results => $link_results, link_results_loop => \@link_results_loop, category_results => $category_results, category_results_loop => \@category_results_loop, link_hits => $link_count, cat_hits => $cat_count, next => $toolbar, term => $term }; return $results; } sub _search_subcat { # ------------------------------------------------------------------- # First argument is the query/table object, second argument is the current # result set (note: can be quite large). Must return a new result set. # my ($query, $results) = @_; return $results unless (keys %$results); # No matches. my $cat_db = $DB->table ('Category'); my $catlink_db = $DB->table ('CatLinks', 'Category'); # We need the full name of the category. my @cat_ids = $IN->param('catid') or return $results; my (@children, %seen); foreach my $id (@cat_ids) { next if ($id !~ /^\d+$/); my $child = $cat_db->children($id) or next; push @children, @$child, $id; } @children or return $results; @children = grep { ! $seen{$_}++ } @children; # Now do the joined query. my $link_ids = '(' . join(',', keys %{$results}) . ')'; my $cat_ids = '(' . join(',', @children) . ')'; my $cond = GT::SQL::Condition->new ( 'CategoryID', 'IN', \$cat_ids, 'LinkID', 'IN', \$link_ids ); my $sth = $catlink_db->select ($cond, ['LinkID']); my $filtered = {}; while (my ($id) = $sth->fetchrow_array) { $filtered->{$id} = $results->{$id}; } return $filtered; } sub _search_subcat_and { # ------------------------------------------------------------------- # Search subcategories using AND. # my ($query, $results) = @_; return $results unless (keys %$results); # No matches my $cat_db = $DB->table ('Category'); my $catlink_db = $DB->table ('CatLinks', 'Category'); # We need the full name of the category. my @cat_ids = $IN->param('catid') or return $results; my %final = %$results; foreach my $id (@cat_ids) { next unless ($id =~ /^\d+$/); my @children; my $childs = $cat_db->children($id); push @children, @$childs, $id; my $cond = GT::SQL::Condition->new( CategoryID => 'IN' => \@children, LinkID => 'IN' => [ keys %final] ); %final = (); my $sth = $catlink_db->select($cond, ['LinkID']); while (my $link_id = $sth->fetchrow_array) { $final{$link_id} = $results->{$link_id}; } } return \%final; } sub _cat_search_subcat { # ------------------------------------------------------------------- # First argument is the query/table object, second argument is the current # result set (note: can be quite large). Must return a new result set. # my ($query, $results) = @_; return $results unless (keys %$results); # No matches. my $cat_db = $DB->table('Category'); my @cat_ids = $IN->param('catid') or return $results; my (@children, %seen); foreach my $id (@cat_ids) { next if ($id !~ /^\d+$/); my $child = $cat_db->children($id) or next; push @children, @$child, $id; } @children or return $results; @children = grep { ! $seen{$_}++ } @children; my %subcats = map { $_ => 1 } @children; my $filtered = {}; while (my ($k, $s) = each %$results) { $filtered->{$k} = $s if (exists $subcats{$k}); } return $filtered; } sub _make_summary { #-------------------------------------------------------------------- #Make a excerpt from matched sentences of cached pages # Modified from KSearch v1.4, Copyright (C) 2000 David Kim (kscripts.com) my ($terms, $page, $whole_word) = @_; my ($desc, $line, $pre, $post, $match, $prem, $postm, $bdy); my $SHOW_MATCHES_LENGTH=150; #excerpt sentence length for result my $show_matches = 1; # how many matches to show my @lines; $whole_word =1; my $spaces = " " x $SHOW_MATCHES_LENGTH; $bdy = $page; $bdy =~ s/<.*?>|&\w+;|&\w+;|&#\d+;/ /gi; foreach my $term (@$terms) { my $count; if ($whole_word==1) { while ($count < $show_matches && $bdy =~ /\b$term\b/gis) { $count++; $pre = "$`"; $post = "$'"; $match = $&; my $LENGTH = int (($SHOW_MATCHES_LENGTH - length $match)/2); $pre =~ m/\b(.{0,$LENGTH})$/; $prem = $1; $post =~ m/^(.{0,$LENGTH})\b/; $postm = $1; $post = "$'", $bdy = "$pre $post"; $line = join("", '...', $prem, $match, $postm, '...'); push @lines, $line; } } else { while ($count < $show_matches && $bdy =~ /$term/gis) { $count++; $pre = "$`"; $post = "$'"; $match = $&; my $LENGTH = int (($SHOW_MATCHES_LENGTH - length $match)/2); $pre =~ m/\b(.{0,$LENGTH})$/; $prem = $1; $post =~ m/^(.{0,$LENGTH})\b/; $postm = $1; $post = "$'", $bdy = "$pre $post"; $line = join("", '...', $prem, $match, $postm, ' '); push @lines, $line; } } } return join(" ", @lines); } 1;