You are here

antispam.module in AntiSpam 7

Same filename and directory in other branches
  1. 6 antispam.module

Primary hook implementations for the Antispam module.

File

antispam.module
View source
<?php

/**
 * @file
 * Primary hook implementations for the Antispam module.
 */

/**
 * AntiSpam Drupal and Module versions.
 */
define('ANTISPAM_DRUPAL_VERSION', '7');
define('ANTISPAM_MODULE_VERSION', '1.0');
define('ANTISPAM_MODULE_HOMEURL', 'http://www.drupal.org/project/antispam');
define('ANTISPAM_MODULE_USERAGENT', 'Drupal/' . ANTISPAM_DRUPAL_VERSION . ' | antispam.module/' . ANTISPAM_MODULE_VERSION);

/**
 * AntiSpam API constants.
 */
define('ANTISPAM_AKISMET_API_HOST', 'rest.akismet.com');
define('ANTISPAM_AKISMET_API_PORT', 80);
define('ANTISPAM_AKISMET_API_VERSION', '1.1');
define('ANTISPAM_API_USERAGENT', 'Drupal/' . ANTISPAM_DRUPAL_VERSION . ' | antispam.module/' . ANTISPAM_MODULE_VERSION);
define('ANTISPAM_API_RESULT_ERROR', -1);
define('ANTISPAM_API_RESULT_SUCCESS', 0);
define('ANTISPAM_API_RESULT_IS_SPAM', 1);
define('ANTISPAM_API_RESULT_IS_HAM', 2);

/**
 * Service provider constants.
 */
define('ANTISPAM_AKISMET_SERVICE', 0);

// define('ANTISPAM_TYPEPAD_SERVICE', 1); // @obsolete
// define('ANTISPAM_DEFENSIO_SERVICE', 2); // @obsolete
define('ANTISPAM_COUNT_ALL', 0);
define('ANTISPAM_COUNT_SPAM_DETECTED', 1);
define('ANTISPAM_COUNT_HAM_DETECTED', 2);
define('ANTISPAM_COUNT_FALSE_NEGATIVE', 3);
define('ANTISPAM_COUNT_FALSE_POSITIVE', 4);

/**
 * Debug function.
 */
function antispam_debug($str) {
  if (is_array($str) || is_object($str)) {
    error_log(print_r($str, TRUE) . "\r\n", 3, "/tmp/antispam.txt");
  }
  else {
    error_log($str . "\r\n", 3, "/tmp/antispam.txt");
  }
}

/**
 * Implements hook_help().
 */
function antispam_help($path, $arg) {
  switch ($path) {
    case 'admin/help#antispam':
      $output = t('<p>In order to use the AntiSpam, you need a API key for the selected antispam service. If you don\'t have one already, you can get it by simply signing up for a free account at the following sites.</p>
<ul>
  <li><a href="!akismet">akismet.com</a> for Akismet service
</ul>
<p>The <em>antispam module</em> may automatically check for spam posted in content (nodes and/or comments) by any user, except node or comment administrators respectively. It is also possible, from the <a href="!permission">Permission</a> panel, to grant <em>%no-check-perm</em> permission to <em>user roles</em> of your choice.</p>
<p>Content marked as <em>spam</em> is still saved into database so it can be reviewed by content administrators. There is <a href="!antispam-settings">an option</a> that allows you to specify how long this information will be kept in the database. <em>Spam</em> older than a specified age will be automatically removed. Requires crontab.</p>
<p>Automatic spam detection can be enabled or disabled by content type and/or comments. In addition to this, the antspam service makes it easy for <em>content administrators</em> to manually <em>publish</em>/<em>unpublish</em> content and <em>mark</em>/<em>unmark</em> content as spam, from links available at the bottom of content.</p>', array(
        '!akismet' => url('http://akismet.com'),
        '!antispam-settings' => url('admin/config/spamprevention/antispam'),
        '!permission' => url('admin/people/permissions'),
        '%no-check-perm' => t('post with no antispam checking'),
      ));
      return $output;
    case 'admin/help/antispam':
    case 'admin/config/spamprevention/antispam':
      $output = t('<p>The <a href="!antispam-module-home">AntiSpam module</a> for <a href="!drupal">Drupal</a> allows you to use the <a href="!akismet">Akismet</a> anti-spam service to protect your site from being spammed.</p>', array(
        '!antispam-module-home' => url(ANTISPAM_MODULE_HOMEURL),
        '!drupal' => url('http://drupal.org'),
        '!akismet' => url('http://akismet.com'),
      ));
      $output .= t('<p>AntiSpam has caught <strong>@count spam</strong> for you since %since.</p>', array(
        '@count' => antispam_get_total_counter(ANTISPAM_COUNT_SPAM_DETECTED),
        '%since' => antispam_get_counting_since(),
      ));
      return $output;
    case 'admin/content/antispam/nodes/unpublished':
      $output = t('Below is the list of <strong>unpublished nodes</strong> awaiting for moderation.');
      $output .= ' ' . t('Click on the titles to see the content of the nodes or the author\'s name to view the author\'s user information. You may also wish to click on the headers to order the nodes upon your needs.');
      break;
    case 'admin/content/antispam/nodes/published':
      $output = t('Below is the list of <strong>published nodes</strong>.');
      $output .= ' ' . t('Click on the titles to see the content of the nodes or the author\'s name to view the author\'s user information. You may also wish to click on the headers to order the nodes upon your needs.');
      break;
    case 'admin/content/antispam/nodes':

      // spam
      $output = t('Below is the list of <strong>nodes marked as spam</strong> awaiting for moderation.');
      $output .= ' ' . t('Click on the titles to see the content of the nodes or the author\'s name to view the author\'s user information. You may also wish to click on the headers to order the nodes upon your needs.');
      break;
    case 'admin/content/antispam/comments/unpublished':
      $output = t('Below is the list of <strong>unpublished comments</strong> awaiting for moderation.');
      $output .= ' ' . t('Click on the subjects to see the comments or the author\'s name to view the author\'s user information. You may also wish to click on the headers to order the comments upon your needs.');
      break;
    case 'admin/content/antispam/comments/published':
      $output = t('Below is the list of <strong>published comments</strong>.');
      $output .= ' ' . t('Click on the subjects to see the comments or the author\'s name to view the author\'s user information. You may also wish to click on the headers to order the comments upon your needs.');
      break;
    case 'admin/content/antispam/comments':

      // spam
      $output = t('Below is the list of <strong>comments marked as spam</strong> awaiting for moderation.');
      $output .= ' ' . t('Click on the subjects to see the comments or the author\'s name to view the author\'s user information. You may also wish to click on the headers to order the comments upon your needs.');
      break;
  }
  if (arg(0) == 'admin' && arg(1) == 'content ' && arg(2) == 'antispam' && !isset($_POST) && !empty($output)) {
    $output .= '<br />';
    $output .= t('<strong>Note:</strong> To interact fully with the antispam service, you really should try putting data back into the system as well as just taking it out. If it is at all possible, please use the submit <em>ham</em> operation rather than simply publishing content that was identified as spam (false positives). This is necessary in order to let the antispam service learn from its mistakes. Thank you.');
  }
}

/**
 * Implements hook_permission().
 */
function antispam_permission() {
  $permissions = array(
    'administer antispam settings' => array(
      'title' => t('Administer AntiSpam module'),
      'description' => '',
    ),
  );
  $names = node_type_get_names();
  foreach ($names as $type => $name) {
    $index = 'moderate spam in nodes of type ' . $name;
    $permissions[$index] = array(
      'title' => t('Moderate spam in nodes of type !type', array(
        '!type' => $name,
      )),
      'description' => '',
    );
  }
  $permissions['moderate spam in comments'] = array(
    'title' => t('Moderate spam in comments'),
    'description' => '',
  );
  $permissions['post with no antispam checking'] = array(
    'title' => t('Post with no spam checking'),
    'description' => 'Allow to post nodes/comments without being checked by AntiSpam module. ',
  );
  return $permissions;
}

/**
 * Implements hook_cron().
 */
function antispam_cron() {
  module_load_include('inc', 'antispam', 'antispam.cron');
  module_load_include('inc', 'comment', 'comment.admin');
  antispam_cron_shutdown();
}

/**
 * Obtain current service provider.
 */
function antispam_get_service_provider() {
  return variable_get('antispam_service_provider', 0);
}

/**
 * Obtain service provider name.
 */
function antispam_get_provider_name($provider, $with_link = FALSE) {
  switch ($provider) {
    case ANTISPAM_AKISMET_SERVICE:
      return $with_link ? t('<a href="http://akismet.com">Akismet</a>') : t('Akismet');
    default:
      return '';
  }
}

/**
 * Obtain API HOST name for the specified service provider.
 */
function antispam_get_api_host($provider) {
  switch ($provider) {
    case ANTISPAM_AKISMET_SERVICE:
      $api_host = ANTISPAM_AKISMET_API_HOST;
      break;
  }
  return $api_host;
}

/**
 * Obtain API key for the specified service provider.
 */
function antispam_get_api_key($provider) {
  switch ($provider) {
    case ANTISPAM_AKISMET_SERVICE:
      $api_key = variable_get('antispam_wpapikey', '');
      break;
  }
  return $api_key;
}

/**
 * Get max counter value for the specified counter type.
 */
function antispam_get_max_counter($counter_type = '') {
  $rec = db_query("SELECT MAX(spam_detected) AS max_spam, MAX(ham_detected) AS max_ham, MAX(false_negative) AS max_fnegative, MAX(false_positive) AS max_fpositive FROM {antispam_counter}")
    ->fetchObject();
  if ($rec->max_spam == '') {
    $rec->max_spam = 0;
  }
  if ($rec->max_ham == '') {
    $rec->max_ham = 0;
  }
  if ($rec->max_fnegative == '') {
    $rec->max_fnegative = 0;
  }
  if ($rec->max_fpositive == '') {
    $rec->max_fpositive = 0;
  }

  // Returns an array of all totals.
  if (empty($counter_type)) {
    return array(
      'max_spam' => $rec->max_spam,
      'max_ham' => $rec->max_ham,
      'max_fnegative' => $rec->max_fnegative,
      'max_fpositive' => $rec->max_fpositive,
    );
  }
  switch ($counter_type) {
    case ANTISPAM_COUNT_ALL:
      return $rec->max_spam + $rec->max_ham;
    case ANTISPAM_COUNT_SPAM_DETECTED:
      return $rec->max_spam;
    case ANTISPAM_COUNT_HAM_DETECTED:
      return $rec->max_ham;
    case ANTISPAM_COUNT_FALSE_NEGATIVE:
      return $rec->max_fnegative;
    case ANTISPAM_COUNT_FALSE_POSITIVE:
      return $rec->max_fpositive;

    // Just in case.
    default:
      return 0;
  }
}

/**
 * Get total counter value for the specified counter type.
 */
