<?php
defined('ABSPATH') || exit;

/**
 * WPFort Advanced Database Scanner
 * 
 * This engine scans WordPress database content for malicious patterns from the definitions.
 */

/**
 * Scan WordPress posts for malicious content
 * 
 * @param array $db_scan_definitions Database scan pattern definitions
 * @param int $batch_size Number of posts to process in a batch (default 100)
 * @return array Detection results
 */
function wpsec_scan_posts($db_scan_definitions, $batch_size = 100) {
    global $wpdb;
    $results = [];
    
    if (!$db_scan_definitions || !is_array($db_scan_definitions)) {
        wpsec_debug_log('❌ WPFort: No database scan definitions available', 'error');
        return $results;
    }
    
    wpsec_debug_log('🔍 WPFort: Scanning database posts with ' . count($db_scan_definitions) . ' definitions', 'info');
    
    // Get the total number of published posts
    $total_posts = $wpdb->get_var($wpdb->prepare(
        "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_status = %s",
        'publish'
    ));
    wpsec_debug_log('📊 WPFort: Found ' . $total_posts . ' published posts to scan', 'info');
    
    // CRITICAL FIX: Safety check to prevent infinite loops
    if (!$total_posts || $total_posts <= 0) {
        wpsec_debug_log('⚠️ WPFort: No published posts found or unreliable count, skipping database scan', 'warning');
        return $results;
    }
    
    // CRITICAL FIX: Strict limits to prevent runaway scans
    $max_posts_to_scan = 5000; // Much more reasonable limit for production
    $max_scan_time = 30; // Maximum 30 seconds for database scan
    $scan_start_time = time();
    
    if ($total_posts > $max_posts_to_scan) {
        wpsec_debug_log('⚠️ WPFort: Limiting database scan to ' . $max_posts_to_scan . ' posts (found ' . $total_posts . ') to prevent blocking', 'warning');
        $total_posts = $max_posts_to_scan;
    }
    
    // Process in batches for better performance and memory usage
    $offset = 0;
    $scanned = 0;
    $matched = 0;
    $max_iterations = ceil($total_posts / $batch_size);
    $iterations = 0;
    
    // CRITICAL FIX: Add strict iteration and time limits
    while ($offset < $total_posts && $iterations < $max_iterations && $scanned < $max_posts_to_scan) {
        $iterations++;
        
        // CRITICAL FIX: Check for timeout to prevent infinite loops
        if ((time() - $scan_start_time) > $max_scan_time) {
            wpsec_debug_log('🚨 WPFort: Database scan timeout after ' . $max_scan_time . ' seconds, terminating to prevent blocking', 'info');
            break;
        }
        
        // Get a batch of posts
        $posts = $wpdb->get_results($wpdb->prepare(
            "SELECT ID, post_title, post_content, post_excerpt, post_type FROM {$wpdb->posts} 
             WHERE post_status = 'publish' 
             LIMIT %d OFFSET %d",
            $batch_size,
            $offset
        ));
        
        if (!$posts || empty($posts)) {
            wpsec_debug_log('📊 WPFort: No more posts found at offset ' . $offset . ', ending scan', 'info');
            break;
        }
        
        // Scan each post in the batch
        foreach ($posts as $post) {
            $scanned++;
            
            // CRITICAL FIX: Additional safety check to prevent runaway scanning
            if ($scanned >= $max_posts_to_scan) {
                wpsec_debug_log('🚨 WPFort: Reached maximum post scan limit (' . $max_posts_to_scan . '), terminating', 'info');
                break 2; // Break out of both loops
            }
            
            // Combine content fields for comprehensive scanning
            $content_to_scan = $post->post_title . ' ' . $post->post_content . ' ' . $post->post_excerpt;
            
            // Check each database pattern
            foreach ($db_scan_definitions as $pattern_id => $pattern_data) {
                $pattern = $pattern_data[1]; // The regex pattern is typically in this position
                
                if (!$pattern || !is_string($pattern)) {
                    continue; // Skip invalid patterns
                }
                
                // Check if the pattern matches
                if (@preg_match($pattern, $content_to_scan, $matches)) {
                    if (empty($matches[0])) {
                        continue; // Skip empty matches
                    }
                    
                    // Determine threat level based on pattern
                    $threat_score = 3; // Default medium threat
                    
                    // Increase threat score for certain dangerous patterns
                    if (strpos($pattern, 'iframe') !== false || 
                        strpos($pattern, 'javascript:') !== false || 
                        strpos($pattern, 'eval') !== false) {
                        $threat_score = 4; // Higher threat for dangerous content
                    }
                    
                    // Map to standard detection format
                    $results[] = wpsec_map_db_detection(
                        $post->ID,
                        'post:' . $post->post_type,
                        $content_to_scan,
                        $pattern_id,
                        $pattern_data,
                        $matches[0],
                        $threat_score,
                        80 // Higher confidence for database patterns
                    );
                    
                    $matched++;
                    
                    // Only report the first detection for each post
                    break;
                }
            }
        }
        
        // CRITICAL FIX: Log progress less frequently to reduce log spam
        if ($iterations % 10 == 0 || $iterations >= $max_iterations) {
            wpsec_debug_log(sprintf(
                '📊 WPFort: Database scan progress - %d/%d posts scanned, %d matches found (iteration %d/%d, time: %ds)',
                $scanned,
                $total_posts,
                $matched,
                $iterations,
                $max_iterations,
                (time() - $scan_start_time)
            ), 'info');
        }
        
        // Move to the next batch
        $offset += $batch_size;
        
        // CRITICAL FIX: Enhanced safety checks to prevent infinite loops
        if ($iterations >= $max_iterations) {
            wpsec_debug_log('🚨 WPFort: Database scan hit iteration limit, terminating to prevent infinite loop', 'info');
            break;
        }
        
        // Release memory periodically
        if ($iterations % 5 == 0) {
            wp_cache_flush();
            if (function_exists('gc_collect_cycles')) {
                gc_collect_cycles();
            }
        }
    }
    
    $scan_duration = time() - $scan_start_time;
    wpsec_debug_log(sprintf(
        '✅ WPFort: Database post scan complete - %d posts scanned, %d matches found in %d seconds',
        $scanned,
        $matched,
        $scan_duration
    ), 'info');
    
    return $results;
}

