 spambot.admin.inc |  29 +++++++++
 spambot.install   |  19 ++++++
 spambot.module    | 188 +++++++++++++++++++++++++++++++++++++++++++-----------
 spambot.pages.inc |   4 +-
 4 files changed, 201 insertions(+), 39 deletions(-)

diff --git a/spambot.admin.inc b/spambot.admin.inc
index cb311a9..04e9538 100644
--- a/spambot.admin.inc
+++ b/spambot.admin.inc
@@ -155,5 +155,34 @@ function spambot_settings_form($form, &$form_state) {
     '#default_value' => variable_get('spambot_sfs_api_key', FALSE),
   );
 
+  $form['cache'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Caching'),
+    '#description' => t('This module can cache the results from www.stopforumspam.com to reduce the load on the remote server.'),
+    '#collapsible' => TRUE,
+  );
+  $form['cache']['spambot_enable_cache'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Enable result caching'),
+    '#description' => t('If ticked, results from queries to www.stopforumspam.com will be cached.'),
+    '#default_value' => variable_get('spambot_enable_cache', TRUE),
+  );
+  $form['cache']['spambot_cache_expire'] = array(
+    '#type' => 'select',
+    '#title' => t('Time to expire'),
+    '#description' => t('Specify the amount of time to cache the response. See Drupal documentation on Caching for when/how entries are removed from the cache.'),
+    '#options' => array(
+      CACHE_PERMANENT => t('Keep until explicitly removed'),
+      60 => t('1 minute'),
+      300 => t('5 minutes'),
+      1800 => t('30 minutes'),
+      3600 => t('1 hour'),
+      86400 => t('1 day'),
+      604800 => t('1 week'),
+      2592000 => t('1 month (30 days)'),
+    ),
+    '#default_value' => variable_get('spambot_cache_expire', CACHE_PERMANENT),
+  );
+
   return system_settings_form($form);
 }
diff --git a/spambot.install b/spambot.install
index 35aa161..3b73c29 100644
--- a/spambot.install
+++ b/spambot.install
@@ -17,6 +17,10 @@ function spambot_schema() {
       'uid' => array('uid'),
     ),
   );
+
+  $schema['cache_spambot'] = drupal_get_schema_unprocessed('system', 'cache');
+  $schema['cache_spambot']['description'] = t('Cache table for the Spambot module to store responses from www.stopforumspam.com');
+  
   return $schema;
 }
 
@@ -98,3 +102,18 @@ function spambot_update_7101() {
   
   return join('<br />', $messages);
 }
