<?php

/*
Plugin Name: Security by CleanTalk
Plugin URI: https://wordpress.org/plugins/security-malware-firewall/
Description: Security & Malware scan by CleanTalk to protect your website from online threats and viruses. IP/Country FireWall, Web application FireWall. Detailed stats and logs to have full control.
Author: CleanTalk Security
Version: 2.119.1-dev
Author URI: https://cleantalk.org
Text Domain: security-malware-firewall
Domain Path: /i18n
*/

use CleantalkSP\SpbctWP\Counters\SecurityCounter;
use CleantalkSP\SpbctWP\DB;
use CleantalkSP\SpbctWP\Firewall;
use CleantalkSP\SpbctWP\Firewall\BFP;
use CleantalkSP\SpbctWP\Firewall\FW;
use CleantalkSP\SpbctWP\Firewall\TC;
use CleantalkSP\SpbctWP\Firewall\WAF;
use CleantalkSP\SpbctWP\G2FA\GoogleAuthenticator;
use CleantalkSP\SpbctWP\Cron as SpbcCron;
use CleantalkSP\SpbctWP\RemoteCalls as SpbcRemoteCalls;
use CleantalkSP\SpbctWP\RenameLoginPage;
use CleantalkSP\SpbctWP\Sanitize;
use CleantalkSP\SpbctWP\Scanner\CureLog\CureLog;
use CleantalkSP\SpbctWP\Scanner\Stages\SignatureAnalysis\SignatureAnalysisFacade;
use CleantalkSP\SpbctWP\State;
use CleantalkSP\SpbctWP\Transaction;
use CleantalkSP\SpbctWP\Variables\Cookie;
use CleantalkSP\Updater\Updater;
use CleantalkSP\Variables\Get;
use CleantalkSP\Variables\Post;
use CleantalkSP\Variables\Server;
use CleantalkSP\SpbctWP\Helpers\IP;
use CleantalkSP\SpbctWP\Helpers\HTTP;
use CleantalkSP\SpbctWP\API as SpbcAPI;

// Prevent direct call
if ( ! defined('WPINC') ) {
    die('Not allowed!');
}

// Getting version form main file (look above)
$plugin_info           = get_file_data(__FILE__, array('Version' => 'Version', 'Name' => 'Plugin Name', 'Description' => 'Description'));
$plugin_version__agent = $plugin_info['Version'];
// Converts xxx.xxx.xx-dev to xxx.xxx.2xx
// And xxx.xxx.xx-fix to xxx.xxx.1xx
if ( preg_match('@^(\d+)\.(\d+)\.(\d{1,2})-(dev|fix)$@', $plugin_version__agent, $m) ) {
    $plugin_version__agent = $m[1] . '.' . $m[2] . '.' . ($m[4] === 'dev' ? '2' : '1') . str_pad($m[3], 2, '0', STR_PAD_LEFT);
}

// Common params
define('SPBC_NAME', $plugin_info['Name']);
define('SPBC_VERSION', $plugin_info['Version']);
define('SPBC_AGENT', 'wordpress-security-' . $plugin_version__agent);
define('SPBC_USER_AGENT', 'Cleantalk-Security-Wordpress-Plugin/' . $plugin_info['Version']);
define('SPBC_API_URL', 'https://api.cleantalk.org');        //Api URL
define('SPBC_PLUGIN_DIR', dirname(__FILE__) . DIRECTORY_SEPARATOR); //System path. Plugin root folder with '/'.
define('SPBC_PLUGIN_BASE_NAME', plugin_basename(__FILE__)); //Plugin base name.
define(
    'SPBC_PATH',
    is_ssl()
        ? preg_replace('/^http(s)?/', 'https', plugins_url('', __FILE__))
        : plugins_url('', __FILE__)
); //HTTP(S)? path.   Plugin root folder without '/'.

// SSL Serttificate path
if ( ! defined('CLEANTALK_CASERT_PATH') ) {
    define('CLEANTALK_CASERT_PATH', file_exists(ABSPATH . WPINC . '/certificates/ca-bundle.crt') ? ABSPATH . WPINC . '/certificates/ca-bundle.crt' : '');
}

// Options names
define('SPBC_LOGIN_ATTEMPTS', 'spbc_login_attempts');   //Option name with login attempts.
define('SPBC_DATA', 'spbc_data');             //Option name with different plugin data.
define('SPBC_SETTINGS', 'spbc_settings');         //Option name with plugin settings.
define('SPBC_NETWORK_SETTINGS', 'spbc_network_settings'); //Option name with plugin network settings.
define('SPBC_CRON', 'spbc_cron');             //Option name with scheduled tasks.
define('SPBC_ERRORS', 'spbc_errors');           //Option name with errors.
define('SPBC_DEBUG', 'spbc_debug');            //Option name with a debug data. Empty by default.
define('SPBC_PLUGINS', 'spbc_plugins');          //Option name with a debug data. Empty by default.
define('SPBC_THEMES', 'spbc_themes');           //Option name with a debug data. Empty by default.

// Different params
define('SPBC_REMOTE_CALL_SLEEP', 10); //Minimum time between remote call
define('SPBC_LAST_ACTIONS_TO_VIEW', 20); //Nubmer of last actions to show in plugin settings page.

// Auth params
define('SPBC_AUTH__MAX_FAILS', '5');    // Limit of failed attempts before restrict access to backend.
define('SPBC_AUTH__CALC_PERIOD', '3600'); // Time interval to count failed attempts.
define('SPBC_SLEEP_USUAL', '3');    // Give a delay before attempts to login.
define('SPBC_SLEEP_LONG', '10');   // Give a delay before attempts to login.

// DataBase params
global $wpdb;

define('SPBC_TBL_FIREWALL_DATA', $wpdb->base_prefix . 'spbc_firewall_data'); // Legacy
define('SPBC_TBL_FIREWALL_DATA__IPS', $wpdb->prefix . 'spbc_firewall__personal_ips'); // Table with firewall IPS
define('SPBC_TBL_FIREWALL_DATA__COUNTRIES', $wpdb->prefix . 'spbc_firewall__personal_countries'); // Table with firewall countries.
define('SPBC_TBL_FIREWALL_LOG', $wpdb->prefix . 'spbc_firewall_logs'); // Table with firewall logs.
define('SPBC_TBL_SESSIONS', $wpdb->prefix . 'spbc_sessions'); // Alternative sessions table

define('SPBC_TBL_MONITORING_USERS', $wpdb->prefix . 'spbc_monitoring_users'); // Table with users monitoring data
define('SPBC_TBL_SECURITY_LOG', $wpdb->prefix . 'spbc_auth_logs');       // Table with security logs.
define('SPBC_TBL_TC_LOG', $wpdb->prefix . 'spbc_traffic_control_logs'); // Table with traffic control logs.
define('SPBC_TBL_BFP_BLOCKED', $wpdb->prefix . 'spbc_bfp_blocked'); // Table with traffic control logs.
define('SPBC_TBL_SCAN_FILES', $wpdb->base_prefix . 'spbc_scan_results');    // Table with scan results.
define('SPBC_TBL_SCAN_RESULTS_LOG', $wpdb->base_prefix . 'spbc_scan_results_log');    // Table with log of scan results.
define('SPBC_TBL_SCAN_LINKS', $wpdb->prefix . 'spbc_scan_links_logs'); // For links scanner. Results of scan.
define('SPBC_TBL_SCAN_FRONTEND', $wpdb->base_prefix . 'spbc_scan_frontend');   // For frontend scanner. Results of scan.
define('SPBC_TBL_SCAN_SIGNATURES', $wpdb->base_prefix . 'spbc_scan_signatures'); // For malware signatures.
define('SPBC_TBL_BACKUPED_FILES', $wpdb->prefix . 'spbc_backuped_files');  // Contains backuped files
define('SPBC_TBL_BACKUPS', $wpdb->prefix . 'spbc_backups');         // Contains backup info.
define('SPBC_TBL_IMPORTANT_FILES', $wpdb->prefix . 'spbc_important_files'); // Contains important files for monitoring.
define('SPBC_TBL_IMPORTANT_FILES_SNAPSHOTS', $wpdb->prefix . 'spbc_important_file_snapshots'); // Contains state of important files.
define('SPBC_TBL_CURE_LOG', $wpdb->base_prefix . 'spbc_cure_log');    // Table with scan results.
define('SPBC_SELECT_LIMIT', 1500);                 // Select limit for logs.
define('SPBC_WRITE_LIMIT', 5000);                 // Write limit for firewall data.

// Multisite
define('SPBC_WPMS', (is_multisite() ? true : false)); // WMPS is enabled

// Scanner params for background scanning
define('SPBC_SCAN_SURFACE_AMOUNT', 1000); // Surface scan amount for 1 iteration
define('SPBC_SCAN_SURFACE_PERIOD', 30);   // Surface scan call period
define('SPBC_SCAN_MODIFIED_AMOUNT', 5);    // Deep scan amount for 1 iteration
define('SPBC_SCAN_SIGNATURE_AMOUNT', 20);    // Deep scan amount for 1 iteration
define('SPBC_SCAN_MODIFIED_PERIOD', 30);   // Deep scan call period
define('SPBC_SCAN_LINKS_AMOUNT', 10);      // Links scan amount for 1 iteration
define('SPBC_SCAN_FRONTEND_AMOUNT', 10);      // Links scan amount for 1 iteration
define('SPBC_SCAN_LINKS_PERIOD', 30);      // Links scan call period
define('SPBC_PSCAN_UPDATE_FILES_STATUS_PERIOD', 300);      // Check cloud analysis files status period

// Two factor auth params.
define('SPBC_2FA_KEY_TTL', 600);      // 2fa key lifetime in seconds

// brief data limits
define('SPBC_BRIEF_DATA_DAYS_LIMIT', 7);      // how many days will be logs looked for
define('SPBC_BRIEF_DATA_ACTIONS_LIMIT', 10);      // how many actions will be logs looked for


require_once SPBC_PLUGIN_DIR . 'lib/spbc-php-patch.php'; // PHP functions patches
require_once SPBC_PLUGIN_DIR . 'lib/autoloader.php'; // Autoloader

require_once SPBC_PLUGIN_DIR . 'inc/spbc-backups.php';
require_once SPBC_PLUGIN_DIR . 'inc/fw-update.php';

// Misc libs
require_once SPBC_PLUGIN_DIR . 'inc/spbc-tools.php';   // Different helper functions
require_once SPBC_PLUGIN_DIR . 'inc/spbc-pluggable.php'; // WordPress functions
require_once SPBC_PLUGIN_DIR . 'inc/spbc-scanner.php';

// ArrayObject with settings and other global variables
global $spbc;
$spbc = new State(
    'spbc',
    array(
        'settings',
        'data',
        'remote_calls',
        'debug',
        'installing',
        'errors',
        'fw_stats'
    ),
    is_multisite(),
    is_main_site()
);
// Update plugin's data to current version
spbc_update_actions();

// Remote calls

if ( SpbcRemoteCalls::check() ) {
    try {
        if ( Get::get('spbc_remote_call_action') === 'run_service_template_get' ) {
            require_once(SPBC_PLUGIN_DIR . 'inc/spbc-settings.php');
        }
        $rc = new SpbcRemoteCalls($spbc);
        $rc->process();
    } catch ( Exception $e ) {
        die(json_encode(array('ERROR:' => $e->getMessage())));
    }
}

//First start
if ( $spbc->settings && $spbc->key_is_ok) {
    // FireWall
    if (
        ! $spbc->fw_stats['is_on_maintenance']
        && $spbc->moderate                                                       // Plugin is enabled
        && isset($spbc->fw_stats['last_updated'], $spbc->fw_stats['entries'])  // Plugin's FW base is updated
        && ! CleantalkSP\SpbctWP\Firewall::isException()
        && (( ! is_admin()                     // Not admin area
              && ! defined('DOING_AJAX')  // Pass AJAX
              && ! spbc_wp_doing_cron()           // Pass WP cron tasks
              && ! \CleantalkSP\Variables\Server::inUri('/favicon.ico')  // Exclude favicon.ico requests from the check
              && ! spbc_mailpoet_doing_cron())
            || ! empty($_FILES) // Or file downloads
        )
    ) {
        spbc_firewall__check();
    }
} elseif ( isset($spbc->errors) && ! isset($spbc->errors['apikey']) ) {
    if ($spbc->settings['spbc_key'] === '') {
        $text = __('Access key is empty.', 'security-malware-firewall');
    } else {
        $text = __('Unknown access key.', 'security-malware-firewall');
    }
    $spbc->error_add('apikey', $text);
}

// Disable XMLRPC if setting is enabled
if ( $spbc->settings['wp__disable_xmlrpc'] ) {
    add_filter('xmlrpc_enabled', '__return_false');
}

// Disable WordPress REST API for non-authenticated
if ( $spbc->settings['wp__disable_rest_api_for_non_authenticated'] ) {
    add_filter(
        'rest_authentication_errors',
        function ($result) {
            if ( empty($result) && ! is_user_logged_in() ) {
                return new WP_Error(
                    'rest_not_logged_in',
                    'You are not currently logged in.',
                    array('status' => 401)
                );
            }
            return $result;
        }
    );
}

if ( ! is_admin() && $spbc->settings['misc__prevent_logins_collecting'] ) {
    add_filter('redirect_canonical', 'spbc_redirect_to_honeypot_login', 1, 2);
}

