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
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 184 | 214 | 204 | 219 | 203 | 139 | 130 | 215 | 173 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
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