package Kwiki::Atom; use strict; use warnings; use Kwiki::Plugin '-Base'; use Kwiki::Display; use mixin 'Kwiki::Installer'; our $VERSION = '0.10'; BEGIN { unshift @INC, sub { die if $_[1] eq 'XML/LibXML.pm' or $_[1] eq 'XML/LibXSLT.pm'; } } use XML::Atom; use XML::Atom::Feed; use XML::Atom::Link; use XML::Atom::Entry; use XML::Atom::Content; use DateTime; use XML::XPath; use Kwiki::Atom::Server; use constant ATOM_TYPE => "application/atom+xml"; #use constant ATOM_TYPE => "text/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->hub->cgi->page_name, } : ()), { 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); $server->response_content_type(ATOM_TYPE); $server->client($self); no warnings 'redefine'; require Spoon::Cookie; my $ref = Spoon::Cookie->can('content_type'); *Spoon::Cookie::content_type = sub { *Spoon::Cookie::content_type = $ref; return( -type => ATOM_TYPE, @{$self->{headers}}); }; } sub make_entry { my ($page, $depth) = @_; 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($page->id); 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->id); $link_edit->title($page->id); my $entry = XML::Atom::Entry->new; $entry->title($page->id); my $content = XML::Atom::Content->new(Type => 'text/plain'); my $elem = $content->elem; my $text = ($content->LIBXML) ? 'XML::LibXML::Text' : 'XML::XPath::Node::Text'; $elem->appendChild($text->new($page->content)); $elem->setAttribute('mode', 'escaped'); $entry->content($content); $entry->summary(''); $entry->issued( DateTime->from_epoch( epoch => $page->io->ctime )->iso8601 . 'Z' ); $entry->modified( DateTime->from_epoch( epoch => $page->io->mtime )->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 $pages = [ sort { $b->modified_time <=> $a->modified_time } ($depth ? $self->pages->recent_by_count($depth) : $self->pages->all) ]; return $self->generate($pages, $depth); # XXX $self->hub->load_class('cache')->process( sub { $self->generate($pages) }, 'atom', $depth, int(time / 120) ); } use Spiffy '-yaml'; sub generate { my ($pages, $depth) = @_; my $datetime = @$pages ? DateTime->from_epoch( epoch => $pages->[0]->metadata->edit_unixtime ) : DateTime->now; 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) ); } $self->munge($feed->as_xml); } sub munge { my $xml = shift; $xml =~ s/version="1.0"/version="1.0" encoding="UTF-8"/; $xml =~ s/(<\w+)/$1 version="0.3"/; return $xml; } package Kwiki::Atom::CGI; use Kwiki::CGI '-base'; cgi 'depth'; cgi 'POSTDATA'; 1; package Kwiki::Atom; 1; __DATA__ =head1 NAME Kwiki::Atom - Kwiki Atom Plugin =head1 VERSION This document describes version 0.10 of Kwiki::Atom, released September 4, 2004. =head1 SYNOPSIS % cd /path/to/kwiki % kwiki -add Kwiki::Atom =head1 DESCRIPTION This Kwiki plugin provides Atom 0.3 integration with Kwiki. 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 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 -%]