In order to use the Akismet Service, you need a WordPress.com API key. If you don\'t have one already, you can get it by simply signing up for a free account at wordpress.com. Please, consult the Akismet FAQ for further information.
The akismet 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 access control 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 akismet module 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'),
'!wpapikey' => url('http://wordpress.com/api-keys/'),
'!wordpress-com' => url('http://wordpress.com'),
'!akismet-faq' => url('http://akismet.com/faq/'),
'!akismet-settings' => url('admin/settings/akismet'),
'!access-control' => url('admin/access'),
'%no-check-perm' => t('post with no akismet checking')
));
return $output;
case 'admin/help/akismet':
case 'admin/settings/akismet':
$output = t('The akismet module for Drupal allows you to use the Akismet Service to protect your site from being spammed.
',
array(
'!akismet-module-home' => url(AKISMET_MODULE_HOMEURL),
'!drupal' => url('http://drupal.org'),
'!akismet' => url('http://akismet.com')
));
$output .= t('Akismet has caught @count spam for you since %since.
', array('@count' => akismet_get_spam_counter(), '%since' => akismet_get_counting_since()));
return $output;
case 'admin/content/akismet/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/akismet/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/akismet/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/akismet/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/akismet/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/akismet/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) == 'akismet' && !isset($_POST) && !empty($output)) {
$output .= '
'. t('Note: To interact fully with the Akismet 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 Akismet learn from its mistakes. Thank you.', array('!akismet' => url('http://akismet.com')));
}
}
// Check run-time requirements and status information
function akismet_requirements($phase) {
$t = get_t();
if ($phase == 'runtime') {
if (variable_get('akismet_wpapikey', '') == '') {
$requirements['akismet_key'] = array(
'title' => $t('Akismet API key'),
'value' => $t('Not present'),
'description' => $t("Akismet spam protection requires a WordPress.com API key to function. Obtain a key by signing up for a free account at WordPress.com, then enter the key on the Akismet settings page.",
array(
'!wpapikey' => url('http://wordpress.com/api-keys/'),
'!wordpress-com' => url('http://wordpress.com'),
'!akismet-settings' => url('admin/settings/akismet'),
)),
'severity' => REQUIREMENT_ERROR,
);
return $requirements;
}
}
}
/**
* Implementation of hook_perm().
*/
function akismet_perm() {
$perms = array('administer akismet 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 akismet checking';
return $perms;
}
/**
* Implementation of hook_cron().
*/
function akismet_cron() {
require_once('./'. drupal_get_path('module', 'akismet') . '/akismet_cron.inc');
register_shutdown_function('akismet_cron_shutdown');
}
function _akismet_moderator_types_count($types = array()) {
if (empty($types)) {
$types = akismet_get_moderator_types();
}
return count($types);
}
function _akismet_is_moderator($moderator_types = array(), $type = '') {
if (empty($moderator_types)) {
$moderator_types = akismet_get_moderator_types();
}
if (_akismet_moderator_types_count($moderator_types) > 0 && (empty($type) || isset($moderator_types[$type]))) {
return TRUE;
}
else {
return FALSE;
}
}
function _akismet_is_node_moderator($moderator_types = array()) {
if (empty($moderator_types)) {
$moderator_types = akismet_get_moderator_types();
}
if (_akismet_is_moderator($moderator_types) && (!_akismet_is_moderator($moderator_types, $type = 'comments') || _akismet_moderator_types_count($moderator_types) > 1)) {
return TRUE;
}
else {
return FALSE;
}
}
function _akismet_is_moderator_type($type, $types = array()) {
if (empty($types)) {
$types = akismet_get_moderator_types();
}
if (_akismet_moderator_types_count($types) > 0 && isset($types[$type])) {
return TRUE;
}
else {
return FALSE;
}
}
/**
* Implementation of hook_menu().
*/
function akismet_menu() {
$items = array();
$items['admin/settings/akismet'] = array(
'title' => t('Akismet'),
'description' => t('Use the Akismet Service to protect your site from spam.'),
'page callback' => 'drupal_get_form',
'page arguments' => array('akismet_settings'),
'access arguments' => array('administer akismet settings'),
'file' => 'akismet.admin.inc',
);
$moderator_types = akismet_get_moderator_types();
if (_akismet_is_moderator($moderator_types)) {
$items['admin/content/akismet'] = array(
'title' => t('Akismet moderation queue'),
'description' => t('Manage the Akismet spam queue, appropving or deleting content in need of moderation.'),
'page callback' => 'akismet_callback_queue',
'access callback' => '_akismet_is_moderator',
'access arguments' => array($moderator_types),
'file' => 'akismet.admin.inc',
);
$items['admin/content/akismet/overview'] = array(
'title' => t('Overview'),
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => 0,
'file' => 'akismet.admin.inc',
);
if (_akismet_is_node_moderator($moderator_types)) {
$items['admin/content/akismet/nodes'] = array(
'title' => t('Nodes'),
'page callback' => 'akismet_callback_queue',
'page arguments' => array('nodes'),
'access callback' => '_akismet_is_node_moderator',
'access arguments' => array($moderator_types),
'type' => MENU_LOCAL_TASK,
'weight' => 1,
'file' => 'akismet.admin.inc',
);
$items['admin/content/akismet/nodes/spam'] = array(
'title' => t('Spam'),
'page arguments' => array('nodes'),
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => 0,
'file' => 'akismet.admin.inc',
);
$items['admin/content/akismet/nodes/unpublished'] = array(
'title' => t('Unpublished nodes'),
'page arguments' => array('nodes', 'unpublished'),
'type' => MENU_LOCAL_TASK,
'weight' => 1,
'file' => 'akismet.admin.inc',
);
$items['admin/content/akismet/nodes/published'] = array(
'title' => t('Published nodes'),
'callback arguments' => array('nodes', 'published'),
'type' => MENU_LOCAL_TASK,
'weight' => 2,
'file' => 'akismet.admin.inc',
);
}
if (_akismet_is_moderator($moderator_types, 'comments')) {
$items['admin/content/akismet/comments'] = array(
'title' => t('Comments'),
'page callback' => 'akismet_callback_queue',
'page arguments' => array('comments'),
'access callback' => '_akismet_is_moderator',
'access arguments' => array($moderator_types, 'comments'),
'type' => MENU_LOCAL_TASK,
'weight' => 2,
'file' => 'akismet.admin.inc',
);
$items['admin/content/akismet/comments/spam'] = array(
'title' => t('Spam'),
'page arguments' => array('comments'),
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => 0,
'file' => 'akismet.admin.inc',
);
$items['admin/content/akismet/comments/unpublished'] = array(
'title' => t('Unpublished comments'),
'page arguments' => array('comments', 'unpublished'),
'type' => MENU_LOCAL_TASK,
'weight' => 1,
'file' => 'akismet.admin.inc',
);
$items['admin/content/akismet/comments/published'] = array(
'title' => t('Published comments'),
'page arguments' => array('comments', 'published'),
'type' => MENU_LOCAL_TASK,
'weight' => 2,
'file' => 'akismet.admin.inc',
);
}
}
else {
$item = array(
'title' => 'switch content status',
'page callback' => 'akismet_page',
'page arguments' => array(0, 1, 2, 3),
'load arguments' => array('%map', '%index'),
'access callback' => 'akismet_access_callback',
'access argument' => array(0, 1, 2, 3),
);
foreach (array('publish', 'unpublish', 'submit-spam', 'submit-ham') as $op) {
$items['akismet/%akismet/%/'. $op] = $item;
}
}
return $items;
}
function akismet_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' && module_exists('comment')) {
if (!$map[2] = comment_load($map[2])) {
return FALSE;
}
}
$op == $map[3];
if ($op == 'publish' || $op == 'unpublish') {
$map[0] = 'akismet_callback_set_published_status';
}
else if ($op == 'submit-spam' || $op == 'submit-ham') {
$map[0] = 'akismet_callback_set_spam_status';
}
return $map[$index];
}
function akismet_access_callback($callback, $content_type, $object, $op) {
if ($content_type == 'node' && !node_access($map[2])) {
return FALSE;
}
if (function_exists($callback && !akismet_is_spam_moderator(akismet_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 akismet_page($callback, $content_type, $object, $op) {
if (function_exists($callback)) {
return $callback($content_type, $object, $op);
}
drupal_not_found();
}
/**
* Implementation of hook_link().
*/
function akismet_link($type, $content = 0, $main = 0) {
$links = array();
if ($type == 'node' && akismet_is_spam_moderator($content->type)) {
if (variable_get('akismet_node_publish_links', 0)) {
if ($content->status) {
$links['akismet_node_unpublish'] = array('title' => t('Unpublish'), 'href' => 'akismet/node/'. $content->nid .'/unpublish');
}
else {
$links['akismet_node_publish'] = array('title' => t('Publish'), 'href' => 'akismet/node/'. $content->nid .'/publish');
}
}
if (variable_get('akismet_node_spam_links', 0)) {
if (akismet_content_is_spam('node', $content->nid)) {
$links['akismet_node_ham'] = array('title' => (variable_get('akismet_connection_enabled', 1) ? t('Submit ham') : t('Mark as ham')), 'href' => 'akismet/node/'. $content->nid .'/submit-ham');
}
else {
$links['akismet_node_spam'] = array('title' => (variable_get('akismet_connection_enabled', 1) ? t('Submit spam') : t('Mark as spam')), 'href' => 'akismet/node/'. $content->nid .'/submit-spam');
}
}
}
else if ($type == 'comment' && akismet_is_spam_moderator('comments')) {
if (variable_get('akismet_comment_publish_links', 1)) {
if ($content->status == COMMENT_PUBLISHED) {
$links['akismet_comment_unpublish'] = array('title' => t('Unpublish'), 'href' => 'akismet/comment/'. $content->cid .'/unpublish');
}
else if ($content->status == COMMENT_NOT_PUBLISHED) {
$links['akismet_comment_publish'] = array('title' => t('Publish'), 'href' => 'akismet/comment/'. $content->cid .'/publish');
}
}
if (variable_get('akismet_comment_spam_links', 1)) {
if (akismet_content_is_spam('comment', $content->cid)) {
$links['akismet_comment_ham'] = array('title' => (variable_get('akismet_connection_enabled', 1) ? t('Submit ham') : t('Mark as ham')), 'href' => 'akismet/comment/'. $content->cid .'/submit-ham');
}
else {
$links['akismet_comment_spam'] = array('title' => (variable_get('akismet_connection_enabled', 1) ? t('Submit spam') : t('Mark as spam')), 'href' => 'akismet/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 akismet_callback_set_published_status($content_type, $object, $op) {
// Load the content (existence has been checked in hook_menu).
$content = akismet_content_load($content_type, $object);
if ($content_type == 'node') {
$is_published = ($content->status ? TRUE : FALSE);
}
else { // comment
$is_published = ($content->status == COMMENT_PUBLISHED ? TRUE : FALSE);
}
if ($op == 'publish' && !$is_published) {
akismet_content_publish_operation($content_type, $content, 'publish');
}
else if ($op == 'unpublish' && $is_published) {
akismet_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 akismet_callback_set_spam_status($content_type, $object, $op) {
$is_spam = akismet_content_is_spam($content_type, $object);
// Load the content (existence has been checked in hook_menu).
$content = akismet_content_load($content_type, $object);
if ($content_type == 'node') {
$is_published = ($content->status ? TRUE : FALSE);
}
else { // comment
$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) {
akismet_content_spam_operation($content_type, $content, 'submit-spam');
}
if ($is_published) {
akismet_content_publish_operation($content_type, $content, 'unpublish');
}
}
else if ($op == 'submit-ham') {
if ($is_spam) {
akismet_content_spam_operation($content_type, $content, 'submit-ham');
}
if (!$is_published) {
akismet_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 akismet_nodeapi(&$node, $op, $teaser, $page) {
switch ($op) {
case 'insert':
case 'update':
// If Akismet connections are not enabled, we have nothing else to do here.
if (!variable_get('akismet_connection_enabled', 1)) {
akismet_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 (akismet_is_spam_moderator($node->type) || user_access('post with no akismet checking')) {
akismet_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('akismet_check_nodetypes', array());
if (!is_array($check_nodetypes) || !isset($check_nodetypes[$node->type]) || !$check_nodetypes[$node->type]) {
akismet_notify_moderators('node', $node, ($node->status ? TRUE : FALSE), FALSE);
break;
}
// Ok, let's send a query to Akismet.
$akismet_api_result = akismet_api_cmd_comment_check(akismet_prepare_comment_data('node', $node));
if ($akismet_api_result == AKISMET_API_RESULT_IS_HAM) {
akismet_notify_moderators('node', $node, ($node->status ? TRUE : FALSE), FALSE);
}
else {
if ($akismet_api_result == AKISMET_API_RESULT_IS_SPAM) {
// Oops! Akismet is telling us we got spammed, let's mark the comment as such.
akismet_content_spam_operation('node', $node, 'submit-spam', FALSE);
// Increment Akismet spam counter
variable_set('akismet_counter_spam', akismet_get_spam_counter() + 1);
akismet_notify_moderators('node', $node, FALSE, TRUE);
}
else {
akismet_notify_moderators('node', $node, FALSE, FALSE);
}
// Unpublish the node, if necessary.
if ($node->status) {
akismet_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 ($akismet_api_result == AKISMET_API_RESULT_ERROR) {
watchdog('content', 'Akismet 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 Akismet 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('akismet_antispambot_delay', 60)) > 0) {
sleep($seconds);
}
}
}
break;
case 'delete':
db_query('DELETE FROM {akismet_spam_marks} WHERE content_type = \'node\' AND content_id = %d', $node->nid);
break;
}
}
/**
* Implementation of hook_comment().
*/
function akismet_comment(&$comment, $op) {
switch ($op) {
case 'insert':
case 'update':
if (!variable_get('akismet_check_comments', 0) || akismet_is_spam_moderator('comments') || !variable_get('akismet_connection_enabled', 1)) {
akismet_notify_moderators('comment', $comment, ($comment->status == COMMENT_PUBLISHED ? TRUE : FALSE), FALSE);
}
break;
case 'delete':
db_query('DELETE FROM {akismet_spam_marks} WHERE content_type = \'comment\' AND content_id = %d', $comment->cid);
break;
}
}
/**
* Implementation of hook_form_alter().
*/
function akismet_form_alter(&$form, &$form_state, $form_id) {
// Hook into comment edit/reply form.
if ($form_id == 'comment_form' && variable_get('akismet_check_comments', 1)) {
// ...only if current user is not moderator.
if (!akismet_is_spam_moderator('comments')) {
// ...also check if Akismet connections are enabled.
if (variable_get('akismet_connection_enabled', 1)) {
// This is the simple hook method, *if* we already have the $cid.
$form['#submit'][] = '_akismet_comment_form_submit';
if (!isset($form['cid']) || !isset($form['cid']['#value']) || !is_numeric($form['cid']['#value'])) {
// This is a bit more complex, because the user is creating a new comment, so
// how can we get the $cid? See comments below, within our own submit callback.
$form['#comment_form_param1'] = $form['#submit'];
}
}
// Inject anti-spambot code, if requested to.
if (akismet_is_anti_spambot_enabled()) {
$form['#validate'][] = '_akismet_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 (!akismet_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('akismet_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 (akismet_is_anti_spambot_enabled()) {
$form['#validate'][] = '_akismet_node_form_validate';
}
}
}
}
}
/**
* Node form validate callback; check for spambots.
*/
function _akismet_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 = akismet_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 {akismet_spam_marks} s ON s.content_type = \'node\' AND s.content_id = n.nid WHERE (%cond)';
}
else {
$sql_stmt = 'SELECT 1 FROM {akismet_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))) {
akismet_anti_spambot_action(array(
t('SQL') => _akismet_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 _akismet_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 = akismet_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 {akismet_spam_marks} s ON s.content_type = \'comment\' AND s.content_id = c.cid WHERE (%cond)';
}
else {
$sql_stmt = 'SELECT 1 FROM {akismet_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))) {
akismet_anti_spambot_action(array(
t('SQL') => _akismet_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 _akismet_comment_form_submit($form, &$form_state, $original_submit_callback = NULL) {
// Our default destination. It doesn't need to override the original.
$goto = NULL;
// If the comment is being edited, then there's no problem to get the $cid.
// In this case, the original #submit callback has already been called.
if (isset($form_state['values']['cid'])) {
$cid = $form_state['values']['cid'];
}
// However, if the comment is being created, we'll try to get the $cid from the
// return value of the original #submit callback. It's an array that customizes
// the URL the user should be sent when the form is submitted. It contains the
// $cid in the last argument, in the form of "comment-$cid", the hash of the URL.
else {
// Invoke the previous submit callbacks and capture their return values to try
// to get the $cid from there.
// The first critical part with this approach is that we have to emmulate the
// $form argument that form.inc::drupal_submit_form() expects. At this point in
// time, this function just uses the '#submit' element, but that could change in
// the future. We have to keep an eye here, or think about a completely different
// approach. Hopefully, this one will remain stable during the 4.x lifecycle.
$form = array('#submit' => $original_submit_callback);
// The second critical part is that we expect to find the $cid in the 3rd element
// of the $goto array, as described above.
$goto = drupal_submit_form($form_id, $form);
if (is_array($goto) && isset($goto[2]) && preg_match('#^comment-([0-9]+)$#', $goto[2], $match)) {
$cid = $match[1];
}
}
// Once we have a $cid, we can (try to) load the comment with all relevant
// information that we need to make the Akismet request to check for spam.
if ($cid) {
$comment = akismet_content_load('comment', $cid);
// If we got a comment, send query to Akismet.
if ($comment) {
$akismet_api_result = akismet_api_cmd_comment_check(akismet_prepare_comment_data('comment', $comment));
if ($akismet_api_result == AKISMET_API_RESULT_IS_HAM) {
akismet_notify_moderators('comment', $comment, ($comment->status == COMMENT_PUBLISHED ? TRUE : FALSE), FALSE);
}
else {
if ($akismet_api_result == AKISMET_API_RESULT_IS_SPAM) {
// Oops! Akismet is telling us we got spammed, let's mark the comment as such.
akismet_content_spam_operation('comment', $comment, 'submit-spam', FALSE);
// Increment Akismet spam counter
variable_set('akismet_counter_spam', akismet_get_spam_counter() + 1);
akismet_notify_moderators('comment', $comment, FALSE, TRUE);
}
else {
akismet_notify_moderators('comment', $comment, FALSE, FALSE);
}
// Unpublish the comment, if necessary.
if ($comment->status == COMMENT_PUBLISHED) {
akismet_content_publish_operation('comment', $comment, 'unpublish', FALSE);
}
// Since users won't see their replies published, show them a polite explanation on why.
drupal_set_message(t('Your comment has been queued for moderation by site administrators and will be published after approval.'));
// Record the event to watchdog.
if ($akismet_api_result == AKISMET_API_RESULT_ERROR) {
watchdog('content', 'Akismet service seems to be down, comment queued for manual approval: %subject', array('%subject' => $comment->subject), WATCHDOG_WARNING, l(t('view'), 'node/'. $comment->nid, NULL, NULL, 'comment-'. $comment->cid));
}
else {
watchdog('content', 'Spam detected by Akismet in comment: %subject', array('%subject' => $comment->subject), WATCHDOG_WARNING, l(t('view'), 'node/'. $comment->nid, NULL, NULL, 'comment-'. $comment->cid));
// If requested to, generate a delay so the spammer has to wait for a while.
if (($seconds = variable_get('akismet_antispambot_delay', 60)) > 0) {
sleep($seconds);
}
}
}
}
}
// Return NULL or the destination returned by the original #submit callback.
return $goto;
}
/**
* Get anti-spambot rules.
*
* @return array
*/
function akismet_get_anti_spambot_rules() {
static $antispambot_rules = FALSE;
if (!$antispambot_rules) {
$antispambot_rules = array();
$options = variable_get('akismet_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 TRUE if enabled; FALSE otherwise.
*/
function akismet_is_anti_spambot_enabled() {
$antispambot_rules = akismet_get_anti_spambot_rules();
return (count($antispambot_rules) > 0 ? TRUE : FALSE);
}
/**
* Perform an anti-spambot action based on module settings.
*
* @param array Extra data, used here to enhance the logged information, for debugging purposes.
*/
function akismet_anti_spambot_action($debug_info) {
$antispambot_action = variable_get('akismet_antispambot_action', '503');
// First action is generate a delay, if requested to.
if (($seconds = variable_get('akismet_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).');
}
else if ($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('akismet', '%messageAdditional information:
%items', array('%message' => $message, '%items' => theme('item_list', $items)));
module_invoke_all('exit');
exit;
}
/**
* Expand query for debugging purposes.
*
* @param string SQL statement.
* @param mixed array or variable list of arguments.
*/
function _akismet_translate_query($query) {
$args = func_get_args();
array_shift($args);
$query = db_prefix_tables($query);
if (isset($args[0]) && is_array($args[0])) { // 'All arguments in one array' syntax
$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; can be either 'node' or 'comment'.
* @param integer Content ID; can be either a nid or a cid.
* @return boolean TRUE if content is marked as spam; FALSE otherwise.
*/
function akismet_content_is_spam($content_type, $content_id) {
return db_result(db_query('SELECT 1 FROM {akismet_spam_marks} WHERE content_type = \'%s\' AND content_id = %d', $content_type, $content_id));
}
/**
* Get moderator type required for specified content.
*
* @param string Content type; can be either 'node' or 'comment'.
* @param integer Content ID; can be either a nid or a cid.
* @return string Moderator Type or empty string if content is not found.
*/
function akismet_content_get_moderator_type($content_type, $content_id) {
if ($content_type == 'node') {
$moderator_type = db_result(db_query('SELECT type FROM {node} WHERE nid = %d', $content_id));
if (!$moderator_type) {
$moderator_type = '';
}
}
else if ($content_type == 'comment') {
$moderator_type = (db_result(db_query('SELECT 1 FROM {comments} WHERE cid = %d', $content_id)) ? 'comments' : '');
}
else {
$moderator_type = '';
}
return $moderator_type;
}
/**
* Get the types the current user is allowed to moderate.
*
* @param object The account to check; use current user if not given.
* @return array Moderator Types.
*/
function akismet_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_get_types('names');
}
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 (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 (comments, node type or NULL).
* @param object The account to check; use current user if not given.
* @return boolean TRUE if current user is moderator of specified type; FALSE otherwise.
*/
function akismet_is_spam_moderator($moderator_type = NULL, $account = NULL) {
global $user;
if (is_null($account)) {
$account = $user;
}
$moderator_types = akismet_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; can be either 'node' or 'comment'.
* @param object Content object.
* @param boolean TRUE if content is in published status.
* @param boolean TRUE if content has been marked as spam.
*/
function akismet_notify_moderators($content_type, $content, $is_published, $is_spam) {
global $user, $base_url;
// Proceed only if e-mail notifications are enabled.
if (!variable_get('akismet_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 = akismet_get_moderator_types($account);
$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 {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 {akismet_moderator} m ON m.uid = u.uid'.
' WHERE p.perm LIKE \'%%%s%%\''.
' OR p.perm LIKE \'%%%s%%\'';
$result = db_query($sql, $moderator_permission, $administer_permission);
$moderators = array();
while ($u = db_fetch_object($result)) {
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 = akismet_content_load('node', $content->nid))) {
watchdog('akismet', '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, NULL, NULL, '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_args['@type'] = $moderator_types[$content->type];
$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) {
drupal_mail(
'akismet_moderator_notification',
$email_to,
$message_title,
str_replace('@user-name', check_plain($moderators[$uid]['name']), $message_body)
);
}
}
/**
* Implementation of hook_user().
*/
function akismet_user($op, &$edit, &$account, $category = NULL) {
$moderator_email_for_options = array(
'all' => t('All new (or updated) content'),
'approval' => t('Only content needing approval'),
'never' => t('Never')
);
switch ($op) {
case 'form':
if ($category == 'account' && variable_get('akismet_email_enabled', 1)) {
$moderator_types = akismet_get_moderator_types($account);
$moderator_types_count = count($moderator_types);
if ($moderator_types_count > 0) {
$form = array();
$form['akismet_moderator'] = array(
'#type' => 'fieldset', '#title' => t('Akismet 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['akismet_moderator']['akismet_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[$edit['akismet_moderator_email_for']]) ? $edit['akismet_moderator_email_for'] : 'approval')
);
return $form;
}
}
break;
case 'load':
$moderator_types = akismet_get_moderator_types($account);
$moderator_types_count = count($moderator_types);
if ($moderator_types_count > 0) {
$moderator_data = db_fetch_object(db_query('SELECT * FROM {akismet_moderator} WHERE uid = %d', $account->uid));
$account->akismet_moderator_email_for = (isset($moderator_data->email_for) && isset($moderator_email_for_options[$moderator_data->email_for]) ? $moderator_data->email_for : 'approval');
}
break;
case 'insert':
case 'update':
$moderator_types = akismet_get_moderator_types($account);
$moderator_types_count = count($moderator_types);
if ($moderator_types_count > 0 && isset($edit['akismet_moderator_email_for'])) {
if (!isset($moderator_email_for_options[$edit['akismet_moderator_email_for']])) {
$edit['akismet_moderator_email_for'] = 'approval';
}
db_query('UPDATE {akismet_moderator} SET email_for = \'%s\' WHERE uid = %d', $edit['akismet_moderator_email_for'], $account->uid);
if (!db_affected_rows()) {
db_query('INSERT INTO {akismet_moderator} (uid, email_for) VALUES (%d, \'%s\')', $account->uid, $edit['akismet_moderator_email_for']);
}
$edit['akismet_moderator_email_for'] = NULL;
break;
}
// Fall through, to remove possible garbage.
case 'delete':
db_query('DELETE FROM {akismet_moderator} WHERE uid = %d', $account->uid);
break;
}
}
/**
* Get the current akismet spam counter.
*
* @return integer
*/
function akismet_get_spam_counter() {
return (int)variable_get('akismet_counter_spam', 0);
}
/**
* Format the 'Counting since' date.
*
* @return string Counting since date formatted according to module settings.
*/
function akismet_get_counting_since() {
$since = variable_get('akismet_counter_since', array('day' => date('j'), 'month' => date('n'), 'year' => date('Y')));
$format = variable_get('akismet_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', 0),
'F' => format_date(gmmktime(0, 0, 0, $since['month'], 2, 1970), 'custom', 'F', 0),
'Y' => $since['year']
);
return strtr($format, $replace);
}
/**
* Implementation of hook_block().
*/
function akismet_block($op = 'list', $delta = 0, $edit = array()) {
static $block_fields = FALSE;
if (!$block_fields) {
$block_fields = 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 Akismet link in new window'),
'#options' => array('1' => t('Enabled'), '0' => t('Disabled')),
'#default_value' => '1'
)
);
}
switch ($op) {
case 'list':
$blocks_counter = variable_get('akismet_blocks_counter', 1);
$blocks = array();
for ($i = 0; $i < $blocks_counter; $i++) {
$blocks[] = array('info' => t('Akismet spam counter (block: @count)', array('@count' => ($i+1))));
}
return $blocks;
case 'configure':
if ($delta < 0 || $delta >= variable_get('akismet_blocks_counter', 1)) {
drupal_set_message(t('Oops! You are requesting a non-existing block, changes will not be saved.'), 'error');
break;
}
$form = array();
$form['description'] = array(
'#type' => 'markup',
'#value' => ''. t('These options allow to customize the look of this akismet spam counter block.') .'
'
);
$block_settings = variable_get('akismet_blocks_'. $delta, FALSE);
foreach ($block_fields as $field_key => $field_info) {
$field_name = 'akismet_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;
case 'save':
if ($delta < 0 || $delta >= variable_get('akismet_blocks_counter', 1)) {
drupal_set_message(t('Oops! You have requested a non-existing block, changes were not saved.'), 'error');
break;
}
$block_settings = array();
foreach ($block_fields as $field_key => $field_info) {
$field_name = 'akismet_blocks_'. $delta .'_'. $field_key;
$block_settings[$field_key] = $edit[$field_name];
}
variable_set('akismet_blocks_'. $delta, $block_settings);
break;
case 'view':
if ($delta >= 0 && $delta < variable_get('akismet_blocks_counter', 1)) {
$block_settings = variable_get('akismet_blocks_'. $delta, FALSE);
if (!$block_settings) {
$block_settings = array();
}
$block_args = array(
'content' => '', // Built below.
'counter' => akismet_get_spam_counter(),
'since' => akismet_get_counting_since(),
'text' => '', // Built below.
'image' => '', // Built below.
'block' => array('delta' => $delta) // completed below with current block settings.
);
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"' : '');
$block_args['text'] = array(
'plain' => array(
'short' => t('Proudly protected by Akismet, @count spam caught since @since.', array('@site_name' => variable_get('site_name', 'drupal'), '@count' => $block_args['counter'], '@since' => $block_args['since'])),
'long' => t('@site_name is proudly protected by Akismet, @count spam caught since @since.', array('@site_name' => variable_get('site_name', 'drupal'), '@count' => $block_args['counter'], '@since' => $block_args['since'])),
),
'html' => array(
'short' => t('Proudly protected by Akismet, @count spam caught since @since', array('@site_name' => variable_get('site_name', 'drupal'), '!akismet' => url('http://akismet.com'), '@target' => $target, '@count' => $block_args['counter'], '@since' => $block_args['since'])),
'long' => t('@site_name is proudly protected by Akismet, @count spam caught since @since', array('@site_name' => variable_get('site_name', 'drupal'), '!akismet' => url('http://akismet.com'), '@target' => $target, '@count' => $block_args['counter'], '@since' => $block_args['since']))
)
);
$text_version = ($block_settings['sitename'] ? 'long' : 'short');
$title_text = $block_args['text']['plain'][$text_version];
$image_url = base_path() . drupal_get_path('module', 'akismet') .'/akismet.gif';
$block_args['image'] = '';
if ($block_settings['type'] == 'image') {
$block_args['content'] = ''. $block_args['image'] .'';
}
else {
$block_args['content'] = $block_args['text']['html'][$text_version];
}
$block = array();
$block['subject'] = t('Akismet spam counter');
$block['content'] = theme('akismet_counter_block', $block_args);
return $block;
}
break;
}
}
/**
* Implementation of hook_theme().
*/
function akismet_theme() {
return array(
'akismet_counter_block' => array(
'arguments' => 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 akismet spam counter block.
*
* @param array arguments 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_akismet_counter_block($args) {
return $args['content'];
}
/**
* Load a node or a comment.
*
* @param string Content type; can be either 'node' or 'comment'.
* @param integer Content ID; can be either a nid or a cid.
* @return mixed An object with requested content; FALSE on failure.
*/
function akismet_content_load($content_type, $content_id) {
if ($content_type == 'node') {
$content = node_load($content_id);
if (empty($content->nid)) {
$content = FALSE;
}
}
else if ($content_type == 'comment') {
$content = db_fetch_object(db_query(db_rewrite_sql('SELECT c.*, u.name AS registered_name FROM {comments} c INNER JOIN {users} u ON u.uid = c.uid WHERE c.cid = %d', 'c'), $content_id));
if (empty($content->cid)) {
$content = FALSE;
}
else if ($content->uid) {
$content->name = $content->registered_name;
}
}
else {
$content = FALSE;
}
return $content;
}
/**
* Delete a node or a comment.
*
* @param string Content type; can be either 'node' or 'comment'.
* @param integer Content ID; can be either a nid or a cid.
* @return boolean TRUE if specified was there; FALSE otherwise.
*/
function akismet_content_delete($content_type, $content_id) {
if ($content_type == 'node') {
$node = akismet_content_load($content_type, $content_id);
if ($node) {
// Sadly, we cannot invoke node_delete() because it checks for the
// user to have delete permission on nodes, and this function may
// be invoked from a cron task, which is run as anonymous user, so
// we have to hardcode the low level parts here.
// Code based on node.module::node_delete().
db_query('DELETE FROM {node} WHERE nid = %d', $node->nid);
db_query('DELETE FROM {node_revisions} WHERE nid = %d', $node->nid);
// Call the node-specific callback (if any):
node_invoke($node, 'delete');
node_invoke_nodeapi($node, 'delete');
// Remove this node from the search index if needed.
if (function_exists('search_wipe')) {
search_wipe($node->nid, 'node');
}
// Record the event to watchdog.
watchdog('content', '%type: deleted @title.', array('%type' => t($node->type), '@title' => $node->title));
return TRUE;
}
}
else if ($content_type == 'comment') {
$comment = akismet_content_load($content_type, $content_id);
if ($comment) {
_comment_delete_thread($comment);
_comment_update_node_statistics($comment->cid);
return TRUE;
}
}
return FALSE;
}
/**
* Clear Drupal cache.
*
* This function needs to be called so anonymous users get updated content
* when certain operations have been executed.
*/
function akismet_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; can be either 'node' or 'comment'.
* @param object Content object.
* @param string Operation; it can be 'submit-spam' or 'submit-ham'.
* @param boolean TRUE to log action (default); FALSE otherwise (ie. the caller logs the action).
*/
function akismet_content_spam_operation($content_type, $content, $op, $log_action = TRUE) {
if ($content_type == 'node') {
global $user;
$content_id = $content->nid;
$content_title = $content->title;
$content_link = l(t('view'), 'node/'. $content->nid);
$user_mail = (isset($user->mail) ? $user->mail : '');
}
else { // comment
$content_id = $content->cid;
$content_title = $content->subject;
$content_link = l(t('view'), 'node/'. $content->nid, NULL, NULL, 'comment-'. $content->cid);
$user_mail = $content->mail;
}
if ($op == 'submit-spam') {
$hostname = (!empty($content->hostname) ? $content->hostname : ip_address());
db_query('INSERT INTO {akismet_spam_marks} (content_type, content_id, spam_created, hostname, mail) VALUES (\'%s\', %d, %d, \'%s\', \'%s\')', $content_type, $content_id, time(), $hostname, $user_mail);
if (variable_get('akismet_connection_enabled', 1)) {
akismet_api_cmd_submit_spam(akismet_prepare_comment_data($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'));
}
}
else { // submit-ham
db_query('DELETE FROM {akismet_spam_marks} WHERE content_type = \'%s\' AND content_id = %d', $content_type, $content_id);
if (variable_get('akismet_connection_enabled', 1)) {
akismet_api_cmd_submit_ham(akismet_prepare_comment_data($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'));
}
}
if ($log_action) {
watchdog('content', '@action: @title', array('@action' => $action, '@title' => $content_title), WATCHDOG_NOTICE, $content_link);
}
akismet_clear_cache();
}
/**
* Execute content publish/unpublish operations.
*
* @param string Content type; it can be 'node' or 'comment'.
* @param object Content object.
* @param string Operation; it can be 'publish' or 'unpublish'.
* @param boolean TRUE to log action (default); FALSE otherwise (ie. the caller logs the action).
*/
function akismet_content_publish_operation($content_type, $content, $op, $log_action = TRUE) {
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_query('UPDATE {node} SET status = %d WHERE nid = %d', ($op == 'publish' ? 1 : 0), $content->nid);
if ($log_action) {
$action = ($op == 'publish' ? t('Content published') : t('Content unpublished'));
watchdog('content', '@action: @title', array('@action' => $action, '@title' => $content->title), WATCHDOG_NOTICE, l(t('view'), 'node/'. $content->nid));
}
}
else { // comment
// This code snippet is based on comment.module::comment_admin_overview_submit()
// Cache operations in case we need to come here more than once per request.
static $comment_operations = FALSE;
if (!$comment_operations) {
$comment_operations = comment_operations();
}
// extract the appropriate database query operation.
$query = $comment_operations[$op][1];
// perform the update action, then refresh node statistics.
db_query($query, $content->cid);
// Update comment statistics.
_comment_update_node_statistics($content->nid);
// Allow modules to respond to the updating of a comment.
comment_invoke_comment($content, $op);
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, NULL, NULL, 'comment-'. $content->cid));
}
}
akismet_clear_cache();
}
/**
* Prepare comment data for Akismet requests.
*
* @param string 'node' or 'comment'
* @param object $node or $comment
* @return array
*/
function akismet_prepare_comment_data($content_type, $content) {
// Prepare data that is common to nodes/comments.
$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,
);
// Prepare data that varies depending on type of content.
if ($content_type == 'comment') {
$comment_data['permalink'] = url('node/'. $content->nid, array('fragment' => 'comment-'. $content->cid, 'absolute' => TRUE));
$comment_data['comment_author_email'] = $content->mail;
$comment_data['comment_author_url'] = $content->homepage;
$comment_data['comment_content'] = $content->comment;
}
else {
global $user;
$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'] = $content->body;
}
return $comment_data;
}
/************************************************************\
Akismet API implementation
\************************************************************/
/**
* Akismet API: Key Verification - rest.akismet.com/1.1/verify key
*
* @param string WordPress API Key.
* @return integer See constants AKISMET_API_RESULT_xxx.
*/
function akismet_api_cmd_verify_key($key) {
global $base_url;
if (!empty($key)) {
$request = 'key='. $key .'&blog='. $base_url . base_path();
$response = _akismet_api_http_post($request, AKISMET_API_HOST, '/'. AKISMET_API_VERSION .'/verify-key');
}
return (isset($response[1]) && 'valid' == $response[1] ? AKISMET_API_RESULT_SUCCESS : AKISMET_API_RESULT_ERROR);
}
/**
* Akismet API: Comment Check - api-key.rest.akismet.com/1.1/comment-check
*
* @param array Comment data; see function akismet_prepare_comment_data().
* @return integer See constants AKISMET_API_RESULT_xxx.
*/
function akismet_api_cmd_comment_check($comment_data) {
if (!variable_get('akismet_connection_enabled', 1)) {
return AKISMET_API_RESULT_ERROR;
}
$akismet_wpapikey = variable_get('akismet_wpapikey', '');
if (!empty($akismet_wpapikey)) {
$comment_data = array_merge(_akismet_api_prepare_request_data(), $comment_data);
$query_string = _akismet_api_build_query_string($comment_data);
$host = $akismet_wpapikey .'.'. AKISMET_API_HOST;
$response = _akismet_api_http_post($query_string, $host, '/'. AKISMET_API_VERSION .'/comment-check');
}
if (!isset($response[1])) {
return AKISMET_API_RESULT_ERROR;
}
return ('true' == $response[1] ? AKISMET_API_RESULT_IS_SPAM : AKISMET_API_RESULT_IS_HAM);
}
/**
* Akismet API: Submit Spam - api-key.rest.akismet.com/1.1/submit-spam
*
* @param array Comment data; see function akismet_prepare_comment_data().
* @return integer See constants AKISMET_API_RESULT_xxx.
*/
function akismet_api_cmd_submit_spam($comment_data) {
if (!variable_get('akismet_connection_enabled', 1)) {
return AKISMET_API_RESULT_ERROR;
}
$akismet_wpapikey = variable_get('akismet_wpapikey', '');
if (!empty($akismet_wpapikey)) {
$query_string = _akismet_api_build_query_string($comment_data);
$host = $akismet_wpapikey .'.'. AKISMET_API_HOST;
$response = _akismet_api_http_post($query_string, $host, '/'. AKISMET_API_VERSION .'/submit-spam');
}
return (isset($response[1]) ? AKISMET_API_RESULT_SUCCESS : AKISMET_API_RESULT_ERROR);
}
/**
* Akismet API: Submit Ham - api-key.rest.akismet.com/1.1/submit-ham
*
* @param array Comment data; see function akismet_prepare_comment_data().
* @return integer See constants AKISMET_API_RESULT_xxx.
*/
function akismet_api_cmd_submit_ham($comment_data) {
if (!variable_get('akismet_connection_enabled', 1)) {
return AKISMET_API_RESULT_ERROR;
}
$akismet_wpapikey = variable_get('akismet_wpapikey', '');
if (!empty($akismet_wpapikey)) {
$query_string = _akismet_api_build_query_string($comment_data);
$host = $akismet_wpapikey .'.'. AKISMET_API_HOST;
$response = _akismet_api_http_post($query_string, $host, '/'. AKISMET_API_VERSION .'/submit-ham');
}
return (isset($response[1]) ? AKISMET_API_RESULT_SUCCESS : AKISMET_API_RESULT_ERROR);
}
/**
* Prepare user request data for Akismet requests.
*
* @return array Relevant information extracted from $_SERVER superglobal.
*/
function _akismet_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 Akismet request.
*
* @param array
* @return string
*/
function _akismet_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 akismet server.
*
* @param string The request.
* @param string The akismet host, prefixed with the API key (except when checking for the key itself).
* @param string The path where to perform the request.
* @return array Headers in $response[0] and entity in $response[1].
*/
function _akismet_api_http_post($request, $host, $path) {
$fsock_timeout = (int)variable_get('akismet_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: '. AKISMET_API_USERAGENT ."\r\n"
."\r\n"
.$request;
$response = '';
if (false !== ($fs = @fsockopen($host, AKISMET_API_PORT, $errno, $errstr, $fsock_timeout))) {
fwrite($fs, $http_request);
while (!feof($fs)) {
$response .= fgets($fs, 1160); // One TCP-IP packet
}
fclose($fs);
$response = explode("\r\n\r\n", $response, 2);
}
return $response;
}