diff options
Diffstat (limited to 'IkiWiki')
-rw-r--r-- | IkiWiki/Plugin/blogspam.pm | 15 | ||||
-rw-r--r-- | IkiWiki/Plugin/comments.pm | 261 | ||||
-rw-r--r-- | IkiWiki/Plugin/editpage.pm | 5 | ||||
-rw-r--r-- | IkiWiki/Plugin/git.pm | 6 | ||||
-rw-r--r-- | IkiWiki/Plugin/img.pm | 2 | ||||
-rw-r--r-- | IkiWiki/Plugin/remove.pm | 2 | ||||
-rw-r--r-- | IkiWiki/Setup/Automator.pm | 3 |
7 files changed, 243 insertions, 51 deletions
diff --git a/IkiWiki/Plugin/blogspam.pm b/IkiWiki/Plugin/blogspam.pm index cc6e840f0..cbd9859a5 100644 --- a/IkiWiki/Plugin/blogspam.pm +++ b/IkiWiki/Plugin/blogspam.pm @@ -62,11 +62,11 @@ sub checkcontent (@) { } my $url=$defaulturl; - $url = $params{blogspam_server} if exists $params{blogspam_server}; + $url = $config{blogspam_server} if exists $config{blogspam_server}; my $client = RPC::XML::Client->new($url); - my @options = split(",", $params{blogspam_options}) - if exists $params{blogspam_options}; + my @options = split(",", $config{blogspam_options}) + if exists $config{blogspam_options}; # Allow short comments and whitespace-only edits, unless the user # has overridden min-words themselves. @@ -83,7 +83,7 @@ sub checkcontent (@) { # and "buy". push @options, "exclude=stopwords"; - my $res = $client->send_request('testComment', { + my %req=( ip => $ENV{REMOTE_ADDR}, comment => $params{content}, subject => defined $params{subject} ? $params{subject} : "", @@ -92,17 +92,20 @@ sub checkcontent (@) { options => join(",", @options), site => $config{url}, version => "ikiwiki ".$IkiWiki::version, - }); + ); + my $res = $client->send_request('testComment', \%req); if (! ref $res || ! defined $res->value) { debug("failed to get response from blogspam server ($url)"); return undef; } elsif ($res->value =~ /^SPAM:(.*)/) { + eval q{use Data::Dumper}; + debug("blogspam server reports ".$res->value.": ".Dumper(\%req)); return gettext("Sorry, but that looks like spam to <a href=\"http://blogspam.net/\">blogspam</a>: ").$1; } elsif ($res->value ne 'OK') { - debug(gettext("blogspam server failure: ").$res->value); + debug("blogspam server failure: ".$res->value); return undef; } else { diff --git a/IkiWiki/Plugin/comments.pm b/IkiWiki/Plugin/comments.pm index 833bedf25..b2243ce4c 100644 --- a/IkiWiki/Plugin/comments.pm +++ b/IkiWiki/Plugin/comments.pm @@ -26,6 +26,7 @@ sub import { hook(type => "htmlize", id => "_comment", call => \&htmlize); hook(type => "pagetemplate", id => "comments", call => \&pagetemplate); hook(type => "cgi", id => "comments", call => \&linkcgi); + hook(type => "formbuilder_setup", id => "comments", call => \&formbuilder_setup); IkiWiki::loadplugin("inline"); } @@ -134,8 +135,8 @@ sub preprocess { } # no need to bother with htmlize if it's just HTML - $content = IkiWiki::htmlize($page, $params{destpage}, $format, - $content) if defined $format; + $content = IkiWiki::htmlize($page, $params{destpage}, $format, $content) + if defined $format; IkiWiki::run_hooks(sanitize => sub { $content = shift->( @@ -263,13 +264,23 @@ sub linkcgi ($) { } } -# Mostly cargo-culted from IkiWiki::plugin::editpage sub sessioncgi ($$) { my $cgi=shift; my $session=shift; my $do = $cgi->param('do'); - return unless $do eq 'comment'; + if ($do eq 'comment') { + editcomment($cgi, $session); + } + elsif ($do eq 'commentmoderation') { + commentmoderation($cgi, $session); + } +} + +# Mostly cargo-culted from IkiWiki::plugin::editpage +sub editcomment ($$) { + my $cgi=shift; + my $session=shift; IkiWiki::decode_cgi_utf8($cgi); @@ -380,14 +391,7 @@ sub sessioncgi ($$) { IkiWiki::check_canedit($page, $cgi, $session); $postcomment=0; - # FIXME: rather a simplistic way to make the comments... - my $i = 0; - my $file; - my $location; - do { - $i++; - $location = "$page/$config{comments_pagename}$i"; - } while (-e "$config{srcdir}/$location._comment"); + my $location=unique_comment_location($page, $config{srcdir}); my $content = "[[!_comment format=$type\n"; @@ -406,19 +410,19 @@ sub sessioncgi ($$) { if ($config{comments_allowauthor}) { my $author = $form->field('author'); - if (length $author) { + if (defined $author && length $author) { $author =~ s/"/"/g; $content .= " claimedauthor=\"$author\"\n"; } my $url = $form->field('url'); - if (length $url) { + if (defined $url && length $url) { $url =~ s/"/"/g; $content .= " url=\"$url\"\n"; } } my $subject = $form->field('subject'); - if (length $subject) { + if (defined $subject && length $subject) { $subject =~ s/"/"/g; $content .= " subject=\"$subject\"\n"; } @@ -438,29 +442,12 @@ sub sessioncgi ($$) { # - this means that if they do, rocks fall and everyone dies if ($form->submitted eq PREVIEW) { - my $preview = IkiWiki::htmlize($location, $page, '_comment', - IkiWiki::linkify($location, $page, - IkiWiki::preprocess($location, $page, - IkiWiki::filter($location, - $page, $content), - 0, 1))); + my $preview=previewcomment($content, $location, $page, time); IkiWiki::run_hooks(format => sub { - $preview = shift->(page => $page, - content => $preview); - }); - - my $template = template("comment.tmpl"); - $template->param(content => $preview); - $template->param(title => $form->field('subject')); - $template->param(ctime => displaytime(time)); - - IkiWiki::run_hooks(pagetemplate => sub { - shift->(page => $location, - destpage => $page, - template => $template); + $preview = shift->(page => $page, + content => $preview); }); - - $form->tmpl_param(page_preview => $template->output); + $form->tmpl_param(page_preview => $preview); } else { $form->tmpl_param(page_preview => ""); @@ -470,21 +457,34 @@ sub sessioncgi ($$) { IkiWiki::checksessionexpiry($cgi, $session); $postcomment=1; - IkiWiki::check_content(content => $form->field('editcontent'), + my $ok=IkiWiki::check_content(content => $form->field('editcontent'), subject => $form->field('subject'), $config{comments_allowauthor} ? ( author => $form->field('author'), url => $form->field('url'), ) : (), page => $location, - cgi => $cgi, session => $session + cgi => $cgi, + session => $session, + nonfatal => 1, ); $postcomment=0; - - my $file = "$location._comment"; + + if (! $ok) { + my $penddir=$config{wikistatedir}."/comments_pending"; + $location=unique_comment_location($page, $penddir); + writefile("$location._comment", $penddir, $content); + IkiWiki::printheader($session); + print IkiWiki::misctemplate(gettext(gettext("comment stored for moderation")), + "<p>". + gettext("Your comment will be posted after moderator review"), + "</p>"); + exit; + } # FIXME: could probably do some sort of graceful retry # on error? Would require significant unwinding though + my $file = "$location._comment"; writefile($file, $config{srcdir}, $content); my $conflict; @@ -529,6 +529,173 @@ sub sessioncgi ($$) { exit; } +sub commentmoderation ($$) { + my $cgi=shift; + my $session=shift; + + IkiWiki::needsignin($cgi, $session); + if (! IkiWiki::is_admin($session->param("name"))) { + error(gettext("you are not logged in as an admin")); + } + + IkiWiki::decode_cgi_utf8($cgi); + + if (defined $cgi->param('sid')) { + IkiWiki::checksessionexpiry($cgi, $session); + + my $rejectalldefer=$cgi->param('rejectalldefer'); + + my %vars=$cgi->Vars; + my $added=0; + foreach my $id (keys %vars) { + if ($id =~ /(.*)\Q._comment\E$/) { + my $action=$cgi->param($id); + next if $action eq 'Defer' && ! $rejectalldefer; + + # Make sure that the id is of a legal + # pending comment before untainting. + my ($f)= $id =~ /$config{wiki_file_regexp}/; + if (! defined $f || ! length $f || + IkiWiki::file_pruned($f, $config{srcdir})) { + error("illegal file"); + } + + my $page=IkiWiki::possibly_foolish_untaint(IkiWiki::dirname($1)); + my $file="$config{wikistatedir}/comments_pending/". + IkiWiki::possibly_foolish_untaint($id); + + if ($action eq 'Accept') { + my $content=eval { readfile($file) }; + next if $@; # file vanished since form was displayed + my $dest=unique_comment_location($page, $config{srcdir})."._comment"; + writefile($dest, $config{srcdir}, $content); + if ($config{rcs} and $config{comments_commit}) { + IkiWiki::rcs_add($dest); + } + $added++; + } + + # This removes empty subdirs, so the + # .ikiwiki/comments_pending dir will + # go away when all are moderated. + require IkiWiki::Render; + IkiWiki::prune($file); + } + } + + if ($added) { + my $conflict; + if ($config{rcs} and $config{comments_commit}) { + my $message = gettext("Comment moderation"); + IkiWiki::disable_commit_hook(); + $conflict=IkiWiki::rcs_commit_staged($message, + $session->param('name'), $ENV{REMOTE_ADDR}); + IkiWiki::enable_commit_hook(); + IkiWiki::rcs_update(); + } + + # Now we need a refresh + require IkiWiki::Render; + IkiWiki::refresh(); + IkiWiki::saveindex(); + + error($conflict) if defined $conflict; + } + } + + my @comments=map { + my ($id, $ctime)=@{$_}; + my $file="$config{wikistatedir}/comments_pending/$id"; + my $content=readfile($file); + my $preview=previewcomment($content, $id, + IkiWiki::dirname($_), $ctime); + { + id => $id, + view => $preview, + } + } sort { $b->[1] <=> $a->[1] } comments_pending(); + + my $template=template("commentmoderation.tmpl"); + $template->param( + sid => $session->id, + comments => \@comments, + ); + IkiWiki::printheader($session); + my $out=$template->output; + IkiWiki::run_hooks(format => sub { + $out = shift->(page => "", content => $out); + }); + print IkiWiki::misctemplate(gettext("comment moderation"), $out); + exit; +} + +sub formbuilder_setup (@) { + my %params=@_; + + my $form=$params{form}; + if ($form->title eq "preferences") { + push @{$params{buttons}}, "Comment Moderation"; + if ($form->submitted && $form->submitted eq "Comment Moderation") { + commentmoderation($params{cgi}, $params{session}); + } + } +} + +sub comments_pending () { + my $dir="$config{wikistatedir}/comments_pending/"; + return unless -d $dir; + + my @ret; + eval q{use File::Find}; + error($@) if $@; + find({ + no_chdir => 1, + wanted => sub { + $_=decode_utf8($_); + if (IkiWiki::file_pruned($_, $dir)) { + $File::Find::prune=1; + } + elsif (! -l $_ && ! -d _) { + $File::Find::prune=0; + my ($f)=/$config{wiki_file_regexp}/; # untaint + if (defined $f && $f =~ /\Q._comment\E$/) { + my $ctime=(stat($f))[10]; + $f=~s/^\Q$dir\E\/?//; + push @ret, [$f, $ctime]; + } + } + } + }, $dir); + + return @ret; +} + +sub previewcomment ($$$) { + my $content=shift; + my $location=shift; + my $page=shift; + my $time=shift; + + my $preview = IkiWiki::htmlize($location, $page, '_comment', + IkiWiki::linkify($location, $page, + IkiWiki::preprocess($location, $page, + IkiWiki::filter($location, $page, $content), 0, 1))); + + my $template = template("comment.tmpl"); + $template->param(content => $preview); + $template->param(ctime => displaytime($time)); + + IkiWiki::run_hooks(pagetemplate => sub { + shift->(page => $location, + destpage => $page, + template => $template); + }); + + $template->param(have_actions => 0); + + return $template->output; +} + sub commentsshown ($) { my $page=shift; @@ -654,6 +821,20 @@ sub pagetemplate (@) { } } +sub unique_comment_location ($) { + my $page=shift; + my $dir=shift; + + my $location; + my $i = 0; + do { + $i++; + $location = "$page/$config{comments_pagename}$i"; + } while (-e "$dir/$location._comment"); + + return $location; +} + package IkiWiki::PageSpec; sub match_postcomment ($$;@) { diff --git a/IkiWiki/Plugin/editpage.pm b/IkiWiki/Plugin/editpage.pm index bba52e4fd..c206d96a4 100644 --- a/IkiWiki/Plugin/editpage.pm +++ b/IkiWiki/Plugin/editpage.pm @@ -105,11 +105,12 @@ sub check_content (@) { $ok=1; } elsif (ref $ret eq 'CODE') { - $ret->(); + $ret->() unless $params{nonfatal}; $ok=0; } elsif (defined $ret) { - error($ret); + error($ret) unless $params{nonfatal}; + $ok=0; } } diff --git a/IkiWiki/Plugin/git.pm b/IkiWiki/Plugin/git.pm index 3085a3b67..042c69f5a 100644 --- a/IkiWiki/Plugin/git.pm +++ b/IkiWiki/Plugin/git.pm @@ -139,6 +139,12 @@ sub safe_git (&@) { my @lines; while (<$OUT>) { chomp; + + # check for invalid utf-8, and toss it back to avoid crashes + if (! utf8::valid($_)) { + $_=encode_utf8($_); + } + push @lines, $_; } diff --git a/IkiWiki/Plugin/img.pm b/IkiWiki/Plugin/img.pm index 5c580c03c..d295b833b 100644 --- a/IkiWiki/Plugin/img.pm +++ b/IkiWiki/Plugin/img.pm @@ -119,9 +119,9 @@ sub preprocess (@) { } my $imgtag='<img src="'.$imgurl. - '" alt="'.(exists $params{alt} ? $params{alt} : ''). '" width="'.$im->Get("width"). '" height="'.$im->Get("height").'"'. + (exists $params{alt} ? '" alt="'.$params{alt}.'"' : ''). (exists $params{title} ? ' title="'.$params{title}.'"' : ''). (exists $params{class} ? ' class="'.$params{class}.'"' : ''). (exists $params{id} ? ' id="'.$params{id}.'"' : ''). diff --git a/IkiWiki/Plugin/remove.pm b/IkiWiki/Plugin/remove.pm index 21989aff3..ee5784f20 100644 --- a/IkiWiki/Plugin/remove.pm +++ b/IkiWiki/Plugin/remove.pm @@ -218,7 +218,7 @@ sub sessioncgi ($$) { } } else { - IkiWiki::showform($form, $buttons, $session, $q); + removal_confirm($q, $session, 0, $q->param("page")); } exit 0; diff --git a/IkiWiki/Setup/Automator.pm b/IkiWiki/Setup/Automator.pm index b7798fcec..42caf3039 100644 --- a/IkiWiki/Setup/Automator.pm +++ b/IkiWiki/Setup/Automator.pm @@ -9,12 +9,13 @@ use IkiWiki; use IkiWiki::UserInfo; use Term::ReadLine; use File::Path; +use Encode; sub ask ($$) { my ($question, $default)=@_; my $r=Term::ReadLine->new("ikiwiki"); - $r->readline($question." ", $default); + $r->readline(encode_utf8($question)." ", $default); } sub prettydir ($) { |