root/tags/rel0_59/lib/OpenGuides.pm

Revision 997, 69.6 kB (checked in by nick, 22 months ago)

Add test for revert user stuff

  • 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.59';
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 the latest version will be displayed.
167
168=cut
169
170sub display_node {
171    my ($self, %args) = @_;
172    my $return_output = $args{return_output} || 0;
173    my $version = $args{version};
174    my $id = $args{id} || $self->config->home_name;
175    my $wiki = $self->wiki;
176    my $config = $self->config;
177    my $oldid = $args{oldid} || '';
178    my $do_redirect = $args{redirect} || 1;
179
180    my %tt_vars;
181
182    $tt_vars{home_name} = $self->config->home_name;
183   
184    if ( $id =~ /^(Category|Locale) (.*)$/ ) {
185        my $type = $1;
186        $tt_vars{is_indexable_node} = 1;
187        $tt_vars{index_type} = lc($type);
188        $tt_vars{index_value} = $2;
189        $tt_vars{"rss_".lc($type)."_url"} =
190                           $config->script_name . "?action=rc;format=rss;"
191                           . lc($type) . "=" . lc(CGI->escape($2));
192        $tt_vars{"atom_".lc($type)."_url"} =
193                           $config->script_name . "?action=rc;format=atom;"
194                           . lc($type) . "=" . lc(CGI->escape($2));
195    }
196
197    my %current_data = $wiki->retrieve_node( $id );
198    my $current_version = $current_data{version};
199    undef $version if ($version && $version == $current_version);
200    my %criteria = ( name => $id );
201    $criteria{version} = $version if $version; # retrieve_node default is current
202
203    my %node_data = $wiki->retrieve_node( %criteria );
204
205    # Fixes passing undefined values to Text::Wikiformat if node doesn't exist.
206    my $content = '';
207    if ($node_data{content}) {
208        $content    = $wiki->format($node_data{content});
209    }
210
211    my $modified   = $node_data{last_modified};
212    my $moderated  = $node_data{moderated};
213    my %metadata   = %{$node_data{metadata}};
214
215    my ($wgs84_long, $wgs84_lat) = OpenGuides::Utils->get_wgs84_coords(
216                                        longitude => $metadata{longitude}[0],
217                                        latitude => $metadata{latitude}[0],
218                                        config => $config);
219    if ($args{format} && $args{format} eq 'raw') {
220        print "Content-Type: text/plain\n\n";
221        print $node_data{content};
222        return 0;
223    }
224   
225    my %metadata_vars = OpenGuides::Template->extract_metadata_vars(
226                            wiki     => $wiki,
227                            config   => $config,
228                            metadata => $node_data{metadata}
229                        );
230
231    %tt_vars = (
232                   %tt_vars,
233                   %metadata_vars,
234                   content       => $content,
235                   last_modified => $modified,
236                   version       => $node_data{version},
237                   node          => $id,
238                   language      => $config->default_language,
239                   moderated     => $moderated,
240                   oldid         => $oldid,
241                   enable_gmaps  => 1,
242                   wgs84_long    => $wgs84_long,
243                   wgs84_lat     => $wgs84_lat
244               );
245
246    if ( $config->show_gmap_in_node_display
247           && $self->get_cookie( "display_google_maps" ) ) {
248        $tt_vars{display_google_maps} = 1;
249    }
250
251    # Should we include a standard list of categories or locales?
252    if ($config->enable_common_categories || $config->enable_common_locales) {
253        $tt_vars{common_catloc} = 1;
254        $tt_vars{common_categories} = $config->enable_common_categories;
255        $tt_vars{common_locales} = $config->enable_common_locales;
256        $tt_vars{catloc_link} = $config->script_name . "?id=";
257    }
258   
259    if ( $node_data{content} && $node_data{content} =~ /^#REDIRECT\s+(.+?)\s*$/ ) {
260        my $redirect = $1;
261        # Strip off enclosing [[ ]] in case this is an extended link.
262        $redirect =~ s/^\[\[//;
263        $redirect =~ s/\]\]\s*$//;
264
265        # Don't redirect if the parameter "redirect" is given as 0.
266        if ($do_redirect == 0) {
267            return %tt_vars if $args{return_tt_vars};
268            $tt_vars{current} = 1;
269            my $output = $self->process_template(
270                                                  id            => $id,
271                                                  template      => "node.tt",
272                                                  tt_vars       => \%tt_vars,
273                                                );
274            return $output if $return_output;
275            print $output;
276        } elsif ( $wiki->node_exists($redirect) && $redirect ne $id && $redirect ne $oldid ) {
277            # Avoid loops by not generating redirects to the same node or the previous node.
278            my $output = $self->redirect_to_node($redirect, $id);
279            return $output if $return_output;
280            print $output;
281            return 0;
282        }
283    }
284
285    # We've undef'ed $version above if this is the current version.
286    $tt_vars{current} = 1 unless $version;
287
288    if ($id eq "RecentChanges") {
289        $self->display_recent_changes(%args);
290    } elsif ( $id eq $self->config->home_name ) {
291        if ( $self->config->recent_changes_on_home_page ) {
292            my @recent = $wiki->list_recent_changes(
293                last_n_changes => 10,
294                metadata_was   => { edit_type => "Normal edit" },
295            );
296            @recent = map {
297                            {
298                              name          => CGI->escapeHTML($_->{name}),
299                              last_modified =>
300                                  CGI->escapeHTML($_->{last_modified}),
301                              version       => CGI->escapeHTML($_->{version}),
302                              comment       =>
303                                  CGI->escapeHTML($_->{metadata}{comment}[0]),
304                              username      =>
305                                  CGI->escapeHTML($_->{metadata}{username}[0]),
306                              url           => $config->script_name . "?"
307                                             . CGI->escape($wiki->formatter->node_name_to_node_param($_->{name}))
308                            }
309                          } @recent;
310            $tt_vars{recent_changes} = \@recent;
311        }
312        return %tt_vars if $args{return_tt_vars};
313        my $output = $self->process_template(
314                                                id            => $id,
315                                                template      => "home_node.tt",
316                                                tt_vars       => \%tt_vars,
317                                            );
318        return $output if $return_output;
319        print $output;
320    } else {
321        return %tt_vars if $args{return_tt_vars};
322        my $output = $self->process_template(
323                                                id            => $id,
324                                                template      => "node.tt",
325                                                tt_vars       => \%tt_vars,
326                                            );
327        return $output if $return_output;
328        print $output;
329    }
330}
331
332=item B<display_edit_form>
333
334  $guide->display_edit_form(
335                             id => "Vivat Bacchus",
336                           );
337
338Display an edit form for the specified node.  As with other methods, the
339C<return_output> parameter can be used to return the output instead of
340printing it to STDOUT.
341
342=cut
343
344sub display_edit_form {
345    my ($self, %args) = @_;
346    my $return_output = $args{return_output} || 0;
347    my $config = $self->config;
348    my $wiki = $self->wiki;
349    my $node = $args{id};
350    my %node_data = $wiki->retrieve_node($node);
351    my ($content, $checksum) = @node_data{ qw( content checksum ) };
352    my %cookie_data = OpenGuides::CGI->get_prefs_from_cookie(config=>$config);
353
354    my $username = $self->get_cookie( "username" );
355    my $edit_type = $self->get_cookie( "default_edit_type" ) eq "normal"
356                        ? "Normal edit"
357                        : "Minor tidying";
358
359    my %metadata_vars = OpenGuides::Template->extract_metadata_vars(
360                             wiki     => $wiki,
361                             config   => $config,
362                 metadata => $node_data{metadata} );
363
364    $metadata_vars{website} ||= 'http://';
365    my $moderate = $wiki->node_required_moderation($node);
366
367    my %tt_vars = ( content         => CGI->escapeHTML($content),
368                    checksum        => CGI->escapeHTML($checksum),
369                    %metadata_vars,
370                    config          => $config,
371                    username        => $username,
372                    edit_type       => $edit_type,
373                    moderate        => $moderate,
374                    deter_robots    => 1,
375    );
376
377    my $output = $self->process_template(
378                                          id            => $node,
379                                          template      => "edit_form.tt",
380                                          tt_vars       => \%tt_vars,
381                                        );
382    return $output if $return_output;
383    print $output;
384}
385
386=item B<preview_edit>
387
388  $guide->preview_edit(
389                        id      => "Vivat Bacchus",
390                        cgi_obj => $q,
391                      );
392
393Preview the edited version of the specified node.  As with other methods, the
394C<return_output> parameter can be used to return the output instead of
395printing it to STDOUT.
396
397=cut
398
399sub preview_edit {
400    my ($self, %args) = @_;
401    my $node = $args{id};
402    my $q = $args{cgi_obj};
403    my $return_output = $args{return_output};
404    my $wiki = $self->wiki;
405    my $config = $self->config;
406
407    my $content  = $q->param('content');
408    $content     =~ s/\r\n/\n/gs;
409    my $checksum = $q->param('checksum');
410
411    my %new_metadata = OpenGuides::Template->extract_metadata_vars(
412                                               wiki                 => $wiki,
413                                               config               => $config,
414                                               cgi_obj              => $q,
415                                               set_coord_field_vars => 1,
416    );
417    foreach my $var ( qw( username comment edit_type ) ) {
418        $new_metadata{$var} = $q->escapeHTML($q->param($var));
419    }
420
421    if ($wiki->verify_checksum($node, $checksum)) {
422        my $moderate = $wiki->node_required_moderation($node);
423        my %tt_vars = (
424            %new_metadata,
425            config                 => $config,
426            content                => $q->escapeHTML($content),
427            preview_html           => $wiki->format($content),
428            preview_above_edit_box => $self->get_cookie(
429                                                   "preview_above_edit_box" ),
430            checksum               => $q->escapeHTML($checksum),
431            moderate               => $moderate
432        );
433        my $output = $self->process_template(
434                                              id       => $node,
435                                              template => "edit_form.tt",
436                                              tt_vars  => \%tt_vars,
437                                            );
438        return $output if $args{return_output};
439        print $output;
440    } else {
441        return $self->_handle_edit_conflict(
442                                             id            => $node,
443                                             content       => $content,
444                                             new_metadata  => \%new_metadata,
445                                             return_output => $return_output,
446                                           );
447    }
448}
449
450=item B<display_recent_changes> 
451
452  $guide->display_recent_changes;
453
454As with other methods, the C<return_output> parameter can be used to
455return the output instead of printing it to STDOUT.
456
457=cut
458
459sub display_recent_changes {
460    my ($self, %args) = @_;
461    my $config = $self->config;
462    my $wiki = $self->wiki;
463    my $minor_edits = $self->get_cookie( "show_minor_edits_in_rc" );
464    my $id = $args{id} || $self->config->home_name;
465    my $return_output = $args{return_output} || 0;
466    my (%tt_vars, %recent_changes);
467    my $q = CGI->new;
468    my $since = $q->param("since");
469    if ( $since ) {
470        $tt_vars{since} = $since;
471        my $t = localtime($since); # overloaded by Time::Piece
472        $tt_vars{since_string} = $t->strftime;
473        my %criteria = ( since => $since );   
474        $criteria{metadata_was} = { edit_type => "Normal edit" }
475          unless $minor_edits;
476        my @rc = $self->{wiki}->list_recent_changes( %criteria );
477 
478        @rc = map {
479            {
480              name        => CGI->escapeHTML($_->{name}),
481              last_modified => CGI->escapeHTML($_->{last_modified}),
482              version     => CGI->escapeHTML($_->{version}),
483              comment     => CGI->escapeHTML($_->{metadata}{comment}[0]),
484              username    => CGI->escapeHTML($_->{metadata}{username}[0]),
485              host        => CGI->escapeHTML($_->{metadata}{host}[0]),
486              username_param => CGI->escape($_->{metadata}{username}[0]),
487              edit_type   => CGI->escapeHTML($_->{metadata}{edit_type}[0]),
488              url         => $config->script_name . "?"
489      . CGI->escape($wiki->formatter->node_name_to_node_param($_->{name})),
490        }
491                   } @rc;
492        if ( scalar @rc ) {
493            $recent_changes{since} = \@rc;
494        }
495    } else {
496        for my $days ( [0, 1], [1, 7], [7, 14], [14, 30] ) {
497            my %criteria = ( between_days => $days );
498            $criteria{metadata_was} = { edit_type => "Normal edit" }
499              unless $minor_edits;
500            my @rc = $self->{wiki}->list_recent_changes( %criteria );
501
502            @rc = map {
503            {
504              name        => CGI->escapeHTML($_->{name}),
505              last_modified => CGI->escapeHTML($_->{last_modified}),
506              version     => CGI->escapeHTML($_->{version}),
507              comment     => CGI->escapeHTML($_->{metadata}{comment}[0]),
508              username    => CGI->escapeHTML($_->{metadata}{username}[0]),
509              host        => CGI->escapeHTML($_->{metadata}{host}[0]),
510              username_param => CGI->escape($_->{metadata}{username}[0]),
511              edit_type   => CGI->escapeHTML($_->{metadata}{edit_type}[0]),
512              url         => $config->script_name . "?"
513      . CGI->escape($wiki->formatter->node_name_to_node_param($_->{name})),
514        }
515                       } @rc;
516            if ( scalar @rc ) {
517                $recent_changes{$days->[1]} = \@rc;
518        }
519        }
520    }
521    $tt_vars{not_editable} = 1;
522    $tt_vars{recent_changes} = \%recent_changes;
523    my %processing_args = (
524                            id            => $id,
525                            template      => "recent_changes.tt",
526                            tt_vars       => \%tt_vars,
527                           );
528    if ( !$since && $self->get_cookie("track_recent_changes_views") ) {
529    my $cookie =
530           OpenGuides::CGI->make_recent_changes_cookie(config => $config );
531        $processing_args{cookies} = $cookie;
532        $tt_vars{last_viewed} = OpenGuides::CGI->get_last_recent_changes_visit_from_cookie( config => $config );
533    }
534    return %tt_vars if $args{return_tt_vars};
535    my $output = $self->process_template( %processing_args );
536    return $output if $return_output;
537    print $output;
538}
539
540=item B<display_diffs>
541
542  $guide->display_diffs(
543                           id            => "Home Page",
544                           version       => 6,
545                           other_version => 5,
546                       );
547
548  # Or return output as a string (useful for writing tests).
549  my $output = $guide->display_diffs(
550                                        id            => "Home Page",
551                                        version       => 6,
552                                        other_version => 5,
553                                        return_output => 1,
554                                    );
555
556  # Or return the hash of variables that will be passed to the template
557  # (not including those set additionally by OpenGuides::Template).
558  my %vars = $guide->display_diffs(
559                                      id             => "Home Page",
560                                      version        => 6,
561                                      other_version  => 5,
562                                      return_tt_vars => 1,
563                                  );
564
565=cut
566
567sub display_diffs {
568    my ($self, %args) = @_;
569    my %diff_vars = $self->differ->differences(
570                                                  node          => $args{id},
571                                                  left_version  => $args{version},
572                                                  right_version => $args{other_version},
573                                              );
574    $diff_vars{not_deletable} = 1;
575    $diff_vars{not_editable}  = 1;
576    $diff_vars{deter_robots}  = 1;
577    return %diff_vars if $args{return_tt_vars};
578    my $output = $self->process_template(
579                                            id       => $args{id},
580                                            template => "differences.tt",
581                                            tt_vars  => \%diff_vars
582                                        );
583    return $output if $args{return_output};
584    print $output;
585}
586
587=item B<find_within_distance>
588
589  $guide->find_within_distance(
590                                  id => $node,
591                                  metres => $q->param("distance_in_metres")
592                              );
593
594=cut
595
596sub find_within_distance {
597    my ($self, %args) = @_;
598    my $node = $args{id};
599    my $metres = $args{metres};
600    my %data = $self->wiki->retrieve_node( $node );
601    my $lat = $data{metadata}{latitude}[0];
602    my $long = $data{metadata}{longitude}[0];
603    my $script_url = $self->config->script_url;
604    my $q = CGI->new;
605    print $q->redirect( $script_url . "search.cgi?lat=$lat;long=$long;distance_in_metres=$metres" );
606}
607
608=item B<show_backlinks>
609
610  $guide->show_backlinks( id => "Calthorpe Arms" );
611
612As with other methods, parameters C<return_tt_vars> and
613C<return_output> can be used to return these things instead of
614printing the output to STDOUT.
615
616=cut
617
618sub show_backlinks {
619    my ($self, %args) = @_;
620    my $wiki = $self->wiki;
621    my $formatter = $wiki->formatter;
622
623    my @backlinks = $wiki->list_backlinks( node => $args{id} );
624    my @results = map {
625                          {
626                              url   => CGI->escape($formatter->node_name_to_node_param($_)),
627                              title => CGI->escapeHTML($_)
628                          }
629                      } sort @backlinks;
630    my %tt_vars = ( results       => \@results,
631                    num_results   => scalar @results,
632                    not_deletable => 1,
633                    deter_robots  => 1,
634                    not_editable  => 1 );
635    return %tt_vars if $args{return_tt_vars};
636    my $output = OpenGuides::Template->output(
637                                                 node    => $args{id},
638                                                 wiki    => $wiki,
639                                                 config  => $self->config,
640                                                 template=>"backlink_results.tt",
641                                                 vars    => \%tt_vars,
642                                             );
643    return $output if $args{return_output};
644    print $output;
645}
646
647=item B<show_index>
648
649  $guide->show_index(
650                        type   => "category",
651                        value  => "pubs",
652                    );
653
654  # RDF version.
655  $guide->show_index(
656                        type   => "locale",
657                        value  => "Holborn",
658                        format => "rdf",
659                    );
660
661  # RSS / Atom version (recent changes style).
662  $guide->show_index(
663                        type   => "locale",
664                        value  => "Holborn",
665                        format => "rss",
666                    );
667
668  # Or return output as a string (useful for writing tests).
669  $guide->show_index(
670                        type          => "category",
671                        value         => "pubs",
672                        return_output => 1,
673                    );
674
675=cut
676
677sub show_index {
678    my ($self, %args) = @_;
679    my $wiki = $self->wiki;
680    my $formatter = $wiki->formatter;
681    my %tt_vars;
682    my @selnodes;
683
684    if ( $args{type} and $args{value} ) {
685        if ( $args{type} eq "fuzzy_title_match" ) {
686            my %finds = $wiki->fuzzy_title_match( $args{value} );
687            @selnodes = sort { $finds{$a} <=> $finds{$b} } keys %finds;
688            $tt_vars{criterion} = {
689                type  => $args{type},  # for RDF version
690                value => $args{value}, # for RDF version
691                name  => CGI->escapeHTML("Fuzzy Title Match on '$args{value}'")
692            };
693            $tt_vars{not_editable} = 1;
694        } else {
695            @selnodes = $wiki->list_nodes_by_metadata(
696                metadata_type  => $args{type},
697                metadata_value => $args{value},
698                ignore_case    => 1
699            );
700            my $name = ucfirst($args{type}) . " $args{value}";
701            my $url = $self->config->script_name
702                      . "?"
703                      . ucfirst( $args{type} )
704                      . "_"
705                      . uri_escape(
706                                      $formatter->node_name_to_node_param($args{value})
707                                  );
708            $tt_vars{criterion} = {
709                type  => $args{type},
710                value => $args{value}, # for RDF version
711                name  => CGI->escapeHTML( $name ),
712                url   => $url
713            };
714            $tt_vars{not_editable} = 1;
715        }
716    } else {
717        @selnodes = $wiki->list_all_nodes();
718    }
719
720    my @nodes = map {
721                        {
722                            name      => $_,
723                            node_data => { $wiki->retrieve_node( name => $_ ) },
724                            param     => $formatter->node_name_to_node_param($_) }
725                        } sort @selnodes;
726
727    # Convert the lat+long to WGS84 as required
728    for(my $i=0; $i<scalar @nodes;$i++) {
729        my $node = $nodes[$i];
730        if($node) {
731            my %metadata = %{$node->{node_data}->{metadata}};
732            my ($wgs84_long, $wgs84_lat);
733            eval {
734                ($wgs84_long, $wgs84_lat) = OpenGuides::Utils->get_wgs84_coords(
735                                      longitude => $metadata{longitude}[0],
736                                      latitude => $metadata{latitude}[0],
737                                      config => $self->config);
738            };
739            warn $@." on ".$metadata{latitude}[0]." ".$metadata{longitude}[0] if $@;
740
741            push @{$nodes[$i]->{node_data}->{metadata}->{wgs84_long}}, $wgs84_long;
742            push @{$nodes[$i]->{node_data}->{metadata}->{wgs84_lat}},  $wgs84_lat;
743        }
744    }
745
746    $tt_vars{nodes} = \@nodes;
747
748    my ($template, %conf);
749
750    if ( $args{format} ) {
751        if ( $args{format} eq "rdf" ) {
752            $template = "rdf_index.tt";
753            $conf{content_type} = "application/rdf+xml";
754        }
755        elsif ( $args{format} eq "plain" ) {
756            $template = "plain_index.tt";
757            $conf{content_type} = "text/plain";
758        } elsif ( $args{format} eq "map" ) {
759            my $q = CGI->new;
760            $tt_vars{zoom} = $q->param('zoom') || '';
761            $tt_vars{lat} = $q->param('lat') || '';
762            $tt_vars{long} = $q->param('long') || '';
763            $tt_vars{map_type} = $q->param('map_type') || '';
764            $tt_vars{centre_long} = $self->config->centre_long;
765            $tt_vars{centre_lat} = $self->config->centre_lat;
766            $tt_vars{default_gmaps_zoom} = $self->config->default_gmaps_zoom;
767            $tt_vars{enable_gmaps} = 1;
768            $tt_vars{display_google_maps} = 1; # override for this page
769            $template = "map_index.tt";
770           
771        } elsif( $args{format} eq "rss" || $args{format} eq "atom") {
772            # They really wanted a recent changes style rss/atom feed
773            my $feed_type = $args{format};
774            my ($feed,$content_type) = $self->get_feed_and_content_type($feed_type);
775            $feed->set_feed_name_and_url_params(
776                        "Index of $args{type} $args{value}",
777                        "action=index;index_type=$args{type};index_value=$args{value}"
778            );
779
780            # Grab the actual node data out of @nodes
781            my @node_data;
782            foreach my $node (@nodes) {
783                $node->{node_data}->{name} = $node->{name};
784                push @node_data, $node->{node_data};
785            }
786
787            my $output = "Content-Type: ".$content_type."\n";
788            $output .= $feed->build_feed_for_nodes($feed_type, @node_data);
789
790            return $output if $args{return_output};
791            print $output;
792            return;
793        }
794    } else {
795        $template = "site_index.tt";
796    }
797
798    %conf = (
799                %conf,
800                node        => "$args{type} index", # KLUDGE
801                template    => $template,
802                tt_vars     => \%tt_vars,
803            );
804
805    my $output = $self->process_template( %conf );
806    return $output if $args{return_output};
807    print $output;
808}
809
810=item B<list_all_versions>
811
812  $guide->list_all_versions ( id => "Home Page" );
813
814  # Or return output as a string (useful for writing tests).
815  $guide->list_all_versions (
816                                id            => "Home Page",
817                                return_output => 1,
818                            );
819
820  # Or return the hash of variables that will be passed to the template
821  # (not including those set additionally by OpenGuides::Template).
822  $guide->list_all_versions (
823                                id             => "Home Page",
824                                return_tt_vars => 1,
825                            );
826
827=cut
828
829sub list_all_versions {
830    my ($self, %args) = @_;
831    my $return_output = $args{return_output} || 0;
832    my $node = $args{id};
833    my %curr_data = $self->wiki->retrieve_node($node);
834    my $curr_version = $curr_data{version};
835    my @history;
836    for my $version ( 1 .. $curr_version ) {
837        my %node_data = $self->wiki->retrieve_node( name    => $node,
838                                                    version => $version );
839        # $node_data{version} will be zero if this version was deleted.
840        push @history, {
841            version  => CGI->escapeHTML( $version ),
842            modified => CGI->escapeHTML( $node_data{last_modified} ),
843            username => CGI->escapeHTML( $node_data{metadata}{username}[0] ),
844            comment  => CGI->escapeHTML( $node_data{metadata}{comment}[0] ),
845                       } if $node_data{version};
846    }
847    @history = reverse @history;
848    my %tt_vars = (
849                      node          => $node,
850                      version       => $curr_version,
851                      not_deletable => 1,
852                      not_editable  => 1,
853                      deter_robots  => 1,
854                      history       => \@history
855                  );
856    return %tt_vars if $args{return_tt_vars};
857    my $output = $self->process_template(
858                                            id       => $node,
859                                            template => "node_history.tt",
860                                            tt_vars  => \%tt_vars,
861                                        );
862    return $output if $return_output;
863    print $output;
864}
865
866=item B<get_feed_and_content_type>
867
868Fetch the OpenGuides feed object, and the output content type, for the
869supplied feed type.
870
871Handles all the setup for the OpenGuides feed object.
872
873=cut
874
875sub get_feed_and_content_type {
876    my ($self, $feed_type) = @_;
877
878    my $feed = OpenGuides::Feed->new(
879                                        wiki       => $self->wiki,
880                                        config     => $self->config,
881                                        og_version => $VERSION,
882                                    );
883
884    my $content_type = $feed->default_content_type($feed_type);
885
886    return ($feed, $content_type);
887}
888
889=item B<display_feed>
890
891  # Last ten non-minor edits to Hammersmith pages in RSS 1.0 format
892  $guide->display_feed(
893                         feed_type          => 'rss',
894                         feed_listing       => 'recent_changes',
895                         items              => 10,
896                         ignore_minor_edits => 1,
897                         locale             => "Hammersmith",
898                     );
899
900  # All edits bob has made to pub pages in the last week in Atom format
901  $guide->display_feed(
902                         feed_type    => 'atom',
903                         feed_listing => 'recent_changes',
904                         days         => 7,
905                         username     => "bob",
906                         category     => "Pubs",
907                     );
908
909C<feed_type> is a mandatory parameter. Supported values at present are
910"rss" and "atom".
911
912C<feed_listing> is a mandatory parameter. Supported values at present
913are "recent_changes". (More values are coming soon though!)
914
915As with other methods, the C<return_output> parameter can be used to
916return the output instead of printing it to STDOUT.
917
918=cut
919
920sub display_feed {
921    my ($self, %args) = @_;
922
923    my $feed_type = $args{feed_type};
924    croak "No feed type given" unless $feed_type;
925
926    my $feed_listing = $args{feed_listing};
927    croak "No feed listing given" unless $feed_listing;
928   
929    my $return_output = $args{return_output} ? 1 : 0;
930
931    # Basic criteria, whatever the feed listing type is
932    my %criteria = (
933                       feed_type             => $feed_type,
934                       feed_listing          => $feed_listing,
935                       also_return_timestamp => 1,
936                   );
937
938    # Feed listing specific criteria
939    if($feed_listing eq "recent_changes") {
940        $criteria{items} = $args{items} || "";
941        $criteria{days}  = $args{days}  || "";
942        $criteria{ignore_minor_edits} = $args{ignore_minor_edits} ? 1 : 0;
943
944        my $username = $args{username} || "";
945        my $category = $args{category} || "";
946        my $locale   = $args{locale}   || "";
947
948        my %filter;
949        $filter{username} = $username if $username;
950        $filter{category} = $category if $category;
951        $filter{locale}   = $locale   if $locale;
952        if ( scalar keys %filter ) {
953            $criteria{filter_on_metadata} = \%filter;
954        }
955    }
956    elsif($feed_listing eq "node_all_versions") {
957        $criteria{name} = $args{name};
958    }
959
960
961    # Get the feed object, and the content type
962    my ($feed,$content_type) = $self->get_feed_and_content_type($feed_type);
963
964    my $output = "Content-Type: ".$content_type;
965    if($self->config->http_charset) {
966        $output .= "; charset=".$self->config->http_charset;
967    }
968    $output .= "\n";
969   
970    # Get the feed, and the timestamp, in one go
971    my ($feed_output, $feed_timestamp) =
972        $feed->make_feed( %criteria );
973    my $maker = $feed->fetch_maker($feed_type);
974 
975    $output .= "Last-Modified: " . ($maker->parse_feed_timestamp($feed_timestamp))->strftime('%a, %d %b %Y %H:%M:%S +0000') . "\n\n";
976    $output .= $feed_output;
977
978    return $output if $return_output;
979    print $output;
980}
981
982sub display_about {
983    my ($self, %args) = @_;
984
985    my $output;
986
987    if ($args{format} && $args{format} =~ /^rdf$/i) {