Forum rating points

From Pirates@Home

Jump to: navigation, search
BOINC Hacks
  

This page describes modifications you can make to customize your BOINC project or application.


The BOINC forum software allows users to rate forum postings up or down, which is intended to help emphasize good content and to screen offensive material. This can be abused by users who continuously rate down the postings of particular individuals -- what's come to be called a "minus attack".

This page describes changes one can make to the forum software to implement a maximum limit to a user's rating points. I will first describe how I originally set this up to try out the idea. Then I will adjust it to become a proposal for changes to be made to the BOINC source code distribution.


Contents

How it works

Each user has a maximum number of rating points, set by a defined constant MAX_RATING_POINTS (5 seems to work well). Whenever someone rates a forum post or another user's profile one point is deducted from their total. If a user has no remaining points then they are not allowed to rate posts or profiles. Points are slowly added back over time, back up to the maximum.

As long as the maximum number of points is reasonable, most users will find everything works as before, but overuse of the rating controls for "minus attacks" will no longer be allowed.

This system is somewhat similar to "moderation points" on Slashdot, though there are also differences. On Slashdot you are allowed to moderate the posts of others for a short time interval, and if you fail to do so you loose any unused points. Here, in contrast, you can always have rating points available, unless you have used them all up, and your count is slowly but constantly being replenished.

It would be possible to implement a higher maximum number of points for forum moderators or other special users, though I have not done so.

Database

