10 useful PHP PEAR packages


PEAR MDB2 is a merge of the PEAR DB and Metabase php database abstraction layers.

It provides a common API for all supported RDBMS. The main difference to most
other DB abstraction packages is that MDB2 goes much further to ensure
portability. MDB2 provides most of its many features optionally that
can be used to construct portable SQL statements:

  • Object-Oriented API
  • A DSN (data source name) or array format for specifying database servers
  • Datatype abstraction and on demand datatype conversion
  • Various optional fetch modes to fix portability issues
  • Portable error codes
  • Sequential and non sequential row fetching as well as bulk fetching
  • Ability to make buffered and unbuffered queries
  • Ordered array and associative array for the fetched rows
  • Prepare/execute (bind) named and unnamed placeholder emulation
  • Sequence/autoincrement emulation
  • Replace emulation
  • Limited sub select emulation
  • Row limit emulation
  • Transactions/savepoint support
  • Large Object support
  • Index/Unique Key/Primary Key support
  • Pattern matching abstraction
  • Module framework to load advanced functionality on demand
  • Ability to read the information schema
  • RDBMS management methods (creating, dropping, altering)
  • Reverse engineering schemas from an existing database
  • SQL function call abstraction
  • Full integration into the PEAR Framework
  • PHPDoc API documentation

MDB2 – InstallationWiki
How to use PHP and PEAR MDB2


Implementation of CAPTCHAs (Completely Automated Public Turing tests to tell Computers and Humans Apart).
Using PEAR’s Text_CAPTCHA to Secure Web Forms


The Log package provides an abstracted logging framework. It includes output handlers for log files, databases, syslog, email, Firebug, and the console. It also provides composite and subject-observer logging mechanisms.
The Log Package
Debugging PHP With Firebug
Advanced Logging in PHP with PEAR


LiveUser is a set of classes for dealing with user authentication
and permission management. Basically, there are three main elements that make up this package:

  • The LiveUser class
  • The Auth containers
  • The Perm containers

The LiveUser class takes care of the login process and can be configured to use a certain permission container and one or more different auth containers.
That means, you can have your users’ data scattered amongst many data containers and have the LiveUser class try each defined container until the user is found.
For example, you can have all website users who can apply for a new account online on the webserver’s local database. Also, you want to enable all your company’s employees to login to the site without the need to create new accounts for all of them. To achieve that, a second container can be defined to be used by the LiveUser class.

You can also define a permission container of your choice that will manage the rights for each user. Depending on the container, you can implement any kind of permission schemes for your application while having one consistent API.
Using different permission and auth containers, it’s easily possible to integrate newly written applications with older ones that have their own ways of storing permissions and user data. Just make a new container type and you’re ready to go!

PEAR::LiveUser Wiki
Getting Started with LiveUser Permissions
PHP Authentication and Access Control Libraries


This class provides an easy way to retrieve all the strings for a multilingual site from a data source (i.e. db).
The following containers are provided, more will follow:

  • PEAR::DB
  • PEAR::MDB2
  • gettext
  • XML
  • PEAR::DB_DataObject (experimental)

It is designed to reduce the number of queries to the db, caching the results when possible.
An Admin class is provided to easily manage translations (add/remove a language, add/remove a string).
Currently, the following decorators are provided:

  • CacheLiteFunction (for file-based caching)
  • CacheMemory (for memory-based caching)
  • DefaultText (to replace empty strings with their keys)
  • ErrorText (to replace empty strings with a custom error text)
  • Iconv (to switch from/to different encodings)
  • Lang (resort to fallback languages for empty strings)
  • SpecialChars (replace html entities with their hex codes)
  • UTF-8 (to convert UTF-8 strings to ISO-8859-1)

PEAR::Translation2 tutorials


Package to validate various data. It includes :

  • numbers (min/max, decimal or not)
  • email (syntax, domain check, rfc822)
  • string (predefined type alpha upper and/or lowercase, numeric,…)
  • date (min, max, rfc822 compliant)
  • uri (RFC2396)
  • possibility valid multiple data with a single method call (::multiple)

An introduction to PEAR’s Validate package


