<?php
/*
Plugin Name:        Quttera Web Malware Scanner
Plugin URI:         http://quttera.com
Description:        Web malware and vulnerability exploits scanner for WordPress.
Version:            4.0.0.9
Author:             Quttera team
Author URI:         http://quttera.com/
License:            GNU General Public License v2
*/

//define('QTR_DEBUG', true);

require_once('qtrUtils.php');
require_once('qtrConfig.php');
require_once('qtrLogger.php');
require_once('qtrAjaxHandler.php');
require_once('qtrAlarmsManager.php');
require_once('qtrLoginSecurity.php');
require_once('qtrAdminUsersMonitor.php');
require_once('qtrIPManager.php');
require_once('qtrBruteForce.php');
require_once('qtrBotProtection.php');

if(!function_exists('add_action'))
{
    exit(0);
}

/* qtr_wm_scanner */
@ini_set('max_execution_time', 30000 );
@ini_set('max_input_time', 30000 );
@ini_set('memory_limit', '2024M');
@set_time_limit(30000);


define('QUTTERA_WM_SCANNER','quttera_wm_scanner');
define('QUTTERA_WM_SCANNER_VERSION','4.0.0.9');
define('QUTTERA_URL',plugin_dir_url( __FILE__ ));


add_action( 'admin_enqueue_scripts', 'quttera_style', 1 );

add_action( 'admin_menu', 'qtr_setup_scanner_menu' );

// Register hooks EARLY
add_action('plugins_loaded', function () {
    //adding alarms table if needed
    CQtrAlarmsManager::install();

    //initialize login security monitoring
    CQtrLoginSecurity::init();

    //initialize admin users monitoring
    CQtrAdminUsersMonitor::init();

    //initialize brute-force protection 
    CQtrBruteForce::init();

    //initialize bot protection
    CQtrBotProtection::init();

    //schedule bot protection expired blocks cleanup every 15 minutes
    if (!wp_next_scheduled('qtr_cleanup_expired_bot_blocks')) {
        wp_schedule_event(time(), 'qtr_fifteen_minutes', 'qtr_cleanup_expired_bot_blocks');
    }
    add_action('qtr_cleanup_expired_bot_blocks', 'CQtrBotProtection::CleanupExpiredBlocks');

    if (!wp_next_scheduled('qtr_bot_cleanup_database')) {
        wp_schedule_event(time(), 'hourly', 'qtr_bot_cleanup_database');
    }
    add_action('qtr_bot_cleanup_database', 'CQtrBotProtection::CleanupDatabaseTransients');
});

// Add custom 15-minute cron schedule
add_filter('cron_schedules', function($schedules) {
    if (!isset($schedules['qtr_fifteen_minutes'])) {
        $schedules['qtr_fifteen_minutes'] = array(
            'interval' => 900,  // 900 seconds = 15 minutes
            'display'  => __('Every 15 minutes')
        );
    }
    return $schedules;
});


/*
 * setup action @scanner-run_scan mapped to callback qtr_wm_scanner_ajax_run_scan
 * wp_ajax_ prefix used only for logged in users
 */ 
add_action( 'wp_ajax_scanner-run_scan', 'CQtrAjaxHandler::RunExternalScan' );

/*
 * setup action @scanner-run_internal_scan mapped to callback qtr_wm_scanner_ajax_run_internal_scan
 * wp_ajax_ prefix used only for logged in users
 */ 
add_action( 'wp_ajax_scanner-run_internal_scan', 'CQtrAjaxHandler::RunInternalScan' );

add_action( 'wp_ajax_scanner-run_heur_internal_scan', 'CQtrAjaxHandler::RunHeurInternalScan' );

add_action( 'wp_ajax_scanner-settings', 'CQtrAjaxHandler::UpdateSettings' );

add_action( 'wp_ajax_scanner-is_internal_scan_running', 'CQtrAjaxHandler::IsInternalScanNowRunning' );

add_action( 'wp_ajax_scanner-is_external_scan_running', 'CQtrAjaxHandler::IsExternalScanNowRunning' );

add_action( 'wp_ajax_scanner-get_log_lines', 'CQtrAjaxHandler::GetLogLines' );

