<?php
/**
 * Direct Scan Recovery Script
 * 
 * This file can be called directly via URL or external cron job to
 * check for and recover stalled scans without relying on WP Cron.
 */

// Load WordPress core
define('WP_USE_THEMES', false);
require_once(dirname(dirname(dirname(__FILE__))) . '/wp-load.php');

// Security check - verify recovery key
$key = isset($_GET['key']) ? $_GET['key'] : '';
if ($key !== get_option('wpsec_recovery_key')) {
    die('Unauthorized access');
}

// Run recovery
wpsec_direct_scan_recovery();

/**
 * Direct scan recovery function
 * Bypasses WP Cron to check and recover stalled scans
 */
function wpsec_direct_scan_recovery() {
    // Get all active scans
    $scan_ids = get_option('wpsec_active_scans', array());
    $now = time();
    $log_file = WP_CONTENT_DIR . '/wpsec-recovery-log.txt';
    
    // Log start of recovery check
    file_put_contents(
        $log_file, 
        gmdate('Y-m-d H:i:s') . ' - Direct recovery check triggered' . PHP_EOL,
        FILE_APPEND
    );
    
    if (empty($scan_ids)) {
        file_put_contents(
            $log_file, 
            gmdate('Y-m-d H:i:s') . ' - No active scans found' . PHP_EOL,
            FILE_APPEND
        );
        echo "No active scans found.";
        return;
    }
    
    // Process each active scan
    foreach ($scan_ids as $scan_id) {
        $status = get_option('wpsec_scan_' . $scan_id . '_status', '');
        $last_update = get_option('wpsec_scan_' . $scan_id . '_last_update', 0);
        $time_since_update = $now - $last_update;
        $resume_from = get_option('wpsec_scan_' . $scan_id . '_resume_position', 0);
        
        // Log scan status
        file_put_contents(
            $log_file, 
            gmdate('Y-m-d H:i:s') . " - Scan {$scan_id} status: {$status}, Last update: " . 
            gmdate('Y-m-d H:i:s', $last_update) . " ({$time_since_update} seconds ago)" . PHP_EOL,
            FILE_APPEND
        );
        
        // Check if scan is stalled or needs recovery
        $needs_recovery = false;
        
        // Case 1: Scan is paused and no resume is scheduled
        if ($status === 'paused') {
            // Check if any resume is scheduled in WP Cron
            $resume_scheduled = false;
            $cron_events = _get_cron_array();
            
            if (!empty($cron_events)) {
                foreach ($cron_events as $time => $hooks) {
                    if (isset($hooks['wpsec_resume_scan'])) {
                        foreach ($hooks['wpsec_resume_scan'] as $event) {
                            if (!empty($event['args']) && $event['args'][0] === $scan_id) {
                                $resume_scheduled = true;
                                file_put_contents(
                                    $log_file, 
                                    gmdate('Y-m-d H:i:s') . " - Resume already scheduled for scan {$scan_id} at " . 
                                    gmdate('Y-m-d H:i:s', $time) . PHP_EOL,
                                    FILE_APPEND
                                );
                                break 2;
                            }
                        }
                    }
                }
            }
            
            // If no resume scheduled and paused for more than 5 minutes
            if (!$resume_scheduled && $time_since_update > 300) {
                $needs_recovery = true;
                file_put_contents(
                    $log_file, 
                    gmdate('Y-m-d H:i:s') . " - Scan {$scan_id} is paused with no scheduled resume" . PHP_EOL,
                    FILE_APPEND
                );
            }
        }
        
        // Case 2: Scan is scanning but hasn't updated in 5 minutes
        if ($status === 'scanning' && $time_since_update > 300) {
            $needs_recovery = true;
            file_put_contents(
                $log_file, 
                gmdate('Y-m-d H:i:s') . " - Scan {$scan_id} is stalled (no updates for {$time_since_update} seconds)" . PHP_EOL,
                FILE_APPEND
            );
        }
        
        // Case 3: Scan is resuming but hasn't changed to scanning in 5 minutes
        if ($status === 'resuming' && $time_since_update > 300) {
            $needs_recovery = true;
            file_put_contents(
                $log_file, 
                gmdate('Y-m-d H:i:s') . " - Scan {$scan_id} is stuck in resuming state" . PHP_EOL,
                FILE_APPEND
            );
        }
        
        // Perform recovery if needed
        if ($needs_recovery) {
            // Get scan parameters
            $deep_scan = get_option('wpsec_scan_' . $scan_id . '_deep', false);
            $verbose = get_option('wpsec_scan_' . $scan_id . '_verbose', false);
            
            // Log recovery attempt
            file_put_contents(
                $log_file, 
                gmdate('Y-m-d H:i:s') . " - Attempting direct recovery of scan {$scan_id} from position {$resume_from}" . PHP_EOL,
                FILE_APPEND
            );
            
            // Clear any scheduled events
            wp_clear_scheduled_hook('wpsec_resume_scan', array($scan_id));
            
            // Reset memory limits if possible
            if (function_exists('wpsec_increase_memory_limit')) {
                wpsec_increase_memory_limit();
            }
            
            // Update status and run scan
            update_option('wpsec_scan_' . $scan_id . '_status', 'resuming');
            update_option('wpsec_scan_' . $scan_id . '_last_update', time());
            
            // Run the scan
            if (function_exists('wpsec_run_scan')) {
                wpsec_run_scan($scan_id, $deep_scan, $verbose, $resume_from);
                
                file_put_contents(
                    $log_file, 
                    gmdate('Y-m-d H:i:s') . " - Direct recovery initiated for scan {$scan_id}" . PHP_EOL,
                    FILE_APPEND
                );
                
                echo esc_html("Recovery initiated for scan {$scan_id}. Check log for details.");
            } else {
                file_put_contents(
                    $log_file, 
                    gmdate('Y-m-d H:i:s') . " - ERROR: wpsec_run_scan function not found!" . PHP_EOL,
                    FILE_APPEND
                );
                
                echo "ERROR: Required functions not available.";
            }
        } else {
            echo esc_html("No recovery needed for scan {$scan_id}.");
        }
    }
}

// If called directly, output plain text
header('Content-Type: text/plain');
