Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
e290f02
Move honeypot setting and update honeypot field markup
truongwp Mar 19, 2025
45a5f2e
Use JS to hide honeypot fields
truongwp Mar 19, 2025
bec48f8
Add stopforumspam support
truongwp Mar 21, 2025
144f4e1
Complete stopforumspam check
truongwp Mar 26, 2025
375d0aa
Add blacklist spam check
truongwp Apr 7, 2025
37db53d
Reuse existing blacklist_check() method
truongwp Apr 8, 2025
5d64fc6
Add custom blacklist and whitelist settings
truongwp Apr 8, 2025
a350faf
Add WP spam comment check setting
truongwp Apr 9, 2025
15d63c4
Fix blacklist check doesn't work at some cases
truongwp Apr 14, 2025
52ee754
Complete wp spam comment check
truongwp Apr 14, 2025
2a990b4
Remove unused code
Apr 15, 2025
3553510
Improving the antispam code
truongwp Apr 17, 2025
5502046
Improve blacklist IP check
truongwp Apr 21, 2025
ca4ca9d
Update blacklist check
truongwp Apr 21, 2025
912073c
Completed blacklist check improvement
truongwp Apr 22, 2025
68e977d
Clean code
truongwp Apr 23, 2025
edd53ee
Update unit tests for honeypot
truongwp Apr 24, 2025
b4a235e
Fix some unit tests errors
truongwp Apr 24, 2025
b10602a
Adding unit tests for blacklist check
truongwp Apr 24, 2025
a7a2f39
Complete unit tests for blacklist data
truongwp Apr 25, 2025
4597c3d
Fix Psalm and PHPCS
truongwp Apr 25, 2025
a9a04a5
Fix PHPStan and PHPCS
truongwp Apr 25, 2025
5119f38
Merge branch 'master' into antispam-improvements
truongwp Apr 25, 2025
57e29bf
Fix PHPStan
truongwp Apr 25, 2025
867468e
Fix workflow
truongwp Apr 25, 2025
ed74b78
Fix error in unit tests
truongwp Apr 26, 2025
3a0247b
Add doc comments
truongwp Apr 28, 2025
64d97a7
Remove debug code
truongwp Apr 28, 2025
ac62c29
Fix PHPStan
truongwp Apr 28, 2025
e525e71
Fix Psalm
truongwp Apr 28, 2025
42316ed
Fix typos check
truongwp Apr 28, 2025
010838d
Fix unit tests
truongwp Apr 28, 2025
1a22b52
Fix phpunit
truongwp Apr 28, 2025
fdafc93
Update classes/models/FrmHoneypot.php
truongwp Apr 28, 2025
f6cffef
Update get spam comments for better performance
truongwp Apr 28, 2025
ed58657
Fix PHP notice
truongwp Apr 28, 2025
833b929
Support regex spam check
truongwp Apr 28, 2025
8ecb484
Fix stopforumspam
truongwp Apr 28, 2025
060af66
Fix use WP spam comment check
truongwp Apr 28, 2025
0c0796c
Remove TODO
truongwp Apr 29, 2025
e815b10
Merge branch 'master' into antispam-improvements
truongwp Apr 29, 2025
b79ab9e
Add filter hook for whitelist IP
truongwp Apr 29, 2025
b2c2c4b
Rename blacklist folder
truongwp May 1, 2025
b2eee42
Update honeypot tooltip
truongwp May 1, 2025
f1c45d5
Change setting label
truongwp May 1, 2025
e0ad9eb
Rename whitelist IP in code
truongwp May 1, 2025
172b21b
Use denylist instead of blacklist
truongwp May 1, 2025
64440c5
Fix unit tests
truongwp May 6, 2025
1694721
Rename class
truongwp May 6, 2025
46ad799
Update IP check to match IP CIDR format
truongwp May 6, 2025
640da78
Update stopforumspam tooltip
truongwp May 6, 2025
c735512
Add some filters to stopforumspam request
truongwp May 6, 2025
36d7f8f
Add IPv6 loopback address to allowed list
truongwp May 6, 2025
310cfac
Handle stopforumspam failed request
truongwp May 6, 2025
6b99949
Fix unit tests
truongwp May 6, 2025
05c6810
Only support IPv4 for CIDR check
truongwp May 6, 2025
124872c
Add splorp WP comment denylist
truongwp May 6, 2025
12fecdb
Fix unit tests
truongwp May 6, 2025
95d13ab
Fix phpcs
truongwp May 6, 2025
3e37e71
Fix phpcs
truongwp May 6, 2025
01d8a48
Add missing denylist-ip test file
truongwp May 6, 2025
8fd4d90
Merge branch 'master' into antispam-improvements
truongwp May 7, 2025
036c2fc
Fix accessibility issue
truongwp May 7, 2025
d7ed1a3
Use number input for reCAPTCHA threshold setting
truongwp May 8, 2025
3336b08
Update stopforumspam tooltip
truongwp May 8, 2025
146c723
Skip Splord denylist for users with create entries permisison
truongwp May 8, 2025
3a5c129
Add unit tests
truongwp May 8, 2025
c8bf541
Support custom spam message for each check
truongwp May 8, 2025
2a3a82d
Prevent honeypot field is tabbed through
truongwp May 8, 2025
b28f7e6
Prevent autofilling to honeypot field
truongwp May 8, 2025
c52efad
Fix PHP notice related to form_id
truongwp May 8, 2025
8875269
Print CSS for honeypot field if form is loaded via API
truongwp May 8, 2025
1706f1c
Support skipping field types for each denylist
truongwp May 8, 2025
d611f6c
Skip Splord check for file type
truongwp May 8, 2025
dfdedd0
Fix phpcs
truongwp May 8, 2025
085afb5
Fix PHPStan
truongwp May 8, 2025
1535ecd
Fix phpcs
truongwp May 8, 2025
04932ce
Ignore deprecated function for now (Psalm)
Crabcyborg May 8, 2025
d7e0ad7
Merge branch 'master' into antispam-improvements
Crabcyborg May 8, 2025
d052044
Fix e2e test
Crabcyborg May 8, 2025
21bf0ff
Fix another e2e test line
Crabcyborg May 8, 2025
1a4e7ea
Fix unit tests
truongwp May 9, 2025
ceda63a
Use esc_html()
truongwp May 9, 2025
1ee99e0
Move honeypot field to the top of fields
truongwp May 9, 2025
a9be6ff
Remove unnecessary for attribute
truongwp May 9, 2025
62e0972
Revert moving honeypot field
truongwp May 9, 2025
276bc20
Add more doc comments
truongwp May 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions _typos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ check-file = false
extend-glob = ["*.xml"]
check-file = false

