Complete Star Rating Script

Created: March 22, 2013
Last Modified: January 2, 2017
Subscribe to Internet Tips and Tools Feed

★★★★★★★★★★Star Rating Script 3.3 out of 5 stars based on 6 ratings.
5 stars
2 votes
4 stars
1 votes
2 stars
3 votes
Login to submit your rating

This complete 5 star rating script shows you how to not only make a star rating system but also how to store the user ratings in a database and how to use schema.org microdata to have the ratings appear in google, bing and yahoo search results as rich snippets. Go ahead and login so you can see how the system works. Creating an account doesn't require email verification so you can use a fake email address if you desire. NEW!! 7/26/2013 - A PHP login script is available here if you need one to use with the ratings script. NEW!! 4/24/2014 - You can now use this star ratings script without requiring the user to login. The script will use the user's IP address for the user_id.

Features

  • Complete Star Rating System in one file: ratings.php
  • Makes the stars without the use of images through HTML entities or you can supply images
  • Uses Javascript Ajax to submit a user rating
  • Uses PHP and MySQL
  • Automatically builds the html code for the new schema.org microdata standard so that the star ratings show up as rich snippets in google, yahoo and bing search engine results
  • Two MySQL database tables are used for faster access of star rating averages and total ratings
dlc_b

Download

Downloaded 0 times.
This script is provided for free but please consider making a donation to help with server costs and other expenses.

There is one file inside of ratings.zip called ratings.php unzip it and then include the file anywhere you want the star rating to show up in your webpage like this:


	<?PHP require_once('ratings.php'); ?>

If your web page is an .htm or .html file instead of a .php file then you have to make sure that your server can process PHP code in html files by adding a line to the .htaccess file located in the www folder of your server. The line may look something like this:

	addhandler application/x-httpd-php .htm .html
	

Instead of downloading the zip file above you can also copy and paste the entire script below. It is fully documented with comments:

