source: trunk/lib/OpenGuides.pm @ 1317

Last change on this file since 1317 was 1317, checked in by kake, 10 years ago

Fixed bug where missing metadata search was picking up redirect pages.

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