source: trunk/lib/OpenGuides/Feed.pm @ 820

Last change on this file since 820 was 820, checked in by nick, 16 years ago

Make it possible for a feed to override the default feed title and self url, and have the feeds do so

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