# Blosxom Plugin: WritebacksPlus # Author: Fletcher T. Penney http://fletcher.freesheel.org/ # Much of the original code by Rael Dornfest # Version 0.1 package writeback; # --- 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. # my $writeback_dir = "/home/sgp/public_html/weblog/writebacks"; my $writeback_dir = "$blosxom::plugin_state_dir/interactions"; # What flavour should I consider an incoming trackback ping? # Otherwise trackback pings will be ignored! my $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. my $file_extension = "wb"; # What fields are used in your comments form and by trackbacks? my @fields = qw! name url email date time title comment excerpt blog_name ip id!; # CGI parameter to filter for stories with writebacks within # the last x days $switch_word = "recent"; # ie http://some.host/?recent=5 # This would show stories with writebacks in last 5 days # Use embedded datestamp, rather than file modification date # to determine comment dates $use_datestamp = 0; # $use_datestamp = 1; # What field contains the date? $date_field = "date"; # What date format to use? $use_UK_dates = 1; # Default is mm/dd/yy (US) # Set to 1 to use dd/mm/yy (UK) # Should I emulate the seewritebacks plugin? # This automatically displays writeback information on single # story view pages, and when the "?seewritebacks=y" bit is # appended to a url $do_seewritebacks = 1; # -------------------------------- # Comments for a story; use as $writeback::writebacks in flavour templates $writebacks; # 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. This is used to replace the functionality of # the seewritebacks plugin as well. $writebacksform; # -------------------------------- use CGI qw/:standard/; use FileHandle; use File::stat; use Time::Local; my $time = time(); my $fh = new FileHandle; my $email = '[\w\.\-]+@([\w\-]+\.)+[\w]+'; # Strip potentially confounding bits from user-configurable variables $writeback_dir =~ s!/$!!; $file_extension =~ s!^\.!!; # Save Name and URL/Email via cookie if the cookies plug-in is available $cookie; sub start { # $writeback_dir must be set to activate writebacks unless ( $writeback_dir ) { warn "blosxom : writeback plugin > The \$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_dir and ( !-d $writeback_dir or !-w $writeback_dir ) ) { warn "blosxom : writeback plugin > The \$writeback_dir, $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; } # the $writeback_dir does not yet exist, so Blosxom will create it if ( !-e $writeback_dir ) { my $mkdir_r = mkdir("$writeback_dir", 0777); warn $mkdir_r ? "blosxom : writeback plugin > \$writeback_dir, $writeback_dir, created.\n" : "blosxom : writeback plugin > There was a problem creating your \$writeback_dir, $writeback_dir. Writebacks are disabled!\n"; $mkdir_r or return 0; my $chmod_r = chmod 0777, $writeback_dir; warn $chmod_r ? "blosxom : writeback plugin > \$writeback_dir, $writeback_dir, set to 0755 permissions.\n" : "blosxom : writeback plugin > There was a problem setting permissions on \$writeback_dir, $writeback_dir. Writebacks are disabled!\n"; $chmod_r or return 0; warn "blosxom : writeback plugin > writebacks are enabled!\n"; } # Check to see if we should filter for dates... if (CGI::param($switch_word)) { my $time_window = CGI::param($switch_word); $time = $time - $time_window*60*60*24; } else { $time = 0; } $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 $trackback_flavour) ) { my $f = $blosxom::datadir . $path_info; $f =~ s/html$/txt/; if ( stat($f)->mtime >= ( time() - 14*60*60*24 ) ) { #if ( param('comment') !~ /.*open(">> $writeback_dir$path/$fn.$file_extension") ) { foreach ( @fields ) { my $p = param($_); #if ( $_ == "url" ) { # $p =~ s/^($email)/mailto:$1/; #} $p =~ s/\r?\n\r?/\r/mg; # should remove the need to undef $param{url} in the story section. if ( $_ eq "url" ) { undef $p if $p =~ m!^http://$!; } if ( $_ eq "ip" ) { # Log IP address of poster $p = $ENV{'REMOTE_ADDR'}; } if ( $_ eq "date" ) { my @now = split ' ', localtime(); $p = "$now[2] $now[1] $now[4]" } if ( $_ eq "time" ) { my @now = split ' ', localtime(); $p = $now[3]; } if ( $_ eq "comment" ) { # first, convert any ampersands - we'll add other entities later we don't want double encoded: $p =~ s!\&!\&\;!g; # Convert all "$" to entities to prevent variables being included: $p =~ s/\$/$/g; # neutralise html: $p =~ s//\>\;/g; # replace any \r's with
tags to retain line breaks: $p =~ s/\r/
/g; # wrap the comment in