function spbc_redirect_to_honeypot_login($redirect, $request)
{
    if ( preg_match('/author=\d+/i', $request) ) {
        add_filter('author_link', 'spbc_change_author_name', 10, 3);
    }

    return $redirect;
}

function spbc_change_author_name($link, $_author_id, $_author_nicename)
{
    $link = preg_replace('@(.*?)([\w-]+\/)$@', '$1honeypot_login_' . microtime(true), $link);
    wp_redirect($link);
    die();
}

if ( $spbc->settings['2fa__enable'] ) {
    // Profile page hook
    add_action('show_user_profile', 'spbc_2fa__SelfUserProfileEdit', 1);
    add_action('edit_user_profile', 'spbc_2fa__SelfUserProfileEdit', 1);
    // G2FA actions
    add_action('wp_ajax_spbc_get_google_qr_code', 'spbc_2fa__GetGoogleQrCode');
    add_action('wp_ajax_spbc_check_google_code', 'spbc_2fa__CheckGoogleCode');
    add_action('wp_ajax_spbc_disable_google_2fa', 'spbc_2fa__DisableGoogle2fa');
}

if ( $spbc->settings['monitoring__users'] ) {
    add_action('admin_head', array( '\CleantalkSP\Monitoring\User', 'record' ));
    add_action('wp_head', array( '\CleantalkSP\Monitoring\User', 'record' ));
}

if ( $spbc->settings['login_page_rename__enabled'] ) {
    new RenameLoginPage(
        $spbc->settings['login_page_rename__name'],
        $spbc->settings['login_page_rename__redirect']
    );
}

// Set cookie to detect any logged in user
if ( spbc_is_user_logged_in() && ! empty($spbc->settings['data__set_cookies']) && ! Cookie::get('spbc_is_logged_in') ) {
    Cookie::set('spbc_is_logged_in', md5($spbc->data['salt'] . parse_url(get_option('home'), PHP_URL_HOST)), time() + 86400 * 365, '/');
}

// Logged hooks
register_activation_hook(__FILE__, 'spbc_activation');
register_deactivation_hook(__FILE__, 'spbc_deactivation');

// Hook for newly added blog
if ( version_compare(get_bloginfo('version'), '5.1.0', '<') ) {
    add_action('wpmu_new_blog', 'spbc_activation__new_blog', 10, 6);
} else {
    add_action('wp_insert_site', 'spbc_activation__new_blog_5_1', 10, 1);
}

// Auth hooks
add_filter('authenticate', 'spbc_authenticate', 20, 3); // Hooks for authentificate
add_action('login_form', 'spbc_2fa__show_field', 10);
add_action('login_form_login', 'spbc_2fa__authenticate', 1);     // Authenticate with Code
add_action('login_errors', 'spbc_fix_error_messages', 99999); // Filters error message
add_action('after_password_reset', 'spbc_2fa__Google2fa_replace_meta', 10, 1);   // Disable G2FA after password resetting
add_action('wp_logout', 'spbc_wp_logout', 1);     // Hooks for authentificate
add_action('login_header', 'spbc_login_form_notification', 1);     // Hook for registration form notification
add_action('plugins_loaded', 'spbc_plugin_loaded', 1);     // Main hook

// Posts hooks
add_action('wp_insert_post', 'spbc_update_postmeta_links', 10, 3);
add_action('wp_insert_comment', 'spbc_update_postmeta_links__by_comment', 10, 2);

// Set headers
add_action('init', 'spbc_set_headers');

if ( $spbc->settings['spbc_trusted_and_affiliate__footer'] === '1' ) {
    add_action('wp_enqueue_scripts', 'spbc_attach_public_css');
    add_action('wp_footer', 'spbc_hook__wp_footer_trusted_text', 998);
}

// Cron
global $spbc_cron; // Letting know functions that they are running under spbc_cron
$spbc_cron = new SpbcCron();
! SpbcRemoteCalls::check() && $spbc_cron->execute();
unset($spbc_cron);

if ( is_admin() || is_network_admin() ) {
    // Async loading for JavaScript
    add_filter('script_loader_tag', 'spbc_admin_add_script_attribute', 10, 3);

    include_once SPBC_PLUGIN_DIR . 'inc/spbc-admin.php';
    include_once SPBC_PLUGIN_DIR . 'templates/spbc_settings_main.php'; // Templates for settings pgae

    add_action('admin_init', 'spbc_activation__redirect', 1); // Redirect after activation
    add_action('admin_init', 'spbc_admin_init', 1, 1);       // Main admin hook
    add_action('admin_menu', 'spbc_admin_add_page');         // Admin pages
    add_action('network_admin_menu', 'spbc_admin_add_page');         // Network admin pages
    add_action('admin_enqueue_scripts', 'spbc_enqueue_scripts');        // Scripts

    // Getting dashboard widget statistics by click
    if ( (int) Post::get('spbc_brief_refresh') === 1 ) {
        spbc_set_brief_data();
    }

    if ( $spbc->settings['wp__dashboard_widget__show'] ) {
        add_action('wp_dashboard_setup', 'spbc_widget_scripts_init');
        add_action('wp_dashboard_setup', 'spbc_dashboard_statistics_widget');
    }


    add_action('admin_init', function () {
        global $spbc;
        $admin_banners_handler = new \CleantalkSP\SpbctWP\AdminBannersModule\AdminBannersHandler($spbc);
        $admin_banners_handler->handle();
    });

    // Customize row with the plugin on plugins list page.
    if ( ( isset($pagenow) && $pagenow === 'plugins.php' ) || ( isset($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], 'plugins.php') !== false ) ) {
        add_filter('plugin_action_links_' . SPBC_PLUGIN_BASE_NAME, 'spbc_plugin_action_links', 10, 2);
        add_filter('network_admin_plugin_action_links_' . SPBC_PLUGIN_BASE_NAME, 'spbc_plugin_action_links', 10, 2);
        add_filter('all_plugins', 'spbc_admin__change_plugin_description');
        add_filter('plugin_row_meta', 'spbc_plugin_links_meta', 10, 2);
    }

    // Public scripts
} else {
    $spbc->public_scripts_attached = false;

    // Alternative cookies JS script
    add_action('wp_enqueue_scripts', 'spbc_enqueue_scripts__public');
    add_action('login_enqueue_scripts', 'spbc_enqueue_scripts__public');
}

/**
 * Enqueue JS scripts on public page
 */
function spbc_enqueue_scripts__public()
{
    global $spbc;

    if (spbc_is_amp_request()) {
        return;
    }

    if ( ! $spbc->public_scripts_attached && $spbc->settings['data__set_cookies'] ) {
        wp_enqueue_script('spbc_cookie', SPBC_PATH . '/js/spbc-cookie.min.js', array( 'jquery' ), SPBC_VERSION, false /*in header*/);
        wp_localize_script(
            'spbc_cookie',
            'spbcPublic',
            array(
                '_ajax_nonce'                          => wp_create_nonce('ct_secret_stuff'),
                '_rest_nonce'                          => wp_create_nonce('wp_rest'),
                '_ajax_url'                            => admin_url('admin-ajax.php', 'relative'),
                '_rest_url'                            => esc_url(get_rest_url()),
                //            '_apbct_ajax_url'                      => APBCT_URL_PATH . '/lib/Cleantalk/ApbctWP/Ajax.php',
                'data__set_cookies'                    => $spbc->settings['data__set_cookies'],
                'data__set_cookies__alt_sessions_type' => $spbc->settings['data__set_cookies__alt_sessions_type'],
            )
        );

        $spbc->public_scripts_attached = true;
    }
}

function spbc_set_headers()
{
    global $spbc;

    if ( ! headers_sent() ) {
        // Additional headers
        if ( $spbc->settings['data__additional_headers'] ) {
            header('X-XSS-Protection: 1; mode=block');
            header('X-Content-Type-Options: nosniff');
            header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
            header('Referrer-Policy: strict-origin-when-cross-origin');
        }

        // Forbid to show in iframes
        if ( $spbc->settings['misc__forbid_to_show_in_iframes'] ) {
            header('X-Frame-Options: sameorigin', false);
        }
    }
}

function spbc_update_actions()
{
    global $spbc;

    //Update logic
    $current_version = $spbc->data['plugin_version'];

    if ( $current_version != SPBC_VERSION ) {
        // Perform a transaction and exit transaction ID isn't match
        if ( ! Transaction::get('updater', 5)->perform() ) {
            return;
        }

        Updater::runUpdateScripts($current_version, SPBC_VERSION);

        $spbc->data['plugin_version'] = SPBC_VERSION;
        $spbc->save('data');

        Transaction::get('updater')->clearTransactionTimer();
    }
}

function spbc_firewall__check()
{
    global $spbc, $apbct;

    // Skip the check
    // Set skip test cookie
    if ( ! empty($_GET['access']) ) {
        $apbct_settings = get_option('cleantalk_settings');
        $apbct_key      = ! empty($apbct_settings['apikey']) ? $apbct_settings['apikey'] : false;
        if ( ( $_GET['access'] === $spbc->settings['spbc_key'] || ( $apbct_key !== false && $_GET['access'] === $apbct_key ) ) ) {
            Cookie::set('spbc_firewall_pass_key', md5($_SERVER['REMOTE_ADDR'] . $spbc->settings['spbc_key']), time() + 1200, '/');
            Cookie::set('ct_sfw_pass_key', md5($_SERVER['REMOTE_ADDR'] . $apbct_key), time() + 1200, '/');

            return;
        }
    }

    // Turn off the SpamFireWall if Remote Call is in progress
    if ( ( ! empty($apbct) && $apbct->rc_running ) || $spbc->rc_running ) {
        return;
    }

    $firewall = new Firewall();

    if ( (int) $spbc->settings['secfw__enabled'] ) {
        $firewall->loadFwModule(
            new FW(
                array(
                    'data_table__common'             => SPBC_TBL_FIREWALL_DATA,
                    'data_table__personal'           => SPBC_TBL_FIREWALL_DATA__IPS,
                    'data_table__personal_countries' => SPBC_TBL_FIREWALL_DATA__COUNTRIES,
                    'log_table'                      => SPBC_TBL_FIREWALL_LOG,
                    'state'                          => $spbc,
                    'api_key'                        => $spbc->api_key,
                    'cookie_domain'                  => parse_url(get_option('home'), PHP_URL_HOST),
                    'data__set_cookies'              => $spbc->settings['data__set_cookies'],
                )
            )
        );
    }

    if ( $spbc->settings['traffic_control__enabled'] && ! is_admin() ) {
        $firewall->loadFwModule(
            new TC(
                array(
                'data_table'   => SPBC_TBL_FIREWALL_DATA,
                'log_table'    => SPBC_TBL_TC_LOG,
                'state'        => $spbc,
                'api_key'      => $spbc->api_key,
                'is_logged_in' => Cookie::get('spbc_is_logged_in') === md5($spbc->data['salt'] . parse_url(get_option('home'), PHP_URL_HOST)),
                'user_is_admin' => spbc_user_is_admin(),
                'store_interval' => $spbc->settings['traffic_control__autoblock_timeframe'],
                'tc_limit'     => $spbc->settings['traffic_control__autoblock_amount'],
                'block_period' => $spbc->settings['traffic_control__autoblock_period'],
                )
            )
        );
    }

    if ( $spbc->settings['waf__enabled'] ) {
        $firewall->loadFwModule(
            new WAF(
                array(
                'api_key'                           => $spbc->api_key,
                'log_table'                         => SPBC_TBL_TC_LOG,
                'state'                             => $spbc,
                'waf__xss_check'                    => $spbc->settings['waf__xss_check'],
                'waf__sql_check'                    => $spbc->settings['waf__sql_check'],
                'waf__file_check'                   => $spbc->settings['waf__file_check'],
                'waf__file_check__uploaded_plugins' => $spbc->settings['waf__file_check__uploaded_plugins'],
                'waf__exploit_check'                => $spbc->settings['waf__exploit_check'],
                )
            )
        );
    }

    //todo This rewrite could break permalinks, need to implement new logic
    if ( class_exists('Poppyz_Core') ) { //fix poppyz plugin early start conflict
        $GLOBALS['wp_rewrite'] = new WP_Rewrite(); // Fix for early load WP_Rewrite
    }

    $login_url = wp_login_url();
    if ( $spbc->settings['login_page_rename__enabled'] ) {
        //todo This rewrite could break permalinks, need to implement new logic
        $GLOBALS['wp_rewrite'] = new WP_Rewrite(); // Fix for early load WP_Rewrite
        $login_url = RenameLoginPage::getURL($spbc->settings['login_page_rename__name']);
    }

    $firewall->loadFwModule(
        new BFP(
            array(
            'api_key'       => $spbc->api_key,
            'state'         => $spbc,
            'is_login_page' => strpos(trim(Server::getURL(), '/'), trim($login_url, '/')) === 0,
            'is_logged_in'  => Cookie::get('spbc_is_logged_in') === md5($spbc->data['salt'] . parse_url(get_option('home'), PHP_URL_HOST)),
            'bf_limit'      => $spbc->settings['bfp__allowed_wrong_auths'],
            'block_period'  => $spbc->settings['bfp__block_period__5_fails'],
            'count_period'  => $spbc->settings['bfp__count_interval'], // Counting login attempts in this interval
            )
        )
    );

    //Pass the check if cookie is set.
    foreach ( $firewall->ip_array as $spbc_cur_ip ) {
        if ( Cookie::get('spbc_firewall_pass_key') == md5($spbc_cur_ip . $spbc->settings['spbc_key']) ) {
            return;
        }
    }

    $firewall->run();
}

