<?php
/**
 * MainWP Child Vulnerability Checker
 *
 * MainWP Vulnerability Checker Extension handler.
 *
 * @link https://mainwp.com/extension/vulnerability-checker/
 *
 * @package MainWP\Child
 *
 * Credits
 *
 * Plugin-Name: Vulnerability Alerts
 * Plugin URI: http://wordpress.org/plugins/vulnerability-alerts/
 * Author: Edir Pedro
 * Author URI: http://edirpedro.com.br
 * License: GPL2
 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
 */

namespace MainWP\Child;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * Class MainWP_Child_Vulnerability_Checker
 *
 * MainWP Vulnerability Checker Extension handler.
 */
class MainWP_Child_Vulnerability_Checker {

    /**
     * Public static variable to hold the single instance of the class.
     *
     * @var mixed Default null
     */
    public static $instance = null;

    /**
     * WPVulnDB API URL
     *
     * @var string
     */
    private $wpvulndb_api = 'https://wpvulndb.com/api/v3/';

    /**
     * Nvd.nist.gov API URL
     *
     * @var string
     */
    private $wpvulndb_nvd_api = 'https://services.nvd.nist.gov/rest/json/cves/2.0';

    /**
     * Wordfence API URL
     *
     * @var string
     */
    private $wordfence_api_url = 'https://www.wordfence.com/api/intelligence/v2/vulnerabilities/';

    /**
     * Method instance()
     *
     * Create a public static instance.
     *
     * @return mixed Class instance.
     */
    public static function instance() {
        if ( null === static::$instance ) {
            static::$instance = new self();
        }
        return static::$instance;
    }

    /**
     * MainWP_Child_Vulnerability_Checker constructor.
     *
     * Run any time class is called.
     */
    public function __construct() {
        $wpvulndb_token = get_option( 'mainwp_child_wpvulndb_token', '' );
        if ( ! empty( $wpvulndb_token ) ) {
            delete_option( 'mainwp_child_wpvulndb_token' ); // old value.
        }
        add_action( 'mainwp_child_cron_plugin_vuln_nvd_continue_check', array( $this, 'cron_nvd_continue_check_plugins' ) );
        add_action( 'mainwp_child_cron_plugin_vuln_nvd_continue_force_check', array( $this, 'cron_nvd_continue_force_check_plugins' ) );
    }

    /**
     * MainWP Child Vulnerability Checker actions.
     *
     * @uses \MainWP\Child\MainWP_Helper::write()
     */
    public function action() {
        $information = array();
        $mwp_action  = MainWP_System::instance()->validate_params( 'mwp_action' );

        if ( 'vulner_recheck' === $mwp_action ) {
            $information = $this->vulner_recheck();
        }

        MainWP_Helper::write( $information );
    }

    /**
     * Check for vulnerabilities.
     *
     * @return array Return an array $information[] containing $results[] array.
     */
    public function vulner_recheck() {
        $result = array();
        // phpcs:disable WordPress.Security.NonceVerification
        $force = ( isset( $_POST['force'] ) && ! empty( $_POST['force'] ) ) ? true : false;

        $service = '';
        if ( isset( $_POST['service'] ) && 'nvd_nist' === $_POST['service'] ) {
            $service = 'nvd_nist';
        } elseif ( isset( $_POST['service'] ) && 'wordfence' === $_POST['service'] ) {
                $service = 'wordfence';
        } else {
            $service = 'wpvulndb';
        }

        if ( 'wpvulndb' === $service ) {
            if ( empty( $_POST['wpvulndb_tk'] ) ) {
                return array( 'error' => 'Error: empty WPScan API token.' );
            }
            $encrypted = MainWP_Child_Keys_Manager::instance()->encrypt_string( sanitize_text_field( wp_unslash( $_POST['wpvulndb_tk'] ) ) );
            set_transient( 'mainwp_child_trans_wpvulndb_tk', $encrypted, 30 * MINUTE_IN_SECONDS );
        } elseif ( ! empty( $_POST['nvd_nist_tk'] ) ) {
                $encrypted = MainWP_Child_Keys_Manager::instance()->encrypt_string( sanitize_text_field( wp_unslash( $_POST['nvd_nist_tk'] ) ) );
                set_transient( 'mainwp_child_trans_nvd_nist_tk', $encrypted, 30 * MINUTE_IN_SECONDS );
        } else {
            delete_transient( 'mainwp_child_trans_nvd_nist_tk' );
        }
        // phpcs:enable

        $result['plugin'] = $this->check_plugins( $force, $service );
        $result['wp']     = $this->check_wp( $force, $service );
        $result['theme']  = $this->check_themes( $force, $service );
        return array(
            'result' => $result,
            'ok'     => 1,
        );
    }