tags: $p = "

$p

"; } if ( $_ eq "id" ) { my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); $year += 1900; $mon += 1; if ( length($mon) == 1 ){ $mon = "0$mon"; } if ( length($mday) == 1 ) { $mday = "0$mday"; } $p = "$fn$year$mon$mday$hour$min$sec"; } print $fh "$_: $p\n"; } print $fh "-----\n"; $fh->close(); chmod (0777,"$writeback_dir$path/$fn.$file_extension"); $trackback_response =~ s!!0!m; $trackback_response =~ s!\n!!s; $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 $pref_name = param('name') || ''; $pref_url = param('url') || ''; $pref_email = param('email') || ''; } else { warn "couldn't >> $writeback_dir$path/$fn.$file_extension\n"; $trackback_response =~ s!!1!m; $trackback_response =~ s!trackback error!!m; $writeback_response = "There was a problem posting your comment."; } #} #else { #$writeback_response = "Sorry, you can't include links in your comment."; #} } else { $writeback_response = "Sorry, writebacks are closed on this post."; } } 1; } sub story { my($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_; # Only display writebacks under these condtions: # - $do_seewritebacks is set AND # - we've ben passed ?seewritebacks=y OR # - we've been passed plugin=writeback OR # - $blosxom::path_info contains a "." (ie, this is single entry view) if ( $do_seewritebacks && ( (CGI::param("seewritebacks")) || (CGI::param("plugin") eq 'writeback') || ($blosxom::path_info =~ /\./) ) ) { $path =~ s!^/*!!; $path &&= "/$path"; ($writebacks, $count) = ('', 0); my %param = (); my($open, $fl); $fl = "$blosxom::datadir$path/$filename.$blosxom::file_extension"; if ( stat($fl)->mtime >= ( time() - 14*60*60*24 ) ) { $open = 1; } # Prepopulate Name and URL/Email with cookie-baked preferences, if any $pref_url ||= 'http://'; if ( $blosxom::plugins{cookies} > 0 and my $cookie = &cookies::get('writeback') ) { $pref_name ||= $cookie->{'name'}; $pref_url ||= $cookie->{'url'}; $pref_email ||= $cookie->{'email'}; } if ( $fh->open("$writeback_dir$path/$filename.$file_extension") ) { foreach my $line (<$fh>) { $line =~ /^(.+?): (.*)$/ and $param{$1} = $2; if ( $line =~ /^-----$/ ) { # lose useless $param{url}: #undef $param{url} if $param{url} =~ m!^http://$!; # Is our poster identified? $param{name} = 'anonymous' if ( !$param{name} and !$param{blog_name} ); 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

