Recent Topics

1 Dec 20, 2004 05:40    

Below are the steps that I have taken to implement a hit tracking system in my blog. I realize that the best way to share this is thru a plugin - however, I do not know what is the proper syntax for writing one. If anyone can help me to translate this into a plugin, it would be greatly appreciated.

The code changes below will allow you to track the number of times each post is hit in single display mode. The number of views (if > 0) will then be displayed write after the trackback(x) text at the bottom of the post.

This hack is implemented and working on my blog: http://ellisweb.net/aliyah. I have set it so that if a post does not have any hits it will not display the hit total. You can see it in action by clicking the permalink to any post.

1) Add column to database:

ALTER TABLE `evo_posts` ADD `post_hits` INT( 10 ) DEFAULT '0' NOT NULL

2) Add function to conf/hacks.php file (create it if you dont have it) to increment post_hits column for given post:

// Increments the post_hits field in the evo_posts table for given post ID. ADDED BY JELLIS
function increment_post_hits( $p_ID, $a_ID ) {
   	global $DB, $current_User, $tableposts;
   	if( is_logged_in() ) {
   	   if( $current_User->get('ID') == $a_ID ) {
    	     return;
		}
	}
	if (check_hit(false)) {
		$sql_query = 'SELECT post_hits FROM evo_posts WHERE ID = ' . $p_ID;
		$post_hits = $DB->get_var($sql_query);
	   	$post_hits++;
	   	$sql_query = 'UPDATE evo_posts SET post_hits = ' . $post_hits . ' WHERE ID ='. $p_ID;
	   	$DB->query($sql_query);
	}
}

3) Add function to conf/hacks.php file to retrieve post_hits column for given post:

// Returns the post_hits field for the selected post ID. ADDED BY JELLIS
function get_post_hits($p_ID) {
	global $DB;
	$sql_query = 'SELECT post_hits FROM evo_posts WHERE ID = ' . $p_ID;
   	$post_hits = $DB->get_var($sql_query);
	return $post_hits;
}

4) Add code to _main.php file for your skin to call the increment function when posting in single display mode. (I can get away with this because I use just one skin on my blog. I am sure that there is a better play to put this call for blogs with multiple skins. Any ideas?):

	if( isset($MainList) ) while( $Item = $MainList->get_item() )
	{
		// ADDED BY JELLIS: LOG HIT TO POST IF IN SINGLE POST MODE
		if( $disp == 'single' ) {
			increment_post_hits( $Item->get('ID'), $Item->Author->get('ID') ); 
		}

5) Add code to b2evocore/_class_item.php to retrieve and display the number of hits for a given post:

Initialize new local variable for the function:

$include_link = true; // ADDED BY JELLIS

Add a case to the switch structure at the top of the function:

// ADDED BY JACOB	
			case 'hits':
				if( $hideifnone == '#' ) $hideifnone = true;
				if( $title == '#' ) $title = T_('Display Number of Views');
				if( $zero == '#' ) $zero = T_('0 Views');
				if( $one == '#' ) $one = T_('1 View');
				if( $more == '#' ) $more = T_('%d Views');
				$include_link = false;
				break;

Add If/Else structure to the code where the link is developed. I added this and the local variable above because I did not want the hits text to appear as a link:

// IF/ELSE ADDED BY JACOB
		if ($include_link) {
			echo '<a href="', $url;
			echo '#', $type, '" ';	// Position on feedback
			echo 'title="', $title, '"';
			if( $use_popup ) echo ' onclick="b2open(this.href); return false"';
			echo '>';
		} else {
			echo '<strong>'; // if no link, make bold
		}

and the closing tags:

// IF/ELSE ADDED BY JACOB
		if ($include_link)
			echo '</a>';
		else
			echo '</strong>';

6) Add code to b2evocore/_functions_comments.php to retrieve the number of hits to a given post. Code should be added anywhere in the generic_ctp_number function. This is necessary for step 5 to work:

	// store number of post hits in cache ADDED BY JELLIS
	if( $mode == 'hits' ) {
		$num_of_hits = get_post_hits($post_id);
		return $num_of_hits;
	}

