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.
The antispam module 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 Permission panel, to grant %no-check-perm permission to user roles of your choice.
Content marked as spam is still saved into database so it can be reviewed by content administrators. There is an option that allows you to specify how long this information will be kept in the database. Spam older than a specified age will be automatically removed. Requires crontab.
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 content administrators to manually publish/unpublish content and mark/unmark content as spam, from links available at the bottom of content.
',
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('The AntiSpam module for Drupal allows you to use the Akismet anti-spam service to protect your site from being spammed.
',
array(
'!antispam-module-home' => url(ANTISPAM_MODULE_HOMEURL),
'!drupal' => url('http://drupal.org'),
'!akismet' => url('http://akismet.com'),
));
$output .= t('AntiSpam has caught @count spam for you since %since.
', 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 unpublished nodes 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 published nodes.');
$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 nodes marked as spam 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 unpublished comments 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 published comments.');
$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 comments marked as spam 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 .= '
';
$output .= t('Note: 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 ham 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('Akismet') : 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);
}
// Comment.
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);
}
// Comment.
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);
}
// Comment.
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[] = '' . check_plain($label) . ': ' . 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', '%messageAdditional information:
%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(<< 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' => '' . t('These options allow to customize the look of this antispam spam counter block.') . '
'
);
$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 AntiSpam, %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'] = '
';
if ($block_settings['type'] == 'image') {
$block_args['content'] = '' . $block_args['image'] . '';
}
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 : '');
}
// Comment.
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();
}
// Submit-ham.
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));
}
}
// Comment.
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') {
$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'] = render($content->body[$content->language][0]['value']);
}
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;
}
// Error - shouldn't happen.
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;
}