add_action( 'wp_ajax_scanner-clean_log', 'CQtrAjaxHandler::CleanLogLines' );

add_action( 'wp_ajax_scanner-get_stats', 'CQtrAjaxHandler::GetStats' );

add_action( 'wp_ajax_scanner-stop_internal_scan', 'CQtrAjaxHandler::StopInternalScan' );

add_action( 'wp_ajax_scanner-get_detected_threats', 'CQtrAjaxHandler::GetDetectedThreatsReport' );

add_action( 'wp_ajax_scanner-get_ignored_threats', 'CQtrAjaxHandler::GetIgnoredThreatsReport' );

add_action( 'wp_ajax_scanner-ignore_threat', 'CQtrAjaxHandler::IgnoreThreat' );

add_action( 'wp_ajax_scanner-get_file_report', 'CQtrAjaxHandler::ScannerReport' );

add_action( 'wp_ajax_scanner-show_file', 'CQtrAjaxHandler::ShowFile' );

/* 
 * return threat back to report
 */
add_action( 'wp_ajax_scanner-unignore_threat', 'CQtrAjaxHandler::RemoveFromIgnoreList' );

add_action( 'wp_ajax_scanner-clean_ignore_list', 'CQtrAjaxHandler::CleanIgnoreList');

add_action( 'wp_ajax_scanner-acknowledge_alarm','CQtrAjaxHandler::AcknowledgeAlarm' );

add_action( 'wp_ajax_scanner-get-alarms', 'CQtrAjaxHandler::GetAlarms' );

add_action( 'wp_ajax_scanner-whitelist_threat','CQtrAjaxHandler::WhiteListThreat' );

add_action( 'wp_ajax_scanner-clean_threats_whitelist', 'CQtrAjaxHandler::CleanThreatsWhiteList');

// Brute-force IP Management AJAX handlers
add_action('wp_ajax_scanner-get-bruteforce-blocked-ips', 'CQtrAjaxHandler::GetBruteForceBlockedIPs');
add_action('wp_ajax_scanner-remove-bruteforce-blocked-ip', 'CQtrAjaxHandler::RemoveBruteForceBlockedIP');
add_action('wp_ajax_scanner-clear-all-bruteforce-blocked-ips', 'CQtrAjaxHandler::ClearAllBruteForceBlockedIPs');

// Bot Protection IP Management AJAX handlers
add_action('wp_ajax_scanner-get-bot-protection-blocked-ips', 'CQtrAjaxHandler::GetBotProtectionBlockedIPs');
add_action('wp_ajax_scanner-remove-bot-protection-blocked-ip', 'CQtrAjaxHandler::RemoveBotProtectionBlockedIP');
add_action('wp_ajax_scanner-clear-all-bot-protection-blocked-ips', 'CQtrAjaxHandler::ClearAllBotProtectionBlockedIPs');

// Protection Statistics APIs
add_action('wp_ajax_scanner-get-bruteforce-stats', 'CQtrAjaxHandler::GetBruteForceStats');
add_action('wp_ajax_scanner-get-botprotection-stats', 'CQtrAjaxHandler::GetBotProtectionStats');


add_action( 'wp_ajax_scanner-whitelist_file', 'CQtrAjaxHandler::WhiteListFile');

add_action( 'wp_ajax_scanner-clean_files_whitelist', 'CQtrAjaxHandler::CleanFilesWhiteList');


function quttera_style() 
{
    if(isset($_GET['page']) && is_string($_GET['page']) && preg_match('/quttera_wm_scanner/',$_GET['page']) ) 
    {
	    echo '<link rel="stylesheet" href="'. QUTTERA_URL . DIRECTORY_SEPARATOR . "CSS" . DIRECTORY_SEPARATOR . 'bootstrap.min.css" type="text/css" media="all"/>' . "\n";
    }
}


/**
 * Set up the menu item and register with hooks to print JS and help.
 */