/**
 * Scan WordPress comments for malicious content
 * 
 * @param array $db_scan_definitions Database scan pattern definitions
 * @param int $batch_size Number of comments to process in a batch (default 200)
 * @return array Detection results
 */
function wpsec_scan_comments($db_scan_definitions, $batch_size = 200) {
    global $wpdb;
    $results = [];
    
    if (!$db_scan_definitions || !is_array($db_scan_definitions)) {
        return $results;
    }
    
    wpsec_debug_log('🔍 WPFort: Scanning database comments with ' . count($db_scan_definitions) . ' definitions', 'info');
    
    // Get the total number of approved comments
    $total_comments = $wpdb->get_var($wpdb->prepare(
        "SELECT COUNT(*) FROM {$wpdb->comments} WHERE comment_approved = %s",
        '1'
    ));
    wpsec_debug_log('📊 WPFort: Found ' . $total_comments . ' approved comments to scan', 'info');
    
    // Process in batches
    $offset = 0;
    $scanned = 0;
    $matched = 0;
    
    while ($offset < $total_comments) {
        // Get a batch of comments
        $comments = $wpdb->get_results($wpdb->prepare(
            "SELECT comment_ID, comment_content, comment_author, comment_author_url 
             FROM {$wpdb->comments} 
             WHERE comment_approved = '1' 
             LIMIT %d OFFSET %d",
            $batch_size,
            $offset
        ));
        
        if (!$comments) {
            break;
        }
        
        // Scan each comment in the batch
        foreach ($comments as $comment) {
            $scanned++;
            
            // Combine content fields for comprehensive scanning
            $content_to_scan = $comment->comment_content . ' ' . $comment->comment_author . ' ' . $comment->comment_author_url;
            
            // Check each database pattern
            foreach ($db_scan_definitions as $pattern_id => $pattern_data) {
                $pattern = $pattern_data[1]; // The regex pattern is typically in this position
                
                if (!$pattern || !is_string($pattern)) {
                    continue; // Skip invalid patterns
                }
                
                // Check if the pattern matches
                if (@preg_match($pattern, $content_to_scan, $matches)) {
                    if (empty($matches[0])) {
                        continue; // Skip empty matches
                    }
                    
                    // Determine threat level based on pattern
                    $threat_score = 3; // Default medium threat
                    
                    // Increase threat score for certain dangerous patterns
                    if (strpos($pattern, 'iframe') !== false || 
                        strpos($pattern, 'javascript:') !== false || 
                        strpos($pattern, 'eval') !== false) {
                        $threat_score = 4; // Higher threat for dangerous content
                    }
                    
                    // Map to standard detection format
                    $results[] = wpsec_map_db_detection(
                        $comment->comment_ID,
                        'comment',
                        $content_to_scan,
                        $pattern_id,
                        $pattern_data,
                        $matches[0],
                        $threat_score,
                        75 // Confidence level for comments
                    );
                    
                    $matched++;
                    
                    // Only report the first detection for each comment
                    break;
                }
            }
        }
        
        // Log progress
        wpsec_debug_log(sprintf(
            '📊 WPFort: Database scan progress - %d/%d comments scanned, %d matches found',
            $scanned,
            $total_comments,
            $matched
        ), 'info');
        
        // Move to the next batch
        $offset += $batch_size;
        
        // Release memory
        wp_cache_flush();
        if (function_exists('gc_collect_cycles')) {
            gc_collect_cycles();
        }
    }
    
    wpsec_debug_log(sprintf(
        '✅ WPFort: Database comment scan complete - %d comments scanned, %d matches found',
        $scanned,
        $matched
    ), 'info');
    
    return $results;
}

