<?php

namespace WPSecurityNinja\Plugin;

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

define('WF_SN_MS_OPTIONS', 'wf_sn_ms_options');
define('WF_SN_MS_CACHE', 'wf_sn_ms_cache');
define('WF_SN_MS_INTEGRITY_RESULTS', 'wf_sn_ms_integrity_results');
define('WF_SN_MS_RESULTS', 'wf_sn_ms_results');
define('WF_SN_MS_WHITELIST', 'wf_sn_ms_whitelist');
define('WF_SN_MS_DELETELIST', 'wf_sn_ms_deletelist');
define('WF_SN_MS_SCAN_TOTAL_TIME', 'wf_sn_ms_scan_total_time');
define('WF_SN_MS_SK', 'wf_sn_ms_sk'); // Secret key
define('WF_SN_MS_IV', 'wf_sn_ms_iv'); // IV
define('WF_SN_MS_LAST_PATTERN_UPDATE', 'wf_sn_ms_last_pattern_update'); // When was last updated

class Wf_Sn_Ms
{



	/**
	 * get_whitelist.
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @return  mixed
	 */
	/**
	 * Get legacy whitelist patterns (for backward compatibility)
	 * This function now returns patterns from the new advanced whitelist system
	 *
	 * @return array Array of whitelist patterns
	 */
	public static function get_whitelist()
	{
		// Define the same patterns as in the advanced whitelist system
		// This avoids the need to instantiate the MalwareScanner class
		$internal_whitelist = array(
			'*.DS_Store',
			'*/js_composer/vc_classmap.json.php',
			'*/plugins/pretty-link/pro/*',
			'*/Divi/includes/builder/frontend-builder/helpers.php',
			'*/wp-snapshots/*',
			'*/plugins/jetpack/*',
			'*/uploads/backupbuddy_temp/*',
			'*/uploads/pb_backupbuddy/*',
			'*/plugins/security-ninja/*',
			'*/plugins/security-ninja-premium/*',
			'*/plugins/seo-booster-premium/*',
			'*/plugins/backupbuddy/*',
			'*/plugins/wp-spamshield/*',
			'*/plugins/login-ninja/*',
			'*/plugins/gravityforms/gravityforms.php',
			'*/plugins/5sec-google-authenticator/*',
			'*/plugins/google-maps-widget/*',
			'*/plugins/optin-ninja/*',
			'*/plugins/smartmonitor/*',
			'*/plugins/under-construction-page/*',
			'*/plugins/profit_builder/*',
			'*/plugins/leadsflow-pro/*',
			'*/wflogs/*',
			'*/themes/enfold/includes/admin/demo_files/*',
			'*/freemius/templates/secure-https-header.php',
			'*/themes/enfold/css/dynamic-css.php',
			'*/tcpdf/tcpdf_barcodes_1d.php',
			'*/plugins/wp-seo-keyword-optimizer-premium/classes/class.import.php',
			'*/phpseclib/Crypt/*',
			'*/guzzle/src/*',
			'*/dompdf/lib/fonts/*',
			'*/plugins/wp-seo-keyword-optimizer-premium/admin/controller/controller.php',
			'*/plugins/wp-reset/*',
			'*/plugins/minimal-coming-soon-maintenance-mode/*',
			'*/plugins/wp-seopress-pro/*',
			'*/plugins/clsop/inc/Engine/Optimization/DeferJS/DeferJS.php',
		);

		// Apply the same filter as before for backward compatibility
		$internal_whitelist = apply_filters('securityninja_whitelist', $internal_whitelist);
		
		return $internal_whitelist;
	}


	/**
	 * Schedule cron jobs
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @return  void
	 */
	public static function schedule_cron_jobs()
	{
		// Update GEOIP database - once a month
		if (! wp_next_scheduled('secnin_update_samples')) {
			wp_schedule_event(time(), 'monthly', 'secnin_update_samples');
		}
	}



	/**
	 * Encrypt and decrypt - Modified
	 * Stores both key and IV in options table - no further need for hiding details.
	 *
	 * @author  Nazmul Ahsan <n.mukto@gmail.com>
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @param   mixed   $string string to be encrypted/decrypted
	 * @param   string  $action what to do with this? e for encrypt, d for decrypt
	 * @return  mixed
	 */
	public static function string_crypt($string, $action = 'e')
	{

		$wf_sn_ms_sk = get_option(WF_SN_MS_SK, false);
		if (! $wf_sn_ms_sk) {
			$wf_sn_ms_sk = base64_encode(\openssl_random_pseudo_bytes(32));
			update_option(WF_SN_MS_SK, $wf_sn_ms_sk, false);
		}

		$wf_sn_ms_iv = get_option(WF_SN_MS_IV, false);
		if (! $wf_sn_ms_iv) {
			$wf_sn_ms_iv = \openssl_random_pseudo_bytes(\openssl_cipher_iv_length('aes-256-cbc'));
			$wf_sn_ms_iv = substr(hash('sha256', $wf_sn_ms_iv), 0, 16);
			update_option(WF_SN_MS_IV, $wf_sn_ms_iv, false);
		}

		$output = false;

		if ('e' === $action) {
			$output = base64_encode(\openssl_encrypt($string, 'aes-256-cbc', $wf_sn_ms_sk, 0, $wf_sn_ms_iv));
		} elseif ('d' === $action) {
			$output = \openssl_decrypt(base64_decode($string), 'aes-256-cbc', $wf_sn_ms_sk, 0, $wf_sn_ms_iv);
		}

		return $output;
	}





	/**
	 * Returns status of scan stored in an option
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @return  boolean
	 */
	public static function get_scan_status()
	{
		check_ajax_referer('sn_ms');
		if (! current_user_can('manage_options')) {
			wp_send_json_error(
				array(
					'message' => __('Failed.', 'security-ninja'),
				)
			);
		}
		$wf_sn_ms_results = get_option(WF_SN_MS_RESULTS);
		if (isset($wf_sn_ms_results['do_mal_scan'])) {
			wp_send_json_success($wf_sn_ms_results['do_mal_scan']);
		} else {
			wp_send_json_error(__('No results', 'security-ninja'));
		}
		return false;
	}