function antispam_get_total_counter($counter_type = '') {
  $rec = db_query("SELECT SUM(spam_detected) AS total_spam, SUM(ham_detected) AS total_ham, SUM(false_negative) AS total_fnegative, SUM(false_positive) AS total_fpositive FROM {antispam_counter}")
    ->fetchObject();
  if ($rec->total_spam == '') {
    $rec->total_spam = 0;
  }
  if ($rec->total_ham == '') {
    $rec->total_ham = 0;
  }
  if ($rec->total_fnegative == '') {
    $rec->total_fnegative = 0;
  }
  if ($rec->total_fpositive == '') {
    $rec->total_fpositive = 0;
  }

  // Returns an array of all totals.
  if (empty($counter_type)) {
    return array(
      'total_spam' => $rec->total_spam,
      'total_ham' => $rec->total_ham,
      'total_fnegative' => $rec->total_fnegative,
      'total_fpositive' => $rec->total_fpositive,
    );
  }
  switch ($counter_type) {
    case ANTISPAM_COUNT_ALL:
      return $rec->total_spam + $rec->total_ham;
    case ANTISPAM_COUNT_SPAM_DETECTED:
      return $rec->total_spam;
    case ANTISPAM_COUNT_HAM_DETECTED:
      return $rec->total_ham;
    case ANTISPAM_COUNT_FALSE_NEGATIVE:
      return $rec->total_fnegative;
    case ANTISPAM_COUNT_FALSE_POSITIVE:
      return $rec->total_fpositive;

    // Just in case.
    default:
      return 0;
  }
}

/**
* Get today's counter value for the specified counter type.
*/
function antispam_get_counter($counter_type) {
  $rec = db_query("SELECT * FROM {antispam_counter} WHERE date=:date", array(
    ':date' => mktime(0, 0, 0, date("m"), date("d"), date("Y")),
  ))
    ->fetchObject();

  // No counter record for today.
  if (empty($rec)) {
    return 0;
  }
  switch ($counter_type) {
    case ANTISPAM_COUNT_ALL:
      return $rec->spam_detected + $rec->ham_detected;
    case ANTISPAM_COUNT_SPAM_DETECTED:
      return $rec->spam_detected;
    case ANTISPAM_COUNT_HAM_DETECTED:
      return $rec->ham_detected;
    case ANTISPAM_COUNT_FALSE_NEGATIVE:
      return $rec->false_negative;
    case ANTISPAM_COUNT_FALSE_POSITIVE:
      return $rec->false_positive;

    // Just in case.
    default:
      return 0;
  }
}

/**
 * Set today's counter value for the specified counter type.
 */
function antispam_set_counter($counter_type, $count) {
  switch ($counter_type) {
    case ANTISPAM_COUNT_ALL:
      return;
    case ANTISPAM_COUNT_SPAM_DETECTED:
      $field = 'spam_detected';
      break;
    case ANTISPAM_COUNT_HAM_DETECTED:
      $field = 'ham_detected';
      break;
    case ANTISPAM_COUNT_FALSE_NEGATIVE:
      $field = 'false_negative';
      break;
    case ANTISPAM_COUNT_FALSE_POSITIVE:
      $field = 'false_positive';
      break;
    default:
      return;
  }
  $today = mktime(0, 0, 0, date("m"), date("d"), date("Y"));
  $result = db_query("SELECT * FROM {antispam_counter} WHERE date=:date", array(
    ':date' => $today,
  ));
  if ($result
    ->rowCount()) {

    // Update.
    db_update('antispam_counter')
      ->fields(array(
      $field => $count,
    ))
      ->condition('date', $today)
      ->execute();
  }
  else {

    // Insert.
    $provider = antispam_get_service_provider();
    db_insert('antispam_counter')
      ->fields(array(
      'date' => strtotime(date('Y-m-d 00:00:00')),
      'provider' => $provider,
      $field => $count,
    ))
      ->execute();
  }
}

/**
 * Increase today's counter value for the specified counter type by 1.
 */
function antispam_increase_counter($counter_type) {
  antispam_set_counter($counter_type, antispam_get_counter($counter_type) + 1);
}

/**
 *
 */
function _antispam_moderator_types_count($types = array()) {
  if (empty($types)) {
    $types = antispam_get_moderator_types();
  }
  return count($types);
}

/**
 *
 */
function _antispam_is_moderator($moderator_types = array(), $type = '') {
  global $user;

  // Admin.
  if ($user->uid == 1) {
    return TRUE;
  }
  if (empty($moderator_types)) {
    $moderator_types = antispam_get_moderator_types();
  }
  if (_antispam_moderator_types_count($moderator_types) > 0 && (empty($type) || isset($moderator_types[$type]))) {
    return TRUE;
  }
  else {
    return FALSE;
  }
}

/**
*
*/
function _antispam_is_node_moderator($moderator_types = array()) {
  global $user;

  // Admin.
  if ($user->uid == 1) {
    return TRUE;
  }
  if (empty($moderator_types)) {
    $moderator_types = antispam_get_moderator_types();
  }
  if (_antispam_is_moderator($moderator_types) && (!_antispam_is_moderator($moderator_types, $type = 'comments') || _antispam_moderator_types_count($moderator_types) > 1)) {
    return TRUE;
  }
  else {
    return FALSE;
  }
}

/**
 *
 */
function _antispam_is_moderator_type($type, $types = array()) {
  if (empty($types)) {
    $types = antispam_get_moderator_types();
  }
  if (_antispam_moderator_types_count($types) > 0 && isset($types[$type])) {
    return TRUE;
  }
  else {
    return FALSE;
  }
}

/**
 * Implements hook_menu().
 */
function antispam_menu() {
  $items = array();
  $moderator_types = antispam_get_moderator_types();

  // New menu block.
  $items['admin/config/spamprevention'] = array(
    'title' => 'AntiSpam',
    'description' => 'Use the anti-spam service to protect your site from spam.',
    'position' => 'right',
    'page callback' => 'system_admin_menu_block_page',
    'access arguments' => array(
      'access administration pages',
    ),
    'file' => 'system.admin.inc',
    'file path' => drupal_get_path('module', 'system'),
  );

  // A menu item for the menu block.
  $items['admin/config/spamprevention/antispam'] = array(
    'title' => t('AntiSpam'),
    'description' => t('Use the anti-spam service to protect your site from spam.'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'antispam_settings_form',
    ),
    'access arguments' => array(
      'administer antispam settings',
    ),
    'file' => 'antispam.admin.inc',
    // Needs to be normal to show up.
    'type' => MENU_NORMAL_ITEM,
  );
  $items['admin/content/antispam/overview'] = array(
    'title' => 'Overview',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => 0,
    'file' => 'antispam.admin.inc',
  );
  $items['admin/content/antispam/nodes'] = array(
    'title' => 'Nodes',
    'page callback' => 'antispam_callback_queue',
    'page arguments' => array(
      'nodes',
    ),
    'access callback' => '_antispam_is_node_moderator',
    'access arguments' => array(
      $moderator_types,
    ),
    'type' => MENU_LOCAL_TASK,
    'weight' => 1,
    'file' => 'antispam.admin.inc',
  );
  $items['admin/content/antispam/nodes/spam'] = array(
    'title' => 'Spam',
    'page callback' => 'antispam_callback_queue',
    'page arguments' => array(
      'nodes',
    ),
    'access callback' => '_antispam_is_node_moderator',
    'access arguments' => array(
      $moderator_types,
    ),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => 0,
    'file' => 'antispam.admin.inc',
  );
  $items['admin/content/antispam/nodes/unpublished'] = array(
    'title' => 'Unpublished nodes',
    'page callback' => 'antispam_callback_queue',
    'page arguments' => array(
      'nodes',
      'unpublished',
    ),
    'access callback' => '_antispam_is_node_moderator',
    'access arguments' => array(
      $moderator_types,
    ),
    'type' => MENU_LOCAL_TASK,
    'weight' => 1,
    'file' => 'antispam.admin.inc',
  );
  $items['admin/content/antispam/nodes/published'] = array(
    'title' => 'Published nodes',
    'page callback' => 'antispam_callback_queue',
    'page arguments' => array(
      'nodes',
      'published',
    ),
    'access callback' => '_antispam_is_node_moderator',
    'access arguments' => array(
      $moderator_types,
    ),
    'type' => MENU_LOCAL_TASK,
    'weight' => 2,
    'file' => 'antispam.admin.inc',
  );
  if (module_exists('comment')) {
    $items['admin/content/antispam/comments'] = array(
      'title' => 'Comments',
      'page callback' => 'antispam_callback_queue',
      'page arguments' => array(
        'comments',
      ),
      'access callback' => '_antispam_is_moderator',
      'access arguments' => array(
        $moderator_types,
        'comments',
      ),
      'type' => MENU_LOCAL_TASK,
      'weight' => 2,
      'file' => 'antispam.admin.inc',
    );
    $items['admin/content/antispam/comments/spam'] = array(
      'title' => 'Spam',
      'page callback' => 'antispam_callback_queue',
      'page arguments' => array(
        'comments',
      ),
      'access callback' => '_antispam_is_moderator',
      'access arguments' => array(
        $moderator_types,
        'comments',
      ),
      'type' => MENU_DEFAULT_LOCAL_TASK,
      'weight' => 0,
      'file' => 'antispam.admin.inc',
    );
    $items['admin/content/antispam/comments/unpublished'] = array(
      'title' => 'Unpublished comments',
      'page callback' => 'antispam_callback_queue',
      'page arguments' => array(
        'comments',
        'unpublished',
      ),
      'access callback' => '_antispam_is_moderator',
      'access arguments' => array(
        $moderator_types,
        'comments',
      ),
      'type' => MENU_LOCAL_TASK,
      'weight' => 1,
      'file' => 'antispam.admin.inc',
    );
    $items['admin/content/antispam/comments/published'] = array(
      'title' => 'Published comments',
      'page callback' => 'antispam_callback_queue',
      'page arguments' => array(
        'comments',
        'published',
      ),
      'access callback' => '_antispam_is_moderator',
      'access arguments' => array(
        $moderator_types,
        'comments',
      ),
      'type' => MENU_LOCAL_TASK,
      'weight' => 2,
      'file' => 'antispam.admin.inc',
    );
  }
  $items['admin/content/antispam/statistics'] = array(
    'title' => 'Statistics',
    'page callback' => 'antispam_callback_queue',
    'page arguments' => array(
      'statistics',
    ),
    'access callback' => '_antispam_is_moderator',
    'access arguments' => array(
      $moderator_types,
    ),
    'type' => MENU_LOCAL_TASK,
    'weight' => 3,
    'file' => 'antispam.admin.inc',
  );
  $item = array(
    'title' => 'switch content status',
    'page callback' => 'antispam_page',
    'page arguments' => array(
      0,
      1,
      2,
      3,
    ),
    'load arguments' => array(
      '%map',
      '%index',
    ),
    'access callback' => 'antispam_access_callback',
    'access arguments' => array(
      0,
      1,
      2,
      3,
    ),
  );
  foreach (array(
    'publish',
    'unpublish',
    'submit-spam',
    'submit-ham',
  ) as $op) {
    $items['antispam/%antispam/%/' . $op] = $item;
  }
  return $items;
}

/**
 * Implements hook_load().
 */
function antispam_load($arg, &$map, $index) {
  if (!is_numeric($map[2])) {

    // Node and comment ids are always numeric!
    return FALSE;
  }
  $content_type = $map[1];
  if ($content_type == 'node') {
    if (!($map[2] = node_load($map[2]))) {
      return FALSE;
    }
  }
  if ($content_type == 'comment') {
    if (!($map[2] = comment_load($map[2]))) {
      return FALSE;
    }
  }
  $op = $map[3];
  if ($op == 'publish' || $op == 'unpublish') {
    $map[0] = 'antispam_callback_set_published_status';
  }
  elseif ($op == 'submit-spam' || $op == 'submit-ham') {
    $map[0] = 'antispam_callback_set_spam_status';
  }
  return $map[$index];
}