    /**
     * Method is_fixed_version.
     *
     * @param  mixed $version version.
     * @param  mixed $data data.
     * @return bool
     */
    public function is_fixed_version( $version, $data ) {
        if ( ! is_array( $data ) || ! isset( $data['from_version'] ) || ! isset( $data['to_version'] ) || ( '*' === $data['from_version'] && '*' === $data['to_version'] ) ) {
            return false;
        }
        if ( '*' === $data['from_version'] ) {
            $data['from_version'] = '0';
        }
        $fromOperator = $data['from_inclusive'] ? '>=' : '>';
        $toOperator   = $data['to_inclusive'] ? '<=' : '<';
        return version_compare( $version, $data['from_version'], $fromOperator ) && version_compare( $version, $data['to_version'], $toOperator ) ? false : true;
    }


    /**
     * Get vuln wordfence info.
     *
     * @param  mixed $data data.
     * @param  mixed $slug  slug.
     * @param  mixed $version version.
     * @return array
     */
    public function get_vuln_wordfence_info( $data, $slug, $version ) {  //phpcs:ignore -- NOSONAR - complexity.

        $data          = json_decode( $data, true );
        $filtered_data = array();

        if ( is_array( $data ) && isset( $data['result'] ) && isset( $data['result']['CVE_Items'] ) ) {
            $vulns          = array();
            $version_missed = true;
            $fixed          = false;

            foreach ( $data['result']['CVE_Items'] as $item ) {
                $info        = array();
                $remediation = '';
                foreach ( $item['software'] as $software ) {
                    foreach ( $software['affected_versions'] as $affected_version ) {
                        $fixed          = $this->is_fixed_version( $version, $affected_version );
                        $version_missed = false;
                        if ( $fixed ) {
                            break;
                        }
                    }

                    $remediation .= ' [' . ucfirst( $software['type'] ) . '->' . $software['name'] . '] ' . $software['remediation'];
                }

                if ( ! $fixed ) {
                    if ( isset( $item['published'] ) ) {
                        $info['date'] = $item['published'];
                    }

                    if ( isset( $item['description'] ) ) {
                        $info['detail'] = $item['description'] . $remediation;
                    }

                    $customCveId    = explode( '-', $item['id'] );
                    $info['cve_id'] = $item['cve'] ?? end( $customCveId );
                    $info['slug']   = $slug;

                    if ( isset( $item['title'] ) ) {
                        $info['title'] = $item['title'];
                    }
                    if ( isset( $item['cvss'] ) ) {
                        $info['cvss_score']  = $item['cvss']['score'];
                        $info['cvss_rating'] = $item['cvss']['rating'];
                    }
                    if ( isset( $item['software'][0]['patched'] ) ) {
                        $info['patched'] = $item['software'][0]['patched'];
                    }
                    if ( isset( $item['software'][0]['name'] ) ) {
                        $info['software_name'] = $item['software'][0]['name'];
                    }
                }
                if ( $version_missed ) {
                    $info['missed_version'] = 1;
                }
                if ( ! empty( $info ) ) {
                    $vulns[] = $info;
                }
            }
            $filtered_data[ $slug ]['vulnerabilities'] = $vulns;
        }

        return $filtered_data;
    }

    /**
     * Handle continue check for plugin vulnerabilities.
     */
    public function cron_nvd_continue_check_plugins() {
        $this->check_plugins( false, 'nvd_nist' );
    }

    /**
     * Handle continue force check for plugin vulnerabilities.
     */
    public function cron_nvd_continue_force_check_plugins() {
        $this->check_plugins( true, 'nvd_nist' );
    }

