Author: earle Date: 2005-06-15 12:57:30 +0100 (Wed, 15 Jun 2005) New Revision: 652
Modified: trunk/Build.PL trunk/Changes trunk/lib/OpenGuides.pm trunk/lib/OpenGuides/RDF.pm Log: RSS timestamp
Modified: trunk/Build.PL =================================================================== --- trunk/Build.PL 2005-06-14 12:03:05 UTC (rev 651) +++ trunk/Build.PL 2005-06-15 11:57:30 UTC (rev 652) @@ -222,7 +222,7 @@ 'CGI::Wiki::Plugin::Categoriser' => 0, 'CGI::Wiki::Plugin::Diff' => '0.07', # earlier buggy 'CGI::Wiki::Plugin::Locator::Grid'=> '0.02', # cope with sqlite 3 - 'CGI::Wiki::Plugin::RSS::ModWiki' => '0.06', # for fancy stuff + 'CGI::Wiki::Plugin::RSS::ModWiki' => '0.071', # provides RSS timestamp 'CGI::Wiki::Plugin::RSS::Reader' => '1.3', # earlier versions don't support RSS 2.0 'Class::Accessor' => 0, 'Config::Tiny' => 0,
Modified: trunk/Changes =================================================================== --- trunk/Changes 2005-06-14 12:03:05 UTC (rev 651) +++ trunk/Changes 2005-06-15 11:57:30 UTC (rev 652) @@ -11,6 +11,8 @@ applications. Added owl:sameAs property to RDF output for nodes that are redirects to other nodes. + RSS feed now has correct timestamp (matching most recent item) and + matching Last-Modified HTTP header. Reorder navigation bar to provide more logical groupings. Add "format=plain" option for all-nodes index listing and associated template plain_index.tt.
Modified: trunk/lib/OpenGuides/RDF.pm =================================================================== --- trunk/lib/OpenGuides/RDF.pm 2005-06-14 12:03:05 UTC (rev 651) +++ trunk/lib/OpenGuides/RDF.pm 2005-06-15 11:57:30 UTC (rev 652) @@ -8,8 +8,230 @@ use CGI::Wiki::Plugin::RSS::ModWiki; use Time::Piece; use URI::Escape; -use Carp qw( croak ); +use Carp 'croak';
+sub new +{ + my ($class, @args) = @_; + my $self = {}; + bless $self, $class; + $self->_init(@args); +} + +sub _init +{ + my ($self, %args) = @_; + + my $wiki = $args{wiki}; + + unless ($wiki && UNIVERSAL::isa($wiki, "CGI::Wiki")) + { + croak "No CGI::Wiki object supplied."; + } + $self->{wiki} = $wiki; + + my $config = $args{config}; + unless ($config && UNIVERSAL::isa($config, "OpenGuides::Config")) + { + croak "No OpenGuides::Config object supplied."; + } + $self->{config} = $config; + + $self->{make_node_url} = + sub + { + my ($node_name, $version) = @_; + + my $config = $self->{config}; + + my $node_url = $config->script_url . uri_escape($config->script_name) . '?'; + $node_url .= 'id=' if defined $version; + $node_url .= uri_escape($self->{wiki}->formatter->node_name_to_node_param($node_name)); + $node_url .= '&version=' . uri_escape($version) if defined $version; + + $node_url; + }; + $self->{site_name} = $config->site_name; + $self->{default_city} = $config->default_city || ""; + $self->{default_country} = $config->default_country || ""; + $self->{site_description} = $config->site_desc || ""; + + $self; +} + +sub emit_rdfxml +{ + my ($self, %args) = @_; + + my $node_name = $args{node}; + my $wiki = $self->{wiki}; + + my %node_data = $wiki->retrieve_node( $node_name ); + my $phone = $node_data{metadata}{phone}[0] || ''; + my $fax = $node_data{metadata}{fax}[0] || ''; + my $website = $node_data{metadata}{website}[0] || ''; + my $opening_hours_text = $node_data{metadata}{opening_hours_text}[0] || ''; + my $postcode = $node_data{metadata}{postcode}[0] || ''; + my $city = $node_data{metadata}{city}[0] || $self->{default_city}; + my $country = $node_data{metadata}{country}[0] || $self->{default_country}; + my $latitude = $node_data{metadata}{latitude}[0] || ''; + my $longitude = $node_data{metadata}{longitude}[0] || ''; + my $version = $node_data{version}; + my $username = $node_data{metadata}{username}[0] || ''; + my $os_x = $node_data{metadata}{os_x}[0] || ''; + my $os_y = $node_data{metadata}{os_y}[0] || ''; + my $catrefs = $node_data{metadata}{category}; + my @locales = @{ $node_data{metadata}{locale} || [] }; + + # replace any errant characters in data to prevent illegal XML + foreach ($phone, $fax, $website, $opening_hours_text, $postcode, $city, $country, + $latitude, $longitude, $version, $os_x, $os_y, $catrefs, @locales) + { + if ($_) + { + $_ =~ s/&/&/g; + $_ =~ s/</</g; + $_ =~ s/>/>/g; + } + } + + my ($is_geospatial, $objType); + + if ($latitude || $longitude || $postcode || @locales) + { + $is_geospatial = 1; + $objType = 'geo:SpatialThing'; + } + else + { + $objType = 'rdf:Description'; + } + + my $timestamp = $node_data{last_modified}; + # Make a Time::Piece object. + my $timestamp_fmt = $CGI::Wiki::Store::Database::timestamp_fmt; + + if ($timestamp) + { + my $time = Time::Piece->strptime( $timestamp, $timestamp_fmt ); + $timestamp = $time->strftime( "%Y-%m-%dT%H:%M:%S" ); + } + + my $url = $self->{make_node_url}->($node_name, $version); + my $version_indpt_url = $self->{make_node_url}->($node_name); + + my $rdf = qq{<?xml version="1.0"?> + <rdf:RDF + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:dcterms="http://purl.org/dc/terms/" + xmlns:foaf="http://xmlns.com/foaf/0.1/" + xmlns:wiki="http://purl.org/rss/1.0/modules/wiki/" + xmlns:chefmoz="http://chefmoz.org/rdf/elements/1.0/" + xmlns:wn="http://xmlns.com/wordnet/1.6/" + xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" + xmlns:os="http://downlode.org/rdf/os/0.1/" + xmlns:owl="http://www.w3.org/2002/07/owl#" + xmlns="http://www.w3.org/2000/10/swap/pim/contact#" + > + + <rdf:Description rdf:about=""> + dc:title} . $self->{site_name} . qq{: $node_name</dc:title> + dc:date$timestamp</dc:date> + dcterms:modified$timestamp</dcterms:modified> + dc:contributor$username</dc:contributor> + <dc:source rdf:resource="$version_indpt_url" /> + wiki:version$version</wiki:version> + <foaf:topic rdf:resource="#obj" /> + </rdf:Description> + + <$objType rdf:ID="obj" dc:title="$node_name"> +}; + $rdf .= "\n <!-- categories -->\n\n" if $catrefs; + $rdf .= " dc:subject$_</dc:subject>\n" foreach @{$catrefs}; + $rdf .= "\n <!-- address and geospatial data -->\n\n" if $is_geospatial; + $rdf .= " <city>$city</city>\n" if $city && $is_geospatial; + $rdf .= " <postalCode>$postcode</postalCode>\n" if $postcode && $is_geospatial; + $rdf .= " <country>$country</country>\n" if $country && $is_geospatial; + + $rdf .= qq{ + foaf:based_near + wn:Neighborhood + foaf:name$_</foaf:name> + </wn:Neighborhood> + </foaf:based_near>\n} foreach @locales; + + if ($latitude && $longitude) + { + $rdf .= qq{ + geo:lat$latitude</geo:lat> + geo:long$longitude</geo:long>\n}; + } + + if ($os_x && $os_y) + { + $rdf .= qq{ + os:x$os_x</os:x> + os:y$os_y</os:y>}; + } + + $rdf .= "\n\n <!-- contact information -->\n\n" if ($phone || $fax || $website || $opening_hours_text); + $rdf .= " <phone>$phone</phone>\n" if $phone; + $rdf .= " <fax>$fax</fax>\n" if $fax; + $rdf .= " <foaf:homepage rdf:resource="$website" />\n" if $website; + $rdf .= " chefmoz:Hours$opening_hours_text</chefmoz:Hours>\n" if $opening_hours_text; + + if ($node_data{content} =~ /^#REDIRECT [[(.*?)]]$/) + { + my $redirect = $1; + + $rdf .= qq{ <owl:sameAs rdf:resource="} . $self->{config}->script_url + . uri_escape($self->{config}->script_name) . '?id=' + . uri_escape($wiki->formatter->node_name_to_node_param($redirect)) + . ';format=rdf#obj'; + $rdf .= qq{" />\n}; + } + + $rdf .= qq{ </$objType> +</rdf:RDF> + +}; + + return $rdf; +} + +sub rss_maker +{ + my $self = shift; + + unless ($self->{rss_maker}) # OAOO, please. + { + $self->{rss_maker} = CGI::Wiki::Plugin::RSS::ModWiki->new( + wiki => $self->{wiki}, + site_name => $self->{site_name}, + site_description => $self->{site_description}, + make_node_url => $self->{make_node_url}, + recent_changes_link => $self->{config}->script_url . uri_escape($self->{config}->script_name) . "?RecentChanges" + ); + } + + $self->{rss_maker}; +} + +sub make_recentchanges_rss +{ + my ($self, %args) = @_; + + $self->rss_maker->recent_changes(%args); +} + +sub rss_timestamp +{ + my ($self, %args) = @_; + + $self->rss_maker->rss_timestamp(%args); +} + =head1 NAME
OpenGuides::RDF - An OpenGuides plugin to output RDF/XML. @@ -30,14 +252,15 @@ my $wiki = CGI::Wiki->new( ... ); my $config = OpenGuides::Config->new( file => "wiki.conf" ); my $rdf_writer = OpenGuides::RDF->new( wiki => $wiki, - config => $config ); + config => $config );
# RDF version of a node. - print "Content-type: text/plain\n\n"; + print "Content-Type: text/plain\n\n"; print $rdf_writer->emit_rdfxml( node => "Masala Zone, N1 0NU" );
# Ten most recent changes. - print "Content-type: text/plain\n\n"; + print "Content-Type: text/plain\n"; + print "Last-Modified: " . $self->rss_timestamp( items => 10 ) . "\n\n"; print $rdf_writer->make_recentchanges_rss( items => 10 );
=head1 METHODS @@ -52,48 +275,7 @@ C<wiki> must be a LCGI::Wiki object and C<config> must be an LOpenGuides::Config object. Both arguments mandatory.
-=cut
-sub new { - my ($class, @args) = @_; - my $self = {}; - bless $self, $class; - return $self->_init(@args); -} - -sub _init { - my ($self, %args) = @_; - - my $wiki = $args{wiki}; - unless ( $wiki && UNIVERSAL::isa( $wiki, "CGI::Wiki" ) ) { - croak "No CGI::Wiki object supplied."; - } - $self->{wiki} = $wiki; - - my $config = $args{config}; - unless ( $config && UNIVERSAL::isa( $config, "OpenGuides::Config" ) ) { - croak "No OpenGuides::Config object supplied."; - } - $self->{config} = $config; - - $self->{site_name} = $config->site_name; - $self->{make_node_url} = sub { - my ($node_name, $version) = @_; - if ( defined $version ) { - return $config->script_url . uri_escape($config->script_name) . "?id=" . uri_escape($wiki->formatter->node_name_to_node_param($node_name)) . ";version=" . uri_escape($version); - } else { - return $config->script_url . uri_escape($config->script_name) . "?" . uri_escape($wiki->formatter->node_name_to_node_param($node_name)); - } - }; - $self->{default_city} = $config->default_city || ""; - $self->{default_country} = $config->default_country || ""; - $self->{site_description} = $config->site_desc || ""; - - return $self; -} - -=cut - =item B<emit_rdfxml>
$wiki->write_node( "Masala Zone, N1 0NU", @@ -101,11 +283,10 @@ $checksum, { comment => "New page", username => "Kake", - locale => "Islington" - } + locale => "Islington" } );
- print "Content-type: text/plain\n\n"; + print "Content-Type: text/plain\n\n"; print $rdf_writer->emit_rdfxml( node => "Masala Zone, N1 0NU" );
BNote: Some of the fields emitted by the RDF/XML generator are taken @@ -133,185 +314,40 @@
=back
-=cut +=item B<rss_maker>
-sub emit_rdfxml { - my ($self, %args) = @_; +Returns a raw LCGI::Wiki::Plugin::RSS::ModWiki object created with the values you +invoked this module with.
- my $node_name = $args{node}; - my $wiki = $self->{wiki}; - - my %node_data = $wiki->retrieve_node( $node_name ); - my $phone = $node_data{metadata}{phone}[0] || ''; - my $fax = $node_data{metadata}{fax}[0] || ''; - my $website = $node_data{metadata}{website}[0] || ''; - my $opening_hours_text = $node_data{metadata}{opening_hours_text}[0] || ''; - my $postcode = $node_data{metadata}{postcode}[0] || ''; - my $city = $node_data{metadata}{city}[0] || $self->{default_city}; - my $country = $node_data{metadata}{country}[0] || $self->{default_country}; - my $latitude = $node_data{metadata}{latitude}[0] || ''; - my $longitude = $node_data{metadata}{longitude}[0] || ''; - my $version = $node_data{version}; - my $username = $node_data{metadata}{username}[0] || ''; - my $os_x = $node_data{metadata}{os_x}[0] || ''; - my $os_y = $node_data{metadata}{os_y}[0] || ''; - my $catrefs = $node_data{metadata}{category}; - my @locales = @{ $node_data{metadata}{locale} || [] }; - - # replace any errant characters in data to prevent illegal XML - foreach ($phone, $fax, $website, $opening_hours_text, $postcode, $city, $country, - $latitude, $longitude, $version, $os_x, $os_y, $catrefs, @locales) - { - if ($_) - { - $_ =~ s/&/&/g; - $_ =~ s/</</g; - $_ =~ s/>/>/g; - } - } - - my ($is_geospatial, $objType); - - if ($latitude || $longitude || $postcode || @locales) - { - $is_geospatial = 1; - $objType = 'geo:SpatialThing'; - } - else - { - $objType = 'rdf:Description'; - } - - my $timestamp = $node_data{last_modified}; - # Make a Time::Piece object. - my $timestamp_fmt = $CGI::Wiki::Store::Database::timestamp_fmt; -# my $timestamp_fmt = $wiki->{store}->timestamp_fmt; - if ( $timestamp ) { - my $time = Time::Piece->strptime( $timestamp, $timestamp_fmt ); - $timestamp = $time->strftime( "%Y-%m-%dT%H:%M:%S" ); - } - - my $url = $self->{make_node_url}->( $node_name, $version ); - my $version_indpt_uri = $self->{make_node_url}->( $node_name ); - - my $rdf = qq{<?xml version="1.0"?> - <rdf:RDF - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:dcterms="http://purl.org/dc/terms/" - xmlns:foaf="http://xmlns.com/foaf/0.1/" - xmlns:wiki="http://purl.org/rss/1.0/modules/wiki/" - xmlns:chefmoz="http://chefmoz.org/rdf/elements/1.0/" - xmlns:wn="http://xmlns.com/wordnet/1.6/" - xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" - xmlns:os="http://downlode.org/rdf/os/0.1/" - xmlns:owl="http://www.w3.org/2002/07/owl#" - xmlns="http://www.w3.org/2000/10/swap/pim/contact#" - > - - <rdf:Description rdf:about=""> - dc:title} . $self->{site_name} . qq{: $node_name</dc:title> - dc:date$timestamp</dc:date> - dcterms:modified$timestamp</dcterms:modified> - dc:contributor$username</dc:contributor> - <dc:source rdf:resource="$version_indpt_uri" /> - wiki:version$version</wiki:version> - <foaf:topic rdf:resource="#obj" /> - </rdf:Description> - - <$objType rdf:ID="obj" dc:title="$node_name"> -}; - $rdf .= "\n <!-- categories -->\n\n" if $catrefs; - $rdf .= " dc:subject$_</dc:subject>\n" foreach @{$catrefs}; - $rdf .= "\n <!-- address and geospatial data -->\n\n" if $is_geospatial; - $rdf .= " <city>$city</city>\n" if $city && $is_geospatial; - $rdf .= " <postalCode>$postcode</postalCode>\n" if $postcode && $is_geospatial; - $rdf .= " <country>$country</country>\n" if $country && $is_geospatial; - - $rdf .= qq{ - foaf:based_near - wn:Neighborhood - foaf:name$_</foaf:name> - </wn:Neighborhood> - </foaf:based_near>\n} foreach @locales; - - if ($latitude && $longitude) { - $rdf .= qq{ - geo:lat$latitude</geo:lat> - geo:long$longitude</geo:long>\n}; - } - - if ($os_x && $os_y) { - $rdf .= qq{ - os:x$os_x</os:x> - os:y$os_y</os:y>}; - } - - $rdf .= "\n\n <!-- contact information -->\n\n" if ($phone || $fax || $website || $opening_hours_text); - $rdf .= " <phone>$phone</phone>\n" if $phone; - $rdf .= " <fax>$fax</fax>\n" if $fax; - $rdf .= " <foaf:homepage rdf:resource="$website" />\n" if $website; - $rdf .= " chefmoz:Hours$opening_hours_text</chefmoz:Hours>\n" if $opening_hours_text; - - if ($node_data{content} =~ /^#REDIRECT [[(.*?)]]$/) - { - my $redirect = $1; - - $rdf .= qq{ <owl:sameAs rdf:resource="} . $self->{config}->script_url - . uri_escape($self->{config}->script_name) . '?id=' - . uri_escape($wiki->formatter->node_name_to_node_param($redirect)) - . ';format=rdf#obj'; - $rdf .= qq{" />\n}; - - } - - $rdf .= qq{ </$objType> -</rdf:RDF> - -}; - - return $rdf; -} - =item B<make_recentchanges_rss>
# Ten most recent changes. - print "Content-type: text/plain\n\n"; - print $rdf_writer->make_recentchanges_rss( - items => 10, - ); + print "Content-Type: text/plain\n"; + print "Last-Modified: " . $rdf_writer->rss_timestamp( items => 10 ) . "\n\n"; + print $rdf_writer->make_recentchanges_rss( items => 10 );
# All the changes made by bob in the past week, ignoring minor edits. - print "Content-type: text/plain\n\n"; - print $rdf_writer->make_recentchanges_rss( - days => 7, - ignore_minor_edits => 1, - filter_on_metadata => { username => "bob" }, - );
-=cut + my %args = ( + days => 7, + ignore_minor_edits => 1, + filter_on_metadata => { username => "bob" }, + );
-sub make_recentchanges_rss { - my ($self, %args) = @_; + print "Content-Type: text/plain\n"; + print "Last-Modified: " . $rdf_writer->rss_timestamp( %args ) . "\n\n"; + print $rdf_writer->make_recentchanges_rss( %args );
- my $rssmaker = CGI::Wiki::Plugin::RSS::ModWiki->new( - wiki => $self->{wiki}, - site_name => $self->{site_name}, - site_description => $self->{site_description}, - make_node_url => $self->{make_node_url}, - recent_changes_link => $self->{config}->script_url . uri_escape($self->{config}->script_name) . "?RecentChanges" - ); +=item B<rss_timestamp>
- my %criteria; - $criteria{items} = $args{items} if $args{items}; - $criteria{days} = $args{days} if $args{days}; - $criteria{ignore_minor_changes} = $args{ignore_minor_edits} - if $args{ignore_minor_edits}; - $criteria{filter_on_metadata} = $args{filter_on_metadata} - if $args{filter_on_metadata}; - return $rssmaker->recent_changes( %criteria ); -} + print "Last-Modified: " . $rdf_writer->rss_timestamp( %args ) . "\n\n";
+Returns the timestamp of the RSS feed in POSIX::strftime style ("Tue, 29 Feb 2000 +12:34:56 GMT"), which is equivalent to the timestamp of the most recent item +in the feed. Takes the same arguments as make_recentchanges_rss(). You will most +likely need this to print a Last-Modified HTTP header so user-agents can determine +whether they need to reload the feed or not. + =back
=head1 SEE ALSO @@ -332,7 +368,7 @@
=head1 COPYRIGHT
- Copyright (C) 2003-2005 The OpenGuides Project. All Rights Reserved. +Copyright (C) 2003-2005 The OpenGuides Project. All Rights Reserved.
This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
Modified: trunk/lib/OpenGuides.pm =================================================================== --- trunk/lib/OpenGuides.pm 2005-06-14 12:03:05 UTC (rev 651) +++ trunk/lib/OpenGuides.pm 2005-06-15 11:57:30 UTC (rev 652) @@ -170,6 +170,7 @@ $redirect =~ s/^[[//; $redirect =~ s/]]\s*$//; # See if this is a valid node, if not then just show the page as-is. + # Avoid loops by not generating redirects to the same node or the # previous node. if ( $wiki->node_exists($redirect) && $redirect != $id && $redirect != $oldid ) { @@ -619,7 +620,8 @@
my $rdf_writer = OpenGuides::RDF->new( wiki => $self->wiki, config => $self->config ); - my $output = "Content-type: text/plain\n\n"; + my $output = "Content-Type: text/plain\n"; + $output .= "Last-Modified: " . $rdf_writer->rss_timestamp( %criteria ) . "\n\n"; $output .= $rdf_writer->make_recentchanges_rss( %criteria ); return $output if $return_output; print $output; @@ -854,18 +856,19 @@
sub redirect_to_node { my ($self, $node, $redirect) = @_; + my $script_url = $self->config->script_url; my $script_name = $self->config->script_name; my $formatter = $self->wiki->formatter; + my $id = $formatter->node_name_to_node_param( $node ); - my $oldid; $oldid = $formatter->node_name_to_node_param( $redirect ) if $redirect; - + my $redir_param =''; $redir_param = "&oldid=$oldid" if $oldid; - - return CGI->redirect( "$script_url$script_name?id=$id$redir_param" ); + + return CGI->redirect( "$script_url$script_name?id=$id$redir_param" ); }
sub get_cookie {
openguides-commits@lists.openguides.org