/**
 * Plugin activation
 *
 * @param $network
 * @param $redirect
 *
 * @return void
 * @throws Exception
 */
function spbc_activation($network, $redirect = true)
{
    global $wpdb, $spbc;

    if ( current_action() !== 'wp_insert_site' ) {
        delete_option('spbc_deactivation_in_process');
    }

    $tables_analyzer = new \CleantalkSP\SpbctWP\DB\TablesAnalyzer();
    foreach ( $tables_analyzer->getNotExistingTables() as $not_existing_table ) {
        $db_tables_creator = new \CleantalkSP\SpbctWP\DB\TablesCreator();
        $db_tables_creator->createTable($not_existing_table);
    }

    // Activation for network
    if ( is_multisite() && $network ) {
        // For all blogs
        SpbcCron::addTask('scanner_update_signatures', 'spbc_scanner__signatures_update', 86400, time() + 100);
        SpbcCron::addTask('send_php_logs', 'spbc_PHP_logs__send', 3600, time() + 300);

        if ( empty($spbc->errors['configuration']) ) {
            $scanner_launch_data = spbc_get_custom_scanner_launch_data(true);
            SpbcCron::addTask(
                'scanner__launch',
                'spbc_scanner__launch',
                $scanner_launch_data['period'],
                $scanner_launch_data['start_time']
            );
        }

        // MU-Plugin
        if ( ! spbc_mu_plugin__install() ) {
            spbc_log('Couldn\'t install Must-Use Plugin. This\'s not critical but it could help plugin to work faster.');
        }

        // For each blog
        $initial_blog = get_current_blog_id();
        $blogs        = array_keys($wpdb->get_results('SELECT blog_id FROM ' . $wpdb->blogs, OBJECT_K));
        foreach ( $blogs as $blog ) {
            switch_to_blog($blog);
            SpbcCron::addTask('send_logs', 'spbc_send_logs', 3600, time() + 1800);
            SpbcCron::addTask('send_report', 'spbc_send_daily_report', 86400, time() + 43200);
            SpbcCron::addTask('firewall_update', 'spbc_security_firewall_update__init', 86400);
            SpbcCron::addTask('send_firewall_logs', 'spbc_send_firewall_logs', 3600, time() + 1800);
            SpbcCron::addTask('access_key_notices', 'spbc_access_key_notices', 3600, time() + 3500);
            SpbcCron::addTask('service_get', 'spbct_perform_service_get', 86400, time() + 3500);
        }
        switch_to_blog($initial_blog);

        // Activation for blog
    } elseif ( is_multisite() ) {
        //Cron jobs
        SpbcCron::addTask('send_logs', 'spbc_send_logs', 3600, time() + 1800);
        SpbcCron::addTask('send_report', 'spbc_send_daily_report', 86400, time() + 43200);
        SpbcCron::addTask('firewall_update', 'spbc_security_firewall_update__init', 86400);
        SpbcCron::addTask('send_firewall_logs', 'spbc_send_firewall_logs', 3600, time() + 1800);
        SpbcCron::addTask('access_key_notices', 'spbc_access_key_notices', 3600, time() + 3500);
        SpbcCron::addTask('service_get', 'spbct_perform_service_get', 86400, time() + 3500);
        SpbcCron::addTask('security_log_clear', 'spbc_security_log_clear', 86400, time() + 43200);
        SpbcCron::addTask('get_brief_data', 'spbc_set_brief_data', 86400, time() + 1800);
    } elseif ( ! is_multisite() ) {
        // Cron
        SpbcCron::addTask('send_logs', 'spbc_send_logs', 3600, time() + 1800);
        SpbcCron::addTask('send_report', 'spbc_send_daily_report', 86400, time() + 43200);
        SpbcCron::addTask('firewall_update', 'spbc_security_firewall_update__init', 86400);
        SpbcCron::addTask('send_firewall_logs', 'spbc_send_firewall_logs', 3600, time() + 1800);
        SpbcCron::addTask('access_key_notices', 'spbc_access_key_notices', 3600, time() + 3500);
        SpbcCron::addTask('scanner_update_signatures', 'spbc_scanner__signatures_update', 86400, time() + 100);
        SpbcCron::addTask('send_php_logs', 'spbc_PHP_logs__send', 3600, time() + 300);
        SpbcCron::addTask('service_get', 'spbct_perform_service_get', 86400, time() + 3500);
        SpbcCron::addTask('security_log_clear', 'spbc_security_log_clear', 86400, time() + 43200);
        SpbcCron::addTask('get_brief_data', 'spbc_set_brief_data', 86400, time() + 1800);

        if ( empty($spbc->errors['configuration']) ) {
            $scanner_launch_data = spbc_get_custom_scanner_launch_data(true);
            SpbcCron::addTask(
                'scanner__launch',
                'spbc_scanner__launch',
                $scanner_launch_data['period'],
                $scanner_launch_data['start_time']
            );
        }

        // MU-Plugin
        if ( ! spbc_mu_plugin__install() ) {
            spbc_log('Couldn\'t install Must-Use Plugin. This\'s not critical but it could help plugin to work faster.');
        }
    }

    // Redirect
    if ( $redirect ) {
        add_option('spbc_activation__redirect', $spbc->settings_link);
    }

    if ( $spbc->api_key || ( ! is_main_site() && $spbc->network_settings['ms__work_mode'] != 2 ) ) {
        return;
    }
    /**
     * Filters a getting API key flag
     *
     * @param bool Set true if you want to get key automatically after activation the plugin
     */
    if ( apply_filters('spbc_is_get_api_key', false) ) {
        spbc_get_key_auto(true);
    }
}

/**
 * Function for redirect to settings
 *
 * @return void
 */
function spbc_activation__redirect()
{
    $settings_rediect = get_option('spbc_activation__redirect', false);

    if ( $settings_rediect && ! isset($_GET['activate-multi']) ) {
        delete_option('spbc_activation__redirect');
        wp_redirect($settings_rediect);
    }
}

function spbc_activation__new_blog($blog_id, $_user_id, $_domain, $_path, $_site_id, $_meta)
{
    global $spbc;

    if ( spbc_is_plugin_active_for_network('security-malware-firewall/security-malware-firewall.php') ) {
        $spbc = State::resetState();
        switch_to_blog($blog_id);

        spbc_activation(false, false);

        if ( ! $spbc->is_mainsite && ! $spbc->ms__work_mode == 1 ) {
            spbc_set_api_key();
        }

        restore_current_blog();
        State::restoreState();
    }
}

/**
 * Wrapper for spbc_activation__new_blog()
 *
 * @param $new_site
 */
function spbc_activation__new_blog_5_1($new_site)
{
    spbc_activation__new_blog($new_site->blog_id, null, null, null, null, null);
}

/**
 * A code during plugin deactivation.
 *
 * @param $network
 *
 * @return void
 */
function spbc_deactivation($network)
{
    global $spbc, $wpdb;

    Cookie::set('spbc_is_logged_in', '0', time() - 30, '/');
    Cookie::set('spbc_admin_logged_in', '0', time() - 30, '/');

    unregister_setting(SPBC_SETTINGS, SPBC_SETTINGS);

    // Deactivation for network
    if ( is_multisite() && $network ) {
        update_option('spbc_deactivation_in_process', true, false);

        spbc_mu_plugin__uninstall();

        if ( ! empty($spbc->settings['misc__complete_deactivation']) ) {
            $initial_blog = get_current_blog_id();

            delete_site_option(SPBC_NETWORK_SETTINGS);

            $blogs = array_keys($wpdb->get_results('SELECT blog_id FROM ' . $wpdb->blogs, OBJECT_K));

            // Deleting data from each blog
            foreach ( $blogs as $blog ) {
                switch_to_blog($blog);
                spbc_deactivation__delete_blog_tables();

                // Deleting all data from wp_options
                $wpdb->query(
                    'DELETE FROM ' . $wpdb->options
                    . ' WHERE'
                    . ' option_name LIKE "spbc_%" AND'
                    . ' option_name <> "spbc_deactivation_in_process"'
                );

                // Deleting scan links results
                $wpdb->query(
                    $wpdb->prepare(
                        "DELETE FROM {$wpdb->postmeta}
                        WHERE meta_key = %s OR meta_key = %s",
                        '_spbc_links_checked',
                        'spbc_links_checked'
                    )
                );
                // Deleting scan frontend results
                $wpdb->query(
                    $wpdb->prepare(
                        "DELETE FROM {$wpdb->postmeta}
                        WHERE meta_key = %s OR meta_key = %s OR meta_key = %s OR meta_key = %s",
                        '_spbc_frontend__last_checked',
                        '_spbc_frontend__approved',
                        'spbc_frontend__last_checked',
                        'spbc_frontend__approved'
                    )
                );
            }

            switch_to_blog($initial_blog);
            spbc_deactivation__delete_common_tables();

            delete_option(SPBC_PLUGINS);
            delete_option(SPBC_THEMES);

            // Delete backups
            \CleantalkSP\SpbctWP\Helpers\Data::removeDirectoryRecursively(SPBC_PLUGIN_DIR . 'backups');

            //Delete Cure log
            $cure_log = new CureLog();
            $cure_log->clearLogData();
        }

        delete_option('spbc_deactivation_in_process');

        // Deactivation for blog
    } elseif ( is_multisite() ) {
        spbc_deactivation__delete_blog_tables();

        delete_option(SPBC_SETTINGS);
        delete_option(SPBC_DATA);
        delete_option(SPBC_LOGIN_ATTEMPTS);
        delete_option(SPBC_CRON);
        delete_option(SPBC_ERRORS);

        // Deactivation on standalone blog
    } elseif ( ! is_multisite() ) {
        spbc_mu_plugin__uninstall();

        delete_option('spbc_deactivation_in_process');

        if ( ! empty($spbc->settings['misc__complete_deactivation']) ) {
            spbc_deactivation__delete_blog_tables();
            spbc_deactivation__delete_common_tables();

            // Deleting all data from wp_options
            $wpdb->query(
                'DELETE FROM ' . $wpdb->options
                . ' WHERE'
                . ' option_name LIKE "spbc_%" AND'
                . ' option_name <> "spbc_deactivation_in_process"'
            );


            // Deleting scan links results
            $wpdb->query(
                $wpdb->prepare(
                    "DELETE FROM {$wpdb->postmeta}
					WHERE meta_key = %s OR meta_key = %s",
                    '_spbc_links_checked',
                    'spbc_links_checked'
                )
            );

            // Deleting scan frontend results
            $wpdb->query(
                $wpdb->prepare(
                    "DELETE FROM {$wpdb->postmeta}
                        WHERE meta_key = %s OR meta_key = %s OR meta_key = %s OR meta_key = %s",
                    '_spbc_frontend__last_checked',
                    '_spbc_frontend__approved',
                    'spbc_frontend__last_checked',
                    'spbc_frontend__approved'
                )
            );

            // Delete backups
            \CleantalkSP\SpbctWP\Helpers\Data::removeDirectoryRecursively(SPBC_PLUGIN_DIR . 'backups');

            //Delete Cure log
            $cure_log = new CureLog();
            $cure_log->clearLogData();
        }
    }
}

function spbc_deactivation__delete_blog_tables()
{
    global $wpdb;
    $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->prefix . 'spbc_auth_logs');
    $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->prefix . 'spbc_monitoring_users');
    $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->prefix . 'spbc_firewall__personal_ips');
    $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->prefix . 'spbc_firewall__personal_countries');
    $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->prefix . 'spbc_firewall_logs');
    $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->prefix . 'spbc_traffic_control_logs');
    $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->prefix . 'spbc_traffic_control_logs');
    $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->prefix . 'spbc_bfp_blocked');
    $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->prefix . 'spbc_sessions');
}

function spbc_deactivation__delete_common_tables()
{
    global $wpdb;
    $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->base_prefix . 'spbc_scan_results');
    $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->base_prefix . 'spbc_firewall_data');
    $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->base_prefix . 'spbc_scan_links_logs');
    $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->base_prefix . 'spbc_scan_signatures');
    $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->base_prefix . 'spbc_scan_frontend');
    $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->base_prefix . 'spbc_backups');
    $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->base_prefix . 'spbc_backuped_files');
    $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->base_prefix . 'spbc_scan_results_log');
    $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->base_prefix . 'spbc_important_files');
    $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->base_prefix . 'spbc_important_file_snapshots');
}

// Misc functions to test the plugin.
function spbc_plugin_loaded()
{
    global $spbc;

    if ( is_admin() || is_network_admin() ) {
        $dir    = plugin_basename(dirname(__FILE__)) . '/i18n';
        load_plugin_textdomain('security-malware-firewall', false, $dir);
    }

    if ( $spbc->settings['spbc_trusted_and_affiliate__shortcode'] === '1' ) {
        add_action('wp_enqueue_scripts', 'spbc_attach_public_css');
        add_shortcode('cleantalk_security_affiliate_link', 'spbc_trusted_text_shortcode_handler');
    }
}


function spbc_2fa__Google2fa_replace_meta(\WP_User $user)
{
    return delete_user_meta($user->ID, 'spbc_2fa_type') && delete_user_meta($user->ID, 'spbc_g2fa_token');
}

/**
 * Adding notification to registration form
 *
 * @return null
 */
