source: trunk/lib/OpenGuides.pm @ 483

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

Recent Changes RSS fixed so "wiki:importance?" is set correctly.

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