Well, here's a little something I created on my blog that not only keeps a running record of these totals but also allows you to display the results in a couple of ways.
You can check out my blog yourself to see a working example. On the left, I have a short listing under the "Most visited" heading, followed by a button which allows you to see a complete listing. In addition to this, each individual archived entry ends with the status line, for example: "239 visitors (most popular). "
In order to understand my methods you will have to have a minimum knowledge of Perl (sorry) and your hosting plan will have to include SSI (server side includes).
My method includes four modules which all need to be placed in the same directory (pageviews). For starters, here is the common module which is the only one you have to edit:
CODE
# Name:
# pv_common.pm
#
# Description:
# Common module for pageview stuff.
#
package pv_common;
use strict;
use vars qw(@ISA @EXPORT @EXPORT_OK $VERSION);
use Exporter;
$VERSION = 1.00;
@ISA = qw(Exporter);
@EXPORT = qw(MT_DIR IGNORE_ME FNAME_DB);
@EXPORT_OK = @EXPORT;
#
# Please edit the following lines.
#
use constant MT_DIR => "../../cgi-bin/mt/";
use constant IGNORE_ME => "62.131.63.114";
use constant FNAME_DB => "pageviews.db";
1;
# pv_common.pm
#
# Description:
# Common module for pageview stuff.
#
package pv_common;
use strict;
use vars qw(@ISA @EXPORT @EXPORT_OK $VERSION);
use Exporter;
$VERSION = 1.00;
@ISA = qw(Exporter);
@EXPORT = qw(MT_DIR IGNORE_ME FNAME_DB);
@EXPORT_OK = @EXPORT;
#
# Please edit the following lines.
#
use constant MT_DIR => "../../cgi-bin/mt/";
use constant IGNORE_ME => "62.131.63.114";
use constant FNAME_DB => "pageviews.db";
1;
You only need to modify the first two constants, where MT_DIR is the relative path to the main cgi-bin MT-directory, and IGNORE_ME is an optional client IP-address to be ignored in the stats. For example, you might want to filter out your own visits for more accurate results. Disable this feature by changing it to something meaningless, like "---". You can probably just leave FNAME_DB alone, unless it conflicts with another file name.
The next script takes an entry id as input and records it to the database.
CODE
#!/usr/bin/perl -w
#
# Name:
# pv_add.pl
#
# Syntax:
# pv_add.pl?entry_id=213
#
# Description:
# Increment count for given entry of given blog or add
# record in database if not already present. Ignore
# own IP-address. The database is simply a list of
# records using a tied hash.
#
# Other modules
use CGI;
use CGI::Carp qw(fatalsToBrowser);
use DB_File;
use strict;
# Common code
use pv_common;
# Get parameters, if possible.
my $q = new CGI;
print $q->header;
# Ignore self.
my $client_ip = $q->remote_host() || $q->remote_addr() || "-";
exit if ($client_ip eq IGNORE_ME);
my $entry_id = $q->param('entry_id') || die "No entry id given.";
tie my %rec, "DB_File", FNAME_DB or die "Cannot open DB file '" . FNAME_DB . "' ($!)";
$rec{$entry_id}++;
untie %rec;
#
# Name:
# pv_add.pl
#
# Syntax:
# pv_add.pl?entry_id=213
#
# Description:
# Increment count for given entry of given blog or add
# record in database if not already present. Ignore
# own IP-address. The database is simply a list of
# records using a tied hash.
#
# Other modules
use CGI;
use CGI::Carp qw(fatalsToBrowser);
use DB_File;
use strict;
# Common code
use pv_common;
# Get parameters, if possible.
my $q = new CGI;
print $q->header;
# Ignore self.
my $client_ip = $q->remote_host() || $q->remote_addr() || "-";
exit if ($client_ip eq IGNORE_ME);
my $entry_id = $q->param('entry_id') || die "No entry id given.";
tie my %rec, "DB_File", FNAME_DB or die "Cannot open DB file '" . FNAME_DB . "' ($!)";
$rec{$entry_id}++;
untie %rec;
This script is invoked by putting the following SSI statement somewhere in your individual entry archive page:
CODE
<!--#exec cgi="<$MTBlogRelativeURL$>/pageviews/pv_add.pl?entry_id=<$MTEntryID$>"-->
Now everytime this web page is viewed, the script is called and the counter for the given entry is incremented by one, or added if it was not already present in the database.
The following script is used to generate the actual listing, and it looks like this:
CODE
!/usr/bin/perl -w
#
# Name:
# pv_show.pl
#
# Syntax:
# pv_show.pl?blog_id=1
# pv_show.pl?max=20&maxlen=20
#
# Options:
# blog_id - blog id
# max - max number of items to be listed
# maxw = max. length of words in title
#
# Description:
# Display list of most visited entries in order of
# most popular. If no blog given, assumed id = 1.
# Database is simply a list of records using a
# tied hash (see pv_add.pl). A maximum can be given,
# meaning that no more than this many are shown.
# Format: "(CNT) <a href='LINK'>TITLE</a><br />"
#
# Other modules
use CGI;
use CGI::Carp qw(fatalsToBrowser);
use DB_File;
use strict;
# Common code
use pv_common;
# MT stuff
use lib MT_DIR . "lib";
use lib MT_DIR . "extlib";
use MT;
use MT::Util qw( format_ts );
require MT::Entry;
# Get parameters, if possible.
my $q = new CGI;
print $q->header;
my $blog_id = $q->param('blog_id') || 1;
my $max = $q->param('max');
my $maxlen = $q->param('maxlen');
my $showdate = $q->param('showdate');
my $cnt;
my $mt = MT->new( Config => MT_DIR . 'mt.cfg' ) or die MT->errstr;
my $blog = MT::Blog->load($blog_id);
tie my %rec, "DB_File", FNAME_DB or die "Cannot open DB file.";
$cnt = 0;
foreach (sort { $rec{$b} <=> $rec{$a} } keys %rec)
{
my $entry = MT::Entry->load($_);
if (!$entry)
{
print "(0) No entry available for entry_id='$_'<br />";
delete $rec{$_};
next;
}
my $title = $entry->title;
if ($maxlen && length($title) > $maxlen)
{
# Make sure that $lines does not contain any really long words.
my @words = split(" ", $title);
for (@words)
{
if (length($_) > $maxlen) { $_ = substr($_, 0, $maxlen - 3) . "...";}
}
$title = join(" ", @words);
}
print "($rec{$_}) <a href=\"", $blog->archive_url, $entry->archive_file('Individual'), "\" title=\"Created on ", format_ts("%B %e, %Y", $entry->created_on), "\">", $title, "</a>";
print " ", format_ts("%B %e, %Y", $entry->created_on) if $showdate;
print "<br />\n";
$cnt++;
last if ($max && ($cnt >= $max));
}
untie %rec;
#
# Name:
# pv_show.pl
#
# Syntax:
# pv_show.pl?blog_id=1
# pv_show.pl?max=20&maxlen=20
#
# Options:
# blog_id - blog id
# max - max number of items to be listed
# maxw = max. length of words in title
#
# Description:
# Display list of most visited entries in order of
# most popular. If no blog given, assumed id = 1.
# Database is simply a list of records using a
# tied hash (see pv_add.pl). A maximum can be given,
# meaning that no more than this many are shown.
# Format: "(CNT) <a href='LINK'>TITLE</a><br />"
#
# Other modules
use CGI;
use CGI::Carp qw(fatalsToBrowser);
use DB_File;
use strict;
# Common code
use pv_common;
# MT stuff
use lib MT_DIR . "lib";
use lib MT_DIR . "extlib";
use MT;
use MT::Util qw( format_ts );
require MT::Entry;
# Get parameters, if possible.
my $q = new CGI;
print $q->header;
my $blog_id = $q->param('blog_id') || 1;
my $max = $q->param('max');
my $maxlen = $q->param('maxlen');
my $showdate = $q->param('showdate');
my $cnt;
my $mt = MT->new( Config => MT_DIR . 'mt.cfg' ) or die MT->errstr;
my $blog = MT::Blog->load($blog_id);
tie my %rec, "DB_File", FNAME_DB or die "Cannot open DB file.";
$cnt = 0;
foreach (sort { $rec{$b} <=> $rec{$a} } keys %rec)
{
my $entry = MT::Entry->load($_);
if (!$entry)
{
print "(0) No entry available for entry_id='$_'<br />";
delete $rec{$_};
next;
}
my $title = $entry->title;
if ($maxlen && length($title) > $maxlen)
{
# Make sure that $lines does not contain any really long words.
my @words = split(" ", $title);
for (@words)
{
if (length($_) > $maxlen) { $_ = substr($_, 0, $maxlen - 3) . "...";}
}
$title = join(" ", @words);
}
print "($rec{$_}) <a href=\"", $blog->archive_url, $entry->archive_file('Individual'), "\" title=\"Created on ", format_ts("%B %e, %Y", $entry->created_on), "\">", $title, "</a>";
print " ", format_ts("%B %e, %Y", $entry->created_on) if $showdate;
print "<br />\n";
$cnt++;
last if ($max && ($cnt >= $max));
}
untie %rec;
You can optionally limit the list to say the top twenty, like I do in the sidebar, or you can embed in a separate page to list the whole thing. (The maxlen thingie is to limit the length of individual words just in case long terms push my left margin div over too much and obscures the main content div). The creation dates can also optionally be included by passing the showdate parameter.
So what I have included in the sidebar is:
CODE
Here are the top twenty visited entries in this blog:
<!--#exec cgi="/Blogger/pageviews/pv_show.pl?max=20&maxlen=20" -->
<p align="center"><input class="button" type="button" value="Show all" onclick="document.location='http://www.cyber-gish.com/Blogger/pageviews.html'" title="Show me a list of all the blogs ordered by popularity..."></p>
<!--#exec cgi="/Blogger/pageviews/pv_show.pl?max=20&maxlen=20" -->
<p align="center"><input class="button" type="button" value="Show all" onclick="document.location='http://www.cyber-gish.com/Blogger/pageviews.html'" title="Show me a list of all the blogs ordered by popularity..."></p>
And then the separate web page called pageviews.html contains the following SSI statement:
CODE
<!--#exec cgi="/Blogger/pageviews/pv_show.pl?max=20&maxlen=20" -->
Finally, last but not least: the status line stating per entry what its visitor rating is:
CODE
#!/usr/bin/perl -w
#
# Name:
# pv_count.pl
#
# Syntax:
# pv_show.pl?blog_id=1&entry_id=531
# pv_show.pl?entry_id=44
#
# Options:
# blog_id - blog id
# entry_id - the entry id
#
# Description:
# Display the count (number of views)
# for the given blog entry.
#
# Other modules
use CGI;
use CGI::Carp qw(fatalsToBrowser);
use DB_File;
use strict;
# Common code
use pv_common;
# MT stuff
use lib MT_DIR . "lib";
use lib MT_DIR . "extlib";
use MT;
use MT::Util qw( format_ts );
require MT::Entry;
# Get parameters, if possible.
my $q = new CGI;
print $q->header;
my $blog_id = $q->param('blog_id') || 1;
my $entry_id = $q->param('entry_id') || die "No entry_id.";
my ($cnt, $max) = (0, 0);
my $mt = MT->new( Config => MT_DIR . 'mt.cfg' ) or die MT->errstr;
my $blog = MT::Blog->load($blog_id);
my $entry = MT::Entry->load($entry_id) || "Invalid entry id.";
tie my %rec, "DB_File", FNAME_DB or die "Cannot open DB file.";
foreach (sort { $rec{$b} <=> $rec{$a} } keys %rec)
{
$max || ($max = $rec{$_}); # First one is most.
if ($_ == $entry_id)
{
$cnt = $rec{$_};
last;
}
}
untie %rec;
print "$cnt visitor" . (($cnt != 1) ? "s" : "");
print " (most popular)" if ($cnt == $max);
#
# Name:
# pv_count.pl
#
# Syntax:
# pv_show.pl?blog_id=1&entry_id=531
# pv_show.pl?entry_id=44
#
# Options:
# blog_id - blog id
# entry_id - the entry id
#
# Description:
# Display the count (number of views)
# for the given blog entry.
#
# Other modules
use CGI;
use CGI::Carp qw(fatalsToBrowser);
use DB_File;
use strict;
# Common code
use pv_common;
# MT stuff
use lib MT_DIR . "lib";
use lib MT_DIR . "extlib";
use MT;
use MT::Util qw( format_ts );
require MT::Entry;
# Get parameters, if possible.
my $q = new CGI;
print $q->header;
my $blog_id = $q->param('blog_id') || 1;
my $entry_id = $q->param('entry_id') || die "No entry_id.";
my ($cnt, $max) = (0, 0);
my $mt = MT->new( Config => MT_DIR . 'mt.cfg' ) or die MT->errstr;
my $blog = MT::Blog->load($blog_id);
my $entry = MT::Entry->load($entry_id) || "Invalid entry id.";
tie my %rec, "DB_File", FNAME_DB or die "Cannot open DB file.";
foreach (sort { $rec{$b} <=> $rec{$a} } keys %rec)
{
$max || ($max = $rec{$_}); # First one is most.
if ($_ == $entry_id)
{
$cnt = $rec{$_};
last;
}
}
untie %rec;
print "$cnt visitor" . (($cnt != 1) ? "s" : "");
print " (most popular)" if ($cnt == $max);
By placing the following SSI statement at the spot on the individual archive page where you want the message to appear:
CODE
<!--#exec cgi="<$MTBlogRelativeURL$>/pageviews/pv_count.pl?entry_id=<$MTEntryID$>"-->
This results in the following message:
CODE
46 visitors
Or for the page with the most views:
CODE
240 visitors (most popular)
Well, that's pretty much that. I guess it could also probably be done using other technologies, like PHP. But I'll leave that up to another enthusiastic MT-er to figure that out.
Also, I am sure that an MT-plugin could be made for this, providing a much easier interface and implementation for those poor folks lacking Perl and/or SSI. But due to limited time, and despite the fact that I might make a good chance at winning the MT-plugin developer's contest, I will have to leave that to another day.
Have fun in the meanwhile.