function spbc_login_form_notification()
{
    global $spbc;

    //Return if it's not a registration page.
    $login_url = rtrim(wp_login_url(), '/');
    $server_url = rtrim(Server::getURL(), '/');
    if ( empty(Server::get('REQUEST_SCHEME')) ) {
        $login_url = preg_replace('/https?/', '', $login_url);
        $server_url = preg_replace('/https?/', '', $server_url);
    }
    if ( strpos($server_url, $login_url) === 0 && Get::get('action') !== 'register' ) {
        $show_notification = ( ! empty($spbc->settings['misc__show_link_in_login_form']) ? true : false );
        if ( $show_notification ) {
            $link = $spbc->settings['spbc_trusted_and_affiliate__footer']
                ? spbc_generate_affiliate_link()
                : "<a rel='nofollow' href='https://wordpress.org/plugins/security-malware-firewall/' target='_blank'>" . $spbc->data["wl_brandname"] . "</a>";

            // @ToDo this section need to be refactored
            $link = ! $spbc->data["wl_mode_enabled"] ? $link : $spbc->data["wl_brandname"];
            $logo_img = $spbc->data["wl_mode_enabled"] ? "" : "<img style='vertical-align: bottom; width: 12px; height: 15px;' src='" . SPBC_PATH . "/images/logo_small.png'>";
            echo "<div style='position: absolute; bottom: 20px; right: 20px;'>"
                 . "<p style='text-align: right;'>"
                 . __('Brute Force Protection by', 'security-malware-firewall')
                 . "&nbsp;"
                 . $logo_img
                 . $link
                 . ".</p>"
                 . "<p style='text-align: right;'>" . __('All attempts are logged.', 'security-malware-firewall') . "</p>
			</div>";
        }
    }

    return null;
}

/**
 * Check user for brute force attack
 *
 * @param WP_User|WP_Error $user
 * @param string           $username
 * @param string           $password
 *
 * @return WP_Error|WP_User
 */
function spbc_authenticate($user, $username)
{
    global $spbc;

    if ( is_wp_error($user) ) {
        spbc_authenticate__check_brute_force();

        $spbc->login_error = true;

        $err_codes = $user->get_error_codes();

        // Passwords brute force
        if ( in_array('incorrect_password', $err_codes, true) ) {
            do_action('spbc_log_wrong_auth');

            spbc_auth_log(
                array(
                'username' => $username,
                'event'    => 'invalid_password',
                )
            );
        }

        // Usernames brute force.
        if ( in_array('invalid_username', $err_codes, true) ) {
            do_action('spbc_log_wrong_auth');

            spbc_auth_log(
                array(
                'username' => $username,
                'event'    => 'invalid_username',
                )
            );
        }

        // Emails brute force.
        if ( in_array('invalid_email', $err_codes, true) ) {
            do_action('spbc_log_wrong_auth');

            spbc_auth_log(
                array(
                'username' => $username,
                'event'    => 'invalid_email',
                )
            );
        }
    }

    // The user is logged in.
    if ( $user instanceof WP_User && $user->ID > 0 ) {
        // Detecting new device
        $browser_sign__old = spbc_authenticate__browser_sign__get($user);
        $browser_sign      = spbc_authenticate__browser_sign__create();

        $roles = null;
        if ( ! empty($user->roles) && is_array($user->roles) ) {
            $roles = $user->roles[0]; // Takes only first role.
        }

        $is_new_device = $browser_sign__old !== $browser_sign;

        spbc_auth_log(
            array(
                'username'     => $username,
                'event'        => $is_new_device ? 'login_new_device' : 'login',
                'roles'        => $roles,
                'user_agent'   => filter_input(INPUT_SERVER, 'HTTP_USER_AGENT'),
                'browser_sign' => $browser_sign,
            )
        );

        // Skip for ZAPIER
        if (
            spbc_is_plugin_active('zapier/zapier.php') &&
            Server::get('REQUEST_URI') === '/wp-json/zapier/v1/token'
        ) {
            return $user;
        }

        // Redirect if 2fa is enabled
        if ( $spbc->settings['2fa__enable'] == 1 || ( $is_new_device && $spbc->settings['2fa__enable'] == - 1 ) ) {
            if ( spbc_is_user_role_in($spbc->settings['2fa__roles'], $user) ) {
                if ( ( $is_new_device && $spbc->settings['2fa__enable'] == - 1 )
                    || $spbc->settings['2fa__enable'] == 1
                    || Cookie::get('spbc_2fa_passed') === hash('sha256', $spbc->data['salt'] . $user->ID)
                ) {
                    $type2fa = get_user_meta($user->ID, 'spbc_2fa_type', true);
                    if ( $type2fa !== 'google_authenticator' ) {
                        spbc_2fa__send_mail($user);
                    }
                    wp_redirect(
                        wp_login_url()
                        . ( strpos(wp_login_url(), '?') === false ? '?' : '&' )
                        . 'spbc_2fa_user=' . rawurlencode($user->user_login)
                    );
                    die();
                }
            }
        } else {
            // Sends logs to get notify about superuser login.
            $result = spbc_send_logs();
            if ( empty($result['error']) ) {
                $spbc->error_delete('send_logs');
                $spbc->data['logs_last_sent']         = current_time('timestamp');
                $spbc->data['last_sent_events_count'] = $result;
                $spbc->save('data');
            } else {
                $spbc->error_add('send_logs', $result);
            }
            spbc_authenticate__browser_sign__set($user, $browser_sign);
            spbc_authenticate__user_agent__set($user);
        }
    }

    return $user;
}

/**
 * Check brute force attack
 *
 * @return void
 */
function spbc_authenticate__check_brute_force()
{
    global $spbc;

    $login_url = wp_login_url();
    if ($spbc->settings['login_page_rename__enabled']) {
        $GLOBALS['wp_rewrite'] = new WP_Rewrite();
        $login_url = RenameLoginPage::getURL($spbc->settings['login_page_rename__name']);
    }

    $bfp = new BFP(
        array(
            'api_key'       => $spbc->api_key,
            'state'         => $spbc,
            'is_login_page' => strpos(trim(Server::getURL(), '/'), trim($login_url, '/')) === 0,
            'is_logged_in'  => Cookie::get('spbc_is_logged_in') === md5($spbc->data['salt'] . parse_url(get_option('home'), PHP_URL_HOST)),
            'bf_limit'      => $spbc->settings['bfp__allowed_wrong_auths'],
            'block_period'  => $spbc->settings['bfp__block_period__5_fails'],
            'count_period'  => $spbc->settings['bfp__count_interval'],
        )
    );

    $bfp->setDb(new DB());
    $bfp->setIpArray([IP::get()]);
    $bfp_result = $bfp->check();
    $bfp->middleAction();

    if (!empty($bfp_result)) {
        $bfp->_die($bfp_result[0]);
    }
}

/**
 * Returns browser sign
 *
 * @return string
 */
function spbc_authenticate__browser_sign__create()
{
    return md5(filter_input(INPUT_SERVER, 'HTTP_USER_AGENT') . filter_input(INPUT_SERVER, 'HTTP_ACCEPT_LANGUAGE'));
}

/**
 * Set browser sign of user
 *
 * @param WP_User $user
 * @param string  $browser_sign Browser sign
 */
function spbc_authenticate__browser_sign__set($user, $browser_sign)
{
    update_user_meta($user->ID, 'spbc_browser_sign', $browser_sign);
    update_user_meta($user->ID, 'spbc_browser_sign__updated', time());
}

/**
 * Gets browser sign of user
 *
 * @param $user
 *
 * @return bool|string Browser sign and update time
 */
function spbc_authenticate__browser_sign__get($user)
{
    return get_user_meta($user->ID, 'spbc_browser_sign', true);
}


/**
 * Set browser sign of user
 *
 * @param WP_User $user
 * @param string  $browser_sign Browser sign
 */
function spbc_authenticate__user_agent__set($user)
{
    update_user_meta($user->ID, 'spbc_user_agent', filter_input(INPUT_SERVER, 'HTTP_USER_AGENT'));
}

/**
 * Gets last user agent
 *
 * @param $user
 *
 * @return bool|string Browser sign and update time
 */
function spbc_authenticate__user_agent__get($user)
{
    return get_user_meta($user->ID, 'spbc_user_agent', true);
}

function spbc_2fa__send_mail($user)
{
    global $spbc;

    spbc_2fa__key_remove_old();

    if ( isset($spbc->data['2fa_keys'][ $user->user_login ]) && $spbc->data['2fa_keys'][ $user->user_login ]['generated'] > time() - SPBC_2FA_KEY_TTL ) {
        // Set existing code if exists.
        $key = $spbc->data['2fa_keys'][ $user->user_login ]['val'];
    } else {
        // Set new code if no code spotted
        $key = spbc_2fa__key_generate_and_store($user);
    }

    $brand_name = $spbc->data['wl_brandname'];
    $support_link = $spbc->data['wl_support_url'];

    wp_mail(
        $user->user_email,
        // Subject
        sprintf(
            __(esc_html__($brand_name) . '\'s Security "%s"', 'security-malware-firewall'),
            parse_url(get_option('home'), PHP_URL_HOST)
        ),
        // Message
        sprintf(
            __('Two factor authentication code for user "%s" on "%s" website is %d' . PHP_EOL . 'Lifetime of the code 5 minutes.' . PHP_EOL . PHP_EOL . esc_html__($brand_name) . '\'s Security: ' . esc_url($support_link), 'security-malware-firewall'),
            $user->user_login,
            parse_url(get_option('home'), PHP_URL_HOST),
            $key
        )
    );

    return $user;
}

function spbc_2fa__show_field()
{
    global $spbc;

    spbc_2fa__key_remove_old();

    if ( $spbc->settings['2fa__enable'] ) {
        if ( isset($_GET['spbc_2fa_user']) ) {
            $user_name = rawurldecode($_GET['spbc_2fa_user']);
            $user      = spbc_get_user_by('login', $user_name);

            if ( ! $user ) {
                return;
            }

            // Recombining form to show only code input
            $label       = $spbc->data["wl_brandname"] . __(' authorization code');
            $description = __('Please, check your email to get code. If you have not received the e-mail, please, check "spam" folder.', 'security-malware-firewall');
            $err_text    = __('Please, check your e-mail to gain pass code', 'security-malware-firewall');

            $type2fa = get_user_meta($user->ID, 'spbc_2fa_type', true);

            if ( $type2fa === 'google_authenticator' ) {
                $label       = __('Google Authenticator authorization code');
                $description = __('Please, check your Google Authenticator to get code.', 'security-malware-firewall');
                $err_text    = __('Please, check your Google Authenticator to gain pass code', 'security-malware-firewall');
            }

            // Error displaying
            if ( isset($_GET['spbc_2fa_error']) ) {
                echo '<script>'
                     . 'var spbc_err = document.createElement("div");'
                     . 'spbc_err.innerHTML = \'<div id="login_error">'
                     . '<strong>' . __('WRONG CODE: ', 'security-malware-firewall') . '</strong>'
                     . $err_text . '\';'
                     . 'document.getElementById("login").insertBefore(spbc_err, document.getElementById("login").children[1]);'
                     . '</script>';
            }

            // Recombining form to show only code input
            if ( spbc_is_user_role_in($spbc->settings['2fa__roles'], $user_name)
                && ( isset($spbc->data['2fa_keys'][ $user_name ]) || $type2fa === 'google_authenticator' )
            ) {
                $replacement =
                    '<h3 style="text-align: center;margin: 0 0 10px 0;">' . $spbc->data["wl_brandname"] . '</h3>'
                    . '<p id="spbc_2fa_wrapper" style="display: inline !important;">'
                    . '<label for="spbc_2fa">' . $label . '</label>'
                    . '<input type="text"   name="spbc_2fa" id="spbc_2fa" class="input" value="" size="20" />'
                    . '<input type="hidden" name="log"                    class="input" value="' . $user_name . '" />'
                    . $description . '<br><br>Contact <a href="' . $spbc->data["wl_support_url"] . '">tech support</a> if you have questions.<br><br>'
                    . '</p>'
                    . '<p class="submit" style="display: inline !important;">'
                    . '<input type="submit" name="wp-submit" id="wp-submit" class="button button-primary button-large" value="Log In">'
                    . '<input type="hidden" name="redirect_to" value="' . admin_url() . '">'
                    . '<input type="hidden" name="testcookie" value="1">'
                    . '</p>';
                // Deleting all form and then put out content in it.
                echo '<script>'
                     . 'var spbc_form = document.getElementById("loginform");'
                     . "spbc_form.innerHTML = '';"
                     . 'setTimeout(function(){'
                     . "spbc_form.innerHTML = '" . $replacement . "';"
                     . "document.getElementById('spbc_2fa').focus();"
                     . '}, 500);'
                     . '</script>';
            }
        }
    }
}

function spbc_2fa__key_generate_and_store($user)
{
    global $spbc;

    // Generate key
    $left_part_for_pad = hexdec(substr(hash('sha256', $spbc->data['salt'] . rand(0, getrandmax())), 0, 6)) % 1000;
    $right_part_for_pad = hexdec(substr(hash('sha256', $spbc->data['salt'] . rand(0, getrandmax())), 0, 6)) % 1000;
    $left_part  = str_pad(
        (string) $left_part_for_pad,
        3,
        '1',
        STR_PAD_LEFT
    );
    $right_part = str_pad(
        (string) $right_part_for_pad,
        3,
        '0',
        STR_PAD_LEFT
    );
    $key        = $left_part . $right_part;

    $spbc->data['2fa_keys'][ $user->user_login ] = array(
        'generated' => time(),
        'val'       => $key,
    );
    $spbc->save('data');

    return (int) $key;
}