<?PHP
	/* Complete Star Rating Script
		Created By Jeff Baker on March 22, 2013
		Copyright (C) 2014 Jeff Baker
		www.seabreezecomputers.com/rating
		ver 1.45 - June 29, 2016
	*/
	@session_start();
	
	// Enter your mysql database username, password and database name below.  
	// Usually leave server as localhost
	$db_username="your_mysql_username"; 
	$db_pw="your_pw";
	$server="localhost";
	$database="your_mysql_database";
	
	@include_once('settings.php');
			
	$require_login = 1; // 1 = yes; 0 = no (Not recommended. User's IP Address is used as user_id)
	$your_login_page = "login.php"; /* Put the link to your login page here */
	//$star_image_width = 1; // Version 1.45 - Not used anymore; Calculating with javascript
	//$star_width_type = 'em'; // Version 1.45 - Not used anymore; Calculating with javascript
	$yellow_star = '&#9733;'; // HTML entity of a star
	//$yellow_star = "<img src='images/yellowstar.gif'>"; // If you want to use an image then uncomment this line
	$grey_star = '&#9733;'; // HTML entity of a star used for blank stars in ratings
	//$grey_star = "<img src='images/greystar.gif'>"; // If you want to use an image then uncomment this line
	$user_star = '&#9733;'; // HTML entity of a star used for the users personal rating
	//$user_star = "<img src='images/redstar.gif'>"; // If you want to use an image then uncomment this line
	$blank_star = '&#9734;'; // HTML entity of a star outline used for a users personal rating before rating
	//$blank_star = "<img src='images/blankstar.gif'>"; // If you want to use an image then uncomment this line
	
	$yellow = "#F99B00"; // Color used for HTML star entity
	$grey = "#999999"; // Color used for remainder of stars not in the rating value
	$red = "#FF5555"; // Color used for the stars of the users personal rating
	
	// connect to mysql
	$link = mysql_connect($server,$db_username,$db_pw)
		or die("Couldn't connect to MySQL".mysql_error());
	
	// connect to database
	mysql_select_db($database , $link)
		or die("Select DB Error: ".mysql_error());
		
	/* 	$id is an id number that you have given this product or webpage to differentiate it from
		other products or webpages. Version 1.45 $id is now the url of the webpage including domain.
		You can exclude domain by changing to: $id = $_SERVER['REQUEST_URI'];
		You may replace $_REQUEST['id'] with a unique number for this page if you like.
		For example: Change it to: $id = $_REQUEST['id']
		to get the id=5 on the url from: http://www.website.com/thispage.php?id=5
		In the star_rating table $id is stored in the 'page' field.
	*/
	$id = $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']; // Version 1.45 - Changed $_REQUEST['id'] to $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
	
	/* 	$name is the name of the product being reviewed and rated.
		In this instance $name is derived from a mysql row called $r and a database field 'name'
		You would set this up on the webpage that calls this script.  Something like:
		$query = "SELECT name FROM producs WHERE id = '$id' LIMIT 1";
		$result = mysql_query($query) or die($query." : ".mysql_error());
		if (mysql_num_rows($result))
		{
			$r = mysql_fetch_assoc($result); 
		}
		Or you can just make up a name here if you arn't calling this script from multiple pages:
		$name = "Green Widget";
	*/
	$name = $r['name']; // Name of product. Needed for schema.org microdata and for rich snippets to appear in search engines
	
	
	/* $user_id is how this script allows users to submit ratings.  They must be logged
		in and a $_SESSION['user_id'] must be set.  Instead, you may be using $_SESSION['username'] 
		on your server.  In that case you will need to change the star_rating table
		to have something like "user_id VARCHAR(50)," in place of "user_id INT," and you
		will need to replace all references of $_SESSION['user_id'] in this script to $_SESSION['username']
	*/
	if ($require_login) // 4/24/2014 ver. 1.4 (Allow script to be used without login.php)
		$user_id = mysql_real_escape_string(strip_tags(substr($_SESSION['user_id'],0,50)));
	else
	{
		if (isset($_COOKIE['user_id'])) // If they have rated before and have a cookie set
			$user_id = $_SESSION['user_id'] = mysql_real_escape_string(strip_tags(substr($_COOKIE['user_id'],0,50)));
		else // new user using ip address as user_id
		{
			$user_id = $_SESSION['user_id'] = ip2long(mysql_real_escape_string(strip_tags(substr($_SERVER['REMOTE_ADDR'],0,50))));
			$expire_time = time()+60*60*24*365*10; // 10 years
			@setcookie("user_id", $user_id, $expire_time);
		}
	}
	/* $_SESSION['refer'] is set so that if a user is not logged in then your login script
	can check to see if $_SESSION['refer'] is set so that the user is redirected back to
	this page so that they can submit a rating.  It can be checked in your login.php file
	like this:
	if (isset($_SESSION['refer']))
		$location = $_SESSION['refer'];
	*/
	//if (!isset($_REQUEST['rating'])) // Only set refer if script is not being called by ajax - 7/29/2013
	//	$_SESSION['refer'] = $_SERVER['REQUEST_URI'];
	
	$avg_stars = $total_votes = 0;
	
	// Create star_rating table if it does not exist
	$result = mysql_query("CREATE TABLE IF NOT EXISTS star_rating
	( id INT NOT NULL AUTO_INCREMENT, 
	PRIMARY KEY(id), 
	page VARCHAR(250),
	INDEX page(page),
	user_id INT unsigned,
	INDEX user_id(user_id),
	stars TINYINT NOT NULL,				
	sent_date DATETIME)")
		or die("Couldn't create table star_rating: ".mysql_error());
	mysql_query("ALTER TABLE star_rating MODIFY page VARCHAR(250)"); // Version 1.45	
	// Create star_rating_averages table if it does not exist
	$result = mysql_query("CREATE TABLE IF NOT EXISTS star_rating_averages
	( id INT NOT NULL AUTO_INCREMENT, 
	PRIMARY KEY(id), 
	page VARCHAR(250),
	INDEX page(page),
	avg_stars FLOAT NOT NULL,	
	total_votes INT)")
		or die("Couldn't create table star_rating_averages: ".mysql_error());
	mysql_query("ALTER TABLE star_rating_averages MODIFY page VARCHAR(250)"); // Version 1.45	
	$id = mysql_real_escape_string(strip_tags(substr($id,0,250))); // Version 1.45 - page is not VARCHAR(250) instead of INT
	
	// Fill in some test values (Uncomment this section to fill in test values in mysql table)
/*	if (isset($id))
	{
		$query = "INSERT INTO star_rating_averages (page, avg_stars, total_votes)
					VALUES ('$id', 0, 0)";
		$result = mysql_query($query) or die($query." : ".mysql_error());
		for ($i = 1; $i <= 5; $i++)
		{
			$query = "INSERT INTO star_rating (page, user_id, stars, sent_date)
						VALUES ('$id', '$user_id', '$i', NOW())";
			$result = mysql_query($query) or die($query." : ".mysql_error());				
		}
		$query = "UPDATE star_rating_averages
					SET avg_stars = (SELECT ROUND(AVG(stars),1)
							FROM star_rating WHERE page='$id'),
					 	total_votes = (SELECT COUNT(*)
					 		FROM star_rating WHERE page='$id')
					 	WHERE page = '$id' LIMIT 1";
		$result = mysql_query($query) or die($query." : ".mysql_error());	
	}
	*/

	if (isset($_REQUEST['id']))
	if (isset($_REQUEST['rating'])) // The user has selected a star rating
	if (isset($_SESSION['user_id'])) // But they must be logged in to submit their rating
	{
		/* If they have submitted a value already then just update their rating.
			Having the date field in star_rating table makes it always update even on a duplicate rating
			that way we don't end up with an extra vote with an update and an insert.
		*/
		$id = mysql_real_escape_string(strip_tags(substr($_REQUEST['id'],0,250)));
		$rating = mysql_real_escape_string(strip_tags(substr($_REQUEST['rating'],0,1)));
		if ($rating > 5) $rating = 5; // Make sure star rating is maximum of 5 and ...
		else if ($rating < 1) $rating = 1; // ... minimum of 1
		
		// First see if the user has submitted a rating for this product in the past
		$query = "SELECT id, stars FROM star_rating
					WHERE page = '$id' AND user_id = '$user_id' LIMIT 1";
		$result = mysql_query($query) or die($query." : ".mysql_error());
		if (mysql_num_rows($result))
		{
			$your_row = mysql_fetch_array($result);	
			$old_rating = $your_row['stars'];
			$your_row = $your_row['id'];
			// Only update rating if the new rating is different from the old rating
			if ($old_rating != $rating)
			{
				$query = "UPDATE star_rating SET stars='$rating', sent_date = NOW()
					WHERE id='$your_row' LIMIT 1";
				$result = mysql_query($query) or die($query." : ".mysql_error());	
				// Calculate the difference between users $old_rating and users new $rating
				$diff = $rating - $old_rating;
				// Update star_rating_averages table
				$query = "UPDATE star_rating_averages 
						SET avg_stars = ROUND((avg_stars * total_votes + $diff) / total_votes, 2)
						WHERE page = '$id' LIMIT 1";
				$result = mysql_query($query) or die($query." : ".mysql_error());		
			}
			
		}
		else // User is rating this product for the first time
		{
			$query = "INSERT INTO star_rating (page, user_id, stars, sent_date)
						VALUES ('$id', '$user_id', '$rating', NOW())";
			$result = mysql_query($query) or die($query." : ".mysql_error());
			// Update star_rating_averages table
			$query = "UPDATE star_rating_averages 
						SET avg_stars = ROUND((avg_stars * total_votes + $rating) / (total_votes+1), 2),
						total_votes = total_votes + 1
						WHERE page = '$id' LIMIT 1";
			$result = mysql_query($query) or die($query." : ".mysql_error());
			if (mysql_affected_rows()==0) // Or add to table if page of $id does not exist
			{
				$query = "INSERT INTO star_rating_averages (page, avg_stars, total_votes)
						VALUES ('$id', '$rating', '1')"; 
				$result = mysql_query($query) or die($query." : ".mysql_error());	
			}				
		}				
					
	}
	
	if (isset($id))
	{
		
		/* Get average star rating and amount of users voting
			ROUND is a mysql command that rounds a number. In this instance to 1 decimal point.
			So 3.4333 will just be 3.4
			AVG gets the averager of all 'stars' fields
			The 'page' field is an id number made up by you as a reference for the current webpage
		*/
		// This is the old database intensive way (not being run)
		$query = "SELECT ROUND(AVG(stars),1) AS avg_stars, COUNT(*) AS total_votes 
				FROM star_rating
				WHERE page = '$id'";
		// The new way grabs the votes from the star_rating_averages table
		$query = "SELECT ROUND(avg_stars,1) AS avg_stars, total_votes
				FROM star_rating_averages
				WHERE page = '$id'";
		$result = mysql_query($query) or die($query." : ".mysql_error());
		if (mysql_num_rows($result))
		{
			$row = mysql_fetch_array($result);	
			$total_votes = $row['total_votes'];
			$avg_stars = $row['avg_stars'];
			if (is_null($avg_stars)) $avg_stars = 0;
			
			/* Now that we have the average stars we want to have ratings detail and
				get the total votes for each star rating and the percentage of the vote
				for each star rating
			*/
			$ratings_detail = '<table>';
			$query = "SELECT stars, COUNT(*) AS votes 
					FROM star_rating
					WHERE page = '$id' GROUP BY stars ORDER BY stars DESC";
			$result = mysql_query($query) or die($query." : ".mysql_error());
			while($row = mysql_fetch_assoc($result)) 
			{	
				$percentage = round($row['votes'] / $total_votes * 100);
				$ratings_detail .= "<tr><td>".$row['stars']." stars </td>".
									"<td><hr align='left' noshade size='4' width='".($percentage * 2)."'></td>".
									"<td>".$row['votes']." votes</td></tr>";
			}
			$ratings_detail .= '</table>';
				
		}
		else // No votes for this page yet
		{
			$total_votes = 0;
			$avg_stars = 0;
		}
		
	}
	else
	{
		echo "No item id is specified so star ratings can't be done.";
		//exit;
	}
	
	$error="";
	$message="";

	/* Setup the stars by using images or HTML Entities Star: &#9733; 
		We will have one absolute positioned span inside of and on top of one relative span
	*/	
	// Calculate the width of the yellow stars
	//$avg_stars_width = round($star_image_width * $avg_stars, 1); // Version 1.45 - Not used anymore; Calculating with javascript
	// Put 5 grey stars in relative span
	$stars_display = "<span class='stars_display' data-avg-stars='".$avg_stars."' style='position: relative; display:inline-block; overflow:hidden; color: $grey; '>".
						$grey_star.$grey_star.$grey_star.$grey_star.$grey_star;
	// Overlay 5 yellow stars on top of the grey stars
	$stars_display .= "<span style='position: absolute; top: 0; left: 0; overflow:hidden; color: $yellow;'>".
						$yellow_star.$yellow_star.$yellow_star.$yellow_star.$yellow_star."</span>";
	// Close relative span
	$stars_display .= "</span>"; 
	
	/*	Setting the schema.org microdata for Ratings Rich Snippets in a Google Search
		
		http://schema.org/docs/full.html has the full type heirarchy, but the only types
		that seem to display stars in the Google Structured Data Testing Tool is
		Product and Restaurant
		
		Could also add content="4" such as <span itemprop="ratingValue" content="4">****</span>
	*/	
	$schema = '<span itemscope itemtype="http://schema.org/Product">'.
			  '<span itemprop="name">'.addslashes($name).'</span>'. 
			 	' <span itemprop="aggregateRating" itemscope itemtype="http://schema.org/AggregateRating">'.
			      ' <span itemprop="ratingValue">'.$avg_stars.'</span>'.
			      ' out of <span itemprop="bestRating">5</span> stars'.
				' based on <span itemprop="ratingCount">'.$total_votes.'</span> ratings.'.
			  '</span> '.
			'</span>';
			
	/* See if current user has already submitted a rating for this page */
	if (isset($_SESSION['user_id']))
	{
		$query = "SELECT stars, sent_date FROM star_rating
					WHERE page = '$id' AND user_id = '$user_id' LIMIT 1";
		$result = mysql_query($query) or die($query." : ".mysql_error());
		if (mysql_num_rows($result))
		{
			$your_row = mysql_fetch_array($result);	
			$your_rating = $your_row['stars'];
			$your_date = date('F j, Y', strtotime($your_row['sent_date']));
		}
				
		/* Create a star ratings span for the current user to be able to submit a rating */
		$your_rating_display = "<br>Your Rating: <span id='your_rating_display' style='cursor:pointer; position: relative; display:inline-block; overflow: hidden; color: $grey; ' >"; // Version 1.45 - Removed .= to avoid PHP Notice
		for ($i=1; $i <= 5; $i++) // Five empty stars with mouseover events (or some yellow stars with your previous rating)
		{
		//	$stars_width = round($star_image_width * $i, 1).$star_width_type; // Version 1.45 - Not used anymore; Calculating with javascript
			if (isset($your_rating) && $i <= $your_rating) // Your previous rating stars
				$your_rating_display .= "<span style='color: $red' onmouseover='if (!(\"ontouchstart\" in document.documentElement)) ".
									"change_your_stars($i)' onclick='send_rating($i)'>".
									"$user_star</span>";
			else // empty stars				
				$your_rating_display .= "<span onmouseover='if (!(\"ontouchstart\" in document.documentElement)) ".
									"change_your_stars($i)' onclick='send_rating($i)'>".
									"$blank_star</span>";
		}	
		// Overlay 5 yellow stars on top of the grey stars
		$your_rating_display .= "<span id='your_stars' style='position: absolute; top: 0; left: 0; overflow: hidden; color: $yellow;".
								" width: 0; ' onmouseout='this.style.width = \"0\"'>";
		for ($i=1; $i <= 5; $i++) // Five yellow stars with mouseover events
		{
			//$stars_width = round($star_image_width * $i, 1).$star_width_type; // Version 1.45 - Not used anymore; Calculating with javascript
			$your_rating_display .= "<span onmouseover='if (!(\"ontouchstart\" in document.documentElement)) ".
									"change_your_stars($i)' onclick='send_rating($i)'>".
									"$yellow_star</span>";
		}					
		$your_rating_display .= "</span></span>";
		if (isset($your_rating)) 
			$your_rating_display .= " $your_rating stars on $your_date"; // Display date next to your rating
	}
	else 
		$your_rating_display = "<a href='$your_login_page'>Login</a> to submit your rating<br>";
	
	if ($error != '') // if there is an error than print it.
	{
	    addslashes($error);
	    echo 'document.write("<P>Error: '.$error.'");'.chr(13);
	}

	/* If the user is submitting a rating through ajax 
		then we are sending output through ajax so don't surround with div
		so that javascript ratings_get_ajax() can put the HTML into the 'star_rating_div' div
	*/
	if (isset($_REQUEST['rating'])) 
	{	
		echo $stars_display.$schema.$ratings_detail.$your_rating_display;
		exit;
	}
	else // This document is not being called through ajax.  Output to screen:
	{
		// schema microdata can't be read through javasctipt so we create the div in html not document.write
		$message = "<div id='star_rating_div'>".$stars_display.$schema.$ratings_detail.$your_rating_display."</div>";
		echo $message;
	}


?>
<script type="text/javascript">
var page_id = '<?PHP echo $id;?>';
var req = false;

function change_avg_stars()
{
	/* Version 1.45 - This function gets the width of 5 stars and then
		calculates the width of the avg_stars span inside it (firstChild) */
	var all_el = document.getElementsByTagName('*');

	for (var i = 0; i < all_el.length; i++) {
	    if (all_el[i].className && all_el[i].className.match(/stars_display/i)) {
			var stars_display = all_el[i];
			var full_width = stars_display.clientWidth;
			var avg_stars = stars_display.getAttribute("data-avg-stars");
			var avg_stars_width = (avg_stars / 5) * full_width;
			for (var s = 0; s < stars_display.children.length; s++)
			{
				if (stars_display.children[s].nodeName == "IMG" && !stars_display.children[s].complete)
				{
					setTimeout("change_avg_stars()", 500); // if stars are images and they haven't been loaded yet then call again 
					break;
				}
				else if (stars_display.children[s].nodeName == "SPAN")
					stars_display.children[s].style.width = avg_stars_width + "px";	
			}  
	    }
	}	
}

change_avg_stars();

function change_your_stars(your_stars)
{
	/* Version 1.45 - This function gets the width of 5 stars and then
		calculates the width of your_stars span */
	var full_width = document.getElementById('your_rating_display').clientWidth;
	var your_stars_width = (your_stars / 5) * full_width;
	document.getElementById('your_stars').style.width = your_stars_width + "px";		
}


function ratings_send_ajax(params) 
{
	//loadXMLDoc
	req = false;
    
	// branch for native XMLHttpRequest object
    if(window.XMLHttpRequest && !(window.ActiveXObject)) 
	{
    	try 
		{
			req = new XMLHttpRequest();
        } 
		catch(e) 
		{
			req = false;
        }
    // branch for IE/Windows ActiveX version
    } 
	else if(window.ActiveXObject) 
	{
       	try 
		{
        	req = new ActiveXObject("Msxml2.XMLHTTP");
      	} 
		catch(e) 
		{
        	try 
			{
          		req = new ActiveXObject("Microsoft.XMLHTTP");
        	} 
			catch(e) 
			{
          		req = false;
        	}
		}
    }
	if(req) 
	{
		req.onreadystatechange = ratings_get_ajax;
		req.open("POST", "ratings.php", true);
		//Send the proper header information along with the request
		req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
		req.send(params);
	}
} // end function loadXMLDoc


function ratings_get_ajax() 
{
    // only if req shows "loaded"
    if (req.readyState == 4) 
	{
        // only if "OK"
        if (req.status == 200) 
		{
            if (req.responseText)
			{
				document.getElementById('star_rating_div').innerHTML = req.responseText;
				change_avg_stars(); // Version 1.45		
			}
		} 
		else 
		{
            alert("There was a problem retrieving the XML data:\n" +
                req.status);
        }
    }
} // end function processReqChange()

function send_rating(rating)
{
	params = "id="+page_id+"&rating="+rating;
	ratings_send_ajax(params);

} // end function send_rating(rating)
</script>

One nice thing about having two tables with one table holding the average stars and total votes for each product is that if you have a webpage that displays many products or stores at once you may want to get the rating for each product or store without retrieving the ratings details. This can be done without doing a lot of searching through the mysql database. Here is an example:

<?PHP
/* multiple_ratings.php
	taken from cavesearch/cave-ajax.php
*/
	/* You may have a webpage that displays multiple products at one time.
		On this page you may want to display the star rating average for each product
		but not the star rating details.  Here is a mysql example of getting
		stores within a certain distance and then joining the star_rating_averages for
		each store, by the markers.id being the same as the star_rating_averages page.
	*/
	$rows = array();
	
	// To search by kilometers instead of miles, replace 3959 with 6371. 
	$query = "SELECT markers.*, ( 3959 * acos( cos( radians($lat) ) * cos( radians( lat ) ) * cos( radians( lng ) 
				- radians($lng) ) + sin( radians($lat) ) * sin( radians( lat ) ) ) ) AS distance, 
				star_rating_averages.avg_stars, star_rating_averages.total_votes
				FROM markers
				LEFT JOIN star_rating_averages ON markers.id = star_rating_averages.page
				WHERE archive = 0 AND ";
	if ($w > $e) { // If the bounds include the intl. date line
	    $query .= "(lat BETWEEN $s AND $n) AND ((lng BETWEEN -180 AND $e) OR (lng BETWEEN $w AND 180))";
	} else {
	    $query .= "(lat BETWEEN $s AND $n) AND (lng BETWEEN $w AND $e)";
	}
	$query .= " ORDER BY distance ASC LIMIT 100";
	
	$result = mysql_query($query) or die($query." : ".mysql_error());
	while($r = mysql_fetch_assoc($result)) 
	{
		$r['distance'] = round($r['distance'], 1);
		$r = array_map('utf8_encode', $r);
		$rows['marker'.$r['id']] = $r;
	}
	
	// Echo $rows associate array into javascript obj
	echo '<script type="text/javascript">'.chr(13);
	print "var obj = ".json_encode($rows).';'.chr(13);
	echo '</script>';
	