/**
 * Scan WordPress options for malicious content
 * 
 * @param array $db_scan_definitions Database scan pattern definitions
 * @return array Detection results
 */
function wpsec_scan_options($db_scan_definitions) {
    global $wpdb;
    $results = [];
    
    if (!$db_scan_definitions || !is_array($db_scan_definitions)) {
        return $results;
    }
    
    wpsec_debug_log('🔍 WPFort: Scanning database options with ' . count($db_scan_definitions) . ' definitions', 'info');
    
    // Get all options that might contain content (exclude transients and internal WP options)
    $options = $wpdb->get_results(
        "SELECT option_id, option_name, option_value 
         FROM {$wpdb->options} 
         WHERE option_name NOT LIKE '\_%transient\_%' 
         AND option_name NOT LIKE 'cron' 
         AND option_name NOT LIKE 'siteurl' 
         AND option_name NOT LIKE 'home' 
         AND option_name NOT LIKE 'active_plugins'"
    );
    
    wpsec_debug_log('📊 WPFort: Found ' . count($options) . ' options to scan', 'info');
    
    $scanned = 0;
    $matched = 0;
    
    // Scan each option
    foreach ($options as $option) {
        $scanned++;
        
        // Skip binary data or very large options
        if (!is_string($option->option_value) || strlen($option->option_value) > 1000000) {
            continue;
        }
        
        // Check each database pattern
        foreach ($db_scan_definitions as $pattern_id => $pattern_data) {
            $pattern = $pattern_data[1]; // The regex pattern is typically in this position
            
            if (!$pattern || !is_string($pattern)) {
                continue; // Skip invalid patterns
            }
            
            // Check if the pattern matches
            if (@preg_match($pattern, $option->option_value, $matches)) {
                if (empty($matches[0])) {
                    continue; // Skip empty matches
                }
                
                // Options are critical if infected
                $threat_score = 5; // High threat for option injections
                
                // Map to standard detection format
                $results[] = wpsec_map_db_detection(
                    $option->option_id,
                    'option',
                    $option->option_value,
                    $pattern_id,
                    $pattern_data,
                    $matches[0],
                    $threat_score,
                    90 // High confidence for option matching
                );
                
                $matched++;
                
                // Only report the first detection for each option
                break;
            }
        }
        
        // Log progress periodically
        if ($scanned % 100 === 0) {
            wpsec_debug_log(sprintf(
                '📊 WPFort: Database options scan progress - %d/%d options scanned, %d matches found',
                $scanned,
                count($options),
                $matched
            ), 'info');
        }
    }
    
    wpsec_debug_log(sprintf(
        '✅ WPFort: Database options scan complete - %d options scanned, %d matches found',
        $scanned,
        $matched
    ), 'info');
    
    return $results;
}

