| 11 | | use Carp qw( croak ); |
| | 11 | use Carp 'croak'; |
| | 12 | |
| | 13 | sub new |
| | 14 | { |
| | 15 | my ($class, @args) = @_; |
| | 16 | my $self = {}; |
| | 17 | bless $self, $class; |
| | 18 | $self->_init(@args); |
| | 19 | } |
| | 20 | |
| | 21 | sub _init |
| | 22 | { |
| | 23 | my ($self, %args) = @_; |
| | 24 | |
| | 25 | my $wiki = $args{wiki}; |
| | 26 | |
| | 27 | unless ($wiki && UNIVERSAL::isa($wiki, "CGI::Wiki")) |
| | 28 | { |
| | 29 | croak "No CGI::Wiki object supplied."; |
| | 30 | } |
| | 31 | $self->{wiki} = $wiki; |
| | 32 | |
| | 33 | my $config = $args{config}; |
| | 34 | unless ($config && UNIVERSAL::isa($config, "OpenGuides::Config")) |
| | 35 | { |
| | 36 | croak "No OpenGuides::Config object supplied."; |
| | 37 | } |
| | 38 | $self->{config} = $config; |
| | 39 | |
| | 40 | $self->{make_node_url} = |
| | 41 | sub |
| | 42 | { |
| | 43 | my ($node_name, $version) = @_; |
| | 44 | |
| | 45 | my $config = $self->{config}; |
| | 46 | |
| | 47 | my $node_url = $config->script_url . uri_escape($config->script_name) . '?'; |
| | 48 | $node_url .= 'id=' if defined $version; |
| | 49 | $node_url .= uri_escape($self->{wiki}->formatter->node_name_to_node_param($node_name)); |
| | 50 | $node_url .= '&version=' . uri_escape($version) if defined $version; |
| | 51 | |
| | 52 | $node_url; |
| | 53 | }; |
| | 54 | $self->{site_name} = $config->site_name; |
| | 55 | $self->{default_city} = $config->default_city || ""; |
| | 56 | $self->{default_country} = $config->default_country || ""; |
| | 57 | $self->{site_description} = $config->site_desc || ""; |
| | 58 | |
| | 59 | $self; |
| | 60 | } |
| | 61 | |
| | 62 | sub emit_rdfxml |
| | 63 | { |
| | 64 | my ($self, %args) = @_; |
| | 65 | |
| | 66 | my $node_name = $args{node}; |
| | 67 | my $wiki = $self->{wiki}; |
| | 68 | |
| | 69 | my %node_data = $wiki->retrieve_node( $node_name ); |
| | 70 | my $phone = $node_data{metadata}{phone}[0] || ''; |
| | 71 | my $fax = $node_data{metadata}{fax}[0] || ''; |
| | 72 | my $website = $node_data{metadata}{website}[0] || ''; |
| | 73 | my $opening_hours_text = $node_data{metadata}{opening_hours_text}[0] || ''; |
| | 74 | my $postcode = $node_data{metadata}{postcode}[0] || ''; |
| | 75 | my $city = $node_data{metadata}{city}[0] || $self->{default_city}; |
| | 76 | my $country = $node_data{metadata}{country}[0] || $self->{default_country}; |
| | 77 | my $latitude = $node_data{metadata}{latitude}[0] || ''; |
| | 78 | my $longitude = $node_data{metadata}{longitude}[0] || ''; |
| | 79 | my $version = $node_data{version}; |
| | 80 | my $username = $node_data{metadata}{username}[0] || ''; |
| | 81 | my $os_x = $node_data{metadata}{os_x}[0] || ''; |
| | 82 | my $os_y = $node_data{metadata}{os_y}[0] || ''; |
| | 83 | my $catrefs = $node_data{metadata}{category}; |
| | 84 | my @locales = @{ $node_data{metadata}{locale} || [] }; |
| | 85 | |
| | 86 | # replace any errant characters in data to prevent illegal XML |
| | 87 | foreach ($phone, $fax, $website, $opening_hours_text, $postcode, $city, $country, |
| | 88 | $latitude, $longitude, $version, $os_x, $os_y, $catrefs, @locales) |
| | 89 | { |
| | 90 | if ($_) |
| | 91 | { |
| | 92 | $_ =~ s/&/&/g; |
| | 93 | $_ =~ s/</</g; |
| | 94 | $_ =~ s/>/>/g; |
| | 95 | } |
| | 96 | } |
| | 97 | |
| | 98 | my ($is_geospatial, $objType); |
| | 99 | |
| | 100 | if ($latitude || $longitude || $postcode || @locales) |
| | 101 | { |
| | 102 | $is_geospatial = 1; |
| | 103 | $objType = 'geo:SpatialThing'; |
| | 104 | } |
| | 105 | else |
| | 106 | { |
| | 107 | $objType = 'rdf:Description'; |
| | 108 | } |
| | 109 | |
| | 110 | my $timestamp = $node_data{last_modified}; |
| | 111 | # Make a Time::Piece object. |
| | 112 | my $timestamp_fmt = $CGI::Wiki::Store::Database::timestamp_fmt; |
| | 113 | |
| | 114 | if ($timestamp) |
| | 115 | { |
| | 116 | my $time = Time::Piece->strptime( $timestamp, $timestamp_fmt ); |
| | 117 | $timestamp = $time->strftime( "%Y-%m-%dT%H:%M:%S" ); |
| | 118 | } |
| | 119 | |
| | 120 | my $url = $self->{make_node_url}->($node_name, $version); |
| | 121 | my $version_indpt_url = $self->{make_node_url}->($node_name); |
| | 122 | |
| | 123 | my $rdf = qq{<?xml version="1.0"?> |
| | 124 | <rdf:RDF |
| | 125 | xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" |
| | 126 | xmlns:dc="http://purl.org/dc/elements/1.1/" |
| | 127 | xmlns:dcterms="http://purl.org/dc/terms/" |
| | 128 | xmlns:foaf="http://xmlns.com/foaf/0.1/" |
| | 129 | xmlns:wiki="http://purl.org/rss/1.0/modules/wiki/" |
| | 130 | xmlns:chefmoz="http://chefmoz.org/rdf/elements/1.0/" |
| | 131 | xmlns:wn="http://xmlns.com/wordnet/1.6/" |
| | 132 | xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" |
| | 133 | xmlns:os="http://downlode.org/rdf/os/0.1/" |
| | 134 | xmlns:owl="http://www.w3.org/2002/07/owl#" |
| | 135 | xmlns="http://www.w3.org/2000/10/swap/pim/contact#" |
| | 136 | > |
| | 137 | |
| | 138 | <rdf:Description rdf:about=""> |
| | 139 | <dc:title>} . $self->{site_name} . qq{: $node_name</dc:title> |
| | 140 | <dc:date>$timestamp</dc:date> |
| | 141 | <dcterms:modified>$timestamp</dcterms:modified> |
| | 142 | <dc:contributor>$username</dc:contributor> |
| | 143 | <dc:source rdf:resource="$version_indpt_url" /> |
| | 144 | <wiki:version>$version</wiki:version> |
| | 145 | <foaf:topic rdf:resource="#obj" /> |
| | 146 | </rdf:Description> |
| | 147 | |
| | 148 | <$objType rdf:ID="obj" dc:title="$node_name"> |
| | 149 | }; |
| | 150 | $rdf .= "\n <!-- categories -->\n\n" if $catrefs; |
| | 151 | $rdf .= " <dc:subject>$_</dc:subject>\n" foreach @{$catrefs}; |
| | 152 | $rdf .= "\n <!-- address and geospatial data -->\n\n" if $is_geospatial; |
| | 153 | $rdf .= " <city>$city</city>\n" if $city && $is_geospatial; |
| | 154 | $rdf .= " <postalCode>$postcode</postalCode>\n" if $postcode && $is_geospatial; |
| | 155 | $rdf .= " <country>$country</country>\n" if $country && $is_geospatial; |
| | 156 | |
| | 157 | $rdf .= qq{ |
| | 158 | <foaf:based_near> |
| | 159 | <wn:Neighborhood> |
| | 160 | <foaf:name>$_</foaf:name> |
| | 161 | </wn:Neighborhood> |
| | 162 | </foaf:based_near>\n} foreach @locales; |
| | 163 | |
| | 164 | if ($latitude && $longitude) |
| | 165 | { |
| | 166 | $rdf .= qq{ |
| | 167 | <geo:lat>$latitude</geo:lat> |
| | 168 | <geo:long>$longitude</geo:long>\n}; |
| | 169 | } |
| | 170 | |
| | 171 | if ($os_x && $os_y) |
| | 172 | { |
| | 173 | $rdf .= qq{ |
| | 174 | <os:x>$os_x</os:x> |
| | 175 | <os:y>$os_y</os:y>}; |
| | 176 | } |
| | 177 | |
| | 178 | $rdf .= "\n\n <!-- contact information -->\n\n" if ($phone || $fax || $website || $opening_hours_text); |
| | 179 | $rdf .= " <phone>$phone</phone>\n" if $phone; |
| | 180 | $rdf .= " <fax>$fax</fax>\n" if $fax; |
| | 181 | $rdf .= " <foaf:homepage rdf:resource=\"$website\" />\n" if $website; |
| | 182 | $rdf .= " <chefmoz:Hours>$opening_hours_text</chefmoz:Hours>\n" if $opening_hours_text; |
| | 183 | |
| | 184 | if ($node_data{content} =~ /^\#REDIRECT \[\[(.*?)]\]$/) |
| | 185 | { |
| | 186 | my $redirect = $1; |
| | 187 | |
| | 188 | $rdf .= qq{ <owl:sameAs rdf:resource="} . $self->{config}->script_url |
| | 189 | . uri_escape($self->{config}->script_name) . '?id=' |
| | 190 | . uri_escape($wiki->formatter->node_name_to_node_param($redirect)) |
| | 191 | . ';format=rdf#obj'; |
| | 192 | $rdf .= qq{" />\n}; |
| | 193 | } |
| | 194 | |
| | 195 | $rdf .= qq{ </$objType> |
| | 196 | </rdf:RDF> |
| | 197 | |
| | 198 | }; |
| | 199 | |
| | 200 | return $rdf; |
| | 201 | } |
| | 202 | |
| | 203 | sub rss_maker |
| | 204 | { |
| | 205 | my $self = shift; |
| | 206 | |
| | 207 | unless ($self->{rss_maker}) # OAOO, please. |
| | 208 | { |
| | 209 | $self->{rss_maker} = CGI::Wiki::Plugin::RSS::ModWiki->new( |
| | 210 | wiki => $self->{wiki}, |
| | 211 | site_name => $self->{site_name}, |
| | 212 | site_description => $self->{site_description}, |
| | 213 | make_node_url => $self->{make_node_url}, |
| | 214 | recent_changes_link => $self->{config}->script_url . uri_escape($self->{config}->script_name) . "?RecentChanges" |
| | 215 | ); |
| | 216 | } |
| | 217 | |
| | 218 | $self->{rss_maker}; |
| | 219 | } |
| | 220 | |
| | 221 | sub make_recentchanges_rss |
| | 222 | { |
| | 223 | my ($self, %args) = @_; |
| | 224 | |
| | 225 | $self->rss_maker->recent_changes(%args); |
| | 226 | } |
| | 227 | |
| | 228 | sub rss_timestamp |
| | 229 | { |
| | 230 | my ($self, %args) = @_; |
| | 231 | |
| | 232 | $self->rss_maker->rss_timestamp(%args); |
| | 233 | } |
| 136 | | =cut |
| 137 | | |
| 138 | | sub emit_rdfxml { |
| 139 | | my ($self, %args) = @_; |
| 140 | | |
| 141 | | my $node_name = $args{node}; |
| 142 | | my $wiki = $self->{wiki}; |
| 143 | | |
| 144 | | my %node_data = $wiki->retrieve_node( $node_name ); |
| 145 | | my $phone = $node_data{metadata}{phone}[0] || ''; |
| 146 | | my $fax = $node_data{metadata}{fax}[0] || ''; |
| 147 | | my $website = $node_data{metadata}{website}[0] || ''; |
| 148 | | my $opening_hours_text = $node_data{metadata}{opening_hours_text}[0] || ''; |
| 149 | | my $postcode = $node_data{metadata}{postcode}[0] || ''; |
| 150 | | my $city = $node_data{metadata}{city}[0] || $self->{default_city}; |
| 151 | | my $country = $node_data{metadata}{country}[0] || $self->{default_country}; |
| 152 | | my $latitude = $node_data{metadata}{latitude}[0] || ''; |
| 153 | | my $longitude = $node_data{metadata}{longitude}[0] || ''; |
| 154 | | my $version = $node_data{version}; |
| 155 | | my $username = $node_data{metadata}{username}[0] || ''; |
| 156 | | my $os_x = $node_data{metadata}{os_x}[0] || ''; |
| 157 | | my $os_y = $node_data{metadata}{os_y}[0] || ''; |
| 158 | | my $catrefs = $node_data{metadata}{category}; |
| 159 | | my @locales = @{ $node_data{metadata}{locale} || [] }; |
| 160 | | |
| 161 | | # replace any errant characters in data to prevent illegal XML |
| 162 | | foreach ($phone, $fax, $website, $opening_hours_text, $postcode, $city, $country, |
| 163 | | $latitude, $longitude, $version, $os_x, $os_y, $catrefs, @locales) |
| 164 | | { |
| 165 | | if ($_) |
| 166 | | { |
| 167 | | $_ =~ s/&/&/g; |
| 168 | | $_ =~ s/</</g; |
| 169 | | $_ =~ s/>/>/g; |
| 170 | | } |
| 171 | | } |
| 172 | | |
| 173 | | my ($is_geospatial, $objType); |
| 174 | | |
| 175 | | if ($latitude || $longitude || $postcode || @locales) |
| 176 | | { |
| 177 | | $is_geospatial = 1; |
| 178 | | $objType = 'geo:SpatialThing'; |
| 179 | | } |
| 180 | | else |
| 181 | | { |
| 182 | | $objType = 'rdf:Description'; |
| 183 | | } |
| 184 | | |
| 185 | | my $timestamp = $node_data{last_modified}; |
| 186 | | # Make a Time::Piece object. |
| 187 | | my $timestamp_fmt = $CGI::Wiki::Store::Database::timestamp_fmt; |
| 188 | | # my $timestamp_fmt = $wiki->{store}->timestamp_fmt; |
| 189 | | if ( $timestamp ) { |
| 190 | | my $time = Time::Piece->strptime( $timestamp, $timestamp_fmt ); |
| 191 | | $timestamp = $time->strftime( "%Y-%m-%dT%H:%M:%S" ); |
| 192 | | } |
| 193 | | |
| 194 | | my $url = $self->{make_node_url}->( $node_name, $version ); |
| 195 | | my $version_indpt_uri = $self->{make_node_url}->( $node_name ); |
| 196 | | |
| 197 | | my $rdf = qq{<?xml version="1.0"?> |
| 198 | | <rdf:RDF |
| 199 | | xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" |
| 200 | | xmlns:dc="http://purl.org/dc/elements/1.1/" |
| 201 | | xmlns:dcterms="http://purl.org/dc/terms/" |
| 202 | | xmlns:foaf="http://xmlns.com/foaf/0.1/" |
| 203 | | xmlns:wiki="http://purl.org/rss/1.0/modules/wiki/" |
| 204 | | xmlns:chefmoz="http://chefmoz.org/rdf/elements/1.0/" |
| 205 | | xmlns:wn="http://xmlns.com/wordnet/1.6/" |
| 206 | | xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" |
| 207 | | xmlns:os="http://downlode.org/rdf/os/0.1/" |
| 208 | | xmlns:owl="http://www.w3.org/2002/07/owl#" |
| 209 | | xmlns="http://www.w3.org/2000/10/swap/pim/contact#" |
| 210 | | > |
| 211 | | |
| 212 | | <rdf:Description rdf:about=""> |
| 213 | | <dc:title>} . $self->{site_name} . qq{: $node_name</dc:title> |
| 214 | | <dc:date>$timestamp</dc:date> |
| 215 | | <dcterms:modified>$timestamp</dcterms:modified> |
| 216 | | <dc:contributor>$username</dc:contributor> |
| 217 | | <dc:source rdf:resource="$version_indpt_uri" /> |
| 218 | | <wiki:version>$version</wiki:version> |
| 219 | | <foaf:topic rdf:resource="#obj" /> |
| 220 | | </rdf:Description> |
| 221 | | |
| 222 | | <$objType rdf:ID="obj" dc:title="$node_name"> |
| 223 | | }; |
| 224 | | $rdf .= "\n <!-- categories -->\n\n" if $catrefs; |
| 225 | | $rdf .= " <dc:subject>$_</dc:subject>\n" foreach @{$catrefs}; |
| 226 | | $rdf .= "\n <!-- address and geospatial data -->\n\n" if $is_geospatial; |
| 227 | | $rdf .= " <city>$city</city>\n" if $city && $is_geospatial; |
| 228 | | $rdf .= " <postalCode>$postcode</postalCode>\n" if $postcode && $is_geospatial; |
| 229 | | $rdf .= " <country>$country</country>\n" if $country && $is_geospatial; |
| 230 | | |
| 231 | | $rdf .= qq{ |
| 232 | | <foaf:based_near> |
| 233 | | <wn:Neighborhood> |
| 234 | | <foaf:name>$_</foaf:name> |
| 235 | | </wn:Neighborhood> |
| 236 | | </foaf:based_near>\n} foreach @locales; |
| 237 | | |
| 238 | | if ($latitude && $longitude) { |
| 239 | | $rdf .= qq{ |
| 240 | | <geo:lat>$latitude</geo:lat> |
| 241 | | <geo:long>$longitude</geo:long>\n}; |
| 242 | | } |
| 243 | | |
| 244 | | if ($os_x && $os_y) { |
| 245 | | $rdf .= qq{ |
| 246 | | <os:x>$os_x</os:x> |
| 247 | | <os:y>$os_y</os:y>}; |
| 248 | | } |
| 249 | | |
| 250 | | $rdf .= "\n\n <!-- contact information -->\n\n" if ($phone || $fax || $website || $opening_hours_text); |
| 251 | | $rdf .= " <phone>$phone</phone>\n" if $phone; |
| 252 | | $rdf .= " <fax>$fax</fax>\n" if $fax; |
| 253 | | $rdf .= " <foaf:homepage rdf:resource=\"$website\" />\n" if $website; |
| 254 | | $rdf .= " <chefmoz:Hours>$opening_hours_text</chefmoz:Hours>\n" if $opening_hours_text; |
| 255 | | |
| 256 | | if ($node_data{content} =~ /^\#REDIRECT \[\[(.*?)]\]$/) |
| 257 | | { |
| 258 | | my $redirect = $1; |
| 259 | | |
| 260 | | $rdf .= qq{ <owl:sameAs rdf:resource="} . $self->{config}->script_url |
| 261 | | . uri_escape($self->{config}->script_name) . '?id=' |
| 262 | | . uri_escape($wiki->formatter->node_name_to_node_param($redirect)) |
| 263 | | . ';format=rdf#obj'; |
| 264 | | $rdf .= qq{" />\n}; |
| 265 | | |
| 266 | | } |
| 267 | | |
| 268 | | $rdf .= qq{ </$objType> |
| 269 | | </rdf:RDF> |
| 270 | | |
| 271 | | }; |
| 272 | | |
| 273 | | return $rdf; |
| 274 | | } |
| | 317 | =item B<rss_maker> |
| | 318 | |
| | 319 | Returns a raw L<CGI::Wiki::Plugin::RSS::ModWiki> object created with the values you |
| | 320 | invoked this module with. |
| 285 | | print "Content-type: text/plain\n\n"; |
| 286 | | print $rdf_writer->make_recentchanges_rss( |
| 287 | | days => 7, |
| 288 | | ignore_minor_edits => 1, |
| 289 | | filter_on_metadata => { username => "bob" }, |
| 290 | | ); |
| 291 | | |
| 292 | | =cut |
| 293 | | |
| 294 | | sub make_recentchanges_rss { |
| 295 | | my ($self, %args) = @_; |
| 296 | | |
| 297 | | my $rssmaker = CGI::Wiki::Plugin::RSS::ModWiki->new( |
| 298 | | wiki => $self->{wiki}, |
| 299 | | site_name => $self->{site_name}, |
| 300 | | site_description => $self->{site_description}, |
| 301 | | make_node_url => $self->{make_node_url}, |
| 302 | | recent_changes_link => $self->{config}->script_url . uri_escape($self->{config}->script_name) . "?RecentChanges" |
| 303 | | ); |
| 304 | | |
| 305 | | my %criteria; |
| 306 | | $criteria{items} = $args{items} if $args{items}; |
| 307 | | $criteria{days} = $args{days} if $args{days}; |
| 308 | | $criteria{ignore_minor_changes} = $args{ignore_minor_edits} |
| 309 | | if $args{ignore_minor_edits}; |
| 310 | | $criteria{filter_on_metadata} = $args{filter_on_metadata} |
| 311 | | if $args{filter_on_metadata}; |
| 312 | | return $rssmaker->recent_changes( %criteria ); |
| 313 | | } |
| | 330 | |
| | 331 | my %args = ( |
| | 332 | days => 7, |
| | 333 | ignore_minor_edits => 1, |
| | 334 | filter_on_metadata => { username => "bob" }, |
| | 335 | ); |
| | 336 | |
| | 337 | print "Content-Type: text/plain\n"; |
| | 338 | print "Last-Modified: " . $rdf_writer->rss_timestamp( %args ) . "\n\n"; |
| | 339 | print $rdf_writer->make_recentchanges_rss( %args ); |
| | 340 | |
| | 341 | =item B<rss_timestamp> |
| | 342 | |
| | 343 | print "Last-Modified: " . $rdf_writer->rss_timestamp( %args ) . "\n\n"; |
| | 344 | |
| | 345 | Returns the timestamp of the RSS feed in POSIX::strftime style ("Tue, 29 Feb 2000 |
| | 346 | 12:34:56 GMT"), which is equivalent to the timestamp of the most recent item |
| | 347 | in the feed. Takes the same arguments as make_recentchanges_rss(). You will most |
| | 348 | likely need this to print a Last-Modified HTTP header so user-agents can determine |
| | 349 | whether they need to reload the feed or not. |