?>

<script type="text/javascript">
	/* Here is a javascript example of displaying the star_ratings_averages for each store
		once they have been retrieved from mysql and php.
		Taken from cavesearch\ajax.js
	*/
	
	// Star rating variables
	var star_image_width = 1; // 1em; Could change to 16px for image of a star that is 16 pixels wide
	var star_width_type = 'em'; // Change to 'px' for pixels if using an image instead of HTML entity
	var yellow_star = '&#9733;'; // HTML entity of a star
	//var yellow_star = "<img src='yellowstar.gif'>"; // If you want to use an image then uncomment this line
	var grey_star = '&#9733;'; // HTML entity of a star used for blank stars in ratings
	//var grey_star = "<img src='greystar.gif'>"; // If you want to use an image then uncomment this line
	var user_star = '&#9733;'; // HTML entity of a star used for the users personal rating
	//var user_star = "<img src='redstar.gif'>"; // If you want to use an image then uncomment this line
	var blank_star = '&#9734;'; // HTML entity of a star outline used for a users personal rating before rating
	//var blank_star = "<img src='blankstar.gif'>"; // If you want to use an image then uncomment this line
	
	var yellow = "#F99B00"; // Color used for HTML star entity
	var grey = "#999999"; // Color used for remainder of stars not in the rating value
	var red = "#FF5555"; // Color used for the stars of the users personal rating
	