/**
 * Scan WordPress database for malicious content
 * 
 * @param array $db_scan_definitions Database scan pattern definitions
 * @return array Array of detection results
 */
/**
 * Safely scan WordPress options for malicious content with memory constraints
 * This function scans options in smaller batches to avoid memory exhaustion
 * 
 * @param array $db_scan_definitions Database scan pattern definitions
 * @return array Detection results
 */
function wpsec_scan_options_safely($db_scan_definitions) {
    global $wpdb, $wpsec_current_scan_id;
    $results = [];
    
    if (empty($db_scan_definitions)) {
        return $results;
    }
    
    wpsec_debug_log('🔍 WPFort: Scanning database options with memory-safe batching', 'info');
    
    // Set a maximum time limit for the entire options scan - 3 minutes is generous but prevents infinite loops
    $max_scan_time = 180; // 3 minutes
    $scan_start = time();
    
    // Build exclusion conditions for known large options directly in SQL
    $exclusion_conditions = wpsec_get_option_exclusion_sql();
    
    // Get the total count of options (excluding known large ones)
    // Note: $exclusion_conditions already contains prepared SQL from wpsec_get_option_exclusion_sql()
    $total_options = $wpdb->get_var(
        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $exclusion_conditions contains prepared SQL from wpsec_get_option_exclusion_sql()
        "SELECT COUNT(*) FROM {$wpdb->options} WHERE 1=1" . $exclusion_conditions
    );
    
    if (!$total_options) {
        wpsec_debug_log('⚠️ WPFort: No options found in database after exclusions', 'warning');
        return $results;
    }
    
    wpsec_debug_log("📊 WPFort: Found {$total_options} options to scan after excluding known large options", 'info');
    
    // Set a larger batch size for better performance while still being memory safe
    $batch_size = 400;
    
    // Calculate number of batches
    $total_batches = ceil($total_options / $batch_size);
    
    // Counter to track when to update overall scan progress
    $progress_update_counter = 0;
    
    // Process options in batches
    for ($batch = 0; $batch < $total_batches; $batch++) {
        // Check if we've exceeded the time limit
        if (time() - $scan_start > $max_scan_time) {
            wpsec_debug_log("⚠️ WPFort: Options scan time limit reached after {$batch} batches. Switching to critical-only scan.", 'critical');
            
            // If we hit the time limit, try scanning just the critical options
            $critical_results = wpsec_scan_critical_options_only($db_scan_definitions);
            $results = array_merge($results, $critical_results);
            
            // Update scan progress to prevent timeout
            if ($wpsec_current_scan_id) {
                // Get current progress percentage
                $current_progress = get_option('wpsec_scan_' . $wpsec_current_scan_id . '_progress', 0);
                // Move progress to 92% (simulate database scan completion)
                if ($current_progress < 92) {
                    update_option('wpsec_scan_' . $wpsec_current_scan_id . '_progress', 92);
                    update_option('wpsec_scan_' . $wpsec_current_scan_id . '_last_progress_update', time());
                    wpsec_debug_log("📊 WPFort: Updated scan progress to 92% to prevent timeout", 'info');
                }
            }
            
            return $results;
        }
        
        $offset = $batch * $batch_size;
        
        wpsec_debug_log("📊 WPFort: Processing options batch {$batch}/{$total_batches} (offset {$offset})", 'info');
        
        // Force garbage collection before each batch
        if (function_exists('gc_collect_cycles')) {
            gc_collect_cycles();
        }
        
        // Update overall scan progress periodically to prevent timeout detection
        $progress_update_counter++;
        if ($wpsec_current_scan_id && $progress_update_counter % 5 == 0) {
            // Get current progress percentage
            $current_progress = get_option('wpsec_scan_' . $wpsec_current_scan_id . '_progress', 0);
            $files_scanned = get_option('wpsec_scan_' . $wpsec_current_scan_id . '_files_scanned', 0);
            
            // Simulate incremental progress during database scan (88% to 92%)
            $new_progress = $current_progress;
            if ($current_progress < 92) {
                $new_progress = min(92, $current_progress + 1);
            }
            
            if ($new_progress > $current_progress) {
                update_option('wpsec_scan_' . $wpsec_current_scan_id . '_progress', $new_progress);
                update_option('wpsec_scan_' . $wpsec_current_scan_id . '_last_progress_update', time());
                wpsec_debug_log("📊 WPFort: Updated scan progress to {$new_progress}% during database scan to prevent timeout", 'info');
            }
        }
        
        // Get batch of options - exclude known large options directly in SQL for better performance
        // Note: $exclusion_conditions already contains prepared SQL from wpsec_get_option_exclusion_sql()
        $options = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT option_name, option_value FROM {$wpdb->options} 
                // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $exclusion_conditions contains prepared SQL from wpsec_get_option_exclusion_sql()
                WHERE 1=1 " . $exclusion_conditions . " 
                LIMIT %d OFFSET %d", 
                $batch_size, $offset
            )
        );
        
        if (!$options) {
            continue;
        }
        
        $processed = 0;
        $matches_in_batch = 0;
        
        foreach ($options as $option) {
            $option_name = $option->option_name;
            $option_value = $option->option_value;
            
            // Additional runtime check for oversized values
            if (strlen($option_value) > 500000) {
                wpsec_debug_log("⚠️ WPFort: Skipping large option '{$option_name}' (size: " . strlen($option_value) . " bytes)", 'warning');
                continue;
            }
            
            $processed++;
            
            // Check the option against all patterns
            foreach ($db_scan_definitions as $def) {
                if (empty($def['pattern'])) {
                    continue;
                }
                
                $pattern = $def['pattern'];
                
                // Test the pattern against both the option name and value with time limit via pcre.backtrack_limit
                $backtrack_limit = ini_get('pcre.backtrack_limit');
                ini_set('pcre.backtrack_limit', 1000000); // Reasonable limit to prevent excessive matching time
                
                try {
                    // Test pattern match with a timeout safety net
                    $match_name = @preg_match($pattern, $option_name);
                    $match_value = @preg_match($pattern, $option_value);
                    
                    if ($match_name || $match_value) {
                        // Match found - add to results
                        $result = [
                            'type' => 'option',
                            'option_name' => $option_name,
                            'match_context' => substr($option_value, 0, 200) . (strlen($option_value) > 200 ? '...' : ''),
                            'match_pattern' => $def['name'],
                            'file_path' => 'DB:' . $wpdb->options . ':' . $option_name,
                            'detections' => [
                                [
                                    'pattern' => $def['name'],
                                    'type' => 'db_scan',
                                    'threat_level' => $def['threat_level'] ?? 'high',
                                    'confidence' => $def['confidence'] ?? 80
                                ]
                            ]
                        ];
                        
                        $results[] = $result;
                        $matches_in_batch++;
                        
                        wpsec_debug_log("⚠️ WPFort: Found suspicious option: {$option_name}", 'warning');
                    }
                } catch (Exception $e) {
                    // Just skip this pattern if it causes issues
                    wpsec_debug_log("⚠️ WPFort: Error matching pattern on option: {$e->getMessage()}", 'error');
                }
                
                // Restore original backtrack limit
                ini_set('pcre.backtrack_limit', $backtrack_limit);
            }
        }
        
        wpsec_debug_log("📊 WPFort: Completed batch {$batch}/{$total_batches} - processed {$processed} options, found {$matches_in_batch} matches", 'info');
    }
    
    wpsec_debug_log("✅ WPFort: Options scan complete - found " . count($results) . " total matches", 'info');
    
    return $results;
}

