The feedback plugin, an alternative to writeback

When I originally put up my blog one of the major things lacking was support for comments and TrackBacks. After looking at the various alternatives (the writeback plugin, the writebackplus plugin, and so on) I decided to embark on a complete rewrite of the writeback plugin in order to support my particular requirements for a comments system. After much struggle I created an initial version of my feedback plugin for publication and use on my site; since that time I've upgraded the plugin and incorporated bug fixes suggested by various people.


I did a pretty much complete rewrite of writeback and the various writeback derivatives, both because they didn't support particular features of interest to me and also because I didn't understand exactly why they did certain things the way they did. Here are the key things I wanted, all of which I managed to implement in one form or another:

  • no need to use a special writeblack flavour
  • different formatting for comments vs. TrackBacks
  • correct formatting of basic plain-text comments (in particular, if you enter paragraphs separated by blank lines then the resulting comment should actually display as multiple paragraphs, without requiring you to use HTML tags)
  • comment previewing
  • comment and TrackBack moderation
  • basic spam blacklist checking using Akismet
  • basic security measures against code injection attacks
  • Markdown support (although I'm not using it right now)
  • clean enough code structure to allow easy addition of new features
  • minimal dependence on other plugins


There were a number of other features that I didn't care about and decided to leave out:

  • Captcha support. I omitted this in favor of a combination of Akismet spam checking plus (optional) moderation; unlike captchas this protects against TrackBack spam, not just comment spam.

  • HTML tags in comments (even just a subset). I think Markdown is a better approach for those who want links and more complicated formatting.

  • Comment threading (e.g., as implemented in the comments plugin). This just wasn't that important to me.

  • A writeback compatibility mode (i.e., the ability to use the feedback plugin as a drop-in replacement for thr writeback plugin, using legacy writeback templates and variables). The plugin could probably be enhanced to support this, but I'll leave it to someone else to do this.

  • Correct display of comment and TrackBack counts (e.g., displaying "1 comment" vs. '2 comments"). I left this as a future task.

  • A generalized API for extending features through feedback-specific plugins, e.g., as implemented by Doug Alcorn for writeback. While an interesting concept, I felt this was a bit heavyweight for what I wanted to do.

There were also several features that I initially implemented and then later pulled out in the interest of simplicity:

  • Ability to display comments and TrackBacks on index and archive pages. (Right now the plugin displays comments and TrackBacks only on individual story pages.) I've seen some Blosxom-based blogs that do this, but it wasn't something I was interested in. (For anyone who wants to do this, it's a trivial patch.)

  • Support for "comments_head" and "comments_foot" templates to provide additional content to be displayed before all comments and after all comments (with analogous "trackbacks_head" and "trackbacks_foot" templates for TrackBacks). In the end I found I could use variable interpolation (as supported by the interpolate_fancy plugin) in the story and/or foot templates to achieve the look I wanted.

  • Support for a separate "preview template" used for previewed comments; in the end I just reused the comment template for this.

  • Support for HTML email for moderation and notification messages. I decided that plain text email worked fine and was arguably better from a security point of view.


Finally, here some implementation details that might be of interest to people using or (especially) writing writeback-like plugins:

  • The feedback plug-in contains all the default templates needed that are specific to the plug-in; you should only have to modify your existing story and foot templates to reference the plugin variables (as described in the documentation at the bottom of the plugin).

  • I used the same basic file structure to store comments and TrackBacks as is used by writeback and friends. (In fact, I think you may be able to use existing writeback files with this plugin, but I haven't tested this.)

  • I moved processing of submitted comments from the start subroutine into the story subroutine. This seemed to simplify the implementation, especially when it came to deciding whether or not comments or TrackBacks should be closed after a certain time. (To do this properly you need the modification date/time of the story, which you don't have until the date subroutine runs, right before the story subroutine.)

  • Comment previewing was pretty straightforward to implement: It's basically a matter of keying off the particular submit button clicked, formatting a special "preview" comment separate from the main comments, and then pre-filling the comment form fields with the previewed field values.

  • Moderation was also pretty straightforward (after a couple of false starts): Write the moderated comment or TrackBack not to the main feedback file for the story, but to a separate file whose name contains a randomly-generated 8-character alphanumeric string. To support approval or rejection of the comment or TrackBack, create moderate=approve and moderate=reject URLs referencing that string as a query parameter (e.g., feedback=mi3g9qcl4) and send those URLs to the moderator as part of an email message notifying them of the comment/TrackBack. When the moderator clicks on the appropriate URL and the plugin processes the GET request, it will either append the temporary file to the main feedback file (if the request was approved) or just delete it (if the request was rejected). The use of a random string minimizes the possibility of unauthorized persons trying to approve their own requests. (This doesn't protect against eavesdropping attacks, of course; doing that would require some use of cryptography.)

  • The plugin seems to support non-ASCII posts (e.g., in Japanese, etc.), except in the notification/moderation email messages. I suspect this is just be an artifact of the particular version of Perl I'm using; I certainly didn't do any work to support anything but 7-bit ASCII, and it may be that some things are still broken even in my case.

Anyway, I hope this may be of interest to some people. As always, please feel free to re-use the code or ideas as you wish. See the plugin itself for the full documentation on how to configure it. If you encounter problems with the plugin (or if you just use it and like it) please send me email.

UPDATE: Added mention of Akismet support. Thanks go to Kevin Scaldeferri for the Akismet support (adapted from his version of writebackplus) and to Keith Carangelo, Gustaf Erikson, Matthijs Kooijman, and Michael Lamertz for various bug fixes and related suggestions for improvement.


Levi Wolfe wrote at 2005-10-02 19:16:

The comments part works like a charm, but I can't get trackbacks to work with it. I've tried two different standalone ping tools, on both your blog, and mine. Fails each time. The tools I tried are:

Joshua S Dennis wrote at 2005-10-08 19:52:

I had some ideas about the great hacking you have done on blosxom that might improve it a little bit.

I am not sure, but for me it would really help for readability (<---actually a word) if the links to the bottom right that let you continue reading a post said something to the effect of ...Continue reading $postname. Just for continuuity, and feeling connected. I read a couple of entries and tried to click on the .. that ended some sentences awkwardly, knowing that the post went on somewhere. I consider myself savy in the way of the web, but the great seperation between the link and the break in the post threw me off.

Please don't leave the links with real mailto's in the comments. If you want them to have a way of talking to people, store them. Don't leave them for any spider to come harvest.

dan McGinn-Combs wrote at 2005-10-14 21:44:

Hmmmm.... I've found a problem with two installations of Blosxom (one on Gentoo Linux and one on Windows). I find that I get an error message: Use of uninitialized value in substitution (s///) ... line 273

This line is: $path =~ s!^/*!!; $path &&= "/$path";

I've found the error -- apparently the STORY function within feedback doesn't get the $path variable. Now I'm tracking that one down. Any thoughts on what would help?

Frank Hecker wrote at 2005-10-14 22:58:

I'm not sure why $path would be undefined; it's a standard variable passed into the story subroutine of all plugins by blosxom.cgi. Did the error occur on a story right under the blog root (i.e., not in a category)? That's the only time when I could imagine $path being null or undefined.

Moshe Yudkowsky wrote at 2005-11-10 23:39:

I just ran into the same problem with this plugin, and I ran into the empty $path problem long ago when I first installed blosxom.

$path is undefined on my system because $path contains subdir, i.e., category information, not the entire path. If you use a flat-file, all in $datadir configuration, $path is an empty string. Frank uses subdirs, and AFAICT would not bump into this problem. A symptom of empty path is when you notice that the standard story.html template's $path is never filled in, either.

To solve this problem, I tried adding the line

if (! defined($path)) { $path = "" ; }

just before the problem line in Frank's plugin. This fixes the problem. However, the $trackback::commentform, etc., does not appear on the web page, for individually-displayed files, even with this fix. Any suggestions?

Moshe Yudkowsky wrote at 2005-11-10 23:55:

And I see why this does not work in my system for individual stories. The feedback plugin looks for a suffix (such as .html) at the end of $blosxom::path_info. If there isn't one -- and there often isn't, in my setup, because (for example) is a valid URL -- then the plugin decides that the page has is not a single-story page.

I don't have any suggestions to solve this. My Perl skills are a bit rusty... Cursory examination of the blosxom CGI does not suggest any blosxom variable or array that can be examined for a prospective list of how many files will appear on the page.

Suggestion: Perhaps *after* the fact, there's a count of stories somewhere in blosxom -- or maybe maintained in the feeback plugin itself? Then comment forms, etc., can safely go in the foot.

Moshe Yudkowsky wrote at 2005-11-11 14:48:

OK, here's a solution that works for me.

I use the following idea. First, I declare what a single file permanent link looks like:

my $single_file_pattern = "m!(.*/)?[[:digit:]]{4}?/[[:digit:]]{2}?/[[:digit:]]{2}?#blog-!" ;

This is the default pattern of "permanent link" that comes with blosxom: a year, month, day, a #, and then the specific filename without extension. (On my system, I use "blog-" as a prefix to each blog file to make 'em easier to search for with other tools.)

Next, at line 290 or so, I use this Perl expression:

$is_story_page = $blosxom::path_info =~ eval($single_file_pattern) ? 1 : 0;

This seems to work, for the most part. However, I get an error in my log files:

Use of uninitialized value in pattern match (m//) at (eval 42) line 1, <GEN0> line 30.

Which doesn't make much sense to me.

Frank, feel free to contact me at "speech .at." if you have any suggestions 'bout this.

Also, if you're feeling kind, perhaps you might reveal how *you* leave trackbacks for stuff you write! Is there a blosxom-friendly method to do this?

Moshe Yudkowsky wrote at 2005-11-11 15:13:

Uh, on further testing.... this only works in part; there are other problems, because your RDF output isn't compatible with what I've written. I will be back to you on this, but if you have a suggestion, please let me know.

Moshe Yudkowsky wrote at 2005-11-11 15:49:

The problem is that $path_info, which is tested at line 290, is a null string.

A valid, single-file URL such as weblog/2005/11/10#blog-20051110T1314 evaluates to null. I am confused... I think the feedback is expecting URLs that are formatted differently, and I can't seem to find the correct test.

Frank Hecker wrote at 2005-11-11 15:54:

To Moshe: Re the problem of distinguishing individual stories: I follow the Blosxom convention that individual story URLs always have to have a flavour extension. I myself use extensionless URLs (i.e., without flavour extensions) for my own postings, but my extensionless plugin adds the flavour extension to $blosxom::path_info so that the storystate plugin, feedback plugin, etc., can use the standard Blosxom test.

Checking for an individual story based on the number of stories on the page (which I think you could do by checking the number of entries in the %files hash) won't work in general, because it's perfectly possible for an index or archive page to have but one story on it. I think the only other alternative approach would be to define an alternate convention regarding what a URL for an individual story looks like, and pattern match against that -- in other words, the approach you're taking.

I'm not a real Perl expert either, but I think that you could use a slightly different approach to the pattern matching:

my $single_file_pattern = '(.*/)?[[:digit:]]{4}?/[[:digit:]]{2}?/[[:digit:]]{2}?#blog-';


$is_story_page = $blosxom::path_info =~ m!^($single_file_pattern)! ? 1 : 0;

In other words, you don't need to do an explicit eval. (Also, I think it would be safer to put the regular expression in single quotes to avoid inadvertent variable interpolation.)

However... note that it would be perfectly possible to have a single day with multiple stories, so what you're calling a "single file permanent link" isn't exactly the same as what I am calling an individual story URL. In the case where a single day had multiple stories you'd end up with multiple independent comment forms (one for each story) if the comment form were in the story template. You could try to fix this by putting the comment form in the foot template, but then you'd have the problem of deciding which story the comment form applied to (i.e., what to set the FORM ACTION to).

Frank Hecker wrote at 2005-11-11 16:13:

See my prior comment. The underlying problem is that Blosxom does *not* consider a URL of the form '.../2005/11/10#foo' to be a reference to an individual story page; instead this is a date-based archive URL. $path_info as set by Blosxom does not contain either the date info or the fragment identifier (stuff after the '#'); instead the date info goes into $path_info_yr, $path_info_mo, and $path_info_da, and the fragment identifier is (I think) discarded. $path_info is set to contain *only* the category name(s) and the story's filename and extension (if supplied).

For more on this see the blosxom.cgi code, lines 102-125, and my annotations to the code <>.

Moshe Yudkowsky wrote at 2005-11-14 14:55:

Frank, once again, thanks for the excellent plugin.

In the end, I solved the problem as follows. Because I use tagging, the pattern match on $blosxom::path_info doesn't work when, for example, the URL is of the form ?tag=something, as in for example.

I created a plugin called storycount. It runs as the last filter, counts the number of stories in %files, and makes that count available as $storycount::count. I then use that in your feedback plugin to determine if it's a single story or not.

I also use cooluri2 to help fix things up a bit, but I am not certain just yet just why it's needed... I suspect I may be able to get along without it if I get the configuration correct.

Please let me know offline if you want any of the gory details. I suspect I should publish storycount, in all of its three or so lines of glory.

Moshe Yudkowsky wrote at 2005-11-14 15:03:

Frank, once again, thanks for the excellent plugin.

In the end, I solved the problem as follows. Because I use tagging, the pattern match on $blosxom::path_info doesn't work when, for example, the URL is of the form ?tag=something, as in for example.

I created a plugin called storycount. It runs as the last filter, counts the number of stories in %files, and makes that count available as $storycount::count. I then use that in your feedback plugin to determine if it's a single story or not.

I also use cooluri2 to help fix things up a bit, but I am not certain just yet just why it's needed... I suspect I may be able to get along without it if I get the configuration correct.

Please let me know offline if you want any of the gory details. I suspect I should publish storycount, in all of its three or so lines of glory.

Frank Hecker wrote at 2006-07-25 06:36:

Testing the commenting facility in version 0.23 of the feedback plugin. This version loads the Net::Akismet and Net::SMTP modules only when needed, so people can use the plugin on systems where these modules are not available, as long as they don't use the corresponding features of spam checking or notification/moderation.


Levi's Blog mentioned this post in "Tell me how you really feel.":

I've gotten Frank Hecker's excellent feedback plugin installed and running. It works pretty good, but I did have a couple issues setting it up.

Submit a comment

Please enter comments as plain text only; no HTML tags are allowed. All comments and trackbacks are moderated, and will not be displayed until approved by the moderator.

Comments are closed for this story.

Trackbacks are closed for this story.