<?php
/*
* Security Ninja
* Test functions
*/

// phpcs:disable PHPCompatibility.PHP.DeprecatedIniDirectives -- This file intentionally checks deprecated INI settings for security testing

namespace WPSecurityNinja\Plugin;

// this is an include only WP file
if ( ! defined( 'ABSPATH' ) ) {
	die;
}

class Wf_Sn_Tests extends WF_SN {

	/**
	 * Security test definitions keyed by test id.
	 *
	 * @var array<string, array{title: string, score: int}>|null
	 */
	public static $security_tests = null;


	private static function is_local_request() {
		$remote_addr = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_key( $_SERVER['REMOTE_ADDR'] ) : '';
		return in_array( $remote_addr, array( '127.0.0.1', '::1' ), true );
	}


	public static function return_security_tests() {
		return array(
			'ver_check'                 => array(
				'title' => __( 'Check if WordPress core is up to date.', 'security-ninja' ),
				'score' => 5,
			),

			'core_updates_check'        => array(
				'title' => __( 'Check if automatic WordPress core updates are enabled.', 'security-ninja' ),
				'score' => 5,
			),

			// Plugins

			'plugins_ver_check'         => array(
				'title' => __( 'Check if plugins are up to date.', 'security-ninja' ),
				'score' => 5,
			),

			'deactivated_plugins'       => array(
				'title' => __( 'Check if there are deactivated plugins.', 'security-ninja' ),
				'score' => 3,
			),

			'old_plugins'               => array(
				'title' => __( 'Check if active plugins have been updated in the last 12 months.', 'security-ninja' ),
				'score' => 3,
			),

			'incompatible_plugins'      => array(
				'title' => __( 'Check if active plugins are compatible with your version of WP.', 'security-ninja' ),
				'score' => 3,
			),

			// themes

			'themes_ver_check'          => array(
				'title' => __( 'Check if themes are up to date.', 'security-ninja' ),
				'score' => 5,
			),

			'deactivated_themes'        => array(
				'title' => __( 'Check if there are unnecessary themes.', 'security-ninja' ),
				'score' => 3,
			),

			// WP header
			'wp_header_meta'            => array(
				'title' => __( 'Check if full WordPress version info is revealed in page\'s meta data.', 'security-ninja' ),
				'score' => 3,
			),

			'wlw_meta'                  => array(
				'title' => __( 'Check if Windows Live Writer link is present in the header data.', 'security-ninja' ),
				'score' => 1,
			),

			// server related
			'php_ver'                   => array(
				'title' => __( 'Check the PHP version.', 'security-ninja' ),
				'score' => 4,
			),

			// database
			'mysql_ver'                 => array(
				'title' => __( 'Check the MySQL version.', 'security-ninja' ),
				'score' => 4,
			),

			'db_table_prefix_check'     => array(
				'title' => __( 'Check if database table prefix is the default one (wp_).', 'security-ninja' ),
				'score' => 3,
			),

			// server headers
			'php_headers'               => array(
				'title' => __( 'Check if server response headers contain detailed PHP version info.', 'security-ninja' ),
				'score' => 2,
			),

			'expose_php_check'          => array(
				'title' => __( 'Check if expose_php PHP directive is turned off.', 'security-ninja' ),
				'score' => 1,
			),

			// users
			'user_exists'               => array(
				'title' => __( 'Check if user with username "admin" exists.', 'security-ninja' ),
				'score' => 4,
			),

			'anyone_can_register'       => array(
				'title' => __( 'Check if "anyone can register" option is enabled.', 'security-ninja' ),
				'score' => 3,
			),

			'id1_user_check'            => array(
				'title' => __( 'Check if user with ID "1" exists.', 'security-ninja' ),
				'score' => 1,
			),

			// login
			'check_failed_login_info'   => array(
				'title' => __( 'Check for display of unnecessary information on failed login attempts.', 'security-ninja' ),
				'score' => 3,
			),

			// wp-config file checks
			'config_chmod'              => array(
				'title' => __( 'Check if wp-config.php file has the right permissions (chmod) set.', 'security-ninja' ),
				'score' => 5,
			),

			'config_location'           => array(
				'title' => __( 'Check if wp-config.php is present on the default location.', 'security-ninja' ),
				'score' => 2,
			),

			'db_password_check'         => array(
				'title' => __( 'Check the strength of WordPress database password.', 'security-ninja' ),
				'score' => 5,
			),

			'salt_keys_check'           => array(
				'title' => __( 'Check if security keys and salts have proper values.', 'security-ninja' ),
				'score' => 3,
			),

			'salt_keys_age_check'       => array(
				'title' => __( 'Check the age of security keys and salts.', 'security-ninja' ),
				'score' => 1,
			),

			'debug_check'               => array(
				'title' => __( 'Check if general debug mode is enabled.', 'security-ninja' ),
				'score' => 4,
			),

			'debug_log_file_check'      => array(
				'title' => __( 'Check if WordPress debug.log file exists.', 'security-ninja' ),
				'score' => 4,
			),

			'db_debug_check'            => array(
				'title' => __( 'Check if database debug mode is enabled.', 'security-ninja' ),
				'score' => 4,
			),

			'script_debug_check'        => array(
				'title' => __( 'Check if JavaScript debug mode is enabled.', 'security-ninja' ),
				'score' => 4,
			),

			'display_errors_check'      => array(
				'title' => __( 'Check if display_errors PHP directive is turned off.', 'security-ninja' ),
				'score' => 4,
			),

			'blog_site_url_check'       => array(
				'title' => __( 'Check if WordPress installation address is the same as the site address.', 'security-ninja' ),
				'score' => 2,
			),

			// server settings and PHP checks
			'register_globals_check'    => array(
				'title' => __( 'Check if register_globals PHP directive is turned off.', 'security-ninja' ),
				'score' => 5,
			),

			'safe_mode_check'           => array(
				'title' => __( 'Check if PHP safe mode is disabled.', 'security-ninja' ),
				'score' => 5,
			),

			'allow_url_include_check'   => array(
				'title' => __( 'Check if allow_url_include PHP directive is turned off.', 'security-ninja' ),
				'score' => 5,
			),

			// WordPress features
			'file_editor'               => array(
				'title' => __( 'Check if plugins/themes file editor is enabled.', 'security-ninja' ),
				'score' => 2,
			),

			'uploads_browsable'         => array(
				'title' => __( 'Check if uploads folder is browsable by browsers.', 'security-ninja' ),
				'score' => 2,
			),

			'application_passwords'     => array(
				'title' => __( 'Check if Application Passwords are enabled.', 'security-ninja' ),
				'score' => 2,
			),

			'mysql_external'            => array(
				'title' => __( 'Check if MySQL server is connectable from outside with the WP user.', 'security-ninja' ),
				'score' => 2,
			),

			'rpc_meta'                  => array(
				'title' => __( 'Check if EditURI (XML-RPC) link is present in the header data.', 'security-ninja' ),
				'score' => 1,
			),

			'tim_thumb'                 => array(
				'title' => __( 'Check if Timthumb script is used in the active theme.', 'security-ninja' ),
				'score' => 5,
			),

			'shellshock_6271'           => array(
				'title' => __( 'Check if the server is vulnerable to the Shellshock bug #6271.', 'security-ninja' ),
				'score' => 4,
			),

			'shellshock_7169'           => array(
				'title' => __( 'Check if the server is vulnerable to the Shellshock bug #7169.', 'security-ninja' ),
				'score' => 4,
			),

			'admin_ssl'                 => array(
				'title' => __( 'Check if admin interface is delivered via SSL', 'security-ninja' ),
				'score' => 3,
			),

			'mysql_permissions'         => array(
				'title' => __( 'Check if MySQL account used by WordPress has too many permissions', 'security-ninja' ),
				'score' => 2,
			),

			'usernames_enumeration'     => array(
				'title' => __( 'Check if the list of usernames can be fetched by looping through user IDs', 'security-ninja' ),
				'score' => 3,
			),

			'rest_api_links'            => array(
				'title' => __( 'Check if the REST API links are shown in code', 'security-ninja' ),
				'score' => 2,
			),

			'x_content_type_options'    => array(
				'title' => __( 'Check if server response headers contain X-Content-Type-Options.', 'security-ninja' ),
				'score' => 4,
			),

			'x_frame_options'           => array(
				'title' => __( 'Check if server response headers contain X-Frame-Options.', 'security-ninja' ),
				'score' => 4,
			),

			'strict_transport_security' => array(
				'title' => __( 'Check if server response headers contain Strict-Transport-Security.', 'security-ninja' ),
				'score' => 4,
			),

			'referrer_policy'           => array(
				'title' => __( 'Check if server response headers contain Referrer-Policy.', 'security-ninja' ),
				'score' => 4,
			),

			'feature_policy'            => array(
				'title' => __( 'Check if server response headers contain Permissions-Policy.', 'security-ninja' ),
				'score' => 4,
			),

			'content_security_policy'   => array(
				'title' => __( 'Check if server response headers contain Content-Security-Policy.', 'security-ninja' ),
				'score' => 4,
			),

			'rest_api_enabled'          => array(
				'title' => __( 'Check if the REST API is enabled.', 'security-ninja' ),
				'score' => 2,
			),

			'dangerous_files'           => array(
				'title' => __( 'Check for unwanted files in your root folder you should remove.', 'security-ninja' ),
				'score' => 3,

			),
		);
	}

	/**
	 * rest_api_enabled.
	 *
	 * @author  Lars Koudal
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0    Monday, January 25th, 2021.
	 * @version v1.0.1    Wednesday, December 20th, 2023.
	 * @access  public static
	 * @return  void
	 */
	public static function rest_api_enabled() {
		$return = array();

		$url      = get_rest_url();
		$is_local = self::is_local_request();

		// Check if the REST API URL was retrieved successfully
		if ( ! $url ) {
			return array(
				'status'    => 10,
				'msg'       => __( 'REST API URL not found.', 'security-ninja' ),
				'sslverify' => ! $is_local,

			);
		}

		$response = wp_remote_get( $url, array( 'redirection' => 0 ) );

		// Check if the request was successful
		if ( is_wp_error( $response ) ) {
			// Could not reach the endpoint (e.g. timeout). Treat as inconclusive (Warning) rather than assuming it is not accessible.
			return array(
				'status' => 5,
				'msg'    => sprintf( esc_html__( 'Could not determine REST API accessibility. Error: %s', 'security-ninja' ), esc_html( $response->get_error_message() ) ),
			);
		}

		// Check the response code
		$response_code = wp_remote_retrieve_response_code( $response );
		if ( 200 === $response_code ) {
			return array(
				'status' => 5,
				'msg'    => __( 'REST API is accessible.', 'security-ninja' ),
			);
		} else {
			return array(
				'status' => 10,
				/* translators: %s: HTTP response code */
				'msg'    => sprintf( __( 'REST API is not accessible. Response Code: %s', 'security-ninja' ), esc_html( $response_code ) ),
			);
		}
	}


	/**
	 * Scan for dangerous files in root
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @access  public static
	 * @return  mixed
	 */
	public static function dangerous_files() {

		$return = array();

		$dangerous_files  = array(
			'wp-config.php.old'      => __( 'Common name for config file backup - can contain critical information', 'security-ninja' ),
			'wp-config.php_bak'      => __( 'Common name for config file backup - can contain critical information', 'security-ninja' ),
			'wp-config.php~'         => __( 'Common name for config file backup - can contain critical information', 'security-ninja' ),
			'wp-config.php-'         => __( 'Common name for config file backup - can contain critical information', 'security-ninja' ),
			'wp-config.php--'        => __( 'Common name for config file backup - can contain critical information', 'security-ninja' ),
			'wp-config.php---'       => __( 'Common name for config file backup - can contain critical information', 'security-ninja' ),
			'wp-config.php.bkp'      => __( 'Common name for config file backup - can contain critical information', 'security-ninja' ),
			'wp-config.php_revision' => __( 'Common name for config file backup - can contain critical information', 'security-ninja' ),
			'php_errorlog'           => __( 'Can contain server details or errors that can be exploited.', 'security-ninja' ),
			'php_mail.log'           => __( 'Can contain user details or errors that can be exploited.', 'security-ninja' ),
			'.htaccess.sg'           => __( '.htaccess backup files on SiteGround - Can show server details or configurations that should not be public.', 'security-ninja' ),
			'.htaccess_swift_backup' => __( '.htaccess backup file by Swift Performance - Can show server details or configurations that should not be public.', 'security-ninja' ),
			'*.sql'                  => __( '.sql files should not be kept on your server - they may contain sensitive data.', 'security-ninja' ),
			'phpinfo.php'            => __( 'Displays all details about PHP on your website, should only exist briefly during development.', 'security-ninja' ),
			'info.php'               => __( 'Should only exist briefly during development and not on a live site.', 'security-ninja' ),
			'test.php'               => __( 'Should only exist briefly during development and not on a live site.', 'security-ninja' ),
			'*.bak'                  => __( 'Copies of old files could contain important info about your server.', 'security-ninja' ),
		);
		$return['status'] = 10;
		$return['msg']    = '<dl>';

		foreach ( $dangerous_files as $key => $explanation ) {
			// If its a wildcard search
			if ( false !== strpos( $key, '*.' ) ) {
				$files = glob( ABSPATH . $key );
				if ( ( is_array( $files ) ) && ( count( $files ) > 0 ) ) {
					foreach ( $files as $f ) {
						$display_name   = str_replace( ABSPATH, '', $f );
						$return['msg'] .= '<dt><strong>' . $display_name . '</strong></dt><dd>' . $explanation . '</dd>';
					}
					$return['status'] = 0;
				}
			} else {
				$check = file_exists( ABSPATH . $key );
				if ( $check ) {
					$return['msg']   .= '<dt><strong>' . $key . '</strong></dt><dd>' . $explanation . '</dd>';
					$return['status'] = 0;
				}
			}
		}

		$return['msg'] .= '</dl>';

		return $return;
	}

	/**
	 * Checks if the website is SSL or not
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 8th, 2021.
	 * @access  public static
	 * @return  mixed
	 */
	public static function check_server_ssl() {
		$is_https = isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] === 'on'
			|| isset( $_SERVER['REQUEST_SCHEME'] ) && $_SERVER['REQUEST_SCHEME'] === 'https'
			|| isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && strtolower( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) === 'https';

		if ( $is_https ) {
			return array(
				'status' => 10,
				'msg'    => __( 'The server is using HTTPS. This is the recommended configuration.', 'security-ninja' ),
			);
		} else {
			return array(
				'status' => 0,
				'msg'    => __( 'The server is not using HTTPS. It is recommended to use HTTPS to enhance security.', 'security-ninja' ),
			);
		}
	}

	/**
	 * Checks if admin is using SSL
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @access  public static
	 * @return  mixed
	 */
	public static function admin_ssl() {
		$return = array();

		if ( false === stripos( get_admin_url(), 'https' ) ) {
			$return['status'] = 0;
			$return['msg']    = __( 'You should set your Settings -> General URLs to start with https://', 'security-ninja' );
		} else {
			$return['status'] = 10;
			$return['msg']    = __( 'Admin URLs are set to start with https.', 'security-ninja' );
		}

		$force_ssl_admin = force_ssl_admin();
		if ( ! $force_ssl_admin ) {
			$return['status'] = 0;
			$return['msg']    = __( 'Admin pages are not secured by SSL. It is recommended to force SSL in admin pages.', 'security-ninja' );
		} elseif ( $return['status'] !== 0 ) {
			// Only update the message if the previous check didn't already set the status to 0
			$return['status'] = 10;
			$return['msg']    = __( 'Great, admin pages are secured by SSL.', 'security-ninja' );
		}

		return $return;
	}


	/**
	 * check if Timthumb is used
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @access  public static
	 * @return  mixed
	 */
	public static function tim_thumb() {
		$return             = array();
		$theme              = wp_get_theme();
		$theme_name_version = $theme->get( 'Name' ) . ' v' . $theme->get( 'Version' ); // Use get() method for properties
		$scan_result        = self::tim_thumb_scan( get_theme_root() );
		if ( is_wp_error( $scan_result ) ) {
			$return['status'] = 5; // Indicate an error
			$return['msg']    = __( 'An error occurred during the TimThumb scan.', 'security-ninja' );
		} elseif ( $scan_result && isset( $scan_result['status'] ) && 0 === $scan_result['status'] ) {
			$return['status'] = 0; // Indicate a potential issue
			/* translators: %s: theme name and version */
			$return['msg'] = sprintf( __( 'TimThumb file found in theme: %s. Please remove or replace it.', 'security-ninja' ), $theme_name_version );
		} else {
			$return['status'] = 10; // No issue found
			/* translators: %s: theme name and version */
			$return['msg'] = sprintf( __( 'No TimThumb file found in theme: %s. Your theme is safe.', 'security-ninja' ), $theme_name_version );
		}

		return $return;
	}



	/**
	 * scan all PHP files and look for timtumb script
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @version v1.0.1  Saturday, November 18th, 2023.
	 * @access  public static
	 * @param   mixed $path
	 * @return  array
	 */
	public static function tim_thumb_scan( $path ) {
		global $wp_filesystem;

		// Setup the WordPress filesystem, if not already set up
		if ( empty( $wp_filesystem ) ) {
			include_once ABSPATH . '/wp-admin/includes/file.php';
			WP_Filesystem();
		}

		// Array to hold all the PHP files we want to scan
		$php_files = array();

		// Scan the directory for PHP files
		$files = $wp_filesystem->dirlist( $path );
		if ( ! $files ) {
			return array(
				'status' => 5,
				'msg'    => __( 'Failed to list directory contents.', 'security-ninja' ),
			);
		}

		foreach ( $files as $file => $fileinfo ) {
			if ( 'php' === pathinfo( $file, PATHINFO_EXTENSION ) ) {
				$php_files[] = trailingslashit( $path ) . $file;
			}

			if ( 'd' === $fileinfo['type'] ) {
				$sub_files = $wp_filesystem->dirlist( trailingslashit( $path ) . $file );
				foreach ( $sub_files as $sub_file => $sub_fileinfo ) {
					if ( 'php' === pathinfo( $sub_file, PATHINFO_EXTENSION ) ) {
						$php_files[] = trailingslashit( $path ) . trailingslashit( $file ) . $sub_file;
					}
				}
			}
		}

		foreach ( $php_files as $php_file ) {
			$content = $wp_filesystem->get_contents( $php_file );
			if ( false === $content ) {
				return array(
					'status' => 5,
					'msg'    => __( 'Failed to read file contents.', 'security-ninja' ),
				);
			}

			// Define multiple unique identifiers for TimThumb
			$timThumbIdentifiers = array(
				'TimThumb script created by Tim McDaniels',
				'CACHE_MAX_FILE_AGE', // Example of a unique constant in TimThumb
				'TIMTHUMB_VERSION',   // Example of a unique version identifier
			);

			// Check for the presence of all unique identifiers
			$foundIdentifiers = 0;
			foreach ( $timThumbIdentifiers as $identifier ) {
				if ( false !== stripos( $content, $identifier ) ) {
					++$foundIdentifiers;
				}
			}

			// If multiple identifiers are found, it's likely a TimThumb script
			if ( $foundIdentifiers >= 2 ) { // Adjust the threshold as needed
				return array(
					'status' => 0,
					'msg'    => __( 'TimThumb script detected.', 'security-ninja' ),
				);
			}
		}

		return array(
			'status' => 10,
			'msg'    => __( 'No TimThumb scripts detected.', 'security-ninja' ),
		);
	}





	/**
	 * check if user with DB ID 1 exists
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @access  public static
	 * @return  mixed
	 */
	public static function id1_user_check() {
		$return = array();

		$user_data = get_userdata( 1 );
		if ( $user_data ) {
			$return['status'] = 0;
			/* translators: %s: username of the user with ID 1 */
			$return['msg'] = sprintf( __( 'The user with ID 1 exists, and the username is %s.', 'security-ninja' ), esc_html( $user_data->user_login ) );
		} else {
			$return['status'] = 10;
			$return['msg']    = __( 'No user exists with ID 1.', 'security-ninja' );
		}

		return $return;
	}




	/**
	 * check if wp-config is present on the default location
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @access  public static
	 * @return  mixed
	 */
	public static function config_location() {
		$return = array();

		// Check one level up (recommended security practice)
		$parent_dir  = dirname( ABSPATH );
		$parent_file = $parent_dir . '/wp-config.php';

		// Check if the path is within open_basedir restrictions before checking file_exists
		$parent_check = false;
		if ( function_exists( 'ini_get' ) ) {
			$open_basedir = ini_get( 'open_basedir' );
			if ( empty( $open_basedir ) ) {
				// No restrictions, safe to check
				$parent_check = file_exists( $parent_file );
			} else {
				// Check if the path is within allowed directories
				$allowed_paths          = explode( PATH_SEPARATOR, $open_basedir );
				$is_allowed             = false;
				$parent_file_normalized = wp_normalize_path( $parent_file );
				foreach ( $allowed_paths as $allowed_path ) {
					$allowed_path_normalized = wp_normalize_path( rtrim( $allowed_path, DIRECTORY_SEPARATOR ) );
					// Check if parent_file starts with allowed_path (path is within allowed directory)
					if ( strpos( $parent_file_normalized, $allowed_path_normalized ) === 0 ) {
						$is_allowed = true;
						break;
					}
				}
				if ( $is_allowed ) {
					$parent_check = @file_exists( $parent_file );
				}
				// If not allowed, $parent_check remains false (no file check attempted)
			}
		} else {
			// Fallback if ini_get is not available - use error suppression to avoid warnings
			$parent_check = @file_exists( $parent_file );
		}

		if ( $parent_check ) {
			// Found one level up - this is the best practice
			$return['status'] = 0;
			$return['msg']    = __( 'wp-config.php file found one level up from WordPress root directory. This is a great security improvement!', 'security-ninja' );
			/* translators: %s: path to wp-config.php */
			$return['details'] = sprintf( __( 'Found file here: %s', 'security-ninja' ), esc_html( $parent_dir ) );
		} else {
			// Found in default location - warning level 5
			// Note: wp-config.php must exist somewhere for WordPress to run, so if not found one level up, it's in the default location
			$return['status'] = 5;
			$return['msg']    = __( 'wp-config.php file found in the WordPress root directory. Consider moving it one level up for better security.', 'security-ninja' );
			/* translators: %s: path to WordPress root directory */
			$return['details'] = sprintf( __( 'Found file here: %s', 'security-ninja' ), esc_html( ABSPATH ) );
		}

		return $return;
	}



	/**
	 * check if the WP MySQL user can connect from an external host
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @access  public static
	 * @return  mixed
	 */
	public static function mysql_external() {
		$return = array();
		global $wpdb;

		$current_user = $wpdb->get_var( 'SELECT CURRENT_USER();' );
		if ( false === $current_user ) {
			$return['status'] = 5;
			$return['msg']    = __( 'Unable to determine the MySQL current user.', 'security-ninja' );
		} elseif ( strpos( $current_user, '@%' ) !== false ) {
			$return['status'] = 0;
			$return['msg']    = __( 'MySQL current user can connect from any host, which might be a security risk.', 'security-ninja' );
		} elseif ( strpos( $current_user, '@127.0.0.1' ) !== false || stripos( $current_user, '@localhost' ) !== false ) {
			$return['status'] = 10;
			$return['msg']    = __( 'MySQL current user is restricted to localhost, which is a secure configuration.', 'security-ninja' );
		} else {
			$return['status'] = 5;
			/* translators: %s: MySQL current user host value */
			$return['msg'] = sprintf( __( 'Unexpected MySQL current user host: %s', 'security-ninja' ), esc_html( $current_user ) );
		}

		return $return;
	}





	/**
	 * check if the WP MySQL user has too many permissions granted
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @access  public static
	 * @return  mixed
	 */
	public static function mysql_permissions() {
		$return = array(
			'status' => 10,
			'msg'    => __( 'Database permissions are set correctly.', 'security-ninja' ),
		);

		global $wpdb;

		$grants = $wpdb->get_results( 'SHOW GRANTS', ARRAY_N );

		if ( is_wp_error( $grants ) ) {
			$return['status'] = 5;
			$return['msg']    = __( 'Failed to retrieve database permissions.', 'security-ninja' );
			return $return;
		}

		foreach ( $grants as $grant ) {
			if ( false !== stripos( $grant[0], 'GRANT ALL PRIVILEGES' ) ) {
				$return['status'] = 5;
				$return['msg']    = __( 'Database permissions are too permissive.', 'security-ninja' );
				break;
			}
		}

		return $return;
	}





	/**
	 * check if WLW link is present in header
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function wlw_meta() {
		$return   = array();
		$is_local = self::is_local_request();

		$response = wp_remote_get(
			add_query_arg( 'secnin-test', wp_rand(), get_home_url() ),
			array(
				'timeout'     => 25,
				'redirection' => 2,
				'sslverify'   => ! $is_local,
			)
		);
		if ( is_wp_error( $response ) ) {
			$return['status'] = 5;
			$return['msg']    = __( 'Failed to retrieve the home page for analysis.', 'security-ninja' );
		} else {
			$html = wp_remote_retrieve_body( $response );

			if ( ! empty( $html ) ) {
				$return['status'] = 10;
				$start            = strpos( $html, '<head' );
				$end              = strpos( $html, '</head>', $start );
				$head_content     = substr( $html, $start, $end - $start + strlen( '</head>' ) ); // Correctly include the closing </head>

				preg_match_all( '#<link\s+[^>]*>#si', $head_content, $matches );
				$meta_tags          = $matches[0];
				$wlw_manifest_found = false;

				foreach ( $meta_tags as $meta_tag ) {
					if ( false !== stripos( $meta_tag, 'wlwmanifest' ) ) {
						$wlw_manifest_found = true;
						break;
					}
				}

				if ( $wlw_manifest_found ) {
					$return['status'] = 0;
					$return['msg']    = __( 'The wlwmanifest link is present. It is recommended to remove it if not needed.', 'security-ninja' );
				} else {
					$return['msg'] = __( 'The wlwmanifest link is not present. This is the recommended configuration.', 'security-ninja' );
				}
			} else {
				$return['status'] = 5;
				$return['msg']    = __( 'Unable to retrieve the home page content.', 'security-ninja' );
			}
		}

		return $return;
	}




	/**
	 * check if RPC link is present in header
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function rpc_meta() {
		$return   = array();
		$is_local = self::is_local_request();

		$request = wp_remote_get(
			add_query_arg( 'secnin-test', wp_rand(), get_home_url() ),
			array(
				'timeout'     => 25,
				'redirection' => 2,
				'sslverify'   => ! $is_local,

			)
		);

		if ( is_wp_error( $request ) ) {
			$return['status'] = 5;
			$return['msg']    = __( 'Failed to retrieve the home page for analysis.', 'security-ninja' );
		} else {
			$html = wp_remote_retrieve_body( $request );

			if ( ! empty( $html ) ) {
				$return['status'] = 10;
				// Correctly extract content within <head> tags
				$start        = strpos( $html, '<head' );
				$end          = strpos( $html, '</head>', $start );
				$head_content = substr( $html, $start, $end - $start + strlen( '</head>' ) );
				// Find all link tags
				preg_match_all( '#<link\s+[^>]*>#si', $head_content, $matches );
				$meta_tags      = $matches[0];
				$edit_uri_found = false;

				foreach ( $meta_tags as $meta_tag ) {
					if ( false !== stripos( $meta_tag, 'EditURI' ) ) {
						$edit_uri_found = true;
						break;
					}
				}

				if ( $edit_uri_found ) {
					$return['status'] = 0;
					$return['msg']    = __( 'The EditURI link is present. It is recommended to remove it if not needed.', 'security-ninja' );
				} else {
					$return['msg'] = __( 'The EditURI link is not present. This is the recommended configuration.', 'security-ninja' );
				}
			} else {
				$return['status'] = 5;
				$return['msg']    = __( 'Unable to retrieve the home page content.', 'security-ninja' );
			}
		}

		return $return;
	}



	/**
	 * check if register_globals is off
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function register_globals_check() {
		$return           = array();
		$ini_option_name  = 'register_globals';
		$register_globals = (bool) ini_get( $ini_option_name );

		if ( $register_globals ) {
			$return['status'] = 0;
			$return['msg']    = __( 'register_globals is enabled. This is a security risk.', 'security-ninja' );
		} else {
			$return['status'] = 10;
			$return['msg']    = __( 'register_globals is disabled. This is the recommended configuration.', 'security-ninja' );
		}

		return $return;
	}





	/**
	 * check if display_errors is off
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function display_errors_check() {
		$return = array();

		$display_errors = (bool) ini_get( 'display_errors' );
		if ( $display_errors ) {
			$return['status'] = 0;
			$return['msg']    = __( 'display_errors is enabled. This can reveal sensitive information and is a security risk.', 'security-ninja' );
		} else {
			$return['status'] = 10;
			$return['msg']    = __( 'display_errors is disabled. This is the recommended configuration for production environments to prevent revealing sensitive information.', 'security-ninja' );
		}

		return $return;
	}



	/**
	 * Tests for Application Passwords feature is enabled.
	 *
	 * @author Lars Koudal
	 * @since  v0.0.1
	 * @access public static
	 * @global
	 * @return mixed
	 */
	public static function application_passwords() {
		$return = array();

		if ( ! function_exists( 'wp_is_application_passwords_available' ) ) {
			$return['status'] = 0;
			$return['msg']    = __( 'The application passwords feature is not available in your WordPress installation.', 'security-ninja' );
			return $return;
		}

		if ( ! wp_is_application_passwords_available() ) {
			$return['status'] = 10;
			$return['msg']    = __( 'Application passwords are not available. This is the recommended configuration.', 'security-ninja' );
		} else {
			$return['status'] = 0;
			$return['msg']    = __( 'Application passwords are available. Consider disabling this feature if not needed to enhance security.', 'security-ninja' );
		}

		return $return;
	}



	/**
	 * is theme/plugin editor disabled?
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Wednesday, December 16th, 2020.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function file_editor() {
		$return = array();

		if ( defined( 'DISALLOW_FILE_EDIT' ) && DISALLOW_FILE_EDIT ) {
			$return['status'] = 10;
			$return['msg']    = __( 'File editing is correctly disabled on this site.', 'security-ninja' );
		} else {
			$return['status'] = 0;
			$return['msg']    = __( 'File editing is not disabled on this site. It is recommended to set DISALLOW_FILE_EDIT to true in your wp-config.php file for security reasons.', 'security-ninja' );
		}

		return $return;
	}

	/**
	 * check if expose_php is off
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Wednesday, December 16th, 2020.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function expose_php_check() {
		$return = array();

		$expose_php_setting = ini_get( 'expose_php' );

		if ( $expose_php_setting === false ) {
			$return['status'] = 5; // Indicate an error or unexpected result
			$return['msg']    = __( 'Unable to determine the "expose_php" setting. Please check your configuration.', 'security-ninja' );
		} elseif ( $expose_php_setting ) {
			$return['status'] = 0; // Not the recommended setting
			$return['msg']    = __( 'Warning: "expose_php" is enabled. It is recommended to disable "expose_php" to hide PHP version info from headers for security reasons.', 'security-ninja' );
		} else {
			$return['status'] = 10; // Recommended setting
			$return['msg']    = __( '"expose_php" is disabled. Your PHP version info is not exposed in headers, which is the recommended setting.', 'security-ninja' );
		}

		return $return;
	}


	/**
	 * check if allow_url_include is off
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Wednesday, December 16th, 2020.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function allow_url_include_check() {
		$return = array(
			'status' => 10, // Assume the recommended setting by default
			'msg'    => '',
		);

		// Check if allow_url_include is enabled
		// phpcs:ignore PHPCompatibility.PHP.DeprecatedIniDirectives -- Intentionally checking deprecated setting for security testing
		$allowUrlInclude = ini_get( 'allow_url_include' );
		if ( $allowUrlInclude ) {
			$return['status'] = 0; // Not OK
			$return['msg']    = __( 'Warning: allow_url_include is enabled. It is recommended to disable this setting for security reasons.', 'security-ninja' );
		} else {
			$return['msg'] = __( 'allow_url_include is disabled - Recommended setting.', 'security-ninja' );
		}
		return $return;
	}

	/**
	 * check if safe mode is off
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Wednesday, December 16th, 2020.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function safe_mode_check() {
		$return          = array();
		$safeModeEnabled = ini_get( 'safe_mode' ); // phpcs:ignore PHPCompatibility.PHP.DeprecatedIniDirectives -- Intentionally checking deprecated setting for security testing

		if ( $safeModeEnabled === false ) {
			$return['status'] = 10;
			$return['msg']    = __( 'Safe Mode is disabled, which is the recommended setting.', 'security-ninja' );
		} elseif ( $safeModeEnabled === '' ) {
			$return['status'] = 10;
			$return['msg']    = __( 'Safe Mode is not applicable in PHP versions 5.3.0 and above.', 'security-ninja' );
		} else {
			$return['status'] = 0;
			$return['msg']    = __( 'Safe Mode is enabled. It is recommended to disable Safe Mode if possible.', 'security-ninja' );
		}

		return $return;
	}


	/**
	 * check if anyone can register on the site
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Wednesday, December 16th, 2020.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function anyone_can_register() {
		$return = array();

		// In multisite, check both network and site settings
		if ( is_multisite() ) {
			$network_can_register = get_site_option( 'registration', 'none' );
			$site_can_register    = get_option( 'users_can_register' );

			if ( $network_can_register !== 'none' || $site_can_register ) {
				$return['status'] = 0;
				$return['msg']    = __( 'User registration is enabled on this multisite network.', 'security-ninja' );
			} else {
				$return['status'] = 10;
				$return['msg']    = __( 'User registration is properly disabled on this multisite network.', 'security-ninja' );
			}
		} else {
			// Single site check
			$can_register = get_option( 'users_can_register' );

			// Check if the option retrieval has encountered any errors
			if ( is_wp_error( $can_register ) ) {
				$return['status'] = 5; // Error status
				$return['msg']    = __( 'An error occurred while checking the registration setting.', 'security-ninja' );
			} elseif ( $can_register ) {
				$return['status'] = 0;
				$return['msg']    = __( 'Anyone can register on the site.', 'security-ninja' );
			} else {
				$return['status'] = 10;
				$return['msg']    = __( 'Only administrators can add new users.', 'security-ninja' );
			}
		}

		return $return;
	}

	/**
	 * check REST api is enabled
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Wednesday, December 16th, 2020.
	 * @version v1.0.1  Tuesday, June 11th, 2024.
	 * @access  public static
	 * @return  mixed
	 */
	public static function rest_api_links() {
		$return = array();

		// Collect the priorities of REST API link outputs to determine their presence.
		$collected_prios = intval( has_action( 'xmlrpc_rsd_apis', 'rest_output_rsd' ) ) +
			intval( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) +
			intval( has_action( 'template_redirect', 'rest_output_link_header' ) );

		if ( $collected_prios > 0 ) {
			$return['status'] = 5;
			$return['msg']    = __( 'REST API links are present in the site headers, which could expose sensitive information.', 'security-ninja' );
		} else {
			$return['status'] = 10;
			$return['msg']    = __( 'REST API links are not present in the site headers.', 'security-ninja' );
		}

		return $return;
	}



	/**
	 * check WP version
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Wednesday, December 16th, 2020.
	 * @version v1.0.1  Tuesday, June 11th, 2024.
	 * @access  public static
	 * @return  mixed
	 */
	public static function ver_check() {
		$return = array();

		if ( ! function_exists( 'get_preferred_from_update_core' ) ) {
			include_once ABSPATH . 'wp-admin/includes/update.php';
		}

		// Ensure wp_version_check() is called to check for the latest version.
		wp_version_check();

		// Attempt to retrieve the latest core update information.
		$latest_core_update = get_preferred_from_update_core();

		// Check for errors or if an upgrade is available.
		if ( is_wp_error( $latest_core_update ) ) {
			$return['status'] = 5;
			$return['msg']    = __( 'An error occurred while checking the WordPress version.', 'security-ninja' );
		} elseif ( isset( $latest_core_update->response ) && 'upgrade' === $latest_core_update->response ) {
			$return['status'] = 0;
			$return['msg']    = __( 'A WordPress core update is available.', 'security-ninja' );
		} else {
			$return['status'] = 10;
			$return['msg']    = __( 'Your WordPress version is up to date.', 'security-ninja' );
		}

		return $return;
	}



	/**
	 * check if debug.log file is accessible.
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Wednesday, December 16th, 2020.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function debug_log_file_check() {
		$return   = array();
		$url      = trailingslashit( content_url() ) . 'debug.log';
		$is_local = self::is_local_request();

		$response = wp_remote_get(
			$url,
			array(
				'redirection' => 0,
				'sslverify'   => ! $is_local,
			)
		);

		if ( is_wp_error( $response ) ) {
			$return['status'] = 5;
			$return['msg']    = __( 'An error occurred while checking the debug.log file.', 'security-ninja' );
		} elseif ( 200 === wp_remote_retrieve_response_code( $response ) ) {
			$return['status'] = 0;
			$return['msg']    = __( 'The debug.log file is accessible.', 'security-ninja' );
		} elseif ( 404 === wp_remote_retrieve_response_code( $response ) ) {
			$return['status'] = 10;
			$return['msg']    = __( 'The debug.log file does not exist or is not accessible.', 'security-ninja' );
		} else {
			$return['status'] = 10;
			$return['msg']    = __( 'Unexpected HTTP response code received.', 'security-ninja' );
		}

		return $return;
	}





	/**
	 * core updates should be enabled only for minor updates - multisite compatible
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Wednesday, December 16th, 2020.
	 * @version v1.0.1  Tuesday, June 11th, 2024.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function core_updates_check() {
		$return = array();

		// Check if automatic updates are explicitly enabled or disabled
		if ( defined( 'WP_AUTO_UPDATE_CORE' ) ) {
			if ( \WP_AUTO_UPDATE_CORE === false ) {
				$return['status'] = 0;
				$return['msg']    = __( 'Automatic core updates are explicitly disabled.', 'security-ninja' );
			} elseif ( \WP_AUTO_UPDATE_CORE === true || \WP_AUTO_UPDATE_CORE === 'minor' ) {
				$return['status'] = 10;
				$return['msg']    = __( 'Automatic core updates are enabled.', 'security-ninja' );
			} else {
				$return['status'] = 5;
				$return['msg']    = __( 'Automatic core updates are configured with an unknown setting.', 'security-ninja' );
			}
		} else {
			// Check if updates are disabled via filters (common in multisite)
			if ( has_filter( 'automatic_updater_disabled' ) && apply_filters( 'automatic_updater_disabled', false ) ) {
				$return['status'] = 0;
				$return['msg']    = __( 'Automatic core updates are disabled via filters.', 'security-ninja' );
			} elseif ( is_multisite() && ! is_main_site() ) {
				// In multisite, sub-sites typically don't handle core updates
				$return['status'] = 5;
				$return['msg']    = __( 'This is a subsite in a multisite network. Core updates are typically handled by the main site.', 'security-ninja' );
			} else {
				$return['status'] = 10;
				$return['msg']    = __( 'Automatic core updates are enabled (default setting).', 'security-ninja' );
			}
		}

		return $return;
	}




	/**
	 * check if certain username exists
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Wednesday, December 16th, 2020.
	 * @access  public static
	 * @global
	 * @param   string $username Default: 'admin'
	 * @return  mixed
	 */
	public static function user_exists( $username = 'admin' ) {
		$return = array();

		if ( username_exists( $username ) ) {
			$return['status'] = 0;
			/* translators: %s: username */
			$return['msg'] = sprintf( __( 'User "%s" exists.', 'security-ninja' ), $username );
		} else {
			$return['status'] = 10;
			/* translators: %s: username */
			$return['msg'] = sprintf( __( 'User "%s" does not exist.', 'security-ninja' ), $username );
		}

		return $return;
	}




	/**
	 * check if plugins are up to date
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Wednesday, December 16th, 2020.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function plugins_ver_check() {
		$return = array();

		// Force a plugin update check
		wp_update_plugins();

		// Retrieve the updated plugin update information
		$current = get_site_transient( 'update_plugins' );

		// Count the number of plugins with updates available
		$plugin_update_cnt = isset( $current->response ) && is_array( $current->response ) ? count( $current->response ) : 0;

		// Construct the message based on the number of updates available
		if ( $plugin_update_cnt > 0 ) {
			$return['status'] = 0;
			/* translators: %s: number of plugin updates available */
			$return['msg'] = sprintf( _n( 'There is %s plugin update available.', 'There are %s plugin updates available.', $plugin_update_cnt, 'security-ninja' ), number_format_i18n( $plugin_update_cnt ) );
		} else {
			$return['status'] = 10;
			$return['msg']    = __( 'All plugins are up to date.', 'security-ninja' );
		}

		return $return;
	}



	/**
	 * check if there are deactivated plugins
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Wednesday, December 16th, 2020.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function deactivated_plugins() {
		$return = array();

		$all_plugins    = get_plugins();
		$active_plugins = get_option( 'active_plugins', array() );

		// For multisite, merge network activated plugins
		$network_active_plugins = is_multisite() ? array_keys( get_site_option( 'active_sitewide_plugins', array() ) ) : array();
		$active_plugins         = array_unique( array_merge( $active_plugins, $network_active_plugins ) );

		// Calculate deactivated plugins
		$deactivated_plugins_count = count( $all_plugins ) - count( $active_plugins );

		// Messages
		/* translators: %s: number of plugins */
		$total_plugins_text = sprintf( _n( 'There is %s plugin in total.', 'There are %s plugins in total.', count( $all_plugins ), 'security-ninja' ), number_format_i18n( count( $all_plugins ) ) );
		/* translators: %s: number of active plugins */
		$active_plugins_text = sprintf( _n( '%s is active.', '%s are active.', count( $active_plugins ), 'security-ninja' ), number_format_i18n( count( $active_plugins ) ) );
		/* translators: %s: number of deactivated plugins */
		$deactivated_plugins_text = sprintf( _n( '%s is deactivated.', '%s are deactivated.', $deactivated_plugins_count, 'security-ninja' ), number_format_i18n( $deactivated_plugins_count ) );

		// Construct message based on site type
		if ( is_multisite() ) {
			/* translators: %s: number of network-activated plugins */
			$network_active_text       = sprintf( _n( '%s is network activated.', '%s are network activated.', count( $network_active_plugins ), 'security-ninja' ), number_format_i18n( count( $network_active_plugins ) ) );
			$site_active_plugins_count = count( array_diff( $active_plugins, $network_active_plugins ) );
			/* translators: %s: number of plugins activated on this site */
			$site_active_text = sprintf( _n( '%s is activated on this site.', '%s are activated on this site.', $site_active_plugins_count, 'security-ninja' ), number_format_i18n( $site_active_plugins_count ) );
			$return['msg']    = implode( ' ', array( $total_plugins_text, $active_plugins_text, $network_active_text, $site_active_text, $deactivated_plugins_text ) );
		} else {
			$return['msg'] = implode( ' ', array( $total_plugins_text, $active_plugins_text, $deactivated_plugins_text ) );
		}

		// Status based on deactivated plugins count
		$return['status'] = $deactivated_plugins_count > 0 ? 0 : 10;

		return $return;
	}






	/**
	 * check if there are deactivated themes
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Wednesday, December 16th, 2020.
	 * @version v1.0.1  Monday, February 28th, 2022.
	 * @access  public static
	 * @return  mixed
	 */
	public static function deactivated_themes() {
		$return     = array();
		$all_themes = wp_get_themes();

		unset( $all_themes[ get_template() ], $all_themes[ get_stylesheet() ] );

		$theme_names = array_map(
			function ( $theme ) {
				return $theme->get( 'Name' );
			},
			$all_themes
		);

		if ( count( $all_themes ) > 0 ) {
			$return['status'] = 0;
			/* translators: 1: number of themes, 2: comma-separated theme names */
			$return['msg'] = sprintf( __( 'Safe to remove %1$d themes: %2$s', 'security-ninja' ), count( $all_themes ), implode( ', ', $theme_names ) );
		} else {
			$return['status'] = 10;
			$return['msg']    = __( 'All unnecessary themes are already removed.', 'security-ninja' );
		}

		return $return;
	}


	/**
	 * check themes versions
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Wednesday, December 16th, 2020.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function themes_ver_check() {
		$return = array();

		// Attempt to retrieve the current theme updates
		$current = get_site_transient( 'update_themes' );

		if ( ! is_object( $current ) ) {
			$current = new \stdClass();
		}

		// Force a theme update check
		wp_update_themes();

		// Retrieve the updated transient
		$current = get_site_transient( 'update_themes' );

		if ( isset( $current->response ) && is_array( $current->response ) ) {
			$theme_update_cnt = count( $current->response );
		} else {
			$theme_update_cnt = 0;
		}

		if ( $theme_update_cnt > 0 ) {
			$return['status'] = 0;
			/* translators: %d: number of themes with available updates */
			$return['msg'] = sprintf( __( 'There are %d themes with available updates.', 'security-ninja' ), $theme_update_cnt );
		} else {
			$return['status'] = 10;
			$return['msg']    = __( 'All themes are up to date.', 'security-ninja' );
		}

		return $return;
	}


	/**
	 * check DB table prefix
	 *
	 * @author  Unknown
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Wednesday, December 16th, 2020.
	 * @version v1.0.1  Monday, June 10th, 2024.
	 * @access  public static
	 * @return  mixed
	 */
	public static function db_table_prefix_check() {
		global $wpdb;
		$return = array();

		if ( 'wp_' === $wpdb->prefix || 'wordpress_' === $wpdb->prefix || 'wp3_' === $wpdb->prefix ) {
			$return['status'] = 0;
			$return['msg']    = __( 'Using a default database prefix is a security risk.', 'security-ninja' );
		} else {
			$return['status'] = 10;
			$return['msg']    = __( 'Your database prefix is customized. This is the recommended configuration.', 'security-ninja' );
		}

		return $return;
	}

	/**
	 * check if global WP debugging is enabled
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Wednesday, December 16th, 2020.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function debug_check() {
		$return = array();

		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			$return['status'] = 0;
			$return['msg']    = __( 'WP_DEBUG is enabled. While this is helpful for development, it should be disabled on a live site.', 'security-ninja' );
		} else {
			$return['status'] = 10;
			$return['msg']    = __( 'WP_DEBUG is disabled. This is the recommended configuration for production sites.', 'security-ninja' );
		}

		return $return;
	}

	/**
	 * check if global WP JS debugging is enabled
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Wednesday, December 16th, 2020.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function script_debug_check() {
		$return = array();

		if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
			$return['status'] = 0;
			$return['msg']    = __( 'SCRIPT_DEBUG is enabled. While this is helpful for development, it should be disabled on a live site to improve performance and security.', 'security-ninja' );
		} else {
			$return['status'] = 10;
			$return['msg']    = __( 'SCRIPT_DEBUG is disabled. This is the recommended configuration for production sites.', 'security-ninja' );
		}

		return $return;
	}

	/**
	 * check if DB debugging is enabled
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Wednesday, December 16th, 2020.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function db_debug_check() {
		global $wpdb;
		$return = array();

		if ( true === $wpdb->show_errors ) {
			$return['status'] = 0;
			$return['msg']    = __( 'Database debugging is enabled. This can reveal sensitive information and is a security risk.', 'security-ninja' );
		} else {
			$return['status'] = 10;
			$return['msg']    = __( 'Database debugging is disabled. This is the recommended configuration.', 'security-ninja' );
		}

		return $return;
	}


	/**
	 * Check if wp-config.php has the right chmod
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function config_chmod() {
		$return = array();
		include_once ABSPATH . 'wp-admin/includes/file.php';

		WP_Filesystem();
		global $wp_filesystem;

		$tested_file = ABSPATH . 'wp-config.php';
		if ( ! file_exists( $tested_file ) ) {
			// Move up one folder if wp-config.php is not in the ABSPATH directory
			$tested_file = trailingslashit( dirname( ABSPATH ) ) . 'wp-config.php';
		}

		$mode = $wp_filesystem->getchmod( $tested_file );

		$return['details'] = 'Tested file: ' . $tested_file;

		$good_modes = array( '400', '440', '0400', '0440', '660', '0660', '664', '0664' );

		if ( ! $mode ) {
			$return['status'] = 5;
			$return['msg']    = __( 'Unable to determine the file permissions.', 'security-ninja' );
		} elseif ( ! in_array( $mode, $good_modes, true ) ) {
			$return['status'] = 0;
			/* translators: %s: file permissions (e.g. 0644) */
			$return['msg'] = sprintf( __( 'Current file permissions are %s, which are not secure.', 'security-ninja' ), $mode );
		} else {
			$return['status'] = 10;
			/* translators: %s: file permissions (e.g. 0644) */
			$return['msg'] = sprintf( __( 'Current file permissions are %s. This is a secure configuration.', 'security-ninja' ), $mode );
		}

		return $return;
	}





	/**
	 * check for unnecessary information on failed login
	 *
	 * @author  Lars Koudal
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @version v1.0.1  Saturday, February 4th, 2023.
	 * @access  public static
	 * @return  mixed
	 */
	public static function check_failed_login_info() {
		$return = array();

		$params = array(
			'log' => 'sn-test_3453344355',
			'pwd' => 'sn-test_2344323335',
		);

		if ( ! class_exists( 'WP_Http' ) ) {
			include_once ABSPATH . WPINC . '/class-http.php';
		}
		$is_local = self::is_local_request();

		$http     = new \WP_Http();
		$response = $http->request(
			wp_login_url(),
			array(
				'method'      => 'POST',
				'body'        => $params,
				'redirection' => 0,
				'timeout'     => 45,
				'sslverify'   => ! $is_local,
			)
		);

		if ( is_wp_error( $response ) ) {
			// Could not reach the login page (e.g. timeout). Treat as inconclusive (Warning), not a confirmed failure.
			$return['status'] = 5;
			$return['msg']    = __( 'Failed to make a request to the login page.', 'security-ninja' );
		} elseif ( isset( $response['body'] ) && stripos( $response['body'], 'invalid username' ) !== false ) {
			$return['status'] = 0;
			$return['msg']    = __( 'Failed login detected with the provided test credentials.', 'security-ninja' );
		} else {
			$return['status'] = 10;
			$return['msg']    = __( 'No failed login detected with the provided test credentials.', 'security-ninja' );
		}

		return $return;
	}





	/**
	 * helper function
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @access  public static
	 * @param   mixed $username
	 * @param   mixed $password
	 * @return  void
	 */
	public static function try_login( $username, $password ) {
		$user = apply_filters( 'authenticate', null, $username, $password );

		if ( isset( $user->ID ) && ! empty( $user->ID ) ) {
			return true;
		} else {
			return false;
		}
	}





	/**
	 * bruteforce user login
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @access  public static
	 * @return  mixed
	 */
	public static function bruteforce_login() {
		$return              = array();
		$max_users_attack    = 5;
		$passwords_file_path = WF_SN_PLUGIN_DIR . 'includes/brute-force-dictionary.txt';

		// Check if the dictionary file exists and is readable
		if ( ! file_exists( $passwords_file_path ) || ! is_readable( $passwords_file_path ) ) {
			$return['status'] = 0;
			$return['msg']    = __( 'The brute-force dictionary file is missing or not readable.', 'security-ninja' );
			return $return;
		}

		$passwords = file( $passwords_file_path, FILE_IGNORE_NEW_LINES );

		$bad_usernames = array();

		if ( ! $max_users_attack ) {
			$return['status'] = 5;
			$return['msg']    = __( 'Maximum users attack value is not set.', 'security-ninja' );
			return $return;
		}

		$roles = array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' );
		$users = array();

		foreach ( $roles as $role ) {
			$users = array_merge( $users, get_users( array( 'role' => $role ) ) );
			if ( count( $users ) >= $max_users_attack ) {
				break;
			}
		}

		$i = 0;
		foreach ( $users as $user ) {
			if ( ++$i > $max_users_attack ) {
				break;
			}

			$passwords[] = $user->user_login; // Adding username to the list of passwords to check for simple password

			foreach ( $passwords as $password ) {
				if ( self::try_login( $user->user_login, $password ) ) {
					$bad_usernames[] = $user->user_login;
					break;
				}
			}
		}

		if ( empty( $bad_usernames ) ) {
			$return['status'] = 10;
			$return['msg']    = __( 'No vulnerable accounts found.', 'security-ninja' );
		} else {
			$return['status'] = 0;
			/* translators: %s: comma-separated list of vulnerable usernames */
			$return['msg'] = sprintf( __( 'Vulnerable accounts: %s', 'security-ninja' ), implode( ', ', $bad_usernames ) );
		}

		return $return;
	}


	/**
	 * Test for Strict-Transport-Security in http header
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function strict_transport_security() {
		$return   = array();
		$is_local = self::is_local_request();

		// Make a remote request to the home URL with a random query parameter
		$response = wp_remote_get(
			add_query_arg( 'secnin-test', wp_rand(), get_home_url() ),
			array(
				'timeout'     => 25,
				'redirection' => 2,
				'sslverify'   => ! $is_local,

			)
		);

		// Check for errors in the response
		if ( is_wp_error( $response ) ) {
			$error_message    = $response->get_error_message();
			// Could not reach the site (e.g. timeout). Treat as inconclusive (Warning), not a confirmed failure.
			$return['status'] = 5;
			$return['msg']    = sprintf( esc_html__( 'Failed to retrieve the site headers. Error: %s', 'security-ninja' ), esc_html( $error_message ) );
			return $return;
		}

		// Access the headers directly from the response array
		$raw_headers = isset( $response['headers'] ) ? $response['headers'] : array();

		// Convert headers to an array and make them case-insensitive
		if ( is_object( $raw_headers ) ) {
			$headers = $raw_headers->getAll();
		} else {
			$headers = (array) $raw_headers;
		}

		// Convert headers to lower case keys
		$headers = array_change_key_case( $headers, CASE_LOWER );

		// Check for the Strict-Transport-Security header
		if ( isset( $headers['strict-transport-security'] ) ) {
			$hsts_values = $headers['strict-transport-security'];
			if ( is_array( $hsts_values ) ) {
				$return['status'] = 0;
				$return['msg']    = __( 'Error, multiple Strict-Transport-Security headers found. You should only have one.', 'security-ninja' );
			} else {
				$return['status']  = 10;
				$return['msg']     = __( 'Great, Strict-Transport-Security has been set.', 'security-ninja' );
				$return['details'] = '"' . esc_html( $hsts_values ) . '"';
			}
		} else {
			$total_headers    = count( $headers );
			$return['status'] = 0;
			/* translators: %d: total number of response headers detected */
			$return['msg'] = sprintf( __( 'Strict-Transport-Security header is not set. Detected %d headers in total. Ensure your server is configured to send the HSTS header.', 'security-ninja' ), $total_headers );
		}

		return $return;
	}



	/**
	 * Test for Content Security Policy in http header
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function referrer_policy() {
		$result   = array();
		$is_local = self::is_local_request();

		$response = wp_remote_get(
			add_query_arg( 'secnin-test', wp_rand(), get_home_url() ),
			array(
				'timeout'     => 25,
				'redirection' => 2,
				'sslverify'   => ! $is_local,
			)
		);

		if ( is_wp_error( $response ) ) {
			$error_message    = $response->get_error_message();
			// Could not reach the site (e.g. timeout). Treat as inconclusive (Warning), not a confirmed failure.
			$return['status'] = 5;
			$return['msg']    = sprintf( esc_html__( 'Failed to retrieve the site headers. Error: %s', 'security-ninja' ), esc_html( $error_message ) );
			return $return;
		}

		// Access the headers directly from the response array
		$raw_headers = isset( $response['headers'] ) ? $response['headers'] : array();

		// Convert headers to an array and make them case-insensitive
		if ( is_object( $raw_headers ) ) {
			$headers = $raw_headers->getAll();
		} else {
			$headers = (array) $raw_headers;
		}

		$headers = array_change_key_case( $headers, CASE_LOWER ); // Convert headers to lowercase

		if ( isset( $headers['referrer-policy'] ) ) {
			$referrer_policy_values = $headers['referrer-policy'];
			if ( is_array( $referrer_policy_values ) ) {
				$result['status'] = 0;
				$result['msg']    = esc_html__( 'Error, multiple Referrer-Policy headers found. You should only have one.', 'security-ninja' );
			} else {
				$result['status']  = 10;
				$result['msg']     = esc_html__( 'Great, Referrer-Policy has been set.', 'security-ninja' );
				$result['details'] = '"' . esc_html( $referrer_policy_values ) . '"';
			}
		} else {
			$total_headers    = count( $headers );
			$result['status'] = 0;
			/* translators: %d: total number of response headers detected */
			$result['msg'] = sprintf( __( 'Referrer-Policy header is not set. Detected %d headers in total.', 'security-ninja' ), $total_headers );
		}

		return $result;
	}


	/**
	 * Test for Feature Policy in http header
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function feature_policy() {
		$result   = array();
		$is_local = self::is_local_request();

		$response = wp_remote_get(
			add_query_arg( 'secnin-test', wp_rand(), get_home_url() ),
			array(
				'timeout'     => 25,
				'redirection' => 2,
				'sslverify'   => ! $is_local,

			)
		);

		if ( is_wp_error( $response ) ) {
			$error_message    = $response->get_error_message();
			// Could not reach the site (e.g. timeout). Treat as inconclusive (Warning), not a confirmed failure.
			$return['status'] = 5;
			$return['msg']    = sprintf( esc_html__( 'Failed to retrieve the site headers. Error: %s', 'security-ninja' ), esc_html( $error_message ) );
			return $return;
		}

		// Access the headers directly from the response array
		$raw_headers = isset( $response['headers'] ) ? $response['headers'] : array();

		// Convert headers to an array and make them case-insensitive
		if ( is_object( $raw_headers ) ) {
			$headers = $raw_headers->getAll();
		} else {
			$headers = (array) $raw_headers;
		}

		$headers = array_change_key_case( $headers, CASE_LOWER ); // Convert headers to lowercase

		if ( isset( $headers['permissions-policy'] ) ) {
			if ( is_array( $headers['permissions-policy'] ) ) {
				$result['status'] = 0;
				$result['msg']    = esc_html__( 'Error, multiple Permissions-Policy headers found. You should only have one.', 'security-ninja' );
			} else {
				$result['status']  = 10;
				$result['msg']     = esc_html__( 'Great, Permissions-Policy has been set.', 'security-ninja' );
				$result['details'] = '"' . esc_html( $headers['permissions-policy'] ) . '"';
			}
		} else {
			$total_headers    = count( $headers );
			$result['status'] = 0;
			$result['msg']    = esc_html__( 'Permissions-Policy header is not set.', 'security-ninja' ) . ' ' . sprintf( esc_html__( 'Detected %d headers in total.', 'security-ninja' ), $total_headers );
		}

		return $result;
	}


	/**
	 * Test for Content Security Policy in http header
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function content_security_policy() {
		$return   = array();
		$is_local = self::is_local_request();

		$response = wp_remote_get(
			add_query_arg( 'secnin-test', wp_rand(), get_home_url() ),
			array(
				'timeout'     => 25,
				'redirection' => 2,
				'sslverify'   => ! $is_local,

			)
		);

		if ( is_wp_error( $response ) ) {
			// Could not reach the site (e.g. timeout). Treat as inconclusive (Warning), not a confirmed failure.
			$return['status'] = 5;
			$return['msg']    = __( 'Failed to retrieve the home page headers.', 'security-ninja' );
			return $return;
		}
		// Access the headers directly from the response array
		$raw_headers = isset( $response['headers'] ) ? $response['headers'] : array();

		// Convert headers to an array and make them case-insensitive
		if ( is_object( $raw_headers ) ) {
			$headers = $raw_headers->getAll();
		} else {
			$headers = (array) $raw_headers;
		}

		$headers = array_change_key_case( $headers, CASE_LOWER ); // Convert headers to lowercase

		$csp    = isset( $headers['content-security-policy'] ) ? $headers['content-security-policy'] : false;
		$csp_ro = isset( $headers['content-security-policy-report-only'] ) ? $headers['content-security-policy-report-only'] : false;

		if ( $csp && is_array( $csp ) ) {
			$return['status'] = 0;
			$return['msg']    = __( 'Multiple Content Security Policy headers found. You should only have one.', 'security-ninja' );
		} elseif ( $csp ) {
			$return['status']  = 10;
			$return['msg']     = __( 'Great, Content Security Policy has been set.', 'security-ninja' );
			$return['details'] = '"' . implode( '", "', (array) $csp ) . '"';
		}

		if ( $csp_ro && is_array( $csp_ro ) ) {
			$return['status'] = 0;
			$return['msg']   .= __( ' Multiple Content Security Policy Report-Only headers found. You should only have one.', 'security-ninja' );
		} elseif ( $csp_ro ) {
			$status             = $csp ? 10 : 5; // If CSP is set, maintain its status; otherwise, set to report-only status
			$return['status']   = $status;
			$return['msg']      = $csp ? $return['msg'] : __( 'Content Security Policy is set to report-only mode.', 'security-ninja' );
			$return['details']  = isset( $return['details'] ) ? $return['details'] . ' ' : '';
			$return['details'] .= '"' . implode( '", "', (array) $csp_ro ) . '"';
		}

		if ( ! $csp && ! $csp_ro ) {
			$total_headers    = count( $headers );
			$return['status'] = 0;
			/* translators: %d: total number of response headers detected */
			$return['msg'] = sprintf( __( 'Content-Security-Policy header is not set. Detected %d headers in total.', 'security-ninja' ), $total_headers );
		}

		return $return;
	}



	/**
	 * Test for X-Frame-Options in http header
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function x_frame_options() {
		$return_array = array();
		$is_local     = self::is_local_request();

		$response = wp_remote_get(
			add_query_arg( 'secnin-test', wp_rand(), get_home_url() ),
			array(
				'timeout'     => 25,
				'redirection' => 2,
				'sslverify'   => ! $is_local,
			)
		);

		if ( is_wp_error( $response ) ) {
			$error_message          = $response->get_error_message();
			// Could not reach the site (e.g. timeout). Treat as inconclusive (Warning), not a confirmed failure.
			$return_array['status'] = 5;
			$return_array['msg']    = sprintf( esc_html__( 'Failed to retrieve the site headers. Error: %s', 'security-ninja' ), esc_html( $error_message ) );
			return $return_array;
		}

		// Access the headers directly from the response array
		$raw_headers = isset( $response['headers'] ) ? $response['headers'] : array();

		// Convert headers to an array and make them case-insensitive
		if ( is_object( $raw_headers ) ) {
			$headers = $raw_headers->getAll();
		} else {
			$headers = (array) $raw_headers;
		}

		$headers = array_change_key_case( $headers, CASE_LOWER ); // Convert headers to lowercase

		if ( isset( $headers['x-frame-options'] ) ) {
			$x_frame_options = $headers['x-frame-options'];
			if ( is_array( $x_frame_options ) ) {
				// Header is set multiple times
				$return_array['status']  = 0;
				$return_array['msg']     = __( 'X-Frame-Options header is set multiple times. This should be set only once.', 'security-ninja' );
				$return_array['details'] = '"' . implode( '", "', $x_frame_options ) . '"';
			} else {
				$return_array['status']  = 10;
				$return_array['msg']     = __( 'X-Frame-Options header is properly set.', 'security-ninja' );
				$return_array['details'] = '"' . $x_frame_options . '"';
			}
		} else {
			$total_headers          = count( $headers );
			$return_array['status'] = 0;
			/* translators: %d: total number of response headers detected */
			$return_array['msg'] = sprintf( __( 'X-Frame-Options header is not set. Detected %d headers in total. This can make your site vulnerable to clickjacking attacks.', 'security-ninja' ), $total_headers );
		}

		return $return_array;
	}





	/**
	 * Test for X-Content-Type-Options in http header
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function x_content_type_options() {
		$return   = array();
		$is_local = self::is_local_request();

		$response = wp_remote_get(
			add_query_arg( 'secnin-test', wp_rand(), get_home_url() ),
			array(
				'timeout'     => 25,
				'redirection' => 2,
				'sslverify'   => ! $is_local,
			)
		);

		if ( is_wp_error( $response ) ) {
			$error_message    = $response->get_error_message();
			// Could not reach the site (e.g. timeout). Treat as inconclusive (Warning), not a confirmed failure.
			$return['status'] = 5;
			$return['msg']    = sprintf( esc_html__( 'Failed to retrieve the site headers. Error: %s', 'security-ninja' ), esc_html( $error_message ) );
			return $return;
		}

		// Access the headers directly from the response array
		$raw_headers = isset( $response['headers'] ) ? $response['headers'] : array();

		// Convert headers to an array and make them case-insensitive
		if ( is_object( $raw_headers ) ) {
			$headers = $raw_headers->getAll();
		} else {
			$headers = (array) $raw_headers;
		}

		$headers = array_change_key_case( $headers, CASE_LOWER ); // Convert headers to lowercase

		if ( isset( $headers['x-content-type-options'] ) ) {
			$x_content_type_options = $headers['x-content-type-options'];
			if ( is_array( $x_content_type_options ) ) {
				// Header is set multiple times
				$return['status']  = 0;
				$return['msg']     = __( 'X-Content-Type-Options header is set multiple times. This should be set only once.', 'security-ninja' );
				$return['details'] = '"' . implode( '", "', $x_content_type_options ) . '"';
			} else {
				$return['status']  = 10;
				$return['msg']     = __( 'X-Content-Type-Options header is properly set.', 'security-ninja' );
				$return['details'] = '"' . $x_content_type_options . '"';
			}
		} else {
			$total_headers    = count( $headers );
			$return['status'] = 0;
			/* translators: %d: total number of response headers detected */
			$return['msg'] = sprintf( __( 'X-Content-Type-Options header is not set. Detected %d headers in total. This can make your site vulnerable to MIME type sniffing attacks.', 'security-ninja' ), $total_headers );
		}

		return $return;
	}

	/**
	 * Check if PHP headers contain sensitive information.
	 *
	 * @since   0.0.1
	 * @version 1.0.2  Tuesday, April 30th, 2024.
	 *
	 * @return array Test result with status and message.
	 */
	public static function php_headers() {
		$return = array(
			'status' => 10,
			'msg'    => __( 'Great, no sensitive information exposed in headers.', 'security-ninja' ),
		);

		$is_local = self::is_local_request();

		$response = wp_remote_get(
			add_query_arg( 'secnin-test', wp_rand(), home_url() ),
			array(
				'timeout'     => 30,
				'redirection' => 2,
				'sslverify'   => ! $is_local,
			)
		);

		if ( is_wp_error( $response ) ) {
			// Could not reach the site (e.g. timeout). Treat as inconclusive (Warning), not a confirmed failure.
			$return['status'] = 5;
			/* translators: %s: error message from HTTP request */
			$return['msg'] = sprintf( __( 'Error: Unable to get the response. %s', 'security-ninja' ), $response->get_error_message() );
			return $return;
		}

		if ( wp_remote_retrieve_response_code( $response ) !== 200 ) {
			$return['status'] = 0;
			/* translators: %d: HTTP response code */
			$return['msg'] = sprintf( __( 'Error: Unexpected response code: %d', 'security-ninja' ), wp_remote_retrieve_response_code( $response ) );
			return $return;
		}

		$raw_headers = wp_remote_retrieve_headers( $response );
		$headers     = array();

		if ( is_object( $raw_headers ) ) {
			$headers = $raw_headers->getAll();
		} elseif ( is_array( $raw_headers ) ) {
			$headers = $raw_headers;
		}

		$headers = array_change_key_case( $headers, CASE_LOWER );

		$php_version       = phpversion();
		$sensitive_headers = array( 'server', 'x-powered-by', 'x-debug-token', 'x-debug-token-link' );
		$sensitive_found   = false;
		$sensitive_details = array();

		foreach ( $headers as $header_name => $header_value ) {
			if ( in_array( strtolower( $header_name ), $sensitive_headers, true ) ) {
				if ( is_array( $header_value ) ) {
					$header_value = implode( ', ', $header_value );
				}
				if ( stripos( $header_value, 'php' ) !== false ||
					stripos( $header_value, $php_version ) !== false ||
					stripos( $header_value, 'debug' ) !== false ) {
					$sensitive_found     = true;
					$sensitive_details[] = $header_name . ': ' . $header_value;
				}
			}
		}

		if ( $sensitive_found ) {
			$return['status'] = 0;
			/* translators: %s: semicolon-separated list of sensitive header details */
			$return['msg'] = sprintf( __( 'Sensitive information exposed in headers: %s', 'security-ninja' ), implode( '; ', $sensitive_details ) );
		}

		return $return;
	}





	/**
	 * check for WP version in meta tags
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function wp_header_meta() {
		$return = array();

		$is_local = self::is_local_request();

		$response = wp_remote_get(
			add_query_arg( 'secnin-test', wp_rand(), get_home_url() ),
			array(
				'timeout'     => 25,
				'redirection' => 2,
				'sslverify'   => ! $is_local,
			)
		);
		if ( is_wp_error( $response ) ) {
			$error_message    = $response->get_error_message();
			// Could not reach the site (e.g. timeout). Treat as inconclusive (Warning), not a confirmed failure.
			$return['status'] = 5;
			$return['msg']    = sprintf( esc_html__( 'Failed to retrieve the site headers. Error: %s', 'security-ninja' ), esc_html( $error_message ) );
			return $return;
		}

		$html = wp_remote_retrieve_body( $response );

		if ( $html ) {
			$return['status'] = 10;
			// Extract content in <head> tags
			$start = strpos( $html, '<head' );
			$end   = strpos( $html, '</head>', $start );
			$html  = substr( $html, $start, $end - $start + strlen( '</head>' ) );
			// Find all Meta Tags
			preg_match_all( '#<meta([^>]*)>#si', $html, $matches );
			$meta_tags = $matches[0];

			foreach ( $meta_tags as $meta_tag ) {
				// More specific check for WordPress core generator tag
				if (
					// Check for name="generator" attribute
					( stripos( $meta_tag, 'name="generator"' ) !== false || stripos( $meta_tag, "name='generator'" ) !== false )
					// Check for content that specifically mentions WordPress with the version
					&& (
						stripos( $meta_tag, 'content="WordPress ' . get_bloginfo( 'version' ) ) !== false
						|| stripos( $meta_tag, "content='WordPress " . get_bloginfo( 'version' ) ) !== false
					)
				) {
					$return['status'] = 0;
					$return['msg']    = __( 'The WordPress version is exposed in the meta generator tag, which is a security risk.', 'security-ninja' );
					break;
				}
			}

			if ( $return['status'] === 10 ) {
				$return['msg'] = __( 'The WordPress version is not exposed in the meta generator tag.', 'security-ninja' );
			}
		} else {
			// Error
			$return['status'] = 5;
			$return['msg']    = __( 'Failed to retrieve the site content.', 'security-ninja' );
		}

		return $return;
	}



	/**
	 * compare WP Blog Url with WP Site Url
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function blog_site_url_check() {
		$return = array();

		$site_url = home_url(); // Retrieves the URL for the site
		$wp_url   = site_url(); // Retrieves the URL for the WordPress installation

		// In multisite, this check is less critical as subdirectories are common
		if ( is_multisite() ) {
			if ( $site_url === $wp_url ) {
				$return['status'] = 10;
				$return['msg']    = __( 'The Site Address (URL) and WordPress Address (URL) are the same.', 'security-ninja' );
			} else {
				$return['status'] = 5; // Warning instead of error in multisite
				$return['msg']    = __( 'The Site Address (URL) and WordPress Address (URL) are different. This is common in multisite installations.', 'security-ninja' );
			}
		} else {
			// Single site check - different URLs are a security concern
			if ( $site_url === $wp_url ) {
				$return['status'] = 10;
				$return['msg']    = __( 'The Site Address (URL) and WordPress Address (URL) are the same.', 'security-ninja' );
			} else {
				$return['status'] = 0;
				$return['msg']    = __( 'The Site Address (URL) and WordPress Address (URL) are different.', 'security-ninja' );
			}
		}

		return $return;
	}


	/**
	 * brute force attack on password
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @access  public static
	 * @global
	 * @param   mixed $password
	 * @return  void
	 */
	public static function dictionary_attack( $password ) {
		$dictionary = file( WF_SN_PLUGIN_DIR . 'includes/brute-force-dictionary.txt', FILE_IGNORE_NEW_LINES );

		if ( in_array( $password, $dictionary, true ) ) {
			return true;
		} else {
			return false;
		}
	}



	/**
	 * check database password
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function db_password_check() {
		$return   = array();
		$password = DB_PASSWORD;

		if ( empty( $password ) ) {
			$return['status'] = 0;
			$return['msg']    = __( 'The database password is empty.', 'security-ninja' );
		} elseif ( self::dictionary_attack( $password ) ) {
			$return['status'] = 0;
			$return['msg']    = __( 'The database password is a simple word from the dictionary.', 'security-ninja' );
		} elseif ( strlen( $password ) < 6 ) {
			$return['status'] = 0;
			/* translators: %d: number of characters in the database password */
			$return['msg'] = sprintf( __( 'The database password length is only %d characters.', 'security-ninja' ), strlen( $password ) );
		} elseif ( count( count_chars( $password, 1 ) ) < 5 ) {
			$return['status'] = 0;
			$return['msg']    = __( 'The database password is too simple.', 'security-ninja' );
		} else {
			$return['status'] = 10;
			$return['msg']    = __( 'The database password is strong.', 'security-ninja' );
		}

		return $return;
	}


	/**
	 * unique config keys check
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function salt_keys_check() {
		$return   = array();
		$ok       = true;
		$bad_keys = array(); // Initialize the array to store keys that are not set properly
		$keys     = array(
			'AUTH_KEY',
			'SECURE_AUTH_KEY',
			'LOGGED_IN_KEY',
			'NONCE_KEY',
			'AUTH_SALT',
			'SECURE_AUTH_SALT',
			'LOGGED_IN_SALT',
			'NONCE_SALT',
		);

		foreach ( $keys as $key ) {
			if ( defined( $key ) ) {
				$constant = constant( $key );
				if ( 'put your unique phrase here' === trim( $constant ) || strlen( $constant ) < 50 ) {
					$bad_keys[] = $key;
					$ok         = false;
				}
			} else {
				// If the key is not defined, it's considered bad
				$bad_keys[] = $key;
				$ok         = false;
			}
		} // foreach

		if ( $ok ) {
			$return['status'] = 10;
			$return['msg']    = __( 'All security keys and salts are properly set.', 'security-ninja' );
		} else {
			$return['status'] = 0;
			$return['msg']    = sprintf(
				__( 'The following keys are not properly set: %s. Please update them for enhanced security.', 'security-ninja' ),
				implode( ', ', $bad_keys )
			);
		}

		return $return;
	}




	/**
	 * check if wp-config.php has the right chmod
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function salt_keys_age_check() {
		$return = array();

		// Define the path for wp-config.php
		$wp_config_path = file_exists( ABSPATH . 'wp-config.php' ) ? ABSPATH . 'wp-config.php' : ABSPATH . '../wp-config.php';

		// Check if wp-config.php exists in the defined path
		if ( ! file_exists( $wp_config_path ) ) {
			$return['status'] = 5;
			$return['msg']    = __( 'Unable to locate wp-config.php.', 'security-ninja' );
			return $return;
		}

		// Get the last modification time of wp-config.php
		$age = filemtime( $wp_config_path );

		// Check if there was an error retrieving the file modification time
		if ( false === $age ) {
			$return['status'] = 5;
			$return['msg']    = __( 'Failed to retrieve the last modification time of wp-config.php.', 'security-ninja' );
			return $return;
		}

		// Calculate the difference in time from the last modification
		$diff = time() - $age;

		// Check if the difference is greater than 93 days
		if ( $diff > DAY_IN_SECONDS * 93 ) {
			$return['status'] = 0;
			$return['msg']    = __( 'The salts in wp-config.php are older than 93 days. Consider refreshing them for enhanced security.', 'security-ninja' );
		} else {
			$return['status'] = 10;
			$return['msg']    = __( 'The salts in wp-config.php are up to date.', 'security-ninja' );
		}

		return $return;
	}



	/**
	 * uploads_browsable.
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function uploads_browsable() {
		$return     = array();
		$upload_dir = wp_upload_dir();
		$is_local   = self::is_local_request();

		$args = array(
			'method'      => 'GET',
			'timeout'     => 5,
			'redirection' => 0,
			'httpversion' => 1.0,
			'blocking'    => true,
			'headers'     => array(),
			'body'        => null,
			'cookies'     => array(),
			'sslverify'   => ! $is_local,

		);
		$response = wp_remote_get( rtrim( $upload_dir['baseurl'], '/' ) . '/?nocache=' . wp_rand(), $args );

		if ( is_wp_error( $response ) ) {
			$return['status'] = 5;
			$return['msg']    = $upload_dir['baseurl'] . '/';
		} elseif ( '200' === $response['response']['code'] && false !== stripos( $response['body'], 'index' ) ) {
			$return['status'] = 0;
			$return['msg']    = $upload_dir['baseurl'] . '/';
		} else {
			$return['status'] = 10;
		}

		return $return;
	}



	/**
	 * shellshock_6271.
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function shellshock_6271() {
		$return = array();

		// Check if proc_open is allowed
		if ( ! function_exists( 'proc_open' ) ) {
			$return['status'] = 10;
			$return['msg']    = __( 'The PHP module proc_open is not allowed. This is a good sign.', 'security-ninja' );
			return $return;
		}

		if ( strtoupper( substr( PHP_OS, 0, 3 ) ) === 'WIN' ) {
			$return['status'] = 10;
			$return['msg']    = __( 'Shellshock not applicable on Windows servers.', 'security-ninja' );
			return $return;
		}

		$env = array( 'SHELL_SHOCK_TEST' => '() { :;}; echo VULNERABLE' );

		$desc = array(
			0 => array( 'pipe', 'r' ),
			1 => array( 'pipe', 'w' ),
			2 => array( 'pipe', 'w' ),
		);

		$process = @proc_open( 'bash -c "echo Test"', $desc, $pipes, null, $env );
		if ( ! is_resource( $process ) || ! isset( $pipes[1] ) ) {
			$return['status'] = 5;
			$return['msg']    = __( 'Unable to execute bash command.', 'security-ninja' );
			return $return;
		}
		$output = stream_get_contents( $pipes[1] );
		proc_close( $process );

		if ( strpos( $output, 'VULNERABLE' ) === false ) {
			$return['status'] = 10;
			$return['msg']    = __( 'Server is not vulnerable to Shellshock.', 'security-ninja' );
		} else {
			$return['status'] = 0;
			$return['msg']    = __( 'Server is vulnerable to Shellshock!', 'security-ninja' );
		}

		return $return;
	}



	/**
	 * shellshock_7169.
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @access  public static
	 * @global
	 * @return  mixed
	 */
	public static function shellshock_7169() {
		$return = array();

		// Check if proc_open is allowed
		if ( ! function_exists( 'proc_open' ) ) {
			$return['status'] = 10;
			$return['msg']    = __( 'The PHP module proc_open is not allowed. This is a good sign.', 'security-ninja' );
			return $return;
		}

		if ( strtoupper( substr( PHP_OS, 0, 3 ) ) === 'WIN' ) {
			$return['status'] = 10;
			$return['msg']    = __( 'Shellshock not applicable on Windows servers.', 'security-ninja' );
			return $return;
		}

		$desc = array(
			0 => array( 'pipe', 'r' ),
			1 => array( 'pipe', 'w' ),
			2 => array( 'pipe', 'w' ),
		);

		$process = @proc_open( "rm -f echo; env 'x=() { (a)=>\' bash -c \"echo date +%Y\"; cat echo", $desc, $pipes, sys_get_temp_dir() );
		if ( ! is_resource( $process ) ) {
			$return['status'] = 5;
			$return['msg']    = __( 'Unable to initiate a process for testing.', 'security-ninja' );
			return $return;
		}

		$output = stream_get_contents( $pipes[1] );
		proc_close( $process );

		$current_year = gmdate( 'Y' );

		if ( trim( $output ) === $current_year ) {
			$return['status'] = 0;
			$return['msg']    = __( 'Server is vulnerable to Shellshock.', 'security-ninja' );
		} else {
			$return['status'] = 10;
			$return['msg']    = __( 'Server is not vulnerable to Shellshock.', 'security-ninja' );
		}

		return $return;
	}





	/**
	 * Check if any active plugin hasn't been updated in last 365 days.
	 * Uses WordPress.org plugin information in this request only (nothing persisted to wp_options).
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @access  public static
	 * @return  mixed
	 */
	public static function old_plugins() {
		$return         = array();
		$good           = array();
		$bad            = array();
		$active_plugins = get_option( 'active_plugins', array() );

		foreach ( $active_plugins as $plugin_path ) {
			if ( empty( $plugin_path ) ) {
				continue;
			}

			$plugin_slug = dirname( $plugin_path );

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

			$response = wp_remote_get( esc_url_raw( 'https://api.wordpress.org/plugins/info/1.1/?action=plugin_information&request[slug]=' . $plugin_slug ), array( 'timeout' => 5 ) );

			if ( is_wp_error( $response ) ) {
				continue;
			}

			if ( wp_remote_retrieve_response_code( $response ) !== 200 ) {
				continue;
			}

			$details = json_decode( wp_remote_retrieve_body( $response ), true );

			if ( empty( $details ) || ! isset( $details['last_updated'] ) ) {
				continue;
			}

			$updated = strtotime( $details['last_updated'] );

			if ( $updated + 365 * DAY_IN_SECONDS < time() ) {
				$bad[ $plugin_path ] = true;
			} else {
				$good[ $plugin_path ] = true;
			}
		}

		if ( empty( $bad ) && empty( $good ) ) {
			$return['status'] = 5;
			$return['msg']    = __( 'No plugins found.', 'security-ninja' );
		} elseif ( empty( $bad ) ) {
			$return['status'] = 10;
			$return['msg']    = __( 'All plugins are up to date.', 'security-ninja' );
		} else {
			$plugins           = get_plugins();
			$bad_plugins_names = array();
			foreach ( $bad as $plugin_path => $tmp ) {
				$bad_plugins_names[] = $plugins[ $plugin_path ]['Name'];
			}
			$return['msg']    = implode( ', ', $bad_plugins_names );
			$return['status'] = 0;
			/* translators: %s: list or message describing outdated plugins */
			$return['msg'] = sprintf( __( 'The following plugins are outdated: %s', 'security-ninja' ), $return['msg'] );
		}

		return $return;
	}




	/**
	 * check if any active plugins are not compatible with current ver of WP
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @access  public static
	 * @return  mixed
	 */
	public static function incompatible_plugins() {
		global $wp_version;

		$return            = array();
		$return['details'] = '';
		$good              = array();
		$bad               = array();

		$wf_sn_active_plugins = array();

		$active_plugins = get_option( 'active_plugins', array() );

		foreach ( $active_plugins as $plugin_path ) {
			$plugin = explode( '/', $plugin_path );

			if ( empty( $plugin ) || empty( $plugin_path ) ) {
				continue;
			}
			if ( isset( $plugin[0] ) ) {
				$plugin = sanitize_text_field( $plugin[0] );
			}

			$response = wp_remote_get( 'https://api.wordpress.org/plugins/info/1.1/?action=plugin_information&request%5Bslug%5D=' . rawurlencode( $plugin ), array( 'timeout' => 5 ) );
			if ( ! is_wp_error( $response ) && wp_remote_retrieve_response_code( $response ) === 200 && wp_remote_retrieve_body( $response ) ) {
				$details = wp_remote_retrieve_body( $response );
				$details = json_decode( $details, true );
				if ( empty( $details ) ) {
					// No details detected
					continue;
				}
				$wf_sn_active_plugins[ $plugin_path ] = $details;
				$updated                              = strtotime( $details['last_updated'] );
				if ( $updated + 365 * DAY_IN_SECONDS < time() ) {
					$bad[ $plugin_path ] = true;
				} else {
					$good[ $plugin_path ] = true;
				}
			}
		} // foreach active plugin

		if ( empty( $wf_sn_active_plugins ) ) {
			// No plugin data returned from the WordPress.org API for any active plugin.
			return array(
				'status' => 0,
				'msg'    => esc_html__( 'No active plugins to check.', 'security-ninja' ),
			);
		}

		$all_plugins = get_plugins();

		foreach ( $wf_sn_active_plugins as $plugin_path => $plugin ) {

			if ( version_compare( $wp_version, $plugin['tested'], '>' ) ) {
				$bad[ $plugin_path ] = $plugin;
			} else {
				$good[ $plugin_path ] = $plugin;
			}
		}

		if ( empty( $bad ) ) {
			$return['status'] = 10;
			$return['msg']    = esc_html__( 'All plugins are compatible with your version of WordPress.', 'security-ninja' );
		} else {
			$plugins = get_plugins();
			foreach ( $bad as $plugin_path => $tmp ) {
				$plugin_name = esc_html( $tmp['name'] );
				if ( '' !== $return['details'] ) {
					// add comma if not empty
					$return['details'] .= ', ';
				}
				$return['details'] .= $plugin_name . ' <small>(' . esc_html__( 'tested up to', 'security-ninja' ) . ' ' . esc_html( $tmp['tested'] ) . ')</small>';
			}
			$return['msg']    = esc_html__( 'The following plugins may not be compatible with your version of WordPress:', 'security-ninja' ) . ' ' . implode(
				', ',
				array_map(
					function ( $item ) {
						return esc_html( $item['name'] );
					},
					$bad
				)
			);
			$return['status'] = 0;
		}
		return $return;
	}





	/**
	 * check if PHP is up-to-date
	 *
	 * @author  Lars Koudal
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @version v1.0.1  Tuesday, June 11th, 2024.
	 * @access  public static
	 * @return  mixed
	 */
	public static function php_ver() {
		if ( version_compare( PHP_VERSION, '5.6', '<' ) ) {
			$return = array(
				'status' => 0,
				/* translators: %s: current PHP version string */
				'msg'    => sprintf( __( 'Current PHP version: %s. This version is outdated and not recommended for use. Please upgrade to a newer version of PHP for improved performance and security.', 'security-ninja' ), PHP_VERSION ),
			);
		} elseif ( version_compare( PHP_VERSION, '7.4', '<=' ) ) {
			$return = array(
				'status' => 5,
				/* translators: %s: current PHP version string */
				'msg'    => sprintf( __( 'Current PHP version: %s. This version is below the recommended PHP version. Consider upgrading to PHP 7.4 or higher for better performance and security.', 'security-ninja' ), PHP_VERSION ),
			);
		} else {
			$return = array(
				'status' => 10,
				/* translators: %s: current PHP version string */
				'msg'    => sprintf( __( 'Current PHP version: %s. Your PHP version is okay.', 'security-ninja' ), PHP_VERSION ),
			);
		}

		return $return;
	}



	/**
	 * check if mysql is up-to-date
	 *
	 * @author  Lars Koudal
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @version v1.0.1  Tuesday, June 11th, 2024.
	 * @access  public static
	 * @return  mixed
	 */
	public static function mysql_ver() {
			global $wpdb;

			$mysql_version = $wpdb->get_var( 'SELECT VERSION()' );

		if ( version_compare( $mysql_version, '5.0', '<' ) ) {
				$return = array(
					'status' => 0,
					/* translators: %s: current MySQL version string */
					'msg'    => sprintf( __( 'Your MySQL version %s is outdated. Consider updating to a newer version for better performance and security.', 'security-ninja' ), $mysql_version ),
				);
		} elseif ( version_compare( $mysql_version, '5.6', '<' ) ) {
				$return = array(
					'status' => 5,
					/* translators: %s: current MySQL version string */
					'msg'    => sprintf( __( 'Your MySQL version %s is below the recommended version. Updating is recommended.', 'security-ninja' ), $mysql_version ),
				);
		} elseif ( version_compare( $mysql_version, '8.0', '<' ) ) {
				$return = array(
					'status' => 7,
					/* translators: %s: current MySQL version string */
					'msg'    => sprintf( __( 'Your MySQL version %s is below the optimal version. Consider updating to MySQL 8.0 or greater for best performance and security.', 'security-ninja' ), $mysql_version ),
				);
		} else {
				$return = array(
					'status' => 10,
					/* translators: %s: current MySQL version string */
					'msg'    => sprintf( __( 'Your MySQL version %s meets the recommended version for optimal performance and security.', 'security-ninja' ), $mysql_version ),
				);
		}

			return $return;
	}

	/**
	 * Try getting usernames from user IDs
	 *
	 * @author  Lars Koudal
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Monday, January 25th, 2021.
	 * @version v1.0.1  Tuesday, June 11th, 2024.
	 * @access  public static
	 * @return  mixed
	 */
	public static function usernames_enumeration() {
		$users    = get_users( 'number=10' );
		$success  = false;
		$url      = home_url() . '/?author=';
		$args     = array();
		$is_local = self::is_local_request();

		if ( $is_local ) {
			$args['sslverify']   = false;
			$args['redirection'] = 0;

		}

		$request_failed = false;
		foreach ( $users as $user ) {
			$response = wp_remote_get( $url . $user->ID, $args );
			if ( is_wp_error( $response ) ) {
				$request_failed = true;
				continue;
			}
			$response_code = wp_remote_retrieve_response_code( $response );
			if ( 301 === $response_code ) {
				$success = true;
				break;
			}
		} // foreach

		if ( $success ) {
			$return['status'] = 0;
			$return['msg']    = __( 'Username enumeration test passed.', 'security-ninja' );
		} elseif ( $request_failed ) {
			// Could not reach the site (e.g. timeout). Treat as inconclusive (Warning) rather than assuming enumeration is blocked.
			$return['status'] = 5;
			$return['msg']    = __( 'Could not determine username enumeration status - the site could not be reached.', 'security-ninja' );
		} else {
			$return['status'] = 10;
			$return['msg']    = __( 'Username enumeration test failed.', 'security-ninja' );
		}

		return $return;
	}
}
