PHP visitor tracking script with jQuery and Raphael JavaScript library

This simple PHP visitor tracking script uses jQuery and Raphael javascript library to display daily hits to your page. The class features:

  • IP filter (for IP's you don't want to track)
  • Unique visits counter
  • Raphael analytics (example on the bottom of the page)
  • No search engine robots tracking
  • Total hits counter
  • Visitor IP's info
  • Visitor host info
  • Referring pages info
  • Visited pages info
  • MySQL log table creation

Dependencies

MySQL Table

This table could be expanded as needed. For example, you could add "browser" field to collect visitor browser information.

CREATE TABLE IF NOT EXISTS 'logger' (
			  'log_id' int(11) NOT NULL auto_increment,
			  'ip' varchar(16) NOT NULL default '',
			  'host' varchar(100) NOT NULL default '',
			  'visitdate' datetime NOT NULL default '0000-00-00 00:00:00',
			  'host' varchar(255) NOT NULL default '',
			  'ref_domain' varchar(255) NOT NULL default '',
			  PRIMARY KEY  ('logid')
			) TYPE=MyISAM

PHP Class (class.logger.php)

IMPORTANT: Make sure to fill in the database info. See comments for the details.

class logger {
//database setup 
//MAKE SURE TO FILL IN DATABASE INFO
var $hostname_logon = '';    //Database server LOCATION
var $database_logon = '';    //Database NAME
var $username_logon = '';    //Database USERNAME
var $password_logon = '';    //Database PASSWORD
    
//table fields
var $tablename = '';        //logger table name
var $filter = array();      //array of ip's you don't want tracked

//connect to database
function dbconnect(){
	$connections = mysql_connect($this->hostname_logon, $this->username_logon, $this->password_logon) or die ('Unabale to connect to the database');
	mysql_select_db($this->database_logon) or die ("Error in query: $qry. " . mysql_error());   
}

//log visit
function logvisit($ip, $host, $filename, $ref_domain){
	//check if ip is filtered
	if(!in_array($ip,$this->filter)){
		$qry = "INSERT INTO ".$this->tablename." (ip, host, visitdate, filename, ref_domain)VALUES('".$ip."','".$host."',NOW(),'".$filename."', '".$ref_domain."' )";
		$result = mysql_query($qry) or die ("Error in query: $qry. " . mysql_error());
	}
}

//show total uniqe visitors to date(parameter: (int) number of months). For example: 3 will start counting from 3 monhts before today.
function totalunique($month){		
	$lastmonth = mktime(01, 01, 01, date("m")-$month, date("d"),   date("Y"));
	$from =  date("Y-m-d H:i:s", $lastmonth);
	$to = date("Y-m-d H:i:s");
	$qry = "SELECT DISTINCT ip, visitdate, host FROM ".$this->tablename." WHERE visitdate BETWEEN '".$from."' AND '".$to."' GROUP BY ip";
	$result = mysql_query($qry) or die ("Error in query: $qry. " . mysql_error());
	$totalunique = mysql_num_rows($result);
	return $totalunique;
}

//show total visits to date(parameter: (int) number of months). For example: 3 will start counting from 3 monhts before today.
function totalhits($month){		
	$lastmonth = mktime(01, 01, 01, date("m")-$month, date("d"),   date("Y"));
	$from =  date("Y-m-d H:i:s", $lastmonth);
	$to = date("Y-m-d H:i:s");
	$qry = "SELECT ip, visitdate, host FROM ".$this->tablename." WHERE visitdate BETWEEN '".$from."' AND '".$to."'";
	$result = mysql_query($qry) or die ("Error in query: $qry. " . mysql_error());
	$totalhitsmonth = mysql_num_rows($result);
	return $totalhitsmonth;
}


//show daily visits for a specific month /  year and div in which to laod the graph
function dailyhits($month, $year, $div, $tableid){		
	//current month and year
	if($month == ""){
		$month = date("m");
	}
	if($year == ""){
		$year = date("Y");
	}
	
	//get number of days
	$numdaysinmonth = cal_days_in_month(CAL_GREGORIAN, $month, $year);          
	
	echo '< table cellpadding="0" cellspacing="0" border="0" class="'.$tableid.'" >
	< tfoot >
		';
	  
		for($x=1;$x<=$numdaysinmonth;$x++){
			echo ''.$x.''; 
		}
	   
	echo '
	< /tfoot >
	< tbody >
		'; 
		$totalhits = 0;
		for($y=1;$y<=$numdaysinmonth;$y++){ 
		 if($y < 10){
			$b = '0'.$y;     
		 }else{
			$b = $y;     
		 }
		 $current_month = date('Y-m').'-'.$b;
		 $qry = "SELECT DISTINCT ip FROM ".$this->tablename." WHERE visitdate LIKE '$current_month%' GROUP BY visitdate";
		 $result = mysql_query($qry) or die ("Error in query: $qry. " . mysql_error());
		 $numrows = mysql_num_rows($result);
		 
			echo '';
			echo $numrows;
			$totalhits = $totalhits + $numrows;
			echo '';
		}
			
	 echo '
	< /tbody >
	< /table >
	';
}

//display visit details (parameter (int) number of last visits to display
function visitdetails($num){
	
	$qry = "SELECT * FROM ".$this->tablename." WHERE visitdate!='' ORDER BY log_id DESC LIMIT 0, ".$num."";
	$result = mysql_query($qry) or die ("Error in query: $qry. " . mysql_error());;

	if (mysql_num_rows($result) != 0){
		echo '< table cellpadding="0" cellspacing="0" border="0" class="visitdetails" >';
		echo '< thead>IPHostReferring PagePage Visited< /thead >';
		echo '< tbody>';
		while ($row = mysql_fetch_object($result)){
			$nowww = ereg_replace('www.','',$row->ref_domain);
			$domain = parse_url($nowww);  
			if(!empty($domain["host"]))  {  
				$host = $domain["host"]; 
			} 
		  echo ''.$row->ip.'
		  '.$row->host.'
		  '.$host.'
		  '.$row->filename.'
		  ';
		}
		echo '< /tbody >';
		echo '< /table >';
	}
}

//raphael stuff(depends on including raphael.js library
function loadraphael($tableid, $div, $month_year, $width, $height, $leftgutter, $bottomgutter, $topgutter,$strokecolor, $fontcolor,$fillcolor,$gridcolor){
echo '< script type="text/javascript" >
	//raphael.path.methods.js
	Raphael.el.isAbsolute = true;
	Raphael.el.absolutely = function () {
		this.isAbsolute = 1;
		return this;
	};
	Raphael.el.relatively = function () {
		this.isAbsolute = 0;
		return this;
	};
	Raphael.el.moveTo = function (x, y) {
		this._last = {x: x, y: y};
		return this.attr({path: this.attrs.path + ["m", "M"][+this.isAbsolute] + parseFloat(x) + " " + parseFloat(y)});
	};
	Raphael.el.lineTo = function (x, y) {
		this._last = {x: x, y: y};
		return this.attr({path: this.attrs.path + ["l", "L"][+this.isAbsolute] + parseFloat(x) + " " + parseFloat(y)});
	};
	Raphael.el.arcTo = function (rx, ry, large_arc_flag, sweep_flag, x, y, angle) {
		this._last = {x: x, y: y};
		return this.attr({path: this.attrs.path + ["a", "A"][+this.isAbsolute] + [parseFloat(rx), parseFloat(ry), +angle, large_arc_flag, sweep_flag, parseFloat(x), parseFloat(y)].join(" ")});
	};
	Raphael.el.curveTo = function () {
		var args = Array.prototype.splice.call(arguments, 0, arguments.length),
			d = [0, 0, 0, 0, "s", 0, "c"][args.length] || "";
		this.isAbsolute && (d = d.toUpperCase());
		this._last = {x: args[args.length - 2], y: args[args.length - 1]};
		return this.attr({path: this.attrs.path + d + args});
	};
	Raphael.el.cplineTo = function (x, y, w) {
		this.attr({path: this.attrs.path + ["C", this._last.x + w, this._last.y, x - w, y, x, y]});
		this._last = {x: x, y: y};
		return this;
	};
	Raphael.el.qcurveTo = function () {
		var d = [0, 1, "t", 3, "q"][arguments.length],
			args = Array.prototype.splice.call(arguments, 0, arguments.length);
		if (this.isAbsolute) {
			d = d.toUpperCase();
		}
		this._last = {x: args[args.length - 2], y: args[args.length - 1]};
		return this.attr({path: this.attrs.path + d + args});
	};
	Raphael.el.addRoundedCorner = function (r, dir) {
		var rollback = this.isAbsolute;
		rollback && this.relatively();
		this._last = {x: r * (!!(dir.indexOf("r") + 1) * 2 - 1), y: r * (!!(dir.indexOf("d") + 1) * 2 - 1)};
		this.arcTo(r, r, 0, {"lu": 1, "rd": 1, "ur": 1, "dl": 1}[dir] || 0, this._last.x, this._last.y);
		rollback && this.absolutely();
		return this;
	};
	Raphael.el.andClose = function () {
		return this.attr({path: this.attrs.path + "z"});
	};

	
	//raphael analytics
	Raphael.fn.drawGrid = function (x, y, w, h, wv, hv, color) {
	color = color || "'.$fillcolor.'";
	var path = ["M", x, y, "L", x + w, y, x + w, y + h, x, y + h, x, y],
		rowHeight = h / hv,
		columnWidth = w / wv;
	for (var i = 1; i < hv; i++) {
		path = path.concat(["M", x, y + i * rowHeight, "L", x + w, y + i * rowHeight]);
	}
	for (var i = 1; i < wv; i++) {
		path = path.concat(["M", x + i * columnWidth, y, "L", x + i * columnWidth, y + h]);
	}
	return this.path(path.join(",")).attr({stroke: color});
	};
	$(function () {
		$(".'.$tableid.'").css({
			position: "absolute",
			left: "-9999em",
			top: "-9999em"
		});
	});
	window.onload = function () {
	// Grab the data
	var labels = [],
		data = [];
	$(".'.$tableid.' tfoot th").each(function () {
		labels.push($(this).html());
	});
	$(".'.$tableid.' tbody td").each(function () {
		data.push($(this).html());
	});
	
	// Draw
	var width = '.$width.',
		height = '.$height.',
		leftgutter = '.$leftgutter.',
		bottomgutter = '.$bottomgutter.',
		topgutter = '.$topgutter.',
		colorhue = .6 || Math.random(),
		color = "hsb(" + [colorhue, 1, .75] + ")",
		r = Raphael("'.$div.'", width, height),
		txt = {font: '12px Fontin-Sans, Arial', fill: "'.$fontcolor.'"},
		txt1 = {font: '10px Fontin-Sans, Arial', fill: "'.$fontcolor.'"},
		txt2 = {font: '12px Fontin-Sans, Arial', fill: "'.$fillcolor.'"},
		X = (width - leftgutter) / labels.length,
		max = Math.max.apply(Math, data),
		Y = (height - bottomgutter - topgutter) / max;
	r.drawGrid(leftgutter + X * .5, topgutter, width - leftgutter - X, height - topgutter - bottomgutter, 10, 10, "'.$gridcolor.'");
	var path = r.path().attr({stroke: color, "stroke-width": 4, "stroke-linejoin": "round"}),
		bgp = r.path().attr({stroke: "none", opacity: .3, fill: color}).moveTo(leftgutter + X * .5, height - bottomgutter),
		frame = r.rect(10, 10, 100, 40, 5).attr({fill: "'.$fillcolor.'", stroke: "'.$strokecolor.'", "stroke-width": 2}).hide(),
		label = [],
		is_label_visible = false,
		leave_timer,
		blanket = r.set();
	label[0] = r.text(60, 10, "24 hits").attr(txt).hide();
	label[1] = r.text(60, 40, "22 September 2008").attr(txt1).attr({fill: color}).hide();
	for (var i = 0, ii = labels.length; i < ii; i++) {
		var y = Math.round(height - bottomgutter - Y * data[i]),
			x = Math.round(leftgutter + X * (i + .5)),
			t = r.text(x, height - 6, labels[i]).attr(txt).toBack();
		bgp[i == 0 ? "lineTo" : "cplineTo"](x, y, 10);
		path[i == 0 ? "moveTo" : "cplineTo"](x, y, 10);
		var dot = r.circle(x, y, 5).attr({fill: color, stroke: "'.$fillcolor.'"});
		blanket.push(r.rect(leftgutter + X * i, 0, X, height - bottomgutter).attr({stroke: "none", fill: "'.$fontcolor.'", opacity: 0}));
		var rect = blanket[blanket.length - 1];
		(function (x, y, data, lbl, dot) {
			var timer, i = 0;
			$(rect.node).hover(function () {
				clearTimeout(leave_timer);
				var newcoord = {x: +x + 7.5, y: y - 19};
				if (newcoord.x + 100 > width) {
					newcoord.x -= 114;
				}
				frame.show().animate({x: newcoord.x, y: newcoord.y}, 200 * is_label_visible);
				label[0].attr({text: data + " hit" + ((data % 10 == 1) ? "" : "s")}).show().animateWith(frame, {x: +newcoord.x + 50, y: +newcoord.y + 12}, 200 * is_label_visible);
				label[1].attr({text: lbl + " '.$month_year.'"}).show().animateWith(frame, {x: +newcoord.x + 50, y: +newcoord.y + 27}, 200 * is_label_visible);
				dot.attr("r", 7);
				is_label_visible = true;
			}, function () {
				dot.attr("r", 5);
				leave_timer = setTimeout(function () {
					frame.hide();
					label[0].hide();
					label[1].hide();
					is_label_visible = false;
					// r.safari();
				}, 1);
			});
		})(x, y, data[i], labels[i], dot);
	}
	bgp.lineTo(x, height - bottomgutter).andClose();
	frame.toFront();
	label[0].toFront();
	label[1].toFront();
	blanket.toFront();
	};< /script >';   
	}

