1 package BSE::UI::Search;
3 use base 'BSE::UI::Dispatch';
6 use Constants qw(:search);
10 use BSE::Util::HTML qw':default popup_menu';
11 use BSE::Util::Tags qw(tag_article);
14 our $VERSION = "1.001";
21 sub actions { \%actions }
23 sub default_action { 'search' }
26 my ($class, $req) = @_;
31 my $results_per_page = int($cgi->param('pp') || 10);
32 $results_per_page >= 1 or $results_per_page = 10;
33 my $words = $cgi->param('q');
34 my $section = $cgi->param('s');
35 my $date = $cgi->param('d');
36 my $admin = $cgi->param('admin') ? 1 : 0;
37 my $match_all = $cgi->param('match_all');
38 $section = '' if !defined $section;
39 $date = 'ar' if ! defined $date;
41 my @terms; # terms as parsed by the search engine
43 if (defined $words && length $words) {
44 $case_sensitive = $words ne lc $words;
45 @results = getSearchResult($req, $words, $section, $date, \@terms, $match_all);
48 $words = ''; # so we don't return junk for the form default
51 my $page_count = int((@results + $results_per_page - 1)/$results_per_page);
53 my $page_number = $cgi->param('page') || 1;
54 $page_number = $page_count if $page_number > $page_count;
58 my $articles_start = ($page_number-1) * $results_per_page;
59 my $articles_end = $articles_start + $results_per_page-1;
60 $articles_end = $#results if $articles_end >= @results;
62 if ($cfg->entry('search', 'keep_inaccessible')) {
63 for my $entry (@results[$articles_start..$articles_end]) {
64 my $article = Articles->getByPkey($entry->[0])
65 or die "Cannot retrieve article $entry->[0]\n";
66 push(@articles, $article);
70 my %remove; # used later to remove the inaccessible from @results;
71 # we need to check accessiblity on each article
74 while ($index < @results && $seen <= $articles_end) {
75 my $id = $results[$index][0];
76 my $article = Articles->getByPkey($id)
77 or die "Cannot retrieve article $id\n";
78 if ($req->siteuser_has_access($article)) {
79 if ($seen >= $articles_start) {
80 push @articles, $article;
89 @results = grep !$remove{$_->[0]}, @results;
93 for my $article (@articles) {
94 my $generator = $article->{generator};
95 eval "use $generator";
96 my $gen = $generator->new(top=>$article, cfg=>$cfg);
97 $article = $gen->get_real_article($article);
100 $page_count = int((@results + $results_per_page - 1)/$results_per_page);
102 # make an array of hashes (to preserve order)
104 @excluded{@SEARCH_EXCLUDE} = @SEARCH_EXCLUDE;
106 @included{@SEARCH_INCLUDE} = @SEARCH_INCLUDE;
107 my @sections = map { { $_->{id} => $_->{title} } }
108 sort { $b->{displayOrder} <=> $a->{displayOrder} }
109 grep { ($_->{listed} || $included{$_->{id}})
110 && !$excluded{$_->{id}} }
111 Articles->getBy('level', 1);
112 unshift(@sections, { ""=>$SEARCH_ALL });
113 my %sections = map { %$_ } @sections;
114 # now a list of values ( in the correct order
115 @sections = map { keys %$_ } @sections;
117 my %scores = map @$_, @results;
120 for my $score (values %scores) {
121 $score > $max_score and $max_score = $score;
124 my %highlight_prefix;
125 my %highlight_suffix;
126 for my $type (qw(keyword author pageTitle file_displayName
127 file_description file_notes summary description product_code)) {
128 $highlight_prefix{$type} =
129 $cfg->entry('search highlight', "${type}_prefix", "<b>");
130 $highlight_suffix{$type} =
131 $cfg->entry('search highlight', "${type}_suffix", "</b>");
134 my $page_num_iter = 0;
136 my $article_index = -1;
137 my $result_seq = ($page_number-1) * $results_per_page;
140 my $words_re_str = '\b('.join('|', map quotemeta, @terms).')';
141 my $highlight_partial = $cfg->entryBool('search', 'highlight_partial', 1);
142 $words_re_str .= '\b' unless $highlight_partial;
143 my $words_re = qr/$words_re_str/i;
150 $req->dyn_user_tags(),
155 if ($article_index < @articles) {
156 $current_result = $articles[$article_index];
158 $excerpt = excerpt($cfg, $admin, $case_sensitive, $current_result, \$found, \@terms);
160 $req->set_article(result => $current_result);
162 for my $field (qw/pageTitle summary keyword description author product_code/) {
163 my $value = $current_result->{$field};
164 defined $value or $value = '';
165 $value =~ s!$words_re!$highlight_prefix{$field}$1$highlight_suffix{$field}!g
167 $match_tags{$field} = $value;
172 for my $file ($current_result->files) {
175 for my $field (qw(displayName description notes)) {
176 my $prefix = $highlight_prefix{"file_$field"};
177 my $suffix = $highlight_suffix{"file_$field"};
178 $fileout{$field. "_matched"} = $file->{$field} =~ /$words_re/;
179 ++$found if ($fileout{$field} = $file->{$field})
180 =~ s!$words_re!$prefix$1$suffix!g;
183 $fileout{notes_excerpt} =
184 excerpt($cfg, $admin, $case_sensitive, $current_result, \$found, \@terms, 'file_notes', $file->{notes});
185 push @files, [ \%fileout, $file ];
192 $req->set_article(result => undef);
200 if ($arg eq 'score') {
201 return sprintf("%.1f", 100.0 * $scores{$current_result->{id}} / $max_score);
203 return tag_article($current_result, $cfg, $arg);
207 my ($func, $args) = split ' ', $_[0];
208 use POSIX 'strftime';
210 or return "** $func not found for date **";
211 my $date = $acts{$func}->($args)
213 my ($year, $month, $day) = $date =~ /(\d+)\D+(\d+)\D+(\d+)/;
216 return strftime('%d-%b-%Y', 0, 0, 0, $day, $month, $year, 0, 0);
218 keywords => sub { $match_tags{keyword} },
219 author => sub { $match_tags{author} },
220 pageTitle => sub { $match_tags{pageTitle} },
221 match_summary => sub { $match_tags{summary} },
222 description => sub { $match_tags{description} },
223 product_code => sub { $match_tags{product_code} },
225 ifMatchfiles => sub { @files },
226 matchfile_count => sub { @files },
227 matchfile_index => sub { $file_index },
228 iterate_matchfiles_reset => sub { $file_index = -1 },
229 iterate_matchfiles => sub { ++$file_index < @files },
233 $file_index < @files or return '';
234 my $file_entry = $files[$file_index];
235 # already html escaped
236 exists $file_entry->[0]{$args} and return $file_entry->[0]{$args};
238 my $value = $file_entry->[1]{$args};
239 defined $value or return '';
244 ifResults => sub { scalar @results; },
245 ifSearch => sub { defined $words and length $words },
246 dateSelected => sub { $_[0] eq $date ? 'selected="selected"' : '' },
253 return $admin ? $current_result->{admin} : $current_result->link($cfg);
255 count => sub { scalar @results },
256 multiple => sub { @results != 1 },
257 terms => sub { escape_html($words) },
258 resultSeq => sub { $result_seq },
259 list => sub { popup_menu(-name=>'s', -id => 'search_s',
262 -default=>$section) },
267 return ++$page_num_iter <= $page_count;
269 page => sub { $page_num_iter },
270 ifCurrentSearchPage =>
271 sub { $page_num_iter == $page_number },
274 my $work_words = $words;
275 $ENV{SCRIPT_NAME} . "?q=" . escape_uri($work_words) .
276 "&s=" . escape_uri($section) .
277 "&d=" . escape_uri($date) .
278 "&page=".$page_num_iter .
279 "&pp=$results_per_page";
282 [ \&tag_highlight_result, \$current_result, $cfg, $words_re ],
283 admin_search => $admin,
286 my $template = $cgi->param('embed') ? 'include/search_results' : 'search';
287 my $result = $req->dyn_response($template, \%acts);
288 %acts = (); # remove any circular refs
293 sub tag_highlight_result {
294 my ($rcurrent_result, $cfg, $words_re, $arg) = @_;
297 or return "** highlight_result must be in results iterator **";
299 my $text = $$rcurrent_result->{$arg};
300 defined $text or return '';
302 $text = escape_html($text);
304 my $prefix = $cfg->entry('search highlight', "${arg}_prefix", "<b>");
305 my $suffix = $cfg->entry('search highlight', "${arg}_suffix", "</b>");
307 $text =~ s/$words_re/$prefix$1$suffix/g;
312 sub getSearchResult {
313 my ($req, $words, $section, $date, $terms, $match_all) = @_;
316 my $searcher_class = $cfg->entry('search', 'searcher', 'BSE::Search::BSE');
317 (my $searcher_file = $searcher_class . '.pm') =~ s!::!/!g;;
318 require $searcher_file;
319 my $searcher = $searcher_class->new(cfg => $cfg);
320 return $searcher->search($words, $section, $date, $terms, $match_all, $req);
326 my ($cfg, $admin, $case_sensitive, $article, $found, $terms, $type, $text) = @_;
328 my $generator = $article->{generator};
330 $generator =~ /\S/ or confess "generator for $article->{id} is blank";
332 eval "use $generator";
333 confess "Cannot use $generator: $@" if $@;
335 $gens{$generator} ||= $generator->new(admin=>$admin, cfg=>$cfg, top=>$article);
337 return $gens{$generator}->excerpt($article, $found, $case_sensitive, $terms, $type, $text);