Spreadsheet_Excel_Writer was born as a porting of the Spreadsheet::WriteExcel Perl module to PHP.
It allows writing of Excel spreadsheets without the need for COM objects.
It supports formulas, images (BMP) and all kinds of formatting for text and cells.
It currently supports the BIFF5 format (Excel 5.0), so functionality appeared in the latest Excel versions is not yet available.
Generating Spreadsheets with PHP and PEAR
What is Spreadsheet_Excel_Writer?


A library that uses Maxmind’s GeoIP databases to accurately determine geographic location of an IP address.
Tutorial from HTML Blog


This library makes it very easy to use, writing simple code, yet the library is very powerful.
It lets you easily read or generate tar, gz, tgz, bz2, tbz, zip, ar (or deb) archives to files, memory, mail or standard output.
File_Archive tutorial
SitePoint » File_Archive


XML_Serializer serializes complex data structures like arrays or object as XML documents.
This class helps you generating any XML document you require without the need for DOM.
Furthermore this package can be used as a replacement to serialize() and unserialize() as it comes with a matching

XML_Unserializer that is able to create PHP data structures (like arrays and objects) from XML documents, if type hints are available.
If you use the XML_Unserializer on standard XML files, it will try to guess how it has to be unserialized. In most cases it does exactly what you expect it to do.

Dev Shed XML_Serializer
Instant XML with PHP and PEAR::XML_Serializer

Bubble menu javascript or playing with YUI’s event delegation

Image courtesy of jobee59’s photostream

Event delegation refers to the use of a single event listener on a parent object to listen for events happening on its children (or deeper descendants). Event delegation allows developers to be sparse in their application of event listeners while still reacting to events as they happen on highly specific targets. This proves to be a key strategy for maintaining high performance in event-rich web projects, where the creation of hundreds of event listeners can quickly degrade performance. More on event delegation including examples.

This post illustrates the use of event delegation to create a “bubble” menu, inspired by Bedrich Rios’ post on Nettuts.


We’ll need the following components from the YUI library:

  • yahoo-dom-event – for DOM/Event handling
  • animation – for sliding effects

So our basic HTML page is like this:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
		<!-- Combo-handled YUI JS files: -->
		<script type="text/javascript" src="http://yui.yahooapis.com/combo?2.6.0/build/yahoo-dom-event/yahoo-dom-event.js&2.6.0/build/animation/animation-min.js"></script>


Next we’ll add some structural markup, a simple list, with “menu” as class name and with 5 items.

<ul class="menu">
	<li>Menu item 1</li>
	<li>Menu item 2</li>
	<li>Menu item 3</li>
	<li>Menu item 4</li>
	<li>Menu item 5</li>

Updated HTML page:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
		<!-- Combo-handled YUI JS files: -->
		<script type="text/javascript" src="http://yui.yahooapis.com/combo?2.6.0/build/yahoo-dom-event/yahoo-dom-event.js&2.6.0/build/animation/animation-min.js"></script>
		<ul class="menu">
			<li>Menu item 1</li>
			<li>Menu item 2</li>
			<li>Menu item 3</li>
			<li>Menu item 4</li>
			<li>Menu item 5</li>

The Javascript

Then we just have to add the bubble.js javascript file to our page and it’s done, we’ll have a nice “bubble” menu.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
		<!-- Combo-handled YUI JS files: -->
		<script type="text/javascript" src="http://yui.yahooapis.com/combo?2.6.0/build/yahoo-dom-event/yahoo-dom-event.js&2.6.0/build/animation/animation-min.js"></script>
		<!-- our bubble file -->
		<script type="text/javascript" src="bubble.js"></script>
		<ul class="menu">
			<li>Menu item 1</li>
			<li>Menu item 2</li>
			<li>Menu item 3</li>
			<li>Menu item 4</li>
			<li>Menu item 5</li>


