2 # Copyright © 2006-2008 Joey Hess <joey@ikiwiki.info>
3 # Copyright © 2008 Simon McVittie <http://smcv.pseudorandom.co.uk/>
4 # Licensed under the GNU GPL, version 2, or any later version published by the
5 # Free Software Foundation
6 package IkiWiki
::Plugin
::comments
;
12 use POSIX
qw(strftime);
14 use constant PREVIEW
=> "Preview";
15 use constant POST_COMMENT
=> "Post comment";
16 use constant CANCEL
=> "Cancel";
22 hook
(type
=> "checkconfig", id
=> 'comments', call
=> \
&checkconfig
);
23 hook
(type
=> "getsetup", id
=> 'comments', call
=> \
&getsetup
);
24 hook
(type
=> "preprocess", id
=> 'comment', call
=> \
&preprocess
);
25 hook
(type
=> "preprocess", id
=> 'commentmoderation', call
=> \
&preprocess_moderation
);
26 # here for backwards compatability with old comments
27 hook
(type
=> "preprocess", id
=> '_comment', call
=> \
&preprocess
);
28 hook
(type
=> "sessioncgi", id
=> 'comment', call
=> \
&sessioncgi
);
29 hook
(type
=> "htmlize", id
=> "_comment", call
=> \
&htmlize
);
30 hook
(type
=> "htmlize", id
=> "_comment_pending",
31 call
=> \
&htmlize_pending
);
32 hook
(type
=> "pagetemplate", id
=> "comments", call
=> \
&pagetemplate
);
33 hook
(type
=> "formbuilder_setup", id
=> "comments",
34 call
=> \
&formbuilder_setup
);
35 # Load goto to fix up user page links for logged-in commenters
36 IkiWiki
::loadplugin
("goto");
37 IkiWiki
::loadplugin
("inline");
47 comments_pagespec
=> {
49 example
=> 'blog/* and !*/Discussion',
50 description
=> 'PageSpec of pages where comments are allowed',
51 link => 'ikiwiki/PageSpec',
55 comments_closed_pagespec
=> {
57 example
=> 'blog/controversial or blog/flamewar',
58 description
=> 'PageSpec of pages where posting new comments is not allowed',
59 link => 'ikiwiki/PageSpec',
63 comments_pagename
=> {
65 default => 'comment_',
66 description
=> 'Base name for comments, e.g. "comment_" for pages like "sandbox/comment_12"',
67 safe
=> 0, # manual page moving required
70 comments_allowdirectives
=> {
73 description
=> 'Interpret directives in comments?',
77 comments_allowauthor
=> {
80 description
=> 'Allow anonymous commenters to set an author name?',
87 description
=> 'commit comments to the VCS',
88 # old uncommitted comments are likely to cause
89 # confusion if this is changed
96 $config{comments_commit
} = 1
97 unless defined $config{comments_commit
};
98 $config{comments_pagespec
} = ''
99 unless defined $config{comments_pagespec
};
100 $config{comments_closed_pagespec
} = ''
101 unless defined $config{comments_closed_pagespec
};
102 $config{comments_pagename
} = 'comment_'
103 unless defined $config{comments_pagename
};
108 return $params{content
};
111 sub htmlize_pending
{
113 return sprintf(gettext
("this comment needs %s"),
115 IkiWiki
::cgiurl
(do => "commentmoderation").'">'.
116 gettext
("moderation").'</a>');
119 # FIXME: copied verbatim from meta
122 if (exists $IkiWiki::Plugin
::htmlscrubber
::{safe_url_regexp
} &&
123 defined $IkiWiki::Plugin
::htmlscrubber
::safe_url_regexp
) {
124 return $url=~/$IkiWiki::Plugin::htmlscrubber::safe_url_regexp/;
133 my $page = $params{page
};
135 my $format = $params{format
};
136 if (defined $format && ! exists $IkiWiki::hooks
{htmlize
}{$format}) {
137 error
(sprintf(gettext
("unsupported page format %s"), $format));
140 my $content = $params{content
};
141 if (! defined $content) {
142 error
(gettext
("comment must have content"));
144 $content =~ s/\\"/"/g;
146 if ($config{comments_allowdirectives
}) {
147 $content = IkiWiki
::preprocess
($page, $params{destpage
},
151 # no need to bother with htmlize if it's just HTML
152 $content = IkiWiki
::htmlize
($page, $params{destpage
}, $format, $content)
155 IkiWiki
::run_hooks
(sanitize
=> sub {
158 destpage
=> $params{destpage
},
163 # set metadata, possibly overriding [[!meta]] directives from the
169 my $commentauthorurl;
170 my $commentauthoravatar;
172 if (defined $params{username
}) {
173 $commentuser = $params{username
};
175 my $oiduser = eval { IkiWiki
::openiduser
($commentuser) };
177 if (defined $oiduser) {
178 # looks like an OpenID
179 $commentauthorurl = $commentuser;
180 $commentauthor = (defined $params{nickname
} && length $params{nickname
}) ?
$params{nickname
} : $oiduser;
181 $commentopenid = $commentuser;
184 $commentauthorurl = IkiWiki
::cgiurl
(
186 page
=> IkiWiki
::userpage
($commentuser)
189 $commentauthor = $commentuser;
192 eval q{use Libravatar::URL};
194 my $https=defined $config{url
} && $config{url
}=~/^https:/;
196 if (defined $commentopenid) {
198 $commentauthoravatar = libravatar_url
(openid
=> $commentopenid, https
=> $https);
201 if (! defined $commentauthoravatar &&
202 (my $email = IkiWiki
::userinfo_get
($commentuser, 'email'))) {
204 $commentauthoravatar = libravatar_url
(email
=> $email, https
=> $https);
210 if (defined $params{ip
}) {
211 $commentip = $params{ip
};
213 $commentauthor = gettext
("Anonymous");
216 $commentstate{$page}{commentuser
} = $commentuser;
217 $commentstate{$page}{commentopenid
} = $commentopenid;
218 $commentstate{$page}{commentip
} = $commentip;
219 $commentstate{$page}{commentauthor
} = $commentauthor;
220 $commentstate{$page}{commentauthorurl
} = $commentauthorurl;
221 $commentstate{$page}{commentauthoravatar
} = $commentauthoravatar;
222 if (! defined $pagestate{$page}{meta
}{author
}) {
223 $pagestate{$page}{meta
}{author
} = $commentauthor;
225 if (! defined $pagestate{$page}{meta
}{authorurl
}) {
226 $pagestate{$page}{meta
}{authorurl
} = $commentauthorurl;
229 if ($config{comments_allowauthor
}) {
230 if (defined $params{claimedauthor
}) {
231 $pagestate{$page}{meta
}{author
} = $params{claimedauthor
};
234 if (defined $params{url
}) {
235 my $url=$params{url
};
237 eval q{use URI::Heuristic};
239 $url=URI
::Heuristic
::uf_uristr
($url);
243 $pagestate{$page}{meta
}{authorurl
} = $url;
248 $pagestate{$page}{meta
}{author
} = $commentauthor;
249 $pagestate{$page}{meta
}{authorurl
} = $commentauthorurl;
252 if (defined $params{subject
}) {
253 # decode title the same way meta does
254 eval q{use HTML::Entities};
255 $pagestate{$page}{meta
}{title
} = decode_entities
($params{subject
});
258 if ($params{page
} =~ m/\/\Q
$config{comments_pagename
}\E\d
+_
/) {
259 $pagestate{$page}{meta
}{permalink
} = urlto
(IkiWiki
::dirname
($params{page
})).
260 "#".page_to_id
($params{page
});
263 eval q{use Date::Parse};
265 my $time = str2time
($params{date
});
266 $IkiWiki::pagectime
{$page} = $time if defined $time;
272 sub preprocess_moderation
{
275 $params{desc
}=gettext
("Comment Moderation")
276 unless defined $params{desc
};
278 if (length $config{cgiurl
}) {
280 IkiWiki
::cgiurl
(do => 'commentmoderation').
281 '">'.$params{desc
}.'</a>';
284 return $params{desc
};
288 sub sessioncgi
($$) {
292 my $do = $cgi->param('do');
293 if ($do eq 'comment') {
294 editcomment
($cgi, $session);
296 elsif ($do eq 'commentmoderation') {
297 commentmoderation
($cgi, $session);
299 elsif ($do eq 'commentsignin') {
300 IkiWiki
::cgi_signin
($cgi, $session);
305 # Mostly cargo-culted from IkiWiki::plugin::editpage
306 sub editcomment
($$) {
310 IkiWiki
::decode_cgi_utf8
($cgi);
312 eval q{use CGI::FormBuilder};
315 my @buttons = (POST_COMMENT
, PREVIEW
, CANCEL
);
316 my $form = CGI
::FormBuilder
->new(
317 fields
=> [qw{do sid page subject editcontent type author url
}],
320 required
=> [qw{editcontent
}],
323 action
=> IkiWiki
::cgiurl
(),
326 template
=> { template
('editcomment.tmpl') },
329 IkiWiki
::decode_form_utf8
($form);
330 IkiWiki
::run_hooks
(formbuilder_setup
=> sub {
331 shift->(title
=> "comment", form
=> $form, cgi
=> $cgi,
332 session
=> $session, buttons
=> \
@buttons);
334 IkiWiki
::decode_form_utf8
($form);
336 my $type = $form->param('type');
337 if (defined $type && length $type && $IkiWiki::hooks
{htmlize
}{$type}) {
338 $type = IkiWiki
::possibly_foolish_untaint
($type);
341 $type = $config{default_pageext
};
346 if (exists $IkiWiki::hooks
{htmlize
}) {
347 foreach my $key (grep { !/^_/ } keys %{$IkiWiki::hooks
{htmlize
}}) {
348 push @page_types, [$key, $IkiWiki::hooks
{htmlize
}{$key}{longname
} || $key];
351 @page_types=sort @page_types;
353 $form->field(name
=> 'do', type
=> 'hidden');
354 $form->field(name
=> 'sid', type
=> 'hidden', value
=> $session->id,
356 $form->field(name
=> 'page', type
=> 'hidden');
357 $form->field(name
=> 'subject', type
=> 'text', size
=> 72);
358 $form->field(name
=> 'editcontent', type
=> 'textarea', rows
=> 10);
359 $form->field(name
=> "type", value
=> $type, force
=> 1,
360 type
=> 'select', options
=> \
@page_types);
362 $form->tmpl_param(username
=> $session->param('name'));
364 if ($config{comments_allowauthor
} and
365 ! defined $session->param('name')) {
366 $form->tmpl_param(allowauthor
=> 1);
367 $form->field(name
=> 'author', type
=> 'text', size
=> '40');
368 $form->field(name
=> 'url', type
=> 'text', size
=> '40');
371 $form->tmpl_param(allowauthor
=> 0);
372 $form->field(name
=> 'author', type
=> 'hidden', value
=> '',
374 $form->field(name
=> 'url', type
=> 'hidden', value
=> '',
378 if (! defined $session->param('name')) {
379 # Make signinurl work and return here.
380 $form->tmpl_param(signinurl
=> IkiWiki
::cgiurl
(do => 'commentsignin'));
381 $session->param(postsignin
=> $ENV{QUERY_STRING
});
382 IkiWiki
::cgi_savesession
($session);
385 # The untaint is OK (as in editpage) because we're about to pass
386 # it to file_pruned and wiki_file_regexp anyway.
387 my ($page) = $form->field('page')=~/$config{wiki_file_regexp}/;
388 $page = IkiWiki
::possibly_foolish_untaint
($page);
389 if (! defined $page || ! length $page ||
390 IkiWiki
::file_pruned
($page)) {
391 error
(gettext
("bad page name"));
394 $form->title(sprintf(gettext
("commenting on %s"),
395 IkiWiki
::pagetitle
(IkiWiki
::basename
($page))));
397 $form->tmpl_param('helponformattinglink',
398 htmllink
($page, $page, 'ikiwiki/formatting',
400 linktext
=> 'FormattingHelp'),
401 allowdirectives
=> $config{allow_directives
});
403 if ($form->submitted eq CANCEL
) {
404 # bounce back to the page they wanted to comment on, and exit.
405 IkiWiki
::redirect
($cgi, urlto
($page));
409 if (not exists $pagesources{$page}) {
410 error
(sprintf(gettext
(
411 "page '%s' doesn't exist, so you can't comment"),
415 if (pagespec_match
($page, $config{comments_closed_pagespec
},
416 location
=> $page)) {
417 error
(sprintf(gettext
(
418 "comments on page '%s' are closed"),
422 # Set a flag to indicate that we're posting a comment,
423 # so that postcomment() can tell it should match.
425 IkiWiki
::check_canedit
($page, $cgi, $session);
428 my $content = "[[!comment format=$type\n";
430 if (defined $session->param('name')) {
431 my $username = $session->param('name');
432 $username =~ s/"/"/g;
433 $content .= " username=\"$username\"\n";
435 if (defined $session->param('nickname')) {
436 my $nickname = $session->param('nickname');
437 $nickname =~ s/"/"/g;
438 $content .= " nickname=\"$nickname\"\n";
440 elsif (defined $session->remote_addr()) {
441 my $ip = $session->remote_addr();
442 if ($ip =~ m/^([.0-9]+)$/) {
443 $content .= " ip=\"$1\"\n";
447 if ($config{comments_allowauthor
}) {
448 my $author = $form->field('author');
449 if (defined $author && length $author) {
450 $author =~ s/"/"/g;
451 $content .= " claimedauthor=\"$author\"\n";
453 my $url = $form->field('url');
454 if (defined $url && length $url) {
455 $url =~ s/"/"/g;
456 $content .= " url=\"$url\"\n";
460 my $subject = $form->field('subject');
461 if (defined $subject && length $subject) {
462 $subject =~ s/"/"/g;
465 $subject = "comment ".(num_comments
($page, $config{srcdir
}) + 1);
467 $content .= " subject=\"$subject\"\n";
469 $content .= " date=\"" . decode_utf8
(strftime
('%Y-%m-%dT%H:%M:%SZ', gmtime)) . "\"\n";
471 my $editcontent = $form->field('editcontent');
472 $editcontent="" if ! defined $editcontent;
473 $editcontent =~ s/\r\n/\n/g;
474 $editcontent =~ s/\r/\n/g;
475 $editcontent =~ s/"/\\"/g;
476 $content .= " content=\"\"\"\n$editcontent\n\"\"\"]]\n";
478 my $location=unique_comment_location
($page, $content, $config{srcdir
});
480 # This is essentially a simplified version of editpage:
481 # - the user does not control the page that's created, only the parent
482 # - it's always a create operation, never an edit
483 # - this means that conflicts should never happen
484 # - this means that if they do, rocks fall and everyone dies
486 if ($form->submitted eq PREVIEW
) {
487 my $preview=previewcomment
($content, $location, $page, time);
488 IkiWiki
::run_hooks
(format
=> sub {
489 $preview = shift->(page
=> $page,
490 content
=> $preview);
492 $form->tmpl_param(page_preview
=> $preview);
495 $form->tmpl_param(page_preview
=> "");
498 if ($form->submitted eq POST_COMMENT
&& $form->validate) {
499 IkiWiki
::checksessionexpiry
($cgi, $session);
502 my $ok=IkiWiki
::check_content
(content
=> $form->field('editcontent'),
503 subject
=> $form->field('subject'),
504 $config{comments_allowauthor
} ?
(
505 author
=> $form->field('author'),
506 url
=> $form->field('url'),
516 $location=unique_comment_location
($page, $content, $config{srcdir
}, "._comment_pending");
517 writefile
("$location._comment_pending", $config{srcdir
}, $content);
519 # Refresh so anything that deals with pending
520 # comments can be updated.
521 require IkiWiki
::Render
;
523 IkiWiki
::saveindex
();
525 IkiWiki
::printheader
($session);
526 print IkiWiki
::cgitemplate
($cgi, gettext
(gettext
("comment stored for moderation")),
528 gettext
("Your comment will be posted after moderator review").
533 # FIXME: could probably do some sort of graceful retry
534 # on error? Would require significant unwinding though
535 my $file = "$location._comment";
536 writefile
($file, $config{srcdir
}, $content);
540 if ($config{rcs
} and $config{comments_commit
}) {
541 my $message = gettext
("Added a comment");
542 if (defined $form->field('subject') &&
543 length $form->field('subject')) {
545 gettext
("Added a comment: %s"),
546 $form->field('subject'));
549 IkiWiki
::rcs_add
($file);
550 IkiWiki
::disable_commit_hook
();
551 $conflict = IkiWiki
::rcs_commit_staged
(
555 IkiWiki
::enable_commit_hook
();
556 IkiWiki
::rcs_update
();
559 # Now we need a refresh
560 require IkiWiki
::Render
;
562 IkiWiki
::saveindex
();
564 # this should never happen, unless a committer deliberately
565 # breaks it or something
566 error
($conflict) if defined $conflict;
568 # Jump to the new comment on the page.
569 # The trailing question mark tries to avoid broken
570 # caches and get the most recent version of the page.
571 IkiWiki
::redirect
($cgi, urlto
($page).
572 "?updated#".page_to_id
($location));
576 IkiWiki
::showform
($form, \
@buttons, $session, $cgi,
583 sub commentmoderation
($$) {
587 IkiWiki
::needsignin
($cgi, $session);
588 if (! IkiWiki
::is_admin
($session->param("name"))) {
589 error
(gettext
("you are not logged in as an admin"));
592 IkiWiki
::decode_cgi_utf8
($cgi);
594 if (defined $cgi->param('sid')) {
595 IkiWiki
::checksessionexpiry
($cgi, $session);
597 my $rejectalldefer=$cgi->param('rejectalldefer');
601 foreach my $id (keys %vars) {
602 if ($id =~ /(.*)\._comment(?:_pending)?$/) {
603 $id=decode_utf8
($id);
604 my $action=$cgi->param($id);
605 next if $action eq 'Defer' && ! $rejectalldefer;
607 # Make sure that the id is of a legal
609 my ($f) = $id =~ /$config{wiki_file_regexp}/;
610 if (! defined $f || ! length $f ||
611 IkiWiki
::file_pruned
($f)) {
612 error
("illegal file");
615 my $page=IkiWiki
::dirname
($f);
616 my $file="$config{srcdir}/$f";
619 $file="$config{wikistatedir}/comments_pending/".$f;
622 if ($action eq 'Accept') {
623 my $content=eval { readfile
($file) };
624 next if $@
; # file vanished since form was displayed
625 my $dest=unique_comment_location
($page, $content, $config{srcdir
})."._comment";
626 writefile
($dest, $config{srcdir
}, $content);
627 if ($config{rcs
} and $config{comments_commit
}) {
628 IkiWiki
::rcs_add
($dest);
633 require IkiWiki
::Render
;
634 IkiWiki
::prune
($file);
640 if ($config{rcs
} and $config{comments_commit
}) {
641 my $message = gettext
("Comment moderation");
642 IkiWiki
::disable_commit_hook
();
643 $conflict=IkiWiki
::rcs_commit_staged
(
647 IkiWiki
::enable_commit_hook
();
648 IkiWiki
::rcs_update
();
651 # Now we need a refresh
652 require IkiWiki
::Render
;
654 IkiWiki
::saveindex
();
656 error
($conflict) if defined $conflict;
661 my ($id, $dir, $ctime)=@
{$_};
662 my $content=readfile
("$dir/$id");
663 my $preview=previewcomment
($content, $id,
669 } sort { $b->[2] <=> $a->[2] } comments_pending
();
671 my $template=template
("commentmoderation.tmpl");
674 comments
=> \
@comments,
675 cgiurl
=> IkiWiki
::cgiurl
(),
677 IkiWiki
::printheader
($session);
678 my $out=$template->output;
679 IkiWiki
::run_hooks
(format
=> sub {
680 $out = shift->(page
=> "", content
=> $out);
682 print IkiWiki
::cgitemplate
($cgi, gettext
("comment moderation"), $out);
686 sub formbuilder_setup
(@
) {
689 my $form=$params{form
};
690 if ($form->title eq "preferences" &&
691 IkiWiki
::is_admin
($params{session
}->param("name"))) {
692 push @
{$params{buttons
}}, "Comment Moderation";
693 if ($form->submitted && $form->submitted eq "Comment Moderation") {
694 commentmoderation
($params{cgi
}, $params{session
});
699 sub comments_pending
() {
702 eval q{use File::Find};
706 my $origdir=getcwd
();
708 my $find_comments=sub {
711 return unless -d
$dir;
713 chdir($dir) || die "chdir $dir: $!";
718 my $file=decode_utf8
($_);
720 return if ! length $file || IkiWiki
::file_pruned
($file)
721 || -l
$_ || -d _
|| $file !~ /\Q$extension\E$/;
722 my ($f) = $file =~ /$config{wiki_file_regexp}/; # untaint
724 my $ctime=(stat($_))[10];
725 push @ret, [$f, $dir, $ctime];
730 chdir($origdir) || die "chdir $origdir: $!";
733 $find_comments->($config{srcdir
}, "._comment_pending");
735 $find_comments->("$config{wikistatedir}/comments_pending/",
741 sub previewcomment
($$$) {
747 # Previewing a comment should implicitly enable comment posting mode.
748 my $oldpostcomment=$postcomment;
751 my $preview = IkiWiki
::htmlize
($location, $page, '_comment',
752 IkiWiki
::linkify
($location, $page,
753 IkiWiki
::preprocess
($location, $page,
754 IkiWiki
::filter
($location, $page, $content), 0, 1)));
756 my $template = template
("comment.tmpl");
757 $template->param(content
=> $preview);
758 $template->param(ctime
=> displaytime
($time, undef, 1));
759 $template->param(html5
=> $config{html5
});
761 IkiWiki
::run_hooks
(pagetemplate
=> sub {
762 shift->(page
=> $location,
764 template
=> $template);
767 $template->param(have_actions
=> 0);
769 $postcomment=$oldpostcomment;
771 return $template->output;
774 sub commentsshown
($) {
777 return pagespec_match
($page, $config{comments_pagespec
},
781 sub commentsopen
($) {
784 return length $config{cgiurl
} > 0 &&
785 (! length $config{comments_closed_pagespec
} ||
786 ! pagespec_match
($page, $config{comments_closed_pagespec
},
790 sub pagetemplate
(@
) {
793 my $page = $params{page
};
794 my $template = $params{template
};
795 my $shown = ($template->query(name
=> 'commentslink') ||
796 $template->query(name
=> 'commentsurl') ||
797 $template->query(name
=> 'atomcommentsurl') ||
798 $template->query(name
=> 'comments')) &&
799 commentsshown
($page);
801 if ($template->query(name
=> 'comments')) {
802 my $comments = undef;
804 $comments = IkiWiki
::preprocess_inline
(
805 pages
=> "comment($page) and !comment($page/*)",
806 template
=> 'comment',
810 destpage
=> $params{destpage
},
811 feedfile
=> 'comments',
816 if (defined $comments && length $comments) {
817 $template->param(comments
=> $comments);
820 if ($shown && commentsopen
($page)) {
821 $template->param(addcommenturl
=> addcommenturl
($page));
826 if ($template->query(name
=> 'commentsurl')) {
827 $template->param(commentsurl
=>
828 urlto
($page).'#comments');
831 if ($template->query(name
=> 'atomcommentsurl') && $config{usedirs
}) {
832 # This will 404 until there are some comments, but I
833 # think that's probably OK...
834 $template->param(atomcommentsurl
=>
835 urlto
($page).'comments.atom');
838 if ($template->query(name
=> 'commentslink')) {
839 my $num=num_comments
($page, $config{srcdir
});
842 $link = htmllink
($page, $params{destpage
}, $page,
843 linktext
=> sprintf(ngettext
("%i comment", "%i comments", $num), $num),
844 anchor
=> "comments",
848 elsif (commentsopen
($page)) {
849 $link = "<a href=\"".addcommenturl
($page)."\">".
850 #translators: Here "Comment" is a verb;
851 #translators: the user clicks on it to
852 #translators: post a comment.
856 $template->param(commentslink
=> $link)
861 # everything below this point is only relevant to the comments
863 if (!exists $commentstate{$page}) {
867 if ($template->query(name
=> 'commentid')) {
868 $template->param(commentid
=> page_to_id
($page));
871 if ($template->query(name
=> 'commentuser')) {
872 $template->param(commentuser
=>
873 $commentstate{$page}{commentuser
});
876 if ($template->query(name
=> 'commentopenid')) {
877 $template->param(commentopenid
=>
878 $commentstate{$page}{commentopenid
});
881 if ($template->query(name
=> 'commentip')) {
882 $template->param(commentip
=>
883 $commentstate{$page}{commentip
});
886 if ($template->query(name
=> 'commentauthor')) {
887 $template->param(commentauthor
=>
888 $commentstate{$page}{commentauthor
});
891 if ($template->query(name
=> 'commentauthorurl')) {
892 $template->param(commentauthorurl
=>
893 $commentstate{$page}{commentauthorurl
});
896 if ($template->query(name
=> 'commentauthoravatar')) {
897 $template->param(commentauthoravatar
=>
898 $commentstate{$page}{commentauthoravatar
});
901 if ($template->query(name
=> 'removeurl') &&
902 IkiWiki
::Plugin
::remove
->can("check_canremove") &&
903 length $config{cgiurl
}) {
904 $template->param(removeurl
=> IkiWiki
::cgiurl
(do => 'remove',
906 $template->param(have_actions
=> 1);
910 sub addcommenturl
($) {
913 return IkiWiki
::cgiurl
(do => 'comment', page
=> $page);
916 sub num_comments
($$) {
920 my @comments=glob("$dir/$page/$config{comments_pagename}*._comment");
921 return int @comments;
924 sub unique_comment_location
($$$$) {
926 eval q{use Digest::MD5 'md5_hex'};
928 my $content_md5=md5_hex
(Encode
::encode_utf8
(shift));
930 my $ext=shift || "._comment";
933 my $i = num_comments
($page, $dir);
936 $location = "$page/$config{comments_pagename}${i}_${content_md5}";
937 } while (-e
"$dir/$location$ext");
943 # Converts a comment page name into a unique, legal html id
944 # attribute value, that can be used as an anchor to link to the
948 eval q{use Digest::MD5 'md5_hex'};
951 return "comment-".md5_hex
(Encode
::encode_utf8
(($page)));
954 package IkiWiki
::PageSpec
;
956 sub match_postcomment
($$;@
) {
960 if (! $postcomment) {
961 return IkiWiki
::FailReason
->new("not posting a comment");
963 return match_glob
($page, $glob, @_);
966 sub match_comment
($$;@
) {
970 if (! $postcomment) {
971 # To see if it's a comment, check the source file type.
972 # Deal with comments that were just deleted.
973 my $source=exists $IkiWiki::pagesources
{$page} ?
974 $IkiWiki::pagesources
{$page} :
975 $IkiWiki::delpagesources
{$page};
976 my $type=defined $source ? IkiWiki
::pagetype
($source) : undef;
977 if (! defined $type || $type ne "_comment") {
978 return IkiWiki
::FailReason
->new("$page is not a comment");
982 return match_glob
($page, "$glob/*", internal
=> 1, @_);
985 sub match_comment_pending
($$;@
) {
989 my $source=exists $IkiWiki::pagesources
{$page} ?
990 $IkiWiki::pagesources
{$page} :
991 $IkiWiki::delpagesources
{$page};
992 my $type=defined $source ? IkiWiki
::pagetype
($source) : undef;
993 if (! defined $type || $type ne "_comment_pending") {
994 return IkiWiki
::FailReason
->new("$page is not a pending comment");
997 return match_glob
($page, "$glob/*", internal
=> 1, @_);