+sub commentdate () {
+ strftime_utf8('%Y-%m-%dT%H:%M:%SZ', gmtime);
+}
+
+sub getavatar ($) {
+ my $user=shift;
+ return undef unless defined $user;
+
+ my $avatar;
+ eval q{use Libravatar::URL};
+ if (! $@) {
+ my $oiduser = eval { IkiWiki::openiduser($user) };
+ my $https=defined $config{url} && $config{url}=~/^https:/;
+
+ if (defined $oiduser) {
+ eval {
+ $avatar = libravatar_url(openid => $user, https => $https);
+ }
+ }
+ if (! defined $avatar &&
+ (my $email = IkiWiki::userinfo_get($user, 'email'))) {
+ eval {
+ $avatar = libravatar_url(email => $email, https => $https);
+ }
+ }
+ }
+ return $avatar;
+}
+
+
+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 =~ /(.*)\._comment(?:_pending)?$/) {
+ $id=decode_utf8($id);
+ my $action=$cgi->param($id);
+ next if $action eq 'Defer' && ! $rejectalldefer;
+
+ # Make sure that the id is of a legal
+ # pending comment.
+ my ($f) = $id =~ /$config{wiki_file_regexp}/;
+ if (! defined $f || ! length $f ||
+ IkiWiki::file_pruned($f)) {
+ error("illegal file");
+ }
+
+ my $page=IkiWiki::dirname($f);
+ my $filedir=$IkiWiki::Plugin::transient::transientdir;
+ my $file="$filedir/$f";
+ if (! -e $file) {
+ # old location
+ $file="$config{srcdir}/$f";
+ $filedir=$config{srcdir};
+ if (! -e $file) {
+ # older location
+ $file="$config{wikistatedir}/comments_pending/".$f;
+ $filedir="$config{wikistatedir}/comments_pending";
+ }
+ }
+
+ if ($action eq 'Accept') {
+ my $content=eval { readfile($file) };
+ next if $@; # file vanished since form was displayed
+ my $dest=unique_comment_location($page, $content, $config{srcdir})."._comment";
+ writefile($dest, $config{srcdir}, $content);
+ if ($config{rcs} and $config{comments_commit}) {
+ IkiWiki::rcs_add($dest);
+ }
+ $added++;
+ }
+
+ require IkiWiki::Render;
+ IkiWiki::prune($file, $filedir);
+ }
+ }
+
+ 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 => $message,
+ session => $session,
+ );
+ 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, $dir, $ctime)=@{$_};
+ my $content=readfile("$dir/$id");
+ my $preview=previewcomment($content, $id,
+ $id, $ctime);
+ {
+ id => $id,
+ view => $preview,
+ }
+ } sort { $b->[2] <=> $a->[2] } comments_pending();
+
+ my $template=template("commentmoderation.tmpl");
+ $template->param(
+ sid => $session->id,
+ comments => \@comments,
+ cgiurl => IkiWiki::cgiurl(),
+ );
+ IkiWiki::printheader($session);
+ my $out=$template->output;
+ IkiWiki::run_hooks(format => sub {
+ $out = shift->(page => "", content => $out);
+ });
+ print IkiWiki::cgitemplate($cgi, gettext("comment moderation"), $out);
+ exit;
+}
+
+sub formbuilder_setup (@) {
+ my %params=@_;
+
+ my $form=$params{form};
+ if ($form->title eq "preferences" &&
+ IkiWiki::is_admin($params{session}->param("name"))) {
+ push @{$params{buttons}}, "Comment Moderation";
+ if ($form->submitted && $form->submitted eq "Comment Moderation") {
+ commentmoderation($params{cgi}, $params{session});
+ }
+ }
+}
+
+sub comments_pending () {
+ my @ret;
+
+ eval q{use File::Find};
+ error($@) if $@;
+ eval q{use Cwd};
+ error($@) if $@;
+ my $origdir=getcwd();
+
+ my $find_comments=sub {
+ my $dir=shift;
+ my $extension=shift;
+ return unless -d $dir;
+
+ chdir($dir) || die "chdir $dir: $!";
+
+ find({
+ no_chdir => 1,
+ wanted => sub {
+ my $file=decode_utf8($_);
+ $file=~s/^\.\///;
+ return if ! length $file || IkiWiki::file_pruned($file)
+ || -l $_ || -d _ || $file !~ /\Q$extension\E$/;
+ my ($f) = $file =~ /$config{wiki_file_regexp}/; # untaint
+ if (defined $f) {
+ my $ctime=(stat($_))[10];
+ push @ret, [$f, $dir, $ctime];
+ }
+ }
+ }, ".");
+
+ chdir($origdir) || die "chdir $origdir: $!";
+ };
+
+ $find_comments->($IkiWiki::Plugin::transient::transientdir, "._comment_pending");
+ # old location
+ $find_comments->($config{srcdir}, "._comment_pending");
+ # old location
+ $find_comments->("$config{wikistatedir}/comments_pending/",
+ "._comment");
+
+ return @ret;
+}
+
+sub previewcomment ($$$) {
+ my $content=shift;
+ my $location=shift;
+ my $page=shift;
+ my $time=shift;
+
+ # Previewing a comment should implicitly enable comment posting mode.
+ my $oldpostcomment=$postcomment;
+ $postcomment=1;
+
+ 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, undef, 1));
+ $template->param(html5 => $config{html5});
+
+ IkiWiki::run_hooks(pagetemplate => sub {
+ shift->(page => $location,
+ destpage => $page,
+ template => $template);
+ });
+
+ $template->param(have_actions => 0);
+
+ $postcomment=$oldpostcomment;
+
+ return $template->output;
+}
+