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

Last change on this file since 1034 was 1034, checked in by Dominic Hargreaves, 14 years ago

Pre-release bits

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