	//function to create Logger table
	function cratetable($tablename){
		$qry = "CREATE TABLE IF NOT EXISTS '".$tablename."' (
			  'log_id' int(11) NOT NULL auto_increment,
			  'ip' varchar(16) NOT NULL default '',
			  'host' varchar(100) NOT NULL default '',
			  'visitdate' datetime NOT NULL default '0000-00-00 00:00:00',
			  'host' varchar(255) NOT NULL default '',
			  'ref_domain' varchar(255) NOT NULL default '',
			  PRIMARY KEY  ('logid')
			) TYPE=MyISAM";
		$result = mysql_query($qry) or die(mysql_error());
		return;
	}  
    
}

Usage

Make sure to include and instantiate the class on every page you use it.

include("class.logger.php");
$log = new logger();                  //Instentiate the class
$log->tablename = "logger";       //set logger table name
$log->dbconnect();                    //connect to the database

Create Logger Table

Run this code only once to create the log on table.

include("class.logger.php");                   //path to class.logger.php file
$log = new logger();                            //Instentiate the class
$log->dbconnect();                             //connect to database
//pass the name of the table as the parameter. I.e. 'logger"
$log->cratetable('logger');

Log visits

Create a file logger.php and place it inside a folder "/logger".

include("class.logger.php");                   //path to class.logger.php file
$log = new logger();                            //Instentiate the class
$log->tablename = "logger";                //set logger table name
$log->dbconnect();                             //connect to database
$log->filter = array('00.00.00.000');      //array of IP addresses you don't want to track

