<?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 {

	public static $security_tests;

	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 = in_array($_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1'));

		// 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)) {
			return array(
				'status'  => 10,
				'msg' => 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,
				'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'),
			'license.txt'            => __('Default license.txt file', 'security-ninja'),
			'readme.html'            => __('Default readme.html', '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
			$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
			$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  integer
	 */
	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;
			$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();
		$tested_file = ABSPATH . 'wp-config.php';
		$check = file_exists($tested_file);

		$return['details'] = sprintf(__('Looked for file here: %s', 'security-ninja'), esc_html(ABSPATH));
		if ($check) {
			$return['status'] = 0;
			$return['msg'] = __('wp-config.php file found in the WordPress root directory.', 'security-ninja');
		} else {
			$return['status'] = 10;
			$return['msg'] = __('wp-config.php file not found in the WordPress root directory. It is recommended to place wp-config.php in the root directory for standard WordPress installations.', 'security-ninja');
		}

		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;
			$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'] = 0;
			$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 ispresent 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 = in_array($_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1'));

		$response = wp_remote_get(
				add_query_arg('secnin-test', \mt_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 ispresent 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 = in_array($_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1'));

		$request = wp_remote_get(
			add_query_arg( 'secnin-test', \mt_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 = in_array($_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1'));
		
		$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;
			$return['msg'] = sprintf(__('User "%s" exists.', 'security-ninja'), $username);
		} else {
			$return['status'] = 10;
			$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;
			$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
		$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)));
		$active_plugins_text = sprintf(_n('%s is active.', '%s are active.', count($active_plugins), 'security-ninja'), number_format_i18n(count($active_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()) {
			$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));
			$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();
		$wp_themes_to_keep = array(
			'twentythirty',
			'twentytwentynine',
			'twentytwentyeight',
			'twentytwentyseven',
			'twentytwentysix',
			'twentytwentyfive',
			'twentytwentyfour',
			'twentytwentythree',
			'twentytwentytwo',
			'twentytwentyone',
			'twentytwenty',
			'twentynineteen',
			'twentyseventeen',
			'twentysixteen',
			'twentyfifteen',
			'twentyfourteen',
			'twentythirteen',
			'twentytwelve',
			'twentyeleven',
			'twentyten',
		);

		$get_template = get_template();
		$get_stylesheet = get_stylesheet();

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

		$newest_wp_found = false;
		foreach ($wp_themes_to_keep as $theme_slug) {
			if (!$newest_wp_found && isset($all_themes[$theme_slug])) {
				unset($all_themes[$theme_slug]);
				$newest_wp_found = true;
			}
		}

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

		if (count($themes_to_remove) > 0) {
			$return['status'] = 0;
			$return['msg'] = sprintf(__('Safe to remove %d themes: %s', 'security-ninja'), count($themes_to_remove), 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;
			$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;
			$return['msg'] = sprintf(__('Current file permissions are %s, which are not secure.', 'security-ninja'), $mode);
		} else {
			$return['status'] = 10;
			$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 = in_array($_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1'));

		$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)) {
			$return['status'] = 0;
			$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;
			$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 = in_array($_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1'));

    // Make a remote request to the home URL with a random query parameter
    $response = wp_remote_get(
			add_query_arg('secnin-test', \mt_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();
        $return['status'] = 0;
        $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;
        $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 = in_array($_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1'));

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

		if (is_wp_error($response)) {
			$error_message = $response->get_error_message();
			$return['status'] = 0;
			$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;
			$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 = in_array($_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1'));

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

			)
		);

		if (is_wp_error($response)) {
			$error_message = $response->get_error_message();
			$return['status'] = 0;
			$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 = in_array($_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1'));

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

				)
		);

		if (is_wp_error($response)) {
			$return['status'] = 0;
			$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;
			$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 = in_array($_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1'));

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

		if (is_wp_error($response)) {
			$error_message = $response->get_error_message();
			$return_array['status'] = 0;
			$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;
			$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 = in_array($_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1'));

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

		if (is_wp_error($response)) {
			$error_message = $response->get_error_message();
			$return['status'] = 0;
			$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;
			$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 = in_array($_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1'));
		
		$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)) {
			$return['status'] = 0;
			$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;
			$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;
			$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 = in_array($_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1'));
		
		$response = wp_remote_get(
				add_query_arg('secnin-test', mt_rand(), get_home_url()),
				array(
						'timeout'     => 25,
						'redirection' => 2,
						'sslverify'   => !$is_local,
				)
		);
		if (is_wp_error($response)) {
			$error_message = $response->get_error_message();
			$return['status'] = 0;
			$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;
			$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 = in_array($_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1'));

		$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
	 * Note: This function stores details about plugins and stores it in an option for later use in incompatible_plugins() - This test needs to be run before incompatible_plugins().
	 *
	 * @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();
		$wf_sn_active_plugins = 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;
			}

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

		update_option('wf_sn_active_plugins', $wf_sn_active_plugins, false);

		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;
			$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=' . urlencode($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 active plugins stored from the old_plugins() test
			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,
				'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,
				'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,
				'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,
							'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,
							'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,
							'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,
							'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 = [];
		$is_local = in_array($_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1'));

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

		}

		foreach ($users as $user) {
			$response      = wp_remote_get($url . $user->ID, $args);
			$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');
		} else {
			$return['status'] = 10;
			$return['msg'] = __('Username enumeration test failed.', 'security-ninja');
		}

		return $return;
	}
}