/**
 *
 */
function antispam_access_callback($callback, $content_type, $object, $op) {
  if ($content_type == 'node' && !node_access('update', $object)) {
    return FALSE;
  }
  $content_id = '';
  if ($content_type == 'node') {
    $content_id = $object->nid;
  }
  elseif ($content_type == 'comment') {
    $content_id = $object->cid;
  }
  if (function_exists($callback && !antispam_is_spam_moderator(antispam_content_get_moderator_type($content_type, $content_id)))) {
    return FALSE;
  }

  // Is there a comment access check we need to run? If yes, then do the same
  // as above.
  return TRUE;
}

/**
 * Menu callback to build a page for a specific module callback.
 */
function antispam_page($callback, $content_type, $object, $op) {
  if (function_exists($callback)) {
    return $callback($content_type, $object, $op);
  }
  drupal_not_found();
}

/**
 * Menu callback; publish/unpublish content.
 *
 * @param string $content_type
 *   Can be 'node' or 'comment'.
 * @param object $object
 *   Can be either a nid or a cid.
 * @param string $op
 *   The operation being performed, can be 'publish' or 'unpublish'.
 */
function antispam_callback_set_published_status($content_type, $object, $op) {

  // TODO: Should we be passing the object around or just the ID, or should we
  // have antispam_content_load at all?
  if ($content_type == 'node') {

    // Load the content (existence has been checked in hook_menu).
    $content = antispam_content_load($content_type, $object->nid);
    $is_published = $content->status ? TRUE : FALSE;
  }
  else {

    // Load the content (existence has been checked in hook_menu).
    $content = antispam_content_load($content_type, $object->cid);
    $is_published = $content->status == COMMENT_PUBLISHED ? TRUE : FALSE;
  }
  if ($op == 'publish' && !$is_published) {
    antispam_content_publish_operation($content_type, $content, 'publish');
  }
  elseif ($op == 'unpublish' && $is_published) {
    antispam_content_publish_operation($content_type, $content, 'unpublish');
  }
  if ($content_type == 'node') {
    drupal_goto('node/' . $content->nid);
  }
  else {

    // comment
    drupal_goto('node/' . $content->nid, array(
      'fragment' => 'comment-' . $content->cid,
    ));
  }
}

/**
 * Menu callback; mark/unmark content as spam.
 *
 * When content is marked as spam, it is also unpublished (if necessary) and
 * vice-versa.
 *
 * @param string Content type; it can be 'node' or 'comment' .
 * @param integer Content ID; can be either a nid or a cid.
 * @param string Operation; it can be 'submit-spam' or 'submit-ham' .
 */
function antispam_callback_set_spam_status($content_type, $object, $op) {
  if ($content_type == 'node') {
    $is_spam = antispam_content_is_spam($content_type, $object->nid);

    // Load the content (existence has been checked in hook_menu).
    $content = antispam_content_load($content_type, $object->nid);
    $is_published = $content->status ? TRUE : FALSE;
  }
  else {
    $is_spam = antispam_content_is_spam($content_type, $object->cid);

    // Load the content (existence has been checked in hook_menu).
    $content = antispam_content_load($content_type, $object->cid);
    $is_published = $content->status == COMMENT_PUBLISHED ? TRUE : FALSE;
  }

  // Insert or remove the spam marker (publishing/unpublishing if necessary).
  if ($op == 'submit-spam') {
    if (!$is_spam) {
      antispam_content_spam_operation($content_type, $content, 'submit-spam');
      antispam_increase_counter(ANTISPAM_COUNT_FALSE_NEGATIVE);
    }
    if ($is_published) {
      antispam_content_publish_operation($content_type, $content, 'unpublish');
    }
  }
  elseif ($op == 'submit-ham') {
    if ($is_spam) {
      antispam_content_spam_operation($content_type, $content, 'submit-ham');
      antispam_increase_counter(ANTISPAM_COUNT_FALSE_POSITIVE);
    }
    if (!$is_published) {
      antispam_content_publish_operation($content_type, $content, 'publish');
    }
  }
  if ($content_type == 'node') {
    drupal_goto('node/' . $content->nid);
  }
  else {
    drupal_goto('node/' . $content->nid, array(
      'fragment' => 'comment-' . $content->cid,
    ));
  }
}

/**
 * Webform: Check submitted values for spam.
 */
function antispam_webform_check($form, &$form_state) {
  if (variable_get('antispam_webform_enabled', FALSE)) {
    foreach ($form_state['values']['submitted'] as $value) {
      if (is_string($value) && !is_numeric($value) && strlen($value) > 5 && antispam_api_cmd_spam_check($value) === ANTISPAM_API_RESULT_IS_SPAM) {
        watchdog('spam detected', "Spam detected in webform value '%value'", array(
          '%value' => $value,
        ), WATCHDOG_NOTICE);
        form_error($form_state['clicked_button'], t('Invalid input'));
        return;
      }
    }
  }
}

/*******************************************************************************
 * Node operations
 ******************************************************************************/

/**
 * Implements hook_node_load().
 */
function antispam_node_load($nodes, $types) {
  foreach ($nodes as $node) {
    $rec = db_query("SELECT signature, spaminess FROM {antispam_spam_marks} WHERE content_type='node' AND content_id=:content_id", array(
      ':content_id' => $node->nid,
    ))
      ->fetchObject();
    if ($rec) {
      $node->signature = $rec->signature;
      $node->spaminess = $rec->spaminess;
    }
    else {
      $node->signature = '';
      $node->spaminess = 0.0;
    }
  }
}

/**
 * Implements hook_node_insert().
 */
function antispam_node_insert($node) {
  _antispam_node_save($node);
}

/**
 * Implements hook_node_update().
 */
function antispam_node_update($node) {
  _antispam_node_save($node);
}

/**
 * Called from hook_node_insert() and hook_node_update()
 */
function _antispam_node_save(&$node) {

  // If anti-spam servie connections are not enabled, we have nothing else to
  // do here.
  if (!variable_get('antispam_connection_enabled', 1)) {
    antispam_notify_moderators('node', $node, $node->status ? TRUE : FALSE, FALSE);
    return;
  }

  // Also quit ASAP, if current user has administration permission or
  // permission to post without spam checking.
  if (antispam_is_spam_moderator($node->type) || user_access('post with no antispam checking')) {
    antispam_notify_moderators('node', $node, $node->status ? TRUE : FALSE, FALSE);
    return;
  }

  // Now, check if it's about a node type that we have not been explicitly
  // requested to check.
  $check_nodetypes = variable_get('antispam_check_nodetypes', array());
  if (!is_array($check_nodetypes) || !isset($check_nodetypes[$node->type]) || !$check_nodetypes[$node->type]) {
    antispam_notify_moderators('node', $node, $node->status ? TRUE : FALSE, FALSE);
    return;
  }

  // Ok, let's send a query to anti-spam service.
  $api_result = antispam_api_cmd_comment_check('node', $node);
  if ($api_result[0] == ANTISPAM_API_RESULT_IS_HAM) {
    antispam_notify_moderators('node', $node, $node->status ? TRUE : FALSE, FALSE);
    antispam_increase_counter(ANTISPAM_COUNT_HAM_DETECTED);
  }
  else {
    if ($api_result[0] == ANTISPAM_API_RESULT_IS_SPAM) {
      $node->signature = $api_result[1];
      $node->spaminess = $api_result[2];
      antispam_increase_counter(ANTISPAM_COUNT_SPAM_DETECTED);
      antispam_notify_moderators('node', $node, FALSE, TRUE);
    }
    else {
      antispam_notify_moderators('node', $node, FALSE, FALSE);
    }

    // Unpublish the node, if necessary.
    if ($node->status) {
      antispam_content_publish_operation('node', $node, 'unpublish', FALSE);
    }

    // Since users won't see their content published, show them a polite
    // explanation on why.
    $content_type_name = node_type_get_name($node);
    drupal_set_message(t('Your %content-type-name has been queued for moderation by site administrators and will be published after approval.', array(
      '%content-type-name' => $content_type_name,
    )));

    // Record the event to watchdog.
    if ($api_result[0] == ANTISPAM_API_RESULT_ERROR) {
      watchdog('content', 'AntiSpam service seems to be down, %content-type-name queued for manual approval: %title', array(
        '%content-type-name' => $content_type_name,
        '%title' => $node->title,
      ), WATCHDOG_WARNING, l(t('view'), 'node/' . $node->nid));
    }
    else {
      watchdog('content', 'Spam detected by AntiSpam in %content-type-name: %title', array(
        '%content-type-name' => $content_type_name,
        '%title' => $node->title,
      ), WATCHDOG_WARNING, l(t('view'), 'node/' . $node->nid));

      // If requested to, generate a delay so the spammer has to wait for a
      // while.
      if (($seconds = variable_get('antispam_antispambot_delay', 60)) > 0) {
        sleep($seconds);
      }
    }
  }
}

/**
 * Implements hook_node_validate().
 */
function antispam_node_validate($node, $form, &$form_state) {
  global $user;
  $num_condition = 0;
  $debug_info = array();

  // Ok, let's build a quick query to see if we can catch a spambot.
  $antispambot_rules = antispam_get_anti_spambot_rules();
  $query = db_select('antispam_spam_marks', 's');
  if (!empty($antispambot_rules['body'])) {
    $query
      ->join('field_data_body', 'e', 'e.entity_id = s.content_id');
  }
  $query
    ->fields('s', array(
    'content_id',
  ));
  if (!empty($antispambot_rules['body'])) {
    $query
      ->addField('e', 'body_value', 'body');
  }
  $query
    ->condition('s.content_type', 'node');
  $query
    ->range(0, 1);
  if (!empty($antispambot_rules['ip'])) {
    $query
      ->condition('s.hostname', ip_address());
    $debug_info['IP-address'] = ip_address();
    $num_condition++;
  }
  if (!empty($antispambot_rules['mail']) && !empty($user->mail)) {
    $query
      ->condition('s.mail', $user->mail);
    $debug_info['E-mail'] = $user->mail;
    $num_condition++;
  }
  if (!empty($antispambot_rules['body']) && !empty($node->body)) {
    $query
      ->condition('e.body_value', $node->body[$node->language][0]['value']);
    $debug_info['Content'] = $node->body[$node->language][0]['value'];
    $num_condition++;
  }
  if ($num_condition) {
    $has_rows = $query
      ->execute()
      ->fetchField();
    if ($has_rows) {
      antispam_anti_spambot_action($debug_info);
    }
  }
}

/**
 * Implements hook_node_delete().
 */
function antispam_node_delete($node) {
  db_delete('antispam_spam_marks')
    ->condition('content_type', 'node')
    ->condition('content_id', $node->nid)
    ->execute();
}

/**
 * Implements hook_node_view().
 */
