Sindbad~EG File Manager

Current Path : /var/www/html/sumar.com.py/wp-content/plugins/migrator-plugin-0.1.0/includes/
Upload File :
Current File : /var/www/html/sumar.com.py/wp-content/plugins/migrator-plugin-0.1.0/includes/class-core.php

<?php
/**
 * The core plugin class.
 *
 * This is used to define internationalization, admin-specific hooks, and
 * public-facing site hooks.
 *
 * @package CustomMigrator
 */

/**
 * The core plugin class.
 */
class Custom_Migrator_Core {

    /**
     * The single instance of the class.
     *
     * @var Custom_Migrator_Core
     */
    private static $instance = null;

    /**
     * The filesystem handler.
     *
     * @var Custom_Migrator_Filesystem
     */
    private $filesystem;

    /**
     * The admin handler.
     *
     * @var Custom_Migrator_Admin
     */
    private $admin;

    /**
     * The fallback exporter handler.
     *
     * @var Custom_Migrator_Fallback_Exporter
     */
    private $fallback_exporter;

    /**
     * Initialize the plugin.
     *
     * @return void
     */
    public function init() {
        $this->load_dependencies();
        $this->define_admin_hooks();
        $this->define_ajax_hooks();
        $this->define_cron_hooks();
    }

    /**
     * Load the required dependencies for this plugin.
     *
     * @return void
     */
    private function load_dependencies() {
        // Admin class
        require_once CUSTOM_MIGRATOR_PLUGIN_DIR . 'admin/class-admin.php';
        
        // Include classes
        require_once CUSTOM_MIGRATOR_PLUGIN_DIR . 'includes/class-helper.php';
        require_once CUSTOM_MIGRATOR_PLUGIN_DIR . 'includes/class-file-enumerator.php';
        require_once CUSTOM_MIGRATOR_PLUGIN_DIR . 'includes/class-exporter.php';
        require_once CUSTOM_MIGRATOR_PLUGIN_DIR . 'includes/class-database-exporter.php';
        require_once CUSTOM_MIGRATOR_PLUGIN_DIR . 'includes/class-filesystem.php';
        require_once CUSTOM_MIGRATOR_PLUGIN_DIR . 'includes/class-metadata.php';
        require_once CUSTOM_MIGRATOR_PLUGIN_DIR . 'includes/class-s3-uploader.php';
        require_once CUSTOM_MIGRATOR_PLUGIN_DIR . 'includes/class-fallback-exporter.php';
        
        // Initialize the filesystem class for use throughout the plugin
        $this->filesystem = new Custom_Migrator_Filesystem();
        $this->admin = new Custom_Migrator_Admin();
        $this->fallback_exporter = new Custom_Migrator_Fallback_Exporter();
    }

    /**
     * Register all of the hooks related to the admin area functionality.
     *
     * @return void
     */
    private function define_admin_hooks() {
        $admin = new Custom_Migrator_Admin();
        add_action( 'admin_menu', array( $admin, 'add_admin_menu' ) );
        add_action( 'admin_enqueue_scripts', array( $admin, 'enqueue_scripts' ) );
        add_action( 'admin_init', array( $admin, 'handle_form_submission' ) );
        
        // Add settings link to the plugins page
        add_filter( 'plugin_action_links_' . plugin_basename( CUSTOM_MIGRATOR_PLUGIN_DIR . 'custom-migrator.php' ), 
                  array( $this, 'add_settings_link' ) );
        
        // Add custom cron schedules
        add_filter('cron_schedules', array($this, 'add_custom_cron_schedules'));
    }

    /**
     * Register all of the hooks related to AJAX functionality.
     *
     * @return void
     */
    private function define_ajax_hooks() {
        // AJAX handlers
        add_action( 'wp_ajax_cm_start_export', array( $this, 'handle_start_export' ) );
        add_action( 'wp_ajax_cm_check_status', array( $this, 'handle_check_status' ) );
        add_action( 'wp_ajax_cm_process_export_step', array( $this, 'process_export_step' ) );
        add_action( 'wp_ajax_cm_force_continue', array( $this, 'handle_force_continue' ) );
        add_action( 'wp_ajax_cm_run_export_now', array( $this, 'handle_run_export_now' ) );
        add_action( 'wp_ajax_cm_upload_to_s3', array( $this, 'handle_upload_to_s3' ) );
        add_action( 'wp_ajax_cm_check_s3_status', array( $this, 'handle_check_s3_status' ) );
        add_action( 'wp_ajax_cm_debug_status', array( $this, 'handle_debug_status' ) );
        
        // Status display handlers (no privilege required for UI display)
        add_action( 'wp_ajax_cm_get_export_status_display', array( $this, 'handle_get_export_status_display' ) );
        add_action( 'wp_ajax_cm_get_s3_status_display', array( $this, 'handle_get_s3_status_display' ) );
        
        // Plugin management handlers
        add_action( 'wp_ajax_cm_delete_plugin', array( $this, 'handle_delete_plugin' ) );
        
        // FALLBACK AJAX EXPORT SYSTEM - Following All-in-One WP Migration approach
        // Register both privileged and non-privileged actions for maximum compatibility
        add_action( 'wp_ajax_cm_fallback_export', array( $this->fallback_exporter, 'handle_fallback_export' ) );
        add_action( 'wp_ajax_nopriv_cm_fallback_export', array( $this->fallback_exporter, 'handle_fallback_export' ) );
        add_action( 'wp_ajax_cm_fallback_status', array( $this->fallback_exporter, 'handle_fallback_status' ) );
        add_action( 'wp_ajax_nopriv_cm_fallback_status', array( $this->fallback_exporter, 'handle_fallback_status' ) );
        
        add_action( 'cm_run_export', array( $this, 'run_export' ) );
    }

    /**
     * Define cron-related hooks with improved monitoring.
     *
     * @return void
     */
    private function define_cron_hooks() {
        add_action( 'cm_run_export', array( $this, 'run_export' ) );
        add_action( 'cm_monitor_export', array( $this, 'monitor_export_progress' ) );
        add_action( 'cm_run_export_direct', array( $this, 'run_export_directly' ) );
        add_action( 'cm_process_export_step_fallback', array( $this, 'process_export_step_fallback' ) );
        
        // Enhanced monitoring: Schedule frequent checks (every 1 minute for faster detection)
        if (!wp_next_scheduled('cm_monitor_export')) {
            wp_schedule_event(time(), 'custom_migrator_1min', 'cm_monitor_export');
        }
    }

