Monthly Archives: December 2007

Update 8.27.11: The method described in this post uses PHP to generate the timestamps. If your site is using a caching plugin, the timestamps in the HTML will be stale, and this method will not work. Please see my updated post at A cache-friendly method for reducing WordPress comment spam for a new method using JavaScript for sites that use page caching.

In this post, I’ll explain how to reduce the amount of comment spam your WordPress blog receives by using an unobtrusive ‘handshake’ between the two files necessary for a valid comment submission to take place. I’ve written a few different articles on reducing comment spam by means of a challenge response test that the visitor must complete before submitting a comment, but I’m now looking for ways to achieve the same results while keeping the anti-spam method invisible to the visitor.

I’m a big fan of Akismet, but I also want to block as much spam as possible before it is caught by Akisment in order to reduce the number of database entries.

One thing this method does not do is rename and hide the path to the form processing script, but it makes that technique obsolete, anyway.

In the timestamp handshake method, a first timestamp is generated and written as a hidden input field when the post page loads. When the comment is submitted, a second timestamp is generated by the comment-processing script and it and the page-load timestamp are saved as variables. If the page-load timestamp variable is blank, which should be the case if the spambot uses any other page to populate the comment, the script will die. The page-load timestamp is then subtracted from the comment-submission timestamp. If the comment was submitted less than 60 seconds after the post page was loaded, the script dies with a descriptive error message. Hopefully, this will separate the bots’ comments from those left by thoughtful human visitors who have taken the time to read your post. If a human visitor does happen to submit a comment within 60 seconds of the page loading, he or she can click his or her browser’s back button and try resubmitting the comment again in a few seconds.

One drawback is that this method does involve editing a core file – wp-comments-post.php. You’ll have to re-edit it each time you upgrade WordPress, which is a nuisance, I know. The good thing is that if you forget to do this, people can still comment – you just won’t have the anti-spam protection.

Note that the instructions in the following steps are based on the code in WordPress version 2.3 and the Kubrick theme included with that release. You may need to adjust for your version of WordPress.

Step 1 – Add the hidden timestamp input field to the comment form

Open the comments.php file in your current theme’s folder and find the following lines:

<p><textarea name="comment" id="comment" cols="100%" rows="10" tabindex="4"></textarea></p>

<p><input name="submit" type="submit" id="submit" tabindex="5" value="Submit Comment" />

Add the following line between them:

<p><input type="hidden" name="timestamp" id="timestamp" value="<?php echo time(); ?>" size="22" /></p>

Step 2 – Modify the wp-comments-post.php file to create the second timestamp and perform the comparison

Open wp-comments-post.php and find the lines:

$comment_author       = trim(strip_tags($_POST['author']));
$comment_author_email = trim($_POST['email']);
$comment_author_url   = trim($_POST['url']);
$comment_content      = trim($_POST['comment']);

Immediately after them, add the following lines:

$comment_timestamp    = trim($_POST['timestamp']);
$submitted_timestamp  = time();

if ( $comment_timestamp == '' )
	wp_die( __('Hello, spam bot!') );
	
if ( $submitted_timestamp - $comment_timestamp < 60 )
	wp_die( __('Error: you must wait at least 1 minute before posting a comment.') );

That’s it; you’re done.

Credits

Thanks to Jonathan Bailey for suggesting the handshake in his post at http://www.plagiarismtoday.com/2007/07/24/wordpress-and-comment-spam/.

This is a collection of php code snippets that seem to come in handy rather often. They are assembled here more for my own organization than anything else.

String: trim and convert to lowercase

A very straightforward but useful snippet. A string is first trimmed of any leading or trailing white space, and then converted to lowercase letters. Good for normalizing user input.

<?php
$string = "Orange";
$string = strtolower(trim($string));
echo $string;
?>

String: truncate and break at word

This will attempt to shorten a string to $length characters, but will then increase the string (if necessary) to break at the next whole word and then append an ellipses to the end of the string. Good for shortening readable text while keeping it looking pretty.

<?php 
function truncate($string, $length) {
	if (strlen($string) > $length) {
		$pos = strpos($string, " ", $length);
		return substr($string, 0, $pos) . "...";
	}
	
	return $string;
}

	echo truncate('the quick brown fox jumped over the lazy dog', 10);
?> 

In the above example, the resultant output will be the quick brown…, because the 10th character is the space immediately before the ‘b’ in ‘brown’, which is counted as part of the word ‘brown’.

What season is it?

Note that this is only a very rough approximation of when a season begins and ends. This snippet would be good for rotating a seasonal background or something, but it’s not astronomically correct, and I wouldn’t use it as a calendar. Reckoning a season is rather complex.

