source: wiki-toolkit/trunk/lib/Wiki/Toolkit/Feed/Atom.pm @ 315

Last change on this file since 315 was 315, checked in by nick, 14 years ago

Refactoring - shift fetch methods into Listing.pm, to avoid yet more duplication

  • Property svn:executable set to *
File size: 10.0 KB
Line 
1package Wiki::Toolkit::Feed::Atom;
2
3use strict;
4
5use vars qw( @ISA $VERSION );
6$VERSION = '0.01';
7
8use POSIX 'strftime';
9use Time::Piece;
10use URI::Escape;
11use Carp qw( croak );
12
13use Wiki::Toolkit::Feed::Listing;
14@ISA = qw( Wiki::Toolkit::Feed::Listing );
15
16sub new
17{
18  my $class = shift;
19  my $self  = {};
20  bless $self, $class;
21
22  my %args = @_;
23  my $wiki = $args{wiki};
24
25  unless ($wiki && UNIVERSAL::isa($wiki, 'Wiki::Toolkit'))
26  {
27    croak 'No Wiki::Toolkit object supplied';
28  }
29 
30  $self->{wiki} = $wiki;
31 
32  # Mandatory arguments.
33  foreach my $arg (qw/site_name site_url make_node_url recent_changes_link atom_link/)
34  {
35    croak "No $arg supplied" unless $args{$arg};
36    $self->{$arg} = $args{$arg};
37  }
38 
39  # Optional arguments.
40  foreach my $arg (qw/site_description software_name software_version software_homepage/)
41  {
42    $self->{$arg} = $args{$arg} || '';
43  }
44
45  $self->{timestamp_fmt} = $Wiki::Toolkit::Store::Database::timestamp_fmt;
46  $self->{utc_offset} = strftime "%z", localtime;
47  $self->{utc_offset} =~ s/(..)(..)$/$1:$2/;
48 
49  $self;
50}
51
52=item <generate_node_list_feed>
53 
54Generate and return an Atom feed for a list of nodes
55 
56=cut
57sub generate_node_list_feed {
58  my ($self,$atom_timestamp,@nodes) = @_;
59
60  my $generator = '';
61 
62  if ($self->{software_name})
63  {
64    $generator  = '  <generator';
65    $generator .= ' uri="' . $self->{software_homepage} . '"'   if $self->{software_homepage};
66    $generator .= ' version=' . $self->{software_version} . '"' if $self->{software_version};
67    $generator .= ">\n";
68    $generator .= $self->{software_name} . "</generator>\n";
69  }                         
70
71  my $subtitle = $self->{site_description}
72                 ? '<subtitle>' . $self->{site_description} . "</subtitle>\n"
73                 : '';
74                 
75  my $atom = qq{<?xml version="1.0" encoding="UTF-8"?>
76
77<feed xmlns="http://www.w3.org/2005/Atom">
78
79  <link href="}            . $self->{site_url}     . qq{" />
80  <title>}                 . $self->{site_name}    . qq{</title>
81  <link rel="self" href="} . $self->{atom_link}    . qq{" />
82  <updated>}               . $atom_timestamp       . qq{</updated>
83  <id>}                    . $self->{site_url}     . qq{</id>
84  $subtitle};
85
86  my (@urls, @items);
87
88  foreach my $node (@nodes)
89  {
90    my $node_name = $node->{name};
91
92    my $item_timestamp = $node->{last_modified};
93   
94    # Make a Time::Piece object.
95    my $time = Time::Piece->strptime($item_timestamp, $self->{timestamp_fmt});
96
97    my $utc_offset = $self->{utc_offset};
98   
99    $item_timestamp = $time->strftime( "%Y-%m-%dT%H:%M:%S$utc_offset" );
100
101    my $author      = $node->{metadata}{username}[0] || $node->{metadata}{host}[0] || 'Anonymous';
102    my $description = $node->{metadata}{comment}[0]  || 'No description given for node';
103
104    $description .= " [$author]" if $author;
105
106    my $version = $node->{version};
107    my $status  = (1 == $version) ? 'new' : 'updated';
108
109    my $major_change = $node->{metadata}{major_change}[0];
110       $major_change = 1 unless defined $major_change;
111    my $importance = $major_change ? 'major' : 'minor';
112
113    my $url = $self->{make_node_url}->($node_name, $version);
114
115    # make XML-clean
116    my $title =  $node_name;
117       $title =~ s/&/&amp;/g;
118       $title =~ s/</&lt;/g;
119       $title =~ s/>/&gt;/g;
120
121    # Pop the categories into atom:category elements (4.2.2)
122    # We can do this because the spec says:
123    #   "This specification assigns no meaning to the content (if any)
124    #    of this element."
125    # TODO: Decide if we should include the "all categories listing" url
126    #        as the scheme (URI) attribute?
127    my $category_atom;
128    if($node->{metadata}->{category}) {
129        foreach my $cat (@{ $node->{metadata}->{category} }) {
130            $category_atom .= "    <category term=\"$cat\" />\n";
131        }
132    }
133
134    # TODO: Find an Atom equivalent of ModWiki, so we can include more info
135   
136    push @items, qq{
137  <entry>
138    <title>$title</title>
139    <link href="$url" />
140    <id>$url</id>
141    <summary>$description</summary>
142    <updated>$item_timestamp</updated>
143    <author><name>$author</name></author>
144$category_atom
145  </entry>
146};
147
148  }
149 
150  $atom .= join('', @items) . "\n</feed>\n";
151
152  return $atom;   
153}
154
155=item B<feed_timestamp>
156
157Generate the timestamp for the Atom, based on the newest node (if available)
158
159=cut
160sub feed_timestamp
161{
162  my ($self, $newest_node) = @_;
163 
164  if ($newest_node->{last_modified})
165  {
166    my $time = Time::Piece->strptime( $newest_node->{last_modified}, $self->{timestamp_fmt} );
167
168    my $utc_offset = $self->{utc_offset};
169   
170    return $time->strftime( "%Y-%m-%dT%H:%M:%S$utc_offset" );
171  }
172  else
173  {
174    return '1970-01-01T00:00:00+0000';
175  }
176}
177
1781;
179
180__END__
181
182=head1 NAME
183
184  Wiki::Toolkit::Feed::Atom - A Wiki::Toolkit plugin to output RecentChanges Atom.
185
186=head1 DESCRIPTION
187
188This is an alternative access to the recent changes of a Wiki::Toolkit
189wiki. It outputs the Atom Syndication Format as described at
190L<http://www.atomenabled.org/developers/syndication/>.
191
192This module is a straight port of L<Wiki::Toolkit::Feed::RSS>.
193
194=head1 SYNOPSIS
195
196  use Wiki::Toolkit;
197  use Wiki::Toolkit::Feed::Atom;
198
199  my $wiki = Wiki::Toolkit->new( ... );  # See perldoc Wiki::Toolkit
200
201  # Set up the RSS feeder with the mandatory arguments - see
202  # C<new()> below for more, optional, arguments.
203  my $atom = Wiki::Toolkit::Feed::Atom->new(
204    wiki                => $wiki,
205    site_name           => 'My Wiki',
206    site_url            => 'http://example.com/',
207    make_node_url       => sub
208                           {
209                             my ($node_name, $version) = @_;
210                             return 'http://example.com/?id=' . uri_escape($node_name) . ';version=' . uri_escape($version);
211                           },
212    recent_changes_link => 'http://example.com/?RecentChanges',
213    atom_link => 'http://example.com/?action=rc;format=atom',
214  );
215
216  print "Content-type: application/atom+xml\n\n";
217  print $atom->recent_changes;
218
219=head1 METHODS
220
221=head2 C<new()>
222
223  my $atom = Wiki::Toolkit::Feed::Atom->new(
224    # Mandatory arguments:
225    wiki                 => $wiki,
226    site_name            => 'My Wiki',
227    site_url             => 'http://example.com/',
228    make_node_url        => sub
229                            {
230                              my ($node_name, $version) = @_;
231                              return 'http://example.com/?id=' . uri_escape($node_name) . ';version=' . uri_escape($version);
232                            },
233    recent_changes_link  => 'http://example.com/?RecentChanges',,
234    atom_link => 'http://example.com/?action=rc;format=atom',
235
236    # Optional arguments:
237    site_description     => 'My wiki about my stuff',
238    software_name        => $your_software_name,     # e.g. "Wiki::Toolkit"
239    software_version     => $your_software_version,  # e.g. "0.73"
240    software_homepage    => $your_software_homepage, # e.g. "http://search.cpan.org/dist/CGI-Wiki/"
241  );
242
243C<wiki> must be a L<Wiki::Toolkit> object. C<make_node_url>, if supplied, must
244be a coderef.
245
246The mandatory arguments are:
247
248=over 4
249
250=item * wiki
251
252=item * site_name
253
254=item * site_url
255
256=item * make_node_url
257
258=item * recent_changes_link
259
260=item * atom_link
261
262=back
263
264The three optional arguments
265
266=over 4
267
268=item * software_name
269
270=item * software_version
271
272=item * software_homepage
273
274=back
275
276are used to generate the C<generator> part of the feed.
277
278=head2 C<recent_changes()>
279
280  $wiki->write_node(
281                     'About This Wiki',
282                     'blah blah blah',
283                                 $checksum,
284                           {
285                       comment  => 'Stub page, please update!',
286                                   username => 'Fred',
287                     }
288  );
289
290  print "Content-type: application/atom+xml\n\n";
291  print $atom->recent_changes;
292
293  # Or get something other than the default of the latest 15 changes.
294  print $atom->recent_changes( items => 50 );
295  print $atom->recent_changes( days => 7 );
296
297  # Or ignore minor edits.
298  print $atom->recent_changes( ignore_minor_edits => 1 );
299
300  # Personalise your feed further - consider only changes
301  # made by Fred to pages about bookshops.
302  print $atom->recent_changes(
303             filter_on_metadata => {
304                         username => 'Fred',
305                         category => 'Bookshops',
306                       },
307              );
308
309If using C<filter_on_metadata>, note that only changes satisfying
310I<all> criteria will be returned.
311
312B<Note:> Many of the fields emitted by the Atom generator are taken
313from the node metadata. The form of this metadata is I<not> mandated
314by L<Wiki::Toolkit>. Your wiki application should make sure to store some or
315all of the following metadata when calling C<write_node>:
316
317=over 4
318
319=item B<comment> - a brief comment summarising the edit that has just been made; will be used in the summary for this item.  Defaults to the empty string.
320
321=item B<username> - an identifier for the person who made the edit; will be used as the Dublin Core contributor for this item, and also in the RDF description.  Defaults to 'No description given for change'.
322
323=item B<host> - the hostname or IP address of the computer used to make the edit; if no username is supplied then this will be used as the author for this item.  Defaults to 'Anonymous'.
324
325=back
326
327=head2 C<feed_timestamp()>
328
329  print $atom->feed_timestamp();
330
331Returns the timestamp of the feed in POSIX::strftime style ("Tue, 29 Feb 2000
33212:34:56 GMT"), which is equivalent to the timestamp of the most recent item
333in the feed. Takes the same arguments as recent_changes(). You will most likely
334need this to print a Last-Modified HTTP header so user-agents can determine
335whether they need to reload the feed or not.
336 
337=head1 SEE ALSO
338
339=over 4
340
341=item * L<Wiki::Toolkit>
342
343=item * L<http://www.atomenabled.org/developers/syndication/>
344
345=back
346
347=head1 MAINTAINER
348
349The Wiki::Toolkit team, http://www.wiki-toolkit.org/.
350
351=head1 COPYRIGHT AND LICENSE
352
353Copyright 2006 Earle Martin and the Wiki::Toolkit team.
354
355This module is free software; you can redistribute it and/or modify it
356under the same terms as Perl itself.
357
358=head1 THANKS
359
360Kake Pugh for originally writing Wiki::Toolkit::Feed::RSS and indeed
361Wiki::Toolkit itself.
362
363=cut
Note: See TracBrowser for help on using the repository browser.