source: trunk/lib/OpenGuides.pm @ 475

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

Added navbar to diff and history pages, made them non-editable and non-deletable.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 27.1 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    my $written = $wiki->write_node($node, $content, $checksum, \%metadata );
594
595    if ($written) {
596        print $self->redirect_to_node($node);
597    } else {
598        my %node_data = $wiki->retrieve_node($node);
599        my %tt_vars = ( checksum       => $node_data{checksum},
600                        new_content    => $content,
601                        stored_content => $node_data{content} );
602        foreach my $mdvar ( keys %metadata ) {
603            if ($mdvar eq "locales") {
604                $tt_vars{"stored_$mdvar"} = $node_data{metadata}{locale};
605                $tt_vars{"new_$mdvar"}    = $metadata{locale};
606            } elsif ($mdvar eq "categories") {
607                $tt_vars{"stored_$mdvar"} = $node_data{metadata}{category};
608                $tt_vars{"new_$mdvar"}    = $metadata{category};
609            } elsif ($mdvar eq "username" or $mdvar eq "comment"
610                      or $mdvar eq "edit_type" ) {
611                $tt_vars{$mdvar} = $metadata{$mdvar};
612            } else {
613                $tt_vars{"stored_$mdvar"} = $node_data{metadata}{$mdvar}[0];
614                $tt_vars{"new_$mdvar"}    = $metadata{$mdvar};
615            }
616        }
617        return %tt_vars if $args{return_tt_vars};
618        my $output = $self->process_template(
619                                              id       => $node,
620                                              template => "edit_conflict.tt",
621                                              tt_vars  => \%tt_vars,
622                                            );
623        return $output if $args{return_output};
624        print $output;
625    }
626}
627
628
629=item B<delete_node>
630
631  $guide->delete_node(
632                       id       => "FAQ",
633                       version  => 15,
634                       password => "beer",
635                     );
636
637C<version> is optional - if it isn't supplied then all versions of the
638node will be deleted; in other words the node will be entirely
639removed.
640
641If C<password> is not supplied then a form for entering the password
642will be displayed.
643
644=cut
645
646sub delete_node {
647    my ($self, %args) = @_;
648    my $node = $args{id} or croak "No node ID supplied for deletion";
649
650    my %tt_vars = (
651                    not_editable  => 1,
652                    not_deletable => 1,
653                  );
654    $tt_vars{delete_version} = $args{version} || "";
655
656    my $password = $args{password};
657
658    if ($password) {
659        if ($password ne $self->config->{_}->{admin_pass}) {
660            print $self->process_template(
661                                     id       => $node,
662                                     template => "delete_password_wrong.tt",
663                                     tt_vars  => \%tt_vars,
664                                   );
665        } else {
666            $self->wiki->delete_node(
667                                      name    => $node,
668                                      version => $args{version},
669                                    );
670            # Check whether any versions of this node remain.
671            my %check = $self->wiki->retrieve_node( name => $node );
672            $tt_vars{other_versions_remain} = 1 if $check{version};
673            print $self->process_template(
674                                     id       => $node,
675                                     template => "delete_done.tt",
676                                     tt_vars  => \%tt_vars,
677                                   );
678        }
679    } else {
680        print $self->process_template(
681                                 id       => $node,
682                                 template => "delete_confirm.tt",
683                                 tt_vars  => \%tt_vars,
684                               );
685    }
686}
687
688sub process_template {
689    my ($self, %args) = @_;
690    my %output_conf = ( wiki     => $self->wiki,
691                        config   => $self->config,
692                        node     => $args{id},
693                        template => $args{template},
694                        vars     => $args{tt_vars},
695                        cookies  => $args{cookies},
696    );
697    if ( $args{content_type} ) {
698        $output_conf{content_type} = "";
699        my $output = "Content-Type: $args{content_type}\n\n"
700                     . OpenGuides::Template->output( %output_conf );
701    } else {
702        return OpenGuides::Template->output( %output_conf );
703    }
704}
705
706sub redirect_to_node {
707    my ($self, $node) = @_;
708    my $script_url = $self->config->{_}->{script_url};
709    my $script_name = $self->config->{_}->{script_name};
710    my $formatter = $self->wiki->formatter;
711    my $param = $formatter->node_name_to_node_param( $node );
712    return CGI->redirect( "$script_url$script_name?$param" );
713}
714
715sub get_cookie {
716    my $self = shift;
717    my $config = $self->config;
718    my $pref_name = shift or return "";
719    my %cookie_data = OpenGuides::CGI->get_prefs_from_cookie(config=>$config);
720    return $cookie_data{$pref_name};
721}
722
723sub make_geocache_link {
724    my $self = shift;
725    my $wiki = $self->wiki;
726    my $config = $self->config;
727    return "" unless $self->get_cookie( "include_geocache_link" );
728    my $node = shift || $config->{_}->{home_name};
729    my %current_data = $wiki->retrieve_node( $node );
730    my %criteria     = ( name => $node );
731    my %node_data    = $wiki->retrieve_node( %criteria );
732    my %metadata     = %{$node_data{metadata}};
733    my $latitude     = $metadata{latitude}[0];
734    my $longitude    = $metadata{longitude}[0];
735    my $geocache     = CGI::Wiki::Plugin::GeoCache->new();
736    my $link_text    = "Look for nearby geocaches";
737
738    if ($latitude && $longitude) {
739        my $cache_url    = $geocache->make_link(
740                                        latitude  => $latitude,
741                                        longitude => $longitude,
742                                        link_text => $link_text
743                                );
744        return $cache_url;
745    }
746    else {
747        return "";
748    }
749}
750
751=back
752
753=head1 BUGS AND CAVEATS
754
755At the moment, the location data uses a United-Kingdom-specific module,
756so the location features might not work so well outside the UK.
757
758=head1 SEE ALSO
759
760=over 4
761
762=item * L<http://london.openguides.org/|The Open Guide to London>, the first and biggest OpenGuides site.
763
764=item * L<http://openguides.org/|The OpenGuides website>, with a list of all live OpenGuides installs.
765
766=item * L<CGI::Wiki>, the Wiki toolkit which does the heavy lifting for OpenGuides
767
768=back
769
770=head1 FEEDBACK
771
772If you have a question, a bug report, or a patch, or you're interested
773in joining the development team, please contact openguides-dev@openguides.org
774(moderated mailing list, will reach all current developers but you'll have
775to wait for your post to be approved) or kake@earth.li (a real person who
776may take a little while to reply to your mail if she's busy).
777
778=head1 AUTHOR
779
780The OpenGuides Project (openguides-dev@openguides.org)
781
782=head1 COPYRIGHT
783
784     Copyright (C) 2003-4 The OpenGuides Project.  All Rights Reserved.
785
786The OpenGuides distribution is free software; you can redistribute it
787and/or modify it under the same terms as Perl itself.
788
789=head1 CREDITS
790
791Programming by Dominic Hargreaves, Earle Martin, Kake Pugh, and Ivor
792Williams.  Testing and bug reporting by Billy Abbott, Jody Belka,
793Kerry Bosworth, Simon Cozens, Cal Henderson, Steve Jolly, and Bob
794Walker (among others).  Much of the Module::Build stuff copied from
795the Siesta project L<http://siesta.unixbeard.net/>
796
797=cut
798
7991;
Note: See TracBrowser for help on using the repository browser.