# Blosxom Plugin: wikieditish # Author(s): Rael Dornfest # ugly hacks by David Williams # Version: 2004-08-21 DAW # Documentation: See the bottom of this file or type: perldoc wikieditish package wikieditish; # --- Configurable variables ----- # Should I attempt to preserve the last modified date/time when # editing an existing file? (May not work on your operating system.) # 0 = no (default), 1 = yes # date based permalinks break otherwise $preserve_lastmodified = 0; # Should editing this blog require a password? # 0 = no, 1 = yes (default) $require_password = 1; # What is the password for editing this blog? my $blog_password = ''; # Should editing this blog be restricted to a particular set of IPs? # 0 = no (default), 1 = yes $restrict_by_ip = 0; # To what IPs should editing this blog be restricted? @ips = qw/ 127.0.0.1 /; # What file extension should I use for edited pages? # (Not sure why you'd change this, but just in case...) my $file_extension = $blosxom::file_extension; #added for file uploads: #physical path to store the files in: my $uploads_dir = "/home/httpd/html/blosxom/graphics/"; my $imagemagick = 1; #if you have and want to use imagemagick to resize my $convert_cmd = "/usr/bin/convert"; #path to the imagemagick resize command my $max_size = 300; #the largest dimension you want the resized graphic to be my $quality = 30; #the quality of the thumbnail (smaller numbers, lower fidelity, smaller size) my $prefix = "sm_"; #the prefix for the resized graphic #added for edit locking on too many failed passwords #file locking (if enabled below) creates a blog_title.badpw file in plugins/state for each #blank or incorrect password. blog_title.badpw files less than bad_passwords_lockout_time old #are counted and if more than max_bad_passwords files are found in that directory, #no changes are made to any files. blog_title.badpw files older than #bad_passwords_lockout_time are removed my $lock_bad_password = 1; #1 turn this on, 0 turn it off my $max_bad_passwords = 5; #lock after 10 attempts my $bad_passwords_lockout_time = 3600; #seconds to stay locked -- 3600 = 1 hour # -------------------------------- # Response to wikieditish; use as $wikieditish::response in # flavour templates $response = ''; # The raw title and body ($title, $body) = ('', ''); # The password entered into the form (for prepopulating the form upon Save) $password = ''; #this must be undefined here to use conditional in the template for pre-post #it fixes the $blosxom::fn problem in response to a post (the $blosxom::fn is different) $filename; # -------------------------------- use vars qw/$require_password/; use CGI qw/:standard/; use FileHandle; my $fh = new FileHandle; # Strip potentially confounding bits from user-configurable variables $file_extension =~ s!^\.!!; sub start { #my ugly style to always have the filename: $filename = $blosxom::path_info; $filename =~ /\/?(.*?)\.$blosxom::flavor/; $filename = $1; # Only spring into action if POSTing to the wikieditish plug-in if ( request_method() eq 'POST' and param('plugin') eq 'wikieditish' ) { my($path,$fn) = $blosxom::path_info =~ m!^(?:(.*)/)?(.*)\.$blosxom::flavour!; $path =~ m!^/! or $path = "/$path"; $password = param('password'); $title = param('title'); $body = param('body'); # Something's fishy with the path $path =~ /[^\/\w\-]/ and warn "blosxom : wikieditish plugin : something's fishy with the path, $path\n" and $response = "Something didn't go as expected; page not saved." and return 1; # password required, but not set $require_password and !$blog_password and warn "blosxom : wikieditish plugin : password required but is not yet set; trying to > $blosxom::datadir$path/$fn.file_extension\n" and $response = "A password is required to edit this page but one has not yet been set; page not saved." and return 1; #if we're bad password lockout is enabled, test # of bad passwords: if ($lock_bad_password) { if(test_password_lock() == 0) { $response = "This system is locked due to too many incorrect passwords"; return 1; } } #I couldn't keep this syntax and touch a file # # password required, set, but not correctly supplied # $require_password and (!param('password') or (param('password') and param('password') ne $blog_password)) # and warn "blosxom : wikieditish plugin : incorrect password supplied for > $blosxom::datadir$path/$fn.file_extension\n" # and $response = "Incorrect password supplied; page not saved." #added for password lockout: # and (note_bad_password() ) # and return 1; #so I rewrote this a bit to support bad password tracking: # # password required, set, but not correctly supplied if($require_password and (!param('password') or (param('password') and param('password') ne $blog_password))) { warn "blosxom : wikieditish plugin : incorrect password supplied for > $blosxom::datadir$path/$fn.file_extension\n"; $response = "Incorrect password supplied; page not saved."; note_bad_password() if($lock_bad_password); return 1; } # restricted by ip $restrict_by_ip and !grep(/^\Q$ENV{'REMOTE_ADDR'}\E$/, @ips) and warn "blosxom : wikieditish plugin : incorrect IP address > $blosxom::datadir$path/$fn.file_extension\n" and $response = "Incorrect IP address; page not saved." and return 1; # blosxom's $datadir is not writeable !-w $blosxom::datadir and warn "blosxom : wikieditish plugin > \$blosxom::datadir, $blosxom::datadir, is not writeable.\n" and $response = "Something didn't go as expected; page not saved." and return 1; # the destination directory for this blog entry does not yet exist unless ( -d "$blosxom::datadir$path" and -w "$blosxom::datadir$path" ) { warn "blosxom : wikieditish plugin : mkdir $blosxom::datadir$path\n"; foreach ( ('', split /\//, $path) ) { $p .= "/$_"; $p =~ s!^/!!; -d "$blosxom::datadir/$p" or mkdir "$blosxom::datadir/$p", 0755 or ( warn "blosxom : wikieditish plugin : couldn't mkdir $blosxom::datadir/$p." and return 1 ); } } # If file already exists, memorize the lastmodified date/time my $mtime = (stat "$blosxom::datadir$path/$fn.$file_extension")[9]; # If file is writeable if ( $fh->open("> $blosxom::datadir$path/$fn.$file_extension") ) { print $fh join "\n", $title, $body; $fh->close(); $response = "Page saved successfully (back to the entry)."; # reset lastmodified date/time to memorized value (if possible) $preserve_lastmodified and utime(time, $mtime, "$blosxom::datadir$path/$fn.$file_extension") ? $response .= " Preserved last modified date/time." : warn "blosxom : wikieditish plugin : couldn't reset lastmodified time on $blosxom::datadir$path/$fn.$file_extension."; #addition to allow file uploads: upload_files($fn); #end upload chunck } else { warn "couldn't > $blosxom::datadir$path/$fn.file_extension"; $response = "There was a problem saving this page."; } } 1; } sub story { my($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_; unless ( param('plugin') eq 'wikieditish' ) { my @body; ($title, @body) = split /\n/, $blosxom::raw; $body = join "\n", @body; } 1; } #added to support file uploads sub upload_files { my $fn = shift(@_); my $sBuffer; my @aFiles; my $sFile; my $sFileName; my $sResizeCommand; my $sExt; my $iCount = 0; my $iOK = 1; my $sResults = ""; #we're assuming the field is called file_upload and there could be more than one: my @aFiles = param('file_upload'); foreach $sFile (@aFiles) { next if(length($sFile) < 5); $sBuffer = ""; while(<$sFile>) { $sBuffer .= $_; } #get the extension: $sFile =~ /(.*)(\..*?)$/; $sExt = $2; $sExt = "" if(!defined($sExt)); $sExt = lc($sExt); #increment counter and write out the file: $iCount++; $sFileName = ">$uploads_dir" . $fn . "_$iCount" . "$sExt"; open UPLOAD, $sFileName or $iOK = 0; if($iOK) { print UPLOAD ($sBuffer); close UPLOAD; $response .= "
Uploaded $sFile as $fn
"; #resize as needed: if($imagemagick) { $sResizeCommand = "$convert_cmd -size $max_sizeX$max_size -quality $quality "; $sResizeCommand .= $uploads_dir . $fn . "_$iCount" . "$sExt "; $sResizeCommand .= "-resize $max_sizeX$max_size +profile \"*\" "; $sResizeCommand .= $uploads_dir . $prefix . $fn . "_$iCount" . "$sExt "; `$sResizeCommand`; } } else { $response .= "
Error uploading $sFile to $sFilename
"; } } } sub note_bad_password { my $sLockFile = $blosxom::plugin_state_dir . "/" . $blosxom::blog_title . ".badpw."; $sLockFile .= time(); `touch '$sLockFile'`; # `touch '$blosxom::plugin_state_dir/$blosxom::blog_title'`; warn "touched $sLockFile\n"; } sub test_password_lock { my $iOK = 1; my $iCount = 0; my $iNow = time(); my @aStat; my @aFiles; opendir STATE, $blosxom::plugin_state_dir or $iOK = 0; if($iOK == 0) { warn "Can't open $blosxom::plugin_state_dir to look for bad password files\n"; return 0; } @aFiles = grep /$blosxom::blog_title\.badpw/, readdir STATE; foreach $sFile (@aFiles) { # warn "trying $sFile\n"; @aStat = stat($blosxom::plugin_state_dir . "/" . $sFile); if($iNow - $aStat[9] > $bad_passwords_lockout_time) { unlink $blosxom::plugin_state_dir . "/" . $sFile; } else { $iCount++; } } if($iCount > $max_bad_passwords) { warn "Too many bad password files found: $iCount\n"; return 0; } return 1; } 1; __END__ =head1 NAME Blosxom Plug-in: wikieditish =head1 SYNOPSIS Edit a Blosxom blog wiki-style, from right in the browser. =head2 QUICK START Drop this wikieditish plug-in file into your plug-ins directory (whatever you set as $plugin_dir in blosxom.cgi). Wikieditish, being a well-behaved plug-in, won't do anything until you either set a password or turn off the password requirement (set $require_password = 0). Move the contents of the flavours folder included in this distribution into your Blosxom data directory (whatever you set as $datadir in blosxom.cgi). Don't move the folder itself, only the files it contains! If you don't have the the sample flavours handy, you can download them from: http://www.raelity.org/apps/blosxom/downloads/plugins/wikieditish.zip Point your browser at one of your Blosxom entries, specifying the wikieditish flavour (e.g. http://localhost/cgi-bin/blosxom.cgi/path/to/a/post.wikieditish) Edit the entry, supply your password (if required -- the default), and hit the Save button to save your changes. You can just as easily create a new blog entry by pointing your browser at a non-existent filename, potentially on a non-existent path (e.g. http://localhost/cgi-bin/blosxom.cgi/path/to/a/nonexistent_post.wikieditish). Give the entry a title and body, supply your password (again, if required), and hit the Save button. The wikieditish plug-in will create a new blog entry for you on your specified path, creating the supplied path's directory structure for you on the fly if necessary. Enjoy! =back =head2 SAMPLE FLAVOUR TEMPLATES I've made sample flavour templates available to you to help with any learning curve this plug-in might require. Take a gander at the source HTML for: =item * head.wikieditish, a basic head template -- nothing special. =item * story.wikieditish, a basic story template -- nothing special. =item * foot.wikieditish, a basic foot template just about like any other. The big difference is the "edit this blog" form for editing the current blog entry or creating a fresh one. NOTE: The wikieditish plug-in requires the presence of a "plugin" form variable with the value set to "wikieditish"; this tells the plug-in that it should handle the incoming POSTing data rather than leaving it for another plug-in. =back =head2 FLAVOURING WIKIEDITISH While there's not much in the way of template variables and the sample foot.wikieditish provides about everything you'll need, here's a list of variables and their purposes for your reference: =item * $wikieditish::title and $wikieditish::body prepopulate the form with the values from the existing blog entry file. =item * $wikieditish::password is prepopulated with the password you just entered into and submitted in the "edit this blog" form or preferences stored in a 'wikieditish' cookie, if you've the cookie plug-in installed and enabled. =back =head2 INVITING CONTRIBUTIONS The wikieditish plug-in serves dual purposes: as a browser-based editor for your Blosxom blog and as a wiki-style community blog, allowing contributions by a particular group of bloggers (using a shared password) or passers-by (without need of a password -- true Wiki-style). If you'd like to invite contribution, you can assocate an "edit" button with each entry like so: edit this blog HERE =head1 INSTALLATION Drop wikieditish into your plug-ins directory ($blosxom::plugin_dir). =head1 CONFIGURATION =head2 PRESERVING LAST MODIFIED DATE/TIME ON EDITED ENTRY The wikieditish plug-in can attempt to maintain the last modified date/time stamp on any blog entry you're editing. Otherwise, editing an entry will cause it to rise to the top of your blog like so much cream. I say "attempt" since this doesn't work on every operating system (it doesn't do any harm, though). To turn this feature on -- it's off by default -- set the $preserve_lastmodified variable to 1. =head2 RESTRICTING BY PASSWORD By default, the wikieditish plug-in requires a password. You'll need to set one before being able to edit anything. Set the $blog_password configuration variable to anything you wish (e.g. my $blog_password = 'abc123';). Just be sure to use something you'll remember and other's won't guess. You can disable password-protection if you wish, allowing passers-by to contribute to your blog, Wiki-style. Be sure this is something you want to do. It has some possible security implications, anyone being able to write to your server's hard drive, post to your public-facing blog, and edit (read: alter, spindle, contort) any blog postings. Those warning's out of the way, to turn off password-protection, set $require_password to 0. =head2 RESTRICTING BY IP You can alternatively decide to restrict editing to a particular IP address or addresses -- those in your office, for example, or the machine actually running Blosxom (127.0.0.1). To do so, set $restrict_by_ip to 1 (it's off, or 0, by default), and populate the @ips array with a list of approved IP addresses. By default, this is set to "this machine", the machine running Blosxom; shorthand for "this machine" in IP-speak is 127.0.0.1. The following example restricts editing to those coming from three IPs, including 127.0.0.1: # Should editing this blog be restricted to a particular set of IPs? # 0 = no (default), 1 = yes $restrict_by_ip = 1; # To what IPs should editing this blog be restricted? @ips = qw/ 127.0.0.1 10.0.0.3 140.101.22.10/; Of course, you can use a combination of password-protection and IP restriction if you so wish. =head1 VERSION 2003-05-29 Version number is the date on which this version of the plug-in was created. =head1 AUTHOR Rael Dornfest , http://www.raelity.org/ =head1 SEE ALSO The wikieditish plug-in plays nicely with the wikiwordish plug-in [http://www.raelity.org/apps/blosxom/plugins/text/wikiwordish.individual] for wiki-style linking action. And for wiki-style markup, be sure to try the textile [http://www.raelity.org/apps/blosxom/plugins/text/textile.individual] or tiki [http://www.raelity.org/apps/blosxom/plugins/text/tiki.individual] plug-ins. Blosxom Home/Docs/Licensing: http://www.raelity.org/apps/blosxom/ Blosxom Plugin Docs: http://www.raelity.org/apps/blosxom/plugin.shtml =head1 BUGS Address bug reports and comments to the Blosxom mailing list [http://www.yahoogroups.com/group/blosxom]. =head1 LICENSE Blosxom and this Blosxom Plug-in Copyright 2003, Rael Dornfest 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.