    /**
     * Check for plugin vulnerabilities.
     *
     * @param bool $force Whether or not to force check. Default: false.
     * @param bool $service Selected service.
     *
     * @return array Return $result array.
     */
    public function check_plugins( $force = false, $service = '' ) { //phpcs:ignore -- NOSONAR - ignore complex.
        $result = array();

        if ( ! function_exists( '\get_plugins' ) ) {
            require_once ABSPATH . 'wp-admin/includes/plugin.php'; // NOSONAR - ok.
        }
        $all_plugins    = \get_plugins();
        $limit_request  = 0;
        $max_request    = 5;
        $nvd_nist_token = '';

        $nvd_continue_checks = array();

        if ( 'nvd_nist' === $service ) {

            $encrypted      = get_transient( 'mainwp_child_trans_nvd_nist_tk' );
            $nvd_nist_token = MainWP_Child_Keys_Manager::instance()->decrypt_string( $encrypted );

            if ( ! empty( $nvd_nist_token ) ) {
                $max_request = 50;
            } else {
                $nvd_continue_checks = get_option( 'mainwp_child_vuln_nvd_checking_list', '' );
                if ( empty( $nvd_continue_checks ) ) {
                    $nvd_continue_checks = array();
                    foreach ( $all_plugins as $plug => $plugin_info ) {
                        $nvd_continue_checks[ $plug ] = 0; // not checked.
                    }
                    update_option( 'mainwp_child_vuln_nvd_checking_list', wp_json_encode( $nvd_continue_checks ) );
                } else {
                    $nvd_continue_checks = json_decode( $nvd_continue_checks, true );
                }

                if ( ! is_array( $nvd_continue_checks ) ) {
                    $nvd_continue_checks = array();
                }
            }
        }

        if ( ! empty( $all_plugins ) ) {
            foreach ( $all_plugins as $plug => $plugin_info ) {

                $plugin_version = isset( $plugin_info['Version'] ) ? $plugin_info['Version'] : '';
                $plugin_name    = isset( $plugin_info['Name'] ) ? strtolower( $plugin_info['Name'] ) : '';

                if ( empty( $plugin_name ) ) {
                    continue;
                }

                $slug      = str_replace( array( '-', ' ' ), '_', $plugin_name );
                $string    = explode( '/', $plug );
                $plug_vuln = get_transient( 'mainwp_vulnche_trans_plug_' . $string[0] );

                if ( false === $plug_vuln || $force ) {

                    if ( 'nvd_nist' === $service ) {
                        if ( 'wordfence' === strtolower( $string[0] ) ) {
                            continue;
                        }

                        if ( ! empty( $nvd_continue_checks[ $plug ] ) ) {
                            continue;
                        }

                        $nvd_continue_checks[ $plug ] = 1;

                        ++$limit_request;

                        if ( $limit_request >= $max_request ) {
                            if ( ! empty( $nvd_nist_token ) ) {
                                sleep( 30 );
                            } else {
                                break;
                            }
                        }

                        // URL Syntax example: https://services.nvd.nist.gov/rest/json/cves/2.0?virtualMatchString=cpe:2.3:a:automattic:akismet:* .
                        $url = $this->wpvulndb_nvd_api . '?virtualMatchString=cpe:2.3:a:*:' . $slug . ':*:*:*:*:*:wordpress:*:*'; //phpcs:ignore --Misspelled.
                    } elseif ( 'wordfence' === $service ) {
                        $url = $this->wordfence_api_url . 'production?keyword=' . $string[0];
                    } else {
                        $url = $this->wpvulndb_api . 'plugins/' . $string[0];
                    }
                    $plug_vuln = $this->vulnche_get_content( $url, $service );
                    set_transient( 'mainwp_vulnche_trans_plug_' . $string[0], $plug_vuln, 1 * DAY_IN_SECONDS );
                } elseif ( 'nvd_nist' === $service ) {
                    $nvd_continue_checks[ $plug ] = 1;
                }

                if ( $plug_vuln ) {
                    $plug_vuln_filter = array();
                    if ( 'nvd_nist' === $service ) {
                        $plug_vuln_filter = $this->get_vuln_nvd_nist_info( $plug_vuln, $string[0], $plugin_version );
                    } elseif ( 'wordfence' === $service ) {
                        $plug_vuln_filter = $this->get_vuln_wordfence_info( $plug_vuln, $string[0], $plugin_version );
                    } else {
                        $plug_vuln        = json_decode( $plug_vuln, true );
                        $plug_vuln_filter = $plug_vuln;
                        foreach ( $plug_vuln as $slug => $pl_data ) {
                            if ( isset( $pl_data['vulnerabilities'] ) && count( $pl_data['vulnerabilities'] ) > 0 ) {
                                $plug_vulner_data = array();
                                foreach ( $pl_data['vulnerabilities'] as $vuln_data ) {
                                    if ( isset( $vuln_data['fixed_in'] ) && version_compare( $plugin_version, $vuln_data['fixed_in'] ) >= 0 ) {
                                        continue;
                                    }
                                    $plug_vulner_data[] = $vuln_data;
                                }

                                if ( empty( $plug_vulner_data ) ) {
                                    unset( $plug_vuln_filter[ $slug ] );
                                } else {
                                    $plug_vuln_filter[ $slug ]['vulnerabilities']  = $plug_vulner_data;
                                    $plug_vuln_filter[ $slug ]['detected_version'] = $plugin_version;
                                    $plug_vuln_filter[ $slug ]['plugin_slug']      = $plug;
                                }
                            } else {
                                unset( $plug_vuln_filter[ $slug ] );
                            }
                        }
                    }

                    if ( empty( $plug_vuln_filter ) ) {
                        continue;
                    }
                    $plug_vuln = wp_json_encode( $plug_vuln_filter );
                } else {
                    continue;
                }
                $result[ $plug ] = $plug_vuln;
            }

            if ( 'nvd_nist' === $service && empty( $nvd_nist_token ) ) {
                if ( $limit_request < $max_request ) {
                    // finished bulk checks.
                    update_option( 'mainwp_child_vuln_nvd_checking_list', '' ); // clear list.
                } else {
                    update_option( 'mainwp_child_vuln_nvd_checking_list', wp_json_encode( $nvd_continue_checks ) );
                    if ( $force ) {
                        wp_schedule_single_event( time() + 30, 'mainwp_child_cron_plugin_vuln_nvd_continue_force_check' );
                    } else {
                        wp_schedule_single_event( time() + 30, 'mainwp_child_cron_plugin_vuln_nvd_continue_check' );
                    }
                }
            }
        }
        return $result;
    }

