Current File : /home/n742ef5/.trash/wp-content.3/plugins/security-malware-firewall/security-malware-firewall.php
<?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.127
Author URI: https://cleantalk.org
Text Domain: security-malware-firewall
Domain Path: /i18n
*/

use CleantalkSP\SpbctWP\Activator;
use CleantalkSP\SpbctWP\FSWatcher\SpbctWpFSWController as FSWatcherController;
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\Cron as SpbcCron;
use CleantalkSP\SpbctWP\Firewall\WafBlocker;
use CleantalkSP\SpbctWP\HTTP\CDNHeadersChecker;
use CleantalkSP\SpbctWP\RemoteCalls as SpbcRemoteCalls;
use CleantalkSP\SpbctWP\RenameLoginPage;
use CleantalkSP\SpbctWP\Sanitize;
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_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_2FA_KEY_TTL', 600);      // 2fa key lifetime in seconds

// DataBase params
global $wpdb;

define('SPBC_TBL_FIREWALL_DATA', $wpdb->base_prefix . 'spbc_firewall_data');
define('SPBC_TBL_FIREWALL_DATA_V4', SPBC_TBL_FIREWALL_DATA . '_v4');
define('SPBC_TBL_FIREWALL_DATA_V6', SPBC_TBL_FIREWALL_DATA . '_v6');
define('SPBC_TBL_FIREWALL_DATA__IPS', $wpdb->prefix . 'spbc_firewall__personal_ips');
define('SPBC_TBL_FIREWALL_DATA__IPS_V4', SPBC_TBL_FIREWALL_DATA__IPS . '_v4'); // Table with firewall IPS v4
define('SPBC_TBL_FIREWALL_DATA__IPS_V6', SPBC_TBL_FIREWALL_DATA__IPS . '_v6'); // Table with firewall IPS v6
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

// 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()
);

require_once SPBC_PLUGIN_DIR . 'inc/spbc-auth.php';

// 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_upload_checker__check();
        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['monitoring__users'] ) {
    add_action('admin_head', array( '\CleantalkSP\Monitoring\User', 'record' ));
    add_action('wp_head', array( '\CleantalkSP\Monitoring\User', 'record' ));
}

//Password-protected pages also uses wp-login page, we should not break it
if ( $spbc->settings['login_page_rename__enabled'] ) {
    if ( Get::get('action') === 'postpass' ) {
        require ABSPATH . 'wp-includes/pluggable.php';
        require ABSPATH . 'wp-login.php';
    }

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

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

// Hook for newly added blog
Activator::addActionForNetworkBlogLegacy(get_bloginfo('version'));

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 ($spbc->settings['scanner__fs_watcher']) {
    $fswatch_params = array(
        'dir_to_watch' => ABSPATH,
        'exclude_dirs' => array(),
        'extensions_to_watch' => array('php'),
    );
    FSWatcherController::work($fswatch_params);
}

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', array('CleantalkSP\SpbctWP\Activator', 'redirectAfterActivation'), 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);
        }

        // 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, '/');
        }
    }
}

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();
    }
}

/**
 * Wrapper to call UploadChecker logic.
 * @return void
 */
function spbc_upload_checker__check()
{
    global $spbc;
    if ( $spbc->settings['upload_checker__file_check'] && !empty($_FILES) ) {
        $upload_checker = new Firewall\UploadChecker(array(
            'upload_checker__do_check_wordpress_modules' => $spbc->settings['upload_checker__do_check_wordpress_modules'],
            'api_key'                    => $spbc->api_key,
        ));
        $firewall = new Firewall();
        $firewall->loadFwModule($upload_checker);
        $firewall->run();
    }
}

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();

    $secfw_enabled_on_main_site = false;
    if (!is_main_site() && $spbc->network_settings['ms__work_mode'] == 2) {
        $spbc_settings_main_site = get_blog_option(1, 'spbc_settings');
        if ($spbc_settings_main_site['secfw__enabled']) {
            $secfw_enabled_on_main_site = true;
        }
    }

    if ( (int) $spbc->settings['secfw__enabled'] || $secfw_enabled_on_main_site ) {
        $firewall->loadFwModule(
            new FW(
                array(
                    'data_table__personal_countries' => SPBC_TBL_FIREWALL_DATA__COUNTRIES,
                    'log_table'                      => SPBC_TBL_FIREWALL_LOG,
                    'state'                          => $spbc,
                    'api_key'                        => $spbc->api_key,
                )
            )
        );
    }

    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'] ) {
        $waf_params = [
            '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__exploit_check'                => $spbc->settings['waf__exploit_check']
        ];
        if ( $spbc->settings['waf_blocker__enabled'] ) {
            $waf_blocker_params = [
                'is_logged_in' => Cookie::get('spbc_is_logged_in') === md5($spbc->data['salt'] . parse_url(get_option('home'), PHP_URL_HOST)),
                'db' => DB::getInstance(),
                'ip_array' => $firewall->ip_array
            ];
            $waf_blocker = new WafBlocker($waf_blocker_params);
            $waf_params['waf_blocker'] = $waf_blocker;
            $firewall->loadFwModule($waf_blocker);
        }
        $firewall->loadFwModule(new WAF($waf_params));
    }

    //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)
{
    Activator::activation($network, $redirect);
}


/**
 * A code during plugin deactivation.
 *
 * @param $network
 *
 * @return void
 */
function spbc_deactivation($network)
{
    \CleantalkSP\SpbctWP\Deactivator::deactivation($network);
}

/**
 * Run deactivation process (complete deactivation forced) for hook register_uninstall_hook.
 * @param bool $network Is network wide command.
 * @return void
 */
function spbc_uninstall($network)
{
    global $spbc;
    $spbc->settings['misc__complete_deactivation'] = 1;
    $spbc->save('settings');
    \CleantalkSP\SpbctWP\Deactivator::deactivation($network);
}

/**
 * @deprecated 2.125 use Deactivator::deleteBlogTables()
 * @return void
 */
function spbc_deactivation__delete_blog_tables() //deprecated
{
    \CleantalkSP\SpbctWP\Deactivator::deleteBlogTables();
}

/**
 * @deprecated  2.125 use Deactivator::deleteCommonTables()
 * @return void
 */
function spbc_deactivation__delete_common_tables() //deprecated
{
    \CleantalkSP\SpbctWP\Deactivator::deleteCommonTables();
}

// 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');
    }
}

/**
 * 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]);
    }
}

//
// 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;
            }

            //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(),
                $metadata
            );
        } elseif ( $action === 'delete' ) {
            $handler_output = FW::privateRecordsDelete(
                DB::getInstance(),
                $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
 * @deprecated 2.125 Use Deactivator::muPluginUninstall
 * @return bool
 */
function spbc_mu_plugin__uninstall()
{
    return \CleantalkSP\SpbctWP\Deactivator::muPluginUninstall();
}

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;
}

/**
 * Parse CDN checker self-request to find CDN headers.
 * @return array|null[]|string[]
 */
function spbc_cdn_checker__parse_request()
{
    global $spbc;
    if ($spbc->settings['secfw__get_ip__enable_cdn_auto_self_check']) {
        return CDNHeadersChecker::check();
    }
    return array('error' => 'CDN checker disabled');
}

/**
 * Send test request to host. Then it should be parsed to find CDN headers.
 * @return void
 * @psalm-suppress
 */
function spbc_cdn_checker__send_request()
{
    global $spbc;
    if ($spbc->settings['secfw__get_ip__enable_cdn_auto_self_check']) {
        CDNHeadersChecker::sendCDNCheckerRequest();
    }
}