	/**
	 * Gets sample files first time or updates local sample files *
	 *
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @return  void
	 */
	public static function get_or_update_samples()
	{
		// Checks if updated recently and bails if so.
		$last_update = get_option(WF_SN_MS_LAST_PATTERN_UPDATE);
		if ($last_update) {
			$current_time = time();
			$seconds_diff = $current_time - $last_update;
			if ($seconds_diff < DAY_IN_SECONDS) {
				return true;
			}
		}

		$api_url = 'https://wpsecurityninja.sfo2.cdn.digitaloceanspaces.com/samples.json';

		global $secnin_fs;

		$user = $secnin_fs->get_user();

		if (! $user || ! isset($user->public_key)) {
			return false;
		}

		$publickey = $secnin_fs->get_user()->public_key;

		if (! $publickey) {
			return false;
		}

		$request_url = add_query_arg('pk', $publickey, $api_url);
		$request_url = add_query_arg('ver', wf_sn::$version, $request_url);

		$request = wp_remote_get($request_url);

		if (is_wp_error($request)) {
			wf_sn_el_modules::log_event('security_ninja', 'malware_scanner_definitions_error', 'Error getting malware definitions from API.', array('response' => $request->get_error_message()));
			return false; // Bail early
		}

		$body = wp_remote_retrieve_body($request);

		$data = json_decode($body);

		if (! empty($data)) {

			if ((isset($data->code)) && ('rate_limit_exceeded' === $data->code)) {
				return false;
			} else {

				global $wp_filesystem;
				if (empty($wp_filesystem)) {
					require_once ABSPATH . '/wp-admin/includes/file.php';
					WP_Filesystem();
				}
				$upload_dir            = wp_upload_dir();
				$secninja_upload_dir   = $upload_dir['basedir'] . '/security-ninja/';
				$patternsfoldername    = $secninja_upload_dir . 'base64_patterns/';
				$definitionsfoldername = $secninja_upload_dir . 'definitions/';

				if (! $wp_filesystem->exists($secninja_upload_dir)) {
					$wp_filesystem->mkdir($secninja_upload_dir);
				}

				if (! $wp_filesystem->exists($patternsfoldername)) {
					$wp_filesystem->mkdir($patternsfoldername);
				}

				if (! $wp_filesystem->exists($definitionsfoldername)) {
					$wp_filesystem->mkdir($definitionsfoldername);
				}

				$oldfile = $secninja_upload_dir . 'base64_patterns/php_functions.txt';
				if (file_exists($oldfile)) {
					unlink($oldfile);
				}
				$oldfile = $secninja_upload_dir . 'base64_patterns/php_keywords.txt';
				if (file_exists($oldfile)) {
					unlink($oldfile);
				}
				$oldfile = $secninja_upload_dir . 'definitions/patterns_iraw.txt';
				if (file_exists($oldfile)) {
					unlink($oldfile);
				}
				$oldfile = $secninja_upload_dir . 'definitions/patterns_raw.txt';
				if (file_exists($oldfile)) {
					unlink($oldfile);
				}
				$oldfile = $secninja_upload_dir . 'definitions/patterns_re.txt';
				if (file_exists($oldfile)) {
					unlink($oldfile);
				}


				if (isset($data->php_functions)) {
					$logfile = $patternsfoldername . 'php_functions.dat';
					// Attempt to write to the file
					$result = $wp_filesystem->put_contents($logfile, self::string_crypt($data->php_functions, 'e'), FS_CHMOD_FILE);
					if (! $result) {
						// Logging with additional context
						$error_message = sprintf('Problem storing the php_functions.dat file. Target path: %s', $logfile);

						// Check if the directory is writable
						if (! $wp_filesystem->is_writable(dirname($logfile))) {
							$error_message .= ' Directory not writable.';
						}

						// Include information about whether the file exists and its write permissions
						if ($wp_filesystem->exists($logfile) && ! $wp_filesystem->is_writable($logfile)) {
							$error_message .= ' File exists but is not writable.';
						} elseif (! $wp_filesystem->exists($logfile)) {
							$error_message .= ' File does not exist.';
						}

						// Log the constructed error message
						wf_sn_el_modules::log_event('security_ninja', 'malware_scanner_definitions_error', $error_message);
					}
				}

				if (isset($data->php_keywords)) {
					$logfile = $patternsfoldername . 'php_keywords.dat';
					if (! $wp_filesystem->put_contents($logfile, self::string_crypt($data->php_keywords, 'e'), FS_CHMOD_FILE)) {
						wf_sn_el_modules::log_event('security_ninja', 'malware_scanner_definitions_error', 'Problem storing the php_keywords.dat file.');
					}
				}

				// PATTERNS
				if (isset($data->patterns_iraw)) {
					$logfile = $definitionsfoldername . 'patterns_iraw.dat';
					if (! $wp_filesystem->put_contents($logfile, self::string_crypt($data->patterns_iraw, 'e'), FS_CHMOD_FILE)) {
						wf_sn_el_modules::log_event('security_ninja', 'malware_scanner_definitions_error', 'Problem storing the patterns_iraw.dat file.', '');
					}
				}

				if (isset($data->patterns_raw)) {
					$logfile = $definitionsfoldername . 'patterns_raw.dat';
					if (! $wp_filesystem->put_contents($logfile, self::string_crypt($data->patterns_raw, 'e'), FS_CHMOD_FILE)) {
						wf_sn_el_modules::log_event('security_ninja', 'malware_scanner_definitions_error', 'Problem storing the patterns_raw.dat file.');
					}
				}

				if (isset($data->patterns_re)) {
					$logfile = $definitionsfoldername . 'patterns_re.dat';
					if (! $wp_filesystem->put_contents($logfile, self::string_crypt($data->patterns_re, 'e'), FS_CHMOD_FILE)) {
						wf_sn_el_modules::log_event('security_ninja', 'malware_scanner_definitions_error', 'Problem storing the patterns_re.dat file.');
					}
				}

				$last_update = time();
				update_option(WF_SN_MS_LAST_PATTERN_UPDATE, $last_update, false);
			}
		} else {
			// removes the last update to force routine to update again.
			delete_option(WF_SN_MS_LAST_PATTERN_UPDATE);
			wf_sn_el_modules::log_event('security_ninja', 'malware_scanner_definitions_error', 'Empty response from API.');
		}
	}


	/**
	 * init plugin
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @return  void
	 */
	public static function init()
	{
		// does the user have enough privilages to use the plugin?
		if (is_admin()) {
			// add tab to Security Ninja tabs
			add_filter('sn_tabs', array(__NAMESPACE__ . '\\wf_sn_ms', 'sn_tabs'));
			add_action('secnin_update_samples', array(__NAMESPACE__ . '\\wf_sn_ms', 'get_or_update_samples'));
			add_action('init', array(__NAMESPACE__ . '\\wf_sn_ms', 'schedule_cron_jobs'));
			add_action('admin_enqueue_scripts', array(__NAMESPACE__ . '\\wf_sn_ms', 'enqueue_scripts'));

			add_action('wp_ajax_sn_ms_run_scan', array(__NAMESPACE__ . '\\wf_sn_ms', 'run_scan'));
			add_action('wp_ajax_sn_ms_whitelist_file', array(__NAMESPACE__ . '\\wf_sn_ms', 'add_whitelisted_file'));
			add_action('wp_ajax_revert_whitelist_btn', array(__NAMESPACE__ . '\\wf_sn_ms', 'revert_whitelisted_file'));

			add_action('wp_ajax_sn_ms_reset_whitelist', array(__NAMESPACE__ . '\\wf_sn_ms', 'reset_whitelist'));
			add_action('wp_ajax_sn_ms_delete_file', array(__NAMESPACE__ . '\\wf_sn_ms', 'delete_file'));
			add_action('admin_notices', array(__NAMESPACE__ . '\\wf_sn_ms', 'warnings'));
		//	add_action('sn_overlay_content', array(__NAMESPACE__ . '\\wf_sn_ms', 'overlay_content'));
		}
	}

	/**
	 * Enqueue CSS and JS scripts on plugin's admin page
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @return  void
	 */
	public static function enqueue_scripts()
	{
		if (wf_sn::is_plugin_page()) {
			$plugin_url = plugin_dir_url(__FILE__);

			wp_enqueue_script('sn-ms-js', $plugin_url . 'js/min/wf-sn-ms-min.js', array('jquery'), wf_sn::$version, true);

			$js_vars = array(
				'nonce' => wp_create_nonce('sn_ms'),
			);
			wp_localize_script('sn-ms-js', 'wf_sn_ms', $js_vars);
		}
	}



	/**
	 * add new tab
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @param   mixed   $tabs
	 * @return  mixed
	 */
	public static function sn_tabs($tabs)
	{
		$core_tab = array(
			'id'       => 'sn_malware',
			'class'    => '',
			'label'    => __('Malware', 'security-ninja'),
			'callback' => array(__NAMESPACE__ . '\\wf_sn_ms', 'malware_page'),
		);
		$done     = 0;

		for ($i = 0; $i < count($tabs); $i++) {
			if ('sn_malware' === $tabs[$i]['id']) {
				$tabs[$i] = $core_tab;
				$done       = 1;
				break;
			}
		}

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

		return $tabs;
	}



	/**
	 * add custom message to overlay
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @return  void

	 */

	 /*
	public static function overlay_content()
	{
		echo '<div id="sn-malware-scanner" style="display: none;">';
		echo '<h3>Scanning your website for malware</h3>';

		$wf_sn_ms_scan_total_time = get_option(WF_SN_MS_SCAN_TOTAL_TIME);

		echo '<p><span id="mscounterminutes">00</span>:<span id="mscounterseconds">00</span></p>';

		echo '<ul class="malware-scan-list"></ul>';
		$infolink = Utils::generate_sn_web_link('malscan-overlay', '/docs/malware-scanner/malware-scanner/');

		if (! \WPSecurityNinja\Plugin\Wf_Sn_Wl::is_active()) {
			echo '<p><small>Wondering what is happening? <a href="' . esc_url($infolink) . '" target="_blank">Click here</a> (opens a new window)</small></p>';
		}

		if (isset($wf_sn_ms_scan_total_time)) {
			$total_scan_time = $wf_sn_ms_scan_total_time;
			if ($total_scan_time) {
				$spenttime = self::seconds2human($total_scan_time);
				echo '<p><small>' . sprintf(esc_html__('Your last malware scan took %s ', 'security-ninja'), $spenttime) . '</small></p>';
			}
		}
		echo '</div><!-- #sn-malware-scanner -->';
	}
*/


	/**
	 * generate a list of files to scan in a folder
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @param   mixed   $path
	 * @param   mixed   $extensions     Default: null
	 * @param   integer $depth          Default: 3
	 * @param   string  $relative_path  Default: ''
	 * @return  mixed
	 */
	public static function scan_folder($path, $extensions = null, $depth = 3, $relative_path = '')
	{
		if (! is_dir($path)) {
			return false;
		}

		if ($extensions) {
			$extensions  = (array) $extensions;
			$_extensions = implode('|', $extensions);
		} else {
			$extensions  = array(); // empty array to find all types of files.
			$_extensions = implode('|', $extensions);
		}

		$relative_path = trailingslashit($relative_path);
		if ('/' === $relative_path) {
			$relative_path = '';
		}

		$results = scandir($path);
		$files   = array();

		foreach ($results as $result) {
			if ('.' === $result[0]) {
				continue;
			}

			if (is_dir($path . '/' . $result)) {
				if (! $depth || 'CVS' === $result) {
					continue;
				}
				$found = self::scan_folder($path . '/' . $result, $extensions, $depth - 1, $relative_path . $result);
				$files = array_merge_recursive($files, $found);
			} elseif (! $extensions || preg_match('~\.(' . $_extensions . ')$~', $result)) {
				$files[$relative_path . $result] = $path . '/' . $result;
			}
		} // foreach result

		return $files;
	}



