<?php

namespace WPSecurityNinja\Plugin;

use Error;
use wf_sn_cf;
use Wf_Sn_Cs;
use Wf_Sn_Wl;
use Utils;

if ( ! function_exists( 'add_action' ) ) {
	die( 'Please don\'t open this file directly!' );
}

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

define( 'WF_SN_SS_OPTIONS_KEY', 'wf_sn_ss' );
define( 'WF_SN_SS_CRON', 'wf_sn_ss_cron' );
define( 'WF_SN_SS_TABLE', 'wf_sn_ss_log' );
define( 'WF_SN_SS_LOG_LIMIT', 50 );

// Load ActionScheduler from bundle (premium only; this file is only loaded in premium).
// Prefer WF_SN_PLUGIN_DIR; fallback to path relative to this file for different server configs.
if ( ! class_exists( 'ActionScheduler_Versions' ) ) {
	$action_scheduler_file = null;
	if ( defined( 'WF_SN_PLUGIN_DIR' ) ) {
		$path = WF_SN_PLUGIN_DIR . 'vendor/woocommerce/action-scheduler/action-scheduler.php';
		if ( file_exists( $path ) && is_readable( $path ) ) {
			$action_scheduler_file = $path;
		}
	}
	if ( ! $action_scheduler_file ) {
		$plugin_root = dirname( __DIR__, 2 );
		$path        = $plugin_root . '/vendor/woocommerce/action-scheduler/action-scheduler.php';
		if ( file_exists( $path ) && is_readable( $path ) ) {
			$action_scheduler_file = $path;
		}
	}
	if ( $action_scheduler_file ) {
		require_once $action_scheduler_file;
	}
}


class Wf_sn_ss {





	public static $options = null;

	/**
	 * plugins_loaded.
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Tuesday, November 14th, 2023.
	 * @access  static
	 * @return  void
	 */
	public static function plugins_loaded() {
		add_filter( 'cron_schedules', array( __NAMESPACE__ . '\\wf_sn_ss', 'cron_intervals' ) );
	}


	/**
	 * init.
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Tuesday, November 14th, 2023.
	 * @access  static
	 * @return  void
	 */
	public static function init() {
		// does the user have enough privileges to use the plugin?
		if ( is_admin() ) {
			// add tab to Security Ninja tabs
			add_filter( 'sn_tabs', array( __NAMESPACE__ . '\\wf_sn_ss', 'sn_tabs' ) );
			add_action( 'admin_enqueue_scripts', array( __NAMESPACE__ . '\\wf_sn_ss', 'enqueue_scripts' ) );
			add_action( 'sn_overlay_content', array( __NAMESPACE__ . '\\wf_sn_ss', 'overlay_content' ) );
			add_action( 'wp_ajax_sn_ss_truncate_log', array( __NAMESPACE__ . '\\wf_sn_ss', 'truncate_log' ) );
			add_action( 'wp_ajax_sn_ss_sn_details', array( __NAMESPACE__ . '\\wf_sn_ss', 'ajax_dialog_sn_details' ) );
			add_action( 'wp_ajax_sn_ss_cs_details', array( __NAMESPACE__ . '\\wf_sn_ss', 'ajax_dialog_cs_details' ) );
			add_action( 'wp_ajax_sn_ss_ms_details', array( __NAMESPACE__ . '\\wf_sn_ss', 'ajax_dialog_ms_details' ) );
			add_action( 'wp_ajax_sn_ss_cs_test', array( __NAMESPACE__ . '\\wf_sn_ss', 'do_cron_task_ajax' ) );

			add_action( 'wp_ajax_sn_ss_get_logs', array( __NAMESPACE__ . '\\wf_sn_ss', 'get_logs' ) );

			// check and set default settings
			add_action( 'admin_init', array( __NAMESPACE__ . '\\wf_sn_ss', 'register_settings' ) );
		}
		add_action( 'init', array( __NAMESPACE__ . '\\wf_sn_ss', 'load_options' ) );
		add_action( 'init', array( __NAMESPACE__ . '\\wf_sn_ss', 'maybe_reschedule' ), 20 );
		add_action( 'wf_sn_ss_cron', array( __NAMESPACE__ . '\\wf_sn_ss', 'do_cron_task' ) );
		add_action( 'wf_sn_ss_run_scheduled_scan', array( __NAMESPACE__ . '\\wf_sn_ss', 'do_cron_task' ) );
	}

	/**
	 * Self-heal the scheduled scan event.
	 *
	 * Scheduled scans are stored as an Action Scheduler (or WP-Cron) event. That
	 * event can disappear without the settings changing - for example when a scan
	 * fatals/times out so Action Scheduler never queues the next occurrence, or
	 * when a third-party cron/optimization plugin clears scheduled events. When
	 * that happens the previous behaviour required the user to re-save settings to
	 * recreate it. This method detects "scheduling enabled but nothing scheduled"
	 * and recreates the event automatically.
	 *
	 * Runs on every request but is throttled to once per hour to keep it cheap.
	 *
	 * @access  public static
	 * @return  void
	 */
	public static function maybe_reschedule() {
		// Scheduled scanning is a premium feature.
		$can_use_premium = false;
		if ( secnin_fs()->can_use_premium_code__premium_only() ) {
			$can_use_premium = true;
		}
		if ( ! $can_use_premium ) {
			return;
		}

		if ( ! self::is_scheduling_enabled() ) {
			return;
		}

		// Throttle the check so it does not run a query on every single request.
		if ( get_transient( 'wf_sn_ss_reschedule_check' ) ) {
			return;
		}
		set_transient( 'wf_sn_ss_reschedule_check', 1, HOUR_IN_SECONDS );

		$state = self::get_scheduler_state();
		if ( 0 < (int) $state['timestamp'] ) {
			return;
		}

		// Scheduling is enabled but no run is currently scheduled - recreate it.
		self::setup_cron();

		if ( class_exists( __NAMESPACE__ . '\\wf_sn_el_modules' ) ) {
			\WPSecurityNinja\Plugin\wf_sn_el_modules::log_event(
				'security_ninja',
				'scheduled_scanner',
				__( 'Scheduled scan event was missing and has been recreated automatically.', 'security-ninja' ),
				''
			);
		}
	}

	/**
	 * Load options during init hook to ensure translations are available
	 */
	public static function load_options() {
		self::$options = self::get_options();
	}

	/**
	 * Get log lines via AJAX call
	 *
	 * @author  AI Assistant
	 * @since   v0.0.1
	 * @version v1.0.0  Tuesday, November 14th, 2023.
	 * @access  public
	 * @return  void
	 */
	public static function get_logs() {
		check_ajax_referer( 'secnin_scheduled_scanner', 'nonce' );

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( esc_html__( 'You do not have sufficient permissions to access this page.', 'security-ninja' ) );
		}

		// Early return if no valid premium license - prevents database errors during race condition
		$can_use_premium = false;
		if ( secnin_fs()->can_use_premium_code__premium_only() ) {
			$can_use_premium = true;
		}
		if ( ! $can_use_premium ) {
			wp_send_json_error( esc_html__( 'No logs found.', 'security-ninja' ) );
		}

		global $wpdb;

		$cache_key = 'wf_sn_ss_logs';

		// Try to get the logs from cache
		$logs = wp_cache_get( $cache_key, 'security-ninja' );

		if ( false === $logs ) {
			// If not cached, query the database
			$query = $wpdb->prepare(
				'SELECT * FROM ' . $wpdb->prefix . WF_SN_SS_TABLE . ' ORDER BY timestamp DESC LIMIT %d',
				WF_SN_SS_LOG_LIMIT
			);

			$logs = $wpdb->get_results( $query );

			// Store the result in cache
			wp_cache_set( $cache_key, $logs, 'security-ninja', 900 ); // Cache for 15 minutes
		}
		// $logs = $wpdb->get_results('SELECT * FROM ' . $wpdb->prefix . WF_SN_SS_TABLE . ' ORDER by timestamp DESC LIMIT ' . WF_SN_SS_LOG_LIMIT);

