<?php

/*
Plugin Name: Security Ninja (Premium)
Plugin URI: https://wpsecurityninja.com/
Description: Check your site for security vulnerabilities and get precise suggestions for corrective actions on passwords, user accounts, file permissions, database security, version hiding, plugins, themes, security headers and other security aspects.
Author: WP Security Ninja
Version: 5.295
Update URI: https://api.freemius.com
Author URI: https://wpsecurityninja.com/
License: GPLv3
License URI: https://www.gnu.org/licenses/gpl-3.0.html
Text Domain: security-ninja
Domain Path: /languages/mo

@fs_premium_only /modules/auto-fixer/, /modules/cloud-firewall/libraries/,/modules/cloud-firewall/class-sn-404-guard.php,/modules/cloud-firewall/class-wf-sn-cf-ip-management.php,/modules/cloud-firewall/class-sn-geolocation.php,/modules/cloud-firewall/class-sn-ip-asn.php,/modules/cloud-firewall/geoip-countrylist.json,/modules/cloud-firewall/whitelist-managewp.json,/modules/cloud-firewall/whitelist-uptimerobot.json,/modules/cloud-firewall/whitelist-uptimia.json,/modules/cloud-firewall/tabs/404guard.php,/modules/cloud-firewall/tabs/ip-management.php,/modules/cloud-firewall/tabs/login-protection.php,/modules/cloud-firewall/tabs/visitor-logging.php,/modules/cloud-firewall/tabs/woocommerce.php,/modules/2fa/, /modules/malware-scanner/, /modules/scheduled-scanner/, /modules/ai-security-advisor/class-wf-sn-ai-advisor-scheduler.php, /modules/whitelabel/, /modules/visitor-log/, /includes/php-malware-scanner/, /modules/rename-login/, /modules/malware-scanner/js/wf-sn-ms.js, /images/flags/, /images/virustotal-icon.svg, /vendor/2amigos/, /vendor/bacon/, /vendor/ip2location/, /vendor/woocommerce/, /languages/mo/, /modules/woocommerce/

Copyright
2011-2019 Web Factory Ltd
2019-     Larsik Corp

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA


This plugin uses the following 3rd party MIT licensed projects - Thank you for making other developer lives easier :-)

* Rename login module based on the WordPress Rename Login by Prathap Rathod.

* Country flags Copyright (c) 2017 Go Squared Ltd. http://www.gosquared.com/ - https://github.com/gosquared/flags. MIT license.

* PHP malware scanner - https://github.com/scr34m/php-malware-scanner
This plugin works on a modified version of the excellent PHP malware scanner.
*/
namespace WPSecurityNinja\Plugin;