/**
 * Scan only critical WordPress options known to be commonly infected
 * This is a fallback for when a full options scan fails due to memory constraints
 * 
 * @param array $db_scan_definitions Database scan pattern definitions
 * @return array Detection results
 */
function wpsec_scan_critical_options_only($db_scan_definitions) {
    global $wpdb;
    $results = [];
    
    // List of critical options commonly targeted by malware
    $critical_options = [
        'active_plugins',
        'template',
        'stylesheet',
        'upload_path',
        'siteurl',
        'home',
        'widget_%',        // Widgets can contain malicious JavaScript
        'theme_mods_%',    // Theme modifications
        'cron',            // Scheduled tasks
        'recently_edited', // Recently edited files
        '%_transient_%',   // Transients often targeted
        'auto_updater.lock', // Auto update settings
        'finished_updating', // Update status
        'admin_email',     // Admin email
        'users_can_register', // Registration settings
        'wp_user_roles',  // User roles
        'wp_user_%',      // User settings
        'session_tokens', // Session tokens
    ];
    
    wpsec_debug_log('🔍 WPFort: Running minimal options scan (critical options only)', 'critical');
    
    foreach ($critical_options as $option_pattern) {
        // Force garbage collection on each iteration to maintain memory
        if (function_exists('gc_collect_cycles')) {
            gc_collect_cycles();
        }
        
        // Get options matching the current pattern
        $options = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT option_name, option_value FROM {$wpdb->options} WHERE option_name LIKE %s LIMIT 100",
                $option_pattern
            )
        );
        
        if (!$options) {
            continue;
        }
        
        wpsec_debug_log("📊 WPFort: Scanning {$option_pattern} options - found " . count($options) . " matches", 'info');
        
        foreach ($options as $option) {
            $option_name = $option->option_name;
            $option_value = $option->option_value;
            
            // Skip overly large values
            if (strlen($option_value) > 500000) {
                wpsec_debug_log("⚠️ WPFort: Skipping large option '{$option_name}' (size: " . strlen($option_value) . " bytes)", 'warning');
                continue;
            }
            
            // Check the option against all patterns
            foreach ($db_scan_definitions as $def) {
                if (empty($def['pattern'])) {
                    continue;
                }
                
                $pattern = $def['pattern'];
                
                // Safely perform regex matching with a time limit
                $match_found = false;
                try {
                    // Perform matching with a custom time limit
                    if (preg_match($pattern, $option_name) || preg_match($pattern, $option_value)) {
                        $match_found = true;
                    }
                } catch (Exception $e) {
                    wpsec_debug_log("⚠️ WPFort: Error matching pattern on option '{$option_name}': " . $e->getMessage(), 'error');
                    continue;
                }
                
                if ($match_found) {
                    $result = [
                        'type' => 'option',
                        'option_name' => $option_name,
                        'match_context' => substr($option_value, 0, 200) . (strlen($option_value) > 200 ? '...' : ''),
                        'match_pattern' => $def['name'],
                        'file_path' => 'DB:' . $wpdb->options . ':' . $option_name,
                        'detections' => [
                            [
                                'pattern' => $def['name'],
                                'type' => 'db_scan',
                                'threat_level' => $def['threat_level'] ?? 'high',
                                'confidence' => $def['confidence'] ?? 80
                            ]
                        ]
                    ];
                    
                    $results[] = $result;
                    wpsec_debug_log("⚠️ WPFort: Found suspicious option: {$option_name}", 'warning');
                    
                    // Break once we find a match for this option
                    break;
                }
            }
        }
    }
    
    wpsec_debug_log("✅ WPFort: Critical options scan complete - found " . count($results) . " total matches", 'critical');
    
    return $results;
}

/**
 * Check if an option should be excluded from scanning
 * 
 * @param string $option_name The WordPress option name
 * @return bool True if the option should be excluded
 */
/**
 * Generates SQL conditions to exclude known large options
 * 
 * @return string SQL WHERE clause conditions to exclude large options
 */
function wpsec_get_option_exclusion_sql() {
    // Options commonly containing large serialized data or cache information that should be excluded
    $excluded_patterns = [
        'wpsupercache_gc_time',       // WP Super Cache
        'w3tc_pgcache_cache',         // W3 Total Cache
        'wp_rocket%',                 // WP Rocket Cache
        'objectcache_%',              // Object Cache
        '_transient_feed_%',          // Feed transients
        '_site_transient_timeout_%',  // Site transients
        '_transient_timeout_%',       // Transient timeouts
        '_transient_doing_cron',      // Cron transient
        'jetpack_sync_%',             // Jetpack sync data
        'theme_mods_',                // Theme mods can be large
        '_elementor_data',            // Elementor page builder data
        'db_upgraded',                // Database upgrade flag
        'upload_space_check_disabled', // Upload space check
        'rewrite_rules',              // Rewrite rules can be large
        'uninstall_plugins',          // Uninstall plugins list
        'woocommerce_meta_box_errors', // WooCommerce errors
        'wpforms_email_summaries_log', // WPForms logs
        'wp_%_backup',                 // Various backups
        'sitemap_%',                    // Sitemap data
        'itsec_%',                      // iThemes Security data
        'wordfence%',                   // Wordfence cache
        'wpvivid%',                     // WPVivid backup
        'updraftplus%',                 // UpdraftPlus backup
        'ai1wm_%',                      // All-in-One WP Migration
        'wfls-backup-%',                // Wordfence Login Security backup
        'wf_scan%',                     // Wordfence scan data
        'elementor_elements_usage',     // Elementor usage data
    ];
    
    // Build SQL exclusion conditions
    $sql_conditions = [];
    global $wpdb;
    
    foreach ($excluded_patterns as $pattern) {
        // Convert wildcard pattern to SQL LIKE pattern
        $sql_pattern = str_replace('%', '\\%', $pattern); // Escape % for SQL
        $sql_pattern = str_replace('*', '%', $sql_pattern); // Convert * to %
        
        // Add the NOT LIKE condition
        $sql_conditions[] = $wpdb->prepare("option_name NOT LIKE %s", $sql_pattern);
    }
    
    // Additional condition for option_value size
    $sql_conditions[] = "LENGTH(option_value) < 500000"; // Skip options larger than 500KB
    
    // Combine all conditions with AND
    return " AND " . implode(" AND ", $sql_conditions);
}

