source: trunk/lib/OpenGuides/Search.pm

Last change on this file was 1328, checked in by bob, 10 years ago

merge 0.66 release branch

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 26.1 KB
Line 
1package OpenGuides::Search;
2use strict;
3our $VERSION = '0.14';
4
5use CGI qw( :standard );
6use Wiki::Toolkit::Plugin::Locator::Grid;
7use File::Spec::Functions qw(:ALL);
8use OpenGuides::Template;
9use OpenGuides::Utils;
10use Parse::RecDescent;
11
12=head1 NAME
13
14OpenGuides::Search - Search form generation and processing for OpenGuides.
15
16=head1 DESCRIPTION
17
18Does search stuff for OpenGuides.  Distributed and installed as part of
19the OpenGuides project, not intended for independent installation.
20This documentation is probably only useful to OpenGuides developers.
21
22=head1 SYNOPSIS
23
24  use CGI;
25  use OpenGuides::Config;
26  use OpenGuides::Search;
27
28  my $config = OpenGuides::Config->new( file => "wiki.conf" );
29  my $search = OpenGuides::Search->new( config => $config );
30  my %vars = CGI::Vars();
31  $search->run( vars => \%vars );
32
33=head1 METHODS
34
35=over 4
36
37=item B<new>
38
39  my $config = OpenGuides::Config->new( file => "wiki.conf" );
40  my $search = OpenGuides::Search->new( config => $config );
41
42=cut
43
44sub new {
45    my ($class, %args) = @_;
46    my $config = $args{config};
47    my $self   = { config => $config };
48    bless $self, $class;
49
50    my $wiki = OpenGuides::Utils->make_wiki_object( config => $config );
51
52    $self->{wiki}     = $wiki;
53    $self->{wikimain} = $config->script_url . $config->script_name;
54    $self->{css}      = $config->stylesheet_url;
55    $self->{head}     = $config->site_name . " Search";
56
57    my $geo_handler = $config->geo_handler;
58    my %locator_params;
59    if ( $geo_handler == 1 ) {
60        %locator_params = ( x => "os_x", y => "os_y" );
61    } elsif ( $geo_handler == 2 ) {
62        %locator_params = ( x => "osie_x", y => "osie_y" );
63    } elsif ( $geo_handler == 3 ) {
64        %locator_params = ( x => "easting", y => "northing" );
65    }
66
67    my $locator = Wiki::Toolkit::Plugin::Locator::Grid->new( %locator_params );
68    $wiki->register_plugin( plugin => $locator );
69    $self->{locator} = $locator;
70
71    return $self;
72}
73
74=item B<wiki>
75
76  my $wiki = $search->wiki;
77
78An accessor; returns the underlying L<Wiki::Toolkit> object.
79
80=cut
81
82sub wiki {
83    my $self = shift;
84    return $self->{wiki};
85}
86
87=item B<config>
88
89  my $config = $search->config;
90
91An accessor; returns the underlying L<OpenGuides::Config> object.
92
93=cut
94
95sub config {
96    my $self = shift;
97    return $self->{config};
98}
99
100=item B<run>
101
102  my %vars = CGI::Vars();
103  $search->run(
104                vars           => \%vars,
105                return_output  => 1,   # defaults to 0
106                return_tt_vars => 1,  # defaults to 0
107              );
108
109The C<return_output> parameter is optional.  If supplied and true, the
110stuff that would normally be printed to STDOUT will be returned as a
111string instead.
112
113The C<return_tt_vars> parameter is also optional.  If supplied and
114true, the template is not processed and the variables that would have
115been passed to it are returned as a hash.  This parameter takes
116precedence over C<return_output>.
117
118These two parameters exist to make testing easier; you probably don't
119want to use them in production.
120
121You can also request just the raw search results:
122
123  my %results = $search->run(
124                              os_x    => 528864,
125                              os_y    => 180797,
126                              os_dist => 750,
127                              format  => "raw",
128                            );
129
130Results are returned as a hash, keyed on the page name.  All results
131are returned, not just the first C<page>.  The values in the hash are
132hashes themselves, with the following key/value pairs:
133
134=over 4
135
136=item * name
137
138=item * wgs84_lat - WGS-84 latitude
139
140=item * wgs84_long - WGS-84 longitude
141
142=item * summary
143
144=item * distance - distance (in metres) from origin, if origin exists
145
146=item * score - relevance to search string, if search string exists; higher score means more relevance
147
148=back
149
150In case you're struggling to follow the code, it does the following:
1511) Processes the parameters, and bails out if it hit a problem with them
1522) If a search string was given, do a text search
1533) If distance search paramaters were given, do a distance search
1544) If no search has occured, print out the search form
1555) If an error occured, bail out
1566) If we got a single hit on a string search, redirect to it
1577) If no results were found, give an empty search results page
1588) Sort the results by either score or distance
1599) Decide which results to show, based on paging
16010) Display the appropriate page of the results
161
162=back
163
164=cut
165
166sub run {
167    my ($self, %args) = @_;
168    $self->{return_output}  = $args{return_output}  || 0;
169    $self->{return_tt_vars} = $args{return_tt_vars} || 0;
170
171    my $want_raw;
172    if ( $args{vars}{format} && $args{vars}{format} eq "raw" ) {
173        $want_raw = 1;
174    }
175
176    $self->process_params( $args{vars} );
177    if ( $self->{error} ) {
178        warn $self->{error};
179        my %tt_vars = ( error_message => $self->{error} );
180        $self->process_template( tt_vars => \%tt_vars );
181        return;
182    }
183
184    my %tt_vars = (
185                   format      => $args{'vars'}->{'format'},
186                   ss_version  => $VERSION,
187                   ss_info_url => 'http://openguides.org/search_help'
188                  );
189
190    my $doing_search;
191
192    # Run a text search if we have a search string.
193    if ( $self->{search_string} ) {
194        $doing_search = 1;
195        $tt_vars{search_terms} = $self->{search_string};
196        $self->run_text_search;
197    }
198
199    # Run a distance search if we have sufficient criteria.
200    if ( defined $self->{distance_in_metres}
201         && defined $self->{x} && defined $self->{y} ) {
202        $doing_search = 1;
203        # Make sure to pass the criteria to the template.
204        $tt_vars{dist} = $self->{distance_in_metres};
205        $tt_vars{latitude} = $self->{latitude};
206        $tt_vars{longitude} = $self->{longitude};
207        if ( $self->config->geo_handler eq 1 ) {
208            $tt_vars{coord_field_1_value} = $self->{os_x};
209            $tt_vars{coord_field_2_value} = $self->{os_y};
210        } elsif ( $self->config->geo_handler eq 2 ) {
211            $tt_vars{coord_field_1_value} = $self->{osie_x};
212            $tt_vars{coord_field_2_value} = $self->{osie_y};
213        } elsif ( $self->config->geo_handler eq 3 ) {
214            $tt_vars{coord_field_1_value} = $self->{latitude};
215            $tt_vars{coord_field_2_value} = $self->{longitude};
216        }
217        $self->run_distance_search;
218    }
219
220    # If we're not doing a search then just print the search form (or return
221    # an empty hash if we were asked for raw results).
222    if ( !$doing_search ) {
223        if ( $want_raw ) {
224            return ( );
225        } else {
226            return $self->process_template( tt_vars => \%tt_vars );
227        }
228    }
229
230    # At this point either $self->{error} or $self->{results} will be filled.
231    if ( $self->{error} ) {
232        $tt_vars{error_message} = $self->{error};
233        $self->process_template( tt_vars => \%tt_vars );
234        return;
235    }
236
237    # So now we know that we have been asked to perform a search, and we
238    # have performed it.
239    #
240    # $self->{results} will be a hash of refs to hashes like so:
241    #   'Node Name' => {
242    #                    name     => 'Node Name',
243    #                    distance => $distance_from_origin_if_any,
244    #                    score    => $relevance_to_search_string
245    #                  }
246
247    my %results_hash = %{ $self->{results} || [] };
248
249    # If we were asked for just the raw results, return them now, after
250    # grabbing additional info.
251    if ( $want_raw ) {
252        foreach my $node ( keys %results_hash ) {
253            my %data = $self->wiki->retrieve_node( $node );
254            $results_hash{$node}{summary} = $data{metadata}{summary}[0];
255            my $lat  = $data{metadata}{latitude}[0];
256            my $long = $data{metadata}{longitude}[0];
257            my ( $wgs84_lat, $wgs84_long ) = OpenGuides::Utils->get_wgs84_coords( latitude => $lat, longitude => $long, config => $self->config );
258            $results_hash{$node}{wgs84_lat} = $wgs84_lat;
259            $results_hash{$node}{wgs84_long} = $wgs84_long;
260        }
261        return %results_hash;
262    }
263
264    my @results = values %results_hash;
265    my $numres = scalar @results;
266
267    # If we only have a single hit, and the title is a good enough match
268    # to the search string, redirect to that node.
269    # (Don't try a fuzzy search on a blank search string - Plucene chokes.)
270    if ( $self->{search_string} && $numres == 1 && !$self->{return_tt_vars}) {
271        my %fuzzies = $self->wiki->fuzzy_title_match($self->{search_string});
272        if ( scalar keys %fuzzies ) {
273            my $node = $results[0]{name};
274            my $formatter = $self->wiki->formatter;
275            my $node_param = CGI::escape(
276                            $formatter->node_name_to_node_param( $node )
277                                        );
278            my $output = CGI::redirect( $self->{wikimain} . "?$node_param" );
279            return $output if $self->{return_output};
280            print $output;
281            return;
282        }
283    }
284
285    # If we had no hits then go straight to the template.
286    if ( $numres == 0 ) {
287        %tt_vars = (
288                     %tt_vars,
289                     first_num => 0,
290                     results   => [],
291                   );
292        return $self->process_template( tt_vars => \%tt_vars );
293    }
294
295    # Otherwise, we browse through the results a page at a time.
296
297    # Figure out which results we're going to be showing on this
298    # page, and what the first one for the next page will be.
299    my $startpos = $args{vars}{next} || 0;
300    $tt_vars{first_num} = $numres ? $startpos + 1 : 0;
301    $tt_vars{last_num}  = $numres > $startpos + 20 ? $startpos + 20 : $numres;
302    $tt_vars{total_num} = $numres;
303    if ( $numres > $startpos + 20 ) {
304        $tt_vars{next_page_startpos} = $startpos + 20;
305    }
306
307    # Sort the results - by distance if we're searching on that
308    # or by score otherwise.
309    if ( $self->{distance_in_metres} ) {
310        @results = sort { $a->{distance} <=> $b->{distance} } @results;
311    } else {
312        @results = sort { $b->{score} <=> $a->{score} } @results;
313    }
314
315    # Now snip out just the ones for this page.  The -1 is because
316    # arrays index from 0 and people from 1.
317    my $from = $tt_vars{first_num} ? $tt_vars{first_num} - 1 : 0;
318    my $to   = $tt_vars{last_num} - 1; # kludge to empty arr for no results
319    @results = @results[ $from .. $to ];
320
321    # Add the URL to each result hit.
322    my $formatter = $self->wiki->formatter;
323    foreach my $i ( 0 .. $#results ) {
324        my $name = $results[$i]{name};
325
326        # Add the one-line summary of the node, if there is one.
327        my %node = $self->wiki->retrieve_node($name);
328        $results[$i]{summary} = $node{metadata}{summary}[0];
329
330        my $node_param = $formatter->node_name_to_node_param( $name );
331        $results[$i]{url} = $self->{wikimain} . "?$node_param";
332    }
333
334    # Finally pass the results to the template.
335    $tt_vars{results} = \@results;
336    $self->process_template( tt_vars => \%tt_vars );
337}
338
339sub run_text_search {
340    my $self = shift;
341    my $searchstr = $self->{search_string};
342    my $wiki = $self->wiki;
343
344    # Create parser to parse the search string.
345    my $parser = Parse::RecDescent->new( q{
346
347        search: list eostring {$return = $item[1]}
348
349        list: comby(s)
350            {$return = (@{$item[1]}>1) ? ['AND', @{$item[1]}] : $item[1][0]}
351
352        comby: <leftop: term ',' term>
353            {$return = (@{$item[1]}>1) ? ['OR', @{$item[1]}] : $item[1][0]}
354
355        term: '(' list ')' {$return = $item[2]}
356            |        '-' term {$return = ['NOT', @{$item[2]}]}
357            |        '"' word(s) '"' {$return = ['phrase', join " ", @{$item[2]}]}
358            |        word {$return = ['word', $item[1]]}
359            |        '[' word(s) ']' {$return = ['title', @{$item[2]}]}
360
361        word: /[\w'*%]+/ {$return = $item[1]}
362
363        eostring: /^\Z/
364
365    } );
366
367    unless ( $parser ) {
368        warn $@;
369        $self->{error} = "Can't create parse object - $@";
370        return $self;
371    }
372
373    # Run parser over search string.
374    my $tree = $parser->search( $searchstr );
375    unless ( $tree ) {
376        $self->{error} = "Syntax error in search: $searchstr";
377        return $self;
378    }
379
380    # Run the search over the generated search tree.
381    my %results = $self->_run_search_tree( tree => $tree );
382    $self->{results} = \%results;
383    return $self;
384}
385
386sub _run_search_tree {
387    my ($self, %args) = @_;
388    my $tree = $args{tree};
389    my @tree_arr = @$tree;
390    my $op = shift @tree_arr;
391    my $method = "_run_" . $op . "_search";
392    return $self->can($method) ? $self->$method(@tree_arr) : undef;
393}
394
395=head1 INPUT
396
397=over
398
399=item B<word>
400
401a single word will be matched as-is. For example, a search on
402
403  escalator
404
405will return all pages containing the word "escalator".
406
407=cut
408
409sub _run_word_search {
410    my ($self, $word) = @_;
411    # A word is just a small phrase.
412    return $self->_run_phrase_search( $word );
413}
414
415=item B<AND searches>
416
417A list of words with no punctuation will be ANDed, for example:
418
419  restaurant vegetarian
420
421will return all pages containing both the word "restaurant" and the word
422"vegetarian".
423
424=cut
425
426sub _run_AND_search {
427    my ($self, @subsearches) = @_;
428
429    # Do the first subsearch.
430    my %results = $self->_run_search_tree( tree => $subsearches[0] );
431
432    # Now do the rest one at a time and remove from the results anything
433    # that doesn't come up in each subsearch.  Results that survive will
434    # have a score that's the sum of their score in each subsearch.
435    foreach my $tree ( @subsearches[ 1 .. $#subsearches ] ) {
436        my %subres = $self->_run_search_tree( tree => $tree );
437        my @pages = keys %results;
438        foreach my $page ( @pages ) {
439          if ( exists $subres{$page} ) {
440                $results{$page}{score} += $subres{$page}{score};
441              } else {
442                delete $results{$page};
443            }
444        }
445      }
446
447    return %results;
448}
449
450=item B<OR searches>
451
452A list of words separated by commas (and optional spaces) will be ORed,
453for example:
454
455  restaurant, cafe
456
457will return all pages containing either the word "restaurant" or the
458word "cafe".
459
460=cut
461
462sub _run_OR_search {
463    my ($self, @subsearches) = @_;
464
465    # Do all the searches.  Results will have a score that's the sum
466    # of their score in each subsearch.
467    my %results;
468    foreach my $tree ( @subsearches ) {
469        my %subres = $self->_run_search_tree( tree => $tree );
470        foreach my $page ( keys %subres ) {
471          if ( $results{$page} ) {
472                $results{$page}{score} += $subres{$page}{score};
473              } else {
474                $results{$page} = $subres{$page};
475            }
476        }
477      }
478    return %results;
479}
480
481=item B<phrase searches>
482
483Enclose phrases in double quotes, for example:
484
485  "meat pie"
486
487will return all pages that contain the exact phrase "meat pie" - not pages
488that only contain, for example, "apple pie and meat sausage".
489
490=cut
491
492sub _run_phrase_search {
493    my ($self, $phrase) = @_;
494    my $wiki = $self->wiki;
495
496    # Search title and body.
497    my %contents_res = $wiki->search_nodes( $phrase );
498
499    # Rationalise the scores a little.  The scores returned by
500    # Wiki::Toolkit::Search::Plucene are simply a ranking.
501    my $num_results = scalar keys %contents_res;
502    foreach my $node ( keys %contents_res ) {
503        $contents_res{$node} = int( $contents_res{$node} / $num_results ) + 1;
504    }
505
506    my @tmp = keys %contents_res;
507    foreach my $node ( @tmp ) {
508        my $content = $wiki->retrieve_node( $node );
509
510        # Don't include redirects in search results.
511        if ($content =~ /^#REDIRECT/) {
512            delete $contents_res{$node};
513            next;
514        }
515       
516        # It'll be a real phrase (as opposed to a word) if it has a space in it.
517        # In this case, dump out the nodes that don't match the search exactly.
518        # I don't know why the phrase searching isn't working properly.  Fix later.
519        if ( $phrase =~ /\s/ ) {
520            unless ( $content =~ /$phrase/i || $node =~ /$phrase/i ) {
521                delete $contents_res{$node};
522            }
523        }
524
525    }
526
527    my %results = map { $_ => { name => $_, score => $contents_res{$_} } }
528                      keys %contents_res;
529
530    # Bump up the score if the title matches.
531    foreach my $node ( keys %results ) {
532        $results{$node}{score} += 10 if $node =~ /$phrase/i;
533    }
534
535    # Search categories.
536    my @catmatches = $wiki->list_nodes_by_metadata(
537                                 metadata_type  => "category",
538                                 metadata_value => $phrase,
539                                 ignore_case    => 1,
540    );
541
542    foreach my $node ( @catmatches ) {
543        if ( $results{$node} ) {
544            $results{$node}{score} += 3;
545        } else {
546            $results{$node} = { name => $node, score => 3 };
547        }
548    }
549
550    # Search locales.
551    my @locmatches = $wiki->list_nodes_by_metadata(
552                                 metadata_type  => "locale",
553                                 metadata_value => $phrase,
554                                 ignore_case    => 1,
555    );
556
557    foreach my $node ( @locmatches ) {
558        if ( $results{$node} ) {
559            $results{$node}{score} += 3;
560        } else {
561            $results{$node} = { name => $node, score => 3 };
562        }
563    }
564
565    return %results;
566}
567
568=back
569
570=head1 SEARCHING BY DISTANCE
571
572To perform a distance search, you need to supply one of the following
573sets of criteria to specify the distance to search within, and the
574origin (centre) of the search:
575
576=over
577
578=item B<os_dist, os_x, and os_y>
579
580Only works if you chose to use British National Grid in wiki.conf
581
582=item B<osie_dist, osie_x, and osie_y>
583
584Only works if you chose to use Irish National Grid in wiki.conf
585
586=item B<latlong_dist, latitude, and longitude>
587
588Should always work, but has a habit of "finding" things a couple of
589metres away from themselves.
590
591=back
592
593You can perform both pure distance searches and distance searches in
594combination with text searches.
595
596=cut
597
598# Note this is called after any text search is run, and it is only called
599# if there are sufficient criteria to perform the search.
600sub run_distance_search {
601    my $self = shift;
602    my $x    = $self->{x};
603    my $y    = $self->{y};
604    my $dist = $self->{distance_in_metres};
605
606    my @close = $self->{locator}->find_within_distance(
607                                                        x      => $x,
608                                                        y      => $y,
609                                                        metres => $dist,
610                                                      );
611
612    if ( $self->{results} ) {
613        my %close_hash = map { $_ => 1 } @close;
614        my %results = %{ $self->{results} };
615        my @candidates = keys %results;
616        foreach my $node ( @candidates ) {
617            if ( exists $close_hash{$node} ) {
618                my $distance = $self->_get_distance(
619                                                     node => $node,
620                                                     x    => $x,
621                                                     y    => $y,
622                                                   );
623                $results{$node}{distance} = $distance;
624            } else {
625                delete $results{$node};
626            }
627        }
628        $self->{results} = \%results;
629    } else {
630        my %results;
631        foreach my $node ( @close ) {
632            my $distance = $self->_get_distance (
633                                                     node => $node,
634                                                     x    => $x,
635                                                     y    => $y,
636                                                   );
637            $results{$node} = {
638                                name     => $node,
639                                distance => $distance,
640                              };
641        }
642        $self->{results} = \%results;
643    }
644    return $self;
645}
646
647sub _get_distance {
648    my ($self, %args) = @_;
649    my ($node, $x, $y) = @args{ qw( node x y ) };
650    return $self->{locator}->distance(
651                                       from_x  => $x,
652                                       from_y  => $y,
653                                       to_node => $node,
654                                       unit    => "metres"
655                                     );
656}
657
658sub process_params {
659    my ($self, $vars_hashref) = @_;
660    my %vars = %{ $vars_hashref || {} };
661
662    # Make sure that we don't have any data left over from previous invocation.
663    # This is useful for testing purposes at the moment and will be essential
664    # for mod_perl implementations.
665    delete $self->{x};
666    delete $self->{y};
667    delete $self->{distance_in_metres};
668    delete $self->{search_string};
669    delete $self->{results};
670
671    # Strip out any non-digits from distance and OS co-ords.
672    foreach my $param ( qw( os_x os_y osie_x osie_y
673                            osie_dist os_dist latlong_dist ) ) {
674        if ( defined $vars{$param} ) {
675            $vars{$param} =~ s/[^0-9]//g;
676            # 0 is an allowed value but the empty string isn't.
677            delete $vars{$param} if $vars{$param} eq "";
678        }
679    }
680
681    # Latitude and longitude are also allowed '-' and '.'
682    foreach my $param( qw( latitude longitude ) ) {
683        if ( defined $vars{$param} ) {
684            $vars{$param} =~ s/[^-\.0-9]//g;
685            # 0 is an allowed value but the empty string isn't.
686            delete $vars{$param} if $vars{$param} eq "";
687        }
688    }
689
690    # Set $self->{distance_in_metres}, $self->{x}, $self->{y},
691    # depending on whether we got
692    # OS co-ords or lat/long.  Only store parameters if they're complete,
693    # and supported by our method of distance calculation.
694    if ( defined $vars{os_x} && defined $vars{os_y} && defined $vars{os_dist}
695         && $self->config->geo_handler eq 1 ) {
696        $self->{x} = $vars{os_x};
697        $self->{y} = $vars{os_y};
698        $self->{distance_in_metres} = $vars{os_dist};
699    } elsif ( defined $vars{osie_x} && defined $vars{osie_y}
700         && defined $vars{osie_dist}
701         && $self->config->geo_handler eq 2 ) {
702        $self->{x} = $vars{osie_x};
703        $self->{y} = $vars{osie_y};
704        $self->{distance_in_metres} = $vars{osie_dist};
705    } elsif ( defined $vars{latitude} && defined $vars{longitude}
706              && defined $vars{latlong_dist} ) {
707        # All handlers can do lat/long, but they all do it differently.
708        if ( $self->config->geo_handler eq 1 ) {
709            require Geo::Coordinates::OSGB;
710            my ( $x, $y ) = Geo::Coordinates::OSGB::ll_to_grid(
711                                $vars{latitude}, $vars{longitude} );
712            $self->{x} = sprintf( "%d", $x );
713            $self->{y} = sprintf( "%d", $y );
714        } elsif ( $self->config->geo_handler eq 2 ) {
715            require Geo::Coordinates::ITM;
716            my ( $x, $y ) = Geo::Coordinates::ITM::ll_to_grid(
717                                $vars{latitude}, $vars{longitude} );
718            $self->{x} = sprintf( "%d", $x );
719            $self->{y} = sprintf( "%d", $y );
720        } elsif ( $self->config->geo_handler eq 3 ) {
721            require Geo::Coordinates::UTM;
722            my ($zone, $x, $y) = Geo::Coordinates::UTM::latlon_to_utm(
723                                                $self->config->ellipsoid,
724                                                $vars{latitude},
725                                                $vars{longitude},
726                                              );
727            $self->{x} = $x;
728            $self->{y} = $y;
729        }
730        $self->{distance_in_metres} = $vars{latlong_dist};
731    }
732
733    # Store os_x etc so we can pass them to template.
734    foreach my $param ( qw( os_x os_y osie_x osie_y latitude longitude ) ) {
735        $self->{$param} = $vars{$param};
736    }
737
738    # Strip leading and trailing whitespace from search text.
739    $vars{search} ||= ""; # avoid uninitialised value warning
740    $vars{search} =~ s/^\s*//;
741    $vars{search} =~ s/\s*$//;
742
743    # Check for only valid characters in tainted search param
744    # (quoted literals are OK, as they are escaped)
745    # This regex copied verbatim from Ivor's old supersearch.
746    if ( $vars{search}
747         && $vars{search} !~ /^("[^"]*"|[\w \-',()!*%\[\]])+$/i) {
748        $self->{error} = "Search expression $vars{search} contains invalid character(s)";
749        return $self;
750    }
751    $self->{search_string} = $vars{search};
752
753    return $self;
754}
755
756# thin wrapper around OpenGuides::Template, or OpenGuides::Feed
757sub process_template {
758    my ($self, %args) = @_;
759
760    my $tt_vars = $args{tt_vars} || {};
761    $tt_vars->{not_editable} = 1;
762    $tt_vars->{not_deletable} = 1;
763    return %$tt_vars if $self->{return_tt_vars};
764
765    # Do we want a feed, or TT html?
766    my $output;
767    if($tt_vars->{'format'}) {
768        my $format = $tt_vars->{'format'};
769        my @nodes = @{$tt_vars->{'results'}};
770
771        my $feed = OpenGuides::Feed->new(
772                                               wiki       => $self->wiki,
773                                               config     => $self->config,
774                                               og_version => $VERSION,
775                                        );
776        $feed->set_feed_name_and_url_params(
777                    "Search Results for ".$tt_vars->{search_terms},
778                    "search.cgi?search=".$tt_vars->{search_terms}
779        );
780
781        $output  = "Content-Type: ".$feed->default_content_type($format)."\n";
782        $output .= $feed->build_mini_feed_for_nodes($format,@nodes);
783    } else {
784        $output =  OpenGuides::Template->output(
785                                                wiki     => $self->wiki,
786                                                config   => $self->config,
787                                                template => "search.tt",
788                                                vars     => $tt_vars,
789                                              );
790    }
791
792    return $output if $self->{return_output};
793
794    print $output;
795    return 1;
796}
797
798=head1 OUTPUT
799
800Results will be put into some form of relevance ordering.  These are
801the rules we have tests for so far (and hence the only rules that can
802be relied on):
803
804=over
805
806=item *
807
808A match on page title will score higher than a match on page category
809or locale.
810
811=item *
812
813A match on page category or locale will score higher than a match on
814page content.
815
816=item *
817
818Two matches in the title beats one match in the title and one in the content.
819
820=back
821
822=cut
823
824=head1 AUTHOR
825
826The OpenGuides Project (openguides-dev@lists.openguides.org)
827
828=head1 COPYRIGHT
829
830     Copyright (C) 2003-2012 The OpenGuides Project.  All Rights Reserved.
831
832The OpenGuides distribution is free software; you can redistribute it
833and/or modify it under the same terms as Perl itself.
834
835=head1 SEE ALSO
836
837L<OpenGuides>
838
839=cut
840
8411;
Note: See TracBrowser for help on using the repository browser.