function processMarkers(obj)
{
	for (var key in obj)
	{
		if (obj.hasOwnProperty(key))
		{
			if (key.match(/^marker\d+>/i)) // find 'marker##'
			{
				processMarker(obj[key]);	
			}
		}
	}
}
	
function processMarker(obj)
{	
	// Calculate the width of the yellow stars
	//var avg_stars_width = Math.round((star_image_width * obj['avg_stars'])*10)/10; // Version 1.45 - Not used anymore; Calculating with javascript
	// Put 5 grey stars in relative span
	var stars_display = "<span class='stars_display' data-avg-stars='"+obj['avg_stars']="' style='position: relative; display:inline-block; overflow:hidden; color: "+grey+"; '>"+
						grey_star+grey_star+grey_star+grey_star+grey_star;
	// Overlay 5 yellow stars on top of the grey stars
	stars_display += "<span style='position: absolute; top: 0; left: 0; color: "+yellow+"; overflow:hidden;'>"+
						yellow_star+yellow_star+yellow_star+yellow_star+yellow_star+"</span>";
	// Close relative span
	stars_display += "</span>"+" ("+obj['total_votes']+" ratings)"; 

	document.write(obj['id']+". "+obj['name']+": "+stars_display+"<br>");
}


function change_avg_stars()
{
	/* Version 1.45 - This function gets the width of 5 stars and then
		calculates the width of the avg_stars span inside it (firstChild) */
	var all_el = document.getElementsByTagName('*');

	for (var i = 0; i < all_el.length; i++) {
	    if (all_el[i].className && all_el[i].className.match(/stars_display/i)) {
			var stars_display = all_el[i];
			var full_width = stars_display.clientWidth;
			var avg_stars = stars_display.getAttribute("data-avg-stars");
			var avg_stars_width = (avg_stars / 5) * full_width;
			for (var s = 0; s < stars_display.children.length; s++)
			{
				if (stars_display.children[s].nodeName == "IMG" && !stars_display.children[s].complete)
				{
					setTimeout("change_avg_stars()", 500); // if stars are images and they haven't been loaded yet then call again 
					break;
				}
				else if (stars_display.children[s].nodeName == "SPAN")
					stars_display.children[s].style.width = avg_stars_width + "px";	
			}  
	    }
	}	
}

