<?php
/**
 * Handle security headers settings.
 *
 * @package WP_Defender\Model\Setting
 */

namespace WP_Defender\Model\Setting;

use Calotes\Model\Setting;
use WP_Defender\Component\Security_Headers\Sh_X_Frame;
use WP_Defender\Component\Security_Headers\Sh_Feature_Policy;
use WP_Defender\Component\Security_Headers\Sh_XSS_Protection;
use WP_Defender\Component\Security_Headers\Sh_Referrer_Policy;
use WP_Defender\Component\Security_Headers\Sh_Strict_Transport;
use WP_Defender\Component\Security_Headers\Sh_Content_Type_Options;

/**
 * Model for security headers settings.
 */
class Security_Headers extends Setting {

	/**
	 * Option name.
	 *
	 * @var string
	 */
	protected $table = 'wd_security_headers_settings';

	/**
	 * Is X-Frame-Options activated.
	 *
	 * @var bool
	 * @defender_property
	 */
	public $sh_xframe = false;
	/**
	 * X-Frame-Options value.
	 *
	 * @var string
	 * @defender_property
	 */
	public $sh_xframe_mode = 'sameorigin';
	/**
	 * Is XSS Protection activated.
	 *
	 * @var bool
	 * @defender_property
	 */
	public $sh_xss_protection = false;
	/**
	 * XSS Protection value.
	 *
	 * @var string
	 * @defender_property
	 */
	public $sh_xss_protection_mode = 'sanitize';
	/**
	 * Is Content-Type Options activated.
	 *
	 * @var bool
	 * @defender_property
	 */
	public $sh_content_type_options = false;
	/**
	 * Content-Type Options value.
	 *
	 * @var string
	 * @defender_property
	 */
	public $sh_content_type_options_mode = 'nosniff';
	/**
	 * Is Strict Transport Security activated.
	 *
	 * @var bool
	 * @defender_property
	 */
	public $sh_strict_transport = false;
	/**
	 * Should preload HSTS?
	 *
	 * @var int
	 * @defender_property
	 */
	public $hsts_preload = 0;
	/**
	 * Should include subdomains?
	 *
	 * @var int
	 * @defender_property
	 */
	public $include_subdomain = 0;
	/**
	 * HSTS cache duration.
	 *
	 * @var string
	 * @defender_property
	 */
	public $hsts_cache_duration = '30 days';
	/**
	 * Is Referrer Policy activated.
	 *
	 * @var bool
	 * @defender_property
	 */
	public $sh_referrer_policy = false;
	/**
	 * Referrer Policy value.
	 *
	 * @var string
	 * @defender_property
	 */
	public $sh_referrer_policy_mode = 'origin-when-cross-origin';
	/**
	 * Is Feature Policy activated.
	 *
	 * @var bool
	 * @defender_property
	 */
	public $sh_feature_policy = false;
	/**
	 * Feature Policy value.
	 *
	 * @var string
	 * @defender_property
	 */
	public $sh_feature_policy_mode = 'self';
	/**
	 * Feature Policy URLs.
	 *
	 * @var string
	 * @defender_property
	 * @sanitize sanitize_textarea_field
	 */
	public $sh_feature_policy_urls = '';
	/**
	 * Contains all the data generated by rules
	 *
	 * @var array
	 */
	public $data = array();

	/**
	 * Define settings labels.
	 *
	 * @return array
	 */
	public function labels(): array {
		return array(
			'sh_xframe'                    => esc_html__( 'Enable X-Frame-Options', 'wpdef' ),
			'sh_xframe_mode'               => esc_html__( 'X-Frame-Options mode', 'wpdef' ),
			'sh_xss_protection'            => esc_html__( 'Enable X-XSS-Protection', 'wpdef' ),
			'sh_xss_protection_mode'       => esc_html__( 'X-XSS-Protection mode', 'wpdef' ),
			'sh_content_type_options'      => esc_html__( 'Enable X-Content-Type-Options', 'wpdef' ),
			'sh_content_type_options_mode' => esc_html__( 'X-Content-Type-Options mode', 'wpdef' ),
			'sh_strict_transport'          => esc_html__( 'Enable Strict Transport', 'wpdef' ),
			'hsts_preload'                 => esc_html__( 'HSTS Preload', 'wpdef' ),
			'include_subdomain'            => esc_html__( 'Include Subdomains', 'wpdef' ),
			'hsts_cache_duration'          => esc_html__( 'Browser caching', 'wpdef' ),
			'sh_referrer_policy'           => esc_html__( 'Enable Referrer Policy', 'wpdef' ),
			'sh_referrer_policy_mode'      => esc_html__( 'Referrer Information', 'wpdef' ),
			'sh_feature_policy'            => esc_html__( 'Enable Permissions-Policy', 'wpdef' ),
			'sh_feature_policy_mode'       => esc_html__( 'Permissions-Policy mode', 'wpdef' ),
			'sh_feature_policy_urls'       => esc_html__( 'Specific Origins', 'wpdef' ),
		);
	}