The rating system requires one extra database field in the user table, which is used to keep track of the number of rating points each user currently has available. I originally set up the demonstration system by borrowing an unused field called seti_id (which presumably is used by SETI@Home to store the user's ID from the older "classic" version of the project, and so is otherwise unused on any other project).

To implement this system in the main BOINC distribution one would want to create a new database field for this purpose.

You would also need to modify the User object class to add an accessor to return the current value in this field. For the prototype demonstration of the idea on Pirates@Home I implemented an extension of the "User" object, called "Pirate", in project/project.inc (see below). If this is ever incorporated into the BOINC code base then one would simply add the accessor function directly to the User object class.


File Changes

Implementing this system currently requires changes to several files, rather than simply adding a new file of functions, though you could collect the additions to project.inc in a separate file and then include that file.


project.inc

The file project/project.inc is used to set project-specific preferences and to define supporting functions which are not provided by the BOINC code base. First of all, you should add

define("MAX_RATING_POINTS", 5);

All of the modifications which follow are only activated if this named constant is defined. You can therefore easily switch back to unlimited rating points by commenting out or removing this line. If you make any changes or additions to this system beyond what I describe here you should protect them with a test to verify that this named constant is defined, so that one can still easily turn the system on or off. You will see examples of how that is done in the code below.


To keep track of the number of rating points a user has at any given time we need an accessor function to get that field from the database. For testing I created an extension to the User object class, called the Pirate object class. This can be added to project.inc or put in a separate file which is included by it. The extension is:

class Pirate extends User {
   function getDubloons(){ // we 'borrow' this db field for UOTD count
       return $this->dbObj->seti_nresults;
   }
   function getRatingPoints(){ // we 'borrow' this db field for ratings
       return $this->dbObj->seti_id;
   }
}

For testing I just borrowed the otherwise unused database field seti_id. As you can see, I also borrowed the database field seti_nresults to keep track of how many dubloons a user has. Dubloons are given out on Pirates@Home for being chosen as Pirate of the Day or for other meritorious service to the project.


We also add here functions to obtain the user's rating points count and to deduct a single point:

function user_rating_points($user){
   if(empty($user)) return 0;
   $p = new Pirate($user->getID());                                  
   $N_rate = $p->getRatingPoints();
   return $N_rate;
}
function deduct_rating_point($user){
   $id = $user->getID();
   $query = "UPDATE user SET seti_id=seti_id-1 WHERE id=$id";
   if(mysql_query($query)) {
          error_log("Deducted rating point from user $id ("
                    .$user->getName(). ")" );
   }
   else {
          error_log("Error trying to deduct rating point from user $id (".
                    $user->getName(). ")" );
   }
}

If and when this system for limiting rating points is added to the main BOINC code base and a database field is added to the User table then these functions can be turned into methods attached to the User object class. I did not do so at first because it would require creating a new instance of the Pirate class each time I wanted to use it. (Yes, the code is a bit rough -- remember, it's a prototype)


forum_rate.php

The actual rating of a single forum posting is done in the file user/forum_rate.php. You need to add to the beginning of this file a test to verify that the user has rating points available. Along with the other conditions already there, add this:

   if( defined('MAX_RATING_POINTS') ){
       if( user_rating_points($user) < 1 ) {
           error_page("You do not have any rating points left.<br/><br/>                 
               <a href='forum_thread.php?nowrap=true&id=".$thread->getID()
                      ."#".$post->getID()."'>Return to thread</a>");
       }
   }

The function user_rating_points() is defined in profile.inc (see above), though it could eventually be replaced by a method attached to the User object class.


While you are at it, you can also add the condition that the user cannot rate his/her own posts:

   if ( $user->getID() == $post->getOwnerID() ) {
       error_page("You cannot rate your own posts.<br/><br/>                             
                 <a href='forum_thread.php?nowrap=true&id=".$thread->getID()
                  ."#".$post->getID()."'>Return to thread</a>");
   }

Now farther down the file, after the posting has been rated, deduct one point, thus:

  $success = $post->rate($user, $rating);

   if( $success && defined('MAX_RATING_POINTS') ){                 
       deduct_rating_point($user);
   }

The function deduct_rating_point() is defined in project.inc (see above), but eventually it could be replaced by a method attached to the User object class.


forum.inc

The functions in this file provide support for displaying postings in the discussion forums. We want to change how the rating controls are displayed underneath each posting, so that the controls are only shown if rating is in use and if the user viewing the posting has rating points available, and to show the number of rating points remaining. The controls will also be made a bit clearer, and we will add "alt=" text which is shown when the user moves the mouse over the controls.

In the file inc/forum.inc find the definition of the function show_post() [not "show_posts()"]. It is quite long. Scroll down to the part which deals with rating the posting. It is a block which looks like this:

     if ($no_forum_rating != NULL) {
           echo " | <a href=\"forum_report_post.php?post=".$post->getID()."\"><img src=\$
       }
       else {
       $rating = $post->getRating();
       echo " | Rating: ", $rating, "</i> | rate: <a href=\"forum_rate.php?post=".$post-$
       }

We replace this with two separate parts. First, a clearer control to report offensive postings:

           echo " | Report:  ";
           echo "<a href='forum_report_post.php?post=".$post->getID()."'>                
                   <img src=\"".REPORT_POST_IMAGE."\"                                    
                        alt='Report as offensive'                                        
                title='Report this post as offensive'                                    
                        height=\"".REPORT_POST_IMAGE_HEIGHT."\" border=0></a>  ";

Then the controls for rating postings, which are only shown under the proper circumstances:

      /* Forum rating: show [+] or [-] controls, unless <no_forum_rating/>              
       *     or if MAX_RATING_POINTS is set but user has no points left. */

      if ( !$no_forum_rating ) {
           $rating = $post->getRating();
           echo " | Rating: ". $rating. " ";

           if( !defined('MAX_RATING_POINTS') ||
               ($N_rate = user_rating_points($logged_in_user)) > 0 ){
               echo "| Rate:                                                             
               <a href='forum_rate.php?post=" .$post->getID()."&choice=p'>           
               <img src='".RATE_POSITIVE_IMAGE."' alt='Rate up'                          
                    title='Rate this post up'                                            
                    height='".RATE_POSITIVE_IMAGE_HEIGHT."'                              
                    border=0></a>"
                   ." or                                                                 
               <a href='forum_rate.php?post=".$post->getID()."&choice=n'>            
               <img src='".RATE_NEGATIVE_IMAGE."' alt='Rate down'                        
                    title='Rate this post down'                                          
                    height='".RATE_NEGATIVE_IMAGE_HEIGHT."'                              
                    border=0></a> \n";
               if( defined('MAX_RATING_POINTS') ){
                   if( $N_rate>1 )  echo " ($N_rate points left) ";
                   if( $N_rate==1 ) echo " ($N_rate point left) ";
               }
           }// showing rating controls?                                                  
      }//<no_forum_rating/>


profile_rate.php

The idea to limit the use of rating points can be extended to apply to user profiles as well as forum posts. To do this you need to modify the file user/profile_rate.php. To do this, add the following to the beginning of this file (after $logged_in_user has been set):

if( defined('MAX_RATING_POINTS') ){
   if( user_rating_points($logged_in_user) < 1 ){
       error_page("You do not have any rating points left.<br/><br/>"
                  ."<a href='view_profile.php?userid=". $userid
                  ."'>Return to profile</a>");
   }
}

While you are at it, you could also add the following to prevent users from rating their own profiles:

if( $logged_in_user->getID() == $userid ) {
   error_page("You cannot rate your own profile.<br/><br/>"
     ."<a href='view_profile.php?userid=". $userid ."'>Return to profile</a>");
}

Recharge

A separate task, run periodically, adds one rating point to every user who does not already have the maximum. This is done by adding to the <task> section of the config.xml file

    <task>
       <cmd> run_in_ops php refresh_rating_pts.php </cmd>
       <output> cron.log </output>
       <period> 8 hr </period>
     </task>

I run this every 8 hours, but as you can see you can easily change this as you wish. The file ops/refresh_rating_pts.php contains

require_once("../inc/db.inc");
require_once("../inc/user.inc");
require_once("../inc/util.inc");
require_once("../inc/ops.inc");
require_once("../project/project.inc");

db_init();

/* For testing we use seti_id to hold the user's points.  */

$query = "UPDATE user SET seti_id = seti_id+1 WHERE seti_id < "
    .MAX_RATING_POINTS;
echo "  mysql> $query \n";
$result = mysql_query($query);
$N = mysql_affected_rows();
echo "  $N rows affected.\n";

You can comment out the echo lines if you don't want any output in the log

With a little extra work one could easily craft a database query which adds points for moderators or administrators up to a higher maximum value, but I have not done this.

Personal tools