7) Add code to _main.php of skin to display the number of hits. This is added immediately after comments and trackbacks:


			<?php $Item->feedback_link( 'comments' ) // Link to comments ?>
			<?php $Item->feedback_link( 'trackbacks', ' &bull; ' ) // Link to trackbacks ?>
			<?php //$Item->feedback_link( 'pingbacks', ' &bull; ' ) // Link to trackbacks ?>
			<?php $Item->feedback_link( 'hits', ' &bull; ' ) // Link to hits ADDED BY JELLIS ?>

When I have another free Sunday I hope to develop a hack for the Admin section that will show you a ranking of the most popular posts, and a similar hack to display the most popular posts in the sidebar section of the skin.

8. Add function check_rererrer to config/_hacks.php - this is called in the increment_post_hits function, and checks to make sure that the post is not being hit by a search engine robot:

// checks to make sure that referer is valid. ADDED BY JELLIS
// code modified and pruned from b2evocore/_functions_hitlogs.php function: log_hit()
// unless $check_referrer is set to false, this will just check to make sure
// that post is not being hit by Robot
function check_hit($check_referrer = false) {
	global $DB, $blog, $tablehitlog, $blackList, $search_engines, $user_agents;
	global $doubleCheckReferers, $comments_allowed_uri_scheme, $HTTP_REFERER, $page, $ReqURI, $ReqPath;

	$ref = $HTTP_REFERER;
	$RemoteAddr = $_SERVER['REMOTE_ADDR'];
	$UserAgent = $_SERVER['HTTP_USER_AGENT'];
	if ($UserAgent != strip_tags($UserAgent))
	{ //then they have tried something funny,
		//putting HTML or PHP into the HTTP_USER_AGENT
		debug_log( 'Hit Log: '.T_("bad char in User Agent"));
		$UserAgent = '';
	}
	
	$ignore = 'no';  // So far so good
			
	if( stristr($ReqPath, 'rss')
			|| stristr($ReqPath, 'rdf')
			|| stristr($ReqPath, 'atom')  )
	{
		$ignore = 'rss';
		// don't mess up the XML!! debug_log( 'Hit Log: referer ignored (RSS));
	}
	else
	{	// Lookup robots
		foreach ($user_agents as $user_agent)
		{
			if( ($user_agent[0] == 'robot') && (strstr($UserAgent, $user_agent[1])) )
			{
				$ignore = "robot";
				debug_log( 'Hit Log: '. T_('referer ignored'). ' ('. T_('robot'). ')');
				break;
			}
		}
	}
	
	if( $ignore == 'no' )
	{
		if( strlen($ref) < 13 )
		{	// minimum http://az.fr/ , this will be considered direct access (although it could be https:)
			$ignore = 'invalid';
			debug_log( 'Hit Log: '. T_('referer ignored'). ' ('. T_('invalid'). ')' );
		}
	}

	if( $ignore == 'no' )
	{	// identify search engines
		foreach($search_engines as $engine)
		{
			// debug_log( 'Hit Log: '."engine: ".$engine);
			if(stristr($ref, $engine))
			{
				$ignore = 'search';
				debug_log( 'Hit Log: '. T_('referer ignored'). " (". T_('search engine'). ")");
				break;
			}
		}
	}	

	if( $doubleCheckReferers && $check_referrer )
	{
		debug_log( 'Hit Log: '. T_('loading referering page') );

		// this is so that the page up until the call to
		// logReferer will get shown before it tries to check
		// back against the refering URL.
		flush();

		$goodReferer = 0;
		if ( strlen($ref) > 0 )
		{
			$fp = @fopen ($ref, 'r');
			if ($fp)
			{
				//timeout after 5 seconds
				socket_set_timeout($fp, 5);
				while (!feof ($fp))
				{
					$page .= trim(fgets($fp));
				}
				if (strstr($page,$fullCurrentURL))
				{
					debug_log( 'Hit Log: '. T_('found current url in page') );
					$goodReferer = 1;
				}
			}
		} else {
			// Direct accesses are always good hits
			$goodReferer = 1;
		}

		if(!$goodReferer)
		{	// This was probably spam!
			debug_log( 'Hit Log: '. sprintf('did not find %s in %s', $fullCurrentURL, $page ) );
			$ref="";
			return;
		}

	}
	
	if ($ignore == 'no') { //if you have gotten this far then the referrer checks out
		return true;
	}
}

