atom.pl 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. # Copyright (C) 2004, 2006, 2008, 2014 Alex Schroeder <alex@gnu.org>
  2. #
  3. # This program is free software; you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation; either version 3 of the License, or
  6. # (at your option) any later version.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. use strict;
  16. use v5.10;
  17. use XML::Atom;
  18. use XML::Atom::Entry;
  19. use XML::Atom::Link;
  20. use XML::Atom::Person;
  21. AddModuleDescription('atom.pl', 'Atom Extension');
  22. 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);
  23. push(@MyInitVariables, \&AtomInit);
  24. sub AtomInit {
  25. SetParam('action', 'atom') if $q->path_info =~ m|/atom\b|;
  26. }
  27. $Action{atom} = \&DoAtom;
  28. sub DoAtom {
  29. my $id = shift;
  30. if ($q->request_method eq 'POST') {
  31. DoAtomSave('POST');
  32. } elsif (GetParam('info', 0) or $id eq 'info') {
  33. DoAtomIntrospection();
  34. } elsif (GetParam('wiki', 0)) {
  35. if ($q->request_method eq 'PUT') {
  36. DoAtomSave('PUT', $id);
  37. } elsif ($q->request_method eq 'DELETE') {
  38. DoAtomDelete($id);
  39. } else {
  40. DoAtomGet($id);
  41. }
  42. } else {
  43. SetParam($id, 1); # /atom/full should work, too
  44. print GetHttpHeader('application/atom+xml');
  45. print GetRcAtom();
  46. }
  47. }
  48. # from http://www.ietf.org/internet-drafts/draft-ietf-atompub-protocol-10.txt
  49. sub DoAtomIntrospection {
  50. print GetHttpHeader('application/atomserv+xml');
  51. my @types = ('entry', );
  52. push(@types, @UploadTypes) if $UploadAllowed;
  53. my $upload = '<accept>' . join(', ', @types) . '</accept>';
  54. print <<EOT;
  55. <?xml version="1.0" encoding='UTF-8'?>
  56. <service xmlns="http://purl.org/atom/app#">
  57. <workspace title="Wiki" >
  58. <collection title="$SiteName" href="$ScriptName/atom/wiki">
  59. $upload
  60. </collection>
  61. </workspace>
  62. </service>
  63. EOT
  64. }
  65. sub AtomTag {
  66. my ($tag, $value) = @_;
  67. return '' unless $value;
  68. return "<$tag>$value</$tag>\n";
  69. }
  70. # based on GetRcRss
  71. sub GetRcAtom {
  72. my $url = QuoteHtml($ScriptName) . ($UsePathInfo ? "/" : "?");
  73. my $diffPrefix = QuoteHtml($ScriptName) . "?action=browse;diff=1;id=";
  74. my $historyPrefix = QuoteHtml($ScriptName) . "?action=history;id=";
  75. my $limit = GetParam("rsslimit", 15); # Only take the first 15 entries
  76. my $count = 0;
  77. my $feed = qq{<?xml version="1.0" encoding="UTF-8"?>\n};
  78. if ($RssStyleSheet =~ /\.(xslt?|xml)$/) {
  79. $feed .= qq{<?xml-stylesheet type="text/xml" href="$RssStyleSheet" ?>\n};
  80. } elsif ($RssStyleSheet) {
  81. $feed .= qq{<?xml-stylesheet type="text/css" href="$RssStyleSheet" ?>\n};
  82. }
  83. $feed .= qq{<feed xmlns="http://www.w3.org/2005/Atom"
  84. xmlns:wiki="http://purl.org/rss/1.0/modules/wiki/"
  85. xmlns:cc="http://backend.userland.com/creativeCommonsRssModule">\n};
  86. $feed .= AtomTag('title', QuoteHtml($SiteName) . ': ' . GetParam('title', QuoteHtml($RCName)));
  87. $feed .= qq{<link href="} . $url . UrlEncode($RCName) . qq{"/>\n};
  88. $feed .= AtomTag('subtitle', QuoteHtml($SiteDescription));
  89. $feed .= AtomTag('updated', TimeToW3($LastUpdate));
  90. $feed .= qq{<generator uri="http://www.oddmuse.org/">Oddmuse</generator>\n};
  91. $feed .= AtomTag('rights', $RssRights) if $RssRights;
  92. $feed .= join('', map { AtomTag('<cc:license>', QuoteHtml($_)) }
  93. (ref $RssLicense eq 'ARRAY' ? @$RssLicense : $RssLicense));
  94. $feed .= AtomTag('wiki:interwiki', $InterWikiMoniker) if $InterWikiMoniker;
  95. $feed .= AtomTag('logo', $RssImageUrl) if $RssImageUrl;
  96. my %excluded = ();
  97. if (GetParam("exclude", 1)) {
  98. foreach (split(/\n/, GetPageContent($RssExclude))) {
  99. if (/^ ([^ ]+)[ \t]*$/) { # only read lines with one word after one space
  100. $excluded{$1} = 1;
  101. }
  102. }
  103. }
  104. # Now call GetRc with some blocks of code as parameters:
  105. ProcessRcLines(sub {}, sub {
  106. my ($pagename, $timestamp, $host, $username, $summary, $minor, $revision, $languages, $cluster) = @_;
  107. return if $excluded{$pagename} or ($limit ne 'all' and $count++ >= $limit);
  108. my $name = NormalToFree($pagename);
  109. $username = QuoteHtml($username);
  110. $username = $host unless $username;
  111. $feed .= "\n<entry>\n";
  112. $feed .= AtomTag('title', QuoteHtml($name));
  113. $feed .= qq{<link rel="alternate" href="}
  114. . (GetParam('all', $cluster)
  115. ? QuoteHtml($ScriptName) . "?" . GetPageParameters('browse', $pagename, $revision, $cluster)
  116. : $url . UrlEncode($pagename)) . qq{"/>\n};
  117. $feed .= AtomLink("$ScriptName/atom/wiki/$pagename");
  118. $feed .= AtomTag('summary', QuoteHtml($summary));
  119. $feed .= qq{<content type="xhtml">\n<div xmlns="http://www.w3.org/1999/xhtml">\n}
  120. . PageHtml($pagename, 50*1024,$q->div(T('This page is too big to send over RSS.')))
  121. . qq{\n</div>\n</content>\n} if GetParam('full', 0);
  122. $feed .= AtomTag('published', TimeToW3($timestamp));
  123. $feed .= qq{<link rel="replies" href="} . $url . $CommentsPrefix . UrlEncode($pagename) . qq{"/>\n}
  124. if $CommentsPrefix and $pagename !~ /^$CommentsPrefix/;
  125. $feed .= AtomTag('author', substr(AtomTag('name', $username), 0, -1)); # strip one newline
  126. $feed .= AtomTag('wiki:username', $username);
  127. $feed .= AtomTag('wiki:status', 1 == $revision ? 'new' : 'updated');
  128. $feed .= AtomTag('wiki:importance', $minor ? 'minor' : 'major');
  129. $feed .= AtomTag('wiki:version', $revision);
  130. $feed .= AtomTag('wiki:history', $historyPrefix . UrlEncode($pagename));
  131. $feed .= AtomTag('wiki:diff', $diffPrefix . UrlEncode($pagename))
  132. if $UseDiff and GetParam('diffrclink', 1);
  133. $feed .= "</entry>\n";
  134. });
  135. $feed .= "</feed>\n";
  136. return $feed;
  137. }
  138. # Based on DoPost
  139. sub DoAtomSave {
  140. my ($type, $oldid) = @_;
  141. my $entry = AtomEntry($type);
  142. my $title = $entry->title();
  143. my $author = $entry->author();
  144. SetParam('username', $author->name) if $author; # Used in Save()
  145. my $id = FreeToNormal($title);
  146. UserCanEditOrDie($id);
  147. $oldid = $id unless $oldid;
  148. ValidIdOrDie($oldid);
  149. my $summary = $entry->summary();
  150. # Lock before getting old page to prevent races
  151. RequestLockOrError(); # fatal
  152. OpenPage($oldid);
  153. my $old = $Page{text};
  154. # FIXME: Assuming XML Type content, because that's what
  155. # XML::Atom::Client does. Sent mail to the maintainers, asking for
  156. # clarification.
  157. $_ = $entry->content()->{elem}->getChildrenByTagName('div')->[0]->textContent;
  158. foreach my $macro (@MyMacros) {
  159. &$macro;
  160. }
  161. my $string = $_;
  162. # Massage the string
  163. $string =~ s/\r//g;
  164. $string .= "\n" if ($string !~ /\n$/);
  165. $string =~ s/$FS//g;
  166. # Banned Content
  167. if (not UserIsEditor()) {
  168. my $rule = BannedContent($string) || BannedContent($summary);
  169. ReportError(T('Edit Denied'), '403 FORBIDDEN', undef,
  170. $q->p(T('The page contains banned text.')),
  171. $q->p(T('Contact the wiki administrator for more information.')),
  172. $q->p($rule . ' ' . Ts('See %s for more information.', GetPageLink($BannedContent))))
  173. if $rule;
  174. }
  175. my $oldrev = $Page{revision};
  176. if ($old eq $string and $oldid eq $id) {
  177. ReportError(T('No changes to be saved.'), '200 OK'); # an update without consequence
  178. } elsif ($oldrev == 0 and $string eq "\n") {
  179. ReportError(T('No changes to be saved.'), '400 BAD REQUEST'); # don't fake page creation because of webdav
  180. } else {
  181. # My providing a different title, the entry is automatically renamed
  182. if ($oldrev > 0 and $oldid ne $id) {
  183. Save($oldid, $DeletedPage, Ts('Renamed to %s', NormalToFree($id)));
  184. OpenPage($id);
  185. }
  186. # Now save the new page
  187. Save($id, $string, $summary);
  188. ReleaseLock();
  189. # Do we reply 200 or 201 depending on the request, or depending on
  190. # the action taken?
  191. my $url = "$ScriptName/atom/wiki/$id";
  192. if ($type eq 'POST') { # instead of $oldrev == 0
  193. print $q->header(-status=>'201 CREATED', -location=>$url);
  194. } else {
  195. print $q->header(-status=>'200 OK');
  196. }
  197. $entry->title(NormalToFree($id));
  198. $entry->add_link(AtomLink($url));
  199. print '<?xml version="1.0" encoding="utf-8"?>', "\n";
  200. print $entry->{elem}->toString;
  201. }
  202. }
  203. sub DoAtomGet {
  204. print $q->header(-status=>'304 NOT MODIFIED') and return if FileFresh(); # return value is ignored
  205. print GetHttpHeader('application/atomserv+xml');
  206. print '<?xml version="1.0" encoding="utf-8"?>', "\n";
  207. my $id = GetId();
  208. OpenPage($id);
  209. my $entry = XML::Atom::Entry->new;
  210. my $person = XML::Atom::Person->new;
  211. $person->name($Page{username});
  212. $entry->author($person) if $Page{username};
  213. $entry->title(NormalToFree($id));
  214. $entry->summary($Page{summary});
  215. $entry->content($Page{text});
  216. $entry->add_link(AtomLink("$ScriptName/atom/wiki/$id"));
  217. print $entry->{elem}->toString;
  218. }
  219. sub AtomEntry {
  220. my $type = shift || 'POST';
  221. my $data = $q->param($type . 'DATA'); # PUTDATA or POSTDATA
  222. my $entry = XML::Atom::Entry->new(\$data);
  223. return $entry;
  224. }
  225. sub AtomLink {
  226. my $url = shift;
  227. my $link = XML::Atom::Link->new;
  228. $link->href($url);
  229. $link->rel('edit');
  230. return $link;
  231. }