1 # Blosxom Plug-in: feedback
2 # Author: Frank Hecker (http://www.hecker.org/)
11 # --- Configurable variables ---
13 # --- You *must* set the following variables properly for your blog ---
15 # Where should I keep the feedback hierarchy?
16 # (By default it goes in the Blosxom state directory. However you may
17 # prefer it to go in the same directory as the Blosxom data directory.
18 # If so, delete the following line and uncomment the line following it.)
19 $fb_dir = "$blosxom::plugin_state_dir/feedback";
20 # $fb_dir = "$blosxom::datadir/../feedback";
23 # --- Set the following variables according to your preferences ---
25 # Are comments and TrackBacks allowed? Set to zero to disable either or both.
26 my $allow_comments = 1;
27 my $allow_trackbacks = 1;
29 # Don't allow comments/TrackBacks if story is older than this (in seconds).
30 # (Set to zero to keep story open for comments/TrackBacks forever.)
31 my $comment_period = 90 * 86400; # 90 days
32 my $trackback_period = 90 * 86400; # 90 days
34 # Do Akismet checking of comments and/or TrackBacks for spam.
35 my $akismet_comments = 0;
36 my $akismet_trackbacks = 0;
38 # WordPress API key for use with Akismet.
39 # (Register at <http://wordpress.com/> to get your own API key.)
40 my $wordpress_api_key = '';
42 # Do MT-blacklist checking of comments and/or TrackBacks for spam.
43 # NOTE: The MT-Blacklist file is no longer maintained; we suggest using
45 my $blacklist_comments = 0;
46 my $blacklist_trackbacks = 0;
48 # Where can I find the local copy of the MT-Blacklist file?
49 my $blacklist_file = "$blosxom::plugin_state_dir/blacklist.txt";
51 # Send an email message to notify the blog owner of new comments and/or
52 # TrackBacks and (optionally) request approval of new comments/TrackBacks.
53 my $notify_comments = 0;
54 my $notify_trackbacks = 0;
55 my $moderate_comments = 1;
56 my $moderate_trackbacks = 1;
58 # Email address and SMTP server used for notifications and moderation requests.
59 my $address = 'jdoe@example.com';
60 my $smtp_server = 'smtp.example.com';
62 # Default values for fields not submitted with the comment or TrackBack ping.
63 my $default_name = "Someone";
64 my $default_blog_name = "An unnamed blog";
65 my $default_title = "an article";
67 # The formatting used for comments, i.e., how they are translated to (X)HTML.
68 # Valid choices at present are 'none', 'plaintext' and 'markdown'.
69 my $comment_format = 'plaintext';
71 # Should we accept and display commenter's email addresses? (The default is
72 # to support http/https URLs only; this may be the only option in future.)
76 # --- You should not normally need to change the following variables ---
78 # What flavour should I consider an incoming TrackBack ping?
79 $trackback_flavour = "trackback";
81 # What file extension should I use for saved comments and TrackBacks?
82 my $fb_extension = "wb";
84 # What fields are used in the comments form?
85 my @comment_fields = qw! name url comment !;
87 # What fields are used by TrackBacks?
88 my @trackback_fields = qw! blog_name url title excerpt !;
90 # Limit all fields to this length or less (just in case).
91 my $max_param_length = 10000;
94 # --- Variables for use in flavour templates (e.g., as $feedback::foo) ---
96 # Comment and TrackBack fields, for use in the comment, preview, and
97 # trackback templates.
99 $name_link = ''; # Combines name and link for email/URL
104 $title_link = ''; # Combines title and link to article
106 $url = ''; # Also used in $name_link, $title_link
108 # Field values for previewed comments, used in the commentform template.
110 $comment_preview = '';
113 # Message displayed in response to a comment submission (e.g., to display
114 # an error message), for use in the story or foot templates. The response is
115 # formatted for use in HTML/XHTML content.
116 $comment_response = '';
118 # XML message displayed in response to a TrackBack ping (e.g., to display
119 # an error message or indicate success), per the TrackBack Technical
120 # Specification <http://www.sixapart.com/pronet/docs/trackback_spec>.
121 $trackback_response = '';
123 # All comments and TrackBacks for a particular story, for use in the story
124 # template for an individual story page. Also includes content from the
125 # comments_head/comments_foot and trackbacks_head/trackbacks_foot templates.
129 # Counts of comments and TrackBacks for a story, for use in the story
130 # template (e.g., for index and archive pages).
132 $trackbacks_count = 0;
133 $count = 0; # total of both
135 # Previewed comment for a particular story, for use in the story
136 # template for an individual story page.
139 # Default comment submission form, for use in the foot template (for an
140 # individual story page). The plug-in sets this value to null if comments
141 # are disabled or in cases where the page is not for an individual story
142 # or the story is older than the allowed comment period.
145 # TrackBack discovery information, for use in the foot template (for
146 # an individual story page). The code sets this value to null if TrackBacks
147 # are disabled or in cases where the page is not for an individual story
148 # or the story is older than the allowed TrackBack period.
152 # --- External modules required ---
154 use CGI qw/:standard/;
160 # --- Global variables (used in interpolation) ---
162 use vars qw! $fb_dir $trackback_flavour $name $name_link $date $comment
163 $blog_name $title $name_preview $comment_preview $url_preview
164 $comment_response $trackback_response $comments $trackbacks
165 $comments_count $trackbacks_count $count $preview $commentform
169 # --- Private static variables ---
171 # Spam blacklist array.
172 my @blacklist_entries = ();
174 # File handle for use in reading/writing the feedback file, etc.
175 my $fh = new FileHandle;
177 # Path and filename for the main feedback file for a story, and item name
178 # used in contructing filenames for files containing moderated items.
182 # Whether comments or TrackBacks are closed for a given story.
183 my $closed_comments = 0;
184 my $closed_trackbacks = 0;
187 # --- Plug-in initialization ---
189 # Strip potentially confounding final slash from feedback directory path.
192 # Strip potentially confounding initial period from file extension.
193 $fb_extension =~ s!^\.!!;
195 # Initialize the default templates; use $blosxom::template so we can leverage
196 # the Blosxom template subroutine (whether default or replaced by a plug-in).
199 last if /^(__END__)?$/;
200 # TODO: Fix this to correctly handle empty flavours (i.e., no $txt).
201 my ($ct, $comp, $txt) = /^(\S+)\s(\S+)(?:\s(.*))?$/;
202 # my ($ct, $comp, $txt) = /^(\S+)\s(\S+)\s(.*)$/;
203 $txt = '' unless defined($txt);
205 $blosxom::template{$ct}{$comp} = $txt;
208 # Moderation implies notification.
209 $notify_comments = 1 if $moderate_comments;
210 $notify_trackbacks = 1 if $moderate_trackbacks;
213 # --- Plug-in subroutines ---
215 # Create feedback directory if needed.
217 # The $fb_dir variable must be set to activate feedback.
220 "The \$fb_dir configurable variable is not set; "
221 . "please set it to enable comments or TrackBacks.\n";
225 # The value of $fb_dir must be a writeable directory.
226 if (-e $fb_dir && !(-d $fb_dir && -w $fb_dir)) {
227 warn "feedback: The feedback directory '$fb_dir' "
228 . "must be a writeable directory; please rename or remove it "
229 . "and Blosxom will create it properly for you.\n";
233 # The $fb_dir does not yet exist, so Blosxom will create it.
234 unless (-e $fb_dir) {
235 return 0 unless (mk_feedback_dir($fb_dir));
242 # Decide whether to close comments and TrackBacks for a story.
244 my ($pkg, $file, $date_ref, $mtime, $dw, $mo, $mo_num, $da, $ti, $yr) = @_;
246 # A positive value of $comment_period represents the time in seconds
247 # during which posting comments or TrackBacks is allowed after a
248 # story has been published. (Note that updating a story has the effect
249 # of reopening the feedback period.) A zero or negative value for
250 # $comment_period means that posting feedback is always allowed.
252 if ($comment_period <= 0) {
253 $closed_comments = 0;
254 } elsif ($allow_comments && (time - $mtime) > $comment_period) {
255 $closed_comments = 1;
257 $closed_comments = 0;
260 # $trackback_period works the same way as $comment_period.
262 if ($trackback_period <= 0) {
263 $closed_trackbacks = 0;
264 } elsif ($allow_trackbacks && (time - $mtime) > $trackback_period) {
265 $closed_trackbacks = 1;
267 $closed_trackbacks = 0;
274 # Parse posted TrackBacks and comments and take action as appropriate.
275 # Retrieve comments and TrackBacks and format according to the templates.
276 # Display a comment form and/or TrackBack URL as appropriate.
279 my ($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_;
284 # We have five possible tasks in this subroutine:
286 # * handle submitted TrackBack pings or comments (or related requests)
287 # * display previously-submitted TrackBacks or comments
288 # * display a comment being previewed
289 # * display a form for entering a comment (or editing a previewed one)
290 # * display information about submitting TrackBacks
292 # Exactly what we do depends whether we are rendering dynamically or
293 # statically and on the type of request (GET, HEAD, or POST) (when
294 # dynamically rendering), the Blosxom flavour, the parameters associated
295 # with the request, the age of the story, and the way the feedback
296 # plug-in itself is configured.
298 # Make $path empty if at top level, preceded by a single slash otherwise.
299 !defined($path) and $path = "";
300 $path =~ s!^/*!!; $path &&= "/$path";
302 # Set feedback path and filename for this story.
304 $fb_fn = $filename . '.' . $fb_extension;
306 # Determine whether this is an individual story page or not.
308 $blosxom::path_info =~ m!^(.*/)?(.+)\.(.+)$! ? 1 : 0;
310 # For dynamic rendering of an individual story page *only*, check to
311 # see if this is a feedback-related request, take action, and formulate
314 # We have five possible cases: TrackBack ping, comment preview, comment
315 # post, moderator approval, and moderator rejection. These are
316 # distinguished based on the type of request (POST vs. GET/HEAD),
317 # the flavour (for TrackBack pings only), and the request parameters.
319 $submission_type = $comment_response = $trackback_response = '';
320 if ($blosxom::static_or_dynamic eq 'dynamic' && $is_story_page) {
321 ($submission_type, $status_msg) = process_submission();
322 if ($submission_type eq 'trackback') {
323 $trackback_response = format_tb_response($status_msg);
324 return 1; # All done.
325 } elsif ($submission_type eq 'comment'
326 || $submission_type eq 'preview'
327 || $submission_type eq 'approve'
328 || $submission_type eq 'reject') {
329 $comment_response = format_cmt_response($status_msg);
333 # Display previously-submitted comments and TrackBacks for this story.
334 # For index and and archive pages we just display counts of the comments
337 $comments = $trackbacks = '';
338 $comments_count = $trackbacks_count = 0;
339 if ($is_story_page) {
340 ($comments, $comments_count, $trackbacks, $trackbacks_count) =
343 ($comments_count, $trackbacks_count) = count_feedback();
345 $count = $comments_count + $trackbacks_count;
347 # If we are previewing a comment then format the comment for display.
349 if ($submission_type eq 'preview') {
350 $preview = get_preview($path);
353 # Display a form for comment submission, if we are on an individual
354 # story page and comments are (still) allowed. (If we are previewing
355 # a comment then the form will be pre-filled as appropriate.)
358 if ($is_story_page && $allow_comments) {
359 if ($closed_comments) {
361 "<p class=\"commentform\">"
362 . "Comments are closed for this story.</p>";
364 # Get the commentform template and interpolate variables in it.
366 &$blosxom::template($path,'commentform',$blosxom::flavour)
367 || &$blosxom::template($path,'commentform','general');
368 $commentform = &$blosxom::interpolate($commentform);
372 # Display information on submitting TrackPack pings (including code for
373 # TrackBack autodiscovery), if we are on an individual story page and
374 # TrackBacks are (still) allowed.
377 if ($is_story_page && $allow_trackbacks) {
378 if ($closed_trackbacks) {
380 "<p class=\"trackbackinfo\">"
381 . "Trackbacks are closed for this story.</p>";
383 # Get the trackbackinfo template and interpolate variables in it.
385 &$blosxom::template($path,'trackbackinfo',$blosxom::flavour)
386 || &$blosxom::template($path,'trackbackinfo','general');
387 $trackbackinfo = &$blosxom::interpolate($trackbackinfo);
391 # For interpolate_fancy to work properly when deciding whether to include
392 # certain content or not, the associated variables must be undefined if
393 # there is no actual content to be displayed.
395 $comment_response =~ m!^\s*$! and $comment_response = undef;
396 $comments =~ m!^\s*$! and $comments = undef;
397 $trackbacks =~ m!^\s*$! and $trackbacks = undef;
398 $preview =~ m!^\s*$! and $preview = undef;
399 $commentform =~ m!^\s*$! and $commentform = undef;
400 $trackbackinfo =~ m!^\s*$! and $trackbackinfo = undef;
406 # --- Helper subroutines ---
408 # Process a submitted HTTP request and take whatever action is appropriate.
409 # Returns the type of submission: 'trackback', 'comment', 'preview',
410 # 'approve', 'reject', or null for a request not related to feedback.
411 # Also sets $comment_response and $trackback_response;
413 sub process_submission {
414 my $submission_type = '';
417 if (request_method() eq 'POST') {
418 # We have two possible cases: a TrackBack ping (identified by
419 # the flavour extension) or a submitted comment.
421 if ($blosxom::flavour eq $trackback_flavour) {
422 $status_msg = handle_feedback('trackback');
423 $submission_type = 'trackback';
425 # Comment posts may or may not use a particular flavour
426 # extension, so we check for the value of the 'plugin'
427 # hidden field (from the comment form).
429 my $plugin_param = sanitize_param(param('plugin'));
430 if ($plugin_param eq 'writeback') {
431 # Comment previews are distinguished from comment posts
432 # by the value of the 'submit' parameter associated with
433 # the 'Post' and 'Preview' form buttons.
435 my $submit_param = sanitize_param(param('submit'));
437 if ($submit_param eq 'Preview') {
438 $status_msg = handle_feedback('preview');
439 $submission_type = 'preview';
440 } elsif ($submit_param eq 'Post') {
441 $status_msg = handle_feedback('comment');
442 $submission_type = 'comment';
444 $status_msg = "The submit parameter must have the value "
445 . "'Preview' or 'Post'";
449 } elsif (request_method() eq 'GET' || request_method() eq 'HEAD') {
450 my $moderate_param = sanitize_param(param('moderate'));
451 my $feedback_param = sanitize_param(param('feedback'));
453 if ($moderate_param) {
454 # We have two possible cases: moderator approval or rejection,
455 # distinguished based on the value of the 'moderate' parameter.
457 if (!$feedback_param) {
459 "You must provide a 'feedback' parameter and item.";
460 } elsif ($moderate_param eq 'approve') {
461 $status_msg = approve_feedback($feedback_param);
462 $submission_type = 'approve';
463 } elsif ($moderate_param eq 'reject') {
464 $status_msg = reject_feedback($feedback_param);
465 $submission_type = 'reject';
468 "'moderate' parameter must "
469 . "have the value 'approve' or 'reject'.";
474 return $submission_type, $status_msg;
478 # Retrieve comments and TrackBacks for a story and format them according
479 # to the appropriate templates for the story (based on the story's path).
480 # For comments we use the comment template for each individual comment,
481 # along with the optional comments_head and comments_foot templates (before
482 # and after the comments proper). For TrackBacks we use the corresponding
483 # trackback template for each TrackBack, together with the optional
484 # trackbacks_head and trackbacks_foot templates.
488 my ($comments, $comments_count, $trackbacks, $trackbacks_count);
490 $comments = $trackbacks = '';
491 $comments_count = $trackbacks_count = 0;
493 # Retrieve the templates for individual comments and TrackBacks.
494 my $comment_template =
495 &$blosxom::template($path, 'comment', $blosxom::flavour)
496 || &$blosxom::template($path, 'comment', 'general');
498 my $trackback_template =
499 &$blosxom::template($path, 'trackback', $blosxom::flavour)
500 || &$blosxom::template($path, 'trackback', 'general');
502 # Open the feedback file (if it exists) and read any comments or
503 # TrackBacks. Note that we can distinguish comments from TrackBacks
504 # because comments have a 'comment' field and TrackBacks don't.
507 if ($fh->open("$fb_dir$fb_path/$fb_fn")) {
508 foreach my $line (<$fh>) {
509 $line =~ /^(.+?): (.*)$/ and $param{$1} = $2;
510 if ( $line =~ /^-----$/ ) {
511 if ($param{'comment'}) {
512 $comment = format_comment($param{'comment'});
513 $date = format_date($param{'date'});
514 ($name, $name_link) =
515 format_name($param{'name'}, $param{'url'});
517 my $cmt = $comment_template;
518 $cmt = &$blosxom::interpolate($cmt);
524 $blog_name = format_blog_name($param{'blog_name'});
525 $excerpt = format_excerpt($param{'excerpt'});
526 $date = format_date($param{'date'});
527 ($title, $title_link) =
528 format_title($param{'title'}, $param{'url'});
530 my $trackback = $trackback_template;
531 $trackback = &$blosxom::interpolate($trackback);
533 $trackbacks .= $trackback;
541 return ($comments, $comments_count, $trackbacks, $trackbacks_count);
545 # Retrieve comments and TrackBacks for a story and (just) count them.
548 my $comments_count = 0;
549 my $trackbacks_count = 0;
551 # Open the feedback file (if it exists) and count any comments or
552 # TrackBacks. Note that we can distinguish comments from TrackBacks
553 # because comments have a 'comment' field and TrackBacks don't.
556 if ($fh->open("$fb_dir$fb_path/$fb_fn")) {
557 foreach my $line (<$fh>) {
558 $line =~ /^(.+?): (.*)$/ and $param{$1} = $2;
559 if ( $line =~ /^-----$/ ) {
560 if ($param{'comment'}) {
570 return ($comments_count, $trackbacks_count);
574 # Format a previewed comment according to the appropriate preview template
575 # for the story (based on the story's path).
581 # Retrieve the comment template (also used for previewed comments).
582 my $comment_template =
583 &$blosxom::template($path, 'comment', $blosxom::flavour)
584 || &$blosxom::template($path, 'comment', 'general');
586 # Format the previewed comment using the submitted values.
587 $comment = format_comment($comment_preview);
588 $date = format_date($date_preview);
589 ($name, $name_link) =
590 format_name($name_preview, $url_preview);
592 $preview = &$blosxom::interpolate($comment_template);
598 # Create top-level directory to hold feedback files, and make it writeable.
599 sub mk_feedback_dir {
600 my $mkdir_r = mkdir("$fb_dir", 0755);
602 ? "feedback: $fb_dir created.\n"
603 : "feedback: Could not create $fb_dir.\n";
604 $mkdir_r or return 0;
606 my $chmod_r = chmod 0755, $fb_dir;
608 ? "feedback: $fb_dir set to 0755 permissions.\n"
609 : "feedback: Could not set permissions on $fb_dir.\n";
610 $chmod_r or return 0;
612 warn "feedback: feedback is enabled!\n";
617 # Create subdirectories of feedback directory as necessary.
618 sub mk_feedback_subdir {
622 return 1 if !defined($dir) or $dir eq '';
624 foreach (('', split /\//, $dir)) {
628 unless (-d "$fb_dir/$p" or mkdir "$fb_dir/$p", 0755);
635 # Process a submitted comment or TrackBack.
636 sub handle_feedback {
637 my $feedback_type = shift;
643 # Set up to handle either a comment, preview, or TrackBack as requested.
644 if ($feedback_type eq 'comment') {
647 } elsif ($feedback_type eq 'preview') {
655 my $allow = $is_comment ? $allow_comments : $allow_trackbacks;
656 my $closed = $is_comment ? $closed_comments : $closed_trackbacks;
657 my $period = $is_comment ? $comment_period : $trackback_period;
658 my $akismet = $is_comment ? $akismet_comments : $akismet_trackbacks;
659 my $blacklist = $is_comment ? $blacklist_comments : $blacklist_trackbacks;
660 my $notify = $is_comment ? $notify_comments : $notify_trackbacks;
661 my $moderate = $is_comment ? $moderate_comments : $moderate_trackbacks;
662 my @fields = $is_comment ? @comment_fields : @trackback_fields;
664 # Reject request if feedback is not (still) allowed.
665 unless ($allow && !$closed) {
668 "This story is older than " . ($period/86400) . " days. "
669 . ($is_comment ? "Comments" : "TrackBacks")
670 . " have now been closed.";
673 ($is_comment ? "Comments" : "TrackBacks")
674 . " are not enabled for this site.";
679 # Filter out the "good" fields from the CGI parameters.
680 my %params = copy_params(\@fields);
682 # Comments must have (at least) a comment parameter, and TrackBacks a URL.
684 unless ($params{'comment'}) {
686 "You didn't enter anything in the comment field.";
689 } elsif (!$params{'url'}) {
690 $status_msg = "No URL specified for the TrackBack";
694 # Check feedback to see if it's spam.
695 if (is_spam(\%params, $is_comment, $akismet, $blacklist)) {
696 # If we are previewing a comment then we allow the poster a
697 # chance to revise the comment; otherwise we just reject it.
701 "Your comment appears to be spam and will be rejected "
702 . "unless it is revised. ";
705 "Your feedback was rejected because it appears to be spam; "
706 . "please contact the site administrator if you believe that "
707 . "your feedback was rejected in error.";
712 # If we are previewing a comment then just save the fields for later
713 # use in the previewed comment and (as prefilled values) in the comment
714 # form. Otherwise attempt to save the new feedback information, either
715 # into the permanent feedback file for this story (if no moderation) or
716 # into a temporary file (for later moderation).
719 $status_msg .= save_preview(\%params);
721 ($fb_item, $status_msg) = save_feedback(\%params, $moderate);
722 return $status_msg unless $fb_item;
724 # Send a moderation message or notify blog owner of the new feedback.
725 if ($moderate || $notify) {
726 send_notification(\%params, $moderate, $fb_item);
734 # Make a "safe" copy of the CGI parameters based on the expected
735 # field names associated with either a comment or TrackBack.
737 my $fields_ref = shift;
740 foreach my $key (@$fields_ref) {
741 my $value = substr(param($key), 0, $max_param_length) || "";
743 # Eliminate leading and trailing whitespace, use carriage returns
744 # as line delimiters, and collapse multiple blank lines into one.
748 $value =~ s/\r?\n\r?/\r/mg;
749 $value =~ s/\r\r\r*/\r\r/mg;
751 $params{$key} = $value;
758 # Send notification or moderation email to blog owner.
759 sub send_notification {
760 my ($params_ref, $moderate, $fb_item) = @_;
762 unless ($address && $smtp_server) {
763 warn "feedback: No address or SMTP server for notifications\n";
767 my $message = "New feedback for your post \"$blosxom::title\" ("
768 . $blosxom::path_info . "):\n\n";
770 if ($$params_ref{'comment'}) {
771 $message .= "Name : " . $$params_ref{'name'} . "\n";
772 $message .= "Email/URL: " . $$params_ref{'url'} . "\n";
773 $message .= "Comment :\n";
774 my $comment = $$params_ref{'comment'};
775 $comment =~ s!\r!\n!g;
776 $message .= $comment . "\n";
778 $message .= "Blog name: " . $$params_ref{'blog_name'} . "\n";
779 $message .= "Article : " . $$params_ref{'title'} . "\n";
780 $message .= "URL : " . $$params_ref{'url'} . "\n";
781 $message .= "Excerpt :\n";
782 my $excerpt = $$params_ref{'excerpt'};
783 $excerpt =~ s!\r!\n!g;
784 $message .= $excerpt . "\n";
788 # For TrackBacks use the default flavour for the approve/reject URI.
789 my $moderate_flavour = $blosxom::flavour;
790 $moderate_flavour eq $trackback_flavour
791 and $moderate_flavour = $blosxom::default_flavour;
793 $message .= "\n\nTo approve this feedback, please click on the URL\n"
794 . "$blosxom::url$blosxom::path/$blosxom::fn.$moderate_flavour"
795 . "?moderate=approve;feedback=" . uri_escape($fb_item) . "\n";
797 $message .= "\nTo reject this feedback, please click on the URL\n"
798 . "$blosxom::url$blosxom::path/$blosxom::fn.$moderate_flavour"
799 . "?moderate=reject;feedback=" . uri_escape($fb_item) . "\n";
802 # Load Net::SMTP module only now that it's needed.
803 require Net::SMTP; Net::SMTP->import;
805 my $smtp = Net::SMTP->new($smtp_server);
806 $smtp->mail($address);
809 $smtp->datasend("To: $address\n");
810 $smtp->datasend("From: $address\n");
811 $smtp->datasend("Subject: [$blosxom::blog_title] Feedback: "
812 . "\"$blosxom::title\"\n");
813 $smtp->datasend("\n\n");
814 $smtp->datasend($message);
822 # Format the date used in comments and TrackBacks. If the argument is a
823 # number then it is considered to be a date/time in seconds since the
824 # (Perl) epoch; otherwise we assume that the date is already formatted.
825 # (This may allow the feedback plug-in to use legacy writeback files.)
828 my $date_value = shift;
830 if ($date_value =~ m!^\d+$!) {
831 my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) =
832 localtime($date_value);
835 # Modify the following to match your preference.
836 return sprintf("%4d-%02d-%02d %02d:%02d",
837 $year, $mon+1, $mday, $hour, $min);
844 # Format the name used in comments.
846 my ($name, $url) = @_;
848 # If the user didn't supply a name, try to use something sensible.
850 if ($url =~ m/^mailto:/) {
851 $name = substr($url, 7);
853 $name = $default_name;
857 # Link to a URL if one was provided.
859 $url ? "<a href=\"$url\" rel=\"nofollow\">$name</a>" : $name ;
861 return $name, $name_link;
865 # Format the comment response message.
866 sub format_cmt_response {
867 my $response = shift;
869 # Clean up the response.
870 $response =~ s/^\s+//;
871 $response =~ s/\s+$//;
873 # Convert the response into a special type of paragraph.
874 # NOTE: A value 'OK' for $response indicates a successful comment.
875 if ($response eq 'OK') {
876 $response = '<p class="comment-response">Thanks for the comment!</p>';
878 $response = '<p class="comment-response">' . $response . '</p>';
885 # Format the TrackBack response message.
886 sub format_tb_response {
887 my $response = shift;
889 # Clean up the response.
890 $response =~ s/^\s+//;
891 $response =~ s/\s+$//;
893 # Convert the response into an XML message per the TrackBack Technical
894 # Specification <http://www.sixapart.com/pronet/docs/trackback_spec>.
895 # NOTE: A value 'OK' for $response indicates a successful TrackBack;
896 # note that this value is *not* used as part of the TrackBack response.
898 if ($response eq 'OK') {
899 $response = "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>"
900 . "<response><error>0</error></response>";
902 $response = "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>"
903 . "<response><error>1</error>"
904 . "<message>$response</message></response>";
911 # Format the comment itself.
915 # TODO: Support other comment formats such as Textile.
917 if ($comment_format eq 'none') {
918 # A no-op, assumes formatting will be added in the template.
919 } elsif ($comment_format eq 'plaintext') {
920 # Simply convert the comment into a series of paragraphs.
921 $comment = '<p>' . $comment . '</p>';
922 $comment =~ s!\r\r!</p><p>!mg;
923 } elsif ($comment_format eq 'markdown'
924 && $blosxom::plugins{'Markdown'} > 0) {
925 $comment = &Markdown::Markdown($comment);
932 # Format the blog name used in TrackBacks.
933 sub format_blog_name {
934 my $blog_name = shift;
936 $blog_name or $blog_name = $default_blog_name;
942 # Format the title used in TrackBacks.
944 my ($title, $url) = @_;
947 # Link to article, quoting the title if one was supplied.
949 $title_link = "\"<a href=\"$url\" rel=\"nofollow\">$title</a>\"";
951 $title = $default_title;
952 $title_link = "<a href=\"$url\" rel=\"nofollow\">$title</a>";
955 return $title, $title_link;
959 # Format the TrackBack excerpt.
963 # TODO: Truncate excerpts at some reasonable length.
965 # Simply convert the excerpt into a series of paragraphs.
967 $excerpt = '<p>' . $excerpt . '</p>';
968 $excerpt =~ s!\r\r!</p><p>!mg;
975 # Read in the MT-Blacklist file.
978 # No need to do anything if we've already read in the blacklist file.
979 return 1 if @blacklist_entries;
981 # Try to find the blacklist file and open it.
982 open BLACKLIST, "$blacklist_file"
983 or die "Can't read '$blacklist_file', $!\n";
985 my @lines = grep {! /^\s*\#/ } <BLACKLIST>;
987 die "No blacklists?\n" unless @lines;
989 foreach my $line (@lines) {
991 $line =~ s/\s*[^\\]\#.*//;
993 push @blacklist_entries, $line;
995 die "No entries in blacklist file?\n" unless @blacklist_entries;
1001 # Do spam tests on comment or TrackBack; returns 1 if spam, 0 if OK.
1003 my ($params_ref, $is_comment, $akismet, $blacklist) = @_;
1005 # Perform a series of spam tests. If any show positive then reject.
1007 # Does the host part of the URL reference an IP address?
1008 return 1 if uses_ipaddr($$params_ref{'url'});
1010 # Does the comment or TrackBack match against the Akismet service?
1011 return 1 if $akismet && matches_akismet($params_ref, $is_comment);
1013 # Does the comment or TrackBack match against the MT-Blacklist file
1016 if $blacklist && matches_blacklist((join "\n", values %$params_ref));
1018 # TODO: Add other useful spam checks.
1020 # Got by all the tests, so assume it's not spam.
1025 # Check host part of URL to see if it is an IP address.
1029 return 0 unless $uri;
1031 # Construct URI object.
1032 my $u = URI->new($uri);
1034 # Return if this not actually a URI (i.e., it's an email address).
1035 return 0 unless defined($u->scheme);
1037 # Check for an IPv4 or IPv6 address on http/https URLs.
1038 if ($u->scheme eq 'http' || $u->scheme eq 'https') {
1039 if ($u->authority =~ m!^\[?\d!) {
1048 # Check comment or TrackBack against the Akismet online service.
1049 sub matches_akismet {
1050 my ($params_ref, $is_comment) = @_;
1052 # Load Net:Akismet module only now that it's needed.
1053 require Net::Akismet; Net::Akismet->import;
1055 # Attempt to connect to the Askimet service.
1056 my $akismet = Net::Akismet->new(KEY => $wordpress_api_key,
1057 URL => $blosxom::url);
1059 warn "feedback: Akismet key verification failed\n";
1063 # Set up fields to be verified. Note that we do not use the REFERRER,
1064 # PERMALINK, or COMMENT_AUTHOR_EMAIL fields supported by Akismet.
1066 my %fields = (USER_IP => $ENV{'REMOTE_ADDR'});
1068 $fields{COMMENT_TYPE} = 'comment';
1069 $fields{COMMENT_CONTENT} = $$params_ref{'comment'};
1070 $fields{COMMENT_AUTHOR} = $$params_ref{'name'};
1071 $fields{COMMENT_AUTHOR_URL} = $$params_ref{'url'};
1073 $fields{COMMENT_TYPE} = 'trackback';
1074 $fields{COMMENT_CONTENT} =
1075 $$params_ref{'title'} . "\n" . $$params_ref{'excerpt'};
1076 $fields{COMMENT_AUTHOR} = $$params_ref{'blog_name'};
1077 $fields{COMMENT_AUTHOR_URL} = $$params_ref{'url'};
1081 return 1 if $akismet->check(%fields) eq 'true';
1088 # Check comment or TrackBack against the MT-Blacklist file (deprecated).
1089 sub matches_blacklist {
1090 my $params_string = shift;
1092 # Read in the blacklist file.
1095 # Check each blacklist entry against the comment or TrackBack.
1096 foreach my $spam (@blacklist_entries) {
1098 return 1 if $params_string =~ /$spam/;
1105 # Save comment or TrackBack to disk. If moderating, returns the (randomly-
1106 # generated) id of the item saved for later approval or rejection (plus
1107 # a status message). If not moderating returns the name of the feedback
1108 # file in which the item was saved instead of the id. Returns null on errors.
1111 my ($params_ref, $moderate) = @_;
1113 my $feedback_fn = '';
1114 my $status_msg = '';
1116 # Clear values used to prefill commentform.
1117 $name_preview = $url_preview = $comment_preview = '';
1119 # Create a new directory if needed to contain the feedback file.
1120 unless (mk_feedback_subdir($fb_path)) {
1121 $status_msg = 'Could not save comment or TrackBack.';
1122 return '', $status_msg;
1125 # Save into the main feedback file or a temporary file, depending on
1126 # whether feedback is being moderated or not.
1128 $fb_item = rand_alphanum(8);
1129 $feedback_fn = $fb_item . '-' . $fb_fn;
1131 $feedback_fn = $fb_fn;
1134 # Attempt to open the file and append to it.
1135 unless ($fh->open(">> $fb_dir$fb_path/$feedback_fn")) {
1136 warn "couldn't >> $fb_dir$fb_path/$feedback_fn\n";
1137 $status_msg = 'Could not save comment or TrackBack.';
1138 return '', $status_msg;
1141 # Write each parameter out as a line in the file.
1142 foreach my $key (sort keys %$params_ref) {
1143 my $value = $$params_ref{$key};
1145 # Eliminate leading and trailing whitespace, use carriage returns
1146 # as line delimiters, and collapse multiple blank lines into one.
1150 $value =~ s/\r?\n\r?/\r/mg;
1151 $value =~ s/\r\r\r*/\r\r/mg;
1153 # Ensure URL and other fields are sanitized.
1154 if ($key eq 'url') {
1155 $value = sanitize_uri($value);
1157 $value = escapeHTML($value);
1160 print $fh "$key: $value\n";
1163 # Save the date/time (in seconds) and IP address as well.
1164 print $fh "date: " . time() ."\n";
1165 print $fh "ip: " . $ENV{'REMOTE_ADDR'} . "\n";
1167 # End the entry and close the file.
1168 print $fh "-----\n";
1171 # Set responses to indicate success.
1174 "Your feedback has been submitted for a moderator's approval; "
1175 . "it may take 24 hours or more to appear on the site.";
1176 return $fb_item, $status_msg;
1179 return $feedback_fn, $status_msg;
1184 # Generate random alphanumeric string of the specified length.
1187 return '' if $size <= 0;
1189 my @alphanumeric = ('a'..'z', 'A'..'Z', 0..9);
1190 return join '', map $alphanumeric[rand @alphanumeric], 0..$size;
1194 # Save previewed comment for later viewing (on the same page).
1195 # Sets $status_msg with an appropriate message.
1197 my $params_ref = shift;
1200 # Save each parameter for later use in the preview template.
1201 foreach my $key (sort keys %$params_ref) {
1202 my $value = $$params_ref{$key};
1204 # Eliminate leading and trailing whitespace, use carriage returns
1205 # as line delimiters, and collapse multiple blank lines into one.
1209 $value =~ s/\r?\n\r?/\r/mg;
1210 $value =~ s/\r\r\r*/\r\r/mg;
1212 # Ensure URL and other fields are sanitized.
1213 if ($key eq 'url') {
1214 $value = sanitize_uri($value);
1216 $value = escapeHTML($value);
1219 if ($key eq 'name') {
1220 $name_preview = $value;
1221 } elsif ($key eq 'url') {
1222 $url_preview = $value;
1223 } elsif ($key eq 'comment') {
1224 $comment_preview = $value;
1228 # Save the date/time (in seconds) as well.
1229 $date_preview = time();
1231 # Set response to indicate success.
1233 "Please review your previewed comment below and submit it when "
1240 # Approve a moderated comment or TrackBack (add it to feedback file).
1241 sub approve_feedback {
1244 my $status_msg = '';
1246 # Construct filename containing item to be approved, checking the
1247 # item name against the proper format from save_feedback().
1248 if ($item =~ m!^[a-zA-Z0-9]{8}!) {
1249 $item_fn = $item . "-" . $fb_fn;
1252 "The item name to be approved was not in the proper format.";
1256 # Read lines from file containing the approved comment or TrackBack.
1257 unless ($fh->open("$fb_dir$fb_path/$item_fn")) {
1258 warn "feedback: couldn't < $fb_dir$fb_path/$item_fn\n";
1260 "There was a problem approving the comment or TrackBack.";
1264 my @new_feedback = ();
1266 push @new_feedback, $_;
1270 # Attempt to open the story's feedback file and append to it.
1271 # TODO: Try to make this more resistant to race conditions.
1273 unless ($fh->open(">> $fb_dir$fb_path/$fb_fn")) {
1274 warn "couldn't >> $fb_dir$fb_path/$fb_fn\n";
1276 "There was a problem approving the comment or TrackBack.";
1280 foreach my $line (@new_feedback) {
1284 # Close the feedback file, delete the file with the approved item.
1286 chdir("$fb_dir$fb_path")
1287 or warn "feedback: Couldn't cd to $fb_dir$fb_path\n";
1289 or warn "feedback: Couldn't delete $item_fn\n";
1291 # Set response to indicate successful approval.
1292 $status_msg = "Feedback '$item' approved by moderator. ";
1298 # Reject a moderated comment or TrackBack (delete the temporary file).
1299 sub reject_feedback {
1304 # Construct filename containing item to be rejected, checking the
1305 # item name against the proper format from save_feedback().
1306 if ($item =~ m!^[a-zA-Z0-9]{8}!) {
1307 $item_fn = $item . "-" . $fb_fn;
1310 "The item name to be rejected was not in the proper format.";
1314 # TODO: Optionally report comment or TrackBack to Akismet as spam.
1316 # Delete the file with the rejected item.
1317 chdir("$fb_dir$fb_path")
1318 or warn "feedback: Couldn't cd to '$fb_dir$fb_path'\n";
1320 or warn "feedback: Couldn't delete '$item_fn'\n";
1322 # Set response to indicate successful rejection.
1323 $status_msg = "Feedback '$item' rejected by moderator.";
1329 # Sanitize a query parameter to remove unexpected characters.
1332 my $param = shift || '';
1334 # Allow only alphanumeric, underscore, dash, and period.
1335 $param and $param =~ s/[^-.\w]/_/go;
1345 # Construct URI object.
1346 my $u = URI->new($uri);
1348 # If it's not a URI then assume it's an email address.
1349 $u->scheme('mailto') unless defined($u->scheme);
1351 # We check email addresses (if allowed) separately from web addresses.
1352 if ($allow_mailto && $u->scheme eq 'mailto') {
1353 # Make sure this is a valid RFC 822 address.
1354 if (valid($u->opaque)) {
1355 $uri = $u->canonical;
1357 $status_msg = "You submitted an invalid email address. ";
1360 } elsif ($u->scheme eq 'http' || $u->scheme eq 'https') {
1361 if ($u->authority =~ m!^.*@!) {
1363 "Userids and passwords are not permitted in the URL field. ";
1365 } elsif ($u->authority =~ m!^\d! || $u->authority =~ m!^\[\d!) {
1367 "IP addresses are not permitted in the URL field. ";
1370 $uri = $u->canonical;
1374 "You specified an invalid scheme in the URL field; ";
1375 if ($allow_mailto) {
1377 "the only allowed schemes are 'http', 'https', and 'mailto'. ";
1380 "the only allowed schemes are 'http' and 'https'. ";
1388 # The following is taken from the Mail::RFC822::Address module, for
1389 # sites that don't have that module loaded.
1392 # Preloaded methods go here.
1393 my $lwsp = "(?:(?:\\r\\n)?[ \\t])";
1396 # Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
1397 # comment. We must allow for lwsp (or comments) after each of these.
1398 # This regexp will only work on addresses which have had comments stripped
1399 # and replaced with lwsp.
1401 my $specials = '()<>@,;:\\\\".\\[\\]';
1402 my $controls = '\\000-\\031';
1404 my $dtext = "[^\\[\\]\\r\\\\]";
1405 my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$lwsp*";
1407 my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$lwsp)*\"$lwsp*";
1409 # Use zero-width assertion to spot the limit of an atom. A simple
1410 # $lwsp* causes the regexp engine to hang occasionally.
1411 my $atom = "[^$specials $controls]+(?:$lwsp+|\\Z|(?=[\\[\"$specials]))";
1412 my $word = "(?:$atom|$quoted_string)";
1413 my $localpart = "$word(?:\\.$lwsp*$word)*";
1415 my $sub_domain = "(?:$atom|$domain_literal)";
1416 my $domain = "$sub_domain(?:\\.$lwsp*$sub_domain)*";
1418 my $addr_spec = "$localpart\@$lwsp*$domain";
1420 my $phrase = "$word*";
1421 my $route = "(?:\@$domain(?:,\@$lwsp*$domain)*:$lwsp*)";
1422 my $route_addr = "\\<$lwsp*$route?$addr_spec\\>$lwsp*";
1423 my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
1425 my $group = "$phrase:$lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
1426 my $address = "(?:$mailbox|$group)";
1428 return "$lwsp*$address";
1431 sub strip_comments {
1433 # Recursively remove comments, and replace with a single space. The simpler
1434 # regexps in the Email Addressing FAQ are imperfect - they will miss escaped
1435 # chars in atoms, for example.
1437 while ($s =~ s/^((?:[^"\\]|\\.)*
1438 (?:"(?:[^"\\]|\\.)*"(?:[^"\\]|\\.)*)*)
1439 \((?:[^()\\]|\\.)*\)/$1 /osx) {}
1443 # valid: returns true if the parameter is an RFC822 valid address
1446 my $s = strip_comments(shift);
1449 $rfc822re = make_rfc822re();
1452 return $s =~ m/^$rfc822re$/so;
1459 # Default feedback templates.
1461 html comment \n<div class="comment"><p>$feedback::name_link wrote at $feedback::date:</p>\n<blockquote>$feedback::comment</blockquote></div>
1462 html trackback \n<div class="trackback"><p>$feedback::blog_name mentioned this post in $feedback::title_link<?$feedback::excerpt eq="">.</p></?><?$feedback::excerpt ne="">:</p>\n<blockquote>$feedback::excerpt</blockquote></?></div>
1463 html commentform \n<form method="POST" action="$blosxom::url$blosxom::path/$blosxom::fn.$blosxom::flavour">\n<table><tr><td>Name:</td><td><input name="name" size="35" value="$feedback::name_preview"></td></tr>\n<tr><td>URL (optional):</td><td><input name="url" size="35" value="$feedback::url_preview"></td></tr>\n<tr><td>Comments:</td><td><textarea name="comment" rows="5" cols="60">$feedback::comment_preview</textarea></td></tr>\n<tr><td><input type="hidden" name="plugin" value="writeback"><input type="submit" name="submit" value="Preview"></td><td><input type="submit" name="submit" value="Post"></td></tr>\n</table></form>
1464 html trackbackinfo <p>URL for TrackBack pings: <code>$blosxom::url$blosxom::path/$blosxom::fn.$feedback::trackback_flavour</code></p>\n<!--\n<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/">\n<rdf:Description rdf:about="$blosxom::url$blosxom::path/$blosxom::fn.$blosxom::flavour" dc:identifier="$blosxom::url$blosxom::path/$blosxom::fn.$blosxom::flavour" dc:title="$blosxom::title" trackback:ping="$blosxom::url$blosxom::path/$blosxom::fn.$feedback::trackback_flavour" />\n</rdf:RDF>\n-->
1465 general comment \n<div class="comment"><p>$feedback::name_link wrote at $feedback::date:</p>\n<blockquote>$feedback::comment</blockquote></div>
1466 general trackback \n<div class="trackback"><p>$feedback::blog_name mentioned this post in $feedback::title_link<?$feedback::excerpt eq="">.</p></?><?$feedback::excerpt ne="">:</p>\n<blockquote>$feedback::excerpt</blockquote></?></div>
1467 general commentform \n<form method="POST" action="$blosxom::url$blosxom::path/$blosxom::fn.$blosxom::flavour">\n<table><tr><td>Name:</td><td><input name="name" size="35" value="$feedback::name_preview"></td></tr>\n<tr><td>URL (optional):</td><td><input name="url" size="35" value="$feedback::url_preview"></td></tr>\n<tr><td>Comments:</td><td><textarea name="comment" rows="5" cols="60">$feedback::comment_preview</textarea></td></tr>\n<tr><td><input type="hidden" name="plugin" value="writeback"><input type="submit" name="submit" value="Preview"></td><td><input type="submit" name="submit" value="Post"></td></tr>\n</table></form>
1468 general trackbackinfo <p>URL for TrackBack pings: <code>$blosxom::url$blosxom::path/$blosxom::fn.$feedback::trackback_flavour</code></p>\n<!--\n<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/">\n<rdf:Description rdf:about="$blosxom::url$blosxom::path/$blosxom::fn.$blosxom::flavour" dc:identifier="$blosxom::url$blosxom::path/$blosxom::fn.$blosxom::flavour" dc:title="$blosxom::title" trackback:ping="$blosxom::url$blosxom::path/$blosxom::fn.$feedback::trackback_flavour" />\n</rdf:RDF>\n-->
1469 trackback content_type application/xml
1471 trackback story $feedback::trackback_response
1478 Blosxom Plug-in: feedback
1482 Provides comments and TrackBacks
1483 (C<http://www.movabletype.org/trackback/>); also supports comment and
1484 TrackBack moderation and spam filtering using Akismet and/or
1485 MT-Blacklist (deprecated). Inspired by the original writeback plug-in
1486 and the various enhanced versions of it.
1488 Comments and TrackBack pings for a particular story are kept in
1489 C<$fb_dir/$path/$filename.wb>.
1493 Drop this feedback plug-in file into your plug-ins directory (whatever
1494 you set as C<$plugin_dir> in C<blosxom.cgi>), and modify the file to
1495 set the configurable variable C<$fb_dir>. You must also modify the
1496 variable C<$wordpress_api_key> if you are using the Akismet spam
1497 blacklist service, the variable C<$blacklist_file> if you are using
1498 the MT-Blacklist file (deprecated), and the variables C<$address> and
1499 C<$smtp_server> if you want feedback notification or moderation. (See
1500 below for more information on these optional features.)
1502 Note that by default all comments and TrackBacks are allowed, with no
1503 spam checking, moderation, or notification.
1505 Modify your story template (e.g., C<story.html> in your Blosxom data
1506 directory) to include the variables C<$feedback::comments> and
1507 C<$feedback::trackbacks> at the points where you'd like comments and
1508 trackbacks to be inserted.
1510 Modify your story template or foot template (e.g., C<foot.html> in
1511 your Blosxom data directory) to include the variables
1512 C<$feedback::comment_response>, C<$feedback::preview>,
1513 C<$feedback::commentform> and C<$feedback::trackbackinfo> at the
1514 points where you'd like to insert the response to a submitted comment,
1515 the previewed comment (if any), the comment submission form and the
1516 TrackBack information (including TrackBack auto-discovery code).
1518 =head1 CONFIGURATION
1520 By default C<$fb_dir> is set to put the feedback directory and its
1521 contents in the plug-in state directory. (For example, if
1522 C<$plugin_state_dir> is C</foo/blosxom/state> then the feedback
1523 directory C<$fb_dir> is set to C</foo/blosxom/state/feedback>.)
1524 However a better approach may be to keep the feedback directory at the
1525 same level as C<$datadir>. (For example, if C<$datadir> is
1526 C</foo/blosxom/data> then use C</foo/blosxom/feedback> for the
1527 feedback directory.) This helps ensure that you don't accidentally
1528 delete previously-submitted comments and TrackBacks (e.g., if you
1529 clean out the plug-in state directory).
1531 Once C<$fb_dir> is set, the next time you visit your site the feedback
1532 plug-in will perform some checks, creating the directory C<$fb_dir>
1533 and setting appropriate permissions on the directory if it doesn't
1534 already exist. (Check your web server error log for details of what's
1535 happening behind the scenes.)
1537 Set the variables C<$allow_comments> and C<$allow_trackbacks> to
1538 enable or disable comments and/or TrackBacks; by default the plug-in
1539 allows both comments and TrackBacks to be submitted. The variables
1540 C<$comment_period> and C<$trackback_period> specify the amount of time
1541 after a story is published (or updated) during which comments or
1542 TrackBacks may be submitted (90 days by default); set these variables
1543 to zero to allow submission of feedback at any time after publication.
1545 Set the variables C<$akismet_comments> and C<$akismet_trackbacks> to
1546 enable or disable checking of comments and/or TrackBacks against the
1547 Akismet spam blacklist service (C<http://www.akismet.com>). If Akismet
1548 checking is enabled then you must also set C<$wordpress_api_key> to
1549 your personal WordPress API key, which is required to connect to the
1550 Akismet service. (You can obtain a WordPress API key by registering
1551 for a free blog at C<http://www.wordpress.com>; as a side effect of
1552 registering you will get an API key that you can then use on any of
1553 your blogs, whether they're hosted at wordpress.com or not.)
1555 Set the variables C<$blacklist_comments> and C<$blacklist_trackbacks>
1556 to enable or disable checking of comments and/or TrackBacks against
1557 the MT-Blacklist file. If blacklist checking is enabled then you must
1558 also set C<$blacklist_file> to a valid value. (Note that in the past
1559 you could get a copy of the MT-Blacklist file from
1560 C<http://www.jayallen.org/comment_spam/blacklist.txt>; however that
1561 URL is no longer active and no one is currently maintaining the
1562 MT-Blacklist file. We are therefore deprecating use of the
1563 MT-Blacklist file, except for people who already have a copy of the
1564 file and are currently using it; we suggest using Akismet instead.)
1566 Set the variables C<$notify_comments> and C<$notify_trackbacks> to
1567 enable or disable sending an email message to you each time a new
1568 comment and/or TrackBack is submitted. If notification is enabled then
1569 you must set C<$address> and C<$smtp_server> to valid values.
1570 Typically you would set C<$address> to your own email address (e.g.,
1571 'jdoe@example.com') and C<$smtp_server> to the fully-qualified domain
1572 name of the SMTP server you normally use to send outbound mail from
1573 your email account (e.g., 'smtp.example.com').
1575 Set the variables C<$moderate_comments> and C<$moderate_trackbacks> to
1576 enable or disable moderation of comments and/or TrackBacks; moderation
1577 is done by sending you an email message with the submitted comment or
1578 TrackBack and links on which you can click to approve or reject the
1579 comment or TrackBack. If moderation is enabled then you must set
1580 C<$address> and C<$smtp_server> to valid values; see the discussion of
1581 notification above for more information.
1583 =head1 FLAVOUR TEMPLATE VARIABLES
1585 Unlike Rael Dornfest's original writeback plug-in, this plug-in does
1586 not require or assume that you will be using a special Blosxom flavour
1587 (e.g., the 'writeback' flavour) in order to display comments with
1588 stories. Instead you can display comments and/or TrackBacks with any
1589 flavour whatsoever (except the 'trackback' flavour, which is reserved
1590 for use with TrackBack pings). Also unlike the original writeback
1591 plug-in, this plug-in separates display of comments from display of
1592 TrackBacks and allows them to be formatted in different ways.
1594 Insert the variables C<$feedback::comments> and/or
1595 C<$feedback::trackbacks> into the story template for the flavour or
1596 flavours for which you wish comments and/or TrackBacks to be displayed
1597 (e.g., C<story.html>). Note that the plug-in will automatically set
1598 these variables to undefined values unless the page being displayed is
1599 for an individual story.
1601 Insert the variables C<$feedback::comments_count> and/or
1602 C<$feedback::trackbacks_count> into the story templates where you wish
1603 to display a count of the comments and/or TrackBacks for a particular
1604 story. Note that these variables are available on all pages, including
1605 index and archive pages. As an alternative you can use the variable
1606 C<$feedback::count> to display the combined total number of comments
1607 and TrackBacks (analogous to the variable C<$writeback::count> in the
1608 original writeback plug-in).
1610 Insert the variables C<$feedback::commentform> and
1611 C<$feedback::trackbackinfo> into your story or foot template for the
1612 flavour or flavours for which you want to enable submission of
1613 comments and/or TrackBacks (e.g., C<foot.html>);
1614 C<$feedback::commentform> is an HTML form for comment submission,
1615 while C<$feedback::trackbackinfo> displays the URL for TrackBack pings
1616 and also includes RDF code to support auto-discovery of the TrackBack
1617 ping URL. Note that the plug-in sets C<$feedback::commentform> and
1618 C<$feedback::trackbackinfo> to be undefined unless the page being
1619 displayed is for an individual story.
1621 The plug-in also sets C<$feedback::commentform> and/or
1622 C<$feedback::trackbackinfo> to be undefined if comments and/or
1623 TrackBacks have been disabled globally (i.e., using C<$allow_comments>
1624 or C<$allow_trackbacks>). However if comments or TrackBacks are closed
1625 because the story is older than the time set using C<$comment_period>
1626 or C<$trackback_period> then the plug-in sets C<$feedback::commentform>
1627 or C<$feedback::trackbackinfo> to display an appropriate message.
1629 Insert the variable C<$feedback::comment_response> into your story or
1630 foot template to display a message indicating the results of
1631 submitting or moderating a comment. Note that
1632 C<$feedback::comment_response> has an undefined value unless the
1633 displayed page is in response to a POST request containing a comment
1634 submission (i.e., using the 'Post' or 'Preview' buttons) or a GET
1635 request containing a moderator approval or rejection.
1637 Insert the variable C<$feedback::preview> into your story or foot
1638 template at the point at which you'd like a previewed comment to be
1639 displayed. Note that C<$feedback::preview> will be undefined except on
1640 an individual story page displayed in response to a comment submission
1641 using the 'Preview' button.
1643 =head1 COMMENT AND TRACKBACK TEMPLATES
1645 This plug-in uses a number of flavour templates to format comments and
1646 TrackBacks; the plug-in contains a full set of default templates for
1647 use with the 'html' flavour, as well as a full set of 'general'
1648 templates used as a default for other flavours. You can also supply
1649 your own comment and TrackBack templates in the same way that you can
1650 define other Blosxom templates, by putting appropriately-named
1651 template files into the Blosxom data directory (or one or more of its
1652 subdirectories, if you want different templates for different
1655 The templates used for displaying comments and TrackBacks are
1656 analogous to the story template used for displaying stories; the
1657 templates are used for each and every comment or TrackBack displayed
1664 comment template (e.g., C<comment.html>). This template contains the
1665 content to be displayed for each comment (analogous to the writeback
1666 template used in the original writeback plug-in). Within this template
1667 you can use the variables C<$feedback::name> (name of the comment
1668 submitter), C<$feedback::url> (URL containing the comment submitter's
1669 email address or web site), C<$feedback::date> (date/time the comment
1670 was submitted), and C<$feedback::comment> (the comment itself). You
1671 can also use the variable C<$feedback::name_link>, which combines
1672 C<feedback::name> and C<$feedback::url> to create an (X)HTML link if
1673 the commenter supplied a URL, and otherwise is the same as
1674 C<$feedback::name>. Note that this template is also used for previewed
1679 trackback template (e.g., C<trackback.html>). This template contains
1680 the content to be displayed for each TrackBack (analogous to the
1681 writeback template used in the original writeback plug-in). Within
1682 this template you can use the variables C<$feedback::blog_name> (name
1683 of the blog submitting the TrackBack), C<$feedback::title> (title of
1684 the blog post making the TrackBack), C<$feedback::url> (URL for the
1685 blog post making the TrackBack), C<$feedback::date> (date/time the
1686 TrackBack was submitted), and C<$feedback::excerpt> (an excerpt from
1687 the blog post making the TrackBack). You can also use the variable
1688 C<$feedback::title_link>, which combines C<$feedback::title> and
1689 C<$feedback::url> and is analogous to C<$feedback::name_link>.
1693 The feedback plug-in also uses the following templates:
1699 commentform template (e.g., C<commentform.html>). This template
1700 provides a form for submitting a comment. The default template
1701 contains a form containing fields for the submitter's name, email
1702 address or URL, and the comment itself; submitting the form initiates
1703 a POST request to the same URL (and Blosxom flavour) used in
1704 displaying the page on which the form appears. If you define your own
1705 commentform template note that the plug-in requires the presence of a
1706 'plugin' hidden form variable with the value set to 'writeback'; this
1707 tells the plug-in that it should handle the incoming data from the POST
1708 request rather than leaving it for another plug-in. Also note that in
1709 order to support both comment posting and previewing the form has two
1710 buttons, both with name 'submit' and with values 'Post' and 'Preview'
1711 respectively; if you change these names and values then you must
1712 change the plug-in's code.
1716 trackbackinfo template (e.g., C<trackbackinfo.html>). This template
1717 provides information for how to go about submitting a TrackBack. The
1718 default template provides both a displayed reference to the TrackBack
1719 ping URL and non-displayed RDF code by which other systems can
1720 auto-discover the TrackBack ping URL.
1726 This plug-in has at least the following security-related issues, which
1727 we attempt to address as described:
1733 The plug-in handles POST and GET requests with included parameters of
1734 potentially arbitrary length. To help minimize the possibility of
1735 problems (e.g., buffer overruns) the plug-in truncates all parameters
1736 to a maximum length (currently 10,000 bytes).
1740 People can submit arbitrary content as part of a submitted comment or
1741 TrackBack ping, with that content then being displayed as part of the
1742 page viewed by other users. To help minimize the possibility of
1743 attacks involving injection of arbitrary page content, the plug-in
1744 "sanitizes" any submitted HTML/XHTML content by converting the '<'
1745 character and other problematic characters (including '>' and the
1746 double quote character) to the corresponding HTML/XHTML character
1747 entities. The plug-in also sanitizes submitted URLs by URL-encoding
1748 characters that are not permitted in a URL.
1752 When using moderation, comments or TrackBacks are approved (or
1753 rejected) by invoking a GET (or HEAD) request using the URL of the
1754 story to which the comment or TrackBack applies, with the URL having
1755 some additional parameters to signal whether the comment should be
1756 approved or rejected. Since the feedback plug-in does not track (much
1757 less validate) the source of the moderation request, in theory
1758 spammers could approve their own comments or TrackBacks simply by
1759 following up their feedback submission with a GET request of the
1760 proper form. To minimize the possibility of this happening we generate
1761 a random eight-character alphanumeric key for each submitted comment
1762 or TrackBack, and require that that key be supplied in the approval or
1763 rejection request. This provides reasonable protection assuming that a
1764 spammer is not intercepting and reading your personal email (since the
1765 key is included in the moderation email message).
1775 This plug-in was created by Frank Hecker, hecker@hecker.org; it was
1776 based on and inspired by the original writeback plug-in by Rael
1777 Dornfest together with modifications made by Fletcher T. Penney, Doug
1778 Alcorn, Kevin Scaldeferri, and others.
1782 More on the feedback plug-in: http://www.hecker.org/blosxom/feedback
1784 Blosxom Home/Docs/Licensing: http://www.blosxom.com/
1786 Blosxom Plug-in Docs: http://www.blosxom.com/plugin.shtml
1790 Address bug reports and comments to the Blosxom mailing list
1791 [http://www.yahoogroups.com/group/blosxom].
1795 The feedback plug-in
1796 Copyright 2003-2006 Frank Hecker, Rael Dornfest, Fletcher T. Penney,
1797 Doug Alcorn, Kevin Scaldeferri, and others
1799 Permission is hereby granted, free of charge, to any person obtaining a
1800 copy of this software and associated documentation files (the "Software"),
1801 to deal in the Software without restriction, including without limitation
1802 the rights to use, copy, modify, merge, publish, distribute, sublicense,
1803 and/or sell copies of the Software, and to permit persons to whom the
1804 Software is furnished to do so, subject to the following conditions:
1806 The above copyright notice and this permission notice shall be included
1807 in all copies or substantial portions of the Software.
1809 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1810 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1811 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
1812 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
1813 OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
1814 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
1815 OTHER DEALINGS IN THE SOFTWARE.