source: trunk/lib/OpenGuides/Feed.pm

Last change on this file was 1281, checked in by Dominic Hargreaves, 12 years ago

bump module version numbers

File size: 12.7 KB
Line 
1package OpenGuides::Feed;
2
3use strict;
4
5use vars qw( $VERSION );
6$VERSION = '0.02';
7
8use Wiki::Toolkit::Feed::Atom;
9use Wiki::Toolkit::Feed::RSS;
10use Time::Piece;
11use URI::Escape;
12use Carp 'croak';
13
14use base qw( Class::Accessor );
15# Add more here if we need them - this one added for testing purposes.
16my @variables = qw( html_equiv_link );
17OpenGuides::Feed->mk_accessors( @variables );
18
19sub new {
20    my ($class, @args) = @_;
21    my $self = {};
22    bless $self, $class;
23    $self->_init(@args);
24}
25
26sub _init {
27    my ($self, %args) = @_;
28
29    my $wiki = $args{wiki};
30   
31    unless ( $wiki && UNIVERSAL::isa( $wiki, "Wiki::Toolkit" ) ) {
32       croak "No Wiki::Toolkit object supplied.";
33    }
34    $self->{wiki} = $wiki;
35
36    my $config = $args{config};
37
38    unless ( $config && UNIVERSAL::isa( $config, "OpenGuides::Config" ) ) {
39        croak "No OpenGuides::Config object supplied.";
40    }
41    $self->{config} = $config;
42
43    $self->{make_node_url} = sub {
44        my ($node_name, $version) = @_;
45
46        my $config = $self->{config};
47   
48        my $node_url = $config->script_url . uri_escape($config->script_name) . '?';
49        $node_url .= 'id=' if defined $version;
50        $node_url .= uri_escape($self->{wiki}->formatter->node_name_to_node_param($node_name));
51        $node_url .= ';version=' . uri_escape($version) if defined $version;
52
53        $node_url;
54      }; 
55    $self->{site_name}        = $config->site_name . " - Recent Changes";
56    $self->{default_city}     = $config->default_city      || "";
57    $self->{default_country}  = $config->default_country   || "";
58    $self->{site_description} = $config->site_desc         || "";
59    $self->{og_version}       = $args{og_version};
60    $self->{html_equiv_link}  = $self->{config}->script_url
61                                . $self->{config}->script_name . '?action=rc';
62
63    $self;
64}
65
66=over 4
67
68=item B<set_feed_name_and_url_params>
69Overrides the default feed name and default feed http equivalent url.
70Useful on custom feeds, where the defaults are incorrect.
71
72   $feed->set_feed_name_and_url("Search Results", "search=pub");
73   $feed->build_mini_feed_for_nodes("rss", @search_results);
74
75=cut
76
77sub set_feed_name_and_url_params {
78    my ($self, $name, $url) = @_;
79
80    unless($url =~ /^http/) {
81        my $b_url = $self->{config}->script_url;
82        unless($url =~ /\.cgi\?/) { $b_url .= "?"; }
83        $b_url .= $url;
84        $url = $b_url;
85    }
86
87    $self->{site_name} = $self->{config}->{site_name} . " - " . $name;
88    $self->{html_equiv_link} = $url;
89}
90
91=item B<make_feed>
92
93Produce one of the standard feeds, in the requested format.
94
95
96my $feed_contents = feeds->make_feed(
97                                feed_type => 'rss',
98                                feed_listing => 'recent_changes'
99                    );
100
101Passes additional arguments through to the underlying Wiki::Toolkit::Feed
102
103=cut
104
105sub make_feed {
106    my ($self, %args) = @_;
107   
108    my $feed_type = $args{feed_type};
109    my $feed_listing = $args{feed_listing};
110   
111    my %known_listings = (
112                          'recent_changes' => 1,
113                          'node_all_versions' => 1,
114                         );
115                     
116    croak "No feed listing specified" unless $feed_listing;
117    croak "Unknown feed listing: $feed_listing" unless $known_listings{$feed_listing};
118
119
120    # Tweak any settings, as required by our feed listing
121    if ($feed_listing eq 'node_all_versions') {
122        $self->set_feed_name_and_url_params(
123                    "All versions of ".$args{'name'},
124                    "action=list_all_versions;id=".$args{'name'}
125        );
126    }
127
128
129    # Fetch the right Wiki::Toolkit::Feeds::Listing instance to use
130    my $maker = $self->fetch_maker($feed_type);
131
132
133    # Call the appropriate feed listing from it
134    if ($feed_listing eq 'recent_changes') {
135        return $maker->recent_changes(%args);
136    }
137    elsif ($feed_listing eq 'node_all_versions') {
138        return $maker->node_all_versions(%args);
139    }
140}
141
142=item B<build_feed_for_nodes>
143
144For the given feed type, build a feed from the supplied list of nodes.
145Will figure out the feed timestamp from the newest node, and output a
146 last modified header based on this.
147
148my @nodes = $wiki->fetch_me_nodes_I_like();
149my $feed_contents = $feed->build_feed_for_nodes("rss", @nodes);
150
151=cut
152
153sub build_feed_for_nodes {
154    my ($self, $format, @nodes) = @_;
155    return $self->render_feed_for_nodes($format, undef, 1, @nodes);
156}
157
158=item B<build_mini_feed_for_nodes>
159
160For the given feed type, build a mini feed (name and distance) from the
161 supplied list of nodes.
162Will figure out the feed timestamp from the newest node, and output a
163 last modified header based on this.
164
165my @nodes = $wiki->search_near_here();
166my $feed_contents = $feed->build_mini_feed_for_nodes("rss", @nodes);
167
168=cut
169
170sub build_mini_feed_for_nodes {
171    my ($self, $format, @nodes) = @_;
172    return $self->render_feed_for_nodes($format, undef, 0, @nodes);
173}
174
175=item B<render_feed_for_nodes>
176
177Normally internal method to perform the appropriate building of a feed
178based on a list of nodes.
179
180=cut
181
182sub render_feed_for_nodes {
183    my ($self, $format, $html_url, $is_full, @nodes) = @_;
184
185    # Grab our feed maker
186    my $maker = $self->fetch_maker($format);
187
188    # Find our newest node, so we can use that for the feed timestamp
189    my $newest_node;
190    foreach my $node (@nodes) {
191        if($node->{last_modified}) {
192            if((!$newest_node) || ($node->{last_modified} gt $newest_node->{last_modified})) {
193                $newest_node = $node;
194            }
195        }
196    }
197
198    # Grab the timestamp, and do our header
199    my $timestamp = $maker->feed_timestamp($newest_node);
200
201    my $feed = "Last-Modified: ".$timestamp."\n\n";
202
203    # Generate the feed itself
204    if($is_full) {
205        $feed .= $maker->generate_node_list_feed($timestamp, @nodes);
206    } else {
207        $feed .= $maker->generate_node_name_distance_feed($timestamp, @nodes);
208    }
209
210    return $feed;
211}
212
213=item B<default_content_type>
214
215For the given feed type, return the default content type for that feed.
216
217my $content_type = $feed->default_content_type("rss");
218
219=cut
220
221sub default_content_type {
222    my ($self,$feed_type) = @_;
223
224    my $content_type;
225
226    if ($feed_type eq 'rss') {
227        $content_type = "application/rdf+xml";
228    }
229    elsif ($feed_type eq 'atom') {
230        $content_type = "application/atom+xml";
231    }
232    else {
233        croak "Unknown feed type given: $feed_type";
234    }
235
236    return $content_type;
237}
238
239=item B<fetch_maker>
240
241For the given feed type, identify and return the maker routine for feeds
242of that type.
243
244my $maker = $feed->fetch_maker("rss");
245my $feed_contents = maker->node_all_versions(%options);
246
247Will always return something of type Wiki::Toolkit::Feed::Listing
248
249=cut
250
251sub fetch_maker {
252    my ($self,$feed_type) = @_;
253
254    my %known_types = (
255                          'atom'  => \&atom_maker,
256                          'rss' => \&rss_maker,
257                      );
258
259    croak "No feed type specified" unless $feed_type;
260    croak "Unknown feed type: $feed_type" unless $known_types{$feed_type};
261
262    return &{$known_types{$feed_type}};
263}
264
265sub atom_maker {
266    my $self = shift;
267 
268    unless ($self->{atom_maker}) {
269        $self->{atom_maker} = Wiki::Toolkit::Feed::Atom->new(
270            wiki                => $self->{wiki},
271            site_name           => $self->{site_name},
272            site_url            => $self->{config}->script_url,
273            site_description    => $self->{site_description},
274            make_node_url       => $self->{make_node_url},
275            html_equiv_link     => $self->{html_equiv_link},
276            atom_link           => $self->{html_equiv_link} . ";format=atom",
277            software_name       => 'OpenGuides',
278            software_homepage   => 'http://openguides.org/',
279            software_version    => $self->{og_version},
280            encoding            => $self->{config}->http_charset,
281        );
282    }
283   
284    $self->{atom_maker};
285}
286
287sub rss_maker {
288    my $self = shift;
289
290    unless ($self->{rss_maker}) {
291        $self->{rss_maker} = Wiki::Toolkit::Feed::RSS->new(
292            wiki                => $self->{wiki},
293            site_name           => $self->{site_name},
294            site_url            => $self->{config}->script_url,
295            site_description    => $self->{site_description},
296            make_node_url       => $self->{make_node_url},
297            html_equiv_link     => $self->{html_equiv_link},
298            software_name       => 'OpenGuides',
299            software_homepage   => 'http://openguides.org/',
300            software_version    => $self->{og_version},
301            encoding            => $self->{config}->http_charset,
302        );
303    }
304   
305    $self->{rss_maker};
306}
307
308sub feed_timestamp {
309    my ($self, %args) = @_;
310
311    # Call the compatability timestamping method on the RSS Feed.
312    # People should really just pass in also_return_timestamp to the
313    #  feed method, and get the timestamp at the same time as their data
314    $self->rss_maker->rss_timestamp(%args);
315}
316
317=back
318
319=head1 NAME
320
321OpenGuides::Feed - generate data feeds for OpenGuides in various formats.
322
323=head1 DESCRIPTION
324
325Produces RSS 1.0 and Atom 1.0 feeds for OpenGuides.  Distributed and
326installed as part of the OpenGuides project, not intended for independent
327installation.  This documentation is probably only useful to OpenGuides
328developers.
329
330=head1 SYNOPSIS
331
332    use Wiki::Toolkit;
333    use OpenGuides::Config;
334    use OpenGuides::Feed;
335
336    my $wiki = Wiki::Toolkit->new( ... );
337    my $config = OpenGuides::Config->new( file => "wiki.conf" );
338    my $feed = OpenGuides::Feed->new( wiki       => $wiki,
339                                      config     => $config,
340                                      og_version => '1.0', );
341
342    # Ten most recent changes in RSS format.
343    my %args = ( items     => 10,
344                 feed_type => 'rss',
345                 also_return_timestamp => 1 );
346    my ($feed_output,$feed_timestamp) = $feed->make_feed( %args );
347
348    print "Content-Type: application/rdf+xml\n";
349    print "Last-Modified: " . $feed_timestamp . "\n\n";
350    print $feed_output;
351
352=head1 METHODS
353
354=over 4
355
356=item B<new>
357
358    my $feed = OpenGuides::Feed->new( wiki       => $wiki,
359                                      config     => $config,
360                                      og_version => '1.0', );
361
362C<wiki> must be a L<Wiki::Toolkit> object and C<config> must be an
363L<OpenGuides::Config> object.  Both of these arguments are mandatory.
364C<og_version> is an optional argument specifying the version of
365OpenGuides for inclusion in the feed.
366
367=item B<rss_maker>
368
369Returns a raw L<Wiki::Toolkit::Feed::RSS> object created with the values you
370invoked this module with.
371
372=item B<atom_maker>
373
374Returns a raw L<Wiki::Toolkit::Feed::Atom> object created with the values you
375invoked this module with.
376
377=item B<make_feed>
378
379    # Ten most recent changes in RSS format.
380    my %args = ( items     => 10,
381                 feed_type => 'rss',
382                 also_return_timestamp => 1 );
383    my ($feed_output,$feed_timestamp) = $rdf_writer->make_feed( %args );
384
385    print "Content-Type: application/rdf+xml\n";
386    print "Last-Modified: " . $feed_timestamp . "\n\n";
387    print $feed_output;
388    print $rdf_writer->make_feed( %args );
389
390
391    # All the changes made by bob in the past week, ignoring minor edits, in Atom.
392    $args{days}               = 7;
393    $args{ignore_minor_edits  = 1;
394    $args{filter_on_metadata} => { username => "bob" };
395    $args{also_return_timestamp} => 1;
396
397    my ($feed_output,$feed_timestamp) = $rdf_writer->make_feed( %args );
398    print "Content-Type: application/atom+xml\n";
399    print "Last-Modified: " . $feed_timestamp . "\n\n";
400    print $feed_output;
401
402=item B<feed_timestamp>
403
404Instead of calling this, you should instead pass in the 'also_return_timestamp'
405option. You will then get back the feed timestamp, along with the feed output.
406
407This method will be removed in future, and currently will only return
408meaningful values if your arguments relate to recent changes.
409
410    print "Last-Modified: " . $feed->feed_timestamp( %args ) . "\n\n";
411
412Returns the timestamp of something in POSIX::strftime style ("Tue, 29 Feb 2000
41312:34:56 GMT"). Takes the same arguments as make_recentchanges_rss().
414You will most likely need this to print a Last-Modified HTTP header so
415user-agents can determine whether they need to reload the feed or not.
416
417=back
418
419=head1 SEE ALSO
420
421=over 4
422
423=item * L<Wiki::Toolkit>, L<Wiki::Toolkit::Feed::RSS> and L<Wiki::Toolkit::Feed::Atom>
424
425=item * L<http://openguides.org/>
426
427=back
428
429=head1 AUTHOR
430
431The OpenGuides Project (openguides-dev@lists.openguides.org)
432
433=head1 COPYRIGHT
434
435Copyright (C) 2003-2009 The OpenGuides Project.  All Rights Reserved.
436
437This module is free software; you can redistribute it and/or modify it
438under the same terms as Perl itself.
439
440=head1 CREDITS
441
442Written by Earle Martin, based on the original OpenGuides::RDF by Kake Pugh.
443
444=cut
445
4461;
Note: See TracBrowser for help on using the repository browser.