function qtr_setup_scanner_menu() {
    /* 
     * FIXME - this image should be moved to wp.quttera.com 
     */
    $image_path = "http://wp.quttera.com/images/quttera_16x16.png";

    /***********************************************************
     *          dashboard menu and pages
     **********************************************************/
    $page_hook = add_submenu_page(  'quttera_wm_scanner',
                                    "Quttera Web Malware Scanner", 
                                    '<span style = "">Dashboard</span>', 
                                    "manage_options", 
                                    "quttera_wm_scanner", 
                                    "qtr_dashboard_page");   


    $page_hook  = add_menu_page('Quttera Web Malware Scanner', 
                                'Quttera',
                                'manage_options',
                                'quttera_wm_scanner', 
                                'qtr_dashboard_page',
                                $image_path);

    if ( $page_hook ) 
    {
        add_action( "admin_print_styles-$page_hook", 'add_thickbox' );
        add_action( "admin_footer-$page_hook", 'qtr_load_dashboard_frontend' );
    }

    /***********************************************************
     *          External scanner menu and pages
     **********************************************************/
    $page_hook = add_submenu_page(  'quttera_wm_scanner',
                                    "Quttera Web Malware Scanner", 
                                    '<span style = "">External Scanner</span>', 
                                    "manage_options", 
                                    "quttera_wm_scanner_ext", 
                                    "qtr_external_scan_page" );  

    if ( $page_hook ) 
    {
        add_action( "admin_print_styles-$page_hook", 'add_thickbox' );
        add_action( "admin_footer-$page_hook", 'qtr_load_escan_frontend' );
    }

    /***********************************************************
     *          Internal scanner menu and pages
     **********************************************************/
    $page_hook = add_submenu_page(  'quttera_wm_scanner',
                                    "Quttera Web Malware Scanner", 
                                    '<span style = "">Internal Scanner</span>',
                                    "manage_options", 
                                    "quttera_wm_scanner_int", 
                                    "qtr_internal_scan_page" );  
    if ( $page_hook ) 
    {
        add_action( "admin_print_styles-$page_hook", 'add_thickbox' );
        add_action( "admin_footer-$page_hook", 'qtr_load_iscan_frontend' );
    }


    /***********************************************************
     *          Heuristic Internal scanner menu and pages
     *********************************************************
    $page_hook = add_submenu_page(  'quttera_wm_scanner',
                                    "Quttera Web Malware Scanner", 
                                    '<span style = "">Internal Scanner - High Sensitivity</span>',
                                    "manage_options", 
                                    "quttera_wm_scanner_heur_int", 
                                    "qtr_heur_internal_scan_page" );  
    if ( $page_hook ) 
    {
        add_action( "admin_print_styles-$page_hook", 'add_thickbox' );
        add_action( "admin_footer-$page_hook", 'qtr_load_heur_iscan_frontend' );
    }*/

    /***********************************************************
     *    Brute-Force Blocked IPs viewer menu and pages
     **********************************************************/

    $page_hook = add_submenu_page(  'quttera_wm_scanner',
                                    "Quttera Brute-Force Blocked IPs", 
                                    '<span style = "">Brute-Force</span>',
                                    "manage_options", 
                                    "quttera_wm_scanner_bruteforce", 
                                    "qtr_brute_force_blocked_ips_page");
    
    if ( $page_hook ) 
    {
        add_action( "admin_print_styles-$page_hook", 'add_thickbox' );
        add_action( "admin_footer-$page_hook", 'qtr_load_brute_force_frontend' );
    }

    /***********************************************************
     *    Bot Protection Blocked IPs viewer menu and pages
     **********************************************************/

    $page_hook = add_submenu_page(  'quttera_wm_scanner',
                                    "Quttera Bot Protection Blocked IPs", 
                                    '<span style = "">Bot Protection</span>',
                                    "manage_options", 
                                    "quttera_wm_scanner_bot_protection", 
                                    "qtr_bot_protection_blocked_ips_page");
    
    if ( $page_hook ) 
    {
        add_action( "admin_print_styles-$page_hook", 'add_thickbox' );
        add_action( "admin_footer-$page_hook", 'qtr_load_bot_protection_frontend' );
    }

    /***********************************************************
     *          Settings menu and pages
     **********************************************************/
    $page_hook = add_submenu_page(  'quttera_wm_scanner',
                                    "Quttera Web Malware Scanner", 
                                    '<span style = "">Settings</span>',
                                    "manage_options", 
                                    "quttera_wm_scanner_settings", 
                                    "qtr_settings_page" );  
    if ( $page_hook ) 
    {
        add_action( "admin_print_styles-$page_hook", 'add_thickbox' );
        add_action( "admin_footer-$page_hook", 'qtr_load_settings_frontend' );
    }

        /***********************************************************
     *          Settings menu and pages
     **********************************************************/
    $page_hook = add_submenu_page(  'quttera_wm_scanner',
                                    "Quttera Web Malware Scanner", 
                                    '<span style = "">How To - User Guide</span>',
                                    "manage_options", 
                                    "quttera_wm_scanner_howto", 
                                    "qtr_howto_page" );  
    if ( $page_hook ) 
    {
        add_action( "admin_print_styles-$page_hook", 'add_thickbox' );
        add_action( "admin_footer-$page_hook", 'qtr_load_settings_frontend' );
    }


    /***********************************************************
     *          FAQ menu and pages
     **********************************************************/
    $page_hook = add_submenu_page(  'quttera_wm_scanner',
                                    "Quttera Web Malware Scanner - FAQ", 
                                    '<span style = "">FAQ</span>',
                                    "manage_options", 
                                    "quttera_wm_scanner_faq", 
                                    "qtr_faq_page" );

    /***********************************************************
     *         Scanner log viewer menu and pages
     **********************************************************/

    $page_hook =  add_submenu_page( 'quttera_wm_scanner',
                                    "Quttera Web Malware Scanner Log", 
                                    '<span style = "">Operational Log</span>',
                                    "manage_options", 
                                    "quttera_wm_scanner_logss", 
                                    "quttera_scanner_log_page");  
    
    if ( $page_hook ) 
    {
        add_action( "admin_print_styles-$page_hook", 'add_thickbox' );
        add_action( "admin_footer-$page_hook", 'qtr_load_log_frontend' );
    }
    
}