[type.txt]
extend-glob = ["denylist/*.txt"]
check-file = false

[default]
extend-ignore-re = [
"/mis'",
Expand Down
128 changes: 128 additions & 0 deletions classes/controllers/FrmAntiSpamController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<?php
/**
* Anti-spam controller
*
* @package Formidable
* @since x.x
*/

if ( ! defined( 'ABSPATH' ) ) {
die( 'You are not allowed to call this page directly.' );
}

class FrmAntiSpamController {

/**
* Checks if given entry values is spam.
*
* @param array $values Entry values.
*
* @return bool|string Return spam message if is spam or `false` if is not spam.
*/
public static function is_spam( $values ) {
$methods = array(
'contains_wp_disallowed_words',
'is_denylist_spam',
'is_stopforumspam_spam',
'is_wp_comment_spam',
);

foreach ( $methods as $method ) {
if ( ! is_callable( array( __CLASS__, $method ) ) ) {
continue;
}

$is_spam = call_user_func( array( __CLASS__, $method ), $values );
if ( $is_spam ) {
return $is_spam;
}
}

return false;
}

/**
* Checks spam using stopforumspam API.
*
* @param array $values Entry values.
*
* @return bool|string Return spam message if is spam or `false` if is not spam.
*/
private static function is_stopforumspam_spam( $values ) {
$spam_check = new FrmSpamCheckStopForumSpam( $values );
return $spam_check->is_spam();
}

/**
* Checks spam using WordPress spam comments.
*
* @param array $values Entry values.
*
* @return bool|string Return spam message if is spam or `false` if is not spam.
*/
private static function is_wp_comment_spam( $values ) {
$spam_check = new FrmSpamCheckUseWPComments( $values );
return $spam_check->is_spam();
}

/**
* Checks spam using WordPress disallowed words.
*
* @param array $values Entry values.
*
* @return bool|string Return spam message if is spam or `false` if is not spam.
*/
public static function contains_wp_disallowed_words( $values ) {
$spam_check = new FrmSpamCheckWPDisallowedWords( $values );
return $spam_check->is_spam();
}

/**
* Checks spam using denylist.
*
* @param array $values Entry values.
*
* @return bool|string Return spam message if is spam or `false` if is not spam.
*/
public static function is_denylist_spam( $values ) {
$spam_check = new FrmSpamCheckDenylist( $values );
return $spam_check->is_spam();
}

/**
* Gets spam message.
*
* @return string
*/
public static function get_default_spam_message() {
return __( 'Your entry appears to be spam!', 'formidable' );
}

/**
* Extracts email addresses from values.
*
* @param array $values Values to check.
* @return string[]
*/
public static function extract_emails_from_values( $values ) {
$values = FrmAppHelper::maybe_json_encode( $values );
preg_match_all( '/[\._a-zA-Z0-9-]+@[\._a-zA-Z0-9-]+/i', $values, $matches );
return $matches[0];
}

/**
* Gets allowed IP addresses.
*
* @return string[]
*/
public static function get_allowed_ips() {
/**
* Filter the allowed IP addresses.
*
* @since x.x
*
* @params string[] $allowed_ips Allowed IP addresses.
*/
return apply_filters( 'frm_allowed_ips', array( '', '127.0.0.1', '::1' ) );
}
}
6 changes: 5 additions & 1 deletion classes/controllers/FrmFormsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -1543,7 +1543,7 @@ public static function render_spam_settings( $values ) {
if ( function_exists( 'akismet_http_post' ) ) {
include FrmAppHelper::plugin_path() . '/classes/views/frm-forms/spam-settings/akismet.php';
}
include FrmAppHelper::plugin_path() . '/classes/views/frm-forms/spam-settings/honeypot.php';
include FrmAppHelper::plugin_path() . '/classes/views/frm-forms/spam-settings/stopforumspam.php';
include FrmAppHelper::plugin_path() . '/classes/views/frm-forms/spam-settings/antispam.php';
}

Expand Down Expand Up @@ -3222,6 +3222,10 @@ public static function footer_js( $location = 'footer' ) {
// load formidable js
wp_enqueue_script( 'formidable' );
}

if ( ! FrmAppHelper::is_admin() ) {
FrmHoneypot::maybe_print_honeypot_js();
}
}

