Using Akismet to Detect Spam Email
September 6, 2008 1:48 am PHPAfter seeing the effectiveness of the Akisment WordPress plug-in at filtering out spam comments here, I decided to see if I could use it in conjunction with a email contact form. I thought it might be interesting to some of my readers (there are at least a couple) to keep a sort of journal here of what I do to accomplish that, plus it might help encourage me to finish it.
So the first thing I've done is to create an Akismet class that can take the pertinent data, contact the Akismet server via cURL and send it that data, and then return the response as a boolean (true == spam, false == not spam).
I started at the Akisment API page to figure out what needed to be done. It rurns out that things are not only fairly straight-forward, but the documentation is clear enough that I got things to work pretty quickly with no headaches (other than my typos). At this point I have my Akismet class:
<?php
/**
* @author Charles Reace (www.charles-reace.com)
* @version 0.1
*/
/**
* Utilize the Akismet API for detecting spam emails
* See http://akismet.com/development/api/ for more info on its API
*/
class Akismet
{
/**
* @var string API key from WordPress at http://wordpress.com/signup/
*/
protected $apiKey;
/**
* @var string blog URL that was registered for the above API key
*/
protected $blogUrl;
/**
* Constructor
* @return void
*/
public function __construct($apiKey, $blogUrl)
{
$this->apiKey = $apiKey;
$this->blogUrl = $blogUrl;
if(!$this->verifyApiKey())
{
throw new Exception('Invalid API key');
}
}
/**
* Verify the API key to be used
* @return bool
*/
public function verifyApiKey()
{
$url = 'http://rest.akismet.com/1.1/verify-key';
$postData = array(
'key' => $this->apiKey,
'blog' => $this->blogUrl
);
return($this->sendRequest($url, $postData) == 'valid');
}
/**
* Check if Akismet thinks a message is spam
* @return bool TRUE if it is spam, else false
* @param string $name
* @param string $email
* @param string $subject
* @param string $message
*/
public function isSpam($name, $email, $message)
{
$data = array();
$data['user_ip'] = $_SERVER['REMOTE_ADDR'];
$data['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
$data['referrer'] = @$_SERVER['HTTP_REFERER'];
$data['blog'] = $this->blogUrl;
$data['comment_author'] = $name;
$data['comment_author_email'] = $email;
$data['comment_content'] = $message;
$url = 'http://' . $this->apiKey . '.rest.akismet.com/1.1/comment-check';
$result = $this->sendRequest($url, $data);
return ($result == 'true') ? true : false;
}
/**
* Send the request via cURL
* @return string
* @param string $url
* @param array $data
*/
protected function sendRequest($url, $data)
{
$postData = $this->makePostString($data);
if($postData === false)
{
return false;
}
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
/**
* Take associative array and turn it into post data string
* @return string
* @param array $data
*/
protected function makePostString($data)
{
if(!is_array($data))
{
user_error(__METHOD__.'(): arg must be an array');
return false;
}
$parts = array();
foreach($data as $key => $val)
{
$parts[] = urlencode($key) . '=' . urlencode($val);
}
return(implode('&', $parts));
}
}
Here's a little test script:
<?php
// TEST Akismet class
require_once 'Akismet.class.php';
echo "<pre>";
try
{
$test = new Akismet('********', 'http://www.charles-reace.com/blog/');
$result = $test->isSpam('Charles Reace', '*****@*****.com', 'this is a test');
var_dump($result); // should be false;
$result = $test->isSpam('viagra-test-123', '*****@*****.com', 'this is a test');
var_dump($result); // should be true;
}
catch(Exception $e)
{
print_r($e);
}
echo "</pre>";
And here is the test result:
bool(false) bool(true)
Stay tuned for future installments as I incorporate this class into a contact form, and then figure out how to add the ability to report false positives and false negatives.

Charles :
Date: September 6, 2008 @ 14:16
Quick update: due to a misunderstanding on my part, the API verification will be removed from the constructor and made a separate, stand-alone public function, as it does not need to be executed with each request.