James Caws

Dynamically count exit link click-throughs using JavaScript and PHP

Jump down to:

Background

If you are looking for a way to count how often outbound links located in your content are clicked, it doesn’t take long to find some sort of solution documented on the Internet. Most exit link counters work in one of two ways:

  1. They require you to actually set the link to point to a script on your website that in turn redirects the user to the real location. For example, you would set the link as /link/?id=1 and then your script would look up what real destination ID 1 is, log a click against it and then redirect the user.
  2. A JavaScript onClick event needs to be added that in turn calls a link counter script, perhaps related to Google Analytics. The downside to this is that in many cases such a method results in the logging of a unique URI that you associate with the link, for example an outbound link to www.yahoo.com would be registered such as onClick=”registerClick(‘/outclick/yahoo_com’);”, then in the stats software you look up how many times a hit has been registered against /outclick/yahoo_com to see how popular a link is. Obviously for long URLs this can get messy.

The major downside to both of these methods is that you manually have to adjust all links in your content in order for them to be logged. If you run a blog, a news site or any other type of website that can receive content via users or is continually being updated with new content not necessarily by yourself, it is either nigh on impossible, or would at least be very difficult to instruct all users on how to add link tracking code, or extremely inconvenient for you to have to go through all content and adjust links yourself.

This was the situation I faced with PhDSeek.com, a website of mine that allows educational organisations to advertise their available PhD projects and studentships free of charge. It seems that in more cases than not, universities and institutes like to use their listings as a gateway to their website. Although I can view from my Google Analytics reports popularity of a given listing, it is hard to know whether those reading the listings actually click the link(s) contained within the text to visit the organisation’s website and if they do, if they ever come back to PhDSeek.com given that the listers probably don’t select to open the link in a new window.

With these points in mind I set about creating a simple link tracking set-up, that would dynamically tag all links to external websites listed on a page, then automatically open them in a new window as well as log an outclick count against them when clicked on. The beauty of the script is that it could be customised further so that the outclicks are logged against the listing, which in future means I can add a feature to the listing software that will allow users listing PhD’s to view how well it is performing in terms of generating click throughs, without them having to request access to organisation log files and learn how to process them to find if my site is generating traffic (extremely unlikely to happen given that these users are on the whole very non-technical).

The solution – overview

The solution I created uses JavaScript to find all external links and adjust them accordingly. When an outbound link is clicked, JavaScript dynamically adds a hidden image to the document, the source (or src) of which is a PHP script on the server that returns a 1×1 transparent GIF. When this image is added to the document, it is called with the URL of the link as an argument. Because the request for the image is essentially coming from the user, their IP and user agent can also be registered with the hit.

The PHP back-end script logs the link (unless it has already logged it in the past) and assigns it a unique ID. The link’s ID is then logged in a separate log table against the IP and user agent of the user, as well as the click through date and time. This data can then be used in what ever way you wish to generate your own custom reports. In the case of PhDSeek.com, I have further extended the code to log the ID of the listing a link appears in, assuming the same link can be present in multiple adverts that are not necessarily owned by the same user.

Because this solution primarily uses JavaScript to dynamically tag all external links on a page, it means that the majority of search engine crawlers will not cause this software to kick in and as the href of anchor tags remains as the real destination link, you can be sure your click-thrus will only be logged when a real user follows the link and not search engines. Furthermore, as we’re not freaking with links per se, any penalisation that may occur from a /redirect/123/ style link should not apply. As the user agent and IP address is stored for each hit though, you can easily remove non-human generated hits and adjust the back-end PHP script to ignore future counts should a bot cause counts to be registered.

One downside to the solution and code below though is that it overrides any existing onClick handler attached to an anchor that points to an external site (it doesn’t touch your own internal links with onClick handlers). For my situation this is fine though, in fact preferred, given that I don’t want advertisers having the option to add their own onClick events. If you think this is going to be a problem for you, there is a function you can adjust to utilise addEventListener and attachEvent which preserve existing onClick event handlers and append your own.

I have used PHP’s PEAR DB library in my code as the database abstraction layer. This is purely out of habit. If you don’t use PEAR DB, don’t want to download the library or would simply prefer to use another type of abstraction layer or method for querying your database, it is very straight forward to see where minimal changes are required. I have used MySQL as my database of choice, naturally the choice is yours.

The solution – files

Two files are required – you can view them in full or save them directly:

The PHP script has the MySQL table structure at the top of it as a comment.

Both files are fully commented. It should be very straight forward to modify them for your exact needs if they don’t do quite what you want.

The only configurables you need to worry about are:

In the JS: aIgnoreHosts – an array of hostnames to ignore and NOT track. You’ll probably want to include your site’s main hostname here. sLogScript – the location of the PHP script that does the logging. It can be local or remote. If you don’t want tracked links to open in a new window, or you rather that choice was left to the person putting the links into the content, look for the CT_LOGGER.log_click function and remove the set ‘target’ attribute line.

