Skip to content
antispam.module 87.4 KiB
Newer Older
pixture's avatar
pixture committed
<?php
// $Id$

/**
 * AntiSpam Drupal and Module versions.
 */
define('ANTISPAM_DRUPAL_VERSION', '6');
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('AKISMET_API_HOST', 'rest.akismet.com');
define('TYPEPAD_API_HOST', 'api.antispam.typepad.com');
define('DEFENSIO_API_HOST', 'api.defensio.com');
define('ANTISPAM_API_PORT', 80);
define('AKISMET_API_VERSION', '1.1');
define('DEFENSIO_API_VERSION', '1.2');
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('AKISMET_SERVICE', 0);
define('TYPEPAD_SERVICE', 1);
define('DEFENSIO_SERVICE', 2);

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);

/**
 * Implementation of 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="!wordpress">wordpress.com</a> for <a href="!akismet">Akismet</a> service
<li><a href="!typepad">typepad.com</a> for TypePad AntiSpam service
<li><a href="!defensio">defensio.com</a> for Defensio 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 antispam 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>
<p></p>',
        array(
          '!wordpress' => url('http://wordpress.com'),
          '!akismet' => url('http://akismet.com'),
          '!typepad' => url('http://antispam.typepad.com'),
          '!defensio' => url('http://defensio.com'),
          '!antispam-settings' => url('admin/settings/antispam'),
          '!permission' => url('admin/user/permissions'),
          '%no-check-perm' => t('post with no antispam checking')
        ));
      return $output;
    case 'admin/help/antispam':
    case 'admin/settings/antispam':
      $output = t('<p>The <a href="!antispam-module-home">AntiSpam module</a> for <a href="!drupal">Drupal</a> allows you to use either one of the <a href="!akismet">Akismet</a>, <a href="!typepad">TypePad AntiSpam</a> or <a href="!defensio">Defensio</a> 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'),
          '!typepad' => url('http://antispam.typepad.com'),
          '!defensio' => url('http://defensio.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 />'. 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.');
  }
}

/**
 * Implementation of hook_requirement().
 */
function antispam_requirements($phase) {
  $t = get_t();

  $provider = antispam_get_service_provider();

  if ($phase == 'runtime') {
    if ($provider == AKISMET_SERVICE) {
      if (variable_get('antispam_wpapikey', '') == '') {
        $requirements['antispam_key'] = array(
          'title' => t('Akismet API key'),
          'value' => t('Not present'),
          'description' => t("Akismet spam protection service requires a <a href='!wpapikey'>WordPress.com API key</a> to function. Obtain a key by signing up for a free account at <a href='!wordpress-com'>WordPress.com</a>, then enter the key on the <a href='!antispam-settings'>AntiSpam settings page</a>.",
            array(
              '!wpapikey' => url('http://wordpress.com/api-keys/'),
              '!wordpress-com' => url('http://wordpress.com'),
              '!antispam-settings' => url('admin/settings/antispam'),
            )),
          'severity' => REQUIREMENT_ERROR,
        );
        return $requirements;
      }
    }
    else if ($provider == TYPEPAD_SERVICE) {
      if (variable_get('antispam_tpapikey', '') == '') {
        $requirements['antispam_key'] = array(
          'title' => t('TypePad AntiSpam API key'),
          'value' => t('Not present'),
          'description' => t("TypePad AntiSpam service requires a <a href='!tpapikey'>TypePad.com AntiSpam API key</a> to function. Obtain a key by signing up for a free account at <a href='!typepad-com'>Typepad.com</a>. Once you get a free account, visit the <a href='!tpapikey'>TypePad AntiSpam Service</a> and get your free TypePad AntiSpam API Key there, then enter the key on the <a href='!antispam-settings'>AntiSpam settings page</a>.",
            array(
              '!tpapikey' => url('http://antispam.typepad.com/info/get-api-key.html'),
              '!typepad-com' => url('http://typepad.com/connect/register'),
              '!antispam-settings' => url('admin/settings/antispam'),
            )),
          'severity' => REQUIREMENT_ERROR,
        );
        return $requirements;
      }
    }
    else if ($provider == DEFENSIO_SERVICE) {
      if (variable_get('antispam_deapikey', '') == '') {
        $requirements['antispam_key'] = array(
          'title' => t('Defensio AntiSpam API key'),
          'value' => t('Not present'),
          '#description' => t("Defensio spam protection service requires a <a href='!defensio-com'>Defensio API key</a> to function. Obtain a key by signing up for a free account at <a href='!defensio-key'>Defensio.com</a> and get your free Defensio API key there, then enter the key on the <a href='!antispam-settings'>AntiSpam settings page</a>.",
            array(
              '!defensio-com' => url('http://defensio.com/'),
              '!defensio-key' => url('http://defensio.com/signup/'),
              '!antispam-settings' => url('admin/settings/antispam'),
            )),
          'severity' => REQUIREMENT_ERROR,
        );
        return $requirements;
      }
    }
  }
}