	/**
	 * generate a list of folders within a folder to scan
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @param   mixed   $parent_folder
	 * @param   mixed   $skip           Default: null
	 * @return  void
	 */
	public static function get_folders($parent_folder, $skip = null)
	{
		if (! $parent_folder) {
			return array();
		}

		$folders = glob(trailingslashit($parent_folder) . '*', GLOB_ONLYDIR);

		if (isset($skip) && count($folders)) {
			$tmp     = count($folders);
			$cleaned = array();
			$skip    = (array) $skip;
			for ($i = 0; $i < $tmp; $i++) {
				$filtered = false;
				foreach ($skip as $pattern) {
					if (stripos($folders[$i], $pattern) !== false) {
						$filtered = true;
					}
				} // foreach skip
				if (! $filtered) {
					$cleaned[] = $folders[$i];
				}
			} // for folders
			$folders = $cleaned;
		} // if skip

		if (! $folders) {
			return array();
		} else {
			return $folders;
		}
	} // get_folders



	/**
	 * update_cache.
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @param   mixed   $data
	 * @param   mixed   $key    Default: null
	 * @return  mixed
	 */
	public static function update_cache($data, $key = null)
	{
		if (! isset($key)) {
			$cache = $data;
		} else {
			$cache         = get_option(WF_SN_MS_CACHE);
			$cache[$key] = $data;
		}

		return update_option(WF_SN_MS_CACHE, $cache, false);
	}

	/**
	 * retreive cached data
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @param   mixed   $key    Default: null
	 * @return  void
	 */
	public static function get_cache($key = null)
	{
		$cache = get_option(WF_SN_MS_CACHE);

		if (isset($key) && isset($cache[$key])) {
			return $cache[$key];
		} elseif (isset($key)) {
			return array();
		} else {
			return $cache;
		}
	} // get_cache

	/**
	 * save results data
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @param   mixed   $data
	 * @param   mixed   $key    Default: null
	 * @return  mixed
	 */
	public static function update_results($data, $key = null)
	{
		if (! isset($key)) {
			$cache = $data;
		} else {
			$cache         = get_option(WF_SN_MS_RESULTS);
			$cache[$key] = $data;
		}
		$cache['last_run'] = time();

		return update_option(WF_SN_MS_RESULTS, $cache, false);
	} // update_results

	/**
	 * retreive results data
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @param   mixed   $key    Default: null
	 * @return  array
	 */
	public static function get_results($key = null)
	{
		$cache = get_option(WF_SN_MS_RESULTS);

		if (isset($key) && isset($cache[$key])) {
			return $cache[$key];
		} elseif (isset($key)) {
			return array();
		} else {
			return $cache;
		}
	}



	/**
	 * ajax_revert_whitelist_file.
	 *
	 * @author	Unknown
	 * @since	v0.0.1
	 * @version	v1.0.0	Tuesday, June 18th, 2024.
	 * @access	public static
	 * @global
	 * @return	void
	 */
	public static function ajax_revert_whitelist_file()
	{
		check_ajax_referer('sn_ms');

		if (! current_user_can('manage_options')) {
			wp_send_json_error(
				array(
					'message' => __('Failed.', 'security-ninja'),
				)
			);
		}
	}


	/**
	 * add file hash to whitelist
	 *
	 * @author	Unknown
	 * @since	v0.0.1
	 * @version	v1.0.0	Thursday, April 28th, 2022.	
	 * @version	v1.0.1	Tuesday, June 18th, 2024.
	 * @access	public static
	 * @return	void
	 */
	public static function add_whitelisted_file()
	{
		check_ajax_referer('sn_ms');

		if (! current_user_can('manage_options')) {
			wp_send_json_error(
				array(
					'message' => __('Failed.', 'security-ninja'),
				)
			);
		}

		$whitelist = get_option(WF_SN_MS_WHITELIST, array());

		if (isset($_POST['hash']) && isset($_POST['nonce']) && isset($_POST['filename'])) {
			$hash = trim($_POST['hash']);
			$nonce = trim($_POST['nonce']);
			$filename = sanitize_text_field(wp_unslash($_POST['filename']));
			
			// Validate the secure token
			if (! \WPSecurityNinja\Plugin\Wf_Sn_Crypto::validate_secure_file_token($filename, $hash, $nonce, 'whitelist_file')) {
				wp_send_json_error(
					array(
						'message' => __('Invalid or expired security token.', 'security-ninja'),
					)
				);
			}
			
			$new = array(
				'hash'     => esc_attr($hash),
				'filename' => esc_attr($filename),
			);

			// Check for duplicates
			$is_duplicate = false;
			foreach ($whitelist as $item) {
				if ($item['hash'] === $new['hash'] && $item['filename'] === $new['filename']) {
					$is_duplicate = true;
					break;
				}
			}

			if (!$is_duplicate) {
				$whitelist[] = $new;
				update_option(WF_SN_MS_WHITELIST, $whitelist, false);
				
				// Generate fresh output and get accurate count
				$fresh_output = self::generate_fresh_output();
				$suspicious_count = self::count_suspicious_files();
				
				wp_send_json_success(array(
					'message' => __('File added to whitelist successfully.', 'security-ninja'),
					'fresh_output' => $fresh_output,
					'suspicious_count' => $suspicious_count
				));
			} else {
				// Handle the duplicate case - does not really matter anyway, as long as the value is saved.
				wp_send_json_success(array(
					'message' => __('File was already in whitelist.', 'security-ninja')
				));
			}
		}
	}


	/**
	 * revert_whitelisted_file.
	 *
	 * @author	Lars Koudal
	 * @since	v0.0.1
	 * @version	v1.0.0	Tuesday, June 18th, 2024.
	 * @access	public static
	 * @return	void
	 */
	public static function revert_whitelisted_file()
	{
		check_ajax_referer('sn_ms');

		if (! current_user_can('manage_options')) {
			wp_send_json_error(
				array(
					'message' => __('Failed.', 'security-ninja'),
				)
			);
		}

		$whitelist = get_option(WF_SN_MS_WHITELIST, array());

		if (isset($_POST['hash']) && isset($_POST['nonce']) && isset($_POST['filename'])) {
			$hash = trim($_POST['hash']);
			$nonce = trim($_POST['nonce']);
			$filename = sanitize_text_field(wp_unslash($_POST['filename']));
			
			// Validate the secure token
			if (! \WPSecurityNinja\Plugin\Wf_Sn_Crypto::validate_secure_file_token($filename, $hash, $nonce, 'revert_whitelist')) {
				wp_send_json_error(
					array(
						'message' => __('Invalid or expired security token.', 'security-ninja'),
					)
				);
			}

			// Find and remove the specified file from the whitelist
			$whitelist = array_filter($whitelist, function ($item) use ($hash, $filename) {
				return !($item['hash'] === $hash && $item['filename'] === $filename);
			});

			// Reindex the array to ensure proper indexing
			$whitelist = array_values($whitelist);

			// Remove duplicates based on 'hash' and 'filename' combination
			$whitelist = array_map("unserialize", array_unique(array_map("serialize", $whitelist)));

			update_option(WF_SN_MS_WHITELIST, $whitelist, false);
			
			// Generate fresh output and get accurate count
			$fresh_output = self::generate_fresh_output();
			$suspicious_count = self::count_suspicious_files();
			
			wp_send_json_success(array(
				'message' => __('File removed from whitelist successfully.', 'security-ninja'),
				'fresh_output' => $fresh_output,
				'suspicious_count' => $suspicious_count
			));
		} else {
			wp_send_json_error(
				array(
					'message' => __('Invalid input.', 'security-ninja'),
				)
			);
		}
	}






	/**
	 * remove all files from user created whitelist
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @return  void
	 */
	public static function reset_whitelist()
	{
		check_ajax_referer('sn_ms');

		if (! current_user_can('manage_options')) {
			wp_send_json_error(
				array(
					'message' => __('Failed.', 'security-ninja'),
				)
			);
		}

		update_option(WF_SN_MS_WHITELIST, array(), false);

		wp_send_json_success();
	}



