<?php
/**
 * LoginPress Password Strength Class.
 *
 * Handles password strength validation and requirements.
 *
 * @package LoginPress
 * @since 4.0.0
 * @version 6.2.0
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

if ( ! class_exists( 'LoginPress_Password_Strength' ) ) :

	/**
	 * LoginPress Password Strength class.
	 *
	 * @since 4.0.0
	 */
	class LoginPress_Password_Strength {

		/**
		 * LoginPress settings array containing password strength configuration.
		 *
		 * @var array<string, mixed> Settings array with password strength requirements and options.
		 */
		public $loginpress_setting;

		/**
		 * Class constructor.
		 *
		 * @since 4.0.0
		 * @return void
		 */
		public function __construct() {
			$this->loginpress_setting = get_option( 'loginpress_setting' );
			$this->hooks();
		}

		/**
		 * Hook into actions and filters.
		 *
		 * @since 4.0.0
		 * @return void
		 */
		public function hooks() {

			$enable_password_strength = isset( $this->loginpress_setting['enable_pass_strength'] ) ? $this->loginpress_setting['enable_pass_strength'] : 'off';
			if ( 'on' === $enable_password_strength ) {

				$enable_pass_strength  = isset( $this->loginpress_setting['enable_pass_strength_forms'] ) ? $this->loginpress_setting['enable_pass_strength_forms'] : 'off';
				$strength_meter_enable = isset( $this->loginpress_setting['password_strength_meter'] ) ? $this->loginpress_setting['password_strength_meter'] : 'off';
				$register              = isset( $enable_pass_strength['register'] ) ? $enable_pass_strength['register'] : false;
				$wc_reset              = isset( $enable_pass_strength['wc_forms'] ) ? $enable_pass_strength['wc_forms'] : false;
				$wp_reset              = isset( $enable_pass_strength['reset'] ) ? $enable_pass_strength['reset'] : false;
				if ( $register ) {
					add_action( 'registration_errors', array( $this, 'validate_password_requirements' ), 10 );
				}
				if ( 'on' === $strength_meter_enable && $register ) {
					add_action( 'login_enqueue_scripts', array( $this, 'loginpress_password_strength_meter' ) );
				}
				if ( $wp_reset || $wc_reset ) {
					add_action( 'validate_password_reset', array( $this, 'validate_password_requirements' ), 10 );
				}
				if ( $wp_reset ) {
					add_filter( 'password_hint', array( $this, 'loginpress_password_hint' ) );
				}
				if ( $wc_reset ) {
					add_filter( 'woocommerce_get_script_data', array( $this, 'loginpress_wc_reset_password_hint' ), 10, 2 );
				} elseif ( ! $wc_reset ) {
					add_filter( 'woocommerce_get_script_data', array( $this, 'loginpress_wc_reset_remove_hint' ), 10, 2 );
				}
			}
		}

		/**
		 * Handles password field errors for registration form.
		 *
		 * @param WP_Error $errors WP_Error object.
		 * @since 4.0.0
		 * @return WP_Error object.
		 */
		public function validate_password_requirements( $errors ) {
			if ( ! is_wp_error( $errors ) ) {
				$errors = new WP_Error();
			}

			$enable_pass_strength = isset( $this->loginpress_setting['enable_pass_strength_forms'] ) ? $this->loginpress_setting['enable_pass_strength_forms'] : 'off';
			$wc_reset             = isset( $enable_pass_strength['wc_forms'] ) ? $enable_pass_strength['wc_forms'] : false;
			$wp_reset             = isset( $enable_pass_strength['reset'] ) ? $enable_pass_strength['reset'] : false;

			$password = '';
			if ( current_filter() === 'registration_errors' ) {
				$password = isset( $_POST['loginpress-reg-pass'] ) ? sanitize_text_field( wp_unslash( $_POST['loginpress-reg-pass'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Registration form validation.
			} elseif ( current_filter() === 'validate_password_reset' ) {
				$wc_password = isset( $_POST['password_1'] ) ? sanitize_text_field( wp_unslash( $_POST['password_1'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Password reset validation.
				$wp_password = isset( $_POST['pass1'] ) ? sanitize_text_field( wp_unslash( $_POST['pass1'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Password reset validation.

				if ( $wc_password ) {
					$password = $wc_password;
				} elseif ( $wp_password ) {
					$password = $wp_password;
				}
			}

			$validate_filter = current_filter() === 'validate_password_reset' ? true : false;

			if ( isset( $_POST['password_1'] ) && false === $wc_reset && $validate_filter ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Password validation logic.
				return $errors;
			}
			if ( ! isset( $_POST['password_1'] ) && $wc_reset && false === $wp_reset && $validate_filter ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Password validation logic.
				return $errors;
			}
			if ( ! isset( $_POST['pass1'] ) && ! isset( $_POST['password_1'] ) && $validate_filter ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Password validation logic.
				return $errors;
			}

			$min_length           = isset( $this->loginpress_setting['minimum_pass_char'] ) ? $this->loginpress_setting['minimum_pass_char'] : '';
			$require_upper_lower  = isset( $this->loginpress_setting['pass_strength']['lower_upper_char_must'] ) ? $this->loginpress_setting['pass_strength']['lower_upper_char_must'] : 'off';
			$require_special_char = isset( $this->loginpress_setting['pass_strength']['special_char_must'] ) ? $this->loginpress_setting['pass_strength']['special_char_must'] : 'off';
			$integer_number_must  = isset( $this->loginpress_setting['pass_strength']['integer_no_must'] ) ? $this->loginpress_setting['pass_strength']['integer_no_must'] : 'off';
			if ( $min_length ) {
				if ( strlen( $password ) < $min_length ) {
					$errors->add(
						'password_too_short',
						sprintf(
						// translators: Minimum password length.
							esc_html__( 'Password must be at least %d characters long.', 'loginpress' ),
							$min_length
						)
					);
				}
			}
			if ( 'off' !== $require_upper_lower ) {
				if ( ( ! preg_match( '/[A-Z]/', $password ) || ! preg_match( '/[a-z]/', $password ) ) ) {
					$errors->add( 'password_upper_lower_case', esc_html__( 'Password must contain at least one both upper and lower case letters.', 'loginpress' ) );
				}
			}
			if ( 'off' !== $require_special_char ) {
				if ( ! preg_match( '/[^a-zA-Z0-9]/', $password ) ) {
					$errors->add( 'password_special_character', esc_html__( 'Password must contain at least one special character.', 'loginpress' ) );
				}
			}
			if ( 'off' !== $integer_number_must ) {
				if ( ! preg_match( '/[0-9]/', $password ) ) {
					$errors->add( 'password_integer_number', esc_html__( 'Password must contain at least one integer number.', 'loginpress' ) );
				}
			}

			return $errors;
		}

		/**
		 * Enqueue password strength meter script.
		 *
		 * @since 4.0.0
		 * @access public
		 * @return void
		 */
		public function loginpress_password_strength_meter() {
			wp_enqueue_script( 'loginpress-pasword-strength-meter', plugins_url( 'js/password-strength-meter.js', defined( 'LOGINPRESS_ROOT_FILE' ) ? LOGINPRESS_ROOT_FILE : '' ), array( 'jquery', 'password-strength-meter' ), defined( 'LOGINPRESS_VERSION' ) ? LOGINPRESS_VERSION : '1.0.0', true );
		}

		/**
		 * Show password hint message based on password strength settings.
		 *
		 * @since 4.0.0
		 * @access public
		 * @param string $hint the password hint message.
		 * @return string the modified hint message.
		 */
		public function loginpress_password_hint( $hint ) {

			$hint = self::loginpress_hint_creator();
			return $hint;
		}

		/**
		 * Modify WooCommerce password strength meter hint message with LoginPress password strength message.
		 *
		 * @since 4.0.0
		 * @access public
		 * @param array<string, mixed> $params the password strength meter parameters.
		 * @param string               $handle the handle of the script.
		 * @return array<string, mixed> the modified password strength meter parameters.
		 */
		public function loginpress_wc_reset_password_hint( $params, $handle ) {
			$hint = self::loginpress_hint_creator();
			if ( 'wc-password-strength-meter' === $handle && isset( $params['i18n_password_hint'] ) ) {
				$params['i18n_password_hint'] = $hint;
			}
			return $params;
		}

		/**
		 * Remove the password strength meter hint message on WooCommerce reset password page.
		 *
		 * @since 4.0.0
		 * @access public
		 * @param array<string, mixed> $params the password strength meter parameters.
		 * @param string               $handle the handle of the script.
		 * @return array<string, mixed> the modified password strength meter parameters.
		 */
		public function loginpress_wc_reset_remove_hint( $params, $handle ) {
			if ( 'wc-password-strength-meter' === $handle && isset( $params['i18n_password_hint'] ) ) {
				$params['i18n_password_hint'] = '';
			}
			return $params;
		}

		/**
		 * Create a hint based on password strength settings.
		 *
		 * @since 4.0.0
		 * @access public
		 * @return string the hint message.
		 */
		public static function loginpress_hint_creator() {
			$loginpress_setting   = get_option( 'loginpress_setting' );
			$min_length           = isset( $loginpress_setting['minimum_pass_char'] ) ? $loginpress_setting['minimum_pass_char'] : '';
			$require_upper_lower  = isset( $loginpress_setting['pass_strength']['lower_upper_char_must'] ) ? $loginpress_setting['pass_strength']['lower_upper_char_must'] : 'off';
			$require_special_char = isset( $loginpress_setting['pass_strength']['special_char_must'] ) ? $loginpress_setting['pass_strength']['special_char_must'] : 'off';
			$integer_number_must  = isset( $loginpress_setting['pass_strength']['integer_no_must'] ) ? $loginpress_setting['pass_strength']['integer_no_must'] : 'off';
			$upper_lower_text     = 'off' !== $require_upper_lower ? esc_html__( 'Require upper and lower case letters.', 'loginpress' ) : '';
			$special_char_text    = 'off' !== $require_special_char ? esc_html__( 'Require special characters like ! " ? $ % ^ & *', 'loginpress' ) : '';
			$integer_number_text  = 'off' !== $integer_number_must ? esc_html__( 'Require numbers.', 'loginpress' ) : '';

			$hint = sprintf(
						// translators: Minimum password length.
				__( 'Hint: The password should be at least %1$d characters long. %2$s %3$s %4$s ', 'loginpress' ),
				$min_length,
				$upper_lower_text,
				$integer_number_text,
				$special_char_text
			);
			return $hint;
		}
	}

endif;