Please let me know if you have any comments, etc. And again, if anyone can help me develop this into a plugin, or at least point me in the right direction, I will be most grateful.

2 Dec 20, 2004 14:48

Hi jellis. I can think of one or two things on this of the top of my head. Since I haven't tried it yet so I can't speak to more than just quick thoughts (other than it's obviously working!).

jellis613 wrote:

... 2) Add function to conf/hacks.php file (create it if you dont have it and then reference to it in conf/_main.php) to increment post_hits column for given post ...

I'm not sure what you meant by this. There is no _main.php in the conf folder, and I see further down the hack instructions you identify the need to call it from _main.php in the skins/skinname folder, so all I can think of is you are trying to tell b2evo to read the hacks file. There is no need to add a reference to hacks.php - if you add it to the conf folder b2evolution will read it.

jellis613 wrote:

... When I have another free Sunday I hope to develop a hack for the Admin section that will show you a ranking of the most popular posts ...

You might want to check out captsolo's "[url=http://forums.b2evolution.net/viewtopic.php?t=2204]Statistics Deluxe[/url]" hack. It adds some groovy filtering of traffic data, and provides a nice vehicle for displaying the information your hack is collecting.

jellis613 wrote:

... and a similar hack to display the most popular posts in the sidebar section of the skin ...

I think that one will be easy. After I install your hack it should be no problem to support this with a new file in skins/skinname and a new sidebar bit.

Thanks for the hack!

3 Dec 20, 2004 15:27

Thanks for the feedback Ed. You are right, there is no file conf/_main.php. What I meant was that I thought that if you created a conf/hacks.php file that you would have to add a require_once reference to the hacks file in the conf/_config.php file (where all of the other pages in the conf folder are referenced). If you dont include a reference to _hacks.php in this file, how else would it be referenced?

I'll check out the Statistics Deluxe hack, play around with it a bit and see if I can fit my module into there.

More thoughts for future development of this hack:

dont count hits when they come from domains on your black list (most importantly so that you dont count your own hits. Of course, you may want to do this for more selfish reasons)

Make a table that tracks all of the individual hits to posts over x number of days. This can then be used to post a list of the most popular pages over the last day/week/month, etc.

jellis

4 Dec 20, 2004 15:55

http://b2evolution.net/man/2004/06/16/hacks_howto

From b2evocore/_main.php:

// Load hacks file if it exists
@include_once( dirname(__FILE__) . '/../conf/hacks.php' );

This came up in a hack recently and I thought "huh?". I am now taking advantage of this by (slowly but surely) moving my hacks to that file. Not all hacks can go, but most can - and should I guess.

5 Dec 22, 2004 14:26

Stopping author-hits from incrementing the counter is not hard. Add this to the top of the function:

function blahblah....
global blahblah...
if( is_logged_in() ) {
if( $current_User->get( 'ID' ) == $Item->Author->get( 'ID' ) {
return;
}
}
the rest of the function

Disclaimer: I use the first conditional statement on the installation I'm testing this in, but not the second because I'm the only blogger on my blog. It wouldn't surprise me if something goes wrong with the second if statement, and if something does go wrong it'll be in the second half of the second if. I use

if( is_logged_in() ) {
if( $current_User->get( 'ID' ) == 1 ) {
auto_close_comments( '<li>', '</li>', 45 );
}
}

without issue, so I know two thirds of the first code block is valid ;)

The reason for the disclaimer is that I made up a neat little skin thing to list and link post titles in the category of the item viewed in single page mode that works great in one skin and fails completely in a normal skin.

Back on this hack: it needs to exclude robot hits. Google did me last night, or more accurately did about 80% of my posts. Why it chose those and not all others is beyond me, but it did. As you can imagine, all those posts got incremented. Guess it's time to hack a bit deeper eh?

6 Dec 22, 2004 15:12

EdB wrote:

Disclaimer: I use the first conditional statement on the installation I'm testing this in, but not the second because I'm the only blogger on my blog. It wouldn't surprise me if something goes wrong with the second if statement, and if something does go wrong it'll be in the second half of the second if.