processMarkers(obj);
change_avg_stars();
</script>

History

June 29, 2016 - Ver 1.45 - Bug fix: The width of the average stars was sometimes being miscalculated and displayed incorrectly because it was relying on font-size which does not go by width but by height. Corrected! You can also use images for the stars instead of HTML entities if desired. Also the script now uses the web page address for the id of the product in the mysql database, so that you no longer have to worry about setting an id for star ratings in each web page.

October 3, 2014 - Ver 1.4a - I don't think we need $_SESSION['refer'] = $_SERVER['REQUEST_URI']; on line 94 and 95 so I removed it. It was for a login script to change location back to referring page. But I think login scripts will have their own method.

April 24, 2014 - Ver 1.4 - Now the script does not require the user to login. However, this is not recommended. Change the $require_login variable at the beginning of the script to $require_login = 0; Then the script will use the user's IP address as a unique identifier (user_id). However, most user's have a dynamic IP address (changing address). So they could come back in a few days and put another star rating as a different user. To try and offset this the script will also set a cookie with the user's IP address. If the cookie is found then even though they have a new IP address the script will treat them as a previous user. (Of course a user could still delete their cookies)

April 9, 2014 - Ver 1.3a - Bug fix for iOS, iPhone, iPad devices. Because iOS tries to implement onmouseover (or hover) events but doesn't do it well, a user had to click on a star twice for it to register the vote. The first click just acted as an onmouseover event and the second click was an onclick event. Now detecting iOS devices on lines 295 to 324 with if (!(\"ontouchstart\" in document.documentElement)) to stop this bug.

