RatingExtension

From PeacockWiki

Revision as of 07:15, 30 July 2006; Trevorp (Talk | contribs)
(diff) ←Older revision | Current revision | Newer revision→ (diff)
Jump to: navigation, search

Contents

Details

  • Version 0.1
  • Date 30 July 2006
  • Tested on MediaWiki 1.7.1, PHP 5.0.5 (apache2handler)
  • Tested on MediaWiki 1.6.6, PHP 5.0.5 (apache2handler)

Features

  • Allows 5 star rating for each page in a wiki
  • With too few ratings it will use previous ratings
  • With too few ratings, rating will change color to draw attention to get ratings

Installation

Media

Download Media:Rating.tar.gz (Image:Rating.tar.gz) and extract to the 'extensions/' directory. (Extensions directory should now contain a 'rating' subdirectory containing several png files)

Database

Run the following sql code on your database to create the ratings table. Ensure you have selected the correct database using the USE command.

CREATE TABLE IF NOT EXISTS `ratings` (
  `page_oldid` int(8) unsigned NOT NULL auto_increment,
  `user_id` varchar(15) NOT NULL,
  `page_rating` int(8) unsigned NOT NULL,
  PRIMARY KEY  (`page_oldid`, `user_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci;

Extension

Copy and paste the source code into a file named 'rating.php', and place it in the 'extensions/' directory of your mediawiki installation.

Insert the following line into the end of 'LocalSettings.php' (before the '?>')

require("extensions/rating.php");

Verification

To check to see if it is installed properly, visit your Version page, eg Special:Version.

You should see the following items:

Releases

Todo

  • Add ratings into search output
  • Fix sql injection possibilities
  • Add tags to allow insertion of ratings into a page, or a list of "top 5" ratings etc.

RoadMap

0.2 No Date

  • Ratings in search output
  • Fix sql injection possibilities

History

0.1 - Date 30 July 2006

  • Allows 5 star rating for each page in a wiki
  • With too few ratings it will use previous ratings
  • With too few ratings, rating will change color to draw attention to get ratings

Source Code

Authoritative version of source code available at RatingExtension/Source_Code.

<?php

################################################################################
# Setup                                                                        #
################################################################################
# This section defines parameters of the plugin                                #
################################################################################

#-------------------------------------------------------------------------------
# Sets the minimum number of ratings that can be returned in a ratings tag     #
# This helps keep performance for large wikis acceptable                       #
#-------------------------------------------------------------------------------
$renderLimit=100;

#-------------------------------------------------------------------------------
# Sets the minimum number of ratings required for the rating to be valid       #
# If the number of ratings are less, rating is 0 and bolded                    #
#-------------------------------------------------------------------------------
$countLimit=3;

#-------------------------------------------------------------------------------
# Defines namespaces for which ratings are enabled                             #
#-------------------------------------------------------------------------------
$allowedNamespaces=array(
  0=>true, //Default
  1=>true, //Talk
  2=>true, //User
  3=>true, //User_talk
  4=>true, //Project
  5=>true, //Project_talk
  6=>true, //Image
  7=>true, //Image_talk
#  8=>true, //MediaWiki
#  9=>true, //MediaWiki_talk
  10=>true, //Template
  11=>true, //Template_talk
  12=>true, //Help
  13=>true, //Help_talk
  14=>true, //Category
  15=>true //Category_talk 
);

################################################################################
# Core Code                                                                    #
################################################################################
# This section handles the core functions of this extension                    #
#   * Serving Files                                                            #
#   * Recording Ratings                                                        #
#   * Displaying Ratings                                                       #
################################################################################

#-------------------------------------------------------------------------------
# If html request requests file, serve file                                    #
# Files are requested by adding the ?file= GET parameter                       #
#-------------------------------------------------------------------------------
if(isset($_GET['file']))
  doFile();

#-------------------------------------------------------------------------------
# If html request contains rate information, process it                        #
# Rating information indicated by ?rate= GET parameter                         #
#                                                                              #
# MediaWiki engine is started by imitating the code in index.php               #
# This ensures database functions are working                                  #
#-------------------------------------------------------------------------------
if(isset($_GET['rate']))
{
  #Init
  #----------------
  require_once( 'includes/Setup.php' );
  require_once( "includes/Wiki.php" );
  $mediaWiki = new MediaWiki();
  $action = $wgRequest->getVal( 'action', 'view' );
  $title = $wgRequest->getVal( 'title' );
  $wgTitle = $mediaWiki->checkInitialQueries($title, $action, $wgOut,
    $wgRequest, $wgContLang);


  #Record Rating
  #----------------
  $dbr =& wfGetDB( DB_WRITE );
  $sql = "REPLACE INTO `ratings` (`page_oldid`, `user_id`, `page_rating`) ".
    "VALUES (".intval($_GET['oldid']).", '".
    ($wgUser->getID()?$wgUser->getID():$wgUser->getName()).
    "', ".intval($_GET['rate']).")";
  $res=wfQuery($sql, DB_WRITE, "");

  calculateRating($_GET['oldid']);
  
  #Return to refering page and exit
  #----------------
  $wgTitle->invalidateCache();
  $dbr->immediateCommit();
  header( 'Location: '.$_SERVER["HTTP_REFERER"] ) ;
  die();
}

#-------------------------------------------------------------------------------
# Initialize extension                                                         #
# Sets up credit information, and hooks                                        #
#-------------------------------------------------------------------------------
if(isset($wgScriptPath))
{
  $wgExtensionCredits["parserhook"][]=array(
    'name' => 'Rating Extension',
    'version' => '0.3.1',
    'url' => 'http://wiki.peacocktech.com/wiki/RatingExtension',
    'author' => '[http://about.peacocktech.com/trevorp/ Trevor Peacock]',
    'description' => 'Adds rating mechanism' );
  $wgHooks['OutputPageBeforeHTML'][] = 'InsertRating';
  $wgHooks['SkinTemplateSetupPageCss'][] = 'RatingCss';
  $wgExtensionFunctions[] = "RatingExtension";
}

function RatingExtension()
{
  global $wgParser;
  $wgParser->setHook("rating", "renderRating");
  $wgParser->setHook("ratingcount", "renderRatingCount");
  $wgParser->setHook("ratings", "renderRatings");
}

#-------------------------------------------------------------------------------
# Render the <rating> tag                                                      #
# Shows the rating of the page specified by the text between the tags          #
#-------------------------------------------------------------------------------
function renderRating($input, $argv, &$parser)
{
  $rating=getRatingCache(getOldIDFromTitle(trim($input)));
  return number_format($rating['rating'], 2);
}

#-------------------------------------------------------------------------------
# Render the <rating> tag                                                      #
# Shows the number of ratings of the page specified by the text between        #
#  the tags                                                                    #
#-------------------------------------------------------------------------------
function renderRatingCount($input, $argv, &$parser)
{
  $rating=getRatingCache(getOldIDFromTitle(trim($input)));
  return number_format($rating['count'], 2);
}

#-------------------------------------------------------------------------------
# Displays a list of ratings based on the parameters given between the tags    #
#-------------------------------------------------------------------------------
function renderRatings($input, $argv, &$parser)
{
  $parameters=splitParameters($input);
  $sortdata=getSortData(isset($parameters['namespace'])?
    $parameters['namespace']:array());
  $ratings=getRatingList(isset($parameters['namespace'])?
    $parameters['namespace']:array(), $sortdata['column'], $sortdata['order'],
    isset($parameters['limit'])?$parameters['limit'][0]:10);
  $output='';
  $displayCount=(isset($parameters['displaycount'])?
    $parameters['displaycount'][0]:'false')!='false';
  $displayRating=(isset($parameters['displayrating'])?
    $parameters['displayrating'][0]:'true')!='false';
  $pattern=isset($parameters['pattern'])?$parameters['pattern'][0]:
    '[[$1|$2]]'.($displayRating?' ($3)':'').($displayCount?' ($4 ratings)':'');
  $limit=isset($parameters['limit'])?$parameters['limit'][0]:10;
  foreach($ratings as $rating)
  {
    $output.=str_replace(array('$1', '$2', '$3', '$4', '$5'), 
      array(($rating['namespace']==6?':':'').$rating['title'],
        strtr($rating['title'], '_', ' '), 
        number_format($rating['rating'], 2), 
        $rating['count'], $rating['oldid']), $pattern)."\n\n";
    if(--$limit==0) break;
  }
  return renderWikiText(trim($output), $parser);
}

#-------------------------------------------------------------------------------
# Insert rating to top of page                                                 #
#-------------------------------------------------------------------------------
function InsertRating($parserOutput, $text) {
  global $wgArticle, $allowedNamespaces;
  if(!$allowedNamespaces[$wgArticle->getTitle()->getNamespace()])
    return;
  $oldid=getOldID($wgArticle);
  if($oldid)
    $text='<div id="ratingsection">'.
      getRatingHTML(getRatingCache($oldid), $oldid).'</div>'.$text;
}

#-------------------------------------------------------------------------------
# Add CSS into skin for rating                                                 #
#-------------------------------------------------------------------------------
function RatingCss(&$css) {
  global $wgScriptPath;
  $css = "/*<![CDATA[*/".
  " @import \"$wgScriptPath/?file=rating.css\"; ".
  "/*]]>*/";
  return true;
}

################################################################################
# Supporting Functions                                                         #
################################################################################
# These functions support the core functions of the extension                  #
################################################################################

#-------------------------------------------------------------------------------
# Processes sortby parameters to determine how to sort ratings                 #
#-------------------------------------------------------------------------------
function getSortData($sort)
{
  $column='rating';
  $order=-1;
  foreach($sort as $item)
  {
    switch($item)
    {
      case 'rating':
        $column='rating'; break;
      case 'name':
        $column='title'; break;
      case 'count':
        $column='count'; break;
      case 'ascending':
        $order=SORT_ASC; break;
      case 'descending':
        $order=SORT_DESC; break;
    }
  }
  if($order==-1)
  {
    switch($column)
    {
      case 'rating':
        $order=SORT_DESC; break;
      case 'title':
        $order=SORT_ASC; break;
      case 'count':
        $order=SORT_DESC; break;
      default:
        $order=SORT_DESC;
    }
  }
  return array('column'=>$column, 'order'=>$order);
}

#-------------------------------------------------------------------------------
# Processes the given text using the mediawiki parser engine                   #
#-------------------------------------------------------------------------------
function renderWikiText($input, &$parser)
{
  return $parser->parse($input, $parser->mTitle, $parser->mOptions, 
    false, false)->getText();
}

#-------------------------------------------------------------------------------
# Returns the page rating given the pages string title                         #
#-------------------------------------------------------------------------------
function getOldIDFromTitle($title)
{
  $title=Title::newFromText($title);
  if($title==null)
  {
    echo "NoArticle";
    return 0;
  }
  $oldid=getOldID(new Article($title));
  if(!$oldid)
  {
    return "NoOldID";
    return 0;
  }
  return $oldid;
}

#-------------------------------------------------------------------------------
# Completes ratings_cache table for any revisions without ratings              #
#-------------------------------------------------------------------------------
function doFillCache()
{
  global $allowedNamespaces;
  $namespacestring='';
  foreach(array_keys($allowedNamespaces) as $space)
  {
    if(is_numeric($space))
    {
      $namespacestring.=', '.$space;
    }
  }
  $namespacestring=substr($namespacestring, 2);
  $sql="SELECT `page_latest` AS `oldid` FROM `page` WHERE".
    ' `page_namespace` IN ('.$namespacestring.') AND'.' `page_latest` NOT IN '.
    '(SELECT `page_oldid` FROM `ratings_cache`);';
  $articles=runQuery2($sql);
  foreach($articles as $article)
    getRating($article->oldid);
}

#-------------------------------------------------------------------------------
# Returns a list of pages in the specified namespaces                          #
# Optionally it may return sorting rating information for results              #
#-------------------------------------------------------------------------------
function getPageList($namespace=array(), $ratings=false, $sort='title',
    $order=SORT_ASC, $limit=100)
{
  global $renderLimit, $allowedNamespaces;
  $limit=($limit>$renderLimit?$renderLimit:$limit);
  $namespacestring='';
  $filterByNamespace=false;
  foreach($namespace as $space)
  {
    if(is_numeric($space) && isset($allowedNamespaces[$space]))
    {
      $namespacestring.=', '.$space;
      $filterByNamespace=true;
    }
  }
  if($filterByNamespace)
    $namespacestring=substr($namespacestring, 2);
  $sql="SELECT `page_title` AS `title`, `page_namespace` AS `namespace`, 
    `page_latest` AS `oldid` FROM `page`".($filterByNamespace?
    ' WHERE `page_namespace` IN ('.$namespacestring.')':"").';';
  if($ratings)
    $sql="SELECT `page_title` AS `title`, `page_namespace` AS `namespace`, ".
      "`page_latest` AS `oldid`, `page_oldid`, `page_rating` AS `rating`, ".
      "`page_rating_count` AS `count` FROM `page`, `ratings_cache` ".
      "WHERE `page_latest`=`page_oldid`".($filterByNamespace?
      ' AND `page_namespace` IN ('.$namespacestring.')':"").
      ' ORDER BY '.$sort.($order==SORT_ASC?'':'').
      ($order==SORT_DESC?' DESC':'').' LIMIT '.$limit.';';
  return runQuery2($sql);
}

#-------------------------------------------------------------------------------
# Returns a list of pages and their ratings for all pages in the specified     #
#  namespaces                                                                  #
#-------------------------------------------------------------------------------
function getRatingList($namespace=array(), $sort='title', $order=SORT_ASC,
    $limit=100)
{
  global $wgNamespaceNamesEn, $renderLimit;
  doFillCache();
  $limit=($limit>$renderLimit?$renderLimit:$limit);
  $articles=getPageList($namespace, true, $sort, $order, $limit);
  $ratings=array();
  foreach($articles as $article)
  {
    $rating=formatRating(array('count'=>$article->count,
      'rating'=>$article->rating));
    $ratings[]=array('title' => $wgNamespaceNamesEn[$article->namespace].
      ($article->namespace==0?'':':').$article->title, 
        'rating' => $rating['rating'], 'count' => $rating['count'],
        'oldid'=>$article->oldid, 'title_name'=>$article->title,
        'namespace_name'=>$wgNamespaceNamesEn[$article->namespace],
	'namespace'=>$article->namespace);
  }
  return $ratings;
}

#-------------------------------------------------------------------------------
# Splits parameters from the wikitext.                                         #
# Each parameter should be on its own line in the format  parameter = value    #
#-------------------------------------------------------------------------------
function splitParameters($input)
{
  $parameters=array();
  foreach(split("\n", $input) as $parameter)
  {
    $parameter=split('=', $parameter, 2);
    if(count($parameter)==2)
    {
      foreach($parameter as $key => $val)
        $parameter[$key]=trim($val);
      if(isset($parameters[$parameter[0]]))
        $parameters[$parameter[0]][]=$parameter[1];
      else
        $parameters[$parameter[0]]=array($parameter[1]);
    }
  }
  return $parameters;
}

#-------------------------------------------------------------------------------
# Use the mediawiki engine to run the given sql code and return an object      #
# containing the first result of the query                                     #
#-------------------------------------------------------------------------------
function runQuery($sql)
{
  $dbr =& wfGetDB( DB_SLAVE );
  $res=wfQuery($sql, DB_SLAVE, "");
  if(wfNumRows($res)>0)
    return $dbr->fetchObject( $res );
  else
    return null;
}

function runQuery2($sql)
{
  $dbr =& wfGetDB( DB_SLAVE );
  $res=wfQuery($sql, DB_SLAVE, "");
  $array=array();
  while($item=$dbr->fetchObject( $res ))
    $array[]=$item;
  return $array;
}

#-------------------------------------------------------------------------------
# Return the oldid of the current page                                         #
# If oldid=0 (most current revision) take the latest oldid from the database   #
# for the current article                                                      #
#-------------------------------------------------------------------------------
function getOldID($article)
{
  $oldid=$article->getOldIDFromRequest();
  if($oldid!=0)
    return $oldid;
  $dbr =& wfGetDB( DB_SLAVE );
  $sql="SELECT `page_latest` AS `oldid` FROM `page` ".
    "WHERE `page_id`=".$article->getID().";";
  $res=wfQuery($sql, DB_SLAVE, "");
  $row=$dbr->fetchObject( $res );
  if($row->oldid)
    return $row->oldid;
  return null;
}

#-------------------------------------------------------------------------------
# Performs a SQL query to fetch a rating for page oldid                        #
#-------------------------------------------------------------------------------
function getRatingData($oldid)
{
  $sql="SELECT COUNT(*) AS `count`, AVG(`page_rating`) AS `rating` ".
    "FROM ratings WHERE `page_oldid`=".intval($oldid)." GROUP BY `page_oldid`;";
  return runQuery($sql);
}

#-------------------------------------------------------------------------------
# Returns the ID for the revision before $revision                             #
#-------------------------------------------------------------------------------
function getPreviousRevisionID( $revision ) {
	$dbr =& wfGetDB( DB_SLAVE );
	return $dbr->selectField( 'revision', 'rev_id',
		'rev_page=(SELECT `rev_page` from `revision` WHERE `rev_id`='.
		intval( $revision ).')'.' AND rev_id<' . intval( $revision ) . 
		' ORDER BY rev_id DESC' );
}

#-------------------------------------------------------------------------------
# Fetch and calculate a rating for page oldid                                  #
# If there are not enough ratings for the current revivion, cycle              #
# older revisions to gather a minimum number of ratings                        #
# Updates cache table with calcaulated values                                  #
#-------------------------------------------------------------------------------
function calculateRating($oldid)
{
  global $wgTitle, $countLimit;
  $origid=$oldid;
  $ratingdata=getRatingData($oldid);
  $finalrating='?';
  $currentcount=number_format($ratingdata->count, 0);
  #If there are not enough ratings for the current revision
  if($ratingdata->count<$countLimit)
  {
    $count=$ratingdata->count;
    $rating=$count*$ratingdata->rating;
    #cycle older revisions looking for more ratings
    while($oldid=getPreviousRevisionID($oldid)) 
    {
      $ratingdata=getRatingData($oldid);
      #If still not enough ratings
      if($count+$ratingdata->count<$countLimit)
      {
        $count+=$ratingdata->count;
        $rating+=$ratingdata->count*$ratingdata->rating;
      }
      else #found enough ratings
      {
        $rating+=($countLimit-$count)*$ratingdata->rating;
        $count=$countLimit;
        $finalrating=$rating/$count;
        $oldid=false;
      }
    }
  }
  else
    $finalrating=$ratingdata->rating;

  $dbr =& wfGetDB( DB_WRITE );
  $sql = "REPLACE INTO `ratings_cache` (`page_oldid`, `page_rating`, ".
    "`page_rating_count`) VALUES (".intval($origid).", ".
    (is_numeric($finalrating)?$finalrating:0).", ".$currentcount.")";
  $res=wfQuery($sql, DB_WRITE, "");
  
  return array('rating'=>$finalrating, 'count'=>$currentcount);
}

#-------------------------------------------------------------------------------
# Formats rating array for insertion to page rating section                    #
#-------------------------------------------------------------------------------
function formatRating($rating)
{
  $finalrating=$rating['rating'];
  $currentcount=$rating['count'];
  
  #format rating data
  if(is_numeric($finalrating) && $finalrating>0)
    $finalrating=($finalrating-1)*1.25;
  $ratingarray=array('display'=>
      (is_numeric($finalrating)?number_format($finalrating, 2):$finalrating).
      " ($currentcount ratings)",
    'count'=>$currentcount,
    'rating'=>(is_numeric($finalrating)?$finalrating:0));
  return $ratingarray;
}

#-------------------------------------------------------------------------------
# Calculates rating from database for specified revision                       #
# Updates rating cache table                                                   #
#-------------------------------------------------------------------------------
function getRating($oldid)
{
  return formatRating(calculateRating($oldid));
}

#-------------------------------------------------------------------------------
# Fetches the ratings from cache table                                         #
# Calculates rating if it is not found in cache                                #
#-------------------------------------------------------------------------------
function getRatingCache($oldid)
{
  $sql='SELECT * FROM `ratings_cache` WHERE `page_oldid`='.intval($oldid).';';
  $rating=runQuery($sql);
  $rating=array('rating'=>$rating->page_rating,
    'count'=>$rating->age_rating_count);
  if($rating->page_oldid==null)
    $rating=calculateRating($oldid);
  return formatRating($rating);
}

#-------------------------------------------------------------------------------
# Given the rating array and the page oldid, generate HTML code to be          #
# displayed                                                                    #
#-------------------------------------------------------------------------------
function getRatingHTML($rating, $oldid)
{
  global $wgTitle, $wgScriptPath, $countLimit;
  $html='';
  #generate stars
  for($x=0;$x<=4;$x++)
  {
    $html.='<a href="'.
      $wgTitle->getFullURL('oldid='.$oldid.'&rate='.($x+1)).'" rel="nofollow">'.
      '<img src="?file=Star';
    if($rating['rating']>=$x+1) #larger than current star : filled
         $html.='4'.($rating['count']<$countLimit?'b':'');
    elseif($rating['rating']>=$x+0.75) #3/4 current star : 3/4 filled
      $html.='3'.($rating['count']<$countLimit?'b':'');
    elseif($rating['rating']>=$x+0.5) #1/2 current star : 1/2 filled
      $html.='2'.($rating['count']<$countLimit?'b':'');
    elseif($rating['rating']>=$x+0.25) #1/4 current star : 1/4 filled
      $html.='1'.($rating['count']<$countLimit?'b':'');
    else #less than current star : empty
      $html.='0';
    $html.='.png" align=bottom/></a>'."\n";
  }
  #add text rating 
  $html.=' '.$rating['display'].'';
  $html=($rating['count']<$countLimit?'<b>'.$html.'</b>':$html);
  return $html;
}


#-------------------------------------------------------------------------------
# Return a file and exit.                                                      #
# File determined by ?file= GET parameter                                      #
#-------------------------------------------------------------------------------
function doFile()
{
  switch ($_GET['file'])
  {
    #Star .png files
    case "Star0.png":
    case "Star1.png":
    case "Star1b.png":
    case "Star2.png":
    case "Star2b.png":
    case "Star3.png":
    case "Star3b.png":
    case "Star4.png":
    case "Star4b.png":
      header("Content-type: image/png");
      echo readFile('extensions/rating/'.$_GET['file']);
      die();
    #extension css styling
    case "rating.css":
      header("Content-type: text/css");
        ?>
#ratingsection {
  float: right;
  margin-top: -3.7em;
  padding: 3px;
}

#ratingsection b {
  color: red;
  padding: 4px;
}
<?php
      die();
  }
}

?>

Development version

Personal tools