    /**
     * Monitor export progress and detect stuck processes.
     * Enhanced to detect failed resume attempts on paused exports.
     *
     * @return void
     */
    public function monitor_export_progress() {
        // CRITICAL: Do not interfere with fallback exports
        if ($this->fallback_exporter->is_fallback_export_active()) {
            $this->filesystem->log("Monitor: Fallback export detected, skipping monitoring");
            return;
        }
        
        $status_file = $this->filesystem->get_status_file_path();
        
        if (!file_exists($status_file)) {
            return; // No export in progress
        }
        
        $status = trim(file_get_contents($status_file));
        
        // CRITICAL: Do NOT interfere with fallback exports
        if (strpos($status, 'fallback_') === 0) {
            $this->filesystem->log("Monitor: Detected fallback export ($status), skipping regular monitoring");
            return;
        }
        
        $modified_time = filemtime($status_file);
        $current_time = time();
        $time_diff = $current_time - $modified_time;
        
        // Check for stuck exports with more conservative timeouts to prevent premature restarts
        $stuck_statuses = ['starting', 'exporting', 'resuming'];
        if (in_array($status, $stuck_statuses)) {
            if ($time_diff > 300) { // 5 minutes timeout (was 2 minutes)
                $this->filesystem->log("Export appears stuck in '$status' state for $time_diff seconds. Attempting recovery.");
                
                // Try to restart the export
                $this->force_export_restart();
                return;
            } else if ($time_diff > 180) { // 3 minutes - just log warning
                $this->filesystem->log("Export running longer than expected in '$status' state ($time_diff seconds), monitoring...");
            }
        }
        
        // ENHANCED: Check for failed resume on paused exports
        if ($status === 'paused') {
            $resume_info_file = $this->filesystem->get_export_dir() . '/export-resume-info.json';
            
            if (file_exists($resume_info_file)) {
                $resume_data = json_decode(file_get_contents($resume_info_file), true);
                $last_update = isset($resume_data['last_update']) ? $resume_data['last_update'] : $modified_time;
                $pause_age = $current_time - $last_update;
                
                // If paused for more than 30 seconds without resume, force resume (reduced from 60s)
                if ($pause_age > 30) {
                    $files_processed = isset($resume_data['files_processed']) ? $resume_data['files_processed'] : 0;
                    $this->filesystem->log("Paused export detected ($files_processed files processed, {$pause_age}s since last update). Forcing resume.");
                    
                    // Enhanced resume: Multiple attempts with different methods
                    $this->force_resume_paused_export();
                    return;
                }
            }
        }
        
        // Clean up old monitoring if export is done
        if ($status === 'done' || strpos($status, 'error:') === 0) {
            wp_clear_scheduled_hook('cm_monitor_export');
        }
    }

    /**
     * Force restart a stuck export.
     *
     * @return void
     */
    private function force_export_restart() {
        // CRITICAL: Do not restart during fallback exports
        if ($this->fallback_exporter->is_fallback_export_active()) {
            $this->filesystem->log("Monitor: Fallback export active, skipping restart");
            return;
        }
        
        // Clear any existing scheduled events
        wp_clear_scheduled_hook('cm_run_export');
        
        // Update status to indicate restart
        $this->filesystem->write_status('restarting');
        $this->filesystem->log('Forcing export restart due to stuck process');
        
        // Schedule immediate restart
        wp_schedule_single_event(time() + 5, 'cm_run_export');
        
        // Force cron to run
        $this->trigger_cron_execution();
    }

    /**
     * Improved cron triggering with multiple fallback methods.
     *
     * @return void
     */
    private function trigger_cron_execution() {
        // CRITICAL: Do not trigger cron during fallback exports
        if ($this->fallback_exporter->is_fallback_export_active()) {
            $this->filesystem->log("Cron: Fallback export active, skipping cron trigger");
            return;
        }
        
        // Method 1: Standard WordPress spawn_cron
        if (function_exists('spawn_cron')) {
            spawn_cron();
            $this->filesystem->log('Triggered cron via spawn_cron()');
        }
        
        // Method 2: Direct HTTP request to wp-cron.php
        $cron_url = site_url('wp-cron.php');
        $this->non_blocking_request($cron_url . '?doing_wp_cron=1');
        $this->filesystem->log('Triggered cron via HTTP request');
        
        // Method 3: If cron is disabled, try direct execution
        if (defined('DISABLE_WP_CRON') && DISABLE_WP_CRON) {
            $this->filesystem->log('WP_CRON disabled, scheduling direct execution');
            wp_schedule_single_event(time() + 2, 'cm_run_export_direct');
        }
    }

    /**
     * Direct export execution for environments with disabled cron.
     *
     * @return void
     */
    public function run_export_directly() {
        $this->filesystem->log('Running export directly (cron disabled)');
        $this->run_export();
    }

    /**
     * Handle the AJAX request to check S3 upload status.
     *
     * @return void
     */
    public function handle_check_s3_status() {
        // Security check
        if ( ! current_user_can( 'manage_options' ) || ! check_ajax_referer( 'custom_migrator_nonce', 'nonce', false ) ) {
            wp_send_json_error( array( 'message' => 'Security check failed' ) );
        }

        $s3_status_file = WP_CONTENT_DIR . '/hostinger-migration-archives/s3-upload-status.txt';

        if ( ! file_exists( $s3_status_file ) ) {
            wp_send_json_error( array( 'status' => 'not_started' ) );
        }

        $status = trim( file_get_contents( $s3_status_file ) );
        
        if ( $status === 'done' ) {
            wp_send_json_success( array(
                'status' => 'done',
                'message' => 'S3 upload completed successfully'
            ) );
        } elseif (strpos($status, 'error:') === 0) {
            // Return error status
            wp_send_json_error(array(
                'status' => 'error',
                'message' => substr($status, 6) // Remove 'error:' prefix
            ));
        } else {
            // Get current file being uploaded if it's in the format "uploading_filetype"
            $current_file = '';
            if (strpos($status, 'uploading_') === 0) {
                $current_file = substr($status, 10); // Remove 'uploading_' prefix
            }
            
            wp_send_json_success( array( 
                'status' => $status,
                'current_file' => $current_file,
                'message' => 'Upload in progress' . ($current_file ? ': ' . $current_file : '')
            ));
        }
    }

    /**
     * Handle S3 upload request.
     *
     * @return void
     */
    public function handle_upload_to_s3() {
        // Try to increase PHP execution time limit
        @set_time_limit(0);  // Try to remove the time limit
        @ini_set('max_execution_time', 3600); // Try to set to 1 hour
    
        // Security check
        if ( ! current_user_can( 'manage_options' ) || ! check_ajax_referer( 'custom_migrator_nonce', 'nonce', false ) ) {
            wp_send_json_error( array( 'message' => 'Security check failed' ) );
        }
        
        // Check if export is done
        $status_file = $this->filesystem->get_status_file_path();
        if ( ! file_exists( $status_file ) || trim( file_get_contents( $status_file ) ) !== 'done' ) {
            wp_send_json_error( array( 'message' => 'Export is not complete. Please wait for export to finish before uploading to S3.' ) );
            return;
        }
        
        // Get pre-signed URLs
        $s3_urls = array(
            'hstgr'    => isset( $_POST['s3_url_hstgr'] ) ? sanitize_text_field( $_POST['s3_url_hstgr'] ) : '',
            'sql'      => isset( $_POST['s3_url_sql'] ) ? sanitize_text_field( $_POST['s3_url_sql'] ) : '',
            'metadata' => isset( $_POST['s3_url_metadata'] ) ? sanitize_text_field( $_POST['s3_url_metadata'] ) : '',
        );
        
        // Check if at least one URL is provided
        if ( empty( $s3_urls['hstgr'] ) && empty( $s3_urls['sql'] ) && empty( $s3_urls['metadata'] ) ) {
            wp_send_json_error( array( 'message' => 'Please provide at least one pre-signed URL for upload.' ) );
            return;
        }
        
        // Initialize S3 uploader
        $s3_uploader = new Custom_Migrator_S3_Uploader();
        
        // Upload files to S3
        $result = $s3_uploader->upload_to_s3( $s3_urls );
        
        if ( $result['success'] ) {
            wp_send_json_success( array( 
                'message' => 'Files uploaded successfully to S3.',
                'details' => $result['messages'],
                'uploaded_files' => $result['uploaded']
            ) );
        } else {
            wp_send_json_error( array( 
                'message' => 'Error uploading files to S3. Please check the export log for details.',
                'details' => $result['messages']
            ) );
        }
    }