/**
 * Print scripts that power paged scanning and diff modal.
 */
function qtr_load_dashboard_frontend() {
    require "JS" . DIRECTORY_SEPARATOR . "dashboard_frontend.php";
}

function qtr_load_escan_frontend() { 
    require "JS" . DIRECTORY_SEPARATOR . "escan_frontend.php";
}


function qtr_load_iscan_frontend(){
    require "JS" . DIRECTORY_SEPARATOR . "iscan_frontend.php";
}


function qtr_load_heur_iscan_frontend(){
    require "JS" . DIRECTORY_SEPARATOR . "heur_iscan_frontend.php";
}

function qtr_load_settings_frontend(){
    require "JS" . DIRECTORY_SEPARATOR . "settings_frontend.php";
}

function qtr_load_log_frontend(){
    require "JS" . DIRECTORY_SEPARATOR . "scanner_log_frontend.php";
}

function qtr_load_brute_force_frontend(){
    require "JS" . DIRECTORY_SEPARATOR . "brute_force_frontend.php";
}

function qtr_load_bot_protection_frontend(){
    require "JS" . DIRECTORY_SEPARATOR . "bot_protection_frontend.php";
}

function qtr_wm_scanner_ajax_run_scan()
{
    if(!current_user_can('manage_options'))
    {
        wp_die(__('You do not have sufficient permissions to access this page.') );
    }
	
    //check_ajax_referer( 'qtr_wm_scanner-scan' );
    $this_url = trim($_POST['_this']);        /* domain name of this server */
    $qtr_url  = trim($_POST['_qtr_url']);     /* quttera investigation server name */
     
    if( empty($this_url) )
    {
        $this_url = CQtrUtils::GetDomainName();
    }
    else if( empty($qtr_url) )
    {
        $qtr_url = "http://wp.quttera.com";
    }

    if( strpos($this_url, "://" ) != false )
    {
        $parse      = parse_url($this_url);
        $this_url   = $parse['host'];
    }
    /* 
     * validate input of host name 
     */
    if(filter_var(gethostbyname($this_url), FILTER_VALIDATE_IP) === FALSE )
    {
        /* send error to frontend */
	    echo json_encode( array( 'content' => array( "state" => "Failed to access local server",
                                                     "age" => time(),
                                                     "url" => "localhost" )));
        exit;

    }
    
    /* validate quttera server address */
    if( filter_var($qtr_url, FILTER_VALIDATE_URL) === FALSE )
    {
        /* send error to fronend */
    	echo json_encode( array( 'content' => array( "state" => "Failed to access remote server",
                                                     "age" => time(),
                                                     "url" => $this_url )));
        exit;
    }

    $investigation_url =  $qtr_url . "/wp_scan/" . $this_url;

    if( filter_var($investigation_url, FILTER_VALIDATE_URL) === FALSE )
    {
	    echo json_encode( array( 'content' => array( "state" => "Remote server address is invalid",
                                                     "age" => time(),
                                                     "url" => "<undefined>" )));
        exit;
    }

    usleep(1000000); //sleep for a second

    //$output = file_get_contents($investigation_url);
    $output = qtr_scanner_query($investigation_url);
    
    if( empty($output) )
    {
        //$output = file_get_contents($investigation_url);
        $output = qtr_scanner_query($investigation_url);
    
        if( $output == false )
        {
            echo json_encode( array( 'content' => array( "state" => "Failed to access investigation server [ " . $qtr_url . " ]",
                                                         "age" => time(),
                                                         "url" => $this_url,
                                                         "img" => plugins_url( 'loader.gif', __FILE__ )) 
                                    ) 
                            );
            exit;
        }
    }
    
    $output = json_decode($output);
    
    //if state is not finished sleep for a second
    echo json_encode( array( 'content' => $output ) );
    exit;
}

