# Blosxom Plugin: writeback++ -*- Perl -*- # Authors: # Active: Peter Gammie http://peteg.org/ # Based on work by: # Fletcher T. Penney http://fletcher.freesheel.org/ (WritebacksPlus) # Much of the original writebacks code by Rael Dornfest # Pasi Savolainen (notification code) # Version 0.0.1 # FIXME use the Cache package to provide a database. # Perhaps there's better? # KISS: just load/store a hash for now, see perlfaq4 # FIXME: sort out the dates here. Support a blosxom-esque: # $dw, $mo $da, $yr.
# Fix the serialisation (presentation independent) # Fix the directory permissions: 777 is a bit much. # Concurrency safe? # Lint this whole thing. # Add spam protection # - logic puzzle? # - captcha? # - either/or? # Re-use bloxsom's abstractions, e.g. interpolation and date.flavour package writeback; # This plugin is essentially a drop in replacement for the original # writeback plugin by Rael. There were a couple of issues with the # original, plus I have added a lot of new features. # # But wait, that's not all. It also attempts to do some escaping to # prevent blosxom variables OR errant HTML tags from being displayed in comments # # NOTE: This means you cannot include HTML code in comments, other than

and #
tags at present. Support for additional tags can be added. # # Enjoy # Instructions # To set this up, do the following: # - Put this plugin in your plugin directory, for ease of use, leave it # named 1writeback # - You need 2 flavour files ( writeback.flavour and # writebacksform.flavour ), or the corresponding sections # in your theme. (NOTE: You can create versions for each of your # flavours or themes, or you can create a set ending in # ".general" that will be used for all themes) Put them wherever # you store your flavour files. # - writeback.flavour contains the display information for comments # if absent, the default will be used which is built in to this # plugin. # - writebacksform.flavour contains the writeback submission form and # other data. A sample was included with this file. # - You need to put two variables in your story.flavour or the # story section of your theme file, $writeback::writebacks, and # $writeback::writebacksform # - You need to configure this plugin so that it knows where to store # the writebacks. Edit the $writeback_dir variable, and ensure that # the corresponding folder exists and has read/write permissions # (ie chmod 777) # - Add the

Comments...

