package Kwiki::Atom; use strict; use warnings; use Kwiki::Plugin '-Base'; use Kwiki::Display; use mixin 'Kwiki::Installer'; our $VERSION = '0.15'; use XML::Atom; use XML::Atom::Feed; use XML::Atom::Link; use XML::Atom::Entry; use XML::Atom::Content; use DateTime; use Kwiki::Atom::Server; use constant ATOM_TYPE => "application/atom+xml"; const class_id => 'atom'; const class_title => 'Atom'; const css_file => 'atom.css'; const config_file => 'atom.yaml'; const cgi_class => 'Kwiki::Atom::CGI'; const server_class => 'Kwiki::Atom::Server'; field depth => 0; field 'headers'; field 'server'; sub process { } sub register { my $registry = shift; $registry->add(action => 'atom_edit'); $registry->add(action => 'atom_feed'); $registry->add(action => 'atom_post'); $registry->add(toolbar => 'recent_changes_atom_button', template => 'recent_changes_atom_button.html', show_for => ['recent_changes'], ); $registry->add(toolbar => 'edit_atom_button', template => 'edit_atom_button.html', show_for => ['display'], params_class => $self->class_id, ); } sub fill_links { my $name = eval { $self->hub->cgi->page_name }; my $url = CGI->new->url; push @{ $self->hub->{links}{all} }, ($name ? { rel => 'alternate', type => ATOM_TYPE, href => "$url?action=atom_edit;page_name=". $self->pages->current->uri, } : ()), { rel => 'service.feed', type => ATOM_TYPE, href => "$url?action=atom_feed", }, { rel => 'service.post', type => ATOM_TYPE, href => "$url?action=atom_post", }; return; } sub toolbar_params { # require YAML; # open X, '>>/tmp/post.log'; # print X "POSTDATA:\n", $self->cgi->POSTDATA, "\n"; # print X "HEADERS:\n", YAML::Dump(\%ENV), $/; # close X; return () unless $ENV{CONTENT_TYPE} and ($ENV{CONTENT_TYPE} eq ATOM_TYPE or $ENV{CONTENT_TYPE} =~ m{^\w+/xml}); # XXX ecto XXX $self->atom_post; my %header = &Spoon::Cookie::content_type; print CGI::header(%header); print $self->server->print; exit; } sub fill_header { $self->wrap_header if !$self->headers; $self->headers( [ @{$self->headers||[]}, @_ ] ); } sub wrap_header { my $server = $self->server($self->server_class->new); my %accept = map { $_ => 1 } $server->request_header('Accept') =~ m{([^\s,]+/[^;,]+)}g; my $content_type = 'text/xml'; # fallback foreach my $try_type (qw( application/atom+xml application/x.atom+xml application/xml )) { $accept{$try_type} or next; $content_type = $try_type; last; } $content_type .= '; charset=UTF-8'; $server->response_content_type($content_type); $server->client($self); $self->hub->headers->content_type($content_type); } sub make_entry { my ($page, $depth, $flavor) = @_; my $url = $self->server->uri; my $author = XML::Atom::Person->new; $author->name($page->metadata->edit_by); my $link_html = XML::Atom::Link->new; $link_html->type('text/html'); $link_html->rel('alternate'); $link_html->href("$url?".$page->uri); $link_html->title(''); my $link_edit = XML::Atom::Link->new; $link_edit->type(ATOM_TYPE); $link_edit->rel('service.edit'); $link_edit->href("$url?action=atom_edit;page_name=".$page->uri); $link_edit->title(''); my $entry = XML::Atom::Entry->new; $entry->title($page->title); my $content = XML::Atom::Content->new; my $elem = $content->elem; my $text = ($content->LIBXML) ? 'XML::LibXML::Text' : 'XML::XPath::Node::Text'; if ($flavor and $flavor eq 'html') { $content->type('text/html'); my $data = $page->to_html; my $copy = qq(
$data
); my $node; local $@; eval { if ($content->LIBXML) { require XML::LibXML; my $parser = XML::LibXML->new; my $tree = $parser->parse_string($copy); $node = $tree->getDocumentElement; } else { require XML::XPath; my $xp = XML::XPath->new(xml => $copy); $node = (($xp->find('/')->get_nodelist)[0]->getChildNodes)[0] if $xp; } }; if (!$@ && $node) { $elem->appendChild($node); $elem->setAttribute('mode', 'xml'); } else { $elem->appendChild($text->new($data)); $elem->setAttribute('mode', 'escaped'); } } else { $content->type('text/plain'); $elem->appendChild($text->new($page->content)); $elem->setAttribute('mode', 'escaped'); } $entry->content($content); $entry->summary(''); $entry->issued( DateTime->from_epoch( epoch => $page->io->ctime || time )->iso8601 . 'Z' ); $entry->modified( DateTime->from_epoch( epoch => $page->io->mtime || time )->iso8601 . 'Z' ); $entry->id("$url?".$page->uri); $entry->author($author); $entry->add_link($link_html); $entry->add_link($link_edit); return $entry; } sub update_page { my $page = shift; my $method = $self->server->request_method; my $entry = eval { $self->server->atom_body }; if (!$entry) { print "Status: 400\n\n"; $self->fill_header( -status => 400 ); return; } if (!$page) { my $title = $entry->title; if ($entry->content->type =~ /\bx?html\b/i) { require HTML::Entities; HTML::Entities::decode_entities($title); } $page = $self->pages->new_page($title); if ($page->exists and $method eq 'POST') { $self->server->response_code(409); $self->server->{_error} = 'This page already exists'; $self->fill_header( -status => 409, -type => 'text/plain', -warning => 'This page already exists', ); return undef; } } $self->hub->users->current->name( eval { $self->server->get_auth_info->{Username} } || $self->hub->config->user_default_name ); my $body = $entry->content->body; if ($entry->content->type =~ /\bx?html\b/i) { $body =~ s/<[^>]+>//g; require HTML::Entities; HTML::Entities::decode_entities($body); } $page->content($body); $page->update->store; return $page; } sub atom_list { my $url = $self->server->uri; my $link_feed = XML::Atom::Link->new; $link_feed->type(ATOM_TYPE); $link_feed->rel('service.feed'); $link_feed->title($self->config->site_title); $link_feed->href("$url?action=atom_feed"); my $link_post = XML::Atom::Link->new; $link_post->type(ATOM_TYPE); $link_post->rel('service.post'); $link_post->title($self->config->site_title); $link_post->href("$url?action=atom_post"); my $feed = XML::Atom::Feed->new; $feed->title($self->config->site_title); $feed->info($self->config->site_title); $feed->add_link($link_feed); $feed->add_link($link_post); $feed->modified(DateTime->now->iso8601 . 'Z'); $self->munge($feed->as_xml); } sub atom_post { $self->fill_header; return $self->atom_list if $self->server->request_method eq 'GET'; $self->server->{request_content} = $self->cgi->POSTDATA if $self->server->request_method eq 'POST'; $self->server->run; $self->server->print; } sub atom_edit { $self->fill_header; $self->server->run; $self->server->print; } sub atom_feed { $self->fill_header; my $depth = $self->cgi->depth; my $flavor = $self->cgi->flavor; my $pages = [ sort { $b->modified_time <=> $a->modified_time } ($depth ? $self->pages->recent_by_count($depth) : $self->pages->all) ]; my $timestamp = @$pages ? $pages->[0]->metadata->edit_unixtime : time; my $cache = eval { $self->hub->load_class('cache') } or return $self->generate($pages, $depth, $flavor, $timestamp); $cache->process( sub { $self->generate($pages, $depth, $flavor, $timestamp) }, 'atom', $depth, $flavor, $timestamp, int(time / 600) ); } sub generate { my ($pages, $depth, $flavor, $timestamp) = @_; my $datetime = DateTime->from_epoch( epoch => $timestamp ); my $url = $self->server->uri; my $link_html = XML::Atom::Link->new; $link_html->type('text/html'); $link_html->rel('alternate'); $link_html->title($self->config->site_title); $link_html->href($url); my $link_post = XML::Atom::Link->new; $link_post->type('application/atom+xml'); $link_post->rel('service.post'); $link_post->title($self->config->site_title); $link_post->href("$url?action=atom_post"); my $feed = XML::Atom::Feed->new; $feed->title($self->config->site_title); $feed->info($self->config->site_title); $feed->add_link($link_html); $feed->add_link($link_post); $feed->modified($datetime->iso8601 . 'Z'); my $author = XML::Atom::Person->new; $author->name($self->config->site_url); $self->config->script_name($url); for my $page (@$pages) { $feed->add_entry( $self->make_entry($page, $depth, $flavor) ); } $self->munge($feed->as_xml); } sub munge { my $xml = shift; $xml =~ /<\?xml/ or $xml = qq($xml); $xml =~ s/version="1.0"(?![^>]*encoding=)/version="1.0" encoding="UTF-8"/; $xml =~ s/(<\w+)/$1 version="0.3"/; $xml =~ s{\?>}{?>}; return $xml; } package Kwiki::Atom::CGI; use Kwiki::CGI '-base'; cgi 'depth'; cgi 'flavor'; cgi 'POSTDATA'; 1; package Kwiki::Atom; 1; __DATA__ =head1 NAME Kwiki::Atom - Kwiki Atom Plugin =head1 VERSION This document describes version 0.15 of Kwiki::Atom, released April 1, 2005. =head1 SYNOPSIS % cd /path/to/kwiki % kwiki -add Kwiki::Atom =head1 DESCRIPTION This Kwiki plugin provides Atom 0.3 integration with Kwiki. If you plan to offer your Atom feeds to the public, please consider installing the B module, which can significantly reduce the server load. For more info about this kind of integration, please refer to L. Currently, this plugin has been tested with the following AtomAPI clients: =over 4 =item * wxAtomClient.py L =item * ecto L =back =head1 AUTHOR Autrijus Tang Eautrijus@autrijus.orgE =head1 COPYRIGHT Copyright 2004, 2005 by Autrijus Tang Eautrijus@autrijus.orgE. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See http://www.perl.com/perl/misc/Artistic.html =cut __config/atom.yaml__ site_description: The Kwiki Wiki site_url: http://localhost/par/ __template/tt2/recent_changes_atom_button.html__ [% INCLUDE recent_changes_atom_button_icon.html %] __template/tt2/recent_changes_atom_button_icon.html__ Atom __icons/gnome/template/recent_changes_atom_button_icon.html__ Atom __icons/gnome/image/atom_feed.png__ iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPBAMAAADJ+Ih5AAAAMFBMVEX////yZ2fh4eH5+fm+ v7/r6+u3SEjV1dWmenqzs7PPz8/Hx8elpaXGxsbMzMyUlJQfgNlcAAAAAXRSTlMAQObYZgAA ABZ0RVh0U29mdHdhcmUAZ2lmMnBuZyAyLjQuNqQzgxcAAAB7SURBVHjaY2DAAEy3d++NelfM wBBrwHzziuodA4ZFDOvKy51YFjBctdVqy7iieoCh6JGFYFqr7wQGewU1QbE8/g0MlUBGWpb9 BIbNSy3S0lqnTmC4YLeyLeNIwQGGCwxaV+ZMYjjA8IiB+c7PUJ0CBrvbWyZZvduKsBMAMi0q dW1+s4IAAAAASUVORK5CYII= __template/tt2/edit_atom_button.html__ [% INCLUDE edit_atom_button_icon.html %] __template/tt2/edit_atom_button_icon.html__ Atom __icons/gnome/template/edit_atom_button_icon.html__ Atom __icons/gnome/image/atom_edit.png__ iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPBAMAAADJ+Ih5AAAAMFBMVEX////yZ2fh4eH5+fm+ v7/r6+u3SEjV1dWmenqzs7PPz8/Hx8elpaXGxsbMzMyUlJQfgNlcAAAAAXRSTlMAQObYZgAA ABZ0RVh0U29mdHdhcmUAZ2lmMnBuZyAyLjQuNqQzgxcAAAB7SURBVHjaY2DAAEy3d++NelfM wBBrwHzziuodA4ZFDOvKy51YFjBctdVqy7iieoCh6JGFYFqr7wQGewU1QbE8/g0MlUBGWpb9 BIbNSy3S0lqnTmC4YLeyLeNIwQGGCwxaV+ZMYjjA8IiB+c7PUJ0CBrvbWyZZvduKsBMAMi0q dW1+s4IAAAAASUVORK5CYII= __template/tt2/kwiki_begin.html__ [% IF hub.action == 'display' || hub.action == 'edit' || hub.action == 'revisions' %] [% hub.cgi.page_name %] - [% END %] [% IF hub.action != 'display' %] [% self.class_title %] - [% END %] [% site_title %] [% hub.load_class('atom').fill_links %] [% FOR link = hub.links.all -%] [% END %] [% FOR css_file = hub.css.files -%] [% END -%] [% FOR javascript_file = hub.javascript.files -%] [% END -%] __css/atom.css__ feed { display:block; font-family:verdana, sans-serif; margin:2%; font-size:90%; color:#000000; background:#ffffff; } title { display:block; font-size:1.3em; color:inherit; background:inherit; font-weight:bold; } tagline, link { display:block; font-size:0.9em; } id, modified, url { display:none; } generator { display:block; font-size:0.9em; } info { display:block; margin:3em 4em 3em 4em; color:#CC3333; background:#FFFF66; border:solid #CCCC66 2px; text-align:center; padding:1.5em; font-family:mono; font-size:0.8em; } entry { display:block; color:inherit; background:inherit; padding:0; margin:1em 1em 2em 1em; } entry modified, entry name { display:inline; color:#999999; background:inherit; font-size:0.8em; } entry created, entry issued, entry id { display:none; } entry title { display:block; font-size:1em; font-weight:bold; color:inherit; background:inherit; padding:1em 1em 0em 1em; margin:0; border-top:solid 1px #dddddd; } img.floatright { padding-left: 1em; float: right; } img.floatleft { float: left; padding-right: 1em; padding-bottom: 0.2em; } summary { display:block; background: #FFFF88; font-size:0.9em; color:inherit; margin:1em; line-height:1.5em; } content { display:block; font-size:0.9em; color:inherit; background:inherit; margin:1em; line-height:1.5em; }