	/**
	 * format_results_cont.
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @return  mixed
	 */
	public static function format_results_cont()
	{
		$out = '';
		$cnt = 0;

		$malscan       = self::get_results('do_mal_scan');
		$integrityscan = self::get_results('do_integrity_scan');

		if (isset($integrityscan['modified_files']) && is_array($integrityscan['modified_files'])) {
			if (count($integrityscan['modified_files']) > 0) {

				$out .= '<div class="card">';

				$haveoutput = false;
				foreach ($integrityscan['modified_files'] as $key => $is) {

					$filelist = $is['files'];

					$comment = '';
					if (is_array($filelist)) {

						if (! isset($is['Path'])) {
							$is['Path'] = '';
						}

						foreach ($filelist as $fi) {

							$filename = $is['Path'] . $fi;
							$filehash = @md5_file($filename);

							if (! self::in_whitelist($filename, $filehash)) {
								if (! $haveoutput) {
									$out       .= '<h3>' . esc_html__('Modified Plugin Files', 'security-ninja') . '</h3>';
									$haveoutput = true;
								}

								$parts            = explode('/', $filename);
								$last_part        = array_pop($parts);
								$second_last_part = array_pop($parts);
								$third_last_part  = array_pop($parts);
								$fourth_last_part = array_pop($parts);

								if ($fourth_last_part) {
									$filename_title = '.../' . $fourth_last_part . '/' . $third_last_part . '/' . $second_last_part . '/' . $last_part;
								} elseif ($third_last_part) {
									$filename_title = '.../' . $third_last_part . '/' . $second_last_part . '/' . $last_part;
								} elseif ($second_last_part) {
									$filename_title = '.../' . $second_last_part . '/' . $last_part;
								} else {
									$filename_title = '/' . $last_part;
								}

								$out .= self::format_result_line(
									$filename_title,
									$filename,
									$filehash,
									$comment,
									null,
									null,
									false,
									true,
									true,
									$filename,
									false,
									true
								);
								++$cnt;
							}
						}
					}
				}
				$out .= '</div>';
			}
		}

		/* UNKNOWN FILES */

		if (isset($integrityscan['unknown_files']) && is_array($integrityscan['unknown_files'])) {
			$results = [];

			foreach ($integrityscan['unknown_files'] as $key => $is) {
				$filelist = $is['files'];
				$comment = '';

				if (is_array($filelist)) {
					if (! isset($is['Path'])) {
						$is['Path'] = '';
					}

					foreach ($filelist as $fi) {
						if (file_exists($fi)) {
							$filename = $fi;
							$filehash = @md5_file($fi);

							if (! self::in_whitelist($fi, $filehash)) {
								$results[] = self::format_result_line(
									$is['Name'],
									$fi,
									$filehash,
									$comment,
									$fi,
									null,
									false,
									true,
									true,
									$fi,
									false,
									true
								);
							}
						}
					}
				}
			}

			if (!empty($results)) {
				$out .= '<div class="card">';
				$out .= '<h3>' . esc_html__('Unknown Files', 'security-ninja') . '</h3>';
				$out .= implode('', $results);
				$out .= '</div>';
			}
		}

		$filtered_malscan_results = [];
		$filtered_malscan_results_count = 0;
		// checking the results of the malware scan
		if (isset($malscan['files'])) {
			// loop through and make a filtered array of files that are not in the whitelist

			foreach ($malscan['files'] as $fi) {
	
				if (file_exists($fi['path'])) {
					$filehash = @md5_file($fi['path']);
			} else {
					$filehash = false;
					
			}
			
			if (! self::in_whitelist($fi['path'], $filehash)) {
					$filtered_malscan_results[] = $fi;
					$filtered_malscan_results_count++;
					$whitelist  = get_option(WF_SN_MS_WHITELIST, array());
				}
			}

			if (0 < $filtered_malscan_results_count) {
				$out .= '<h3>Signs of suspicious or malicious code</h3>' . self::format_results($filtered_malscan_results);
			}
		}

		if (defined('DOING_AJAX') && DOING_AJAX) {
			do_action('security_ninja_malware_scanner_done_scanning', $filtered_malscan_results_count);
		}

		if (0 < $filtered_malscan_results_count) {
			// prepend the count
			$out = '<h3 class="error">' . sprintf(esc_html(_n('%d suspicious file found', '%d issues found', $filtered_malscan_results_count, 'security-ninja')), $filtered_malscan_results_count) . '</h3>' . $out;
		} else {

			$last_run = self::get_results('last_run');

			if (isset($last_run) && $last_run) {

				$out .= '<h3>Great! There are no suspicious files to report at the moment</h3>';
			}
		}

		return $out;
	}





	/**
	 * delete file via ajax
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @return  void
	 */
	public static function delete_file()
	{
		check_ajax_referer('sn_ms');

		if (! current_user_can('manage_options')) {
			wp_send_json_error(
				array(
					'message' => __('Failed.', 'security-ninja'),
				)
			);
		}

		$file = isset($_POST['filename']) ? sanitize_text_field(wp_unslash($_POST['filename'])) : '';

		// Validate the filename
		if (empty($file) || !is_string($file) || strpos($file, '..') !== false) {
			wp_send_json_error(
				array(
					'message' => __('Invalid filename.', 'security-ninja'),
				)
			);
		}

		// Validate that the file is within WordPress installation directory
		if (! \WPSecurityNinja\Plugin\FileViewer::is_within_wordpress_installation($file)) {
			wp_send_json_error(
				array(
					'message' => __('Access denied: File is not within WordPress installation directory.', 'security-ninja'),
				)
			);
		}

		// Validate the secure token
		$hash = isset($_POST['hash']) ? trim($_POST['hash']) : '';
		$nonce = isset($_POST['nonce']) ? trim($_POST['nonce']) : '';
		
		if (empty($hash) || empty($nonce)) {
			wp_send_json_error(
				array(
					'message' => __('Missing security token.', 'security-ninja'),
				)
			);
		}

		if (! \WPSecurityNinja\Plugin\Wf_Sn_Crypto::validate_secure_file_token($file, $hash, $nonce, 'delete_file')) {
			wp_send_json_error(
				array(
					'message' => __('Invalid or expired security token.', 'security-ninja'),
				)
			);
		}

		// Use WordPress filesystem for secure file deletion
		global $wp_filesystem;
		if (empty($wp_filesystem)) {
			require_once ABSPATH . 'wp-admin/includes/file.php';
			WP_Filesystem();
		}

		if ($wp_filesystem->delete($file, false)) {
			$deletelist   = get_option(WF_SN_MS_DELETELIST, array());
			$deletelist[] = $hash;
			$deletelist   = array_unique($deletelist);
			update_option(WF_SN_MS_DELETELIST, $deletelist, false);
			
			// Generate fresh output and get accurate count
			$fresh_output = self::generate_fresh_output();
			$suspicious_count = self::count_suspicious_files();
			
			wp_send_json_success(array(
				'message' => __('File deleted successfully.', 'security-ninja'),
				'fresh_output' => $fresh_output,
				'suspicious_count' => $suspicious_count
			));
		} else {
			wp_send_json_error(
				array(
					'message' => __('Failed to delete file.', 'security-ninja'),
				)
			);
		}
	} // delete_file




	/**
	 * ajax function for running scan
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @param   boolean $opt    Default: false
	 * @return  void
	 */
	public static function run_scan($opt = false)
	{

		if (defined('DOING_AJAX') && DOING_AJAX) {
			check_ajax_referer('sn_ms');

			if (! current_user_can('manage_options')) {
				wp_send_json_error(
					array(
						'message' => __('Failed.', 'security-ninja'),
					)
				);
			}
		}

		$out['err'] = '';
		if (! $opt) {
			if (isset($_POST['what'])) {

				$opt = trim(htmlspecialchars($_POST['what'], ENT_QUOTES, 'UTF-8'));
			} else {
				$opt = ''; // Default or error handling.
			}
		}


		switch ($opt) {
				// First time - So lets start
			case 'clean_cache':
				\WPSecurityNinja\Plugin\Utils::timerstart('WF_SN_MS_SCAN');
				self::update_cache(array());
				self::update_results(array());
				delete_option(WF_SN_MS_DELETELIST);
				break;

			case 'update_samples':
				// Make sure we have the files as base for scanning
				$updateSamples = self::get_or_update_samples();
				break;

			case 'get_db_rows':
				global $wpdb;
				$query      = $wpdb->prepare('SELECT COUNT(option_id) FROM ' . $wpdb->options . ' WHERE option_name NOT LIKE %s', '\_%');
				$cnt        = $wpdb->get_var($query);
				$out['cnt'] = $cnt;
				break;

			case 'do_integrity_scan':
				$integrity_results = wf_sn_plugins_integrity::runInternal();
				if ($integrity_results) {
					self::update_results($integrity_results, 'do_integrity_scan');
				}
				$out['msg'] = 'Scan completed';
				break;

				case 'do_db_scan':
					$db_scan_results = self::scan_db();
					if ($db_scan_results) {
						self::update_results($db_scan_results, 'do_db_scan');
				
						$out['cnt'] = count($db_scan_results);
						$out['msg'] = sprintf(
							__('Database scan completed. Found %d suspicious entries.', 'security-ninja'),
							$out['cnt']
						);
					} else {
						$out['cnt'] = 0;
						$out['msg'] = __('Database scan completed. No suspicious entries found.', 'security-ninja');
					}
					break;

			case 'do_mal_scan':
				require_once WF_SN_PLUGIN_DIR . 'includes/php-malware-scanner/sn-scan.php';
				$malscan = new MalwareScanner(false);
				$malscan->setFlagHideOk(true);
				$malscan->setFlagComments(true);
				$malscan->setFlagHideWhitelist(true);
				$malscan->setFlagNoStop(false);
				$malscan->setFlagLineNumber(true);

				$integrity_whitelist = array();
				$integrity_results   = self::get_results('do_integrity_scan');
				if ($integrity_results) {
					if (is_array($integrity_results['validated_plugins'])) {
						foreach ($integrity_results['validated_plugins'] as $key => $valid_dir) {
							$integrity_whitelist[] = str_replace(ABSPATH, '', $valid_dir['Path']);
						}
					}
				}

				$custom_whitelist = get_option(WF_SN_MS_WHITELIST, array());

				$merged_whitelist = array_merge(self::get_whitelist(), $custom_whitelist, $integrity_whitelist);

		
				$malscan->setIgnore($merged_whitelist);

				$scanresults = $malscan->do_scan(ABSPATH); // WF_SN_PLUGIN_DIR WP_PLUGIN_DIR WP_CONTENT_DIR // ABS_PATH
				if ($scanresults) {
					self::update_results($scanresults, 'do_mal_scan');
				}
				break;

			case 'get_db_rows':
				global $wpdb;

				$query = $wpdb->prepare('SELECT COUNT(option_id) FROM ' . $wpdb->options . ' WHERE option_name NOT LIKE %s', '\_%');
				$cnt   = $wpdb->get_var($query);

				$out['cnt'] = $cnt;
				break;

			case 'scan_db':
				$results = self::scan_db();
				self::update_results($results, 'db_options');
				$out['cnt'] = count($results);
				break;

			case 'get_results':
				$out = '<div class="sncard">' . self::generate_fresh_output() . '</div>';
				// Gets and saves the total scan time as an option
				update_option(WF_SN_MS_SCAN_TOTAL_TIME, \WPSecurityNinja\Plugin\Utils::timerstop('WF_SN_MS_SCAN'), false);
				break;

			default:
				wp_send_json_error(__('Invalid option.', 'security-ninja'));
		}

		if (defined('DOING_AJAX') && DOING_AJAX) {
			wp_send_json_success($out);
		} else {
			return $out;
		}
	}