function spbc_2fa__key_remove_old()
{
    global $spbc;

    // Check for old keys
    $keys = $spbc->data['2fa_keys'];
    foreach ( $keys as $index => $key ) {
        if ( $key['generated'] < time() - SPBC_2FA_KEY_TTL ) {
            unset($spbc->data['2fa_keys'][ $index ]);
        }
    }

    $spbc->save('data');
}

function spbc_2fa__authenticate()
{
    global $spbc;

    if ( $spbc->settings['2fa__enable'] ) {
        if ( isset($_POST['spbc_2fa'], $_POST['log']) ) {
            $user = get_user_by('login', $_POST['log']);

            if (!$user) {
                $user = get_user_by('email', $_POST['log']);
            }

            if ( $user instanceof WP_User ) {
                spbc_2fa__key_remove_old();

                $type2fa    = get_user_meta($user->ID, 'spbc_2fa_type', true);
                $g2fa_token = get_user_meta($user->ID, 'spbc_g2fa_token', true);
                $ga         = new GoogleAuthenticator();

                // 2fa passed
                if ( isset($spbc->data['2fa_keys'][ $user->user_login ]) && $spbc->data['2fa_keys'][ $user->user_login ]['val'] == $_POST['spbc_2fa'] ) {
                    // Delete it so no one could login via this code
                    unset($spbc->data['2fa_keys'][ $user->user_login ]);
                    $spbc->save('data');

                    spbc_2fa__success($user);
                } elseif ( $type2fa === 'google_authenticator' && $g2fa_token && $_POST['spbc_2fa'] == $ga->getCode($g2fa_token) ) {
                    spbc_2fa__success($user);
                } else {
                    spbc_2fa__failed($user);
                }
            }
        }
    }
}

function spbc_2fa__success(\WP_User $user)
{
    global $spbc;

    $type2fa = get_user_meta($user->ID, 'spbc_2fa_type', true);
    $event   = $type2fa === 'google_authenticator' ? 'login_g2fa' : 'login_2fa';

    // Add event to security log
    spbc_auth_log(
        array(
        'username'     => $user->user_login,
        'event'        => $event,
        'roles'        => reset($user->roles),
        'user_agent'   => filter_input(INPUT_SERVER, 'HTTP_USER_AGENT'),
        'browser_sign' => spbc_authenticate__browser_sign__create(),
        )
    );

    // Sends logs to get notify about superuser login.
    $result = spbc_send_logs();
    if ( empty($result['error']) ) {
        $spbc->error_delete('send_logs');
        $spbc->data['logs_last_sent']         = current_time('timestamp');
        $spbc->data['last_sent_events_count'] = $result;
        $spbc->save('data');
    } else {
        $spbc->error_add('send_logs', $result);
    }

    // Athorize user and redirect to wp-admin
    wp_set_auth_cookie($user->ID);
    $browser_sign = spbc_authenticate__browser_sign__create();
    spbc_authenticate__browser_sign__set($user, $browser_sign);
    spbc_authenticate__user_agent__set($user);
    Cookie::set('spbc_2fa_passed', hash('sha256', $spbc->data['salt'] . $user->ID), time() + 60 * 60 * 24 * 30, '/', parse_url(get_option('home'), PHP_URL_HOST), false, true);
    if ( isset($_REQUEST['redirect_to']) ) {
        $redirect_to = $_REQUEST['redirect_to'];
    } else {
        $redirect_to = admin_url();
    }
    wp_redirect($redirect_to);
    die();
}

function spbc_2fa__failed(\WP_User $user)
{
    $type2fa = get_user_meta($user->ID, 'spbc_2fa_type', true);
    $event   = $type2fa === 'google_authenticator' ? 'auth_failed_g2fa' : 'auth_failed_2fa';

    spbc_auth_log(
        array(
        'username'     => $user->user_login,
        'event'        => $event,
        'roles'        => 'administrator',
        'user_agent'   => filter_input(INPUT_SERVER, 'HTTP_USER_AGENT'),
        'browser_sign' => spbc_authenticate__browser_sign__create(),
        )
    );
    wp_redirect(
        wp_login_url()
        . ( strpos(wp_login_url(), '?') === false ? '?' : '&' )
        . 'spbc_2fa_error=1&spbc_2fa_user=' . $user->user_login
    );
    die();
}

/**
 * Logs a logout event
 *
 * @param $id
 *
 * @return null
 */
function spbc_wp_logout($id)
{
    $user = get_user_by('id', $id);

    // The user is logged out.
    if ( isset($user->ID) && $user->ID > 0 ) {
        $roles = null;
        if ( is_array($user->roles) ) {
            $roles = $user->roles[0]; // Takes only first role.
        }
        spbc_auth_log(
            array(
            'username'     => $user->get('user_login'),
            'event'        => 'logout',
            'roles'        => $roles,
            'user_agent'   => spbc_authenticate__user_agent__get($user),
            'browser_sign' => spbc_authenticate__browser_sign__get($user),
            )
        );
    }

    Cookie::set('spbc_is_logged_in', '0', time() - 30, '/');

    return null;
}

function spbc_fix_error_messages($error_msg)
{
    global $spbc;

    //Fixed for custom filters
    if ( ! is_string($error_msg) ) {
        return $error_msg;
    }

    // 2fa wrong code
    if ( isset($_GET['spbc_2fa_error']) ) {
        $error_msg = explode('<br />', $error_msg);

        return $error_msg[0] . '<br />' . __('Wrong temporary code. Check your email to gain the code.', 'security-malware-firewall');
    }

    // Custom block message
    if ( $spbc->login_error ) {
        return '<strong>' . __('Error') . '</strong>: '
               . __('Entered credentials are wrong, please, try again.', 'security-malware-firewall')
               . ' <a href="' . esc_url(wp_lostpassword_url()) . '">' . __('Lost your password?') . '</a>';
    }

    return $error_msg;
}

/**
 * Adds a record to failed logins1
 * Logs only new password occurrence
 *
 * @param $username
 * @param $password
 * @param $attempts
 * @param bool $new_issue
 */
function spbc_add_issue($username, $password, $attempts, $new_issue = false)
{
    // Login new issue
    if (
        $new_issue || (
            isset($attempts[ $username ]['first_issue']) &&
            time() - $attempts[ $username ]['first_issue'] > SPBC_AUTH__CALC_PERIOD // Drop attempts for username because calculation period is passed
        )
    ) {
        unset($attempts[ $username ]);
        $attempts[ $username ]['attempts']    = 1;
        $attempts[ $username ]['first_issue'] = time();
        $attempts[ $username ]['passwords'][] = md5($password);
        update_option(SPBC_LOGIN_ATTEMPTS, $attempts, false);
        spbc_auth_log(
            array(
            'username' => $username,
            'event'    => 'auth_failed'
            )
        );

        // New password attempt
    } elseif ( ! in_array(md5($password), $attempts[ $username ]['passwords'], true) ) {
        $attempts[ $username ]['attempts'] ++;
        $attempts[ $username ]['passwords'][] = md5($password);
        update_option(SPBC_LOGIN_ATTEMPTS, $attempts, false);
        spbc_auth_log(
            array(
            'username' => $username,
            'event'    => 'auth_failed'
            )
        );
    }
}

/**
 * The function logs any attempt to log in the WordPress backend.
 *
 * @param array $params
 *
 * @return int Inserted log ID
 */
function spbc_auth_log($params)
{
    global $wpdb;

    SecurityCounter::increment($params['event']);

    $params_default = array(
        'username'     => null,
        'event'        => null,
        'page'         => null,
        'page_time'    => null,
        'roles'        => null,
        'blog_id'      => ( SPBC_WPMS ? get_current_blog_id() : null ),
        'user_agent'   => null,
        'browser_sign' => null,
    );
    $params         = array_merge($params_default, $params);

    // Cutting to 1024 symbols
    $params['user_agent'] = is_string($params['user_agent'])
        ? substr($params['user_agent'], 0, 1024)
        : $params['user_agent'];

    $auth_ip = IP::get();

    // To fix issue with NULL values for not NULL field.
    $blog_id = isset($params['blog_id']) && $params['blog_id'] !== null ? $params['blog_id'] : 1;

    // @todo Learn the prepare method to insert NULL value
    $wpdb->query(
        $wpdb->prepare(
            'INSERT INTO ' . SPBC_TBL_SECURITY_LOG
            . '(`datetime`, `timestamp_gmt`, `user_login`, `event`, `auth_ip`, `page`, `page_time`, `blog_id`, `role`, `user_agent`, `browser_sign` )'
            . ' VALUES( %s, %d, %s, %s, %s, %s, %d, %d, %s, %s, %s )',
            array(
            date('Y-m-d H:i:s'),
            time(),
            $params['username'],
            $params['event'],
            $auth_ip,
            $params['page'],
            $params['page_time'],
            $blog_id,
            $params['roles'],
            $params['user_agent'],
            $params['browser_sign'],
            )
        )
    );

    return $wpdb->insert_id;
}

//
// Sorts some data.
//
function spbc_usort_desc($a, $b)
{
    return $b->datetime_ts - $a->datetime_ts;
}

/**
 * Function to get the countries by IPs list.
 *
 * @param $ips_data
 *
 * @return array
 */
function spbc_get_countries_by_ips($ips_data = '')
{
    $ips_c = array();

    if ( $ips_data === '' ) {
        return $ips_c;
    }

    $result = SpbcAPI::method__ip_info($ips_data);

    if ( empty($result['error']) ) {
        foreach ( $result as $ip_dec => $v2 ) {
            if ( isset($v2['country_code']) ) {
                $ips_c[ $ip_dec ]['country_code'] = $v2['country_code'];
            }
            if ( isset($v2['country_name']) ) {
                $ips_c[ $ip_dec ]['country_name'] = $v2['country_name'];
            }
        }
    }

    return $ips_c;
}

/**
 * Gets and write new signatures in local database
 *
 * @return bool|array
 * @global State $spbc
 * @global WPDB $wpdb
 */
function spbc_scanner__signatures_update()
{
    global $spbc;

    $latest_signature_submitted_time = SignatureAnalysisFacade::getLatestSignatureSubmittedTime();

    $signatures_from_cloud = SignatureAnalysisFacade::getSignaturesFromCloud($latest_signature_submitted_time);

    // Signatures updated
    if (isset($signatures_from_cloud['error']) && $signatures_from_cloud['error'] === 'UP_TO_DATE') {
        return array('success' => 'UP_TO_DATE');
    }

    // There is errors
    if (isset($signatures_from_cloud['error'])) {
        return $signatures_from_cloud;
    }

    $signatures = $signatures_from_cloud['values'];
    $map        = $signatures_from_cloud['map'];

    SignatureAnalysisFacade::clearSignaturesTable();

    $signatures_added = SignatureAnalysisFacade::addSignaturesToDb($map, $signatures);

    if (!$signatures_added) {
        // Attempt to record one at a time
        $signatures_added = SignatureAnalysisFacade::addSignaturesToDbOneByOne($map, $signatures);

        if (isset($signatures_added['bad_signatures'])) {
            $spbc->error_add('scanner_update_signatures_bad_signatures', $signatures_added['bad_signatures']);
        } else {
            /**
             * @psalm-suppress InvalidScalarArgument
             */
            $spbc->error_delete('scanner_update_signatures_bad_signatures', 'save');
        }
    }

    $spbc->data['scanner']['last_signature_update'] = current_time('timestamp');
    $spbc->data['scanner']['signature_count']       = count($signatures);
    $spbc->save('data');

    return true;
}

/**
 * Sending Security FireWall logs
 *
 * @param $api_key
 *
 * @return array|int
 */
function spbc_send_firewall_logs($api_key = false)
{
    global $spbc;

    $api_key = ! empty($api_key) ? $api_key : $spbc->api_key;

    if ( ! empty($api_key) ) {
        $result = FW::sendLog(
            DB::getInstance(),
            SPBC_TBL_FIREWALL_LOG,
            $api_key
        );

        if ( empty($result['error']) ) {
            $spbc->fw_stats['last_send']       = current_time('timestamp');
            $spbc->fw_stats['last_send_count'] = $result;
            $spbc->save('fw_stats', true, false);

            return $result;
        }

        return $result;
    }

    return array(
        'error' => 'KEY_EMPTY'
    );
}

/**
 * Drop Security FireWall data
 *
 * @return bool|string[]
 */
function spbc_security_firewall_drop()
{
    global $wpdb;

    $result = $wpdb->query('DELETE FROM `' . SPBC_TBL_FIREWALL_DATA . '`;');

    if ( $result !== false ) {
        return true;
    }

    return array( 'error' => 'DELETE_ERROR' );
}

/**
 * Handle firewall private_records remote call.
 * @param $action string 'add','delete'
 * @param $test_data string JSON string used in test cases
 * @return string JSON string of results
 * @throws Exception
 */
function spbct_sfw_private_records_handler($action, $test_data = null)
{

    $error = 'secfw_private_records_handler: ';

    if ( !empty($action) && (in_array($action, array('add', 'delete'))) ) {
        $metadata = !empty($test_data) ? $test_data : Post::get('metadata');

        /**
         * Validate JSON
         */
        if ( !empty($metadata) ) {
            $metadata = json_decode(stripslashes($metadata), true);
            if ( $metadata === 'NULL' || $metadata === null ) {
                throw new InvalidArgumentException($error . 'metadata JSON decoding failed');
            }
        } else {
            throw new InvalidArgumentException($error . 'metadata is empty');
        }

        foreach ( $metadata as $_key => &$row ) {
            $row = explode(',', $row);

            /**
             * Validation of JSON decoded array data
             */
            $ip_validated = false;
            $validation_error = '';

            //validate IP
            if ( IP::validate($row[0]) === 'v6' ) {
                $ip_validated = $row[0];
            } elseif (
                IP::validate(long2ip((int)$row[0])) === 'v4'
                && (int)($row[0]) === ip2long(long2ip((int)$row[0]))
            ) {
                $ip_validated = (int)$row[0];
            } else {
                $validation_error = 'network value does not look like IP address ';
            }

            //do this to get info more obvious
            $metadata_assoc_array = array(
                'network' => $ip_validated ?: null,
                'mask' => (int)$row[1],
                'status' => isset($row[2]) && $row[2] !== '' ? (int)$row[2] : null,
            );

            //validate mask and status
            if ( $metadata_assoc_array['mask'] === 0
                || $metadata_assoc_array['mask'] > 4294967295
            ) {
                $validation_error = 'metadata validate failed on "mask" value';
            }

            //only for adding
            if ( $action === 'add' ) {
                if ( !in_array($metadata_assoc_array['status'], array(-4, -3, -2, -1, 0, 1, 2, 99)) ) {
                    $validation_error = 'metadata validate failed on "status" value';
                }
            }

            if ( !empty($validation_error) ) {
                throw new InvalidArgumentException($error . $validation_error);
            }

            /**
             * Ip version logic
             */
            if ( is_string($metadata_assoc_array['network']) ) {
                $metadata_assoc_array['network'] = IP::convertIPv6ToFourIPv4(IP::extendIPv6(IP::normalizeIPv6($metadata_assoc_array['network'])));


                if ($metadata_assoc_array['mask'] > 128) {
                    $validation_error = 'metadata validate failed on "mask" value';
                    break;
                }

                /**
                 * Versatility for mask for v6 and v4
                 * @psalm-suppress LoopInvalidation
                 */
                for ( $masks = array(), $mask = $metadata_assoc_array['mask'], $k = 4; $k >= 1; $k-- ) {
                    $masks[$k] = (2 ** 32) - (2 ** (32 - ($mask > 32 ? 32 : $mask)));
                    $mask -= 32;
                    $mask = $mask > 0 ? $mask : 0;
                }
                $metadata_assoc_array['mask'] = $masks;
            } else {
                $metadata_assoc_array['network'] = array(0, 0, 0, $metadata_assoc_array['network']);
                $metadata_assoc_array['mask'] = array(null, 0, 0, 0, $metadata_assoc_array['mask']);
            }

            //all checks done, change on link
            $row = $metadata_assoc_array;
        }
        unset($row);

        if ( !empty($validation_error) ) {
            throw new InvalidArgumentException($error . $validation_error);
        }

        //method selection
        if ( $action === 'add' ) {
            $handler_output = FW::privateRecordsAdd(
                DB::getInstance(),
                SPBC_TBL_FIREWALL_DATA__IPS,
                $metadata
            );
        } elseif ( $action === 'delete' ) {
            $handler_output = FW::privateRecordsDelete(
                DB::getInstance(),
                SPBC_TBL_FIREWALL_DATA__IPS,
                $metadata
            );
        } else {
            $error .= 'unknown action name: ' . $action;
            throw new InvalidArgumentException($error);
        }
    } else {
        throw new InvalidArgumentException($error . 'empty action name');
    }

    return json_encode(array('OK' => $handler_output));
}

function spbc_update_postmeta_links($post_ID)
{
    delete_post_meta($post_ID, '_spbc_links_checked');
    delete_post_meta($post_ID, 'spbc_links_checked');
}

function spbc_update_postmeta_links__by_comment($id)
{
    $comment = get_comment($id);
    spbc_update_postmeta_links($comment->comment_post_ID);
}

// Install MU-plugin
function spbc_mu_plugin__install()
{

    // If WPMU_PLUGIN_DIR is not exists -> create it
    if ( ! is_dir(WPMU_PLUGIN_DIR) && ! mkdir(WPMU_PLUGIN_DIR) && ! is_dir(WPMU_PLUGIN_DIR) ) {
        throw new \RuntimeException(sprintf('Directory "%s" was not created', WPMU_PLUGIN_DIR));
    }

    // Get data from info file and write it to new plugin file
    $file = '<?php' . PHP_EOL . file_get_contents(SPBC_PLUGIN_DIR . '/install/security-malware-firewall-mu.php');

    return @file_put_contents(WPMU_PLUGIN_DIR . '/0security-malware-firewall-mu.php', $file) ? true : false;
}

// Uninstall MU-plugin
function spbc_mu_plugin__uninstall()
{
    if ( file_exists(WPMU_PLUGIN_DIR . '/0security-malware-firewall-mu.php') ) {
        return unlink(WPMU_PLUGIN_DIR . '/0security-malware-firewall-mu.php');
    }

    return false;
}

function spbc_is_user_logged_in()
{
    return (bool) preg_grep("/wordpress_logged_in/", array_keys($_COOKIE));
}

function spbc_user_is_admin()
{
    global $spbc;

    if (!empty($spbc->settings['data__set_cookies'])) {
        return
            Cookie::get('spbc_is_logged_in') === md5($spbc->data['salt'] . parse_url(get_option('home'), PHP_URL_HOST)) &&
            Cookie::get('spbc_admin_logged_in') === md5($spbc->data['salt'] . 'admin' . parse_url(get_option('home'), PHP_URL_HOST));
    }

    return is_admin();
}