/**
 * Implementation of hook_perm().
 */
function antispam_perm() {
  $perms = array('administer antispam settings');

  foreach (node_get_types('names') as $type => $name) {
    $perms[] = 'moderate spam in nodes of type '. $name;
  }
  $perms[] = 'moderate spam in comments';
  $perms[] = 'post with no antispam checking';

  return $perms;
}

/**
 * Implementation of hook_cron().
 */
function antispam_cron() {
  module_load_include('inc', 'antispam', 'antispam.cron');
pixture's avatar
 
pixture committed
  module_load_include('inc', 'comment', 'comment.admin'); 
  antispam_cron_shutdown();
pixture's avatar
pixture committed
}

/**
 * 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 AKISMET_SERVICE:
      return $with_link ? t('<a href="http://akismet.com">Akismet</a>') : t('Akismet');
    case TYPEPAD_SERVICE:
      return $with_link ? t('<a href="http://antispam.typepad.com">TypePad AntiSpam</a>') : t('TypePad AntiSpam');
    case DEFENSIO_SERVICE:
      return $with_link ? t('<a href="http://defensio.com">Defensio</a>') : t('Defensio');
    default:
      return '';
  }
}

/**
 * Obtain API HOST name for the specified service provider
 */
function antispam_get_api_host($provider) {
  switch ($provider) {
    case AKISMET_SERVICE:
      $api_host = AKISMET_API_HOST;
      break;
    case TYPEPAD_SERVICE:
      $api_host = TYPEPAD_API_HOST;
      break;
    case DEFENSIO_SERVICE:
      $api_host = DEFENSIO_API_HOST;
      break;
  }
  return $api_host;
}

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

/**
 * Get max counter value for the specified counter type
 */
function antispam_get_max_counter($counter_type = '') {
  $rec = db_fetch_object(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}"));
  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;

  if (empty($counter_type)) { // returns an array of all totals
    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;
    default:
      return 0; // just in case
  }
}

/**
 * Get total counter value for the specified counter type
 */
function antispam_get_total_counter($counter_type = '') {
  $rec = db_fetch_object(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}"));
  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;

  if (empty($counter_type)) { // returns an array of all totals
    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;
    default:
      return 0; // just in case
  }
}

/**
 * Get today's counter value for the specified counter type
 */
function antispam_get_counter($counter_type) { // for today
  $rec = db_fetch_object(db_query("SELECT * FROM {antispam_counter} WHERE date = '%s'", date('Y-m-d 00:00:00')));
  if (!$rec) {
    return 0; // no counter record for today
  }
  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;
    default:
      return 0; // just in case
  }
}

/**
 * Set today's counter value for the specified counter type
 */
function antispam_set_counter($counter_type, $count) { // for today
  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;
  }
  db_query("UPDATE {antispam_counter} SET %s = %d WHERE date = '%s'", $field, $count, date('Y-m-d 00:00:00'));
  if (!db_affected_rows()) {
    $provider = antispam_get_service_provider();
    db_query("INSERT INTO {antispam_counter} (date, provider, %s) VALUES ('%s', %d, %d)", $field, date('Y-m-d 00:00:00'), $provider, $count);
  }
}

/**
 * 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;
  if ($user->uid == 1) {
    return TRUE;  // admin
  }
  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;
  if ($user->uid == 1) {
    return TRUE;  // admin
  }
  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;
  }
}

/**
 * Implementation of hook_menu().
 */