	/**
	 * scan provided list of files, mind the whitelist
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  static
	 * @param   mixed   $files
	 * @return  mixed
	 */
	static function process_files($files)
	{
		$tmp     = 0;
		$results = array();
		$files   = array_slice($files, 0, 500);
		foreach ($files as $file) {
			$hash = @md5_file($file);
			if (self::in_whitelist($file, $hash)) {
				continue;
			}
			// check if in list of ignored file extensions
			if (self::in_ignored_file_extensions($file)) {
				continue;
			}
			$result = self::scan_file($file);
			if ($result && is_array($result)) {
				$results[] = array(
					'filename' => $file,
					'hash'     => $hash,
					'lines'    => $result,
				);
			}

		}
		return $results;
	}

	/**
	 * in_ignored_file_extensions.
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @param   boolean $file   Default: false
	 * @return  boolean
	 */
	public static function in_ignored_file_extensions($file = false)
	{
		if (! $file) {
			return false;
		}

		$ignored_extensions = self::get_ignored_file_extensions();
		if (! $ignored_extensions) {
			return false;
		}

		$ext = pathinfo($file, PATHINFO_EXTENSION);
		if (in_array($ext, $ignored_extensions, true)) {
			return true;
		}
		return false;
	}



	/**
	 * check if file is in whitelist, user or global one
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @param   mixed   $filename
	 * @param   mixed   $hash
	 * @return  boolean
	 */

	public static function in_whitelist($filename, $hash = null)
	{
		$whitelist  = get_option(WF_SN_MS_WHITELIST, array());
		$deletelist = get_option(WF_SN_MS_DELETELIST, array());




		// Check if the file ends with '.DS_Store'
		if (basename($filename) === '.DS_Store') {
			return true;
		}


		// Tests if file is in whitelist by filename
		foreach ($whitelist as $whitelist_item) {
			if ($whitelist_item['filename'] === $filename) {
				return true;
			}
		}


		// Tests if file is in whitelist by hash and filename
		foreach ($whitelist as $whitelist_item) {
			if ((empty($hash) || $whitelist_item['hash'] === $hash) && $whitelist_item['filename'] === $filename) {
				return true;
			}
		}

		// Tests if in list of files to delete
		foreach ($deletelist as $deletelist_item) {
			if (is_array($deletelist_item) && (empty($hash) || $deletelist_item['hash'] === $hash)) {
				return true;
			}
		}

		// Tests against known folder names to ignore
		foreach (self::get_whitelist() as $pattern) {
			if (fnmatch($pattern, strtolower($filename))) {
				return true;
			}
		}

		// If none of the conditions match, the file is not whitelisted
		return false;
	}


	/**
	 * Returns a formatted line in malware results
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @param   mixed   $title
	 * @param   mixed   $path
	 * @param   mixed   $hash
	 * @param   mixed   $comment        Default: null
	 * @param   mixed   $matchedline    Default: null
	 * @param   mixed   $line           Default: null
	 * @param   mixed   $pattern        Default: null
	 * @param   boolean $delete_btn     [description]
	 * @param   boolean $whitelist_btn  [description]
	 * @param   mixed   $files          Default: null
	 * @return  mixed
	 */
	public static function format_result_line(
		$title,
		$path,
		$hash,
		$comment = null,
		$matchedline = null,
		$line = null,
		$pattern = null,
		$delete_btn = false,
		$whitelist_btn = false,
		$files = null,
		$revert_whitelist_btn = false,
		$view_file = false
	) {
		$out  = '<div class="sn-malware-filebox">';
		$out .= '<div class="sn-malware-title">';
		$out .= '<div class="sn-malware-title-inner">';
		$out .= '<div class="malpath">' . esc_html($title) . '</div>';
		
		$out .= '<div class="malactions">';	
//		$out .= '<span class="order-lower-indicator" aria-hidden="true"></span> <a href="#" class="toggle-slide"><div class="dashicons-before dashicons-arrow-up-alt2"></div></a>';

		if ($delete_btn) {
			$token = \WPSecurityNinja\Plugin\Wf_Sn_Crypto::generate_secure_file_token($path, 'delete_file');
			$out .= '<button class="button button-small input-button sn_ms_delete" data-filename="' . esc_attr($path) . '" data-hash="' . esc_attr($token['hash']) . '" data-nonce="' . esc_attr($token['nonce']) . '">' . esc_html__('Delete', 'security-ninja') . '</button>';
		}

		if ($whitelist_btn) {
			$token = \WPSecurityNinja\Plugin\Wf_Sn_Crypto::generate_secure_file_token($path, 'whitelist_file');
			$out .= '<button class="button button-small input-button gray sn_ms_whitelist" data-hash="' . esc_attr($token['hash']) . '" data-nonce="' . esc_attr($token['nonce']) . '" data-filename="' . esc_attr($path) . '">' . esc_html__('Whitelist', 'security-ninja') . '</button>';
		}

		if ($revert_whitelist_btn) {
			$token = \WPSecurityNinja\Plugin\Wf_Sn_Crypto::generate_secure_file_token($path, 'revert_whitelist');
			$out .= '<button class="button button-small input-button gray sn_ms_revert_whitelist" data-hash="' . esc_attr($token['hash']) . '" data-nonce="' . esc_attr($token['nonce']) . '" data-filename="' . esc_attr($path) . '">' . esc_html__('Revert Whitelist', 'security-ninja') . '</button>';
		}

		if ($view_file && \WPSecurityNinja\Plugin\FileViewer::can_view_file($path) && file_exists($path)) {
			$file_view_url = \WPSecurityNinja\Plugin\FileViewer::generate_file_view_url($path, $line);

			$out .= '<button class="button button-small input-button gray sn_ms_view_file" data-href="' . esc_attr($file_view_url) . '">' . esc_html__('View File', 'security-ninja') . '</button>';
		}
		$out .= '</div><!-- malactions -->';
		$out .= '</div>';
		$out .= '</div>';

		$out .= '<div class="sn-malware-file-code closed">';
		/*
																			if ( isset( $matchedline ) ) {
																			$out .= '<div class="matchedline"><code>' . esc_html( $matchedline ) . '</code></div>';
																			}
																			*/
		/*
																			if ( isset( $line ) ) {
																			$out .= '<div class="linenumber">' . esc_html__( 'Line:', 'security-ninja' ) . ' ' . esc_html( $line ) . '</div>';
																			}
																			*/
		if (isset($pattern)) {
			$out .= '<div class="pattern">Matched pattern "' . esc_html($pattern) . '"</div>';
		}

		if (is_array($files)) {
			$out .= '<div class="files"><ul>';
			foreach ($files as $file) {
				$out .= '<li>' . esc_html($file) . '</li>';
			}
			$out .= '</ul></div>';
		}

		if ($comment) {
			$out .= '<div class="comment">' . esc_html($comment) . '</div>';
		}

		if ($path) {
			$out .= '<div class="path">' . esc_html__('Located here:', 'security-ninja') . ' <code>' . esc_html($path) . '</code></div>';
		}

		$out .= '</div>';
		$out .= '</div>';

		return $out;
	}





	/**
	 * Check if a file should be included in results (exists and not whitelisted)
	 *
	 * @param string $filepath The file path to check
	 * @param string $filehash The file hash (optional)
	 * @return bool Whether the file should be included
	 */
	public static function should_include_file($filepath, $filehash = null)
	{
		// Skip files that don't exist
		if (!file_exists($filepath)) {
			return false;
		}
		
		// Get hash if not provided
		if ($filehash === null) {
			$filehash = @md5_file($filepath);
		}
		
		// Skip whitelisted files
		return !self::in_whitelist($filepath, $filehash);
	}