/**
 * Check if an option should be excluded from scanning
 * 
 * @param string $option_name The WordPress option name
 * @return bool True if the option should be excluded
 */
function wpsec_is_excluded_option($option_name) {
    // Options commonly containing large serialized data or cache information
    $excluded_options = [
        'wpsupercache_gc_time',       // WP Super Cache
        'w3tc_pgcache_cache',         // W3 Total Cache
        'wp_rocket%',                 // WP Rocket Cache
        'objectcache_%',              // Object Cache
        '_transient_feed_%',          // Feed transients
        '_site_transient_timeout_%',  // Site transients
        '_transient_timeout_%',       // Transient timeouts
        '_transient_doing_cron',      // Cron transient
        'jetpack_sync_%',             // Jetpack sync data
        'theme_mods_',                // Theme mods can be large
        '_elementor_data',            // Elementor page builder data
        'db_upgraded',                // Database upgrade flag
        'upload_space_check_disabled', // Upload space check
        'rewrite_rules',              // Rewrite rules can be large
        'uninstall_plugins',          // Uninstall plugins list
        'woocommerce_meta_box_errors', // WooCommerce errors
        'wpforms_email_summaries_log', // WPForms logs
        'wp_%_backup',                 // Various backups
        'sitemap_%',                    // Sitemap data
        'itsec_%',                      // iThemes Security data
        'wordfence%',                   // Wordfence cache
        'wpvivid%',                     // WPVivid backup
        'updraftplus%',                 // UpdraftPlus backup
        'ai1wm_%',                      // All-in-One WP Migration
        'wfls-backup-%',                // Wordfence Login Security backup
        'wf_scan%',                     // Wordfence scan data
        'elementor_elements_usage',     // Elementor usage data
    ];
    
    // Check if the option name matches any excluded pattern
    foreach ($excluded_options as $pattern) {
        if (fnmatch($pattern, $option_name)) {
            return true;
        }
    }
    
    return false;
}