'; # Build a contact link: if ( $param{name} ) { # It must be a comment, not a TB: if ( !$param{url} and !$param{email} ) { # there is no contact data for a link: $param{attribution} = "$param{name} commented:"; } elsif ( !$param{url} ) { # there is an email address, but not a URL: $param{attribution} = "
$param{name} commented:"; } else { # both email and URL are present, URL takes precedence: $param{attribution} = "$param{name} commented:"; } } else { # it is a TB. All data required should be there: $param{attribution} = "$param{blog_name} tracked back:"; } # sort any $param{title}: if ( $param{title} ) { $param{title} = "$param{title}"; } # Create a timestamp variable for backward compatibility: if ( $param{date} ) { $param{timestamp} = "Posted at $param{time} on $param{date}"; } else { $param{timestamp} = ""; } # Create an id tag anda permalink if there is an id... if ( $param{id} ) { $param{id_tag} = " id=\"$param{id}\""; $param{p_link} = "link2me"; } else { $param{id_tag} = ""; } $writeback =~ s/\$writeback::(\w+)/$param{$1}/ge; $writebacks .= $writeback; $count++; } } # Added for spam protection while ($writebacks =~ /($email)/ig) { $original = $1; $masked = ""; @decimal = unpack('C*', $original); foreach $i (@decimal) { $masked.="&#".$i.";"; } $writebacks =~ s/$original/$masked/ig; } } $trackback_path_and_filename = "$path/$filename"; # Load the writebacksform template and fill in variables: if ( $open ) { $writebacksform = &$blosxom::template($path,'writebacksform',$blosxom::flavour) || &$blosxom::template($path,'writebacksform','general'); $writebacksform = &$blosxom::interpolate($writebacksform); } else { $writebacksform = &$blosxom::template($path,'writebacksclose',$blosxom::flavour); $writebacksform = &$blosxom::interpolate($writebacksform); } # Even if we are not showing writebacks, we need to count any that might exist for a given story: } else { $count = 0; if ( $fh->open("$writeback_dir$path/$filename.$file_extension") ) { foreach my $line (<$fh>) { $count++ if $line =~ /^-----$/; } } } # sort out $count so that it makes good english: if ( $count == 1 ) { $count .= ' comment'; } elsif ( $count > 1 ) { $count .= " comments"; } else { $count = "no comments..."; } 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 ) ); } sub filter { # This filters articles based on comment date my ($pkg, $files_ref) = @_; my @files_list = keys %$files_ref; # If $time was set to 0, then don't filter return 1 if ( $time == 0 ); foreach $file (@files_list) { $realfile = $file; $file =~ s/$blosxom::datadir/$writeback_dir/; $file =~ s/txt$/$file_extension/; if ($fh->open("$file")) { $keep = 0; $stats = stat($file)->mtime; if ( $use_datestamp == 1 ) { while ( $line = <$fh> ) { # modified due to my use of separate fields for the date and time. $line =~ /^(.+?): (.*)$/ and $params{$1} = $2; if ( $line =~ /^-----$/ ) { $datestamp = "$params{date} $params{time}"; $stat2 = parsedate($datestamp); if ($stat2 gt $stats) { $stats = $stat2; } } } } if ($stats lt $time) { delete $files_ref->{$realfile}; } } else { # No writebacks available delete $files_ref->{$realfile}; } } 1; } sub parsedate { my ($datestring) = @_; # Possible formatting # Month can be 3 letter abbreviation or full name (in English) # Time must be hh:mm or hh:mm:ss in 24 hour format # Year must be yyyy # The remaining 1 or 2 digits are treated as date # ie: May 25 2003 18:40 # order is not important as long as pieces are there # Find Month $mon = 0 if ($datestring =~ s/(Jan|January)//i); $mon = 1 if ($datestring =~ s/(Feb|February)//i); $mon = 2 if ($datestring =~ s/(Mar|March)//i); $mon = 3 if ($datestring =~ s/(Apr|April)//i); $mon = 4 if ($datestring =~ s/(May)//i); $mon = 5 if ($datestring =~ s/(Jun|June)//i); $mon = 6 if ($datestring =~ s/(Jul|July)//i); $mon = 7 if ($datestring =~ s/(Aug|August)//i); $mon = 8 if ($datestring =~ s/(Sep|September)//i); $mon = 9 if ($datestring =~ s/(Oct|October)//i); $mon = 10 if ($datestring =~ s/(Nov|November)//i); $mon = 11 if ($datestring =~ s/(Dec|December)//i); # Find Time if ($datestring =~ s/(\d\d?):(\d\d)(:\d\d)?//) { $hour = $1; $min = $2; $sec = $3; } if ($datestring =~ s/(\d\d\d\d)//) { $year = $1; } if ($datestring =~ s/(\d\d?)//) { $day = $1; } return timelocal($sec,$min,$hour,$day,$mon,$year); } 1; __END__ # 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) # # 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! =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.