logger = ASBFG_Logger::get_instance(); } public function enqueue_block_scripts( $hook ) { // Enqueue on Gravity Forms entries pages if ( strpos( $hook, 'gf_entries' ) === false && strpos( $hook, 'gf_entry' ) === false ) { return; } wp_enqueue_script( 'asbfg-quick-block', plugin_dir_url( dirname( __FILE__ ) ) . 'assets/quick-block.js', [ 'jquery' ], '1.0.1', true ); wp_localize_script( 'asbfg-quick-block', 'asbfgAjax', [ 'ajaxurl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'asbfg_quick_block_nonce' ), ] ); wp_enqueue_style( 'asbfg-admin-styles', plugin_dir_url( dirname( __FILE__ ) ) . 'assets/admin-styles.css', [], '1.6.1' ); } public function handle_quick_block() { check_ajax_referer( 'asbfg_quick_block_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( 'Unauthorized' ); } // Rate limiting: max 10 blocks per minute per user $user_id = get_current_user_id(); $rate_limit_key = 'asbfg_rate_limit_' . $user_id; $rate_limit_data = get_transient( $rate_limit_key ); if ( false === $rate_limit_data ) { $rate_limit_data = [ 'count' => 0, 'timestamp' => time() ]; } // Reset counter if more than 60 seconds have passed if ( time() - $rate_limit_data['timestamp'] > 60 ) { $rate_limit_data = [ 'count' => 0, 'timestamp' => time() ]; } // Check if rate limit exceeded if ( $rate_limit_data['count'] >= 10 ) { wp_send_json_error( 'Rate limit exceeded. Please wait a moment before blocking more items.' ); } // Increment counter $rate_limit_data['count']++; set_transient( $rate_limit_key, $rate_limit_data, 60 ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified via check_ajax_referer above $entry_id = isset( $_POST['entry_id'] ) ? absint( $_POST['entry_id'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified via check_ajax_referer above $ip = isset( $_POST['ip'] ) ? sanitize_text_field( wp_unslash( $_POST['ip'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified via check_ajax_referer above $email = isset( $_POST['email'] ) ? sanitize_email( wp_unslash( $_POST['email'] ) ) : ''; if ( ! $entry_id ) { wp_send_json_error( 'Missing entry ID' ); } // Validate IP format if ( $ip && ! filter_var( $ip, FILTER_VALIDATE_IP ) ) { wp_send_json_error( 'Invalid IP address format' ); } // Validate email format if ( $email && ! is_email( $email ) ) { wp_send_json_error( 'Invalid email address format' ); } $blocked_count = 0; // Block IP if ( $ip ) { $blocked_ips = (array) get_option( 'asbfg_blocked_ips', [] ); if ( ! in_array( $ip, $blocked_ips, true ) ) { $blocked_ips[] = $ip; update_option( 'asbfg_blocked_ips', $blocked_ips ); $blocked_count++; } } // Block Email if ( $email ) { $blocked_emails = (array) get_option( 'asbfg_blocked_emails', [] ); if ( ! in_array( $email, $blocked_emails, true ) ) { $blocked_emails[] = $email; update_option( 'asbfg_blocked_emails', $blocked_emails ); $blocked_count++; } } if ( $blocked_count > 0 ) { wp_send_json_success( 'Blocked ' . $blocked_count . ' item(s)' ); } else { wp_send_json_error( 'Already blocked or no data to block' ); } } public function asbfg_validate_submission( $result, $value, $form, $field ) { if ( ! $result['is_valid'] ) { return $result; // Skip further checks if already invalid. } // Check if we've already logged for this submission static $logged_submissions = []; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified by Gravity Forms $submission_key = md5( serialize( $_POST ) ); // Cache blocked lists in static variable for performance (avoid repeated get_option calls) static $cached_lists = null; if ( null === $cached_lists ) { $blocked_ips_raw = (array) get_option( 'asbfg_blocked_ips', [] ); $blocked_emails_raw = (array) get_option( 'asbfg_blocked_emails', [] ); $blocked_domains_raw = (array) get_option( 'asbfg_blocked_domains', [] ); $blocked_text = (array) get_option( 'asbfg_blocked_text', [] ); $blocked_countries_raw = (array) get_option( 'asbfg_blocked_countries', [] ); // Convert to associative arrays for O(1) lookup performance $cached_lists = [ 'ips' => array_flip( array_map( 'strtolower', $blocked_ips_raw ) ), 'emails' => array_flip( array_map( 'strtolower', $blocked_emails_raw ) ), 'domains' => array_flip( array_map( 'strtolower', $blocked_domains_raw ) ), 'text' => $blocked_text, // Keep as array for stripos check 'countries' => array_flip( array_map( 'strtoupper', $blocked_countries_raw ) ), 'messages' => [ 'ip' => get_option( 'asbfg_custom_ip_message', 'Submissions from your IP address are not allowed.' ), 'email' => get_option( 'asbfg_custom_email_message', 'Submissions from this email are not allowed.' ), 'domain' => get_option( 'asbfg_custom_domain_message', 'Submissions from this email domain are not allowed.' ), 'text' => get_option( 'asbfg_custom_text_message', 'Submissions containing blocked text are not allowed.' ), 'country' => get_option( 'asbfg_custom_country_message', 'Submissions from your country are not allowed.' ), ], ]; } $user_ip = $this->asbfg_get_user_ip(); // Check country blocking first if ( ! empty( $cached_lists['countries'] ) && $user_ip ) { $country_code = $this->asbfg_get_country_from_ip( $user_ip ); if ( $country_code && isset( $cached_lists['countries'][ strtoupper( $country_code ) ] ) ) { if ( ! isset( $logged_submissions[ $submission_key ] ) ) { $this->logger->log_blocked_submission( $form['id'], 'country', $country_code, $user_ip, '' ); $logged_submissions[ $submission_key ] = true; } $result['is_valid'] = false; $result['message'] = $cached_lists['messages']['country']; return $result; } } // Check IP blocking with O(1) lookup if ( $user_ip && isset( $cached_lists['ips'][ strtolower( $user_ip ) ] ) ) { if ( ! isset( $logged_submissions[ $submission_key ] ) ) { $this->logger->log_blocked_submission( $form['id'], 'ip', $user_ip, $user_ip, '' ); $logged_submissions[ $submission_key ] = true; } $result['is_valid'] = false; $result['message'] = $cached_lists['messages']['ip']; return $result; } if ( $field->type === 'email' ) { $email = strtolower( $value ); // Check email blocking with O(1) lookup if ( isset( $cached_lists['emails'][ $email ] ) ) { if ( ! isset( $logged_submissions[ $submission_key ] ) ) { $this->logger->log_blocked_submission( $form['id'], 'email', $email, $user_ip, $email ); $logged_submissions[ $submission_key ] = true; } $result['is_valid'] = false; $result['message'] = $cached_lists['messages']['email']; return $result; } // Check domain blocking with O(1) lookup $email_domain = substr( strrchr( $email, '@' ), 1 ); if ( isset( $cached_lists['domains'][ $email_domain ] ) ) { if ( ! isset( $logged_submissions[ $submission_key ] ) ) { $this->logger->log_blocked_submission( $form['id'], 'domain', $email_domain, $user_ip, $email ); $logged_submissions[ $submission_key ] = true; } $result['is_valid'] = false; $result['message'] = $cached_lists['messages']['domain']; return $result; } } // Check text blocking (must remain as loop for partial matching) if ( is_string( $value ) && ! empty( $cached_lists['text'] ) ) { foreach ( $cached_lists['text'] as $text ) { if ( stripos( $value, $text ) !== false ) { if ( ! isset( $logged_submissions[ $submission_key ] ) ) { $this->logger->log_blocked_submission( $form['id'], 'text', $text, $user_ip, '' ); $logged_submissions[ $submission_key ] = true; } $result['is_valid'] = false; $result['message'] = $cached_lists['messages']['text']; return $result; } } } return $result; } private function asbfg_get_user_ip() { // Sanitize and validate the IP address if ( isset( $_SERVER['REMOTE_ADDR'] ) ) { $user_ip = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ); return filter_var( $user_ip, FILTER_VALIDATE_IP ); } } private function asbfg_get_country_from_ip( $ip ) { // Handle localhost/private IPs if ( in_array( $ip, [ '127.0.0.1', '::1', 'localhost' ], true ) || preg_match( '/^(10|172\.(1[6-9]|2[0-9]|3[01])|192\.168)\./', $ip ) ) { return false; // Skip country check for localhost } // Check transient cache first (cache for 24 hours) $cache_key = 'asbfg_country_' . md5( $ip ); $cached_country = get_transient( $cache_key ); if ( false !== $cached_country ) { return $cached_country; } // Use ip-api.com free API with HTTPS (60 requests/minute limit) // Note: HTTPS requires pro plan, falling back to HTTP for free tier $api_url = 'http://ip-api.com/json/' . $ip . '?fields=countryCode'; $response = wp_remote_get( $api_url, [ 'timeout' => 5, 'user-agent' => 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ), ] ); if ( is_wp_error( $response ) ) { // Cache failed lookup for 5 minutes to avoid hammering the API set_transient( $cache_key, '', 300 ); return false; } $response_code = wp_remote_retrieve_response_code( $response ); if ( 200 !== $response_code ) { // Cache failed lookup for 5 minutes set_transient( $cache_key, '', 300 ); return false; } $body = wp_remote_retrieve_body( $response ); $data = json_decode( $body, true ); if ( isset( $data['countryCode'] ) && ! empty( $data['countryCode'] ) ) { $country_code = sanitize_text_field( $data['countryCode'] ); // Cache the result for 24 hours set_transient( $cache_key, $country_code, DAY_IN_SECONDS ); return $country_code; } // Cache empty result for 5 minutes to avoid repeated failed lookups set_transient( $cache_key, '', 300 ); return false; } public function add_block_link( $form_id, $field_id, $value, $entry ) { if ( ! class_exists( 'GFAPI' ) ) { return; } $form = GFAPI::get_form( $entry['form_id'] ); $email = ''; // Find email field foreach ( $form['fields'] as $field ) { if ( $field->type === 'email' ) { $email = rgar( $entry, (string) $field->id ); if ( $email ) { break; } } } $ip = isset( $entry['ip'] ) ? $entry['ip'] : ''; echo ' | Block'; } public function add_block_link_to_entry_detail( $form, $entry ) { if ( ! class_exists( 'GFAPI' ) ) { return; } $email = ''; // Find email field foreach ( $form['fields'] as $field ) { if ( $field->type === 'email' ) { $email = rgar( $entry, (string) $field->id ); if ( $email ) { break; } } } $ip = isset( $entry['ip'] ) ? $entry['ip'] : ''; echo '