//Function to send logs
function spbc_send_logs($api_key = null)
{
    global $spbc, $wpdb;

    if ( $api_key == null ) {
        if ( ! $spbc->is_mainsite && $spbc->ms__work_mode == 2 ) {
            $api_key = $spbc->network_settings['spbc_key'];
        } else {
            $api_key = $spbc->settings['spbc_key'];
        }
    }

    $wpms_snippet = SPBC_WPMS
        ? (" WHERE blog_id = " . get_current_blog_id() . ' AND ')
        : " WHERE ";

    $rows = $wpdb->get_results(
        "SELECT id, datetime, timestamp_gmt, user_login, page, page_time, event, auth_ip, role, user_agent, browser_sign
		FROM " . SPBC_TBL_SECURITY_LOG
        . $wpms_snippet
        . " sent <> 1"
        . " ORDER BY datetime DESC"
        . " LIMIT " . SPBC_SELECT_LIMIT . ";"
    );

    $rows_count = count($rows);

    if ( $rows_count ) {
        $data = array();

        foreach ( $rows as $record ) {
            $page_time = (string) $record->page_time;
            if ((int)$page_time <= 0) {
                $page_time = '1';
            }
            $data[] = array(
                'log_id'        => (string) $record->id,
                'datetime'      => (string) $record->datetime,
                'datetime_gmt'  => $record->timestamp_gmt,
                'user_log'      => (string) $record->user_login,
                'event'         => (string) $record->event,
                'auth_ip'       => strpos($record->auth_ip, ':') === false
                    ? (int) sprintf('%u', ip2long($record->auth_ip))
                    : (string) $record->auth_ip,
                'page_url'      => (string) $record->page,
                'event_runtime' => $page_time,
                'role'          => (string) $record->role,
            );

            // Adding user agent and browser sign if it's login event
            if ( in_array(strval($record->event), array( 'login', 'login_2fa', 'login_new_device', 'logout', )) ) {
                $data[] = array_merge(
                    array_pop($data),
                    array(
                        'user_agent'        => $record->user_agent,
                        'browser_signature' => $record->browser_sign,
                    )
                );
            }
        }

        $result = SpbcAPI::method__security_logs($api_key, $data);

        if ( empty($result['error']) ) {
            //Clear local table if it's ok.
            if ( $result['rows'] == $rows_count ) {
                $updated_ids = array();
                foreach ($data as $item) {
                    $updated_ids[] = $item['log_id'];
                }
                if ( SPBC_WPMS ) {
                    $wpdb->query(
                        "UPDATE " . SPBC_TBL_SECURITY_LOG
                        . " SET sent = 1
	                    WHERE id IN ("
                        . implode(',', $updated_ids) .
                        ")"
                        . ( $spbc->ms__work_mode == 2 ? '' : ' AND blog_id = ' . get_current_blog_id() )
                        . ";"
                    );
                } else {
                    $wpdb->query(
                        "UPDATE " . SPBC_TBL_SECURITY_LOG
                        . " SET sent = 1
	                    WHERE id IN ("
                        . implode(',', $updated_ids) .
                        ");"
                    );
                }
                $result = $rows_count;
            } else {
                $result = array(
                    'error' => sprintf(__('Sent: %d. Confirmed receiving of %d rows.', 'security-malware-firewall'), $rows_count, intval($result['rows']))
                );
            }
        }
    } else {
        $result = array(
            'error' => 'NO_LOGS_TO_SEND'
        );
    }

    global $spbc_cron;
    if ( ! empty($spbc_cron) && empty($result['error']) ) {
        $spbc->data['logs_last_sent']         = current_time('timestamp');
        $spbc->data['last_sent_events_count'] = $result;
    }

    return $result;
}

/**
 * @return bool
 * @psalm-suppress RedundantCondition
 */
function spbc_set_api_key()
{
    global $spbc;

    $website        = parse_url(get_option('home'), PHP_URL_HOST) . parse_url(get_option('home'), PHP_URL_PATH);
    $platform       = 'wordpress';
    $user_ip        = IP::get();
    $timezone       = spbc_wp_timezone_string();
    $language       = Server::get('HTTP_ACCEPT_LANGUAGE');
    $is_wpms        = is_multisite() && defined('SUBDOMAIN_INSTALL') && ! SUBDOMAIN_INSTALL;
    $white_label    = false;
    $hoster_api_key = $spbc->ms__hoster_api_key;


    $result = SpbcAPI::method__get_api_key(
        'security',
        get_network_option(0, 'admin_email'),
        $website,
        $platform,
        $timezone,
        $language,
        $user_ip,
        $is_wpms,
        $white_label,
        $hoster_api_key
    );

    if ( ! empty($result['error']) ) {
        $spbc->data['key_is_ok'] = false;
        $spbc->error_add('get_key', $result);

        return false;
    } else {
        $api_key                    = trim($result['auth_key']);
        $api_key                    = preg_match('/^[a-z\d]*$/', $api_key) ? $api_key : $spbc->settings['spbc_key']; // Check key format a-z\d
        $api_key                    = is_main_site() || $spbc->ms__work_mode != 2 ? $api_key : $spbc->network_settings['spbc_key'];
        $spbc->settings['spbc_key'] = $api_key;
        $spbc->save('settings');

        $spbc->data['user_token']  = ( ! empty($result['user_token']) ? $result['user_token'] : '' );
        $spbc->data['key_is_ok']   = spbc_api_key__is_correct($api_key);
        $spbc->data['key_changed'] = true;
        $spbc->save('data');

        $spbc->error_delete('get_key api_key');

        return true;
    }
}

/**
 * The functions check to check an account
 * Executes only via cron (on the main blog)
 *
 * @param null $spbc_key
 *
 * @return array|bool|bool[]|mixed|string[]
 */
function spbc_access_key_notices($spbc_key = null)
{
    global $spbc;

    $spbc_key = $spbc_key ?: $spbc->settings['spbc_key'];

    if ( empty($spbc_key) ) {
        if ( ! $spbc->is_mainsite && $spbc->ms__work_mode != 2 ) {
            $spbc_key = ! empty($spbc->network_settings['spbc_key']) ? $spbc->network_settings['spbc_key'] : false;
            if ( ! $spbc_key ) {
                return array( 'error' => 'KEY_IS_NOT_OK_ON_MAIN_WPMS_SITE' );
            }
        } else {
            $spbc_key =  ! empty($spbc->settings['spbc_key']) ? $spbc->settings['spbc_key'] : false;
            if ( ! $spbc_key ) {
                return array( 'error' => 'KEY_IS_NOT_OK' );
            }
        }
    }

    try {
        spbc_check_account_status($spbc_key);
    } catch (\Exception $exception) {
        return array( 'error' => $exception->getMessage() );
    }

    return true;
}

function spbc_PHP_logs__collect($last_log_sent)
{
    $logs            = array();
    $start_timestamp = time();


    // Try to get log from wp-content/debug/log if default file is not accessible
    $file = ini_get('error_log');
    $file = file_exists($file) && is_readable($file)
        ? $file
        : WP_CONTENT_DIR . '/debug.log';

    if ( file_exists($file) ) {
        if ( is_readable($file) ) {
            // Return if file is empty
            if ( ! filesize($file) ) {
                return array();
            }

            $fd = @fopen($file, 'rb');

            if ( $fd ) {
                $eol = spbc_PHP_logs__detect_EOL_type($file, false);
                if (is_null($eol) || is_int($eol)) {
                    $eol = "\n";
                }

                for (
                    // Initialization
                    $fsize = filesize($file), $offset = 1024 * 5, $position = $fsize - $offset,
                    $max_log_size = 1024 * 1024 * 1, $max_read_size = 1024 * 1024 * 4,
                    $log_size = 0, $read = 0,
                    $log_count = 0;
                    // Conditions
                    $log_size < $max_log_size && // Max usefull data
                    $offset === 1024 * 5 &&        // End of file
                    $read < $max_read_size &&    // Max read depth
                    $log_count < 3500 &&
                    time() < $start_timestamp + 25;
                    // Iteartion adjustments
                    $position -= $offset
                ) {
                    $offset   = $position < 0 ? $offset + $position : $offset;
                    $position = $position < 0 ? 0 : $position;

                    // Set pointer to $it * $offset from the EOF. Or 0 if it's negative.
                    fseek($fd, $position);

                    // Read $offset bytes
                    $it_logs = fread($fd, $offset);

                    // Clean to first EOL, splitting to array by PHP_EOL.
                    if ( $position != 0 ) {
                        $position_adjustment = strpos($it_logs, $eol);
                        $position            += $position_adjustment + 1;
                        $it_logs             = substr($it_logs, $position_adjustment);
                    }

                    $read    += strlen($it_logs);
                    $it_logs = explode($eol, $it_logs);

                    // Filtering and parsing
                    foreach ( $it_logs as $log_line ) {
                        if ( spbc_PHP_logs__filter($log_line, $last_log_sent) ) {
                            $log_size += strlen($log_line);
                            $log_count++;
                            $parsed_log_line = spbc_PHP_logs__parse_line($log_line);
                            if ( $parsed_log_line ) {
                                $logs[] = $parsed_log_line;
                            }
                        }
                    }
                }

                return $logs;
            } else {
                return array( 'error' => 'COULDNT_OPEN_LOG_FILE' );
            }
        } else {
            return array( 'error' => 'LOG_FILE_IS_UNACCESSIBLE' );
        }
    } else {
        return array( 'error' => 'LOG_FILE_NOT_EXISTS' );
    }
}

function spbc_PHP_logs__filter($line, $php_logs_last_sent)
{
    $line = trim($line);

    if ( ! empty($line) ) {
        preg_match('/^\[(.*?\s\d\d:\d\d:\d\d.*?)]/', $line, $matches);
        if ( isset($matches[1]) && strtotime($matches[1]) >= $php_logs_last_sent ) {
            if ( preg_match('/^\[(.*?)\]\s+PHP\s(Warning|Fatal|Notice|Parse)/', $line) ) {
            } else {
                $line = false;
            }
        } else {
            $line = false;
        }
    } else {
        $line = false;
    }

    return $line;
}

function spbc_PHP_logs__parse_line($line)
{
    if ( preg_match('/^\[(.*?)\]\s((.*?):\s+(.+))$/', $line, $matches) ) {
        return array(
            date('Y-m-d H:i:s', strtotime($matches[1])),
            $matches[2],
        );
    }
}

function spbc_PHP_logs__send()
{
    global $spbc;

    if ( empty($spbc->settings['misc__backend_logs_enable']) || empty($spbc->settings['spbc_key']) ) {
        return true;
    }

    $logs = spbc_PHP_logs__collect($spbc->data['last_php_log_sent']);

    if ( empty($logs['error']) ) {
        if ( ! empty($logs) ) {
            $result = SpbcAPI::method__security_backend_logs($spbc->settings['spbc_key'], $logs);

            if ( empty($result['error']) ) {
                if ( isset($result['total_logs_found']) ) {
                    if ( $result['total_logs_found'] == count($logs) ) {
                        $spbc->data['last_php_log_sent']   = time();
                        $spbc->data['last_php_log_amount'] = $result['total_logs_found'];
                        $spbc->save('data');

                        return true;
                    } else {
                        return array( 'error' => 'LOGS_COUNT_DOES_NOT_MATCH' );
                    }
                } else {
                    return array( 'error' => 'LOGS_COUNT_IS_EMPTY' );
                }
            } else {
                return $result;
            }
        } else {
            return true;
        }
    } else {
        return $logs;
    }
}

function spbc_check_ajax_referer($action = - 1, $query_arg = false, $die = true)
{
    $res = true;
    if ( function_exists('check_ajax_referer') ) {
        /** @psalm-suppress ForbiddenCode */
        $res = check_ajax_referer($action, $query_arg, $die);

        if ( $res && ! current_user_can('manage_options') ) {
            if ( $die ) {
                wp_die('-1', 403);
            }
            $res = false;
        }
    }
    return $res;
}

/**
 * Check connection to the API servers
 *
 * @param array $urls_to_test
 *
 * @return array
 */
function spbc_test_connection($urls_to_test = array())
{

    $out          = array();
    $urls_to_test = $urls_to_test ?: array_keys(\CleantalkSP\SpbctWP\Helpers\IP::$cleantalks_servers);

    foreach ( $urls_to_test as $url ) {
        $start  = microtime(true);
        $result = HTTP::getContentFromURL($url);

        $out[ $url ] = array(
            'result'    => ! empty($result['error']) ? $result['error'] : 'OK',
            'exec_time' => microtime(true) - $start,
        );
    }

    return $out;
}

function spbc_sync($direct_call = false)
{

    if ( ! $direct_call ) {
        spbc_check_ajax_referer('spbc_secret_nonce', 'security');
    }

    global $spbc;

    //Clearing all errors
    $spbc->error_delete_all('and_save_data');

    // If key provided by super admin
    if ( $spbc->is_mainsite || $spbc->ms__work_mode != 2 ) {
        // Checking account status
        try {
            spbc_check_account_status($spbc->api_key);
        } catch ( Exception $exception ) {
            $error_text = $exception->getMessage() === 'KEY_IS_NOT_VALID'
                ? sprintf(__('Access key is not valid. Key: %s.', 'security-malware-firewall'), $spbc->settings['spbc_key'])
                : $exception->getMessage();
            $spbc->error_add('apikey', $error_text);
        }
    }

    // Sending logs.
    $result = spbc_send_logs($spbc->api_key);
    if ( empty($result['error']) ) {
        $spbc->data['logs_last_sent']         = current_time('timestamp');
        $spbc->data['last_sent_events_count'] = $result;
        $spbc->error_delete('send_logs');
    } else {
        $spbc->error_add('send_logs', $result);
    }

    // If key provided by super admin
    if ( is_main_site() ) {
        // Updating signtaures
        $result = spbc_scanner__signatures_update();
        empty($result['error'])
            ? $spbc->error_delete('scanner_update_signatures', 'save')
            : $spbc->error_add('scanner_update_signatures', $result);
    }

    $out = array(
        'success' => true,
        'reload'  => $spbc->data['key_changed'],
    );

    // Sending FW logs
    $result = spbc_send_firewall_logs($spbc->api_key);
    if ( empty($result['error']) ) {
        $spbc->fw_stats['last_send']       = current_time('timestamp');
        $spbc->fw_stats['last_send_count'] = $result;
        $spbc->error_delete('send_firewall_logs');
    } else {
        $spbc->error_add('send_firewall_logs', $result);
    }

    $spbc->data['key_changed'] = false;
    $spbc->save('data');
    $spbc->save('fw_stats', true, false);

    // Do async actions after all so data can't be overwrite by sync actions

    // Updating FW
    //Reset last call of update_sec_fw
    $spbc->remote_calls['update_security_firewall']['last_call'] = 0;
    $spbc->save('remote_calls', true, false);

    $result = spbc_security_firewall_update__init();

    if ( ! empty($result['error']) ) {
        $spbc->error_add('firewall_update', $result['error']);
    }

    // Get custom message for security firewall
    $result_service_get = spbct_perform_service_get();
    if ( ! empty($result_service_get['error']) ) {
        if ($result_service_get['error_no'] !== 403) {
            $spbc->error_add('service_customize', $result_service_get['error']);
        }
    }

    if ( $direct_call ) {
        return $out;
    }
    wp_send_json($out);
}

function spbct_perform_service_get()
{
    global $spbc;

    $result_service_get = SpbcAPI::method__service_get(
        $spbc->api_key,
        $spbc->data['user_token']
    );

    if ( empty($result_service_get['error']) ) {
        $spbc->settings['fw__custom_message']          = isset($result_service_get['server_response'])
            ? $result_service_get['server_response']
            : '';
        $spbc->save('settings');
    }

    return $result_service_get;
}

// The functions sends daily reports about attempts to login.
function spbc_send_daily_report($skip_data_rotation = false)
{

    if ( ! function_exists('wp_mail') ) {
        add_action('plugins_loaded', 'spbc_send_daily_report');

        return;
    }

    global $spbc, $wpdb, $spbc_tpl;

    //If key is not ok, send daily report!
    if ( ! $spbc->key_is_ok ) {
        include_once SPBC_PLUGIN_DIR . 'templates/spbc_send_daily_report.php';

        // Hours
        $report_interval = 24 * 7;

        $admin_email = spbc_get_admin_email();
        if ( ! $admin_email ) {
            error_log(
                sprintf(
                    '%s: can\'t send the Daily report because of empty Admin email. File: %s, line %d.',
                    $spbc->data["wl_brandname"],
                    __FILE__,
                    __LINE__
                )
            );

            return false;
        }

        $sql  = sprintf(
            'SELECT id,datetime,user_login,event,auth_ip,page,page_time
			FROM %s WHERE datetime between now() - interval %d hour and now();',
            SPBC_TBL_SECURITY_LOG,
            $report_interval
        );
        $rows = $wpdb->get_results($sql);
        foreach ( $rows as $k => $v ) {
            if ( isset($v->datetime) ) {
                $v->datetime_ts = strtotime($v->datetime);
            }
            $rows[$k] = $v;
        }
        usort($rows, "spbc_usort_desc");

        $record_datetime         = time();
        $events                  = array();
        $auth_failed_events      = array();
        $invalid_username_events = array();
        $auth_failed_count       = 0;
        $invalid_username_count  = 0;
        $ips_data                = '';
        foreach ( $rows as $record ) {
            if ( strtotime($record->datetime) > $record_datetime ) {
                $record_datetime = strtotime($record->datetime);
            }
            $events[ $record->event ][ $record->user_login ][] = array(
                'datetime'   => $record->datetime,
                'auth_ip'    => $record->auth_ip,
                'user_login' => $record->user_login,
                'page'       => $record->page ?: '-',
                'page_time'  => $record->page_time ?: 'Unknown'
            );

            switch ( $record->event ) {
                case 'auth_failed':
                    $auth_failed_events[ $record->user_login ][ $record->auth_ip ] = array(
                        'attempts'   => isset($auth_failed_events[ $record->user_login ][ $record->auth_ip ]['attempts'])
                            ? $auth_failed_events[ $record->user_login ][ $record->auth_ip ]['attempts'] + 1
                            : 1,
                        'auth_ip'    => $record->auth_ip,
                        'user_login' => $record->user_login
                    );
                    $auth_failed_count++;
                    break;
                case 'invalid_username':
                    $invalid_username_events[ $record->user_login ][ $record->auth_ip ] = array(
                        'attempts'   => isset($invalid_username_events[ $record->user_login ][ $record->auth_ip ]['attempts'])
                            ? $invalid_username_events[ $record->user_login ][ $record->auth_ip ]['attempts'] + 1
                            : 1,
                        'auth_ip'    => $record->auth_ip,
                        'user_login' => $record->user_login
                    );
                    $invalid_username_count++;
                    break;
            }
            if ( $ips_data != '' ) {
                $ips_data .= ',';
            }
            $ips_data .= $record->auth_ip;
        }

        $ips_c = spbc_get_countries_by_ips($ips_data);

        $event_part       = '';
        $auth_failed_part = sprintf(
            "<p style=\"color: #666;\">%s</p>",
            _("0 brute force attacks have been made for past day.")
        );
        if ( $auth_failed_count ) {
            foreach ( $auth_failed_events as $e ) {
                $ip_part = '';
                foreach ( $e as $ip ) {
                    $country_part = spbc_report_country_part($ips_c, $ip['auth_ip']);
                    $ip_part      .= sprintf(
                        "<a href=\"https://cleantalk.org/blacklists/%s\">%s</a>, #%d, %s<br />",
                        $ip['auth_ip'],
                        $ip['auth_ip'],
                        $ip['attempts'],
                        $country_part
                    );
                }
                $event_part .= sprintf($spbc_tpl['event_part_tpl'], $ip['user_login'], $ip_part);
            }
            $auth_failed_part = sprintf($spbc_tpl['auth_failed_part'], $event_part);
        }

        $invalid_username_part = sprintf(
            "<p style=\"color: #666;\">%s</p>",
            _('0 brute force attacks have been made for past day.')
        );

        if ( $invalid_username_count ) {
            foreach ( $invalid_username_events as $e ) {
                $ip_part = '';
                foreach ( $e as $ip ) {
                    $country_part = spbc_report_country_part($ips_c, $ip['auth_ip']);
                    $ip_part      .= sprintf(
                        "<a href=\"https://cleantalk.org/blacklists/%s\">%s</a>, #%d, %s<br />",
                        $ip['auth_ip'],
                        $ip['auth_ip'],
                        $ip['attempts'],
                        $country_part
                    );
                }
                $event_part .= sprintf(
                    $spbc_tpl['event_part_tpl'],
                    $ip['user_login'],
                    $ip_part
                );
            }
            $invalid_username_part = sprintf($spbc_tpl['auth_failed_part'], $event_part);
        }

        $logins_part = sprintf(
            "<p style=\"color: #666;\">%s</p>",
            _('0 users have been logged in for past day.')
        );
        if ( isset($events['login']) && count($events['login']) ) {
            $event_part = '';
            foreach ( $events['login'] as $user_login => $e ) {
                $l_part = '';
                foreach ( $e as $e2 ) {
                    $country_part = spbc_report_country_part($ips_c, $e2['auth_ip']);
                    $l_part       .= sprintf(
                        "%s, <a href=\"https://cleantalk.org/blacklists/%s\">%s</a>, %s<br />",
                        date("M d Y H:i:s", strtotime($e2['datetime'])),
                        $e2['auth_ip'],
                        $e2['auth_ip'],
                        $country_part
                    );
                }
                $event_part .= sprintf(
                    $spbc_tpl['event_part_tpl'],
                    $user_login,
                    $l_part
                );
            }
            $logins_part = sprintf(
                $spbc_tpl['logins_part_tpl'],
                $event_part
            );
        }

        $title_main_part = _('Daily security report');
        $subject         = sprintf(
            '%s %s',
            parse_url(get_option('home'), PHP_URL_HOST),
            $title_main_part
        );

        $message_anounce = sprintf(
            _('%s brute force attacks or failed logins, %d successful logins.'),
            number_format($auth_failed_count + $invalid_username_count, 0, ',', ' '),
            isset($events['login']) ? count($events['login']) : 0
        );


        $message = sprintf(
            $spbc_tpl['message_tpl'],
            $spbc_tpl['message_style'],
            $title_main_part,
            $message_anounce,
            $auth_failed_part,
            $invalid_username_part,
            $logins_part,
            $spbc->data["wl_brandname"]
        );


        $headers = array('Content-Type: text/html; charset=UTF-8');
        wp_mail(
            $admin_email,
            $subject,
            $message,
            $headers
        );

        if ( ! $skip_data_rotation ) {
            $sql = sprintf(
                "delete from %s where datetime <= '%s';",
                SPBC_TBL_SECURITY_LOG,
                date("Y-m-d H:i:s", $record_datetime)
            );
            $wpdb->query($sql);
        };
    }

    return null;
}

function spbc_private_list_add()
{
    global $spbc, $current_user;

    $ip = IP::get();

    if ( Cookie::get('spbc_secfw_ip_wl') === md5($ip . $spbc->spbc_key) ) {
        return;
    }

    if ( in_array('administrator', $current_user->roles) ) {
        $res = spbc_private_list_add_api_call($ip);
        if ( $res ) {
            if ( ! headers_sent() ) {
                $cookie_val = md5($ip . $spbc->spbc_key);
                Cookie::set('spbc_secfw_ip_wl', $cookie_val, time() + 86400 * 25, '/', '', false, true);
            }

            // Add to the local database
            $status_for_db = 1;
            $version = IP::validate($ip);
            if ( $version === 'v4' ) {
                $data[] = ip2long($ip) . ',' . ip2long('255.255.255.255') . ',' . $status_for_db;
            } elseif ( $version === 'v6' ) {
                $data[] = $ip . ',' . '128' . ',' . $status_for_db;
            } else {
                wp_send_json_error('Local database: adding IP ' . $ip . ' failed: ip does not look like a valid IP address');
            }
            try {
                $res_local = spbct_sfw_private_records_handler('add', json_encode($data, JSON_FORCE_OBJECT));
                wp_send_json_success($res_local);
            } catch (\Exception $e) {
                wp_send_json_error('Local database: adding IP ' . $ip . ' failed: ' . $e->getMessage());
            }
        }
        wp_send_json_error('API wrong answer.');
    }
}

function spbc_private_list_add_api_call($ip)
{
    global $spbc;
    if ( IP::validate($ip) !== false ) {
        $res = SpbcAPI::method__private_list_add__secfw_wl($spbc->user_token, $ip, $spbc->data['service_id']);

        return isset($res['records'][0]['operation_status']) && $res['records'][0]['operation_status'] === 'SUCCESS';
    }

    return false;
}

/**
 * Cron. Update statuses of files sent to the cloud sandbox.
 */
function spbc_scanner_update_pscan_files_status()
{
    global $wpdb;
    // Reading DB for NEW files
    $undone_files_list = $wpdb->get_results(
        'SELECT fast_hash'
        . ' FROM ' . SPBC_TBL_SCAN_FILES
        . ' WHERE pscan_processing_status <> "DONE" AND pscan_processing_status IS NOT NULL',
        ARRAY_A
    );

    if ( !empty($undone_files_list) ) {
        $files_fast_hashes_to_update = array();
        foreach ( $undone_files_list as $file ) {
            $files_fast_hashes_to_update[] = $file['fast_hash'];
        }
        spbc_scanner_pscan_check_analysis_status(true, $files_fast_hashes_to_update);
    } else {
        \CleantalkSP\SpbctWP\Cron::removeTask('scanner_update_pscan_files_status');
    }
}

/**
 * Cron. Resend files that were not added to the cloud sandbox queue.
 */
function spbc_scanner_resend_pscan_files($do_rescan = true)
{
    global $wpdb;
    // Reading DB for unqueued files
    $unqueued_files_list = $wpdb->get_results(
        'SELECT fast_hash, status'
        . ' FROM ' . SPBC_TBL_SCAN_FILES
        . ' WHERE pscan_pending_queue = 1',
        ARRAY_A
    );

    if ( !empty($unqueued_files_list) ) {
        foreach ( $unqueued_files_list as $file ) {
            //fix for files sent to manual analysis
            if ( !empty($file['status']) && $file['status'] === 'APPROVED_BY_CT') {
                $update_sql =
                    'UPDATE ' . SPBC_TBL_SCAN_FILES
                    . ' SET '
                    . 'pscan_pending_queue = 0 '
                    . 'WHERE fast_hash = "' . $file['fast_hash'] . '"';
                $wpdb->query($update_sql);
                continue;
            }
            spbc_scanner_file_send(true, $file['fast_hash'], $do_rescan);
        }
    } else {
        \CleantalkSP\SpbctWP\Cron::removeTask('scanner_resend_pscan_files');
    }
}

function spbc_check_account_status($api_key)
{
    global $spbc, $plugin_info;

    // Checking account status
    $result = SpbcAPI::method__notice_paid_till(
        $api_key,
        preg_replace('/http[s]?:\/\//', '', get_option('home'), 1), // Site URL
        'security'
    );

    // API returns an error
    if ( ! empty($result['error']) ) {
        $spbc->data['key_is_ok'] = false;
        $spbc->save('data');
        throw new \RuntimeException($result['error']);
    }

    // Key is not valid
    if ( ! $result['valid'] ) {
        $spbc->data['key_is_ok'] = false;
        $spbc->data['notice_trial'] = 0;
        $spbc->save('data');
        throw new \RuntimeException('KEY_IS_NOT_VALID');
    }

    $spbc->data['key_is_ok'] = true;

    if ( isset($result['user_token']) ) {
        $spbc->data['user_token'] = $result['user_token'];
    }
    $spbc->data['notice_show']                   = isset($result['show_notice']) ? $result['show_notice'] : 0;
    $spbc->data['notice_renew']                  = isset($result['renew']) ? $result['renew'] : 0;
    $spbc->data['notice_trial']                  = isset($result['trial']) ? $result['trial'] : 0;
    $spbc->data['notice_review']                 = isset($result['show_review']) ? (int)$result['show_review'] : 0;
    $spbc->data['notice_auto_update']            = isset($result['show_auto_update_notice']) ? $result['show_auto_update_notice'] : 0;
    $spbc->data['service_id']                    = isset($result['service_id']) ? $result['service_id'] : 0;
    $spbc->data['user_id']                       = isset($result['user_id']) ? $result['user_id'] : 0;
    $spbc->data['moderate']                      = isset($result['moderate']) ? $result['moderate'] : 0;
    $spbc->data['auto_update_app ']              = isset($result['auto_update_app']) ? $result['auto_update_app'] : 0;
    $spbc->data['license_trial']                 = isset($result['license_trial']) ? $result['license_trial'] : 0;
    $spbc->data['account_name_ob']               = isset($result['account_name_ob']) ? $result['account_name_ob'] : '';
    $spbc->data['extra_package']['backend_logs'] = isset($result['extra_package']) && is_array($result['extra_package']) && in_array('backend_logs', $result['extra_package'], true)
        ? 1
        : 0;

    //todo:temporary solution for description, until we found the way to transfer this from cloud
    if (defined('SPBC_WHITELABEL_PLUGIN_DESCRIPTION')) {
        $result['wl_plugin_description'] = SPBC_WHITELABEL_PLUGIN_DESCRIPTION;
    }

    //todo:temporary solution for FAQ
    if (defined('SPBC_WHITELABEL_FAQ_LINK')) {
        $result['wl_faq_url'] = SPBC_WHITELABEL_FAQ_LINK;
    }

    if ( $spbc->is_network && $spbc->is_mainsite && $spbc->ms__work_mode == 1 ) {
        $spbc->data['services_count ']      = isset($result['services_count']) ? $result['services_count'] : '';
        $spbc->data['services_max']         = isset($result['services_max']) ? $result['services_max'] : '';
        $spbc->data['services_utilization'] = isset($result['services_utilization']) ? $result['services_utilization'] : '';
    }

    if ( isset($result['wl_status']) && $result['wl_status'] === 'ON' ) {
        $spbc->data['wl_mode_enabled'] = true;
        $spbc->data['wl_brandname']     = isset($result['wl_brandname'])
            ? Sanitize::cleanTextField($result['wl_brandname'])
            : $spbc->default_data['wl_brandname'];
        $spbc->data['wl_url']           = isset($result['wl_url'])
            ? Sanitize::cleanUrl($result['wl_url'])
            : $spbc->default_data['wl_url'];

        if (isset($result['wl_faq_url'])) {
            $spbc->data['wl_support_faq'] = Sanitize::cleanUrl($result['wl_faq_url']);
        } elseif (isset($result['wl_support_url'])) {
            $spbc->data['wl_support_faq'] = Sanitize::cleanUrl($result['wl_support_url']);
        } else {
            $spbc->data['wl_support_faq'] = $spbc->default_data['wl_support_url'];
        }

        $spbc->data['wl_support_url']   = isset($result['wl_support_url'])
            ? Sanitize::cleanUrl($result['wl_support_url'])
            : $spbc->default_data['wl_support_url'];
        $spbc->data['wl_support_email'] = isset($result['wl_support_email'])
            ? Sanitize::cleanEmail($result['wl_support_email'])
            : $spbc->default_data['wl_support_email'];
        $spbc->data['wl_plugin_description']     = isset($result['wl_plugin_description'])
            ? Sanitize::cleanTextField($result['wl_plugin_description'])
            : $plugin_info['Description'];
    } else {
        $spbc->data['wl_mode_enabled'] = false;
        $spbc->data['wl_brandname']     = $spbc->default_data['wl_brandname'];
        $spbc->data['wl_url']           = $spbc->default_data['wl_url'];
        $spbc->data['wl_support_faq']   = $spbc->default_data['wl_support_url'];
        $spbc->data['wl_support_url']   = $spbc->default_data['wl_support_url'];
        $spbc->data['wl_support_email'] = $spbc->default_data['wl_support_email'];
    }

    // Disable/enable the collecting backend PHP log depends on the extra package data
    $spbc->settings['misc__backend_logs_enable'] = (int) $spbc->data['extra_package']['backend_logs'];
    $spbc->save('settings');
    $spbc->save('data');

    if ( SPBC_WPMS ) {
        $spbc->network_settings['moderate']  = $spbc->data['moderate'];
        $spbc->network_settings['key_is_ok'] = $spbc->data['key_is_ok'];
        $spbc->save('network_settings');
    }
}

/**
 * Clears the table with security logs. Leaves only 50 entries.
 */
function spbc_security_log_clear()
{
    global $spbc, $wpdb;

    $remain_ids = array();

    // Getting ids of last 50 rows
    try {
        $ids = $wpdb->get_results(
            "SELECT id
		    FROM " . SPBC_TBL_SECURITY_LOG
            . " WHERE sent=1"
            . ( SPBC_WPMS ? " AND blog_id = " . get_current_blog_id() : '' )
            . " ORDER BY datetime DESC"
            . " LIMIT 50;",
            'ARRAY_N'
        );

        if ($ids) {
            foreach ($ids as $id) {
                $remain_ids[] = $id[0];
            }
        }
    } catch (\Exception $e) {
        return false;
    }

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

    if ( SPBC_WPMS ) {
        $wpdb->query(
            "DELETE FROM " . SPBC_TBL_SECURITY_LOG
            . " WHERE sent = 1
        	AND id NOT IN ("
                . implode(',', $remain_ids) .
            ")"
            . ( $spbc->ms__work_mode == 2 ? '' : ' AND blog_id = ' . get_current_blog_id() )
            . ";"
        );
    } else {
        $wpdb->query(
            "DELETE FROM " . SPBC_TBL_SECURITY_LOG
            . " WHERE sent = 1
        	AND id NOT IN ("
            . implode(',', $remain_ids) .
            ");"
        );
    }

    return true;
}

/**
 * Check whether the request is AMP
 *
 * @return bool
 */
function spbc_is_amp_request()
{
    if (function_exists('amp_is_request')) {
        return amp_is_request();
    }

    return false;
}