    /**
     * Check for WP vulnerabilities.
     *
     * @param bool $force Whether to force check. Default: false.
     * @param bool $service Selected service.
     * @return bool|string|null
     */
    public function check_wp( $force = false, $service = '' ) {
        $wp_vuln = get_transient( 'mainwp_vulnche_trans_wp_json' );
        // Get WordPress version via the shared helper method for consistency.
        // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Using static access for centralized version retrieval
        $wp_ver         = MainWP_Child_Server_Information_Base::get_wordpress_version();
        $number_version = str_replace( '.', '', $wp_ver );
        if ( false === $wp_vuln || $force ) {
            if ( 'nvd_nist' === $service ) {
                $url = $this->wpvulndb_nvd_api . '?virtualMatchString=cpe:2.3:a:wordpress:wordpress:' . $wp_ver; //phpcs:ignore -- Misspelled.
            } elseif ( 'wordfence' === $service ) {
                $url = $this->wordfence_api_url . 'production?keyword=wordpress';//phpcs:ignore -- Misspelled.
            } else {
                $url = $this->wpvulndb_api . 'wordpresses/' . $number_version;
            }
            $wp_vuln = $this->vulnche_get_content( $url, $service );

            if ( 'nvd_nist' === $service ) {
                $wp_vuln = $this->get_vuln_nvd_nist_info( $wp_vuln, 'WordPress', $wp_ver ); //phpcs:ignore -- wordpress.
                $wp_vuln = wp_json_encode( $wp_vuln );
            }

            if ( 'wordfence' === $service ) {
                $wp_vuln = $this->get_vuln_wordfence_info( $wp_vuln, 'WordPress', $wp_ver );
                $wp_vuln = wp_json_encode( $wp_vuln );
            }

            set_transient( 'mainwp_vulnche_trans_wp_json', $wp_vuln, 1 * DAY_IN_SECONDS );
        }
        return $wp_vuln;
    }

