bac5d1fded0e3645883af76b4d1773a01e01339c
[matthijs/upstream/blosxom-plugins.git] / karma
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
5
6 # Version: 0.6
7
8 package karma;
9
10 # --- Configurable variables ----
11
12 # Where is the data kept?
13 $datafile = "$blosxom::plugin_state_dir/karma.dat";
14
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
18
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'
22
23 $ip_list = '';
24
25 # CGI variable names to use for specifying certain minimum scores
26 $minkarma_var           = "minkarma";
27 $mincontroversy_var     = "mincontroversy";
28 $mininterest_var        = "mininterest";
29
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.
32
33 # -------------------------------
34
35 use CGI;
36
37 # Initialize variables
38 $stats = "";            # This variable will contain the results
39 $votelinks = "";        # This variable will contain the links to submit a vote
40
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;
52
53 $minkarma = 0;
54 $mincontroversy = 0;
55 $mininterest = 0;
56
57 $message = "";          # Use this to report messages to the visitor
58
59
60 sub start {
61
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/^\/*//;
69
70         $remoteIP = $ENV{'REMOTE_ADDR'};        # Get IP address for visitor
71         $ip_list = 'disable' if ($ip_list eq "");       # Prevent an empty match expression
72
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
77
78         if (($path_noflavour !~ /index$/) && ($path_noflavour !~ /\/$/)) {
79                 $do_increment = 1 if ($remoteIP !~ /($ip_list)/);
80
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         }
89
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;
104
105                                 # Try to correct for uncounted visits
106                                 $visits = $total_votes if ($total_votes > $visits);
107
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));
111
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                 }
117
118                 close (DATA);
119                 open (DATA, ">$datafile");
120                 flock (DATA, 2);        # Lock again, if it was unlocked
121                 print DATA $karmadata;
122
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;
130
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));
134
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
147
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
151
152         1;
153 }
154
155
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         }
164
165         if (CGI::param($mincontroversy_var)) {
166                 $mincontroversy = CGI::param($mincontroversy_var);
167                 $dofilter = 1;
168         }
169
170         if (CGI::param($mininterest_var)) {
171                 $mininterest = CGI::param($mininterest_var);
172                 $dofilter = 1;
173         }
174
175
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;
197 }
198
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;
205
206         my ($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_;
207
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;
217
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         }
225
226
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";
230
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;
240 }
241
242 1;
243
244
245 __END__
246
247
248 =head1 NAME
249
250 Blosxom Plug-in: karma
251
252 =head1 DESCRIPTION
253
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.
255
256 To install:
257
258 1) place the karma plugin in your plugins directory, with proper file permissions
259
260 2) ensure the configuration variables are properly set:
261         $datafile should be left alone, unless you have customized where plugins store information
262
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
264
265         The $minkarma_var, $mincontroversy_var, and $mininterest_var variables can be used to customize the url's used to filter articles.
266
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.
268
269
270 Once installed, modify your story.flavour files to include the $karma::stats and $karma::votelinks variables.
271
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.
273
274 These numbers are used to calculate several variables:
275
276         $karma_index - this is a rating from 0 to 10 of how positively the story was viewed
277
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...)
279
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.
281
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.
283
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>
286
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.
288
289 =head1 NOTE
290
291 This plugin filters out articles only.  It does not locate any articles on its own.
292
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.
294
295 =head1 AUTHOR
296
297 Fletcher T. Penney - http://fletcher.freeshell.org
298 Concept by Beau Lebens
299
300 This plugin is now maintained by the Blosxom Sourceforge Team,
301 <blosxom-devel@lists.sourceforge.net>.
302
303 =head1 LICENSE
304
305 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.
306
307 THIS SOFTWARE IS PROVIDED AS IS AND WITHOUT ANY WARRANTY OF ANY KIND.  USE AT YOUR OWN RISK!