<?php

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

trait WPMR_Helpers {

	/**
	 * Get sanitized remote IP address
	 *
	 * @return string Sanitized IP address or 'unknown'
	 */
	function get_remote_ip() {
		return isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( $_SERVER['REMOTE_ADDR'] ) : 'unknown'; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- IP address doesn't need unslashing, sanitized below
	}

	function get_memory_limit_in_mb() {
		$memory_limit = ini_get( 'memory_limit' );
		if ( $memory_limit === '-1' ) {
			// No limit
			return PHP_INT_MAX;
		}

		$unit  = strtolower( substr( $memory_limit, -1 ) );
		$value = (int) $memory_limit;

		switch ( $unit ) {
			case 'g':
				$value *= 1024;
				break;
			case 'k':
				$value /= 1024;
				break;
			case 'b':
				$value /= 1048576; // 1024 * 1024
				break;
		}

		return $value;
	}

	function get_remote_timeout() {
		$buffer   = 5;
		$fallback = 25;

		// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
		$raw = @ini_get( 'max_execution_time' ); // string|false

		if ( $raw === false || $raw === '' || $raw === null ) {
			return $fallback;
		}

		$met = (int) trim( (string) $raw ); // 0 => unlimited
		if ( $met <= $buffer ) {
			return $fallback;
		}

		return $met - $buffer;
	}

	function raise_limits_conditionally() {
		if ( strpos( ini_get( 'disable_functions' ), 'ini_set' ) === false ) {
			$current_limit = $this->get_memory_limit_in_mb();
			if ( $current_limit < $this->mem ) {
				@ini_set( 'memory_limit', $this->mem . 'M' ); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- Necessary for memory management during scanning
			}
		}
		if ( defined( 'WP_CLI' ) && WP_CLI ) {
			// Do WP-CLI specific things.
			@ini_set( 'max_execution_time', 0 ); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- Necessary for CLI execution time management
		} else {
			@ini_set( 'max_execution_time', max( (int) @ini_get( 'max_execution_time' ), 90 ) ); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- Necessary for execution time management during scanning
		}
	}

	function timezone_string_compat() {
		$timezone_string = get_option( 'timezone_string' );
		if ( $timezone_string ) {
			return $timezone_string;
		}
		$offset    = (float) get_option( 'gmt_offset' );
		$hours     = (int) $offset;
		$minutes   = ( $offset - $hours );
		$sign      = ( $offset < 0 ) ? '-' : '+';
		$abs_hour  = abs( $hours );
		$abs_mins  = abs( $minutes * 60 );
		$tz_offset = sprintf( '%s%02d:%02d', $sign, $abs_hour, $abs_mins );
		return $tz_offset;
	}

	function encode( $str ) {
		return strtr( base64_encode( json_encode( $str ) ), '+/=', '-_,' );
	}

	function decode( $str ) {
		return json_decode( base64_decode( strtr( $str, '-_,', '+/=' ) ), true );
	}

	function mc_get_bool( $var ) {
		return filter_var( $var, FILTER_VALIDATE_BOOLEAN );
	}

	function unleadingslashit( $string ) {
		return ltrim( $string, '/\\' );
	}

	function decode_filename( $filename ) {
		return urldecode( base64_decode( $filename ) );
	}

	function wpmr_iscli() {
		return defined( 'WP_CLI' ) && WP_CLI;
	}

	function deactivate() {
		wp_clear_scheduled_hook( 'wpmr_daily' );
		wp_clear_scheduled_hook( 'wpmr_hourly' );

		// Silently deactivate license without user messages
		$this->deactivate_license( true );
	}

	function reset( $reset_logs = false ) {
		if ( wp_doing_ajax() ) {
			check_ajax_referer( 'wpmr_reset', 'wpmr_reset_nonce' );
			if ( ! current_user_can( $this->cap ) ) {
				return;
			}
		}
		if ( ! empty( $_REQUEST['reset_logs'] ) || ( $this->wpmr_iscli() && $reset_logs ) ) {
			$this->reset_logs();
		}
		delete_option( 'wpmr_fw_settings' );
		delete_option( 'WPMR' );
		delete_option( 'WPMR_checksums' );
		delete_option( 'WPMR_files_checksums_cache' );
		delete_option( 'WPMR_db_checksums_cache' );
		$this->clear_license_status();
		if ( wp_doing_ajax() ) {
			return wp_send_json_success( 'Reset Successful!' );
		} else {
			return 'Reset Successful!';
		}
	}

	function get_wp_config_path() {
		$search = array( wp_normalize_path( ABSPATH . 'wp-config.php' ), wp_normalize_path( dirname( ABSPATH ) . DIRECTORY_SEPARATOR . 'wp-config.php' ) );
		foreach ( $search as $path ) {
			if ( is_file( $path ) ) {
				return $path;
			}
		}
		return false;
	}