    /**
     * Check if themes have vunerabilities.
     *
     * @param bool $force Whether or not to force check. Default: false.
     * @param bool $service Selected service.
     *
     * @return array Return $result array.
     */
    public function check_themes( $force = false, $service = '' ) {  // phpcs:ignore -- NOSONAR - ignore complex method notice.

        include_once ABSPATH . '/wp-admin/includes/update.php'; // NOSONAR -- WP compatible.
        require_once ABSPATH . 'wp-admin/includes/misc.php'; // NOSONAR -- WP compatible.
        require_once ABSPATH . 'wp-admin/includes/theme.php'; // NOSONAR -- WP compatible.

        if ( current_user_can( 'switch_themes' ) ) {
            $themes = wp_prepare_themes_for_js();
        } else {
            $themes = wp_prepare_themes_for_js( array( wp_get_theme() ) );
        }
        wp_reset_vars( array( 'theme', 'search' ) );
        $result = array();
        if ( ! empty( $themes ) ) {
            foreach ( $themes as $th ) {
                if ( empty( $th['parent'] ) ) {
                    $th_vuln = get_transient( 'mainwp_vulnche_trans_theme_' . $th['id'] );
                    if ( false === $th_vuln || $force ) {

                        if ( 'nvd_nist' === $service ) {
                            $url = $this->wpvulndb_nvd_api . '?keywordSearch=' . $th['id'] . '&keywordExactMatch';
                        } elseif ( 'wordfence' === $service ) {
                            $url = $this->wordfence_api_url . 'production?keyword=' . $th['id'];
                        } else {
                            $url = $this->wpvulndb_api . 'themes/' . $th['id'];
                        }

                        $th_vuln = $this->vulnche_get_content( $url, $service );
                        set_transient( 'mainwp_vulnche_trans_theme_' . $th['id'], $th_vuln, 1 * DAY_IN_SECONDS );
                    }

                    if ( $th_vuln ) {
                        if ( 'nvd_nist' === $service ) {
                            $th_vuln_filter = $this->get_vuln_nvd_nist_info( $th_vuln, $th['id'], $th['version'] );
                        } elseif ( 'wordfence' === $service ) {
                            $th_vuln_filter = $this->get_vuln_wordfence_info( $th_vuln, $th['id'], $th['version'] );
                        } else {
                            $th_vuln        = json_decode( $th_vuln, true );
                            $th_vuln_filter = $th_vuln;
                            foreach ( $th_vuln as $slug => $th_data ) {
                                if ( isset( $th_data['vulnerabilities'] ) && count( $th_data['vulnerabilities'] ) > 0 ) {
                                    $th_vulner_data = array();
                                    foreach ( $th_data['vulnerabilities'] as $vuln_data ) {
                                        if ( empty( $vuln_data ) ) {
                                            continue;
                                        }
                                        if ( isset( $vuln_data['fixed_in'] ) && version_compare( $th['version'], $vuln_data['fixed_in'] ) >= 0 ) {
                                            continue;
                                        }
                                        $th_vulner_data[] = $vuln_data;
                                    }
                                    if ( empty( $th_vulner_data ) ) {
                                        unset( $th_vuln_filter[ $slug ] );
                                    } else {
                                        $th_vuln_filter[ $slug ]['vulnerabilities'] = $th_vulner_data;
                                    }
                                } else {
                                    unset( $th_vuln_filter[ $slug ] );
                                }
                            }
                        }

                        if ( empty( $th_vuln_filter ) ) {
                            continue;
                        }
                        $th_vuln = wp_json_encode( $th_vuln_filter );

                    } else {
                        continue;
                    }
                    $result[ $th['id'] ]['vulner_data']      = $th_vuln;
                    $result[ $th['id'] ]['name']             = $th['name'];
                    $result[ $th['id'] ]['author']           = $th['author'];
                    $result[ $th['id'] ]['detected_version'] = $th['version'];
                }
            }
        }
        return $result;
    }

