source: wiki-toolkit/trunk/lib/Wiki/Toolkit/Feed/RSS.pm @ 309

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

Start to fix the indenting

  • Property svn:executable set to *
File size: 12.4 KB
Line 
1package Wiki::Toolkit::Feed::RSS;
2
3use strict;
4
5use vars qw( @ISA $VERSION );
6$VERSION = '0.10';
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/)
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 interwiki_identifier make_diff_url make_history_url
41                        software_name software_version software_homepage/)
42    {
43        $self->{$arg} = $args{$arg} || '';
44    }
45
46    $self->{timestamp_fmt} = $Wiki::Toolkit::Store::Database::timestamp_fmt;
47    $self->{utc_offset} = strftime "%z", localtime;
48    $self->{utc_offset} =~ s/(..)(..)$/$1:$2/;
49
50    $self;
51}
52
53=item B<recent_changes>
54
55Build an RSS Feed of the recent changes to the Wiki::Toolkit instance,
56using any supplied parameters to narrow the results.
57
58=cut
59sub recent_changes
60{
61    my ($self, %args) = @_;
62
63    my @changes = $self->fetch_recently_changed_nodes(%args);
64    my $feed_timestamp = $self->feed_timestamp(
65                              $self->fetch_newest_for_recently_changed(%args)
66    );
67
68    return $self->generate_node_list_feed($feed_timestamp, @changes);
69}
70
71
72=item B<node_all_versions>
73
74Build an RSS Feed of all the different versions of a given node.
75
76=cut
77sub node_all_versions
78{
79    my ($self, %args) = @_;
80
81    my @all_versions = $self->fetch_node_all_versions(%args);
82    my $feed_timestamp = $self->feed_timestamp( $all_versions[0] );
83
84    return $self->generate_node_list_feed($feed_timestamp, @all_versions);
85}
86
87
88=item <generate_node_list_feed>
89
90Generate and return an RSS feed for a list of nodes
91
92=cut
93sub generate_node_list_feed {
94  my ($self,$feed_timestamp,@nodes) = @_;
95
96  #"http://purl.org/rss/1.0/modules/wiki/"
97  my $rss = qq{<?xml version="1.0" encoding="UTF-8"?>
98
99<rdf:RDF
100 xmlns         = "http://purl.org/rss/1.0/"
101 xmlns:dc      = "http://purl.org/dc/elements/1.1/"
102 xmlns:doap    = "http://usefulinc.com/ns/doap#"
103 xmlns:foaf    = "http://xmlns.com/foaf/0.1/"
104 xmlns:rdf     = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
105 xmlns:rdfs    = "http://www.w3.org/2000/01/rdf-schema#"
106 xmlns:modwiki = "http://www.usemod.com/cgi-bin/mb.pl?ModWiki"
107>
108
109<channel rdf:about="">
110
111<dc:publisher>}       . $self->{site_url}   . qq{</dc:publisher>\n};
112
113if ($self->{software_name})
114{
115  $rss .= qq{<foaf:maker>
116  <doap:Project>
117    <doap:name>} . $self->{software_name} . qq{</doap:name>\n};
118}
119
120if ($self->{software_name} && $self->{software_homepage})
121{
122  $rss .= qq{    <doap:homepage rdf:resource="} . $self->{software_homepage} . qq{" />\n};
123}
124
125if ($self->{software_name} && $self->{software_version})
126{
127  $rss .= qq{    <doap:release>
128      <doap:Version>
129        <doap:revision>} . $self->{software_version} . qq{</doap:revision>
130      </doap:Version>
131    </doap:release>\n};
132}
133
134if ($self->{software_name})
135{
136  $rss .= qq{  </doap:Project>
137</foaf:maker>\n};
138}
139
140$rss .= qq{<title>}   . $self->{site_name}            . qq{</title>
141<link>}               . $self->{recent_changes_link}  . qq{</link>
142<description>}        . $self->{site_description}     . qq{</description>
143<dc:date>}            . $feed_timestamp                . qq{</dc:date>
144<modwiki:interwiki>}     . $self->{interwiki_identifier} . qq{</modwiki:interwiki>};
145
146  my (@urls, @items);
147
148  foreach my $node (@nodes)
149  {
150    my $node_name = $node->{name};
151
152    my $timestamp = $node->{last_modified};
153   
154    # Make a Time::Piece object.
155    my $time = Time::Piece->strptime($timestamp, $self->{timestamp_fmt});
156
157    my $utc_offset = $self->{utc_offset};
158   
159    $timestamp = $time->strftime( "%Y-%m-%dT%H:%M:%S$utc_offset" );
160
161    my $author      = $node->{metadata}{username}[0] || $node->{metadata}{host}[0] || '';
162    my $description = $node->{metadata}{comment}[0]  || '';
163
164    $description .= " [$author]" if $author;
165
166    my $version = $node->{version};
167    my $status  = (1 == $version) ? 'new' : 'updated';
168
169    my $major_change = $node->{metadata}{major_change}[0];
170       $major_change = 1 unless defined $major_change;
171    my $importance = $major_change ? 'major' : 'minor';
172
173    my $url = $self->{make_node_url}->($node_name, $version);
174
175    push @urls, qq{    <rdf:li rdf:resource="$url" />\n};
176
177    my $diff_url = '';
178   
179    if ($self->{make_diff_url})
180    {
181            $diff_url = $self->{make_diff_url}->($node_name);
182    }
183
184    my $history_url = '';
185   
186    if ($self->{make_history_url})
187    {
188      $history_url = $self->{make_history_url}->($node_name);
189    }
190
191    my $node_url = $self->{make_node_url}->($node_name);
192
193    my $rdf_url =  $node_url;
194       $rdf_url =~ s/\?/\?id=/;
195       $rdf_url .= ';format=rdf';
196
197    # make XML-clean
198    my $title =  $node_name;
199       $title =~ s/&/&amp;/g;
200       $title =~ s/</&lt;/g;
201       $title =~ s/>/&gt;/g;
202   
203    push @items, qq{
204<item rdf:about="$url">
205  <title>$title</title>
206  <link>$url</link>
207  <description>$description</description>
208  <dc:date>$timestamp</dc:date>
209  <dc:contributor>$author</dc:contributor>
210  <modwiki:status>$status</modwiki:status>
211  <modwiki:importance>$importance</modwiki:importance>
212  <modwiki:diff>$diff_url</modwiki:diff>
213  <modwiki:version>$version</modwiki:version>
214  <modwiki:history>$history_url</modwiki:history>
215  <rdfs:seeAlso rdf:resource="$rdf_url" />
216</item>
217};
218  }
219 
220  $rss .= qq{
221
222<items>
223  <rdf:Seq>
224} . join('', @urls) . qq{  </rdf:Seq>
225</items>
226
227</channel>
228} . join('', @items) . "\n</rdf:RDF>\n";
229 
230  return $rss;   
231}
232
233=item B<feed_timestamp>
234
235Generate the timestamp for the RSS, based on the newest node (if available)
236
237=cut
238sub feed_timestamp
239{
240    my ($self, $newest_node) = @_;
241
242    if ($newest_node->{last_modified})
243    {
244        my $time = Time::Piece->strptime( $newest_node->{last_modified}, $self->{timestamp_fmt} );
245
246        my $utc_offset = $self->{utc_offset};
247
248        return $time->strftime( "%Y-%m-%dT%H:%M:%S$utc_offset" );
249    }
250    else
251    {
252        return '1970-01-01T00:00:00+0000';
253    }
254}
255
2561;
257
258__END__
259
260=head1 NAME
261
262  Wiki::Toolkit::Feed::RSS - Output RecentChanges RSS for Wiki::Toolkit.
263
264=head1 DESCRIPTION
265
266This is an alternative access to the recent changes of a Wiki::Toolkit
267wiki. It outputs RSS as described by the ModWiki proposal at
268L<http://www.usemod.com/cgi-bin/mb.pl?ModWiki>
269
270=head1 SYNOPSIS
271
272  use Wiki::Toolkit;
273  use Wiki::Toolkit::Feed::RSS;
274
275  my $wiki = CGI::Wiki->new( ... );  # See perldoc Wiki::Toolkit
276
277  # Set up the RSS feeder with the mandatory arguments - see
278  # C<new()> below for more, optional, arguments.
279  my $rss = Wiki::Toolkit::Feed::RSS->new(
280    wiki                => $wiki,
281    site_name           => 'My Wiki',
282    site_url            => 'http://example.com/',
283    make_node_url       => sub
284                           {
285                             my ($node_name, $version) = @_;
286                             return 'http://example.com/?id=' . uri_escape($node_name) . ';version=' . uri_escape($version);
287                           },
288    recent_changes_link => 'http://example.com/?RecentChanges',
289  );
290
291  print "Content-type: application/xml\n\n";
292  print $rss->recent_changes;
293
294=head1 METHODS
295
296=head2 C<new()>
297
298  my $rss = Wiki::Toolkit::Feed::RSS->new(
299    # Mandatory arguments:
300    wiki                 => $wiki,
301    site_name            => 'My Wiki',
302    site_url             => 'http://example.com/',
303    make_node_url        => sub
304                            {
305                              my ($node_name, $version) = @_;
306                              return 'http://example.com/?id=' . uri_escape($node_name) . ';version=' . uri_escape($version);
307                            },
308    recent_changes_link  => 'http://example.com/?RecentChanges',
309
310    # Optional arguments:
311    site_description     => 'My wiki about my stuff',
312    interwiki_identifier => 'MyWiki',
313    make_diff_url        => sub
314                            {
315                              my $node_name = shift;
316                              return 'http://example.com/?diff=' . uri_escape($node_name)
317                            },
318    make_history_url     => sub
319                            {
320                              my $node_name = shift;
321                              return 'http://example.com/?hist=' . uri_escape($node_name)
322                            },
323    software_name        => $your_software_name,     # e.g. "CGI::Wiki"
324    software_version     => $your_software_version,  # e.g. "0.73"
325    software_homepage    => $your_software_homepage, # e.g. "http://search.cpan.org/dist/Wiki-Toolkit/"
326  );
327
328C<wiki> must be a L<Wiki::Toolkit> object. C<make_node_url>, and
329C<make_diff_url> and C<make_history_url>, if supplied, must be coderefs.
330
331The mandatory arguments are:
332
333=over 4
334
335=item * wiki
336
337=item * site_name
338
339=item * site_url
340
341=item * make_node_url
342
343=item * recent_changes_link
344
345=back
346
347The three optional arguments
348
349=over 4
350
351=item * software_name
352
353=item * software_version
354
355=item * software_homepage
356
357=back
358
359are used to generate DOAP (Description Of A Project - see L<http://usefulinc.com/doap>) metadata
360for the feed to show what generated it.
361
362=head2 C<recent_changes()>
363
364  $wiki->write_node(
365                     'About This Wiki',
366                     'blah blah blah',
367                                 $checksum,
368                           {
369                       comment  => 'Stub page, please update!',
370                                   username => 'Fred',
371                     }
372  );
373
374  print "Content-type: application/xml\n\n";
375  print $rss->recent_changes;
376
377  # Or get something other than the default of the latest 15 changes.
378  print $rss->recent_changes( items => 50 );
379  print $rss->recent_changes( days => 7 );
380
381  # Or ignore minor edits.
382  print $rss->recent_changes( ignore_minor_edits => 1 );
383
384  # Personalise your feed further - consider only changes
385  # made by Fred to pages about bookshops.
386  print $rss->recent_changes(
387             filter_on_metadata => {
388                         username => 'Fred',
389                         category => 'Bookshops',
390                       },
391              );
392
393If using C<filter_on_metadata>, note that only changes satisfying
394I<all> criteria will be returned.
395
396B<Note:> Many of the fields emitted by the RSS generator are taken
397from the node metadata. The form of this metadata is I<not> mandated
398by L<Wiki::Toolkit>. Your wiki application should make sure to store some or
399all of the following metadata when calling C<write_node>:
400
401=over 4
402
403=item B<comment> - a brief comment summarising the edit that has just been made; will be used in the RDF description for this item.  Defaults to the empty string.
404
405=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 the empty string.
406
407=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 Dublin Core contributor for this item.  Defaults to the empty string.
408
409=item B<major_change> - true if the edit was a major edit and false if it was a minor edit; used for the importance of the item.  Defaults to true (ie if C<major_change> was not defined or was explicitly stored as C<undef>).
410
411=back
412
413=head2 C<feed_timestamp()>
414
415  print $rss->feed_timestamp();
416
417Returns the timestamp of the feed in POSIX::strftime style ("Tue, 29 Feb 2000
41812:34:56 GMT"), which is equivalent to the timestamp of the most recent item
419in the feed. Takes the same arguments as recent_changes(). You will most likely
420need this to print a Last-Modified HTTP header so user-agents can determine
421whether they need to reload the feed or not.
422 
423=head1 SEE ALSO
424
425=over 4
426
427=item * L<Wiki::Toolkit>
428
429=item * L<http://web.resource.org/rss/1.0/spec>
430
431=item * L<http://www.usemod.com/cgi-bin/mb.pl?ModWiki>
432
433=back
434
435=head1 MAINTAINER
436
437The Wiki::Toolkit project. Originally by Kake Pugh <kake@earth.li>.
438
439=head1 COPYRIGHT AND LICENSE
440
441Copyright 2003-4 Kake Pugh.
442Copyright 2005 Earle Martin.
443Copyright 2006 the Wiki::Toolkit team
444
445This module is free software; you can redistribute it and/or modify it
446under the same terms as Perl itself.
447
448=head1 THANKS
449
450The members of the Semantic Web Interest Group channel on irc.freenode.net,
451#swig, were very useful in the development of this module.
452
453=cut
Note: See TracBrowser for help on using the repository browser.