function qtr_dashboard_page()
{
    if(!current_user_can('manage_options'))
    {
        wp_die(__('You do not have sufficient permissions to access this page.') );
    }

    require("dashboard.php");
}

function qtr_settings_page()
{
    if(!current_user_can('manage_options'))
    {
        wp_die(__('You do not have sufficient permissions to access this page.') );
    }

    require("settings.php");
}

function qtr_howto_page()
{
    if(!current_user_can('manage_options'))
    {
        wp_die(__('You do not have sufficient permissions to access this page.') );
    }

    require("qtr_howto_page.php");
}


/**
 *
 */
function qtr_external_scan_page()
{
    if(!current_user_can('activate_plugins'))
    {
        wp_die(__('You do not have sufficient permissions to access this page.') );
    }

    require ("external_scan_page.php");
}


/**
 *
 */
function qtr_internal_scan_page()
{
    if(!current_user_can('activate_plugins'))
    {
        wp_die(__('You do not have sufficient permissions to access this page.') );
    }

    require("internal_scan_page.php");
}

/**
 *
 */
function qtr_heur_internal_scan_page()
{
    if(!current_user_can('activate_plugins'))
    {
        wp_die(__('You do not have sufficient permissions to access this page.') );
    }

    require("heur_internal_scan_page.php");
}



function qtr_faq_page()
{
    if(!current_user_can('activate_plugins'))
    {
        wp_die(__('You do not have sufficient permissions to access this page.') );
    }

    require "qtr_faq_page.php";
}


function quttera_scanner_log_page() {
    // Check user permissions
    if (!current_user_can('manage_options')) {
        wp_die(__('You do not have sufficient permissions to access this page.'));
    }
    
    // Include the log page template
    require("scanner_log_page.php");
}

function qtr_brute_force_blocked_ips_page() {
    // Check user permissions
    if (!current_user_can('manage_options')) {
        wp_die(__('You do not have sufficient permissions to access this page.'));
    }
    
    // Include the brute-force blocked IPs page template
    require("brute_force_blocked_ips_page.php");
}

function qtr_bot_protection_blocked_ips_page() {
    // Check user permissions
    if (!current_user_can('manage_options')) {
        wp_die(__('You do not have sufficient permissions to access this page.'));
    }
    
    // Include the bot protection blocked IPs page template
    require("bot_protection_blocked_ips_page.php");
}

/**
 * Activation callback.
 *
 * Add database version info. Set up non-autoloaded options as there is no need
 * to load results or clean core files on any page other than the exploit scanner page.
 */
