Add georss support to atomfeed.
[blosxom-plugins.git] / xtaran / comments_antispam
blob32553f6d5defc81a8b6ac9fe79f0a42673fad30e
1 # Blosxom Plugin: comments_antispam
2 # Author(s): Axel Beckert <abe@deuxchevaux.org> based on work of
3 #            Kevin Lyda <kevin@ie.suberic.net> and
4 #            Rael Dornfest <rael@oreilly.com>
5 # Version: 0.1+0.6
6 # Documentation: See the bottom of this file or type: perldoc comments
8 package comments_antispam;
10 use Net::hostent;
11 use Mail::Send;
13 # --- Configurable variables -----
15 # Where is the old writeback hierarchy?
17 # NOTE: By setting this variable, you are telling the plug-in to go ahead
18 # and create a comments directory for you.
19 #my $writeback_dir = "$blosxom::plugin_state_dir/writeback";
20 my $comments_dir = "$blosxom::plugin_state_dir/comments";
22 # What flavour should I consider an incoming trackback ping?
23 # Otherwise trackback pings will be ignored!
24 my $trackback_flavour = "trackback";
26 # What file extension should I use for writebacks? 
27 # Make sure this is different from that used for your Blosxom weblog
28 # entries, usually txt.
29 my $writeback_file_ext = "wb";
30 my $comments_file_ext = "comments";
32 # max comment indent depth
33 my $max_level = 5;
35 # time configs
36 my $time_approx_prefix = "some point before ";
37 # time as described by the strftime man page.  aAbBcdHIjmMpSUwWxXyYZ%
38 my $time_fmt           = "%a, %d %b %Y %H:%M";
39 my $time_unknown       = "some point unknown";
41 my $reply_prefix       = "Re: ";
43 my @count_noun         = ("comments",   # if there are no comments
44                           "comment",    # if there is only 1 comment
45                           "comments");  # all the rest
47 # What fields were used in your writeback form and by trackbacks?
48 my @fields = qw! cname url title comment excerpt blog_name parent time !;
50 my $to_mail   = 'blog-owner@example.com';
51 my $from_mail = 'blog-server@example.com';
52 my $blacklist_file = "$blosxom::datadir/comments_blacklist";
54 # --- Export variables -----
56 # Comments for a story; use as $comments_antispam::comments in flavour templates
57 $comments;
59 # Count of writebacks for a story; use as $comments_antispam::count in flavour templates
60 $count;
61 $count_noun;
63 # parent id and title fields of replies
64 $parent = "";
65 $title = "";
67 # The path and filename of the last story on the page (ideally, only 1 story
68 # in this view) for displaying the trackback URL;
69 # use as $comments_antispam::trackback_path_and_filename in your foot flavour templates
70 $base_url;
71 $trackback_path_and_filename;
73 # Response to comments; use as $comments_antispam::comment_response in 
74 # flavour templates
75 $comment_response;
77 $pref_name;
78 $pref_url;
80 # Response to a trackback ping; use as $writeback::trackback_response in
81 # head.trackback flavour template
82 $trackback_response =<<"TRACKBACK_RESPONSE";
83 <?xml version="1.0" encoding="iso-8859-1"?>
84 <response>
85 <error></error>
86 <message></message>
87 </response>
88 TRACKBACK_RESPONSE
90 # --- Utility routines -----
92 use CGI            qw/:standard/;
93 use FileHandle;
94 use Data::Dumper;  $Data::Dumper::Indent=1;
95 use POSIX          qw/strftime/;
97 my $fh = new FileHandle;
99 # Strip potentially confounding bits from user-configurable variables
100 $writeback_dir =~ s!/$!!; $file_extension =~ s!^\.!!;
101 $comments_dir =~ s!/$!!;
103 # Save Name and URL/Email via cookie if the cookies plug-in is available
104 $cookie;
105 $errormsg;
107 sub save_comment {
108     my($path, $filename, $new) = @_;
110     open(BL, "< $blacklist_file") 
111         or die "Can't open blacklist file $blacklist_file: $!";
112     my @blacklist = ();
113     while (my $line = <BL>) {
114         next if $line =~ /^#/; # strip unix like comments
115         $line =~ s/^\s*(.*?)\s*$/$1/; # strip leading and trailing blanks
116         push(@blacklist, split(/\s+/s, $line));
117     }
118     close(BL);
120     foreach my $v (values %$new) {
121         foreach my $crit (@blacklist) {
122             if ($v =~ m($crit)is) {
123                 $errormsg = "<br/><br/>Something in your post was on the blacklist. Use the back button and try changing the post to look less like comment spam. It also may be that your hostname or webbrowser is on the blacklist. Bad luck, if that's the case.<br /><br />Sorry for the inconvenience.";
124                 return 0;
125             }
126         }
127     }
129     my($rc) = 0;
130     my($p) = "";
131     foreach ("", split(/\//, $path)) {
132         $p .= "$_/";
133         -d "$comments_dir$p" or mkdir "$comments_dir$p", 0755;
134     }
136     # lock and read in comments
137     # TODO: lock file in nfs-safe way - see LockFile::Simple
138     if ($fh->open("$comments_dir$path/$filename.$comments_file_ext")) {
139         eval join("", <$fh>);
140         $fh->close;
141     } else {
142         $saved_comments = {};
143     }
145     # determine if this is a reply
146     if (exists($new->{"parent"})) {
147         if (!exists($saved_comments->{$new->{"parent"}})) {
148             # TODO: unlock file in nfs-safe way
149             return 0;
150         }
151         $prefix = $new->{"parent"}. "-";
152         delete($new->{"parent"});
153     } else {
154         $prefix = "";
155     }
156     $id = 0;
157     while (exists($saved_comments->{$prefix. $id})) {
158         $id++;
159     }
160     $id = $prefix. $id;
162     my $message = '';
163     foreach (keys %{$new}) {
164         $saved_comments->{$id}->{$_} = $new->{$_};
165         $message .= "$_: $new->{$_}\n";  #append data in parallel to flatfile.
166     }
167     $saved_comments->{$id}->{"time"} = time();
169     #------------Begin sendmail routine -------------
170     my $msg = Mail::Send->new;
171     $msg->to($to_mail);
172     $msg->set('From', $from_mail);
173     $msg->subject("Comment to $path/$filename by $new->{cname}");
174     $mfh = $msg->open;
175     print $mfh $message;
176     $mfh->close;
177     #--------------------------------------------------
179     if ($fh->open(">$comments_dir$path/$filename.$comments_file_ext")) {
180         $fh->print(Data::Dumper->Dump([$saved_comments], ["saved_comments"]));
181         $fh->close;
182         $rc = 1;
183     }
184     # TODO: unlock file in nfs-safe way
185     return $rc;
188 sub print_comments {
189     my($path, $filename, $id_limit) = @_;
191     # lock and read in comments
192     if ($fh->open("$comments_dir$path/$filename.$comments_file_ext")) {
193         eval join("", <$fh>);
194         $fh->close;
195     } else {
196         $count_noun = $count_noun[$count = 0];
197         $comments = "";
198         return 0;
199     }
201     my($last_level, $this_level) = (0, 0);
202     my($push) = &$blosxom::template($path, "comments-push", $blosxom::flavour)
203                         or "<blockquote>";
204     my($pop) = &$blosxom::template($path, "comments-pop", $blosxom::flavour)
205                         or "</blockquote>";
206     my($comment) = &$blosxom::template($path, "comments", $blosxom::flavour)
207                         or '<p><b>Name:</b> '.
208                            '$comments_antispam::cname<br /><b>URL:</b> '.
209                            '$comments_antispam::url<br /><b>Title:</b> '.
210                            '$comments_antispam::title<br /><b>Comment:</b> '.
211                            '$comments_antispam::comment</p>';
212     my($trackback) = &$blosxom::template($path, "trackback", $blosxom::flavour)
213                         or '<p><b>Blog:</b> '.
214                            '$comments_antispam::blog_name<br /><b>URL:</b> '.
215                            '$comments_antispam::url<br /><b>Title:</b> '.
216                            '$comments_antispam::title<br /><b>Excerpt:</b> '.
217                            '$comments_antispam::excerpt</p>';
219 #    print STDERR "Comment Template:\n'$comment'\n";
221     my(@id);
222     if (defined($id_limit)) {
223         @id = ($id_limit);
224     } else {
225         my($i);
226         @id = sort {
227                      @aa = split(/-/, $a); @bb = split(/-/, $b);
228                      $i = 0;
229                      while ($i <= $#aa
230                                 and $i <= $#bb and $aa[$i] == $bb[$i]) {
231                          $i++;
232                      }
233                      $aa[$i] = -1 if ($i > $#aa);
234                      $bb[$i] = -1 if ($i > $#bb);
235                      return $aa[$i] <=> $bb[$i];
236                    } (keys(%{$saved_comments}));
237     }
238     foreach $id (@id) {
239         $this_level = split(/-/, $id) - 1;
240         $this_level = $max_level if ($max_level < $this_level);
241         if ($this_level > $last_level) {
242             $tmp = $push x ($this_level - $last_level);
243         } else {
244             $tmp = $pop x ($last_level - $this_level);
245         }
246         $last_level = $this_level;
247         if (exists($saved_comments->{$id}->{"blog_name"}) or
248             exists($saved_comments->{$id}->{"excerpt"})) {
249             $tmp .= $trackback;
250         } else {
251             $tmp .= $comment;
252         }
253         if (exists($saved_comments->{$id}->{"time"})) {
254             if ($saved_comments->{$id}->{"time"} =~ /^a(.*)/) {
255                 $time = $1;
256                 $saved_comments->{$id}->{"time"} = $time_approx_prefix;
257             } else {
258                 $time = $saved_comments->{$id}->{"time"};
259                 $saved_comments->{$id}->{"time"} = "";
260             }
261             $saved_comments->{$id}->{"time"}
262                                 .= strftime($time_fmt, localtime($time));
263         } else {
264             $saved_comments->{$id}->{"time"} = $time_unknown;
265         }
266         $saved_comments->{$id}->{"parent"} = $id;
267         $saved_comments->{$id}->{"reply_title"} =
268             ($saved_comments->{$id}->{"title"} =~ /^$reply_prefix/ ?
269              '' : $reply_prefix) . $saved_comments->{$id}->{"title"};
271         $saved_comments->{$id}->{"reply_title_escaped"} = 
272             $saved_comments->{$id}->{"reply_title"};
273         
274         $saved_comments->{$id}->{"reply_title_escaped"} =~
275             s([^a-zA-Z0-9_:./])(sprintf('%%%02X', ord($&)))ge;
277         $saved_comments->{$id}->{"base_url"} = $base_url;
278         $parent = $saved_comments->{$id}->{"parent"};
279 #       print STDERR "tmp1 ($id):\n$tmp";
280         $tmp =~ s/\$comments_antispam::([_\w]+)/$saved_comments->{$id}->{$1}/ge;
281 #       print STDERR "tmp2 ($id):\n$tmp";
282         $comments .= $tmp;
283     }
285     $comments .= $pop x $last_level;
286     $count = scalar(keys(%{$saved_comments}));
287     $count_noun = $count_noun[$count < 2? $count: 2];
289 #    use Data::Dumper;
290 #    print STDERR 'Comments: '.
291 #       Dumper(\@id,$comments,$count,$count_noun,$saved_comments,$tmp);
292 #    print STDERR $comments;
296 sub convert_writebacks {
297     my($path, $filename) = @_;
299     # read in old writebacks
300     if ($fh->open("$writeback_dir$path/$filename.$writeback_file_ext")) {
301         $saved_comments = {};
302         $id = 0;
303         $time = (stat("$writeback_dir$path/$filename.$writeback_file_ext"))[9];
304         foreach my $line (<$fh>) {
305             $line =~ /^(.+?):(.*)$/ and $saved_comments->{$id}->{$1} = $2;
306             $field = $1;
307             $saved_comments->{$id}->{$field} =~ s/^ *//;
308             $saved_comments->{$id}->{$field} =~ s/ *$//;
309             if ($saved_comments->{$id}->{$field} eq "") {
310                 delete $saved_comments->{$id}->{$field};
311             }
312             if ( $line =~ /^-----$/ ) {
313                 if (!exists($saved_comments->{$id}->{"time"})) {
314                     $saved_comments->{$id}->{"time"} = "a$time";
315                 }
316                 $id++;
317             }
318         }
319         $fh->close;
321         # make sure comments dir exists.
322         my($p) = "";
323         foreach ( ("", split /\//, $path) ) {
324             $p .= "$_/";
325             -d "$comments_dir$p" or mkdir "$comments_dir$p", 0755;
326         }
327         if ($fh->open(">$comments_dir$path/$filename.$comments_file_ext")) {
328             $fh->print(Data::Dumper->Dump([$saved_comments],
329                                             ["saved_comments"]));
330             $fh->close;
331         } else {
332             warn "blosxom: comments: convert_writeback: ".
333                     "couldn't open comment file '".
334                     "$comments_dir$path/$filename.$comments_file_ext\n";
335         }
336     } else {
337         warn "blosxom: comments: convert_writeback: ".
338                 "couldn't open writeback file '".
339                 "$writeback_dir$path/$filename.$writeback_file_ext'\n";
340     }
343 # --- Plugin Exports -----
345 sub start {
347     # $comments_dir must be set to activate writebacks
348     unless ($comments_dir) { 
349         warn "blosxom: comments: \$comments_dir".
350              " is not set; please set it to enable comments.".
351              " Comments are disabled!\n";
352         return 0;
353     }
355     # the $comments_dir exists, but is not a directory
356     if ( -e $comments_dir and ( !-d $comments_dir or !-w $comments_dir)) { 
357         warn "blosxom: comments: \$comments_dir, $comments_dir, must be".
358              " a writeable directory; please move or remove it and Blosxom".
359              " will create it properly for you.  Comments are disabled!\n";
360         return 0;
361     }
362   
363     # the $comments_dir does not yet exist, so Blosxom will create it
364     if ( !-e $comments_dir)  {
365         if (mkdir("$comments_dir", 0755)) {
366             warn "blosxom: comments: \$comments_dir, $comments_dir, created.\n"
367         } else {
368             warn "blosxom: comments: There was a problem creating".
369             " \$comments_dir, $comments_dir. Comments are disabled!\n";
370             return 0;
371         }
373         if (chmod(0755, $comments_dir)) {
374             warn "blosxom: comments: \$comments_dir, $comments_dir, set to".
375                  " 0755 permissions.\n"
376         } else {
377             warn "blosxom: comments: Problem setting perms on \$comments_dir".
378                  ", $comments_dir. Comments are disabled!\n";
379             return 0;
380         }
382         #warn "blosxom: comments: comments are enabled!\n";
383     }
385     $path_info = path_info();
387     # Symlink detection
388     my $postfile = "$blosxom::datadir$path_info";
389     $postfile =~ s/$blosxom::flavour$/$blosxom::file_extension/;
390     if (-l $postfile) {
391         my $newpath = readlink $postfile;
392         $path_info =~ s/$blosxom::file_extension$/$blosxom::flavour/;
394         if ($newpath =~ m(^/)) {
395             $newpath =~ s/\Q$blosxom::datadir\E//;
396             $path_info = $newpath;
397         } else {
398             $path_info =~ s(/[^/]*$)();
399             $newpath = "$path_info/$newpath";
400             while ($newpath =~ m(/\.\./)) {
401                 $newpath =~ s(/[^/]+/\.\./)(/)s or last;
402             }
403             $path_info = $newpath;
404         }
405     }
407     my($path, $filename) = $path_info =~ m!^(?:(.*)/)?(.*)\.$blosxom::flavour!;
408     $path =~ m!^/! or $path = "/$path";
409     $path = "/$path";
410     ($path_info_escaped = $path_info) 
411         =~ s([^-\w/.])(sprintf('%%%02x',ord($&)))ge;
413     $base_url = $blosxom::url.$path_info_escaped;
415     # Only spring into action if POSTing to the writeback plug-in
416     if (request_method() eq "POST" and
417                 (param("plugin") eq "comments"
418                  or $blosxom::flavour eq $trackback_flavour) ) {
419         my(%new);
420         foreach ( @fields ) {
421             $new{$_} = param($_);
422             if (!defined($new{$_}) or length($new{$_}) == 0) {
423                 delete $new{$_};
424             } elsif ($_ eq "url" and $new{$_} !~ /:/
425                                  and length($new{$_}) > 2) {
426                 $new{$_} = "mailto:". $new{$_};
427             } elsif ($_ eq "comment") {
428                 $new{$_} =~ s/\n?\r\n?/\n/mg;
429                 $new{$_} =~ s/\n\n/<\/p><p>/mg;
430                 $new{$_} = "<p>". $new{$_}. "</p>";
431             }
432         }
434         $new{referrer} = $ENV{HTTP_REFERER} if $ENV{HTTP_REFERER};
435         $new{host} = $ENV{REMOTE_HOST} if $ENV{REMOTE_HOST};
436         $new{host_ip} = $ENV{REMOTE_ADDR} if $ENV{REMOTE_ADDR};
437         $new{host} ||=  gethost($new{host_ip})->name;
438         $new{user_agent} = $ENV{HTTP_USER_AGENT} if $ENV{HTTP_USER_AGENT};
440         if (! -f "$comments_dir$path/$filename.$comments_file_ext"
441                 and -f "$writeback_dir$path/$filename.$writeback_file_ext") {
442             convert_writebacks($path, $filename);
443         }
444         if (save_comment("$comment_dir$path", $filename, \%new)) {
446             $trackback_response =~ s!<error></error>!<error>0</error>!m;
447             $trackback_response =~ s!<message></message>\n!!s;
448             $comment_response = "Thanks for the comment.";
450             # Make a note to save Name & URL/Email if save_preferences checked
451             param("save_preferences") and $cookie++;
452             # Pre-set Name and URL/Email based on submitted values
453             $pref_name = param("cname") || "";
454             $pref_url = param("url") || "";
456         } else {
457             warn "blosxom: comments: start: couldn't >>".
458                     " $comments_dir$path/$filename.$file_extension\n";
459             $trackback_response =~ s!<error></error>!<error>1</error>!m;
460             $trackback_response =~ s!<message>trackback error</message>!!m;
461             $comment_response = "There was a problem saving your comment. $errormsg";
462         }
463         Delete("parent");
464     }
466     1;
469 sub story {
470     my($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_;
471     
472     $path =~ s!^/*!!; $path &&= "/$path";
474     # Symlink detection
475     my $postfile = "$blosxom::datadir$path/$filename.$blosxom::file_extension";
476     if (-l $postfile) {
477         my $newpath = readlink $postfile;
478         $newpath =~ s/\Q$filename.$blosxom::file_extension\E$//;
480         if ($newpath =~ m(^/)) {
481             $newpath =~ s/\Q$blosxom::datadir\E//;
482             $path = $newpath;
483         } else {
484             $newpath = "$path/$newpath";
485             while ($newpath =~ m(/\.\./)) {
486                 $newpath =~ s(/[^/]+/\.\./)(/)s or last;
487             }
488             $path = $newpath;
489         }
490     }
492     # convert those old writebacks!
493     if (! -f "$comments_dir$path/$filename.$comments_file_ext"
494             and -f "$writeback_dir$path/$filename.$writeback_file_ext") {
495         convert_writebacks($path, $filename);
496     }
498     # Prepopulate Name and URL/Email with cookie-baked preferences, if any
499     if ($blosxom::plugins{"cookies"} > 0
500                 and my $cookie = &cookies::get("comments") ) {
501         $pref_name ||= $cookie->{"cname"};
502         $pref_url ||= $cookie->{"url"};
503     }
505     # print the comments
506     print_comments($path, $filename, param("parent"));
507     if (defined(param("parent"))) {
508         $parent = param("parent");
509         $title = param("title");
510     } else {
511         $parent = "";
512         $title = $blosxom::title;
513     }
514     $title = $reply_prefix.$title if $title !~ /^$reply_prefix/;
516     $trackback_path_and_filename = "$path/$filename";
518     1;
521 sub foot {
522     if ($pref_name or $pref_url) {
523         $blosxom::plugins{"cookies"} > 0 and $cookie and &cookies::add(
524             cookie(
525                 -name=>"comments", 
526                 -value=>{ "cname" => $pref_name, "url" => $pref_url }, 
527                 -path=>$cookies::path,
528                 -domain=>$cookies::domain,
529                 -expires=>$cookies::expires
530             )
531         );
532     }
537 __END__
539 =head1 NAME
541 Blosxom Plug-in: comments
543 =head1 SYNOPSIS
545 Provides comments and TrackBacks [http://www.movabletype.org/trackback/].
546 It will also convert your old writebacks.
548 This modified version from Axel Beckert also features mail
549 notification for the blog owner and a comment and trackback spam
550 blacklist.
552 All comments and TrackBack pings for a particular story are kept in
553 $comments_dir/$path/$filename.comment.
555 =head2 QUICK START
557 Drop this comments plug-in file into your plug-ins directory 
558 (whatever you set as $plugin_dir in blosxom.cgi).
560 Comments, being a well-behaved plug-in, won't do anything until you
561 set $comments_dir.  If you were using writebacks, you need to set
562 $writeback_dir.
564 While you can use the same directory as your blosxom $datadir (comments
565 are saved as path/weblog_entry_name.comment), it's probably better to keep
566 them separate.
568 Once set, the next time you visit your site, the comments plug-in will
569 perform some checks, creating the $comments_dir and setting appropriate
570 permissions if it doesn't already exist.  (Check your error_log for details
571 of what's happening behind the scenes.)
573 Move the contents of the flavours folder included in this distribution 
574 into your Blosxom data directory (whatever you set as $datadir in blosxom.cgi).
575 Don't move the folder itself, only the files it contains!  If you don't
576 have the the sample flavours handy, you can download them from:
578 FIXME: http://ie.suberic.net/~kevin/blosxom/comments.tar.gz
580 Point your browser at one of your Blosxom entries, specifying the comments 
581 flavour (e.g. http://localhost/cgi-bin/blosxom.cgi/path/to/a/post.comment)
583 Enjoy!
585 =back
587 =head2 BLACKLIST
589 The blacklist is a list of perl regular expressions separeted by blank
590 characters (e.g. space, tab and newline) and supports shell script
591 like comment with "#" at the beginning of the line. If any of the
592 regexps in the blacklist matches any value (including title, commenty
593 body, poster's name, URL, poster's hostname or user agent), the
594 comment or trackback will not be accepted. So be careful and wise when
595 choosing which words or domains you block. And remember: "." matches
596 any character, so escape it as "\.".
598 Example blacklist:
600    ---snip---
601    # Meds
602    acyclovir adipex alprazolam ativan butalbital carisoprodol
603    carisoprodol casino celexa cialis condylox cyclobenzaprine ddrx
604    diazepam didrex diethylpropion diflucan drofa ephedrine ephedrine
605    fioricet flexeril fluoxetine fluoxetine hydrocodone levitra lexapro
606    lipitor lorazepam lortab lrzpm meridia musapuk nextel norvasc paxil
607    penis pharmacy phentermine prilosec propecia prozac renova rkwdl rolex
608    skelaxin tadalafil tenuate tramadol tricyclen triphasil ultracet
609    ultram valium valtrex vaniqa viagra xanax xenical zanaflex zithromax
610    zoloft zovirax zyban
612    # Paths
613    ringtones/
614    lotto/
616    # Hostnames (used example.com here as an example for real regexps :-)
617    \.example.(com|net)\b
619    # Wiki syntax
620    \[(url|link)=
621    ---snap---
623 =head2 MAIL NOTIFICATION
625 Set $to_mail to the receipient and $from_mail to the sender
626 address. Needs perl module Mail::Send installed.
628 =head2 FLAVOUR TEMPLATE VARIABLES
630 Wherever you wish all the comments for a particular story to appear
631 in your flavour templates, use $comments_antispam::comments.
633 A count of WriteBacks for each story may be embedded in your flavour
634 templates with $comments_antispam::count.  Based on the @count_noun config array,
635 $comments_antispam::count_noun will reflect a $comments_antispam::count of 0, 1, many.
637 If you'd like, you can embed a "Thanks for the comment." or 
638 "There was a problem saving your comment." message after posting with
639 $comments_antispam::comment_response.
641 =head2 SAMPLE FLAVOUR TEMPLATES
643 I've made sample flavour templates available to you to help with any
644 learning curve this plug-in might require.
646 Take a gander at the source HTML/XML for:
648 =item * story.comments, a basic example of a single-entry story
649 flavour with comments embedded.  You should not use this as your
650 default flavour since every story on the home page would have comments
651 right there with the story itself.
653 =item * head.trackback, all you need to provide the right response to
654 a TrackBack ping.  Notice in story.comments that the auto-discovery
655 and human-readable TrackBack ping URLs point at this flavour.
657 =item * foot.comments provides a simple comment form for posting to the
658 comments plug-in.  NOTE: The comments plug-in requires the presence
659 of a "plugin" form variable with the value set to "comments"; this tells
660 the plug-in that it should handle the incoming POSTing data rather than
661 leaving it for another plug-in.
663 =item * comments.comments is a sample flavour file for comments themselves. 
664 Think of a comments flavour file as a story flavour file for individual 
665 comments.
667 =item * comments-push.comments and comments-pop.comments are sample
668 flavour files to handling threads (descending into a thread and coming
669 out respectively).
671 =back
673 =head2 FLAVOURING COMMENTS
675 While the default does a pretty good job, you can flavour your comments
676 in the comments flavour file (e.g. comments.comments) using the following 
677 variables:
679 =item * $comments_antispam::name$comments_antispam::blog_name - Name entered in comment
680 form or weblog name used in TrackBack ping.
682 =item * $comments_antispam::url - URL entered in comment form or that of writing
683 citing your weblog entry via TrackBack ping.
685 =item * $comments_antispam::title - Title entered into comment form or that of writing citing your weblog entry via TrackBack ping.
687 =item * $comments_antispam::comment$comments_antispam::excerpt - Comment entered
688 into comment form or excerpt of writing citing your weblog entry via
689 TrackBack ping.
691 =item * $comments_antispam::pref_name and $comments_antispam::pref_url are prepopulated
692 with the values of the form you just submitted or preferences stored in a
693 'comments' cookie, if you've the cookie plug-in installed an enabled.
695 =back
697 =head2 INVITING AND SUPPORTING TRACKBACKS
699 You should provide the TrackBack ping URL for each story so that those
700 wanting to TrackBack ping you manually know where to ping.  
701 $comments_antispam::trackback_path_and_filename, together with $url and 
702 a TrackBack flavour will provide them all they need.
704 e.g. $url$comments_antispam::trackback_path_and_filename.trackback
706 You need to provide an XML response to TrackBack pings to let them
707 know whether or not the ping was successful.  Thankfully, the plug-in
708 does just about all the work for you.  You should, however, create a
709 head.trackback flavour file (only the head is needed) containing simply:
711 $comments_antispam::trackback_response
713 Be sure that the flavour of the head file (suggested: head.trackback)
714 corresponds to the $trackback_flavour configurable setting otherwise
715 Blosxom will ignore incoming TrackBack pings!
717 =head1 INSTALLATION
719 Drop comments into your plug-ins directory ($blosxom::plugin_dir).
721 =head1 CONFIGURATION
723 =head2 (REQUIRED) SPECIFYING A COMMENTS DIRECTORY
725 Comments, being a well-behaved plug-in, won't do anything until you set 
726 $comments_dir, create the directory, and make it write-able by Blosxom.
727 Make sure to set $writeback_dir if you were using the WriteBack plugin.
729 Create a directory to save comments to (e.g. $plugin_state_dir/comments),
730 and set $comments_dir to the path to that directory.
732 While you can use the same directory as your blosxom $datadir (comments
733 are saved as path/weblog_entry_name.wb), it's probably better to keep
734 them separate.
736 The comments plug-in will create the appropriate paths to mimick your
737 $datadir hierarchy on-the-fly.  So, for a weblog entry in 
738 $datadir/some/path/or/other/entry.txt, comments will be kept in
739 $comments_dir/some/path/or/other/entry.wb.
741 =head2 (OPTIONAL) ALTERING THE TRACKBACK FLAVOUR
743 The $trackback_flavour sets the flavour the plug-in associates with
744 incoming TrackBack pings.  Unless this corresponds to the flavour
745 associated with your trackback URL, the comments plug-in will ignore
746 incoming pings.
748 =head2 (OPTIONAL) SPECIFYING AN EXTENSION FOR COMMENT FILES
750 The default extension for comments is comments.  You can change this
751 if you wish by altering the $comments_file_ext value.  The default for
752 writebacks is wb - changed by changing $writeback_file_ext.
754 =head2 (OPTIONAL) SPECIFYING WHAT FIELDS YOU EXPECT IN YOUR COMMENTS FORM
756 The defaults are probably ok here, but you can specify that the comments
757 plug-in should look for more fields in your comments form by adding to this
758 list.  You should keep at least the defaults in place so as not to break
759 anything.
761 my @fields = qw! name url title comment excerpt blog_name !;
763 Second part of the version number is the comments plugin version on
764 which it is based upon.
766 =head1 AUTHORS
768   Axel Beckert <blosxom@deuxchevaux.org>, http://noone.org/blog
769   Kevin Lyda <kevin@ie.suberic.net>, http://ie.suberic.net/~kevin/cgi-bin/blog
770   Rael Dornfest <rael@oreilly.com>, http://www.raelity.org/
772 =head DOWNLOAD
774 Latest version can be found at http://noone.org/blosxom/comments_antispam
776 =head1 SEE ALSO
778   Homepage: http://noone.org/blog?-tags=comments_antispam
780   Blosxom Home/Docs/Licensing: http://www.raelity.org/apps/blosxom/
781   Blosxom Download: http://blosxom.sourceforge.net/
782   Blosxom Plugin Docs: http://www.raelity.org/apps/blosxom/plugin.shtml
783   Blosxom User Group: http://blosxom.ookee.com/blog
784   WriteBack Plugin: http://www.raelity.org/apps/blosxom/downloads/plugins/writeback.zip
786 =head1 BUGS
788 Address bug reports and comments to the Blosxom mailing list 
789 [http://www.yahoogroups.com/group/blosxom].
791 =head1 LICENSE
793 Blosxom and this Blosxom Plug-in
794 Copyright 2003, Rael Dornfest 
795 Copyright 2005-2006, Axel Beckert
797 Permission is hereby granted, free of charge, to any person obtaining a
798 copy of this software and associated documentation files (the "Software"),
799 to deal in the Software without restriction, including without limitation
800 the rights to use, copy, modify, merge, publish, distribute, sublicense,
801 and/or sell copies of the Software, and to permit persons to whom the
802 Software is furnished to do so, subject to the following conditions:
804 The above copyright notice and this permission notice shall be included
805 in all copies or substantial portions of the Software.
807 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
808 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
809 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
810 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
811 OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
812 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
813 OTHER DEALINGS IN THE SOFTWARE.