1 # Copyright (C) 2005 Fletcher T. Penney <fletcher@freeshell.org>
2 # Copyright (C) 2004 Alex Schroeder <alex@emacswiki.org>
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the
16 # Free Software Foundation, Inc.
17 # 59 Temple Place, Suite 330
18 # Boston, MA 02111-1307 USA
20 $ModulesDescription .= '<p><a href="http://git.savannah.gnu.org/cgit/oddmuse.git/tree/modules/static-hybrid.pl">static-hybrid.pl</a>, see <a href="http://www.oddmuse.org/cgi-bin/oddmuse/Static_Hybrid_Module">Static Hybrid Module</a></p>';
22 $Action{static
} = \
&DoStatic
;
24 use vars
qw($StaticDir $StaticAlways %StaticMimeTypes $StaticUrl
25 %StaticLinkedPages @StaticIgnoredPages);
27 $StaticDir = '' unless defined $StaticDir;
28 $StaticUrl = '' unless defined $StaticUrl; # change this!
29 $StaticAlways = 0 unless defined $StaticFilesAlways;
30 # 1 = uploaded files only, 2 = all pages
32 my $StaticMimeTypes = '/etc/http/mime.types';
35 my $StaticAction = 0; # Are we doing action or not?
38 my $ClusterHasChanged = 0;
39 my $PageBeingSaved = "";
43 return unless UserIsAdminOrError();
44 my $raw = GetParam('raw', 0);
46 print GetHttpHeader('text/plain');
48 print GetHeader('', T('Static Copy'), '');
50 CreateDir($StaticDir);
51 %StaticMimeTypes = StaticMimeTypes() unless %StaticMimeTypes;
53 my $id = GetParam('id', '');
55 local *GetDownloadLink = *StaticGetDownloadLink;
60 print '</p>' unless $raw;
61 PrintFooter() unless $raw;
66 # the default mapping matches the default @UploadTypes...
67 open(F,$StaticMimeTypes)
68 or return ('image/jpeg' => 'jpg', 'image/png' => 'png', 'image/gif' => 'gif');
70 s/\#.*//; # remove comments
71 my($type, $ext) = split;
72 $hash{$type} = $ext if $ext;
78 sub StaticWriteFiles {
79 my $raw = GetParam('raw', 0);
80 local *GetDownloadLink = *StaticGetDownloadLink;
81 foreach my $id (AllPagesList()) {
82 SetParam('rcclusteronly',0);
83 if (! grep(/^$id$/,@StaticIgnoredPages)) {
89 sub StaticGetDownloadLink {
90 my ($name, $image, $revision, $alt) = @_; # ignore $revision
91 $alt = $name unless $alt;
92 my $id = FreeToNormal($name);
94 # if the page does not exist
95 return '[' . ($image ? 'image' : 'link') . ':' . $name . ']' unless $IndexHash{$id};
97 my $result = $q->img({-src=>StaticFileName($id), -alt=>$alt, -class=>'upload'});
98 $result = ScriptLink($id, $result, 'image');
101 return ScriptLink($id, $alt, 'upload');
108 $id =~ s/#.*//; # remove named anchors for the filename test
109 return $StaticFiles{$id} if $StaticFiles{$id}; # cache filenames
110 my ($status, $data) = ReadFile(GetPageFile(StaticUrlDecode($id)));
111 print "cannot read " . GetPageFile(StaticUrlDecode($id)) . $q->br() unless $status;
112 my %hash = ParseData($data);
114 if ($hash{text} =~ /^\#FILE ([^ \n]+)\n(.*)/s) {
115 $ext = $StaticMimeTypes{$1};
116 $ext = '.' . $ext if $ext;
118 $StaticFiles{$id} = $id . $ext;
119 return $StaticFiles{$id};
122 sub StaticUrlDecode {
124 $str =~ s/%([0-9a-f][0-9a-f])/chr(hex($1))/ge;
128 sub StaticWriteFile {
130 my $raw = GetParam('raw', 0);
131 my $html = GetParam('html', 1);
132 my $filename = StaticFileName($id);
135 my ($mimetype, $data) = $Page{text} =~ /^\#FILE ([^ \n]+)\n(.*)/s;
136 return unless $html or $data;
137 open(F,"> $StaticDir/$filename") or ReportError(Ts('Cannot write %s', $filename));
139 StaticFile($id, $mimetype, $data);
144 chmod 0644,"$StaticDir/$filename";
145 if (lc(GetParam('action','')) eq "static") {
146 print $filename, $raw ? "\n" : $q->br();
151 my ($id, $type, $data) = @_;
152 require MIME::Base64;
154 print F MIME::Base64::decode($data);
158 my $id = FreeToNormal(shift);
163 local *GetHttpHeader = *StaticGetHttpHeader;
164 local *GetCommentForm = *StaticGetCommentForm;
169 open(STDOUT, '>', \$result);
171 open(STDERR, '>/dev/null');
175 # encoding is left off, so fix it:
176 print qq!<?xml version="1.0" encoding="UTF-8" ?>!;
177 print GetHeader($id, QuoteHtml($id), undef, "");
178 print $q->start_div({-class=> 'content browse'});
181 SetParam('rcclusteronly', $id) if (FreeToNormal(GetCluster($Page{text})) eq $id);
182 if (($id eq $RCName) || (T($RCName) eq $id) || (T($id) eq $RCName)
183 || GetParam('rcclusteronly', '')) {
184 print $q->start_div({-class=>'rc'});;
185 print $q->hr() if not GetParam('embed', $EmbedWiki);
194 *StaticFilesOldDoPost = *DoPost;
195 *DoPost = *StaticFilesNewDoPost;
197 sub StaticFilesNewDoPost {
198 my $id = FreeToNormal(shift);
200 my $old_cluster = FreeToNormal(GetCluster($Page{text}));
201 StaticFilesOldDoPost($id);
202 my $new_cluster = FreeToNormal(GetCluster($Page{text}));
204 $ClusterHasChanged = 1 if ($old_cluster ne $new_cluster);
208 StaticDeleteFile($OpenPageName);
209 if ($Page{text} =~ /^\#FILE / # if a file was uploaded
210 or $StaticAlways > 1) {
211 CreateDir($StaticDir);
212 # If new Page added, update index
213 if (! $IndexHash{$OpenPageName} ) {
214 push(@IndexList, $OpenPageName);
215 $IndexHash{$OpenPageName} = 1;
218 StaticWriteFile($OpenPageName);
219 $PageBeingSaved = $OpenPageName;
220 AddLinkedFilesToQueue($OpenPageName);
221 StaticWriteLinkedFiles();
226 *StaticOldDeletePage = *DeletePage;
227 *DeletePage = *StaticNewDeletePage;
229 sub StaticNewDeletePage {
231 StaticDeleteFile($id) if ($StaticAlways);
232 return StaticOldDeletePage($id);
235 sub StaticDeleteFile {
237 %StaticMimeTypes = StaticMimeTypes() unless %StaticMimeTypes;
238 # we don't care if the files or $StaticDir don't exist -- just delete!
239 for my $f (map { "$StaticDir/$id.$_" } (values %StaticMimeTypes, 'html')) {
240 unlink $f; # delete copies with different extensions
244 # override the default!
245 sub GetDownloadLink {
246 my ($name, $image, $revision, $alt) = @_;
247 $alt = $name unless $alt;
248 my $id = FreeToNormal($name);
250 # if the page does not exist
251 return '[' . ($image ? T('image') : T('download')) . ':' . $name
252 . ']' . GetEditLink($id, '?', 1) unless $IndexHash{$id};
255 $action = "action=download;id=" . UrlEncode($id) . ";revision=$revision";
256 } elsif ($UsePathInfo) {
257 $action = "download/" . UrlEncode($id);
259 $action = "action=download;id=" . UrlEncode($id);
262 if ($UsePathInfo and not $revision) {
263 if ($StaticAlways and $StaticUrl) {
264 my $url = $StaticUrl;
265 my $img = UrlEncode(StaticFileName($id));
266 $url =~ s/\%s/$img/g or $url .= $img;
269 $action = $ScriptName . '/' . $action;
272 $action = $ScriptName . '?' . $action;
274 my $result = $q->img({-src=>$action, -alt=>$alt, -class=>'upload'});
275 $result = ScriptLink(UrlEncode($id), $result, 'image') unless $id eq $OpenPageName;
278 return ScriptLink($action, $alt, 'upload');
282 # override function from Image Extension to support advanced image tags
283 sub ImageGetInternalUrl{
286 if ($StaticAlways and $StaticUrl) {
287 my $url = $StaticUrl;
288 my $img = UrlEncode(StaticFileName($id));
289 $url =~ s/\%s/$img/g or $url .= $img;
292 return $ScriptName . '/download/' . UrlEncode($id);
295 return $ScriptName . '?action=download;id=' . UrlEncode($id);
299 sub AddLinkedFilesToQueue {
302 foreach my $pattern (keys %StaticLinkedPages) {
303 if ($id =~ /$pattern/) {
304 AddNewFilesToQueue(@{$StaticLinkedPages{$pattern}})
308 # If you modify a comment page, then update the original
309 # Don't check for recursive updates - the only thing that
310 # changed was the CommentCount - no reason to waste time
311 if ($id =~ /^$CommentsPrefix(.*)/) {
313 push(@StaticQueue,$match);
316 # If the page added belongs to a cluster, update the cluster's page
317 # and the $ClusterMapPage
318 # especially important with the clustermap module
320 local $OpenPageName = '';
322 my $cluster = FreeToNormal(GetCluster($Page{text}));
324 # Only move up the cluster hierarchy if the page we originally
325 # edited has a cluster
326 if ($PageBeingSaved = $id) {
327 if ($cluster ne "" && $cluster ne $id) {
328 AddNewFilesToQueue($cluster);
330 # If we are using clustermaps then update
332 # But only if cluster has changed
333 if ($ClusterHasChanged) {
334 if ($ClusterMapPage ne "") {
335 AddNewFilesToQueue($ClusterMapPage);
343 sub StaticWriteLinkedFiles {
344 my $raw = GetParam('raw', 0);
346 local *GetDownloadLink = *StaticGetDownloadLink;
348 foreach my $id (@StaticQueue) {
349 if (! grep(/^$id$/,@StaticIgnoredPages)) {
350 StaticWriteFile($id);
351 SetParam('rcclusteronly',0);
356 sub StaticGetCommentForm {
357 my ($id, $rev, $comment) = @_;
358 if ($CommentsPrefix ne '' and $id and $rev ne 'history' and $rev ne 'edit'
359 and $OpenPageName =~ /^$CommentsPrefix/) {
360 return $q->div({-class=>'comment'}, GetFormStart(undef, undef, 'comment'),
361 $q->p(GetHiddenValue('title', $OpenPageName),
362 GetTextArea('aftertext', $comment ? $comment : $NewComment)),
363 $q->p(T('Username:'), ' ',
364 $q->textfield(-name=>'username', -default=>'',
365 -override=>1, -size=>20, -maxlength=>50),
366 T('Homepage URL:'), ' ',
367 $q->textfield(-name=>'homepage', -default=>'',
368 -override=>1, -size=>40, -maxlength=>100)),
369 $q->p($q->submit(-name=>'Save', -accesskey=>T('s'), -value=>T('Save')), ' ',
370 $q->submit(-name=>'Preview', -value=>T('Preview'))),
376 sub StaticGetHttpHeader {
380 sub AddNewFilesToQueue {
381 # Add a file to queue, but only if not already there
384 foreach my $id (@ids) {
385 if (! grep(/^$id$/,@StaticQueue)) {
386 push(@StaticQueue,$id);
387 AddLinkedFilesToQueue($id);
392 # Make rollback compatible
394 *StaticOldDoRollback = *DoRollback;
395 *DoRollback = *StaticNewDoRollback;
396 $Action{rollback} = \&StaticNewDoRollback;
398 # Delete the static file so that changes made during a rollback are propogated
400 sub StaticNewDoRollback {
402 my $to = GetParam('to', 0);
403 ReportError(T('Missing target for rollback.'), '400 BAD REQUEST') unless $to;
404 ReportError(T('Target for rollback is too far back.'), '400 BAD REQUEST') unless $page or RollbackPossible($to);
405 ReportError(T('A username is required for ordinary users.'), '403 FORBIDDEN') unless GetParam('username', '') or UserIsEditor();
407 if (not $page) { # cannot just use list length because of ('')
408 return unless UserIsAdminOrError(); # only admins can do mass changes
409 my %ids = map { my ($ts, $id) = split(/$FS/o); $id => 1; } # make unique via hash
410 GetRcLines($Now - $KeepDays * 86400, 1); # 24*60*60
415 RequestLockOrError();
416 print GetHeader('', T('Rolling back changes')), $q->start_div({-class=>'content rollback'}), $q->start_p();
417 foreach my $id (@ids) {
419 my ($text, $minor, $ts) = GetTextAtTime($to);
420 if ($Page{text} eq $text) {
421 print T("The two revisions are the same."), $q->br() if $page; # no message when doing mass revert
422 } elsif (!UserCanEdit($id, 1)) {
423 print Ts('Editing not allowed for %s.', $id), $q->br();
425 Save($id, $text, Ts('Rollback to %s', TimeToText($to)), $minor, ($Page{ip} ne $ENV{REMOTE_ADDR}));
426 StaticDeleteFile($id);
427 print Ts('%s rolled back', GetPageLink($id)), ($ts ? ' ' . Ts('to %s', TimeToText($to)) : ''), $q->br();
430 WriteRcLog('[[rollback]]', '', $to) unless $page; # leave marker for DoRc() if mass rollback
431 print $q->end_p() . $q->end_div();
436 *StaticOldDespamPage = *DespamPage;
437 *DespamPage = *StaticNewDespamPage;
439 sub StaticNewDespamPage {
442 my @revisions = sort {$b <=> $a} map { m|/([0-9]+).kp$|; $1; } GetKeepFiles($OpenPageName);
443 foreach my $revision (@revisions) { # remember the last revision checked
444 my ($text, $rev) = GetTextRevision($revision, 1); # quiet
446 print ': ' . Ts('Cannot find revision %s.', $revision);
448 } elsif (not DespamBannedContent($text)) {
449 my $summary = Tss('Revert to revision %1: %2', $revision, $rule);
450 print ': ' . $summary;
451 Save($OpenPageName, $text, $summary) unless GetParam('debug', 0);
452 StaticDeleteFile($OpenPageName);
456 if (grep(/^1$/, @revisions) or not @revisions) { # if there is no kept revision, yet
457 my $summary = Ts($rule). ' ' . Ts('Marked as %s.', $DeletedPage);
458 print ': ' . $summary;
459 Save($OpenPageName, $DeletedPage, $summary) unless GetParam('debug', 0);
460 StaticDeleteFile($OpenPageName);
462 print ': ' . T('Cannot find unspammed revision.'. $revision);