Add Fletcher Penney plugins to general.
[blosxom-plugins.git] / general / karma
blobec6d321a1ce41789c946919b384c7bd0983efaf7
1 # Bloxsom Plugin: Karma
2 # Author: Fletcher T. Penney < http://fletcher.freeshell.org/ >
3 # Original Concept: Beau Lebens
4 # Idea of using a form - Pasi Savolainen
6 # Version: 0.6
8 package karma;
10 # --- Configurable variables ----
12 # Where is the data kept?
13 $datafile = "$blosxom::plugin_state_dir/karma.dat";
15 # FYI, the data format is as follows
16 # Visits Positives Negatives Interest_Index Karma_Index Controversy_Index /Path/To/Article
17 # tabs are used as separators
19 # List of ip addresses to ignore for visit counts.
20 # Use '|' as a separator if you have more than one
21 # ie. $ip_list = '192.168.1.1|192.168.1.2'
23 $ip_list = '';
25 # CGI variable names to use for specifying certain minimum scores
26 $minkarma_var           = "minkarma";
27 $mincontroversy_var     = "mincontroversy";
28 $mininterest_var        = "mininterest";
30 # For instance, by adding "?minkarma=5", stories with a karma of less than 5 would be filtered.
31 # You can change the terminology above if you don't like the names.
33 # -------------------------------
35 use CGI;
37 # Initialize variables
38 $stats = "";            # This variable will contain the results
39 $votelinks = "";        # This variable will contain the links to submit a vote
41 my $vote = "";
42 my $karmadata = "";
43 my $do_increment = 0;
44 my $matchfound = 0;
45 my $visits = 0;
46 my $pos = 0;
47 my $neg = 0;
48 my $visits = 0;
49 my $interest_index = 0;
50 my $karma_index = 0;
51 my $controversy_index = 0;
53 $minkarma = 0;
54 $mincontroversy = 0;
55 $mininterest = 0;
57 $message = "";          # Use this to report messages to the visitor
60 sub start {
62         $path_noflavour = $blosxom::path_info;
63         if ($path_noflavour !~ s/\.[^\.]*$//) {
64                 $path_noflavour =~ s/\/$//;
65                 $path_noflavour .= "\/index";
66                 $path_noflavour =~ s/^([^\/])/$1/;
67         }
68         $path_noflavour =~ s/^\/*//;
70         $remoteIP = $ENV{'REMOTE_ADDR'};        # Get IP address for visitor
71         $ip_list = 'disable' if ($ip_list eq "");       # Prevent an empty match expression
73         # Check to see if we are viewing a single story.
74         # If so, we want to increment the visits, unless this is a vote submission
75         # This does create the problem of reading a story on a category page, and voting for it
76         # No visit will be recorded - still looking for a solution
78         if (($path_noflavour !~ /index$/) && ($path_noflavour !~ /\/$/)) {
79                 $do_increment = 1 if ($remoteIP !~ /($ip_list)/);
81                 # Check to see whether a vote has been set
82                 if (CGI::param("setkarma")) {
83                         $vote = CGI::param("setkarma");
84                         # If this is a vote, we don't want to update visit count
85                         # They have already read the article and been counted
86                         $do_increment = 0;
87                 }
88         }
90         # Now we want to read in the data file, and update if appropriate
91         if (($do_increment eq 1) || ($vote ne "")) {
92                 # The file should be locked during this process in case of two simultaneous accesses
93                 open (DATA, "<$datafile");
94                 flock (DATA, 2);        # I think I am using this properly...
95                 while ( $line = <DATA>) {
96                         if ($line =~ /(\d+)\t(\d+)\t(\d+)\t[\.\d]+\t[\.\d]+\t[\.\d]+\t\/$path_noflavour$/) {
97                                 # This is the story currently being viewed, so read in info
98                                 $visits = $1 + $do_increment;
99                                 $pos = $2;
100                                 $neg = $3;
101                                 $pos ++ if ($vote =~ /^p/i);
102                                 $neg ++ if ($vote =~ /^n/i);
103                                 $total_votes = $pos + $neg;
105                                 # Try to correct for uncounted visits
106                                 $visits = $total_votes if ($total_votes > $visits);
108                                 $interest_index = ($pos + $neg)/$visits if ($visits ne 0);
109                                 $karma_index = $pos/($total_votes) if ( $total_votes ne 0);
110                                 $controversy_index = 2*$interest_index *(0.5 - abs($karma_index - 0.5));
112                                 $line = "$visits\t$pos\t$neg\t$interest_index\t$karma_index\t$controversy_index\t\/$path_noflavour\n";
113                                 $matchfound = 1;
114                         }
115                         $karmadata .= $line;
116                 }
118                 close (DATA);
119                 open (DATA, ">$datafile");
120                 flock (DATA, 2);        # Lock again, if it was unlocked
121                 print DATA $karmadata;
123                 # If no match was found for the story, create a new record
124                 if ($matchfound eq 0) {
125                         $visits = 1;
126                         $pos = $neg = 0;
127                         $pos ++ if ($vote =~ /^p/i);
128                         $neg ++ if ($vote =~ /^n/i);
129                         $total_votes = $pos + $neg;
131                         $interest_index = ($pos + $neg)/$visits if ($visits ne 0);
132                         $karma_index = $pos/($total_votes) if ( $total_votes ne 0);
133                         $controversy_index = 2*$interest_index *(0.5 - abs($karma_index - 0.5));
135                         $line = "$visits\t$pos\t$neg\t$interest_index\t$karma_index\t$controversy_index\t\/$path_noflavour\n";
136                         print DATA "$line";
137                         $karmadata .= "$line\n";
138                 }
139                 close (DATA);
140         } else {
141                 # Read in the existing data only - we are not viewing a single story
142                 open(DATA, "<$datafile");
143                 while ( $line = <DATA>) {$karmadata.=$line;}
144                 close (DATA);
145         }
146         flock(DATA, 8);         # Unlock file
148         # Here is where we can create list of top controversies, etc
149         # And we have info loaded for the sort routine if necessary
150         # Currently the karma plugin can filter for certain scores, but not sort
152         1;
156 sub filter {
157         my ($pkg, $files_ref) = @_;
158         my @files_list = keys %$files_ref;
159         my $dofilter = 0;
160         if (CGI::param($minkarma_var)) {
161                 $minkarma = CGI::param($minkarma_var);
162                 $dofilter = 1;
163         }
165         if (CGI::param($mincontroversy_var)) {
166                 $mincontroversy = CGI::param($mincontroversy_var);
167                 $dofilter = 1;
168         }
170         if (CGI::param($mininterest_var)) {
171                 $mininterest = CGI::param($mininterest_var);
172                 $dofilter = 1;
173         }
176         if ($dofilter eq 1) {
177         foreach $file (@files_list) {
178                 $realfile = $file;
179                 $file =~ s/^$blosxom::datadir//;
180                 $file =~ s/\.$blosxom::file_extension//;
181                 if ($karmadata =~ /(\d+)\t(\d+)\t(\d+)\t([\.\d]+)\t([\.\d]+)\t([\.\d]+)\t$file/) {
182                         if ($minkarma && ($5 < $minkarma/10)) {
183                                 delete $files_ref->{$realfile};
184                         }
185                         if ($mincontroversy && ($6 < $mincontroversy/10)) {
186                                 delete $files_ref->{$realfile};
187                         }
188                         if ($mininterest && ($4 < $mininterest/10)) {
189                                 delete $files_ref->{$realfile};
190                         }
191                 } else {
192                         delete $files_ref->{$realfile};
193                 }
194         }
195         }
196         1;
199 sub story {
200         my $karma_index = 0;
201         my $interest_index = 0;
202         my $controversy_index = 0;
203         my $total_votes = 0;
204         $pos = $neg = $visits = 0;
206         my ($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_;
208         if ($karmadata =~ /(\d+)\t(\d+)\t(\d+)\t([\.\d]+)\t([\.\d]+)\t([\.\d]+)\t$path\/$filename/) {
209                 $visits = $1;
210                 $pos = $2;
211                 $neg = $3;
212                 $total_votes = $pos + $neg;
213                 $visits = $total_votes if ($total_votes > $visits);
214                 $interest_index = $4;
215                 $karma_index = $5;
216                 $controversy_index = $6;
218                 # Clean up the values for display - we'll want to use the originals (or at least 
219                 # more decimal places for comparisons)
220                 # All values are integers on scale from 1-10
221                 $karma_index = int($karma_index * 10);
222                 $controversy_index = int($controversy_index * 10);
223                 $interest_index = int($interest_index * 10);
224         }
227         # Here you can define the HTML code that gets used to display your results
228         $stats = "Visits: $visits | Positives: $pos | Negatives: $neg<br>";
229         $stats.= "Karma: $karma_index | Interest: $interest_index | Controversy: $controversy_index";
231         # And this is the HTML code for the two vote submission links
232         #$votelinks = "<a href=\"$blosxom::url$path/$filename.$blosxom::flavour?setkarma=p\">I liked this article</a> | <a href=\"$blosxom::url$path/$filename.$blosxom::flavour?setkarma=n\">I didn't like it</a>.";
233         
234         $votelinks = qq!<form method="POST" action="$blosxom::url$path/$filename.html">
235         <div><input type="submit" name="setkarma" value="p">I liked this article |
236         <input type="submit" name="setkarma" value="n">I didn't like it
237         </div></form>!;
238                 
239         1;
245 __END__
248 =head1 NAME
250 Blosxom Plug-in: karma
252 =head1 DESCRIPTION
254 Karma allows you to have your visitors score articles in a positive/negative fashion.  It then calculates various scores that you can use to incorporate a "Top 10 Articles" section, or something similar, into your website.
256 To install:
258 1) place the karma plugin in your plugins directory, with proper file permissions
260 2) ensure the configuration variables are properly set:
261         $datafile should be left alone, unless you have customized where plugins store information
263         $ip_list should include the IP addresses of any visitors you do NOT wish to be counted in scoring.  For instance, you should probably include your own IP address to prevent falsely biasing the numbers as you read and reread your site looking for errors
265         The $minkarma_var, $mincontroversy_var, and $mininterest_var variables can be used to customize the url's used to filter articles.
267 3) You can modify the HTML code snippets at the bottom of the story subroutine ($karma::stats and $karma::votelinks) to alter the display of the scores, and the vote submission links as you see fit.
270 Once installed, modify your story.flavour files to include the $karma::stats and $karma::votelinks variables.
272 Now, when you visit your site, you should see the scoring and links displayed with each story.  Each time you view a story individually, its visit count is advanced one.  Each time you click on the "I liked..." or "I didn't..." links, the positive and negative vote scores will be modified appropriately.
274 These numbers are used to calculate several variables:
276         $karma_index - this is a rating from 0 to 10 of how positively the story was viewed
278         $interest_index - this is a rating from 0 to 10 of what proportion of people who viewed the story bothered to vote on it.  This will be artificially low, as many people might view the story as part of a category, and their visit will NOT be counted.  I am open to ideas as to how to more accurately handle this, keeping in mind that many stories that are displayed are not actually read (ie, older stories at the bottom of a blog...)
280         $controversy_index - this is a rating that I came up with just for fun.  It is a measure of how many people voted, and how divergent their opinions were.  For instance, as the interest_index goes up, the controversy_index goes up.  As the votes are more evenly split between positives and negatives, the controversy_index goes up.  A story with 100% positive (or negative) votes, will have a 0 controversy index (everyone is in agreement, therefore there is no controversy...)  It's not a very scientific number at all, but sort of fun nonetheless.
282 4) You can add links to your site that will display the articles with the highest interest, karma, or controversy by using the $minkarma_var variables discussed in #3 above.
284 For instance you could add a url in your header file like this:
285 <a href="/weblog/index.html?minkarma=5">Show The Most-Liked Articles</a>
287 This would allow the display of only those articles with a karma above 5.  In the future, I hope to add a sort routine that would sort stories by the various indices, but currently the stories are unsorted.
289 =head1 NOTE
291 This plugin filters out articles only.  It does not locate any articles on its own.
293 Also, it uses the filter routine.  Therefore, it will interact with any other plugins that make use of the filtered articles list.  You may need to add numbers to the beginning of plugin names (ie, "55karma") to control the load order if you seem to be having unintended results.  For instance, karma will prevent some topics from being added by my "menu" plugin, unless it loads after menu.
295 =head1 AUTHOR
297 Fletcher T. Penney - http://fletcher.freeshell.org
298 Concept by Beau Lebens
300 =head1 LICENSE
302 This source is submitted to the public domain.  Feel free to use and modify it.  If you like, a comment in your modified source attributing credit to myself and Beau Lebens would be appreciated.
304 THIS SOFTWARE IS PROVIDED AS IS AND WITHOUT ANY WARRANTY OF ANY KIND.  USE AT YOUR OWN RISK!