July 29, 2013 - ver 1.3 - Bug Fix in star_ratings_averages table. Previously the averages were being stored with only 1 decimal place. Changed to 2 decimals on lines 162 and 175 with mysql ROUND because with only 1 decimal place if a user changes their vote by one star up or down multiple times then the average rating can start to be off. Changed line 203 to grab the avg_stars with 1 decimal place so that it is a pleasing visual for the user.

July 29, 2013 - ver 1.25 - Bug Fix. On line 80 changed $_SESSION['refer'] to only be set if ratings.php is not being called by ajax. This prevents login from changing location to ratings.php

May 17, 2013 - ver 1.2 - Version 1.1 no longer is used. It has been removed. Fixed the yellow stars alignment for all browsers including iPad and iPhone Safari by adding display:inline-block; to line 254 and 294 of ratings.php and to line 84 of multiple_ratings.php.

May 15, 2013 - ver 1.1 - Fixed iPhone bug around line 256 where stars are aligned 3 pixels too low. Fixed with adding top:-3px for iphone only.

March 22, 2013 - Created Complete Star Rating Script

Back to www.seabreezecomputers.com
Subscribe to Internet Tips and Tools Feed        

User Comments

There are 36 comments.

Displaying first 50 comments.

1. Posted By: Vova Feldman - - April 22, 2013, 3:49 am
Great job Jeff! Love the way you managed to squeeze the back-end and client-side in one script. It's great approach you are a web developer and know how to setup a DB etc. But if you are not savvy in computers, I suggest to use the Rating-Widget.com project. It happens to be that I'm the lead developer of the project ;) which is a very user-friendly and full solution for a Rating System (client+backend). Check it here: rating-widget.com
For the ease of use we've developed several plugins/apps for different platforms:
1. WordPress plugin - rating-widget.com/get-the-word-press-plugin/
2. Blogger gadget - rating-widget.com/blog/star-rating-for-blogger/
3. Shopify app - shopify.rating-widget.com/
4. WiX app - www.wix.com/support/main/html5/wix-app-market/all-apps/rating-widget

