tagging: Fix one more HTML escaping issue.
[matthijs/upstream/blosxom-plugins.git] / xtaran / comments_antispam
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
7
8 package comments_antispam;
9
10 use Net::hostent;
11 use Mail::Send;
12
13 # --- Configurable variables -----
14
15 # Where is the old writeback hierarchy?
16 #
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";
21
22 # What flavour should I consider an incoming trackback ping?
23 # Otherwise trackback pings will be ignored!
24 my $trackback_flavour = "trackback";
25
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";
31
32 # max comment indent depth
33 my $max_level = 5;
34
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";
40
41 my $reply_prefix       = "Re: ";
42
43 my @count_noun         = ("comments",   # if there are no comments
44                           "comment",    # if there is only 1 comment
45                           "comments");  # all the rest
46
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 !;
49
50 my $to_mail   = 'blog-owner@example.com';
51 my $from_mail = 'blog-server@example.com';
52 my $blacklist_file = "$blosxom::datadir/comments_blacklist";
53
54 # --- Export variables -----
55  
56 # Comments for a story; use as $comments_antispam::comments in flavour templates
57 $comments;
58
59 # Count of writebacks for a story; use as $comments_antispam::count in flavour templates
60 $count;
61 $count_noun;
62
63 # parent id and title fields of replies
64 $parent = "";
65 $title = "";
66
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;
72
73 # Response to comments; use as $comments_antispam::comment_response in 
74 # flavour templates
75 $comment_response;
76
77 $pref_name;
78 $pref_url;
79
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
89  
90 # --- Utility routines -----
91
92 use CGI            qw/:standard/;
93 use FileHandle;
94 use Data::Dumper;  $Data::Dumper::Indent=1;
95 use POSIX          qw/strftime/;
96
97 my $fh = new FileHandle;
98
99 # Strip potentially confounding bits from user-configurable variables
100 $writeback_dir =~ s!/$!!; $file_extension =~ s!^\.!!;
101 $comments_dir =~ s!/$!!;
102
103 # Save Name and URL/Email via cookie if the cookies plug-in is available
104 $cookie;
105 $errormsg;
106
107 sub save_comment {
108     my($path, $filename, $new) = @_;
109
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);
119
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     }
128
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     }
135
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     }
144
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;
161
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();
168
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     #--------------------------------------------------
178
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;
186 }
187
188 sub print_comments {
189     my($path, $filename, $id_limit) = @_;
190
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     }
200
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>';
218
219 #    print STDERR "Comment Template:\n'$comment'\n";
220
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"};
270
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;
276
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     }
284
285     $comments .= $pop x $last_level;
286     $count = scalar(keys(%{$saved_comments}));
287     $count_noun = $count_noun[$count < 2? $count: 2];
288
289 #    use Data::Dumper;
290 #    print STDERR 'Comments: '.
291 #       Dumper(\@id,$comments,$count,$count_noun,$saved_comments,$tmp);
292 #    print STDERR $comments;
293
294 }
295
296 sub convert_writebacks {
297     my($path, $filename) = @_;
298
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;
320
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     }
341 }
342
343 # --- Plugin Exports -----
344
345 sub start {
346
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     }
354
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         }
372
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         }
381
382         #warn "blosxom: comments: comments are enabled!\n";
383     }
384
385     $path_info = path_info();
386
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/;
393
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     }
406
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;
412
413     $base_url = $blosxom::url.$path_info_escaped;
414
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         }
433
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};
439
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)) {
445
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.";
449
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") || "";
455
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     }
465
466     1;
467 }
468
469 sub story {
470     my($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_;
471     
472     $path =~ s!^/*!!; $path &&= "/$path";
473
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$//;
479
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     }
491
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     }
497
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     }
504
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/;
515
516     $trackback_path_and_filename = "$path/$filename";
517
518     1;
519 }
520
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     }
533 }
534
535 1;
536
537 __END__
538
539 =head1 NAME
540
541 Blosxom Plug-in: comments
542
543 =head1 SYNOPSIS
544
545 Provides comments and TrackBacks [http://www.movabletype.org/trackback/].
546 It will also convert your old writebacks.
547
548 This modified version from Axel Beckert also features mail
549 notification for the blog owner and a comment and trackback spam
550 blacklist.
551
552 All comments and TrackBack pings for a particular story are kept in
553 $comments_dir/$path/$filename.comment.
554
555 =head2 QUICK START
556
557 Drop this comments plug-in file into your plug-ins directory 
558 (whatever you set as $plugin_dir in blosxom.cgi).
559
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.
563
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.
567
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.)
572
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:
577
578 FIXME: http://ie.suberic.net/~kevin/blosxom/comments.tar.gz
579
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)
582
583 Enjoy!
584
585 =back
586
587 =head2 BLACKLIST
588
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 "\.".
597
598 Example blacklist:
599
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
611
612    # Paths
613    ringtones/
614    lotto/
615
616    # Hostnames (used example.com here as an example for real regexps :-)
617    \.example.(com|net)\b
618
619    # Wiki syntax
620    \[(url|link)=
621    ---snap---
622
623 =head2 MAIL NOTIFICATION
624
625 Set $to_mail to the receipient and $from_mail to the sender
626 address. Needs perl module Mail::Send installed.
627
628 =head2 FLAVOUR TEMPLATE VARIABLES
629
630 Wherever you wish all the comments for a particular story to appear
631 in your flavour templates, use $comments_antispam::comments.
632
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.
636
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.
640
641 =head2 SAMPLE FLAVOUR TEMPLATES
642
643 I've made sample flavour templates available to you to help with any
644 learning curve this plug-in might require.
645
646 Take a gander at the source HTML/XML for:
647
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.
652
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.
656
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.
662
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.
666
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).
670
671 =back
672
673 =head2 FLAVOURING COMMENTS
674
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:
678
679 =item * $comments_antispam::name$comments_antispam::blog_name - Name entered in comment
680 form or weblog name used in TrackBack ping.
681
682 =item * $comments_antispam::url - URL entered in comment form or that of writing
683 citing your weblog entry via TrackBack ping.
684
685 =item * $comments_antispam::title - Title entered into comment form or that of writing citing your weblog entry via TrackBack ping.
686
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.
690
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.
694
695 =back
696
697 =head2 INVITING AND SUPPORTING TRACKBACKS
698
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.
703
704 e.g. $url$comments_antispam::trackback_path_and_filename.trackback
705
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:
710
711 $comments_antispam::trackback_response
712
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!
716
717 =head1 INSTALLATION
718
719 Drop comments into your plug-ins directory ($blosxom::plugin_dir).
720
721 =head1 CONFIGURATION
722
723 =head2 (REQUIRED) SPECIFYING A COMMENTS DIRECTORY
724
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.
728
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.
731
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.
735
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.
740
741 =head2 (OPTIONAL) ALTERING THE TRACKBACK FLAVOUR
742
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.
747
748 =head2 (OPTIONAL) SPECIFYING AN EXTENSION FOR COMMENT FILES
749
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.
753
754 =head2 (OPTIONAL) SPECIFYING WHAT FIELDS YOU EXPECT IN YOUR COMMENTS FORM
755
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.
760
761 my @fields = qw! name url title comment excerpt blog_name !;
762
763 Second part of the version number is the comments plugin version on
764 which it is based upon.
765
766 =head1 AUTHORS
767
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/
771
772 =head DOWNLOAD
773
774 Latest version can be found at http://noone.org/blosxom/comments_antispam
775
776 =head1 SEE ALSO
777
778   Homepage: http://noone.org/blog?-tags=comments_antispam
779
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
785
786 =head1 BUGS
787
788 Address bug reports and comments to the Blosxom mailing list 
789 [http://www.yahoogroups.com/group/blosxom].
790
791 =head1 LICENSE
792
793 Blosxom and this Blosxom Plug-in
794 Copyright 2003, Rael Dornfest 
795 Copyright 2005-2006, Axel Beckert
796
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:
803
804 The above copyright notice and this permission notice shall be included
805 in all copies or substantial portions of the Software.
806
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.