*	Bubble Menu
*	@author Asvin Balloo (https://htmlblog.net)
bubbleMenu = {
	// private variables
	// class name of our menu
	menuClassName: null,
	// by how much we are sliding
	moveToLength : 10,
	*	Init the whole process here
	*	@method init
	*	@params menuClassName {string} class name of our menus
	init: function(menuClassName){
		// set our class name to be used later one
		bubbleMenu.menuClassName = menuClassName;
		// get all the lists with the class name
		var list = YAHOO.util.Dom.getElementsByClassName(menuClassName);
		// loop through them
		for(var i=0; i<list.length; i++) {
			// add the onmouseover event to it, call the mouseOverHandler function
			YAHOO.util.Event.on(list[i], "mouseover", bubbleMenu.mouseOverHandler);
			// get the first child and its padding left for future reference. Useful when using onmouseout to 

restore the item to original state
			var listItems = list[i].getElementsByTagName('li');
			var startingPadding = parseInt(YAHOO.util.Dom.getStyle(listItems[0], 'paddingLeft'));
			// add the onmouseout event, call the mouseOutHandler function
			YAHOO.util.Event.on(list[i], "mouseout", bubbleMenu.mouseOutHandler, {'list':list[i], "padding" : 

	*	Mouse over handler
	*	@params e {Event} the event
	mouseOverHandler: function(e){
		// get the target
		var elTarget = YAHOO.util.Event.getTarget(e);
		// calculate the current padding left
		var paddingLeft = parseInt(YAHOO.util.Dom.getStyle(elTarget, 'paddingLeft'));
		// call the delegate method, with the target and the padding left value
		bubbleMenu.delegate(elTarget, paddingLeft);
	*	Mouse out handler
	*	@params e {Event} the event
	*	@params o {JSON} JSON data containing the list + padding value, eg. {'list': mylist, 'padding': 10} 
	mouseOutHandler: function(e, o){
		// get the target
		var elTarget = YAHOO.util.Event.getTarget(e);
		// calculate the left padding, the original padding value - by how much we have padded
		var paddingLeft = o.padding - bubbleMenu.moveToLength;
		// call the delegate method with the target and padding value
		bubbleMenu.delegate(elTarget, paddingLeft);
	*	Cool things go here, like animation
	*	@params elTarget {Target} our target
	*	@params padding {Int} by how much we are going to padd
	delegate: function(elTarget, padding){
		//walk up the DOM tree looking for an <li> in the target's ancestry; desist when you reach the container with our class name
		while (elTarget.className != bubbleMenu.menuClassName) {
			// are you an LI?
			if(elTarget.nodeName.toUpperCase() == "LI") {
				// if so then animate, set the attributes, in our case the left padding
				var attributes = { paddingLeft: {"to": [padding + bubbleMenu.moveToLength] } };
				// create our animation
				var anim = new YAHOO.util.Motion(elTarget, attributes, 0.6, YAHOO.util.Easing.bounceOut);
				// then animate
			else {
				// it's not an li, so we keep looking and looking
	            elTarget = elTarget.parentNode; 

// init the whole thing here
YAHOO.util.Event.on(window, "load", function(){

You can have unlimited (don’t abuse) number of bubble menus by just adding the “menu” class to each one of them and including the bubble.js file.
If you’re not happy with the menu class or you found another cool class name, then you’ll have to update the bubble.js file towards the end:

// init the whole thing here
YAHOO.util.Event.on(window, "load", function(){

Also you can modify by how much an item slides by modifying the moveToLength variable (default 10):

// by how much we are sliding
moveToLength : 30,

Note that the demo is using reset-fonts-grids CSS and the page has been styled with the following:

body {
	margin: 0;
	padding: 0;
	background: #1d1d1d;
	font-family: "Lucida Grande", Verdana, sans-serif;
	font-size: 100%;
.menu li{
	padding-left: 20px;
	line-height: 2em;
	width: 150px;
	color: #999;

	background: #222;
	border: 1px solid #1a1a1a;
	-moz-border-radius: 10px;

AJAX image cropper with YUI and PHP

This post will show you how to build an AJAX crop image tool using the image cropper control from YUI library and PHP.

The ImageCropper Control from the YUI library gives you an interactive interface for getting the dimensions to crop an image and using these dimensions in PHP, we can do some cropping.

The script we are going to build will

  • allow users to upload an image via AJAX
  • then allow them to select an area for cropping
  • lastly, provide a download link for the cropped image.

There are 3 files we are going to use

  • index.php – will contain the form for image upload as well as the cropping interface
  • upload.php – provides uploading functionality
  • crop.php – provides cropping functionality

From a technical point of view, the flow will be like this :

  1. user uploads jpg image (index.php)
  2. index.php then posts the image asynchronously to upload.php which will do the upload on the server, returning JSON data containing the image file name, its width and its height.
  3. with the JSON data and innerHTML we put the image in our page
  4. initialize the javascript cropping tool
  5. generate a download link (crop.php)

Let’s have a look at index.php

The index.php is our main file where users will be able upload images and then download the cropped ones.

We’ll need the following components from the YUI library :

  • yahoo-dom-event.js – for DOM manipulation and Event handling
  • dragdrop – dependency for the image cropper control
  • element – dependency for the image cropper control
  • resize – dependency for the image cropper control
  • connection – for AJAX requests, in our case for image uploads via AJAX
  • json – for parsing JSON data
  • imagecropper – our most important control

Of course we’ll use Yahoo combo handling and add the js to our page along with the CSS needed for the above controls :

<link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.5.2/build/resize/assets/skins/sam/resize.css" />
<link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.5.2/build/imagecropper/assets/skins/sam/imagecropper.css" />
<!-- js -->
<script type="text/javascript" src="http://yui.yahooapis.com/combo?2.5.2/build/yahoo-dom-event/yahoo-dom-event.js&2.5.2/build/dragdrop/dragdrop-min.js&2.5.2/build/element/element-beta-min.js&2.5.2/build/resize/resize-beta-min.js&2.5.2/build/imagecropper/imagecropper-beta-min.js&2.

Next users must be able to upload images via AJAX, so we add a form to our page:

<form action="upload.php" enctype="multipart/form-data" method="post" name="uploadForm" id="uploadForm">
	Image : <input type="file" name="uploadImage" id="uploadImage" />
	<input type="button" id="uploadButton" value="Upload"/>

We have an onclick event to the upload button to fire the uploading process.

// add listeners
YAHOO.util.Event.on('uploadButton', 'click', uploader.carry);

We’ll also need 2 containers :

  • imageContainer – will contain our uploaded image
  • downloadLink – will contain the download link
<div id="imageContainer"></div>
<div id="downloadLink"></div>

Both containers will be updated via innerHTML afterwards.

AJAX upload

For the AJAX upload, Code Central

has an excellent tutorial which I highly recommend. I took the code sample and modified it a bit to fit my needs. Finally I came up with a nice JSON object called uploader which has one method, carry. The latter just posts form data to a specified URL.

uploader = {
	carry: function(){
		// set form
		YAHOO.util.Connect.setForm('uploadForm', true);
		// upload image
		YAHOO.util.Connect.asyncRequest('POST', 'upload.php', {
			upload: function(o){
				// parse our json data
				var jsonData = YAHOO.lang.JSON.parse(o.responseText);
				// put image in our image container
				YAHOO.util.Dom.get('imageContainer').innerHTML = '<img id="yuiImg" src="' + jsonData.image + '" width="' + jsonData.width + '" height="' + jsonData.height + '" alt="" />';
				// init our photoshop
				// get first cropped image

When upload is complete, we get the JSON data we mentioned earlier on. For e.g :

{"image" : "images/myimage.jpg", "width" : "500", "height" : 400}

With this data and using innerHTML we update our imageContainer div to put our image which will have yuiImg as id :

YAHOO.util.Dom.get('imageContainer').innerHTML = '<img id="yuiImg" src="' + jsonData.image + '" width="' + jsonData.width + '" height="' + jsonData.height + '" alt="" />';

It’s very important to specify the image width and height else the image cropper won’t work.
Next we initialize another JSON object, photoshop which we’ll have a look now.

Our photoshop object

photoshop = {
	image: null,
	crop: null,
	init: function(image){
		// set our image
		photoshop.image = image;
		// our image cropper from the uploaded image					
		photoshop.crop = new YAHOO.widget.ImageCropper('yuiImg');
		photoshop.crop.on('moveEvent', function() {
			// get updated coordinates
	getCroppedImage: function(){
		var coordinates = photoshop.getCoordinates();
		var url = 'crop.php?image=' + photoshop.image + '&cropStartX=' + coordinates.left +'&cropStartY=' + coordinates.top +'&cropWidth=' + coordinates.width +'&cropHeight=' + coordinates.height;
		YAHOO.util.Dom.get('downloadLink').innerHTML = '<a href="' + url + '">download cropped image</a>';		


	getCoordinates: function(){
		return photoshop.crop.getCropCoords();

The init function iniatializes the YUI image cropper from the uploaded image which has yuiImg as id.

photoshop.crop = new YAHOO.widget.ImageCropper('yuiImg');

We also subscribe to the moveEvent for the cropper since we’ll need to update the download link for the cropped image. So whenever the image cropper is moved/resized, we call the getCroppedImage function.

photoshop.crop.on('moveEvent', function() {
	// get updated coordinates

The getCroppedImage function will generate the download link for the cropped image. To do image cropping in PHP we’ll need

  • the image we want to crop
  • the X,Y coordinates
  • height and width of the to be cropped area

Fortunately the YUI cropper utility has a function which will give us what we want, it’s the getCropCoords() method. So, whenever the getCroppedImage function is called, we get the coordinates of the cropped area, build a URL and finally put the download link in our downloadLink container.

// get coordinates
var coordinates = photoshop.getCoordinates();

// build our url
var url = 'crop.php?image=' + photoshop.image + '&cropStartX=' + coordinates.left +'&cropStartY=' + coordinates.top +'&cropWidth=' + coordinates.width +'&cropHeight=' + coordinates.height;

// put download link in our page
YAHOO.util.Dom.get('downloadLink').innerHTML = '<a href="' + url + '">download cropped image</a>';

This is all we need for the index page.


if(!empty($_FILES["uploadImage"])) {
  	// get file name
	$filename = basename($_FILES['uploadImage']['name']);
	// get extension
  	$ext = substr($filename, strrpos($filename, '.') + 1);
  	// check for jpg only
  	if ($ext == "jpg") {
      		// generate unique file name
  		$newName = 'images/'.time().'.'.$ext;
  		// upload files
        	if ((move_uploaded_file($_FILES['uploadImage']['tmp_name'], $newName))) {
        		// get height and width for image uploaded
        		list($width, $height) = getimagesize($newName);
        		// return json data
           		echo '{"image" : "'.$newName.'", "height" : "'.$height.'", "width" : "'.$width.'" }';
        	else {
           		echo '{"error" : "An error occurred while moving the files"}';
  	else {
     		echo '{"error" : "Invalid image format"}';

The upload.php file too is self explanatory, we check for a jpg image only, then generate an unique filename, put it in the images folder and finally build json data which we’ll use for DOM manipulation. Of course the images folder must be writable by the web server.


// get variables
$imgfile = $_GET['image'];
$cropStartX = $_GET['cropStartX'];
$cropStartY = $_GET['cropStartY'];
$cropW = $_GET['cropWidth'];
$cropH = $_GET['cropHeight'];

// Create two images
$origimg = imagecreatefromjpeg($imgfile);
$cropimg = imagecreatetruecolor($cropW,$cropH);

// Get the original size
list($width, $height) = getimagesize($imgfile);

// Crop
imagecopyresized($cropimg, $origimg, 0, 0, $cropStartX, $cropStartY, $width, $height, $width, $height);

// force download nes image
header("Content-type: image/jpeg");
header('Content-Disposition: attachment; filename="'.$imgfile.'"');

// destroy the images

Crop.php allows us to crop our uploaded image. First we get all the variables passed to us via the AJAX request,

// get variables
$imgfile = $_GET['image'];
$cropStartX = $_GET['cropStartX'];
$cropStartY = $_GET['cropStartY'];
$cropW = $_GET['cropWidth'];
$cropH = $_GET['cropHeight'];

Then we create 2 images, the original one and the cropped one, and use the imagecopyresized function to generate the cropped image. We add some header information to tell the world it’s an image and prompt for a save dialog.

Expanding/collapsing javascript menu

In this post I’ll show you how to create an expanding/collapsing menu like Slashdot left menu used to be, using the YUI library and Dav GlassYUI effects widget.

The HTML markup for our menu is :

<div id="menu">
			<h1 id="menu-title">Menu</h1>
			<div id="menu-links">

We use 2 div tags, one for holding the menu itself and the other one which contains the links in an unordered list and 1 h1 tag for the title.

The CSS for the menu :

/** width of the menu */
				width: 180px;

/** the main title */
			#menu h1{
				line-height: 35px;
				color: #fff;
				/** simulate link */
				cursor: pointer;
				font-weight: bold;
				/** background image will change when menu expands/collapses */
				background: #666 url(images/block-arrow-expanded.gif) no-repeat scroll 30px 13px;
/** the links */
			#menu-links li{
				line-height: 35px;
				border-bottom:1px solid #ddd;
				background-color: #eee;

Please pay attention to the background for the #menu h1. It contains the image, which will change when the menu collapses or expands. When the menu collapses we’ll be using the block-arrow-collapsed.gif image and when the menu expands we’ll be using the block-arrow-expanded.gif image. You can find the images in the sample demo file in the images directory.

To do the collapsing/expanding effects, we need to include the following javascript files beforehand :

<!-- js -->
<script type="text/javascript"  src="http://yui.yahooapis.com/combo?2.5.2/build/yahoo-dom-event/yahoo-dom-event.js&2.5.2/build/animation/animation-min.js"></script> 
<script type="text/javascript" src="js/tools-min.js"></script>
<script type="text/javascript" src="js/effects-min.js"></script>

We use Dav Glass YAHOO.Tools package and his effects widget along with yahoo-dom-event-animation aggregate file which are hosted on Yahoo servers.

Finally the javascript to do the magical stuff goes like that :

<script type="text/javascript">
			YAHOO.util.Event.addListener('menu-title', 'click', function(){
				if(YAHOO.util.Dom.getStyle('menu-links', 'display') == 'block'){
					new YAHOO.widget.Effects.BlindUp('menu-links', {seconds: 0.2});
					YAHOO.util.Dom.setStyle('menu-title', 'background-image', 'url(images/block-arrow-collapsed.gif)');
					new YAHOO.widget.Effects.BlindDown('menu-links', {seconds: 0.2});
					YAHOO.util.Dom.setStyle('menu-title', 'background-image', 'url(images/block-arrow-expanded.gif)');

In line 3 of the above listing, we wait when the user clicks on the menu-title container and we check the display attribute of our links. If the links are not currently displayed then we display them by using Dav’s BlindDown effect with a duration of 0.2 seconds ;-).

new YAHOO.widget.Effects.BlindDown('menu-links', {seconds: 0.2});

We also change the background image for the menu title by using the YAHOO.util.Dom.setStyle method:

YAHOO.util.Dom.setStyle('menu-title', 'background-image', 'url(images/block-arrow-expanded.gif)');

If the links are visible then we do the contrary, i.e we use the BlindUp effect and use the collapsed gif as background image.

Geolocate your visitors with PHP (part 1)

In this short post, I’ll show you how to geolocate your visitors with PHP. First of all let’s have a look at what you’ll need :

  • PEAR GeoIP
  • MaxMind GeoLite Country

Installing PEAR GeoIP

Using your console or using the command prompt type :

pear install http://download.pear.php.net/package/Net_GeoIP-1.0.0RC1.tgz

Obtaining MaxMind GeoLite Country

The database can be downloaded freely here. Download it and put it in the same folder where your script will reside.

The code


	$geoip = Net_GeoIP::getInstance('./GeoIP.dat');
	$ip = getenv('REMOTE_ADDR');
	$country = $geoip->lookupCountryName($ip);

	echo "Hi, you're from ".$country;


Now let’s go through the lines of code. The first line tells us to include the GeoIP class, which we installed previously and then create a geoip object in line 5 from the database we downloaded.
Next, in line 6 we get the IP address of the visitor and finally we call the lookupCountryName method with the IP address as parameter in line 7 and it will return us the country.

The last line just outputs the country, for example, “Hi, you’re from Mauritius”. That’s it, now you know where your visitor’s from.

In the second part of this article I’ll show you how to use the script to visually map your visitors using Google Maps. You can have a little preview of the script here.