source: trunk/lib/OpenGuides.pm @ 1269

Last change on this file since 1269 was 1269, checked in by Dominic Hargreaves, 13 years ago

Don't display revision info for nodes that don't exist (fixes #193)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 83.1 KB
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.64';
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    $tt_vars{empty_node} = !($tt_vars{content} ||
292                             $tt_vars{coord_field_1_value} ||
293                             $tt_vars{coord_field_2_value} ||
294                             $tt_vars{latitude} ||
295                             $tt_vars{longitude});
296
297    # Hide from search engines if showing a specific version.
298    $tt_vars{'deter_robots'} = 1 if $args{version};
299
300    if ( $config->show_gmap_in_node_display
301           && $self->get_cookie( "display_google_maps" ) ) {
302        $tt_vars{display_google_maps} = 1;
303    }
304
305    my $redirect = OpenGuides::Utils->detect_redirect(
306                                              content => $node_data{content} );
307    if ( $redirect ) {
308        # Don't redirect if the parameter "redirect" is given as 0.
309        if ($do_redirect == 0) {
310            $tt_vars{current} = 1;
311            return %tt_vars if $args{return_tt_vars};
312            my $output = $self->process_template(
313                                                  id            => $id,
314                                                  template      => "node.tt",
315                                                  tt_vars       => \%tt_vars,
316                                                );
317            return $output if $return_output;
318            print $output;
319        } elsif ( $wiki->node_exists($redirect) && $redirect ne $id && $redirect ne $oldid ) {
320            # Avoid loops by not generating redirects to the same node or the previous node.
321            if ( $return_output ) {
322                if ( $intercept_redirect ) {
323                    return $self->redirect_to_node( $redirect, $id );
324                } else {
325                    return $self->display_node( id            => $redirect,
326                                                oldid         => $id,
327                                                return_output => 1,
328                                              );
329                }
330            }
331            print $self->redirect_to_node( $redirect, $id );
332            return 0;
333        }
334    }
335
336    # We've undef'ed $version above if this is the current version.
337    $tt_vars{current} = 1 unless $version;
338
339    if ($id eq "RecentChanges") {
340        $self->display_recent_changes(%args);
341    } elsif ( $id eq $self->config->home_name ) {
342        if ( $self->config->recent_changes_on_home_page ) {
343            my @recent = $wiki->list_recent_changes(
344                last_n_changes => 10,
345                metadata_was   => { edit_type => "Normal edit" },
346            );
347            my $base_url = $config->script_name . '?';
348            @recent = map {
349                            {
350                              name          => CGI->escapeHTML($_->{name}),
351                              last_modified =>
352                                  CGI->escapeHTML($_->{last_modified}),
353                              version       => CGI->escapeHTML($_->{version}),
354                              comment       => OpenGuides::Utils::parse_change_comment(
355                                  CGI->escapeHTML($_->{metadata}{comment}[0]),
356                                  $base_url,
357                              ),
358                              username      =>
359                                  CGI->escapeHTML($_->{metadata}{username}[0]),
360                              url           => $base_url
361                                             . CGI->escape($wiki->formatter->node_name_to_node_param($_->{name}))
362                            }
363                          } @recent;
364            $tt_vars{recent_changes} = \@recent;
365        }
366        return %tt_vars if $args{return_tt_vars};
367        my $output = $self->process_template(
368                                                id            => $id,
369                                                template      => "home_node.tt",
370                                                tt_vars       => \%tt_vars,
371                                            );
372        return $output if $return_output;
373        print $output;
374    } else {
375        return %tt_vars if $args{return_tt_vars};
376        my $output = $self->process_template(
377                                                id            => $id,
378                                                template      => "node.tt",
379                                                tt_vars       => \%tt_vars,
380                                            );
381        return $output if $return_output;
382        print $output;
383    }
384}
385
386=item B<display_random_page>
387
388  $guide->display_random_page;
389
390Display a random page.  As with other methods, the C<return_output>
391parameter can be used to return the output instead of printing it to STDOUT.
392You can also restrict it to a given category and/or locale by supplying
393appropriate parameters:
394
395  $guide->display_random_page(
396                               category => "pubs",
397                               locale   => "bermondsey",
398                             );
399
400The values of these parameters are case-insensitive.
401
402You can make sure this method never returns pages that are themselves
403categories and/or locales by setting C<random_page_omits_categories>
404and/or C<random_page_omits_locales> in your wiki.conf.
405
406=cut
407
408sub display_random_page {
409    my ( $self, %args ) = @_;
410    my $wiki = $self->wiki;
411    my $config = $self->config;
412
413    my ( @catnodes, @locnodes, @nodes );
414    if ( $args{category} ) {
415        @catnodes = $wiki->list_nodes_by_metadata(
416            metadata_type  => "category",
417            metadata_value => $args{category},
418            ignore_case    => 1,
419        );
420    }
421    if ( $args{locale} ) {
422        @locnodes = $wiki->list_nodes_by_metadata(
423            metadata_type  => "locale",
424            metadata_value => $args{locale},
425            ignore_case    => 1,
426        );
427    }
428
429    if ( $args{category} && $args{locale} ) {
430        # If we have both category and locale, return the intersection.
431        my %count;
432        foreach my $node ( @catnodes, @locnodes ) {
433            $count{$node}++;
434        }
435        foreach my $node ( keys %count ) {
436            push @nodes, $node if $count{$node} > 1;
437        }
438    } elsif ( $args{category} ) {
439        @nodes = @catnodes;
440    } elsif ( $args{locale} ) {
441        @nodes = @locnodes;
442    } else {
443        @nodes = $wiki->list_all_nodes();
444    }
445
446    my $omit_cats = $config->random_page_omits_categories;
447    my $omit_locs = $config->random_page_omits_locales;
448
449    if ( $omit_cats || $omit_locs ) {
450        my %all_nodes = map { $_ => $_ } @nodes;
451        if ( $omit_cats ) {
452            my @cats = $wiki->list_nodes_by_metadata(
453                                                  metadata_type  => "category",
454                                                  metadata_value => "category",
455                                                  ignore_case => 1,
456            );
457            foreach my $omit ( @cats ) {
458                delete $all_nodes{$omit};
459            }
460        }
461        if ( $omit_locs ) {
462            my @locs = $wiki->list_nodes_by_metadata(
463                                                  metadata_type  => "category",
464                                                  metadata_value => "locales",
465                                                  ignore_case => 1,
466            );
467            foreach my $omit ( @locs ) {
468                delete $all_nodes{$omit};
469            }
470        }
471        @nodes = keys %all_nodes;
472    }
473    my $node = $nodes[ rand @nodes ];
474    my $output;
475
476    if ( $node ) {
477        $output = $self->redirect_to_node( $node );
478    } else {
479        my %tt_vars = (
480                        category => $args{category},
481                        locale   => $args{locale},
482                      );
483        $output = OpenGuides::Template->output(
484            wiki     => $wiki,
485            config   => $config,
486            template => "random_page_failure.tt",
487            vars     => \%tt_vars,
488        );
489    }
490    return $output if $args{return_output};
491    print $output;
492}
493
494=item B<display_edit_form>
495
496  $guide->display_edit_form(
497                             id => "Vivat Bacchus",
498                             vars => \%vars,
499                             content => $content,
500                             metadata => \%metadata,
501                             checksum => $checksum
502                           );
503
504Display an edit form for the specified node.  As with other methods, the
505C<return_output> parameter can be used to return the output instead of
506printing it to STDOUT.
507
508If this is to redisplay an existing edit, the content, metadata
509and checksum may be supplied in those arguments
510
511Extra template variables may be supplied in the vars argument
512
513=cut
514
515sub display_edit_form {
516    my ($self, %args) = @_;
517    my $return_output = $args{return_output} || 0;
518    my $config = $self->config;
519    my $wiki = $self->wiki;
520    my $node = $args{id};
521    my %node_data = $wiki->retrieve_node($node);
522    my ($content, $checksum) = @node_data{ qw( content checksum ) };
523    my %cookie_data = OpenGuides::CGI->get_prefs_from_cookie(config=>$config);
524
525    my $username = $self->get_cookie( "username" );
526    my $edit_type = $self->get_cookie( "default_edit_type" ) eq "normal"
527                        ? "Normal edit"
528                        : "Minor tidying";
529
530    my %metadata_vars = OpenGuides::Template->extract_metadata_vars(
531                             wiki     => $wiki,
532                             config   => $config,
533                 metadata => $node_data{metadata} );
534
535    $metadata_vars{website} ||= 'http://';
536    my $moderate = $wiki->node_required_moderation($node);
537
538    my %tt_vars = ( content         => CGI->escapeHTML($content),
539                    checksum        => CGI->escapeHTML($checksum),
540                    %metadata_vars,
541                    config          => $config,
542                    username        => $username,
543                    edit_type       => $edit_type,
544                    moderate        => $moderate,
545                    deter_robots    => 1,
546    );
547
548    # Override some things if we were supplied with them
549    $tt_vars{content} = $args{content} if $args{content};
550    $tt_vars{checksum} = $args{checksum} if $args{checksum};
551    if (defined $args{vars}) {
552        my %supplied_vars = %{$args{vars}};
553        foreach my $key ( keys %supplied_vars ) {
554            $tt_vars{$key} = $supplied_vars{$key};
555        }
556    }
557    if (defined $args{metadata}) {
558        my %supplied_metadata = %{$args{metadata}};
559        foreach my $key ( keys %supplied_metadata ) {
560            $tt_vars{$key} = $supplied_metadata{$key};
561        }
562    }
563
564    my $output = $self->process_template(
565                                          id            => $node,
566                                          template      => "edit_form.tt",
567                                          tt_vars       => \%tt_vars,
568                                        );
569    return $output if $return_output;
570    print $output;
571}
572
573=item B<preview_edit>
574
575  $guide->preview_edit(
576                        id      => "Vivat Bacchus",
577                        cgi_obj => $q,
578                      );
579
580Preview the edited version of the specified node.  As with other methods, the
581C<return_output> parameter can be used to return the output instead of
582printing it to STDOUT.
583
584=cut
585
586sub preview_edit {
587    my ($self, %args) = @_;
588    my $node = $args{id};
589    my $q = $args{cgi_obj};
590    my $return_output = $args{return_output};
591    my $wiki = $self->wiki;
592    my $config = $self->config;
593
594    my $content  = $q->param('content');
595    $content     =~ s/\r\n/\n/gs;
596    my $checksum = $q->param('checksum');
597
598    my %new_metadata = OpenGuides::Template->extract_metadata_vars(
599                                               wiki                 => $wiki,
600                                               config               => $config,
601                                               cgi_obj              => $q,
602                                               set_coord_field_vars => 1,
603    );
604    foreach my $var ( qw( username comment edit_type ) ) {
605        $new_metadata{$var} = $q->escapeHTML($q->param($var));
606    }
607
608    if ($wiki->verify_checksum($node, $checksum)) {
609        my $moderate = $wiki->node_required_moderation($node);
610        my %tt_vars = (
611            %new_metadata,
612            config                 => $config,
613            content                => $q->escapeHTML($content),
614            preview_html           => $wiki->format($content),
615            preview_above_edit_box => $self->get_cookie(
616                                                   "preview_above_edit_box" ),
617            checksum               => $q->escapeHTML($checksum),
618            moderate               => $moderate
619        );
620        my $output = $self->process_template(
621                                              id       => $node,
622                                              template => "edit_form.tt",
623                                              tt_vars  => \%tt_vars,
624                                            );
625        return $output if $args{return_output};
626        print $output;
627    } else {
628        return $self->_handle_edit_conflict(
629                                             id            => $node,
630                                             content       => $content,
631                                             new_metadata  => \%new_metadata,
632                                             return_output => $return_output,
633                                           );
634    }
635}
636
637=item B<display_recent_changes> 
638
639  $guide->display_recent_changes;
640
641As with other methods, the C<return_output> parameter can be used to
642return the output instead of printing it to STDOUT.
643
644=cut
645
646sub display_recent_changes {
647    my ($self, %args) = @_;
648    my $config = $self->config;
649    my $wiki = $self->wiki;
650    my $minor_edits = $self->get_cookie( "show_minor_edits_in_rc" );
651    my $id = $args{id} || $self->config->home_name;
652    my $return_output = $args{return_output} || 0;
653    my (%tt_vars, %recent_changes);
654    my $q = CGI->new;
655    my $since = $q->param("since");
656    if ( $since ) {
657        $tt_vars{since} = $since;
658        my $t = localtime($since); # overloaded by Time::Piece
659        $tt_vars{since_string} = $t->strftime;
660        my %criteria = ( since => $since );   
661        $criteria{metadata_was} = { edit_type => "Normal edit" }
662          unless $minor_edits;
663        my @rc = $self->{wiki}->list_recent_changes( %criteria );
664
665        my $base_url = $config->script_name . '?';
666       
667        @rc = map {
668            {
669              name        => CGI->escapeHTML($_->{name}),
670              last_modified => CGI->escapeHTML($_->{last_modified}),
671              version     => CGI->escapeHTML($_->{version}),
672              comment     => OpenGuides::Utils::parse_change_comment(
673                  CGI->escapeHTML($_->{metadata}{comment}[0]),
674                  $base_url,
675              ),
676              username    => CGI->escapeHTML($_->{metadata}{username}[0]),
677              host        => CGI->escapeHTML($_->{metadata}{host}[0]),
678              username_param => CGI->escape($_->{metadata}{username}[0]),
679              edit_type   => CGI->escapeHTML($_->{metadata}{edit_type}[0]),
680              url         => $base_url
681      . CGI->escape($wiki->formatter->node_name_to_node_param($_->{name})),
682        }
683                   } @rc;
684        if ( scalar @rc ) {
685            $recent_changes{since} = \@rc; 
686        }
687    } else {
688        for my $days ( [0, 1], [1, 7], [7, 14], [14, 30] ) {
689            my %criteria = ( between_days => $days );
690            $criteria{metadata_was} = { edit_type => "Normal edit" }
691              unless $minor_edits;
692            my @rc = $self->{wiki}->list_recent_changes( %criteria );
693
694            my $base_url = $config->script_name . '?';
695           
696            @rc = map {
697            {
698              name        => CGI->escapeHTML($_->{name}),
699              last_modified => CGI->escapeHTML($_->{last_modified}),
700              version     => CGI->escapeHTML($_->{version}),
701              comment     => OpenGuides::Utils::parse_change_comment(
702                  CGI->escapeHTML($_->{metadata}{comment}[0]),
703                  $base_url,
704              ),
705              username    => CGI->escapeHTML($_->{metadata}{username}[0]),
706              host        => CGI->escapeHTML($_->{metadata}{host}[0]),
707              username_param => CGI->escape($_->{metadata}{username}[0]),
708              edit_type   => CGI->escapeHTML($_->{metadata}{edit_type}[0]),
709              url         => $base_url
710      . CGI->escape($wiki->formatter->node_name_to_node_param($_->{name})),
711        }
712                       } @rc;
713            if ( scalar @rc ) {
714                $recent_changes{$days->[1]} = \@rc;
715        }
716        }
717    }
718    $tt_vars{not_editable} = 1;
719    $tt_vars{recent_changes} = \%recent_changes;
720    my %processing_args = (
721                            id            => $id,
722                            template      => "recent_changes.tt",
723                            tt_vars       => \%tt_vars,
724                           );
725    if ( !$since && $self->get_cookie("track_recent_changes_views") ) {
726    my $cookie =
727           OpenGuides::CGI->make_recent_changes_cookie(config => $config );
728        $processing_args{cookies} = $cookie;
729        $tt_vars{last_viewed} = OpenGuides::CGI->get_last_recent_changes_visit_from_cookie( config => $config );
730    }
731    return %tt_vars if $args{return_tt_vars};
732    my $output = $self->process_template( %processing_args );
733    return $output if $return_output;
734    print $output;
735}
736
737=item B<display_diffs>
738
739  $guide->display_diffs(
740                           id            => "Home Page",
741                           version       => 6,
742                           other_version => 5,
743                       );
744
745  # Or return output as a string (useful for writing tests).
746  my $output = $guide->display_diffs(
747                                        id            => "Home Page",
748                                        version       => 6,
749                                        other_version => 5,
750                                        return_output => 1,
751                                    );
752
753  # Or return the hash of variables that will be passed to the template
754  # (not including those set additionally by OpenGuides::Template).
755  my %vars = $guide->display_diffs(
756                                      id             => "Home Page",
757                                      version        => 6,
758                                      other_version  => 5,
759                                      return_tt_vars => 1,
760                                  );
761
762=cut
763
764sub display_diffs {
765    my ($self, %args) = @_;
766    my %diff_vars = $self->differ->differences(
767                                                  node          => $args{id},
768                                                  left_version  => $args{version},
769                                                  right_version => $args{other_version},
770                                              );
771    $diff_vars{not_deletable} = 1;
772    $diff_vars{not_editable}  = 1;
773    $diff_vars{deter_robots}  = 1;
774    return %diff_vars if $args{return_tt_vars};
775    my $output = $self->process_template(
776                                            id       => $args{id},
777                                            template => "differences.tt",
778                                            tt_vars  => \%diff_vars
779                                        );
780    return $output if $args{return_output};
781    print $output;
782}
783
784=item B<find_within_distance>
785
786  $guide->find_within_distance(
787                                  id => $node,
788                                  metres => $q->param("distance_in_metres")
789                              );
790
791=cut
792
793sub find_within_distance {
794    my ($self, %args) = @_;
795    my $node = $args{id};
796    my $metres = $args{metres};
797    my %data = $self->wiki->retrieve_node( $node );
798    my $lat = $data{metadata}{latitude}[0];
799    my $long = $data{metadata}{longitude}[0];
800    my $script_url = $self->config->script_url;
801    my $q = CGI->new;
802    print $q->redirect( $script_url . "search.cgi?lat=$lat;long=$long;distance_in_metres=$metres" );
803}
804
805=item B<show_backlinks>
806
807  $guide->show_backlinks( id => "Calthorpe Arms" );
808
809As with other methods, parameters C<return_tt_vars> and
810C<return_output> can be used to return these things instead of
811printing the output to STDOUT.
812
813=cut
814
815sub show_backlinks {
816    my ($self, %args) = @_;
817    my $wiki = $self->wiki;
818    my $formatter = $wiki->formatter;
819
820    my @backlinks = $wiki->list_backlinks( node => $args{id} );
821    my @results = map {
822                          {
823                              url   => CGI->escape($formatter->node_name_to_node_param($_)),
824                              title => CGI->escapeHTML($_)
825                          }
826                      } sort @backlinks;
827    my %tt_vars = ( results       => \@results,
828                    num_results   => scalar @results,
829                    not_deletable => 1,
830                    deter_robots  => 1,
831                    not_editable  => 1 );
832    return %tt_vars if $args{return_tt_vars};
833    my $output = OpenGuides::Template->output(
834                                                 node    => $args{id},
835                                                 wiki    => $wiki,
836                                                 config  => $self->config,
837                                                 template=>"backlink_results.tt",
838                                                 vars    => \%tt_vars,
839                                             );
840    return $output if $args{return_output};
841    print $output;
842}
843
844=item B<show_index>
845
846  $guide->show_index(
847                        type   => "category",
848                        value  => "pubs",
849                    );
850
851  # RDF version.
852  $guide->show_index(
853                        type   => "locale",
854                        value  => "Holborn",
855                        format => "rdf",
856                    );
857
858  # RSS / Atom version (recent changes style).
859  $guide->show_index(
860                        type   => "locale",
861                        value  => "Holborn",
862                        format => "rss",
863                    );
864
865  # Or return output as a string (useful for writing tests).
866  $guide->show_index(
867                        type          => "category",
868                        value         => "pubs",
869                        return_output => 1,
870                    );
871
872If either the C<type> or the C<value> parameter is omitted, then all pages
873will be returned.
874
875=cut
876
877sub show_index {
878    my ($self, %args) = @_;
879    my $wiki = $self->wiki;
880    my $formatter = $wiki->formatter;
881    my %tt_vars;
882    my @selnodes;
883
884    if ( $args{type} and $args{value} ) {
885        if ( $args{type} eq "fuzzy_title_match" ) {
886            my %finds = $wiki->fuzzy_title_match( $args{value} );
887            @selnodes = sort { $finds{$a} <=> $finds{$b} } keys %finds;
888            $tt_vars{criterion} = {
889                type  => $args{type},  # for RDF version
890                value => $args{value}, # for RDF version
891                name  => CGI->escapeHTML("Fuzzy Title Match on '$args{value}'")
892            };
893            $tt_vars{not_editable} = 1;
894        } else {
895            @selnodes = $wiki->list_nodes_by_metadata(
896                metadata_type  => $args{type},
897                metadata_value => $args{value},
898                ignore_case    => 1
899            );
900            my $name = ucfirst($args{type}) . " $args{value}";
901            my $url = $self->config->script_name
902                      . "?"
903                      . ucfirst( $args{type} )
904                      . "_"
905                      . uri_escape(
906                                      $formatter->node_name_to_node_param($args{value})
907                                  );
908            $tt_vars{criterion} = {
909                type  => $args{type},
910                value => $args{value}, # for RDF version
911                name  => CGI->escapeHTML( $name ),
912                url   => $url
913            };
914            $tt_vars{not_editable} = 1;
915        }
916    } else {
917        @selnodes = $wiki->list_all_nodes();
918    }
919
920    my @nodes = map {
921                        {
922                            name      => $_,
923                            node_data => { $wiki->retrieve_node( name => $_ ) },
924                            param     => $formatter->node_name_to_node_param($_) }
925                        } sort @selnodes;
926
927    # Convert the lat+long to WGS84 as required
928    for(my $i=0; $i<scalar @nodes;$i++) {
929        my $node = $nodes[$i];
930        if($node) {
931            my %metadata = %{$node->{node_data}->{metadata}};
932            my ($wgs84_long, $wgs84_lat);
933            eval {
934                ($wgs84_long, $wgs84_lat) = OpenGuides::Utils->get_wgs84_coords(
935                                      longitude => $metadata{longitude}[0],
936                                      latitude => $metadata{latitude}[0],
937                                      config => $self->config);
938            };
939            warn $@." on ".$metadata{latitude}[0]." ".$metadata{longitude}[0] if $@;
940
941            push @{$nodes[$i]->{node_data}->{metadata}->{wgs84_long}}, $wgs84_long;
942            push @{$nodes[$i]->{node_data}->{metadata}->{wgs84_lat}},  $wgs84_lat;
943        }
944    }
945
946    $tt_vars{nodes} = \@nodes;
947
948    my ($template, %conf);
949
950    if ( $args{format} ) {
951        if ( $args{format} eq "rdf" ) {
952            $template = "rdf_index.tt";
953            $conf{content_type} = "application/rdf+xml";
954        } elsif ( $args{format} eq "json" ) {
955            $template = "json_index.tt";
956            $conf{content_type} = "text/javascript";
957        } elsif ( $args{format} eq "plain" ) {
958            $template = "plain_index.tt";
959            $conf{content_type} = "text/plain";
960        } elsif ( $args{format} eq "map" ) {
961            my $q = CGI->new;
962            $tt_vars{zoom} = $q->param('zoom') || '';
963            $tt_vars{lat} = $q->param('lat') || '';
964            $tt_vars{long} = $q->param('long') || '';
965            $tt_vars{map_type} = $q->param('map_type') || '';
966            $tt_vars{centre_long} = $self->config->centre_long;
967            $tt_vars{centre_lat} = $self->config->centre_lat;
968            $tt_vars{default_gmaps_zoom} = $self->config->default_gmaps_zoom;
969            $tt_vars{enable_gmaps} = 1;
970            $tt_vars{display_google_maps} = 1; # override for this page
971            $template = "map_index.tt";
972           
973        } elsif( $args{format} eq "rss" || $args{format} eq "atom") {
974            # They really wanted a recent changes style rss/atom feed
975            my $feed_type = $args{format};
976            my ($feed,$content_type) = $self->get_feed_and_content_type($feed_type);
977            $feed->set_feed_name_and_url_params(
978                        "Index of $args{type} $args{value}",
979                        "action=index;index_type=$args{type};index_value=$args{value}"
980            );
981
982            # Grab the actual node data out of @nodes
983            my @node_data;
984            foreach my $node (@nodes) {
985                $node->{node_data}->{name} = $node->{name};
986                push @node_data, $node->{node_data};
987            }
988
989            my $output = "Content-Type: ".$content_type."\n";
990            $output .= $feed->build_feed_for_nodes($feed_type, @node_data);
991
992            return $output if $args{return_output};
993            print $output;
994            return;
995        }
996    } else {
997        $template = "site_index.tt";
998    }
999
1000    %conf = (
1001                %conf,
1002                template    => $template,
1003                tt_vars     => \%tt_vars,
1004            );
1005
1006    my $output = $self->process_template( %conf );
1007    return $output if $args{return_output};
1008    print $output;
1009}
1010
1011=item B<show_metadata>
1012
1013  $guide->show_metadata();
1014  $guide->show_metadata(type => "category");
1015  $guide->show_metadata(type => "category", format => "json");
1016
1017Lists all metadata types, or all metadata values of a given
1018type. Useful for programatically discovering a guide.
1019
1020As with other methods, parameters C<return_tt_vars> and
1021C<return_output> can be used to return these things instead of
1022printing the output to STDOUT.
1023
1024=cut
1025sub show_metadata {
1026    my ($self, %args) = @_;
1027    my $wiki = $self->wiki;
1028    my $formatter = $wiki->formatter;
1029
1030    my @values;
1031    my $type;
1032    my $may_descend = 0;
1033    if($args{"type"} && $args{"type"} ne "metadata_type") {
1034       $type = $args{"type"};
1035       @values = $wiki->store->list_metadata_by_type($args{"type"});
1036    } else {
1037       $may_descend = 1;
1038       $type = "metadata_type";
1039       @values = $wiki->store->list_metadata_names;
1040    }
1041
1042    my %tt_vars = ( type          => $type,
1043                    may_descend   => $may_descend,
1044                    metadata      => \@values,
1045                    num_results   => scalar @values,
1046                    not_deletable => 1,
1047                    deter_robots  => 1,
1048                    not_editable  => 1 );
1049    return %tt_vars if $args{return_tt_vars};
1050
1051    my $output;
1052    my $content_type;
1053
1054    if($args{"format"}) {
1055       if($args{"format"} eq "json") {
1056          $content_type = "text/javascript";
1057          my $json = OpenGuides::JSON->new( wiki => $wiki, 
1058                                            config => $self->config );
1059          $output = $json->output_as_json(
1060                                 $type => \@values
1061          );
1062       }
1063    }
1064    unless($output) {
1065       $output = OpenGuides::Template->output(
1066                                                 wiki    => $wiki,
1067                                                 config  => $self->config,
1068                                                 template=>"metadata.tt",
1069                                                 vars    => \%tt_vars,
1070                                             );
1071    }
1072    return $output if $args{return_output};
1073
1074    if($content_type) {
1075       print "Content-type: $content_type\n\n";
1076    }
1077    print $output;
1078}
1079
1080=item B<list_all_versions>
1081
1082  $guide->list_all_versions ( id => "Home Page" );
1083
1084  # Or return output as a string (useful for writing tests).
1085  $guide->list_all_versions (
1086                                id            => "Home Page",
1087                                return_output => 1,
1088                            );
1089
1090  # Or return the hash of variables that will be passed to the template
1091  # (not including those set additionally by OpenGuides::Template).
1092  $guide->list_all_versions (
1093                                id             => "Home Page",
1094                                return_tt_vars => 1,
1095                            );
1096
1097=cut
1098
1099sub list_all_versions {
1100    my ($self, %args) = @_;
1101    my $return_output = $args{return_output} || 0;
1102    my $node = $args{id};
1103    my %curr_data = $self->wiki->retrieve_node($node);
1104    my $curr_version = $curr_data{version};
1105    my @history;
1106    for my $version ( 1 .. $curr_version ) {
1107        my %node_data = $self->wiki->retrieve_node( name    => $node,
1108                                                    version => $version );
1109        # $node_data{version} will be zero if this version was deleted.
1110        push @history, {
1111            version  => CGI->escapeHTML( $version ),
1112            modified => CGI->escapeHTML( $node_data{last_modified} ),
1113            username => CGI->escapeHTML( $node_data{metadata}{username}[0] ),
1114            comment  => OpenGuides::Utils::parse_change_comment(
1115                CGI->escapeHTML( $node_data{metadata}{comment}[0] ),
1116                $self->config->script_name . '?',
1117            ),
1118        } if $node_data{version};
1119    }
1120    @history = reverse @history;
1121    my %tt_vars = (
1122                      node          => $node,
1123                      version       => $curr_version,
1124                      not_deletable => 1,
1125                      not_editable  => 1,
1126                      deter_robots  => 1,
1127                      history       => \@history
1128                  );
1129    return %tt_vars if $args{return_tt_vars};
1130    my $output = $self->process_template(
1131                                            id       => $node,
1132                                            template => "node_history.tt",
1133                                            tt_vars  => \%tt_vars,
1134                                        );
1135    return $output if $return_output;
1136    print $output;
1137}
1138
1139=item B<get_feed_and_content_type>
1140
1141Fetch the OpenGuides feed object, and the output content type, for the
1142supplied feed type.
1143
1144Handles all the setup for the OpenGuides feed object.
1145
1146=cut
1147
1148sub get_feed_and_content_type {
1149    my ($self, $feed_type) = @_;
1150
1151    my $feed = OpenGuides::Feed->new(
1152                                        wiki       => $self->wiki,
1153                                        config     => $self->config,
1154                                        og_version => $VERSION,
1155                                    );
1156
1157    my $content_type = $feed->default_content_type($feed_type);
1158
1159    return ($feed, $content_type);
1160}
1161
1162=item B<display_feed>
1163
1164  # Last ten non-minor edits to Hammersmith pages in RSS 1.0 format
1165  $guide->display_feed(
1166                         feed_type          => 'rss',
1167                         feed_listing       => 'recent_changes',
1168                         items              => 10,
1169                         ignore_minor_edits => 1,
1170                         locale             => "Hammersmith",
1171                     );
1172
1173  # All edits bob has made to pub pages in the last week in Atom format
1174  $guide->display_feed(
1175                         feed_type    => 'atom',
1176                         feed_listing => 'recent_changes',
1177                         days         => 7,
1178                         username     => "bob",
1179                         category     => "Pubs",
1180                     );
1181
1182C<feed_type> is a mandatory parameter. Supported values at present are
1183"rss" and "atom".
1184
1185C<feed_listing> is a mandatory parameter. Supported values at present
1186are "recent_changes". (More values are coming soon though!)
1187
1188As with other methods, the C<return_output> parameter can be used to
1189return the output instead of printing it to STDOUT.
1190
1191=cut
1192
1193sub display_feed {
1194    my ($self, %args) = @_;
1195
1196    my $feed_type = $args{feed_type};
1197    croak "No feed type given" unless $feed_type;
1198
1199    my $feed_listing = $args{feed_listing};
1200    croak "No feed listing given" unless $feed_listing;
1201   
1202    my $return_output = $args{return_output} ? 1 : 0;
1203
1204    # Basic criteria, whatever the feed listing type is
1205    my %criteria = (
1206                       feed_type             => $feed_type,
1207                       feed_listing          => $feed_listing,
1208                       also_return_timestamp => 1,
1209                   );
1210
1211    # Feed listing specific criteria
1212    if($feed_listing eq "recent_changes") {
1213        $criteria{items} = $args{items} || "";
1214        $criteria{days}  = $args{days}  || "";
1215        $criteria{ignore_minor_edits} = $args{ignore_minor_edits} ? 1 : 0;
1216
1217        my $username = $args{username} || "";
1218        my $category = $args{category} || "";
1219        my $locale   = $args{locale}   || "";
1220
1221        my %filter;
1222        $filter{username} = $username if $username;
1223        $filter{category} = $category if $category;
1224        $filter{locale}   = $locale   if $locale;
1225        if ( scalar keys %filter ) {
1226            $criteria{filter_on_metadata} = \%filter;
1227        }
1228    }
1229    elsif($feed_listing eq "node_all_versions") {
1230        $criteria{name} = $args{name};
1231    }
1232
1233
1234    # Get the feed object, and the content type
1235    my ($feed,$content_type) = $self->get_feed_and_content_type($feed_type);
1236
1237    my $output = "Content-Type: ".$content_type;
1238    if($self->config->http_charset) {
1239        $output .= "; charset=".$self->config->http_charset;
1240    }
1241    $output .= "\n";
1242   
1243    # Get the feed, and the timestamp, in one go
1244    my ($feed_output, $feed_timestamp) = 
1245        $feed->make_feed( %criteria );
1246    my $maker = $feed->fetch_maker($feed_type);
1247 
1248    $output .= "Last-Modified: " . ($maker->parse_feed_timestamp($feed_timestamp))->strftime('%a, %d %b %Y %H:%M:%S +0000') . "\n\n";
1249    $output .= $feed_output;
1250
1251    return $output if $return_output;
1252    print $output;
1253}
1254
1255=item B<display_about>
1256
1257                print $guide->display_about(format => "rdf");
1258
1259Displays static 'about' information in various format. Defaults to HTML.
1260
1261=cut
1262
1263sub display_about {
1264    my ($self, %args) = @_;
1265
1266    my $output;
1267
1268    if ($args{format} && $args{format} =~ /^rdf$/i) {
1269        $output = qq{Content-Type: application/rdf+xml
1270
1271<?xml version="1.0" encoding="UTF-8"?>
1272<rdf:RDF xmlns      = "http://usefulinc.com/ns/doap#"
1273         xmlns:rdf  = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
1274         xmlns:foaf = "http://xmlns.com/foaf/0.1/">
1275<Project rdf:ID="OpenGuides">
1276  <name>OpenGuides</name>
1277
1278  <created>2003-04-29</created>
1279 
1280  <shortdesc xml:lang="en">
1281    A wiki engine for collaborative description of places with specialised
1282    geodata metadata features.
1283  </shortdesc>
1284
1285  <description xml:lang="en">
1286    OpenGuides is a collaborative wiki environment, written in Perl, for
1287    building guides and sharing information, as both human-readable text
1288    and RDF. The engine contains a number of geodata-specific metadata
1289    mechanisms such as locale search, node classification and integration
1290    with Google Maps.
1291  </description>
1292
1293  <homepage rdf:resource="http://openguides.org/" />
1294  <mailing-list rdf:resource="http://lists.openguides.org/mailman/listinfo/openguides-dev/" />
1295  <mailing-list rdf:resource="http://urchin.earth.li/mailman/listinfo/openguides-commits/" />
1296
1297  <maintainer>
1298    <foaf:Person rdf:ID="OpenGuidesMaintainer">
1299      <foaf:name>Dominic Hargreaves</foaf:name>
1300      <foaf:homepage rdf:resource="http://www.larted.org.uk/~dom/" />
1301    </foaf:Person>
1302  </maintainer>
1303
1304  <repository>
1305    <SVNRepository rdf:ID="OpenGuidesSVN">
1306      <location rdf:resource="https://urchin.earth.li/svn/openguides/" />
1307      <browse rdf:resource="http://dev.openguides.org/browser" />
1308    </SVNRepository>
1309  </repository>
1310
1311  <release>
1312    <Version rdf:ID="OpenGuidesVersion">
1313      <revision>$VERSION</revision>
1314    </Version>
1315  </release>
1316
1317  <download-page rdf:resource="http://search.cpan.org/dist/OpenGuides/" />
1318 
1319  <!-- Freshmeat category: Internet :: WWW/HTTP :: Dynamic Content -->
1320  <category rdf:resource="http://freshmeat.net/browse/92/" />
1321 
1322  <license rdf:resource="http://www.opensource.org/licenses/gpl-license.php" />
1323  <license rdf:resource="http://www.opensource.org/licenses/artistic-license.php" />
1324
1325</Project>
1326
1327</rdf:RDF>};
1328    } elsif ($args{format} && $args{format} eq 'opensearch') {
1329        my $site_name  = $self->config->site_name;
1330        my $search_url = $self->config->script_url . 'search.cgi';
1331        my $contact_email = $self->config->contact_email;
1332        $output = qq{Content-Type: application/opensearchdescription+xml; charset=utf-8
1333
1334<?xml version="1.0" encoding="UTF-8"?>
1335
1336<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
1337 <ShortName>$site_name</ShortName>
1338 <Description>Search the site '$site_name'</Description>
1339 <Tags>$site_name</Tags>
1340 <Contact>$contact_email</Contact>
1341 <Url type="application/atom+xml"
1342   template="$search_url?search={searchTerms};format=atom"/>
1343 <Url type="application/rss+xml"
1344   template="$search_url?search={searchTerms};format=rss"/>
1345 <Url type="text/html"
1346   template="$search_url?search={searchTerms}"/>
1347 <Query role="example" searchTerms="pubs"/>
1348</OpenSearchDescription>};
1349    } else {
1350        my $site_name  = $self->config->{site_name};
1351        my $script_name = $self->config->{script_name};
1352        $output = qq{Content-Type: text/html; charset=utf-8
1353
1354<html>
1355<head>
1356  <title>About $site_name</title>
1357<style type="text/css">
1358body        { margin: 0px; }
1359#content    { padding: 50px; margin: auto; width: 50%; }
1360h1          { margin-bottom: 0px; font-style: italic; }
1361h2          { margin-top: 0px; }
1362#logo       { text-align: center; }
1363#about      { margin: 0em 0em 1em 0em; border-top: 1px solid #ddd; border-bottom: 1px solid #ddd; }
1364#meta       { font-size: small; text-align: center;}
1365</style>
1366<link rel="alternate"
1367  type="application/rdf+xml"
1368  title="DOAP (Description Of A Project) profile for this site's software"
1369  href="$script_name?action=about;format=rdf" />
1370</head>
1371<body>
1372<div id="content">
1373<div id="logo">
1374<a href="http://openguides.org/"><img
1375src="http://openguides.org/img/logo.png" alt="OpenGuides"></a>
1376<h1><a href="$script_name">$site_name</a></h1>
1377<h2>is powered by <a href="http://openguides.org/">OpenGuides</a> -<br>
1378the guides made by you.</h2>
1379<h3>version <a href="http://search.cpan.org/~dom/OpenGuides-$VERSION">$VERSION</a></h3>
1380</div>
1381<div id="about">
1382<p>
1383<a href="http://www.w3.org/RDF/"><img
1384src="http://openguides.org/img/rdf_icon.png" width="44" height="48"
1385style="float: right; margin-left: 10px; border: 0px"></a> OpenGuides is a
1386web-based collaborative <a href="http://wiki.org/wiki.cgi?WhatIsWiki">wiki</a>
1387environment for building guides and sharing information, as both
1388human-readable text and <a href="http://www.w3.org/RDF/"><acronym
1389title="Resource Description Framework">RDF</acronym></a>. The engine contains
1390a number of geodata-specific metadata mechanisms such as locale search, node
1391classification and integration with <a href="http://maps.google.com/">Google
1392Maps</a>.
1393</p>
1394<p>
1395OpenGuides is written in <a href="http://www.perl.org/">Perl</a>, and is
1396made available under the same license as Perl itself (dual <a
1397href="http://dev.perl.org/licenses/artistic.html" title='The "Artistic Licence"'>Artistic</a> and <a
1398href="http://www.opensource.org/licenses/gpl-license.php"><acronym
1399title="GNU Public Licence">GPL</acronym></a>). Developer information for the
1400project is available from the <a href="http://dev.openguides.org/">OpenGuides
1401development site</a>.
1402</p>
1403<p>
1404Copyright &copy;2003-2008, <a href="http://openguides.org/">The OpenGuides
1405Project</a>. "OpenGuides", "[The] Open Guide To..." and "The guides made by
1406you" are trademarks of The OpenGuides Project. Any uses on this site are made
1407with permission.
1408</p>
1409</div>
1410<div id="meta">
1411<a href="$script_name?action=about;format=rdf"><acronym
1412title="Description Of A Project">DOAP</acronym> RDF version of this
1413information</a>
1414</div>
1415</div>
1416</body>
1417</html>};
1418    }
1419   
1420    return $output if $args{return_output};
1421    print $output;
1422}
1423
1424=item B<commit_node>
1425
1426  $guide->commit_node(
1427                         id      => $node,
1428                         cgi_obj => $q,
1429                     );
1430
1431As with other methods, parameters C<return_tt_vars> and
1432C<return_output> can be used to return these things instead of
1433printing the output to STDOUT.
1434
1435If you have specified the C<spam_detector_module> option in your
1436C<wiki.conf>, this method will attempt to call the <looks_like_spam>
1437method of that module to determine whether the edit is spam.  If this
1438method returns true, then the C<spam_detected.tt> template will be
1439used to display an error message.
1440
1441The C<looks_like_spam> method will be passed a datastructure containing
1442content and metadata.
1443
1444The geographical data that you should provide in the L<CGI> object
1445depends on the handler you chose in C<wiki.conf>.
1446
1447=over
1448
1449=item *
1450
1451B<British National Grid> - provide either C<os_x> and C<os_y> or
1452C<latitude> and C<longitude>; whichever set of data you give, it will
1453be converted to the other and both sets will be stored.
1454
1455=item *
1456
1457B<Irish National Grid> - provide either C<osie_x> and C<osie_y> or
1458C<latitude> and C<longitude>; whichever set of data you give, it will
1459be converted to the other and both sets will be stored.
1460
1461=item *
1462
1463B<UTM ellipsoid> - provide C<latitude> and C<longitude>; these will be
1464converted to easting and northing and both sets of data will be stored.
1465
1466=back
1467
1468=cut
1469
1470sub commit_node {
1471    my ($self, %args) = @_;
1472    my $node = $args{id};
1473    my $q = $args{cgi_obj};
1474    my $return_output = $args{return_output};
1475    my $wiki = $self->wiki;
1476    my $config = $self->config;
1477
1478    my $content  = $q->param("content");
1479    $content =~ s/\r\n/\n/gs;
1480    my $checksum = $q->param("checksum");
1481
1482    my %new_metadata = OpenGuides::Template->extract_metadata_vars(
1483        wiki    => $wiki,
1484        config  => $config,
1485        cgi_obj => $q
1486    );
1487
1488    delete $new_metadata{website} if $new_metadata{website} eq 'http://';
1489
1490    $new_metadata{opening_hours_text} = $q->param("hours_text") || "";
1491
1492    # Pick out the unmunged versions of lat/long if they're set.
1493    # (If they're not, it means they weren't munged in the first place.)
1494    $new_metadata{latitude} = delete $new_metadata{latitude_unmunged}
1495        if $new_metadata{latitude_unmunged};
1496    $new_metadata{longitude} = delete $new_metadata{longitude_unmunged}
1497        if $new_metadata{longitude_unmunged};
1498
1499    foreach my $var ( qw( summary username comment edit_type ) ) {
1500        $new_metadata{$var} = $q->param($var) || "";
1501    }
1502    $new_metadata{host} = $ENV{REMOTE_ADDR};
1503
1504    # Wiki::Toolkit::Plugin::RSS::ModWiki wants "major_change" to be set.
1505    $new_metadata{major_change} = ( $new_metadata{edit_type} eq "Normal edit" )
1506                                    ? 1
1507                                    : 0;
1508
1509    # General validation
1510    my $fails = OpenGuides::Utils->validate_edit(
1511        cgi_obj  => $q
1512    );
1513
1514    if ( scalar @{$fails} ) {
1515        my %vars = (
1516            validate_failed => $fails
1517        );
1518
1519        my $output = $self->display_edit_form(
1520                           id            => $node,
1521                           content       => CGI->escapeHTML($content),
1522                           metadata      => \%new_metadata,
1523                           vars          => \%vars,
1524                           checksum      => CGI->escapeHTML($checksum),
1525                           return_output => 1
1526        );
1527
1528        return $output if $return_output;
1529        print $output;
1530        return;
1531    }
1532
1533    # If we can, check to see if this edit looks like spam.
1534    my $spam_detector = $config->spam_detector_module;
1535    my $is_spam;
1536    if ( $spam_detector ) {
1537        eval {
1538            eval "require $spam_detector";
1539            $is_spam = $spam_detector->looks_like_spam(
1540                node    => $node,
1541                content => $content,
1542                metadata => \%new_metadata,
1543            );
1544        };
1545    }
1546
1547    if ( $is_spam ) {
1548        my $output = OpenGuides::Template->output(
1549            wiki     => $self->wiki,
1550            config   => $config,
1551            template => "spam_detected.tt",
1552            vars     => {
1553                          not_editable => 1,
1554                        },
1555        );
1556        return $output if $return_output;
1557        print $output;
1558        return;
1559    }
1560
1561    # Check to make sure all the indexable nodes are created
1562    # Skip this for nodes needing moderation - this occurs for them once
1563    #  they've been moderated
1564    my $needs_moderation = $wiki->node_required_moderation($node);
1565    my $in_moderate_whitelist
1566        = OpenGuides::Utils->in_moderate_whitelist($self->config, $new_metadata{host});
1567
1568    if ( $in_moderate_whitelist or not $needs_moderation ) {
1569        $self->_autoCreateCategoryLocale(
1570                                          id       => $node,
1571                                          metadata => \%new_metadata
1572        );
1573    }
1574   
1575    my $written = $wiki->write_node( $node, $content, $checksum,
1576                                     \%new_metadata );
1577
1578    if ($written) {
1579        if ( $needs_moderation ) {
1580            if ( $in_moderate_whitelist ) {
1581                $self->wiki->moderate_node(
1582                                            name    => $node,
1583                                            version => $written
1584                );
1585            }
1586            elsif ( $config->send_moderation_notifications ) {
1587                my $body = "The node '$node' in the OpenGuides installation\n" .
1588                    "'" . $config->site_name . "' requires moderation. ".
1589                    "Please visit\n" .
1590                    $config->script_url . $config->script_name .
1591                    "?action=show_needing_moderation\nat your convenience.\n";
1592                eval {
1593                    OpenGuides::Utils->send_email(
1594                        config        => $config,
1595                        subject       => "Node requires moderation",
1596                        body          => $body,
1597                        admin         => 1,
1598                        return_output => $return_output
1599                    );
1600                };
1601                warn $@ if $@;
1602            }
1603        }
1604
1605        my $output = $self->redirect_to_node($node);
1606        return $output if $return_output;
1607        print $output;
1608    } else {
1609        return $self->_handle_edit_conflict(
1610                                             id            => $node,
1611                                             content       => $content,
1612                                             new_metadata  => \%new_metadata,
1613                                             return_output => $return_output,
1614                                           );
1615    }
1616}
1617
1618sub _handle_edit_conflict {
1619    my ($self, %args) = @_;
1620    my $return_output = $args{return_output} || 0;
1621    my $config = $self->config;
1622    my $wiki = $self->wiki;
1623    my $node = $args{id};
1624    my $content = $args{content};
1625    my %new_metadata = %{$args{new_metadata}};
1626
1627    my %node_data = $wiki->retrieve_node($node);
1628    my %tt_vars = ( checksum       => $node_data{checksum},
1629                    new_content    => $content,
1630                    content        => $node_data{content} );
1631    my %old_metadata = OpenGuides::Template->extract_metadata_vars(
1632                                           wiki     => $wiki,
1633                                           config   => $config,
1634                                           metadata => $node_data{metadata} );
1635    # Make sure we look at all variables.
1636    my @tmp = (keys %new_metadata, keys %old_metadata );
1637    my %tmp_hash = map { $_ => 1; } @tmp;
1638    my @all_vars = keys %tmp_hash;
1639
1640    foreach my $mdvar ( keys %new_metadata ) {
1641        if ($mdvar eq "locales") {
1642            $tt_vars{$mdvar} = $old_metadata{locales};
1643            $tt_vars{"new_$mdvar"} = $new_metadata{locale};
1644        } elsif ($mdvar eq "categories") {
1645            $tt_vars{$mdvar} = $old_metadata{categories};
1646            $tt_vars{"new_$mdvar"} = $new_metadata{category};
1647        } elsif ($mdvar eq "username" or $mdvar eq "comment"
1648                  or $mdvar eq "edit_type" ) {
1649            $tt_vars{$mdvar} = $new_metadata{$mdvar};
1650        } else {
1651            $tt_vars{$mdvar} = $old_metadata{$mdvar};
1652            $tt_vars{"new_$mdvar"} = $new_metadata{$mdvar};
1653        }
1654    }
1655
1656    $tt_vars{coord_field_1} = $old_metadata{coord_field_1};
1657    $tt_vars{coord_field_2} = $old_metadata{coord_field_2};
1658    $tt_vars{coord_field_1_value} = $old_metadata{coord_field_1_value};
1659    $tt_vars{coord_field_2_value} = $old_metadata{coord_field_2_value};
1660    $tt_vars{"new_coord_field_1_value"}
1661                                = $new_metadata{$old_metadata{coord_field_1}};
1662    $tt_vars{"new_coord_field_2_value"}
1663                                = $new_metadata{$old_metadata{coord_field_2}};
1664
1665    $tt_vars{conflict} = 1;
1666    return %tt_vars if $args{return_tt_vars};
1667    my $output = $self->process_template(
1668                                          id       => $node,
1669                                          template => "edit_form.tt",
1670                                          tt_vars  => \%tt_vars,
1671                                        );
1672    return $output if $args{return_output};
1673    print $output;
1674}
1675
1676=item B<_autoCreateCategoryLocale>
1677
1678  $guide->_autoCreateCategoryLocale(
1679                         id       => "FAQ",
1680                         metadata => \%metadata,
1681                     );
1682
1683When a new node is added, or a previously un-moderated node is moderated,
1684identifies if any of its Categories or Locales are missing, and creates them.
1685
1686Guide admins can control the text that gets put into the content field of the
1687autocreated node by putting it in custom_autocreate_content.tt in their custom
1688templates directory.  The following TT variables will be available to the
1689template:
1690
1691=over
1692
1693=item * index_type (e.g. C<Category>)
1694
1695=item * index_value (e.g. C<Vegan-friendly>)
1696
1697=item * node_name (e.g. C<Category Vegan-Friendly>)
1698
1699=back
1700
1701(Note capitalisation - index_value is what they typed in to the form, and
1702node_name is the fully free-upper-ed name of the autocreated node.)
1703
1704For nodes not requiring moderation, should be called on writing the node
1705For nodes requiring moderation, should only be called on moderation
1706
1707=cut
1708
1709sub _autoCreateCategoryLocale {
1710    my ($self, %args) = @_;
1711
1712    my $wiki = $self->wiki;
1713    my $id = $args{'id'};
1714    my %metadata = %{$args{'metadata'}};
1715
1716    # Check to make sure all the indexable nodes are created
1717    my $config = $self->config;
1718    my $template_path = $config->template_path;
1719    my $custom_template_path = $config->custom_template_path || "";
1720    my $tt = Template->new( { INCLUDE_PATH =>
1721                                  "$custom_template_path:$template_path" } );
1722
1723    foreach my $type (qw(Category Locale)) {
1724        my $lctype = lc($type);
1725        foreach my $index (@{$metadata{$lctype}}) {
1726            $index =~ s/(.*)/\u$1/;
1727            my $node = $type . " " . $index;
1728            # Uppercase the node name before checking for existence
1729            $node = $wiki->formatter->_do_freeupper( $node );
1730            unless ( $wiki->node_exists($node) ) {
1731                my $category = $type eq "Category" ? "Category" : "Locales";
1732                # Try to get the autocreated content from a custom template;
1733                # if we fail, use some default text.
1734                my $blurb;
1735                my %tt_vars = (
1736                                index_type  => $type,
1737                                index_value => $index,
1738                                node_name   => $node,
1739                              );
1740                my $ok = $tt->process( "custom_autocreate_content.tt",
1741                                       \%tt_vars, \$blurb );
1742                if ( !$ok ) {
1743                    $blurb = "\@INDEX_LINK [[$node]]";
1744                }
1745                $wiki->write_node(
1746                                     $node,
1747                                     $blurb,
1748                                     undef,
1749                                     {
1750                                         username => "Auto Create",
1751                                         comment  => "Auto created $lctype stub page",
1752                                         category => $category
1753                                     }
1754                );
1755            }
1756        }
1757    }
1758}
1759
1760
1761=item B<delete_node>
1762
1763  $guide->delete_node(
1764                         id       => "FAQ",
1765                         version  => 15,
1766                         password => "beer",
1767                     );
1768
1769C<version> is optional - if it isn't supplied then all versions of the
1770node will be deleted; in other words the node will be entirely
1771removed.
1772
1773If C<password> is not supplied then a form for entering the password
1774will be displayed.
1775
1776As with other methods, parameters C<return_tt_vars> and
1777C<return_output> can be used to return these things instead of
1778printing the output to STDOUT.
1779
1780=cut
1781
1782sub delete_node {
1783    my ($self, %args) = @_;
1784    my $node = $args{id} or croak "No node ID supplied for deletion";
1785    my $return_tt_vars = $args{return_tt_vars} || 0;
1786    my $return_output = $args{return_output} || 0;
1787
1788    my %tt_vars = (
1789                      not_editable  => 1,
1790                      not_deletable => 1,
1791                      deter_robots  => 1,
1792                  );
1793    $tt_vars{delete_version} = $args{version} || "";
1794
1795    my $password = $args{password};
1796
1797    if ($password) {
1798        if ($password ne $self->config->admin_pass) {
1799            return %tt_vars if $return_tt_vars;
1800            my $output = $self->process_template(
1801                                                    id       => $node,
1802                                                    template => "delete_password_wrong.tt",
1803                                                    tt_vars  => \%tt_vars,
1804                                                );
1805            return $output if $return_output;
1806            print $output;
1807        } else {
1808            $self->wiki->delete_node(
1809                                        name    => $node,
1810                                        version => $args{version},
1811                                    );
1812            # Check whether any versions of this node remain.
1813            my %check = $self->wiki->retrieve_node( name => $node );
1814            $tt_vars{other_versions_remain} = 1 if $check{version};
1815            return %tt_vars if $return_tt_vars;
1816            my $output = $self->process_template(
1817                                                    id       => $node,
1818                                                    template => "delete_done.tt",
1819                                                    tt_vars  => \%tt_vars,
1820                                                );
1821            return $output if $return_output;
1822            print $output;
1823        }
1824    } else {
1825        return %tt_vars if $return_tt_vars;
1826        my $output = $self->process_template(
1827                                                id       => $node,
1828                                                template => "delete_confirm.tt",
1829                                                tt_vars  => \%tt_vars,
1830                                            );
1831        return $output if $return_output;
1832        print $output;
1833    }
1834}
1835
1836=item B<set_node_moderation>
1837
1838  $guide->set_node_moderation(
1839                         id       => "FAQ",
1840                         password => "beer",
1841                         moderation_flag => 1,
1842                     );
1843
1844Sets the moderation needed flag on a node, either on or off.
1845
1846If C<password> is not supplied then a form for entering the password
1847will be displayed.
1848
1849=cut
1850
1851sub set_node_moderation {
1852    my ($self, %args) = @_;
1853    my $node = $args{id} or croak "No node ID supplied for node moderation";
1854    my $return_tt_vars = $args{return_tt_vars} || 0;
1855    my $return_output = $args{return_output} || 0;
1856
1857    # Get the moderation flag into something sane
1858    if($args{moderation_flag} eq "1" || $args{moderation_flag} eq "yes" ||
1859       $args{moderation_flag} eq "on" || $args{moderation_flag} eq "true") {
1860        $args{moderation_flag} = 1;
1861    } else {
1862        $args{moderation_flag} = 0;
1863    }
1864
1865    # Set up the TT variables
1866    my %tt_vars = (
1867                      not_editable  => 1,
1868                      not_deletable => 1,
1869                      deter_robots  => 1,
1870                      moderation_action => 'set_moderation',
1871                      moderation_flag   => $args{moderation_flag},
1872                      moderation_url_args => 'action=set_moderation;moderation_flag='.$args{moderation_flag},
1873                  );
1874
1875    my $password = $args{password};
1876
1877    if ($password) {
1878        if ($password ne $self->config->admin_pass) {
1879            return %tt_vars if $return_tt_vars;
1880            my $output = $self->process_template(
1881                                                    id       => $node,
1882                                                    template => "moderate_password_wrong.tt",
1883                                                    tt_vars  => \%tt_vars,
1884                                                );
1885            return $output if $return_output;
1886            print $output;
1887        } else {
1888            my $worked = $self->wiki->set_node_moderation(
1889                                        name    => $node,
1890                                        required => $args{moderation_flag},
1891                         );
1892            my $moderation_flag = "changed";
1893            unless($worked) {
1894                $moderation_flag = "unknown_node";
1895                warn("Tried to set moderation status on node '$node', which doesn't exist");
1896            }
1897
1898            # Send back to the admin interface
1899            my $script_url = $self->config->script_url;
1900            my $script_name = $self->config->script_name;
1901            my $q = CGI->new;
1902            my $output = $q->redirect( $script_url.$script_name."?action=admin;moderation=".$moderation_flag );
1903            return $output if $return_output;
1904            print $output;
1905        }
1906    } else {
1907        return %tt_vars if $return_tt_vars;
1908        my $output = $self->process_template(
1909                                                id       => $node,
1910                                                template => "moderate_confirm.tt",
1911                                                tt_vars  => \%tt_vars,
1912                                            );
1913        return $output if $return_output;
1914        print $output;
1915    }
1916}
1917
1918=item B<moderate_node>
1919
1920  $guide->moderate_node(
1921                         id       => "FAQ",
1922                         version  => 12,
1923                         password => "beer",
1924                     );
1925
1926Marks a version of a node as moderated. Will also auto-create and Locales
1927and Categories for the newly moderated version.
1928
1929If C<password> is not supplied then a form for entering the password
1930will be displayed.
1931
1932=cut
1933
1934sub moderate_node {
1935    my ($self, %args) = @_;
1936    my $node = $args{id} or croak "No node ID supplied for node moderation";
1937    my $version = $args{version} or croak "No node version supplied for node moderation";
1938    my $return_tt_vars = $args{return_tt_vars} || 0;
1939    my $return_output = $args{return_output} || 0;
1940
1941    # Set up the TT variables
1942    my %tt_vars = (
1943                      not_editable  => 1,
1944                      not_deletable => 1,
1945                      deter_robots  => 1,
1946                      version       => $version,
1947                      moderation_action => 'moderate',
1948                      moderation_url_args => 'action=moderate;version='.$version
1949                  );
1950
1951    my $password = $args{password};
1952    unless($self->config->moderation_requires_password) {
1953        $password = $self->config->admin_pass;
1954    }
1955
1956    if ($password) {
1957        if ($password ne $self->config->admin_pass) {
1958            return %tt_vars if $return_tt_vars;
1959            my $output = $self->process_template(
1960                                                    id       => $node,
1961                                                    template => "moderate_password_wrong.tt",
1962                                                    tt_vars  => \%tt_vars,
1963                                                );
1964            return $output if $return_output;
1965            print $output;
1966        } else {
1967            $self->wiki->moderate_node(
1968                                        name    => $node,
1969                                        version => $version
1970                                    );
1971
1972            # Create any categories or locales for it
1973            my %details = $self->wiki->retrieve_node(
1974                                        name    => $node,
1975                                        version => $version
1976                                    );
1977            $self->_autoCreateCategoryLocale(
1978                                          id       => $node,
1979                                          metadata => $details{'metadata'}
1980            );
1981
1982            # Send back to the admin interface
1983            my $script_url = $self->config->script_url;
1984            my $script_name = $self->config->script_name;
1985            my $q = CGI->new;
1986            my $output = $q->redirect( $script_url.$script_name."?action=admin;moderation=moderated" );
1987            return $output if $return_output;
1988            print $output;
1989        }
1990    } else {
1991        return %tt_vars if $return_tt_vars;
1992        my $output = $self->process_template(
1993                                                id       => $node,
1994                                                template => "moderate_confirm.tt",
1995                                                tt_vars  => \%tt_vars,
1996                                            );
1997        return $output if $return_output;
1998        print $output;
1999    }
2000}
2001
2002=item B<show_missing_metadata>
2003
2004Search for nodes which don't have a certain kind of metadata. Optionally
2005also excludes Locales and Categories
2006
2007=cut
2008
2009sub show_missing_metadata {
2010    my ($self, %args) = @_;
2011    my $return_tt_vars = $args{return_tt_vars} || 0;
2012    my $return_output = $args{return_output} || 0;
2013
2014    my $wiki = $self->wiki;
2015    my $formatter = $self->wiki->formatter;
2016    my $script_name = $self->config->script_name;
2017
2018    my ($metadata_type, $metadata_value, $exclude_locales, $exclude_categories)
2019        = @args{ qw( metadata_type metadata_value exclude_locales exclude_categories ) };
2020
2021    my @nodes;
2022    my $done_search = 0;
2023
2024    # Only search if they supplied at least a metadata type
2025    if($metadata_type) {
2026        $done_search = 1;
2027        @nodes = $wiki->list_nodes_by_missing_metadata(
2028                            metadata_type => $metadata_type,
2029                            metadata_value => $metadata_value,
2030                            ignore_case    => 1,
2031        );
2032
2033        # Do we need to filter some nodes out?
2034        if($exclude_locales || $exclude_categories) {
2035            my @all_nodes = @nodes;
2036            @nodes = ();
2037
2038            foreach my $node (@all_nodes) {
2039                if($exclude_locales && $node =~ /^Locale /) { next; }
2040                if($exclude_categories && $node =~ /^Category /) { next; }
2041                push @nodes, $node;
2042            }
2043        }
2044    }
2045
2046    # Build nice edit etc links for our nodes
2047    my @tt_nodes;
2048    for my $node (@nodes) {
2049        my %n;
2050
2051        # Make the URLs
2052        my $node_param = uri_escape( $formatter->node_name_to_node_param( $node ) );
2053
2054        # Save into the hash
2055        $n{'name'} = $node;
2056        $n{'view_url'} = $script_name . "?id=" . $node_param;
2057        $n{'edit_url'} = $script_name . "?id=" . $node_param . ";action=edit";
2058        push @tt_nodes, \%n;
2059    }
2060
2061    # Set up our TT variables, including the search parameters
2062    my %tt_vars = (
2063                      not_editable  => 1,
2064                      not_deletable => 1,
2065                      deter_robots  => 1,
2066
2067                      nodes => \@tt_nodes,
2068                      done_search    => $done_search,
2069                      metadata_type  => $metadata_type,
2070                      metadata_value => $metadata_value,
2071                      exclude_locales => $exclude_locales,
2072                      exclude_categories => $exclude_categories,
2073
2074                      script_name => $script_name
2075                  );
2076    return %tt_vars if $return_tt_vars;
2077
2078    # Render to the page
2079    my $output = $self->process_template(
2080                                           id       => "",
2081                                           template => "missing_metadata.tt",
2082                                           tt_vars  => \%tt_vars,
2083                                        );
2084    return $output if $return_output;
2085    print $output;
2086}
2087
2088=item B<revert_user_interface>
2089
2090If C<password> is not supplied then a form for entering the password
2091will be displayed, along with a list of all the edits the user made.
2092
2093If the password is given, will delete all of these versions.
2094=cut
2095sub revert_user_interface {
2096    my ($self, %args) = @_;
2097
2098    my $password = $args{password} || '';
2099    my $return_tt_vars = $args{return_tt_vars} || 0;
2100    my $return_output = $args{return_output} || 0;
2101
2102    my $wiki = $self->wiki;
2103    my $formatter = $self->wiki->formatter;
2104    my $script_name = $self->config->script_name;
2105
2106    my ($type,$value);
2107    if($args{'username'}) {
2108        ($type,$value) = ('username', $args{'username'});
2109    }
2110    if($args{'host'}) {
2111        ($type,$value) = ('host', $args{'host'});
2112    }
2113    unless($type && $value) {
2114        croak("One of username or host must be given");
2115    }
2116
2117    # Grab everything they've touched, ever
2118    my @user_edits = $self->wiki->list_recent_changes(
2119                            since => 1,
2120                            metadata_was => { $type => $value },
2121    );
2122
2123    if ($password) {
2124        if ($password ne $self->config->admin_pass) {
2125            croak("Bad password supplied");
2126        } else {
2127            # Delete all these versions
2128            foreach my $edit (@user_edits) {
2129                $self->wiki->delete_node(
2130                                name => $edit->{name},
2131                                version => $edit->{version},
2132                );
2133            }
2134
2135            # Grab new list
2136            @user_edits = $self->wiki->list_recent_changes(
2137                            since => 1,
2138                            metadata_was => { $type => $value },
2139            );
2140        }
2141    } else {
2142        # Don't do anything
2143    }
2144
2145    # Set up our TT variables, including the search parameters
2146    my %tt_vars = (
2147                      not_editable  => 1,
2148                      not_deletable => 1,
2149                      deter_robots  => 1,
2150
2151                      edits          => \@user_edits,
2152                      username       => $args{username},
2153                      host           => $args{host},
2154                      by_type        => $type,
2155                      by             => $value,
2156
2157                      script_name => $script_name
2158                  );
2159    return %tt_vars if $return_tt_vars;
2160
2161    # Render to the page
2162    my $output = $self->process_template(
2163                                           id       => "",
2164                                           template => "admin_revert_user.tt",
2165                                           tt_vars  => \%tt_vars,
2166                                        );
2167    return $output if $return_output;
2168    print $output;
2169}
2170
2171=item B<display_admin_interface>
2172
2173Fetch everything we need to display the admin interface, and passes it off
2174 to the template
2175
2176=cut
2177
2178sub display_admin_interface {
2179    my ($self, %args) = @_;
2180    my $return_tt_vars = $args{return_tt_vars} || 0;
2181    my $return_output = $args{return_output} || 0;
2182
2183    my $wiki = $self->wiki;
2184    my $formatter = $self->wiki->formatter;
2185    my $script_name = $self->config->script_name;
2186
2187    # Grab all the recent nodes
2188    my @all_nodes = $wiki->list_recent_changes(last_n_changes => 100);
2189
2190    # Split into nodes, Locales and Categories
2191    my @nodes;
2192    my @categories;
2193    my @locales;
2194    for my $node (@all_nodes) {
2195        # Add moderation status
2196        $node->{'moderate'} = $wiki->node_required_moderation($node->{'name'});
2197
2198        # Make the URLs
2199        my $node_param = uri_escape( $formatter->node_name_to_node_param( $node->{'name'} ) );
2200        $node->{'view_url'} = $script_name . "?id=" . $node_param;
2201        $node->{'versions_url'} = $script_name .
2202                        "?action=list_all_versions;id=" . $node_param;
2203        $node->{'moderation_url'} = $script_name .
2204                        "?action=set_moderation;id=" . $node_param;
2205        $node->{'revert_user_url'} = $script_name . "?action=revert_user" .
2206                        ";username=".$node->{metadata}->{username}->[0];
2207
2208        # Filter
2209        if($node->{'name'} =~ /^Category /) {
2210            $node->{'page_name'} = $node->{'name'};
2211            $node->{'name'} =~ s/^Category //;
2212            push @categories, $node;
2213        } elsif($node->{'name'} =~ /^Locale /) {
2214            $node->{'page_name'} = $node->{'name'};
2215            $node->{'name'} =~ s/^Locale //;
2216            push @locales, $node;
2217        } else {
2218            push @nodes, $node;
2219        }
2220    }
2221
2222    # Handle completed notice for actions
2223    my $completed_action = "";
2224    if($args{moderation_completed}) {
2225        if($args{moderation_completed} eq "moderation") {
2226            $completed_action = "Version moderated";
2227        }
2228        if($args{moderation_completed} eq "changed") {
2229            $completed_action = "Node moderation flag changed";
2230        }
2231        if($args{moderation_completed} eq "unknown_node") {
2232            $completed_action = "Node moderation flag not changed, node not known";
2233        }
2234    }
2235
2236    # Render in a template
2237    my %tt_vars = (
2238                      not_editable  => 1,
2239                      not_deletable => 1,
2240                      deter_robots  => 1,
2241                      nodes      => \@nodes,
2242                      categories => \@categories,
2243                      locales    => \@locales,
2244                      completed_action => $completed_action
2245                  );
2246    return %tt_vars if $return_tt_vars;
2247    my $output = $self->process_template(
2248                                           id       => "",
2249                                           template => "admin_home.tt",
2250                                           tt_vars  => \%tt_vars,
2251                                        );
2252    return $output if $return_output;
2253    print $output;
2254}
2255
2256sub process_template {
2257    my ($self, %args) = @_;
2258    my %output_conf = (
2259                          wiki     => $self->wiki,
2260                          config   => $self->config,
2261                          node     => $args{id},
2262                          template => $args{template},
2263                          vars     => $args{tt_vars},
2264                          cookies  => $args{cookies},
2265                      );
2266    if ( $args{content_type} ) {
2267        $output_conf{content_type} = $args{content_type};
2268    }
2269    return OpenGuides::Template->output( %output_conf );
2270}
2271
2272sub redirect_to_node {
2273    my ($self, $node, $redirected_from) = @_;
2274   
2275    my $script_url = $self->config->script_url;
2276    my $script_name = $self->config->script_name;
2277    my $formatter = $self->wiki->formatter;
2278
2279    my $id = $formatter->node_name_to_node_param( $node );
2280    my $oldid;
2281    $oldid = $formatter->node_name_to_node_param( $redirected_from ) if $redirected_from;
2282
2283    my $redir_param = "$script_url$script_name?";
2284    $redir_param .= 'id=' if $oldid;
2285    $redir_param .= $id;
2286    $redir_param .= ";oldid=$oldid" if $oldid;
2287   
2288    my $q = CGI->new;
2289    return $q->redirect( $redir_param );
2290}
2291
2292sub get_cookie {
2293    my $self = shift;
2294    my $config = $self->config;
2295    my $pref_name = shift or return "";
2296    my %cookie_data = OpenGuides::CGI->get_prefs_from_cookie(config=>$config);
2297    return $cookie_data{$pref_name};
2298}
2299
2300=back
2301
2302=head1 BUGS AND CAVEATS
2303
2304UTF8 data are currently not handled correctly throughout.
2305
2306Other bugs are documented at
2307L<http://dev.openguides.org/>
2308
2309=head1 SEE ALSO
2310
2311=over 4
2312
2313=item * The Open Guide to London, at L<http://london.openguides.org/>, is the first and biggest OpenGuides site.
2314
2315=item * A list of live OpenGuides installs is available at L<http://openguides.org/>.
2316
2317=item * L<Wiki::Toolkit>, the Wiki toolkit which does the heavy lifting for OpenGuides.
2318
2319=back
2320
2321=head1 FEEDBACK
2322
2323If you have a question, a bug report, or a patch, or you're interested
2324in joining the development team, please contact openguides-dev@lists.openguides.org
2325(moderated mailing list, will reach all current developers but you'll have
2326to wait for your post to be approved) or file a bug report at
2327L<http://dev.openguides.org/>
2328
2329=head1 AUTHOR
2330
2331The OpenGuides Project (openguides-dev@lists.openguides.org)
2332
2333=head1 COPYRIGHT
2334
2335     Copyright (C) 2003-2009 The OpenGuides Project.  All Rights Reserved.
2336
2337The OpenGuides distribution is free software; you can redistribute it
2338and/or modify it under the same terms as Perl itself.
2339
2340=head1 CREDITS
2341
2342Programming by Dominic Hargreaves, Earle Martin, Kake Pugh, and Ivor
2343Williams.  Testing and bug reporting by Billy Abbott, Jody Belka,
2344Kerry Bosworth, Simon Cozens, Cal Henderson, Steve Jolly, and Bob
2345Walker (among others).  Much of the Module::Build stuff copied from
2346the Siesta project L<http://siesta.unixbeard.net/>
2347
2348=cut
2349
23501;
Note: See TracBrowser for help on using the repository browser.