source: trunk/lib/OpenGuides/Search.pm @ 1023

Last change on this file since 1023 was 1023, checked in by kake, 15 years ago

Added format => "raw" option to OpenGuides::Search->run to let you get your results back as a hash.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 26.0 KB
Line 
1package OpenGuides::Search;
2use strict;
3our $VERSION = '0.10';
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{format} && $args{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/page/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    # It'll be a real phrase (as opposed to a word) if it has a space in it.
507    # In this case, dump out the nodes that don't match the search exactly.
508    # I don't know why the phrase searching isn't working properly.  Fix later.
509    if ( $phrase =~ /\s/ ) {
510        my @tmp = keys %contents_res;
511        foreach my $node ( @tmp ) {
512            my $content = $wiki->retrieve_node( $node );
513            unless ( $content =~ /$phrase/i || $node =~ /$phrase/i ) {
514                delete $contents_res{$node};
515            }
516        }
517    }
518
519    my %results = map { $_ => { name => $_, score => $contents_res{$_} } }
520                      keys %contents_res;
521
522    # Bump up the score if the title matches.
523    foreach my $node ( keys %results ) {
524        $results{$node}{score} += 10 if $node =~ /$phrase/i;
525    }
526
527    # Search categories.
528    my @catmatches = $wiki->list_nodes_by_metadata(
529                                 metadata_type  => "category",
530                                 metadata_value => $phrase,
531                                 ignore_case    => 1,
532    );
533
534    foreach my $node ( @catmatches ) {
535        if ( $results{$node} ) {
536            $results{$node}{score} += 3;
537        } else {
538            $results{$node} = { name => $node, score => 3 };
539        }
540    }
541
542    # Search locales.
543    my @locmatches = $wiki->list_nodes_by_metadata(
544                                 metadata_type  => "locale",
545                                 metadata_value => $phrase,
546                                 ignore_case    => 1,
547    );
548
549    foreach my $node ( @locmatches ) {
550        if ( $results{$node} ) {
551            $results{$node}{score} += 3;
552        } else {
553            $results{$node} = { name => $node, score => 3 };
554        }
555    }
556
557    return %results;
558}
559
560=back
561
562=head1 SEARCHING BY DISTANCE
563
564To perform a distance search, you need to supply one of the following
565sets of criteria to specify the distance to search within, and the
566origin (centre) of the search:
567
568=over
569
570=item B<os_dist, os_x, and os_y>
571
572Only works if you chose to use British National Grid in wiki.conf
573
574=item B<osie_dist, osie_x, and osie_y>
575
576Only works if you chose to use Irish National Grid in wiki.conf
577
578=item B<latlong_dist, latitude, and longitude>
579
580Should always work, but has a habit of "finding" things a couple of
581metres away from themselves.
582
583=back
584
585You can perform both pure distance searches and distance searches in
586combination with text searches.
587
588=cut
589
590# Note this is called after any text search is run, and it is only called
591# if there are sufficient criteria to perform the search.
592sub run_distance_search {
593    my $self = shift;
594    my $x    = $self->{x};
595    my $y    = $self->{y};
596    my $dist = $self->{distance_in_metres};
597
598    my @close = $self->{locator}->find_within_distance(
599                                                        x      => $x,
600                                                        y      => $y,
601                                                        metres => $dist,
602                                                      );
603
604    if ( $self->{results} ) {
605        my %close_hash = map { $_ => 1 } @close;
606        my %results = %{ $self->{results} };
607        my @candidates = keys %results;
608        foreach my $node ( @candidates ) {
609            if ( exists $close_hash{$node} ) {
610                my $distance = $self->_get_distance(
611                                                     node => $node,
612                                                     x    => $x,
613                                                     y    => $y,
614                                                   );
615                $results{$node}{distance} = $distance;
616            } else {
617                delete $results{$node};
618            }
619        }
620        $self->{results} = \%results;
621    } else {
622        my %results;
623        foreach my $node ( @close ) {
624            my $distance = $self->_get_distance (
625                                                     node => $node,
626                                                     x    => $x,
627                                                     y    => $y,
628                                                   );
629            $results{$node} = {
630                                name     => $node,
631                                distance => $distance,
632                              };
633        }
634        $self->{results} = \%results;
635    }
636    return $self;
637}
638
639sub _get_distance {
640    my ($self, %args) = @_;
641    my ($node, $x, $y) = @args{ qw( node x y ) };
642    return $self->{locator}->distance(
643                                       from_x  => $x,
644                                       from_y  => $y,
645                                       to_node => $node,
646                                       unit    => "metres"
647                                     );
648}
649
650sub process_params {
651    my ($self, $vars_hashref) = @_;
652    my %vars = %{ $vars_hashref || {} };
653
654    # Make sure that we don't have any data left over from previous invocation.
655    # This is useful for testing purposes at the moment and will be essential
656    # for mod_perl implementations.
657    delete $self->{x};
658    delete $self->{y};
659    delete $self->{distance_in_metres};
660    delete $self->{search_string};
661    delete $self->{results};
662
663    # Strip out any non-digits from distance and OS co-ords.
664    foreach my $param ( qw( os_x os_y osie_x osie_y
665                            osie_dist os_dist latlong_dist ) ) {
666        if ( defined $vars{$param} ) {
667            $vars{$param} =~ s/[^0-9]//g;
668            # 0 is an allowed value but the empty string isn't.
669            delete $vars{$param} if $vars{$param} eq "";
670        }
671    }
672
673    # Latitude and longitude are also allowed '-' and '.'
674    foreach my $param( qw( latitude longitude ) ) {
675        if ( defined $vars{$param} ) {
676            $vars{$param} =~ s/[^-\.0-9]//g;
677            # 0 is an allowed value but the empty string isn't.
678            delete $vars{$param} if $vars{$param} eq "";
679        }
680    }
681
682    # Set $self->{distance_in_metres}, $self->{x}, $self->{y},
683    # depending on whether we got
684    # OS co-ords or lat/long.  Only store parameters if they're complete,
685    # and supported by our method of distance calculation.
686    if ( defined $vars{os_x} && defined $vars{os_y} && defined $vars{os_dist}
687         && $self->config->geo_handler eq 1 ) {
688        $self->{x} = $vars{os_x};
689        $self->{y} = $vars{os_y};
690        $self->{distance_in_metres} = $vars{os_dist};
691    } elsif ( defined $vars{osie_x} && defined $vars{osie_y}
692         && defined $vars{osie_dist}
693         && $self->config->geo_handler eq 2 ) {
694        $self->{x} = $vars{osie_x};
695        $self->{y} = $vars{osie_y};
696        $self->{distance_in_metres} = $vars{osie_dist};
697    } elsif ( defined $vars{latitude} && defined $vars{longitude}
698              && defined $vars{latlong_dist} ) {
699        # All handlers can do lat/long, but they all do it differently.
700        if ( $self->config->geo_handler eq 1 ) {
701            require Geography::NationalGrid::GB;
702            my $point = Geography::NationalGrid::GB->new(
703                Latitude  => $vars{latitude},
704                Longitude => $vars{longitude},
705            );
706            $self->{x} = $point->easting;
707            $self->{y} = $point->northing;
708        } elsif ( $self->config->geo_handler eq 2 ) {
709            require Geography::NationalGrid::IE;
710            my $point = Geography::NationalGrid::IE->new(
711                Latitude  => $vars{latitude},
712                Longitude => $vars{longitude},
713            );
714            $self->{x} = $point->easting;
715            $self->{y} = $point->northing;
716        } elsif ( $self->config->geo_handler eq 3 ) {
717            require Geo::Coordinates::UTM;
718            my ($zone, $x, $y) = Geo::Coordinates::UTM::latlon_to_utm(
719                                                $self->config->ellipsoid,
720                                                $vars{latitude},
721                                                $vars{longitude},
722                                              );
723            $self->{x} = $x;
724            $self->{y} = $y;
725        }
726        $self->{distance_in_metres} = $vars{latlong_dist};
727    }
728
729    # Store os_x etc so we can pass them to template.
730    foreach my $param ( qw( os_x os_y osie_x osie_y latitude longitude ) ) {
731        $self->{$param} = $vars{$param};
732    }
733
734    # Strip leading and trailing whitespace from search text.
735    $vars{search} ||= ""; # avoid uninitialised value warning
736    $vars{search} =~ s/^\s*//;
737    $vars{search} =~ s/\s*$//;
738
739    # Check for only valid characters in tainted search param
740    # (quoted literals are OK, as they are escaped)
741    # This regex copied verbatim from Ivor's old supersearch.
742    if ( $vars{search}
743         && $vars{search} !~ /^("[^"]*"|[\w \-',()!*%\[\]])+$/i) {
744        $self->{error} = "Search expression $vars{search} contains invalid character(s)";
745        return $self;
746    }
747    $self->{search_string} = $vars{search};
748
749    return $self;
750}
751
752# thin wrapper around OpenGuides::Template, or OpenGuides::Feed
753sub process_template {
754    my ($self, %args) = @_;
755
756    my $tt_vars = $args{tt_vars} || {};
757    $tt_vars->{not_editable} = 1;
758    $tt_vars->{not_deletable} = 1;
759    return %$tt_vars if $self->{return_tt_vars};
760
761    # Do we want a feed, or TT html?
762    my $output;
763    if($tt_vars->{'format'}) {
764        my $format = $tt_vars->{'format'};
765        my @nodes = @{$tt_vars->{'results'}};
766
767        my $feed = OpenGuides::Feed->new(
768                                               wiki       => $self->wiki,
769                                               config     => $self->config,
770                                               og_version => $VERSION,
771                                        );
772        $feed->set_feed_name_and_url_params(
773                    "Search Results for ".$tt_vars->{search_terms},
774                    "search.cgi?search=".$tt_vars->{search_terms}
775        );
776
777        $output  = "Content-Type: ".$feed->default_content_type($format)."\n";
778        $output .= $feed->build_mini_feed_for_nodes($format,@nodes);
779    } else {
780        $output =  OpenGuides::Template->output(
781                                                wiki     => $self->wiki,
782                                                config   => $self->config,
783                                                template => "search.tt",
784                                                vars     => $tt_vars,
785                                              );
786    }
787
788    return $output if $self->{return_output};
789
790    print $output;
791    return 1;
792}
793
794=head1 OUTPUT
795
796Results will be put into some form of relevance ordering.  These are
797the rules we have tests for so far (and hence the only rules that can
798be relied on):
799
800=over
801
802=item *
803
804A match on page title will score higher than a match on page category
805or locale.
806
807=item *
808
809A match on page category or locale will score higher than a match on
810page content.
811
812=item *
813
814Two matches in the title beats one match in the title and one in the content.
815
816=back
817
818=cut
819
820=head1 AUTHOR
821
822The OpenGuides Project (openguides-dev@lists.openguides.org)
823
824=head1 COPYRIGHT
825
826     Copyright (C) 2003-2006 The OpenGuides Project.  All Rights Reserved.
827
828The OpenGuides distribution is free software; you can redistribute it
829and/or modify it under the same terms as Perl itself.
830
831=head1 SEE ALSO
832
833L<OpenGuides>
834
835=cut
836
8371;
Note: See TracBrowser for help on using the repository browser.