|
11 | 11 |
|
12 | 12 | namespace CF7_AntiSpam\Core; |
13 | 13 |
|
| 14 | +use CF7_AntiSpam\Admin\CF7_AntiSpam_Admin_Tools; |
14 | 15 | use Exception; |
15 | 16 | use WPCF7_Submission; |
16 | 17 |
|
@@ -248,6 +249,15 @@ public function cf7a_spam_filter( $spam ) { |
248 | 249 | /* Get plugin options */ |
249 | 250 | $options = get_option( 'cf7a_options', array() ); |
250 | 251 |
|
| 252 | + /* Check the period of grace and, if it is expired, reset the error count */ |
| 253 | + if ( isset( $options['last_update_data']['errors'] ) ) { |
| 254 | + if ( time() - $options['last_update_data']['errors']['timestamp'] > $options['cf7a_period_of_grace'] ) { |
| 255 | + $options['last_update_data']['errors'] = array(); |
| 256 | + } |
| 257 | + // then save the updated options to the database |
| 258 | + update_option( 'cf7a_options', $options ); |
| 259 | + } |
| 260 | + |
251 | 261 | /* Get basic submission details */ |
252 | 262 | $mail_tags = $contact_form->scan_form_tags(); |
253 | 263 | $email_tag = sanitize_title( cf7a_get_mail_meta( $contact_form->pref( 'flamingo_email' ) ) ); |
@@ -506,23 +516,75 @@ public function filter_plugin_version( $data ) { |
506 | 516 |
|
507 | 517 | $cf7a_version = isset( $_POST[ $prefix . 'version' ] ) ? cf7a_decrypt( sanitize_text_field( wp_unslash( $_POST[ $prefix . 'version' ] ) ), $options['cf7a_cipher'] ) : false; |
508 | 518 |
|
| 519 | + // CASE A: Version field is completely missing or empty -> SPAM |
509 | 520 | if ( ! $cf7a_version ) { |
510 | 521 | $data['spam_score'] += $score_fingerprinting; |
511 | 522 | $data['reasons']['data_mismatch'] = sprintf( "Version mismatch (empty) != '%s'", CF7ANTISPAM_VERSION ); |
512 | 523 | cf7a_log( sprintf( "The 'version' field submitted by %s is empty", $data['remote_ip'] ), 1 ); |
513 | | - } else if ( $cf7a_version !== CF7ANTISPAM_VERSION ) { |
514 | | - // check the last update of the plugin |
515 | | - $last_update = $options['last_update']; |
516 | | - if ( $last_update < time() - WEEK_IN_SECONDS ) { |
517 | | - $data['spam_score'] += $score_fingerprinting; |
518 | | - $data['reasons']['data_mismatch'] = "Version mismatch '$cf7a_version' != '" . CF7ANTISPAM_VERSION . "'"; |
519 | | - cf7a_log( "The 'version' field submitted by {$data['remote_ip']} is empty", 1 ); |
520 | | - } |
521 | | - // Failsafe for cache issues: do not mark as spam, but logic dictates we might unset spam flag |
522 | | - // if it was set purely by accident here (though logic above prevents entry). |
523 | | - // Original code: $spam = false; |
524 | | - // Since we are in a filter, we simply do nothing or reset specific flags if required. |
| 524 | + |
| 525 | + return $data; |
525 | 526 | } |
| 527 | + |
| 528 | + // CASE B: Version matches current version -> OK |
| 529 | + if ( $cf7a_version === CF7ANTISPAM_VERSION ) { |
| 530 | + return $data; |
| 531 | + } |
| 532 | + |
| 533 | + // CASE C: Version Mismatch logic (Cache vs Spam) |
| 534 | + // Retrieve update data stored during the last plugin update |
| 535 | + $last_update_data = $options['last_update_data'] ?? null; |
| 536 | + |
| 537 | + // Check if we have update data and if the submitted version matches the PREVIOUS version |
| 538 | + $is_old_version_match = ( $last_update_data && isset( $last_update_data['old_version'] ) && $cf7a_version === $last_update_data['old_version'] ); |
| 539 | + |
| 540 | + // Check if the update happened less than a week ago |
| 541 | + $period_of_grace = apply_filters('cf7a_period_of_grace', WEEK_IN_SECONDS); |
| 542 | + $is_within_grace_period = ( $last_update_data && isset( $last_update_data['time'] ) && ( time() - $last_update_data['time'] ) < $period_of_grace ); |
| 543 | + |
| 544 | + if ( $is_old_version_match && $is_within_grace_period ) { |
| 545 | + |
| 546 | + // --- CACHE ISSUE DETECTED (FALLBACK) --- |
| 547 | + // Do NOT mark as spam. This is likely a cached user. |
| 548 | + |
| 549 | + cf7a_log( "Cache mismatch detected for IP {$data['remote_ip']}. Submitted: $cf7a_version. Expected: " . CF7ANTISPAM_VERSION, 1 ); |
| 550 | + |
| 551 | + // Record the error |
| 552 | + if ( ! isset( $options['last_update_data']['errors'] ) ) { |
| 553 | + $options['last_update_data']['errors'] = array(); |
| 554 | + } |
| 555 | + |
| 556 | + // Add error details |
| 557 | + $options['last_update_data']['errors'][] = array( |
| 558 | + 'ip' => $data['remote_ip'], |
| 559 | + 'time' => time(), |
| 560 | + ); |
| 561 | + |
| 562 | + $error_count = count( $options['last_update_data']['errors'] ); |
| 563 | + |
| 564 | + // Check trigger for email notification (Exactly on the 5th error) |
| 565 | + $cf7a_period_of_grace_max_attempts = intval(apply_filters( 'cf7a_period_of_grace_max_attempts', 5)); |
| 566 | + if ( $cf7a_period_of_grace_max_attempts === $error_count || $error_count * 3 === $cf7a_period_of_grace_max_attempts ) { |
| 567 | + $this->send_cache_warning_email( $options['last_update_data'] ); |
| 568 | + cf7a_log( "Cache warning email sent to admin.", 1 ); |
| 569 | + } |
| 570 | + |
| 571 | + // SAVE OPTIONS: We must save the error count to the database |
| 572 | + // Update the local $options variable first so subsequent filters use it if needed (though unlikely) |
| 573 | + $data['options'] = $options; |
| 574 | + |
| 575 | + // Persist to DB |
| 576 | + update_option( 'cf7a_options', $options ); |
| 577 | + |
| 578 | + } else { |
| 579 | + |
| 580 | + // --- REAL SPAM / INVALID VERSION --- |
| 581 | + // Either the grace period expired, or the version is completely random |
| 582 | + |
| 583 | + $data['spam_score'] += $score_fingerprinting; |
| 584 | + $data['reasons']['data_mismatch'] = "Version mismatch '$cf7a_version' != '" . CF7ANTISPAM_VERSION . "'"; |
| 585 | + cf7a_log( "The 'version' field submitted by {$data['remote_ip']} is mismatching (expired grace period or invalid)", 1 ); |
| 586 | + } |
| 587 | + |
526 | 588 | return $data; |
527 | 589 | } |
528 | 590 |
|
@@ -959,4 +1021,23 @@ public function filter_b8_bayesian( $data ) { |
959 | 1021 | } |
960 | 1022 | return $data; |
961 | 1023 | } |
| 1024 | + |
| 1025 | + /** |
| 1026 | + * Sends an email to the admin, warning them to clear the cache. |
| 1027 | + * @param array $update_data the array of data to be sent to the admin |
| 1028 | + * @return void |
| 1029 | + */ |
| 1030 | + private function send_cache_warning_email( $update_data ): void { |
| 1031 | + $tools = new CF7_AntiSpam_Admin_Tools(); |
| 1032 | + $recipient = get_option( 'admin_email' ); |
| 1033 | + $body = sprintf( |
| 1034 | + "Hello Admin,\n\nWe detected 5 users trying to submit forms with the old version (%s) instead of the new one (%s).\n\nThis usually means your website cache (or CDN) hasn't been cleared after the last update.\n\nPlease purge your site cache immediately to prevent legitimate users from being flagged as spam.\n\nTime of update: %s", |
| 1035 | + $update_data['old_version'], |
| 1036 | + $update_data['new_version'], |
| 1037 | + gmdate( 'Y-m-d H:i:s', $update_data['time'] ) |
| 1038 | + ); |
| 1039 | + $subject = 'CF7 AntiSpam - Cache Warning Alert'; |
| 1040 | + |
| 1041 | + $tools->send_email_to_admin( $subject, $recipient, $body, $recipient ); |
| 1042 | + } |
962 | 1043 | } |
0 commit comments