//get IP
$ip = $_SERVER['REMOTE_ADDR'];
//get host
$host = gethostbyaddr($_SERVER['REMOTE_ADDR']);
//get fisited page
$filevisited = $_REQUEST['f'];
if($filevisited ==""){
    $filevisited = $_SERVER["PHP_SELF"];
} 
//get referring page
$ref_domain = urldecode($_REQUEST['r']);
if($ref_domain ==""){
    $ref_domain = $_SERVER["HTTP_REFERER"];
}
//log visit
$logger->logvisit($browser, $ip, $host, $filevisited,  $ref_domain);
To ensure bot's are not logged add Disallow directive to the robots.txt.

robots.txt (example)

User-Agent: *
Allow: /
Disallow: /logger/

Next place this code before the end < /body >tag on every page you want to track.

< script type="text/javascript" src="../logger/logger.php?t=< ? php echo time(); ? >&f=< ? php echo $_SERVER['REQUEST_URI']; ? >&r=< ? php echo urlencode($_SERVER["HTTP_REFERER"]); ? >">

Display Daily Hits with jQuery and Raphael.js

First make sure that you are including  jquery.js and raphael.js files.

include(class.logger.php); 
$log = new logger();                  //Instentiate the class
$log->tablename = "logger";       //set logger table name
$log->dbconnect();                     //connect to database