	/**
	 * Count the number of suspicious files across all scan types
	 *
	 * @return int Number of suspicious files
	 */
	public static function count_suspicious_files()
	{
		$suspicious_count = 0;
		$malscan = self::get_results('do_mal_scan');
		$integrityscan = self::get_results('do_integrity_scan');
		
		// Count malware scan results
		if (isset($malscan['files'])) {
			foreach ($malscan['files'] as $fi) {
				if (self::should_include_file($fi['path'])) {
					$suspicious_count++;
				}
			}
		}
		
		// Count integrity scan results - modified files
		if (isset($integrityscan['modified_files']) && is_array($integrityscan['modified_files'])) {
			foreach ($integrityscan['modified_files'] as $is) {
				$filelist = $is['files'];
				if (is_array($filelist)) {
					foreach ($filelist as $fi) {
						$filename = (isset($is['Path']) ? $is['Path'] : '') . $fi;
						if (self::should_include_file($filename)) {
							$suspicious_count++;
						}
					}
				}
			}
		}
		
		// Count integrity scan results - unknown files
		if (isset($integrityscan['unknown_files']) && is_array($integrityscan['unknown_files'])) {
			foreach ($integrityscan['unknown_files'] as $is) {
				$filelist = $is['files'];
				if (is_array($filelist)) {
					foreach ($filelist as $fi) {
						if (self::should_include_file($fi)) {
							$suspicious_count++;
						}
					}
				}
			}
		}
		
		return $suspicious_count;
	}

	/**
	 * Generate fresh output using cached scan data but with current tokens
	 *
	 * @return string Fresh HTML output with current tokens
	 */
	public static function generate_fresh_output()
	{
		$out = '';
		$cnt = 0;

		$malscan       = self::get_results('do_mal_scan');
		$integrityscan = self::get_results('do_integrity_scan');

		if (isset($integrityscan['modified_files']) && is_array($integrityscan['modified_files'])) {
			if (count($integrityscan['modified_files']) > 0) {

				$out .= '<div class="card">';

				$haveoutput = false;
				foreach ($integrityscan['modified_files'] as $key => $is) {

					$filelist = $is['files'];

					$comment = '';
					if (is_array($filelist)) {

						if (! isset($is['Path'])) {
							$is['Path'] = '';
						}

						foreach ($filelist as $fi) {

							$filename = $is['Path'] . $fi;
							$filehash = @md5_file($filename);

							if (! self::in_whitelist($filename, $filehash)) {
								if (! $haveoutput) {
									$out       .= '<h3>' . esc_html__('Modified Plugin Files', 'security-ninja') . '</h3>';
									$haveoutput = true;
								}

								$parts            = explode('/', $filename);
								$last_part        = array_pop($parts);
								$second_last_part = array_pop($parts);
								$third_last_part  = array_pop($parts);
								$fourth_last_part = array_pop($parts);

								if ($fourth_last_part) {
									$filename_title = '.../' . $fourth_last_part . '/' . $third_last_part . '/' . $second_last_part . '/' . $last_part;
								} elseif ($third_last_part) {
									$filename_title = '.../' . $third_last_part . '/' . $second_last_part . '/' . $last_part;
								} elseif ($second_last_part) {
									$filename_title = '.../' . $second_last_part . '/' . $last_part;
								} else {
									$filename_title = '/' . $last_part;
								}

								$out .= self::format_result_line(
									$filename_title,
									$filename,
									$filehash,
									$comment,
									null,
									null,
									false,
									true,
									true,
									$filename,
									false,
									true
								);
								++$cnt;
							}
						}
					}
				}
				$out .= '</div>';
			}
		}

		/* UNKNOWN FILES */

		if (isset($integrityscan['unknown_files']) && is_array($integrityscan['unknown_files'])) {
			$results = [];

			foreach ($integrityscan['unknown_files'] as $key => $is) {
				$filelist = $is['files'];
				$comment = '';

				if (is_array($filelist)) {
					if (! isset($is['Path'])) {
						$is['Path'] = '';
					}

					foreach ($filelist as $fi) {
						if (file_exists($fi)) {
							$filename = $fi;
							$filehash = @md5_file($fi);

							if (! self::in_whitelist($fi, $filehash)) {
								$results[] = self::format_result_line(
									$is['Name'],
									$fi,
									$filehash,
									$comment,
									null,
									null,
									false,
									true,
									true,
									$fi,
									false,
									true
								);
								++$cnt;
							}
						}
					}
				}
			}

			if (count($results) > 0) {
				$out .= '<div class="card">';
				$out .= '<h3>' . esc_html__('Unknown Files', 'security-ninja') . '</h3>';
				$out .= implode('', $results);
				$out .= '</div>';
			}
		}

		/* MALWARE SCAN RESULTS */

		$filtered_malscan_results = [];
		$filtered_malscan_results_count = 0;
		// checking the results of the malware scan
		if (isset($malscan['files'])) {
			// loop through and make a filtered array of files that are not in the whitelist

			foreach ($malscan['files'] as $fi) {
				if (self::should_include_file($fi['path'])) {
					$filtered_malscan_results[] = $fi;
					$filtered_malscan_results_count++;
				}
			}

			if (0 < $filtered_malscan_results_count) {
				$out .= '<h3>Signs of suspicious or malicious code</h3>' . self::format_results($filtered_malscan_results);
			}
		}

		// Get total count of all suspicious files
		$total_suspicious_count = self::count_suspicious_files();

		if (defined('DOING_AJAX') && DOING_AJAX) {
			do_action('security_ninja_malware_scanner_done_scanning', $total_suspicious_count);
		}

		if (0 < $total_suspicious_count) {
			// prepend the count
			$out = '<h3 class="error">' . sprintf(esc_html(_n('%d suspicious file found', '%d issues found', $total_suspicious_count, 'security-ninja')), $total_suspicious_count) . '</h3>' . $out;
		} else {

			$last_run = self::get_results('last_run');

			if (isset($last_run) && $last_run) {

				$out .= '<h3>Great! There are no suspicious files to report at the moment</h3>';
			}
		}

		return $out;
	}

	/**
	 * format_results.
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @param   mixed   $files
	 * @return  mixed
	 */
	public static function format_results($files)
	{
		$out = '';

		if (! $files) {
			return '';
		}

		foreach ($files as $file) {
			// Ensure $file is an array and has required keys
			if (! is_array($file)) {
				continue; // Skip non-array items
			}

			// Check if required keys exist
			if (! isset($file['path']) || ! isset($file['hash'])) {
				continue; // Skip items without required keys
			}

			if (! self::in_whitelist($file['path'], $file['hash'])) {

				$out .= self::format_result_line(
					str_replace(ABSPATH, '', $file['path']),
					$file['path'],
					$file['hash'],
					isset($file['comment']) ? $file['comment'] : null,
					isset($file['matchedline']) ? $file['matchedline'] : null,
					isset($file['line']) ? $file['line'] : null,
					isset($file['pattern']) ? $file['pattern'] : null,
					true,
					true,
					null,
					false,
					true
				);
			}
		}

		return $out;
	}







	/**
	 * prepare results for output
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @param   mixed   $options
	 * @return  mixed
	 */
	public static function format_db_results($options)
	{
		global $wpdb;
		$out = '';

		if (! $options) {
			return '';
		}

		$out .= '<div class="sn-malware-filebox">';
		$out .= '<div class="sn-malware-title">';
		$out .= '<div class="sn-malware-title-inner">';
		$out .= '<span>' . __('Database table', 'security-ninja') . ' ' . DB_NAME . '.' . $wpdb->options . '</span> <a href="#" class="toggle-slide"><div class="dashicons-before dashicons-arrow-up-alt2"></div></a><br>';
		$out .= '</div>';
		$out .= '</div>';
		$out .= '<div class="sn-malware-file-code closed">';
		foreach ($options as $key => $result) {
			$result = htmlspecialchars($result);
			$result = str_replace(array('@span@', '@/span@'), array('<span class="sn_ms_highlight">', '</span>'), $result);
			$out   .= '<div class="one-line">';
			$out   .= '<span class="line-number">' . $key . '</span> <p>' . $result . '</p>';
			$out   .= '</div>';
		} // foreach line
		$out .= '</div>';
		$out .= '</div>';

		return $out;
	} // format_db_results





	/**
	 * generate regular expression for malware search
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @param   string  $filetype   Default: 'php'
	 * @return  mixed
	 */
	public static function line_match_pattern($filetype = 'php')
	{
		// Match whole words only
		$php = '/[\n\r\s\,](assert|file_get_contents|curl_exec|popen|proc_open|unserialize|eval|base64_encode|base64_decode|create_function|exec|shell_exec|system|passthru|ob_get_contents|file|curl_init|readfile|fopen|fsockopen|pfsockopen|fclose|fread|file_put_contents)\s*?\(/';
		return $php;

	}




