root/branches/new-install-process/lib/OpenGuides.pm

Revision 1141, 80.1 kB (checked in by earle, 12 months ago)

Allow wiki links in change summaries. Closes #115.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1package OpenGuides;
2use strict;
3
4use Carp "croak";
5use CGI;
6use Wiki::Toolkit::Plugin::Diff;
7use Wiki::Toolkit::Plugin::Locator::Grid;
8use OpenGuides::CGI;
9use OpenGuides::Feed;
10use OpenGuides::Template;
11use OpenGuides::Utils;
12use Time::Piece;
13use URI::Escape;
14
15use vars qw( $VERSION );
16
17$VERSION = '0.61';
18
19=head1 NAME
20
21OpenGuides - A complete web application for managing a collaboratively-written guide to a city or town.
22
23=head1 DESCRIPTION
24
25The OpenGuides software provides the framework for a collaboratively-written
26city guide.  It is similar to a wiki but provides somewhat more structured
27data storage allowing you to annotate wiki pages with information such as
28category, location, and much more.  It provides searching facilities
29including "find me everything within a certain distance of this place".
30Every page includes a link to a machine-readable (RDF) version of the page.
31
32=head1 METHODS
33
34=over
35
36=item B<new>
37
38  my $config = OpenGuides::Config->new( file => "wiki.conf" );
39  my $guide = OpenGuides->new( config => $config );
40
41=cut
42
43sub new {
44    my ($class, %args) = @_;
45    my $self = {};
46    bless $self, $class;
47    my $wiki = OpenGuides::Utils->make_wiki_object( config => $args{config} );
48    $self->{wiki} = $wiki;
49    $self->{config} = $args{config};
50
51    my $geo_handler = $self->config->geo_handler;
52    my $locator;
53    if ( $geo_handler == 1 ) {
54        $locator = Wiki::Toolkit::Plugin::Locator::Grid->new(
55                                             x => "os_x",    y => "os_y" );
56    } elsif ( $geo_handler == 2 ) {
57        $locator = Wiki::Toolkit::Plugin::Locator::Grid->new(
58                                             x => "osie_x"y => "osie_y" );
59    } else {
60        $locator = Wiki::Toolkit::Plugin::Locator::Grid->new(
61                                             x => "easting", y => "northing" );
62    }
63    $wiki->register_plugin( plugin => $locator );
64    $self->{locator} = $locator;
65
66    my $differ = Wiki::Toolkit::Plugin::Diff->new;
67    $wiki->register_plugin( plugin => $differ );
68    $self->{differ} = $differ;
69
70    if($self->config->ping_services) {
71        eval {
72            require Wiki::Toolkit::Plugin::Ping;
73        };
74
75        if ( $@ ) {
76            warn "You asked for some ping services, but can't find "
77                 . "Wiki::Toolkit::Plugin::Ping";
78        } else {
79            my @ws = split(/\s*,\s*/, $self->config->ping_services);
80            my %well_known = Wiki::Toolkit::Plugin::Ping->well_known;
81            my %services;
82            foreach my $s (@ws) {
83                if($well_known{$s}) {
84                    $services{$s} = $well_known{$s};
85                } else {
86                    warn("Ignoring unknown ping service '$s'");
87                }
88            }
89            my $ping = Wiki::Toolkit::Plugin::Ping->new(
90                node_to_url => $self->{config}->{script_url}
91                               . $self->{config}->{script_name} . '?$node',
92                services => \%services
93            );
94            $wiki->register_plugin( plugin => $ping );
95        }
96    }
97
98    return $self;
99}
100
101=item B<wiki>
102
103An accessor, returns the underlying L<Wiki::Toolkit> object.
104
105=cut
106
107sub wiki {
108    my $self = shift;
109    return $self->{wiki};
110}
111
112=item B<config>
113
114An accessor, returns the underlying L<OpenGuides::Config> object.
115
116=cut
117
118sub config {
119    my $self = shift;
120    return $self->{config};
121}
122
123=item B<locator>
124
125An accessor, returns the underlying L<Wiki::Toolkit::Plugin::Locator::UK> object.
126
127=cut
128
129sub locator {
130    my $self = shift;
131    return $self->{locator};
132}
133
134=item B<differ>
135
136An accessor, returns the underlying L<Wiki::Toolkit::Plugin::Diff> object.
137
138=cut
139
140sub differ {
141    my $self = shift;
142    return $self->{differ};
143}
144
145=item B<display_node>
146
147  # Print node to STDOUT.
148  $guide->display_node(
149                          id      => "Calthorpe Arms",
150                          version => 2,
151                      );
152
153  # Or return output as a string (useful for writing tests).
154  $guide->display_node(
155                          id            => "Calthorpe Arms",
156                          return_output => 1,
157                      );
158
159  # Or return the hash of variables that will be passed to the template
160  # (not including those set additionally by OpenGuides::Template).
161  $guide->display_node(
162                          id             => "Calthorpe Arms",
163                          return_tt_vars => 1,
164                      );
165
166If C<version> is omitted then it will assume you want the latest version.
167
168Note that if you pass the C<return_output> parameter, and your node is a
169redirecting node, this method will fake the redirect and return the output
170that will actually end up in the user's browser.  If instead you want to see
171the HTTP headers that will be printed in order to perform the redirect, pass
172the C<intercept_redirect> parameter as well.  The C<intercept_redirect>
173parameter has no effect if the node isn't a redirect, or if the
174C<return_output> parameter is omitted.
175
176(At the moment, C<return_tt_vars> acts as if the C<intercept_redirect>
177parameter was passed.)
178
179If you have specified the C<host_checker_module> option in your
180C<wiki.conf>, this method will attempt to call the <blacklisted_host>
181method of that module to determine whether the host requesting the node
182has been blacklisted. If this method returns true, then the
183C<blacklisted_host.tt> template will be used to display an error message.
184
185The C<blacklisted_host> method will be passed a scalar containing the host's
186IP address.
187
188=cut
189
190sub display_node {
191    my ($self, %args) = @_;
192    my $return_output = $args{return_output} || 0;
193    my $intercept_redirect = $args{intercept_redirect};
194    my $version = $args{version};
195    my $id = $args{id} || $self->config->home_name;
196    my $wiki = $self->wiki;
197    my $config = $self->config;
198    my $oldid = $args{oldid} || '';
199    my $do_redirect = defined($args{redirect}) ? $args{redirect} : 1;
200
201    my %tt_vars;
202
203    # If we can, check to see if requesting host is blacklisted.
204    my $host_checker = $config->host_checker_module;
205    my $is_blacklisted;
206    if ( $host_checker ) {
207        eval {
208            eval "require $host_checker";
209            $is_blacklisted = $host_checker->blacklisted_host(CGI->new->remote_host);
210        };
211    }
212
213    if ( $is_blacklisted ) {
214        my $output = OpenGuides::Template->output(
215            wiki     => $self->wiki,
216            config   => $config,
217            template => "blacklisted_host.tt",
218            vars     => {
219                          not_editable => 1,
220                        },
221        );
222        return $output if $return_output;
223        print $output;
224        return;
225    }
226
227    $tt_vars{home_name} = $self->config->home_name;
228   
229    if ( $id =~ /^(Category|Locale) (.*)$/ ) {
230        my $type = $1;
231        $tt_vars{is_indexable_node} = 1;
232        $tt_vars{index_type} = lc($type);
233        $tt_vars{index_value} = $2;
234        $tt_vars{"rss_".lc($type)."_url"} =
235                           $config->script_name . "?action=rc;format=rss;"
236                           . lc($type) . "=" . lc(CGI->escape($2));
237        $tt_vars{"atom_".lc($type)."_url"} =
238                           $config->script_name . "?action=rc;format=atom;"
239                           . lc($type) . "=" . lc(CGI->escape($2));
240    }
241
242    my %current_data = $wiki->retrieve_node( $id );
243    my $current_version = $current_data{version};
244    undef $version if ($version && $version == $current_version);
245    my %criteria = ( name => $id );
246    $criteria{version} = $version if $version; # retrieve_node default is current
247
248    my %node_data = $wiki->retrieve_node( %criteria );
249
250    # Fixes passing undefined values to Text::Wikiformat if node doesn't exist.
251    my $content = '';
252    if ($node_data{content}) {
253        $content    = $wiki->format($node_data{content});
254    }
255
256    my $modified   = $node_data{last_modified};
257    my $moderated  = $node_data{moderated};
258    my %metadata   = %{$node_data{metadata}};
259
260    my ($wgs84_long, $wgs84_lat) = OpenGuides::Utils->get_wgs84_coords(
261                                        longitude => $metadata{longitude}[0],
262                                        latitude => $metadata{latitude}[0],
263                                        config => $config);
264    if ($args{format} && $args{format} eq 'raw') {
265        print "Content-Type: text/plain\n\n";
266        print $node_data{content};
267        return 0;
268    }
269   
270    my %metadata_vars = OpenGuides::Template->extract_metadata_vars(
271                            wiki     => $wiki,
272                            config   => $config,
273                            metadata => $node_data{metadata}
274                        );
275
276    %tt_vars = (
277                   %tt_vars,
278                   %metadata_vars,
279                   content       => $content,
280                   last_modified => $modified,
281                   version       => $node_data{version},
282                   node          => $id,
283                   language      => $config->default_language,
284                   moderated     => $moderated,
285                   oldid         => $oldid,
286                   enable_gmaps  => 1,
287                   wgs84_long    => $wgs84_long,
288                   wgs84_lat     => $wgs84_lat
289               );
290
291    # Hide from search engines if showing a specific version.
292    $tt_vars{'deter_robots'} = 1 if $args{version};
293
294    if ( $config->show_gmap_in_node_display
295           && $self->get_cookie( "display_google_maps" ) ) {
296        $tt_vars{display_google_maps} = 1;
297    }
298
299    my $redirect = OpenGuides::Utils->detect_redirect(
300                                              content => $node_data{content} );
301    if ( $redirect ) {
302        # Don't redirect if the parameter "redirect" is given as 0.
303        if ($do_redirect == 0) {
304            $tt_vars{current} = 1;
305            return %tt_vars if $args{return_tt_vars};
306            my $output = $self->process_template(
307                                                  id            => $id,
308                                                  template      => "node.tt",
309                                                  tt_vars       => \%tt_vars,
310                                                );
311            return $output if $return_output;
312            print $output;
313        } elsif ( $wiki->node_exists($redirect) && $redirect ne $id && $redirect ne $oldid ) {
314            # Avoid loops by not generating redirects to the same node or the previous node.
315            if ( $return_output ) {
316                if ( $intercept_redirect ) {
317                    return $self->redirect_to_node( $redirect, $id );
318                } else {
319                    return $self->display_node( id            => $redirect,
320                                                oldid         => $id,
321                                                return_output => 1,
322                                              );
323                }
324            }
325            print $self->redirect_to_node( $redirect, $id );
326            return 0;
327        }
328    }
329
330    # We've undef'ed $version above if this is the current version.
331    $tt_vars{current} = 1 unless $version;
332
333    if ($id eq "RecentChanges") {
334        $self->display_recent_changes(%args);
335    } elsif ( $id eq $self->config->home_name ) {
336        if ( $self->config->recent_changes_on_home_page ) {
337            my @recent = $wiki->list_recent_changes(
338                last_n_changes => 10,
339                metadata_was   => { edit_type => "Normal edit" },
340            );
341            my $base_url = $config->script_name . '?';
342            @recent = map {
343                            {
344                              name          => CGI->escapeHTML($_->{name}),
345                              last_modified =>
346                                  CGI->escapeHTML($_->{last_modified}),
347                              version       => CGI->escapeHTML($_->{version}),
348                              comment       => OpenGuides::Utils::parse_change_comment(
349                                  CGI->escapeHTML($_->{metadata}{comment}[0]),
350                                  $base_url,
351                              ),
352                              username      =>
353                                  CGI->escapeHTML($_->{metadata}{username}[0]),
354                              url           => $base_url
355                                             . CGI->escape($wiki->formatter->node_name_to_node_param($_->{name}))
356                            }
357                          } @recent;
358            $tt_vars{recent_changes} = \@recent;
359        }
360        return %tt_vars if $args{return_tt_vars};
361        my $output = $self->process_template(
362                                                id            => $id,
363                                                template      => "home_node.tt",
364                                                tt_vars       => \%tt_vars,
365                                            );
366        return $output if $return_output;
367        print $output;
368    } else {
369        return %tt_vars if $args{return_tt_vars};
370        my $output = $self->process_template(
371                                                id            => $id,
372                                                template      => "node.tt",
373                                                tt_vars       => \%tt_vars,
374                                            );
375        return $output if $return_output;
376        print $output;
377    }
378}
379
380=item B<display_random_page>
381
382  $guide->display_random_page;
383
384Display a random page.  As with other methods, the C<return_output>
385parameter can be used to return the output instead of printing it to STDOUT.
386You can also restrict it to a given category and/or locale by supplying
387appropriate parameters:
388
389  $guide->display_random_page(
390                               category => "pubs",
391                               locale   => "bermondsey",
392                             );
393
394The values of these parameters are case-insensitive.
395
396You can make sure this method never returns pages that are themselves
397categories and/or locales by setting C<random_page_omits_categories>
398and/or C<random_page_omits_locales> in your wiki.conf.
399
400=cut
401
402sub display_random_page {
403    my ( $self, %args ) = @_;
404    my $wiki = $self->wiki;
405    my $config = $self->config;
406
407    my ( @catnodes, @locnodes, @nodes );
408    if ( $args{category} ) {
409        @catnodes = $wiki->list_nodes_by_metadata(
410            metadata_type  => "category",
411            metadata_value => $args{category},
412            ignore_case    => 1,
413        );
414    }
415    if ( $args{locale} ) {
416        @locnodes = $wiki->list_nodes_by_metadata(
417            metadata_type  => "locale",
418            metadata_value => $args{locale},
419            ignore_case    => 1,
420        );
421    }
422
423    if ( $args{category} && $args{locale} ) {
424        # If we have both category and locale, return the intersection.
425        my %count;
426        foreach my $node ( @catnodes, @locnodes ) {
427            $count{$node}++;
428        }
429        foreach my $node ( keys %count ) {
430            push @nodes, $node if $count{$node} > 1;
431        }
432    } elsif ( $args{category} ) {
433        @nodes = @catnodes;
434    } elsif ( $args{locale} ) {
435        @nodes = @locnodes;
436    } else {
437        @nodes = $wiki->list_all_nodes();
438    }
439
440    my $omit_cats = $config->random_page_omits_categories;
441    my $omit_locs = $config->random_page_omits_locales;
442
443    if ( $omit_cats || $omit_locs ) {
444        my %all_nodes = map { $_ => $_ } @nodes;
445        if ( $omit_cats ) {
446            my @cats = $wiki->list_nodes_by_metadata(
447                                                  metadata_type  => "category",
448                                                  metadata_value => "category",
449                                                  ignore_case => 1,
450            );
451            foreach my $omit ( @cats ) {
452                delete $all_nodes{$omit};
453            }
454        }
455        if ( $omit_locs ) {
456            my @locs = $wiki->list_nodes_by_metadata(
457                                                  metadata_type  => "category",
458                                                  metadata_value => "locales",
459                                                  ignore_case => 1,
460            );
461            foreach my $omit ( @locs ) {
462                delete $all_nodes{$omit};
463            }
464        }
465        @nodes = keys %all_nodes;
466    }
467    my $node = $nodes[ rand @nodes ];
468    my $output;
469
470    if ( $node ) {
471        $output = $self->redirect_to_node( $node );
472    } else {
473        my %tt_vars = (
474                        category => $args{category},
475                        locale   => $args{locale},
476                      );
477        $output = OpenGuides::Template->output(
478            wiki     => $wiki,
479            config   => $config,
480            template => "random_page_failure.tt",
481            vars     => \%tt_vars,
482        );
483    }
484    return $output if $args{return_output};
485    print $output;
486}
487
488=item B<display_edit_form>
489
490  $guide->display_edit_form(
491                             id => "Vivat Bacchus",
492                             vars => \%vars,
493                             content => $content,
494                             metadata => \%metadata,
495                             checksum => $checksum
496                           );
497
498Display an edit form for the specified node.  As with other methods, the
499C<return_output> parameter can be used to return the output instead of
500printing it to STDOUT.
501
502If this is to redisplay an existing edit, the content, metadata
503and checksum may be supplied in those arguments
504
505Extra template variables may be supplied in the vars argument
506
507=cut
508
509sub display_edit_form {
510    my ($self, %args) = @_;
511    my $return_output = $args{return_output} || 0;
512    my $config = $self->config;
513    my $wiki = $self->wiki;
514    my $node = $args{id};
515    my %node_data = $wiki->retrieve_node($node);
516    my ($content, $checksum) = @node_data{ qw( content checksum ) };
517    my %cookie_data = OpenGuides::CGI->get_prefs_from_cookie(config=>$config);
518
519    my $username = $self->get_cookie( "username" );
520    my $edit_type = $self->get_cookie( "default_edit_type" ) eq "normal"
521                        ? "Normal edit"
522                        : "Minor tidying";
523
524    my %metadata_vars = OpenGuides::Template->extract_metadata_vars(
525                             wiki     => $wiki,
526                             config   => $config,
527                 metadata => $node_data{metadata} );
528
529    $metadata_vars{website} ||= 'http://';
530    my $moderate = $wiki->node_required_moderation($node);
531
532    my %tt_vars = ( content         => CGI->escapeHTML($content),
533                    checksum        => CGI->escapeHTML($checksum),
534                    %metadata_vars,
535                    config          => $config,
536                    username        => $username,
537                    edit_type       => $edit_type,
538                    moderate        => $moderate,
539                    deter_robots    => 1,
540    );
541
542    # Override some things if we were supplied with them
543    $tt_vars{content} = $args{content} if $args{content};
544    $tt_vars{checksum} = $args{checksum} if $args{checksum};
545    if (defined $args{vars}) {
546        my %supplied_vars = %{$args{vars}};
547        foreach my $key ( keys %supplied_vars ) {
548            $tt_vars{$key} = $supplied_vars{$key};
549        }
550    }
551    if (defined $args{metadata}) {
552        my %supplied_metadata = %{$args{metadata}};
553        foreach my $key ( keys %supplied_metadata ) {
554            $tt_vars{$key} = $supplied_metadata{$key};
555        }
556    }
557
558    my $output = $self->process_template(
559                                          id            => $node,
560                                          template      => "edit_form.tt",
561                                          tt_vars       => \%tt_vars,
562                                        );
563    return $output if $return_output;
564    print $output;
565}
566
567=item B<preview_edit>
568
569  $guide->preview_edit(
570                        id      => "Vivat Bacchus",
571                        cgi_obj => $q,
572                      );
573
574Preview the edited version of the specified node.  As with other methods, the
575C<return_output> parameter can be used to return the output instead of
576printing it to STDOUT.
577
578=cut
579
580sub preview_edit {
581    my ($self, %args) = @_;
582    my $node = $args{id};
583    my $q = $args{cgi_obj};
584    my $return_output = $args{return_output};
585    my $wiki = $self->wiki;
586    my $config = $self->config;
587
588    my $content  = $q->param('content');
589    $content     =~ s/\r\n/\n/gs;
590    my $checksum = $q->param('checksum');
591
592    my %new_metadata = OpenGuides::Template->extract_metadata_vars(
593                                               wiki                 => $wiki,
594                                               config               => $config,
595                                               cgi_obj              => $q,
596                                               set_coord_field_vars => 1,
597    );
598    foreach my $var ( qw( username comment edit_type ) ) {
599        $new_metadata{$var} = $q->escapeHTML($q->param($var));
600    }
601
602    if ($wiki->verify_checksum($node, $checksum)) {
603        my $moderate = $wiki->node_required_moderation($node);
604        my %tt_vars = (
605            %new_metadata,
606            config                 => $config,
607            content                => $q->escapeHTML($content),
608            preview_html           => $wiki->format($content),
609            preview_above_edit_box => $self->get_cookie(
610                                                   "preview_above_edit_box" ),
611            checksum               => $q->escapeHTML($checksum),
612            moderate               => $moderate
613        );
614        my $output = $self->process_template(
615                                              id       => $node,
616                                              template => "edit_form.tt",
617                                              tt_vars  => \%tt_vars,
618                                            );
619        return $output if $args{return_output};
620        print $output;
621    } else {
622        return $self->_handle_edit_conflict(
623                                             id            => $node,
624                                             content       => $content,
625                                             new_metadata  => \%new_metadata,
626                                             return_output => $return_output,
627                                           );
628    }
629}
630
631=item B<display_recent_changes> 
632
633  $guide->display_recent_changes;
634
635As with other methods, the C<return_output> parameter can be used to
636return the output instead of printing it to STDOUT.
637
638=cut
639
640sub display_recent_changes {
641    my ($self, %args) = @_;
642    my $config = $self->config;
643    my $wiki = $self->wiki;
644    my $minor_edits = $self->get_cookie( "show_minor_edits_in_rc" );
645    my $id = $args{id} || $self->config->home_name;
646    my $return_output = $args{return_output} || 0;
647    my (%tt_vars, %recent_changes);
648    my $q = CGI->new;
649    my $since = $q->param("since");
650    if ( $since ) {
651        $tt_vars{since} = $since;
652        my $t = localtime($since); # overloaded by Time::Piece
653        $tt_vars{since_string} = $t->strftime;
654        my %criteria = ( since => $since );   
655        $criteria{metadata_was} = { edit_type => "Normal edit" }
656          unless $minor_edits;
657        my @rc = $self->{wiki}->list_recent_changes( %criteria );
658
659        my $base_url = $config->script_name . '?';
660       
661        @rc = map {
662            {
663              name        => CGI->escapeHTML($_->{name}),
664              last_modified => CGI->escapeHTML($_->{last_modified}),
665              version     => CGI->escapeHTML($_->{version}),
666              comment     => OpenGuides::Utils::parse_change_comment(
667                  CGI->escapeHTML($_->{metadata}{comment}[0]),
668                  $base_url,
669              ),
670              username    => CGI->escapeHTML($_->{metadata}{username}[0]),
671              host        => CGI->escapeHTML($_->{metadata}{host}[0]),
672              username_param => CGI->escape($_->{metadata}{username}[0]),
673              edit_type   => CGI->escapeHTML($_->{metadata}{edit_type}[0]),
674              url         => $base_url
675      . CGI->escape($wiki->formatter->node_name_to_node_param($_->{name})),
676        }
677                   } @rc;
678        if ( scalar @rc ) {
679            $recent_changes{since} = \@rc;
680        }
681    } else {
682        for my $days ( [0, 1], [1, 7], [7, 14], [14, 30] ) {
683            my %criteria = ( between_days => $days );
684            $criteria{metadata_was} = { edit_type => "Normal edit" }
685              unless $minor_edits;
686            my @rc = $self->{wiki}->list_recent_changes( %criteria );
687
688            my $base_url = $config->script_name . '?';
689           
690            @rc = map {
691            {
692              name        => CGI->escapeHTML($_->{name}),
693              last_modified => CGI->escapeHTML($_->{last_modified}),
694              version     => CGI->escapeHTML($_->{version}),
695              comment     => OpenGuides::Utils::parse_change_comment(
696                  CGI->escapeHTML($_->{metadata}{comment}[0]),
697                  $base_url,
698              ),
699              username    => CGI->escapeHTML($_->{metadata}{username}[0]),
700              host        => CGI->escapeHTML($_->{metadata}{host}[0]),
701              username_param => CGI->escape($_->{metadata}{username}[0]),
702              edit_type   => CGI->escapeHTML($_->{metadata}{edit_type}[0]),
703              url         => $base_url
704      . CGI->escape($wiki->formatter->node_name_to_node_param($_->{name})),
705        }
706                       } @rc;
707            if ( scalar @rc ) {
708                $recent_changes{$days->[1]} = \@rc;
709        }
710        }
711    }
712    $tt_vars{not_editable} = 1;
713    $tt_vars{recent_changes} = \%recent_changes;
714    my %processing_args = (
715                            id            => $id,
716                            template      => "recent_changes.tt",
717                            tt_vars       => \%tt_vars,
718                           );
719    if ( !$since && $self->get_cookie("track_recent_changes_views") ) {
720    my $cookie =
721           OpenGuides::CGI->make_recent_changes_cookie(config => $config );
722        $processing_args{cookies} = $cookie;
723        $tt_vars{last_viewed} = OpenGuides::CGI->get_last_recent_changes_visit_from_cookie( config => $config );
724    }
725    return %tt_vars if $args{return_tt_vars};
726    my $output = $self->process_template( %processing_args );
727    return $output if $return_output;
728    print $output;
729}
730
731=item B<display_diffs>
732
733  $guide->display_diffs(
734                           id            => "Home Page",
735                           version       => 6,
736                           other_version => 5,
737                       );
738
739  # Or return output as a string (useful for writing tests).
740  my $output = $guide->display_diffs(
741                                        id            => "Home Page",
742                                        version       => 6,
743                                        other_version => 5,
744                                        return_output => 1,
745                                    );
746
747  # Or return the hash of variables that will be passed to the template
748  # (not including those set additionally by OpenGuides::Template).
749  my %vars = $guide->display_diffs(
750                                      id             => "Home Page",
751                                      version        => 6,
752                                      other_version  => 5,
753                                      return_tt_vars => 1,
754                                  );
755
756=cut
757
758sub display_diffs {
759    my ($self, %args) = @_;
760    my %diff_vars = $self->differ->differences(
761                                                  node          => $args{id},
762                                                  left_version  => $args{version},
763                                                  right_version => $args{other_version},
764                                              );
765    $diff_vars{not_deletable} = 1;
766    $diff_vars{not_editable}  = 1;
767    $diff_vars{deter_robots}  = 1;
768    return %diff_vars if $args{return_tt_vars};
769    my $output = $self->process_template(
770                                            id       => $args{id},
771                                            template => "differences.tt",
772                                            tt_vars  => \%diff_vars
773                                        );
774    return $output if $args{return_output};
775    print $output;
776}
777
778=item B<find_within_distance>
779
780  $guide->find_within_distance(
781                                  id => $node,
782                                  metres => $q->param("distance_in_metres")
783                              );
784
785=cut
786
787sub find_within_distance {
788    my ($self, %args) = @_;
789    my $node = $args{id};
790    my $metres = $args{metres};
791    my %data = $self->wiki->retrieve_node( $node );
792    my $lat = $data{metadata}{latitude}[0];
793    my $long = $data{metadata}{longitude}[0];
794    my $script_url = $self->config->script_url;
795    my $q = CGI->new;
796    print $q->redirect( $script_url . "search.cgi?lat=$lat;long=$long;distance_in_metres=$metres" );
797}
798
799=item B<show_backlinks>
800
801  $guide->show_backlinks( id => "Calthorpe Arms" );
802
803As with other methods, parameters C<return_tt_vars> and
804C<return_output> can be used to return these things instead of
805printing the output to STDOUT.
806
807=cut
808
809sub show_backlinks {
810    my ($self, %args) = @_;
811    my $wiki = $self->wiki;
812    my $formatter = $wiki->formatter;
813
814    my @backlinks = $wiki->list_backlinks( node => $args{id} );
815    my @results = map {
816                          {
817                              url   => CGI->escape($formatter->node_name_to_node_param($_)),
818                              title => CGI->escapeHTML($_)
819                          }
820                      } sort @backlinks;
821    my %tt_vars = ( results       => \@results,
822                    num_results   => scalar @results,
823                    not_deletable => 1,
824                    deter_robots  => 1,
825                    not_editable  => 1 );
826    return %tt_vars if $args{return_tt_vars};
827    my $output = OpenGuides::Template->output(
828                                                 node    => $args{id},
829                                                 wiki    => $wiki,
830                                                 config  => $self->config,
831                                                 template=>"backlink_results.tt",
832                                                 vars    => \%tt_vars,
833                                             );
834    return $output if $args{return_output};
835    print $output;
836}
837
838=item B<show_index>
839
840  $guide->show_index(
841                        type   => "category",
842                        value  => "pubs",
843                    );
844
845  # RDF version.
846  $guide->show_index(
847                        type   => "locale",
848                        value  => "Holborn",
849                        format => "rdf",
850                    );
851
852  # RSS / Atom version (recent changes style).
853  $guide->show_index(
854                        type   => "locale",
855                        value  => "Holborn",
856                        format => "rss",
857                    );
858
859  # Or return output as a string (useful for writing tests).
860  $guide->show_index(
861                        type          => "category",
862                        value         => "pubs",
863                        return_output => 1,
864                    );
865
866If either the C<type> or the C<value> parameter is omitted, then all pages
867will be returned.
868
869=cut
870
871sub show_index {
872    my ($self, %args) = @_;
873    my $wiki = $self->wiki;
874    my $formatter = $wiki->formatter;
875    my %tt_vars;
876    my @selnodes;
877
878    if ( $args{type} and $args{value} ) {
879        if ( $args{type} eq "fuzzy_title_match" ) {
880            my %finds = $wiki->fuzzy_title_match( $args{value} );
881            @selnodes = sort { $finds{$a} <=> $finds{$b} } keys %finds;
882            $tt_vars{criterion} = {
883                type  => $args{type},  # for RDF version
884                value => $args{value}, # for RDF version
885                name  => CGI->escapeHTML("Fuzzy Title Match on '$args{value}'")
886            };
887            $tt_vars{not_editable} = 1;
888        } else {
889            @selnodes = $wiki->list_nodes_by_metadata(
890                metadata_type  => $args{type},
891                metadata_value => $args{value},
892                ignore_case    => 1
893            );
894            my $name = ucfirst($args{type}) . " $args{value}";
895            my $url = $self->config->script_name
896                      . "?"
897                      . ucfirst( $args{type} )
898                      . "_"
899                      . uri_escape(
900                                      $formatter->node_name_to_node_param($args{value})
901                                  );
902            $tt_vars{criterion} = {
903                type  => $args{type},
904                value => $args{value}, # for RDF version
905                name  => CGI->escapeHTML( $name ),
906                url   => $url
907            };
908            $tt_vars{not_editable} = 1;
909        }
910    } else {
911        @selnodes = $wiki->list_all_nodes();
912    }
913
914    my @nodes = map {
915                        {
916                            name      => $_,
917                            node_data => { $wiki->retrieve_node( name => $_ ) },
918                            param     => $formatter->node_name_to_node_param($_) }
919                        } sort @selnodes;
920
921    # Convert the lat+long to WGS84 as required
922    for(my $i=0; $i<scalar @nodes;$i++) {
923        my $node = $nodes[$i];
924        if($node) {
925            my %metadata = %{$node->{node_data}->{metadata}};
926            my ($wgs84_long, $wgs84_lat);
927            eval {
928                ($wgs84_long, $wgs84_lat) = OpenGuides::Utils->get_wgs84_coords(
929                                      longitude => $metadata{longitude}[0],
930                                      latitude => $metadata{latitude}[0],
931                                      config => $self->config);
932            };
933            warn $@." on ".$metadata{latitude}[0]." ".$metadata{longitude}[0] if $@;
934
935            push @{$nodes[$i]->{node_data}->{metadata}->{wgs84_long}}, $wgs84_long;
936            push @{$nodes[$i]->{node_data}->{metadata}->{wgs84_lat}},  $wgs84_lat;
937        }
938    }
939
940    $tt_vars{nodes} = \@nodes;
941
942    my ($template, %conf);
943
944    if ( $args{format} ) {
945        if ( $args{format} eq "rdf" ) {
946            $template = "rdf_index.tt";
947            $conf{content_type} = "application/rdf+xml";
948        }
949        elsif ( $args{format} eq "plain" ) {
950            $template = "plain_index.tt";
951            $conf{content_type} = "text/plain";
952        } elsif ( $args{format} eq "map" ) {
953            my $q = CGI->new;
954            $tt_vars{zoom} = $q->param('zoom') || '';
955            $tt_vars{lat} = $q->param('lat') || '';
956            $tt_vars{long} = $q->param('long') || '';
957            $tt_vars{map_type} = $q->param('map_type') || '';
958            $tt_vars{centre_long} = $self->config->centre_long;
959            $tt_vars{centre_lat} = $self->config->centre_lat;
960            $tt_vars{default_gmaps_zoom} = $self->config->default_gmaps_zoom;
961            $tt_vars{enable_gmaps} = 1;
962            $tt_vars{display_google_maps} = 1; # override for this page
963            $template = "map_index.tt";
964           
965        } elsif( $args{format} eq "rss" || $args{format} eq "atom") {
966            # They really wanted a recent changes style rss/atom feed
967            my $feed_type = $args{format};
968            my ($feed,$content_type) = $self->get_feed_and_content_type($feed_type);
969            $feed->set_feed_name_and_url_params(
970                        "Index of $args{type} $args{value}",
971                        "action=index;index_type=$args{type};index_value=$args{value}"
972            );
973
974            # Grab the actual node data out of @nodes
975            my @node_data;
976            foreach my $node (@nodes) {
977                $node->{node_data}->{name} = $node->{name};
978                push @node_data, $node->{node_data};
979            }
980
981            my $output = "Content-Type: ".$content_type."\n";
982            $output .= $feed->build_feed_for_nodes($feed_type, @node_data);
983
984            return $output if $args{return_output};
985            print $output;
986<