function antispam_node_view($node, $view_mode, $langcode) {

  // $view_mode: 'full', 'teaser'
  if (antispam_is_spam_moderator($node->type)) {
    $links = array();

    // Adding publish/unpublish links.
    if (variable_get('antispam_node_publish_links', 0)) {
      if ($node->status) {
        $links['antispam_node_unpublish'] = array(
          'title' => t('Unpublish'),
          'href' => 'antispam/node/' . $node->nid . '/unpublish',
        );
      }
      else {
        $links['antispam_node_publish'] = array(
          'title' => t('Publish'),
          'href' => 'antispam/node/' . $node->nid . '/publish',
        );
      }
    }

    // Adding submit ham/submit spam links.
    if (variable_get('antispam_node_spam_links', 0)) {
      if (antispam_content_is_spam('node', $node->nid)) {
        $links['antispam_node_ham'] = array(
          'title' => t('Not Spam'),
          'href' => 'antispam/node/' . $node->nid . '/submit-ham',
        );
      }
      else {
        $links['antispam_node_spam'] = array(
          'title' => t('Spam'),
          'href' => 'antispam/node/' . $node->nid . '/submit-spam',
        );
      }
    }
    $node->content['links']['node']['#links'] = array_merge($node->content['links']['node']['#links'], $links);
  }
}

/*******************************************************************************
 * Comment operations
 ******************************************************************************/

/**
 * Implements hook_comment_load().
 *
 * Add signature and spaminess to the $comments object.
 */
function antispam_comment_load($comments) {
  foreach ($comments as $comment) {
    $rec = db_query("SELECT signature, spaminess FROM {antispam_spam_marks} WHERE content_type='comment' AND content_id=:content_id", array(
      ':content_id' => $comment->cid,
    ))
      ->fetchObject();
    if ($rec) {
      $comment->signature = $rec->signature;
      $comment->spaminess = $rec->spaminess;
    }
    else {
      $comment->spaminess = 0.0;
    }
  }
}

/**
 * Implements hook_comment_presave()
 */
function antispam_comment_presave($comment) {

  // NOTE: Called from comment_save().
  //
  // $comment->cid is not yet determined at this moment for a new comment.
  // If this is updating existing comment, then cid is already assigned.
  $num_condition = 0;
  $debug_info = array();

  // Ok, let's build a quick query to see if we can catch a spambot.
  $antispambot_rules = antispam_get_anti_spambot_rules();
  $query = db_select('antispam_spam_marks', 's');
  $query
    ->fields('s', array(
    'content_id',
  ));
  $query
    ->condition('s.content_type', 'comment');
  $query
    ->range(0, 1);
  $rules_conditions = db_or();
  if (!empty($antispambot_rules['ip'])) {
    $rules_conditions
      ->condition('s.hostname', ip_address());
    $debug_info['IP-address'] = ip_address();
    $num_condition++;
  }
  if (!empty($antispambot_rules['mail']) && !empty($comment->mail)) {
    $rules_conditions
      ->condition('s.mail', $comment->mail);
    $debug_info['E-mail'] = $comment->mail;
    $num_condition++;
  }
  if (!empty($antispambot_rules['body']) && !empty($comment->comment_body)) {
    if ($num_condition) {
      $join_type = 'LEFT OUTER';
    }
    else {
      $join_type = 'INNER';
    }
    $query
      ->addJoin($join_type, 'field_data_comment_body', 'c', 's.content_type = c.entity_type AND s.content_id = c.entity_id');
    $rules_conditions
      ->condition('c.comment_body_value', $comment->comment_body[$comment->language][0]['value']);
    $debug_info['Content'] = $comment->comment_body[$comment->language][0]['value'];
    $num_condition++;
  }
  if ($num_condition) {
    $query
      ->condition($rules_conditions);
    $has_rows = $query
      ->execute()
      ->fetchField();
    if ($has_rows) {
      antispam_anti_spambot_action($debug_info);
    }
  }
}

/**
 * Implements hook_comment_insert().
 */
function antispam_comment_insert($comment) {
  _antispam_comment_save($comment);
}

/**
 * Implements hook_comment_update().
 */
function antispam_comment_update($comment) {
  _antispam_comment_save($comment);
}

/**
 * Called from hook_comment_insert() and hook_comment_update().
 */
function _antispam_comment_save($comment) {

  // If anti-spam servie connections are not enabled, we have nothing else to
  // do here.
  if (!variable_get('antispam_connection_enabled', 1)) {
    antispam_notify_moderators('node', $node, $node->status ? TRUE : FALSE, FALSE);
    return;
  }

  // Also quit asap, if current user has administration permission or
  // permission to post without spam checking.
  if (antispam_is_spam_moderator('comments') || user_access('post with no antispam checking')) {
    antispam_notify_moderators('comment', $comment, $comment->status == COMMENT_PUBLISHED ? TRUE : FALSE, FALSE);
    return;
  }

  // Ok, let's send a query to anti-spam service.
  $api_result = antispam_api_cmd_comment_check('comment', $comment);
  if ($api_result[0] == ANTISPAM_API_RESULT_IS_HAM) {
    watchdog('antispam', '_antispam_comment_save: it is HAM');
    antispam_notify_moderators('comment', $comment, $comment->status == COMMENT_PUBLISHED ? TRUE : FALSE, FALSE);
    antispam_increase_counter(ANTISPAM_COUNT_HAM_DETECTED);
  }
  else {
    if ($api_result[0] == ANTISPAM_API_RESULT_IS_SPAM) {
      watchdog('antispam', '_antispam_comment_save: it is SPAM');
      $comment->signature = $api_result[1];
      $comment->spaminess = $api_result[2];
      antispam_increase_counter(ANTISPAM_COUNT_SPAM_DETECTED);
      antispam_notify_moderators('comment', $comment, FALSE, TRUE);
    }
    else {
      watchdog('antispam', '_antispam_comment_save: it is ERROR');
      antispam_notify_moderators('comment', $comment, FALSE, FALSE);
    }

    // Unpublish the comment, if necessary.
    if ($comment->status == COMMENT_PUBLISHED) {
      antispam_content_publish_operation('comment', $comment, 'unpublish', FALSE);
    }

    // Record the event to watchdog.
    if ($api_result[0] == ANTISPAM_API_RESULT_ERROR) {
      watchdog('content', 'AntiSpam service seems to be down, comment queued for manual approval: %subject', array(
        '%subject' => $comment->subject,
      ), WATCHDOG_WARNING, l(t('view'), 'node/' . $comment->nid, array(
        'fragment' => 'comment-' . $comment->cid,
      )));
    }
    else {
      watchdog('content', 'Spam detected by AntiSpam in comment: %subject', array(
        '%subject' => $comment->subject,
      ), WATCHDOG_WARNING, l(t('view'), 'node/' . $comment->nid, array(
        'fragment' => 'comment-' . $comment->cid,
      )));

      // If requested to, generate a delay so the spammer has to wait for a
      // while.
      if (($seconds = variable_get('antispam_antispambot_delay', 60)) > 0) {
        sleep($seconds);
      }
    }
  }
}

/**
 * Implements hook_comment_delete().
 */
function antispam_comment_delete($comment) {
  db_delete('antispam_spam_marks')
    ->condition('content_type', 'comment')
    ->condition('content_id', $comment->cid)
    ->execute();
}

/**
 * Implements hook_comment_view().
 */
function antispam_comment_view($comment, $view_mode, $langcode) {

  // $view_mode: 'full', 'teaser'
  if (antispam_is_spam_moderator('comments')) {
    $links = array();
    if (variable_get('antispam_comment_publish_links', 1)) {
      if ($comment->status == COMMENT_PUBLISHED) {
        $links['antispam_comment_unpublish'] = array(
          'title' => t('Unpublish'),
          'href' => 'antispam/comment/' . $comment->cid . '/unpublish',
        );
      }
      elseif ($comment->status == COMMENT_NOT_PUBLISHED) {
        $links['antispam_comment_publish'] = array(
          'title' => t('Publish'),
          'href' => 'antispam/comment/' . $comment->cid . '/publish',
        );
      }
    }
    if (variable_get('antispam_comment_spam_links', 1)) {
      if (antispam_content_is_spam('comment', $comment->cid)) {
        $links['antispam_comment_ham'] = array(
          'title' => t('Not Spam'),
          'href' => 'antispam/comment/' . $comment->cid . '/submit-ham',
        );
      }
      else {
        $links['antispam_comment_spam'] = array(
          'title' => t('Spam'),
          'href' => 'antispam/comment/' . $comment->cid . '/submit-spam',
        );
      }
    }
    $comment->content['links']['comment']['#links'] = array_merge($comment->content['links']['comment']['#links'], $links);
  }
}

/**
 * Get anti-spambot rules.
 *
 * @return array
 */
function antispam_get_anti_spambot_rules() {
  static $antispambot_rules = FALSE;
  if (!$antispambot_rules) {
    $antispambot_rules = array();
    $options = variable_get('antispam_antispambot_rules', array());
    if (is_array($options)) {
      foreach ($options as $key => $value) {
        if (is_string($key)) {
          $antispambot_rules[$key] = $key === $value ? TRUE : FALSE;
        }
      }
    }
  }
  return $antispambot_rules;
}

/**
 * Check if anti-spambot options are enabled.
 *
 * @return boolean
 */
function antispam_is_anti_spambot_enabled() {
  $antispambot_rules = antispam_get_anti_spambot_rules();
  return count($antispambot_rules) > 0 ? TRUE : FALSE;
}

/**
 * Perform an anti-spambot action based on module settings.
 *
 * @param array $debug_info
 *   Extra data, used here to enhance the logged information, for debugging
 *   purposes.
 */
function antispam_anti_spambot_action($debug_info) {
  $antispambot_action = variable_get('antispam_antispambot_action', '503');

  // First action is generate a delay, if requested to.
  if (($seconds = variable_get('antispam_antispambot_delay', 60)) > 0) {
    sleep($seconds);
  }

  // If no other action was set, we're done.
  if ($antispambot_action == 'none') {
    return;
  }
  $items = array();
  foreach ($debug_info as $label => $value) {
    $items[] = '<strong>' . check_plain($label) . '</strong>: ' . check_plain($value);
  }

  // From here on, the request is killed using different methods.
  if ($antispambot_action == '403d') {
    drupal_access_denied();
    $message = t('Spambot detected (action: 403 Forbidden).');
  }
  elseif ($antispambot_action == '403') {
    @header('HTTP/1.0 403 Forbidden');
    print t('Access denied');
    $message = t('Spambot detected (action: 403 Forbidden).');
  }
  else {

    // 503
    @header('HTTP/1.0 503 Service unavailable');
    print t('Service unavailable');
    $message = t('Spambot detected (action: 503 Service unavailable).');
  }
  watchdog('antispam', '%message<p>Additional information:</p>%items', array(
    '%message' => $message,
    '%items' => theme('item_list', $items),
  ));
  module_invoke_all('exit');
  exit;
}

/**
 * Expand query for debugging purposes.
 *
 * @param string $query
 *   SQL statement.
 * @param mixed
 *   Array or variable list of arguments.
 */
function _antispam_translate_query($query) {
  $args = func_get_args();
  array_shift($args);
  $query = db_prefix_tables($query);

  // 'All arguments in one array' syntax.
  if (isset($args[0]) && is_array($args[0])) {
    $args = $args[0];
  }
  _db_query_callback($args, TRUE);
  $query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query);
  return $query;
}

/**
 * Check if specified content is marked as spam.
 *
 * @param string $content_type
 *   Content type; can be either 'node' or 'comment' .
 * @param integer $content_id
 *   Content ID; can be either a nid or a cid.
 *
 * @return boolean
 *   TRUE if content is marked as spam; FALSE otherwise.
 */