	/**
	 * scan individual file
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @param   mixed   $file
	 * @return  boolean
	 */
	public static function scan_file($file)
	{
		// Use comprehensive file validation
		$validation = Wf_Sn_Security_Utils::validate_file($file, array(
			'check_readable' => true,
			'check_size' => true,
			'max_size' => 5 * 1024 * 1024, // 5MB for malware scanning
			'check_type' => true,
			'allowed_extensions' => array('php', 'txt', 'html', 'css', 'js', 'xml', 'json'),
			'check_mime' => false, // Skip MIME check for performance
			'check_path' => true,
			'allowed_dirs' => array(ABSPATH, WP_CONTENT_DIR)
		));
		
		if (!$validation['valid']) {
			return false;
		}
		
		$content = file($file);
		if ($content === false) {
			return false;
		}

		$results = array();

		foreach ($content as $num => $line) {
			if ($result = self::scan_file_line($line, $num)) {
				$results[$num] = $result;
			}
		}

		if (! empty($results)) {
			return $results;
		}

		return false;
	}



	/**
	 * get_ignored_file_extensions.
	 *
	 * @author  Unknown
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @version v1.0.1  Friday, November 17th, 2023.
	 * @access  public static
	 * @return  mixed
	 */
	public static function get_ignored_file_extensions()
	{
		$request_uri_array = apply_filters(
			'securityninja_ignored_file_extensions',
			array(
				'mo',
				'po',
				'md',
				'png',
				'gif',
				'md',
				'pot',
				'css',
				'js',
				'woff',
				'woff2',
				'xml',
				'eot',
				'ttf',
				'svg',
				'DS_Store',
			)
		);
		return $request_uri_array;
	}







	/**
	 * Returns an array of option_names to be skipped in database scan
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @return  mixed
	 */
	public static function get_whitelisted_option_names()
	{
		$oknames = array(
			'wf_sn_ms_results', // lk - internal storing scan results
			'fs_accounts', // lk freemius
			'fs_api_cache', // lk freemius
			'jetpack_protect_key', // lk
			'mwp_public_keys', // lk managewp
		);


		return $oknames;
	}








	/**
	 * scans all option and transient strings in db.options
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @return  mixed
	 */
	public static function scan_db()
	{
		global $wpdb;
		$out = array();

		$records = $wpdb->get_var($wpdb->prepare("SELECT COUNT(option_id) FROM $wpdb->options WHERE option_name NOT LIKE %s", '\_%'));

		$pages = round($records / 100) + 1;
		$pages = max(13, $pages);

		$whitelisted_option_names = self::get_whitelisted_option_names();



		for ($i = 0; $i < $pages; $i++) {
			if ($i % 5 == 0) {
		
			}

			$query = $wpdb->get_results($wpdb->prepare("SELECT option_name, option_value FROM {$wpdb->options} WHERE option_name  NOT LIKE %s LIMIT %d, 100", '\_%', $i * 100));
			foreach ($query as $row) {
				if (! in_array($row->option_name, $whitelisted_option_names, true)) {
					$result = self::scan_file_line($row->option_value, 0, true);
					if ($result) {
						$out[$row->option_name] = $result[0];
						
					}
				}
			}
			unset($query);
			wp_cache_flush();
		}



		return $out;
	}









	/**
	 * Scan one file line
	 *
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @param   string  $line
	 * @param   int     $num
	 * @param   boolean $only_first_line
	 * @return  array|false
	 */
	public static function scan_file_line($line, $num, $only_first_line = false)
	{
		$line = trim((string) $line);

		if (empty($line)) {
			return false;
		}

		$results = array();
		$output  = array();

		// Matches usual patterns
		preg_match_all(self::line_match_pattern('php'), $line, $matches);
		if (!empty($matches[1])) {
			$results = $matches[1];
		}

		// Check for base64 encoded strings
		preg_match_all('/[\'\"\$\\ \/]*?([a-zA-Z0-9]{' . strlen(base64_encode('rainbow + unicorn = magic.')) . ',})/', $line, $matches); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
		if (!empty($matches[1])) {
			$results = array_merge($results, $matches[1]);
		}

		// Looks for iframes
		preg_match_all('/<\s*?(i?frame)/', $line, $matches);
		if (!empty($matches[1])) {
			$results = array_merge($results, $matches[1]);
		}

		// MailPoet Vulnerability
		preg_match_all('/explode\s?\(chr\s?\(\s?\(\d{3}\s?-\s?\d{3}\s?\)\s?\)\s?,/', $line, $matches);
		if (!empty($matches[0])) {
			$results = array_merge($results, $matches[0]);
		}

		if (!empty($results)) {
			$results = array_unique($results);

			foreach ($results as $tag) {
				$cut_line = self::cut_line($line, $tag);
				if ($cut_line === null) {
					continue; // Skip if cut_line returns null
				}
				$string   = str_replace($tag, '<span>' . esc_html($tag) . '</span>', $cut_line);
				$output[] = $string;
				if ($only_first_line) {
					break;
				}
			} // foreach result

			return $output;
		} // if results

		return false;
	}









	/**
	 * Trim code line
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @param   mixed   $line
	 * @param   mixed   $tag
	 * @param   integer $max    [description]
	 * @return  mixed
	 */
	public static function cut_line($line, $tag, $max = 200)
	{
		if (! $line || ! $tag) {
			return false;
		}

		if (strlen($tag) > $max) {
			return $tag;
		}

		$left = round(($max - strlen($tag)) / 2);
		$tag  = preg_quote($tag);

		$output = preg_replace('/(' . $tag . ')(.{' . $left . '}).{0,}$/', '$1$2 ...', $line);
		$output = preg_replace('/^.{0,}(.{' . $left . ',})(' . $tag . ')/', '... $1$2', $output);

		return $output;
	} // cut_line







	/**
	 * Based on anon submission https://snippetsofcode.wordpress.com/2012/08/25/php-function-to-convert-seconds-into-human-readable-format-months-days-hours-minutes/
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @param   boolean $ss Default: false
	 * @return  mixed
	 */
	public static function seconds2human($ss = false)
	{
		if (! $ss) {
			return false;
		}
		$s      = (int) $ss % 60;
		$minraw = floor(((int) $ss % 3600) / 60);
		$m      = '';
		if ($minraw > 0) {
			$m = sprintf(_n('%s minute', '%s minutes', $minraw, 'security-ninja'), esc_html(number_format_i18n($minraw)));
		}
		$hourraw = floor(((int) $ss % 86400) / 3600);
		$h       = '';
		if ($hourraw > 0) {
			$h = sprintf(_n('%s hour', '%s hours', $hourraw, 'security-ninja'), number_format_i18n($hourraw));
		}

		return ltrim("$h $m $s seconds");
	}