function wpsec_scan_database($db_scan_definitions) {
    $results = [];
    $error_sections = [];
    $scan_start_time = time();
    $max_total_scan_time = 60; // Maximum 60 seconds for entire database scan
    
    if (!$db_scan_definitions || !is_array($db_scan_definitions)) {
        wpsec_debug_log('✖ WPFort: No database scan definitions available', 'info');
        return $results;
    }
    
    wpsec_debug_log('🔍 WPFort: Starting comprehensive database scan with ' . count($db_scan_definitions) . ' definitions', 'info');
    
    // CRITICAL FIX: Check timeout before each section
    // Scan posts
    try {
        if ((time() - $scan_start_time) > $max_total_scan_time) {
            wpsec_debug_log('🚨 WPFort: Database scan timeout reached, skipping remaining sections', 'info');
            return $results;
        }
        
        // Set a specific memory limit for this operation to prevent complete exhaustion
        $current_memory_limit = ini_get('memory_limit');
        $current_bytes = wp_convert_hr_to_bytes($current_memory_limit);
        
        wpsec_debug_log('🔍 WPFort: Running post scan with memory limit: ' . $current_memory_limit, 'info');
        $post_results = wpsec_scan_posts($db_scan_definitions);
        $results = array_merge($results, $post_results);
        wpsec_debug_log('✅ WPFort: Post scan completed successfully with ' . count($post_results) . ' results', 'info');
    } catch (Exception $e) {
        wpsec_debug_log('⚠️ WPFort: Error during post scan: ' . $e->getMessage(), 'error');
        $error_sections[] = 'posts';
    }
    
    // Force garbage collection between scans
    if (function_exists('gc_collect_cycles')) {
        gc_collect_cycles();
    }
    
    // CRITICAL FIX: Check timeout before comments scan
    if ((time() - $scan_start_time) > $max_total_scan_time) {
        wpsec_debug_log('🚨 WPFort: Database scan timeout reached, skipping comments and options', 'info');
        return $results;
    }
    
    // Scan comments
    try {
        wpsec_debug_log('🔍 WPFort: Running comment scan', 'info');
        $comment_results = wpsec_scan_comments($db_scan_definitions);
        $results = array_merge($results, $comment_results);
        wpsec_debug_log('✅ WPFort: Comment scan completed successfully with ' . count($comment_results) . ' results', 'info');
    } catch (Exception $e) {
        wpsec_debug_log('⚠️ WPFort: Error during comment scan: ' . $e->getMessage(), 'error');
        $error_sections[] = 'comments';
    }
    
    // Force garbage collection between scans
    if (function_exists('gc_collect_cycles')) {
        gc_collect_cycles();
    }
    
    // CRITICAL FIX: Check timeout before options scan
    if ((time() - $scan_start_time) > $max_total_scan_time) {
        wpsec_debug_log('🚨 WPFort: Database scan timeout reached, skipping options scan', 'info');
        return $results;
    }
    
    // Scan options with a retry mechanism and more careful approach
    try {
        wpsec_debug_log('🔍 WPFort: Running options scan', 'info');
        
        // First, try with a limited batch approach
        try {
            // Only scan options table with a custom function that limits batch size
            $option_results = wpsec_scan_options_safely($db_scan_definitions);
            $results = array_merge($results, $option_results);
            wpsec_debug_log('✅ WPFort: Options scan completed successfully with safe method, ' . count($option_results) . ' results', 'info');
        } catch (Exception $inner_e) {
            // If the safe method fails, try an even more restrictive approach
            wpsec_debug_log('⚠️ WPFort: Safe options scan failed: ' . $inner_e->getMessage(), 'warning');
            wpsec_debug_log('🔄 WPFort: Trying minimal options scan', 'info');
            
            // Attempt minimal options scan (only known problematic options)
            $minimal_results = wpsec_scan_critical_options_only($db_scan_definitions);
            $results = array_merge($results, $minimal_results);
            wpsec_debug_log('✅ WPFort: Minimal options scan completed with ' . count($minimal_results) . ' results', 'info');
        }
    } catch (Exception $e) {
        wpsec_debug_log('⚠️ WPFort: Error during options scan: ' . $e->getMessage(), 'error');
        wpsec_debug_log('🚨 WPFort: Skipping options scan due to memory constraints', 'info');
        $error_sections[] = 'options';
    }
    
    $total_scan_time = time() - $scan_start_time;
    
    // Report final status
    if (!empty($error_sections)) {
        wpsec_debug_log('⚠️ WPFort: Database scan completed with some sections skipped: ' . implode(', ', $error_sections) . ' (time: ' . $total_scan_time . 's)', 'error');
    } else {
        wpsec_debug_log(sprintf(
            '✅ WPFort: Complete database scan finished successfully - found %d total matches in %d seconds',
            count($results),
            $total_scan_time
        ), 'info');
    }
    
    return $results;
}