function antispam_content_is_spam($content_type, $content_id) {
  return db_query_range('SELECT 1 FROM {antispam_spam_marks} WHERE content_type=:content_type AND content_id=:content_id', 0, 1, array(
    ':content_type' => $content_type,
    ':content_id' => $content_id,
  ))
    ->fetchField();
}

/**
 * Get moderator type required for specified content.
 *
 * @param string $content_type
 *   Content type; can be either 'node' or 'comment' .
 * @param integer $content_id
 *   Content ID; can be either a nid or a cid.
 *
 * @return boolean
 *   Moderator Type or empty string if content is not found.
 */
function antispam_content_get_moderator_type($content_type, $content_id) {
  watchdog('antispam', 'antispam_content_get_moderator_type is called. type=' . $content_type . ', id=' . $content_id);
  if ($content_type == 'node') {
    $moderator_type = db_query('SELECT type FROM {node} WHERE nid=:nid', array(
      ':nid' => $content_id,
    ))
      ->fetchField();
    if (!$moderator_type) {
      $moderator_type = '';
    }
  }
  elseif ($content_type == 'comment') {
    $has_rows = db_query_range('SELECT 1 FROM {comment} WHERE cid=:cid', 0, 1, array(
      ':cid' => $content_id,
    ))
      ->fetchField();
    $moderator_type = $has_rows ? 'comments' : '';
  }
  else {
    $moderator_type = '';
  }
  return $moderator_type;
}

/**
 * Get the types the current user is allowed to moderate.
 *
 * @param object $account
 *   The account to check; use current user if not given.
 *
 * @return array
 *   Moderator Types.
 */
function antispam_get_moderator_types($account = NULL) {
  global $user;
  static $node_types = FALSE;
  static $moderator_types = array();
  if (is_null($account)) {
    $account = $user;
  }
  if ($node_types === FALSE) {
    $node_types = node_type_get_names();
  }

  // Suppress warning message.
  $moderator_types[$account->uid] = NULL;
  if (!isset($moderator_types[$account->uid])) {
    if (user_access('administer nodes', $account)) {
      foreach ($node_types as $type => $name) {
        $moderator_types[$account->uid][$type] = $name;
      }
    }
    else {
      foreach ($node_types as $type => $name) {
        if (user_access('moderate spam in nodes of type ' . $node_types[$type], $account)) {
          $moderator_types[$account->uid][$type] = $name;
        }
      }
    }
    if (module_exists('comment') && (user_access('administer comments', $account) || user_access('moderate spam in comments', $account))) {
      $moderator_types[$account->uid]['comments'] = t('comments');
    }
  }
  return $moderator_types[$account->uid];
}

/**
 * Is current user spam moderator?
 *
 * @param string $moderator_type
 *   Moderator Type - comments, node type or NULL.
 * @param object $account
 *   The user account to check; use current user if not given.
 *
 * @return boolean
 *   TRUE if current user is moderator of specified type; FALSE otherwise.
 */
function antispam_is_spam_moderator($moderator_type = NULL, $account = NULL) {
  global $user;
  if (is_null($account)) {
    $account = $user;
  }
  $moderator_types = antispam_get_moderator_types($account);
  if (is_null($moderator_type)) {
    return count($moderator_types) > 0 ? TRUE : FALSE;
  }
  return isset($moderator_types[$moderator_type]);
}

/**
 * Notify moderators of new/updated content, only content needing approval or nothing at all.
 *
 * @param string $content_type
 *   Content type; can be either 'node' or 'comment' .
 * @param object $content
 *   The content object to work on.
 * @param boolean $is_published
 *   TRUE if content is in published status.
 * @param boolean $is_spam
 *   TRUE if content has been marked as spam.
 */
function antispam_notify_moderators($content_type, $content, $is_published, $is_spam) {
  global $user, $base_url;

  // Proceed only if e-mail notifications are enabled.
  if (!variable_get('antispam_email_enabled', 0)) {
    return;
  }

  // Make sure we have an object.
  $content = (object) $content;

  // Compute the related moderator permission.
  if ($content_type == 'comment') {
    $moderator_permission = 'moderate spam in comments';
    $administer_permission = 'administer comments';
  }
  else {
    $moderator_types = antispam_get_moderator_types();
    $moderator_permission = 'moderate spam in nodes of type ' . $moderator_types[$content->type];
    $administer_permission = 'administer nodes';
  }

  // Obtain list of moderators of the specified content type.
  $sql = 'SELECT u.uid, u.name, u.mail, m.email_for' . ' FROM {role_permission} p' . '   INNER JOIN {users_roles} r ON r.rid = p.rid' . '   INNER JOIN {users} u ON u.uid = r.uid OR u.uid = 1' . '   LEFT JOIN {antispam_moderator} m ON m.uid = u.uid' . ' WHERE p.permission LIKE :perm1' . ' OR p.permission LIKE :perm2';
  $result = db_query($sql, array(
    ':perm1' => '%' . $moderator_permission . '%',
    ':perm2' => '%' . $administer_permission . '%',
  ));
  $moderators = array();
  foreach ($result as $u) {

    // If a moderator submit a new node/comment, then exclude them from the
    // list.
    if ($u->uid != $user->uid) {
      $moderators[$u->uid] = array(
        'name' => $u->name,
        'email_to' => $u->mail,
        'email_for' => !is_null($u->email_for) ? $u->email_for : 'approval',
      );
    }
  }

  // Check to see if we need to add the admin to the notification moderator
  // list.
  $sql = 'SELECT u.uid, u.name, u.mail, m.email_for' . ' FROM {users} u' . ' LEFT JOIN {antispam_moderator} m ON m.uid = u.uid' . ' WHERE u.uid=1';
  $u = db_query($sql)
    ->fetchObject();
  if (!empty($u)) {

    // If the admin submit a new node/comment, then exclude from the list.
    if ($u->uid != $user->uid) {
      $moderators[$u->uid] = array(
        'name' => $u->name,
        'email_to' => $u->mail,
        'email_for' => !is_null($u->email_for) ? $u->email_for : 'approval',
      );
    }
  }

  // Extract unique email addresses and ignore those who have requested to not
  // get e-mail notifications.
  $unique_emails = array();
  foreach ($moderators as $uid => $moderator) {
    if ($moderator['email_for'] == 'all' || $moderator['email_for'] == 'approval' && !$is_published) {
      if (!isset($unique_emails[$moderator['email_to']])) {
        $unique_emails[$moderator['email_to']] = $uid;
      }
    }
  }
  if (count($unique_emails) <= 0) {
    return;
  }

  // If this is about a comment, try to load the node. Also, prepare arguments
  // for notification message.
  $site_name = variable_get('site_name', t('Drupal'));
  if ($content_type == 'comment') {
    if (!($node = antispam_content_load('node', $content->nid))) {
      watchdog('antispam', 'An error has ocurred while trying to notify moderators about a comment. The associated node could not be loaded. ', array(), WATCHDOG_NOTICE, l(t('view'), 'node/' . $content->nid, array(
        'fragment' => 'comment-' . $content->cid,
      )));
      return;
    }
    $message_args = array(
      '@title-label' => t('Subject'),
      '@content-title' => $content->subject,
      '@content-type' => t('comment'),
      '!content-link' => url('node/' . $content->nid, array(
        'fragment' => 'comment-' . $content->cid,
        'absolute' => TRUE,
      )),
    );
  }
  else {
    $message_args = array(
      '@title-label' => t('Title'),
      '@content-title' => $content->title,
      '@content-type' => $moderator_types[$content->type],
      '!content-link' => url('node/' . $content->nid, array(
        'absolute' => TRUE,
      )),
    );
  }
  $message_args['@content-status'] = ($is_published ? t('published') : t('not published')) . ($is_spam ? ' (' . t('marked as spam') . ')' : '');
  $message_args['@site-name'] = $site_name;
  $message_args['!site-link'] = $base_url . base_path();
  $message_title = t('[@site-name] moderator notification - Posted @content-type \'@content-title\'', $message_args);
  $message_body = t(<<<EOT
Hello @user-name,

You can use the following information to review this @content-type:

@title-label: @content-title
URL: !content-link
Status: @content-status

Please, do not reply to this e-mail. It is an automated notification you are receiving because you are a moderator at @site-name. If you no longer wish to receive such notifications, you can change your moderator settings in your user profile.

Thank you

!site-link
EOT
, $message_args);

  // Log the notification to watchdog.
  watchdog('antispam', 'Trying to notify the following recipients: %emails', array(
    '%emails' => implode(', ', array_keys($unique_emails)),
  ));

  // Send e-mails.
  foreach ($unique_emails as $email_to => $uid) {
    $message_body = str_replace('@user-name', check_plain($moderators[$uid]['name']), $message_body);
    $params = array(
      'body' => $message_body,
      'subject' => $message_title,
    );
    drupal_mail('antispam', date("Y-m-d"), $email_to, language_default(), $params);
  }
}

/**
 * Implements hook_mail().
 */
function antispam_mail($key, &$message, $params) {
  $headers = array(
    'MIME-Version' => '1.0',
    'Content-Type' => 'text/html; charset=UTF-8; format=flowed',
    'Content-Transfer-Encoding' => '8Bit',
    'X-Mailer' => 'Drupal',
  );
  foreach ($headers as $key => $value) {
    $message['headers'][$key] = $value;
  }
  $message['subject'] = $params['subject'];
  $message['body'][] = $params['body'];
}

/*******************************************************************************
 * User operations.
 ******************************************************************************/

/**
 * Returns the array of fields for AntiSpam block configuration.
 * Called from hook_block_view(), hook_block_save() and hook_block_configure().
 */
function _antispam_get_email_for_options() {
  return array(
    'all' => t('All new (or updated) content'),
    'approval' => t('Only content needing approval'),
    'never' => t('Never'),
  );
}

/**
 * Implements hook_form_alter().
 */
function antispam_form_alter(&$form, &$form_state, $form_id) {

  // Add anti-spam integration to webforms.
  if (strpos($form_id, 'webform_client_form_') !== FALSE) {
    $form['#validate'][] = 'antispam_webform_check';
  }
  if (!($form_id == 'user_register_form' || $form_id == 'user_profile_form')) {
    return;
  }
  if (!variable_get('antispam_email_enabled', 1)) {
    return;
  }
  $uid = $form['#user']->uid;
  $antispam_moderator_email_for = isset($form['#user']->antispam_moderator_email_for) ? $form['#user']->antispam_moderator_email_for : 'approval';
  $moderator_email_for_options = _antispam_get_email_for_options();
  $moderator_types = antispam_get_moderator_types(NULL);
  $moderator_types_count = count($moderator_types);
  if (!$moderator_types_count) {
    return;
  }
  $form['antispam_moderator'] = array(
    '#type' => 'fieldset',
    '#title' => t('AntiSpam moderator settings'),
    '#weight' => 5,
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
    '#description' => t('You are currently moderator for the following content types: %types.', array(
      '%types' => implode(', ', $moderator_types),
    )),
  );
  $form['antispam_moderator']['antispam_moderator_email_for'] = array(
    '#type' => 'radios',
    '#title' => t('Send me e-mails for'),
    '#options' => $moderator_email_for_options,
    '#default_value' => isset($moderator_email_for_options[$antispam_moderator_email_for]) ? $antispam_moderator_email_for : 'approval',
  );
}