    /**
     * Handle direct run export request (simple automation support).
     *
     * @return void
     */
    public function handle_run_export_now() {
        // Check if this is a background request
        $is_background = isset($_REQUEST['background_mode']) && $_REQUEST['background_mode'] === '1';
        
        if (!$is_background) {
            // For foreground requests, use standard WordPress security
            if ( ! current_user_can( 'manage_options' ) || ! check_ajax_referer( 'custom_migrator_nonce', 'nonce', false ) ) {
                wp_send_json_error( array( 'message' => 'Security check failed' ) );
            }
        }
        // Background requests are triggered by authenticated requests, so they don't need additional auth
        
        $this->filesystem->log('Processing export request (background: ' . ($is_background ? 'yes' : 'no') . ')');
        
        // Set proper execution environment for background processing
        if ($is_background) {
            if (function_exists('ignore_user_abort')) {
                ignore_user_abort(true);
            }
            
            if (function_exists('set_time_limit')) {
                set_time_limit(0);
            }
        }
        
        // Start the export process directly
        $this->run_export();
        
        if ($is_background) {
            // For background requests, don't send JSON response
            exit();
        } else {
            wp_send_json_success( array( 'message' => 'Export completed' ) );
        }
    }

    /**
     * Handle the AJAX request to start export process.
     *
     * @return void
     */
    public function handle_start_export() {
        // Security check - more lenient for automation
        if ( ! current_user_can( 'manage_options' ) || ! check_ajax_referer( 'custom_migrator_nonce', 'nonce', false ) ) {
            wp_send_json_error( array( 'message' => 'Security check failed' ) );
        }

        // CRITICAL: Immediately clear status file to prevent "done" status from showing
        $status_file = $this->filesystem->get_status_file_path();
        if (file_exists($status_file)) {
            @unlink($status_file);
        }
        $this->filesystem->write_status('starting');

        $base_dir = $this->filesystem->get_export_dir();
        
        // Create export directory if it doesn't exist
        if ( ! file_exists( $base_dir ) ) {
            try {
                $this->filesystem->create_export_dir();
            } catch ( Exception $e ) {
                wp_send_json_error( array( 'message' => $e->getMessage() ) );
                return;
            }
        }

        // Check current export status
        $status_file = $this->filesystem->get_status_file_path();
        $current_status = '';
        if (file_exists($status_file)) {
            $current_status = trim(file_get_contents($status_file));
        }
        
        // Check if there are existing files and we should resume instead of restart
        $file_paths = $this->filesystem->get_export_file_paths();
        $should_resume = false;
        
        // Resume if export is paused or there are incomplete files
        if ($current_status === 'paused' || (file_exists($file_paths['hstgr']) && filesize($file_paths['hstgr']) > 0 && $current_status !== 'done')) {
            $should_resume = true;
            $this->filesystem->log('Export needs to be resumed - status: ' . $current_status);
        }
        
        if ($should_resume) {
            // Resume via immediate HTTP request - no cron dependency
            $this->filesystem->log('Initiating immediate background resume for paused/incomplete export');
            $this->schedule_immediate_background_resume();
        } else {
            // Clean up any existing export and start fresh
            $this->cleanup_existing_export();
            
            // Important: Delete old filenames to force regeneration with new secure names
            delete_option('custom_migrator_filenames');

            // Update export status and immediately start background processing
            $this->filesystem->write_status( 'starting' );
            $this->filesystem->log('Export started, initiating immediate background processing');
            
            // Use immediate HTTP request instead of cron scheduling
            $this->schedule_immediate_background_start();
        }

        $wp_content_size = $this->filesystem->get_directory_size( WP_CONTENT_DIR );
        wp_send_json_success(array(
            'message' => $should_resume ? 'Resuming paused export immediately' : 'Export started and processing immediately in background',
            'estimated_size' => $this->filesystem->format_file_size($wp_content_size),
            'automation_ready' => true,
            'background_processing' => true,
            'processing_method' => 'immediate_http_request',
            'current_status' => $current_status,
            'monitor_endpoint' => admin_url('admin-ajax.php?action=cm_check_status')
        ));
    }

    /**
     * Schedule immediate HTTP request for background processing (simple).
     *
     * @param array $params Export parameters.
     * @return void
     */
    private function schedule_immediate_http_request($params) {
        $ajax_url = admin_url('admin-ajax.php');
        
        $request_params = array(
            'action' => 'cm_run_export_now',
            'background_mode' => '1'
        );
        
        $request_url = add_query_arg($request_params, $ajax_url);
        
        // Method 1: Immediate HTTP request
        $this->non_blocking_request($request_url);
        
        // Method 2: Schedule backup requests
        wp_schedule_single_event(time() + 1, 'cm_run_export');
        wp_schedule_single_event(time() + 3, 'cm_run_export');
        
        $this->filesystem->log('Simple HTTP request scheduling initiated (no secret keys)');
    }

    /**
     * Schedule immediate background start (no secret keys needed).
     *
     * @return void
     */
    private function schedule_immediate_background_start() {
        // CRITICAL: Do not start regular export during fallback
        if ($this->fallback_exporter->is_fallback_export_active()) {
            $this->filesystem->log("Background start: Fallback export active, skipping regular export start");
            return;
        }
        
        $ajax_url = admin_url('admin-ajax.php');
        
        $request_params = array(
            'action' => 'cm_run_export_now',
            'background_mode' => '1'
        );
        
        $request_url = add_query_arg($request_params, $ajax_url);
        
        // Method 1: Immediate HTTP request
        $this->non_blocking_request($request_url);
        
        // Method 2: Backup cron events
        wp_schedule_single_event(time() + 1, 'cm_run_export');
        wp_schedule_single_event(time() + 3, 'cm_run_export');
        
        // Method 3: Trigger cron execution
        $this->trigger_cron_execution();
        
        $this->filesystem->log('Simple background start initiated (no secret keys required)');
    }

    /**
     * Schedule immediate background resume (no secret keys needed).
     *
     * @return void
     */
    private function schedule_immediate_background_resume() {
        // CRITICAL: Do not resume regular export during fallback
        if ($this->fallback_exporter->is_fallback_export_active()) {
            $this->filesystem->log("Background resume: Fallback export active, skipping regular export resume");
            return;
        }
        
        $ajax_url = admin_url('admin-ajax.php');
        
        $request_params = array(
            'action' => 'cm_run_export_now',
            'background_mode' => '1'
        );
        
        $request_url = add_query_arg($request_params, $ajax_url);
        
        // Method 1: Immediate HTTP request
        $this->non_blocking_request($request_url);
        
        // Method 2: Backup cron events
        wp_schedule_single_event(time() + 1, 'cm_run_export');
        wp_schedule_single_event(time() + 3, 'cm_run_export');
        
        // Method 3: Trigger cron execution
        $this->trigger_cron_execution();
        
        $this->filesystem->log('Simple background resume initiated (no secret keys required)');
    }

    /**
     * Spawn a cURL request as ultimate fallback.
     *
     * @param string $url The URL to request.
     * @return void
     */
    private function spawn_curl_request($url) {
        // Only use cURL if available and on Unix-like systems
        if (!function_exists('exec') || !function_exists('shell_exec')) {
            return;
        }
        
        // Check if we're on a Unix-like system
        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
            return;
        }
        
        // Escape the URL for shell execution
        $escaped_url = escapeshellarg($url);
        
        // Create a cURL command that runs in background
        $curl_cmd = "curl -s -m 5 --connect-timeout 2 " . $escaped_url . " > /dev/null 2>&1 &";
        
        // Execute the command in background
        @exec($curl_cmd);
        
