File: /home/ukqcurpj/www/wp-content/plugins/paid-memberships-pro/includes/spam.php
<?php
/**
* Code related to spam detection and prevention.
*/
// Constants. Define these in wp-config.php to override.
if ( ! defined( 'PMPRO_SPAM_ACTION_NUM_LIMIT' ) ) {
define( 'PMPRO_SPAM_ACTION_NUM_LIMIT', 10 );
}
if ( ! defined( 'PMPRO_SPAM_ACTION_TIME_LIMIT' ) ) {
define( 'PMPRO_SPAM_ACTION_TIME_LIMIT', 900 ); // in seconds
}
/**
* Determine whether the current visitor a spammer.
*
* @since 2.7
*
* @return bool Whether the current visitor a spammer.
*/
function pmpro_is_spammer() {
$is_spammer = false;
$activity = pmpro_get_spam_activity();
if ( false !== $activity && count( $activity ) >= PMPRO_SPAM_ACTION_NUM_LIMIT ) {
$is_spammer = true;
}
/**
* Allow filtering whether the current visitor is a spammer.
*
* @since 2.7
*
* @param bool $is_spammer Whether the current visitor is a spammer.
* @param array $activity The list of potential spam activity.
*/
return apply_filters( 'pmpro_is_spammer', $is_spammer, $activity );
}
/**
* Get the list of potential spam activity.
*
* @since 2.7
*
* @param string|null $ip The IP address to get activity for, or leave as null to attempt to determine current IP address.
*
* @return array|false The list of potential spam activity if successful, or false if IP could not be determined.
*/
function pmpro_get_spam_activity( $ip = null ) {
if ( empty( $ip ) ) {
$ip = pmpro_get_ip();
}
// If we can't determine the IP, let's bail.
if ( empty( $ip ) ) {
return false;
}
$ip = preg_replace( '/[^0-9a-fA-F:., ]/', '', $ip );
$transient_key = 'pmpro_spam_activity_' . $ip;
$activity = get_transient( $transient_key );
if ( empty( $activity ) || ! is_array( $activity ) ) {
$activity = [];
}
// Remove old items.
$new_activity = [];
$now = current_time( 'timestamp', true ); // UTC
foreach( $activity as $item ) {
// Determine whether this item is recent enough to include.
if ( $item > $now-( PMPRO_SPAM_ACTION_TIME_LIMIT ) ) {
$new_activity[] = $item;
}
}
return $new_activity;
}
/**
* Track spam activity.
* When we hit a certain number, the spam flag will trigger.
* For now we are only tracking credit card declines their timestamps.
* IP address isn't a perfect way to track this, but it's the best we have.
*
* @since 2.7
*
* @param string|null $ip The IP address to track activity for, or leave as null to attempt to determine current IP address.
*
* @return bool True if the tracking of activity was successful, or false if IP could not be determined.
*/
function pmpro_track_spam_activity( $ip = null ) {
// Make sure that we only track spam a maximum of once per page load.
static $already_tracked = false;
if ( $already_tracked ) {
return false;
}
$already_tracked = true;
// Get the IP address if it's not provided.
if ( empty( $ip ) ) {
$ip = pmpro_get_ip();
}
// If we can't determine the IP, let's bail.
if ( empty( $ip ) ) {
return false;
}
$activity = pmpro_get_spam_activity( $ip );
$now = current_time( 'timestamp', true ); // UTC
array_unshift( $activity, $now );
// If we have more than the limit, don't bother storing them.
if ( count( $activity ) > PMPRO_SPAM_ACTION_NUM_LIMIT ) {
rsort( $activity );
$activity = array_slice( $activity, 0, PMPRO_SPAM_ACTION_NUM_LIMIT );
}
// Save to transient.
$ip = preg_replace( '/[^0-9a-fA-F:., ]/', '', $ip );
$transient_key = 'pmpro_spam_activity_' . $ip;
set_transient( $transient_key, $activity, (int) PMPRO_SPAM_ACTION_TIME_LIMIT );
return true;
}
/**
* Clears all stored spam activity for an IP address.
* Note that the pmpro_get_spam_activity function clears out old values
* automatically, and this should only be used to completely clear the activity.
*
* @since 2.7
*
* @param string|null $ip The IP address to clear activity for, or leave as null to attempt to determine current IP address.
*
* @return bool True if the clearing of activity was successful, or false if IP could not be determined.
*/
function pmpro_clear_spam_activity( $ip = null ) {
if ( empty( $ip ) ) {
$ip = pmpro_get_ip();
}
// If we can't determine the IP, let's bail.
if ( empty( $ip ) ) {
return false;
}
$transient_key = 'pmpro_spam_activity_' . $ip;
delete_transient( $transient_key );
return true;
}
/**
* Track spam activity when checkouts or billing updates fail.
*
* @since 2.7
* @param MemberOrder $morder The order object used at checkout. We ignore it.
*/
function pmpro_track_failed_checkouts_for_spam( $morder ) {
// Bail if Spam Protection is disabled.
$spamprotection = get_option("pmpro_spamprotection");
if ( empty( $spamprotection ) ) {
return;
}
pmpro_track_spam_activity();
}
add_action( 'pmpro_checkout_processing_failed', 'pmpro_track_failed_checkouts_for_spam' );
add_action( 'pmpro_update_billing_failed', 'pmpro_track_failed_checkouts_for_spam' );
/**
* Disable checkout and billing update forms for spammers.
*
* We're using the pmpro_required_billing_fields filter because it is
* consistently used on the checkout and update billing pages.
* The pmpro_setMessage() function sets the $pmpro_msgt value to error,
* which stops the forms from working and shows the corresponding error.
* We return the $required_fields parameter to keep the filter working.
*
* @since 2.7
*
* @param array $required_fields The list of required fields.
*
* @return array The list of required fields.
*/
function pmpro_disable_checkout_for_spammers( $required_fields ) {
// Bail if Spam Protection is disabled.
$spamprotection = get_option("pmpro_spamprotection");
if ( empty( $spamprotection ) ) {
return $required_fields;
}
if ( pmpro_was_checkout_form_submitted() && pmpro_is_spammer() ) {
pmpro_setMessage( __( 'Suspicious activity detected. Try again in a few minutes.', 'paid-memberships-pro' ), 'pmpro_error' );
}
return $required_fields;
}
add_filter( 'pmpro_required_billing_fields', 'pmpro_disable_checkout_for_spammers' );
/**
* Track spam when trying to apply discount codes.
*
* @param bool $okay true if code check is okay or false if there was an error.
* @param object $dbcode Object containing code data from the database row.
*/
function pmpro_check_discount_code_spam_check( $okay, $dbcode ) {
// Bail if Spam Protection is disabled.
$spamprotection = get_option( 'pmpro_spamprotection' );
if ( empty( $spamprotection ) ) {
return $okay;
}
// If we already know that the visitor is a spammer, we don't need to check again.
if ( pmpro_is_spammer() ) {
// Returning a string is considered returning an error message.
return __( 'Suspicious activity detected. Try again in a few minutes.', 'paid-memberships-pro' );
}
// If the discount code is not a valid code on the site, track the activity.
if ( empty( $dbcode->id ) ) {
pmpro_track_spam_activity();
}
return $okay;
}
add_filter( 'pmpro_check_discount_code', 'pmpro_check_discount_code_spam_check', 10, 2 );
/**
* Track failed login attempts as spam.
* @param string $username The username that failed to login.
*/
function pmpro_track_login_spam( $username ) {
// Bail if Spam Protection is disabled.
$spamprotection = get_option( 'pmpro_spamprotection' );
if ( empty( $spamprotection ) ) {
return;
}
// Okay, track the spam.
pmpro_track_spam_activity();
}
add_action( 'wp_login_failed', 'pmpro_track_login_spam', 9, 1 ); // Priority 9 to run before pmpro_login_failed()
/**
* Block spammers from logging in.
* @param string $user_login The username that is trying to login.
* @param string $user_password The password provided by the user.
*/
function pmpro_block_spammers_from_login( $user_login, $user_password ) {
// Bail if no information was submitted.
if ( empty( $user_login ) && empty( $user_password ) ) {
return;
}
// Bail if Spam Protection is disabled.
$spamprotection = get_option( 'pmpro_spamprotection' );
if ( empty( $spamprotection ) ) {
return;
}
// Bail if the bypass constant is set.
if ( defined( 'PMPRO_BYPASS_LOGIN_BLOCK' ) && PMPRO_BYPASS_LOGIN_BLOCK ) {
return;
}
// Check if the user is a spammer.
if ( pmpro_is_spammer() ) {
// Throw a PHP notice.
die( esc_html__( 'Suspicious activity detected. Try again in a few minutes.', 'paid-memberships-pro' ) );
}
}
add_action( 'wp_authenticate', 'pmpro_block_spammers_from_login', 1, 2 );