+
+function spambot_update_7104() {
+  $messages = array();
+
+  // Create new table cache_spambot
+  if (!db_table_exists('cache_spambot')) {
+    $schema = array();
+    $schema['cache_spambot'] = drupal_get_schema_unprocessed('system', 'cache');
+    $schema['cache_spambot']['description'] = t('Cache table for the Spambot module to store responses from www.stopforumspam.com');
+    db_create_table('cache_spambot', $schema['cache_spambot']);
+    $messages[] = t('Created new table <em>cache_spambot</em>.');
+  }
+  
+  return join('<br />', $messages);
+}
diff --git a/spambot.module b/spambot.module
index ee6f4c2..ab20bc8 100644
--- a/spambot.module
+++ b/spambot.module
@@ -76,34 +76,31 @@ function spambot_user_register_validate($form, &$form_state) {
 
   if ($ip_threshold > 0) {
     $ip = ip_address();
-    // Don't check the loopback interface
-    if ($ip != '127.0.0.1') {
+    // Don't check the loopback interface or IPv6 addresses
+    if (($ip != '127.0.0.1') && (strpos($ip, ':') === FALSE)) {
       $request['ip'] = $ip;
     }
   }
 
-  // Only do a remote API request if there is anything to check
+  // check for a spammer
   if (count($request)) {
-    $data = array();
-    if (spambot_sfs_request($request, $data)) {
+    $reasons = array();
+    if (spambot_is_spammer($request, $reasons) > 0) {
+
       $substitutions = array(
         '@email' => $form_state['values']['mail'], '%email' => $form_state['values']['mail'],
         '@username' => $form_state['values']['name'], '%username' => $form_state['values']['name'],
         '@ip' => ip_address(), '%ip' => ip_address(),
       );
 
-      $reasons = array();
-      if ($email_threshold > 0 && !empty($data['email']['appears']) && $data['email']['frequency'] >= $email_threshold) {
+      if (isset($reasons['email'])) {
         form_set_error('mail', t(variable_get('spambot_blocked_message_email', t(SPAMBOT_DEFAULT_BLOCKED_MESSAGE)), $substitutions));
-        $reasons[] = t('email=@value', array('@value' => $request['email']));
       }
-      if ($username_threshold > 0 && !empty($data['username']['appears']) && $data['username']['frequency'] >= $username_threshold) {
+      if (isset($reasons['name'])) {
         form_set_error('name', t(variable_get('spambot_blocked_message_username', t(SPAMBOT_DEFAULT_BLOCKED_MESSAGE)), $substitutions));
-        $reasons[] = t('username=@value', array('@value' => $request['username']));
       }
-      if ($ip_threshold > 0 && !empty($data['ip']['appears']) && $data['ip']['frequency'] >= $ip_threshold) {
+      if (isset($reasons['ip'])) {
         form_set_error('', t(variable_get('spambot_blocked_message_ip', t(SPAMBOT_DEFAULT_BLOCKED_MESSAGE)), $substitutions));
-        $reasons[] = t('ip=@value', array('@value' => $request['ip']));
       }
 
       if (count($reasons)) {
@@ -199,6 +196,138 @@ function spambot_cron() {
 }
 
 /**
+ * Implementation of hook_flush_caches
+ */
+function spambot_flush_caches() {
+  return array('cache_spambot');
+}
+
+/**
+ * Checks for a spammer.
+ *
+ * @return
+ *   positive if spammer, 0 if not spammer, negative if error
+ */
+function spambot_is_spammer($request, &$reasons) {
+  $query = $request;
+  $reasons = array();
+  $data = array();
+  
+  // use static caching to reduce multiple calls per page/cron request
+  static $static_emails = array();
+  static $static_names = array();
+  static $static_ips = array();
+
+  if (!empty($query['email']) && !empty($static_emails[$query['email']])) {
+    $data['email'] = $static_emails[$query['email']];
+    unset($query['email']);
+  }
+  if (!empty($query['username']) && !empty($static_names[$query['username']])) {
+    $data['username'] = $static_names[$query['username']];
+    unset($query['username']);
+  }
+  if (!empty($query['ip']) && !empty($static_ips[$query['ip']])) {
+    $data['ip'] = $static_ips[$query['ip']];
+    unset($query['ip']);
+  }
+  
+  // use drupal caching to reduce calls to external server
+  if (variable_get('spambot_enable_cache', TRUE)) {
+    static $cache_clear = FALSE;
+    if (!$cache_clear) {
+      cache_clear_all(NULL, 'cache_spambot');
+      $cache_clear = TRUE;
+    }
+    if (!empty($query['email'])) {
+      $cache_data = cache_get("email:{$query['email']}", 'cache_spambot');
+      if ($cache_data !== FALSE) {
+        $data['email'] = $cache_data->data;
+        $static_emails[$query['email']] = $cache_data->data;
+        unset($query['email']);
+      }
+    }
+    if (!empty($query['username'])) {
+      $cache_data = cache_get("username:{$query['username']}", 'cache_spambot');
+      if ($cache_data !== FALSE) {
+        $data['username'] = $cache_data->data;
+        $static_names[$query['username']] = $cache_data->data;
+        unset($query['username']);
+      }
+    }
+    if (!empty($query['ip'])) {
+      $cache_data = cache_get("ip:{$query['ip']}", 'cache_spambot');
+      if ($cache_data !== FALSE) {
+        $data['ip'] = $cache_data->data;
+        $static_ips[$query['ip']] = $cache_data->data;
+        unset($query['ip']);
+      }
+    }
+
+    // check the cached data & bail if we've identified a spammer
+    if ((!empty($data)) && (spambot_check_spammer($request, $data, $reasons))) {
+      return count($reasons);
+    }
+  }
+ 
+  // Only do a remote API request if there is anything left to check
+  // but pass the full request to refresh any cache entries
+  if (count($query)) {
+    if (!spambot_sfs_request($request, $data)) {
+      return -1;
+    }
+
+    // cache the response
+    if (variable_get('spambot_enable_cache', TRUE)) {
+      $expire = variable_get('spambot_cache_expire', CACHE_PERMANENT);
+      $expire = ($expire != CACHE_PERMANENT) ? time() + $expire : CACHE_PERMANENT;
+
+      if (!empty($data['email'])) {
+        cache_set("email:{$request['email']}", $data['email'], 'cache_spambot', $expire);
+        $static_emails[$request['email']] = $data['email'];
+      }
+      if (!empty($data['username'])) {
+        cache_set("username:{$request['username']}", $data['username'], 'cache_spambot', $expire);
+        $static_names[$request['username']] = $data['username'];
+      }
+      if (!empty($data['ip'])) {
+        cache_set("ip:{$request['ip']}", $data['ip'], 'cache_spambot', $expire);
+        $static_ips[$query['ip']] = $data['ip'];
+      }
+    }
+  }
+  
+  if (!empty($data)) {
+    spambot_check_spammer($request, $data, $reasons);
+  }
+  
+  return count($reasons);
+}
+  
+/**
+ * Checks the response data for a spammer.
+ *
+ * @return
+ *   TRUE if spammer, FALSE if not spammer
+ */
+function spambot_check_spammer($request, $data, &$reasons) {
+  $email_threshold = variable_get('spambot_criteria_email', 1);
+  $username_threshold = variable_get('spambot_criteria_username', 0);
+  $ip_threshold = variable_get('spambot_criteria_ip', 20);
+  
+  if ($email_threshold > 0 && !empty($data['email']['appears']) && $data['email']['frequency'] >= $email_threshold) {
+    $reasons['email'] = t('email=@value', array('@value' => $request['email']));
+  }
+  if ($username_threshold > 0 && !empty($data['username']['appears']) && $data['username']['frequency'] >= $username_threshold) {
+    $reasons['name'] = t('username=@value', array('@value' => $request['username']));
+  }
+  if ($ip_threshold > 0 && !empty($data['ip']['appears']) && $data['ip']['frequency'] >= $ip_threshold) {
+    $reasons['ip'] = t('ip=@value', array('@value' => $request['ip']));
+  }
+
+  return (!empty($reasons));
+}
+
+/**
  * Invoke www.stopforumspam.com's api
  *
  * @param $query
@@ -266,7 +395,7 @@ function spambot_account_is_spammer($account) {
   $email_threshold = variable_get('spambot_criteria_email', 1);
   $username_threshold = variable_get('spambot_criteria_username', 0);
   $ip_threshold = variable_get('spambot_criteria_ip', 20);
-  
+ 
   // Build request parameters according to the criteria to use
   $request = array();
   if (!empty($account->mail) && $email_threshold > 0) {
@@ -277,19 +406,9 @@ function spambot_account_is_spammer($account) {
     $request['username'] = $account->name;
   }  
 
-  // Only do a remote API request if there is anything to check
-  if (count($request)) {
-    $data = array();
-    if (spambot_sfs_request($request, $data)) {
-      if (($email_threshold > 0 && !empty($data['email']['appears']) && $data['email']['frequency'] >= $email_threshold) ||
-          ($username_threshold > 0 && !empty($data['username']['appears']) && $data['username']['frequency'] >= $username_threshold)) {
-        return 1;
-      }
-    }
-    else {
-      // Return error
-      return -1;
-    }
+  $is_spammer = spambot_is_spammer($request, $reasons);
+  if ($is_spammer !== 0) {
+    return $is_spammer;
   }
 
   // Now check IP's
@@ -297,21 +416,16 @@ function spambot_account_is_spammer($account) {
   if ($ip_threshold > 0) {
     $ips = spambot_account_ip_addresses($account);
     foreach ($ips as $ip) {
-      // Skip the loopback interface
-      if ($ip == '127.0.0.1') {
+      // Skip the loopback interface and IPv6 addresses
+      if (($ip == '127.0.0.1') || (strpos($ip, ':') !== FALSE)) {
         continue;
       }
       
       $request = array('ip' => $ip);
-      $data = array();
-      if (spambot_sfs_request($request, $data)) {
-        if (!empty($data['ip']['appears']) && $data['ip']['frequency'] >= $ip_threshold) {
-          return 1;
-        }
-      }
-      else {
-        // Abort on error
-        return -1;
+      $reasons = array();
+      $is_spammer = spambot_is_spammer($request, $reasons);
+      if ($is_spammer !== 0) {
+        return $is_spammer;
       }
     }
   }
diff --git a/spambot.pages.inc b/spambot.pages.inc
index 67948e1..f5d8f64 100644
--- a/spambot.pages.inc
+++ b/spambot.pages.inc
@@ -139,8 +139,8 @@ function spambot_user_spam_admin_form_submit($form, &$form_state) {
     if (!$service_down) {
       $ips = spambot_account_ip_addresses($account);
       foreach ($ips as $ip) {
-        // Skip the loopback interface
-        if ($ip == '127.0.0.1') {
+        // Skip the loopback interface and IPv6 addresses
+        if (($ip == '127.0.0.1') || (strpos($ip, ':') !== FALSE)) {
           continue;
         }
         