        $this->filesystem->log('Spawned background cURL request as ultimate fallback');
    }

    /**
     * Test if background HTTP requests are working.
     *
     * @return bool Whether HTTP requests appear to be working.
     */
    private function test_background_http() {
        $test_url = admin_url('admin-ajax.php?action=cm_debug_status&test=1');
        
        $args = array(
            'timeout'   => 2,
            'blocking'  => true, // Blocking for test
            'sslverify' => apply_filters('https_local_ssl_verify', false),
            'user-agent' => 'WordPress/' . get_bloginfo('version') . '; ' . home_url()
        );
        
        $response = wp_remote_get($test_url, $args);
        
        if (is_wp_error($response)) {
            $this->filesystem->log('HTTP test failed: ' . $response->get_error_message());
            return false;
        }
        
        $response_code = wp_remote_retrieve_response_code($response);
        $this->filesystem->log('HTTP test result: response code ' . $response_code);
        
        return $response_code === 200;
    }

    /**
     * Process export step by step (simple automation).
     *
     * @param array $params Export parameters.
     * @return array Updated parameters.
     */
    public function process_export_step($params = array()) {
        // Get params from request if not provided
        if (empty($params)) {
            $params = stripslashes_deep(array_merge($_GET, $_POST));
        }

        // Detect execution context
        $is_cron = defined('DOING_CRON') && DOING_CRON;
        $is_ajax = defined('DOING_AJAX') && DOING_AJAX;
        $is_background = $is_cron || !$is_ajax;
        
        // Simple security check for non-background requests
        if (!$is_background && !current_user_can('manage_options')) {
            $this->filesystem->log('Security check failed - user lacks permissions');
            return $params;
        }

        $current_step = isset($params['step']) ? $params['step'] : 'unknown';
        $this->filesystem->log('Processing export step: ' . $current_step . ' (background: ' . ($is_background ? 'yes' : 'no') . ')');
        
        // Save current step for tracking
        $step_file = $this->filesystem->get_export_dir() . '/export-step.txt';
        file_put_contents($step_file, $current_step);
        
        $this->setup_execution_environment();

        try {
            switch ($params['step']) {
                case 'init':
                    $params = $this->export_init($params);
                    break;
                case 'content':
                    $params = $this->export_content($params);
                    break;
                case 'database':
                    $params = $this->export_database($params);
                    break;
                case 'metadata':
                    $params = $this->export_metadata($params);
                    break;
                case 'finalize':
                    $params = $this->export_finalize($params);
                    break;
                default:
                    $params['completed'] = true;
                    break;
            }

            // Continue to next step if not completed
            if (!isset($params['completed']) || !$params['completed']) {
                $this->continue_export($params);
            } else {
                // Step completed, continue to next step if not done
                if ($params['step'] !== 'finalize') {
                    $this->filesystem->log('Step ' . $params['step'] . ' completed, scheduling next step');
                    $params['completed'] = false; // Reset for next step
                    $this->continue_export($params);
                }
            }

        } catch (Exception $e) {
            $this->filesystem->write_status('error: ' . $e->getMessage());
            $this->filesystem->log('Export step failed: ' . $e->getMessage());
        }

        return $params;
    }

    /**
     * Continue export process with true background processing (no cron dependency).
     *
     * @param array $params Export parameters.
     * @return void
     */
    private function continue_export($params) {
        // CRITICAL: Do not continue regular export during fallback
        if ($this->fallback_exporter->is_fallback_export_active()) {
            $this->filesystem->log("Continue: Fallback export active, skipping regular export continuation");
            return;
        }
        
        $this->filesystem->log("Starting immediate background continuation for step: " . $params['step']);
        
        // Method 1: Immediate HTTP request to self (most reliable)
        $this->schedule_immediate_http_request($params);
        
        // Method 2: Schedule immediate processing
        wp_schedule_single_event(time() + 1, 'cm_run_export');
        
        // Method 3: Force immediate cron execution
        $this->trigger_cron_execution();
        
        $this->filesystem->log("Background processing initiated for step: " . $params['step']);
    }

    /**
     * Initialize export process.
     *
     * @param array $params Export parameters.
     * @return array Updated parameters.
     */
    private function export_init($params) {
        $this->filesystem->log('Starting export initialization');
        $this->filesystem->write_status('initializing');

        // Initialize export environment
        $exporter = new Custom_Migrator_Exporter();
        $exporter->prepare_export();

        $params['step'] = 'content';
        $params['completed'] = true;
        
        $this->filesystem->log('Export initialization complete, proceeding to content step');
        return $params;
    }

    /**
     * Export wp-content files.
     *
     * @param array $params Export parameters.
     * @return array Updated parameters.
     */
    private function export_content($params) {
        $this->filesystem->log('Starting wp-content export');
        $this->filesystem->write_status('exporting');

        $exporter = new Custom_Migrator_Exporter();
        $result = $exporter->export_content_only();

        if ($result === 'paused') {
            // Content export is paused, stay on content step and let resume logic handle it
            $this->filesystem->log('wp-content export paused, staying on content step');
            $params['step'] = 'content'; // Stay on same step
            $params['completed'] = false;
            return $params;
        }

        if (!$result) {
            throw new Exception('Content export failed');
        }

        $params['step'] = 'database';
        $params['completed'] = false;
        
        $this->filesystem->log('wp-content export complete');
        return $params;
    }

    /**
     * Export database.
     *
     * @param array $params Export parameters.
     * @return array Updated parameters.
     */
    private function export_database($params) {
        $this->filesystem->log('Starting database export');
        $this->filesystem->write_status('exporting_database');

        $exporter = new Custom_Migrator_Exporter();
        $result = $exporter->export_database_only();

        if (!$result) {
            throw new Exception('Database export failed');
        }

        $params['step'] = 'metadata';
        $params['completed'] = false;
        
        $this->filesystem->log('Database export complete');
        return $params;
    }

    /**
     * Generate metadata files.
     *
     * @param array $params Export parameters.
     * @return array Updated parameters.
     */
    private function export_metadata($params) {
        $this->filesystem->log('Starting metadata generation');
        $this->filesystem->write_status('generating_metadata');

        $exporter = new Custom_Migrator_Exporter();
        $result = $exporter->generate_metadata_only();

        if (!$result) {
            throw new Exception('Metadata generation failed');
        }

        $params['step'] = 'finalize';
        $params['completed'] = false;
        
        $this->filesystem->log('Metadata generation complete');
        return $params;
    }

    /**
     * Finalize export process.
     *
     * @param array $params Export parameters.
     * @return array Updated parameters.
     */
    private function export_finalize($params) {
        $this->filesystem->log('Finalizing export');
        $this->filesystem->write_status('finalizing');

        // Verify all files exist
        $file_paths = $this->filesystem->get_export_file_paths();
        foreach ($file_paths as $type => $path) {
            if (!file_exists($path)) {
                throw new Exception("Missing export file: $type");
            }
        }

        $this->filesystem->write_status('done');
        $this->filesystem->log('Export completed successfully');

        $params['completed'] = true;
        return $params;
    }

    /**
     * Clean up any existing export files and processes.
     *
     * @return void
     */
    private function cleanup_existing_export() {
        // Clear any pending scheduled events
        wp_clear_scheduled_hook('cm_run_export');
        wp_clear_scheduled_hook('cm_monitor_export');
        
        // Get export directory and files
        $export_dir = $this->filesystem->get_export_dir();
        
        // If directory doesn't exist, nothing to clean
        if (!file_exists($export_dir)) {
            return;
        }
        
        // Clean up status file
        $status_file = $this->filesystem->get_status_file_path();
        if (file_exists($status_file)) {
            @unlink($status_file);
        }
        
        // Clean up resume info file if it exists
        $resume_info_file = $export_dir . '/export-resume-info.json';
        if (file_exists($resume_info_file)) {
            @unlink($resume_info_file);
        }
        
        // Clean up export files if they exist
        $file_paths = $this->filesystem->get_export_file_paths();
        foreach ($file_paths as $path) {
            if (file_exists($path)) {
                @unlink($path);
            }
        }
        
        // Log cleanup
        $this->filesystem->log("Cleaned up previous export files");
    }

    /**
     * Handle the AJAX request to check export status.
     *
     * @return void
     */
    public function handle_check_status() {
        // Security check
        if ( ! current_user_can( 'manage_options' ) || ! check_ajax_referer( 'custom_migrator_nonce', 'nonce', false ) ) {
            wp_send_json_error( array( 'message' => 'Security check failed' ) );
        }

        $status_file = $this->filesystem->get_status_file_path();
        $file_urls = $this->filesystem->get_export_file_urls();

        if ( ! file_exists( $status_file ) ) {
            wp_send_json_error( array( 'status' => 'not_started' ) );
        }

        $status = trim( file_get_contents( $status_file ) );
        $modified_time = filemtime($status_file);
        $current_time = time();
        $time_diff = $current_time - $modified_time;
        
        // PRODUCTION-SAFE: Very conservative stuck detection for high-volume production (3000 sites/week)
        $processing_statuses = ['starting', 'initializing', 'exporting', 'exporting_database', 'generating_metadata', 'finalizing', 'resuming'];
        if (in_array($status, $processing_statuses)) {
            // Very conservative timeouts to prevent premature restarts on large databases
            if ($time_diff > 600) { // 10 minutes warning (was 3 minutes)
                $this->filesystem->log("Export appears stuck in '$status' state for $time_diff seconds");
                
                // Only restart if REALLY stuck - large databases can take time
                if ($time_diff > 1200) { // 20 minutes restart (was 5 minutes)
                    $this->filesystem->log("CRITICAL: Export stuck for 20+ minutes, forcing restart");
                    $this->force_export_restart();
                }
                
                wp_send_json_success(array(
                    'status' => $status . '_stuck',
                    'message' => "Export may be stuck (no activity for " . round($time_diff/60, 1) . " minutes). Monitoring for recovery...",
                    'time_since_update' => $time_diff
                ));
                return;
            }
        }
        
        if ( $status === 'done' ) {
            // Get file information
            $file_paths = $this->filesystem->get_export_file_paths();
            $file_info = array();
            
            foreach ( $file_paths as $type => $path ) {
                if ( file_exists( $path ) ) {
                    $file_info[$type] = array(
                        'size' => $this->filesystem->format_file_size( filesize( $path ) ),
                        'raw_size' => filesize( $path ),
                        'modified' => date( 'Y-m-d H:i:s', filemtime( $path ) ),
                    );
                }
            }
            
            // Clean up monitoring when done
            wp_clear_scheduled_hook('cm_monitor_export');
            
            wp_send_json_success( array(
                'status'          => 'done',
                'hstgr_download'  => $file_urls['hstgr'],
                'sql_download'    => $file_urls['sql'],
                'metadata'        => $file_urls['metadata'],
                'log'             => $file_urls['log'],
                'file_info'       => $file_info,
            ) );
        } elseif ($status === 'paused') {
            // Get resume information 
            $resume_info_file = $this->filesystem->get_export_dir() . '/export-resume-info.json';
            $resume_info = file_exists($resume_info_file) ? json_decode(file_get_contents($resume_info_file), true) : array();
            
            $files_processed = isset($resume_info['files_processed']) ? (int)$resume_info['files_processed'] : 0;
            $bytes_processed = isset($resume_info['bytes_processed']) ? (int)$resume_info['bytes_processed'] : 0;
            
            // Get log for more details
            $log_file = $this->filesystem->get_log_file_path();
            $recent_log = '';
            
            if (file_exists($log_file)) {
                $log_content = file_get_contents($log_file);
                $log_lines = explode("\n", $log_content);
                $recent_log = implode("\n", array_slice($log_lines, -5)); // Get last 5 lines
            }
            
            // Ensure resume is scheduled
            if (!wp_next_scheduled('cm_run_export')) {
                wp_schedule_single_event(time() + 5, 'cm_run_export');
                $this->trigger_cron_execution();
            }
            
            wp_send_json_success(array( 
                'status' => 'paused_resuming',
                'message' => sprintf(
                    'Export is paused after processing %d files (%.2f MB) and will resume automatically', 
                    $files_processed, 
                    $bytes_processed / (1024 * 1024)
                ),
                'recent_log' => $recent_log,
                'progress' => array(
                    'files_processed' => $files_processed,
                    'bytes_processed' => $this->filesystem->format_file_size($bytes_processed),
                    'last_update' => isset($resume_info['last_update']) ? date('Y-m-d H:i:s', $resume_info['last_update']) : ''
                )
            ));
        } elseif (strpos($status, 'error:') === 0) {
            // Return error status
            wp_clear_scheduled_hook('cm_monitor_export');
            wp_send_json_error(array(
                'status' => 'error',
                'message' => substr($status, 6) // Remove 'error:' prefix
            ));
        } else {
            // Try to provide more detailed progress info with user-friendly messages
            $log_file = $this->filesystem->get_log_file_path();
            $recent_log = '';
            
            if (file_exists($log_file)) {
                $log_content = file_get_contents($log_file);
                $log_lines = explode("\n", $log_content);
                $recent_log = implode("\n", array_slice($log_lines, -3)); // Get last 3 lines
            }
            
            // Provide user-friendly status messages
            $status_messages = array(
                'starting' => 'Starting export process...',
                'initializing' => 'Initializing export environment...',
                'exporting' => 'Exporting wp-content files...',
                'exporting_database' => 'Exporting database...',
                'generating_metadata' => 'Generating metadata files...',
                'finalizing' => 'Finalizing export...',
                'resuming' => 'Resuming export process...'
            );
            
            $message = isset($status_messages[$status]) ? $status_messages[$status] : 'Processing: ' . $status;
            
            wp_send_json_success(array( 
                'status' => $status,
                'message' => $message,
                'recent_log' => $recent_log,
                'time_since_update' => $time_diff
            ));
        }
    }

    /**
     * Make a non-blocking HTTP request with multiple attempts for reliability.
     *
     * @param string $url The URL to request.
     * @return void
     */
    private function non_blocking_request($url) {
        // Method 1: Quick non-blocking request (0.5 second timeout)
        $args_quick = array(
            'timeout'   => 0.5,
            'blocking'  => false,
            'sslverify' => apply_filters('https_local_ssl_verify', false),
            'user-agent' => 'WordPress/' . get_bloginfo('version') . '; ' . home_url(),
            'redirection' => 0
        );
        
        wp_remote_get($url, $args_quick);
        
        // Method 2: Slightly longer timeout (1 second) as backup
        $args_backup = array(
            'timeout'   => 1,
            'blocking'  => false,
            'sslverify' => apply_filters('https_local_ssl_verify', false),
            'user-agent' => 'WordPress/' . get_bloginfo('version') . '; ' . home_url(),
            'redirection' => 0
        );
        
        // Small delay then send backup request
        usleep(100000); // 0.1 seconds
        wp_remote_get($url, $args_backup);
        
        $this->filesystem->log('Sent multiple HTTP requests for reliability');
    }

    /**
     * Run the export process.
     *
     * @return void
     */
    public function run_export() {
        // CRITICAL: Do not run regular export during fallback
        if ($this->fallback_exporter->is_fallback_export_active()) {
            $this->filesystem->log("Run export: Fallback export active, skipping regular export");
            return;
        }
        
        // Detect execution environment
        $is_background = $this->is_background_execution();
        $this->filesystem->log('Export execution started (background: ' . ($is_background ? 'yes' : 'no') . ')');
        
        // Set execution environment
        $this->setup_execution_environment();

        try {
            // Check if we are resuming a paused export
            $resume_info_file = $this->filesystem->get_export_dir() . '/export-resume-info.json';
            $is_resume = file_exists($resume_info_file);
            
            if ($is_resume) {
                $this->filesystem->write_status('resuming');
                $this->filesystem->log('Resuming export process');
            } else {
                // Update status to confirm we're running
                $this->filesystem->write_status('exporting');
                $this->filesystem->log('Export process is running');
            }
            
            // Add heartbeat to prevent stuck detection
            add_action('shutdown', array($this, 'update_export_heartbeat'));
            
            // Run the export
            $exporter = new Custom_Migrator_Exporter();
            $result = $exporter->export();
            
            if (!$result) {
                $this->filesystem->write_status('error: Export failed to complete successfully');
                $this->filesystem->log('Export failed to complete successfully');
            }
        } catch (Exception $e) {
            // Log the error and update the status
            $error_message = 'Export failed with error: ' . $e->getMessage();
            $this->filesystem->write_status('error: ' . $error_message);
            $this->filesystem->log($error_message);
            
            // Add additional debugging information if possible
            if (defined('WP_DEBUG') && WP_DEBUG) {
                $this->filesystem->log('Error trace: ' . $e->getTraceAsString());
            }
        } catch (Error $e) {
            // Handle PHP 7+ Fatal Errors
            $error_message = 'Export failed with fatal error: ' . $e->getMessage();
            $this->filesystem->write_status('error: ' . $error_message);
            $this->filesystem->log($error_message);
            $this->filesystem->log('Error trace: ' . $e->getTraceAsString());
        }
    }

    /**
     * Update heartbeat to show export is still active.
     *
     * @return void
     */
    public function update_export_heartbeat() {
        $status_file = $this->filesystem->get_status_file_path();
        if (file_exists($status_file)) {
            $status = trim(file_get_contents($status_file));
            if (in_array($status, ['exporting', 'resuming'])) {
                // Touch the file to update modification time
                touch($status_file);
            }
        }
    }

    /**
     * Detect if we're running in background.
     *
     * @return bool Whether we're running in background.
     */
    private function is_background_execution() {
        return (
            (defined('DOING_CRON') && DOING_CRON) ||
            (defined('DOING_AJAX') && DOING_AJAX) ||
            !isset($_SERVER['HTTP_HOST'])
        );
    }

    /**
     * Setup execution environment for background processing.
     *
     * @return void
     */
    private function setup_execution_environment() {
        // Set time limit
        if (function_exists('set_time_limit') && !ini_get('safe_mode')) {
            @set_time_limit(0);
        }
        
        // Increase memory limit
        $current_limit = $this->get_memory_limit_bytes();
        $target_limit = max($current_limit, 1024 * 1024 * 1024); // At least 1GB
        @ini_set('memory_limit', $this->format_bytes($target_limit));
        
        // Disable output buffering
        while (ob_get_level()) {
            ob_end_clean();
        }
        
        // Enable garbage collection
        if (function_exists('gc_enable')) {
            gc_enable();
        }
        
        // Log environment setup
        $this->filesystem->log('Environment: Memory=' . ini_get('memory_limit') . ', Time=' . ini_get('max_execution_time') . ', Safe Mode=' . (ini_get('safe_mode') ? 'On' : 'Off'));
    }

    /**
     * Attempt to increase memory limit for the export process.
     *
     * @return void
     */
    private function increase_memory_limit() {
        $current_limit = ini_get('memory_limit');
        $current_limit_int = $this->return_bytes($current_limit);
        
        // Try to increase to 512M if current limit is lower
        if ($current_limit_int < 536870912) {
            @ini_set('memory_limit', '512M');
        }
    }

    /**
     * Get memory limit in bytes.
     *
     * @return int Memory limit in bytes.
     */
    private function get_memory_limit_bytes() {
        $limit = ini_get('memory_limit');
        if ($limit == -1) return PHP_INT_MAX;
        
        $unit = strtolower(substr($limit, -1));
        $value = (int) $limit;
        
        switch ($unit) {
            case 'g': $value *= 1024;
            case 'm': $value *= 1024;
            case 'k': $value *= 1024;
        }
        
        return $value;
    }

    /**
     * Convert memory limit string to bytes.
     *
     * @param string $size_str Memory limit string like '256M'.
     * @return int Size in bytes.
     */
    private function return_bytes($size_str) {
        $size_str = trim($size_str);
        $unit = strtolower(substr($size_str, -1));
        $size = (int)$size_str;
        
        switch ($unit) {
            case 'g':
                $size *= 1024;
                // Fall through
            case 'm':
                $size *= 1024;
                // Fall through
            case 'k':
                $size *= 1024;
        }
        
        return $size;
    }

    /**
     * Format bytes to human readable format.
     *
     * @param int $bytes Number of bytes.
     * @return string Formatted string.
     */
    private function format_bytes($bytes) {
        if ($bytes >= 1073741824) return round($bytes / 1073741824, 1) . 'G';
        if ($bytes >= 1048576) return round($bytes / 1048576, 1) . 'M';
        if ($bytes >= 1024) return round($bytes / 1024, 1) . 'K';
        return $bytes . 'B';
    }

    /**
     * Add settings link to the plugins page.
     *
     * @param array $links Plugin action links.
     * @return array Modified plugin action links.
     */
    public function add_settings_link($links) {
        $settings_link = '<a href="' . CUSTOM_MIGRATOR_ADMIN_URL . '">' . __('Export Site', 'custom-migrator') . '</a>';
        array_unshift($links, $settings_link);
        return $links;
    }

    /**
     * Returns the singleton instance of this class.
     *
     * @return Custom_Migrator_Core The singleton instance.
     */
    public static function get_instance() {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Fallback method to process export steps via cron if HTTP requests fail.
     *
     * @param array $params Export parameters.
     * @return void
     */
    public function process_export_step_fallback($params) {
        $this->filesystem->log('Using fallback method to process step: ' . (isset($params['step']) ? $params['step'] : 'unknown'));
        
        // Check current export status first
        $status_file = $this->filesystem->get_status_file_path();
        $current_status = '';
        if (file_exists($status_file)) {
            $current_status = trim(file_get_contents($status_file));
        }
        
        // If export is paused, we need to resume via run_export, not step processing
        if ($current_status === 'paused') {
            // CRITICAL: Do not schedule during fallback exports
            if ($this->fallback_exporter->is_fallback_export_active()) {
                $this->filesystem->log('Fallback export active, skipping paused export resume');
                return;
            }
            
            $this->filesystem->log('Export is paused, scheduling resume via run_export instead of step processing');
            wp_schedule_single_event(time() + 2, 'cm_run_export');
            $this->trigger_cron_execution();
            return;
        }
        
        // Check if the step is still needed by examining the actual files
        $file_paths = $this->filesystem->get_export_file_paths();
        $current_step = isset($params['step']) ? $params['step'] : '';
        
        $step_needed = true;
        
        switch ($current_step) {
            case 'database':
                // Check if database file already exists and is complete
                if ((file_exists($file_paths['sql']) || file_exists($file_paths['sql'] . '.gz'))) {
                    $step_needed = false;
                    $this->filesystem->log('Database file already exists, skipping database fallback');
                }
                break;
                
            case 'metadata':
                // Check if metadata file already exists and is complete
                if (file_exists($file_paths['metadata'])) {
                    $step_needed = false;
                    $this->filesystem->log('Metadata file already exists, skipping metadata fallback');
                }
                break;
                
            case 'finalize':
                // Check if export is already done
                if ($current_status === 'done') {
                    $step_needed = false;
                    $this->filesystem->log('Export already completed, skipping finalize fallback');
                }
                break;
                
            case 'content':
                // For content step, only skip if export is actually done or content is truly complete
                // Don't skip just because .hstgr file exists - it might be incomplete (paused export)
                if ($current_status === 'done') {
                    $step_needed = false;
                    $this->filesystem->log('Export is done, skipping content fallback');
                } else {
                    $this->filesystem->log('Content step needed - export status: ' . $current_status);
                }
                break;
                
            case 'init':
                // Only skip init if we're past initialization
                if (in_array($current_status, ['exporting', 'exporting_database', 'generating_metadata', 'finalizing', 'done'])) {
                    $step_needed = false;
                    $this->filesystem->log('Already past initialization, skipping init fallback');
                }
                break;
        }
        
        if (!$step_needed) {
            $this->filesystem->log('Step ' . $current_step . ' not needed, skipping fallback');
            return;
        }
        
        // Mark this as an auto-continue request to bypass security checks
        $params['auto_continue'] = true;
        $params['is_fallback'] = true;
        
        // Process the step using the normal method
        $this->filesystem->log('Processing fallback for step: ' . $current_step);
        $this->process_export_step($params);
    }

    /**
     * Resume export from where it left off based on step tracking.
     *
     * @return void
     */
    public function resume_export_from_current_state() {
        $this->filesystem->log('Attempting to resume export from current state');
        
        // Check step tracking file first
        $step_file = $this->filesystem->get_export_dir() . '/export-step.txt';
        $next_step = 'init';
        
        if (file_exists($step_file)) {
            $tracked_step = trim(file_get_contents($step_file));
            if (!empty($tracked_step)) {
                $next_step = $tracked_step;
                $this->filesystem->log('Found tracked step: ' . $tracked_step);
            }
        }
        
        // Also check file existence to determine step (fallback)
        $file_paths = $this->filesystem->get_export_file_paths();
        
        if (file_exists($file_paths['hstgr']) && filesize($file_paths['hstgr']) > 0) {
            $this->filesystem->log('Found .hstgr file, considering database step');
            if ($next_step === 'init' || $next_step === 'content') {
                $next_step = 'database';
            }
        }
        if ((file_exists($file_paths['sql']) || file_exists($file_paths['sql'] . '.gz')) && filesize($file_paths['hstgr']) > 0) {
            $this->filesystem->log('Found database file, considering metadata step');
            if (in_array($next_step, ['init', 'content', 'database'])) {
                $next_step = 'metadata';
            }
        }
        if (file_exists($file_paths['metadata']) && filesize($file_paths['metadata']) > 0) {
            $this->filesystem->log('Found metadata file, considering finalize step');
            if (in_array($next_step, ['init', 'content', 'database', 'metadata'])) {
                $next_step = 'finalize';
            }
        }
        
        // Create resume parameters
        $params = array(
            'step' => $next_step,
            'completed' => false,
            'is_resume' => true,
            'start_time' => time()
        );
        
        $this->filesystem->log('Resuming export from step: ' . $next_step);
        $this->process_export_step($params);
    }

    /**
     * Handle force continue export request.
     *
     * @return void
     */
    public function handle_force_continue() {
        // Security check
        if ( ! current_user_can( 'manage_options' ) || ! check_ajax_referer( 'custom_migrator_nonce', 'nonce', false ) ) {
            wp_send_json_error( array( 'message' => 'Security check failed' ) );
        }

        $this->filesystem->log('Force continue requested by user');
        
        // Clear any pending scheduled events that might be stuck
        wp_clear_scheduled_hook('cm_process_export_step_fallback');
        
        // Resume from current state
        $this->resume_export_from_current_state();
        
        wp_send_json_success(array(
            'message' => 'Export force-continued successfully'
        ));
    }

    /**
     * Resume export from where it left off via background processing.
     *
     * @return void
     */
    private function schedule_background_resume() {
        $this->filesystem->log('Scheduling background resume');
        
        // Check step tracking file first
        $step_file = $this->filesystem->get_export_dir() . '/export-step.txt';
        $next_step = 'init';
        
        if (file_exists($step_file)) {
            $tracked_step = trim(file_get_contents($step_file));
            if (!empty($tracked_step)) {
                $next_step = $tracked_step;
                $this->filesystem->log('Found tracked step: ' . $tracked_step);
            }
        }
        
        // Also check file existence to determine step (fallback)
        $file_paths = $this->filesystem->get_export_file_paths();
        
        if (file_exists($file_paths['hstgr']) && filesize($file_paths['hstgr']) > 0) {
            $this->filesystem->log('Found .hstgr file, considering database step');
            if ($next_step === 'init' || $next_step === 'content') {
                $next_step = 'database';
            }
        }
        if ((file_exists($file_paths['sql']) || file_exists($file_paths['sql'] . '.gz')) && filesize($file_paths['hstgr']) > 0) {
            $this->filesystem->log('Found database file, considering metadata step');
            if (in_array($next_step, ['init', 'content', 'database'])) {
                $next_step = 'metadata';
            }
        }
        if (file_exists($file_paths['metadata']) && filesize($file_paths['metadata']) > 0) {
            $this->filesystem->log('Found metadata file, considering finalize step');
            if (in_array($next_step, ['init', 'content', 'database', 'metadata'])) {
                $next_step = 'finalize';
            }
        }
        
        // Create resume parameters
        $params = array(
            'step' => $next_step,
            'completed' => false,
            'is_resume' => true,
            'start_time' => time()
        );
        
        $this->filesystem->log('Resuming export from step: ' . $next_step);
        $this->process_export_step($params);
    }

    /**
     * Handle debug status request.
     *
     * @return void
     */
    public function handle_debug_status() {
        // Security check
        if ( ! current_user_can( 'manage_options' ) || ! check_ajax_referer( 'custom_migrator_nonce', 'nonce', false ) ) {
            wp_send_json_error( array( 'message' => 'Security check failed' ) );
        }

        $status_file = $this->filesystem->get_status_file_path();
        $file_urls = $this->filesystem->get_export_file_urls();

        if ( ! file_exists( $status_file ) ) {
            wp_send_json_error( array( 'status' => 'not_started' ) );
        }

        $status = trim( file_get_contents( $status_file ) );
        $modified_time = filemtime($status_file);
        $current_time = time();
        $time_diff = $current_time - $modified_time;
        
        // Enhanced stuck detection for all processing statuses with conservative timeouts  
        $processing_statuses = ['starting', 'initializing', 'exporting', 'exporting_database', 'generating_metadata', 'finalizing', 'resuming'];
        if (in_array($status, $processing_statuses)) {
            // More conservative timeouts to prevent premature restarts
            if ($time_diff > 180) { // 3 minutes warning (was 1 minute)
                $this->filesystem->log("Export appears stuck in '$status' state for $time_diff seconds");
                
                // Try to restart if really stuck
                if ($time_diff > 300) { // 5 minutes restart (was 2 minutes)
                    $this->force_export_restart();
                }
                
                wp_send_json_success(array(
                    'status' => $status . '_stuck',
                    'message' => "Export may be stuck (no activity for " . round($time_diff/60, 1) . " minutes). Monitoring for recovery...",
                    'time_since_update' => $time_diff
                ));
                return;
            }
        }
        
        if ( $status === 'done' ) {
            // Get file information
            $file_paths = $this->filesystem->get_export_file_paths();
            $file_info = array();
            
            foreach ( $file_paths as $type => $path ) {
                if ( file_exists( $path ) ) {
                    $file_info[$type] = array(
                        'size' => $this->filesystem->format_file_size( filesize( $path ) ),
                        'raw_size' => filesize( $path ),
                        'modified' => date( 'Y-m-d H:i:s', filemtime( $path ) ),
                    );
                }
            }
            
            // Clean up monitoring when done
            wp_clear_scheduled_hook('cm_monitor_export');
            
            wp_send_json_success( array(
                'status'          => 'done',
                'hstgr_download'  => $file_urls['hstgr'],
                'sql_download'    => $file_urls['sql'],
                'metadata'        => $file_urls['metadata'],
                'log'             => $file_urls['log'],
                'file_info'       => $file_info,
            ) );
        } elseif ($status === 'paused') {
            // Get resume information 
            $resume_info_file = $this->filesystem->get_export_dir() . '/export-resume-info.json';
            $resume_info = file_exists($resume_info_file) ? json_decode(file_get_contents($resume_info_file), true) : array();
            
            $files_processed = isset($resume_info['files_processed']) ? (int)$resume_info['files_processed'] : 0;
            $bytes_processed = isset($resume_info['bytes_processed']) ? (int)$resume_info['bytes_processed'] : 0;
            
            // Get log for more details
            $log_file = $this->filesystem->get_log_file_path();
            $recent_log = '';
            
            if (file_exists($log_file)) {
                $log_content = file_get_contents($log_file);
                $log_lines = explode("\n", $log_content);
                $recent_log = implode("\n", array_slice($log_lines, -5)); // Get last 5 lines
            }
            
            // Ensure resume is scheduled
            if (!wp_next_scheduled('cm_run_export')) {
                wp_schedule_single_event(time() + 5, 'cm_run_export');
                $this->trigger_cron_execution();
            }
            
            wp_send_json_success(array( 
                'status' => 'paused_resuming',
                'message' => sprintf(
                    'Export is paused after processing %d files (%.2f MB) and will resume automatically', 
                    $files_processed, 
                    $bytes_processed / (1024 * 1024)
                ),
                'recent_log' => $recent_log,
                'progress' => array(
                    'files_processed' => $files_processed,
                    'bytes_processed' => $this->filesystem->format_file_size($bytes_processed),
                    'last_update' => isset($resume_info['last_update']) ? date('Y-m-d H:i:s', $resume_info['last_update']) : ''
                )
            ));
        } elseif (strpos($status, 'error:') === 0) {
            // Return error status
            wp_clear_scheduled_hook('cm_monitor_export');
            wp_send_json_error(array(
                'status' => 'error',
                'message' => substr($status, 6) // Remove 'error:' prefix
            ));
        } else {
            // Try to provide more detailed progress info with user-friendly messages
            $log_file = $this->filesystem->get_log_file_path();
            $recent_log = '';
            
            if (file_exists($log_file)) {
                $log_content = file_get_contents($log_file);
                $log_lines = explode("\n", $log_content);
                $recent_log = implode("\n", array_slice($log_lines, -3)); // Get last 3 lines
            }
            
            // Provide user-friendly status messages
            $status_messages = array(
                'starting' => 'Starting export process...',
                'initializing' => 'Initializing export environment...',
                'exporting' => 'Exporting wp-content files...',
                'exporting_database' => 'Exporting database...',
                'generating_metadata' => 'Generating metadata files...',
                'finalizing' => 'Finalizing export...',
                'resuming' => 'Resuming export process...'
            );
            
            $message = isset($status_messages[$status]) ? $status_messages[$status] : 'Processing: ' . $status;
            
            wp_send_json_success(array( 
                'status' => $status,
                'message' => $message,
                'recent_log' => $recent_log,
                'time_since_update' => $time_diff
            ));
        }
    }

    /**
     * Force resume a paused export with multiple fallback methods.
     *
     * @return void
     */
    private function force_resume_paused_export() {
        // Method 1: Multiple HTTP requests (more aggressive)
        $ajax_url = admin_url('admin-ajax.php');
        $request_params = array(
            'action' => 'cm_run_export_now',
            'background_mode' => '1'
        );
        
        for ($i = 0; $i < 3; $i++) {
            wp_remote_post($ajax_url, array(
                'method'    => 'POST',
                'timeout'   => 0.01,
                'blocking'  => false,
                'sslverify' => false,
                'headers'   => array('Connection' => 'close'),
                'body'      => $request_params,
            ));
        }
        
        // Method 2: Staggered cron scheduling (multiple attempts)
        wp_schedule_single_event(time() + 5, 'cm_run_export');   // 5 seconds
        wp_schedule_single_event(time() + 15, 'cm_run_export');  // 15 seconds  
        wp_schedule_single_event(time() + 30, 'cm_run_export');  // 30 seconds
        wp_schedule_single_event(time() + 60, 'cm_run_export');  // 1 minute
        wp_schedule_single_event(time() + 120, 'cm_run_export'); // 2 minutes
        
        // Method 3: Enhanced cron triggering
        $this->trigger_cron_execution();
        
        $this->filesystem->log('Forced resume for paused export with multiple fallback methods');
    }

    /**
     * Add custom cron schedules.
     *
     * @param array $schedules Existing cron schedules.
     * @return array Modified cron schedules.
     */
    public function add_custom_cron_schedules($schedules) {
        $schedules['custom_migrator_1min'] = array(
            'interval' => 1 * 60, // 1 minute
            'display' => __('Every 1 minute', 'custom-migrator')
        );
        $schedules['custom_migrator_5min'] = array(
            'interval' => 5 * 60, // 5 minutes (kept for compatibility)
            'display' => __('Every 5 minutes', 'custom-migrator')
        );
        return $schedules;
    }

    /**
     * Handle AJAX request to get export status for UI display.
     * No security check needed as this just reads status text file content.
     *
     * @return void
     */
    public function handle_get_export_status_display() {
        // Enhanced cache-busting headers
        header('Cache-Control: no-cache, no-store, must-revalidate, max-age=0');
        header('Pragma: no-cache');
        header('Expires: Thu, 01 Jan 1970 00:00:00 GMT');
        header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');

        $status_file = $this->filesystem->get_export_dir() . '/export-status.txt';
        
        if ( ! file_exists( $status_file ) ) {
            wp_send_json_success( array( 'status' => '' ) );
            return;
        }

        // Clear any file stat cache to ensure fresh data
        clearstatcache(true, $status_file);
        
        // Get file modification time for additional cache-busting
        $file_mtime = filemtime($status_file);
        
        $status = trim( file_get_contents( $status_file ) );
        
        wp_send_json_success( array( 
            'status' => $status,
            'timestamp' => time(),
            'file_mtime' => $file_mtime
        ) );
    }

    /**
     * Handle AJAX request to get S3 upload status for UI display.
     * No security check needed as this just reads status text file content.
     *
     * @return void
     */
    public function handle_get_s3_status_display() {
        // Enhanced cache-busting headers
        header('Cache-Control: no-cache, no-store, must-revalidate, max-age=0');
        header('Pragma: no-cache');
        header('Expires: Thu, 01 Jan 1970 00:00:00 GMT');
        header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');

        $s3_status_file = $this->filesystem->get_export_dir() . '/s3-upload-status.txt';
        
        if ( ! file_exists( $s3_status_file ) ) {
            wp_send_json_success( array( 'status' => '' ) );
            return;
        }

        // Clear any file stat cache to ensure fresh data
        clearstatcache(true, $s3_status_file);
        
        // Get file modification time for additional cache-busting
        $file_mtime = filemtime($s3_status_file);
        
        $status = trim( file_get_contents( $s3_status_file ) );
        
        wp_send_json_success( array( 
            'status' => $status,
            'timestamp' => time(),
            'file_mtime' => $file_mtime
        ) );
    }

    /**
     * Handle the AJAX request to delete the plugin.
     *
     * @return void
     */
    public function handle_delete_plugin() {
        // Delegate to admin class which has the deletion logic
        $this->admin->delete_plugin();
    }


}

Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists