plugin_dir = trailingslashit( str_replace( '\\', '/', dirname( WPSCAN_PLUGIN_FILE ) ) );
		$this->plugin_url = site_url( str_replace( str_replace( '\\', '/', ABSPATH ), '', $this->plugin_dir ) );
		// Languages.
		load_plugin_textdomain( 'wpscan', false, $this->plugin_dir . 'languages' );
		// Cache values in memory.
		$this->report                  = get_option( $this->OPT_REPORT, array() );
		$this->ignored_vulnerabilities = get_option( $this->OPT_IGNORED, array() );
		// Actions.
		add_action( 'admin_menu', array( $this, 'menu' ) );
		add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue' ) );
		add_action( 'admin_bar_menu', array( $this, 'admin_bar' ), 65 );
		add_action( $this->WPSCAN_SCHEDULE, array( $this, 'check_now' ) );
		add_action( $this->WPSCAN_RUN_ALL, array( $this, 'check_now' ) );
		add_action( 'in_admin_header', array( $this, 'deactivate_screen' ) );
		if ( defined( 'WPSCAN_API_TOKEN' ) ) {
			add_action( 'admin_init', array( $this, 'api_token_from_constant' ) );
		}
		// Filters.
		add_filter( 'plugin_action_links_' . plugin_basename( WPSCAN_PLUGIN_FILE ), array( $this, 'add_action_links' ) );
		// Micro apps (modules).
		$this->classes['report']                = new Report( $this );
		$this->classes['settings']              = new Settings( $this );
		$this->classes['account']               = new Account( $this );
		$this->classes['notification']          = new Notification( $this );
		$this->classes['summary']               = new Summary( $this );
		$this->classes['ignoreVulnerabilities'] = new ignoreVulnerabilities( $this );
		$this->classes['dashboard']             = new Dashboard( $this );
		$this->classes['sitehealth']            = new SiteHealth( $this );
		$this->classes['checks/system']         = new Checks\System( $this );
	}
	/**
	 * Plugin Loaded
	 *
	 * @return void
	 * @since 1.0.0
	 * @access public
	 */
	public function loaded() {
		load_plugin_textdomain( 'wpscan', false, $this->plugin_dir . 'languages' );
	}
	/**
	 * Activate actions. Runs when the plugin is activated.
	 *
	 * @return void
	 * @since 1.0.0
	 * @access public
	 */
	public function activate() {
		$this->delete_doing_cron_transient();
	}
	/**
	 * Deactivate actions
	 *
	 * @return void
	 * @since 1.0.0
	 * @access public
	 */
	public function deactivate() {
		delete_option( $this->OPT_SCANNING_INTERVAL );
		delete_option( $this->OPT_SCANNING_TIME );
		as_unschedule_all_actions( $this->WPSCAN_SCHEDULE );
	}
	/**
	 * Deactivate screen
	 *
	 * @return void
	 * @since 1.14.0
	 * @access public
	 */
	public function deactivate_screen() {
		global $pagenow;
		if ( 'plugins.php' === $pagenow ) {
			include_once plugin_dir_path( WPSCAN_PLUGIN_FILE ) . 'views/deactivate.php';
		}
	}
	/**
	 * Use the global constant WPSCAN_API_TOKEN if defined.
	 *
	 * @return void
	 * @since 1.0.0
	 * @access public
	 * @example define('WPSCAN_API_TOKEN', 'xxx');
	 *
	 */
	public function api_token_from_constant() {
		if ( get_option( $this->OPT_API_TOKEN ) !== WPSCAN_API_TOKEN ) {
			$sanitize = $this->classes['settings']->sanitize_api_token( WPSCAN_API_TOKEN );
			if ( $sanitize ) {
				update_option( $this->OPT_API_TOKEN, WPSCAN_API_TOKEN );
			} else {
				delete_option( $this->OPT_API_TOKEN );
			}
		}
	}
	/**
	 * Register Admin Scripts
	 *
	 * @return void
	 * @since 1.0.0
	 * @access public
	 */
	public function admin_enqueue( $hook ) {
		global $pagenow;
		$screen = get_current_screen();
		if ( $hook === $this->page_hook || 'dashboard' === $screen->id ) {
			wp_enqueue_style(
				'wpscan',
				plugins_url( 'assets/css/style.css', WPSCAN_PLUGIN_FILE ),
				array(),
				$this->wpscan_plugin_version()
			);
		}
		if ( $hook === $this->page_hook ) {
			wp_enqueue_script( 'common' );
			wp_enqueue_script( 'wp-lists' );
			wp_enqueue_script( 'postbox' );
			wp_enqueue_script(
				'wpscan',
				plugins_url( 'assets/js/scripts.js', WPSCAN_PLUGIN_FILE ),
				array( 'jquery' ),
				$this->wpscan_plugin_version()
			);
			
			
			wp_enqueue_script(
				'wpscan-download-report',
				plugins_url( 'assets/js/download-report.js', WPSCAN_PLUGIN_FILE ),
				array( 'pdfmake' ),
				$this->wpscan_plugin_version()
			);
			
			
			wp_enqueue_script(
				'pdfmake',
				plugins_url( 'assets/vendor/pdfmake/pdfmake.min.js', WPSCAN_PLUGIN_FILE ),
				array( 'wpscan' ),
				$this->wpscan_plugin_version()
			);
			
			wp_enqueue_script(
				'wpscan-download-report-fonts',
				plugins_url( 'assets/vendor/pdfmake/vfs_fonts.js', WPSCAN_PLUGIN_FILE ),
				array( 'wpscan' ),
				$this->wpscan_plugin_version()
			);
			$localized = array(
				'ajaxurl'               => admin_url( 'admin-ajax.php' ),
				'action_check'          => 'wpscan_check_now',
				'action_security_check' => 'wpscan_security_check_now',
				'action_cron'           => $this->WPSCAN_TRANSIENT_CRON,
				'ajax_nonce'            => wp_create_nonce( 'wpscan' ),
				'doing_cron'            => false !== as_next_scheduled_action( $this->WPSCAN_RUN_ALL ) ? 'YES' : 'NO',
				'doing_security_cron'   => get_option( $this->WPSCAN_RUN_SECURITY ),
				'running'               => esc_html__( 'Running', 'wpscan' ),
				'not_running'           => esc_html__( 'Run', 'wpscan' ),
			);
			wp_localize_script( 'wpscan', 'wpscan', $localized );
		}
		if ( 'plugins.php' === $pagenow ) {
			wp_enqueue_style(
				'wpscan-deactivate',
				plugins_url( 'assets/css/deactivate.css', WPSCAN_PLUGIN_FILE ),
				array(),
				$this->wpscan_plugin_version()
			);
			wp_enqueue_script(
				'wpscan-deactivate',
				plugins_url( 'assets/js/deactivate.js', WPSCAN_PLUGIN_FILE ),
				array( 'jquery' ),
				$this->wpscan_plugin_version()
			);
		}
	}
	/**
	 * Get the latest report
	 *
	 * @return array|bool
	 * @since 1.0.0
	 * @access public
	 */
	public function get_report() {
		if ( ! empty( $this->report ) ) {
			return ( $this->report );
		}
		return get_option( $this->OPT_REPORT, array() );
	}
	/**
	 * Get the latest report
	 *
	 * @return array|bool
	 * @since 1.0.0
	 * @access public
	 */
	public function get_ignored_vulnerabilities() {
		if ( ! empty( $this->ignored_vulnerabilities ) ) {
			return ( $this->ignored_vulnerabilities );
		}
		return get_option( $this->OPT_IGNORED, array() );
	}
	/**
	 * Return the total of vulnerabilities found or -1 if errors
	 *
	 * @return int
	 * @since 1.0.0
	 * @access public
	 */
	public function get_total() {
		$report = get_option( $this->OPT_REPORT );
		if ( empty( $report ) ) {
			return 0;
		}
		$total = 0;
		foreach ( array( 'wordpress', 'themes', 'plugins', 'security-checks' ) as $type ) {
			if ( isset( $report[ $type ] ) ) {
				if ( isset( $report[ $type ]['total'] ) ) {
					unset( $report[ $type ]['total'] );
				}
				foreach ( $report[ $type ] as $slug => $item ) {
					$p = $report[ $type ][ $slug ];
					if ( isset( $p['vulnerabilities'] ) && is_array( $p['vulnerabilities'] ) ) {
						$total += count( $p['vulnerabilities'] );
					}
				}
			}
		}
		return $total;
	}
	/**
	 * Return the total of vulnerabilities found but not ignored
	 *
	 * @return int
	 * @since 1.0.0
	 * @access public
	 */
	public function get_total_not_ignored() {
		$report  = $this->get_report();
		$ignored = get_option( $this->OPT_IGNORED, array() );
		$total = $this->get_total();
		$types = array( 'wordpress', 'plugins', 'themes', 'security-checks' );
		foreach ( $types as $type ) {
			if ( isset( $report[ $type ] ) ) {
				foreach ( $report[ $type ] as $item ) {
					if ( empty( $item['vulnerabilities'] ) ) {
						continue;
					}
					foreach ( $item['vulnerabilities'] as $vuln ) {
						$id = 'security-checks' === $type ? $vuln['id'] : $vuln->id;
						if ( in_array( $id, $ignored, true ) ) {
							-- $total;
						}
					}
				}
			}
		}
		return $total;
	}
	/**
	 * Create a menu on Tools section
	 *
	 * @return void
	 * @since 1.0.0
	 * @access public
	 */
	public function menu() {
		$total = $this->get_total_not_ignored();
		$count = $total > 0 ? ' ' . $total . '' : null;
		add_menu_page(
			'WPScan',
			'WPScan' . $count,
			$this->WPSCAN_ROLE,
			'wpscan',
			array( $this->classes['report'], 'page' ),
			$this->plugin_url . 'assets/svg/menu-icon.svg',
			null
		);
	}
	/**
	 * Include a shortcut on Plugins Page
	 *
	 * @param array $links - Array of links provided by the filter
	 *
	 * @access public
	 * @return array
	 * @since 1.0.0
	 */
	public function add_action_links( $links ) {
		$links[] = '' . __( 'View' ) . '';
		return $links;
	}
	/**
	 * Get the WPScan plugin version.
	 *
	 * @return string
	 * @since 1.0.0
	 * @access public
	 */
	public function wpscan_plugin_version() {
		if ( ! function_exists( 'get_plugin_data' ) ) {
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
		}
		return get_plugin_data( $this->plugin_dir . 'wpscan.php' )['Version'];
	}
	/**
	 * Get information from the API
	 *
	 * @return object|int the JSON object or the response code.
	 * @since 1.0.0
	 * @access public
	 */
	public function api_get( $endpoint, $api_token = null ) {
		if ( empty( $api_token ) ) {
			$api_token = get_option( $this->OPT_API_TOKEN );
		}
		// Make sure endpoint starts with a slash.
		if ( substr( $endpoint, 0, 1 ) !== '/' ) {
			$endpoint = '/' . $endpoint;
		}
		$args = array(
			'headers' => array(
				'Authorization' => 'Token token=' . $api_token,
				'user-agent'    => 'WordPress/' . get_bloginfo( 'version' ) . '; ' . home_url() . ' WPScan/' . $this->wpscan_plugin_version(),
			),
		);
		// Hook before the request.
		//do_action( 'wpscan/api/get/before', $endpoint );
		// Start the request.
		$response = wp_remote_get( WPSCAN_API_URL . $endpoint, $args );
		$code     = wp_remote_retrieve_response_code( $response );
		// Hook after the request.
		//do_action( 'wpscan/api/get/after', $endpoint, $response );
		if ( 200 === $code ) {
			return json_decode( wp_remote_retrieve_body( $response ) );
		} else {
			$errors = get_option( $this->OPT_ERRORS, array() );
			switch ( $code ) {
				case 401:
					array_push( $errors, __( 'Your API Token expired', 'wpscan' ) );
					break;
				case 403:
					array_push( $errors, __( 'You have entered an invalid API Token', 'wpscan' ) );
					break;
				case 404:
					// We don't have the plugin/theme, do nothing.
					break;
				case 429:
					array_push( $errors, sprintf( '%s %s.', __( 'You hit our free API usage limit. To increase your daily API limit please upgrade to paid usage from your', 'wpscan' ), WPSCAN_PROFILE_URL, __( 'WPScan profile page', 'wpscan' ) ) );
					break;
				case 500:
					array_push( $errors, sprintf( '%s %s', __( 'There seems to be a problem with the WPScan API. Status: 500. Check the ', 'wpscan' ), WPSCAN_STATUS_URL, __( 'API Status', 'wpscan' ) ) );
					break;
				case 502:
					array_push( $errors, sprintf( '%s %s', __( 'There seems to be a problem with the WPScan API. Status: 502. Check the ', 'wpscan' ), WPSCAN_STATUS_URL, __( 'API Status', 'wpscan' ) ) );
					break;
				case '':
					array_push( $errors, sprintf( '%s %s', __( 'We were unable to connect to the WPScan API. Check the ', 'wpscan' ), WPSCAN_STATUS_URL, __( 'API Status', 'wpscan' ) ) );
					break;
				default:
					array_push( $errors, sprintf( '%s %s.', __( 'We received an unknown response from the API. Status: ' . esc_html( $code ), 'wpscan' ), WPSCAN_STATUS_URL, __( 'Check API Status', 'wpscan' ) ) );
					break;
			}
			// Save the errors.
			update_option( $this->OPT_ERRORS, array_unique( $errors ) );
		}
		return $code;
	}
	/**
	 * Function to start checking right now
	 *
	 * @return void
	 * @since 1.0.0
	 * @access public
	 */
	public function check_now() {
		if ( get_transient( $this->WPSCAN_TRANSIENT_CRON ) || empty( get_option( $this->OPT_API_TOKEN ) ) ) {
			return;
		}
		// Start cron job and set timeout.
		set_transient( $this->WPSCAN_TRANSIENT_CRON, time(), 60 );
		$this->verify();
		delete_transient( $this->WPSCAN_TRANSIENT_CRON );
		// Notify by mail when solicited.
		$this->classes['notification']->notify();
	}
	/**
	 * Function to verify on WpScan Database for vulnerabilities
	 *
	 * @return void
	 * @since 1.0.0
	 * @access public
	 */
	public function verify() {
		$ignored = get_option( $this->OPT_IGNORE_ITEMS );
		$ignored = wp_parse_args(
			$ignored,
			array(
				'plugins' => array(),
				'themes'  => array(),
			)
		);
		// Reset errors.
		update_option( $this->OPT_ERRORS, array() );
		update_option( $this->classes['checks/system']->OPT_FATAL_ERRORS, array() );
		// Plugins.
		$this->report['plugins'] = $this->verify_plugins( $ignored['plugins'] );
		// Themes.
		$this->report['themes'] = $this->verify_themes( $ignored['themes'] );
		// WordPress.
		if ( ! isset( $ignored['wordpress'] ) ) {
			$this->report['wordpress'] = $this->verify_wordpress();
		} else {
			$this->report['wordpress'] = array();
		}
		// Security checks.
		$this->report['security-checks'] = array();
		foreach ( $this->classes['checks/system']->checks as $id => $data ) {
			$data['instance']->perform();
			$this->report['security-checks'][ $id ]['vulnerabilities'] = array();
			if ( $data['instance']->vulnerabilities ) {
				$this->report['security-checks'][ $id ]['vulnerabilities'] = $data['instance']->get_vulnerabilities();
				$this->maybe_fire_issue_found_action( 'security-check', $id, $this->report['security-checks'][ $id ] );
			}
		}
		// Caching.
		$this->report['cache'] = strtotime( current_time( 'mysql' ) );
		// Saving.
		update_option( $this->OPT_REPORT, $this->report, true );
		// Updates account status (API calls etc).
		$this->classes['account']->update_account_status();
	}
	/**
	 * Fires the wpscan_issue_found action if needed
	 *
	 * @param string $type - The affected component type: plugin, theme, WordPress or security-check
	 * @param string $slug - The affected component slug.
	 *                       For WordPress, it will be the version (ie 5.5.3)
	 *                       For security-checks, it will be the id of the check, ie xmlrpc-enabled
	 * @param array $details - An array containing some keys, such as vulnerabilities
	 * @param array additional_details - An array with the plugin details, such as Version etc
	 **@since 1.14.0
	 *
	 */
	public function maybe_fire_issue_found_action( $type, $slug, $details, $additional_details = array() ) {
		if ( ! count( $details['vulnerabilities'] ) > 0 ) {
			return;
		}
		do_action( $this->WPSCAN_ISSUE_FOUND, $type, $slug, $details, $additional_details );
	}
	/**
	 * Check plugins for any known vulnerabilities
	 *
	 * @param array $ignored - An array of plugins slug to ignore
	 *
	 * @access public
	 * @return array
	 * @since 1.0.0
	 *
	 */
	public function verify_plugins( $ignored ) {
		$plugins = array();
		if ( ! function_exists( 'get_plugins' ) ) {
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
		}
		foreach ( get_plugins() as $name => $details ) {
			$slug = $this->get_plugin_slug( $name, $details );
			if ( isset( $ignored[ $slug ] ) ) {
				continue;
			}
			$result = $this->api_get( '/plugins/' . $slug );
			if ( is_object( $result ) ) {
				$plugins[ $slug ]['vulnerabilities'] = $this->get_vulnerabilities( $result, $details['Version'] );
				if ( isset( $result->$slug->closed ) ) {
					$plugins[ $slug ]['closed'] = is_object( $result->$slug->closed ) ? true : false;
				} else {
					$plugins[ $slug ]['closed'] = false;
				}
				$this->maybe_fire_issue_found_action( 'plugin', $slug, $plugins[ $slug ], $details );
			} else {
				if ( 404 === $result ) {
					$plugins[ $slug ]['not_found'] = true;
				}
			}
		}
		return $plugins;
	}
	/**
	 * Check themes for any known vulnerabilities
	 *
	 * @param array $ignored - An array of themes slug to ignore.
	 *
	 * @access public
	 * @return array
	 * @since 1.0.0
	 *
	 */
	public function verify_themes( $ignored ) {
		$themes = array();
		if ( ! function_exists( 'wp_get_themes' ) ) {
			require_once ABSPATH . 'wp-admin/includes/theme.php';
		}
		$filter = array(
			'errors'  => null,
			'allowed' => null,
			'blog_id' => 0,
		);
		foreach ( wp_get_themes( $filter ) as $name => $details ) {
			$slug = $this->get_theme_slug( $name, $details );
			if ( isset( $ignored[ $slug ] ) ) {
				continue;
			}
			$result = $this->api_get( '/themes/' . $slug );
			if ( is_object( $result ) ) {
				$themes[ $slug ]['vulnerabilities'] = $this->get_vulnerabilities( $result, $details['Version'] );
				if ( isset( $result->$slug->closed ) ) {
					$themes[ $slug ]['closed'] = is_object( $result->$slug->closed ) ? true : false;
				} else {
					$themes[ $slug ]['closed'] = false;
				}
				$this->maybe_fire_issue_found_action( 'theme', $slug, $themes[ $slug ], $details );
			} else {
				if ( 404 === $result ) {
					$themes[ $slug ]['not_found'] = true;
				}
			}
		}
		return $themes;
	}
	/**
	 * Check WordPress for any known vulnerabilities.
	 *
	 * @return array
	 * @since 1.0.0
	 * @access public
	 */
	public function verify_wordpress() {
		$wordpress = array();
		$version = get_bloginfo( 'version' );
		$result  = $this->api_get( '/wordpresses/' . str_replace( '.', '', $version ) );
		if ( is_object( $result ) ) {
			$wordpress[ $version ]['vulnerabilities'] = $this->get_vulnerabilities( $result, $version );
			$this->maybe_fire_issue_found_action( 'WordPress', $version, $wordpress[ $version ] );
		}
		return $wordpress;
	}
	/**
	 * Filter vulnerability list from WPScan
	 *
	 * @param array $data - Report data for the element to check.
	 * @param string $version - Installed version.
	 *
	 * @access public
	 * @return string
	 * @since 1.0.0
	 *
	 */
	public function get_vulnerabilities( $data, $version ) {
		$list = array();
		$key  = key( $data );
		if ( empty( $data->$key->vulnerabilities ) ) {
			return $list;
		}
		// Trim and remove potential leading 'v'.
		$version = ltrim( trim( $version ), 'v' );
		foreach ( $data->$key->vulnerabilities as $item ) {
			if ( $item->fixed_in ) {
				if ( version_compare( $version, $item->fixed_in, '<' ) ) {
					$list[] = $item;
				}
			} else {
				$list[] = $item;
			}
		}
		return $list;
	}
	/**
	 * Get vulnerability title.
	 *
	 * @param string $vulnerability - element array.
	 *
	 * @access public
	 * @return string
	 * @since 1.0.0
	 *
	 */
	public function get_sanitized_vulnerability_title( $vulnerability ) {
		$title = esc_html( $vulnerability->title ) . ' - ';
		$title .= empty( $vulnerability->fixed_in )
			? __( 'Not fixed', 'wpscan' )
			: sprintf( __( 'Fixed in version %s', 'wpscan' ), esc_html( $vulnerability->fixed_in ) );
		return $title;
	}
	/**
	 * Get the plugin slug for the given name
	 *
	 * @param string $name - plugin name "folder/file.php" or "hello.php".
	 * @param string $details details.
	 *
	 * @access public
	 * @return string
	 * @since 1.0.0
	 *
	 */
	public function get_plugin_slug( $name, $details ) {
		$name = $this->get_name( $name );
		$name = $this->get_real_slug( $name, $details['PluginURI'] );
		return sanitize_title( $name );
	}
	/**
	 * Get the theme slug for the given name.
	 *
	 * @param string $name - plugin name "folder/file.php" or "hello.php".
	 * @param string $details details.
	 *
	 * @access public
	 * @return string
	 * @since 1.0.0
	 *
	 */
	public function get_theme_slug( $name, $details ) {
		$name = $this->get_name( $name );
		$name = $this->get_real_slug( $name, $details['ThemeURI'] );
		return sanitize_title( $name );
	}
	/**
	 * Get the plugin/theme name
	 *
	 * @param string $name - plugin name "folder/file.php" or "hello.php".
	 *
	 * @access public
	 * @return string
	 * @since 1.0.0
	 *
	 */
	private function get_name( $name ) {
		return strstr( $name, '/' ) ? dirname( $name ) : $name;
	}
	/**
	 * Get plugin/theme slug.
	 *
	 * The name returned by get_plugins or get_themes is not always the real slug.
	 * If the pluginURI is a WordPress url, we take the slug from there.
	 * this also fixes folder renames on plugins if the readme is correct.
	 *
	 * @param string $name - asset name from get_plugins or wp_get_themes.
	 * @param string $url - either the value or ThemeURI or PluginURI.
	 *
	 * @access public
	 * @return string
	 * @since 1.0.0
	 *
	 */
	private function get_real_slug( $name, $url ) {
		$slug  = $name;
		$match = preg_match( '/https?:\/\/wordpress\.org\/(?:extend\/)?(?:plugins|themes)\/([^\/]+)\/?/', $url, $matches );
		if ( 1 === $match ) {
			$slug = $matches[1];
		}
		return sanitize_title( $slug );
	}
	/**
	 * Create a shortcut on Admin Bar to show the total of vulnerabilities found.
	 *
	 * @return void
	 * @since 1.0.0
	 * @access public
	 */
	public function admin_bar( $wp_admin_bar ) {
		if ( ! current_user_can( $this->WPSCAN_ROLE ) ) {
			return;
		}
		$total = $this->get_total_not_ignored();
		if ( $total > 0 ) {
			$args = array(
				'id'    => 'wpscan',
				'title' => '' . $total . '',
				'href'  => admin_url( 'admin.php?page=wpscan' ),
				'meta'  => array(
					'title' => sprintf( _n( '%d vulnerability found', '%d vulnerabilities found', $total, 'wpscan' ), $total ),
				),
			);
			$wp_admin_bar->add_node( $args );
		}
	}
	/**
	 * Check if interval scanning is disabled
	 *
	 * @return bool
	 * @since 1.0.0
	 * @access public
	 * @example define('WPSCAN_DISABLE_SCANNING_INTERVAL', true);
	 *
	 */
	public function is_interval_scanning_disabled() {
		if ( defined( 'WPSCAN_DISABLE_SCANNING_INTERVAL' ) ) {
			return WPSCAN_DISABLE_SCANNING_INTERVAL;
		} else {
			return false;
		}
	}
	/**
	 * Delete doing_cron transient, as they could hang in older versions
	 *
	 * @return void
	 * @since 1.12.2
	 * @access public
	 */
	public function delete_doing_cron_transient() {
		delete_transient( $this->WPSCAN_TRANSIENT_CRON );
	}
}