In the PHP: Your database connection details. Find and replace db_username, db_password, db_hostname and db_name accordingly.

The JavaScript should be included at the end of your pages that you want to monitor the click throughs of. If you have a general ‘footer’ template, this should be easy. Whatever your scenario, ensure the include is just before the </body> tag. e.g.:

<script type="text/javascript" src="/javascript/tracker.js"></script>

</body>

Advisable modifications

It is unlikely your PHP logging script will be discovered easily, but as with all things web – never say never. So it is advisable to implement a precautionary step or two to avoid the script being exploited if it is found. A systematic attack could result in your database filling up with thousands or bogus ‘hits’. Here are a couple of suggested checks you can make that if not satisfied, should result in your script exiting or just returning the 1×1 GIF without any processing/logging being carried out:

  • Check for $_SERVER['HTTP_REFERER'] and if it is not set, or it is not equal to your site’s hostname, assume abuse and bail out. In most legitimate cases this will be set so your results will be quite accurate, even if at the odd time it is not set.
  • Monitor how many times an IP is logging clicks within a given time-frame, more than a handful per minute over the course of a few minutes, you might like to start getting suspicious.
  • Set a cookie when the user arrives at your site and then check this cookie is present before logging any clicks.

If as in my case the same link can appear on multiple pages and you want to be able to see which pages generate a higher outclick rate, modify the JavaScript to pull out a unique ID from the host page URL. Say for instance you have an outclick on http://www.mysite.com/article.php?id=123, get the JS to extract the ’123′ and send it along as an additional parameter to the ‘url’. Update the PHP to grab this ID and then log it in the ct_Log table with the IP and user agent. You’ll then know which article a particular user was viewing when they clicked out.

