Skip to content

Instantly share code, notes, and snippets.

@dlxsnippets
Last active June 4, 2023 19:37
Show Gist options
  • Save dlxsnippets/e5efe55f19bfca1c706b25283707fe63 to your computer and use it in GitHub Desktop.
Save dlxsnippets/e5efe55f19bfca1c706b25283707fe63 to your computer and use it in GitHub Desktop.
Pass Search Queries Through Akismet for Spam Checking
<?php
/**
* Plugin Name: Akismet Search Check
* Plugin URI: https://mediaron.com
* Description: Filter search terms through Akismet
* Version: 1.0.0
* Requires at least: 6.0
* Requires PHP: 7.3
* Author: Ronald Huereca
* Author URI: https://mediaron.com
* License: GPL v2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
*
* @package AkismetSearchCheck
*/
// Set to false to effectively disable this plugin and not check for spam.
define( 'MR_AKISMET_SEARCH_CHECK_ENABLED', true );
// MAX length of a search query. Anything above this limit will be checked. Set to zero to check everything.
define( 'MR_AKISMET_SEARCH_CHAR_MAX_LENGTH', 30 );
// Anything greater than this length will be rejected. Set to zero to disable.
define( 'MR_AKISMET_SEARCH_CHAR_REJECT_LENGTH', 75 );
// Skip Logged-in Users. Set to false to check logged-in users.
define( 'MR_AKISMET_SEARCH_SKIP_LOGGED_IN_USERS', true );
// Akismet content type. This is used to identify the content type in Akismet.
define( 'MR_AKISMET_CONTENT_TYPE', 'search-query' );
/**
* Class to handle search redirects. Class used to avoid global vars.
*/
class MR_Search_Redirect {
/**
* A variable to check if we've already validated to search.
*
* @var bool
*/
public static $already_checked = false;
}
// Exit early if disabled.
if ( ! MR_AKISMET_SEARCH_CHECK_ENABLED ) {
return;
}
/**
* Get a user's IP address.
*/
function mr_search_get_user_ip() {
if ( array_key_exists( 'HTTP_X_FORWARDED_FOR', $_SERVER ) && ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
if ( strpos( $_SERVER['HTTP_X_FORWARDED_FOR'], ',' ) > 0 ) {
$addr = explode( ',', $_SERVER['HTTP_X_FORWARDED_FOR'] );
return trim( $addr[0] );
} else {
return $_SERVER['HTTP_X_FORWARDED_FOR'];
}
} else {
return $_SERVER['REMOTE_ADDR'];
}
}
// Check search query for spam with Akismet.
add_action(
'pre_get_posts',
function ( $query ) {
// REST API exclusion check.
if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
return;
}
// Skip if doing AJAX search (e.g., Jetpack Search).
if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
return;
}
// Skip in the admin panel searches.
if ( is_admin() ) {
return;
}
// Skip logged-in users.
if ( is_user_logged_in() && MR_AKISMET_SEARCH_SKIP_LOGGED_IN_USERS ) {
return;
}
// Get the search query, if any.
$search_query = $query->get( 's' );
// Are we in a search query?
if ( ! empty( $search_query ) ) {
// We are in a search query.
// Check to see if we've already checked.
if ( MR_Search_Redirect::$already_checked ) {
return;
}
// Set checked WP_Query var.
MR_Search_Redirect::$already_checked = true;
// Check if it's over a certain number of characters. If so, it's likely malicious.
if ( 0 < MR_AKISMET_SEARCH_CHAR_REJECT_LENGTH && strlen( $search_query ) >= MR_AKISMET_SEARCH_CHAR_REJECT_LENGTH ) {
// Redirect to reject URL.
status_header( 500, 'Cannot process search request.' );
exit;
}
// If less than limit, we can return early and not check the query.
if ( strlen( $search_query ) <= MR_AKISMET_SEARCH_CHAR_MAX_LENGTH && 0 !== MR_AKISMET_SEARCH_CHAR_MAX_LENGTH ) {
return;
}
// Check akismet for spam.
if ( class_exists( 'Akismet' ) ) {
$akismet_api_key = \Akismet::get_api_key();
if ( $akismet_api_key ) {
$akismet_fields = array();
$akismet_fields['blog'] = esc_url( home_url() );
$akismet_fields['comment_type'] = MR_AKISMET_CONTENT_TYPE;
$akismet_fields['comment_content'] = sanitize_text_field( $search_query );
$akismet_fields['contact_form_subject'] = sanitize_text_field( $search_query );
$akismet_fields['comment_author_IP'] = mr_search_get_user_ip();
$akismet_fields['user_ip'] = mr_search_get_user_ip();
$akismet_fields['referrer'] = sanitize_text_field( $_SERVER['HTTP_REFERER'] ?? '' );
// Get all the fields and consolidate.
$akismet_fields = http_build_query( $akismet_fields );
// Submit spam check.
$response = \Akismet::http_post( $akismet_fields, 'comment-check' );
// Get spam response.
$maybe_spam = (bool) filter_var( $response[1] ?? false, FILTER_VALIDATE_BOOLEAN );
if ( $maybe_spam ) {
// Set 500 error header.
status_header( 500, 'Cannot process search request.' );
exit;
}
}
}
}
},
1 /* super high priority */
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment