<?php
/**
 * Security hardening for Security Pack.
 *
 * @package ArkHost_Security_Pack
 */

// Prevent direct access.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Hardening class for security enhancements.
 */
class ARKSP_Hardening {

	/**
	 * Constructor.
	 */
	public function __construct() {
		// Disable XML-RPC.
		if ( ARKSP_Plugin::get_setting( 'disable_xmlrpc', true ) ) {
			add_filter( 'xmlrpc_enabled', '__return_false' );
			add_filter( 'wp_headers', array( $this, 'remove_xmlrpc_headers' ) );
			add_action( 'wp', array( $this, 'block_xmlrpc_requests' ), 1 );
		}

		// Disable file editing.
		if ( ARKSP_Plugin::get_setting( 'disable_file_editing', true ) ) {
			$this->disable_file_editing();
		}

		// Remove WordPress version.
		if ( ARKSP_Plugin::get_setting( 'remove_wp_version', true ) ) {
			add_filter( 'the_generator', '__return_empty_string' );
			remove_action( 'wp_head', 'wp_generator' );
			add_filter( 'style_loader_src', array( $this, 'remove_version_strings' ), 10, 2 );
			add_filter( 'script_loader_src', array( $this, 'remove_version_strings' ), 10, 2 );
		}

		// Add security headers.
		if ( ARKSP_Plugin::get_setting( 'add_security_headers', true ) ) {
			add_action( 'send_headers', array( $this, 'add_security_headers' ) );
		}

		// Restrict REST API.
		if ( ARKSP_Plugin::get_setting( 'restrict_rest_api', true ) ) {
			add_filter( 'rest_authentication_errors', array( $this, 'restrict_rest_api' ) );
		}

		// Disable application passwords for non-admins.
		if ( ARKSP_Plugin::get_setting( 'disable_application_passwords', false ) ) {
			add_filter( 'wp_is_application_passwords_available', '__return_false' );
		}

		// Remove unnecessary headers.
		add_action( 'init', array( $this, 'remove_unnecessary_headers' ) );

		// Disable user enumeration.
		if ( ARKSP_Plugin::get_setting( 'disable_user_enumeration', true ) ) {
			add_action( 'init', array( $this, 'block_author_scanning' ) );
			add_filter( 'rest_endpoints', array( $this, 'restrict_users_endpoint' ) );
			add_filter( 'oembed_response_data', array( $this, 'remove_author_from_oembed' ) );
		}

		// Disable pingbacks/trackbacks.
		if ( ARKSP_Plugin::get_setting( 'disable_pingbacks', true ) ) {
			add_filter( 'xmlrpc_methods', array( $this, 'disable_pingback_methods' ) );
			add_filter( 'wp_headers', array( $this, 'remove_pingback_header' ) );
			add_filter( 'pings_open', '__return_false', 20, 2 );
		}
	}

	/**
	 * Remove XML-RPC related headers.
	 *
	 * @param array $headers HTTP headers.
	 * @return array
	 */
	public function remove_xmlrpc_headers( $headers ) {
		unset( $headers['X-Pingback'] );
		return $headers;
	}

	/**
	 * Block direct XML-RPC requests.
	 */
	public function block_xmlrpc_requests() {
		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$request_uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '';

		if ( strpos( $request_uri, 'xmlrpc.php' ) !== false ) {
			status_header( 403 );
			exit( 'XML-RPC is disabled.' );
		}
	}

	/**
	 * Disable file editing in dashboard.
	 */
	private function disable_file_editing() {
		if ( ! defined( 'DISALLOW_FILE_EDIT' ) ) {
			define( 'DISALLOW_FILE_EDIT', true ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- WordPress core constant.
		}
	}

	/**
	 * Remove version strings from scripts and styles.
	 *
	 * @param string $src    Source URL.
	 * @param string $handle Handle name.
	 * @return string
	 */
	public function remove_version_strings( $src, $handle ) {
		if ( strpos( $src, 'ver=' ) !== false ) {
			$src = remove_query_arg( 'ver', $src );
		}
		return $src;
	}

	/**
	 * Add security headers.
	 */
	public function add_security_headers() {
		// Don't add headers for admin pages if user is logged in.
		if ( is_admin() && is_user_logged_in() ) {
			// Still add some basic headers.
			header( 'X-Content-Type-Options: nosniff' );
			return;
		}

		// Get header settings.
		$headers = ARKSP_Plugin::get_setting( 'security_headers', $this->get_default_headers() );

		// X-Content-Type-Options.
		if ( ! empty( $headers['x_content_type_options'] ) ) {
			header( 'X-Content-Type-Options: ' . $headers['x_content_type_options'] );
		}

		// X-Frame-Options.
		if ( ! empty( $headers['x_frame_options'] ) ) {
			header( 'X-Frame-Options: ' . $headers['x_frame_options'] );
		}

		// X-XSS-Protection.
		if ( ! empty( $headers['x_xss_protection'] ) ) {
			header( 'X-XSS-Protection: ' . $headers['x_xss_protection'] );
		}

		// Referrer-Policy.
		if ( ! empty( $headers['referrer_policy'] ) ) {
			header( 'Referrer-Policy: ' . $headers['referrer_policy'] );
		}

		// Permissions-Policy.
		if ( ! empty( $headers['permissions_policy'] ) ) {
			header( 'Permissions-Policy: ' . $headers['permissions_policy'] );
		}

		// Content-Security-Policy.
		if ( ! empty( $headers['content_security_policy'] ) ) {
			header( 'Content-Security-Policy: ' . $headers['content_security_policy'] );
		}

		// Strict-Transport-Security (HSTS).
		if ( ! empty( $headers['strict_transport_security'] ) && is_ssl() ) {
			header( 'Strict-Transport-Security: ' . $headers['strict_transport_security'] );
		}
	}

	/**
	 * Get default security headers.
	 *
	 * @return array
	 */
	public function get_default_headers() {
		return array(
			'x_content_type_options'    => 'nosniff',
			'x_frame_options'           => 'SAMEORIGIN',
			'x_xss_protection'          => '1; mode=block',
			'referrer_policy'           => 'strict-origin-when-cross-origin',
			'permissions_policy'        => 'geolocation=(), microphone=(), camera=()',
			'content_security_policy'   => '',
			'strict_transport_security' => 'max-age=31536000; includeSubDomains',
		);
	}

	/**
	 * Restrict REST API to authenticated users.
	 *
	 * @param WP_Error|null|bool $result Authentication result.
	 * @return WP_Error|null|bool
	 */
	public function restrict_rest_api( $result ) {
		// If there's already an error, return it.
		if ( is_wp_error( $result ) ) {
			return $result;
		}

		// Allow if user is logged in.
		if ( is_user_logged_in() ) {
			return $result;
		}

		// Get allowed REST routes.
		$allowed_routes = ARKSP_Plugin::get_setting( 'rest_api_allowed_routes', array() );

		// Always allow some essential routes.
		$essential_routes = array(
			'/wp/v2/oembed',
			'/wp-site-health',
		);

		$allowed_routes = array_merge( $allowed_routes, $essential_routes );

		// Get current route.
		$current_route = $GLOBALS['wp']->query_vars['rest_route'] ?? '';

		// Check if current route is allowed.
		foreach ( $allowed_routes as $route ) {
			if ( strpos( $current_route, $route ) === 0 ) {
				return $result;
			}
		}

		// Block unauthenticated access.
		return new WP_Error(
			'rest_not_logged_in',
			__( 'You must be authenticated to access this endpoint.', 'arkhost-security-pack' ),
			array( 'status' => 401 )
		);
	}

	/**
	 * Remove unnecessary headers.
	 */
	public function remove_unnecessary_headers() {
		// Remove Really Simple Discovery link.
		remove_action( 'wp_head', 'rsd_link' );

		// Remove Windows Live Writer manifest link.
		remove_action( 'wp_head', 'wlwmanifest_link' );

		// Remove shortlink.
		remove_action( 'wp_head', 'wp_shortlink_wp_head' );

		// Remove feed links.
		if ( ARKSP_Plugin::get_setting( 'remove_feed_links', false ) ) {
			remove_action( 'wp_head', 'feed_links', 2 );
			remove_action( 'wp_head', 'feed_links_extra', 3 );
		}
	}

	/**
	 * Block author scanning via ?author=N URLs.
	 */
	public function block_author_scanning() {
		if ( is_admin() ) {
			return;
		}

		// Block ?author=N requests for non-logged-in users.
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		if ( ! is_user_logged_in() && isset( $_GET['author'] ) ) {
			wp_safe_redirect( home_url(), 301 );
			exit;
		}
	}

	/**
	 * Restrict REST API users endpoint to authenticated users.
	 *
	 * @param array $endpoints REST API endpoints.
	 * @return array
	 */
	public function restrict_users_endpoint( $endpoints ) {
		if ( is_user_logged_in() ) {
			return $endpoints;
		}

		// Remove users endpoint for unauthenticated requests.
		if ( isset( $endpoints['/wp/v2/users'] ) ) {
			unset( $endpoints['/wp/v2/users'] );
		}
		if ( isset( $endpoints['/wp/v2/users/(?P<id>[\d]+)'] ) ) {
			unset( $endpoints['/wp/v2/users/(?P<id>[\d]+)'] );
		}

		return $endpoints;
	}

	/**
	 * Remove author information from oEmbed responses.
	 *
	 * @param array $data oEmbed response data.
	 * @return array
	 */
	public function remove_author_from_oembed( $data ) {
		if ( isset( $data['author_name'] ) ) {
			unset( $data['author_name'] );
		}
		if ( isset( $data['author_url'] ) ) {
			unset( $data['author_url'] );
		}
		return $data;
	}

	/**
	 * Disable pingback XML-RPC methods.
	 *
	 * @param array $methods XML-RPC methods.
	 * @return array
	 */
	public function disable_pingback_methods( $methods ) {
		unset( $methods['pingback.ping'] );
		unset( $methods['pingback.extensions.getPingbacks'] );
		return $methods;
	}

	/**
	 * Remove X-Pingback header.
	 *
	 * @param array $headers HTTP headers.
	 * @return array
	 */
	public function remove_pingback_header( $headers ) {
		unset( $headers['X-Pingback'] );
		return $headers;
	}
}
