source: trunk/lib/OpenGuides.pm @ 490

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

RSS feeds for contributors, locales and categories.

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