File Coverage

blib/lib/OpenGuides/Feed.pm
Criterion Covered Total %
statement 108 113 95.5
branch 31 40 77.5
condition 7 15 46.6
subroutine 20 21 95.2
pod 11 11 100.0
total 177 200 88.5


line stmt bran cond sub pod time code
1             package OpenGuides::Feed;
2              
3 91     91   442 use strict;
  91         146  
  91         3931  
4              
5 91     91   441 use vars qw( $VERSION );
  91         528  
  91         6421  
6             $VERSION = '0.03';
7              
8 91     91   58042 use Wiki::Toolkit::Feed::Atom;
  91         2150944  
  91         2924  
9 91     91   56341 use Wiki::Toolkit::Feed::RSS;
  91         188002  
  91         2886  
10 91     91   646 use Time::Piece;
  91         158  
  91         476  
11 91     91   5794 use URI::Escape;
  91         157  
  91         5240  
12 91     91   475 use Carp 'croak';
  91         140  
  91         4132  
13              
14 91     91   471 use base qw( Class::Accessor );
  91         132  
  91         75918  
15             # Add more here if we need them - this one added for testing purposes.
16             my @variables = qw( html_equiv_link );
17             OpenGuides::Feed->mk_accessors( @variables );
18              
19             sub new {
20 21     21 1 148 my ($class, @args) = @_;
21 21         40 my $self = {};
22 21         56 bless $self, $class;
23 21         82 $self->_init(@args);
24             }
25              
26             sub _init {
27 21     21   69 my ($self, %args) = @_;
28              
29 21         40 my $wiki = $args{wiki};
30              
31 21 50 33     194 unless ( $wiki && UNIVERSAL::isa( $wiki, "Wiki::Toolkit" ) ) {
32 0         0 croak "No Wiki::Toolkit object supplied.";
33             }
34 21         99 $self->{wiki} = $wiki;
35              
36 21         37 my $config = $args{config};
37              
38 21 50 33     154 unless ( $config && UNIVERSAL::isa( $config, "OpenGuides::Config" ) ) {
39 0         0 croak "No OpenGuides::Config object supplied.";
40             }
41 21         51 $self->{config} = $config;
42              
43             $self->{make_node_url} = sub {
44 36     36   14886 my ($node_name, $version) = @_;
45              
46 36         65 my $config = $self->{config};
47              
48 36         126 my $node_url = $config->script_url . uri_escape($config->script_name) . '?';
49 36 100       966 $node_url .= 'id=' if defined $version;
50 36         145 $node_url .= uri_escape($self->{wiki}->formatter->node_name_to_node_param($node_name));
51 36 100       1380 $node_url .= ';version=' . uri_escape($version) if defined $version;
52              
53 36         233 $node_url;
54 21         151 };
55 21         179 $self->{site_name} = $config->site_name . " - Recent Changes";
56 21   50     296 $self->{default_city} = $config->default_city || "";
57 21   50     286 $self->{default_country} = $config->default_country || "";
58 21   50     265 $self->{site_description} = $config->site_desc || "";
59 21         228 $self->{og_version} = $args{og_version};
60 21         138 $self->{html_equiv_link} = $self->{config}->script_url
61             . $self->{config}->script_name . '?action=rc';
62              
63 21         325 $self;
64             }
65              
66             =over 4
67              
68             =item B
69             Overrides the default feed name and default feed http equivalent url.
70             Useful 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              
77             sub set_feed_name_and_url_params {
78 8     8 1 16 my ($self, $name, $url) = @_;
79              
80 8 50       31 unless($url =~ /^http/) {
81 8         28 my $b_url = $self->{config}->script_url;
82 8 100       30 unless($url =~ /\.cgi\?/) { $b_url .= "?"; }
  6         15  
83 8         15 $b_url .= $url;
84 8         13 $url = $b_url;
85             }
86              
87 8         27 $self->{site_name} = $self->{config}->{site_name} . " - " . $name;
88 8         19 $self->{html_equiv_link} = $url;
89             }
90              
91             =item B
92              
93             Produce one of the standard feeds, in the requested format.
94              
95              
96             my $feed_contents = feeds->make_feed(
97             feed_type => 'rss',
98             feed_listing => 'recent_changes'
99             );
100              
101             Passes additional arguments through to the underlying Wiki::Toolkit::Feed
102              
103             =cut
104              
105             sub make_feed {
106 12     12 1 832 my ($self, %args) = @_;
107              
108 12         25 my $feed_type = $args{feed_type};
109 12         23 my $feed_listing = $args{feed_listing};
110              
111 12         43 my %known_listings = (
112             'recent_changes' => 1,
113             'node_all_versions' => 1,
114             );
115              
116 12 50       40 croak "No feed listing specified" unless $feed_listing;
117 12 50       39 croak "Unknown feed listing: $feed_listing" unless $known_listings{$feed_listing};
118              
119              
120             # Tweak any settings, as required by our feed listing
121 12 100       37 if ($feed_listing eq 'node_all_versions') {
122 4         16 $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 12         39 my $maker = $self->fetch_maker($feed_type);
131              
132              
133             # Call the appropriate feed listing from it
134 12 100       55 if ($feed_listing eq 'recent_changes') {
    50          
135 8         79 return $maker->recent_changes(%args);
136             }
137             elsif ($feed_listing eq 'node_all_versions') {
138 4         35 return $maker->node_all_versions(%args);
139             }
140             }
141              
142             =item B
143              
144             For the given feed type, build a feed from the supplied list of nodes.
145             Will figure out the feed timestamp from the newest node, and output a
146             last modified header based on this.
147              
148             my @nodes = $wiki->fetch_me_nodes_I_like();
149             my $feed_contents = $feed->build_feed_for_nodes("rss", @nodes);
150              
151             =cut
152              
153             sub build_feed_for_nodes {
154 4     4 1 19770 my ($self, $format, @nodes) = @_;
155 4         20 return $self->render_feed_for_nodes($format, undef, 1, @nodes);
156             }
157              
158             =item B
159              
160             For the given feed type, build a mini feed (name and distance) from the
161             supplied list of nodes.
162             Will figure out the feed timestamp from the newest node, and output a
163             last modified header based on this.
164              
165             my @nodes = $wiki->search_near_here();
166             my $feed_contents = $feed->build_mini_feed_for_nodes("rss", @nodes);
167              
168             =cut
169              
170             sub build_mini_feed_for_nodes {
171 4     4 1 6568 my ($self, $format, @nodes) = @_;
172 4         20 return $self->render_feed_for_nodes($format, undef, 0, @nodes);
173             }
174              
175             =item B
176              
177             Normally internal method to perform the appropriate building of a feed
178             based on a list of nodes.
179              
180             =cut
181              
182             sub render_feed_for_nodes {
183 8     8 1 21 my ($self, $format, $html_url, $is_full, @nodes) = @_;
184              
185             # Grab our feed maker
186 8         26 my $maker = $self->fetch_maker($format);
187              
188             # Find our newest node, so we can use that for the feed timestamp
189 8         15 my $newest_node;
190 8         19 foreach my $node (@nodes) {
191 17 100       49 if($node->{last_modified}) {
192 14 100 66     81 if((!$newest_node) || ($node->{last_modified} gt $newest_node->{last_modified})) {
193 6         12 $newest_node = $node;
194             }
195             }
196             }
197              
198             # Grab the timestamp, and do our header
199 8         45 my $timestamp = $maker->feed_timestamp($newest_node);
200              
201 8         658 my $feed = "Last-Modified: ".$timestamp."\n\n";
202              
203             # Generate the feed itself
204 8 100       27 if($is_full) {
205 4         22 $feed .= $maker->generate_node_list_feed($timestamp, @nodes);
206             } else {
207 4         21 $feed .= $maker->generate_node_name_distance_feed($timestamp, @nodes);
208             }
209              
210 8         739 return $feed;
211             }
212              
213             =item B
214              
215             For the given feed type, return the default content type for that feed.
216              
217             my $content_type = $feed->default_content_type("rss");
218              
219             =cut
220              
221             sub default_content_type {
222 12     12 1 31 my ($self,$feed_type) = @_;
223              
224 12         19 my $content_type;
225              
226 12 100       50 if ($feed_type eq 'rss') {
    50          
227 6         12 $content_type = "application/rdf+xml";
228             }
229             elsif ($feed_type eq 'atom') {
230 6         13 $content_type = "application/atom+xml";
231             }
232             else {
233 0         0 croak "Unknown feed type given: $feed_type";
234             }
235              
236 12         43 return $content_type;
237             }
238              
239             =item B
240              
241             For the given feed type, identify and return the maker routine for feeds
242             of that type.
243              
244             my $maker = $feed->fetch_maker("rss");
245             my $feed_contents = maker->node_all_versions(%options);
246              
247             Will always return something of type Wiki::Toolkit::Feed::Listing
248              
249             =cut
250              
251             sub fetch_maker {
252 26     26 1 46 my ($self,$feed_type) = @_;
253              
254 26         179 my %known_types = (
255             'atom' => \&atom_maker,
256             'rss' => \&rss_maker,
257             );
258              
259 26 50       72 croak "No feed type specified" unless $feed_type;
260 26 50       80 croak "Unknown feed type: $feed_type" unless $known_types{$feed_type};
261              
262 26         33 return &{$known_types{$feed_type}};
  26         67  
263             }
264              
265             sub atom_maker {
266 13     13 1 26 my $self = shift;
267              
268 13 100       48 unless ($self->{atom_maker}) {
269 8         41 $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 13         2043 $self->{atom_maker};
285             }
286              
287             sub rss_maker {
288 13     13 1 18 my $self = shift;
289              
290 13 100       54 unless ($self->{rss_maker}) {
291 8         45 $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 13         2078 $self->{rss_maker};
306             }
307              
308             sub feed_timestamp {
309 0     0 1   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 0           $self->rss_maker->rss_timestamp(%args);
315             }
316              
317             =back
318              
319             =head1 NAME
320              
321             OpenGuides::Feed - generate data feeds for OpenGuides in various formats.
322              
323             =head1 DESCRIPTION
324              
325             Produces RSS 1.0 and Atom 1.0 feeds for OpenGuides. Distributed and
326             installed as part of the OpenGuides project, not intended for independent
327             installation. This documentation is probably only useful to OpenGuides
328             developers.
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
357              
358             my $feed = OpenGuides::Feed->new( wiki => $wiki,
359             config => $config,
360             og_version => '1.0', );
361              
362             C must be a L object and C must be an
363             L object. Both of these arguments are mandatory.
364             C is an optional argument specifying the version of
365             OpenGuides for inclusion in the feed.
366              
367             =item B
368              
369             Returns a raw L object created with the values you
370             invoked this module with.
371              
372             =item B
373              
374             Returns a raw L object created with the values you
375             invoked this module with.
376              
377             =item B
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
403              
404             Instead of calling this, you should instead pass in the 'also_return_timestamp'
405             option. You will then get back the feed timestamp, along with the feed output.
406              
407             This method will be removed in future, and currently will only return
408             meaningful values if your arguments relate to recent changes.
409              
410             print "Last-Modified: " . $feed->feed_timestamp( %args ) . "\n\n";
411              
412             Returns the timestamp of something in POSIX::strftime style ("Tue, 29 Feb 2000
413             12:34:56 GMT"). Takes the same arguments as make_recentchanges_rss().
414             You will most likely need this to print a Last-Modified HTTP header so
415             user-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, L and L
424              
425             =item * L
426              
427             =back
428              
429             =head1 AUTHOR
430              
431             The OpenGuides Project (openguides-dev@lists.openguides.org)
432              
433             =head1 COPYRIGHT
434              
435             Copyright (C) 2003-2012 The OpenGuides Project. All Rights Reserved.
436              
437             This module is free software; you can redistribute it and/or modify it
438             under the same terms as Perl itself.
439              
440             =head1 CREDITS
441              
442             Written by Earle Martin, based on the original OpenGuides::RDF by Kake Pugh.
443              
444             =cut
445              
446             1;