/**
 * Implements hook_user_load().
 */
function antispam_user_load($users) {
  $moderator_email_for_options = _antispam_get_email_for_options();
  foreach ($users as $user) {
    $moderator_types = antispam_get_moderator_types($user);
    $moderator_types_count = count($moderator_types);
    if ($moderator_types_count > 0) {
      $moderator_data = db_query('SELECT * FROM {antispam_moderator} WHERE uid=:uid', array(
        ':uid' => $user->uid,
      ))
        ->fetchObject();
      $user->antispam_moderator_email_for = isset($moderator_data->email_for) && isset($moderator_email_for_options[$moderator_data->email_for]) ? $moderator_data->email_for : 'approval';
    }
  }
}

/**
 * Implements hook_user_insert().
 */
function antispam_user_insert(&$edit, $account, $category) {
  $moderator_email_for_options = _antispam_get_email_for_options();
  $moderator_types = antispam_get_moderator_types($account);
  $moderator_types_count = count($moderator_types);
  if ($moderator_types_count > 0 && isset($edit['antispam_moderator_email_for'])) {
    if (!isset($moderator_email_for_options[$edit['antispam_moderator_email_for']])) {
      $edit['antispam_moderator_email_for'] = 'approval';
    }
    $result = db_query("SELECT * FROM {antispam_moderator} WHERE uid=:uid", array(
      ':uid' => $account->uid,
    ));
    if ($result
      ->rowCount()) {

      // update
      db_update('antispam_moderator')
        ->fields(array(
        'email_for' => $edit['antispam_moderator_email_for'],
      ))
        ->condition('uid', $account->uid)
        ->execute();
    }
    else {

      // insert
      db_insert('antispam_moderator')
        ->fields(array(
        'uid' => $account->uid,
        'email_for' => $edit['antispam_moderator_email_for'],
      ))
        ->execute();
    }
    $edit['antispam_moderator_email_for'] = NULL;
  }
}

/**
 * Implements hook_user_update().
 */
function antispam_user_update(&$edit, $account, $category) {
  antispam_user_insert($edit, $account, $category);
}

/**
 * Implements hook_user_delete().
 */
function antispam_user_delete($account) {
  db_delete('antispam_moderator')
    ->condition('uid', $account->uid)
    ->execute();
}

/**
 * Format the 'Counting since' date.
 *
 * @return string
 *   Counting since date formatted according to module settings.
 */
function antispam_get_counting_since() {
  $since = variable_get('antispam_counter_since', array(
    'day' => date('j'),
    'month' => date('n'),
    'year' => date('Y'),
  ));
  $format = variable_get('antispam_counter_date_format', 'F j, Y');
  $replace = array(
    'd' => sprintf('%02d', $since['day']),
    'j' => $since['day'],
    'm' => sprintf('%02d', $since['month']),
    'M' => format_date(gmmktime(0, 0, 0, $since['month'], 2, 1970), 'custom', 'M'),
    'F' => format_date(gmmktime(0, 0, 0, $since['month'], 2, 1970), 'custom', 'F'),
    'Y' => $since['year'],
  );
  return strtr($format, $replace);
}

/**
 * Returns the array of fields for AntiSpam block configuration.
 * Called from hook_block_view(), hook_block_save() and hook_block_configure().
 */
function _antispam_get_block_fields() {
  return array(
    'type' => array(
      '#type' => 'radios',
      '#title' => t('Display counter as'),
      '#options' => array(
        'image' => t('Image'),
        'text' => t('Text'),
      ),
      '#default_value' => 'image',
    ),
    'sitename' => array(
      '#type' => 'radios',
      '#title' => t('Show site name'),
      '#options' => array(
        '1' => t('Enabled'),
        '0' => t('Disabled'),
      ),
      '#default_value' => '1',
    ),
    'newwin' => array(
      '#type' => 'radios',
      '#title' => t('Open AntiSpam link in new window'),
      '#options' => array(
        '1' => t('Enabled'),
        '0' => t('Disabled'),
      ),
      '#default_value' => '1',
    ),
  );
}

/**
 * Implements hook_block_info().
 */
function antispam_block_info() {
  $blocks_counter = variable_get('antispam_blocks_counter', 1);
  $blocks = array();
  for ($i = 0; $i < $blocks_counter; $i++) {
    $blocks[] = array(
      'info' => t('AntiSpam spam counter (block: @count)', array(
        '@count' => $i + 1,
      )),
    );
  }
  return $blocks;
}

/**
 * Implements hook_block_configure().
 */
function antispam_block_configure($delta = '') {
  $block_fields = _antispam_get_block_fields();
  if ($delta >= 0 && $delta < variable_get('antispam_blocks_counter', 1)) {
    $form = array();
    $form['description'] = array(
      '#type' => 'item',
      '#markup' => '<div class="description">' . t('These options allow to customize the look of this <em>antispam spam counter</em> block.') . '</div>',
    );
    $block_settings = variable_get('antispam_blocks_' . $delta, FALSE);
    foreach ($block_fields as $field_key => $field_info) {
      $field_name = 'antispam_blocks_' . $delta . '_' . $field_key;
      $form[$field_name] = array();
      foreach ($field_info as $key => $value) {
        $form[$field_name][$key] = $value;
      }
      if ($block_settings && isset($block_settings[$field_key])) {
        $form[$field_name]['#default_value'] = $block_settings[$field_key];
      }
    }
    return $form;
  }
}

/**
 * Implements hook_block_save().
 */
function antispam_block_save($delta = '', $edit = array()) {
  $block_fields = _antispam_get_block_fields();
  if ($delta >= 0 && $delta < variable_get('antispam_blocks_counter', 1)) {
    $block_settings = array();
    foreach ($block_fields as $field_key => $field_info) {
      $field_name = 'antispam_blocks_' . $delta . '_' . $field_key;
      $block_settings[$field_key] = $edit[$field_name];
    }
    variable_set('antispam_blocks_' . $delta, $block_settings);
  }
}

/**
 * Implements hook_block_view().
 */
function antispam_block_view($delta = '') {
  $block_fields = _antispam_get_block_fields();
  if ($delta >= 0 && $delta < variable_get('antispam_blocks_counter', 1)) {
    $block_settings = variable_get('antispam_blocks_' . $delta, FALSE);
    if (!$block_settings) {
      $block_settings = array();
    }
    $block_args = array(
      'content' => '',
      'counter' => antispam_get_total_counter(ANTISPAM_COUNT_SPAM_DETECTED),
      'since' => antispam_get_counting_since(),
      'text' => '',
      'image' => '',
      // Completed below with current block settings.
      'block' => array(
        'delta' => $delta,
      ),
    );
    foreach ($block_fields as $field_key => $field_info) {
      if (!isset($block_settings[$field_key])) {
        $block_settings[$field_key] = $field_info['#default_value'];
      }
      $block_args['block'][$field_key] = $block_settings[$field_key];
    }
    $target = $block_settings['newwin'] ? ' target="_blank"' : '';
    $sitename = $block_settings['sitename'] ? variable_get('site_name', 'drupal') : t('This site');
    $block_args['text'] = array(
      'plain' => t('@site_name is proudly protected by AntiSpam, !spams caught since @since.', array(
        '@site_name' => $sitename,
        '!spams' => format_plural($block_args['counter'], '1 spam', '@count spams'),
        '@since' => $block_args['since'],
      )),
      'html' => t('@site_name is proudly protected by <a href="!antispam"@target>AntiSpam</a>, %spams caught since @since', array(
        '@site_name' => $sitename,
        '!antispam' => url('http://drupal.org/project/antispam'),
        '@target' => $target,
        '%spams' => format_plural($block_args['counter'], '1 spam', '@count spams'),
        '@since' => $block_args['since'],
      )),
    );
    $title_text = $block_args['text']['plain'];
    $image_url = base_path() . drupal_get_path('module', 'antispam') . '/antispam.png';
    $block_args['image'] = '<img src="' . $image_url . '" title="' . $title_text . '" alt="' . $title_text . '" />';
    if ($block_settings['type'] == 'image') {
      $block_args['content'] = '<a href="http://drupal.org/project/antispam" title="' . $title_text . '"' . $target . '>' . $block_args['image'] . '</a>';
    }
    else {
      $block_args['content'] = $block_args['text']['html'];
    }
    $block = array();
    $block['subject'] = t('AntiSpam spam counter');
    $block['content'] = theme('antispam_counter_block', $block_args);
    return $block;
  }
}

/**
 * Implements hook_theme().
 */
function antispam_theme() {
  return array(
    'antispam_moderation_form' => array(
      'render element' => 'form',
      'file' => 'antispam.admin.inc',
    ),
    'antispam_counter_block' => array(
      'variables' => array(
        'args' => array(
          'content',
          'counter',
          'since',
          'text' => array(
            'plain' => array(
              'short',
              'long',
            ),
            'html' => array(
              'short',
              'long',
            ),
          ),
          'image',
          'block',
        ),
      ),
    ),
  );
}

/**
 * Allow themes customize the content of the antispam spam counter block.
 *
 * @param array $args
 *   A nested array where each element is:
 *     content: String; the completely built block content.
 *     counter: Integer; the current spam counter.
 *     since  : String; the formatted 'counting since' date.
 *     text   : Array; with 2 subarrays defined as follows:
 *              plain: Array with 2 elements ('short' and 'long').
 *              html : Array with 2 elements ('short' and 'long').
 *     image  : String; the completely built IMG tag.
 *     block  : Array; Block settings.
 *
 * @return string
 *   The content of the block.
 */
function theme_antispam_counter_block($args) {
  return $args['content'];
}

/**
 * Load a node or a comment.
 *
 * @param string $content_type
 *   Content type; can be either 'node' or 'comment' .
 * @param integer $content_id
 *   Content ID; can be either a nid or a cid.
 *
 * @return mixed
 *  An object with requested content; FALSE on failure.
 */
function antispam_content_load($content_type, $content_id) {
  switch ($content_type) {
    case 'node':
      $content = node_load($content_id);
      break;
    case 'comment':
      $content = comment_load($content_id);
      break;
  }
  return $content;
}

/**
 * Delete a node or a comment.
 *
 * @param string $content_type
 *   Content type; can be either 'node' or 'comment' .
 * @param integer $content_id
 *   Content ID; can be either a nid or a cid.
 *
 * @return boolean
 *  TRUE if specified was there; FALSE otherwise.
 */
function antispam_content_delete($content_type, $content_id) {
  module_load_include('inc', 'comment', 'comment.admin');
  if ($content_type == 'node') {
    node_delete($content_id);
    return TRUE;
  }
  elseif ($content_type == 'comment') {
    comment_delete($content_id);
    return TRUE;
  }
  return FALSE;
}

/**
 * Implements hook_clear_cache().
 *
 * This function needs to be called so anonymous users get updated content when
 * certain operations have been executed.
 */
function antispam_clear_cache() {
  static $already_done = FALSE;
  if (!$already_done) {
    cache_clear_all();
    $already_done = TRUE;
  }
}

/**
 * Mark content as spam or remove the mark.
 *
 * @param string $content_type
 *   Content type; can be either 'node' or 'comment'.
 * @param object $content
 *   Content object.
 * @param string $op
 *   Operation; it can be 'submit-spam' or 'submit-ham'.
 * @param boolean $log_action
 *   TRUE to log action (default), otherwise the caller logs the action.
 */
