# Blosxom Plugin: hitcounter -*- perl -*- # Author: Steve Schwarz # 2008-JAN-06 0.7 Now stores article title in addition to URL, only # stores entries that exist, non-existent are cleared # from store when accessed. *MUST* upgrade favorites plugin # to at least verion 0.3 # 2007-DEC-23 0.6 Fixed locking # 2006-JUN-24 0.5 add checks of HTTP_USER_AGENT to ignore accesses by bots # 2006-AUG-03 0.4 don't increment count if writeback/trackback rejected # 2006-JAN-24 0.3 added check for invalid write of cache # 2006-JAN-21 0.2 added config var to only retrieve and not increment for # specifed flavours # 2006-JAN-15 0.1 initial version. # Based on categories plugin by Todd Larason (jtl@molehill.org) package hitcounter; # Counts are stored keyed on full path without the flavour # so story/ideas/money_maker.html and story/ideas/money_maker.htm # both increment the same counter. # -- Configuration -- # # Name of file holding hash of paths and their current count. # This file is created automatically in your plugins' state directory: my $file_name = "$blosxom::plugin_state_dir/hit_stats"; # Set $reset_count = 1 so you can append ?count=100 to the URL to # set the count for the supplied $path to 100. You can use this # feature to seed each of your pages based on existing # statistics. Then turn off this feature so no mischief makers set # your values incorrectly. No error checking is performed on the # count CGI parameter. # If you set ?count=0 you can delete an entry for a # deleted article from your statistics my $reset_count = 0; # set to 1 to allow setting count from URL # Set to a flavour you want to use only for retrieving counts # without incrementing the count. Use this flavour to view the # counts for URLs of interest. my $retrieve_only_flavour = 'hd'; # Add filters here to not increment or open the data file # for specific urls or flavours # Ignore all page loads via these agents: @ignore_agents = ('Googlebot', 'Mediapartners', 'Feedfetcher', 'msnbot', 'Yahoo! Slurp', 'Attentio', 'IRLbot', 'Twiceler', 'StackRambler', 'Java', 'Topix.net', 'speedyspider', 'scout', 'moreover', 'voila', 'Technorati', 'kinjabot', 'Magpie', 'Gigabot', 'cazoodle', 'bot.bot', 'voyager', 'yetibot', 'sogou', 'topicblogs', 'fastsearch' ); # Ignore all page loads for these flavors (all rss variants, atom and # all write/trackback variants): @ignore_flavours = ('rss', 'atom', 'back'); # ------------------- use CGI qw/:standard/; use FileHandle; sub start { # This filter ignores the rss, atom, trackback feed variants I provide foreach my $ignore (@ignore_flavours) { return 0 if ($$blosxom::flavour =~ /$ignore/); } # Now ignore specific (bot) user agents $agent = $ENV{'HTTP_USER_AGENT'}; foreach my $ignore (@ignore_agents) { return 0 if ($agent =~ /$ignore/); } return 1; } $count = 0; # use $hitcounter::count to get the current count sub head { my($pkg, $path, $body_ref) = @_; eval "require Storable"; if ($@) { print STDERR "hitcounter disabled, Storable package not available"; return 0; } if (!Storable->can('lock_retrieve')) { print STDERR "hitcounter disabled, Storable::lock_retrieve() not available"; return 0; } $path = '/' if (!$path); # convert root path to "/" $path =~ s/(\.$blosxom::flavour)$//; # strip flavour from end of path # Try to find the file and get it's title my $title = ''; my %cache; my $cacheref = \%cache; if (-r $file_name) { $cacheref = Storable::lock_retrieve($file_name); } else { $cacheref->{$path} = [0, $title]; # no file yet set the count } return 1 unless defined $cacheref; # Reset count or delete entry if count is zero $count = 0; my $delete = 0; if ($reset_count == 1 and defined CGI::param('count')) { if (CGI::param('count') eq '0') { $delete = 1; } else { $count = CGI::param('count'); } } $path =~ s/(\.$blosxom::flavour)$//; # strip flavour from end of path my $suffix = $1; if (0 == $count) { # set $count for display on page being viewed $count = $cacheref->{$path}; # old file format $count = 0 if (! defined $count); # new entry $count = $count->[0] if ($count->[0]); # new format $title = $count->[1] if ($count->[1]); # new format # Don't increment and store for rejected writeback or read only view return 1 if (defined $writeback::rejected and $writeback::rejected == 1); # my custom version of writeback return 1 if ($blosxom::flavour eq $retrieve_only_flavour); ++$count; } if ($suffix ne '' and $title eq '') { # a request that isn't for a directory and for which we don't have a title my $path_file = join "", $blosxom::datadir, '/', $path, '.', $blosxom::file_extension; my $fh = new FileHandle; if (-f "$path_file" && $fh->open("< $path_file")) { chomp($title = <$fh>); $fh->close; } else { # delete (don't store) non-existent entry $delete = 1; } } if ($delete != 0) { delete $cacheref->{$path}; } else { $cacheref->{$path} = [$count, $title]; } # Store in network order to allow editing/transporting to/from different platforms Storable::lock_nstore($cacheref, $file_name); return 1; } 1; __END__ =head1 LICENSE this Blosxom Plug-in Copyright 2006, Steve Schwarz (This license is the same as Blosxom's) 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.