source: trunk/lib/OpenGuides.pm @ 522

Last change on this file since 522 was 522, checked in by kake, 17 years ago

Label locale/category RSS feeds as such rather than 'feed for this node'

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 27.6 KB
Line 
1package OpenGuides;
2use strict;
3
4use Carp "croak";
5use CGI;
6use CGI::Wiki::Plugin::Diff;
7use CGI::Wiki::Plugin::GeoCache;
8use CGI::Wiki::Plugin::Locator::UK;
9use OpenGuides::CGI;
10use OpenGuides::Template;
11use OpenGuides::Utils;
12use Time::Piece;
13use URI::Escape;
14
15use vars qw( $VERSION );
16
17$VERSION = '0.42';
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 $guide = OpenGuides->new( config => $config );
39
40=cut
41
42sub new {
43    my ($class, %args) = @_;
44    my $self = {};
45    bless $self, $class;
46    my $wiki = OpenGuides::Utils->make_wiki_object( config => $args{config} );
47    $self->{wiki} = $wiki;
48    $self->{config} = $args{config};
49    my $locator = CGI::Wiki::Plugin::Locator::UK->new;
50    $wiki->register_plugin( plugin => $locator );
51    $self->{locator} = $locator;
52    my $differ = CGI::Wiki::Plugin::Diff->new;
53    $wiki->register_plugin( plugin => $differ );
54    $self->{differ} = $differ;
55    return $self;
56}
57
58=item B<wiki>
59
60An accessor, returns the underlying L<CGI::Wiki> object.
61
62=cut
63
64sub wiki {
65    my $self = shift;
66    return $self->{wiki};
67}
68
69=item B<config>
70
71An accessor, returns the underlying L<Config::Tiny> object.
72
73=cut
74
75sub config {
76    my $self = shift;
77    return $self->{config};
78}
79
80=item B<locator>
81
82An accessor, returns the underlying L<CGI::Wiki::Plugin::Locator::UK> object.
83
84=cut
85
86sub locator {
87    my $self = shift;
88    return $self->{locator};
89}
90
91=item B<differ>
92
93An accessor, returns the underlying L<CGI::Wiki::Plugin::Diff> object.
94
95=cut
96
97sub differ {
98    my $self = shift;
99    return $self->{differ};
100}
101
102=item B<display_node>
103
104  # Print node to STDOUT.
105  $guide->display_node(
106                        id      => "Calthorpe Arms",
107                        version => 2,
108                      );
109
110  # Or return output as a string (useful for writing tests).
111  $guide->display_node(
112                        id            => "Calthorpe Arms",
113                        return_output => 1,
114                      );
115
116  # Or return the hash of variables that will be passed to the template
117  # (not including those set additionally by OpenGuides::Template).
118  $guide->display_node(
119                        id             => "Calthorpe Arms",
120                        return_tt_vars => 1,
121                      );
122
123If C<version> is omitted then the latest version will be displayed.
124
125=cut
126
127sub display_node {
128    my ($self, %args) = @_;
129    my $return_output = $args{return_output} || 0;
130    my $version = $args{version};
131    my $id = $args{id} || $self->config->{_}->{home_name};
132    my $wiki = $self->wiki;
133    my $config = $self->config;
134
135    my %tt_vars;
136
137    if ( $id =~ /^(Category|Locale) (.*)$/ ) {
138        my $type = $1;
139        $tt_vars{is_indexable_node} = 1;
140        $tt_vars{index_type} = lc($type);
141        $tt_vars{index_value} = $2;
142        $tt_vars{"rss_".lc($type)."_url"} =
143                           $config->{_}{script_name} . "?action=rss;"
144                           . lc($type) . "=" . lc(CGI->escape($2));
145    }
146
147    my %current_data = $wiki->retrieve_node( $id );
148    my $current_version = $current_data{version};
149    undef $version if ($version && $version == $current_version);
150    my %criteria = ( name => $id );
151    $criteria{version} = $version if $version;#retrieve_node default is current
152
153    my %node_data = $wiki->retrieve_node( %criteria );
154    my $raw = $node_data{content};
155    if ( $raw =~ /^#REDIRECT\s+(.+?)\s*$/ ) {
156        my $redirect = $1;
157        # Strip off enclosing [[ ]] in case this is an extended link.
158        $redirect =~ s/^\[\[//;
159        $redirect =~ s/\]\]\s*$//;
160        # See if this is a valid node, if not then just show the page as-is.
161        if ( $wiki->node_exists($redirect) ) {
162            my $output = $self->redirect_to_node($redirect);
163            return $output if $return_output;
164            print $output;
165            exit 0;
166        }
167    }
168    my $content    = $wiki->format($raw);
169    my $modified   = $node_data{last_modified};
170    my %metadata   = %{$node_data{metadata}};
171
172    my %metadata_vars = OpenGuides::Template->extract_metadata_vars(
173                            wiki     => $wiki,
174                            config   => $config,
175                            metadata => $node_data{metadata} );
176
177    %tt_vars = (
178                 %tt_vars,
179                 %metadata_vars,
180                 content       => $content,
181                 geocache_link => $self->make_geocache_link($id),
182                 last_modified => $modified,
183                 version       => $node_data{version},
184                 node          => $id,
185                 language      => $config->{_}->{default_language},
186               );
187
188
189    # We've undef'ed $version above if this is the current version.
190    $tt_vars{current} = 1 unless $version;
191
192    if ($id eq "RecentChanges") {
193        my $minor_edits = $self->get_cookie( "show_minor_edits_in_rc" );
194        my %recent_changes;
195        my $q = CGI->new;
196        my $since = $q->param("since");
197        if ( $since ) {
198            $tt_vars{since} = $since;
199            my $t = localtime($since); # overloaded by Time::Piece
200            $tt_vars{since_string} = $t->strftime;
201            my %criteria = ( since => $since );
202            $criteria{metadata_was} = { edit_type => "Normal edit" }
203              unless $minor_edits;
204            my @rc = $self->{wiki}->list_recent_changes( %criteria );
205
206            @rc = map {
207                {
208                  name        => CGI->escapeHTML($_->{name}),
209                  last_modified => CGI->escapeHTML($_->{last_modified}),
210                  version     => CGI->escapeHTML($_->{version}),
211                  comment     => CGI->escapeHTML($_->{metadata}{comment}[0]),
212                  username    => CGI->escapeHTML($_->{metadata}{username}[0]),
213                  host        => CGI->escapeHTML($_->{metadata}{host}[0]),
214                  username_param => CGI->escape($_->{metadata}{username}[0]),
215                  edit_type   => CGI->escapeHTML($_->{metadata}{edit_type}[0]),
216                  url         => "$config->{_}->{script_name}?"
217          . CGI->escape($wiki->formatter->node_name_to_node_param($_->{name})),
218                }
219                       } @rc;
220            if ( scalar @rc ) {
221                $recent_changes{since} = \@rc;
222            }
223        } else {
224            for my $days ( [0, 1], [1, 7], [7, 14], [14, 30] ) {
225                my %criteria = ( between_days => $days );
226                $criteria{metadata_was} = { edit_type => "Normal edit" }
227                  unless $minor_edits;
228                my @rc = $self->{wiki}->list_recent_changes( %criteria );
229
230                @rc = map {
231                {
232                  name        => CGI->escapeHTML($_->{name}),
233                  last_modified => CGI->escapeHTML($_->{last_modified}),
234                  version     => CGI->escapeHTML($_->{version}),
235                  comment     => CGI->escapeHTML($_->{metadata}{comment}[0]),
236                  username    => CGI->escapeHTML($_->{metadata}{username}[0]),
237                  host        => CGI->escapeHTML($_->{metadata}{host}[0]),
238                  username_param => CGI->escape($_->{metadata}{username}[0]),
239                  edit_type   => CGI->escapeHTML($_->{metadata}{edit_type}[0]),
240                  url         => "$config->{_}->{script_name}?"
241          . CGI->escape($wiki->formatter->node_name_to_node_param($_->{name})),
242                }
243                           } @rc;
244                if ( scalar @rc ) {
245                    $recent_changes{$days->[1]} = \@rc;
246                }
247            }
248        }
249        $tt_vars{recent_changes} = \%recent_changes;
250        my %processing_args = (
251                                id            => $id,
252                                template      => "recent_changes.tt",
253                                tt_vars       => \%tt_vars,
254                               );
255        if ( !$since && $self->get_cookie("track_recent_changes_views") ) {
256            my $cookie =
257               OpenGuides::CGI->make_recent_changes_cookie(config => $config );
258            $processing_args{cookies} = $cookie;
259            $tt_vars{last_viewed} = OpenGuides::CGI->get_last_recent_changes_visit_from_cookie( config => $config );
260        }
261        return %tt_vars if $args{return_tt_vars};
262        my $output = $self->process_template( %processing_args );
263        return $output if $return_output;
264        print $output;
265    } elsif ( $id eq $self->config->{_}->{home_name} ) {
266        my @recent = $wiki->list_recent_changes(
267            last_n_changes => 10,
268            metadata_was   => { edit_type => "Normal edit" },
269        );
270        @recent = map { {name          => CGI->escapeHTML($_->{name}),
271                         last_modified => CGI->escapeHTML($_->{last_modified}),
272                         comment       => CGI->escapeHTML($_->{metadata}{comment}[0]),
273                         username      => CGI->escapeHTML($_->{metadata}{username}[0]),
274                         url           => "$config->{_}->{script_name}?"
275          . CGI->escape($wiki->formatter->node_name_to_node_param($_->{name})) }
276                       } @recent;
277        $tt_vars{recent_changes} = \@recent;
278        return %tt_vars if $args{return_tt_vars};
279        my $output = $self->process_template(
280                                              id            => $id,
281                                              template      => "home_node.tt",
282                                              tt_vars       => \%tt_vars,
283                                            );
284        return $output if $return_output;
285        print $output;
286    } else {
287        return %tt_vars if $args{return_tt_vars};
288        my $output = $self->process_template(
289                                              id            => $id,
290                                              template      => "node.tt",
291                                              tt_vars       => \%tt_vars,
292                                            );
293        return $output if $return_output;
294        print $output;
295    }
296}
297
298=item B<display_diffs>
299
300  $guide->display_diffs(
301                         id            => "Home Page",
302                         version       => 6,
303                         other_version => 5,
304                       );
305
306  # Or return output as a string (useful for writing tests).
307  my $output = $guide->display_diffs(
308                                      id            => "Home Page",
309                                      version       => 6,
310                                      other_version => 5,
311                                      return_output => 1,
312                                    );
313
314  # Or return the hash of variables that will be passed to the template
315  # (not including those set additionally by OpenGuides::Template).
316  my %vars = $guide->display_diffs(
317                                    id             => "Home Page",
318                                    version        => 6,
319                                    other_version  => 5,
320                                    return_tt_vars => 1,
321                                  );
322
323=cut
324
325sub display_diffs {
326    my ($self, %args) = @_;
327    my %diff_vars = $self->differ->differences(
328                                        node          => $args{id},
329                                        left_version  => $args{version},
330                                        right_version => $args{other_version},
331                                              );
332    $diff_vars{not_deletable} = 1;
333    $diff_vars{not_editable} = 1;
334    $diff_vars{deter_robots} = 1;
335    return %diff_vars if $args{return_tt_vars};
336    my $output = $self->process_template(
337                                          id       => $args{id},
338                                          template => "differences.tt",
339                                          tt_vars  => \%diff_vars
340                                        );
341    return $output if $args{return_output};
342    print $output;
343}
344
345=item B<find_within_distance>
346
347  $guide->find_within_distance(
348                                id => $node,
349                                metres => $q->param("distance_in_metres")
350                              );
351
352=cut
353
354sub find_within_distance {
355    my ($self, %args) = @_;
356    my $node = $args{id};
357    my $metres = $args{metres};
358    my $formatter = $self->wiki->formatter;
359    my @finds = $self->locator->find_within_distance(
360                                                      node   => $node,
361                                                      metres => $metres,
362                                                    );
363    my @nodes;
364    foreach my $find ( @finds ) {
365        my $distance = $self->locator->distance(
366                                                 from_node => $node,
367                                                 to_node   => $find,
368                                                 unit      => "metres"
369                                               );
370        push @nodes, {
371                       name     => $find,
372                       param    => $formatter->node_name_to_node_param($find),
373                       distance => $distance,
374                     };
375    }
376    @nodes = sort { $a->{distance} <=> $b->{distance} } @nodes;
377
378    my %tt_vars = (
379                    nodes        => \@nodes,
380                    origin       => $node,
381                    origin_param => $formatter->node_name_to_node_param($node),
382                    limit        => "$metres metres",
383                  );
384
385    print $self->process_template(
386                                   id       => "index", # KLUDGE
387                                   template => "site_index.tt",
388                                   tt_vars  => \%tt_vars,
389                                 );
390}
391
392=item B<show_index>
393
394  $guide->show_index(
395                      type   => "category",
396                      value  => "pubs",
397                    );
398
399  # RDF version.
400  $guide->show_index(
401                      type   => "locale",
402                      value  => "Holborn",
403                      format => "rdf",
404                    );
405
406  # Or return output as a string (useful for writing tests).
407  $guide->show_index(
408                      type          => "category",
409                      value         => "pubs",
410                      return_output => 1,
411                    );
412
413=cut
414
415sub show_index {
416    my ($self, %args) = @_;
417    my $wiki = $self->wiki;
418    my $formatter = $wiki->formatter;
419    my %tt_vars;
420    my @selnodes;
421
422    if ( $args{type} and $args{value} ) {
423        if ( $args{type} eq "fuzzy_title_match" ) {
424            my %finds = $wiki->fuzzy_title_match( $args{value} );
425            @selnodes = sort { $finds{$a} <=> $finds{$b} } keys %finds;
426            $tt_vars{criterion} = {
427                type  => $args{type},  # for RDF version
428                value => $args{value}, # for RDF version
429                name  => CGI->escapeHTML("Fuzzy Title Match on '$args{value}'")
430            };
431        } else {
432            @selnodes = $wiki->list_nodes_by_metadata(
433                metadata_type  => $args{type},
434                metadata_value => $args{value},
435                ignore_case    => 1,
436            );
437            my $name = ucfirst($args{type}) . " $args{value}" ;
438            my $url = $self->config->{_}->{script_name}
439                      . "?"
440                      . ucfirst( $args{type} )
441                      . "_"
442                      . uri_escape(
443                              $formatter->node_name_to_node_param($args{value})
444                                  );
445            $tt_vars{criterion} = {
446                type  => $args{type},
447                value => $args{value}, # for RDF version
448                name  => CGI->escapeHTML( $name ),
449                url   => $url,
450            };
451        }
452    } else {
453        @selnodes = $wiki->list_all_nodes();
454    }
455
456    my @nodes = map { { name      => $_,
457                        node_data => { $wiki->retrieve_node( name => $_ ) },
458                        param     => $formatter->node_name_to_node_param($_) }
459                    } sort @selnodes;
460
461    $tt_vars{nodes} = \@nodes;
462
463    my ($template, %conf);
464
465    if ( $args{format} and $args{format} eq "rdf" ) {
466        $template = "rdf_index.tt";
467        $conf{content_type} = "text/plain";
468    } else {
469        $template = "site_index.tt";
470    }
471
472    %conf = (
473              %conf,
474              node        => "$args{type} index", # KLUDGE
475              template    => $template,
476              tt_vars     => \%tt_vars,
477    );
478
479    my $output = $self->process_template( %conf );
480    return $output if $args{return_output};
481    print $output;
482}
483
484=item B<list_all_versions>
485
486  $guide->list_all_versions ( id => "Home Page" );
487
488  # Or return output as a string (useful for writing tests).
489  $guide->list_all_versions (
490                              id            => "Home Page",
491                              return_output => 1,
492                            );
493
494  # Or return the hash of variables that will be passed to the template
495  # (not including those set additionally by OpenGuides::Template).
496  $guide->list_all_versions (
497                              id             => "Home Page",
498                              return_tt_vars => 1,
499                            );
500
501=cut
502
503sub list_all_versions {
504    my ($self, %args) = @_;
505    my $return_output = $args{return_output} || 0;
506    my $node = $args{id};
507    my %curr_data = $self->wiki->retrieve_node($node);
508    my $curr_version = $curr_data{version};
509    croak "This is the first version" unless $curr_version > 1;
510    my @history;
511    for my $version ( 1 .. $curr_version ) {
512        my %node_data = $self->wiki->retrieve_node( name    => $node,
513                                                    version => $version );
514        # $node_data{version} will be zero if this version was deleted.
515        push @history, {
516            version  => CGI->escapeHTML( $version ),
517            modified => CGI->escapeHTML( $node_data{last_modified} ),
518            username => CGI->escapeHTML( $node_data{metadata}{username}[0] ),
519            comment  => CGI->escapeHTML( $node_data{metadata}{comment}[0] ),
520                       } if $node_data{version};
521    }
522    @history = reverse @history;
523    my %tt_vars = ( node          => $node,
524                    version       => $curr_version,
525                    not_deletable => 1,
526                    not_editable  => 1,
527                    deter_robots  => 1,
528                    history       => \@history );
529    return %tt_vars if $args{return_tt_vars};
530    my $output = $self->process_template(
531                                          id       => $node,
532                                          template => "node_history.tt",
533                                          tt_vars  => \%tt_vars,
534                                        );
535    return $output if $return_output;
536    print $output;
537}
538
539=item B<commit_node>
540
541  $guide->commit_node(
542                       id      => $node,
543                       cgi_obj => $q,
544                     );
545
546As with other methods, parameters C<return_tt_vars> and
547C<return_output> can be used to return these things instead of
548printing the output to STDOUT.
549
550=cut
551
552sub commit_node {
553    my ($self, %args) = @_;
554    my $node = $args{id};
555    my $q = $args{cgi_obj};
556    my $wiki = $self->wiki;
557    my $config = $self->config;
558
559    my $content  = $q->param("content");
560    $content =~ s/\r\n/\n/gs;
561    my $checksum = $q->param("checksum");
562
563    my %metadata = OpenGuides::Template->extract_metadata_vars(
564        wiki    => $wiki,
565        config  => $config,
566        cgi_obj => $q
567    );
568
569    $metadata{opening_hours_text} = $q->param("hours_text") || "";
570
571    # Check to make sure all the indexable nodes are created
572    foreach my $type (qw(Category Locale)) {
573        my $lctype = lc($type);
574        foreach my $index (@{$metadata{$lctype}}) {
575            $index =~ s/(.*)/\u$1/;
576            my $node = $type . " " . $index;
577            # Uppercase the node name before checking for existence
578            $node =~ s/ (\S+)/ \u$1/g;
579            unless ( $wiki->node_exists($node) ) {
580                my $category = $type eq "Category" ? "Category" : "Locales";
581                $wiki->write_node( $node,
582                                   "\@INDEX_LINK [[$node]]",
583                                   undef,
584                                   { username => "Auto Create",
585                                     comment  => "Auto created $lctype stub page",
586                                     category => $category
587                                   }
588                );
589            }
590        }
591    }
592       
593    foreach my $var ( qw( username comment edit_type ) ) {
594        $metadata{$var} = $q->param($var) || "";
595    }
596    $metadata{host} = $ENV{REMOTE_ADDR};
597
598    # CGI::Wiki::Plugin::RSS::ModWiki wants "major_change" to be set.
599    $metadata{major_change} = ( $metadata{edit_type} eq "Normal edit" )
600                            ? 1
601                            : 0;
602
603    my $written = $wiki->write_node($node, $content, $checksum, \%metadata );
604
605    if ($written) {
606        print $self->redirect_to_node($node);
607    } else {
608        my %node_data = $wiki->retrieve_node($node);
609        my %tt_vars = ( checksum       => $node_data{checksum},
610                        new_content    => $content,
611                        stored_content => $node_data{content} );
612        foreach my $mdvar ( keys %metadata ) {
613            if ($mdvar eq "locales") {
614                $tt_vars{"stored_$mdvar"} = $node_data{metadata}{locale};
615                $tt_vars{"new_$mdvar"}    = $metadata{locale};
616            } elsif ($mdvar eq "categories") {
617                $tt_vars{"stored_$mdvar"} = $node_data{metadata}{category};
618                $tt_vars{"new_$mdvar"}    = $metadata{category};
619            } elsif ($mdvar eq "username" or $mdvar eq "comment"
620                      or $mdvar eq "edit_type" ) {
621                $tt_vars{$mdvar} = $metadata{$mdvar};
622            } else {
623                $tt_vars{"stored_$mdvar"} = $node_data{metadata}{$mdvar}[0];
624                $tt_vars{"new_$mdvar"}    = $metadata{$mdvar};
625            }
626        }
627        return %tt_vars if $args{return_tt_vars};
628        my $output = $self->process_template(
629                                              id       => $node,
630                                              template => "edit_conflict.tt",
631                                              tt_vars  => \%tt_vars,
632                                            );
633        return $output if $args{return_output};
634        print $output;
635    }
636}
637
638
639=item B<delete_node>
640
641  $guide->delete_node(
642                       id       => "FAQ",
643                       version  => 15,
644                       password => "beer",
645                     );
646
647C<version> is optional - if it isn't supplied then all versions of the
648node will be deleted; in other words the node will be entirely
649removed.
650
651If C<password> is not supplied then a form for entering the password
652will be displayed.
653
654=cut
655
656sub delete_node {
657    my ($self, %args) = @_;
658    my $node = $args{id} or croak "No node ID supplied for deletion";
659
660    my %tt_vars = (
661                    not_editable  => 1,
662                    not_deletable => 1,
663                    deter_robots  => 1,
664                  );
665    $tt_vars{delete_version} = $args{version} || "";
666
667    my $password = $args{password};
668
669    if ($password) {
670        if ($password ne $self->config->{_}->{admin_pass}) {
671            print $self->process_template(
672                                     id       => $node,
673                                     template => "delete_password_wrong.tt",
674                                     tt_vars  => \%tt_vars,
675                                   );
676        } else {
677            $self->wiki->delete_node(
678                                      name    => $node,
679                                      version => $args{version},
680                                    );
681            # Check whether any versions of this node remain.
682            my %check = $self->wiki->retrieve_node( name => $node );
683            $tt_vars{other_versions_remain} = 1 if $check{version};
684            print $self->process_template(
685                                     id       => $node,
686                                     template => "delete_done.tt",
687                                     tt_vars  => \%tt_vars,
688                                   );
689        }
690    } else {
691        print $self->process_template(
692                                 id       => $node,
693                                 template => "delete_confirm.tt",
694                                 tt_vars  => \%tt_vars,
695                               );
696    }
697}
698
699sub process_template {
700    my ($self, %args) = @_;
701    my %output_conf = ( wiki     => $self->wiki,
702                        config   => $self->config,
703                        node     => $args{id},
704                        template => $args{template},
705                        vars     => $args{tt_vars},
706                        cookies  => $args{cookies},
707    );
708    if ( $args{content_type} ) {
709        $output_conf{content_type} = "";
710        my $output = "Content-Type: $args{content_type}\n\n"
711                     . OpenGuides::Template->output( %output_conf );
712    } else {
713        return OpenGuides::Template->output( %output_conf );
714    }
715}
716
717sub redirect_to_node {
718    my ($self, $node) = @_;
719    my $script_url = $self->config->{_}->{script_url};
720    my $script_name = $self->config->{_}->{script_name};
721    my $formatter = $self->wiki->formatter;
722    my $param = $formatter->node_name_to_node_param( $node );
723    return CGI->redirect( "$script_url$script_name?$param" );
724}
725
726sub get_cookie {
727    my $self = shift;
728    my $config = $self->config;
729    my $pref_name = shift or return "";
730    my %cookie_data = OpenGuides::CGI->get_prefs_from_cookie(config=>$config);
731    return $cookie_data{$pref_name};
732}
733
734sub make_geocache_link {
735    my $self = shift;
736    my $wiki = $self->wiki;
737    my $config = $self->config;
738    return "" unless $self->get_cookie( "include_geocache_link" );
739    my $node = shift || $config->{_}->{home_name};
740    my %current_data = $wiki->retrieve_node( $node );
741    my %criteria     = ( name => $node );
742    my %node_data    = $wiki->retrieve_node( %criteria );
743    my %metadata     = %{$node_data{metadata}};
744    my $latitude     = $metadata{latitude}[0];
745    my $longitude    = $metadata{longitude}[0];
746    my $geocache     = CGI::Wiki::Plugin::GeoCache->new();
747    my $link_text    = "Look for nearby geocaches";
748
749    if ($latitude && $longitude) {
750        my $cache_url    = $geocache->make_link(
751                                        latitude  => $latitude,
752                                        longitude => $longitude,
753                                        link_text => $link_text
754                                );
755        return $cache_url;
756    }
757    else {
758        return "";
759    }
760}
761
762=back
763
764=head1 BUGS AND CAVEATS
765
766At the moment, the location data uses a United-Kingdom-specific module,
767so the location features might not work so well outside the UK.
768
769=head1 SEE ALSO
770
771=over 4
772
773=item * L<http://london.openguides.org/|The Open Guide to London>, the first and biggest OpenGuides site.
774
775=item * L<http://openguides.org/|The OpenGuides website>, with a list of all live OpenGuides installs.
776
777=item * L<CGI::Wiki>, the Wiki toolkit which does the heavy lifting for OpenGuides
778
779=back
780
781=head1 FEEDBACK
782
783If you have a question, a bug report, or a patch, or you're interested
784in joining the development team, please contact openguides-dev@openguides.org
785(moderated mailing list, will reach all current developers but you'll have
786to wait for your post to be approved) or kake@earth.li (a real person who
787may take a little while to reply to your mail if she's busy).
788
789=head1 AUTHOR
790
791The OpenGuides Project (openguides-dev@openguides.org)
792
793=head1 COPYRIGHT
794
795     Copyright (C) 2003-4 The OpenGuides Project.  All Rights Reserved.
796
797The OpenGuides distribution is free software; you can redistribute it
798and/or modify it under the same terms as Perl itself.
799
800=head1 CREDITS
801
802Programming by Dominic Hargreaves, Earle Martin, Kake Pugh, and Ivor
803Williams.  Testing and bug reporting by Billy Abbott, Jody Belka,
804Kerry Bosworth, Simon Cozens, Cal Henderson, Steve Jolly, and Bob
805Walker (among others).  Much of the Module::Build stuff copied from
806the Siesta project L<http://siesta.unixbeard.net/>
807
808=cut
809
8101;
Note: See TracBrowser for help on using the repository browser.