function antispam_content_spam_operation($content_type, $content, $op, $log_action = TRUE) {
  watchdog('antispam', 'antispam_content_spam_operation: type=' . $content_type . ' . op=' . $op);
  if ($content_type == 'node') {
    global $user;
    $content_id = $content->nid;
    $content_title = $content->title;
    $content_link = l(t('view'), 'node/' . $content->nid);
    $user_mail = isset($user->mail) ? $user->mail : '';
  }
  else {
    $content_id = $content->cid;
    $content_title = $content->subject;
    $content_link = l(t('view'), 'node/' . $content->nid, array(
      'fragment' => 'comment-' . $content->cid,
    ));
    $user_mail = $content->mail;
  }
  watchdog('antispam', 'antispam_content_spam_operation: content_id=' . $content_id);
  if (!isset($content->signature)) {
    $content->signature = '';
  }
  if (!isset($content->spaminess)) {
    $content->spaminess = 0.0;
  }
  if ($op == 'submit-spam') {
    if (variable_get('antispam_connection_enabled', 1)) {
      antispam_api_cmd_submit_spam($content_type, $content);
      $action = $content_type == 'node' ? t('Content submitted as spam') : t('Comment submitted as spam');
    }
    else {
      $action = $content_type == 'node' ? t('Content marked as spam') : t('Comment marked as spam');
    }
    $hostname = !empty($content->hostname) ? $content->hostname : ip_address();
    db_insert('antispam_spam_marks')
      ->fields(array(
      'content_type' => $content_type,
      'content_id' => $content_id,
      'spam_created' => mktime(0, 0, 0, date("m"), date("d"), date("Y")),
      'hostname' => $hostname,
      'mail' => $user_mail,
      'signature' => $content->signature,
      'spaminess' => $content->spaminess,
    ))
      ->execute();
  }
  else {
    if (variable_get('antispam_connection_enabled', 1)) {
      antispam_api_cmd_submit_ham($content_type, $content);
      $action = $content_type == 'node' ? t('Content submitted as ham') : t('Comment submitted as ham');
    }
    else {
      $action = $content_type == 'node' ? t('Content marked as ham') : t('Comment marked as ham');
    }
    db_delete('antispam_spam_marks')
      ->condition('content_type', $content_type)
      ->condition('content_id', $content_id)
      ->execute();
  }
  if ($log_action) {
    watchdog('content', '@action: @title', array(
      '@action' => $action,
      '@title' => $content_title,
    ), WATCHDOG_NOTICE, $content_link);
  }
  antispam_clear_cache();
}

/**
 * Execute content publish/unpublish operations.
 *
 * @param string $content_type
 *   Content type; it can be 'node' or 'comment' .
 * @param object $content
 *   Content object.
 * @param string $op
 *   Operation; it can be 'publish' or 'unpublish' .
 * @param boolean $log_action
 *   TRUE to log action (default), otherwise the caller logs the action.
 */
function antispam_content_publish_operation($content_type, $content, $op, $log_action = TRUE) {
  module_load_include('inc', 'comment', 'comment.admin');
  if ($content_type == 'node') {

    // This code snippet is based on node.module::node_admin_nodes_submit().
    // Only the node record is updated, no other hooks are invoked.
    // Perform the update action.
    db_update('node')
      ->fields(array(
      'status' => $op == 'publish' ? 1 : 0,
    ))
      ->condition('nid', $content->nid)
      ->execute();

    // Perform the update action of the revision.
    db_update('node_revision')
      ->fields(array(
      'status' => $op == 'publish' ? 1 : 0,
    ))
      ->condition('nid', $content->nid)
      ->condition('vid', $content->vid)
      ->execute();

    // Reset the cache for the updated node in order to update the dmin views.
    entity_get_controller('node')
      ->resetCache(array(
      $content->nid,
    ));
    if ($log_action) {
      $action = $op == 'publish' ? t('Content published') : t('Content unpublished');
      watchdog('content', '@action: @title', array(
        '@action' => $action,
        '@title' => $content->title,
      ), WATCHDOG_NOTICE, l(t('view'), 'node/' . $content->nid));
    }
  }
  else {
    if ($op == 'publish') {
      comment_publish_action($content, array(
        'cid' => $content->cid,
      ));
      db_update('comment')
        ->fields(array(
        'status' => COMMENT_PUBLISHED,
      ))
        ->condition('cid', $content->cid)
        ->execute();
      module_invoke_all('comment_published', $content);
    }
    elseif ($op == 'unpublish') {
      comment_unpublish_action($content, array(
        'cid' => $content->cid,
      ));
      db_update('comment')
        ->fields(array(
        'status' => COMMENT_NOT_PUBLISHED,
      ))
        ->condition('cid', $content->cid)
        ->execute();

      // Allow modules to respond to the updating of a comment.
      module_invoke_all('comment_unpublished', $content);
    }

    // Update comment statistics.
    _comment_update_node_statistics($content->nid);
    if ($log_action) {
      $action = $op == 'publish' ? t('Comment published') : t('Comment unpublished');
      watchdog('content', '@action: %subject', array(
        '@action' => $action,
        '%subject' => $content->subject,
      ), WATCHDOG_NOTICE, l(t('view'), 'node/' . $content->nid, array(
        'fragment' => 'comment-' . $content->cid,
      )));
    }
  }
  antispam_clear_cache();
}

/**
 * Prepare comment data for AntiSpam requests.
 *
 * @param string $content_type
 *   Content type; it can be 'node' or 'comment' .
 * @param object $content
 *   Content object.
 * @param integer $provider
 *   ANTISPAM_AKISMET_SERVICE
 *
 * @return array
 */
function antispam_prepare_comment_data($content_type, $content, $provider) {

  // Prepare data that is common to nodes/comments.
  global $user, $base_url;
  $comment_data = array(
    // IP address of the comment submitter.
    'user_ip' => !empty($content->hostname) ? $content->hostname : ip_address(),
    // User agent information of the comment submitter.
    'user_agent' => $_SERVER['HTTP_USER_AGENT'],
    // The content of the HTTP_REFERER header should be sent here.
    'referrer' => $_SERVER['HTTP_REFERER'],
    // May be blank, comment, trackback, pingback, or a made up value like
    // "registration".
    'comment_type' => '',
    // Submitted name with the comment.
    'comment_author' => $content->name,
  );
  if ($content_type == 'comment') {
    $comment_data['permalink'] = url('node/' . $content->nid, array(
      'fragment' => 'comment-' . $content->cid,
    ));
    $comment_data['comment_author_email'] = $content->mail;
    $comment_data['comment_author_url'] = $content->homepage;
    $comment_data['comment_content'] = render($content->comment_body[$content->language][0]['value']);

    // If the subject isn't the same as the comment body (which happens when no subject is entered),
    // add it to the beginning.
    if ($content->subject && $content->subject != $comment_data['comment_content']) {
      $comment_data['comment_content'] = $content->subject . "\n\n" . $comment_data['comment_content'];
    }
    $comment_data['comment_content'] = trim($comment_data['comment_content']);
  }
  elseif ($content_type == 'node') {
    $render = reset(entity_view('node', array(
      $content,
    )));
    $rendered = drupal_html_to_text(drupal_render($render), 'a');
    $comment_data['permalink'] = url('node/' . $content->nid);
    $comment_data['comment_author_email'] = isset($user->mail) ? $user->mail : '';
    $comment_data['comment_author_url'] = '';
    $comment_data['comment_content'] = $rendered;
  }
  else {
    $comment_data['permalink'] = '';
    $comment_data['comment-author_email'] = isset($user->mail) ? $user->mail : isset($content->mail) ? $content->mail : '';
    $comment_data['comment-author_url'] = isset($content->homepage) ? $content->homepage : '';
    $comment_data['comment_content'] = render($content->body);
  }
  return $comment_data;
}

/*******************************************************************************
 * AntiSpam API implementation
 ******************************************************************************/

/**
 * AntiSpam API: Key Verification.
 *
 * @param string $key
 *   API Key.
 * @param integer $provider
 *
 * @return integer
 *   See constants ANTISPAM_API_RESULT_xxx.
 */
function antispam_api_cmd_verify_key($key, $provider) {
  global $base_url;
  if (empty($key)) {
    return ANTISPAM_API_RESULT_ERROR;
  }
  $api_host = antispam_get_api_host($provider);
  $request = 'key=' . $key . '&blog=' . $base_url . base_path();
  $response = _antispam_api_http_post($request, $api_host, '/' . ANTISPAM_AKISMET_API_VERSION . '/verify-key');
  if (!isset($response[1])) {
    watchdog('antispam', "verifying a key: can not get a response back from the service provider " . antispam_get_provider_name($provider, FALSE));
    return ANTISPAM_API_RESULT_ERROR;
  }
  return 'valid' == $response[1] ? ANTISPAM_API_RESULT_SUCCESS : ANTISPAM_API_RESULT_ERROR;
}

/**
 * AntiSpam API: Generic Data Check.
 *
 * @return integer
 *   -1 = Error, 0 = Ham, 1 = Spam.
 */
function antispam_api_cmd_spam_check($body, $name = NULL, $mail = NULL, $homepage = NULL) {
  $provider = antispam_get_service_provider();
  $api_host = antispam_get_api_host($provider);
  $api_key = antispam_get_api_key($provider);
  if (empty($api_key)) {
    return array(
      ANTISPAM_API_RESULT_ERROR,
    );
  }
  $content = new stdClass();
  $content->body = $body;
  $content->name = $name;
  $content->mail = $mail;
  $content->homepage = $homepage;
  $api_result = antispam_api_cmd_comment_check('other', $content);
  if ($api_result[0] == ANTISPAM_API_RESULT_IS_HAM) {
    return 0;
  }
  elseif ($api_result[0] == ANTISPAM_API_RESULT_IS_SPAM) {
    return 1;
  }
  else {
    return -1;
  }
}

/**
 * AntiSpam API: Comment Check.
 *
 * @param string $content_type
 *   Content type; it can be 'node' or 'comment' .
 * @param object $content
 *   Content object.
 *
 * @return array 
 *   result[0] - return status (int) See constants ANTISPAM_API_RESULT_xxx.
 *   result[1] - signature (string) - DEFENSIO only (obsolete)
 *   result[2] - spaminess (float) value between 0 to 1 - DEFENSIO only (obsolete)
 */