13 Responses

  1. Pliggs says:

    Is there a way to have it display the count next to the link?

  2. James says:

    The script provided does not provide that option as standard, but how you edit it and use the data that gets stored is up to you.

  3. Sunny says:

    Nice article.

    I have a question.

    I am trying to build something like it. I want to track login users exit links means when user is logged in. I want to track by which link he/she is leaving my website. So, Can I use ur scripts to track adsense clicks without annoying google ??? :)

  4. James says:

    Hi Sunny,

    I’m afraid I cannot give you a definitive answer, after all I don’t know what (if any) negative impacts the script I have provided has on Google Adsense banners. However, what I can tell you is that ad servers such as OpenX modify Adsense links so that the server can track the clicks, so it would appear that it’s not a cardinal sin to implement such a feature.

    An alternative you have is to add the various Adsense server host names to the exclusion list in tracker.js. Now, I can’t provide you with a definitive list of host names, but after reviewing one of the OpenX libraries that is involved in the tracking of Adsense clicks – lib/max/Delivery/google.php – I see they assume the following to be the Adsense servers:

    googlesyndication.com
    ypn-js.overture.com
    googleads.g.doubleclick.net

    So based on that I would modify tracker.js as follows:

    var aIgnoreHosts = new Array (‘googlesyndication.com’,'ypn-js.overture.com’,'googleads.g.doubleclick.net’);

    Hope that helps – if you try it, please leave a message to let us know how you got on.

    Thanks
    James

  5. Patrick says:

    Hi I’m trying to implement a web analytic here. I’m new to java script, and I can manage to keep track of all information I need. I can use your code to track the exit link yet in my java script, I need to have an output or event in order for my php to grab infomation.

    This is what I have
    EXIT_CT.count_click = function() {

    /// assign exit url
    var exit_url = this.href;
    us_exit_url = “” + exit_url;

    var link = us_base_url + us_img + ‘?code=’ + us_code + ‘&rand=’ + us_rand + ‘&title=’ + us_title + ‘&url=’ + us_url + ‘&referrer=’ + us_referrer + ‘&s_width=’ + us_s_width + ‘&s_height=’ + us_s_height + ‘&b_width=’ + us_b_width + ‘&b_height=’ + us_b_height +’&exit_url=’ + us_exit_url;
    var countImage = document.createElement(“IMG”);
    countImage.setAttribute(“src”, link);
    countImage.style.display = ‘none’;
    document.body.appendChild(countImage);

    alert(“count”);

    return true;
    }

    Without using the alert(“count”) or anything, the php can not track the img file url. Any idea why?
    I had document.writeln(….) before, and it works, yet I can’t return to the previous page unless I use your way ( body.appendChild) Thanks

  6. Patrick says:

    oh!!

    Never mind. Sorry for my poor programing skills and English. I just realized you use the new blank window to trigger the log.

    I wonder if there’s anyway I can log the link while not opening a new window and still let the redirection work. Thanks

  7. James says:

    Hi

    The log is made by adding an invisible image to the HTML via JS which is generated by a PHP script that does the logging. The link is opened in a new (_blank) window just so visitors aren’t redirected away from my site. If you don’t want links to open in a new window, look through the JavaScript for the function called CT_LOGGER.log_click and remove the following three lines (or comment them out):

    if (typeof this.target == ‘undefined’ || this.target != ‘_blank’) {
    this.setAttribute(‘target’, ‘_blank’);
    }

  8. Patrick says:

    Hi James

    Thanks for replying. I did try to remove those three lines which sets to start with a blank page. Yet my code wouldn’t work.

    This is my code which is implemented based on yours.
    EXIT_CT.count_click = function() {

    /// assign exit url
    var exit_url = this.href;
    us_exit_url = “” + exit_url;

    var link = us_base_url + us_img + ‘?code=’ + us_code + ‘&rand=’ + us_rand + ‘&title=’ + us_title + ‘&url=’ + us_url + ‘&referrer=’ + us_referrer + ‘&s_width=’ + us_s_width + ‘&s_height=’ + us_s_height + ‘&b_width=’ + us_b_width + ‘&b_height=’ + us_b_height +’&exit_url=’ + us_exit_url;

    var countImage = document.createElement(“IMG”);
    countImage.setAttribute(“src”, link);
    countImage.style.display = ‘none’;
    document.body.appendChild(countImage);

    (need actions here to work).

    return true;
    }

    I’m using this method to implement a web analytics ( without using Google, or other 3rd party programs).

    I’m using a none displayed image to sending values to the server (php script). However, it will not log values, unless I add a line to execute something after I do appendChild and before returning to true.

    Any idea why?

  9. James says:

    Hi,

    Unfortunately I don’t have the time to go through your code and try to figure out what is wrong. It is strange that you need to add in a line after appendChild for this to work. All I can suggest is you use my JS function first and if that works without a problem (as it should do) then slowly and incrementally make changes to it until it does what you need it to do.

    Thanks
    James

  10. Patrick says:

    HI James,

    I figured it out. The onclick wouldn’t work for my case. The onmousedown will do. Thanks anyways.
    Its a great write up of code. Peace

  11. Ed says:

    Hi James,

    Thanks for the script. It works greater. In my situation I actually do want to track some of the local clicks and none of the external clicks. I added this array:

    var aIgnoreURLs = new Array (‘http://mywebsite.com/mypage.php’,'http://mywebsite.com/mypage,.php’,….);

    and the change the function slightly to:

    CT_LOGGER.logableLink = function(sLink) {
    var aLink = sLink.parse_url();

    if (typeof aLink['host'] == ‘undefined’ || aLink['host'] == ”) {
    return false;
    }

    if (in_array(aLink['host'], aIgnoreHosts)) {
    return false;
    }

    ///////////////////////////////////////////////////////////////////
    // Added to ignore specific links on an allowed host.
    if (in_array(sLink, aIgnoreURLs)) {
    return false;
    }
    ///////////////////////////////////////////////////////////////////

    return true;
    }

    I was looking for a way to log clicks to my download links, but not any of the menu items or other links. This also works well because the other links or more or less static, but the download links are dynamically generated as new versions become available.

    Not being familiar with how to install Pear, I wound up converting the php module over to use the standard php calls to the mysql. It would up not adding very much at all to that script while eliminating the need for a whole bunch of Pear files, thus making it stand-alone.

    Best regards,
    Ed

  12. Starfish says:

    I get the following error:

    Fatal error: Call to a member function output() on a non-object in /home/newsoxy/public_html/tracker/index.php on line 116

    Line 116 says: $debugObj->output(“local_getLinkID($db, $debugObj, $sHash) : $sql”);

    Any clue on why this happens?

  13. James says:

    That was happening because there was no such object in existence called $debugObj. I had accidentally left this reference in, I have now since removed it (please re-download the PHP file). I am surprised no one has let me know about this in the past! I guess most have just removed it.

    Thanks.

Leave a Reply

  1. Published Image: Walks in Nature – Melbourne

    My photograph of Sugarloaf Reservoir near Melbourne has been reproduced in a pack of walking cards designed for Melbourne and the surrounding areas.

  2. My Top 5 iPad Apps

    The iPad is probably the greatest device I never knew I needed until after I purchased one and started using it. Just for the convenience of email, web browsing and calendar management it’s superb, but once you start adding on various apps it becomes awesome. It certainly can’t replace a PC, Mac or notebook for… Continue Reading

  3. Assiette Degustation Spring 2010

    Last night we enjoyed Assiette restaurant’s degustation in Surry Hills, Sydney. Wines were well matched to nine of the ten courses including a delicious sake to partner our amuse bouche. Staff were friendly and knowledgeable and the atmosphere upbeat and unpretentious. Our menu for the evening: Seasonal oyster with Vietnamese dressing and baby coriander – Kidiozumi… Continue Reading

More Notes...