//set date and year text for the info bubble that appears on hover
$date = date('F')." ".date('Y');  
//load raphael analytics javascript  
//Parameters: "table[class], div[id], text[date], width, height, left gutter, bottom gutter, top gutter,stroke color, fontcolor,  fill color,  grid color 
$log->loadraphael('data','holder',$date, 600, 250, 20, 30, 30,'#474747', '#000', '#eee', '#eee');
//load table hits containing data
//Parameters: month, year, div[id], table[class]
$log->dailyhits(11, 2009, 'holder', 'data');

(Optional) Example CSS file to position and size the "holder"

#holder {
    width: 600px;
    height: 450px;
    left: 10px;
    position: absolute;
    top: 10px;
}

Display Hits Summary

//show total uniqe visitors to date(parameter: (int) number of months). For example: 3 will start counting from 3 monhts before today.
echo $log->totalunique(3);
//show total visits to date(parameter: (int) number of months). For example: 3 will start counting from 3 monhts before today.
echo $log->totalhits(3);
//visit details (parameter (int) number of last visits to display. For example :100 will show last 100 visits
//the info is displayed in a table with visitdetails[class] for easy CSS styling.
$log->visitdetails(100);

March 2010

123456789101112131415161718192021222324252627282930
184214204219203139130215173000000000000000000000

Do you like this or find it useful? Drop me a note or treat me to a double-espresso from my favorite coffee shop.

    Comments

    December 13th 2009

    Mike - Give archive to download all code

    apex - yup give the working code, this isn't working. There are too many error messages, so this is unusable.

    Reply

    January 24th 2010

    maxmegalon - Thanks for the tutorial. There is a typo in logger.php, the last line should be $log->logvisit($browser, $ip, $host, $filevisited, $ref_domain); instead of $logger->logvisit($browser, $ip, $host, $filevisited, $ref_domain); Furthermore I was wondering whether it would make the code more readable and structured more logical if you were to include analytics.js etc and adding a start call instead of echoing them.

    Reply

    January 24th 2010

    maxmegalon - There is a typo in the function create table. It contains the field 'host' twice. One of the fields should be 'filename'. I'm not sure whether this could be problematic, but in the function logvisit the key 'log_id' is not in the insert statement.

    Reply

    March 6th 2010

    apex - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''logger' ( 'log_id' int(11) NOT NULL auto_increment, 'ip' varchar(16' at line 1

    Reply

    Add Comment | Contact me