Skip to content

Commit 5992f2d

Browse files
authored
Merge pull request #144 from wp-blocks/update-fallback
Update fallback
2 parents c71773d + c0158e3 commit 5992f2d

File tree

4 files changed

+162
-48
lines changed

4 files changed

+162
-48
lines changed

admin/CF7_AntiSpam_Admin_Tools.php

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,6 @@
22

33
namespace CF7_AntiSpam\Admin;
44

5-
use CF7_AntiSpam\Core\CF7_AntiSpam;
6-
use CF7_AntiSpam\Core\CF7_AntiSpam_Filters;
7-
use CF7_AntiSpam\Core\CF7_AntiSpam_Flamingo;
8-
use CF7_AntiSpam\Engine\CF7_AntiSpam_Uninstaller;
9-
105
/**
116
* The plugin admin tools
127
*
@@ -23,17 +18,20 @@ class CF7_AntiSpam_Admin_Tools {
2318
/**
2419
* It sets a transient with the name of `cf7a_notice` and the value of the notice
2520
*
26-
* @param string $message The message you want to display.
27-
* @param string $type error, warning, success, info.
21+
* @param string $message The message you want to display.
22+
* @param string $type error, warning, success, info.
2823
* @param boolean $dismissible when the notice needs the close button.
2924
*/
30-
public static function cf7a_push_notice( $message = 'generic', $type = 'error', $dismissible = true ) {
25+
public static function cf7a_push_notice( string $message = 'generic', string $type = 'error', bool $dismissible = true ) {
3126
$class = "notice notice-$type";
3227
$class .= $dismissible ? ' is-dismissible' : '';
3328
$notice = sprintf( '<div class="%s"><p>%s</p></div>', esc_attr( $class ), esc_html( $message ) );
3429
set_transient( 'cf7a_notice', $notice );
3530
}
3631

32+
/**
33+
* It exports the blacklist
34+
*/
3735
public static function cf7a_export_blacklist() {
3836
global $wpdb;
3937
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
@@ -67,4 +65,38 @@ public function cf7a_handle_actions() {
6765
exit();
6866
}
6967
}
68+
69+
/**
70+
* It sends an email to the admin
71+
*
72+
* @param string $subject the mail message subject
73+
* @param string $recipient the mail recipient
74+
* @param string $body the mail message content
75+
* @param string $sender the mail message sender
76+
*/
77+
public function send_email_to_admin( string $subject, string $recipient, string $body, string $sender ) {
78+
/**
79+
* Filter cf7-antispam before resend an email who was spammed
80+
*
81+
* @param string $body the mail message content
82+
* @param string $sender the mail message sender
83+
* @param string $subject the mail message subject
84+
* @param string $recipient the mail recipient
85+
*
86+
* @returns string the mail body content
87+
*/
88+
$body = apply_filters( 'cf7a_before_resend_email', $body, $sender, $subject, $recipient );
89+
90+
// Set up headers correctly
91+
$site_name = get_bloginfo( 'name' );
92+
$from_email = get_option( 'admin_email' );
93+
94+
$headers = "From: {$site_name} <{$from_email}>\n";
95+
$headers .= "Content-Type: text/html\n";
96+
$headers .= "X-WPCF7-Content-Type: text/html\n";
97+
$headers .= "Reply-To: {$sender}\n";
98+
99+
/* send the email */
100+
return wp_mail( $recipient, $subject, $body, $headers );
101+
}
70102
}

core/CF7_AntiSpam_Filters.php

Lines changed: 93 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace CF7_AntiSpam\Core;
1313

14+
use CF7_AntiSpam\Admin\CF7_AntiSpam_Admin_Tools;
1415
use Exception;
1516
use WPCF7_Submission;
1617

@@ -248,6 +249,15 @@ public function cf7a_spam_filter( $spam ) {
248249
/* Get plugin options */
249250
$options = get_option( 'cf7a_options', array() );
250251

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+
251261
/* Get basic submission details */
252262
$mail_tags = $contact_form->scan_form_tags();
253263
$email_tag = sanitize_title( cf7a_get_mail_meta( $contact_form->pref( 'flamingo_email' ) ) );
@@ -506,23 +516,75 @@ public function filter_plugin_version( $data ) {
506516

507517
$cf7a_version = isset( $_POST[ $prefix . 'version' ] ) ? cf7a_decrypt( sanitize_text_field( wp_unslash( $_POST[ $prefix . 'version' ] ) ), $options['cf7a_cipher'] ) : false;
508518

519+
// CASE A: Version field is completely missing or empty -> SPAM
509520
if ( ! $cf7a_version ) {
510521
$data['spam_score'] += $score_fingerprinting;
511522
$data['reasons']['data_mismatch'] = sprintf( "Version mismatch (empty) != '%s'", CF7ANTISPAM_VERSION );
512523
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;
525526
}
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+
526588
return $data;
527589
}
528590

@@ -959,4 +1021,23 @@ public function filter_b8_bayesian( $data ) {
9591021
}
9601022
return $data;
9611023
}
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+
}
9621043
}