You were right, there was an error in the second half of the code (parsing error). I do not have time to test it right now, but when someone figures out why it is not working, let me know. I will post the change to the hack above (using your method).

7 Dec 22, 2004 15:22

EdB wrote:

Back on this hack: it needs to exclude robot hits. Google did me last night, or more accurately did about 80% of my posts. Why it chose those and not all others is beyond me, but it did. As you can imagine, all those posts got incremented. Guess it's time to hack a bit deeper eh?

Well, I was also thinking along these lines. I have added a new function to the code listing in the first post above: check_referrer. This checks to see if the referrer is a search engine robot (if you include a True) parameter it will check to make sure that the hit referer is good (not recommended without further modification as this will block many hits from logging - all the hits who are browsing from one page on your site to another).

To implement, add the function check_referrer in the post above to your _hacks.php file. You will also need to modify the increment_post_hits function as follows to make the call to check_referrer (also posted above):

function increment_post_hits($p_ID){
   	global $DB, $Item, $current_User;
   	if( is_logged_in() ) { // do not log hit if author is hitting the post
   		if( $current_User->get( 'ID' ) == 1) {
			return;
		}
	}
   if (check_hit()) {
	   $sql_query = 'SELECT post_hits FROM evo_posts WHERE ID = ' . $p_ID;
	   $post_hits = $DB->get_var($sql_query);
	   $post_hits++;
	   $sql_query = 'UPDATE evo_posts SET post_hits = ' . $post_hits . ' WHERE ID ='. $p_ID;
	   $DB->query($sql_query);
	}
} 

Note: Since the whole purpose of this code is to make sure that robot and search engine hits are not recorded, I have no way of testing it. I can say that after implementing it, nothing on my site seems broken. The only way to know for sure is to check in your evo_hitlog table for search engine hits. If you get many of these and no posts hits are incremented like what happened to Ed, then that will confirm that this works. Let me know.

8 Dec 22, 2004 19:45

Got it (I think). Feed another parameter to the increment function from _main.php:

if( $disp == 'single' ) {
	increment_post_hits( $Item->get('ID'), $Item->Author->get('ID') );
	} ?>


Now change the function to take advantage of the new parameter:

function increment_post_hits( $p_ID, $a_ID ) {
	global $DB, $current_User, $tableposts;
	if( is_logged_in() ) {
		if( $current_User->get('ID') == $a_ID ) {
			return;
			}
		}
** the robot checking and excluding bit
** the rest of the actual function
	}


I suppose it could be a tad more efficient by getting the current_User ID while still in _main and feeding a third parameter ($c_ID?) but this method worked on my uniblogger blog. It hasn't been there very long, but I'm not noticing any perceptible change in page generation time so I'm cool with it as-is. I checked it in a browser where I'm logged in and a browser where I'm not. It did not error when I was logged in, and incremented the counter when I was not. Oh goody!

9 Dec 22, 2004 20:10

Thanks for the code Ed. I tested it and it works for me as well - I updated the code in the first post to reflect this.

I also noticed something amiss in the code for the check_hit function that prevented it from working properly (false positives). The ammended function is posted above. It now logs hits properly - only time will tell if it will actually work to prevent robot logs - though I am pretty confident that it will work.

Next for this hack:

Implement tracking of individual hits over set period of time (with autopruning) to be used to track the most popular pages during that time period
Module to display list of top X most popular pages, implement this in the Admin section, in Some Viewing Stats section, and in the _main.php page for the skin
Do the same for most popular pages over last X days. Include in Admin section a place to designate the time for tracking these hits

Any other thoughts?

10 Dec 24, 2004 12:05

Can you please also post the positions of where you put your coding into the source code. I am a bit confused on where they have to go to. A bit of surrounding original source would help for searching. Thanks a again and good work!

11 Dec 24, 2004 12:18

Hi. I was meaning to post this here because all I want for Christmas is traffic but I decided I had better clean up some old hacks first. The way I've finalized on this puts 3 things in existing files, and in only one case do I show flanking code. The second I describe because my versions look nothing like original equipment, and the third just goes in the sidebar. Anyway http://wonderwinds.com/weblog.php/2004/12/23/heavy_hitters isn't exactly what you see in this thread but it serves the same purpose and offers a sidebar rundown.


Form is loading...