forked from github/kensanata.oddmuse
247 lines
9.3 KiB
Perl
247 lines
9.3 KiB
Perl
# Copyright (C) 2004, 2006, 2008, 2014 Alex Schroeder <alex@gnu.org>
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
use strict;
|
|
use v5.10;
|
|
|
|
use XML::Atom;
|
|
use XML::Atom::Entry;
|
|
use XML::Atom::Link;
|
|
use XML::Atom::Person;
|
|
|
|
AddModuleDescription('atom.pl', 'Atom Extension');
|
|
|
|
our ($q, %Page, %Action, $CommentsPrefix, $ScriptName, $SiteName, $MaxPost, $UseDiff, $DeletedPage, @MyInitVariables, @MyMacros, $FS, $BannedContent, $RssStyleSheet, $RssRights, $RssLicense, $RssImageUrl, $RssExclude, $RCName, @UploadTypes, $UploadAllowed, $UsePathInfo, $SiteDescription, $LastUpdate, $InterWikiMoniker);
|
|
|
|
push(@MyInitVariables, \&AtomInit);
|
|
|
|
sub AtomInit {
|
|
SetParam('action', 'atom') if $q->path_info =~ m|/atom\b|;
|
|
}
|
|
|
|
$Action{atom} = \&DoAtom;
|
|
|
|
sub DoAtom {
|
|
my $id = shift;
|
|
if ($q->request_method eq 'POST') {
|
|
DoAtomSave('POST');
|
|
} elsif (GetParam('info', 0) or $id eq 'info') {
|
|
DoAtomIntrospection();
|
|
} elsif (GetParam('wiki', 0)) {
|
|
if ($q->request_method eq 'PUT') {
|
|
DoAtomSave('PUT', $id);
|
|
} elsif ($q->request_method eq 'DELETE') {
|
|
DoAtomDelete($id);
|
|
} else {
|
|
DoAtomGet($id);
|
|
}
|
|
} else {
|
|
SetParam($id, 1); # /atom/full should work, too
|
|
print GetHttpHeader('application/atom+xml');
|
|
print GetRcAtom();
|
|
}
|
|
}
|
|
|
|
# from http://www.ietf.org/internet-drafts/draft-ietf-atompub-protocol-10.txt
|
|
sub DoAtomIntrospection {
|
|
print GetHttpHeader('application/atomserv+xml');
|
|
my @types = ('entry', );
|
|
push(@types, @UploadTypes) if $UploadAllowed;
|
|
my $upload = '<accept>' . join(', ', @types) . '</accept>';
|
|
print <<EOT;
|
|
<?xml version="1.0" encoding='UTF-8'?>
|
|
<service xmlns="http://purl.org/atom/app#">
|
|
<workspace title="Wiki" >
|
|
<collection title="$SiteName" href="$ScriptName/atom/wiki">
|
|
$upload
|
|
</collection>
|
|
</workspace>
|
|
</service>
|
|
EOT
|
|
}
|
|
|
|
sub AtomTag {
|
|
my ($tag, $value) = @_;
|
|
return '' unless $value;
|
|
return "<$tag>$value</$tag>\n";
|
|
}
|
|
|
|
# based on GetRcRss
|
|
sub GetRcAtom {
|
|
my $url = QuoteHtml($ScriptName) . ($UsePathInfo ? "/" : "?");
|
|
my $diffPrefix = QuoteHtml($ScriptName) . "?action=browse;diff=1;id=";
|
|
my $historyPrefix = QuoteHtml($ScriptName) . "?action=history;id=";
|
|
my $limit = GetParam("rsslimit", 15); # Only take the first 15 entries
|
|
my $count = 0;
|
|
my $feed = qq{<?xml version="1.0" encoding="UTF-8"?>\n};
|
|
if ($RssStyleSheet =~ /\.(xslt?|xml)$/) {
|
|
$feed .= qq{<?xml-stylesheet type="text/xml" href="$RssStyleSheet" ?>\n};
|
|
} elsif ($RssStyleSheet) {
|
|
$feed .= qq{<?xml-stylesheet type="text/css" href="$RssStyleSheet" ?>\n};
|
|
}
|
|
$feed .= qq{<feed xmlns="http://www.w3.org/2005/Atom"
|
|
xmlns:wiki="http://purl.org/rss/1.0/modules/wiki/"
|
|
xmlns:cc="http://backend.userland.com/creativeCommonsRssModule">\n};
|
|
$feed .= AtomTag('title', QuoteHtml($SiteName) . ': ' . GetParam('title', QuoteHtml($RCName)));
|
|
$feed .= qq{<link href="} . $url . UrlEncode($RCName) . qq{"/>\n};
|
|
$feed .= AtomTag('subtitle', QuoteHtml($SiteDescription));
|
|
$feed .= AtomTag('updated', TimeToW3($LastUpdate));
|
|
$feed .= qq{<generator uri="http://www.oddmuse.org/">Oddmuse</generator>\n};
|
|
$feed .= AtomTag('rights', $RssRights) if $RssRights;
|
|
$feed .= join('', map { AtomTag('<cc:license>', QuoteHtml($_)) }
|
|
(ref $RssLicense eq 'ARRAY' ? @$RssLicense : $RssLicense));
|
|
$feed .= AtomTag('wiki:interwiki', $InterWikiMoniker) if $InterWikiMoniker;
|
|
$feed .= AtomTag('logo', $RssImageUrl) if $RssImageUrl;
|
|
my %excluded = ();
|
|
if (GetParam("exclude", 1)) {
|
|
foreach (split(/\n/, GetPageContent($RssExclude))) {
|
|
if (/^ ([^ ]+)[ \t]*$/) { # only read lines with one word after one space
|
|
$excluded{$1} = 1;
|
|
}
|
|
}
|
|
}
|
|
# Now call GetRc with some blocks of code as parameters:
|
|
ProcessRcLines(sub {}, sub {
|
|
my ($pagename, $timestamp, $host, $username, $summary, $minor, $revision, $languages, $cluster) = @_;
|
|
return if $excluded{$pagename} or ($limit ne 'all' and $count++ >= $limit);
|
|
my $name = NormalToFree($pagename);
|
|
$username = QuoteHtml($username);
|
|
$username = $host unless $username;
|
|
$feed .= "\n<entry>\n";
|
|
$feed .= AtomTag('title', QuoteHtml($name));
|
|
$feed .= qq{<link rel="alternate" href="}
|
|
. (GetParam('all', $cluster)
|
|
? QuoteHtml($ScriptName) . "?" . GetPageParameters('browse', $pagename, $revision, $cluster)
|
|
: $url . UrlEncode($pagename)) . qq{"/>\n};
|
|
$feed .= AtomLink("$ScriptName/atom/wiki/$pagename");
|
|
$feed .= AtomTag('summary', QuoteHtml($summary));
|
|
$feed .= qq{<content type="xhtml">\n<div xmlns="http://www.w3.org/1999/xhtml">\n}
|
|
. PageHtml($pagename, 50*1024,$q->div(T('This page is too big to send over RSS.')))
|
|
. qq{\n</div>\n</content>\n} if GetParam('full', 0);
|
|
$feed .= AtomTag('published', TimeToW3($timestamp));
|
|
$feed .= qq{<link rel="replies" href="} . $url . $CommentsPrefix . UrlEncode($pagename) . qq{"/>\n}
|
|
if $CommentsPrefix and $pagename !~ /^$CommentsPrefix/;
|
|
$feed .= AtomTag('author', substr(AtomTag('name', $username), 0, -1)); # strip one newline
|
|
$feed .= AtomTag('wiki:username', $username);
|
|
$feed .= AtomTag('wiki:status', 1 == $revision ? 'new' : 'updated');
|
|
$feed .= AtomTag('wiki:importance', $minor ? 'minor' : 'major');
|
|
$feed .= AtomTag('wiki:version', $revision);
|
|
$feed .= AtomTag('wiki:history', $historyPrefix . UrlEncode($pagename));
|
|
$feed .= AtomTag('wiki:diff', $diffPrefix . UrlEncode($pagename))
|
|
if $UseDiff and GetParam('diffrclink', 1);
|
|
$feed .= "</entry>\n";
|
|
});
|
|
$feed .= "</feed>\n";
|
|
return $feed;
|
|
}
|
|
|
|
# Based on DoPost
|
|
sub DoAtomSave {
|
|
my ($type, $oldid) = @_;
|
|
my $entry = AtomEntry($type);
|
|
my $title = $entry->title();
|
|
my $author = $entry->author();
|
|
SetParam('username', $author->name) if $author; # Used in Save()
|
|
my $id = FreeToNormal($title);
|
|
UserCanEditOrDie($id);
|
|
$oldid = $id unless $oldid;
|
|
ValidIdOrDie($oldid);
|
|
my $summary = $entry->summary();
|
|
# Lock before getting old page to prevent races
|
|
RequestLockOrError(); # fatal
|
|
OpenPage($oldid);
|
|
my $old = $Page{text};
|
|
# FIXME: Assuming XML Type content, because that's what
|
|
# XML::Atom::Client does. Sent mail to the maintainers, asking for
|
|
# clarification.
|
|
$_ = $entry->content()->{elem}->getChildrenByTagName('div')->[0]->textContent;
|
|
foreach my $macro (@MyMacros) {
|
|
&$macro;
|
|
}
|
|
my $string = $_;
|
|
# Massage the string
|
|
$string =~ s/\r//g;
|
|
$string .= "\n" if ($string !~ /\n$/);
|
|
$string =~ s/$FS//g;
|
|
# Banned Content
|
|
if (not UserIsEditor()) {
|
|
my $rule = BannedContent($string) || BannedContent($summary);
|
|
ReportError(T('Edit Denied'), '403 FORBIDDEN', undef,
|
|
$q->p(T('The page contains banned text.')),
|
|
$q->p(T('Contact the wiki administrator for more information.')),
|
|
$q->p($rule . ' ' . Ts('See %s for more information.', GetPageLink($BannedContent))))
|
|
if $rule;
|
|
}
|
|
my $oldrev = $Page{revision};
|
|
if ($old eq $string and $oldid eq $id) {
|
|
ReportError(T('No changes to be saved.'), '200 OK'); # an update without consequence
|
|
} elsif ($oldrev == 0 and $string eq "\n") {
|
|
ReportError(T('No changes to be saved.'), '400 BAD REQUEST'); # don't fake page creation because of webdav
|
|
} else {
|
|
# My providing a different title, the entry is automatically renamed
|
|
if ($oldrev > 0 and $oldid ne $id) {
|
|
Save($oldid, $DeletedPage, Ts('Renamed to %s', NormalToFree($id)));
|
|
OpenPage($id);
|
|
}
|
|
# Now save the new page
|
|
Save($id, $string, $summary);
|
|
ReleaseLock();
|
|
# Do we reply 200 or 201 depending on the request, or depending on
|
|
# the action taken?
|
|
my $url = "$ScriptName/atom/wiki/$id";
|
|
if ($type eq 'POST') { # instead of $oldrev == 0
|
|
print $q->header(-status=>'201 CREATED', -location=>$url);
|
|
} else {
|
|
print $q->header(-status=>'200 OK');
|
|
}
|
|
$entry->title(NormalToFree($id));
|
|
$entry->add_link(AtomLink($url));
|
|
print '<?xml version="1.0" encoding="utf-8"?>', "\n";
|
|
print $entry->{elem}->toString;
|
|
}
|
|
}
|
|
|
|
sub DoAtomGet {
|
|
print $q->header(-status=>'304 NOT MODIFIED') and return if FileFresh(); # return value is ignored
|
|
print GetHttpHeader('application/atomserv+xml');
|
|
print '<?xml version="1.0" encoding="utf-8"?>', "\n";
|
|
my $id = GetId();
|
|
OpenPage($id);
|
|
my $entry = XML::Atom::Entry->new;
|
|
my $person = XML::Atom::Person->new;
|
|
$person->name($Page{username});
|
|
$entry->author($person) if $Page{username};
|
|
$entry->title(NormalToFree($id));
|
|
$entry->summary($Page{summary});
|
|
$entry->content($Page{text});
|
|
$entry->add_link(AtomLink("$ScriptName/atom/wiki/$id"));
|
|
print $entry->{elem}->toString;
|
|
}
|
|
|
|
sub AtomEntry {
|
|
my $type = shift || 'POST';
|
|
my $data = $q->param($type . 'DATA'); # PUTDATA or POSTDATA
|
|
my $entry = XML::Atom::Entry->new(\$data);
|
|
return $entry;
|
|
}
|
|
|
|
sub AtomLink {
|
|
my $url = shift;
|
|
my $link = XML::Atom::Link->new;
|
|
$link->href($url);
|
|
$link->rel('edit');
|
|
return $link;
|
|
}
|