	/**
	 * Get headers.
	 *
	 * @return array
	 */
	public function get_headers(): array {
		return array(
			Sh_X_Frame::$rule_slug              => new Sh_X_Frame(),
			Sh_XSS_Protection::$rule_slug       => new Sh_XSS_Protection(),
			Sh_Content_Type_Options::$rule_slug => new Sh_Content_Type_Options(),
			Sh_Strict_Transport::$rule_slug     => new Sh_Strict_Transport(),
			Sh_Referrer_Policy::$rule_slug      => new Sh_Referrer_Policy(),
			Sh_Feature_Policy::$rule_slug       => new Sh_Feature_Policy(),
		);
	}

	/**
	 * Filter the security headers and return data as array.
	 *
	 * @param  bool $sort  Should headers be sorted.
	 *
	 * @return array
	 */
	public function get_headers_as_array( $sort = false ): array {
		$headers = $this->get_headers();
		$data    = array();
		foreach ( $headers as $header ) {
			$data[ $header::$rule_slug ] = array(
				'slug'  => $header::$rule_slug,
				'title' => $header->get_title(),
				'diff'  => $header->get_misc_data(),
			);
		}

		if ( $sort ) {
			ksort( $data );
		}

		return $data;
	}

	/**
	 * Get data values
	 *
	 * @param  mixed $key  The key to retrieve the value for.
	 *
	 * @return mixed|null The value associated with the given key, or null if the key does not exist.
	 */
	public function get_data_values( $key ) {
		if ( is_array( $this->data ) && isset( $this->data[ $key ] ) ) {
			return $this->data[ $key ];
		}

		return null;
	}

	/**
	 * Set the value of a data key.
	 *
	 * @param  mixed $key  The key to set the value for.
	 * @param  mixed $value  The value to set. If null, the key will be unset.
	 *
	 * @return void
	 */
	public function set_data_values( $key, $value ) {
		if ( null === $value ) {
			unset( $this->data[ $key ] );
		} elseif ( is_array( $this->data ) ) {
			$this->data[ $key ] = $value;
		}
		$this->save();
	}

	/**
	 * Validates the security headers settings.
	 *
	 * @return void
	 */
	protected function after_validate(): void {
		if ( true === $this->sh_xframe && ! in_array( $this->sh_xframe_mode, array( 'sameorigin', 'deny' ), true ) ) {
			$this->errors[] = esc_html__( 'X-Frame-Options mode is invalid', 'wpdef' );
		} elseif ( true === $this->sh_xss_protection && ! in_array( $this->sh_xss_protection_mode, array( 'sanitize', 'block', 'none' ), true ) ) {
			$this->errors[] = esc_html__( 'X-XSS-Protection mode is invalid', 'wpdef' );
		} elseif ( true === $this->sh_referrer_policy
					&& ! in_array(
						$this->sh_referrer_policy_mode,
						array(
							'no-referrer',
							'no-referrer-when-downgrade',
							'origin',
							'origin-when-cross-origin',
							'same-origin',
							'strict-origin',
							'strict-origin-when-cross-origin',
							'unsafe-url',
						),
						true
					)
		) {
			$this->errors[] = esc_html__( 'Referrer Policy mode is invalid', 'wpdef' );
		}
	}

	/**
	 * Refresh headers.
	 *
	 * @return array
	 */
	public function refresh_headers(): array {
		$defined_headers = $this->get_headers();
		$enabled         = array();
		foreach ( $defined_headers as $header ) {
			if ( true === $header->check() ) {
				$enabled[] = array( 'title' => $header->get_title() );
			}
		}

		return $enabled;
	}

	/**
	 * Get active, inactive or both types of headers.
	 *
	 * @param  string $type  Can be active|inactive|both. Default 'both'.
	 *
	 * @return array
	 */
	public function get_headers_by_type( string $type = 'both' ): array {
		$headers = array(
			'active'   => array(),
			'inactive' => array(),
		);
		if ( ! in_array( $type, array( 'active', 'inactive', 'both' ), true ) ) {
			return $headers;
		}

		$url = network_admin_url( 'admin.php?page=wdf-advanced-tools&view=security-headers#' );
		foreach ( $this->get_headers() as $header ) {
			$key               = true === $header->check() ? 'active' : 'inactive';
			$headers[ $key ][] = array(
				'title' => $header->get_title(),
				'url'   => $url . $header::$rule_slug,
			);
		}

		return 'both' === $type ? $headers : $headers[ $type ];
	}

	/**
	 * Check if any of the security headers are activated.
	 *
	 * @return bool Returns true if any of the security headers are activated, false otherwise.
	 */
	public function is_any_activated(): bool {
		if (
			true === $this->sh_xframe
			|| true === $this->sh_xss_protection
			|| true === $this->sh_content_type_options
			|| true === $this->sh_feature_policy
			|| true === $this->sh_strict_transport
			|| true === $this->sh_referrer_policy
		) {
			return true;
		}

		return false;
	}

	/**
	 * Get enabled headers.
	 *
	 * @param  int $total  How many headers to return.
	 *
	 * @return array
	 */
	public function get_enabled_headers( int $total = 3 ): array {
		$defined_headers = $this->get_headers();
		$enabled         = array();
		foreach ( $defined_headers as $header ) {
			if ( true === $header->check() ) {
				$enabled[ $header::$rule_slug ] = array( 'title' => $header->get_title() );
			}
		}

		return array_slice( $enabled, 0, $total );
	}

	/**
	 * Get module name.
	 *
	 * @return string
	 */
	public static function get_module_name(): string {
		return esc_html__( 'Security Headers', 'wpdef' );
	}
}