Contact Form 7 anti spam improvements
All checks were successful
Generate Build Info / build-info (push) Successful in 2s
All checks were successful
Generate Build Info / build-info (push) Successful in 2s
This commit is contained in:
181
dapper.php
181
dapper.php
@@ -1093,6 +1093,8 @@ function dapper_woo_conditional_load() {
|
||||
|
||||
if ($lowercase_name || $suspicious_company || (float)$order->get_total_tax() === 0) {
|
||||
$order->update_status('cancelled', 'Blocked: Invalid or missing human verification for PayPal Express (bot pattern detected).');
|
||||
$order->update_meta_data( '_dapper_blocked_bot', '1' );
|
||||
$order->save();
|
||||
dapper_debug_log("Blocked fake PayPal order #$order_id - No valid token | Name: $first $last | Company: $company");
|
||||
}
|
||||
}
|
||||
@@ -1108,31 +1110,20 @@ function dapper_woo_conditional_load() {
|
||||
$order = wc_get_order( $order_id );
|
||||
if ( ! $order ) return;
|
||||
|
||||
// Only act on PayPal orders
|
||||
$method = $order->get_payment_method();
|
||||
if ( ! in_array( $method, [ 'paypal', 'ppec_paypal', 'ppcp-gateway', 'paypal_express', 'braintree_paypal' ], true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this cancellation came from our bot block function
|
||||
$notes = $order->get_customer_order_notes();
|
||||
$latest_note = !empty($notes) ? end($notes)->comment_content : '';
|
||||
|
||||
$bot_patterns = [
|
||||
'Blocked: Invalid or missing human verification',
|
||||
'bot pattern detected',
|
||||
];
|
||||
|
||||
$is_bot = false;
|
||||
foreach ($bot_patterns as $pattern) {
|
||||
if (stripos($latest_note, $pattern) !== false) {
|
||||
$is_bot = true;
|
||||
break;
|
||||
}
|
||||
// Primary check: our custom meta flag
|
||||
if ( $order->get_meta( '_dapper_blocked_bot' ) === '1' ) {
|
||||
wp_trash_post( $order_id );
|
||||
$order->add_order_note( 'Trashed by Dapper — obvious PayPal bot order (blocked by human check)' );
|
||||
dapper_debug_log( "Trashed obvious bot PayPal order #$order_id (meta flag present)" );
|
||||
return;
|
||||
}
|
||||
|
||||
// Extra safety: very young cancelled orders with 0 tax and suspicious name/company
|
||||
if (!$is_bot) {
|
||||
// Fallback safety net (still catch obvious patterns even if meta missed somehow)
|
||||
$age_minutes = ( time() - strtotime( $order->get_date_created() ) ) / 60;
|
||||
$first = strtolower( trim( $order->get_billing_first_name() ?: '' ) );
|
||||
$last = strtolower( trim( $order->get_billing_last_name() ?: '' ) );
|
||||
@@ -1141,41 +1132,28 @@ function dapper_woo_conditional_load() {
|
||||
if ( $age_minutes < 15 &&
|
||||
(float) $order->get_total_tax() === 0 &&
|
||||
( $first === $last || preg_match( '/^[a-z]{5,12}$/', $company ) ) ) {
|
||||
$is_bot = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($is_bot) {
|
||||
wp_trash_post( $order_id );
|
||||
$order->add_order_note('Moved to trash by Dapper — obvious PayPal bot order');
|
||||
dapper_debug_log("Trashed obvious bot PayPal order #$order_id");
|
||||
$order->add_order_note( 'Trashed by Dapper — obvious PayPal bot pattern (fallback check)' );
|
||||
dapper_debug_log( "Trashed obvious bot PayPal order #$order_id (fallback pattern match)" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
add_action( 'plugins_loaded', 'dapper_woo_conditional_load' ); // Late hook – safe for class_exists()
|
||||
|
||||
// ────────────────────────────────────────────────
|
||||
// Contact Form 7 protection (always loaded if CF7 exists)
|
||||
// ────────────────────────────────────────────────
|
||||
|
||||
if ( class_exists( 'WPCF7' ) ) {
|
||||
|
||||
// Option to enable/disable CF7 protection separately
|
||||
// You can add this to settings later if you want
|
||||
|
||||
// 1. Old honeypot + token + speed check (keep it — layers are good)
|
||||
add_action( 'wpcf7_before_send_mail', 'dapper_cf7_human_check', 9, 3 );
|
||||
|
||||
function dapper_cf7_human_check( $contact_form, &$abort, $submission ) {
|
||||
// Skip if disabled via future option
|
||||
// if (get_option('dapper_enable_cf7_human_check', 'on') !== 'on') return;
|
||||
|
||||
$form_id = $contact_form->id();
|
||||
$posted = $_POST;
|
||||
|
||||
// Very fast submission (< 5 seconds) → almost always bot
|
||||
$posted_time = isset($_POST['dapper_cf7_time']) ? (int)$_POST['dapper_cf7_time'] : 0;
|
||||
// Speed check
|
||||
$posted_time = isset( $posted['dapper_cf7_time'] ) ? (int) $posted['dapper_cf7_time'] : 0;
|
||||
if ( $posted_time && ( time() - $posted_time < 5 ) ) {
|
||||
$abort = true;
|
||||
$submission->add_error( 'dapper_speed', 'Submission too fast — please try again.' );
|
||||
@@ -1183,8 +1161,8 @@ if (class_exists('WPCF7')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Honeypot field (random name)
|
||||
foreach ($_POST as $key => $val) {
|
||||
// Honeypot
|
||||
foreach ( $posted as $key => $val ) {
|
||||
if ( strpos( $key, 'dapper_cf7_hp_' ) === 0 && strlen( trim( $val ) ) > 0 ) {
|
||||
$abort = true;
|
||||
$submission->add_error( 'dapper_honeypot', 'Spam detected.' );
|
||||
@@ -1193,8 +1171,8 @@ if (class_exists('WPCF7')) {
|
||||
}
|
||||
}
|
||||
|
||||
// Optional: require JS-set token (stronger)
|
||||
$token = trim($_POST['dapper_cf7_token'] ?? '');
|
||||
// Old JS token (keep for extra layer)
|
||||
$token = trim( $posted['dapper_cf7_token'] ?? '' );
|
||||
if ( empty( $token ) || strpos( $token, 'cf7human_' ) !== 0 ) {
|
||||
$abort = true;
|
||||
$submission->add_error( 'dapper_js', 'Please enable JavaScript and try again.' );
|
||||
@@ -1202,160 +1180,109 @@ if (class_exists('WPCF7')) {
|
||||
}
|
||||
}
|
||||
|
||||
// Add fields + JS to every CF7 form
|
||||
add_filter( 'wpcf7_form_elements', 'dapper_cf7_inject_anti_spam_fields' );
|
||||
|
||||
function dapper_cf7_inject_anti_spam_fields( $form ) {
|
||||
$honeypot_name = 'dapper_cf7_hp_' . wp_generate_password( 7, false );
|
||||
|
||||
$hidden_fields = '
|
||||
<input type="hidden" name="dapper_cf7_time" value="' . time() . '" />
|
||||
<input type="hidden" name="dapper_cf7_token" id="dapper_cf7_token" value="" />
|
||||
<p style="position:absolute; left:-9999px; height:1px; overflow:hidden;">
|
||||
<input type="text" name="' . esc_attr( $honeypot_name ) . '" value="" autocomplete="off" tabindex="-1" />
|
||||
</p>';
|
||||
|
||||
// Insert just before </form>
|
||||
$form = str_replace('</form>', $hidden_fields . '</form>', $form);
|
||||
|
||||
return $form;
|
||||
return str_replace( '</form>', $hidden_fields . '</form>', $form );
|
||||
}
|
||||
|
||||
// Tiny JS — add to footer when CF7 shortcode exists on page
|
||||
add_action( 'wp_footer', 'dapper_cf7_anti_spam_js', 90 );
|
||||
|
||||
function dapper_cf7_anti_spam_js() {
|
||||
if (!function_exists('wpcf7') || !did_action('wp_footer')) return;
|
||||
if ( ! did_action( 'wpcf7_enqueue_scripts' ) ) return;
|
||||
?>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.querySelectorAll('.wpcf7 form').forEach(form => {
|
||||
const tokenField = form.querySelector('#dapper_cf7_token');
|
||||
if (tokenField) {
|
||||
tokenField.value = 'cf7human_' + Math.random().toString(36).substring(2) + '_' + Date.now();
|
||||
}
|
||||
if (tokenField) tokenField.value = 'cf7human_' + Math.random().toString(36).substring(2) + '_' + Date.now();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
add_filter( 'wpcf7_form_elements', 'dapper_cf7_inject_human_checkbox', 20 );
|
||||
|
||||
function dapper_cf7_inject_human_checkbox( $form ) {
|
||||
// 2. NEW: Visible checkbox — use reliable append method
|
||||
add_filter( 'wpcf7_form_elements', 'dapper_cf7_append_human_checkbox', 30 ); // Higher priority = later
|
||||
function dapper_cf7_append_human_checkbox( $form ) {
|
||||
if ( get_option( 'dapper_enable_cf7_human_checkbox', 'on' ) !== 'on' ) {
|
||||
return $form;
|
||||
}
|
||||
|
||||
$unique = uniqid();
|
||||
$checkbox_html = '
|
||||
<div class="dapper-cf7-human-check" style="margin: 1.5em 0; padding: 1em; background: #f8f9fa; border: 1px solid #ccd0d4; border-radius: 4px; text-align: center;">
|
||||
<label style="font-size: 1.1em; cursor: pointer; user-select: none;">
|
||||
<input type="checkbox" name="dapper_cf7_human_confirm" id="dapper_cf7_human_confirm_' . uniqid() . '" value="1" required style="transform: scale(1.4); margin-right: 0.8em; vertical-align: middle;">
|
||||
<div class="dapper-cf7-human-check" style="margin:1.8em 0 1.2em; padding:1em; background:#f8f9fa; border:1px solid #ccd0d4; border-radius:4px; text-align:center;">
|
||||
<label style="font-size:1.1em; cursor:pointer; user-select:none; display:inline-flex; align-items:center; gap:0.8em;">
|
||||
<input type="checkbox" name="dapper_cf7_human_confirm" value="1" required style="transform:scale(1.5);">
|
||||
I am human / not a robot
|
||||
</label>
|
||||
<input type="hidden" name="dapper_cf7_human_token" id="dapper_cf7_human_token_' . uniqid() . '" value="">
|
||||
<input type="hidden" name="dapper_cf7_human_token" value="">
|
||||
<input type="hidden" name="dapper_cf7_human_time" value="' . time() . '">
|
||||
<p style="margin: 0.6em 0 0; font-size: 0.9em; color: #555;">Quick check to help stop spam. Thanks!</p>
|
||||
<p style="margin:0.6em 0 0; font-size:0.9em; color:#555;">Quick check to stop spam — thank you!</p>
|
||||
</div>';
|
||||
|
||||
// 1. Try to insert before the submit input/button (most common cases)
|
||||
// Look for <input type="submit"...> or <button type="submit">...</button>
|
||||
$form = preg_replace(
|
||||
'/(<(?:input|button)[^>]*type=["\']submit["\'][^>]*>)/i',
|
||||
$checkbox_html . '$1',
|
||||
$form,
|
||||
1 // limit to first match
|
||||
);
|
||||
|
||||
// 2. If that didn't work (rare), try before the wrapping <p> of submit
|
||||
if ( strpos( $form, $checkbox_html ) === false ) {
|
||||
$form = preg_replace(
|
||||
'/(<p[^>]*>[\s\S]*?(?:<input[^>]*type=["\']submit["\'][^>]*>|<\/button>)[\s\S]*?<\/p>)/i',
|
||||
$checkbox_html . '$1',
|
||||
$form,
|
||||
1
|
||||
);
|
||||
// Append right before the closing </form> — most reliable
|
||||
return str_replace( '</form>', $checkbox_html . '</form>', $form );
|
||||
}
|
||||
|
||||
// 3. Ultimate fallback: just before </form>
|
||||
if ( strpos( $form, $checkbox_html ) === false ) {
|
||||
$form = str_replace( '</form>', $checkbox_html . '</form>', $form );
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
// 2. Very small JS — runs on every page that has CF7 (cheap)
|
||||
add_action( 'wp_footer', 'dapper_cf7_human_checkbox_js', 95 );
|
||||
|
||||
// Tiny JS to set token + disable submit until checked
|
||||
add_action( 'wp_footer', 'dapper_cf7_human_checkbox_js', 100 );
|
||||
function dapper_cf7_human_checkbox_js() {
|
||||
// Only output if at least one CF7 form exists on page
|
||||
if ( ! did_action( 'wpcf7_enqueue_scripts' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! did_action( 'wpcf7_enqueue_scripts' ) ) return;
|
||||
?>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.querySelectorAll('.wpcf7 form').forEach(form => {
|
||||
const checkbox = form.querySelector('#dapper_cf7_human_confirm');
|
||||
const tokenField = form.querySelector('#dapper_cf7_human_token');
|
||||
const chk = form.querySelector('input[name="dapper_cf7_human_confirm"]');
|
||||
const tok = form.querySelector('input[name="dapper_cf7_human_token"]');
|
||||
const btn = form.querySelector('input[type="submit"], button[type="submit"]');
|
||||
|
||||
if (!checkbox || !tokenField) return;
|
||||
if (!chk || !tok || !btn) return;
|
||||
|
||||
// Enable token only when checked
|
||||
checkbox.addEventListener('change', () => {
|
||||
tokenField.value = checkbox.checked
|
||||
? 'cf7_human_' + Math.random().toString(36).substring(2,10) + '_' + Date.now()
|
||||
: '';
|
||||
// Disable submit until checked
|
||||
btn.disabled = true;
|
||||
|
||||
chk.addEventListener('change', () => {
|
||||
tok.value = chk.checked ? 'cf7_human_' + Math.random().toString(36).substring(2,12) + '_' + Date.now() : '';
|
||||
btn.disabled = !chk.checked;
|
||||
});
|
||||
|
||||
// Optional: disable submit until checked (stronger UX)
|
||||
const submitBtn = form.querySelector('input[type="submit"], button[type="submit"]');
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = true;
|
||||
checkbox.addEventListener('change', () => {
|
||||
submitBtn.disabled = !checkbox.checked;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
// 3. Server-side validation — block if missing / invalid / too fast
|
||||
add_action( 'wpcf7_before_send_mail', 'dapper_cf7_validate_human_checkbox', 11, 3 );
|
||||
|
||||
// Server-side validation for checkbox
|
||||
add_action( 'wpcf7_before_send_mail', 'dapper_cf7_validate_human_checkbox', 12, 3 );
|
||||
function dapper_cf7_validate_human_checkbox( $contact_form, &$abort, $submission ) {
|
||||
if ( get_option( 'dapper_enable_cf7_human_checkbox', 'on' ) !== 'on' ) {
|
||||
return;
|
||||
}
|
||||
if ( get_option( 'dapper_enable_cf7_human_checkbox', 'on' ) !== 'on' ) return;
|
||||
|
||||
$posted = $_POST;
|
||||
|
||||
// A. Checkbox must be checked
|
||||
if ( empty( $posted['dapper_cf7_human_confirm'] ) ) {
|
||||
$abort = true;
|
||||
$submission->add_error( 'dapper_human', __( 'Please confirm you are human.', 'dapper' ) );
|
||||
dapper_debug_log( "CF7 #{$contact_form->id()} blocked — human checkbox not checked" );
|
||||
$submission->add_error( 'dapper_human', 'Please confirm you are human.' );
|
||||
dapper_debug_log( "CF7 #{$contact_form->id()} blocked — checkbox not checked" );
|
||||
return;
|
||||
}
|
||||
|
||||
// B. Token must exist and look valid
|
||||
$token = trim( $posted['dapper_cf7_human_token'] ?? '' );
|
||||
if ( empty( $token ) || strpos( $token, 'cf7_human_' ) !== 0 || strlen( $token ) < 18 ) {
|
||||
if ( empty( $token ) || strpos( $token, 'cf7_human_' ) !== 0 || strlen( $token ) < 20 ) {
|
||||
$abort = true;
|
||||
$submission->add_error( 'dapper_human', __( 'Verification failed. Please try again.', 'dapper' ) );
|
||||
$submission->add_error( 'dapper_human', 'Verification failed — please try again.' );
|
||||
dapper_debug_log( "CF7 #{$contact_form->id()} blocked — invalid/missing human token" );
|
||||
return;
|
||||
}
|
||||
|
||||
// C. Not submitted in < 4 seconds (very strong signal of bot)
|
||||
$time = (int) ( $posted['dapper_cf7_human_time'] ?? 0 );
|
||||
if ( $time && ( time() - $time < 4 ) ) {
|
||||
$abort = true;
|
||||
$submission->add_error( 'dapper_human', __( 'Submission too fast — please try again.', 'dapper' ) );
|
||||
$submission->add_error( 'dapper_human', 'Submission too fast — please try again.' );
|
||||
dapper_debug_log( "CF7 #{$contact_form->id()} blocked — human check too fast" );
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user