function antispam_api_cmd_comment_check($content_type, $content) {
  if (!variable_get('antispam_connection_enabled', 1)) {
    return array(
      ANTISPAM_API_RESULT_ERROR,
    );
  }
  $provider = antispam_get_service_provider();
  $comment_data = antispam_prepare_comment_data($content_type, $content, $provider);
  $api_host = antispam_get_api_host($provider);
  $api_key = antispam_get_api_key($provider);
  if (empty($api_key)) {
    return array(
      ANTISPAM_API_RESULT_ERROR,
    );
  }
  $comment_data = array_merge(_antispam_api_prepare_request_data(), $comment_data);
  $query_string = _antispam_api_build_query_string($comment_data);
  $host = $api_key . '.' . $api_host;
  $response = _antispam_api_http_post($query_string, $host, '/' . ANTISPAM_AKISMET_API_VERSION . '/comment-check');
  if (!isset($response[1])) {
    watchdog('antispam', "cheking a content failed: can not get a response back from the service provider " . antispam_get_provider_name($provider, FALSE));
    return array(
      ANTISPAM_API_RESULT_ERROR,
    );
  }
  $result[0] = 'true' == $response[1] ? ANTISPAM_API_RESULT_IS_SPAM : ANTISPAM_API_RESULT_IS_HAM;
  if (ANTISPAM_API_RESULT_IS_SPAM === $result[0]) {
    if ($content_type == 'node') {
      global $user;
      $content_id = $content->nid;
      $user_mail = isset($user->mail) ? $user->mail : '';
    }
    else {

      // Comment.
      $content_id = $content->cid;
      $user_mail = $content->mail;
    }
    $hostname = !empty($content->hostname) ? $content->hostname : ip_address();
    db_insert('antispam_spam_marks')
      ->fields(array(
      'content_type' => $content_type,
      'content_id' => $content_id,
      'spam_created' => mktime(0, 0, 0, date("m"), date("d"), date("Y")),
      'hostname' => $hostname,
      'mail' => $user_mail,
    ))
      ->execute();
  }

  // Signature.
  $result[1] = '';

  // Spaminess.
  $result[2] = 0.0;
  return $result;
}

/**
 * AntiSpam API: Submit Spam 
 *
 * @param string $content_type
 *   Content type; it can be 'node' or 'comment' .
 * @param object $content
 *   Content object.
 *
 * @return integer
 *   See constants ANTISPAM_API_RESULT_xxx.
 */
function antispam_api_cmd_submit_spam($content_type, $content) {
  if (!variable_get('antispam_connection_enabled', 1)) {
    return ANTISPAM_API_RESULT_ERROR;
  }
  $provider = antispam_get_service_provider();
  $comment_data = antispam_prepare_comment_data($content_type, $content, $provider);
  $api_host = antispam_get_api_host($provider);
  $api_key = antispam_get_api_key($provider);
  if (empty($api_key)) {
    return ANTISPAM_API_RESULT_ERROR;
  }
  $query_string = _antispam_api_build_query_string($comment_data);
  $host = $api_key . '.' . $api_host;
  $response = _antispam_api_http_post($query_string, $host, '/' . ANTISPAM_AKISMET_API_VERSION . '/submit-spam');
  if (!isset($response[1])) {
    watchdog('antispam', "submitting spam: can not get a response back from the service provider " . antispam_get_provider_name($provider, FALSE));
    return ANTISPAM_API_RESULT_ERROR;
  }
  else {
    return ANTISPAM_API_RESULT_SUCCESS;
  }
}

/**
 * AntiSpam API: Submit Ham 
 *
 * @param string $content_type
 *   Content type; it can be 'node' or 'comment' .
 * @param object $content
 *   Content object.
 *
 * @return integer
 *   See constants ANTISPAM_API_RESULT_xxx.
 */
function antispam_api_cmd_submit_ham($content_type, $content) {
  if (!variable_get('antispam_connection_enabled', 1)) {
    return ANTISPAM_API_RESULT_ERROR;
  }
  $provider = antispam_get_service_provider();
  $comment_data = antispam_prepare_comment_data($content_type, $content, $provider);
  $api_host = antispam_get_api_host($provider);
  $api_key = antispam_get_api_key($provider);
  if (empty($api_key)) {
    return ANTISPAM_API_RESULT_ERROR;
  }
  $query_string = _antispam_api_build_query_string($comment_data);
  $host = $api_key . '.' . $api_host;
  $response = _antispam_api_http_post($query_string, $host, '/' . ANTISPAM_AKISMET_API_VERSION . '/submit-ham');
  if (!isset($response[1])) {
    watchdog('antispam', "submitting ham: can not get a response back from the service provider " . antispam_get_provider_name($provider, FALSE));
    return ANTISPAM_API_RESULT_ERROR;
  }
  else {
    return ANTISPAM_API_RESULT_SUCCESS;
  }
}

/**
 * Prepare user request data for AntiSpam requests.
 *
 * @return array
 *   Relevant information extracted from $_SERVER superglobal.
 */
function _antispam_api_prepare_request_data() {

  // You may add more elements here, but they are often related to internal
  // server data that makes little sense to check whether a comment is spam or
  // not. Be sure to not send HTTP_COOKIE as it may compromise your user's
  // privacy!
  static $safe_to_send = array(
    'CONTENT_LENGTH',
    'CONTENT_TYPE',
    'HTTP_ACCEPT',
    'HTTP_ACCEPT_CHARSET',
    'HTTP_ACCEPT_ENCODING',
    'HTTP_ACCEPT_LANGUAGE',
    'HTTP_REFERER',
    'HTTP_USER_AGENT',
    'REMOTE_ADDR',
    'REMOTE_PORT',
    'SCRIPT_URI',
    'SCRIPT_URL',
    'SERVER_ADDR',
    'SERVER_NAME',
    'REQUEST_METHOD',
    'REQUEST_URI',
    'SCRIPT_NAME',
  );

  // The contents of $_SERVER doesn't change between requests, so we can have
  // this cached in static storage.
  static $server_data;
  if (!$server_data) {
    $server_data = array();
    foreach ($_SERVER as $key => $value) {
      if (in_array($key, $safe_to_send)) {
        $server_data[$key] = $value;
      }
    }
  }
  return $server_data;
}

/**
 * Build query string for AntiSpam request.
 *
 * @param array
 *
 * @return string
 */
function _antispam_api_build_query_string($array) {
  global $base_url;
  $string = 'blog=' . $base_url . base_path();
  foreach ($array as $key => $value) {
    $string .= '&' . $key . '=' . urlencode(stripslashes($value));
  }
  return $string;
}

/**
 * Perform a HTTP POST request against the antispam server.
 *
 * @param string $request
 *   The request.
 * @param string $host
 *   The antispam host, prefixed with the API key (except when checking for the
 *   key itself).
 * @param string $path
 *   The path where to perform the request.
 *
 * @return array
 *   Headers in $response[0] and entity in $response[1].
 */
function _antispam_api_http_post($request, $host, $path) {
  $fsock_timeout = (int) variable_get('antispam_connection_timeout', 10);
  $http_request = "POST {$path} HTTP/1.0\r\n" . "Host: {$host}\r\n" . "Content-Type: application/x-www-form-urlencoded; charset=utf-8\r\n" . 'Content-Length: ' . strlen($request) . "\r\n" . 'User-Agent: ' . ANTISPAM_API_USERAGENT . "\r\n" . "\r\n" . $request;
  $response = '';
  if (false !== ($fs = @fsockopen($host, ANTISPAM_AKISMET_API_PORT, $errno, $errstr, $fsock_timeout))) {
    fwrite($fs, $http_request);
    while (!feof($fs)) {

      // One TCP-IP packet.
      $response .= fgets($fs, 1160);
    }
    fclose($fs);
    $response = explode("\r\n\r\n", $response, 2);
  }
  return $response;
}

Functions

Namesort descending Description
antispam_access_callback
antispam_anti_spambot_action Perform an anti-spambot action based on module settings.
antispam_api_cmd_comment_check AntiSpam API: Comment Check.
antispam_api_cmd_spam_check AntiSpam API: Generic Data Check.
antispam_api_cmd_submit_ham AntiSpam API: Submit Ham
antispam_api_cmd_submit_spam AntiSpam API: Submit Spam
antispam_api_cmd_verify_key AntiSpam API: Key Verification.
antispam_block_configure Implements hook_block_configure().
antispam_block_info Implements hook_block_info().
antispam_block_save Implements hook_block_save().
antispam_block_view Implements hook_block_view().
antispam_callback_set_published_status Menu callback; publish/unpublish content.
antispam_callback_set_spam_status Menu callback; mark/unmark content as spam.
antispam_clear_cache Implements hook_clear_cache().
antispam_comment_delete Implements hook_comment_delete().
antispam_comment_insert Implements hook_comment_insert().
antispam_comment_load Implements hook_comment_load().
antispam_comment_presave Implements hook_comment_presave()
antispam_comment_update Implements hook_comment_update().
antispam_comment_view Implements hook_comment_view().
antispam_content_delete Delete a node or a comment.
antispam_content_get_moderator_type Get moderator type required for specified content.
antispam_content_is_spam Check if specified content is marked as spam.
antispam_content_load Load a node or a comment.
antispam_content_publish_operation Execute content publish/unpublish operations.
antispam_content_spam_operation Mark content as spam or remove the mark.
antispam_cron Implements hook_cron().
antispam_debug Debug function.
antispam_form_alter Implements hook_form_alter().
antispam_get_anti_spambot_rules Get anti-spambot rules.
antispam_get_api_host Obtain API HOST name for the specified service provider.
antispam_get_api_key Obtain API key for the specified service provider.
antispam_get_counter Get today's counter value for the specified counter type.
antispam_get_counting_since Format the 'Counting since' date.
antispam_get_max_counter Get max counter value for the specified counter type.
antispam_get_moderator_types Get the types the current user is allowed to moderate.
antispam_get_provider_name Obtain service provider name.
antispam_get_service_provider Obtain current service provider.
antispam_get_total_counter Get total counter value for the specified counter type.
antispam_help Implements hook_help().
antispam_increase_counter Increase today's counter value for the specified counter type by 1.
antispam_is_anti_spambot_enabled Check if anti-spambot options are enabled.
antispam_is_spam_moderator Is current user spam moderator?
antispam_load Implements hook_load().
antispam_mail Implements hook_mail().
antispam_menu Implements hook_menu().
antispam_node_delete Implements hook_node_delete().
antispam_node_insert Implements hook_node_insert().
antispam_node_load Implements hook_node_load().
antispam_node_update Implements hook_node_update().
antispam_node_validate Implements hook_node_validate().
antispam_node_view Implements hook_node_view().
antispam_notify_moderators Notify moderators of new/updated content, only content needing approval or nothing at all.
antispam_page Menu callback to build a page for a specific module callback.
antispam_permission Implements hook_permission().
antispam_prepare_comment_data Prepare comment data for AntiSpam requests.
antispam_set_counter Set today's counter value for the specified counter type.
antispam_theme Implements hook_theme().
antispam_user_delete Implements hook_user_delete().
antispam_user_insert Implements hook_user_insert().
antispam_user_load Implements hook_user_load().
antispam_user_update Implements hook_user_update().
antispam_webform_check Webform: Check submitted values for spam.
theme_antispam_counter_block Allow themes customize the content of the antispam spam counter block.
_antispam_api_build_query_string Build query string for AntiSpam request.
_antispam_api_http_post Perform a HTTP POST request against the antispam server.
_antispam_api_prepare_request_data Prepare user request data for AntiSpam requests.
_antispam_comment_save Called from hook_comment_insert() and hook_comment_update().
_antispam_get_block_fields Returns the array of fields for AntiSpam block configuration. Called from hook_block_view(), hook_block_save() and hook_block_configure().
_antispam_get_email_for_options Returns the array of fields for AntiSpam block configuration. Called from hook_block_view(), hook_block_save() and hook_block_configure().
_antispam_is_moderator
_antispam_is_moderator_type
_antispam_is_node_moderator
_antispam_moderator_types_count
_antispam_node_save Called from hook_node_insert() and hook_node_update()
_antispam_translate_query Expand query for debugging purposes.

Constants