function on_qtr_scanner_activation() {
    // Activation hook logic (if needed in future)
}

register_activation_hook( __FILE__, 'on_qtr_scanner_activation' );

/**
 * Register uninstall hook at plugin load time (not just on activation)
 * This ensures the hook is registered for:
 * - New installations
 * - Plugin updates via WordPress upgrade
 * - Already-installed plugins
 */
register_uninstall_hook( __FILE__, 'on_qtr_scanner_uninstall' );

/**
 * Deactivation callback. Remove transients and unschedule all cron jobs.
 */
function on_qtr_scanner_deactivate() {
    /* remove cached (runtime) parameters */
    
    /*
     * removes hook registration if exists
     */
    wp_clear_scheduled_hook('qtr_internal_scan_cron_hook');
    wp_clear_scheduled_hook('qtr_heur_internal_scan_cron_hook');
    wp_clear_scheduled_hook('qtr_cleanup_expired_bot_blocks');
    wp_clear_scheduled_hook('qtr_bot_cleanup_database');
    wp_clear_scheduled_hook('qtr_admin_monitor_cron');
    wp_clear_scheduled_hook('qtr_hourly_login_check');
}

register_deactivation_hook( __FILE__, 'on_qtr_scanner_deactivate' );


/**
 * Uninstall callback. Remove all data stored by the plugin.
 * Cleans up database tables, WordPress options, transients, and scheduled cron jobs.
 */
function on_qtr_scanner_uninstall()
{
    global $wpdb;
    
    // ===========================
    // 1. DROP CUSTOM DATABASE TABLES
    // ===========================
    
    // Drop alarms table
    $alarms_table = $wpdb->prefix . 'qtr_alarms';
    $wpdb->query("DROP TABLE IF EXISTS `{$alarms_table}`");
    
    // Drop admin users snapshots table
    $snapshots_table = $wpdb->prefix . 'qtr_admin_users_snapshots';
    $wpdb->query("DROP TABLE IF EXISTS `{$snapshots_table}`");
    
    // ===========================
    // 2. DELETE ALL WORDPRESS OPTIONS
    // ===========================
    
    // Core plugin options
    delete_option('qtr_scanner_uuid');
    delete_option('qtr_settings');
    delete_option('quttera_settings_v1');  // Main settings (correct key used by QutteraSettings class)
    delete_option('qtr_hosting_type');
    delete_option('qtr_bruteforce_enabled');
    delete_option('qtr_whitelist_ips');
    delete_option('qtr_manually_blocked_ips');
    delete_option('qtr_trusted_proxy_ips');
    
    // External scan cache
    delete_option('qtr_escan_status');
    delete_option('qtr_last_external_scan_response');  // Legacy option
    
    // Login activity tracking
    delete_option('qtr_login_activity');
    
    // Admin users monitoring
    delete_option('qtr_admin_users_last_check');
    delete_option('qtr_admin_check_debounce');
    
    // ===========================
    // 3. DELETE ALL TRANSIENTS (Rate Limiting, Caching, etc.)
    // ===========================
    
    // Brute force IP locks - delete all qtr_bf_lock_ip_* transients
    $lock_ips = $wpdb->get_results(
        "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '_transient_qtr_bf_lock_ip_%'"
    );
    foreach ($lock_ips as $option) {
        // Extract transient key by removing '_transient_' prefix
        $transient_key = substr($option->option_name, 11); // Remove '_transient_' prefix
        delete_transient($transient_key);
    }
    
    // Brute force combo (IP+username) locks - delete all qtr_bf_lock_combo_* transients
    $lock_combos = $wpdb->get_results(
        "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '_transient_qtr_bf_lock_combo_%'"
    );
    foreach ($lock_combos as $option) {
        $transient_key = substr($option->option_name, 11);
        delete_transient($transient_key);
    }
    
    // Brute force unlock tokens - delete all qtr_bf_unlock_* transients
    $unlock_tokens = $wpdb->get_results(
        "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '_transient_qtr_bf_unlock_%'"
    );
    foreach ($unlock_tokens as $option) {
        $transient_key = substr($option->option_name, 11);
        delete_transient($transient_key);
    }
    
    // Alarm email throttling transients
    delete_transient('qtr_alarm_emails_sent_hour');
    delete_transient('qtr_suppressed_alarms_today');
    
    // Admin user change notification transients - delete all qtr_added_admin_* and qtr_deleted_admin_*
    $admin_transients = $wpdb->get_results(
        "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '_transient_qtr_added_admin_%' OR option_name LIKE '_transient_qtr_deleted_admin_%'"
    );
    foreach ($admin_transients as $option) {
        $transient_key = substr($option->option_name, 11);
        delete_transient($transient_key);
    }
    
    // Bot protection transients (if any)
    $bot_transients = $wpdb->get_results(
        "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '_transient_qtr_bot_%'"
    );
    foreach ($bot_transients as $option) {
        $transient_key = substr($option->option_name, 11);
        delete_transient($transient_key);
    }
    
    // ===========================
    // 5. DELETE LOGIN SECURITY TRANSIENTS
    // ===========================
    
    // Clean up login failure counters and alarm guards
    // This includes hourly counter buckets and per-type alarm guards
    CQtrLoginSecurity::cleanup_transients();
    
    // ===========================
    // 6. CLEAR WP_CACHE GROUP
    // ===========================
    
    // Clear the wp_cache 'qtr_bf' group (used for brute force rate limiting)
    wp_cache_flush();
    
    // ===========================
    // 7. UNSCHEDULE ALL CRON JOBS
    // ===========================
    
    // Remove internal scan cron hook
    wp_clear_scheduled_hook('qtr_internal_scan_cron_hook');
    
    // Remove heuristic internal scan cron hook
    wp_clear_scheduled_hook('qtr_heur_internal_scan_cron_hook');
    
    // Remove bot protection cleanup cron hook
    wp_clear_scheduled_hook('qtr_cleanup_expired_bot_blocks');
    
    // Remove bot protection database transients cleanup cron hook
    wp_clear_scheduled_hook('qtr_bot_cleanup_database');
    
    // Remove admin users monitoring cron hook (if scheduled)
    wp_clear_scheduled_hook('qtr_admin_monitor_cron');
    
    // Remove hourly login check cron hook
    wp_clear_scheduled_hook('qtr_hourly_login_check');
    
    // ===========================
    // 8. REMOVE CUSTOM CRON INTERVAL
    // when all scheduled hooks using them are cleared.
    // The 'qtr_fifteen_minutes' interval registered in add_filter('cron_schedules')
    // doesn't persist in database, so no action needed here.
}