/**
Expand Down
2 changes: 1 addition & 1 deletion classes/controllers/FrmSettingsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ private static function get_settings_tabs() {
'captcha' => array(
'class' => __CLASS__,
'function' => 'captcha_settings',
'name' => __( 'Captcha', 'formidable' ),
'name' => __( 'Captcha/Spam', 'formidable' ),
'icon' => 'frm_icon_font frm_shield_check_icon',
),
'white_label' => array(
Expand Down
4 changes: 3 additions & 1 deletion classes/helpers/FrmAppHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -2528,10 +2528,12 @@ private static function maybe_clear_long_key( $key, $column ) {
}

/**
* @since x.x This is changed from `private` to `public`.
*
* @param int $num_chars
* @return string
*/
private static function generate_new_key( $num_chars ) {
public static function generate_new_key( $num_chars ) {
$max_slug_value = pow( 36, $num_chars );

// We want to have at least 2 characters in the slug.
Expand Down
32 changes: 31 additions & 1 deletion classes/helpers/FrmFormsHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ public static function get_default_opts() {
'success_msg' => $frm_settings->success_msg,
'show_form' => 0,
'akismet' => '',
'honeypot' => 'basic',
'stopforumspam' => 0,
'antispam' => 0,
'no_save' => 0,
'ajax_load' => 0,
Expand Down Expand Up @@ -1862,6 +1862,36 @@ public static function should_block_preview( $form_key ) {
return $should_block;
}

/**
* Checks if the form is loaded by API.
*
* @since x.x
*
* @return bool
*/
public static function form_is_loaded_by_api() {
if ( ! class_exists( 'FrmAPIAppController' ) ) {
return false;
}

$url = FrmAppHelper::get_server_value( 'REQUEST_URI' );
if ( 0 === strpos( $url, '/wp-json/frm/v2/forms/' ) ) {
// Prevent the honeypot from appearing for an API loaded form.
// This is to prevent conflicts where the script is not working.
return true;
}

if ( is_callable( 'FrmProFormState::get_from_request' ) ) {
$api = FrmProFormState::get_from_request( 'a', 0 );

if ( $api ) {
return true;
}
}

return false;
}
Comment thread
Crabcyborg marked this conversation as resolved.

/**
* @since 3.0
* @deprecated 6.11
Expand Down
65 changes: 17 additions & 48 deletions classes/models/FrmEntryValidate.php
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ private static function create_regular_expression_from_format( $pattern ) {
}

/**
* Check for spam
* Check for spam.
*
* @param bool $exclude
* @param array $values
Expand All @@ -428,12 +428,16 @@ public static function spam_check( $exclude, $values, &$errors ) {
}

$antispam_check = self::is_antispam_check( $values['form_id'] );
$spam_msg = FrmAntiSpamController::get_default_spam_message();
if ( is_string( $antispam_check ) ) {
$errors['spam'] = $antispam_check;
} elseif ( self::is_honeypot_spam( $values ) || self::is_spam_bot() ) {
$errors['spam'] = __( 'Your entry appears to be spam!', 'formidable' );
} elseif ( self::blacklist_check( $values ) ) {
$errors['spam'] = __( 'Your entry appears to be blocked spam!', 'formidable' );
$errors['spam'] = $spam_msg;
} else {
$is_spam = FrmAntiSpamController::is_spam( $values );
if ( $is_spam ) {
$errors['spam'] = $is_spam;
}
}

if ( isset( $errors['spam'] ) || self::form_is_in_progress( $values ) ) {
Expand Down Expand Up @@ -507,52 +511,15 @@ private static function is_akismet_enabled_for_user( $form_id ) {
return ( ! empty( $form->options['akismet'] ) && ( $form->options['akismet'] !== 'logged' || ! is_user_logged_in() ) );
}

public static function blacklist_check( $values ) {
if ( ! apply_filters( 'frm_check_blacklist', true, $values ) ) {
return false;
}

$mod_keys = trim( self::get_disallowed_words() );
if ( empty( $mod_keys ) ) {
return false;
}

$content = FrmEntriesHelper::entry_array_to_string( $values );

self::prepare_values_for_spam_check( $values );
$ip = FrmAppHelper::get_ip_address();
$user_agent = FrmAppHelper::get_server_value( 'HTTP_USER_AGENT' );
$user_info = self::get_spam_check_user_info( $values );

return self::check_disallowed_words( $user_info['comment_author'], $user_info['comment_author_email'], $user_info['comment_author_url'], $content, $ip, $user_agent );
}

/**
* For WP 5.5 compatibility.
* Checks spam using WordPress disallowed words and Frm denylist.
*
* @since 4.06.02
*/
private static function check_disallowed_words( $author, $email, $url, $content, $ip, $user_agent ) {
if ( function_exists( 'wp_check_comment_disallowed_list' ) ) {
return wp_check_comment_disallowed_list( $author, $email, $url, $content, $ip, $user_agent );
}
// phpcs:ignore WordPress.WP.DeprecatedFunctions.wp_blacklist_checkFound
return wp_blacklist_check( $author, $email, $url, $content, $ip, $user_agent );
}

/**
* For WP 5.5 compatibility.
* @param array $values Entry values.
*
* @since 4.06.02
* @return bool
*/
private static function get_disallowed_words() {
$keys = get_option( 'disallowed_keys' );
if ( false === $keys ) {
// Fallback for WP < 5.5.
// phpcs:ignore WordPress.WP.DeprecatedParameterValues.Found
$keys = get_option( 'blacklist_keys' );
}
return $keys;
public static function blacklist_check( $values ) {
return FrmAntiSpamController::contains_wp_disallowed_words( $values ) || FrmAntiSpamController::is_denylist_spam( $values );
}

/**
Expand Down Expand Up @@ -625,11 +592,12 @@ private static function add_user_info_to_akismet( &$datas, $values ) {
* Gets user info for Akismet spam check.
*
* @since 5.0.13 Separate code for guest. Handle value of embedded|repeater.
* @since x.x This changed from private to public.
*
* @param array $values Entry values after running through {@see FrmEntryValidate::prepare_values_for_spam_check()}.
* @return array
*/
private static function get_spam_check_user_info( $values ) {
public static function get_spam_check_user_info( $values ) {
if ( ! is_user_logged_in() ) {
return self::get_spam_check_user_info_for_guest( $values );
}
Expand Down Expand Up @@ -924,10 +892,11 @@ private static function get_akismet_skipped_field_ids( $values ) {
* Prepares values array for spam check.
*
* @since 5.0.13
* @since x.x This changed from private to public.
*
* @param array $values Entry values.
*/
private static function prepare_values_for_spam_check( &$values ) {
public static function prepare_values_for_spam_check( &$values ) {
$form_ids = self::get_all_form_ids_and_flatten_meta( $values );
$values['form_ids'] = $form_ids;
}
Expand Down
Loading