<?php echo "It is day " . date('z') . " of the year. <br />"; ?>
<?php $theday = date('z');
	if($theday >= "79" && $theday <= "171") { 
	$season = "Spring";
	} elseif($theday >= "172" && $theday <= "264") { 
	$season = "Summer";
	} elseif($theday >= "265" && $theday <= "355") { 
	$season = "Autumn";
	} else { 
	$season = "Winter";
	}
	echo "It's " . $season . "!";
?>

Get the number of days since something happened

This function takes a date (formatted as a Unix timestamp) and calculates the number of days since that date. The floor() function shouldn’t really be necessary, but it’s a hold-over from a less accurate function that used only the hours elapsed. In that function, the results would vary depending on the time of day the function was called. In this method, the times are normalized to 12:00:00 AM.

function calc_days_ago($date){
	// The function accepts a date formatted as a Unix timestamp
	
	// First, normalize the current date down to the Unix time at 12:00:00 AM (to the second)
	$now = time() - ( (date('G')*(60*60)) + date('i')*60 + date('s') );
	// Second, normalize the given date down to the Unix time at 12:00:00 AM (to the second)
	$then = $date - ( (date('G', $date)*(60*60)) + date('i', $date)*60 + date('s', $date) );
	$diff = $now - $then;
	$days = floor($diff/(24*60*60));
	switch ($days) {
	case 0:
 		$days_ago = "today";
		break;
	case 1:
		$days_ago = $days . " day ago";
		break;
	default:
		$days_ago = $days . " days ago";
	}
	return $days_ago;
}

Get the hours and minutes remaining until something happens

This function takes a time (formatted as a Unix timestamp) and calculates the number of hours and minutes remaining until that time. If the time has already passed, the function returns “historical”. Example outputs would be “7 hours”, “6 hours and 34 minutes”, and “12 minutes”. It could probably be made even more accurate if you changed it to use 3 decimal places and then round to 2 decimal places, but this is good enough for my purposes.

function calc_time_left($date){
	// The function accepts a date formatted as a Unix timestamp

	$now = time();
	$event = $date;
	if ($event >= $now) {
		$diff = $event - $now;
		$unroundedhours = $diff/(60*60);
		// Find the hours, if any, and assemble a string
		$hours = floor($unroundedhours);
		if ($hours > "0") {
			$hourtext = ($hours == "1")? " hour" : " hours";
			$thehours = $hours . $hourtext;
		}else{
			$thehours = "";
		}
		// Find the minutes, if any, and assemble a string
		if (strpos($unroundedhours, '.')) {
			$pos = strpos($unroundedhours, '.') + 1;
			$remainder = substr($unroundedhours, $pos, 2);
			$minutes = floor($remainder * .6);
			$minutetext = ($minutes == "1")? " minute" : " minutes";
			$theminutes = $minutes . $minutetext;
		}elseif ($minutes == "0") {
			$theminutes = "";
		}else{
			$theminutes = "";
		}
		if ($thehours && $theminutes) {
			$sep = " and ";
		}
		$timeleft = $thehours . $sep . $theminutes;
	}else{
		$timeleft = "historical";
	}
	return $timeleft;
}

Get the path of the containing directory

This one really comes in handy. It will give you the URL of the folder where the executing script resides, so you can reference the full path to other files in that folder, no matter where the folder may be located. It works on both Linux and Windows servers, and it adds a trailing slash to the path if one doesn’t already exist, so that root looks the same as a subfolder.

<?php 
function get_path() {
	// Get the path of the folder where the executing script resides, with the trailing slash
	
	// Determine HTTPS or HTTP
	$url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https://' : 'http://';
	$url .= $_SERVER['HTTP_HOST'] . dirname($_SERVER['PHP_SELF']);
	// Convert the trailing backslash (on Windows root) to a forward slash
	$url = str_replace('\\', '/', $url);
	// Determine whether the current location is root by looking for a trailing slash (Windows or Linux)
	if (strlen($url) != strrpos($url, '/') +1) {
		$url .= '/';
	}
	return $url;
}
?>

Centering unordered list items

I wrote this script because I wanted to center the thumbnails in the Plogger image gallery while still using an unordered list item to contain each thumbnail. The script figures out how many thumbnails exist on a page and how many will fit in the space provided, then adds sufficient left padding to each to give the appearance of them being centered. It can be easily adapted for other uses. The full explanation and code example is at //ardamis.com/2007/08/05/centering-the-thumbnails-in-plogger/.

Parse .html as .php (Apache .htaccess)

This isn’t actually a PHP script, but it’s still handy. If you need to write pages with a .html or .htm extension but still want to use PHP in those pages, adding the following line to your .htaccess file will force an Apache server to parse .html files as .php files. I have confirmed this to work with GoDaddy’s hosting (GoDaddy runs PHP as CGI).

AddHandler x-httpd-php .php .htm .html

If you are running an Apache server as part of an XAMPP installation on top of Windows, try using this instead:

AddType application/x-httpd-php .html .htm