/**
 * Update routine to perform database cleanup and to ensure that newly
 * introduced settings and defaults are enforced.
 */
function on_qtr_scanner_admin_init() 
{
    on_qtr_scanner_activation();
    // check if UUID option exists
    $uuid = get_option( 'qtr_scanner_uuid', '' );
    //if not, generate and save it
    if( empty( $uuid ) ){
        $uuid = wp_generate_uuid4();
        update_option( 'qtr_scanner_uuid', $uuid );
    }
}

/* 
 * admin_init is triggered before any other hook when a user access the admin area 
 */
add_action( 'admin_init', 'on_qtr_scanner_admin_init' );

/* ?????? */
function qtr_wm_scanner_plugin_actions( $links, $file ) 
{
    if( $file == 'quttera/quttera_wm_scanner.php' && function_exists( "admin_url" ) ) 
    {
        $settings_link = '<a href="' . admin_url( 'tools.php?page=quttera_wm_scanner' ) . '">' . __('Scanner Settings') . '</a>';
        array_unshift( $links, $settings_link ); // before other links
    }
    return $links;
}


add_filter( 'plugin_action_links', 'qtr_wm_scanner_plugin_actions', 10, 2 );


/**
 * @brief       sends request to remote scanner 
 * @param[in]   remote_url - URL query 
 * @return      on success returns retireved Json, on failure empty string
 */
function qtr_scanner_query($remote_url)
{
    if( function_exists('curl_init') )
    {
       /* curl library loaded */ 
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $remote_url );
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_HEADER, false);
        $data = curl_exec($curl);
        curl_close($curl);
        return $data;
    }else{
        return file_get_contents( $remote_url );
    }
}

/* end of file */
?>