and $writeback::writeback_response # variable to your story flavour # # That should do it! Just read through this file to learn what you can # do! # # NOTE: Rael's documentation was more thorough - you should check out # his writeback plugin's documentation to read more about trackbacks # etc. Just don't install that version anymore - this one is better! # --- Configurable variables ----- # Where should I keep the writeback hierarchy? # I suggest: $writeback_dir = "$blosxom::plugin_state_dir/writeback"; # # NOTE: By setting this variable, you are telling the plug-in to go ahead # and create a writeback directory for you. # FIXME remove when hash works. $writeback_dir = "$blosxom::plugin_state_dir/writeback"; $writeback_file = "$blosxom::plugin_state_dir/writeback.hash"; # What fields are used in your comments form and by trackbacks? @fields = qw! name url date title comment excerpt blog_name ip !; # What flavour should I consider an incoming trackback ping? # Otherwise trackback pings will be ignored! $trackback_flavour = "trackback"; # What file extension should I use for writebacks? # Make sure this is different from that used for your Blosxom weblog # entries, usually txt. $file_extension = "wb"; # CGI parameter to filter for stories with writebacks within # the last x days # ie http://some.host/?recent=5 # This would show stories with writebacks in last 5 days $switch_word = "recent"; # Address that writebacks will be mailed to. # *** This plugin will not action until you set it. *** $wbaddr = 'peteg42@gmail.com'; # Location of sendmail -executable. If default doesn't work, try to find the # right location and replace this variable with it. # This may help you find something suitable: # for i in `locate sendmail`; do if [ -x $i ]; then echo $i; fi; done; $sendmail = "/usr/sbin/sendmail"; # FIXME $number_of_characters = 4; # Generate a captcha. FIXME configure my $captcha = Authen::Captcha->new ( data_folder => "$writeback::writeback_dir" , output_folder => "$blosxom::static_dir/captcha" , expire => 86400 ); # -------------------------------- # FIXME variables stored in the cookie. $pref_name; $pref_url; # Comments for a story; use as $writeback::writebacks in flavour templates $writebacks; # FIXME $captcha_md5sum; # Count of writebacks for a story; use as $writeback::count in flavour templates $count; # The path and filename of the last story on the page (ideally, only 1 story # in this view) for displaying the trackback URL; # use as $writeback::trackback_path_and_filename in your foot flavour templates $trackback_path_and_filename; # Response to writeback; use as $writeback::writeback_response in # flavour templates $writeback_response; # Response to a trackback ping; use as $writeback::trackback_response in # head.trackback flavour template $trackback_response =<<'TRACKBACK_RESPONSE'; TRACKBACK_RESPONSE # Writeback submission form. $writebacksform; # -------------------------------- use CGI qw/:standard/; use FileHandle; use strict; use Authen::Captcha; use Time::Local; my $fh = new FileHandle; # Strip potentially confounding bits from user-configurable variables $writeback::writeback_dir =~ s!/$!!; $writeback::file_extension =~ s!^\.!!; # Save Name and URL/Email via cookie if the cookies plug-in is available my $cookie; # -------------------------------- sub notify { unless ( $writeback::wbaddr ) { # We're not entitled to action as we don't know the target address warn "blosxom : wbnotify plugin > The \$writeback::wbaddr variable is not set; please set it to enable writeback notifications.\n"; return; } if (open(MAIL, "| $writeback::sendmail -t")) { my $name = param('name') || param('blog_name'); my $url = param('url'); my $title_raw = param('title'); my $body = param('comment') || param('excerpt'); # try to stop some spam? my $title = substr($title_raw, 0, 25); $title =~ s/[\n\r]//g; print MAIL <<"_MAIL_"; From: wbnotify\@yourblog.com.invalid To: $writeback::wbaddr Subject: Auto: [writeback] $title Content-Type: text/plain X-Mailer: blosxom writeback plugin Reply-To: do.not\@reply.com.invalid Auto-Submitted: auto-replied Your blog got a new comment at $blosxom::url/$blosxom::yr/$blosxom::mo_num/$blosxom::da/$blosxom::path_info: from $name, homepage at $url : $title_raw $body -- from writeback plugin for blosxom _MAIL_ close(MAIL); } } sub start { # $writeback_dir must be set to activate writebacks unless ( $writeback::writeback_dir ) { warn "blosxom : writeback plugin > The \$writeback::writeback_dir configurable variable is not set; please set it to enable writebacks. Writebacks are disabled!\n"; return 0; } # the $writeback_dir exists, but is not a directory if ( -e $writeback::writeback_dir and ( !-d $writeback::writeback_dir or !-w $writeback::writeback_dir ) ) { warn "blosxom : writeback plugin > The \$writeback::writeback_dir, $writeback::writeback_dir, must be a writeable directory; please move or remove it and Blosxom will create it properly for you. Writebacks are disabled!\n"; return 0; } # FIXME concurrrency. # the $writeback::writeback_dir does not yet exist, so Blosxom will create it if ( !-e $writeback::writeback_dir ) { my $mkdir_r = mkdir("$writeback::writeback_dir", 0775); warn $mkdir_r ? "blosxom : writeback plugin > \$writeback::writeback_dir, $writeback::writeback_dir, created.\n" : "blosxom : writeback plugin > There was a problem creating your \$writeback::writeback_dir, $writeback::writeback_dir. Writebacks are disabled!\n"; $mkdir_r or return 0; my $chmod_r = chmod 0775, $writeback::writeback_dir; warn $chmod_r ? "blosxom : writeback plugin > \$writeback::writeback_dir, $writeback::writeback_dir, set to 0775 permissions.\n" : "blosxom : writeback plugin > There was a problem setting permissions on \$writeback::writeback_dir, $writeback::writeback_dir. Writebacks are disabled!\n"; $chmod_r or return 0; warn "blosxom : writeback plugin > writebacks are enabled!\n"; } # FIXME surely this is working too hard. # Replace the non-linting part of this with a DB / cache. my $path_info = CGI::path_info(); my($path,$fn) = $path_info =~ m!^(?:(.*)/)?(.*)\.$blosxom::flavour!; $path =~ m!^/! or $path = "/$path"; $path = "/$path"; # Only spring into action if POSTing to the writeback plug-in if ( request_method() eq 'POST' and (param('plugin') eq 'writeback' or $blosxom::flavour eq $writeback::trackback_flavour) ) { my $captcha_user = param('captcha_user'); my $captcha_md5sum = param('captcha_md5sum'); my $result = $captcha->check_code($captcha_user, $captcha_md5sum); # warn "$captcha_user, $captcha_md5sum ==> $result"; # check for a valid submitted captcha # $code is the submitted letter combination guess from the user # $md5sum is the submitted md5sum from the user (that we gave them) # my $results = $captcha->check_code($code,$md5sum); # $results will be one of: # 1 : Passed # 0 : Code not checked (file error) # -1 : Failed: code expired # -2 : Failed: invalid code (not in database) # -3 : Failed: invalid code (code does not match crypt) if( $result ne 1 ) { $writeback::writeback_response = "Your captcha input was incorrect.
"; return 1; } notify(); my $p; foreach ( ('', split /\//, $path) ) { $p .= "/$_"; $p =~ s!^/!!; -d "$writeback::writeback_dir/$p" or mkdir "$writeback::writeback_dir/$p", 0775; chmod (0775,"$writeback::writeback_dir/$p"); } my $file = "$writeback::writeback_dir$path/$fn.$writeback::file_extension"; $file =~ s|//+|/|g; if ( open($fh, ">>","$file") ) { foreach ( @writeback::fields ) { $p = param($_); if ( $_ == "url" ) { $p =~ s/^([\w.-]+)@((?:[\w.-]+\.)+\w+)$/mailto:$1 at $2/; } $p =~ s/\r?\n\r?/\r/mg; if ( $_ eq "ip" ) { # Log IP address of poster $p = $ENV{'REMOTE_ADDR'}; } if ( $_ eq "date" ) { # Log datestamp for comment my ($dw,$mo,$mo_num,$da,$ti,$yr) = blosxom::nice_date(time()); $p = "at $ti on $dw, $mo $da, $yr." } if ( $_ eq "comment" ) { # Convert all "$" to web-safe version to prevent accidentally # (or purposefully) including variables in comments that # crash blosxom $p =~ s/\$/$/g; $p =~ s/<(?!(\/?(p|br)\/?))/</g; $p =~ s/
/
/; } print $fh "$_: $p\n"; } print $fh "-----\n"; $fh->close(); chmod (0775,"$file"); $writeback::trackback_response =~ s!!0!m; $writeback::trackback_response =~ s!\n!!s; $writeback::writeback_response = "Thanks for the comment!
"; # Make a note to save Name and URL/Email if save_preferences checked param('save_preferences') and $cookie++; # Pre-set Name and URL/Email based on submitted values $writeback::pref_name = param('name') || ''; $writeback::pref_url = param('url') || ''; } else { warn "couldn't >> $file : $!\n"; $writeback::trackback_response =~ s!!1!m; $writeback::trackback_response =~ s!trackback error!!m; $writeback::writeback_response = "There was a problem posting your comment."; } } 1; } sub story { my($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_; $path =~ s!^/*!!; $path &&= "/$path"; ($writeback::writebacks, $writeback::count) = ('', 0); # Prepopulate Name and URL/Email with cookie-baked preferences, if any if ( $blosxom::plugins{cookies} > 0 and $cookie = &cookies::get('writeback') ) { $writeback::pref_name ||= $cookie->{'name'}; $writeback::pref_url ||= $cookie->{'url'}; } # FIXME: this is BAD. Adjusted to use blosxom's interpolate function. if ( $fh->open("$writeback::writeback_dir$path/$filename.$writeback::file_extension") ) { # FIXME: reset all the fields from one writeback to the next. foreach my $line (<$fh>) { if( $line =~ /^(.+?): (.*)$/) { # FIXME: this is grotty. Add the fields to the 'writeback' package namespace. # warn "$1 => $2"; my $field = $2; $writeback::{$1} = \$field; } if( $line =~ /^-----$/ ) { my $writeback = &$blosxom::template($path,'writeback',$blosxom::flavour) || &$blosxom::template($path,'writeback','general') || '

Name/Blog: $writeback::name$writeback::blog_name
URL: $writeback::url
Title: $writeback::title
Comment/Excerpt: $writeback::comment$writeback::excerpt

'; $writeback = &$blosxom::interpolate($writeback); $writeback::writebacks .= $writeback; $writeback::count++; } } } $writeback::trackback_path_and_filename = "$path/$filename"; # This bit display writebacks if: # - a single story is displayed FIXME verify # I think this works because bloxsom is broken: no '.' in directories. if ( $blosxom::path_info =~ /\./ ) { # warn "$writeback::writeback_dir, $blosxom::static_dir/captcha"; # create a captcha. Image filename is "$md5sum.png" $writeback::captcha_md5sum = $captcha->generate_code($writeback::number_of_characters); # Load the writebacksform template and fill in variables $writeback::writebacksform = &$blosxom::template($path,'writebacksform',$blosxom::flavour) || &$blosxom::template($path,'writebacksform','general'); $writeback::writebacksform = &$blosxom::interpolate($writeback::writebacksform); } 1; } sub head { $blosxom::plugins{cookies} > 0 and $cookie and &cookies::add( cookie( -name=>'writeback', -value=>{ 'name' => param('name'), 'url' => param('url') }, -path=>$cookies::path, -domain=>$cookies::domain, -expires=>$cookies::expires ) ); } 1; __END__ =head1 LICENSE Blosxom and the original Writeback Plug-in Copyright 2003, Rael Dornfest Remainder of this plugin Copyright 2004, Fletcher T. Penney Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.