	/**
	 * Display results
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @return  void
	 */
	public static function malware_page()
	{

		$last_run = self::get_results('last_run');

		$wf_sn_ms_scan_total_time = get_option(WF_SN_MS_SCAN_TOTAL_TIME);

		if (isset($wf_sn_ms_scan_total_time)) {

			$total_scan_time = $wf_sn_ms_scan_total_time;
		}

		
		echo '<div class="sncard">';

		echo '<h2>' . __('Malware Scanner', 'security-ninja') . '</h2>';

		self::get_or_update_samples();

		$pattern_last_update = get_option(WF_SN_MS_LAST_PATTERN_UPDATE, false);

		if ($pattern_last_update) {
?>
			<p class="sn-infobox">
				<?php
				printf(
					// translators: Shows how many vulnerabilities are known and when list was updated
					esc_html__('Malware patterns list last updated %1$s (%2$s)', 'security-ninja'),
					esc_html(date_i18n(get_option('date_format') . ' ' . get_option('time_format'), $pattern_last_update)),
					esc_html(human_time_diff($pattern_last_update, time()) . ' ' . __('ago', 'security-ninja'))
				);
				?>
			</p>
		<?php
		}

		if (version_compare(PHP_VERSION, '7.0', '<')) {
		?>
			<p><strong>
					<?php
					printf(esc_html__('Warning - You are running PHP v. %1$s - Malware Scanner needs PHP 7+ ', 'security-ninja'), PHP_VERSION);
					?>
				</strong></p>
			<?php
			$infolink = Utils::generate_sn_web_link('upgrade-php', '/docs/installation-and-usage/why-upgrade-to-php-7/');
			echo '<p>' . __('Why is this important?', 'security-ninja') . '</p><a href="' . esc_url($infolink) . '" target="_blank">' . __('Read more about PHP 7', 'security-ninja') . '</a> (opens a new window)</small></p>';

		} else {
		?>
			<a id="sn_ms_run_scan" href="#" class="button button-primary button-hero button-center"><?php esc_html_e('Scan your website', 'security-ninja'); ?></a>
		<?php
		}


		if (isset($last_run) && is_int($last_run)) { // Ensure $last_run is an integer
			echo '<p class="sn-infobox">' . __('Files were last scanned on', 'security-ninja') . ': ' . date_i18n(get_option('date_format') . ' ' . get_option('time_format'), $last_run) . '.';

			$wf_sn_ms_results = get_option(WF_SN_MS_RESULTS);

			$wf_sn_ms_results = get_option(WF_SN_MS_RESULTS);

			if (isset($wf_sn_ms_results['do_mal_scan'])) {

				$spenttime = self::seconds2human($total_scan_time);
				if ($spenttime) {
					echo ' Duration ' . esc_html($spenttime) . '.';
				}
			}

			if (isset($wf_sn_ms_results['do_mal_scan'])) {
				printf(' ' . esc_html__('Scanned %1$s files in %2$s directories.', 'security-ninja'), number_format_i18n($wf_sn_ms_results['do_mal_scan']['files_scanned']), number_format_i18n($wf_sn_ms_results['do_mal_scan']['directories_scanned']));
			}

			echo '</p>';
		} else {
			echo '<p>' . __('Depending on the size of your website this can take several minutes.', 'security-ninja') . '</p>';
		}
		?>
		</div>

		<div class="sncard" id="sn_ms_cont">
		<div id="sn_ms_timer" style="display: none;"><span id="mscounterminutes">00</span>:<span id="mscounterseconds">00</span></div>

		<div id="sn_ms_results" class="testresults">

<?php

					// Generate fresh output instead of using cached HTML
		// This ensures tokens are always current
		$scanresults = self::generate_fresh_output();

		if ($scanresults) {
			echo wp_kses_post($scanresults);

				$whitelist = get_option(WF_SN_MS_WHITELIST, array());

				if ($whitelist) {

			?>
					<div class="whitelistcont card">
						<h3>
							<?php
							$count = count($whitelist);
							printf(esc_html(_n('%d whitelisted file', '%d whitelisted files', intval($count), 'security-ninja')), $count);
							?>
						</h3>
						<ul class="whitelistedfiles">
							<?php

							foreach ($whitelist as $wf) {

								$parts            = explode('/', $wf['filename']);
								$last_part        = array_pop($parts);
								$second_last_part = array_pop($parts);
								$third_last_part  = array_pop($parts);
								$fourth_last_part = array_pop($parts);

								if ($fourth_last_part) {
									$filename_title = '.../' . $fourth_last_part . '/' . $third_last_part . '/' . $second_last_part . '/' . $last_part;
								} elseif ($third_last_part) {
									$filename_title = '.../' . $third_last_part . '/' . $second_last_part . '/' . $last_part;
								} elseif ($second_last_part) {
									$filename_title = '.../' . $second_last_part . '/' . $last_part;
								} else {
									$filename_title = '/' . $last_part;
								}

								echo self::format_result_line(
									$filename_title,
									$wf['filename'],
									$wf['hash'],
									'',
									null,
									null,
									false,
									true,
									false,
									$wf['filename'],
									true,
									true
								);
							}
							?>
						</ul>
						<p>
							<?php
							_e('These files are ignored from scans.', 'security-ninja');
							?>
						</p>
						<?php
						echo '<a href="#" id="sn-ms-reset-whitelist" class="button button-secondary">' . __('Reset whitelisted files', 'security-ninja') . '</a>';
						?>
					</div>
				<?php
				}

				?>
				<div id="sn_ms_results_detailed">
					<div class="card">
						<?php
						$last_results = self::get_results();

						if (isset($last_results['do_integrity_scan']['validated_plugins'])) {
							$validated = $last_results['do_integrity_scan']['validated_plugins'];

							if (is_array($validated)) {
						?>
								<h3>Plugins validated</h3>
								<p>These plugins were compared against the info in the public repository. There were no modifications detected.</p>
								<ul class="known-public-plugins">
									<?php
									foreach ($validated as $key => $plugin) {
										echo '<li><strong>' . esc_html($plugin['Name']) . '</strong> by ' . htmlspecialchars($plugin['Author']) . ' (v. ' . esc_html($plugin['Version']) . ')</li>';
									}
									?>
								</ul>
						<?php
							}
						}
						?>
					</div>

					<?php
					if (isset($last_results['do_integrity_scan']['checksums_retrieval_failed'])) {
						$failed = $last_results['do_integrity_scan']['checksums_retrieval_failed'];
						if (is_array($failed) && (count($failed) > 0)) {
					?>
							<div class="card">
								<h3>Plugins not in wordpress.org repository</h3>
								<p>These plugins were not found on the public repository. Perhaps it is a premium plugin or custom made.</p>
								<p>These plugins were scanned for malware.</p>
								<ul class="unknown-public-plugins">
									<?php
									// Filters out plugin names we do not want to show up.
									$ignore_arr = array('Security Ninja Pro', 'Security Ninja (Premium)');
									foreach ($failed as $key => $plugin) {
										if (! in_array($plugin['Name'], $ignore_arr, true)) {
									?>
											<li><a href="<?php echo esc_url($plugin['PluginURI']); ?>" target="_blank"><?php echo esc_html($plugin['Name']); ?></a> by <?php echo esc_html($plugin['Author']); ?> v. <?php echo esc_html($plugin['Version']); ?></li>
									<?php
										}
									}
									?>
								</ul>

							</div>
					<?php
						}
					}
					?>

				</div><!-- .details_box -->
				<!--		</div>--><!-- #sn_ms_results_detailed -->
			<?php
			} else {
			?>

				<p><?php esc_html_e('Click the button to run the first test.', 'security-ninja'); ?></p>

			<?php
			}
			?>
		</div>

		<p class="description"><?php esc_html_e('Files are scanned using a heuristic analysis method that compares their content to patterns and code samples often used by in malicious scripts. It is very important to understand that a file marked by Malware Scanner as suspicious does NOT have contain "bad" code. It might just be code that looks bad.', 'security-ninja'); ?></p>
		<p class="description"><?php esc_html_e("That's why it is very important to go through the files manually and have a look at the suspicious lines of code. If you are certain they are ok - whitelist the file.", 'security-ninja'); ?></p>
		<p class="description"><?php esc_html_e("Many popular plugins' and themes' files can appear on the scan list. Again this does not mean they will do harm to your site. It just means you need to have a closer look at their content.", 'security-ninja'); ?></p>
		</div>

<?php
	}








	/**
	 * Display warning if test were never run
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @return  void
	 */
	public static function warnings()
	{

		if (! wf_sn::is_plugin_page()) {
			return;
		}

		$memory = ini_get('memory_limit');
		if (wf_sn::is_plugin_page() && -1 !== (int) $memory && (int) $memory < 64 && false === stripos($memory, 'G')) {
			echo '<div class="secnin-notice notice notice-warning updated sn_ms_warning"><p>Your PHP memory limit is only ' . esc_html($memory) . 'MB. Malware Scanner may not be able to scan all your files. Please ask your host to increase the limit.</p></div>';
		}

		/*
																					$last_run = self::get_results('last_run');
																					if (!empty($last_run) && wf_sn::is_plugin_page() && (time() - 30 * 24 * 60 * 60) > $last_run) {
																					echo '<div class="secnin-notice notice notice-warning wf-sn-ms-not-run is-dismissible"><p>';
																					echo sprintf(esc_html__("Malware Scanner tests were not run for more than %s days. It's advisable to run them once in a while. Click 'Scan files' to run them now and check your files for malware.", 'security-ninja'), '30');
																					echo '</p></div>';
																					}
																					*/
	}






	public static function activate() {
		// Placeholder, needed for premium version - when running "Update database".
	}





	/**
	 * Clean-up when deactivated
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 28th, 2022.
	 * @access  public static
	 * @return  void
	 */
	public static function deactivate()
	{
		$centraloptions = Wf_Sn::get_options();
		if (! isset($centraloptions['remove_settings_deactivate'])) {
			return;
		}
		if ($centraloptions['remove_settings_deactivate']) {
			delete_option('wf_sn_ms_options');
			delete_option(WF_SN_MS_RESULTS);
			delete_option(WF_SN_MS_INTEGRITY_RESULTS);
			delete_option(WF_SN_MS_CACHE);
			delete_option(WF_SN_MS_WHITELIST);
			delete_option(WF_SN_MS_DELETELIST);
			delete_option(WF_SN_MS_SCAN_TOTAL_TIME);
			delete_option(WF_SN_MS_SK);
			delete_option(WF_SN_MS_IV);
			delete_option(WF_SN_MS_LAST_PATTERN_UPDATE);
		}
		// Removes scheduled event to download samples
		$timestamp = wp_next_scheduled('secnin_update_samples');

		if ($timestamp) {
			wp_unschedule_event($timestamp, 'secnin_update_samples');
		}

		// remove the upload folder with cached files.
		$upload_dir = wp_upload_dir();
		$json_dir   = $upload_dir['basedir'] . '/security-ninja/';

		if (file_exists($json_dir)) {
			$it    = new \RecursiveDirectoryIterator($json_dir, \RecursiveDirectoryIterator::SKIP_DOTS);
			$files = new \RecursiveIteratorIterator($it, \RecursiveIteratorIterator::CHILD_FIRST);
			foreach ($files as $file) {
				if ($file->isDir()) {
					rmdir($file->getRealPath());
				} else {
					unlink($file->getRealPath());
				}
			}
			rmdir($json_dir);
		}
	}
}

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