core/CF7_AntiSpam_Flamingo.php

Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace CF7_AntiSpam\Core;
44

5+
use CF7_AntiSpam\Admin\CF7_AntiSpam_Admin_Tools;
56
use WP_Query;
67
use WPCF7_ContactForm;
78
use WPCF7_Submission;
@@ -208,7 +209,7 @@ private static function cf7a_get_mail_field( $flamingo_post, $field ) {
208209
*
209210
* @return array { success: boolean, message: string }
210211
*/
211-
public function cf7a_resend_mail( $mail_id ) {
212+
public function cf7a_resend_mail( int $mail_id ): array {
212213
$flamingo_data = new Flamingo_Inbound_Message( $mail_id );
213214
$message = self::cf7a_get_mail_field( $flamingo_data, 'message' );
214215

@@ -264,29 +265,9 @@ public function cf7a_resend_mail( $mail_id ) {
264265
}
265266
}
266267

267-
/**
268-
* Filter cf7-antispam before resend an email who was spammed
269-
*
270-
* @param string $body the mail message content
271-
* @param string $sender the mail message sender
272-
* @param string $subject the mail message subject
273-
* @param string $recipient the mail recipient
274-
*
275-
* @returns string the mail body content
276-
*/
277-
$body = apply_filters( 'cf7a_before_resend_email', $body, $sender, $subject, $recipient );
278-
279-
// Set up headers correctly
280-
$site_name = get_bloginfo( 'name' );
281-
$from_email = get_option( 'admin_email' );
282-
283-
$headers = "From: {$site_name} <{$from_email}>\n";
284-
$headers .= "Content-Type: text/html\n";
285-
$headers .= "X-WPCF7-Content-Type: text/html\n";
286-
$headers .= "Reply-To: {$sender}\n";
287-
288-
/* send the email */
289-
$result = wp_mail( $recipient, $subject, $body, $headers );
268+
$tools = new CF7_AntiSpam_Admin_Tools();
269+
$result = $tools->send_email_to_admin( $subject, $recipient, $body, $sender );
270+
290271
if ( $result ) {
291272
return array( 'success'=> true, 'message' => __( 'Email sent with success', 'cf7-antispam' ) );
292273
}
@@ -295,7 +276,7 @@ public function cf7a_resend_mail( $mail_id ) {
295276
}
296277

297278
/**
298-
* Parse CF7 mail tags in recipient field
279+
* Parse CF7 mail tags in the recipient field
299280
*
300281
* @param string $recipient The recipient string that may contain CF7 tags
301282
* @param Flamingo_Inbound_Message $flamingo_data The flamingo message data

engine/CF7_AntiSpam_Activator.php

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,22 @@ public static function install() {
212212
}
213213
}
214214

215+
/**
216+
* Store the update data
217+
*
218+
* @param array $options - the options array.
219+
*/
220+
private static function store_update_data( $options ) {
221+
/* update the plugin update time field */
222+
$options['last_update_data'] = array(
223+
'time' => time(),
224+
'old_version' => $options['cf7a_version'] ?? 'unknown',
225+
'new_version' => CF7ANTISPAM_VERSION,
226+
'errors' => array(),
227+
);
228+
return $options;
229+
}
230+
215231
/**
216232
* Create or Update the CF7 Antispam options
217233
*
@@ -235,10 +251,14 @@ public static function update_options( $reset_options = false ) {
235251
add_option( 'cf7a_options', $new_options );
236252

237253
} else {
238-
239-
/* update the plugin options but add the new options automatically */
254+
/* if the plugin is already installed, update the plugin options automatically */
240255
if ( isset( $options['cf7a_version'] ) ) {
241-
unset( $options['cf7a_version'] );
256+
257+
/* update the plugin last update time field if the current version is set (so we are updating the plugin and not installing it) */
258+
$options = self::store_update_data( $options );
259+
260+
/* remove the version field */
261+
$options['cf7a_version'] = CF7ANTISPAM_VERSION;
242262
}
243263

244264
/* merge previous options with the updated copy keeping the already selected option as default */

0 commit comments

Comments
 (0)