Keep up with the awesome work!

2. Posted By: Jolle - - April 23, 2013, 7:15 am
I like your blog but the ratings.zip file is missing!

3. Posted By: Jeff - - April 23, 2013, 11:42 am
Hi Jolle,

I apologize for the missing file. Not sure what happened. I re-uploaded the file. Please let me know if it is working for you. I am able to download it myself with IE and Chrome. But for some reason my Firefox keeps saying 404 Not Found.

Jeff
www.seabreezecomputers.com/

4. Posted By: JK - jkhubchandani@gmail.com - May 16, 2013, 3:43 pm
Hi Jeff -

I will appreciate it if you can give us also a sample of the login.php code so that it is easier to integrate into our site.

Thanks a ton.
Jeetu

5. Posted By: DJ3 - - July 24, 2013, 12:44 am
Could be a very nice script but there's a lot missing, for example login.php. registrate.php and so on

I hope the maker will post it some day





6. Posted By: Jeff - - July 29, 2013, 12:17 pm
Hello,

A login script that works with this star ratings script is now available at www.seabreezecomputers.com/login/

Jeff
www.seabreezecomputers.com/

7. Posted By: Matt - - September 19, 2013, 9:33 am
Is there a way to combine written reviews with this as well? So people can do somthing similar to what I am right now (writing a comment) based on their rating?

Thanks

8. Posted By: Jeff - - October 24, 2013, 10:22 am
Hi Matt,

Yes, you can have the comments with the user's star ratings. I just created an example php file for you and attached it. It will have to be changed according to your login and comment system.

Jeff
www.seabreezecomputers.com/

9. Posted By: danish - - November 15, 2013, 9:05 am
Hi. when i click on a star to submit rating then a java script popup open with this message

"There was a problem retrieving the XML data:404".

How to resolve this problem. is something missing?

10. Posted By: Jeff - - November 15, 2013, 10:43 am
Hello Danish,

I have not seen this problem before. It appears there might be a problem with your server. Or maybe you have ratings.php in a subfolder. Make sure it is in the same folder as the HTML or PHP file that is calling it.

Jeff
www.seabreezecomputers.com/

11. Posted By: danish - - November 15, 2013, 11:02 am
thanks 4 your fast reply Jeff. it was sub folder problem. data is submitting now.

12. Posted By: Jeff - - November 15, 2013, 11:16 am
Danish,

You are welcome. Glad it's working!

13. Posted By: shefat robin - - November 29, 2013, 10:01 am
Thanks Vova Feldman for your help ....thats great its up and running .... thanks a lot mate.. :)

14. Posted By: Manjua - manjula.alagiyawanna@gmail.com - December 18, 2013, 7:43 pm
HI

Can you post a database. please..!

15. Posted By: Faizal - - February 26, 2014, 12:12 am
Jeff, you are awesome!!. Thanks for sharing. :)

16. Posted By: ahmed - - March 6, 2014, 1:21 am
getting unknown error
please help

Warning: substr() expects parameter 1 to be string, object given in /home/content/75/11751475/html/bikes/bajaj/200ns/ratings.php on line 72
SELECT stars, sent_date FROM star_rating WHERE page='5' AND user_id='' LIMIT 1 : Unknown column 'user_id' in 'where clause'

17. Posted By: Jeff - - March 9, 2014, 5:09 pm
Hello ahmed,

Do you have a link to the server so I can test it? It appears that it is not finding a 'user_id' column in the star_rating table. Did you try to create the tables yourself in the database? Because ratings.php creates the tables itself.

Jeff
www.seabreezecomputers.com/

18. Posted By: hj - - March 19, 2014, 5:03 am


19. Posted By: VJ - - April 10, 2014, 6:59 am
Thanks for the iOS fix. It works great.

20. Posted By: Belal - - April 21, 2014, 4:30 pm
I need helpm I a m begiiner in PHP, need shopping cart Php Mysql....thanx in advance

21. Posted By: dvorak7 - - April 23, 2014, 2:56 am
Hi Jeff,

First, thanks for this script. Great stuff.
I'd like to allow visitors to rate without having to register or login. Is it possible ?


Thanks a lot !

Chris.

22. Posted By: Jeff - - April 24, 2014, 2:36 pm
Hi Chris,