    /**
     * Check if content is vulnerable.
     *
     * @param mixed  $data data of check.
     * @param string $slug slug.
     * @param string $version Current version.
     *
     * @return bool|string|null
     */
    public function get_vuln_nvd_nist_info( $data, $slug, $version ) {  // phpcs:ignore -- NOSONAR - ignore complex method notice.
        $data = ! empty( $data ) ? json_decode( $data, true ) : array();

        $filtered_data = array();
        if ( is_array( $data ) && isset( $data['vulnerabilities'] ) ) {
            $vulns = array();

            foreach ( $data['vulnerabilities'] as $item ) {

                $info = array();

                if ( isset( $item['cve']['published'] ) ) {
                    $info['date'] = $item['cve']['published'];
                }

                if ( isset( $item['cve']['descriptions'][0]['value'] ) ) {
                    $info['detail'] = $item['cve']['descriptions'][0]['value'];
                }

                $version_missed = true;
                if ( ! empty( $info ) ) {
                    $info['cve_id'] = $item['cve']['id'];
                    $info['slug']   = $slug;

                    if ( isset( $item['cve']['metrics']['cvssMetricV31'][0]['cvssData'] ) ) {
                        $cvss                = $item['cve']['metrics']['cvssMetricV31'][0]['cvssData'];
                        $info['cvss_score']  = $cvss['baseScore'];
                        $info['cvss_rating'] = $cvss['baseSeverity'];
                    } elseif ( isset( $item['cve']['metrics']['cvssMetricV30'][0]['cvssData'] ) ) {
                        $cvss                = $item['cve']['metrics']['cvssMetricV30'][0]['cvssData'];
                        $info['cvss_score']  = $cvss['baseScore'];
                        $info['cvss_rating'] = $cvss['baseSeverity'];
                    } elseif ( isset( $item['cve']['metrics']['cvssMetricV2'][0]['cvssData'] ) ) {
                        $cvss                = $item['cve']['metrics']['cvssMetricV2'][0]['cvssData'];
                        $info['cvss_score']  = $cvss['baseScore'];
                        $info['cvss_rating'] = isset( $item['cve']['metrics']['cvssMetricV2'][0]['baseSeverity'] ) ? $item['cve']['metrics']['cvssMetricV2'][0]['baseSeverity'] : '';
                    }

                    $fixed = false;
                    if ( 'wordpress' === $slug ) { //phpcs:ignore -- wordpress.
                        $founded = false;
                        if ( isset( $item['configurations'] ) && isset( $item['configurations'][0]['nodes'] ) ) {
                            foreach ( $item['configurations'][0]['nodes'] as $conf_node ) {
                                if ( isset( $conf_node['cpe_match'] ) ) {
                                    foreach ( $conf_node['cpe_match'] as $cpe_match ) {
                                        if ( ! isset( $cpe_match['cpe23Uri'] ) || false === stripos( $cpe_match['cpe23Uri'], 'cpe:2.3:a:wordpress:wordpress' ) ) { //phpcs:ignore --Misspelled.
                                            continue;
                                        }
                                        $founded = true;
                                        if ( isset( $cpe_match['versionEndExcluding'] ) ) {
                                            $version_end_excluding = $cpe_match['versionEndExcluding'];
                                            if ( version_compare( $version, $version_end_excluding ) >= 0 ) {
                                                $fixed = true; // to continue.
                                            }
                                            $version_missed = false;
                                        } elseif ( isset( $cpe_match['versionEndIncluding'] ) ) {
                                            $version_end_including = $cpe_match['versionEndIncluding'];
                                            if ( version_compare( $version, $version_end_including ) >= 0 ) {
                                                $fixed = true; // to continue.
                                            }
                                            $version_missed = false;
                                        }
                                        if ( $fixed ) {
                                            break;
                                        }
                                    }
                                }

                                if ( ! $fixed && isset( $conf_node['children'] ) ) {
                                    foreach ( $conf_node['children'] as $child_node ) {
                                        if ( isset( $child_node['cpe_match'] ) ) {
                                            foreach ( $child_node['cpe_match'] as $cpe_match ) {
                                                if ( ! isset( $cpe_match['cpe23Uri'] ) || false === stripos( $cpe_match['cpe23Uri'], 'cpe:2.3:a:wordpress:wordpress' ) ) { //phpcs:ignore --Misspelled.
                                                    continue;
                                                }
                                                $founded = true;
                                                if ( isset( $cpe_match['versionEndExcluding'] ) ) {
                                                    $version_end_excluding = $cpe_match['versionEndExcluding'];
                                                    if ( version_compare( $version, $version_end_excluding ) >= 0 ) {
                                                        $fixed = true; // to continue.
                                                    }
                                                    $version_missed = false;
                                                } elseif ( isset( $cpe_match['versionEndIncluding'] ) ) {
                                                    $version_end_including = $cpe_match['versionEndIncluding'];
                                                    if ( version_compare( $version, $version_end_including ) >= 0 ) {
                                                        $fixed = true; // to continue.
                                                    }
                                                    $version_missed = false;
                                                }
                                                if ( $fixed ) {
                                                    break;
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        if ( ! $founded ) {
                            continue;
                        }
                    } elseif ( isset( $item['configurations'] ) && isset( $item['configurations'][0]['nodes'] ) ) {
                        foreach ( $item['configurations'][0]['nodes'] as $conf_node ) {
                            if ( isset( $conf_node['cpe_match'] ) ) {
                                foreach ( $conf_node['cpe_match'] as $cpe_match ) {

                                    if ( isset( $cpe_match['versionEndExcluding'] ) ) {
                                        $version_end_excluding = $cpe_match['versionEndExcluding'];
                                        if ( version_compare( $version, $version_end_excluding ) >= 0 ) {
                                            $fixed = true; // to continue.
                                        }
                                        $version_missed = false;
                                    } elseif ( isset( $cpe_match['versionEndIncluding'] ) ) {
                                        $version_end_including = $cpe_match['versionEndIncluding'];
                                        if ( version_compare( $version, $version_end_including ) >= 0 ) {
                                            $fixed = true; // to continue.
                                        }
                                        $version_missed = false;
                                    }
                                    if ( $fixed ) {
                                        break;
                                    }
                                }
                            }

                            if ( ! $fixed && isset( $conf_node['children'] ) ) {
                                foreach ( $conf_node['children'] as $child_node ) {
                                    if ( isset( $child_node['cpe_match'] ) ) {
                                        foreach ( $child_node['cpe_match'] as $cpe_match ) {
                                            if ( isset( $cpe_match['versionEndExcluding'] ) ) {
                                                $version_end_excluding = $cpe_match['versionEndExcluding'];
                                                if ( version_compare( $version, $version_end_excluding ) >= 0 ) {
                                                    $fixed = true; // to continue.
                                                }
                                                $version_missed = false;
                                            } elseif ( isset( $cpe_match['versionEndIncluding'] ) ) {
                                                $version_end_including = $cpe_match['versionEndIncluding'];
                                                if ( version_compare( $version, $version_end_including ) >= 0 ) {
                                                    $fixed = true; // to continue.
                                                }
                                                $version_missed = false;
                                            }
                                            if ( $fixed ) {
                                                break;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    } elseif ( isset( $item['cve']['configurations'] ) && isset( $item['cve']['configurations'][0]['nodes'] ) ) {
                        // nvd_nist plugins & themes filter.
                        foreach ( $item['cve']['configurations'][0]['nodes'] as $conf_node ) {
                            if ( isset( $conf_node['cpeMatch'] ) ) {
                                foreach ( $conf_node['cpeMatch'] as $cpe_match ) {

                                    if ( isset( $cpe_match['versionEndExcluding'] ) ) {
                                        $version_end_excluding = $cpe_match['versionEndExcluding'];
                                        if ( version_compare( $version, $version_end_excluding ) >= 0 ) {
                                            $fixed = true; // to continue.
                                        }
                                        $version_missed = false;
                                    } elseif ( isset( $cpe_match['versionEndIncluding'] ) ) {
                                        $version_end_including = $cpe_match['versionEndIncluding'];
                                        if ( version_compare( $version, $version_end_including ) >= 0 ) {
                                            $fixed = true; // to continue.
                                        }
                                        $version_missed = false;
                                    }
                                    if ( $fixed ) {
                                        break;
                                    }
                                }
                            }

                            if ( ! $fixed && isset( $conf_node['children'] ) ) {
                                foreach ( $conf_node['children'] as $child_node ) {
                                    if ( isset( $child_node['cpeMatch'] ) ) {
                                        foreach ( $child_node['cpeMatch'] as $cpe_match ) {
                                            if ( isset( $cpe_match['versionEndExcluding'] ) ) {
                                                $version_end_excluding = $cpe_match['versionEndExcluding'];
                                                if ( version_compare( $version, $version_end_excluding ) >= 0 ) {
                                                    $fixed = true; // to continue.
                                                }
                                                $version_missed = false;
                                            } elseif ( isset( $cpe_match['versionEndIncluding'] ) ) {
                                                $version_end_including = $cpe_match['versionEndIncluding'];
                                                if ( version_compare( $version, $version_end_including ) >= 0 ) {
                                                    $fixed = true; // to continue.
                                                }
                                                $version_missed = false;
                                            }
                                            if ( $fixed ) {
                                                break;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }

                    // Plugin was found to be past the vulnerability date.
                    if ( $fixed ) {
                        continue;
                    }
                    if ( $version_missed ) {
                        $info['missed_version'] = 1;
                    }
                    $vulns[] = $info;
                }
            }
            $filtered_data[ $slug ]['vulnerabilities'] = $vulns;
        }

        return $filtered_data;
    }

    /**
     * Check if content is vulnerable.
     *
     * @param string $url URL to check.
     * @param string $service service to check.
     *
     * @return bool|string|null
     */
    public function vulnche_get_content( $url, $service = '' ) { //phpcs:ignore -- NOSONAR - complexity.

        //phpcs:disable WordPress.WP.AlternativeFunctions
        if ( 'wordfence' === $service ) {

            $typeName = explode( '=', $url );
            if ( empty( $this->wordfenceJson ) ) {
                $ch = curl_init();
                curl_setopt( $ch, CURLOPT_URL, $url );
                curl_setopt( $ch, CURLOPT_HEADER, 0 );
                curl_setopt( $ch, CURLOPT_USERAGENT, $this->get_random_user_agent() );
                curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
                curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true );
                $response            = curl_exec( $ch );
                $this->wordfenceJson = json_decode( $response, true );
            }
            $output = array();
            if ( ! empty( $this->wordfenceJson ) && is_array( $this->wordfenceJson ) ) {
                foreach ( $this->wordfenceJson as $feed ) {
                    if ( ! empty( $typeName[1] ) && ! empty( $feed['software'][0]['slug'] ) && $typeName[1] === $feed['software'][0]['slug'] ) {
                        $output[] = $feed;
                    }
                }
            }

            $data = array(
                'resultsPerPage' => count( $output ),
                'startIndex'     => 0,
                'totalResults'   => count( $output ),
                'result'         => array(
                    'CVE_Items' => $output,
                ),
            );

            return json_encode( $data );
        } else {
            $ch = curl_init();
            curl_setopt( $ch, CURLOPT_URL, $url );
            curl_setopt( $ch, CURLOPT_HEADER, 0 );
            if ( 'nvd_nist' !== $service ) {
                $encrypted      = get_transient( 'mainwp_child_trans_wpvulndb_tk' );
                $wpvulndb_token = MainWP_Child_Keys_Manager::instance()->decrypt_string( $encrypted );
                if ( empty( $wpvulndb_token ) ) {
                    if ( 'resource' === gettype( $ch ) ) {
                        curl_close( $ch );
                    }
                    return false;
                }
                curl_setopt( $ch, CURLOPT_HTTPHEADER, array( 'Authorization: Token token=' . $wpvulndb_token ) );
            } else {
                $encrypted      = get_transient( 'mainwp_child_trans_nvd_nist_tk' );
                $nvd_nist_token = MainWP_Child_Keys_Manager::instance()->decrypt_string( $encrypted );

                if ( ! empty( $nvd_nist_token ) ) {
                    curl_setopt(
                        $ch,
                        CURLOPT_HTTPHEADER,
                        array(
                            "apiKey: $nvd_nist_token",
                            'Content-Type: application/json',
                        )
                    );
                }
            }

            curl_setopt( $ch, CURLOPT_USERAGENT, $this->get_random_user_agent() );
            curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
            $output = curl_exec( $ch );
            $info   = curl_getinfo( $ch, CURLINFO_HTTP_CODE );

            if ( 'resource' === gettype( $ch ) ) {
                curl_close( $ch );
            }
            // phpcs:enable WordPress.WP.AlternativeFunctions

            if ( false === $output || 200 !== (int) $info ) {
                $output = null;
            }
            return $output;
        }
    }

    /**
     * Get random useragent.
     *
     * @return string User agent string.
     */
    public function get_random_user_agent() {
        $someUA = array(
            'Mozilla/5.0 (Windows; U; Windows NT 6.0; fr; rv:1.9.1b1) Gecko/20081007 Firefox/3.1b1',
            'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.1) Gecko/2008070208 Firefox/3.0.0',
            'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.19 (KHTML, like Gecko) Chrome/0.4.154.18 Safari/525.19',
            'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/0.2.149.27 Safari/525.13',
            'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30)',
            'Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.40607)',
            'Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.1; .NET CLR 1.1.4322)',
            'Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.1; .NET CLR 1.0.3705; Media Center PC 3.1; Alexa Toolbar; .NET CLR 1.1.4322; .NET CLR 2.0.50727)',
            'Mozilla/45.0 (compatible; MSIE 6.0; Windows NT 5.1)',
            'Mozilla/4.08 (compatible; MSIE 6.0; Windows NT 5.1)',
            'Mozilla/4.01 (compatible; MSIE 6.0; Windows NT 5.1)',
        );
        $i      = wp_rand( 0, count( $someUA ) - 1 );
        return $someUA[ $i ];
    }
}