function antispam_menu() {
  $items = array();

  $items['admin/settings/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'),
    'access arguments' => array('administer antispam settings'),
    'file' => 'antispam.admin.inc',
  );

pixture's avatar
 
pixture committed
  $items['admin/content/antispam'] = array(
    'title' => t('AntiSpam moderation queue'),
    'description' => t('Manage the AntiSpam spam queue, appropving or deleting content in need of moderation.'),
    'page callback' => 'antispam_callback_queue',
    'access callback' => '_antispam_is_moderator',
    'access arguments' => array($moderator_types),
    'file' => 'antispam.admin.inc',
  );
  $items['admin/content/antispam/overview'] = array(
    'title' => t('Overview'),
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => 0,
    'file' => 'antispam.admin.inc',
  );
  $items['admin/content/antispam/nodes'] = array(
    'title' => t('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' => t('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' => t('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' => t('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',
  );
  $items['admin/content/antispam/comments'] = array(
    'title' => t('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' => t('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' => t('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' => t('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' => t('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;
pixture's avatar
pixture committed
562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997
  }
  return $items;
}

function antispam_load($arg, &$map, $index) {
  module_load_include('inc', 'comment', 'comment.admin'); 

  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' && module_exists('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';
  }
  else if ($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;
  }
  if (function_exists($callback && !antispam_is_spam_moderator(antispam_content_get_moderator_type($content_type, $object)))) {
    return FALSE;
  }
     // Is there a comment access check we need to run? If yes, then do the same as above.
  return TRUE;
}

function antispam_page($callback, $content_type, $object, $op) {
  if (function_exists($callback)) {
    return $callback($content_type, $object, $op);
  }
  drupal_not_found();
}

/**
 * Implementation of hook_link().
 */
function antispam_link($type, $content = 0, $main = 0) {
  $links = array();
  if ($type == 'node' && antispam_is_spam_moderator($content->type)) {
    if (variable_get('antispam_node_publish_links', 0)) {
      if ($content->status) {
        $links['antispam_node_unpublish'] = array('title' => t('Unpublish'), 'href' => 'antispam/node/'. $content->nid .'/unpublish');
      }
      else {
        $links['antispam_node_publish'] = array('title' => t('Publish'), 'href' => 'antispam/node/'. $content->nid .'/publish');
      }
    }
    if (variable_get('antispam_node_spam_links', 0)) {
      if (antispam_content_is_spam('node', $content->nid)) {
        $links['antispam_node_ham'] = array('title' => (variable_get('antispam_connection_enabled', 1) ? t('Submit ham') : t('Mark as ham')), 'href' => 'antispam/node/'. $content->nid .'/submit-ham');
      }
      else {
        $links['antispam_node_spam'] = array('title' => (variable_get('antispam_connection_enabled', 1) ? t('Submit spam') : t('Mark as spam')), 'href' => 'antispam/node/'. $content->nid .'/submit-spam');
      }
    }
  }
  else if ($type == 'comment' && antispam_is_spam_moderator('comments')) {
    if (variable_get('antispam_comment_publish_links', 1)) {
      if ($content->status == COMMENT_PUBLISHED) {
        $links['antispam_comment_unpublish'] = array('title' => t('Unpublish'), 'href' => 'antispam/comment/'. $content->cid .'/unpublish');
      }
      else if ($content->status == COMMENT_NOT_PUBLISHED) {
        $links['antispam_comment_publish'] = array('title' => t('Publish'), 'href' => 'antispam/comment/'. $content->cid .'/publish');
      }
    }
    if (variable_get('antispam_comment_spam_links', 1)) {
      if (antispam_content_is_spam('comment', $content->cid)) {
        $links['antispam_comment_ham'] = array('title' => (variable_get('antispam_connection_enabled', 1) ? t('Submit ham') : t('Mark as ham')), 'href' => 'antispam/comment/'. $content->cid .'/submit-ham');
      }
      else {
        $links['antispam_comment_spam'] = array('title' => (variable_get('antispam_connection_enabled', 1) ? t('Submit spam') : t('Mark as spam')), 'href' => 'antispam/comment/'. $content->cid .'/submit-spam');
      }
    }
  }
  return $links;
}

/**
 * Menu callback; publish/unpublish content.
 *
 * @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 '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 { // comment
    // 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');
  }
  else if ($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, NULL, '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) {
// TODO: again passing object around and reloading the object with antispam_content_load.
  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 { // comment
    $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');
    }
  }
  else if ($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 { // comment
    drupal_goto('node/'. $content->nid, NULL, 'comment-'. $content->cid);
  }
}

/**
 * Implementation of hook_nodeapi().
 */
function antispam_nodeapi(&$node, $op, $teaser, $page) {
  switch ($op) {
    case 'insert':
    case 'update':
      // 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);
        break;
      }

      // 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);
        break;
      }

      // 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);
        break;
      }

      // 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];

          // Oops! We got spammed, let's mark the comment as such.
          antispam_content_spam_operation('node', $node, 'submit-spam', FALSE);
          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_get_types('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);
          }
        }
      }
      break;
    case 'delete':
      db_query('DELETE FROM {antispam_spam_marks} WHERE content_type = \'node\' AND content_id = %d', $node->nid);
      break;
    case 'load':
      $rec = db_fetch_object(db_query('SELECT signature, spaminess FROM {antispam_spam_marks} WHERE content_type = \'node\' AND content_id = %d', $node->nid));
      if ($rec) {
        return $rec;
      }
      else {
        return (array('signature' => '', 'spaminess' => 1));
      }
      break;
  }
}

/**
 * Implementation of hook_comment().
 */
function antispam_comment(&$comment, $op) {
  switch ($op) {
    case 'insert':
    case 'update':
      if (!variable_get('antispam_check_comments', 0) || antispam_is_spam_moderator('comments') || !variable_get('antispam_connection_enabled', 1)) {
        antispam_notify_moderators('comment', $comment, ($comment->status == COMMENT_PUBLISHED ? TRUE : FALSE), FALSE);
      }
      break;
    case 'delete':
      db_query('DELETE FROM {antispam_spam_marks} WHERE content_type = \'comment\' AND content_id = %d', $comment->cid);
      break;
    case 'view':
      $rec = db_fetch_object(db_query('SELECT signature, spaminess FROM {antispam_spam_marks} WHERE content_type = \'comment\' AND content_id = %d', $comment->cid));
      if ($rec) {
        return $rec;
      }
      else {
        return (array('signature' => '', 'spaminess' => 1));
      }
      break;
  }
}

/**
 * Implementation of hook_form_alter().
 */
function antispam_form_alter(&$form, $form_state, $form_id) {
  // Hook into comment edit/reply form.
  if ($form_id == 'comment_form' && variable_get('antispam_check_comments', 1)) {
    // ...only if current user is not moderator.
    if (!antispam_is_spam_moderator('comments')) {
      // ...also check if AntiSpam connections are enabled.
      if (variable_get('antispam_connection_enabled', 1)) {
        $form['#submit'][] = '_antispam_comment_form_submit';
      }
      // Inject anti-spambot code, if requested to.
      if (antispam_is_anti_spambot_enabled()) {
        $form['#validate'][] = '_antispam_comment_form_validate';
      }
    }
  }
  // Hook into node edit form.
  else if (isset($form['type']) && $form['type']['#value'] .'_node_form' == $form_id) {
    // ...only if current user is not moderator.
    if (!antispam_is_spam_moderator('comments')) {
      // ...also check if it's about a node type that we have been explicitly requested to check.
      $check_nodetypes = variable_get('antispam_check_nodetypes', array());
      $node_type = $form['type']['#value'];
      if (is_array($check_nodetypes) && isset($check_nodetypes[$node_type]) && $check_nodetypes[$node_type]) {
        // Inject anti-spambot code, if requested to.
        if (antispam_is_anti_spambot_enabled()) {
          $form['#validate'][] = '_antispam_node_form_validate';
        }
      }
    }
  }
}

/**
 * Node form validate callback; check for spambots.
 */
function _antispam_node_form_validate($form, &$form_state) {
  // Quit if there have already been errors in the form.
  if (form_get_errors()) {
    return;
  }

  // Ok, let's build a quick query to see if we can catch a spambot.
  global $user;
  $antispambot_rules = antispam_get_anti_spambot_rules();
  $sql_where = array();
  $sql_args = array();
  if ($antispambot_rules['ip']) {
    $sql_where[] = 's.hostname = \'%s\'';
    $sql_args[] = ip_address();
  }
  if ($antispambot_rules['body'] && !empty($form_state['values']['body'])) {
    $sql_where[] = 'r.body = \'%s\'';
    $sql_args[] = $form_state['values']['body'];
  }
  if ($antispambot_rules['mail'] && !empty($user->mail)) {
    $sql_where[] = 's.mail = \'%s\'';
    $sql_args[] = $user->mail;
  }

  if (count($sql_where) > 0) {
    if ($antispambot_rules['body'] || $antispambot_rules['mail']) {
      $sql_stmt = 'SELECT 1 FROM {node} n INNER JOIN {node_revisions} r ON r.nid = n.nid INNER JOIN {antispam_spam_marks} s ON s.content_type = \'node\' AND s.content_id = n.nid WHERE (%cond)';
    }
    else {
      $sql_stmt = 'SELECT 1 FROM {antispam_spam_marks} s WHERE s.content_type = \'node\' AND (%cond)';
    }
    $sql_stmt = str_replace('%cond', implode(' OR ', $sql_where), $sql_stmt);
    if (db_result(db_query($sql_stmt, $sql_args, 0, 1))) {
      antispam_anti_spambot_action(array(
        t('SQL') => _antispam_translate_query($sql_stmt, $sql_args),
        t('E-mail') => (isset($user->mail) ? $user->mail : ''),
        t('Body') => $form_state['values']['body']
      ));
    }
  }
}

/**
 * Comment form validate callback; check for spambots.
 */
function _antispam_comment_form_validate($form, &$form_state) {
  // Quit if there have already been errors in the form.
  if (form_get_errors()) {
    return;
  }

  // Ok, let's build a quick query to see if we can catch a spambot.
  $antispambot_rules = antispam_get_anti_spambot_rules();
  $sql_where = array();
  $sql_args = array();
  if ($antispambot_rules['ip']) {
    $sql_where[] = 's.hostname = \'%s\'';
    $sql_args[] = ip_address();
  }
  if ($antispambot_rules['body'] && !empty($form_state['values']['comment'])) {
    $sql_where[] = 'c.comment = \'%s\'';
    $sql_args[] = $form_state['values']['comment'];
  }
  if ($antispambot_rules['mail'] && !empty($form_state['values']['mail'])) {
    $sql_where[] = 's.mail = \'%s\'';
    $sql_args[] = $$form_state['values']['mail'];
  }

  if (count($sql_where) > 0) {
    if ($antispambot_rules['body'] || $antispambot_rules['mail']) {
      $sql_stmt = 'SELECT 1 FROM {comments} c INNER JOIN {antispam_spam_marks} s ON s.content_type = \'comment\' AND s.content_id = c.cid WHERE (%cond)';
    }
    else {
      $sql_stmt = 'SELECT 1 FROM {antispam_spam_marks} s WHERE s.content_type = \'comment\' AND (%cond)';
    }
    $sql_stmt = str_replace('%cond', implode(' OR ', $sql_where), $sql_stmt);
    if (db_result(db_query($sql_stmt, $sql_args, 0, 1))) {
      antispam_anti_spambot_action(array(
        t('SQL') => _antispam_translate_query($sql_stmt, $sql_args),
        t('E-mail') => $form_state['values']['mail'],
        t('Comment') => $form_state['values']['comment']
      ));
    }
  }
}

/**
 * Comment form submit callback; check for spam.
 */
function _antispam_comment_form_submit($form, &$form_state, $original_submit_callback = NULL) {
  // Our default destination. It doesn't need to override the original.
  $goto = NULL;

  // We need to get the comment id ($cid) to load the comment.
  if (isset($form_state['values']['cid'])) {
    $cid = $form_state['values']['cid'];
  }
  else {
    $goto = $form_state['redirect'];
    if (is_array($goto) && isset($goto[2]) 
        && preg_match('#^comment-([0-9]+)$#', $goto[2], $match)) {
      $cid = $match[1];
      $goto[2] = NULL;
    }
  }

pixture's avatar
 
pixture committed
  //---- #658990 ----
  // Quit asap, if current user has administration permission
  // or permission to post without spam checking.