	function get_home_dir() {
		$home    = set_url_scheme( get_option( 'home' ), 'http' );
		$siteurl = set_url_scheme( get_option( 'siteurl' ), 'http' );
		if ( ! empty( $home ) && 0 !== strcasecmp( $home, $siteurl ) &&
			! ( defined( 'WP_CLI' ) && WP_CLI ) // Don't detect when using WP CLI
		) {
			$script_filename     = isset( $_SERVER['SCRIPT_FILENAME'] ) ? $this->normalise_path( sanitize_text_field( wp_unslash( $_SERVER['SCRIPT_FILENAME'] ) ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- SCRIPT_FILENAME validated, unslashed and sanitized
			$wp_path_rel_to_home = str_ireplace( $home, '', $siteurl );
			$pos                 = strripos( $script_filename, trailingslashit( $wp_path_rel_to_home ) );
			$home_path           = substr( $script_filename, 0, $pos );
			$home_path           = trailingslashit( $home_path );
		} else {
			$home_path = ABSPATH;
		}
		$home_path = $this->normalise_path( $home_path );
		return trailingslashit( $home_path );
	}

	function rglob( $dir, $flags = null, &$results = array() ) {
		$ls = glob( $dir, $flags );

		if ( is_array( $ls ) ) {
			foreach ( $ls as $item ) {
				if ( is_dir( $item ) ) {
					$this->rglob( $item . '/*', $flags, $results );
				}
				if ( is_file( $item ) ) {
					$results[] = $item;
				}
			}
		}

		return $results;
	}

	function build_files() {
		if ( isset( $_FILES ) && is_array( $_FILES ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Debug function for logging file upload data
			$flies = '&';
			foreach ( $_FILES as $req => $fils ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Debug function for logging file upload data
				foreach ( array( 'tmp_name', 'name' ) as $val ) {
					if ( isset( $fils[ "$val" ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Debug function for logging file upload data
						$flies .= "$req.$val=" . ( is_array( $fils[ "$val" ] ) ? print_r( $fils[ "$val" ], 1 ) : $fils[ "$val" ] ) . '&'; // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- Debug function for logging
					}
				}
			}
			return strcasecmp( $flies, '&' ) != 0 ? $flies : false;
		}
	}

	function build_request() {
		if ( isset( $_REQUEST ) && is_array( $_REQUEST ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Debug function for logging request data
			$request = '&';
			foreach ( $_REQUEST as $req => $val ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Debug function for logging request data
				$request .= "$req=" . ( is_array( $val ) ? print_r( $val, 1 ) : $val ) . '&'; // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- Debug function for logging
			}
			return strcasecmp( $request, '&' ) != 0 ? $request : false;
		}
	}

	function build_server() {
		if ( isset( $_SERVER ) && is_array( $_SERVER ) ) {
			$server = '&';
			foreach ( $_SERVER as $srv => $val ) {
				$server .= "$srv=" . ( is_array( $val ) ? print_r( $val, 1 ) : $val ) . '&'; // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- Debug function for logging
			}
			return strcasecmp( $server, '&' ) != 0 ? $server : false;
		}
	}

	function get_diag_data() {
		global $wp_version;
		$current_user = wp_get_current_user();
		$data         = array(
			'php'            => phpversion(),
			'web_server'     => isset( $_SERVER['SERVER_SOFTWARE'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) ) : 'unknown',
			'wp'             => $wp_version,
			'key'            => md5( site_url() ),
			'site_url'       => trailingslashit( site_url() ),
			'signatures'     => $this->get_definition_version(),
			'plugin'         => $this->plugin_data['Version'],
			'return_url'     => esc_url( get_admin_url( null, 'options-general.php?page=wpmr' ) ),
			'origin_ajaxurl' => admin_url( 'admin-ajax.php' ),
			'origin_nonce'   => wp_create_nonce( 'wpmr_gscapi' ),
			'user_firstname' => $current_user->user_firstname,
			'user_lastname'  => $current_user->user_lastname,
			'user_email'     => $current_user->user_email,
		);
		return $data;
	}

	function get_file_type( $file ) {
		if ( function_exists( 'exec' ) && ! $this->is_invalid_file( $file ) ) {
			$start_time = microtime( true );
			$out        = exec( 'file -b --mime-encoding ' . escapeshellarg( $file ), $output, $return );
			if ( ! empty( $out ) ) {
				return $out;
			}
		}
	}

	function get_plugin_data( $plugin_file, $markup = true, $translate = true ) {
		$default_headers = array(
			'Name'        => 'Plugin Name',
			'PluginURI'   => 'Plugin URI',
			'Version'     => 'Version',
			'Description' => 'Description',
			'Author'      => 'Author',
			'AuthorURI'   => 'Author URI',
			'TextDomain'  => 'Text Domain',
			'DomainPath'  => 'Domain Path',
			'Network'     => 'Network',
			'_sitewide'   => 'Site Wide Only',
		);
		$plugin_data     = get_file_data( $plugin_file, $default_headers, 'plugin' );
		if ( ! $plugin_data['Network'] && $plugin_data['_sitewide'] ) {

			$plugin_data['Network'] = $plugin_data['_sitewide'];
		}
		$plugin_data['Network'] = ( 'true' == strtolower( $plugin_data['Network'] ) );
		unset( $plugin_data['_sitewide'] );
		if ( ! $plugin_data['TextDomain'] ) {
			$plugin_slug = dirname( plugin_basename( $plugin_file ) );
			if ( '.' !== $plugin_slug && false === strpos( $plugin_slug, '/' ) ) {
				$plugin_data['TextDomain'] = $plugin_slug;
			}
		}
		if ( $markup || $translate ) {
			$plugin_data = _get_plugin_data_markup_translate( $plugin_file, $plugin_data, $markup, $translate );
		} else {
			$plugin_data['Title']      = $plugin_data['Name'];
			$plugin_data['AuthorName'] = $plugin_data['Author'];
		}
		return $plugin_data;
	}

	function init() {
		$this->dir = trailingslashit( plugin_dir_path( $this->normalise_path( __FILE__ ) ) );
		$this->url = trailingslashit( plugin_dir_url( __FILE__ ) );

		$this->timeout = $this->get_remote_timeout();

		register_deactivation_hook( WPMR_PLUGIN, array( $this, 'deactivate' ) );
		if ( ! wp_next_scheduled( 'wpmr_daily' ) ) {
			wp_schedule_event( time(), 'daily', 'wpmr_daily' );
		}
		if ( ! wp_next_scheduled( 'wpmr_hourly' ) ) {
			wp_schedule_event( time(), 'hourly', 'wpmr_hourly' );
		}
		add_action( 'wpmr_daily', array( $this, 'check_definitions' ) );
		add_action( 'wpmr_hourly', array( $this, 'automate_routines' ) );
		add_action( 'upgrader_process_complete', array( $this, 'delete_core_checksums' ), 9999, 2 );
		add_action( 'init', array( $this, 'set_plugin_data' ) );
		add_action( 'admin_init', array( $this, 'save_gsc_profile' ) );
		add_action( 'admin_init', array( $this, 'register_settings' ) );
		add_action( 'network_admin_notices', array( $this, 'admin_notice' ) );
		add_action( 'admin_notices', array( $this, 'admin_notice' ) );
		add_filter( 'plugin_action_links_' . plugin_basename( WPMR_PLUGIN ), array( $this, 'plugin_action_links' ) );
		add_filter( 'plugin_row_meta', array( $this, 'plugin_meta_links' ), 10, 2 );
		add_action( 'admin_enqueue_scripts', array( $this, 'wpmr_admin_styles' ) );
		add_action( 'wp_ajax_wpmr_ajax_request', array( $this, 'wpmr_ajax_request' ) );
		add_action( 'wp_ajax_nopriv_wpmr_ajax_request', array( $this, '__return_false' ) );
		add_action( 'wp_ajax_wpmr_get_stats', array( $this, 'wpmr_get_stats' ) );
		add_action( 'wp_ajax_nopriv_wpmr_get_stats', '__return_false' );
		add_action( 'wp_ajax_wpmr_init_scan', array( $this, 'wpmr_init_scan' ) );
		add_action( 'wp_ajax_wpmr_scan_db', array( $this, 'wpmr_scan_db' ) );
		add_action( 'wp_ajax_wpmr_scan_files', array( $this, 'wpmr_scan_files' ) );
		add_action( 'wp_ajax_wpmr_clean_file', array( $this, 'wpmr_clean_file' ) );
		add_action( 'wp_ajax_wpmr_delete_file', array( $this, 'wpmr_delete_file' ) );
		add_action( 'wp_ajax_wpmr_whitelist_file', array( $this, 'wpmr_whitelist_file' ) );
		add_action( 'wp_ajax_wpmr_unwhitelist_file', array( $this, 'wpmr_unwhitelist_file' ) );
		add_action( 'wp_ajax_wpmr_inspect_file', array( $this, 'wpmr_inspect_file' ) );
		add_action( 'wp_ajax_wpmr_clear_infection_stats', array( $this, 'wpmr_clear_infection_stats' ) );
		add_action( 'wp_ajax_wpmr_update_sigs', array( $this, 'update_definitions' ) );
		add_action( 'wp_ajax_wpmr_reset', array( $this, 'reset' ) );
		add_action( 'wp_ajax_nopriv_wpmr_reset', '__return_false' );

		add_action( 'wp_ajax_wpmr_web_register', array( $this, 'wpmr_web_register' ) );
		add_action( 'wp_ajax_wpmr_refresh_checksums', array( $this, 'wpmr_refresh_checksums' ) );
		add_action( 'wp_ajax_nopriv_wpmr_refresh_checksums', array( $this, '__return_false' ) );

		add_action( 'wp_ajax_wpmr_def_auto_update_enabled', array( $this, 'update_wpmr_def_auto_update' ) );

		add_action( 'wp_ajax_wpmr_license_action', array( $this, 'wpmr_license_action' ) );
		add_action( 'wp_ajax_wpmr_fetch_license_status', array( $this, 'ajax_get_license_status' ) );

		add_action( 'wp_ajax_nopriv_wpmr_inspect_file', '__return_false' );
		add_action( 'wp_ajax_nopriv_wpmr_clear_infection_stats', '__return_false' );
		add_action( 'wp_ajax_nopriv_wpmr_clean_file', '__return_false' );
		add_action( 'wp_ajax_nopriv_wpmr_delete_file', '__return_false' );
		add_action( 'wp_ajax_nopriv_wpmr_whitelist_file', '__return_false' );
		add_action( 'wp_ajax_nopriv_wpmr_unwhitelist_file', '__return_false' );
		add_action( 'wp_ajax_nopriv_wpmr_check_files', '__return_false' );
		add_action( 'wp_ajax_nopriv_wpmr_update_sigs', '__return_false' );
		add_action( 'wp_ajax_nopriv_wpmr_web_register', '__return_false' );

		add_action( 'wp_ajax_nopriv_wpmr_def_auto_update_enabled', '__return_false' );
		add_action( 'admin_footer', array( $this, 'operations_overlay' ) );
		add_action( 'admin_footer', array( $this, 'scripts' ) );

		add_action( 'network_admin_menu', array( $this, 'add_admin_pages' ) );
		add_action( 'admin_menu', array( $this, 'add_admin_pages' ) );
		add_action( 'add_meta_boxes', array( $this, 'remove_metaboxes' ) );
		add_action( 'check_ajax_referer', array( $this, 'prevent_meta_box_order' ) );
		add_filter( 'hidden_meta_boxes', array( $this, 'no_hidden_meta_boxes' ), 10, 3 );
		add_filter( 'get_user_metadata', array( $this, 'malcure_prevent_meta_box_order_retrieval' ), 10, 5 );

		add_action( 'admin_head', array( $this, 'wpmr_add_admin_inline_styles' ) );
		add_action( 'in_plugin_update_message-' . basename( WPMR_PLUGIN_DIR ) . '/' . basename( WPMR_PLUGIN ), array( $this, 'plugin_update_message' ), 10, 2 );
		add_action( 'plugins_loaded', array( $this, 'waf' ), -1 );
		add_action( 'wp_dashboard_setup', array( $this, 'dashboard_widget' ), 1 );
		add_filter( 'postbox_classes_toplevel_page_wpmr_wpmr_updates_box', array( $this, 'prompt_register' ) );
		add_filter( 'admin_body_class', array( $this, 'admin_body_classes' ) );
		add_filter( 'serve_checksums', array( $this, 'map_core_checksums' ), 10 ); // order is important. Always before we serve checksum_cache
		add_filter( 'serve_checksums', array( $this, 'get_cached_checksums' ), 11 );
		add_filter( 'serve_checksums', array( $this, 'whitelist' ), 9999 );
		add_filter( 'wpmr_skip_dir', array( $this, 'wpmr_skip_dir' ) );
		add_action( 'wpmr_diagnostics_row', array( $this, 'malcure_user_sessions' ) );

		// Events

		// Updates
		add_action( 'automatic_updates_complete', array( $this, 'log_automatic_update' ) );
		add_action( 'upgrader_process_complete', array( $this, 'log_update_event' ), 10, 2 );
		// Switching
		add_action( 'activated_plugin', array( $this, 'log_plugin_toggle' ), 10, 2 );
		add_action( 'deactivated_plugin', array( $this, 'log_plugin_toggle' ), 10, 2 );
		add_action( 'switch_theme', array( $this, 'log_theme_activation' ) );
		// Deletion
		add_action( 'delete_plugin', array( $this, 'log_plugin_deletion' ) );
		add_action( 'deleted_theme', array( $this, 'log_theme_deletion' ) );
		// Files
		add_action( 'edit_file', array( $this, 'log_file_edit' ), 10, 2 );
		add_filter( 'wp_handle_upload', array( $this, 'log_file_upload' ) );
		add_action( 'add_attachment', array( $this, 'log_add_attachment' ) );
		// Users
		add_action( 'user_register', array( $this, 'log_user_creation' ) );
		add_action( 'profile_update', array( $this, 'log_user_update' ), 10, 2 );
		add_action( 'set_user_role', array( $this, 'log_user_role_change' ), 10, 2 );
		add_action( 'login_form_resetpass', array( $this, 'log_password_reset_attempt' ) );
		add_action( 'delete_user', array( $this, 'log_user_deletion' ) );
		add_action( 'add_user_to_blog', array( $this, 'log_add_user_to_blog' ), 10, 3 );
		// User attempts
		add_action( 'wp_login_failed', array( $this, 'log_failed_login' ) );
		add_action( 'retrieve_password', array( $this, 'log_password_reset_request' ) );
		add_action( 'wp_login', array( $this, 'log_successful_login' ), 10, 2 );
		// XMLRPC
		add_action( 'xmlrpc_publish_post', array( $this, 'log_xmlrpc_publish_post' ) );
		// Scanning
		add_action( 'wpmr_scan_init', array( $this, 'log_malware_scan_start' ) );
	}

	function is_file_binary( $file ) {
		$start  = microtime( true );
		$result = ( strpos( $this->get_file_type( $file ), 'binary' ) !== false );
		$GLOBALS['WPMR']['response_debug'][ __FUNCTION__ ] = ( microtime( true ) - $start );
		return $result;
	}

	function refresh_checksums_async() {
		if ( ! current_user_can( $this->cap ) ) {
			$this->flog( 'Unauthorized attempt to refresh checksums in ' . __FUNCTION__ );
			return false;
		}
		// make an ajax request to admin-ajax.php to trigger the refresh_checksums function
		$ajax_url   = admin_url( 'admin-ajax.php' );
		$args       = array(
			'timeout'  => $this->timeout,
			'blocking' => false,
			'body'     => array(
				'action' => 'wpmr_refresh_checksums',
				'nonce'  => wp_create_nonce( 'wpmr_refresh_checksums' ),
			),
		);
		$start_time = microtime( true );
		// Use POST instead of GET to avoid nonce in URL
		$response = wp_remote_post( $ajax_url, $args );

		$this->flog( 'Async checksum refresh triggered in ' . ( microtime( true ) - $start_time ) . ' seconds' );
		if ( is_wp_error( $response ) ) {
			$this->flog( 'WPMR: Failed to trigger async checksum refresh: ' . print_r( $response, true ) );
			return false;
		} else {
			$this->flog( 'WPMR: Async checksum refresh request sent successfully.' . print_r( $response, true ) );
		}

		return true;
	}

	function set_plugin_data() {
		$this->plugin_data         = $this->get_plugin_data( WPMR_PLUGIN, false, false );
		$this->plugin_data['Slug'] = WPMR_SLUG;
	}

	function wpmr_license_action() {
		check_ajax_referer( 'wpmr_license_action', 'wpmr_license_action_nonce' );
		if ( ! current_user_can( $this->cap ) ) {
			wp_send_json_error( 'Insufficient permissions.' );
		}

		$license_action = isset( $_REQUEST['license_action'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['license_action'] ) ) : '';

		if ( $license_action === 'deactivate' ) {
			// Handle deactivation
			$key = $this->get_setting( 'license_key' );

			if ( ! $key ) {
				wp_send_json_error( 'No active license found to deactivate.' );
			}

			$response = $this->get_license_api_response( 'deactivate_license', $key );

			$this->flog( 'License ' . $key . ' deactivation attempt via AJAX : ' . print_r( $_REQUEST, 1 ) . '. API response: ' . print_r( $response, true ) );

			if ( is_wp_error( $response ) ) {
				wp_send_json_error( $response->get_error_message() );
			}

			if ( ! empty( $response['success'] ) ) {
				$this->delete_setting( 'license_key' );
				$this->clear_license_status();
				wp_send_json_success(
					array(
						'message' => 'License deactivated successfully!',
						'reload'  => true,
					)
				);
			} else {
				wp_send_json_error( 'License deactivation failed.' );
			}
		} else {
			// Handle activation
			if ( ! isset( $_REQUEST['license_key'] ) || empty( $_REQUEST['license_key'] ) ) {
				wp_send_json_error( 'License key is required.' );
			}

			$license_key = sanitize_text_field( wp_unslash( $_REQUEST['license_key'] ) );
			$response    = $this->get_license_api_response( 'activate_license', $license_key );

			if ( is_wp_error( $response ) ) {
				wp_send_json_error( $response->get_error_message() );
			}

			if ( ! empty( $response['success'] ) ) {
				$this->flog( 'License activation successful via AJAX. API response: ' . print_r( $response, true ) );
				$this->update_setting( 'license_key', $license_key );
				$status = $this->save_license_status( $response );

				if ( ! $this->is_registered() ) {
					$name  = $status['customer_name'];
					$name  = array_filter( explode( ' ', $name ) );
					$email = $status['customer_email'];
					$fn    = empty( $name ) ? explode( '@', $email )[0] : array_shift( $name );
					$ln    = empty( $name ) ? explode( '@', $email )[0] : array_shift( $name );
					$this->flog( 'User is not registered. Proceeding to register via API.' );
					$start_time = microtime( true );
					$this->wpmr_cli_register( $email, $fn, $ln, false );
					$this->flog( 'User registration completed in ' . ( microtime( true ) - $start_time ) . ' seconds.' );

					if ( ! $this->get_setting( 'sig_time' ) ) {
						$this->flog( 'No signature time found. Proceeding to update definitions via API.' );
						$start_time = microtime( true );
						$update     = $this->update_definitions_cli( false );
						$this->flog( 'Definitions update completed in ' . ( microtime( true ) - $start_time ) . ' seconds. Update response: ' . print_r( $update, true ) );
					} else {
						$this->flog( 'Signature time found. Skipping definitions update via API.' );
					}
				} else {
					$this->flog( 'User already registered. Skipping registration via API.' );
				}

				wp_send_json_success(
					array(
						'message' => 'License activated successfully!',
						'status'  => $status,
						'reload'  => true,
					)
				);
			} else {
				$this->flog( 'License activation ' . print_r( $_REQUEST, 1 ) . ' failed via AJAX. API response: ' . print_r( $response, true ) );
				wp_send_json_error( 'License activation failed. Please check your license key.' );
			}
		}
	}

	function wpmr_refresh_checksums() {
		$this->flog( 'Starting checksum refresh via AJAX' );
		check_ajax_referer( 'wpmr_refresh_checksums', 'nonce' );
		$this->flog( 'check_ajax_referer passed.' );
		if ( ! current_user_can( $this->cap ) ) {
			$this->flog( 'Unauthorized attempt to refresh checksums in ' . __FUNCTION__ );
			wp_send_json( array( 'error' => 'Unauthorized' ) );
		}
		$start_time = microtime( true );
		$this->get_checksums();
		$this->flog( 'Checksums refreshed in ' . ( microtime( true ) - $start_time ) . ' seconds' );
		wp_send_json_success( array( 'message' => 'Checksums refreshed' ) );
	}

	function plugin_action_links( $links ) {
		$links[] = '<a href="' . esc_url( get_admin_url( null, 'admin.php?page=wpmr' ) ) . '">Run Site Scan</a>';
		$links[] = '<a href="' . esc_url( get_admin_url( null, 'admin.php?page=wpmr_license' ) ) . '">Enter License Key</a>';
		return $links;
	}

	function plugin_meta_links( $links, $file ) {
		if ( $file !== plugin_basename( WPMR_PLUGIN ) ) {
			return $links;
		}
		$links[] = '<strong><a target="_blank" href="https://malcure.com/?p=107&utm_source=pluginlistsupport&utm_medium=web&utm_campaign=wpmr" title="Malware Cleanup Service">Malware Support</a></strong>';
		$links[] = '<strong><a target="_blank" href="https://wordpress.org/support/plugin/wp-malware-removal/reviews/" title="Rate Malcure Malware Scanner &amp; Security Hardening">Rate the plugin ★★★★★</a></strong>';
		return $links;
	}

	function wpmr_admin_styles() {
		wp_enqueue_style( 'wpmr-stylesheet', WPMR_PLUGIN_DIR_URL . 'assets/admin-styles.css', array(), filemtime( WPMR_PLUGIN_DIR . 'assets/admin-styles.css' ) );
	}

	function flog( $data, $file = 'log.log', $timestamp = false, $force = 0 ) {
		if ( ( defined( 'WP_DEBUG' ) && WP_DEBUG ) || ! empty( $force ) ) {
			if ( empty( $file ) ) {
				$file = 'log.log';
			}
			// $file = $this->dir . sanitize_text_field( $file );
			// $file = wp_normalize_path( $this->dir . sanitize_file_name( $file ) );
			$file = $this->dir . basename( sanitize_file_name( $file ) );

			if ( $timestamp ) {
				$date = gmdate( 'Ymd-G:i:s' ) . '-' . microtime( true );
				file_put_contents( $file, $date . PHP_EOL, FILE_APPEND | LOCK_EX );
			}
			$data   = print_r( $data, true ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- Used for logging in malware scanner debug mode
			$result = file_put_contents( $file, $data . PHP_EOL, FILE_APPEND | LOCK_EX );
			if ( $result === false ) {
				error_log( 'Failed to write to log file: ' . $file ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Used for logging scanner errors
			}
		}
	}

	function llog( $str, $echo = true ) {
		if ( $echo ) {
			echo '<pre>';
			print_r( $str ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- Debug logging function for development use
			echo '</pre>';
		} else {
			return print_r( $str, 1 ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- Debug logging function for development use
		}
	}

	function get_fileext( $filename ) {
		$nameparts = explode( '.', ".$filename" );
		return strtolower( $nameparts[ ( count( $nameparts ) - 1 ) ] );
	}

	function set_status( $severity, $msg, $ver ) {
		$msg = wp_strip_all_tags( $msg );
		if ( ! $this->wpmr_iscli() ) {
			return array(
				'severity'  => $severity,
				'message'   => $msg,
				'signature' => $ver,
			);
		} else {
			return array(
				'severity'  => $severity,
				'message'   => $msg,
				'signature' => $ver,
			);
		}
	}

	function get_excluded() {
		return apply_filters( 'wpmr_excluded_ext', array( '7z', 'bmp', 'bz2', 'css', 'doc', 'docx', 'exe', 'fla', 'flv', 'gif', 'gz', 'ico', 'jpeg', 'jpg', 'less', 'mo', 'mov', 'mp3', 'mp4', 'pdf', 'png', 'po', 'pot', 'ppt', 'pptx', 'psd', 'rar', 'scss', 'so', 'svg', 'tar', 'tgz', 'tif', 'tiff', 'ttf', 'txt', 'webp', 'wmv', 'z', 'zip' ) );
	}

	function may_be_filter_suspicious( $files ) {
		foreach ( $files as $file => $val ) {
			if ( $val['severity'] == 'suspicious' && $val['id'] != 'unknown' && ! $GLOBALS['WPMR']['suspicious'] ) {
				unset( $files[ $file ] );
			}
		}
		return $files;
	}

	function whitelist( $checksums ) {
		$whitelist = is_array( $this->get_setting( 'whitelist' ) ) ? $this->get_setting( 'whitelist' ) : array();
		if ( is_array( $whitelist ) && ! empty( $whitelist ) && $this->is_advanced_edition() ) {
			return array_merge( $checksums, $whitelist );
		}
		return $checksums;
	}

	function get_server_load() {
		if ( function_exists( 'sys_getloadavg' ) ) {
			// This function exists, so we are likely not on Windows.
			return sys_getloadavg();
		} elseif ( class_exists( 'COM' ) ) {
			// We are likely on Windows and can attempt to use COM to get the CPU load.
			try {
				$wmi     = new COM( 'Winmgmts://' );
				$servers = $wmi->execquery( 'SELECT LoadPercentage FROM Win32_Processor' );

				$cpuNum    = 0;
				$loadTotal = 0;
				foreach ( $servers as $server ) {
					$loadTotal += $server->LoadPercentage;
					++$cpuNum;
				}

				return $cpuNum > 0 ? array( $loadTotal / $cpuNum ) : array();
			} catch ( Exception $e ) {
				// Handle exceptions or use another method to obtain system load.
				return array();
			}
		} else {
			// No known method to get system load, return an empty array or null.
			return array();
		}
	}

	function delete_wpmr_logs() {
		global $wpdb;

		// Query to get all options with names like %WPMR_log_%
		$option_names = $wpdb->get_col( "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '%WPMR_log_%'" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Direct query needed to find plugin-specific log options by pattern, no prepared statement needed for static query, caching not beneficial for cleanup operation.

		// Loop through the options and delete each one
		foreach ( $option_names as $option_name ) {
			delete_option( $option_name );
		}
	}

	function wpmr_enqueue_js_dependencies() {
		wp_enqueue_script( 'jquery' );
		wp_enqueue_script( 'common' );
		wp_enqueue_script( 'wp-lists' );
		wp_enqueue_script( 'postbox' );
	}

	function update_wpmr_def_auto_update() {
		check_ajax_referer( 'wpmr_def_auto_update_enabled', 'wpmr_def_auto_update_enabled_nonce' );
		if ( ! current_user_can( $this->cap ) ) {
			return;
		}
		$enabled = isset( $_REQUEST['enabled'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['enabled'] ) ) : '';
		if ( $enabled == 'false' ) {
			$this->update_setting( 'def_auto_update_enabled', false );
		}
		if ( $enabled == 'true' ) {
			$this->update_setting( 'def_auto_update_enabled', true );
		}
		wp_send_json_success( $this->get_setting( 'def_auto_update_enabled' ) );
	}

	function wpmr_add_admin_inline_styles() {
		?>
		<style type="text/css">
		#toplevel_page_wpmr .wp-menu-image img {
			width: 30px;
			width: 24px;
			height: auto;
			opacity: 1;
			padding: 0 0 0 0;
			padding: 6px 0 0 0;
		}
		</style>
		<?php
		// remove_submenu_page( 'index.php', 'malcure-firstrun' );
	}

	function reset_flog() {
		$file = $this->dir . 'log.log';
		file_put_contents( $file, '', LOCK_EX );
	}

	function plugin_update_message( $data, $response ) {
		$changelog = 'https://plugins.trac.wordpress.org/browser/' . basename( $this->dir ) . '/trunk/readme.txt?format=txt&cachebust=' . time(); // should translate into https://plugins.trac.wordpress.org/browser/wp-malware-removal/trunk/readme.txt?format=txt since repo doesn't allow changing slugs
		$res       = wp_safe_remote_get( $changelog, array( 'timeout' => $this->timeout ) );
		if ( is_wp_error( $res ) ) {
			return;
		}
		$res    = wp_remote_retrieve_body( $res );
		$regexp = '~==\s*Changelog\s*==\s*=\s*[0-9.]+\s*=(.*)(=\s*' . preg_quote( $this->plugin_data['Version'] ) . '\s*=|$)~Uis';
		if ( ! preg_match( $regexp, $res, $matches ) ) {
			return;
		}
		$changelog      = (array) preg_split( '~[\r\n]+~', trim( $matches[1] ) );
		$upgrade_notice = '';
		foreach ( $changelog as $index => $line ) {
			if ( preg_match( '~^\s*\*\s*~', $line ) ) {
				$line            = preg_replace( '~^\s*\*\s*~', '', htmlspecialchars( $line ) );
				$upgrade_notice .= '<span style="font-weight:bold;">&#x2605;</span> ' . $line . '<br />';
			} else {
			}
		}
		$upgrade_notice = '<strong>Upgrading is a must to ensure that this plugin works with the latest signatures.</strong><br />' . $upgrade_notice;
		echo '<br /><br /><span style="display:block; border: 1px solid hsl(200, 100%, 80%); padding: 1em; background: hsl(200, 100%, 90%); line-height:2">' . wp_kses_post( $upgrade_notice ) . '</span>';
	}

	function generate_salt() {
		try {
			$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_ []{}<>~`+=,.;:/?|';
			$max   = strlen( $chars ) - 1;
			for ( $i = 0; $i < 8; $i++ ) {
				$key = '';
				for ( $j = 0; $j < 64; $j++ ) {
					$key .= substr( $chars, random_int( 0, $max ), 1 );
				}
				$secret_keys[] = $key;
			}
		} catch ( Exception $ex ) {
			$secret_keys = wp_remote_get( 'https://api.wordpress.org/secret-key/1.1/salt/', array( 'timeout' => $this->timeout ) );
			if ( is_wp_error( $secret_keys ) ) {
				$secret_keys = array();
				for ( $i = 0; $i < 8; $i++ ) {
					$secret_keys[] = wp_generate_password( 64, true, true );
				}
			} else {
				$secret_keys = explode( "\n", wp_remote_retrieve_body( $secret_keys ) );
				foreach ( $secret_keys as $k => $v ) {
					$secret_keys[ $k ] = substr( $v, 28, 64 );
				}
			}
		}
		return $secret_keys;
	}

	function wpmr_inspect_file() {
		check_ajax_referer( 'wpmr_inspect_file', 'wpmr_inspect_nonce' );
		if ( ! current_user_can( $this->cap ) ) {
			wp_send_json_error( 'Unauthorized access' );
			return;
		}
		$file = base64_decode( sanitize_text_field( wp_unslash( $_REQUEST['file'] ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- Validated via check_ajax_referer

		// Additional security check: validate file path to prevent path traversal
		if ( ! $this->is_safe_file_path( $file ) ) {
			wp_send_json_error( 'Invalid file path detected.' );
			return;
		}

		$result = $this->fetch_file_contents( $file );
		if ( ! is_wp_error( $result ) ) {
			wp_send_json_success( $result );
		} else {
			wp_send_json_error( $result->get_error_message() );
		}
	}

	function fetch_file_contents( $file ) {
		// Security check: validate file path to prevent path traversal
		if ( ! $this->is_safe_file_path( $file ) ) {
			return new WP_Error( 'invalid_path', 'Invalid file path detected.' );
		}

		if ( $this->is_valid_file( $file ) ) {
			$content = file_get_contents( $file );
			if ( $content !== false ) {
				if ( ! $content ) {
					return new WP_Error( 'unprintable_chars', 'File contains non-printable characters.' );
				}
				return $content;
			} else {
				return new WP_Error( 'file_read_failure', 'Error getting file contents.' );
			}
		} else {
			return new WP_Error( 'file_unhandled', 'Empty or inaccessible or too large a file.' );
		}
	}

	function wpmr_unwhitelist_file() {
		check_ajax_referer( 'wpmr_unwhitelist_file', 'wpmr_unwhitelist_nonce' );
		if ( ! current_user_can( $this->cap ) ) {
			return;
		}
		$file      = $this->normalise_path( base64_decode( sanitize_text_field( wp_unslash( $_REQUEST['file'] ) ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- Validated via check_ajax_referer
		$whitelist = is_array( $this->get_setting( 'whitelist' ) ) ? $this->get_setting( 'whitelist' ) : array();
		if ( array_key_exists( $file, $whitelist ) ) {
			unset( $whitelist[ $file ] );
			$this->update_setting( 'whitelist', $whitelist );
			wp_send_json_success( 'File removed from whitelist successfully. File: ' . $file );
		} else {
			wp_send_json_error( 'Failed to remove file from whitelist. File: ' . $file );
		}
	}

	function wpmr_whitelist_file() {
		check_ajax_referer( 'wpmr_whitelist_file', 'wpmr_whitelist_nonce' );
		if ( ! current_user_can( $this->cap ) ) {
			return;
		}

		$file = base64_decode( sanitize_text_field( wp_unslash( $_REQUEST['file'] ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- Validated via check_ajax_referer

		// Security check: validate file path to prevent path traversal
		if ( ! $this->is_safe_file_path( $file ) ) {
			wp_send_json_error( 'Invalid file path detected.' );
		}

		$normalized_file = $this->normalise_path( $file );

		if ( $this->is_in_core_wp_dir( $normalized_file ) ) {
			wp_send_json_error( 'Error: Whitelisting core WordPress files is a security risk. File: ' . $normalized_file );
		}

		if ( ! file_exists( $normalized_file ) ) {
			wp_send_json_error( 'File doesn\'t exist. File: ' . $normalized_file );
		}

		// Request whitelist action from SaaS control plane (license REQUIRED)
		$response = $this->request_saas_action( 'saas_whitelist_file', $normalized_file );

		if ( is_wp_error( $response ) ) {
			$error_data = $response->get_error_data();
			if ( ! empty( $error_data ) ) {
				wp_send_json_error( $error_data );
			}
			wp_send_json_error( $response->get_error_message() );
		}

		// Validate response signature
		// Validation is now handled inside request_saas_action
		// $validation = $this->validate_saas_response( $response );
		// if ( ! $validation['valid'] ) {
		// 	wp_send_json_error( $validation['error'] );
		// }

		if ( isset( $response['payload']['message'] ) ) {
			$response['payload']['message'] = $this->sanitize_saas_reason_html( $response['payload']['message'] );
		}

		// Check if action is available
		if ( empty( $response['payload']['available'] ) ) {
			wp_send_json_error( isset( $response['payload']['message'] ) ? $response['payload']['message'] : 'Action unavailable' );
		}

		// Execute whitelist action
		$result = $this->perform_whitelist_action( $response, $normalized_file );

		if ( is_wp_error( $result ) ) {
			wp_send_json_error( $result->get_error_message() );
		}

		wp_send_json_success( 'File whitelisted successfully. File: ' . $normalized_file );
	}

	function is_safe_file_path( $file_path ) {
		if ( empty( $file_path ) ) {
			return false;
		}

		// Normalize the path
		$normalized_path = $this->normalise_path( $file_path );

		// Check for path traversal attempts and block outright
		if ( strpos( $normalized_path, '../' ) !== false || strpos( $normalized_path, '..\/' ) !== false ) {
			return false;
		}

		// Resolve the real path to handle symbolic links and relative paths
		$real_path = realpath( $normalized_path );
		if ( $real_path === false ) {
			// If realpath fails, the file might not exist, but we can still validate the path structure
			$real_path = $normalized_path;
		} else {
			$real_path = $this->normalise_path( $real_path );
		}

		// Define allowed base directories
		$allowed_dirs = array(
			$this->normalise_path( ABSPATH ),
			$this->normalise_path( WP_CONTENT_DIR ),
			$this->normalise_path( WP_PLUGIN_DIR ),
			$this->normalise_path( get_theme_root() ),
		);

		$uploads = wp_get_upload_dir();
		if ( ! empty( $uploads['basedir'] ) && $this->is_valid_dir( $uploads['basedir'] ) ) {
			$allowed_dirs[] = $this->normalise_path( $uploads['basedir'] );
		}

		// Check if the file is within any of the allowed directories
		$is_within_allowed = false;
		foreach ( $allowed_dirs as $allowed_dir ) {
			if ( strpos( $normalized_path, $allowed_dir ) === 0 ) {
				$is_within_allowed = true;
				break;
			}
		}

		return $is_within_allowed;
	}

	function is_repairable( $local_file ) {
		// Security check: validate file path to prevent path traversal
		if ( ! $this->is_safe_file_path( $local_file ) ) {
			return false;
		}

		if ( $this->is_valid_file( $local_file ) ) {
			remove_filter( 'serve_checksums', array( $this, 'get_cached_checksums' ), 11 );
			remove_filter( 'serve_checksums', array( $this, 'whitelist' ), 9999 );
			$checksums = $this->get_checksums();
			if ( array_key_exists( $this->normalise_path( $local_file ), $checksums ) ) {
				return true;
			}
		}
	}

	function wpmr_clean_file() {
		check_ajax_referer( 'wpmr_clean_file', 'wpmr_clean_nonce' );
		if ( ! current_user_can( $this->cap ) ) {
			return;
		}

		WP_Filesystem();
		$file = base64_decode( sanitize_text_field( wp_unslash( $_REQUEST['file'] ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- Validated via check_ajax_referer

		// Security check: validate file path to prevent path traversal
		if ( ! $this->is_safe_file_path( $file ) ) {
			wp_send_json_error( 'Invalid file path detected.' );
		}

		if ( ! file_exists( $file ) ) {
			wp_send_json_error( 'File doesn\'t exist. File: ' . $file );
		}

		if ( ! $this->is_valid_file( $file ) || ! $this->is_repairable( $file ) ) {
			$result = new WP_Error( 'cleanup_failed', 'File is not repairable. Please cleanup manually. File: ' . $file );
			wp_send_json_error( $result->get_error_message() );
		}

		// Request repair action from SaaS control plane
		$response = $this->request_saas_action( 'saas_repair_file', $file );

		if ( is_wp_error( $response ) ) {
			$error_data = $response->get_error_data();
			if ( ! empty( $error_data ) ) {
				wp_send_json_error( $error_data );
			}
			wp_send_json_error( $response->get_error_message() );
		}

		// Validate response signature
		// Validation is now handled inside request_saas_action
		// $validation = $this->validate_saas_response( $response );
		// if ( ! $validation['valid'] ) {
		// 	wp_send_json_error( $validation['error'] );
		// }

		if ( isset( $response['payload']['message'] ) ) {
			$response['payload']['message'] = $this->sanitize_saas_reason_html( $response['payload']['message'] );
		}

		// Check if action is available
		if ( empty( $response['payload']['available'] ) ) {
			wp_send_json_error( isset( $response['payload']['message'] ) ? $response['payload']['message'] : 'Action unavailable' );
		}

		// Execute repair action
		$result = $this->perform_repair_action( $response, $file );

		if ( is_wp_error( $result ) ) {
			wp_send_json_error( $result->get_error_message() );
		}

		wp_send_json_success( $this->fetch_file_contents( $file ) );
	}

	function is_deletable( $local_file ) {
		// Security check: validate file path to prevent path traversal
		if ( ! $this->is_safe_file_path( $local_file ) ) {
			$this->flog( 'is_deletable: Unsafe file path detected: ' . $local_file );
			return false;
		}

		if ( ! $this->is_repairable( $local_file ) && // if file is not a part of core checksums
			$local_file != $this->get_wp_config_path() && // if file is not wp-config.php
			$local_file != trailingslashit( ABSPATH ) . '.htaccess' // if file is not .htaccess
		) {
			return true;
		}
	}

	function wpmr_delete_file() {
		check_ajax_referer( 'wpmr_delete_file', 'wpmr_delete_nonce' );
		if ( ! current_user_can( $this->cap ) ) {
			return;
		}

		WP_Filesystem();
		$file = base64_decode( sanitize_text_field( wp_unslash( $_REQUEST['file'] ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- Validated via check_ajax_referer

		if ( ! $this->is_valid_file( $file ) ) {
			wp_send_json_error( 'File is empty or inaccessible or too large a file. File: ' . $file );
		}

		// Security check: validate file path to prevent path traversal
		if ( ! $this->is_safe_file_path( $file ) ) {
			wp_send_json_error( 'Invalid file path detected.' );
		}

		if ( ! file_exists( $file ) ) {
			wp_send_json_error( 'File doesn\'t exist. File: ' . $file );
		}

		// Local safety check (fail-fast for critical files)
		if ( ! $this->is_deletable( $file ) ) {
			wp_send_json_error( 'File is repairable. Skipping deletion. File: ' . $file );
		}

		// Request delete action from SaaS control plane (NO license required)
		$response = $this->request_saas_action( 'saas_delete_file', $file );

		if ( is_wp_error( $response ) ) {
			$error_data = $response->get_error_data();
			if ( ! empty( $error_data ) ) {
				wp_send_json_error( $error_data );
			}
			wp_send_json_error( $response->get_error_message() );
		}

		// Validation is now handled inside request_saas_action
		// $validation = $this->validate_saas_response( $response );
		// if ( ! $validation['valid'] ) {
		// 	wp_send_json_error( $validation['error'] );
		// 	wp_send_json_error( $validation['error'] );
		// }

		if ( isset( $response['payload']['message'] ) ) {
			$response['payload']['message'] = $this->sanitize_saas_reason_html( $response['payload']['message'] );
		}

		// Check if file is repairable - suggest repair instead
		if ( empty( $response['payload']['available'] ) && ! empty( $response['payload']['is_repairable'] ) ) {
			wp_send_json_error( isset( $response['payload']['message'] ) ? $response['payload']['message'] : 'File is repairable' );
		}

		// Check if action is available
		if ( empty( $response['payload']['available'] ) ) {
			wp_send_json_error( isset( $response['payload']['message'] ) ? $response['payload']['message'] : 'Action unavailable' );
		}

		// Execute delete action
		$result = $this->perform_delete_action( $response, $file );

		if ( is_wp_error( $result ) ) {
			wp_send_json_error( $result->get_error_message() );
		}

		wp_send_json_success( 'File deleted successfully. File: ' . $file );
	}


	function get_remote_response( $url ) {
		$response = wp_safe_remote_request( $url, array( 'timeout' => $this->timeout ) );
		if ( 200 != wp_remote_retrieve_response_code( $response ) ) {
			$this->flog( 'Failed to get remote response for URL: ' . $url . ' with response code: ' . wp_remote_retrieve_response_code( $response ) );
			return;
		}
		if ( is_wp_error( $response ) ) {
			$this->flog( 'Error retrieving remote response: ' . $response->get_error_message() );
			return;
		}
		return $response;
	}

	function default_uploads_path() {
		if ( ! is_multisite() ) {
			$expected_uploads_path = trailingslashit( $this->normalise_path( WP_CONTENT_DIR ) ) . 'uploads';
		} else {
			// Determine if this is the main site or a subsite
			$site_id = get_current_blog_id();
			if ( $site_id == 1 ) {
				// Main site follows the single-site structure even in multisite
				$expected_uploads_path = trailingslashit( $this->normalise_path( WP_CONTENT_DIR ) ) . 'uploads';
			} else {
				// Subsites follow the subsite structure
				$expected_uploads_path = trailingslashit( $this->normalise_path( WP_CONTENT_DIR ) ) . 'uploads/sites/' . $site_id;
			}
			$expected_uploads_path = trailingslashit( $this->normalise_path( WP_CONTENT_DIR ) ) . 'uploads';
		}
		return $expected_uploads_path;
	}

	function is_core_wp_file( $file ) {
		$file = $this->normalise_path( $file );
		if ( empty( $checksums ) ) {
			$checksums = array();
		}
		$key           = str_replace( trailingslashit( $this->normalise_path( ABSPATH ) ), '', $file );
		$raw_checksums = get_option( 'WPMR_checksums' );  // RAW checksums else we may get a prefixed key from checksums
		if ( ! is_array( $raw_checksums ) ) {
			$raw_checksums = array();
		}
		$response = $this->is_in_core_wp_dir( $file ) ||
		( dirname( $key ) == '.' && array_key_exists( $key, $raw_checksums ) ); // If there are no slashes in path, a dot ('.') is returned
		return $response;
	}

	function get_core_relative_path( $file ) {
		$file = $this->normalise_path( $file );
		// Core files: relative to ABSPATH (e.g., wp-admin/index.php)
		// Plugins: relative to WP_PLUGIN_DIR (e.g., plugin-name/plugin.php)
		// Themes: relative to theme root (e.g., theme-name/style.css)
		// Everything else: relative to ABSPATH
		return str_replace( trailingslashit( $this->normalise_path( ABSPATH ) ), '', $file );
	}

	function is_in_core_wp_dir( $file ) {
		$file = $this->normalise_path( $file );
		if ( strpos( $file, trailingslashit( $this->normalise_path( ABSPATH ) ) . 'wp-admin/' ) !== false || strpos( $file, trailingslashit( trailingslashit( $this->normalise_path( ABSPATH ) ) . WPINC ) ) !== false ) {
			return true;
		}
		return false;
	}

	function prioritise_core_files( $files ) {
		$files_c   = array();
		$files_wpc = array();
		$files_wpr = array();
		sort( $files );
		foreach ( $files as $key => $file ) {
			if ( $this->is_in_core_wp_dir( $file ) || $this->is_in_root_dir( $file ) ) {
				$files_c[] = $file;
			} elseif ( $this->str_starts_with( $this->normalise_path( dirname( $file ) ), $this->normalise_path( WP_CONTENT_DIR ) ) ) {
				$files_wpc[] = $file;
			} elseif ( $this->str_starts_with( $this->normalise_path( dirname( $file ) ), $GLOBALS['WPMR']['home_dir'] ) ) {
				$files_wpr[] = $file;
			}
		}
		// Implement chunked sorting for memory efficiency
		$files   = $this->chunked_natcasesort( $files );
		$files_c = $this->chunked_natcasesort( $files_c );
		$files   = array_merge( array_values( $files_c ), array_values( $files_wpc ), array_values( $files_wpr ), array_values( $files ) );
		$files   = array_unique( $files );
		return array( 'files' => $files );
	}

	function chunked_natcasesort( $array, $chunk_size = 1000 ) {
		$chunked_array = array_chunk( $array, $chunk_size );
		$sorted_array  = array();

		foreach ( $chunked_array as $chunk ) {
			natcasesort( $chunk );
			$sorted_array = array_merge( $sorted_array, $chunk );
		}

		return $sorted_array;
	}

	function is_in_root_dir( $file ) {
		return $this->normalise_path( ABSPATH ) === $this->normalise_path( dirname( $file ) );
	}

	function process_only_scan_dirs( $files ) {
		if ( ! empty( $GLOBALS['WPMR']['only_scan_dirs'] ) ) {
			// Always scan core
			$GLOBALS['WPMR']['only_scan_dirs'][] = trailingslashit( ABSPATH ) . 'wp-admin';
			$GLOBALS['WPMR']['only_scan_dirs'][] = trailingslashit( ABSPATH ) . WPINC;
			foreach ( $files as $k => $file ) {
				if ( ! $this->path_begins_with_any( $file, $GLOBALS['WPMR']['only_scan_dirs'] ) ) {
					unset( $files[ $k ] );
				}
			}
		}
		return $files;
	}

	function path_begins_with_any( $path, $arr_dirs ) {
		foreach ( $arr_dirs as $dir ) {
			if ( $this->str_starts_with( $path, $dir ) ) {
				return 1;
			}
		}
	}

	function str_starts_with( $string, $startswith ) {
		return ( strpos( (string) $string, (string) $startswith ) === 0 ); // we are not looking for occurance but specifically the begining
	}

	function wpmr_skip_dir( $path ) {

		if ( $path == untrailingslashit( $this->normalise_path( ABSPATH ) ) ) {
			return false;
		}

		if ( ! empty( $GLOBALS['WPMR']['skipdirs'] ) && in_array( basename( $this->normalise_path( $path ) ), $GLOBALS['WPMR']['skipdirs'] ) ) {
			return true;
		}

		if ( file_exists( $this->normalise_path( $path . DIRECTORY_SEPARATOR . '.mcignore' ) ) ) {
			return true;
		}

		// A simple way to check if a symlink recursive is to check if it is pointing to a parent directory
		// recursive /parent/subdir/path (points to) -> /parent/subdir/
		// recursive /parent/subdir/path (points to) -> /parent/somedir/
		if ( is_link( $path ) ) {
			// readlink( realpath DOESNT WORK. STAY AWAY FROM IT. Readlink only reads symlinks. Not real paths
			$link   = $this->normalise_path( dirname( $path ) . DIRECTORY_SEPARATOR . basename( $path ) );
			$target = $this->normalise_path( $link ); // use realpath. readlink result can be another symlink
			if ( $target && str_starts_with( $link, $target ) ) {
				return true;
			}
		}

		return false;
	}

	function return_all_files( $path = false ) {
		// It takes around 4 to 10 seconds to generate a list of files.
		// If the calls are made simultaneously, the list is generated multiple times.
		// It must be possible to store this in a short transient.
		// TBD
		// $time = microtime( true );
		$files = $this->get_all_files( $path );
		return $files;
	}

	function get_all_files( $path = false ) {
		if ( ! $path ) {
			$path = ( ! empty( $GLOBALS['WPMR']['home_dir'] ) ) ? wp_normalize_path( $GLOBALS['WPMR']['home_dir'] ) : ABSPATH;
			if ( empty( $path ) ) {
				$this->flog( 'Failed to get path.', false, false, true );
				return array();
			}
		}
		$path = untrailingslashit( wp_normalize_path( $path ) ); // This could be a symlink or whatever. Let's not touch it before testing via wpmr_skip_dir
		// $path = untrailingslashit( $this->realpath( $path ) ); // NEVER DO THIS ELSE WE CAN'T TEST SYMLINK RECURSION. Dont confuse readlink vs realpath
		$wpmr_skip_dir = apply_filters( 'wpmr_skip_dir', $path );
		if ( ! $wpmr_skip_dir ) {
			$children = @scandir( $path );
			if ( is_array( $children ) ) {
				$children = array_diff( $children, array( '..', '.' ) );
				$files    = array();
				foreach ( $children as $child ) {
					$target = trailingslashit( wp_normalize_path( $path ) ) . $child;
					if ( is_dir( $target ) ) {
						$elements = $this->get_all_files( $target );
						if ( $elements ) {
							foreach ( $elements as $element ) {
								$files[] = $this->normalise_path( $element );
							}
						}
					}
					if ( $this->is_scannable_file( $target ) ) {
						$files[] = $this->normalise_path( $target );
					}
				}
				return $files;
			} else {
				$this->flog( "Failed to read directory: {$path}" );
				return array();
			}
		} else {
			return array();
		}
	}

	function normalise_path( $path ) {
		$realpath = wp_normalize_path( realpath( $path ) );
		if ( $realpath ) {
			return $realpath;
		}
		return wp_normalize_path( $path );
	}

	function is_scannable_file( $file ) {
		$file = $this->normalise_path( $file );
		// File is scannable if: exists, is a file, is within size limit, and either has content OR is an empty core file
		$return = ( file_exists( $file ) && is_file( $file ) && ( filesize( $file ) || ( ! filesize( $file ) && $this->is_core_wp_file( $file ) ) || ! is_readable( $file ) ) && filesize( $file ) <= $this->maxsize );
		return $return;
	}

	function is_valid_file( $file ) {
		return ! $this->is_invalid_file( $file );
	}

	/** Checks if a file is invalid for scanning based on several criteria:
	 * - File does not exist
	 * - File is not readable
	 * - Path is not a file (could be a directory)
	 * - File is empty (0 bytes) and not a core WordPress file
	 * - File exceeds maximum allowed size for scanning
	 */
	function is_invalid_file( $file ) {
		$file = $this->normalise_path( $file );

		return ( ! file_exists( $file ) // Check if file doesn't exist
		|| ! is_readable( $file ) // Check if file is not readable
		|| ! is_file( $file ) // Check if path is not a file (could be a directory)
		|| ( ! filesize( $file ) && ! $this->is_core_wp_file( $file ) ) // Check if file is empty (0 bytes) - empty files in core dirs need special handling
		|| filesize( $file ) > $this->maxsize // Check if file exceeds maximum allowed size for scanning
		);
	}

	function is_valid_dir( $dir ) {
		$dir = $this->normalise_path( $dir );
		return ( file_exists( $dir ) && is_dir( $dir ) && is_readable( $dir ) );
	}

	function glob_files( $path = false ) {
		if ( ! $path ) {
			$path = ABSPATH;
			if ( empty( $path ) ) {
				return array();
			}
			$path = untrailingslashit( $path );
		}
		// $allfiles = new RecursiveIteratorIterator(
		// new RecursiveDirectoryIterator(
		// $path,
		// RecursiveDirectoryIterator::SKIP_DOTS |
		// RecursiveDirectoryIterator::FOLLOW_SYMLINKS |
		// RecursiveDirectoryIterator::KEY_AS_PATHNAME
		// ),
		// RecursiveIteratorIterator::SELF_FIRST,
		// RecursiveIteratorIterator::CATCH_GET_CHILD
		// );
		$allfiles = new RecursiveDirectoryIterator(
			$path,
			RecursiveDirectoryIterator::SKIP_DOTS |
			RecursiveDirectoryIterator::FOLLOW_SYMLINKS |
			RecursiveDirectoryIterator::KEY_AS_PATHNAME
		);

		$files = new RecursiveCallbackFilterIterator(
			$allfiles,
			function ( $current, $key, $iterator ) {
				$this->llog( $current );
				return true;
				if ( $iterator->hasChildren() && $current->isFile() ) {
					return true;
				} else {
					return false;
				}
				// return $current->isFile();
			}
		);

		$this->llog( $files );

		$it = new RecursiveIteratorIterator(
			$files,
			RecursiveIteratorIterator::SELF_FIRST,
			RecursiveIteratorIterator::CATCH_GET_CHILD
		);
		$this->llog( $it );
		foreach ( $it as $k => $v ) {
			print_r( $k ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- Debug function for development use
			print_r( $v ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- Debug function for development use
		}

		return;

		// $allfiles = iterator_to_array( $allfiles, 1 );
		$files = array();
		foreach ( $allfiles as $k => $v ) {
			if ( is_file( $k ) ) {
				// $files[] = $k;
				$this->llog( $k );
			}
			// print_r( $k  );
		}

		return $files;

		$files = array();
		foreach ( new RecursiveDirectoryIterator( $allfiles ) as $filename => $cur ) {
			$this->flog( $filename );
			$files[] = $filename;
		}
		sort( $files );
		return $files;
	}

	function is_excluded( $path ) {
		$skipdirs       = $GLOBALS['WPMR']['skipdirs'];
		$only_scan_dirs = $GLOBALS['WPMR']['only_scan_dirs'];

		$path_excluded_by_skipdirs       = $this->path_excluded_by_skipdirs( $path, $skipdirs );
		$path_included_in_only_scan_dirs = $this->path_included_in_only_scan_dirs( $path, $only_scan_dirs );

		$result = $path_excluded_by_skipdirs && ! $path_included_in_only_scan_dirs;
		return $result;
	}

	function path_excluded_by_skipdirs( $path, $dirs ) {
		$skipped = false;
		if ( ! empty( $dirs ) && is_array( $dirs ) ) {
			foreach ( $dirs as $skipdir ) {
				if ( preg_match( '/' . preg_quote( wp_normalize_path( $skipdir ), '/' ) . '/', $path ) ) {
					return true;
				}
			}
		}
	}

	function path_included_in_only_scan_dirs( $path, $dirs ) {
		if ( empty( $dirs ) || ! is_array( $dirs ) ) {
			return;
		}
		$present  = false;
		$abs_dirs = $dirs;
		// Check if the path to file is present in any of the only_scan_dirs
		foreach ( $abs_dirs as $abs_dir ) {
			if ( preg_match( '/' . preg_quote( wp_normalize_path( $abs_dir ), '/' ) . '/', $path ) ) { // Check if file path has matches in only_scan_dir
				$present = true;
			}
		}
		return $present;
	}

	function get_setting( $setting ) {
		$settings = $this->get_wpmr_option();

		$return = isset( $settings[ $setting ] ) ? $settings[ $setting ] : false;

		return $return;
	}

	function update_setting( $setting, $value ) {
		$settings = $this->get_wpmr_option();
		if ( ! $settings ) {
			$settings = array();
		}
		$settings[ $setting ] = $value;
		if ( $setting == 'signatures' || $setting == 'sig_time' ) {
			$this->delete_generated_checksums();
		}
		wp_cache_delete( 'WPMR', 'options' );
		return update_option( 'WPMR', $settings );
	}

	function delete_setting( $setting ) {
		$settings = $this->get_wpmr_option();
		if ( ! $settings ) {
			$settings = array();
		}
		unset( $settings[ $setting ] );
		$result = update_option( 'WPMR', $settings ); // assignment gets around an opcache bug
		return $result;
	}

	function get_fw_setting( $setting ) {
		$defaults = $this->wpmr_fw_settings_defaults();
		$settings = get_option( 'wpmr_fw_settings' );
		if ( ! $settings ) {
			return $defaults[ $setting ];
		}
		return isset( $settings[ $setting ] ) ? $settings[ $setting ] : 'no';
	}

	function get_wpmr_option() {
		return get_option( 'WPMR', array( 'wpmr_skin' => 'dark' ) );
	}

	function register_settings() {
		register_setting(
			'wpmr_fw_settings',
			'wpmr_fw_settings',
			array(
				'default'           => $this->wpmr_fw_settings_defaults(),
				'sanitize_callback' => array(
					$this,
					'sanitize_fw',
				),
			)
		);
		// add_settings_section( string $id, string $title, callable $callback, string $page, array $args = array() )
		add_settings_section( 'wpmr_fw', 'Security Hardening &amp; Protection', array( $this, 'firewall_section_ui' ), 'wpmr_firewall' );
		// add_settings_field( string $id, string $title, callable $callback, string $page, string $section = 'default', array $args = array() )
		add_settings_field( 'fw_block_path_traversal', '', array( $this, 'fw_block_path_traversal_ui' ), 'wpmr_firewall', 'wpmr_fw' );
		add_settings_field( 'fw_disable_php_upload', '', array( $this, 'fw_disable_php_upload_ui' ), 'wpmr_firewall', 'wpmr_fw' );
		add_settings_field( 'fw_difw_disable_restapi_user_listing', '', array( $this, 'fw_disable_restapi_user_listing_ui' ), 'wpmr_firewall', 'wpmr_fw' );
		add_settings_field( 'fw_disable_user_enumeration', '', array( $this, 'fw_disable_user_enumeration_ui' ), 'wpmr_firewall', 'wpmr_fw' );
	}

	function sanitize_fw( $values ) {
		if ( empty( $values['fw_block_path_traversal'] ) ) {
			$values['fw_block_path_traversal'] = 'no';
		}
		if ( empty( $values['fw_disable_php_upload'] ) ) {
			$values['fw_disable_php_upload'] = 'no';
		}
		if ( empty( $values['fw_disable_restapi_user_listing'] ) ) {
			$values['fw_disable_restapi_user_listing'] = 'no';
		}
		if ( empty( $values['fw_disable_user_enumeration'] ) ) {
			$values['fw_disable_user_enumeration'] = 'no';
		}
		return $values;
	}

	function wpmr_fw_settings_defaults() {
		$defaults = array(
			'fw_block_path_traversal'         => 'yes',
			'fw_disable_php_upload'           => 'yes',
			'fw_disable_restapi_user_listing' => 'yes',
			'fw_disable_user_enumeration'     => 'yes',
		);
		return $defaults;
	}

	/**
	 * Processes AJAX requests for the `wpmr_ajax_request` action.
	 *
	 * Validates the request using a nonce, checks user permissions, dynamically
	 * calls the appropriate class method, and returns a JSON response.
	 *
	 * @return void Outputs a JSON response using wp_send_json, wp_send_json_success,
	 *              or wp_send_json_error.
	 */
	function wpmr_ajax_request() {
		check_ajax_referer( 'wpmr_ajax_data', 'wpmr_ajax_data_nonce' );
		if ( ! current_user_can( $this->cap ) ) {
			wp_send_json_error( 'Not allowed.' );
		}
		if ( empty( $_REQUEST['request'][0] ) ||
		! is_callable(
			array(
				$this,
				sanitize_text_field( wp_unslash( $_REQUEST['request'][0] ) ), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- Validated via is_callable check
			)
		) ) {
			$this->flog( 'not callable', false, false, true );
			$this->flog( sanitize_text_field( wp_unslash( $_REQUEST['request'][0] ) ), false, false, true ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- Validated via is_callable check
			wp_send_json_error( 'Not sure what to do.', false, false, true );
		}
		if ( ! empty( $_REQUEST['request'][1] ) ) {
			if ( ! is_array( wp_unslash( $_REQUEST['request'][1] ) ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Unslashing and type checking for array validation
				$this->flog( 'Arguments must be an array. You passed ' . gettype( wp_unslash( $_REQUEST['request'][1] ) ) . ' "' . print_r( wp_unslash( $_REQUEST['request'][1] ), 1 ) . '"', false, false, true ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r,WordPress.Security.ValidatedSanitizedInput.InputNotValidated,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Debug logging for malware scanner
				wp_send_json_error( 'Arguments must be an array. You passed ' . gettype( wp_unslash( $_REQUEST['request'][1] ) ) . ' "' . print_r( wp_unslash( $_REQUEST['request'][1] ), 1 ) . '"' ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r,WordPress.Security.ValidatedSanitizedInput.InputNotValidated,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Debug logging for malware scanner
			}
			$result = call_user_func_array(
				array( $this, sanitize_text_field( wp_unslash( $_REQUEST['request'][0] ) ) ), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- Validated via is_callable check
				wp_unslash( $_REQUEST['request'][1] ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Arguments are unslashed before function call
			);
		} else {
			$result = call_user_func( array( $this, sanitize_text_field( wp_unslash( $_REQUEST['request'][0] ) ) ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated -- Validated via is_callable check
		}
		if ( is_null( $result ) || $result === true ) { // Function did it's job; may be silently or returned true. Simply return success.
			wp_send_json_success( $result );
		} else { // We don't know if the message is a success or an error. Simply return the message.
			wp_send_json( $result );
		}
	}

	function get_locale() {
		$file = ABSPATH . WPINC . '/version.php';

		if ( $this->is_valid_file( $file ) ) {
			$code = file_get_contents( $file );
		}

		if ( preg_match( '/\$wp_local_package\s*=\s*[\'"](.*?)[\'"]\s*;/', $code, $matches ) ) {
			$locale = $matches[1];
		} else {
			$locale = 'en_US';
		}
		return $locale;
	}

	function reset_logs() {
		global $wpdb;

		$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Necessary for cleanup operation
			$wpdb->prepare(
				"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
				'_transient_WPMR\_log\_%',
				'_transient\_timeout\_WPMR\_log\_%'
			)
		);
	}

	function normalize_host( $host ) {
		return preg_replace( '/^www\./', '', $host );
	}

	function automate_routines() {
		if ( ! ( defined( 'DOING_CRON' ) && DOING_CRON ) || ! $this->is_advanced_edition() ) {
			return;
		}
		$check   = $this->check_definitions();
		$updates = $this->definition_updates_available();
		if ( $updates && $this->get_setting( 'def_auto_update_enabled' ) ) {
			$update = $this->update_definitions_cli( true );
		}
		$this->checksums_delete_invalid();
	}

	function get_users_loggedin() {
		return get_users(
			array(
				'meta_key'     => 'session_tokens', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
				'meta_compare' => 'EXISTS',
			)
		);
	}

	function malcure_user_sessions() {
		?>
		<tr><th>Logged-In Users:</th><td>
		<?php
		submit_button( 'Logout All Users', 'primary', 'malcure_destroy_sessions', false );
		submit_button( 'Shuffle WordPress Salts', 'primary', 'malcure_shuffle_salts', false );
		$users = $this->get_users_loggedin();

		$total_users = count( $users );
		// Fetch only the first 25 users
		$display_users = array_slice( $users, 0, 25 );

		// Display heading when users are skipped
		if ( $total_users > count( $display_users ) ) {
			$skipped_users = $total_users - count( $display_users );
			echo '<h3>Showing ' . esc_html( count( $display_users ) ) . ' of ' . esc_html( $total_users ) . ' logged-in users; ' . esc_html( $skipped_users ) . ' users skipped.</h3>';
		}
		foreach ( $display_users  as $user ) {
			echo '<table class="user_details" id="user_details_' . esc_html( $user->ID ) . '">';
			echo '<tr><th class="user_details_id">User ID</th><td>' . esc_html( $user->ID ) . '</td></tr>';
			echo '<tr><th class="user_details_roles">User Roles</th><td>' . esc_html( implode( ',', $user->roles ) ) . '</td></tr>';
			echo '<tr><th class="user_details_user_login">User Login</th><td>' . esc_html( $user->user_login ) . '</td></tr>';
			echo '<tr><th class="user_details_user_email">User Email</th><td>' . esc_html( $user->user_email ) . '</td></tr>';
			echo '<tr><th class="user_details_display_name">Display Name</th><td>' . esc_html( $user->display_name ) . '</td></tr>';
			echo '<tr><th class="user_details_user_registered">Registered</th><td>' . esc_html( gmdate( 'Y-m-d\TH:i:s\Z', strtotime( $user->user_registered ) ) ) . '</td></tr>';
			$s_details = '';
			$s_details = get_user_meta( $user->ID, 'session_tokens', true );
			echo '<tr><th  class="wpmr_user_details_session_ip">Sessions</th><td>';
			foreach ( $s_details as $s_detail ) {
				echo '<table class="wpmr_user_details_session">';
				echo '<tr><th  class="wpmr_user_details_session_ip">IP Address</th><td>' . esc_html( $s_detail['ip'] ) . '</td></tr>';
				// $hostname = gethostbyaddr( $s_detail['ip'] );
				// if ( $hostname && $hostname !== $s_detail['ip'] ) {
				// echo '<tr><th class="wpmr_user_details_session_hostname">Hostname</th><td>' . esc_html( $hostname ) . '</td></tr>';
				// }
				echo '<tr><th  class="wpmr_user_details_session_ua">User-Agent</th><td>' . esc_html( $s_detail['ua'] ) . '</td></tr>';
				echo '<tr><th  class="wpmr_user_details_session_login">Session Start</th><td>' . esc_html( gmdate( 'Y-m-d\TH:i:s\Z', $s_detail['login'] ) ) . '</td></tr>';
				echo '<tr><th  class="wpmr_user_details_session_expiration">Session Expiration</th><td>' . esc_html( gmdate( 'Y-m-d\TH:i:s\Z', $s_detail['expiration'] ) ) . '</td></tr>';
				echo '</table>';
			}
			echo '</td></tr>';
			echo '</table>';
		}
		?>
		</td></tr>
		<?php
	}

	function debug() {
		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
		}
	}
}