Good question! The should be a unique identifier for the user so that they don't add a new rating every time they click a star. So I thought of one way to do this with their IP address. So they can click on the stars any number of times and it will be just one users rating. However, most Internet users have a changing IP address every day. So they could come back the next day and it would be like a second person adding a rating. But that is the problem with a rating system without a login.

Change line 72 to this:
$user_id=$_SESSION['user_id']=ip2long(mysql_real_escape_string(strip_tags(substr($_SERVER['REMOTE_ADDR'],0,50))));

You should also change 92 to

user_id INT unsigned,

The first line above changes the user_id to a long integer of their IP Address. The next line changes user_id in the SQL database to unsigned INT, because a regular INT will not be enough room to hold all types of IP addresses.

Jeff


23. Posted By: Ravi - - May 2, 2014, 12:58 am
Hello

24. Posted By: Ian - - August 10, 2014, 5:57 am
Is there a way to change $id to be the page url instead of an id number? As my site where I intend to use the script doesn't them.

25. Posted By: Jeff - - August 11, 2014, 6:49 pm
Hello Ian,

I believe you could use the page url for $id, but you would have to change at least four lines in ratings.php

Change line 50 to:
$id=$_SERVER['REQUEST_URI'];

Change line 102 to:
page VARCHAR(120),

Change line 114 to:
page VARCHAR(120),

Change line 437 to:
params="id="+encodeURIComponent(page_id)+"&rating="+rating;

Jeff
www.seabreezecomputers.com/

26. Posted By: Denis - - August 13, 2014, 8:47 am
Hello. Tell me how to display the form results page if no one voted?

27. Posted By: James - - October 21, 2014, 12:10 pm
Very cool. Is this an open source project that can be used on commercial websites or would I need to obtain a license from you?

28. Posted By: Jeff - - October 24, 2014, 9:55 am
Hello Denis,

The script does not display the ratings details unless there is at least 1 rating.

Jeff
www.seabreezecomputers.com/

29. Posted By: Jeff - - October 24, 2014, 9:56 am
Hi James,

Thank you for inquiring. At the moment you can freely use the script for a personal or commercial project.

Jeff
www.seabreezecomputers.com/

30. Posted By: kaps - - September 30, 2015, 6:45 am
Hi,
Thanks for the script.
I only have to update $ID and $Name with appropriate values, right?

which I did but nothing is appearing on the webpage where I have included ratings.php. Infact tables are also not getting created automatically.(I have correctly set up database credentials), can you please help.
i am trying to set it up e.g. here indiacouponsndeals.com/foodpanda-coupon-codes below existing star ratings which I want to replace with this one as it has rich snippet code

31. Posted By: Jeff - - September 30, 2015, 1:22 pm
Hello kaps,

I looked at the source of your page and it is not processing the line:
<?PHP require_once('ratings.php'); ?>

as PHP. It is just reading it as text. Either your server is not supporting PHP or you are using an .html file and your server is not setup to process PHP in an .html file. You may need to add a .htaccess file with the following line in it:

addhandler application/x-httpd-php .htm .html

Also, if you are not using a login script you may want to change line 18 of the ratings.php to:

$require_login=0;

Jeff
www.seabreezecomputers.com/

32. Posted By: Kaps - - October 1, 2015, 1:02 am
Thanks Jeff for your quick response.

I am using this code in .tpl file which is ultimately processed as .php and not html, extention become file.tpl.php.

Do I need to add addhandler application/x-httpd-php .htm .html in my sites .htaccess (one available in public folder) or servers .htaccess (one available in root folder)

I added it to one available in public folder but it did not work, I tried with addhandler application/x-httpd-php .htm .html .tpl and addhandler application/x-httpd-php .htm .html .tpl.php also.

Thank for your help


33. Posted By: Jeff - - October 1, 2015, 11:17 am
Hi Kaps,

Unfortunately I do not have experience with .tpl files. I did some research and according to this page: forum.whmcs.com/showthread.php?16790-How-to-include-a-php-file-inside-tpl you can do something like this:

{php} require("ratings.php"); {/php}

I have no idea if it will work though.

Jeff
www.seabreezecomputers.com/

34. Posted By: Bek - - April 2, 2016, 5:51 am
Hi, thanks so much for this. very grateful.

I'm getting following errors though;

Notice: Undefined variable: stars_display in...
Notice: Undefined variable: your_rating_display in...

Please help!

Many Thanks in advance

35. Posted By: Jeff - - April 2, 2016, 11:27 am
Hi Bek,

I apologize for this error. The PHP in your server is set to display notices and not just errors. There are three things you can do to try and stop this notice. Just do ONE of these things and then try the next if it doesn't work:

1. Near the top of ratings.php where the variables are being defined. Add these two lines:
$stars_display="";
$your_rating_display="";


2. Near the top of ratings.php add this line:
error_reporting(E_ALL & ~E_NOTICE);

3. On your server there should be a php.ini file. Edit the file and look for a line that starts with error_reporting and change it to:
error_reporting=E_ALL & ~E_NOTICE


Jeff
www.seabreezecomputers.com/


36. Posted By: Bek - - April 3, 2016, 3:42 pm
Thanks Jeff for the quick response. All sorted now.

Thanks mate...much appreciated