<?php

namespace PasswordProtection;

use WP_PowerSuite\Captcha\Shared\ClientIp;
use WP_PowerSuite\Core\AbstractModule;
use WP_PowerSuite\Core\TransientPurger;
use WP_PowerSuite\Core\RequestSanitizer;
class Module extends AbstractModule {

	private const MODULE_ID = 'password-protection';
	private const MODULE_VERSION = '1.2.3';
	private const TRANSIENT_PREFIX = 'wpps_pwd_protect_';

	private const RATE_LIMIT_KEYS_OPTION = 'wpps_pwd_protect_rl_keys';
	private const COOKIE_NAME = 'wpps_pwd_access';
	private const MAX_ATTEMPTS = 5;
	private const LOCKOUT_DURATION = 900;

private ?array $settings_cache = null;

	/** @var bool One-time legacy plaintext → hash migration per request */
	private static bool $legacy_migration_ran = false;

	public function getId(): string {
		return self::MODULE_ID;
	}

	public function getName(): string {
		return __( 'Password Protection', 'wppowersuite-lite' );
	}

	public function getVersion(): string {
		return self::MODULE_VERSION;
	}

	public function getDescription(): string {
		return __( 'Put your whole site behind one shared password—ideal for staging, client previews, or a soft launch before you go public.', 'wppowersuite-lite' );
	}

	public function getContext(): string|array {
		return 'both';
	}

	public function getDependencies(): array {
		return [];
	}

		protected function registerHooks(): void {
		if ( ! $this->isLicenseActive() ) {
			return;
		}

		$this->maybeMigrateLegacyPlaintextPassword();

		if ( ! $this->gateIsActive() ) {
		return;
		}

		// Admin bar only when the gate is actually configured and user is an admin
		if ( is_user_logged_in() && current_user_can( 'manage_options' ) ) {
		add_action( 'admin_bar_menu', [ $this, 'addAdminBarNotice' ], 999 );
		add_action( 'wp_enqueue_scripts', [ $this, 'addAdminBarStyles' ], 999 );
		add_action( 'admin_enqueue_scripts', [ $this, 'addAdminBarStyles' ], 999 );
		add_action( 'admin_enqueue_scripts', [ $this, 'addAdminBarScript' ], 1000 );
		add_action( 'wp_enqueue_scripts', [ $this, 'addAdminBarScript' ], 1000 );
		}

		add_action( 'init', [ $this, 'handlePasswordSubmission' ], 1 );
		add_action( 'init', [ $this, 'checkXmlRpcAccess' ], 2 );
		add_action( 'admin_init', [ $this, 'checkAnonymousAjaxAccess' ], 1 );
		add_action( 'template_redirect', [ $this, 'checkAccess' ], 1 );
		add_action( 'rest_api_init', [ $this, 'registerRestProtection' ] );
	}


	/** Register REST filter only when REST API boots (not on every front request). */
	public function registerRestProtection(): void {
		add_filter( 'rest_authentication_errors', [ $this, 'protectRestApi' ], 999 );
	}

	public function handlePasswordSubmission(): void {
		if ( $this->shouldBypassGateForSystemRequest() || $this->isBackendContext() || $this->userBypassesGate() ) {
			return;
		}

		if ( RequestSanitizer::requestMethod() !== 'POST' ) {
			return;
		}

		if ( ! isset( $_POST['wpps_password_protection'] ) || ! isset( $_POST['wpps_site_password'] ) ) {
			return;
		}

		if ( ! isset( $_POST['wpps_password_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['wpps_password_nonce'] ) ), 'wpps_password_protection' ) ) {
			$this->handleFailedAttempt( __( 'Security check failed. Please try again.', 'wppowersuite-lite' ) );
			return;
		}

		if ( $this->isRateLimited() ) {
			$this->handleFailedAttempt( __( 'Too many failed attempts. Please try again in 15 minutes.', 'wppowersuite-lite' ) );
			return;
		}

		// Do not use sanitize_text_field(): it can alter passwords that contain HTML-like characters.
		$submitted_password = isset( $_POST['wpps_site_password'] ) ? (string) wp_unslash( $_POST['wpps_site_password'] ) : '';

		if ( ! $this->verifySubmittedPassword( $submitted_password ) ) {
			$this->recordFailedAttempt();
			$this->handleFailedAttempt( __( 'Incorrect password. Please try again.', 'wppowersuite-lite' ) );
			return;
		}

		$this->grantAccess();

		$redirect_url = ! empty( $_POST['wpps_redirect_to'] )
			? $this->sanitizeRedirectDestinationUrl( (string) wp_unslash( $_POST['wpps_redirect_to'] ) )
			: home_url( '/' );
		if ( $redirect_url === '' ) {
			$redirect_url = home_url( '/' );
		}

		$redirect_url = remove_query_arg( [ 'wpps_password_error', 'wpps_password_required' ], $redirect_url );
		
		wp_safe_redirect( $redirect_url );
		exit;
	}

	public function checkAccess(): void {
		if ( apply_filters( 'wpps_tl_is_token_login_request', false ) ) {
			return;
		}

		if ( ! $this->shouldApplyFrontendGate() ) {
			return;
		}

		// REST API is handled by protectRestApi().
		if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
			return;
		}

		if ( $this->isSearchEngineBot() ) {
			status_header( 403 );
			nocache_headers();
			wp_die( esc_html__( 'Access denied.', 'wppowersuite-lite' ), esc_html__( 'Access Denied', 'wppowersuite-lite' ), [ 'response' => 403 ] );
		}

		if ( $this->hasAccess() ) {
			return;
		}

		$this->showPasswordForm();
	}

	public function protectRestApi( $result ): mixed {
		if ( $this->shouldBypassGateForSystemRequest() || $this->userBypassesGate() || $this->hasAccess() ) {
			return $result;
		}

		return new \WP_Error(
			'rest_password_required',
			__( 'This site is password protected. Please enter the password to access the REST API.', 'wppowersuite-lite' ),
			[ 'status' => 403 ]
		);
	}

	/**
	 * Block anonymous admin-ajax.php handlers that never hit template_redirect.
	 */
	public function checkAnonymousAjaxAccess(): void {
		if ( ! wp_doing_ajax() || is_user_logged_in() ) {
			return;
		}

		if ( apply_filters( 'wpps_tl_is_token_login_request', false ) ) {
			return;
		}

		if ( ! $this->gateIsActive() || $this->shouldBypassGateForSystemRequest() || $this->userBypassesGate() || $this->hasAccess() ) {
			return;
		}

		if ( $this->isAllowlistedAjaxAction() ) {
			return;
		}

		status_header( 403 );
		wp_send_json(
			[
				'success' => false,
				'data'    => __( 'This site is password protected.', 'wppowersuite-lite' ),
			],
			403
		);
	}

	/**
	 * Block unauthenticated XML-RPC when the front-end gate is active.
	 */
	public function checkXmlRpcAccess(): void {
		if ( ! defined( 'XMLRPC_REQUEST' ) || ! XMLRPC_REQUEST ) {
			return;
		}

		if ( apply_filters( 'wpps_tl_is_token_login_request', false ) ) {
			return;
		}

		if ( ! $this->gateIsActive() || $this->shouldBypassGateForSystemRequest() || $this->userBypassesGate() || $this->hasAccess() ) {
			return;
		}

		status_header( 403 );
		nocache_headers();
		wp_die(
			esc_html__( 'This site is password protected.', 'wppowersuite-lite' ),
			esc_html__( 'Access Denied', 'wppowersuite-lite' ),
			[ 'response' => 403 ]
		);
	}

	private function isSearchEngineBot(): bool {
		$user_agent = strtolower( RequestSanitizer::userAgent() );
		if ( $user_agent === '' ) {
			return false;
		}

		static $pattern = null;
		if ( $pattern === null ) {
			$needles = [
				'googlebot', 'bingbot', 'slurp', 'duckduckbot', 'baiduspider', 'yandexbot',
				'sogou', 'exabot', 'facebot', 'ia_archiver', 'applebot', 'msnbot',
				'ahrefsbot', 'semrushbot', 'majestic', 'moz\.com', 'petalbot',
			];
			$quoted = array_map(
				static function ( string $p ): string {
					return preg_quote( $p, '/' );
				},
				$needles
			);
			$pattern = '/' . implode( '|', $quoted ) . '|\b(bot|crawler|spider)\b/i';
		}

		return (bool) preg_match( $pattern, $user_agent );
	}

	private function hasAccess(): bool {
		if ( ! isset( $_COOKIE[ self::COOKIE_NAME ] ) ) {
			return false;
		}

		$cookie_value = sanitize_text_field( wp_unslash( $_COOKIE[ self::COOKIE_NAME ] ) );
		$expected_value = $this->getAccessCookieValue();

		return hash_equals( $expected_value, $cookie_value );
	}

	private function grantAccess(): void {
		$cookie_value = $this->getAccessCookieValue();
		$secure         = is_ssl();
		$path           = defined( 'COOKIEPATH' ) ? COOKIEPATH : '/';
		$domain         = defined( 'COOKIE_DOMAIN' ) ? COOKIE_DOMAIN : '';

		if ( PHP_VERSION_ID >= 70300 ) {
			setcookie(
				self::COOKIE_NAME,
				$cookie_value,
				[
					'expires'  => 0,
					'path'     => $path,
					'domain'   => $domain,
					'secure'   => $secure,
					'httponly' => true,
					'samesite' => 'Lax',
				]
			);
		} else {
			setcookie( self::COOKIE_NAME, $cookie_value, 0, $path, $domain, $secure, true );
		}

		$_COOKIE[ self::COOKIE_NAME ] = $cookie_value;
		$this->clearFailedAttempts();
	}

	private function getAccessCookieValue(): string {
		$ip         = $this->getTrustedClientIp();
		$user_agent = RequestSanitizer::userAgent();
		$settings   = $this->getSettings();
		$secret     = $settings['site_password_hash'] ?? '';
		if ( $secret === '' ) {
			return '';
		}

		return hash_hmac( 'sha256', $ip . $user_agent . $secret, wp_salt() );
	}

	/**
	 * Client IP for cookies and rate limits (trusted proxy headers or REMOTE_ADDR).
	 */
	private function getTrustedClientIp(): string {
		return ClientIp::resolveOrDefault();
	}

	private function isRateLimited(): bool {
		$ip = $this->getTrustedClientIp();
		$lockout_key = self::TRANSIENT_PREFIX . 'lockout_' . md5( $ip );
		return ! empty( get_transient( $lockout_key ) );
	}

	private function recordFailedAttempt(): void {
		$ip = $this->getTrustedClientIp();
		$attempts_key = self::TRANSIENT_PREFIX . 'attempts_' . md5( $ip );
		$attempts = get_transient( $attempts_key ) ?: 0;
		$attempts++;

		set_transient( $attempts_key, $attempts, self::LOCKOUT_DURATION );
		TransientPurger::remember( self::RATE_LIMIT_KEYS_OPTION, $attempts_key );

		if ( $attempts >= self::MAX_ATTEMPTS ) {
			$lockout_key = self::TRANSIENT_PREFIX . 'lockout_' . md5( $ip );
			set_transient( $lockout_key, true, self::LOCKOUT_DURATION );
			TransientPurger::remember( self::RATE_LIMIT_KEYS_OPTION, $lockout_key );
		}
	}

	private function clearFailedAttempts(): void {
		$ip = $this->getTrustedClientIp();
		$attempts_key = self::TRANSIENT_PREFIX . 'attempts_' . md5( $ip );
		$lockout_key = self::TRANSIENT_PREFIX . 'lockout_' . md5( $ip );
		delete_transient( $attempts_key );
		delete_transient( $lockout_key );
	}

	private function handleFailedAttempt( string $message ): void {
		$flash_key = self::TRANSIENT_PREFIX . 'flash_' . md5( $this->getTrustedClientIp() );
		set_transient( $flash_key, $message, MINUTE_IN_SECONDS );
		TransientPurger::remember( self::RATE_LIMIT_KEYS_OPTION, $flash_key );

		$query_params = $this->getRequestQueryParams();
		unset( $query_params['wpps_password_error'] );
		$query_params['wpps_password_required'] = '1';

		$redirect_url = add_query_arg( $query_params, $this->getCurrentFrontUrl() );

		wp_safe_redirect( $redirect_url );
		exit;
	}

	private function showPasswordForm(): void {
		if ( ! defined( 'DONOTCACHEPAGE' ) ) {
			define( 'DONOTCACHEPAGE', true );
		}

		$this->renderPasswordForm( $this->consumeFlashError() );
		exit;
	}

	private function renderPasswordForm( string $error = '' ): void {
		nocache_headers();

		if ( ! headers_sent() ) {
			header( 'X-Frame-Options: SAMEORIGIN' );
			header( 'X-Content-Type-Options: nosniff' );
			header( 'Referrer-Policy: strict-origin-when-cross-origin' );
			header( 'X-Robots-Tag: noindex, nofollow', true );
		}

		$site_name = get_bloginfo( 'name' );
		$settings = $this->getSettings();

		if ( ! empty( $_GET['redirect_to'] ) ) {
			$requested_url = $this->sanitizeRedirectDestinationUrl( (string) wp_unslash( $_GET['redirect_to'] ) );
		} else {
			$query_params = $this->getRequestQueryParams();
			unset( $query_params['wpps_password_error'], $query_params['wpps_password_required'] );
			$clean_query   = http_build_query( $query_params );
			$normalized_uri = $this->normalizeRequestPath( strtok( RequestSanitizer::requestUri() ?: '/', '?' ) ?: '/' );
			$requested_url  = ! empty( $clean_query )
				? home_url( $normalized_uri . '?' . $clean_query )
				: home_url( $normalized_uri );
		}
		if ( $requested_url === '' ) {
			$requested_url = home_url( '/' );
		}

		$current_url = $this->getCurrentFrontUrl();
		?>
<!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
	<meta charset="<?php bloginfo( 'charset' ); ?>">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<meta name="robots" content="noindex,nofollow">
	<?php /* translators: %s: string value. */ ?>
	<title><?php echo esc_html( sprintf( __( 'Password Protected - %s', 'wppowersuite-lite' ), $site_name ) ); ?></title>
	<link rel="stylesheet" href="<?php echo esc_url( includes_url( 'css/dashicons.min.css' ) ); ?>" type="text/css" media="all" />
	<?php
	ob_start();
	?>
		* {
			margin: 0;
			padding: 0;
			box-sizing: border-box;
		}
		html {
			height: 100%;
		}
		body {
			font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
			font-size: 16px;
			line-height: 1.5;
			-webkit-font-smoothing: antialiased;
			<?php if ( ! empty( $settings['background_image'] ) ) : ?>
				background-image: url('<?php echo esc_url( $settings['background_image'] ); ?>');
				background-size: cover;
				background-position: center;
				background-repeat: no-repeat;
				position: relative;
			<?php else : ?>
				background: <?php echo esc_attr( $settings['background_color'] ); ?>;
			<?php endif; ?>
			display: flex;
			align-items: center;
			justify-content: center;
			min-height: 100%;
			min-height: 100vh;
			padding: 24px;
			margin: 0;
		}
		<?php if ( ! empty( $settings['background_image'] ) ) : ?>
		body::before {
			content: '';
			position: fixed;
			top: 0;
			left: 0;
			width: 100%;
			height: 100%;
			background-color: <?php echo esc_attr( $settings['overlay_color'] ); ?>;
			opacity: <?php echo esc_attr( floatval( $settings['overlay_opacity'] ) ); ?>;
			z-index: 0;
		}
		<?php endif; ?>
		.screen-reader-text {
			border: 0;
			clip: rect(1px, 1px, 1px, 1px);
			clip-path: inset(50%);
			height: 1px;
			margin: -1px;
			overflow: hidden;
			padding: 0;
			position: absolute;
			width: 1px;
			word-wrap: normal !important;
		}
		.wpps-password-form-container {
			background: <?php echo esc_attr( $settings['form_bg_color'] ); ?>;
			border-radius: 8px;
			box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
			padding: 40px 32px;
			max-width: 400px;
			width: 100%;
			position: relative;
			z-index: 1;
		}
		.wpps-password-form-logo {
			display: block;
			width: <?php echo esc_attr( absint( $settings['logo_width'] ?? 200 ) ); ?>px;
			max-width: 100%;
			height: auto;
			margin: 0 auto 24px;
		}
		.wpps-password-form-container h1 {
			font-size: 24px;
			font-weight: 600;
			margin: 0 0 8px;
			color: #23282d;
			text-align: center;
			line-height: 1.3;
		}
		.wpps-password-form-container .wpps-password-form-message {
			color: #646970;
			margin: 0 0 24px;
			font-size: 14px;
			text-align: center;
			line-height: 1.5;
		}
		.wpps-password-form-container .error {
			background: #fcf0f1;
			border-left: 4px solid #d63638;
			color: #721c24;
			padding: 12px;
			margin: 0 0 16px;
			border-radius: 4px;
			font-size: 14px;
			line-height: 1.5;
			text-align: left;
		}
		.wpps-password-form-container form {
			margin: 0;
			display: flex;
			flex-direction: column;
			gap: 12px;
		}
		.wpps-password-input-wrapper {
			position: relative;
			margin: 0;
		}
		.wpps-password-form-container input[type="password"],
		.wpps-password-form-container input[type="text"] {
			width: 100%;
			padding: 12px 44px;
			border: 1px solid #ddd;
			border-radius: 4px;
			font-size: 16px;
			line-height: 1.5;
			box-sizing: border-box;
			min-height: 48px;
			height: 48px;
			margin: 0;
			text-align: center;
			-webkit-appearance: none;
			appearance: none;
		}
		.wpps-password-form-container input[type="password"]::placeholder,
		.wpps-password-form-container input[type="text"]::placeholder {
			text-align: center;
			opacity: 0.6;
		}
		/* Ensure placeholder centers properly in all browsers */
		.wpps-password-form-container input[type="password"]::-webkit-input-placeholder,
		.wpps-password-form-container input[type="text"]::-webkit-input-placeholder {
			text-align: center;
		}
		.wpps-password-form-container input[type="password"]::-moz-placeholder,
		.wpps-password-form-container input[type="text"]::-moz-placeholder {
			text-align: center;
		}
		.wpps-password-form-container input[type="password"]:-ms-input-placeholder,
		.wpps-password-form-container input[type="text"]:-ms-input-placeholder {
			text-align: center;
		}
		.wpps-password-form-container input[type="password"]:hover,
		.wpps-password-form-container input[type="text"]:hover {
			border-color: <?php echo esc_attr( $settings['button_color'] ); ?>;
		}
		.wpps-password-form-container input[type="password"]:focus,
		.wpps-password-form-container input[type="text"]:focus {
			outline: none;
			border-color: <?php echo esc_attr( $settings['button_color'] ); ?>;
			box-shadow: 0 0 0 1px <?php echo esc_attr( $settings['button_color'] ); ?>;
		}
		.wpps-password-toggle {
			position: absolute;
			right: 12px;
			top: 50%;
			transform: translateY(-50%);
			background: transparent;
			border: none;
			cursor: pointer;
			padding: 4px;
			width: 32px;
			height: 32px;
			display: flex;
			align-items: center;
			justify-content: center;
			color: #646970;
			opacity: 1;
			transition: color 0.2s;
		}
		.wpps-password-toggle:hover {
			background: transparent !important;
			color: <?php echo esc_attr( $settings['button_color'] ); ?>;
		}
		.wpps-password-toggle:focus {
			background: transparent !important;
			outline: none;
			color: <?php echo esc_attr( $settings['button_color'] ); ?>;
		}
		.wpps-password-toggle:active {
			background: transparent !important;
			color: <?php echo esc_attr( $settings['button_color'] ); ?> !important;
			outline: none !important;
			box-shadow: none !important;
		}
		.wpps-password-toggle .dashicons {
			font-size: 20px;
			width: 20px;
			height: 20px;
			display: inline-block;
			line-height: 1;
			font-family: dashicons;
			font-weight: normal;
			font-style: normal;
			-webkit-font-smoothing: antialiased;
			-moz-osx-font-smoothing: grayscale;
		}
		.wpps-password-form-container button[type="submit"] {
			width: 100%;
			padding: 12px 16px;
			background: <?php echo esc_attr( $settings['button_color'] ); ?>;
			color: #fff;
			border: none;
			border-radius: 4px;
			font-size: 16px;
			font-weight: 600;
			cursor: pointer;
			transition: background 0.2s;
			line-height: 1.5;
			box-sizing: border-box;
			min-height: 48px;
			height: 48px;
			margin: 0;
		}
		.wpps-password-form-container button[type="submit"]:hover {
			background: <?php echo esc_attr( $settings['button_hover_color'] ); ?>;
		}
		.wpps-password-form-container button[type="submit"]:active {
			background: <?php echo esc_attr( $settings['button_color'] ); ?>;
		}
		@media (max-width: 480px) {
			.wpps-password-form-container {
				padding: 30px 20px;
			}
		}
	<?php
	\WP_PowerSuite\Core\InlineAssets::styleNow( 'wpps-password-gate', (string) ob_get_clean() );
	?>
</head>
<body>
	<div class="wpps-password-form-container">
		<?php if ( ! empty( $settings['logo_url'] ) ) : ?>
			<img src="<?php echo esc_url( $settings['logo_url'] ); ?>" alt="<?php esc_attr_e( 'Logo', 'wppowersuite-lite' ); ?>" class="wpps-password-form-logo" />
		<?php endif; ?>
		<h1><?php echo esc_html( ! empty( $settings['form_title'] ) ? $settings['form_title'] : __( 'Password Protected', 'wppowersuite-lite' ) ); ?></h1>
		<p class="wpps-password-form-message"><?php echo esc_html( ! empty( $settings['form_message'] ) ? $settings['form_message'] : __( 'This site is password protected. Please enter the password to continue.', 'wppowersuite-lite' ) ); ?></p>
		
		<?php if ( ! empty( $error ) ) : ?>
			<div class="error" role="alert"><?php echo esc_html( $error ); ?></div>
		<?php endif; ?>

		<form method="post" action="<?php echo esc_url( $current_url ); ?>">
			<?php wp_nonce_field( 'wpps_password_protection', 'wpps_password_nonce' ); ?>
			<input type="hidden" name="wpps_password_protection" value="1">
			<input type="hidden" name="wpps_redirect_to" value="<?php echo esc_attr( $requested_url ); ?>">
			<div class="wpps-password-input-wrapper">
				<label for="wpps_site_password" class="screen-reader-text"><?php esc_html_e( 'Site password', 'wppowersuite-lite' ); ?></label>
				<input type="password" id="wpps_site_password" name="wpps_site_password" placeholder="<?php esc_attr_e( 'Enter password', 'wppowersuite-lite' ); ?>" required autofocus>
				<button type="button" class="wpps-password-toggle" aria-label="<?php esc_attr_e( 'Show password', 'wppowersuite-lite' ); ?>" title="<?php esc_attr_e( 'Show password', 'wppowersuite-lite' ); ?>">
					<span class="dashicons dashicons-visibility"></span>
				</button>
			</div>
			<button type="submit"><?php echo esc_html( ! empty( $settings['button_text'] ) ? $settings['button_text'] : __( 'Submit', 'wppowersuite-lite' ) ); ?></button>
		</form>
	</div>
	<?php
	ob_start();
	?>
	(function() {
		var toggle = document.querySelector('.wpps-password-toggle');
		var passwordInput = document.getElementById('wpps_site_password');
		if (!toggle || !passwordInput) return;

		toggle.addEventListener('click', function() {
			if (passwordInput.type === 'password') {
				passwordInput.type = 'text';
				toggle.querySelector('.dashicons').classList.remove('dashicons-visibility');
				toggle.querySelector('.dashicons').classList.add('dashicons-hidden');
				toggle.setAttribute('aria-label', '<?php echo esc_js( __( 'Hide password', 'wppowersuite-lite' ) ); ?>');
			} else {
				passwordInput.type = 'password';
				toggle.querySelector('.dashicons').classList.remove('dashicons-hidden');
				toggle.querySelector('.dashicons').classList.add('dashicons-visibility');
				toggle.setAttribute('aria-label', '<?php echo esc_js( __( 'Show password', 'wppowersuite-lite' ) ); ?>');
			}
		});
	})();
	<?php
	\WP_PowerSuite\Core\InlineAssets::scriptNow( 'wpps-password-gate', (string) ob_get_clean() );
	?>
</body>
</html>
		<?php
	}

	public function addAdminBarNotice( \WP_Admin_Bar $wp_admin_bar ): void {
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}

		$wp_admin_bar->add_node(
			[
				'id'    => 'wpps-password-protected',
				'title' => '🔒 ' . __( 'Password Protected', 'wppowersuite-lite' ),
				'href'  => admin_url( 'admin.php?page=powersuite' ),
				'meta'  => [
					'class' => 'wpps-password-protected-notice',
					'title' => __( 'Password Protection module is enabled. Click to manage settings.', 'wppowersuite-lite' ),
					'data-module-id' => self::MODULE_ID,
				],
			]
		);
	}

	public function addAdminBarStyles(): void {
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}

		?>
		<?php ob_start(); ?>
			#wpadminbar #wp-admin-bar-wpps-password-protected > .ab-item {
				color: #d63638 !important;
				background: #fcf0f1 !important;
			}
			#wpadminbar #wp-admin-bar-wpps-password-protected:hover > .ab-item {
				background: #f5c2c7 !important;
			}
<?php \WP_PowerSuite\Core\InlineAssets::style( 'wpps-ia-modules-password-protection-module-php-style-2', (string) ob_get_clean() ); ?>
		<?php
	}

	public function addAdminBarScript(): void {
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}

		$module_id = self::MODULE_ID;
		$admin_url = admin_url( 'admin.php?page=powersuite' );
		?>
		<?php ob_start(); ?>
		(function() {
			var link = document.getElementById('wp-admin-bar-wpps-password-protected');
			if (!link) return;

			var anchor = link.querySelector('a');
			if (!anchor) return;

			anchor.addEventListener('click', function(e) {
				e.preventDefault();
				
				if (typeof WPPSAdmin !== 'undefined' && typeof WPPSAdmin.openAdvancedSettings === 'function') {
					WPPSAdmin.openAdvancedSettings('<?php echo esc_js( $module_id ); ?>');
				} else {
					if (typeof sessionStorage !== 'undefined') {
						sessionStorage.setItem('wpps_reopen_modal', '<?php echo esc_js( $module_id ); ?>');
					}
					window.location.href = '<?php echo esc_js( $admin_url ); ?>';
				}
			});
		})();
<?php \WP_PowerSuite\Core\InlineAssets::script( 'wpps-ia-modules-password-protection-module-php-script-2', (string) ob_get_clean() ); ?>
		<?php
	}

	private function isLicenseActive(): bool {
		return function_exists( 'wp_powersuite_is_license_valid' ) && wp_powersuite_is_license_valid();
	}

	private function getSettings(): array {
		if ( $this->settings_cache !== null ) {
			return $this->settings_cache;
		}

		$settings = $this->moduleSettings()->all( [] );
		if ( ! is_array( $settings ) ) {
			$settings = [];
		}

		$this->settings_cache = [
			'site_password_hash' => $settings['site_password_hash'] ?? '',
			'site_password' => $settings['site_password'] ?? '',
			'form_title' => $settings['form_title'] ?? '',
			'form_message' => $settings['form_message'] ?? '',
			'button_text' => $settings['button_text'] ?? '',
			'logo_url' => $settings['logo_url'] ?? '',
			'logo_width' => $settings['logo_width'] ?? '200',
			'background_image' => $settings['background_image'] ?? '',
			'overlay_color' => $settings['overlay_color'] ?? '#000000',
			'overlay_opacity' => $settings['overlay_opacity'] ?? '0.5',
			'background_color' => $settings['background_color'] ?? '#f5f5f5',
			'form_bg_color' => $settings['form_bg_color'] ?? '#ffffff',
			'button_color' => $settings['button_color'] ?? '#2271b1',
			'button_hover_color' => $settings['button_hover_color'] ?? '#135e96',
		];

		return $this->settings_cache;
	}

	/**
	 * wp-admin, wp-login.php, or admin-ajax.php for logged-in users (is_admin() is false on admin-ajax).
	 */
	private function isBackendContext(): bool {
		if ( is_admin() ) {
			return true;
		}
		if ( $this->isLoginScreenRequest() ) {
			return true;
		}
		if ( wp_doing_ajax() && is_user_logged_in() ) {
			return true;
		}
		return false;
	}

	/**
	 * Whether the public (theme) gate should run for this request.
	 */
	private function shouldApplyFrontendGate(): bool {
		if ( $this->shouldBypassGateForSystemRequest() || $this->isBackendContext() ) {
			return false;
		}
		if ( $this->userBypassesGate() ) {
			return false;
		}
		return (bool) apply_filters( 'wpps_password_protection_should_apply_gate', true );
	}

	/**
	 * Whether the current user may skip the site password gate.
	 */
	private function userBypassesGate(): bool {
		$user_id = get_current_user_id();

		if ( $user_id > 0 && user_can( $user_id, 'manage_options' ) ) {
			return (bool) apply_filters( 'wpps_password_protection_user_bypasses_gate', true, $user_id );
		}

		return (bool) apply_filters( 'wpps_password_protection_user_bypasses_gate', false, $user_id );
	}


	private function gateIsActive(): bool {
		if ( ! $this->isEnabled() ) {
			return false;
		}
		$s = $this->getSettings();
		return ! empty( $s['site_password_hash'] ) || ! empty( $s['site_password'] );
	}

	private function verifySubmittedPassword( string $plain ): bool {
		$s = $this->getSettings();
		if ( empty( $s['site_password_hash'] ) ) {
			return false;
		}

		return wp_check_password( $plain, $s['site_password_hash'] );
	}

	private function maybeMigrateLegacyPlaintextPassword(): void {
		if ( self::$legacy_migration_ran ) {
			return;
		}
		self::$legacy_migration_ran = true;

$stored = $this->moduleSettings()->all( [] );
		if ( ! is_array( $stored ) ) {
			return;
		}

		$legacy = isset( $stored['site_password'] ) ? (string) $stored['site_password'] : '';
		$hash = isset( $stored['site_password_hash'] ) ? (string) $stored['site_password_hash'] : '';

		if ( $legacy === '' || $hash !== '' ) {
			return;
		}

		if ( $this->passwordHashLooksLikeWpHash( $legacy ) ) {
			$stored['site_password_hash'] = $legacy;
			unset( $stored['site_password'] );
		} else {
			$stored['site_password_hash'] = wp_hash_password( $legacy );
			unset( $stored['site_password'] );
		}

		$this->moduleSettings()->save( $stored );
		$this->settings_cache = null;
	}

	private function passwordHashLooksLikeWpHash( string $value ): bool {
		return strlen( $value ) >= 34 && ( str_starts_with( $value, '$P$' ) || str_starts_with( $value, '$2y$' ) );
	}

	/**
	 * Delete all rate-limit / lockout transients for this gate (admin recovery).
	 *
	 * Uses the remembered-key registry so Redis/Memcached-backed lockouts are
	 * cleared even when they never had a row in wp_options.
	 *
	 * @return int Number of delete_transient() calls that returned true.
	 */
	public static function clearAllRateLimitTransients(): int {
		$deleted  = TransientPurger::deleteRemembered( self::RATE_LIMIT_KEYS_OPTION );
		$deleted += TransientPurger::deleteByPrefix( self::TRANSIENT_PREFIX );

		return $deleted;
	}

	private function shouldBypassGateForSystemRequest(): bool {
		if ( defined( 'WP_CLI' ) && WP_CLI ) {
			return true;
		}
		if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
			return true;
		}
		return function_exists( 'wp_doing_cron' ) && wp_doing_cron();
	}

	private function isLoginScreenRequest(): bool {
		return function_exists( 'is_login' ) && is_login();
	}

	/**
	 * @return array<string, string>
	 */
	private function getRequestQueryParams(): array {
		$query_string = RequestSanitizer::server( 'QUERY_STRING' );
		if ( $query_string === '' ) {
			return [];
		}
		parse_str( $query_string, $query_params );
		return is_array( $query_params ) ? $query_params : [];
	}

	private function normalizeRequestPath( string $request_uri ): string {
		$home_url_parsed = wp_parse_url( home_url() );
		$home_path       = isset( $home_url_parsed['path'] ) ? rtrim( (string) $home_url_parsed['path'], '/' ) : '';

		if ( $request_uri === '' ) {
			return '/';
		}

		if ( ! empty( $home_path ) && rtrim( $request_uri, '/' ) === $home_path ) {
			return '/';
		}

		if ( $request_uri === '/' ) {
			return '/';
		}

		if ( ! empty( $home_path ) && strpos( $request_uri, $home_path . '/' ) === 0 ) {
			$normalized_uri = substr( $request_uri, strlen( $home_path ) );
			return $normalized_uri === '' ? '/' : $normalized_uri;
		}

		return $request_uri;
	}

	private function getCurrentFrontUrl(): string {
		$request_uri = strtok( RequestSanitizer::requestUri() ?: '/', '?' ) ?: '/';
		return home_url( $this->normalizeRequestPath( $request_uri ) );
	}

	private function consumeFlashError(): string {
		$flash_key = self::TRANSIENT_PREFIX . 'flash_' . md5( $this->getTrustedClientIp() );
		$message   = get_transient( $flash_key );
		if ( is_string( $message ) && $message !== '' ) {
			delete_transient( $flash_key );
			return $message;
		}

		return '';
	}

	private function sanitizeRedirectDestinationUrl( string $url ): string {
		$url = trim( $url );
		if ( $url === '' ) {
			return '';
		}

		$url = esc_url_raw( $url );
		if ( $url === '' ) {
			return '';
		}

		$validated = wp_validate_redirect( $url, false );
		return ( is_string( $validated ) && $validated !== '' ) ? $validated : '';
	}

	private function isAllowlistedAjaxAction(): bool {
		$action = isset( $_REQUEST['action'] ) ? sanitize_key( wp_unslash( $_REQUEST['action'] ) ) : '';
		if ( $action === '' ) {
			return false;
		}

		$allowlisted = apply_filters( 'wpps_password_protection_allowlisted_ajax_actions', [] );
		return is_array( $allowlisted ) && in_array( $action, $allowlisted, true );
	}
}