use Error;
use wf_sn_cf;
use Wf_Sn_Cs;
use Utils;
if ( !defined( 'ABSPATH' ) ) {
    exit;
}
// Free (including older wp.org builds) may already be active: hand off to Freemius, do not load vendor again.
if ( function_exists( '\\WPSecurityNinja\\Plugin\\secnin_fs' ) ) {
    secnin_fs()->set_basename( true, __FILE__ );
    return;
}
if ( file_exists( __DIR__ . '/vendor/autoload.php' ) ) {
    require_once __DIR__ . '/vendor/autoload.php';
}
if ( !function_exists( '\\fs_dynamic_init' ) && file_exists( __DIR__ . '/freemius/start.php' ) ) {
    require_once __DIR__ . '/freemius/start.php';
}
if ( !function_exists( '\\fs_dynamic_init' ) ) {
    return;
}
if ( !function_exists( '\\WPSecurityNinja\\Plugin\\secnin_fs' ) ) {
    define( 'WF_SN_BASE_FILE', __FILE__ );
    // Create a helper function for easy SDK access.
    function secnin_fs() {
        global $secnin_fs;
        if ( !isset( $secnin_fs ) ) {
            // Activate multisite network integration.
            if ( !defined( 'WP_FS__PRODUCT_3690_MULTISITE' ) ) {
                define( 'WP_FS__PRODUCT_3690_MULTISITE', true );
            }
            $secnin_fs = \fs_dynamic_init( array(
                'id'                => '3690',
                'slug'              => 'security-ninja',
                'enable_anonymous'  => true,
                'type'              => 'plugin',
                'public_key'        => 'pk_f990ec18700a90c02db544f1aa986',
                'is_premium'        => true,
                'has_addons'        => true,
                'has_paid_plans'    => true,
                'wp_org_gatekeeper' => 'OA7#BoRiBNqdf52FvzEf!!074aRLPs8fspif$7K1#4u4Csys1fQlCecVcUTOs2mcpeVHi#C2j9d09fOTvbC0HloPT7fFee5WdS3G',
                'trial'             => array(
                    'days'               => 14,
                    'is_require_payment' => false,
                ),
                'has_affiliation'   => false,
                'menu'              => ( is_multisite() ? array(
                    'support' => false,
                    'network' => false,
                    'pricing' => false,
                ) : array(
                    'slug'       => 'wf-sn',
                    'first-path' => 'admin.php?page=security-ninja-wizard',
                    'support'    => false,
                    'network'    => false,
                    'pricing'    => false,
                ) ),
                'is_live'           => true,
            ) );
        }
        return $secnin_fs;
    }

    // Init Freemius.
    secnin_fs();
    // Signal that SDK was initiated.
    do_action( 'secnin_fs_loaded' );
    if ( secnin_fs()->can_use_premium_code__premium_only() ) {
        // Add Freemius license validation hooks
        add_action( 'fs_after_license_loaded_security-ninja', array(__NAMESPACE__ . '\\Wf_Sn', 'handle_license_validation__premium_only') );
        add_action( 'fs_after_license_change_security-ninja', array(__NAMESPACE__ . '\\Wf_Sn', 'handle_license_validation__premium_only') );
        add_action( 'fs_after_license_activation_security-ninja', array(__NAMESPACE__ . '\\Wf_Sn', 'queue_license_activation_redirect__premium_only') );
        add_action( 'fs_after_premium_version_activation_security-ninja', array(__NAMESPACE__ . '\\Wf_Sn', 'handle_license_validation__premium_only') );
        add_action( 'fs_after_premium_version_activation_security-ninja', array(__NAMESPACE__ . '\\Wf_Sn', 'queue_license_activation_redirect__premium_only') );
        // Prevent chillerlan/php-settings-container conflict with AR for WooCommerce and similar plugins.
        add_action( 'admin_init', function () {
            $prefixed = \WPSecurityNinja\Plugin\chillerlan\Settings\SettingsContainerAbstract::class;
            if ( !class_exists( $prefixed ) ) {
                return;
            }
            if ( !class_exists( 'chillerlan\\Settings\\SettingsContainerAbstract', false ) ) {
                class_alias( $prefixed, 'chillerlan\\Settings\\SettingsContainerAbstract' );
            }
        }, 1 );
    }
    define( 'WF_SN_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
    define( 'WF_SN_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
    define( 'WF_SN_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
    include_once WF_SN_PLUGIN_DIR . 'modules/overview/class-wf-sn-overview-tab.php';
    // Dashboard widget
    include_once WF_SN_PLUGIN_DIR . 'modules/dashboard-widget/class-wf-sn-dashboard-widget.php';
    \WPSecurityNinja\Plugin\Wf_Sn_Dashboard_Widget::init();
    // Vulnerabilities
    include_once WF_SN_PLUGIN_DIR . 'modules/vulnerabilities/class-wf-sn-vu.php';
    // Core Scanner
    include_once WF_SN_PLUGIN_DIR . 'modules/core-scanner/core-scanner.php';
    // File viewer
    include_once WF_SN_PLUGIN_DIR . 'modules/file-viewer/class-fileviewer.php';
    include_once WF_SN_PLUGIN_DIR . 'modules/cloud-firewall/class-wf-sn-cf-utils.php';
    include_once WF_SN_PLUGIN_DIR . 'modules/cloud-firewall/class-wf-sn-security-utils.php';
    include_once WF_SN_PLUGIN_DIR . 'modules/cloud-firewall/cloud-firewall.php';
    include_once WF_SN_PLUGIN_DIR . 'includes/class-wf-sn-utils.php';
    include_once WF_SN_PLUGIN_DIR . 'includes/class-wf-sn-test-descriptions.php';
    include_once WF_SN_PLUGIN_DIR . 'includes/class-wf-sn-free-render.php';
    include_once WF_SN_PLUGIN_DIR . 'includes/class-wf-sn-crypto.php';
    include_once WF_SN_PLUGIN_DIR . 'includes/class-wf-sn-admin-links.php';
    include_once WF_SN_PLUGIN_DIR . 'includes/class-wf-sn-security-snapshot.php';
    include_once WF_SN_PLUGIN_DIR . 'includes/class-wf-sn-security-snapshot-diff.php';
    include_once WF_SN_PLUGIN_DIR . 'includes/class-wf-sn-priority-resolver.php';
    include_once WF_SN_PLUGIN_DIR . 'modules/events-logger/events-logger.php';
    include_once WF_SN_PLUGIN_DIR . 'modules/wizard/class-wf-sn-wizard.php';
    if ( apply_filters( 'wf_sn_ai_advisor_enabled', true ) ) {
        include_once WF_SN_PLUGIN_DIR . 'modules/ai-security-advisor/class-wf-sn-ai-advisor.php';
        add_action( 'init', array('\\WPSecurityNinja\\Plugin\\AiAdvisor\\Wf_Sn_Ai_Advisor', 'init') );
    }
    if ( secnin_fs()->can_use_premium_code__premium_only() ) {
        include_once WF_SN_PLUGIN_DIR . 'modules/whitelabel/class-wf-sn-wl.php';
        include_once WF_SN_PLUGIN_DIR . 'modules/malware-scanner/malware-scanner.php';
        include_once WF_SN_PLUGIN_DIR . 'modules/malware-scanner/class-pluginsintegrity.php';
        include_once WF_SN_PLUGIN_DIR . 'modules/malware-scanner/class-wf-sn-ms-suspicious-plugins.php';
        include_once WF_SN_PLUGIN_DIR . 'modules/scheduled-scanner/scheduled-scanner.php';
        if ( apply_filters( 'wf_sn_ai_advisor_enabled', true ) ) {
            include_once WF_SN_PLUGIN_DIR . 'modules/ai-security-advisor/class-wf-sn-ai-advisor-scheduler.php';
            add_action( 'init', array('\\WPSecurityNinja\\Plugin\\AiAdvisor\\Wf_Sn_Ai_Advisor_Scheduler', 'init') );
        }
        include_once WF_SN_PLUGIN_DIR . 'modules/auto-fixer/auto-fixer.php';
        include_once WF_SN_PLUGIN_DIR . 'modules/auto-fixer/class-wf-sn-fixes.php';
        include_once WF_SN_PLUGIN_DIR . 'modules/rename-login/class-wf-sn-rename-login.php';
        include_once WF_SN_PLUGIN_DIR . 'modules/visitor-log/class-wf-sn-visitor-log.php';
        include_once WF_SN_PLUGIN_DIR . 'modules/2fa/class-wf-sn-2fa.php';
        include_once WF_SN_PLUGIN_DIR . 'modules/woocommerce/class-wf-sn-woo-protection.php';
    }
    class Wf_Sn {
        /**
         * Plugin version
         *
         * @var integer
         */
        public static $version = null;

        /**
         * Cached dashboard test score payload.
         *
         * @var array<string, int|string>|null
         */
        public static $test_scores = null;

        /**
         * Plugin name
         *
         * @var string
         */
        public static $name = 'Security Ninja';

        /**
         * List of tests to skip
         *
         * @var array
         */
        public static $skip_tests = array();

        /**
         * Plugin options.
         *
         * @var array<string, mixed>
         */
        public static $options;

        /**
         * Database schema version; bump when table structure changes so dbDelta runs on upgrade.
         *
         * @var int
         */
        const WF_SN_DB_VERSION = 5.292;

        /**
         * Load plugin text domain for translations
         *
         * @author  Lars Koudal
         * @since   v1.0.1
         * @version v1.0.0  Tuesday, November 11th, 2025.
         * @access  public static
         * @return  void
         */
        public static function load_textdomain() {
            load_plugin_textdomain( 'security-ninja', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/mo' );
        }

        /**
         * Init the plugin
         *
         * @author  Lars Koudal
         * @since   v0.0.1
         * @version v1.0.0  Thursday, April 29th, 2021.
         * @access  public static
         * @return  void
         */
        public static function init() {
            self::$options = self::get_options();
            // loads persistent admin notices
            add_action( 'admin_init', array('PAnD', 'init') );
            // Load security tests
            include_once WF_SN_PLUGIN_DIR . 'class-wf-sn-tests.php';
            include_once WF_SN_PLUGIN_DIR . 'includes/class-wf-sn-utils.php';
            // MainWP integration - run here to make sure it's loaded
            add_filter(
                'mainwp_child_extra_execution',
                array(__NAMESPACE__ . '\\Utils', 'do_filter_mainwp_child_extra_execution'),
                10,
                2
            );
            add_filter(
                'mainwp_site_sync_others_data',
                array(__NAMESPACE__ . '\\Utils', 'do_filter_mainwp_site_sync_others_data'),
                10,
                2
            );
            add_action( 'secnin_run_tests_event', array(__NAMESPACE__ . '\\Wf_Sn', 'do_event_run_tests') );
            Wf_Sn_Security_Snapshot::register_hooks();
            // Admin-ajax: register always; each callback enforces current_user_can( 'manage_options' ) (or stricter).
            add_action( 'wp_ajax_sn_run_single_test', array(__NAMESPACE__ . '\\Wf_Sn', 'run_single_test') );
            add_action( 'wp_ajax_sn_get_single_test_details', array(__NAMESPACE__ . '\\Wf_Sn', 'get_single_test_details') );
            add_action( 'wp_ajax_sn_get_test_descriptions', array(__NAMESPACE__ . '\\Wf_Sn', 'get_test_descriptions') );
            add_action( 'wp_ajax_sn_run_tests', array(__NAMESPACE__ . '\\Wf_Sn', 'run_tests') );
            add_action( 'wp_ajax_sn_reset_secret_url', array(__NAMESPACE__ . '\\Wf_Sn', 'reset_secret_url') );
            add_action( 'wp_ajax_wf_sn_dismiss_review', array(__NAMESPACE__ . '\\Wf_Sn', 'wf_sn_dismiss_review') );
            // does the user have enough privilages to use the plugin?
            if ( current_user_can( 'activate_plugins' ) ) {
                // Adds extra permission to Freemius
                if ( function_exists( '\\WPSecurityNinja\\Plugin\\secnin_fs' ) ) {
                    secnin_fs()->add_filter( 'permission_list', array(__NAMESPACE__ . '\\Utils', 'add_freemius_extra_permission') );
                    secnin_fs()->add_filter(
                        'show_admin_notice',
                        array(__NAMESPACE__ . '\\Utils', 'do_filter_show_admin_notice'),
                        10,
                        2
                    );
                    secnin_fs()->add_filter( 'checkout/parameters', array(__NAMESPACE__ . '\\Utils', 'extra_modern_checkout_parameters') );
                    add_action( 'admin_init', array(__NAMESPACE__ . '\\Utils', 'secnin_fs_license_key_migration') );
                    secnin_fs()->add_filter( 'plugin_icon', array(__NAMESPACE__ . '\\Wf_Sn', 'secnin_fs_custom_icon') );
                    // Custom, security-focused opt-in message (Skip remains available).
                    secnin_fs()->add_filter(
                        'connect_message',
                        array(__NAMESPACE__ . '\\Utils', 'freemius_connect_message'),
                        10,
                        6
                    );
                    secnin_fs()->add_filter(
                        'connect_message_on_update',
                        array(__NAMESPACE__ . '\\Utils', 'freemius_connect_message'),
                        10,
                        6
                    );
                }
                add_filter(
                    'sn_tabs',
                    array(__NAMESPACE__ . '\\Wf_Sn', 'return_tabs'),
                    PHP_INT_MAX,
                    1
                );
                add_action( 'admin_menu', array(__NAMESPACE__ . '\\Wf_Sn', 'admin_menu') );
                add_action(
                    'activated_plugin',
                    array(__NAMESPACE__ . '\\Wf_Sn', 'do_action_activated_plugin'),
                    10,
                    2
                );
                add_action( 'admin_enqueue_scripts', array(__NAMESPACE__ . '\\Wf_Sn', 'enqueue_scripts') );
                add_action( 'admin_init', array(__NAMESPACE__ . '\\Wf_Sn', 'register_settings') );
                add_action( 'admin_init', array(__NAMESPACE__ . '\\Wf_Sn', 'do_action_admin_init') );
                add_action( 'admin_init', array(__NAMESPACE__ . '\\Wf_Sn', 'maybe_upgrade_db'), 1 );
                add_action( 'admin_notices', array(__NAMESPACE__ . '\\Utils', 'do_admin_notices') );
                add_action( 'admin_footer', array(__NAMESPACE__ . '\\Utils', 'admin_footer') );
                add_action( 'secnin_signup_to_newsletter', array(__NAMESPACE__ . '\\Utils', 'signup_to_newsletter') );
                add_filter( 'manage_users_columns', array(__NAMESPACE__ . '\\Utils', 'add_user_last_login_column') );
                add_filter(
                    'manage_users_custom_column',
                    array(__NAMESPACE__ . '\\Utils', 'return_last_login_column'),
                    10,
                    3
                );
            }
        }

        /**
         * Run create_tables_for_site (dbDelta) when stored DB version is older than WF_SN_DB_VERSION.
         * Runs only in admin (admin_init) so the front-end is not hit. Set wf_sn_db_version in
         * activate() after creating tables so the next request sees it and we avoid running twice.
         */
        public static function maybe_upgrade_db() {
            $stored = (float) get_option( 'wf_sn_db_version', 0 );
            if ( $stored >= self::WF_SN_DB_VERSION ) {
                return;
            }
            global $wpdb;
            include_once ABSPATH . 'wp-admin/includes/upgrade.php';
            $charset = $wpdb->get_charset_collate();
            \WPSecurityNinja\Plugin\Utils::create_tables_for_site( $charset );
            update_option( 'wf_sn_db_version', self::WF_SN_DB_VERSION );
        }

        /**
         * do_event_run_tests.
         *
         * @author  Lars Koudal
         * @since   v0.0.1
         * @version v1.0.0  Tuesday, December 12th, 2023.
         * @version v1.0.1  Thursday, December 14th, 2023.
         * @access  public static
         * @return  void
         */
        public static function do_event_run_tests() {
            $security_tests = wf_sn_tests::return_security_tests();
            $resultssofar = get_option( 'security_tests_results', array() );
            $set_time_limit = set_time_limit( 200 );
            if ( secnin_fs()->can_use_premium_code__premium_only() ) {
                \WPSecurityNinja\Plugin\wf_sn_el_modules::log_event( 'security_ninja', 'security_tests', 'Running security tests.' );
                if ( !$set_time_limit ) {
                    \WPSecurityNinja\Plugin\wf_sn_el_modules::log_event(
                        'security_ninja',
                        'security_tests',
                        'Could not set PHP time limit set_time_limit() in run_all_tests()',
                        ''
                    );
                }
            }
            if ( !is_array( $security_tests ) || empty( $security_tests ) ) {
                if ( secnin_fs()->can_use_premium_code__premium_only() ) {
                    \WPSecurityNinja\Plugin\wf_sn_el_modules::log_event(
                        'security_ninja',
                        'security_tests',
                        'No security tests available.',
                        ''
                    );
                }
                return;
            }
            $resultssofar['last_run'] = time();
            foreach ( $security_tests as $test_name => $test ) {
                $class_with_namespace = __NAMESPACE__ . '\\Wf_Sn_Tests';
                if ( !method_exists( $class_with_namespace, $test_name ) ) {
                    if ( secnin_fs()->can_use_premium_code__premium_only() ) {
                        \WPSecurityNinja\Plugin\wf_sn_el_modules::log_event( 'security_ninja', 'security_tests', "Method {$test_name} does not exist in Wf_Sn" );
                    }
                    continue;
                }
                // Start timer and call the test dynamically.
                \WPSecurityNinja\Plugin\Utils::timerstart( 'run_test_' . esc_attr( $test_name ) );
                $response = $class_with_namespace::$test_name();
                if ( !is_array( $response ) || empty( $response ) ) {
                    if ( secnin_fs()->can_use_premium_code__premium_only() ) {
                        \WPSecurityNinja\Plugin\wf_sn_el_modules::log_event( 'security_ninja', 'security_tests', "Invalid response for test {$test_name}" );
                    }
                    continue;
                }
                // Setting appropriate message
                if ( 10 === intval( $response['status'] ) ) {
                    $return_message = sprintf( (string) ($test['msg_ok'] ?? '%s'), $response['msg'] ?? '' );
                } elseif ( 0 === intval( $response['status'] ) ) {
                    $return_message = sprintf( (string) ($test['msg_bad'] ?? '%s'), $response['msg'] ?? '' );
                } else {
                    $return_message = sprintf( (string) ($test['msg_warning'] ?? '%s'), $response['msg'] ?? '' );
                }
                // Updates the results
                $resultssofar['test'][$test_name] = array(
                    'title'  => $test['title'],
                    'status' => $response['status'],
                    'score'  => $test['score'],
                    'msg'    => $return_message,
                );
                $end_time = \WPSecurityNinja\Plugin\Utils::timerstop( 'run_test_' . esc_attr( $test_name ) );
                $testresult = array(
                    'testid'    => $test_name,
                    'timestamp' => current_time( 'mysql' ),
                    'title'     => $test['title'],
                    'status'    => $response['status'],
                    'score'     => $test['score'],
                    'runtime'   => $end_time,
                    'msg'       => $return_message,
                    'details'   => '',
                );
                self::update_test_score( $testresult );
                // Update the last test run
                $resultssofar['last_test_run'] = $test_name;
                update_option( 'security_tests_results', $resultssofar );
            }
            if ( class_exists( __NAMESPACE__ . '\\Wf_Sn_Cs' ) ) {
                if ( method_exists( __NAMESPACE__ . '\\Wf_Sn_Cs', 'scan_files' ) ) {
                    \WPSecurityNinja\Plugin\wf_sn_el_modules::log_event( 'security_ninja', 'security_tests', 'Checking core files.' );
                    \WPSecurityNinja\Plugin\Wf_Sn_Cs::scan_files( true );
                }
            }
            if ( secnin_fs()->can_use_premium_code__premium_only() ) {
                if ( class_exists( __NAMESPACE__ . '\\Wf_Sn_Ms' ) ) {
                    if ( method_exists( __NAMESPACE__ . '\\Wf_Sn_Ms', 'run_malware_scan_for_scheduler' ) ) {
                        \WPSecurityNinja\Plugin\Wf_Sn_Ms::run_malware_scan_for_scheduler();
                    }
                }
                \WPSecurityNinja\Plugin\wf_sn_el_modules::log_event(
                    'security_ninja',
                    'security_tests',
                    'Finished running tests.',
                    ''
                );
            }
        }

        /**
         * Redirects the user after plugin activation.
         *
         * @author  Unknown
         * @author  Lars Koudal
         * @since   v0.0.1
         * @version v1.0.0  Tuesday, February 22nd, 2022.
         * @version v1.0.1  Saturday, March 5th, 2022.
         * @access  public static
         * @return  void
         */
        public static function do_action_admin_init() {
            self::maybe_redirect_after_first_install();
            if ( secnin_fs()->can_use_premium_code__premium_only() ) {
                self::maybe_redirect_after_license_activation__premium_only();
            }
            // Check for secret URL reset success notice
            $reset_success_url = get_transient( 'sn_secret_url_reset_success' );
            if ( $reset_success_url ) {
                delete_transient( 'sn_secret_url_reset_success' );
                add_action( 'admin_notices', function () use($reset_success_url) {
                    echo '<div class="notice notice-success secnin-notice"><p>' . esc_html__( 'Secret access URL has been reset successfully.', 'security-ninja' ) . ' <strong>' . esc_html__( 'New URL:', 'security-ninja' ) . '</strong> <code>' . esc_url( $reset_success_url ) . '</code></p></div>';
                } );
            }
            // Check for cleanup success notice.
            if ( isset( $_GET['legacy_cleanup'] ) && 'success' === sanitize_text_field( wp_unslash( $_GET['legacy_cleanup'] ) ) ) {
                add_action( 'admin_notices', function () {
                    echo '<div class="notice notice-success secnin-notice"><p>' . esc_html__( 'Cleanup completed successfully.', 'security-ninja' ) . '</p></div>';
                } );
            }
            if ( isset( $_GET['visitor_log_cleared'] ) && 'success' === sanitize_text_field( wp_unslash( $_GET['visitor_log_cleared'] ) ) ) {
                add_action( 'admin_notices', function () {
                    echo '<div class="notice notice-success secnin-notice"><p>' . esc_html__( 'Firewall visitor log cleared successfully.', 'security-ninja' ) . '</p></div>';
                } );
            }
            // Check for 2FA reset success notice.
            if ( isset( $_GET['reset_2fa'] ) && 'success' === sanitize_text_field( wp_unslash( $_GET['reset_2fa'] ) ) ) {
                add_action( 'admin_notices', function () {
                    echo '<div class="notice notice-success secnin-notice"><p>' . esc_html__( 'Two-factor authentication has been reset for all users.', 'security-ninja' ) . '</p></div>';
                } );
            }
            if ( secnin_fs()->can_use_premium_code__premium_only() ) {
                if ( isset( $_POST['wf-sn-install-routines'] ) ) {
                    if ( check_admin_referer( 'wf-sn-update-database', '_wpnonce_update_database' ) ) {
                        // Check user capabilities
                        if ( !current_user_can( 'manage_options' ) ) {
                            wp_die( 'Permission denied.' );
                        }
                        // Run activation routine for current site only
                        global $wpdb;
                        include_once ABSPATH . 'wp-admin/includes/upgrade.php';
                        $charset = $wpdb->get_charset_collate();
                        \WPSecurityNinja\Plugin\Utils::create_tables_for_site( $charset );
                        // Log the manual activation
                        if ( class_exists( '\\WPSecurityNinja\\Plugin\\Wf_Sn_El_Modules' ) ) {
                            \WPSecurityNinja\Plugin\Wf_Sn_El_Modules::log_event( 'security_ninja', 'installation_routines', 'Ran activation routines manually for site: ' . get_current_blog_id() );
                        }
                        // Redirect with success message
                        wp_safe_redirect( add_query_arg( 'manual_activation', 'success', admin_url( 'admin.php?page=wf-sn-tools' ) ) );
                        exit;
                    } else {
                        wp_die( 'Security check failed.' );
                    }
                }
                if ( isset( $_POST['secnin-reset-2fa'] ) ) {
                    if ( check_admin_referer( 'wf-sn-reset-2fa', '_wpnonce_reset_2fa' ) ) {
                        // check user capabilities
                        if ( !current_user_can( 'manage_options' ) ) {
                            wp_die( 'Permission denied.' );
                        }
                        global $wpdb;
                        $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->usermeta} WHERE meta_key LIKE %s", 'secnin_2fa_%' ) );
                        \WPSecurityNinja\Plugin\Wf_Sn_El_Modules::log_event( 'security_ninja', '2fa', 'Reset 2FA for all users.' );
                        wp_safe_redirect( add_query_arg( 'reset_2fa', 'success', admin_url( 'admin.php?page=wf-sn-tools' ) ) );
                        exit;
                    } else {
                        wp_die( 'Security check failed.' );
                    }
                }
                if ( isset( $_POST['wf-sn-legacy-cleanup'] ) ) {
                    if ( check_admin_referer( 'wf-sn-legacy-cleanup', '_wpnonce_legacy_cleanup' ) ) {
                        if ( !current_user_can( 'manage_options' ) ) {
                            wp_die( 'Permission denied.' );
                        }
                        delete_option( 'wf_sn_active_plugins' );
                        wp_safe_redirect( add_query_arg( 'legacy_cleanup', 'success', admin_url( 'admin.php?page=wf-sn-tools' ) ) );
                        exit;
                    } else {
                        wp_die( 'Security check failed.' );
                    }
                }
                if ( isset( $_POST['wf-sn-clear-visitor-log'] ) ) {
                    if ( check_admin_referer( 'wf-sn-clear-visitor-log', '_wpnonce_clear_visitor_log' ) ) {
                        if ( !current_user_can( 'manage_options' ) ) {
                            wp_die( 'Permission denied.' );
                        }
                        if ( class_exists( __NAMESPACE__ . '\\Wf_sn_cf' ) ) {
                            \WPSecurityNinja\Plugin\Wf_sn_cf::clear_visitor_log();
                        }
                        wp_safe_redirect( add_query_arg( 'visitor_log_cleared', 'success', admin_url( 'admin.php?page=wf-sn-tools' ) ) );
                        exit;
                    } else {
                        wp_die( 'Security check failed.' );
                    }
                }
            }
        }

        /**
         * Returns the one-time post-install/post-license landing page.
         *
         * @since 5.291
         * @access private static
         * @return string Admin URL.
         */
        private static function get_post_install_redirect_url() {
            if ( class_exists( __NAMESPACE__ . '\\Wf_Sn_Wiz' ) && !\WPSecurityNinja\Plugin\Wf_Sn_Wiz::is_wizard_completed() ) {
                return admin_url( 'admin.php?page=security-ninja-wizard' );
            }
            return admin_url( 'admin.php?page=wf-sn' );
        }

        /**
         * Redirects once after a true first install only.
         *
         * The redirect flag is only set when wf_sn_options did not exist before
         * activation, so updates and later reactivations of existing installs do
         * not reopen the wizard.
         *
         * @since 5.291
         * @access private static
         * @return void
         */
        private static function maybe_redirect_after_first_install() {
            if ( wp_doing_ajax() || is_network_admin() || !current_user_can( 'manage_options' ) ) {
                return;
            }
            $redirect_user_id = absint( get_option( 'secnin_activation_redirect', 0 ) );
            if ( !$redirect_user_id || get_current_user_id() !== $redirect_user_id ) {
                return;
            }
            delete_option( 'secnin_activation_redirect' );
            wp_safe_redirect( self::get_post_install_redirect_url() );
            exit;
        }

        /**
         * do_action_activated_plugin.
         *
         * @author  Lars Koudal
         * @since   v0.0.1
         * @version v1.0.0  Saturday, March 6th, 2021.
         * @access  public static
         * @param   mixed $plugin
         * @param   mixed $network_wide
         * @return  void
         */
        public static function do_action_activated_plugin( $plugin, $network_wide ) {
            // Bail if activating from network or bulk sites.
            if ( is_network_admin() || isset( $_GET['activate-multi'] ) ) {
                return;
            }
        }

        /**
         * Handle new site creation in multisite.
         *
         * @author  Lars Koudal
         * @since   v0.0.1
         * @version v1.0.0  Tuesday, December 7th, 2021.
         * @access  public static
         * @param   int $site_id The site ID.
         * @return  void
         */
        public static function handle_new_site( $site_id ) {
            if ( !is_multisite() ) {
                return;
            }
            switch_to_blog( $site_id );
            global $wpdb;
            include_once ABSPATH . 'wp-admin/includes/upgrade.php';
            $charset = $wpdb->get_charset_collate();
            \WPSecurityNinja\Plugin\Utils::create_tables_for_site( $charset );
            restore_current_blog();
        }

        /**
         * Custom logo URL for Freemius dialogue
         *
         * @author  Lars Koudal
         * @since   v0.0.1
         * @version v1.0.0  Wednesday, January 13th, 2021.
         * @access  public static
         * @return  mixed
         */
        public static function secnin_fs_custom_icon() {
            return __DIR__ . '/images/plugin-icon.png';
        }

        /**
         * Update dismissed notice
         *
         * @author  Lars Koudal
         * @author  Unknown
         * @since   v0.0.1
         * @version v1.0.0  Tuesday, January 12th, 2021.
         * @version v1.0.1  Friday, September 8th, 2023.
         * @version v1.0.2  Thursday, November 30th, 2023.
         * @access  public static
         * @return  void
         */
        public static function wf_sn_dismiss_review() {
            check_ajax_referer( 'wf_sn_dismiss_review' );
            if ( !current_user_can( 'manage_options' ) ) {
                wp_send_json_error( array(
                    'message' => 'Error saving dismiss pointer.',
                ) );
                wp_die();
            }
            $review = get_option( 'wf_sn_review_notice' );
            if ( !$review ) {
                $review = array();
            }
            $review['time'] = time() + WEEK_IN_SECONDS * 4;
            $review['dismissed'] = true;
            if ( isset( $_POST['user_data'] ) ) {
                $review['signed_up'] = true;
            }
            update_option( 'wf_sn_review_notice', $review, false );
            die;
        }

        /**
         * Reorders plugin tabs: Events after Firewall, Pro tabs (Login, Scheduler, Malware, Whitelabel) at the end.
         *
         * @param   array $tabs Array of tab definitions (each with 'id', etc.).
         * @return  array Reordered tabs.
         */
        public static function reorder_sn_tabs( $tabs ) {
            if ( !is_array( $tabs ) || empty( $tabs ) ) {
                return $tabs;
            }
            $pro_order = array(
                'sn_login',
                'sn_schedule',
                'sn_malware',
                'sn_whitelabel'
            );
            // 1. Move sn_logger to immediately after sn_cf.
            $idx_cf = null;
            $idx_logger = null;
            foreach ( $tabs as $i => $tab ) {
                if ( !empty( $tab['id'] ) ) {
                    if ( $tab['id'] === 'sn_cf' ) {
                        $idx_cf = $i;
                    }
                    if ( $tab['id'] === 'sn_logger' ) {
                        $idx_logger = $i;
                    }
                }
            }
            if ( $idx_cf !== null && $idx_logger !== null && $idx_logger !== $idx_cf + 1 ) {
                $logger_tab = $tabs[$idx_logger];
                array_splice( $tabs, $idx_logger, 1 );
                $insert_at = ( $idx_logger < $idx_cf ? $idx_cf : $idx_cf + 1 );
                array_splice(
                    $tabs,
                    $insert_at,
                    0,
                    array($logger_tab)
                );
            }
            // 2. Move Pro tabs to the end in order: Login, Scheduler, Malware, Whitelabel.
            $pro_tabs = array();
            foreach ( $pro_order as $id ) {
                foreach ( $tabs as $tab ) {
                    if ( !empty( $tab['id'] ) && $tab['id'] === $id ) {
                        $pro_tabs[] = $tab;
                        break;
                    }
                }
            }
            $tabs = array_values( array_filter( $tabs, function ( $tab ) use($pro_order) {
                return empty( $tab['id'] ) || !in_array( $tab['id'], $pro_order, true );
            } ) );
            return array_merge( $tabs, $pro_tabs );
        }

        /**
         * Prepares the tabs for the plugin interface
         *
         * @author  Lars Koudal <me@larsik.com>
         * @author  Lars Koudal
         * @since   v0.0.1
         * @version v1.0.0  Saturday, March 5th, 2022.
         * @version v1.0.1  Friday, November 17th, 2023.
         * @access  public static
         * @param   mixed $intabs Array of tabs for plugin to be processed
         * @return  mixed
         */
        public static function return_tabs( $intabs ) {
            if ( secnin_fs()->can_use_premium_code__premium_only() ) {
                return self::reorder_sn_tabs( $intabs );
            }
            $malware_tab = array(
                'id'       => 'sn_malware',
                'class'    => 'profeature',
                'label'    => esc_html__( 'Malware', 'security-ninja' ),
                'callback' => array(__NAMESPACE__ . '\\Free_Render', 'render_malware_page'),
            );
            $schedule_tab = array(
                'id'       => 'sn_schedule',
                'class'    => 'profeature',
                'label'    => esc_html__( 'Scheduler', 'security-ninja' ),
                'callback' => array(__NAMESPACE__ . '\\Free_Render', 'render_scheduled_scanner_page'),
            );
            $whitelabel_tab = array(
                'id'       => 'sn_whitelabel',
                'class'    => 'profeature',
                'label'    => esc_html__( 'White label', 'security-ninja' ),
                'callback' => array(__NAMESPACE__ . '\\Utils', 'render_whitelabel_page'),
            );
            $login_tab = array(
                'id'       => 'sn_login',
                'class'    => 'profeature',
                'label'    => esc_html__( 'Login', 'security-ninja' ),
                'callback' => array(__NAMESPACE__ . '\\wf_sn_cf', 'do_login_page'),
            );
            $outtabs = $intabs;
            $login_inserted = false;
            foreach ( $outtabs as $idx => $tab ) {
                if ( !empty( $tab['id'] ) && $tab['id'] === 'sn_cf' ) {
                    $has_login = false;
                    foreach ( $outtabs as $t ) {
                        if ( !empty( $t['id'] ) && $t['id'] === 'sn_login' ) {
                            $has_login = true;
                            break;
                        }
                    }
                    if ( !$has_login ) {
                        array_splice(
                            $outtabs,
                            $idx + 1,
                            0,
                            array($login_tab)
                        );
                    }
                    $login_inserted = true;
                    break;
                }
            }
            if ( !$login_inserted ) {
                $has_login = false;
                foreach ( $outtabs as $t ) {
                    if ( !empty( $t['id'] ) && $t['id'] === 'sn_login' ) {
                        $has_login = true;
                        break;
                    }
                }
                if ( !$has_login ) {
                    $outtabs[] = $login_tab;
                }
            }
            $outtabs[] = $schedule_tab;
            $outtabs[] = $malware_tab;
            $outtabs[] = $whitelabel_tab;
            $outtabs = self::reorder_sn_tabs( $outtabs );
            return $outtabs;
        }

        /**
         * Checks if the current page is a part of this plugin.
         *
         * @author  Lars Koudal
         * @since   v0.0.1
         * @version v1.0.0  Wednesday, January 13th, 2021.
         * @access  public static
         * @return  bool Returns true if the current page is a part of this plugin, otherwise false.
         */
        public static function is_plugin_page() {
            $current_screen = get_current_screen();
            if ( !$current_screen ) {
                return false;
            }
            // Get the current screen ID
            $current_id = $current_screen->id;
            // Extract the page part after the last underscore
            $page_part = substr( $current_id, strrpos( $current_id, '_' ) + 1 );
            // error_log(print_r($page_part, true));
            // Define our plugin pages using just the page part
            $plugin_pages = array();
            $plugin_pages[] = 'wf-sn';
            $plugin_pages[] = 'security-ninja-welcome';
            $plugin_pages[] = 'wf-sn-overview';
            $plugin_pages[] = 'wf-sn-firewall';
            $plugin_pages[] = 'wf-sn-scanner';
            $plugin_pages[] = 'wf-sn-events';
            $plugin_pages[] = 'wf-sn-advisor';
            $plugin_pages[] = 'wf-sn-settings';
            $plugin_pages[] = 'security-ninja-wizard';
            if ( secnin_fs()->can_use_premium_code__premium_only() ) {
                // Visitor Log page only when Cloud Firewall visitor logging is enabled.
                if ( class_exists( __NAMESPACE__ . '\\wf_sn_cf' ) ) {
                    $cf_opts = \WPSecurityNinja\Plugin\wf_sn_cf::get_options();
                    if ( !empty( $cf_opts['trackvisits'] ) ) {
                        $plugin_pages[] = 'wf-sn-visitor-log';
                    }
                }
                $plugin_pages[] = 'wf-sn-tools';
                $plugin_pages[] = 'wf-sn-fixes';
            }
            // Check if the page part matches any of our plugin pages
            return in_array( $page_part, $plugin_pages, true );
        }

        /**
         * Register and enqueue shared SnDialog assets (free + Pro).
         *
         * Safe to call from any admin or front-end surface that needs dialogs.
         *
         * @since 5.292
         * @return void
         */
        public static function enqueue_sn_dialog() {
            wp_enqueue_style(
                'sn-dialog',
                WF_SN_PLUGIN_URL . 'css/sn-dialog.css',
                array(),
                filemtime( WF_SN_PLUGIN_DIR . 'css/sn-dialog.css' )
            );
            if ( !wp_script_is( 'sn-dialog', 'registered' ) ) {
                wp_register_script(
                    'sn-dialog',
                    WF_SN_PLUGIN_URL . 'js/sn-dialog.js',
                    array(),
                    filemtime( WF_SN_PLUGIN_DIR . 'js/sn-dialog.js' ),
                    true
                );
                wp_localize_script( 'sn-dialog', 'snDialogL10n', array(
                    'ok'     => esc_html__( 'OK', 'security-ninja' ),
                    'cancel' => esc_html__( 'Cancel', 'security-ninja' ),
                    'close'  => esc_html__( 'Close', 'security-ninja' ),
                ) );
            }
            wp_enqueue_script( 'sn-dialog' );
        }

        /**
         * Enqueue CSS and JS scripts on plugin's pages
         *
         * @author  Lars Koudal
         * @author  Unknown
         * @since   v0.0.1
         * @version v1.0.0  Wednesday, January 13th, 2021.
         * @version v1.0.1  Sunday, May 11th, 2025.
         * @access  public static
         * @param   mixed $hook
         * @return  void
         */
        public static function enqueue_scripts( $hook ) {
            if ( 'wp-admin/update.php' === $GLOBALS['pagenow'] ) {
                return;
            }
            if ( self::is_plugin_page() ) {
                $sn_global_deps = array('jquery', 'jquery-ui-tabs');
                wp_enqueue_script( 'jquery-ui-tabs' );
                wp_enqueue_script(
                    'sn-global',
                    WF_SN_PLUGIN_URL . 'js/sn-global.js',
                    $sn_global_deps,
                    filemtime( WF_SN_PLUGIN_DIR . 'js/sn-global.js' ),
                    true
                );
                wp_enqueue_script(
                    'sn-jquery-plugins',
                    WF_SN_PLUGIN_URL . 'js/sn-jquery-plugins.js',
                    array('jquery'),
                    filemtime( WF_SN_PLUGIN_DIR . 'js/sn-jquery-plugins.js' ),
                    true
                );
                self::enqueue_sn_dialog();
                if ( secnin_fs()->can_use_premium_code__premium_only() ) {
                    // Loads code only for premium
                    wp_enqueue_style(
                        'sn-css-pro',
                        WF_SN_PLUGIN_URL . 'css/sn-style-pro__premium_only.css',
                        array(),
                        filemtime( WF_SN_PLUGIN_DIR . 'css/sn-style-pro__premium_only.css' )
                    );
                }
                $is_registered = false;
                if ( secnin_fs()->is_registered() ) {
                    $is_registered = true;
                }
                $is_tracking_allowed = false;
                if ( secnin_fs()->is_tracking_allowed() ) {
                    $is_tracking_allowed = true;
                }
                if ( $is_registered && $is_tracking_allowed ) {
                    // Checks if user opted-in (or activated a license) and didn't opt out from tracking.
                    wp_enqueue_script(
                        'security-ninja-widget-sdk',
                        'https://securityninja.productlift.dev/widgets_sdk',
                        array(),
                        \WPSecurityNinja\Plugin\Utils::get_plugin_version(),
                        // Add version to prevent caching issues
                        true
                    );
                }
                // sn-common.js and wf_sn localization on all plugin pages (tests, dialogs, etc.). ProductLift stays gated above.
                wp_register_script(
                    'sn-js',
                    WF_SN_PLUGIN_URL . 'js/sn-common.js',
                    array('jquery', 'wp-i18n', 'sn-dialog'),
                    filemtime( WF_SN_PLUGIN_DIR . 'js/sn-common.js' ),
                    true
                );
                $js_vars = array(
                    'sn_plugin_url'          => WF_SN_PLUGIN_URL,
                    'nonce_run_tests'        => wp_create_nonce( 'wf_sn_run_tests' ),
                    'nonce_refresh_update'   => wp_create_nonce( 'wf_sn_refresh_update' ),
                    'nonce_dismiss_review'   => wp_create_nonce( 'wf_sn_dismiss_review' ),
                    'nonce_reset_activation' => wp_create_nonce( 'wf_sn_reset_activation' ),
                    'nonce_latest_events'    => wp_create_nonce( 'wf_sn_latest_events' ),
                    'nonce_reset_secret_url' => wp_create_nonce( 'wf-sn-reset-secret-url' ),
                    'lc_version'             => \WPSecurityNinja\Plugin\Utils::get_plugin_version(),
                    'lc_site'                => get_home_url(),
                    'lc_ip'                  => $_SERVER['REMOTE_ADDR'],
                    'strings'                => array(
                        'reset_secret_url_confirm' => esc_html__( 'Are you sure you want to reset the secret access URL? The old link will no longer function.', 'security-ninja' ),
                        'resetting'                => esc_html__( 'Resetting...', 'security-ninja' ),
                        'resetting_message'        => esc_html__( 'Resetting secret access URL...', 'security-ninja' ),
                        'reset_button_text'        => esc_html__( 'Reset Secret Access URL', 'security-ninja' ),
                        'error_unknown'            => esc_html__( 'Unknown error occurred', 'security-ninja' ),
                        'error_failed'             => esc_html__( 'Failed to reset secret access URL. Please try again.', 'security-ninja' ),
                    ),
                );
                wp_localize_script( 'sn-js', 'wf_sn', $js_vars );
                wp_enqueue_script( 'sn-js' );
                wp_enqueue_style(
                    'sn-css',
                    WF_SN_PLUGIN_URL . 'css/sn-style.css',
                    array(),
                    filemtime( WF_SN_PLUGIN_DIR . 'css/sn-style.css' )
                );
                // Removing scripts and styles from other plugins we know mess up the interface
                wp_dequeue_style( 'uiStyleSheet' );
                wp_dequeue_style( 'wpcufpnAdmin' );
                wp_dequeue_style( 'unifStyleSheet' );
                wp_dequeue_style( 'wpcufpn_codemirror' );
                wp_dequeue_style( 'wpcufpn_codemirrorTheme' );
                wp_dequeue_style( 'collapse-admin-css' );
                wp_dequeue_style( 'jquery-ui-css' );
                wp_dequeue_style( 'tribe-common-admin' );
                wp_dequeue_style( 'file-manager__jquery-ui-css' );
                wp_dequeue_style( 'file-manager__jquery-ui-css-theme' );
                wp_dequeue_style( 'wpmegmaps-jqueryui' );
                wp_dequeue_style( 'facebook-plugin-css' );
                wp_dequeue_style( 'facebook-tip-plugin-css' );
                wp_dequeue_style( 'facebook-member-plugin-css' );
                wp_dequeue_style( 'kc-testimonial-admin' );
                wp_dequeue_style( 'jquery-ui-style' );
            }
        }

        /**
         * add entry to admin menu
         *
         * @author  Unknown
         * @since   v0.0.1
         * @version v1.0.0  Friday, February 5th, 2021.
         * @access  public static
         * @return  void
         */
        public static function admin_menu() {
            // Define menu constants
            $page_title = 'Security';
            $menu_title = 'Security Ninja';
            $capability = 'manage_options';
            $menu_slug = 'wf-sn';
            $icon_url = \WPSecurityNinja\Plugin\Utils::get_icon_svg( true );
            // Handle whitelabel settings
            if ( secnin_fs()->can_use_premium_code__premium_only() ) {
                if ( file_exists( WF_SN_PLUGIN_DIR . 'modules/whitelabel/class-wf-sn-wl.php' ) ) {
                    include_once WF_SN_PLUGIN_DIR . 'modules/whitelabel/class-wf-sn-wl.php';
                }
                if ( class_exists( 'WPSecurityNinja\\Plugin\\Wf_Sn_Wl' ) ) {
                    if ( \WPSecurityNinja\Plugin\Wf_Sn_Wl::is_active() ) {
                        $get_new_menu_icon_url = \WPSecurityNinja\Plugin\Wf_Sn_Wl::get_new_menu_icon_url();
                        if ( $get_new_menu_icon_url ) {
                            $icon_url = $get_new_menu_icon_url;
                        }
                        $menu_title = \WPSecurityNinja\Plugin\Wf_Sn_Wl::get_new_name();
                    }
                }
            }
            // Add notification count if needed
            $notification_count = false;
            if ( class_exists( __NAMESPACE__ . '\\Wf_Sn_Vu' ) ) {
                $vu_options = wf_sn_vu::get_options();
                if ( $vu_options['enable_admin_notification'] ) {
                    try {
                        $notification_count = Wf_Sn_Vu::return_vuln_count();
                    } catch ( \Exception $e ) {
                        $notification_count = false;
                    }
                }
            }
            // Register main menu only if it doesn't exist
            if ( !menu_page_url( $menu_slug, false ) ) {
                add_menu_page(
                    $page_title,
                    ( $notification_count ? sprintf( $menu_title . ' <span class="awaiting-mod">%d</span>', $notification_count ) : $menu_title ),
                    $capability,
                    $menu_slug,
                    array(__NAMESPACE__ . '\\wf_sn', 'main_page'),
                    $icon_url
                );
            }
            // Register submenus only for pro users
            $add_advisor_for_free = true;
            if ( secnin_fs()->can_use_premium_code__premium_only() ) {
                $add_advisor_for_free = false;
                $show_only_advisor_submenu = true;
                if ( secnin_fs()->can_use_premium_code() ) {
                    $show_only_advisor_submenu = false;
                    // Define submenu items (Security Advisor is shown for all users when enabled).
                    $submenus = array(array(
                        'parent'    => $menu_slug,
                        'title'     => __( 'Security Advisor', 'security-ninja' ),
                        'menu'      => __( 'Security Advisor', 'security-ninja' ),
                        'slug'      => 'wf-sn-advisor',
                        'callback'  => array('\\WPSecurityNinja\\Plugin\\AiAdvisor\\Wf_Sn_Ai_Advisor', 'render_page'),
                        'all_users' => true,
                    ), array(
                        'parent'   => $menu_slug,
                        'title'    => 'Security Fixes',
                        'menu'     => 'Fixes',
                        'slug'     => 'wf-sn-fixes',
                        'callback' => array(__NAMESPACE__ . '\\Wf_Sn_Fixes', 'fixes_page__premium_only'),
                    ));
                    // Visitor Log is a premium feature; only add menu item when Cloud Firewall visitor logging is enabled.
                    if ( class_exists( __NAMESPACE__ . '\\wf_sn_cf' ) ) {
                        $cf_options = \WPSecurityNinja\Plugin\wf_sn_cf::get_options();
                        if ( !empty( $cf_options['trackvisits'] ) ) {
                            $submenus[] = array(
                                'parent'   => $menu_slug,
                                'title'    => 'Visitors',
                                'menu'     => 'Visitor Log',
                                'slug'     => 'wf-sn-visitor-log',
                                'callback' => array(__NAMESPACE__ . '\\Wf_Sn_Visitor_log', 'live_log_page'),
                            );
                        }
                    }
                    $submenus[] = array(
                        'parent'   => $menu_slug,
                        'title'    => 'Tools',
                        'menu'     => 'Tools',
                        'slug'     => 'wf-sn-tools',
                        'callback' => array(__NAMESPACE__ . '\\wf_sn', 'tools_page__premium_only'),
                    );
                    // Register each submenu (advisor skipped when filter disabled).
                    foreach ( $submenus as $submenu ) {
                        $is_advisor = !empty( $submenu['all_users'] );
                        if ( $is_advisor && !apply_filters( 'wf_sn_ai_advisor_enabled', true ) ) {
                            continue;
                        }
                        add_submenu_page(
                            $submenu['parent'],
                            $submenu['title'],
                            $submenu['menu'],
                            $capability,
                            $submenu['slug'],
                            $submenu['callback']
                        );
                    }
                }
                if ( $show_only_advisor_submenu ) {
                    // Premium build but no license: show only Security Advisor (same as free).
                    if ( apply_filters( 'wf_sn_ai_advisor_enabled', true ) ) {
                        add_submenu_page(
                            $menu_slug,
                            __( 'Security Advisor', 'security-ninja' ),
                            __( 'Security Advisor', 'security-ninja' ),
                            $capability,
                            'wf-sn-advisor',
                            array('\\WPSecurityNinja\\Plugin\\AiAdvisor\\Wf_Sn_Ai_Advisor', 'render_page')
                        );
                    }
                }
            }
            if ( $add_advisor_for_free && apply_filters( 'wf_sn_ai_advisor_enabled', true ) ) {
                // Free build: only Security Advisor submenu (premium block was stripped)
                add_submenu_page(
                    $menu_slug,
                    __( 'Security Advisor', 'security-ninja' ),
                    __( 'Security Advisor', 'security-ninja' ),
                    $capability,
                    'wf-sn-advisor',
                    array('\\WPSecurityNinja\\Plugin\\AiAdvisor\\Wf_Sn_Ai_Advisor', 'render_page')
                );
            }
            $wizard_menu_label = ( \WPSecurityNinja\Plugin\Wf_Sn_Wiz::is_wizard_completed() ? __( 'Setup Wizard', 'security-ninja' ) : __( 'Get started', 'security-ninja' ) );
            add_submenu_page(
                $menu_slug,
                __( 'Setup Wizard', 'security-ninja' ),
                $wizard_menu_label,
                $capability,
                'security-ninja-wizard',
                array(__NAMESPACE__ . '\\Wf_Sn_Wiz', 'wizard_page')
            );
            // Append AI badge to Security Advisor menu item (all users when advisor is shown)
            if ( apply_filters( 'wf_sn_ai_advisor_enabled', true ) ) {
                add_filter( 'admin_menu', array(__CLASS__, 'add_advisor_menu_badge'), 999 );
                add_action( 'admin_enqueue_scripts', array(__CLASS__, 'enqueue_advisor_menu_badge_style'), 10 );
            }
        }

        /**
         * Appends the "AI" badge to the Security Advisor submenu title.
         *
         * @since 5.273
         */
        public static function add_advisor_menu_badge() {
            global $submenu;
            $parent = 'wf-sn';
            if ( empty( $submenu[$parent] ) || !is_array( $submenu[$parent] ) ) {
                return;
            }
            foreach ( $submenu[$parent] as $key => $item ) {
                if ( isset( $item[2] ) && $item[2] === 'wf-sn-advisor' ) {
                    $submenu[$parent][$key][0] = $item[0] . ' <span class="sn-ai-menu-badge">AI</span>';
                    break;
                }
            }
        }

        /**
         * Outputs inline CSS for the Security Advisor menu badge on all admin pages.
         *
         * @since 5.273
         */
        public static function enqueue_advisor_menu_badge_style() {
            wp_enqueue_style( 'dashicons' );
            $css = '.sn-ai-menu-badge{display:inline-block;margin-left:4px;padding:2px 6px;font-size:10px;font-weight:600;line-height:1.2;color:#1d2327;background:#dcdcde;border-radius:3px;vertical-align:middle}';
            wp_add_inline_style( 'dashicons', $css );
        }

        /**
         * Returns global options for premium users.
         *
         * This method collects and returns various global options specific to premium users.
         * It includes the plugin version, firewall settings, vulnerability scan results, events logger data,
         * malware scanner information, scheduled scanner settings, whitelabel plugin settings, and fixes settings.
         *
         * @author  Unknown
         * @since   v0.0.1
         * @version v1.0.0  Friday, December 8th, 2023.
         * @access  public static
         * @return  array An array containing global options for premium users.
         */
        public static function return_global_options__premium_only() {
            $globaloptions = array();
            // Add version at the start
            $globaloptions['plugin_version'] = \WPSecurityNinja\Plugin\Utils::get_plugin_version();
            $wf_sn_cf_options = \WPSecurityNinja\Plugin\wf_sn_cf::get_options();
            if ( isset( $wf_sn_cf_options['unblock_url'] ) ) {
                $wf_sn_cf_options = array_diff_key( $wf_sn_cf_options, array(
                    'unblock_url' => true,
                ) );
            }
            $globaloptions['firewall'] = $wf_sn_cf_options;
            // lets not export this
            $wf_sn_vu_options = wf_sn_vu::get_options();
            $globaloptions['vulns'] = $wf_sn_vu_options;
            $globaloptions['eventslogger'] = get_option( 'wf_sn_el' );
            $ss_options = get_option( WF_SN_SS_OPTIONS_KEY );
            $globaloptions['scheduledscanner'] = $ss_options;
            $wl_options = get_option( 'wf_sn_wl' );
            $globaloptions['whitelabel'] = $wl_options;
            $fixes_options = get_option( WF_SN_FIXES_OPTIONS_KEY );
            $globaloptions['fixes'] = $fixes_options;
            $globaloptions['malware_scanner'] = array(
                'whitelist' => get_option( 'wf_sn_ms_whitelist', array() ),
            );
            if ( !class_exists( '\\WPSecurityNinja\\Plugin\\Wf_Sn_Cs_Utils' ) ) {
                require_once WF_SN_PLUGIN_DIR . 'modules/core-scanner/class-wf-sn-cs-utils.php';
            }
            $globaloptions['core_scanner'] = array(
                'user_ignore' => \WPSecurityNinja\Plugin\Wf_Sn_Cs_Utils::get_user_ignored_files(),
            );
            return $globaloptions;
        }

        /**
         * tools_page__premium_only.
         *
         * @author  Lars Koudal
         * @since   v0.0.1
         * @version v1.0.0  Wednesday, January 13th, 2021.
         * @access  public static
         * @return  void
         */
        public static function tools_page__premium_only() {
            ?>
			<div class="wrap">
				<?php 
            \WPSecurityNinja\Plugin\Utils::show_topbar();
            // Display success message if manual activation routine was run
            if ( isset( $_GET['manual_activation'] ) && 'success' === $_GET['manual_activation'] && isset( $_GET['page'] ) && 'wf-sn-tools' === $_GET['page'] ) {
                echo '<div class="notice notice-success is-dismissible secnin-notice"><p>' . esc_html__( 'Database tables have been successfully updated.', 'security-ninja' ) . '</p></div>';
            }
            if ( isset( $_GET['ai_history_cleared'] ) && 'success' === $_GET['ai_history_cleared'] && isset( $_GET['page'] ) && 'wf-sn-tools' === $_GET['page'] ) {
                echo '<div class="notice notice-success is-dismissible secnin-notice"><p>' . esc_html__( 'AI Advisor history cleared. You can generate a fresh report from Security Advisor.', 'security-ninja' ) . '</p></div>';
            }
            ?>
				<div class="secnin_content_wrapper">
					<div class="secnin_content_cell" id="secnin_content_top">
						<div class="submit-test-container sncard settings-card">
							<h2><?php 
            esc_html_e( 'Import Settings', 'security-ninja' );
            ?></h2>
							<form action="options.php" id="sn-import-settings" method="post">
								<?php 
            if ( !empty( $_REQUEST['page'] ) ) {
                $page = sanitize_text_field( $_REQUEST['page'] );
                echo '<input type="hidden" name="page" value="' . esc_attr( $page ) . '" />';
            }
            ?>
								<input type="hidden" name="action" value="wf_sn_import_settings" />
								<?php 
            wp_nonce_field( 'wf-sn-import-nonce', '_wpnonce_import_settings' );
            wp_referer_field();
            ?>
								<textarea name="import_wf_data" id="import_wf_data" class="large-text" rows="10" placeholder="<?php 
            esc_html_e( 'Copy-paste the exported settings from another website and click import.', 'security-ninja' );
            ?>"></textarea>
								<?php 
            submit_button(
                esc_html__( 'Import Settings', 'security-ninja' ),
                'secondary',
                'wf-import-settings',
                true,
                array(
                    'id' => 'wf-import-settings-button',
                )
            );
            ?>
							</form>
							<hr>
							<h3><?php 
            esc_html_e( 'Export Settings', 'security-ninja' );
            ?></h3>
							<?php 
            $globaloptions = self::return_global_options__premium_only();
            ?>
							<textarea name="export_wf_data" id="export_wf_data" class="large-text" rows="7">
							<?php 
            echo esc_textarea( wp_json_encode( $globaloptions ) );
            ?>
							</textarea>
							<p>
								<?php 
            esc_html_e( 'Note: License data is not exported or imported. You have to enter license details before you can import settings.', 'security-ninja' );
            ?>
							</p>

							<hr>

							<h3><?php 
            esc_html_e( 'Debug and Support', 'security-ninja' );
            ?></h3>
							<div class="sn-tools-actions">
								<div class="sn-tools-action">
									<h4><?php 
            esc_html_e( 'Setup wizard', 'security-ninja' );
            ?></h4>
									<p class="description">
										<?php 
            esc_html_e( 'Run the getting started wizard again to apply default firewall, events, vulnerability, login, fixes, and WooCommerce settings. Activate steps replace saved settings for each module.', 'security-ninja' );
            ?>
									</p>
									<p class="sn-tools-action__controls">
										<a
											href="<?php 
            echo esc_url( admin_url( 'admin.php?page=security-ninja-wizard' ) );
            ?>"
											class="button button-secondary sn-confirm-submit"
											data-sn-confirm="<?php 
            echo esc_attr__( 'Restart the setup wizard? Activate steps can replace saved settings for firewall, events, vulnerability, login, fixes, and WooCommerce modules.', 'security-ninja' );
            ?>"
										><?php 
            esc_html_e( 'Restart setup wizard', 'security-ninja' );
            ?></a>
									</p>
								</div>

								<div class="sn-tools-action">
									<h4><?php 
            esc_html_e( 'Update database', 'security-ninja' );
            ?></h4>
									<p class="description">
										<?php 
            esc_html_e( 'Manually run the plugin activation routine to ensure database tables are properly created.', 'security-ninja' );
            ?>
									</p>
									<p class="description">
										<?php 
            esc_html_e( 'This is useful if you experience missing database tables or need to re-run the setup process.', 'security-ninja' );
            ?>
									</p>
									<form action="admin.php?page=wf-sn-tools" method="post" id="sn-update-database" class="sn-tools-action__form">
										<?php 
            if ( !empty( $_REQUEST['page'] ) ) {
                echo '<input type="hidden" name="page" value="' . esc_attr( $_REQUEST['page'] ) . '" />';
            }
            ?>
										<input type="hidden" name="action" value="wf_sn_run_install_routines">
										<input type="hidden" name="_wpnonce_update_database" value="<?php 
            echo esc_attr( wp_create_nonce( 'wf-sn-update-database' ) );
            ?>">
										<?php 
            wp_referer_field();
            ?>
										<p class="sn-tools-action__controls">
											<?php 
            submit_button(
                esc_html__( 'Update Database', 'security-ninja' ),
                'secondary sn-confirm-submit',
                'wf-sn-install-routines',
                false,
                array(
                    'id'              => 'wf-sn-do-install-routines',
                    'data-sn-confirm' => esc_attr__( 'Run the plugin database update routine now? This recreates or verifies Security Ninja tables.', 'security-ninja' ),
                )
            );
            ?>
										</p>
									</form>
								</div>

								<div class="sn-tools-action">
									<h4><?php 
            esc_html_e( 'Reset all users 2FA', 'security-ninja' );
            ?></h4>
									<p class="description">
										<?php 
            esc_html_e( 'Resets all users 2FA setup - forcing all users to start the setup process again before being able to log in.', 'security-ninja' );
            ?>
									</p>
									<form action="admin.php?page=wf-sn-tools" method="post" id="sn-reset-2fa-form" class="sn-tools-action__form">
										<?php 
            if ( !empty( $_REQUEST['page'] ) ) {
                echo '<input type="hidden" name="page" value="' . esc_attr( $_REQUEST['page'] ) . '" />';
            }
            ?>
										<input type="hidden" name="action" value="secnin_reset_2fa">
										<?php 
            wp_nonce_field( 'wf-sn-reset-2fa', '_wpnonce_reset_2fa' );
            wp_referer_field();
            ?>
										<p class="sn-tools-action__controls">
											<?php 
            submit_button(
                esc_html__( 'Reset 2FA - Two Factor Authentication', 'security-ninja' ),
                'secondary sn-confirm-submit',
                'secnin-reset-2fa',
                false,
                array(
                    'id'              => 'secnin-reset-twofa',
                    'data-sn-confirm' => esc_attr__( 'This will reset two-factor authentication for ALL users. Every affected user must set up 2FA again before they can log in. This action cannot be undone. Continue?', 'security-ninja' ),
                    'data-sn-danger'  => '1',
                )
            );
            ?>
										</p>
									</form>
								</div>

								<div class="sn-tools-action">
									<h4><?php 
            esc_html_e( 'Reset Secret Access URL', 'security-ninja' );
            ?></h4>
									<p class="description">
										<?php 
            esc_html_e( 'Warning: The old secret access link will no longer function. Please update wherever you have written down the link.', 'security-ninja' );
            ?>
									</p>
									<form id="sn-reset-secret-url-form" method="post" action="" class="sn-tools-action__form">
										<p class="sn-tools-action__controls">
											<?php 
            submit_button(
                esc_html__( 'Reset Secret Access URL', 'security-ninja' ),
                'secondary',
                'secnin-reset-secret-url',
                false,
                array(
                    'id' => 'secnin-reset-secret-url',
                )
            );
            ?>
										</p>
									</form>
								</div>
							</div>

							<h3><?php 
            esc_html_e( 'Clean up plugin data', 'security-ninja' );
            ?></h3>
							<p class="description">
								<?php 
            esc_html_e( 'Removes legacy Security Ninja database tables and stored data from older plugin versions. Use this only if support recommends it or you are troubleshooting leftover data. Create a full database backup before running cleanup.', 'security-ninja' );
            ?>
							</p>
							<div class="sn-tools-actions">
							<?php 
            global $wpdb;
            $visitor_log_table = $wpdb->prefix . 'wf_sn_cf_vl';
            $visitor_log_count = 0;
            $visitor_log_exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $visitor_log_table ) ) === $visitor_log_table;
            if ( $visitor_log_exists ) {
                $visitor_log_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$visitor_log_table}" );
            }
            ?>
								<div class="sn-tools-action">
									<h4><?php 
            esc_html_e( 'Firewall visitor log', 'security-ninja' );
            ?></h4>
									<p>
										<?php 
            if ( $visitor_log_exists ) {
                printf( 
                    /* translators: %s: number of visitor log entries */
                    esc_html__( 'Current visitor log entries: %s', 'security-ninja' ),
                    esc_html( number_format_i18n( $visitor_log_count ) )
                 );
            } else {
                esc_html_e( 'The visitor log table has not been created yet.', 'security-ninja' );
            }
            ?>
									</p>
									<p class="description">
										<?php 
            esc_html_e( 'Automatic cleanup runs daily based on the retention period under Cloud Firewall → Visitor Logging. Use this button to delete all visitor log entries immediately.', 'security-ninja' );
            ?>
									</p>
									<form action="admin.php?page=wf-sn-tools" method="post" id="sn-clear-visitor-log" class="sn-tools-action__form">
										<?php 
            if ( !empty( $_REQUEST['page'] ) ) {
                echo '<input type="hidden" name="page" value="' . esc_attr( $_REQUEST['page'] ) . '" />';
            }
            wp_nonce_field( 'wf-sn-clear-visitor-log', '_wpnonce_clear_visitor_log' );
            wp_referer_field();
            ?>
										<p class="sn-tools-action__controls">
											<?php 
            submit_button(
                esc_html__( 'Clear visitor log', 'security-ninja' ),
                'secondary sn-confirm-submit',
                'wf-sn-clear-visitor-log',
                false,
                array(
                    'id'              => 'wf-sn-clear-visitor-log',
                    'data-sn-confirm' => esc_attr__( 'This will permanently delete all firewall visitor log entries. This action cannot be undone. Continue?', 'security-ninja' ),
                    'data-sn-danger'  => '1',
                )
            );
            ?>
										</p>
									</form>
								</div>
							<?php 
            if ( apply_filters( 'wf_sn_ai_advisor_enabled', true ) ) {
                \WPSecurityNinja\Plugin\AiAdvisor\Wf_Sn_Ai_Advisor::load_dependencies();
                if ( class_exists( 'WPSecurityNinja\\Plugin\\AiAdvisor\\Wf_Sn_Ai_Advisor_Reports' ) ) {
                    $ai_history_counts = \WPSecurityNinja\Plugin\AiAdvisor\Wf_Sn_Ai_Advisor_Reports::count_all();
                    ?>
								<div class="sn-tools-action">
									<h4><?php 
                    esc_html_e( 'Security Ninja AI Advisor', 'security-ninja' );
                    ?></h4>
									<p>
										<?php 
                    printf( 
                        /* translators: 1: number of saved reports, 2: number of follow-up messages */
                        esc_html__( 'Saved AI reports: %1$s · Follow-up messages: %2$s', 'security-ninja' ),
                        esc_html( number_format_i18n( $ai_history_counts['full_report'] ) ),
                        esc_html( number_format_i18n( $ai_history_counts['prompt_chip'] ) )
                     );
                    ?>
									</p>
									<p class="description">
										<?php 
                    esc_html_e( 'Removes saved AI security reports and follow-up conversation history only. Scan results, data readiness, and site-wide priority hints are not affected.', 'security-ninja' );
                    ?>
									</p>
									<form method="post" action="<?php 
                    echo esc_url( admin_url( 'admin-post.php' ) );
                    ?>" id="sn-clear-ai-history" class="sn-tools-action__form">
										<input type="hidden" name="action" value="wf_sn_ai_advisor_clear_history" />
										<input type="hidden" name="redirect_to" value="wf-sn-tools" />
										<?php 
                    wp_nonce_field( 'wf_sn_ai_advisor_clear_history', '_wpnonce' );
                    ?>
										<p class="sn-tools-action__controls">
											<?php 
                    submit_button(
                        esc_html__( 'Clear all AI reports and history', 'security-ninja' ),
                        'delete sn-confirm-submit',
                        'wf-sn-clear-ai-history',
                        false,
                        array(
                            'id'              => 'wf-sn-clear-ai-history',
                            'data-sn-confirm' => esc_attr__( 'This will permanently delete all AI security reports and follow-up messages. Your connector settings will be kept. Continue?', 'security-ninja' ),
                            'data-sn-danger'  => '1',
                        )
                    );
                    ?>
										</p>
									</form>
								</div>
									<?php 
                }
            }
            ?>
								<div class="sn-tools-action">
									<h4><?php 
            esc_html_e( 'Legacy data cleanup', 'security-ninja' );
            ?></h4>
									<p class="description">
										<?php 
            esc_html_e( 'Removes leftover tables and options from older Security Ninja versions. Only use when support recommends it.', 'security-ninja' );
            ?>
									</p>
									<form action="admin.php?page=wf-sn-tools" method="post" id="sn-legacy-cleanup" class="sn-tools-action__form">
										<?php 
            if ( !empty( $_REQUEST['page'] ) ) {
                echo '<input type="hidden" name="page" value="' . esc_attr( $_REQUEST['page'] ) . '" />';
            }
            wp_nonce_field( 'wf-sn-legacy-cleanup', '_wpnonce_legacy_cleanup' );
            wp_referer_field();
            ?>
										<p class="sn-tools-action__controls">
											<?php 
            submit_button(
                esc_html__( 'Run legacy data cleanup', 'security-ninja' ),
                'secondary sn-confirm-submit',
                'wf-sn-legacy-cleanup',
                false,
                array(
                    'id'              => 'wf-sn-legacy-cleanup',
                    'data-sn-confirm' => esc_attr__( 'Please confirm you have a full database backup before continuing. This action cannot be undone.', 'security-ninja' ),
                    'data-sn-danger'  => '1',
                )
            );
            ?>
										</p>
									</form>
								</div>
							</div>





							<form action="options.php" method="post">
								<?php 
            settings_fields( 'wf_sn_options' );
            ?>
								<h3>
									<?php 
            esc_html_e( 'Remove settings when deactivating', 'security-ninja' );
            ?>
								</h3>
								<?php 
            \WPSecurityNinja\Plugin\Utils::create_toggle_switch( 'wf_sn_options_remove_settings_deactivate', array(
                'saved_value' => self::$options['remove_settings_deactivate'],
                'option_key'  => 'wf_sn_options[remove_settings_deactivate]',
            ) );
            ?>

								<p class="description">
									<?php 
            esc_html_e( 'If enabled all settings and data will be reset when you deactivate the plugin.', 'security-ninja' );
            ?>
								</p>
								<p class="submit"><input type="submit" value="
			<?php 
            esc_html_e( 'Save Changes', 'security-ninja' );
            ?>
																														" class="input-button button-primary" name="Submit" />
							</form>
						</div>
					</div>
				</div>

				<script type="text/javascript">
					jQuery(document).ready(function($) {
						$('#sn-import-settings').on('submit', function() {
							$('#wf-import-settings-button').prop('disabled', true).val('<?php 
            esc_html_e( 'Importing...', 'security-ninja' );
            ?>');
						});
					});
				</script>
			</div>
			<?php 
        }

        /**
         * Add an error to the settings_error
         *
         * @author  Lars Koudal
         * @since   v0.0.1
         * @version v1.0.0  Thursday, January 14th, 2021.
         * @access  public static
         * @param   mixed  $message
         * @param   string $type    Default: 'error'
         * @param   string $code    Default: 'wf_sn'
         * @return  void
         */
        public static function add_settings_error( $message, $type = 'error', $code = 'wf_sn' ) {
            global $wp_settings_errors;
            $new_wp_settings = $wp_settings_errors;
            $new_wp_settings[] = array(
                'setting' => 'wf_sn_options',
                'code'    => $code,
                'message' => $message,
                'type'    => $type,
            );
            set_transient( 'settings_errors', $new_wp_settings );
        }

        /**
         * return default options
         *
         * @author  Lars Koudal
         * @since   v0.0.1
         * @version v1.0.0  Thursday, January 14th, 2021.
         * @access  public static
         * @return  mixed
         */
        public static function default_options() {
            $defaults = array(
                'license_key'                => '',
                'license_active'             => false,
                'license_expires'            => '',
                'license_type'               => '',
                'license_hide'               => false,
                'first_version'              => '',
                'first_install'              => '',
                'wizard_completed_at'        => '',
                'remove_settings_deactivate' => 0,
            );
            return $defaults;
        }

        /**
         * get plugin's options
         *
         * @author  Lars Koudal
         * @since   v0.0.1
         * @version v1.0.0  Thursday, January 14th, 2021.
         * @access  public static
         * @return  mixed
         */
        public static function get_options() {
            if ( isset( self::$options ) && 0 < count( self::$options ) ) {
                return self::$options;
            }
            $options = get_option( 'wf_sn_options', array() );
            if ( isset( $options[0] ) ) {
                unset($options[0]);
            }
            if ( !is_array( $options ) ) {
                $options = array();
            }
            $options = array_merge( self::default_options(), $options );
            self::$options = $options;
            // her sætter vi globale options.
            return $options;
        }

        /**
         * all settings are saved in one option
         *
         * @author  Lars Koudal
         * @since   v0.0.1
         * @version v1.0.0  Thursday, January 14th, 2021.
         * @access  public static
         * @return  void
         */
        public static function register_settings() {
            register_setting( 'wf_sn_options', 'wf_sn_options', array(__NAMESPACE__ . '\\wf_sn', 'sanitize_settings') );
            // we do not want to redirect everyone
            $redirect_user = false;
            if ( isset( $_POST['foo'], $_POST['_wpnonce'] ) && wp_verify_nonce( sanitize_key( $_POST['_wpnonce'] ), 'wf-sn-install-routines' ) ) {
                if ( !current_user_can( 'manage_options' ) ) {
                    wp_send_json_error( array(
                        'success' => false,
                        'message' => esc_html__( 'You do not have permission to do this.', 'security-ninja' ),
                    ) );
                }
                $redirect_user = true;
            }
            if ( secnin_fs()->can_use_premium_code__premium_only() ) {
                // IMPORT SETTINGS
                if ( isset( $_POST['action'] ) && 'wf_sn_import_settings' === $_POST['action'] ) {
                    // Verify nonce and capabilities
                    if ( !isset( $_POST['_wpnonce_import_settings'] ) || !wp_verify_nonce( sanitize_key( $_POST['_wpnonce_import_settings'] ), 'wf-sn-import-nonce' ) ) {
                        wp_die( 'Security check failed.' );
                    }
                    if ( !current_user_can( 'manage_options' ) ) {
                        wp_die( 'You do not have sufficient permissions to import settings.' );
                    }
                    // Sanitize and decode the import data
                    $raw_import_data = ( isset( $_POST['import_wf_data'] ) ? wp_unslash( $_POST['import_wf_data'] ) : '' );
                    if ( empty( $raw_import_data ) ) {
                        wp_die( 'No data to import.' );
                    }
                    // After decoding import data
                    $import_wf_data = json_decode( $raw_import_data, true );
                    if ( !is_array( $import_wf_data ) ) {
                        wp_die( 'Invalid import data format. Please ensure you are importing valid JSON data.' );
                    }
                    // Add version check
                    if ( !empty( $import_wf_data['plugin_version'] ) ) {
                        if ( version_compare( $import_wf_data['plugin_version'], \WPSecurityNinja\Plugin\Utils::get_plugin_version(), '>' ) ) {
                            wp_die( 'The imported settings are from a newer version of Security Ninja. Please update your plugin first.' );
                        }
                    }
                    // Start importing settings
                    $import_log = array();
                    try {
                        // Firewall settings
                        if ( !empty( $import_wf_data['firewall'] ) && is_array( $import_wf_data['firewall'] ) ) {
                            $firewall_options = $import_wf_data['firewall'];
                            // Preserve the unblock_url from existing settings
                            $existing_options = \WPSecurityNinja\Plugin\wf_sn_cf::get_options();
                            if ( isset( $existing_options['unblock_url'] ) ) {
                                $firewall_options['unblock_url'] = $existing_options['unblock_url'];
                            }
                            // Ensure boolean settings are normalized to 0/1
                            $boolean_keys = array(
                                'active',
                                'globalbannetwork',
                                'global',
                                'filterqueries',
                                'trackvisits',
                                'usecloud',
                                'protect_login_form',
                                'whitelist_managewp',
                                'whitelist_wprocket',
                                'whitelist_uptimia',
                                'whitelist_uptimerobot',
                                '2fa_enabled',
                                '2fa_backup_codes_enabled',
                                '404guard_enabled',
                                'woo_rate_limiting_enabled',
                                'woo_coupon_protection_enabled',
                                'change_login_url',
                                'blockadminlogin'
                            );
                            foreach ( $boolean_keys as $key ) {
                                if ( isset( $firewall_options[$key] ) ) {
                                    $firewall_options[$key] = \WPSecurityNinja\Plugin\Utils::normalize_flag( $firewall_options[$key] );
                                }
                            }
                            // Ensure array settings are preserved and sanitized
                            $array_keys = array(
                                'blocked_countries',
                                'whitelist',
                                'blacklist',
                                'autoban_rules'
                            );
                            foreach ( $array_keys as $key ) {
                                if ( !empty( $firewall_options[$key] ) && is_array( $firewall_options[$key] ) ) {
                                    $firewall_options[$key] = array_map( 'sanitize_text_field', $firewall_options[$key] );
                                } else {
                                    $firewall_options[$key] = array();
                                }
                            }
                            // Optional IP notes maps (IP/CIDR => note); keep separate from list arrays.
                            $note_max = 150;
                            foreach ( array('whitelist_notes', 'blacklist_notes') as $notes_key ) {
                                $sanitized_notes = array();
                                if ( !empty( $firewall_options[$notes_key] ) && is_array( $firewall_options[$notes_key] ) ) {
                                    foreach ( $firewall_options[$notes_key] as $note_ip => $note_text ) {
                                        $note_ip = sanitize_text_field( trim( (string) $note_ip ) );
                                        if ( '' === $note_ip ) {
                                            continue;
                                        }
                                        $note_text = sanitize_text_field( (string) $note_text );
                                        if ( '' === $note_text ) {
                                            continue;
                                        }
                                        if ( function_exists( 'mb_substr' ) ) {
                                            $note_text = mb_substr( $note_text, 0, $note_max );
                                        } else {
                                            $note_text = substr( $note_text, 0, $note_max );
                                        }
                                        $sanitized_notes[$note_ip] = $note_text;
                                    }
                                }
                                $firewall_options[$notes_key] = $sanitized_notes;
                            }
                            foreach ( array(
                                'whitelist' => 'whitelist_notes',
                                'blacklist' => 'blacklist_notes',
                            ) as $list_key => $notes_key ) {
                                $list = ( isset( $firewall_options[$list_key] ) && is_array( $firewall_options[$list_key] ) ? array_map( 'strval', $firewall_options[$list_key] ) : array() );
                                $kept = array();
                                foreach ( $firewall_options[$notes_key] as $note_ip => $note_text ) {
                                    if ( in_array( (string) $note_ip, $list, true ) ) {
                                        $kept[$note_ip] = $note_text;
                                    }
                                }
                                $firewall_options[$notes_key] = $kept;
                            }
                            update_option( 'wf_sn_cf', $firewall_options, false );
                            $import_log['firewall'] = 'success';
                        }
                        // Scheduled scanner settings
                        if ( !empty( $import_wf_data['scheduledscanner'] ) && is_array( $import_wf_data['scheduledscanner'] ) ) {
                            $scanner_options = $import_wf_data['scheduledscanner'];
                            // Ensure required scheduler settings are present
                            $default_scanner_options = array(
                                'active'       => false,
                                'main_setting' => '1',
                                'email_report' => false,
                                'email_to'     => get_bloginfo( 'admin_email' ),
                                'schedule'     => 'daily',
                            );
                            $scanner_options = wp_parse_args( $scanner_options, $default_scanner_options );
                            // Sanitize email
                            if ( !empty( $scanner_options['email_to'] ) ) {
                                $scanner_options['email_to'] = sanitize_email( $scanner_options['email_to'] );
                            }
                            update_option( 'wf_sn_ss', $scanner_options, false );
                            $import_log['scheduler'] = 'success';
                        }
                        // Other existing import code sections...
                        if ( !empty( $import_wf_data['vulns'] ) && is_array( $import_wf_data['vulns'] ) ) {
                            update_option( 'wf_sn_vu_settings_group', $import_wf_data['vulns'], false );
                            $import_log['vulns'] = 'success';
                        }
                        if ( !empty( $import_wf_data['eventslogger'] ) && is_array( $import_wf_data['eventslogger'] ) ) {
                            update_option( 'wf_sn_el', $import_wf_data['eventslogger'], false );
                            $import_log['eventslogger'] = 'success';
                        }
                        // Fixes settings
                        if ( !empty( $import_wf_data['fixes'] ) && is_array( $import_wf_data['fixes'] ) ) {
                            $fixes_options = $import_wf_data['fixes'];
                            // Ensure boolean settings are normalized to 0/1
                            $boolean_keys = array(
                                'disable_editors',
                                'disable_wp_debug',
                                'disable_wp_sitemaps',
                                'application_passwords',
                                'disable_xmlrpc',
                                'disable_username_enumeration',
                                'remove_unwanted_files',
                                'hide_wp',
                                'hide_wlw',
                                'secure_cookies',
                                'hide_php_ver',
                                'enable_xcto',
                                'enable_xfo',
                                'enable_sts',
                                'enable_rp',
                                'enable_fp',
                                'enable_csp'
                            );
                            foreach ( $boolean_keys as $key ) {
                                if ( isset( $fixes_options[$key] ) ) {
                                    $fixes_options[$key] = \WPSecurityNinja\Plugin\Utils::normalize_flag( $fixes_options[$key] );
                                }
                            }
                            // Sanitize text fields for security headers
                            $text_keys = array(
                                'sechead_xcto',
                                'sechead_xfo',
                                'sechead_sts',
                                'sechead_rp',
                                'sechead_fp',
                                'sechead_csp'
                            );
                            foreach ( $text_keys as $key ) {
                                if ( isset( $fixes_options[$key] ) ) {
                                    $fixes_options[$key] = sanitize_text_field( $fixes_options[$key] );
                                }
                            }
                            update_option( WF_SN_FIXES_OPTIONS_KEY, $fixes_options, false );
                            $import_log['fixes'] = 'success';
                        }
                        // Whitelabel settings
                        if ( !empty( $import_wf_data['whitelabel'] ) && is_array( $import_wf_data['whitelabel'] ) ) {
                            $whitelabel_options = $import_wf_data['whitelabel'];
                            // Ensure boolean settings are normalized to 0/1
                            $boolean_keys = array('wl_active');
                            foreach ( $boolean_keys as $key ) {
                                if ( isset( $whitelabel_options[$key] ) ) {
                                    $whitelabel_options[$key] = \WPSecurityNinja\Plugin\Utils::normalize_flag( $whitelabel_options[$key] );
                                }
                            }
                            // Sanitize text fields
                            $text_keys = array(
                                'wl_newname',
                                'wl_newdesc',
                                'wl_newauthor',
                                'wl_newurl',
                                'wl_newiconurl',
                                'wl_newmenuiconurl'
                            );
                            foreach ( $text_keys as $key ) {
                                if ( isset( $whitelabel_options[$key] ) ) {
                                    $whitelabel_options[$key] = sanitize_text_field( $whitelabel_options[$key] );
                                }
                            }
                            // Sanitize URLs
                            $url_keys = array('wl_newurl', 'wl_newiconurl', 'wl_newmenuiconurl');
                            foreach ( $url_keys as $key ) {
                                if ( isset( $whitelabel_options[$key] ) && !empty( $whitelabel_options[$key] ) ) {
                                    $whitelabel_options[$key] = esc_url_raw( $whitelabel_options[$key] );
                                }
                            }
                            update_option( 'wf_sn_wl', $whitelabel_options, false );
                            $import_log['whitelabel'] = 'success';
                        }
                        // Malware scanner whitelist (per-file + pattern entries)
                        if ( !empty( $import_wf_data['malware_scanner']['whitelist'] ) && is_array( $import_wf_data['malware_scanner']['whitelist'] ) ) {
                            $sanitized = array();
                            foreach ( $import_wf_data['malware_scanner']['whitelist'] as $item ) {
                                if ( !is_array( $item ) ) {
                                    continue;
                                }
                                if ( isset( $item['pattern'] ) && $item['pattern'] !== '' ) {
                                    $sanitized[] = array(
                                        'pattern' => sanitize_text_field( $item['pattern'] ),
                                    );
                                } elseif ( !empty( $item['filename'] ) ) {
                                    $entry = array(
                                        'filename' => sanitize_text_field( $item['filename'] ),
                                    );
                                    if ( isset( $item['hash'] ) ) {
                                        $entry['hash'] = sanitize_text_field( $item['hash'] );
                                    }
                                    $sanitized[] = $entry;
                                }
                            }
                            update_option( 'wf_sn_ms_whitelist', $sanitized, false );
                            $import_log['malware_scanner'] = 'success';
                        }
                        // Core Scanner user ignore list.
                        if ( !empty( $import_wf_data['core_scanner']['user_ignore'] ) && is_array( $import_wf_data['core_scanner']['user_ignore'] ) ) {
                            if ( !class_exists( '\\WPSecurityNinja\\Plugin\\Wf_Sn_Cs_Utils' ) ) {
                                require_once WF_SN_PLUGIN_DIR . 'modules/core-scanner/class-wf-sn-cs-utils.php';
                            }
                            $sanitized = array();
                            foreach ( $import_wf_data['core_scanner']['user_ignore'] as $path ) {
                                $normalized = \WPSecurityNinja\Plugin\Wf_Sn_Cs_Utils::normalize_relative_path( $path );
                                if ( false !== $normalized ) {
                                    $sanitized[] = $normalized;
                                }
                            }
                            $sanitized = array_values( array_unique( $sanitized ) );
                            if ( count( $sanitized ) > \WPSecurityNinja\Plugin\Wf_Sn_Cs_Utils::USER_IGNORE_MAX ) {
                                $sanitized = array_slice( $sanitized, 0, \WPSecurityNinja\Plugin\Wf_Sn_Cs_Utils::USER_IGNORE_MAX );
                            }
                            update_option( \WPSecurityNinja\Plugin\Wf_Sn_Cs_Utils::USER_IGNORE_OPTION, $sanitized, false );
                            $import_log['core_scanner'] = 'success';
                        }
                        // Log the import action
                        if ( method_exists( '\\WPSecurityNinja\\Plugin\\wf_sn_el_modules', 'log_event' ) ) {
                            \WPSecurityNinja\Plugin\wf_sn_el_modules::log_event( 'security_ninja', 'settings', sprintf( 'Settings imported successfully. Modules: %s', implode( ', ', array_keys( $import_log ) ) ) );
                        }
                        add_settings_error(
                            'wf_sn_options',
                            'settings_updated',
                            __( 'Settings imported successfully!', 'security-ninja' ),
                            'success'
                        );
                        // Set flag for redirect
                        $redirect_user = true;
                    } catch ( \Exception $e ) {
                        // Log the error and notify admin
                        \WPSecurityNinja\Plugin\wf_sn_el_modules::log_event( 'security_ninja', 'error', sprintf( 'Import Error: %s', $e->getMessage() ) );
                        wp_die( 'An error occurred during import. Please try again or contact support.', 'Import Error', array(
                            'response' => 500,
                        ) );
                    }
                }
            }
            if ( $redirect_user ) {
                // Set to false per default, so isset check not needed.
                if ( !isset( $_POST['_wp_http_referer'] ) ) {
                    $_POST['_wp_http_referer'] = wp_login_url();
                }
                $url = sanitize_text_field( wp_unslash( $_POST['_wp_http_referer'] ) );
                wp_safe_redirect( urldecode( $url ) );
                exit;
            }
        }

        /**
         * Sanitize settings on save
         *
         * @author  Lars Koudal
         * @since   v0.0.1
         * @version v1.0.0  Tuesday, January 12th, 2021.
         * @access  public static
         * @param   mixed $new_values
         * @return  void
         */
        public static function sanitize_settings( $new_values ) {
            // Get the raw options from database without merging with defaults
            $old_options = get_option( 'wf_sn_options', array() );
            // Only merge with defaults if the options are actually empty or corrupted
            if ( empty( $old_options ) || !is_array( $old_options ) ) {
                $old_options = self::default_options();
            }
            $old_options['remove_settings_deactivate'] = 0;
            if ( !is_array( $new_values ) ) {
                $arr = array();
                $arr[] = $new_values;
                $new_values = $arr;
            }
            foreach ( $new_values as $key => $value ) {
                $new_values[$key] = sanitize_text_field( $value );
            }
            return array_merge( $old_options, $new_values );
        }

        /**
         * whole options page
         *
         * @author  Lars Koudal
         * @since   v0.0.1
         * @version v1.0.0  Thursday, January 14th, 2021.
         * @access  public static
         * @return  void
         */
        public static function main_page() {
            global $secnin_fs;
            // Ensure premium tables exist if premium code is available
            if ( secnin_fs()->can_use_premium_code__premium_only() ) {
                self::handle_license_validation__premium_only();
            }
            // Display setting errors
            settings_errors();
            $tabs = array();
            $tabs[] = array(
                'id'       => 'sn_overview',
                'class'    => '',
                'label'    => __( 'Overview', 'security-ninja' ),
                'callback' => array(__NAMESPACE__ . '\\WF_SN_Overview_Tab', 'tab_overview'),
            );
            $tabs[] = array(
                'id'       => 'sn_tests',
                'class'    => '',
                'label'    => __( 'Tests', 'security-ninja' ),
                'callback' => array(__NAMESPACE__ . '\\wf_sn', 'tab_tests'),
            );
            $tabs = apply_filters( 'sn_tabs', $tabs );
            ?>
			<div class="wrap">
				<?php 
            \WPSecurityNinja\Plugin\Utils::show_topbar();
            ?>
				<div class="secnin_content_wrapper">
					<div class="secnin_content_cell" id="secnin_content_top">

						<?php 
            do_action( 'secnin_signup_to_newsletter' );
            ?>

						<div class="nav-tab-wrapper" id="wf-sn-tabs">
							<?php 
            foreach ( $tabs as $tab ) {
                $extra = '';
                $class = 'nav-tab ' . $tab['class'];
                if ( 'sn_overview' === $tab['id'] ) {
                    $class .= ' nav-tab-active';
                }
                if ( !empty( $tab['label'] ) ) {
                    if ( isset( $tab['count'] ) ) {
                        $extra = ' <span class="warn-count">' . intval( $tab['count'] ) . '</span>';
                    }
                    echo '<a href="#' . esc_attr( $tab['id'] ) . '" class="' . esc_attr( $class ) . '" id="' . esc_attr( $tab['id'] ) . '-tab">' . esc_html( $tab['label'] ) . wp_kses( $extra, array(
                        'span' => array(
                            'class' => array(),
                        ),
                    ) ) . '</a>';
                }
            }
            ?>
						</div>
						<div id="sn_tabscont">
							<?php 
            foreach ( $tabs as $tab ) {
                if ( !empty( $tab['callback'] ) ) {
                    $class = 'wf-sn-tab';
                    if ( 'sn_overview' === $tab['id'] ) {
                        $class .= ' active';
                    }
                    echo '<div id="' . esc_attr( $tab['id'] ) . '" class="' . esc_attr( $class ) . '">';
                    call_user_func( $tab['callback'] );
                    echo '</div>';
                }
            }
            ?>
						</div>
						<?php 
            include_once 'includes/sidebar.php';
            ?>
					</div>
				</div>
			</div>

			<?php 
        }

        /**
         * returns the current score of the tests + output
         *
         * @author  Lars Koudal
         * @since   v0.0.1
         * @version v1.0.0  Thursday, January 14th, 2021.
         * @access  public static
         * @return  mixed
         */
        public static function return_test_scores() {
            if ( !is_null( self::$test_scores ) ) {
                return self::$test_scores;
            }
            global $wpdb;
            $testsresults = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}wf_sn_tests LIMIT 100;", ARRAY_A );
            $bad = 0;
            $warning = 0;
            $good = 0;
            $score = 0;
            $total = 0;
            if ( $testsresults ) {
                foreach ( $testsresults as $test_details ) {
                    $total += $test_details['score'];
                    if ( 10 === intval( $test_details['status'] ) ) {
                        ++$good;
                        $score += $test_details['score'];
                    } elseif ( 0 === intval( $test_details['status'] ) ) {
                        ++$bad;
                    } else {
                        ++$warning;
                    }
                }
            }
            if ( $total > 0 && $score > 0 ) {
                $score = round( $score / $total * 100 );
            } else {
                $score = 0;
            }
            $response = array();
            $response['good'] = $good;
            $response['bad'] = $bad;
            $response['warning'] = $warning;
            $response['score'] = $score;
            // generate output
            $output = '';
            $output .= '<div id="counters">';
            $output .= '<span class="edge good"><span class="val">' . $good . '</span><i>' . __( 'Passed', 'security-ninja' ) . '</i></span>';
            $output .= '<span class="edge warning"><span class="val">' . $warning . '</span><i>' . __( 'Warnings', 'security-ninja' ) . '</i></span>';
            $output .= '<span class="edge bad"><span class="val">' . $bad . '</span><i>' . __( 'Failed', 'security-ninja' ) . '</i></span>';
            $output .= '<span class="edge score"><span class="val">' . $score . '%</span><i>' . __( 'Score', 'security-ninja' ) . '</i></span>';
            $output .= '</div>';
            $response['output'] = $output;
            self::$test_scores = $response;
            return $response;
        }

        /**
         * Gets test results from database
         *
         * @author  Lars Koudal
         * @since   v0.0.1
         * @version v1.0.0  Thursday, January 14th, 2021.
         * @access  public static
         * @return  mixed
         */
        public static function get_test_results() {
            global $wpdb;
            $table_name = $wpdb->prefix . 'wf_sn_tests';
            $testsresults = $wpdb->get_results( "SELECT * FROM {$table_name}", ARRAY_A );
            if ( !$testsresults ) {
                return false;
            }
            $response = array();
            foreach ( $testsresults as $tr ) {
                $response['test'][$tr['testid']] = $tr;
            }
            return $response;
        }

        /**
         * tab_tests.
         *
         * @author  Lars Koudal
         * @since   v0.0.1
         * @version v1.0.0  Wednesday, February 3rd, 2021.
         * @access  public static
         * @return  void
         */
        public static function tab_tests() {
            $testsresults = self::get_test_results();
            ?>
			<div class="submit-test-container">
				<h2><span class="dashicons dashicons-list-view"></span>
					<?php 
            esc_html_e( 'Test your website security', 'security-ninja' );
            ?>
				</h2>
				<p class="description">
					<?php 
            esc_html_e( 'Run comprehensive security tests to identify potential vulnerabilities and strengthen your website\'s defenses', 'security-ninja' );
            ?>
				</p>
				<div class="testresults" id="testscores">
					<?php 
            $scores = self::return_test_scores();
            if ( isset( $scores['output'] ) ) {
                $allowed_html = array(
                    'div'  => array(
                        'id' => array(),
                    ),
                    'span' => array(
                        'class' => array(),
                    ),
                    'br'   => array(),
                    'i'    => array(),
                );
                echo wp_kses( $scores['output'], $allowed_html );
            }
            ?>
				</div>

				<?php 
            $tests = wf_sn_tests::return_security_tests();
            $out = '<div id="runtestsrow"><input type="submit" value="' . __( 'Run Tests', 'security-ninja' ) . '" id="run-selected-tests" class="button button-primary button-hero" name="Submit" />';
            $out .= '<span class="runtestsbn spinner"></span>';
            $out .= '<div id="secninja-tests-quickselect">';
            $out .= '<span>' . __( 'Quick Filter', 'security-ninja' ) . ':</span><ul><li><a href="#" id="sn-quickselect-all">' . __( 'All', 'security-ninja' ) . '</a></li><li><a href="#" id="sn-quickselect-failed">' . __( 'Failed', 'security-ninja' ) . '</a></li><li><a href="#"  id="sn-quickselect-warning">' . __( 'Warning', 'security-ninja' ) . '</a></li><li><a href="#" id="sn-quickselect-okay">' . __( 'Passed', 'security-ninja' ) . '</a></li><li><a href="#" id="sn-quickselect-untested">' . __( 'Untested', 'security-ninja' ) . '</a></li>';
            if ( secnin_fs()->can_use_premium_code__premium_only() ) {
                $out .= '<li><a href="#" id="sn-quickselect-fixable">' . __( 'Fixable', 'security-ninja' ) . '</a></li>';
            }
            $out .= '</ul>';
            $out .= ' <span class="secnin-expand-all-wrap">| <a href="#" class="secnin_expand_all_details">' . esc_html__( 'Expand all details', 'security-ninja' ) . '</a></span>';
            $out .= '</div></div>';
            $out .= '<table class="wp-list-table widefat" cellspacing="0" id="security-ninja">';
            $out .= '<thead><tr>';
            $out .= '<td id="cb" class="manage-column column-cb check-column">';
            $out .= '<label class="screen-reader-text" for="cb-select-all-1">' . __( 'Select All', 'security-ninja' ) . '</label>';
            $out .= '<input id="cb-select-all-1" type="checkbox"></td>';
            $out .= '<th>' . __( 'Status', 'security-ninja' ) . '</th>';
            $out .= '<th class="column-primary">' . __( 'Security Test', 'security-ninja' ) . '</th>';
            $out .= '<th>' . __( 'Actions', 'security-ninja' );
            $out .= '</th>';
            $out .= '</tr></thead>';
            $out .= '<tbody>';
            if ( is_array( $tests ) ) {
                $stepid = 0;
                foreach ( $tests as $test_name => $details ) {
                    if ( 'ad_' === substr( $test_name, 0, 3 ) || '_' === $test_name[0] ) {
                        continue;
                    }
                    ++$stepid;
                    $outlabel = '';
                    $fixable_row = '';
                    if ( secnin_fs()->can_use_premium_code__premium_only() ) {
                        if ( class_exists( __NAMESPACE__ . '\\Wf_sn_af' ) && Wf_sn_af::test_is_fixable( $test_name ) ) {
                            $fixable_row = ' wf-sn-test-row-fixable';
                        }
                    }
                    // hvis vi har kørt testen før
                    if ( isset( $testsresults['test'][$test_name]['status'] ) ) {
                        $out .= '<tr class="wf-sn-test-row-status-' . $testsresults['test'][$test_name]['status'] . ' test test_' . $test_name . $fixable_row . '">';
                        if ( 0 === intval( $testsresults['test'][$test_name]['status'] ) ) {
                            $outlabel = '<span class="teststatus fail">' . __( '✗', 'security-ninja' ) . '</span>';
                        } elseif ( 5 === intval( $testsresults['test'][$test_name]['status'] ) ) {
                            $outlabel = '<span class="teststatus warning">' . __( '-;', 'security-ninja' ) . '</span>';
                        } elseif ( 10 === intval( $testsresults['test'][$test_name]['status'] ) ) {
                            $outlabel = '<span class="teststatus pass">' . __( '✓', 'security-ninja' ) . '</span>';
                        }
                    } else {
                        // lars - kommenteret ud ellers kom der er et "d" med
                        $out .= '<tr class="wf-sn-test-row-status-null test test_' . $test_name . $fixable_row . '">';
                        $outlabel = '<span class="teststatus untested">' . __( 'Untested', 'security-ninja' ) . '</span>';
                    }
                    $checkedoutput = checked( true, true, false );
                    if ( !isset( $options['run_tests'] ) ) {
                        $checkedoutput = checked( true, true, false );
                    } else {
                        $options = self::get_options();
                        if ( in_array( $test_name, $options['run_tests'], true ) ) {
                            $checkedoutput = checked( true, true, false );
                        } else {
                            $checkedoutput = checked( false, true, false );
                        }
                    }
                    $out .= '<th scope="row" class="check-column"><input id="cb-select-' . $stepid . '" type="checkbox" name="sntest[]" value="' . sanitize_key( $test_name ) . '" ' . $checkedoutput . '/></th>';
                    $out .= '<td scope="row" class="">' . $outlabel . '</td>';
                    $out .= '<td class="column-primary" data-colname="Test"><label for="cb-select-' . $stepid . '"><span class="wf-sn-test-title">' . $details['title'] . '</span></label></br>';
                    if ( isset( $testsresults['test'][$test_name]['msg'] ) ) {
                        // only add details if failed or warning
                        $outmessage = $testsresults['test'][$test_name]['msg'];
                        // Add the details if exists
                        if ( $testsresults['test'][$test_name]['details'] ) {
                            $outmessage .= ' ' . $testsresults['test'][$test_name]['details'];
                        }
                        $out .= '<span class="sn-result-details">' . $outmessage . '</span>';
                    } else {
                        // empty - can be filled via ajax response
                        $out .= '<span class="sn-result-details"></span>';
                    }
                    $out .= '<button type="button" class="toggle-row"> <span class="screen-reader-text">' . __( 'Show details', 'security-ninja' ) . '</span> </button>';
                    $testcssid = 'tdesc-test-id-' . $test_name;
                    $out .= '<div class="testdesc ' . esc_attr( $testcssid ) . '"></div>';
                    if ( class_exists( __NAMESPACE__ . '\\wf_sn_af_fix_' . $test_name ) && isset( $details['status'] ) && 10 !== $details['status'] ) {
                        $details_label = __( 'Details &amp; Fix', 'security-ninja' );
                    } else {
                        $details_label = __( 'Details', 'security-ninja' );
                    }
                    $out .= '<div class="testtimedetails ' . esc_attr( $test_name ) . '"><span class="spinner"></span><span class="lasttest"></span><span class="runtime"></span><span class="score"></span>';
                    $outstatus = '';
                    if ( isset( $testsresults['test'][$test_name]['status'] ) ) {
                        $outstatus = $testsresults['test'][$test_name]['status'];
                    }
                    $out .= '<div class="snautofix" data-test-id="' . esc_attr( $test_name ) . '" data-test-status="' . esc_attr( $outstatus ) . '"></div>';
                    $out .= '</div>';
                    $out .= '</td><td><div class="sn-details"><a data-test-id="' . esc_attr( $test_name ) . '" data-test-status="' . esc_attr( $outstatus ) . '" href="#' . esc_attr( $test_name ) . '" class="action">' . $details_label . '</a></div>';
                    $out .= '</td></tr>';
                }
            }
            $out .= '</tbody>';
            $out .= '<tfoot><tr>';
            $out .= '<th class="manage-column column-cb check-column"><label class="screen-reader-text" for="cb-select-all-2">' . __( 'Select All', 'security-ninja' ) . '</label><input id="cb-select-all-2" type="checkbox"></th>';
            $out .= '<th></th>';
            $out .= '<th class="column-primary">' . __( 'Security Test', 'security-ninja' ) . '</th><th>Actions</th>';
            $out .= '</tr></tfoot>';
            $out .= '</table>';
            $allowed_html = array_merge( wp_kses_allowed_html( 'post' ), array(
                'input' => array(
                    'type'    => array(),
                    'name'    => array(),
                    'id'      => array(),
                    'value'   => array(),
                    'checked' => array(),
                    'class'   => array(),
                ),
            ) );
            $out = apply_filters( 'sn_tests_table', $out, $tests );
            echo wp_kses( $out, $allowed_html );
            ?>
				<p>
					<?php 
            esc_html_e( 'Although these tests cover years of best practices in security, getting all test green does not guarantee your site will not get hacked. Likewise, having them all red does not mean you will get hacked.', 'security-ninja' );
            ?>
				</p>
				<p>
					<?php 
            esc_html_e( "Please read each test's detailed information to see if it represents a real security issue for your site.", 'security-ninja' );
            ?>
				</p>
			</div>
			<?php 
        }

        /**
         * Returns all details about a test in JSON - used in AJAX
         *
         * @author  Lars Koudal
         * @since   v0.0.1
         * @version v1.0.0  Saturday, March 5th, 2022.
         * @access  public static
         * @return  void
         */
        public static function get_single_test_details() {
            if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
                check_ajax_referer( 'wf_sn_run_tests' );
            }
            if ( !current_user_can( 'manage_options' ) ) {
                wp_send_json_error( array(
                    'message' => __( 'Failed.', 'security-ninja' ),
                ) );
            }
            if ( isset( $_POST['testid'] ) ) {
                $testid = sanitize_key( $_POST['testid'] );
                if ( $testid !== $_POST['testid'] ) {
                    wp_send_json_error();
                }
                global $wpdb;
                $table_name = $wpdb->prefix . 'wf_sn_tests';
                $testdata = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}wf_sn_tests WHERE testid = %s", $testid ) );
                if ( $testdata ) {
                    wp_send_json_success( $testdata );
                } else {
                    wp_send_json_error();
                }
            } else {
                wp_send_json_error();
            }
            die;
        }

        /**
         * Returns long help text for all security tests (JSON) for lazy-loaded UI.
         *
         * @since 5.278
         * @return void
         */
        public static function get_test_descriptions() {
            if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
                check_ajax_referer( 'wf_sn_run_tests' );
            }
            if ( !current_user_can( 'manage_options' ) ) {
                wp_send_json_error( array(
                    'message' => __( 'Failed.', 'security-ninja' ),
                ) );
            }
            $payload = Wf_Sn_Test_Descriptions::get_payload_for_tests_ui();
            wp_send_json_success( array(
                'tests' => $payload,
            ) );
        }

        /**
         * Runs single test via AJAX call
         *
         * @author  Lars Koudal
         * @since   v0.0.1
         * @version v1.0.0  Thursday, January 14th, 2021.
         * @access  public static
         * @return  void
         */
        public static function run_single_test() {
            if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
                check_ajax_referer( 'wf_sn_run_tests' );
            }
            if ( !current_user_can( 'manage_options' ) ) {
                wp_send_json_error( array(
                    'message' => __( 'Error. Please contact support', 'security-ninja' ),
                ) );
            }
            if ( isset( $_POST['stepid'] ) ) {
                $stepid = intval( $_POST['stepid'] );
                // Validate and sanitize the testarr input
                $testarr = array();
                if ( isset( $_POST['testarr'] ) && is_array( $_POST['testarr'] ) ) {
                    // Only allow alphanumeric characters, underscores, and hyphens in test IDs
                    $testarr = array_filter( $_POST['testarr'], function ( $test_id ) {
                        return preg_match( '/^[a-zA-Z0-9_-]+$/', $test_id );
                    } );
                }
                if ( !isset( $testarr[$stepid] ) ) {
                    return false;
                }
                $response = false;
                $testid = sanitize_key( $testarr[$stepid] );
                if ( $testid ) {
                    \WPSecurityNinja\Plugin\Utils::timerstart( $testid );
                    $response = wf_sn_tests::$testid();
                }
                if ( $response ) {
                    $json_response = array();
                    if ( isset( $testarr[$stepid + 1] ) ) {
                        $json_response['nexttest'] = $stepid + 1;
                    } else {
                        $json_response['nexttest'] = -1;
                    }
                    $security_tests = wf_sn_tests::return_security_tests();
                    // allow overwriting with function response
                    if ( isset( $response['msg_bad'] ) ) {
                        $test['msg_bad'] = $response['msg_bad'];
                    }
                    if ( isset( $response['msg_ok'] ) ) {
                        $test['msg_ok'] = $response['msg_ok'];
                    }
                    if ( isset( $response['msg_warning'] ) ) {
                        $test['msg_warning'] = $response['msg_warning'];
                    }
                    if ( !isset( $response['msg'] ) ) {
                        $response['msg'] = '';
                    }
                    $json_response['msg'] = $response['msg'];
                    // Get the previous status from the database table
                    $previous_status = null;
                    global $wpdb;
                    $table_name = $wpdb->prefix . 'wf_sn_tests';
                    $previous_test = $wpdb->get_row( $wpdb->prepare( "SELECT status FROM {$table_name} WHERE testid = %s", $testid ) );
                    if ( $previous_test ) {
                        $previous_status = $previous_test->status;
                    }
                    // Check if status changed - ensure both are integers for comparison
                    $previous_status_int = ( $previous_status !== null ? intval( $previous_status ) : null );
                    $new_status_int = intval( $response['status'] );
                    $status_changed = $previous_status_int !== $new_status_int;
                    // Determine change direction for highlighting
                    $change_direction = null;
                    if ( $status_changed && $previous_status_int !== null ) {
                        if ( $new_status_int === 10 && $previous_status_int < 10 ) {
                            $change_direction = 'improved';
                            // Went from fail/warning to pass
                        } elseif ( 10 === $previous_status_int && $new_status_int < 10 ) {
                            $change_direction = 'declined';
                            // Went from pass to fail/warning
                        } else {
                            $change_direction = 'changed';
                            // Other status changes
                        }
                    }
                    $json_response['status_changed'] = $status_changed;
                    $json_response['previous_status'] = $previous_status_int;
                    $json_response['new_status'] = $new_status_int;
                    $json_response['change_direction'] = $change_direction;
                    // Return the correct status icon HTML for the frontend
                    if ( 10 === $response['status'] ) {
                        $json_response['status_icon'] = '<span class="teststatus pass">' . __( '✓', 'security-ninja' ) . '</span>';
                    } elseif ( 0 === $response['status'] ) {
                        $json_response['status_icon'] = '<span class="teststatus fail">' . __( '✗', 'security-ninja' ) . '</span>';
                    } else {
                        $json_response['status_icon'] = '<span class="teststatus warning">' . __( '&#9888;', 'security-ninja' ) . '</span>';
                    }
                    // Keep the label for backward compatibility
                    if ( 10 === $response['status'] ) {
                        $json_response['label'] = '<span class="wf-sn-label sn-success">' . __( 'OK', 'security-ninja' ) . '</span>';
                    } elseif ( 0 === $response['status'] ) {
                        $json_response['label'] = '<span class="wf-sn-label sn-error">' . __( 'Fail', 'security-ninja' ) . '</span>';
                    } else {
                        $json_response['label'] = '<span class="wf-sn-label sn-warning">' . __( 'Warning', 'security-ninja' ) . '</span>';
                    }
                    $json_response['status'] = $response['status'];
                    $testscorearr = array(
                        'testid'    => $testid,
                        'timestamp' => current_time( 'mysql' ),
                        'title'     => $security_tests[$testid]['title'],
                        'status'    => $response['status'],
                        'score'     => $security_tests[$testid]['score'],
                        'msg'       => $json_response['msg'],
                    );
                    // A way to add details
                    if ( isset( $response['details'] ) ) {
                        $testscorearr['details'] = $response['details'];
                        $json_response['details'] = $response['details'];
                    }
                    $endtime = \WPSecurityNinja\Plugin\Utils::timerstop( $testid );
                    if ( $endtime ) {
                        $testscorearr['runtime'] = $endtime;
                    }
                    self::update_test_score( $testscorearr );
                    $scores = self::return_test_scores();
                    if ( $scores ) {
                        $json_response['scores'] = $scores;
                    }
                    wp_send_json_success( $json_response );
                } else {
                    wp_send_json_error( $testid );
                }
            }
            wp_send_json_error( '$stepid not set' );
            die;
        }

        /**
         * Reset Secret Access URL via AJAX
         *
         * @author  Lars Koudal
         * @since   v0.0.1
         * @version v1.0.0  Monday, January 13th, 2025.
         * @access  public static
         * @return  void
         */
        public static function reset_secret_url() {
            check_ajax_referer( 'wf-sn-reset-secret-url' );
            if ( !current_user_can( 'manage_options' ) ) {
                wp_send_json_error( array(
                    'message' => esc_html__( 'You do not have sufficient permissions to perform this action.', 'security-ninja' ),
                ) );
            }
            // Check if firewall module is available
            if ( !class_exists( __NAMESPACE__ . '\\Wf_sn_cf' ) ) {
                wp_send_json_error( array(
                    'message' => esc_html__( 'Firewall module not available.', 'security-ninja' ),
                ) );
            }
            // Get firewall options
            $firewall_options = \WPSecurityNinja\Plugin\Wf_sn_cf::get_options();
            // Generate new secret access URL
            $firewall_options['unblock_url'] = md5( time() . wp_rand() );
            // Update the options
            update_option( 'wf_sn_cf', $firewall_options, false );
            // Log the event
            if ( class_exists( '\\WPSecurityNinja\\Plugin\\Wf_Sn_El_Modules' ) ) {
                \WPSecurityNinja\Plugin\Wf_Sn_El_Modules::log_event( 'security_ninja', 'secret_access_url_reset', 'Secret access URL was reset by user ID: ' . get_current_user_id() );
            }
            // Get the new URL for response
            $new_url = \WPSecurityNinja\Plugin\Wf_sn_cf::get_unblock_url();
            // Set a transient to show notice on page reload
            set_transient( 'sn_secret_url_reset_success', $new_url, 60 );
            wp_send_json_success( array(
                'message' => esc_html__( 'Secret access URL has been reset successfully.', 'security-ninja' ),
            ) );
        }

        /**
         * Runs the tests
         *
         * @author  Lars Koudal
         * @since   v0.0.1
         * @version v1.0.0  Thursday, January 14th, 2021.
         * @access  public static
         * @param   bool $return_response Default: false.
         * @return  void
         */
        public static function run_tests( $return_response = false ) {
            if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
                // Attempt to verify the first nonce
                $nonce_verified = wp_verify_nonce( sanitize_text_field( $_REQUEST['_wpnonce'] ), 'wf_sn_run_tests' );
                // If the first nonce verification fails, try the second nonce
                if ( !$nonce_verified ) {
                    $nonce_verified = wp_verify_nonce( sanitize_text_field( $_REQUEST['_wpnonce'] ), 'secnin_scheduled_scanner' );
                }
                // If both nonce verifications fail, terminate the AJAX call
                if ( !$nonce_verified ) {
                    wp_die( 'Nonce verification failed.', 'Nonce Verification', array(
                        'response' => 403,
                    ) );
                }
            }
            if ( !current_user_can( 'manage_options' ) ) {
                wp_send_json_error( array(
                    'success' => false,
                    'message' => esc_html__( 'You do not have permission to do this.', 'security-ninja' ),
                ) );
            }
            $step = ( isset( $_POST['step'] ) ? absint( $_POST['step'] ) : 1 );
            if ( 1 === $step ) {
                \WPSecurityNinja\Plugin\Utils::timerstart( 'wf_sn_run_tests' );
            }
            if ( !$step ) {
                $step = 0;
            }
            ++$step;
            $json_response = array();
            if ( $step ) {
                $json_response['step'] = $step;
            }
            $security_tests = \WPSecurityNinja\Plugin\Wf_Sn_Tests::return_security_tests();
            $totaltests = 0;
            if ( $security_tests ) {
                $totaltests = count( $security_tests );
                $json_response['totaltests'] = $totaltests;
            }
            $set_time_limit = set_time_limit( 200 );
            if ( secnin_fs()->can_use_premium_code__premium_only() ) {
                if ( !$set_time_limit ) {
                    wf_sn_el_modules::log_event( 'security_ninja', 'security_tests', __( 'Could not set PHP time limit set_time_limit()', 'security-ninja' ) );
                }
            }
            $loop_count = 1;
            $start_time = microtime( true );
            $test_description['last_run'] = time();
            if ( is_array( $security_tests ) ) {
                foreach ( $security_tests as $test_name => $test ) {
                    if ( '_' === $test_name[0] || in_array( $test_name, self::$skip_tests, true ) || 'ad_' === substr( $test_name, 0, 3 ) ) {
                        continue;
                    }
                    // If this is the one to be tested ...
                    if ( $step === $loop_count ) {
                        $response = wf_sn_tests::$test_name();
                        $json_response['last_test'] = $test['title'];
                        if ( isset( $response['status'] ) ) {
                            $json_response['last_status'] = $response['status'];
                        }
                        $json_response['last_score'] = $test['score'];
                        // allow overwriting with function response
                        if ( isset( $response['msg_bad'] ) ) {
                            $test['msg_bad'] = $response['msg_bad'];
                        }
                        if ( isset( $response['msg_ok'] ) ) {
                            $test['msg_ok'] = $response['msg_ok'];
                        }
                        if ( isset( $response['msg_warning'] ) ) {
                            $test['msg_warning'] = $response['msg_warning'];
                        }
                        if ( !isset( $response['msg'] ) ) {
                            $response['msg'] = '';
                        }
                        if ( 10 === intval( $response['status'] ) ) {
                            $json_response['last_msg'] = sprintf( (string) ($test['msg_ok'] ?? '%s'), $response['msg'] ?? '' );
                        } elseif ( 0 === intval( $response['status'] ) ) {
                            $json_response['last_msg'] = sprintf( (string) ($test['msg_bad'] ?? '%s'), $response['msg'] ?? '' );
                        } else {
                            $json_response['last_msg'] = sprintf( (string) ($test['msg_warning'] ?? '%s'), $response['msg'] ?? '' );
                        }
                        // Updates the results
                        $resultssofar['test'][$test_name] = array(
                            'title'  => $test['title'],
                            'status' => $response['status'],
                            'score'  => $test['score'],
                            'msg'    => $json_response['last_msg'],
                        );
                        // A way to add details
                        if ( isset( $response['details'] ) ) {
                            $resultssofar['test'][$test_name]['details'] = $response['details'];
                        }
                        // No more tests - let us stop
                        if ( $step >= $totaltests ) {
                            $json_response['step'] = 'done';
                            $resultssofar['last_run'] = time();
                            $stoptime = \WPSecurityNinja\Plugin\Utils::timerstop( 'wf_sn_run_tests' );
                            if ( $stoptime ) {
                                $resultssofar['run_time'] = $stoptime;
                            }
                            do_action( 'security_ninja_done_testing', $test_description, $resultssofar['run_time'] );
                        }
                        update_option( 'wf_sn_results', $resultssofar, false );
                        wp_send_json_success( $json_response );
                    }
                    ++$loop_count;
                }
            }
            if ( $return_response ) {
                $resultssofar = get_option( 'wf_sn_results' );
                return $resultssofar;
            } else {
                wp_send_json_success( $json_response );
            }
        }

        /**
         * Runs all tests with multisite compatibility
         *
         * @author  Lars Koudal
         * @since   v0.0.1
         * @version v1.0.0  Thursday, January 14th, 2021.
         * @version v1.0.1  Tuesday, June 11th, 2024.
         * @access  public static
         * @param   boolean $return_data Default: false
         * @return  void
         */
        public static function run_all_tests( $return_data = false ) {
            if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
                // Attempt to verify the first nonce.
                $nonce_verified = wp_verify_nonce( sanitize_text_field( $_REQUEST['nonce'] ), 'wf_sn_run_tests' );
                // If the first nonce verification fails, try the second nonce
                if ( !$nonce_verified ) {
                    $nonce = wp_unslash( $_REQUEST['nonce'] );
                    $nonce = sanitize_text_field( $nonce );
                    $nonce_verified = wp_verify_nonce( $nonce, 'secnin_scheduled_scanner' );
                }
                // If both nonce verifications fail, terminate the AJAX call.
                if ( !$nonce_verified ) {
                    wp_die( 'Nonce verification failed.', 'Nonce Verification', array(
                        'response' => 403,
                    ) );
                }
            }
            \WPSecurityNinja\Plugin\Utils::timerstart( 'wf_sn_run_all_tests' );
            $security_tests = wf_sn_tests::return_security_tests();
            $resultssofar = array();
            $json_response = array();
            $set_time_limit = set_time_limit( 200 );
            if ( secnin_fs()->can_use_premium_code__premium_only() ) {
                if ( !$set_time_limit ) {
                    wf_sn_el_modules::log_event(
                        'security_ninja',
                        'security_tests',
                        'Could not set PHP time limit set_time_limit() in run_all_tests()',
                        ''
                    );
                }
            }
            $loop_count = 1;
            $resultssofar['last_run'] = time();
            // Add multisite context information
            if ( is_multisite() ) {
                $resultssofar['multisite'] = array(
                    'is_multisite'    => true,
                    'current_site_id' => get_current_blog_id(),
                    'is_main_site'    => is_main_site(),
                    'network_id'      => get_current_network_id(),
                );
            } else {
                $resultssofar['multisite'] = array(
                    'is_multisite' => false,
                );
            }
            if ( is_array( $security_tests ) ) {
                foreach ( $security_tests as $test_name => $test ) {
                    if ( '_' === $test_name[0] || in_array( $test_name, self::$skip_tests, true ) || 'ad_' === substr( $test_name, 0, 3 ) ) {
                        continue;
                    }
                    // Skip certain tests in multisite subsites if they're network-level concerns
                    if ( is_multisite() && !is_main_site() && self::should_skip_test_in_subsite( $test_name ) ) {
                        $resultssofar['test'][$test_name] = array(
                            'title'  => $test['title'],
                            'status' => 5,
                            'score'  => $test['score'],
                            'msg'    => __( 'This test is typically handled at the network level in multisite installations.', 'security-ninja' ),
                        );
                        ++$loop_count;
                        continue;
                    }
                    $response = wf_sn_tests::$test_name();
                    $json_response = array();
                    $json_response['last_test'] = $test['title'];
                    $json_response['last_status'] = $response['status'];
                    $json_response['last_score'] = $test['score'];
                    if ( !isset( $response['msg'] ) ) {
                        $response['msg'] = '';
                    }
                    // Setting appropriate message.
                    if ( 10 === intval( $response['status'] ) ) {
                        $json_response['last_msg'] = $response['msg'];
                    } elseif ( 0 === intval( $response['status'] ) ) {
                        $json_response['last_msg'] = $response['msg'];
                    } else {
                        $json_response['last_msg'] = $response['msg'];
                    }
                    // Updates the results
                    $resultssofar['test'][$test_name] = array(
                        'title'   => $test['title'],
                        'status'  => $response['status'],
                        'score'   => $test['score'],
                        'msg'     => $json_response['last_msg'],
                        'details' => ( isset( $response['details'] ) ? $response['details'] : '' ),
                    );
                    // Persist to wf_sn_tests table so get_test_results() and AI Advisor have latest findings.
                    $testresult = array(
                        'testid'    => $test_name,
                        'timestamp' => current_time( 'mysql' ),
                        'title'     => $test['title'],
                        'status'    => (int) $response['status'],
                        'score'     => (int) $test['score'],
                        'runtime'   => '',
                        'msg'       => $json_response['last_msg'],
                        'details'   => ( isset( $response['details'] ) ? $response['details'] : '' ),
                    );
                    self::update_test_score( $testresult );
                    ++$loop_count;
                }
                // No more tests - let us stop
                $json_response['step'] = 'done';
                $resultssofar['last_run'] = time();
                $stoptime = \WPSecurityNinja\Plugin\Utils::timerstop( 'wf_sn_run_all_tests' );
                if ( $stoptime ) {
                    $resultssofar['run_time'] = $stoptime;
                }
                update_option( 'wf_sn_results', $resultssofar, false );
            }
            // her stopper det sjove?
            do_action( 'security_ninja_done_testing', __( 'Security Tests - Completed Scanning', 'security-ninja' ), $resultssofar['run_time'] );
            if ( $return_data ) {
                $resultssofar = get_option( 'wf_sn_results' );
                return $resultssofar;
            } else {
                wp_send_json_success( $json_response );
            }
        }

        /**
         * Determines if a test should be skipped in multisite subsites
         *
         * @author  Lars Koudal
         * @since   v1.0.1
         * @version v1.0.0  Tuesday, June 11th, 2024.
         * @access  private static
         * @param   string $test_name The name of the test
         * @return  boolean True if the test should be skipped
         */
        private static function should_skip_test_in_subsite( $test_name ) {
            // Tests that are typically network-level concerns in multisite
            $network_level_tests = array(
                'core_updates_check',
                'config_chmod',
                'config_location',
                'db_password_check',
                'salt_keys_check',
                'salt_keys_age_check',
                'register_globals_check',
                'safe_mode_check',
                'allow_url_include_check',
                'expose_php_check',
                'display_errors_check',
                'php_headers',
                'strict_transport_security',
                'referrer_policy',
                'feature_policy',
                'content_security_policy',
                'x_frame_options',
                'x_content_type_options'
            );
            return in_array( $test_name, $network_level_tests, true );
        }

        /**
         * Resets pointers on activation and saves some information.
         *
         * This method is responsible for resetting pointers and saving specific information during the plugin's activation process.
         * It checks if the plugin is being activated for the first time and sets the initial version and install time if so.
         * Additionally, it may create a database table for storing security test results and performs other activation routines.
         *
         * @author  Lars Koudal
         * @since   v0.0.1
         * @version v1.0.0  Tuesday, December 7th, 2021.
         * @access  public static
         * @param   bool $network_wide Whether the plugin is being activated network-wide.
         * @return  void
         */
        public static function activate( $network_wide = false ) {
            $missing_options_marker = '__secnin_missing_options__';
            $is_first_install = $missing_options_marker === get_option( 'wf_sn_options', $missing_options_marker );
            $options = self::get_options();
            if ( $is_first_install ) {
                $options['first_version'] = \WPSecurityNinja\Plugin\Utils::get_plugin_version();
                $options['first_install'] = time();
                update_option( 'wf_sn_options', $options, false );
                $action = ( isset( $_REQUEST['action'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['action'] ) ) : '' );
                $checked_plugins = ( isset( $_POST['checked'] ) ? array_map( 'sanitize_text_field', wp_unslash( (array) $_POST['checked'] ) ) : array() );
                $is_bulk_activate = 'activate-selected' === $action || count( $checked_plugins ) > 1;
                if ( !$network_wide && !$is_bulk_activate ) {
                    $user_id = get_current_user_id();
                    if ( $user_id ) {
                        update_option( 'secnin_activation_redirect', $user_id, false );
                    }
                }
            }
            global $wpdb;
            include_once ABSPATH . 'wp-admin/includes/upgrade.php';
            $charset = $wpdb->get_charset_collate();
            if ( $network_wide && is_multisite() ) {
                // Network activation - create tables for all sites
                $sites = get_sites( array(
                    'fields' => 'ids',
                ) );
                foreach ( $sites as $site_id ) {
                    switch_to_blog( $site_id );
                    \WPSecurityNinja\Plugin\Utils::create_tables_for_site( $charset );
                    // Ensure Events Logger is enabled by default on first activation
                    if ( class_exists( __NAMESPACE__ . '\\Wf_Sn_El' ) ) {
                        \WPSecurityNinja\Plugin\Wf_Sn_El::default_settings( false );
                    }
                    if ( secnin_fs()->is__premium_only() ) {
                        // Add current admin IP to whitelist if available
                        if ( current_user_can( 'manage_options' ) ) {
                            if ( class_exists( __NAMESPACE__ . '\\wf_sn_cf' ) ) {
                                $current_user_ip = \WPSecurityNinja\Plugin\wf_sn_cf::get_user_ip();
                                if ( $current_user_ip ) {
                                    \WPSecurityNinja\Plugin\wf_sn_cf::add_ip_to_whitelist( $current_user_ip );
                                }
                            }
                        }
                    }
                    update_option( 'wf_sn_db_version', self::WF_SN_DB_VERSION );
                    restore_current_blog();
                }
            } elseif ( !$network_wide ) {
                // Single site activation (only when not network-wide)
                \WPSecurityNinja\Plugin\Utils::create_tables_for_site( $charset );
                // Ensure Events Logger is enabled by default on first activation
                if ( class_exists( __NAMESPACE__ . '\\Wf_Sn_El' ) ) {
                    \WPSecurityNinja\Plugin\Wf_Sn_El::default_settings( false );
                }
                if ( secnin_fs()->is__premium_only() ) {
                    // Add current admin IP to whitelist if available
                    if ( current_user_can( 'manage_options' ) ) {
                        if ( class_exists( __NAMESPACE__ . '\\wf_sn_cf' ) ) {
                            $current_user_ip = \WPSecurityNinja\Plugin\wf_sn_cf::get_user_ip();
                            if ( $current_user_ip ) {
                                \WPSecurityNinja\Plugin\wf_sn_cf::add_ip_to_whitelist( $current_user_ip );
                            }
                        }
                    }
                }
                update_option( 'wf_sn_db_version', self::WF_SN_DB_VERSION );
            }
        }

        /**
         * Performs cleanup operations when the plugin is deactivated.
         *
         * This method checks if the option to remove settings on deactivation is set and if so, it should implement the removal functionality.
         * Currently, the removal functionality is not implemented and is marked as a todo.
         *
         * @author  Lars Koudal
         * @since   v0.0.1
         * @version v1.0.0  Saturday, March 5th, 2022.
         * @access  public static
         * @return  void
         */
        public static function deactivate() {
            $centraloptions = self::get_options();
            if ( !isset( $centraloptions['remove_settings_deactivate'] ) || !$centraloptions['remove_settings_deactivate'] ) {
                return;
            }
            // @todo - implement remove functionality here
        }

        /**
         * Performs cleanup operations when the plugin is uninstalled.
         *
         * This method drops the security tests table, deletes various options and usermeta, and drops additional tables and options if the plugin is premium.
         *
         * @author  Lars Koudal
         * @since   v0.0.1
         * @version v1.0.0  Saturday, March 5th, 2022.
         * @access  public static
         * @return  void
         */
        public static function uninstall() {
            global $wpdb;
            // Drop security tests table
            if ( self::table_exists( $wpdb->prefix . 'wf_sn_tests' ) ) {
                $wpdb->query( 'DROP TABLE IF EXISTS ' . $wpdb->prefix . 'wf_sn_tests' );
            }
            // Delete options
            delete_option( 'wf_sn_results' );
            delete_option( 'wf_sn_options' );
            delete_option( 'wfsn_freemius_state' );
            delete_option( 'wf_sn_active_plugins' );
            delete_option( 'wf_sn_review_notice' );
            delete_option( 'wf_sn_tests' );
            // Delete usermeta
            $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->usermeta} WHERE meta_key = %s", 'sn_last_login' ) );
            // If the plugin is premium, drop additional tables and delete options
            if ( secnin_fs()->can_use_premium_code__premium_only() ) {
                if ( self::table_exists( $wpdb->prefix . 'wf_sn_el' ) ) {
                    $wpdb->query( 'DROP TABLE IF EXISTS ' . $wpdb->prefix . 'wf_sn_el' );
                }
                if ( self::table_exists( $wpdb->prefix . 'wf_sn_cf_bl_ips' ) ) {
                    $wpdb->query( 'DROP TABLE IF EXISTS ' . $wpdb->prefix . 'wf_sn_cf_bl_ips' );
                }
                if ( self::table_exists( $wpdb->prefix . 'wf_sn_ss_log' ) ) {
                    $wpdb->query( 'DROP TABLE IF EXISTS ' . $wpdb->prefix . 'wf_sn_ss_log' );
                }
                if ( self::table_exists( $wpdb->prefix . 'wf_sn_cf_vl' ) ) {
                    $wpdb->query( 'DROP TABLE IF EXISTS ' . $wpdb->prefix . 'wf_sn_cf_vl' );
                }
                delete_option( 'wf_sn_cf_vl' );
                delete_option( 'wf_sn_cf_ips' );
                delete_option( 'wf_sn_cf_blocked_count' );
                delete_option( 'wf_sn_cf' );
                delete_option( 'wf_sn_el' );
            }
        }

        /**
         * Updates test score in database - multisite compatible
         *
         * @author  Lars Koudal
         * @since   v0.0.1
         * @version v1.0.0  Thursday, January 14th, 2021.
         * @version v1.0.1  Tuesday, June 11th, 2024.
         * @access  public static
         * @param   array $testresult Test result data
         * @return  boolean Success status
         */
        public static function update_test_score( $testresult ) {
            if ( !$testresult ) {
                return false;
            }
            global $wpdb;
            // Ensure we're using the correct database prefix for the current site
            $table_name = $wpdb->prefix . 'wf_sn_tests';
            if ( !isset( $testresult['details'] ) ) {
                $testresult['details'] = '';
            }
            // Ensure the table exists before attempting to insert/update
            if ( !self::table_exists( $table_name ) ) {
                // Create the table if it doesn't exist
                include_once ABSPATH . 'wp-admin/includes/upgrade.php';
                $charset = $wpdb->get_charset_collate();
                \WPSecurityNinja\Plugin\Utils::create_tables_for_site( $charset );
            }
            // Also check if premium tables exist when license is validated
            if ( secnin_fs()->can_use_premium_code__premium_only() ) {
                $el_table = $wpdb->prefix . 'wf_sn_el';
                if ( !self::table_exists( $el_table ) ) {
                    // Premium tables don't exist, create them
                    include_once ABSPATH . 'wp-admin/includes/upgrade.php';
                    $charset = $wpdb->get_charset_collate();
                    \WPSecurityNinja\Plugin\Utils::create_tables_for_site( $charset );
                }
            }
            $result = $wpdb->replace( $table_name, $testresult, array(
                '%s',
                '%s',
                '%s',
                '%d',
                '%d',
                '%s',
                '%s'
            ) );
            return false !== $result;
        }

        /**
         * Checks if a database table exists
         *
         * @author  Lars Koudal
         * @since   v1.0.1
         * @version v1.0.0  Tuesday, June 11th, 2024.
         * @access  private static
         * @param   string $table_name The table name to check
         * @return  boolean True if the table exists
         */
        private static function table_exists( $table_name ) {
            global $wpdb;
            $result = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) );
            return $result === $table_name;
        }

        /**
         * Handles license validation events from Freemius
         * Creates database tables when premium code becomes available
         *
         * @author  Lars Koudal
         * @since   v1.0.1
         * @version v1.0.0  Tuesday, June 11th, 2024.
         * @access  public static
         * @return  void
         */
        public static function handle_license_validation__premium_only() {
            global $wpdb;
            // Check if premium tables exist
            $el_table = $wpdb->prefix . 'wf_sn_el';
            $vl_table = $wpdb->prefix . 'wf_sn_cf_vl';
            $bl_table = $wpdb->prefix . 'wf_sn_cf_bl_ips';
            $ss_table = $wpdb->prefix . 'wf_sn_ss_log';
            // If any premium table doesn't exist, create all tables
            if ( !self::table_exists( $el_table ) || !self::table_exists( $vl_table ) || !self::table_exists( $bl_table ) || !self::table_exists( $ss_table ) ) {
                include_once ABSPATH . 'wp-admin/includes/upgrade.php';
                $charset = $wpdb->get_charset_collate();
                \WPSecurityNinja\Plugin\Utils::create_tables_for_site( $charset );
                // Log the automatic table creation
                if ( class_exists( '\\WPSecurityNinja\\Plugin\\wf_sn_el_modules' ) ) {
                    \WPSecurityNinja\Plugin\wf_sn_el_modules::log_event( 'security_ninja', 'license_validation', 'Created database tables after license validation for site: ' . get_current_blog_id() );
                }
            }
        }

        /**
         * Queue a one-time redirect after successful Pro license activation.
         *
         * @since 5.291
         * @access public static
         * @return void
         */
        public static function queue_license_activation_redirect__premium_only() {
            if ( is_network_admin() || wp_doing_ajax() || !current_user_can( 'manage_options' ) ) {
                return;
            }
            $user_id = get_current_user_id();
            if ( !$user_id ) {
                return;
            }
            update_option( 'secnin_license_activation_redirect', $user_id, false );
        }

        /**
         * Redirects once after Pro license activation.
         *
         * @since 5.291
         * @access private static
         * @return void
         */
        private static function maybe_redirect_after_license_activation__premium_only() {
            if ( wp_doing_ajax() || is_network_admin() || !current_user_can( 'manage_options' ) ) {
                return;
            }
            $redirect_user_id = absint( get_option( 'secnin_license_activation_redirect', 0 ) );
            if ( !$redirect_user_id || get_current_user_id() !== $redirect_user_id ) {
                return;
            }
            delete_option( 'secnin_license_activation_redirect' );
            wp_safe_redirect( self::get_post_install_redirect_url() );
            exit;
        }

    }

}
register_activation_hook( __FILE__, array(__NAMESPACE__ . '\\WF_SN', 'activate') );
register_deactivation_hook( __FILE__, array(__NAMESPACE__ . '\\WF_SN', 'deactivate') );
register_uninstall_hook( __FILE__, array(__NAMESPACE__ . '\\WF_SN', 'uninstall') );
// Load translations on init hook with priority 1 (WordPress 6.7+ requirement)
// This ensures translations are loaded early in the init cycle, before any code uses them
add_action( 'init', array(__NAMESPACE__ . '\\WF_SN', 'load_textdomain'), 1 );
add_action( 'init', array(__NAMESPACE__ . '\\WF_SN', 'init') );
// Multisite hooks
if ( is_multisite() ) {
    add_action( 'wpmu_new_blog', array(__NAMESPACE__ . '\\WF_SN', 'handle_new_site') );
    add_action( 'wp_insert_site', array(__NAMESPACE__ . '\\WF_SN', 'handle_new_site') );
}