		if ( $logs ) {
			$html = '';
			foreach ( $logs as $log ) {
				$tmp   = strtotime( $log->timestamp );
				$tmp   = date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $tmp );
				$html .= '<tr>';
				$html .= '<td class="column-primary log-sn-ss-timestamp">' . wp_kses_post( $tmp );
				$html .= '<button type="button" class="toggle-row">';
				$html .= '<span class="screen-reader-text">' . esc_html__( 'Show details', 'security-ninja' ) . '</span>';
				$html .= '</button>';
				$html .= '</td>';

				$html .= '<td class="log-sn-ss-runtime" data-colname="' . esc_attr__( 'Run time', 'security-ninja' ) . '">';
				/* translators: %s: number of seconds */
				$html .= esc_html( sprintf( __( '%s sec', 'security-ninja' ), number_format_i18n( $log->runtime, 1 ) ) );
				$html .= '</td>';

				$html .= '<td class="log-sn-ss-sn" data-colname="' . esc_html__( 'Security Tests', 'security-ninja' ) . '">';
				if ( ! maybe_unserialize( $log->sn_results ) ) {
					$html .= '<i>' . esc_html__( 'Tests were not run.', 'security-ninja' ) . '</i>';
				} else {
					if ( $log->sn_change ) {
						$html .= '<span class="sn-ss-change">' . esc_html__( 'The results have changed since last scan.', 'security-ninja' ) . '</span>';
					} else {
						$html .= '<span class="sn-ss-nochange">' . esc_html__( 'No changes in results since last scan.', 'security-ninja' ) . '</span>';
					}
					$html .= ' &nbsp;&nbsp;<a href="#" data-timestamp="' . esc_attr( $tmp ) . '" data-row-id="' . esc_attr( $log->id ) . '" class="ss-details-sn">' . esc_html__( 'View details', 'security-ninja' ) . '</a>';
				}
				$html .= '</td>';

				$html .= '<td class="log-sn-ss-ss" data-colname="' . esc_html__( 'Core Scanner', 'security-ninja' ) . '">';
				if ( ! ( $log->cs_results ) ) {
					$html .= '<i>' . esc_html__( 'Tests were not run.', 'security-ninja' ) . '</i>';
				} else {
					if ( $log->cs_change ) {
						$html .= '<span class="sn-ss-change">' . esc_html__( 'The results have changed since last scan.', 'security-ninja' ) . '</span>';
					} else {
						$html .= '<span class="sn-ss-nochange">' . esc_html__( 'No changes in results since last scan.', 'security-ninja' ) . '</span>';
					}
					$html .= ' &nbsp;&nbsp;<a href="#" data-timestamp="' . esc_attr( $tmp ) . '" data-row-id="' . esc_attr( $log->id ) . '" class="ss-details-cs">' . esc_html__( 'View details', 'security-ninja' ) . '</a>';
				}
				$html .= '</td>';

				$html .= '<td class="log-sn-ss-ms" data-colname="' . esc_html__( 'Malware Scanner', 'security-ninja' ) . '">';
				if ( ! isset( $log->ms_results ) || null === $log->ms_results || '' === $log->ms_results ) {
					$html .= '<i>' . esc_html__( 'Not run', 'security-ninja' ) . '</i>';
				} else {
					$ms_data   = maybe_unserialize( $log->ms_results );
					$ms_change = isset( $log->ms_change ) ? (int) $log->ms_change : 0;
					$count     = 0;
					if ( is_array( $ms_data ) && ! empty( $ms_data['malware_identified'] ) ) {
						$count = count( (array) $ms_data['malware_identified'] );
					}
					if ( $ms_change ) {
						$html .= '<span class="sn-ss-change">' . esc_html__( 'The results have changed since last scan.', 'security-ninja' ) . '</span>';
					} else {
						$html .= '<span class="sn-ss-nochange">' . esc_html__( 'No changes in results since last scan.', 'security-ninja' ) . '</span>';
					}
					if ( $count > 0 ) {
						/* translators: 1: number of suspicious files */
						$html .= ' &nbsp;(' . sprintf( _n( '%d suspicious file', '%d suspicious files', $count, 'security-ninja' ), $count ) . ')';
					}
					$html .= ' &nbsp;&nbsp;<a href="#" data-timestamp="' . esc_attr( $tmp ) . '" data-row-id="' . esc_attr( $log->id ) . '" class="ss-details-ms">' . esc_html__( 'View details', 'security-ninja' ) . '</a>';
				}
				$html .= '</td>';
				$html .= '</tr>';
			}
			wp_send_json_success( $html );
		} else {
			wp_send_json_error( esc_html__( 'No logs found.', 'security-ninja' ) );
		}
	}


	/**
	 * Enqueue scripts and styles for the Scheduled Scanner module.
	 *
	 * This function enqueues the necessary JavaScript file and localizes
	 * script data for the Scheduled Scanner functionality.
	 *
	 * @since 1.0.0
	 * @access public static
	 * @return void
	 */
	public static function enqueue_scripts() {
		if ( ! Wf_Sn::is_plugin_page() ) {
			return;
		}

		$sn_ss_js = WF_SN_PLUGIN_DIR . 'modules/scheduled-scanner/js/wf-sn-ss.js';
		wp_enqueue_script(
			'sn-ss',
			WF_SN_PLUGIN_URL . 'modules/scheduled-scanner/js/wf-sn-ss.js',
			array( 'jquery', 'sn-dialog' ),
			file_exists( $sn_ss_js ) ? filemtime( $sn_ss_js ) : \WPSecurityNinja\Plugin\Utils::get_plugin_version(),
			true
		);

		wp_localize_script(
			'sn-ss',
			'secninScheduledScanner',
			array(
				'nonce'   => wp_create_nonce( 'secnin_scheduled_scanner' ),
				'strings' => array(
					'loading'                  => __( 'Loading…', 'security-ninja' ),
					'confirm_save_before_test' => __( 'Please remember to save settings before testing them. Continue?', 'security-ninja' ),
					'running_status'           => __( 'Running… This may take several minutes when Malware Scanner is included.', 'security-ninja' ),
					/* translators: %d: number of seconds */
					'time_elapsed_format'      => __( 'Time elapsed: %d seconds', 'security-ninja' ),
					'tasks_completed_fallback' => __( 'Tasks completed successfully.', 'security-ninja' ),
					'request_failed'           => __( 'Request failed or timed out. Try again or run the scheduled scan.', 'security-ninja' ),
					'confirm_delete_logs'      => __( 'Are you sure you want to delete all log entries?', 'security-ninja' ),
					'bad_ajax_response'        => __( 'Bad AJAX response. Please reload the page.', 'security-ninja' ),
					/* translators: %s: date/time */
					'dialog_title_sn'          => __( 'Security Ninja results from %s', 'security-ninja' ),
					/* translators: %s: date/time */
					'dialog_title_cs'          => __( 'Core Scanner results from %s', 'security-ninja' ),
					/* translators: %s: date/time */
					'dialog_title_ms'          => __( 'Malware Scanner results from %s', 'security-ninja' ),
					'error_try_again'          => __( 'An error occurred. Please try again.', 'security-ninja' ),
				),
			)
		);
	}

	/**
	 * add new tab
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Saturday, September 3rd, 2022.
	 * @access  static
	 * @param   mixed   $tabs
	 * @return  mixed
	 */
	public static function sn_tabs( $tabs ) {
		$schedule_tab = array(
			'id'       => 'sn_schedule',
			'class'    => '',
			'label'    => esc_html__( 'Scheduler', 'security-ninja' ),
			'callback' => array( __NAMESPACE__ . '\\wf_sn_ss', 'schedule_page' ),
		);
		$done         = 0;

		$tab_count = count( $tabs );

		for ( $i = 0; $i < $tab_count; $i++ ) {
			if ( 'sn_schedule' === $tabs[ $i ]['id'] ) {
				$tabs[ $i ] = $schedule_tab;
				$done       = 1;
				break;
			}
		}

		if ( ! $done ) {
			$tabs[] = $schedule_tab;
		}

		return $tabs;
	}


	/**
	 * add custom message to overlay
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Saturday, September 3rd, 2022.
	 * @access  static
	 * @return  void
	 */
	public static function overlay_content() {
		echo '<div id="sn-scheduled-scanner" style="display: none;">';
		echo '<h3>' . esc_html__( 'Security Ninja is testing Scheduled Scanner settings.', 'security-ninja' ) . '</h3>';
		echo '<div id="sn-timer"></div>';
		echo '</div>';
	}

	/**
	 * set default options
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Saturday, September 3rd, 2022.
	 * @access  static
	 * @param   boolean $force  Default: false
	 * @return  array
	 */
	public static function default_settings() {
		return array(
			'main_setting'  => '0',
			'scan_schedule' => 'twicedaily',
			'email_report'  => 2,
			'email_to'      => get_bloginfo( 'admin_email' ),
		);
	}




	/**
	 * get_options.
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, May 6th, 2024.
	 * @access  public static
	 * @return  mixed
	 */
	public static function get_options() {

		if ( isset( self::$options ) && ( 0 < count( self::$options ) ) ) {

			return self::$options;
		}
		$options = get_option( WF_SN_SS_OPTIONS_KEY, array() );

		if ( isset( $options[0] ) ) {
			unset( $options[0] );
		}

		if ( ! is_array( $options ) ) {
			$options = array();
		}
		$options       = array_merge( self::default_settings(), $options );
		self::$options = $options; // her sætter vi globale options.
		return $options;
	}







	/**
	 * sanitize settings on save
	 * In scheduled-scanner.php
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Saturday, September 3rd, 2022.
	 * @version v1.0.1  Wednesday, April 10th, 2024.
	 * @access  static
	 * @global
	 * @param   mixed   $values
	 * @return  mixed
	 */
	public static function sanitize_settings( $values ) {
		// Assuming $values is an associative array of settings to be sanitized and saved
		$sanitized_values = array();
		foreach ( $values as $key => $value ) {
			// Sanitize each setting based on its expected type
			// This is a simplified example; adjust sanitization methods as needed
			switch ( $key ) {
				case 'main_setting':
				case 'scan_schedule':
					$sanitized_values[ $key ] = sanitize_text_field( $value );
					break;
				case 'email_report':
					$sanitized_values[ $key ] = filter_var( $value, FILTER_VALIDATE_INT );
					break;
				case 'email_to':
					$sanitized_values[ $key ] = sanitize_text_field( $value );
					break;
			}
		}

		return $sanitized_values;
	}






	/**
	 * Ensure ActionScheduler is loaded (from our bundle). Premium only.
	 *
	 * @return bool True if AS is available.
	 */
	private static function ensure_action_scheduler_loaded() {
		if ( function_exists( 'as_schedule_single_action' ) && function_exists( 'as_has_scheduled_action' ) ) {
			return true;
		}
		$path = null;
		if ( defined( 'WF_SN_PLUGIN_DIR' ) ) {
			$p = WF_SN_PLUGIN_DIR . 'vendor/woocommerce/action-scheduler/action-scheduler.php';
			if ( file_exists( $p ) && is_readable( $p ) ) {
				$path = $p;
			}
		}
		if ( ! $path ) {
			$plugin_root = dirname( __DIR__, 2 );
			$p           = $plugin_root . '/vendor/woocommerce/action-scheduler/action-scheduler.php';
			if ( file_exists( $p ) && is_readable( $p ) ) {
				$path = $p;
			}
		}
		if ( $path ) {
			require_once $path;
			return function_exists( 'as_schedule_single_action' );
		}
		return false;
	}

	/**
	 * Trigger Action Scheduler queue runner if not already scheduled.
	 */
	private static function maybe_run_queue() {
		if ( self::ensure_action_scheduler_loaded() && function_exists( 'as_has_scheduled_action' ) && function_exists( 'as_enqueue_async_action' ) ) {
			if ( ! as_has_scheduled_action( 'action_scheduler_run_queue' ) ) {
				as_enqueue_async_action( 'action_scheduler_run_queue', array(), 'action-scheduler' );
			}
		}
	}

	/**
	 * Check if scheduled scanning is enabled in settings.
	 *
	 * @return bool
	 */
	private static function is_scheduling_enabled() {
		$main_setting = self::$options['main_setting'] ?? '0';
		return in_array( $main_setting, array( '1', '2', '3', '4' ), true );
	}

	/**
	 * Get next scheduled scanner run timestamp from active backend.
	 *
	 * @return array
	 */
	private static function get_scheduler_state() {
		$state = array(
			'timestamp' => 0,
			'backend'   => 'none',
		);

		if ( self::ensure_action_scheduler_loaded() && function_exists( 'as_next_scheduled_action' ) ) {
			$as_timestamp = as_next_scheduled_action( 'wf_sn_ss_run_scheduled_scan', array(), 'security-ninja' );
			if ( false !== $as_timestamp && 0 < (int) $as_timestamp ) {
				$state['timestamp'] = (int) $as_timestamp;
				$state['backend']   = 'action_scheduler';
				return $state;
			}
		}

		$wp_timestamp = wp_next_scheduled( WF_SN_SS_CRON );
		if ( false !== $wp_timestamp && 0 < (int) $wp_timestamp ) {
			$state['timestamp'] = (int) $wp_timestamp;
			$state['backend']   = 'wp_cron';
		}

		return $state;
	}

	/**
	 * register cron event
	 *
	 * @author  Lars Koudal
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Saturday, September 3rd, 2022.
	 * @version v1.0.1  Tuesday, May 21st, 2024.
	 * @access  public static
	 * @param   boolean $options    Default: false
	 * @return  void
	 */
	public static function setup_cron() {
		$main_setting = self::$options['main_setting'] ?? '0';
		$schedule     = self::$options['scan_schedule'] ?? 'twicedaily';
		$schedules     = wp_get_schedules();
		$is_enabled    = in_array( $main_setting, array( '1', '2', '3', '4' ), true );

		if ( ! isset( $schedules[ $schedule ]['interval'] ) ) {
			$schedule = 'twicedaily';
		}
		$interval = isset( $schedules[ $schedule ]['interval'] ) ? (int) $schedules[ $schedule ]['interval'] : ( 12 * HOUR_IN_SECONDS );

		$has_action_scheduler = self::ensure_action_scheduler_loaded() && function_exists( 'as_schedule_recurring_action' ) && function_exists( 'as_unschedule_all_actions' );

		if ( ! $is_enabled ) {
			wp_clear_scheduled_hook( WF_SN_SS_CRON );
			if ( $has_action_scheduler ) {
				as_unschedule_all_actions( 'wf_sn_ss_run_scheduled_scan', array(), 'security-ninja' );
			}
			return;
		}

		if ( $has_action_scheduler ) {
			// Action Scheduler is the primary backend. Remove any WP-Cron legacy event.
			wp_clear_scheduled_hook( WF_SN_SS_CRON );
			as_unschedule_all_actions( 'wf_sn_ss_run_scheduled_scan', array(), 'security-ninja' );
			as_schedule_recurring_action( time(), $interval, 'wf_sn_ss_run_scheduled_scan', array(), 'security-ninja' );
			return;
		}

		// Fallback for environments where Action Scheduler is not available.
		wp_clear_scheduled_hook( WF_SN_SS_CRON );
		wp_schedule_event( time(), $schedule, WF_SN_SS_CRON );
	}

	/**
	 * add additional cron intervals
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Tuesday, November 14th, 2023.
	 * @version v1.0.1  Saturday, November 18th, 2023.
	 * @access  static
	 * @param   mixed   $schedules
	 * @return  mixed
	 */
	public static function cron_intervals( $schedules ) {
		$schedules['monthly'] = array(
			'interval' => DAY_IN_SECONDS * 30,
			'display'  => esc_html__( 'Once Monthly', 'security-ninja' ),
		);
		return $schedules;
	}


	/**
	 * runs cron task
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Tuesday, November 14th, 2023.
	 * @access  static
	 * @return  void
	 */
	public static function do_cron_task_ajax() {
		$nonce = isset( $_POST['nonce'] ) ? sanitize_key( wp_unslash( $_POST['nonce'] ) ) : '';
		if ( ! wp_verify_nonce( $nonce, 'secnin_scheduled_scanner' ) ) {
			wp_send_json_error( __( 'Nonce verification failed', 'security-ninja' ), 403 );
		}

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( __( 'You do not have sufficient permissions to access this page. task_ajax', 'security-ninja' ), 403 );
		}

		if ( self::ensure_action_scheduler_loaded() && function_exists( 'as_schedule_single_action' ) ) {
			$action_id = as_schedule_single_action( time(), 'wf_sn_ss_run_scheduled_scan', array(), 'security-ninja' );
			if ( $action_id ) {
				self::maybe_run_queue();
				wp_send_json_success(
					array(
						'message' => __( 'Scan queued. You will receive the report by email when it finishes.', 'security-ninja' ),
						'note'    => __( 'Runs in background; no need to keep this page open.', 'security-ninja' ),
					)
				);
				return;
			}
		}

		// Fallback only when scheduling failed (e.g. AS DB issue).
		self::do_cron_task();
		wp_send_json_success(
			array(
				'message' => __( 'Scan completed successfully (fallback mode).', 'security-ninja' ),
				'note'    => __( 'Action Scheduler was unavailable, so the scan ran immediately in this request.', 'security-ninja' ),
			)
		);
	}


	/**
	 * core cron function
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Tuesday, November 14th, 2023.
	 * @version v1.0.1  Enhanced email notifications with detailed change information
	 * @access  static
	 * @return  void
	 */
	public static function do_cron_task() {
		global $wpdb;
		// $options           = get_option( WF_SN_SS_OPTIONS_KEY );
		$sn_change         = 0;
		$cs_change         = 0;
		$ms_change         = 0;
		$sn_change_details = array();
		$cs_change_details = array();
		$sn_results        = 0;
		$cs_results        = 0;
		$ms_results        = null;
		$start_time        = microtime( true );

		$old = $wpdb->get_row( 'SELECT * FROM ' . $wpdb->prefix . WF_SN_SS_TABLE . ' ORDER BY id DESC LIMIT 1' );

		if ( '1' === self::$options['main_setting'] || '3' === self::$options['main_setting'] || '4' === self::$options['main_setting'] ) {

			$sn_results = wf_sn::run_all_tests( true );

			if ( ! $old ) {
				$sn_change = 1;
			} else {
				$old_sn_results = maybe_unserialize( $old->sn_results );

				if ( $sn_results && isset( $sn_results['test'] ) && is_array( $sn_results['test'] ) ) {

					foreach ( $sn_results['test'] as $snname => $testresult ) {
						if ( is_array( $testresult ) && isset( $old_sn_results['test'][ $snname ] ) ) {
							$old_testresult = $old_sn_results['test'][ $snname ];
							if ( $testresult !== $old_testresult ) {
								$changes          = array();
								$changes['title'] = $testresult['title'];

								// Always set status information for comparison
								$changes['status_new'] = isset( $testresult['status'] ) ? $testresult['status'] : 'unknown';
								$changes['status_old'] = isset( $old_testresult['status'] ) ? $old_testresult['status'] : 'unknown';

								if ( $changes['status_new'] !== $changes['status_old'] ) {
									// Higher status numbers are better (0 = Failed, 5 = Warning, 10 = OK).
									if ( is_numeric( $changes['status_new'] ) && is_numeric( $changes['status_old'] ) ) {
										if ( (int) $changes['status_new'] > (int) $changes['status_old'] ) {
											$changes['progress'] = 'good';
										} else {
											$changes['progress'] = 'bad';
										}
									}
								}

								// Set a default score if not set in old results
								$old_score = isset( $old_testresult['score'] ) ? $old_testresult['score'] : '';
								if ( isset( $testresult['score'] ) && $testresult['score'] !== $old_score ) {
									$changes['score_new'] = $testresult['score'];
									$changes['score_old'] = $old_score;
								}

								$changes['msg']               = $testresult['msg'];
								$sn_change                    = 1;
								$sn_change_details[ $snname ] = $changes;
							}
						}
					}

					// @todo - figure out WHAT exactly has changed and SHOW IT :-)
				}
			}
		}

		// Core Scanner
		if ( '2' === self::$options['main_setting'] || '3' === self::$options['main_setting'] || '4' === self::$options['main_setting'] ) {
			$cs_results = \WPSecurityNinja\Plugin\Wf_Sn_Cs::scan_files( true );
			// Immediately handle the case where $cs_results is '0'
			if ( is_array( $cs_results ) ) {
				$keys_to_check   = array( 'missing_ok', 'changed_bad', 'missing_bad', 'changed_bad', 'unknown', 'ok' );
				$change_detected = false;

				foreach ( $keys_to_check as $key ) {
					if ( ! empty( $cs_results[ $key ] ) ) {
						$change_detected = true;
						break;
					}
				}

				if ( $change_detected ) {
					$cs_change = 1;
				} elseif ( ! $old ) {
					$cs_change = 1;
				} else {
					$old_cs_results = maybe_unserialize( $old->cs_results );
					if ( is_array( $old_cs_results ) ) {
						unset( $old_cs_results['last_run'], $old_cs_results['run_time'] );
					}
					if ( is_array( $cs_results ) ) {
						unset( $cs_results['last_run'], $cs_results['run_time'] );
					}
					if ( $cs_results !== $old_cs_results ) {
						$cs_change = 1;
					}
				}
			} else {
				// Handle the case where $cs_results is not an array
				$cs_change = 0; // Assuming non-array $cs_results means no changes or an error
			}
		}

		// Malware Scanner (when "all" is selected)
		if ( '4' === self::$options['main_setting'] && class_exists( '\WPSecurityNinja\Plugin\Wf_Sn_Ms' ) ) {
			$ms_results = \WPSecurityNinja\Plugin\Wf_Sn_Ms::run_malware_scan_for_scheduler();
			if ( ! $old || ! isset( $old->ms_results ) ) {
				$ms_change = 1;
			} else {
				$old_ms    = maybe_unserialize( $old->ms_results );
				$old_count = is_array( $old_ms ) && isset( $old_ms['malware_identified'] ) ? count( (array) $old_ms['malware_identified'] ) : 0;
				$new_count = is_array( $ms_results ) && isset( $ms_results['malware_identified'] ) ? count( (array) $ms_results['malware_identified'] ) : 0;
				if ( $old_count !== $new_count ) {
					$ms_change = 1;
				}
			}
		}

		// write results in database
		$date = current_time( 'mysql' );

		$cols           = $wpdb->get_col( 'SHOW COLUMNS FROM `' . $wpdb->prefix . WF_SN_SS_TABLE . '`' );
		$has_ms_columns = $cols && in_array( 'ms_results', $cols, true );

		// If table exists but lacks Malware columns (e.g. created before ms_results was added), add them now.
		if ( ! empty( $cols ) && ! $has_ms_columns ) {
			$table = $wpdb->prefix . WF_SN_SS_TABLE;
			$wpdb->query( "ALTER TABLE `{$table}` ADD COLUMN ms_results longtext, ADD COLUMN ms_change tinyint(4) NOT NULL DEFAULT 0" );
			$has_ms_columns = true;
		}

		if ( $has_ms_columns ) {
			$wpdb->query(
				$wpdb->prepare(
					'INSERT INTO ' . $wpdb->prefix . WF_SN_SS_TABLE .
						' (runtime, timestamp, sn_results, cs_results, sn_change, cs_change, ms_results, ms_change)
					VALUES (%s, %s, %s, %s, %s, %s, %s, %s)',
					microtime( true ) - $start_time,
					$date,
					serialize( $sn_results ),
					serialize( $cs_results ),
					$sn_change,
					$cs_change,
					null === $ms_results ? null : serialize( $ms_results ),
					$ms_change
				)
			);
		} else {
			$wpdb->query(
				$wpdb->prepare(
					'INSERT INTO ' . $wpdb->prefix . WF_SN_SS_TABLE .
						' (runtime, timestamp, sn_results, cs_results, sn_change, cs_change)
					VALUES (%s, %s, %s, %s, %s, %s)',
					microtime( true ) - $start_time,
					$date,
					serialize( $sn_results ),
					serialize( $cs_results ),
					$sn_change,
					$cs_change
				)
			);
		}

		// send report email
		if ( self::$options['email_report'] && ( 0 !== (int) self::$options['email_report'] ) ) {
			// Only send email if either:
			// - email_report is 1 (send every time) OR
			// - email_report is 2 (send only on changes) AND changes were detected
			if ( 1 === (int) self::$options['email_report'] ||
				( 2 === (int) self::$options['email_report'] && ( 1 === (int) $sn_change || 1 === (int) $cs_change || ( '4' === self::$options['main_setting'] && $ms_results && ! empty( $ms_results['malware_identified'] ) ) ) ) ) {

				$subject = esc_html__( 'Security status for', 'security-ninja' ) . ' ' . esc_url( get_home_url() ) . ' ' . esc_html__( 'on', 'security-ninja' ) . ' ' . wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) );

				$body = '<p>' . esc_html__( 'The scan ran at', 'security-ninja' ) . ' ' . wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) ) . ' ' . esc_html__( 'for', 'security-ninja' ) . ' ' . esc_html( round( microtime( true ) - $start_time, 1 ) ) . ' ' . esc_html__( 'sec', 'security-ninja' ) . '</p>';

				if ( ! $sn_results ) {
					$body .= "\r\n";
					$body .= '<h2>' . esc_html__( 'Security Testing Results', 'security-ninja' ) . '</h2>';
					$body .= '<p>' . esc_html__( 'Tests were not run.', 'security-ninja' ) . '</p>';
				} elseif ( $sn_change ) {
					$body .= "\r\n";
					$body .= '<h2>' . esc_html__( 'Security Testing Results', 'security-ninja' ) . '</h2>';
					$body .= '<p>' . esc_html__( 'Results have changed since last scan.', 'security-ninja' ) . '</p>';

					if ( is_array( $sn_change_details ) ) {
						$body .= '<h3>' . esc_html__( 'Detailed Changes:', 'security-ninja' ) . '</h3>';
						$body .= '<div style="margin-bottom: 20px;">';

						foreach ( $sn_change_details as $test_name => $scd ) {
							$body .= '<div style="border: 1px solid #ddd; margin-bottom: 15px; padding: 15px; border-radius: 5px;">';
							$body .= '<h4 style="margin-top: 0; color: #333;">' . esc_html( $scd['title'] ) . '</h4>';

							// Show status change if available
							if ( isset( $scd['status_old'] ) && isset( $scd['status_new'] ) ) {
								$status_old_text = self::get_status_text( $scd['status_old'] );
								$status_new_text = self::get_status_text( $scd['status_new'] );
								$status_color    = ( isset( $scd['progress'] ) && $scd['progress'] === 'good' ) ? '#28a745' : '#dc3545';

								$body .= '<p><strong>' . esc_html__( 'Status Change:', 'security-ninja' ) . '</strong> ';
								$body .= '<span style="color: #666;">' . esc_html( $status_old_text ) . '</span> ';
								$body .= '<span style="color: #999;">→</span> ';
								$body .= '<span style="color: ' . esc_attr( $status_color ) . '; font-weight: bold;">' . esc_html( $status_new_text ) . '</span></p>';

								// Add explanation of what the change means
								$body .= '<p><em>' . esc_html( self::get_status_change_explanation( $scd['status_old'], $scd['status_new'] ) ) . '</em></p>';
							}

							// Show score change if available
							if ( isset( $scd['score_old'] ) && isset( $scd['score_new'] ) ) {
								$body .= '<p><strong>' . esc_html__( 'Score Change:', 'security-ninja' ) . '</strong> ';
								$body .= '<span style="color: #666;">' . esc_html( $scd['score_old'] ) . '</span> ';
								$body .= '<span style="color: #999;">→</span> ';
								$body .= '<span style="color: #333;">' . esc_html( $scd['score_new'] ) . '</span></p>';
							}

							// Show current result message
							$body .= '<p><strong>' . esc_html__( 'Current Result:', 'security-ninja' ) . '</strong> ' . esc_html( $scd['msg'] ) . '</p>';

							$body .= '</div>';
						}

						$body .= '</div>';
					}
				} else {
					$body .= '<h2>' . esc_html__( 'Security Testing Results', 'security-ninja' ) . '</h2>';
					$body .= '<p>' . esc_html__( 'No changes since last scan.', 'security-ninja' ) . '</p>';
				}
				if ( ! $cs_results ) {
					$body .= '<h2>' . esc_html__( 'Core Scanner results', 'security-ninja' ) . '</h2>';
					$body .= '<p>' . esc_html__( 'Test were not run.', 'security-ninja' ) . '</p>';
				} elseif ( $cs_change ) {
					$body .= '<h2>' . esc_html__( 'Core Scanner Results', 'security-ninja' ) . '</h2>';
					$body .= '<p>' . esc_html__( 'Results have changed since last scan.', 'security-ninja' ) . '</p>';

					// Format changed files section
					if ( ! empty( $cs_results['changed_bad'] ) ) {
						$body .= '<h3 style="color: #d63638;">' . esc_html__( 'Modified Core Files', 'security-ninja' ) . '</h3>';
						$body .= '<p>' . esc_html__( 'The following WordPress core files have been modified:', 'security-ninja' ) . '</p>';
						$body .= '<ul style="list-style-type: disc; margin-left: 20px;">';
						foreach ( $cs_results['changed_bad'] as $file ) {
							$body .= '<li>' . esc_html( $file ) . '</li>';
						}
						$body .= '</ul>';
					}

					// Format unknown files section
					if ( ! empty( $cs_results['unknown_bad'] ) ) {
						$body .= '<h3 style="color: #d63638;">' . esc_html__( 'Unknown files in core, site root, or hidden core files', 'security-ninja' ) . '</h3>';
						$body .= '<p>' . esc_html__( 'The following files are not part of the official WordPress package for your version:', 'security-ninja' ) . '</p>';
						$body .= '<ul style="list-style-type: disc; margin-left: 20px;">';
						foreach ( $cs_results['unknown_bad'] as $file ) {
							$body .= '<li>' . esc_html( $file ) . '</li>';
						}
						$body .= '</ul>';
					}

					// Format missing files section
					if ( ! empty( $cs_results['missing_bad'] ) ) {
						$body .= '<h3 style="color: #d63638;">' . esc_html__( 'Missing Core Files', 'security-ninja' ) . '</h3>';
						$body .= '<p>' . esc_html__( 'The following essential WordPress core files are missing:', 'security-ninja' ) . '</p>';
						$body .= '<ul style="list-style-type: disc; margin-left: 20px;">';
						foreach ( $cs_results['missing_bad'] as $file ) {
							$body .= '<li>' . esc_html( $file ) . '</li>';
						}
						$body .= '</ul>';
					}

					// Add summary statistics
					$body    .= '<p><strong>' . esc_html__( 'Scan Summary:', 'security-ninja' ) . '</strong></p>';
					$body    .= '<ul style="list-style-type: none; margin-left: 0;">';
					$body    .= '<li>' . esc_html__( 'Total files scanned:', 'security-ninja' ) . ' ' . esc_html( $cs_results['total'] ) . '</li>';
					$run_time = isset( $cs_results['run_time'] ) ? (float) $cs_results['run_time'] : 0;
					$body    .= '<li>' . esc_html__( 'Scan duration:', 'security-ninja' ) . ' ' . esc_html( round( $run_time, 2 ) ) . ' ' . esc_html__( 'seconds', 'security-ninja' ) . '</li>';
					$body    .= '</ul>';

				} else {
					$body .= '<h3>' . esc_html__( 'Core Scanner results', 'security-ninja' ) . '</h3>';
					$body .= '<p>' . esc_html__( 'No changes since last scan.', 'security-ninja' ) . '</p>';
				}
				// Malware Scanner section (when "all" is selected)
				if ( '4' === self::$options['main_setting'] ) {
					$body .= '<h2>' . esc_html__( 'Malware Scanner', 'security-ninja' ) . '</h2>';
					$issue_count = 0;
					if ( class_exists( '\WPSecurityNinja\Plugin\Wf_Sn_Ms' ) ) {
						$issue_count = (int) \WPSecurityNinja\Plugin\Wf_Sn_Ms::count_suspicious_files();
					} elseif ( is_array( $ms_results ) && ! empty( $ms_results['malware_identified'] ) ) {
						$issue_count = (int) $ms_results['malware_identified'];
					}
					if ( $issue_count > 0 ) {
						$body .= '<p>' . sprintf(
							esc_html__( '%1$s issue(s) detected (review recommended). Open the Malware Scanner tab for details.', 'security-ninja' ),
							number_format_i18n( $issue_count )
						) . '</p>';
						$integrity = class_exists( '\WPSecurityNinja\Plugin\Wf_Sn_Ms' )
							? \WPSecurityNinja\Plugin\Wf_Sn_Ms::get_results( 'do_integrity_scan' )
							: array();
						if ( is_array( $integrity ) && ! empty( $integrity['suspicious_plugins'] ) && is_array( $integrity['suspicious_plugins'] ) ) {
							$body .= '<h3 style="color: #d63638;">' . esc_html__( 'Suspicious plugin / theme folders (review recommended)', 'security-ninja' ) . '</h3><ul style="list-style-type: disc; margin-left: 20px;">';
							foreach ( $integrity['suspicious_plugins'] as $entry ) {
								$name = isset( $entry['Name'] ) ? $entry['Name'] : '';
								$path = isset( $entry['Path'] ) ? str_replace( ABSPATH, '', $entry['Path'] ) : '';
								$body .= '<li>' . esc_html( trim( $name . ( $path ? ' (' . $path . ')' : '' ) ) ) . '</li>';
							}
							$body .= '</ul>';
						}
						$body .= '<p><a href="' . esc_url( admin_url( 'admin.php?page=wf-sn#sn_malware' ) ) . '">' . esc_html__( 'Open Malware Scanner', 'security-ninja' ) . '</a></p>';
					} else {
						$body .= '<p>' . esc_html__( 'No issues detected.', 'security-ninja' ) . '</p>';
					}
				}
				$body .= "\r\n";

				$dashboardlink       = admin_url( '?page=wf-sn' );
				$dashboardlinkanchor = esc_html__( 'Security Ninja Dashboard', 'security-ninja' );

				$emailintrotext = '<h3>' . esc_html__( 'Report from a scheduled scan of your website.', 'security-ninja' ) . '</h3>';

				$emailtitle = $subject;

				$body .= '<p>';
				$body .= esc_html__( 'See details here:', 'security-ninja' ) . ' <a href="' . admin_url( 'admin.php?page=wf-sn#sn_schedule' ) . '" target="_blank">' . admin_url( 'admin.php?page=wf-sn#sn_schedule' ) . '</a></p>';
				$body .= '<p>' . esc_html__( 'Plugin version:', 'security-ninja' ) . ' ' . esc_html( \WPSecurityNinja\Plugin\Utils::get_plugin_version() ) . '</p>';

				$my_replacements = array(
					'%%emailintrotext%%'      => $emailintrotext,
					'%%websitedomain%%'       => site_url(),
					'%%dashboardlink%%'       => $dashboardlink,
					'%%dashboardlinkanchor%%' => $dashboardlinkanchor,
					'%%secninlogourl%%'       => esc_url( WF_SN_PLUGIN_URL . 'images/security-ninja-logo.png' ),
					'%%emailtitle%%'          => $emailtitle,
					'%%sentfromtext%%'        => sprintf(
						__( 'This email was sent by %1$s from %2$s', 'security-ninja' ),
						esc_html( 'WP Security Ninja' ),
						esc_url( \WPSecurityNinja\Plugin\wf_sn_cf::url_to_domain( site_url() ) )
					),
					'%%emailcontent%%'        => nl2br( $body ),
				);

				// inserts the whitelabel name and logo
				if ( class_exists( __NAMESPACE__ . '\Wf_Sn_Wl' ) ) {
					if ( \WPSecurityNinja\Plugin\Wf_Sn_Wl::is_active() ) {
						$pluginname                           = \WPSecurityNinja\Plugin\Wf_Sn_Wl::get_new_name();
						$my_replacements['%%secninlogourl%%'] = esc_url( \WPSecurityNinja\Plugin\Wf_Sn_Wl::get_email_logo_url() );
						$my_replacements['%%sentfromtext%%']  = sprintf(
							esc_html__( 'This email was sent by %1$s from %2$s', 'security-ninja' ),
							esc_html( $pluginname ),
							esc_url( \WPSecurityNinja\Plugin\wf_sn_cf::url_to_domain( site_url() ) )
						);
					}
				}

				$template_path = WF_SN_PLUGIN_DIR . 'includes/email-default.php';

						// Use secure file reading with validation
				$html = Wf_Sn_Security_Utils::secure_file_get_contents(
					$template_path,
					array(
						'check_readable'     => true,
						'check_size'         => true,
						'max_size'           => 1024 * 1024, // 1MB for templates
						'check_type'         => true,
						'allowed_extensions' => array( 'php' ),
						'check_mime'         => false, // Skip MIME check for performance
						'check_path'         => true,
						'allowed_dirs'       => array( WF_SN_PLUGIN_DIR ),
					)
				);

				if ( $html === false ) {
					// Handle error - template not found
					$html = '<p>Email template not found.</p>';
				}

				foreach ( $my_replacements as $needle => $replacement ) {
					$html = str_replace( $needle, $replacement, $html );
				}

				$headers = array( 'Content-Type: text/html; charset=UTF-8' );

				$email_addresses = explode( ',', self::$options['email_to'] );
				$valid_emails    = array();

				foreach ( $email_addresses as $email ) {
					$email = trim( $email );
					if ( filter_var( $email, FILTER_VALIDATE_EMAIL ) ) {
						$valid_emails[] = $email;
					} else {
						// Log invalid email
						\WPSecurityNinja\Plugin\wf_sn_el_modules::log_event(
							'security_ninja',
							'scheduled_scanner',
							__( 'Invalid email address skipped', 'security-ninja' ),
							array(
								'email' => $email,
							)
						);
					}
				}
				// Send email to each valid recipient
				foreach ( $valid_emails as $email ) {
					$send_result = wp_mail( $email, $subject, $html, $headers );

					// Log the email sending result
					\WPSecurityNinja\Plugin\wf_sn_el_modules::log_event(
						'security_ninja',
						'scheduled_scanner',
						__( 'Sent email notification', 'security-ninja' ),
						array(
							'recipient' => $email,
							'result'    => $send_result ? __( 'Success', 'security-ninja' ) : __( 'Failure', 'security-ninja' ),
						)
					);
				}

				// Check if there were no valid emails
				if ( empty( $valid_emails ) ) {
					\WPSecurityNinja\Plugin\wf_sn_el_modules::log_event(
						'security_ninja',
						'scheduled_scanner',
						__( 'Email notification failed', 'security-ninja' ),
						array(
							'reason' => __( 'No valid email addresses provided', 'security-ninja' ),
						)
					);
				}
			} else {
				// Log that an email was not sent due to no changes detected
				\WPSecurityNinja\Plugin\wf_sn_el_modules::log_event(
					'security_ninja',
					'scheduled_scanner',
					__( 'Email notification skipped', 'security-ninja' ),
					array(
						'reason' => __( 'No changes detected and email_report set to only send on changes', 'security-ninja' ),
					)
				);
			}
		}

		do_action( 'security_ninja_scheduled_scanner_done_cron', microtime( true ) - $start_time );
	}





	/**
	 * all settings are saved in one option key
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Tuesday, November 14th, 2023.
	 * @access  static
	 * @return  void
	 */
	public static function register_settings() {
		register_setting(
			WF_SN_SS_OPTIONS_KEY,
			WF_SN_SS_OPTIONS_KEY,
			function ( $value ) {
				return self::handle_settings_change( $value );
			}
		);
	}

	/**
	 * Handle the changes in the settings.
	 * Only update the cron job setup if settings have actually changed.
	 *
	 * @param array $new_settings New settings values.
	 * @return array Returns the sanitized settings.
	 */
	public static function handle_settings_change( $new_settings ) {
		$old_settings = get_option( WF_SN_SS_OPTIONS_KEY, array() );
		if ( $new_settings !== $old_settings ) {
			self::$options = $new_settings;
			self::setup_cron();
		}
		return self::sanitize_settings( $new_settings );
	}


	/**
	 * display results
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Tuesday, November 14th, 2023.
	 * @access  static
	 * @return  void
	 */
	public static function schedule_page() {
		$main_settings   = array();
		$main_settings[] = array(
			'val'   => '0',
			'label' => esc_html__( 'Disable scheduled scans', 'security-ninja' ),
		);
		$main_settings[] = array(
			'val'   => '1',
			'label' => esc_html__( 'Enable scheduled scans only for Security Testing', 'security-ninja' ),
		);
		$main_settings[] = array(
			'val'   => '2',
			'label' => esc_html__( 'Enable scheduled scans only for Core Scanner', 'security-ninja' ),
		);
		$main_settings[] = array(
			'val'   => '3',
			'label' => esc_html__( 'Enable scheduled scans for both', 'security-ninja' ),
		);
		$main_settings[] = array(
			'val'   => '4',
			'label' => esc_html__( 'Enable scheduled scans for all (Security Tests, Core Scanner, Malware Scanner)', 'security-ninja' ),
		);

		$scan_schedule = array();
		$tmp           = wp_get_schedules();
		foreach ( $tmp as $name => $details ) {
			if ( 'twicedaily' === $name ) {
				$scan_schedule[] = array(
					'val'   => $name,
					'label' => $details['display'] . ' ' . esc_html__( '(recommended)', 'security-ninja' ),
				);
			} else {
				$scan_schedule[] = array(
					'val'   => $name,
					'label' => $details['display'],
				);
			}
		}

		$email_reports   = array();
		$email_reports[] = array(
			'val'   => 0,
			'label' => esc_html__( 'Never send any emails', 'security-ninja' ),
		);
		$email_reports[] = array(
			'val'   => 1,
			'label' => esc_html__( 'Send an email each time the tests run', 'security-ninja' ),
		);
		$email_reports[] = array(
			'val'   => 2,
			'label' => esc_html__( 'Send an email only when test results change', 'security-ninja' ),
		);

		echo '<div class="submit-test-container"><div class="sncard settings-card">';

		echo '<h2><span class="dashicons dashicons-info-outline"></span> ' . esc_html__( 'Scheduled Scanner', 'security-ninja' ) . '</h2>';
		echo '<p>' . esc_html__( 'Schedule scans of your website to detect changes in the core files and security tests.', 'security-ninja' ) . '</p>';

		$is_enabled      = self::is_scheduling_enabled();
		$scheduler_state = self::get_scheduler_state();
		$timestamp       = (int) $scheduler_state['timestamp'];
		$backend_label   = 'wp_cron' === $scheduler_state['backend'] ? 'wf_sn_ss_cron' : 'wf_sn_ss_run_scheduled_scan';

		if ( $is_enabled ) {
			$tmp            = wp_get_schedules();
			$schedule_name  = self::$options['scan_schedule'] ?? 'twicedaily';
			$schedule_label = isset( $tmp[ $schedule_name ]['display'] ) ? $tmp[ $schedule_name ]['display'] : esc_html__( 'Twice Daily', 'security-ninja' );

			if ( $timestamp > 0 ) {
				$tmp = '<p class="sn-ss-nochange sncard noerrorsfound">' . esc_html__( 'Scheduled scans are enabled and will run', 'security-ninja' ) . ' ' . strtolower( $schedule_label ) . '</p>';
			} else {
				$tmp = '<p class="sn-ss-change sncard errorsfound">' . esc_html__( 'Scheduled scans are enabled, but no scheduled run is currently set.', 'security-ninja' ) . '</p>';
			}
		} else {
			$tmp = '<span class="sn-ss-change">' . esc_html__( 'Scheduled scans are disabled', 'security-ninja' ) . '</span>';
		}

		echo '<form action="options.php" method="post">';
		settings_fields( 'wf_sn_ss' );

		echo '<p class="ss_header">' . wp_kses_post( $tmp ) . '</p>';
		?>
		<table class="form-table">
			<tbody>
				<?php
				echo '<tr valign="top">
		<th scope="row"><label for="main_setting"><h3>' . esc_html__( 'Scan Settings', 'security-ninja' ) . '</h3>';

				echo '<p class="description">' . esc_html__( 'Depending on the add-ons that are active you can choose to include them in scheduled scans or not.', 'security-ninja' ) . '</p>';
				echo '<p class="description">' . esc_html__( 'To include Malware Scanner in scheduled scans, choose "Enable scheduled scans for all".', 'security-ninja' ) . '</p>';

				echo '</label></th>
		<td class="sn-cf-options"><select id="main_setting" name="wf_sn_ss[main_setting]" class="regular-text">';
				\WPSecurityNinja\Plugin\Utils::create_select_options( $main_settings, self::$options['main_setting'] );
				echo '</select>';

				echo '</td></tr>';

				echo '<tr valign="top">
		<th scope="row"><label for="scan_schedule"><h3>' . esc_html__( 'Scan Schedule', 'security-ninja' ) . '</h3>';

				echo '<p class="description">' . esc_html__( "Running the scan once a day will ensure you get a prompt notice of any problems and at the same time don't overload the server.", 'security-ninja' ) . '</p>';

				echo '</label></th>
		<td class="sn-cf-options"><select id="scan_schedule" name="wf_sn_ss[scan_schedule]" class="regular-text">';
				\WPSecurityNinja\Plugin\Utils::create_select_options( $scan_schedule, self::$options['scan_schedule'] );
				echo '</select>';

				echo '</td></tr>';

				echo '<tr valign="top">
		<th scope="row"><label for="email_report"><h3>' . esc_html__( 'Email Report', 'security-ninja' ) . '</h3>';

				echo '<p class="description">' . esc_html__( 'Depending on the amount of email you like to receive you can get reports for all scans or just ones when results change.', 'security-ninja' ) . '</p>';

				echo '</label></th>
		<td class="sn-cf-options"><select id="email_report" name="wf_sn_ss[email_report]" class="regular-text">';
				\WPSecurityNinja\Plugin\Utils::create_select_options( $email_reports, self::$options['email_report'] );
				echo '</select>';

				echo '</td></tr>';

				echo '<tr valign="top">
			<th scope="row" colspan="2"><label for="email_to"><h3>' . esc_html__( 'Email Recipient', 'security-ninja' ) . '</h3>';

				echo '<p class="description">' . esc_html__( "Email address of the person (usually the site admin) who'll receive the email reports.", 'security-ninja' ) . '</p>';
				echo '<p class="description">' . esc_html__( 'Separate multiple recipients with a comma ","', 'security-ninja' ) . '</p>';

				echo '</label></th></tr>';

				echo '<tr valign="top">
		<td class="fullwidth" colspan="2"><input type="text" class="regular-text" id="email_to" name="wf_sn_ss[email_to]" value="' . esc_attr( self::$options['email_to'] ) . '" />';

				echo '</td></tr>';

				echo '<tr valign="top"><td colspan="2" style="padding:0px;">';

				echo '<p class="submit"><input type="submit" value="' . esc_attr__( 'Save Changes', 'security-ninja' ) . '" class="input-button button-primary" name="Submit"></p>';

				echo '<p class="submit"><input type="button" value="' . esc_attr__( 'Test Settings (run scan)', 'security-ninja' ) . '" class="button input-button gray button-secondary" id="sn-ss-test"></p>';

				echo '<div id="wf-ss-output" class="sn-ss-test-output" style="display:none; margin-top:12px; padding:12px 14px; min-height:48px; background:#f0f6fc; border-radius:2px; max-width:560px;">';
				echo '<p class="sn-ss-test-placeholder" style="margin:0;font-size:13px;color:#1d2327;">' . esc_html__( 'Click "Test settings (run scan)" above. Results will appear here.', 'security-ninja' ) . '</p>';
				echo '</div>';

				?>
				</td>
				</tr>
		</table>
		</form>
		<?php

		if ( $timestamp > 0 ) {
			?>
			<div class="sncard settings-card">
			<?php
			$current_time    = current_time( 'timestamp' );
			$time_until_next = human_time_diff( $current_time, $timestamp );

			$next_scan_at = date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $timestamp );

			$current_server_time = date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $current_time );
			echo '<table>';
			echo '<tr><td>' . esc_html__( 'Server Time:', 'security-ninja' ) . '</td><td>' . esc_html( $current_server_time ) . '</td></tr>';

			echo '<tr><td>' . esc_html__( 'Next Scheduled Scan:', 'security-ninja' ) . ' (' . esc_html( $backend_label ) . ')</td><td>' . esc_html( $next_scan_at ) . ' (' . esc_html( $time_until_next ) . ' ' . esc_html__( 'From now', 'security-ninja' ) . ')</td></tr></table>';
			?>
			</div>
			<?php
		}

		echo '</div>';
		echo "<div class='sncard settings-card'>";

		self::log_list();
		?>
		<p><?php esc_html_e( 'Please read!', 'security-ninja' ); ?></p>
		<p><?php esc_html_e( 'WordPress cron function depends on site visitors to regularly run its tasks. If your site has very few visitors the tasks wont be run on a regular, predefined interval.', 'security-ninja' ); ?></p>
		<?php
		$url = \WPSecurityNinja\Plugin\Utils::generate_sn_web_link( 'schedule_page', '/docs/errors-and-debugging/how-wp-cron-works-in-wordpress/' );
		echo wp_kses(
			sprintf(
				__( 'See our <a href="%s" target="_blank">documentation</a> for tips on ensuring WordPress cron jobs run reliably, even on low-traffic sites.', 'security-ninja' ),
				esc_url( $url )
			),
			array(
				'a' => array(
					'href'   => array(),
					'target' => array(),
				),
				'p' => array(),
			)
		);
		?>
		<p><?php esc_html_e( "Please test the settings after changing them to ensure you're getting the emails and that the testing finish in a timely manner.", 'security-ninja' ); ?></p>
		</div>
		</div>
		<?php
	}











	/**
	 * log_list.
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, May 16th, 2022.
	 * @version v1.0.1  Saturday, September 3rd, 2022.
	 * @access  static
	 * @return  void
	 */
	public static function log_list() {
		// Early return if no valid premium license - prevents database errors during race condition
		$can_use_premium = false;
		if ( secnin_fs()->can_use_premium_code__premium_only() ) {
			$can_use_premium = true;
		}
		if ( ! $can_use_premium ) {
			echo '<h3>' . esc_html__( 'Scan log', 'security-ninja' ) . '</h3>';
			echo '<p>' . esc_html__( 'Premium feature - license required.', 'security-ninja' ) . '</p>';
			return;
		}

		global $wpdb;

		$logs = $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM ' . $wpdb->prefix . WF_SN_SS_TABLE . ' ORDER BY timestamp DESC LIMIT %d', WF_SN_SS_LOG_LIMIT ) );

		?>
		<h3><?php esc_html_e( 'Scan log', 'security-ninja' ); ?></h3>

		<table class="wp-list-table widefat striped" cellspacing="0" id="wf-sn-ss-log">
			<thead>
				<tr>
					<th id="header_time" class="column-primary"><?php esc_html_e( 'Timestamp', 'security-ninja' ); ?></th>
					<th id="header_runtime"><?php esc_html_e( 'Run time', 'security-ninja' ); ?></th>
					<th id="header_sn"><?php esc_html_e( 'Security Tests', 'security-ninja' ); ?></th>
					<th id="header_ss"><?php esc_html_e( 'Core Scanner', 'security-ninja' ); ?></th>
					<th id="header_ms"><?php esc_html_e( 'Malware Scanner', 'security-ninja' ); ?></th>
				</tr>
			</thead>
			<tbody></tbody>
		</table>
		<?php

		echo '<p><input type="button" value="' . esc_html__( 'Delete all log entries', 'security-ninja' ) . '" class="button button-secondary" id="wf-sn-ss-truncate-log"></p>';

		echo '<div id="sn-scheduled-scanner-results"></div>';
	}









	/**
	 * truncate scan log table
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, May 16th, 2022.
	 * @version v1.0.1  Wednesday, April 10th, 2024.
	 * @access  static
	 * @return  void
	 */
	public static function truncate_log() {
		$nonce = isset( $_POST['nonce'] ) ? sanitize_key( wp_unslash( $_POST['nonce'] ) ) : '';
		if ( ! wp_verify_nonce( $nonce, 'secnin_scheduled_scanner' ) ) {
			wp_send_json_error( 'Nonce verification failed', 403 );
		}
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( 'You do not have sufficient permissions to access this page. truncate', 403 );
		}
		global $wpdb;
		$wpdb->query( 'TRUNCATE TABLE ' . $wpdb->prefix . WF_SN_SS_TABLE );
		wp_send_json_success();
	}


	/**
	 * display dialog with SN test details
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, May 16th, 2022.
	 * @access  static
	 * @return  void
	 */
	public static function ajax_dialog_sn_details() {

		$nonce = isset( $_POST['nonce'] ) ? sanitize_key( wp_unslash( $_POST['nonce'] ) ) : '';
		if ( ! wp_verify_nonce( $nonce, 'secnin_scheduled_scanner' ) ) {
			wp_send_json_error( 'Nonce verification failed', 403 );
		}

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( 'You do not have sufficient permissions to access this page. sn_details', 403 );
		}
		global $wpdb;

		$id = isset( $_POST['row_id'] ) ? intval( $_POST['row_id'] ) : 0;

		$table_name = $wpdb->prefix . WF_SN_SS_TABLE; // Assuming WF_SN_SS_TABLE is a defined constant holding your table's suffix.
		$result     = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_name WHERE id = %d LIMIT 1", $id ) );

		if ( $result->sn_results && is_array( maybe_unserialize( $result->sn_results ) ) ) {
			echo '<table class="wp-list-table widefat" cellspacing="0" id="security-ninja">';
			echo '<thead><tr>';
			echo '<th class="sn-status">' . __( 'Status', 'security-ninja' ) . '</th>';
			echo '<th>' . __( 'Test description', 'security-ninja' ) . '</th>';
			echo '<th>' . __( 'Test results', 'security-ninja' ) . '</th>';
			echo '</tr></thead>';
			echo '<tbody>';

			$tmp = maybe_unserialize( $result->sn_results );

			foreach ( $tmp['test'] as $test_name => $details ) {
				echo '<tr>
				<td class="sn-status">' . wp_kses_post( \WPSecurityNinja\Plugin\Utils::status( $details['status'] ) ) . '</td>
				<td>' . esc_attr( $details['title'] ) . '</td>
				<td>' . esc_attr( $details['msg'] ) . '</td>
				</tr>';
			}

			echo '</tbody>';
			echo '<tfoot><tr>';
			echo '<th class="sn-status">' . esc_html__( 'Status', 'security-ninja' ) . '</th>';
			echo '<th>' . esc_html__( 'Test Description', 'security-ninja' ) . '</th>';
			echo '<th>' . esc_html__( 'Test Results', 'security-ninja' ) . '</th>';
			echo '<th>&nbsp;</th>';
			echo '</tr></tfoot>';
			echo '</table>';
		} else {
			echo esc_html__( 'Unknown Error.', 'security-ninja' );
		}

		die();
	}


	/**
	 * displays dialog with core scanner details
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, May 16th, 2022.
	 * @access  static
	 * @param   boolean $return         Default: false
	 * @param   boolean $hidebuttons    Default: false
	 * @return  mixed
	 */
	public static function ajax_dialog_cs_details( $return_output = false ) {
		global $wpdb;

		$nonce = isset( $_POST['nonce'] ) ? sanitize_key( wp_unslash( $_POST['nonce'] ) ) : '';
		if ( ! wp_verify_nonce( $nonce, 'secnin_scheduled_scanner' ) ) {
			wp_send_json_error( __( 'Nonce verification failed', 'security-ninja' ), 403 );
		}

		// Check if the user has the required capability
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( __( 'You do not have sufficient permissions to access this page. cs_details', 'security-ninja' ), 403 );
		}

		$output = '';

		if ( isset( $_POST['row_id'] ) ) {
			$id     = (int) $_POST['row_id'];
			$result = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}" . WF_SN_SS_TABLE . ' WHERE id = %d LIMIT 1', $id ) );
		} else {
			$result = $wpdb->get_row( 'SELECT * FROM ' . $wpdb->prefix . WF_SN_SS_TABLE . ' ORDER BY `id` DESC LIMIT 1;' );
		}

		if ( ( isset( $result->cs_results ) ) && ( is_array( maybe_unserialize( $result->cs_results ) ) ) ) {

			$results = maybe_unserialize( $result->cs_results );

			$output .= '<div style="margin: 20px">';
			if ( isset( $results['changed_bad'] ) && ! empty( $results['changed_bad'] ) ) {
				$output .= '<div class="sn-cs-changed-bad"><h4>' . esc_html__( 'The following WordPress core files have been modified', 'security-ninja' ) . '</h4><p>' . esc_html__( 'If you did not modify the following files, you should review them to make sure no malicious code is there.', 'security-ninja' ) . '</p>';
				$output .= \WPSecurityNinja\Plugin\Wf_Sn_Cs::list_files( $results['changed_bad'], false, false );
				$output .= '</div>';
			}

			if ( isset( $results['unknown_bad'] ) && ! empty( $results['unknown_bad'] ) ) {
				$output .= '<div class="sn-cs-changed-bad"><h4>' . esc_html__( 'Following files are unknown and should not be in your core folders', 'security-ninja' ) . '</h4><p>' . esc_html__( 'These are files not included with WordPress default installation and should not be in your core WordPress folders.', 'security-ninja' ) . '</p>';
				if ( $return_output ) {
					$output .= \WPSecurityNinja\Plugin\Wf_Sn_Cs::list_files( $results['unknown_bad'], false, false, false );
				} else {
					$output .= \WPSecurityNinja\Plugin\Wf_Sn_Cs::list_files( $results['unknown_bad'], true, false, true );
				}
				$output .= '</div>';
			}

			if ( isset( $results['missing_bad'] ) && ! empty( $results['missing_bad'] ) ) {
				$output .= '<div class="sn-cs-missing-bad">';
				$output .= '<h4>' . esc_html__( 'Following core files are missing and they should not be.', 'security-ninja' ) . '</h4>';
				$output .= '<p>' . esc_html__( 'Missing core files my indicate a bad auto-update or they simply were not copied on the server when the site was setup.', 'security-ninja' ) . '<br>' . esc_html__( 'If there is no legitimate reason for the files to be missing use the restore action to create them.', 'security-ninja' ) . '</p>';
				if ( $return_output ) {
					$output .= \WPSecurityNinja\Plugin\Wf_Sn_Cs::list_files( $results['missing_bad'], false, false, false );
				} else {
					$output .= \WPSecurityNinja\Plugin\Wf_Sn_Cs::list_files( $results['missing_bad'], false, false, false );
				}
				$output .= '</div>';
			}

			if ( isset( $results['missing_ok'] ) && ! empty( $results['missing_ok'] ) ) {
				$output .= '<div class="sn-cs-missing-ok">';
				$output .= '<h4>' . esc_html__( 'The following core files are missing but are not essential for WordPress functionality:', 'security-ninja' ) . '</h4>';
				$output .= '<p>' . esc_html__( 'These files are part of the WordPress core but are not necessary for the basic operation of your site. Their absence does not indicate a security issue.', 'security-ninja' ) . '</p>';
				if ( $return_output ) {
					$output .= \WPSecurityNinja\Plugin\Wf_Sn_Cs::list_files( $results['missing_ok'], false, false, false );
				} else {
					$output .= \WPSecurityNinja\Plugin\Wf_Sn_Cs::list_files( $results['missing_ok'], false, false, false );
				}
				$output .= '</div>';
			}

			if ( 0 === intval( $result->cs_change ) ) {
				$output .= '<div class="sn-cs-ok">';
				$total   = isset( $results['total'] ) ? (int) $results['total'] : 0;
				$output .= '<h4>' . sprintf(
					__( 'A total of <span class="sn_count">%1$s</span> files were scanned and <span class="sn_count">%2$s</span> are unmodified and safe.', 'security-ninja' ),
					number_format_i18n( $total ),
					number_format_i18n( $total )
				) . '</h4>';
				$output .= '</div>';
			}
		} else {
			$output .= '<p>' . sprintf(
				__( 'Problem loading Core Scanner Results - %s', 'security-ninja' ),
				esc_html__( 'Undocumented error.', 'security-ninja' )
			) . '</p>';
		}

		$output .= '</div>';

		if ( $return_output ) {
			return $output;
		}
		echo wp_kses_post( $output );
		wp_die();
	}


	/**
	 * Display dialog with Malware Scanner details for a scheduled run.
	 *
	 * @since   v1.0.0
	 * @access  static
	 * @return  void
	 */
	public static function ajax_dialog_ms_details() {
		$nonce = isset( $_POST['nonce'] ) ? sanitize_key( wp_unslash( $_POST['nonce'] ) ) : '';
		if ( ! wp_verify_nonce( $nonce, 'secnin_scheduled_scanner' ) ) {
			wp_send_json_error( __( 'Nonce verification failed', 'security-ninja' ), 403 );
		}

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( __( 'You do not have sufficient permissions to access this page. ms_details', 'security-ninja' ), 403 );
		}

		global $wpdb;

		$id = isset( $_POST['row_id'] ) ? (int) $_POST['row_id'] : 0;
		if ( ! $id ) {
			echo '<p>' . esc_html__( 'Invalid log entry.', 'security-ninja' ) . '</p>';
			wp_die();
		}

		$table_name = $wpdb->prefix . WF_SN_SS_TABLE;
		$result     = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table_name WHERE id = %d LIMIT 1", $id ) );

		if ( ! $result || ! isset( $result->ms_results ) || $result->ms_results === '' || $result->ms_results === null ) {
			echo '<p>' . esc_html__( 'No Malware Scanner results for this run.', 'security-ninja' ) . '</p>';
			wp_die();
		}

		$ms = maybe_unserialize( $result->ms_results );
		if ( ! is_array( $ms ) ) {
			echo '<p>' . esc_html__( 'Unable to load results.', 'security-ninja' ) . '</p>';
			wp_die();
		}

		$files_scanned    = isset( $ms['files_scanned'] ) ? (int) $ms['files_scanned'] : 0;
		$dirs_scanned     = isset( $ms['directories_scanned'] ) ? (int) $ms['directories_scanned'] : 0;
		$total_time       = isset( $ms['total_time'] ) ? (float) $ms['total_time'] : 0;
		$files            = isset( $ms['files'] ) && is_array( $ms['files'] ) ? $ms['files'] : array();
		$suspicious_count = count( $files );

		echo '<div style="margin: 20px;">';
		echo '<p>';
		printf(
			esc_html__( 'Scanned %1$s files in %2$s directories.', 'security-ninja' ),
			number_format_i18n( $files_scanned ),
			number_format_i18n( $dirs_scanned )
		);
		if ( $total_time > 0 ) {
			$seconds = (int) $total_time;
			$mins    = (int) floor( $seconds / 60 );
			$secs    = $seconds % 60;
			echo ' ' . esc_html__( 'Duration:', 'security-ninja' ) . ' ';
			if ( $mins > 0 ) {
				echo esc_html( sprintf( _n( '%d minute', '%d minutes', $mins, 'security-ninja' ), $mins ) );
				if ( $secs > 0 ) {
					echo ' ' . esc_html( sprintf( _n( '%d second', '%d seconds', $secs, 'security-ninja' ), $secs ) );
				}
			} else {
				echo esc_html( sprintf( _n( '%d second', '%d seconds', $secs, 'security-ninja' ), $secs ) );
			}
			echo '.';
		}
		echo '</p>';

		if ( $suspicious_count > 0 ) {
			echo '<h4>' . sprintf(
				esc_html( _n( '%d suspicious file found', '%d suspicious files found', $suspicious_count, 'security-ninja' ) ),
				$suspicious_count
			) . '</h4>';
			echo '<table class="wp-list-table widefat striped" cellspacing="0"><thead><tr>';
			echo '<th>' . esc_html__( 'File', 'security-ninja' ) . '</th>';
			echo '<th>' . esc_html__( 'Pattern / details', 'security-ninja' ) . '</th>';
			echo '</tr></thead><tbody>';
			foreach ( $files as $file ) {
				if ( ! is_array( $file ) ) {
					continue;
				}
				$path    = isset( $file['path'] ) ? $file['path'] : '';
				$display = $path ? str_replace( ABSPATH, '', $path ) : '-';
				$pattern = isset( $file['pattern'] ) ? $file['pattern'] : ( isset( $file['comment'] ) ? $file['comment'] : '-' );
				echo '<tr><td><code>' . esc_html( $display ) . '</code></td><td>' . esc_html( $pattern ) . '</td></tr>';
			}
			echo '</tbody></table>';
		} else {
			echo '<p>' . esc_html__( 'No suspicious files reported for this run.', 'security-ninja' ) . '</p>';
		}

		echo '</div>';

		wp_die();
	}


	/**
	 * Get human-readable status text for security test status codes
	 *
	 * @author  AI Assistant
	 * @since   v1.0.0
	 * @version v1.0.0
	 * @access  static
	 * @param   mixed $status Status code or string
	 * @return  string Human-readable status text
	 */
	public static function get_status_text( $status ) {
		// Handle string 'unknown' value (no status recorded for this test).
		if ( 'unknown' === $status || ! is_numeric( $status ) ) {
			return esc_html__( 'Unknown', 'security-ninja' );
		}

		// Security Ninja test status codes: 0 = Failed, 5 = Warning, 10 = OK.
		switch ( (int) $status ) {
			case 10:
				return esc_html__( 'Good', 'security-ninja' );
			case 5:
				return esc_html__( 'Warning', 'security-ninja' );
			case 0:
				return esc_html__( 'Failed', 'security-ninja' );
			default:
				return esc_html__( 'Unknown', 'security-ninja' );
		}
	}

	/**
	 * Get explanation of what a status change means
	 *
	 * @author  AI Assistant
	 * @since   v1.0.0
	 * @version v1.0.0
	 * @access  static
	 * @param   mixed $old_status Previous status code or string
	 * @param   mixed $new_status New status code or string
	 * @return  string Explanation of the change
	 */
	public static function get_status_change_explanation( $old_status, $new_status ) {
		// Handle unknown / non-numeric status values.
		if ( 'unknown' === $old_status || 'unknown' === $new_status || ! is_numeric( $old_status ) || ! is_numeric( $new_status ) ) {
			return esc_html__( 'Status information is incomplete - the test result has changed but status details are unavailable.', 'security-ninja' );
		}

		$old_status = (int) $old_status;
		$new_status = (int) $new_status;

		// Higher status numbers are better (0 = Failed, 5 = Warning, 10 = OK).
		if ( $new_status > $old_status ) {
			return esc_html__( 'This is an improvement - the security issue has been resolved.', 'security-ninja' );
		} elseif ( $new_status < $old_status ) {
			return esc_html__( 'This indicates a security concern that should be addressed.', 'security-ninja' );
		} else {
			return esc_html__( 'The status level remains the same but other details may have changed.', 'security-ninja' );
		}
	}

	/**
	 * clean-up when deactivated
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, May 16th, 2022.
	 * @access  static
	 * @return  void
	 */
	public static function deactivate() {
		global $wpdb;
		wp_clear_scheduled_hook( WF_SN_SS_CRON );
		delete_option( WF_SN_SS_OPTIONS_KEY );
		$wpdb->query( 'DROP TABLE IF EXISTS ' . $wpdb->prefix . WF_SN_SS_TABLE );
	}
}

add_action( 'plugins_loaded', array( __NAMESPACE__ . '\wf_sn_ss', 'init' ) );
add_action( 'plugins_loaded', array( __NAMESPACE__ . '\wf_sn_ss', 'plugins_loaded' ) );
register_deactivation_hook( WF_SN_BASE_FILE, array( __NAMESPACE__ . '\wf_sn_ss', 'deactivate' ) );
