<?php

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

trait WPMR_Firewall {

	/**
	 * Render the Security Hardening section header.
	 *
	 * Used as the Settings API section callback for the firewall settings screen.
	 * Currently outputs a small summary banner (attacks blocked) and a plugin
	 * rating prompt.
	 *
	 * Call site:
	 * - `add_settings_section( 'wpmr_fw', ..., array( $this, 'firewall_section_ui' ), 'wpmr_firewall' )`
	 *   in `register_settings()`.
	 *
	 * Better name: render_firewall_settings_section()
	 *
	 * @return void
	 */

	function firewall_section_ui() {
		?>
		<?php
		$attacks = $this->get_setting( 'attacks' );
		if ( ! empty( $attacks ) ) {
			echo '<p style="display:table;padding:.618em 1em;background:#080;color:white;">Malcure has blocked ' . esc_html( $attacks ) . ' attacks on your website till date! <a style="font-weight:bolder;color:white" target="_blank" href="https://wordpress.org/support/plugin/wp-malware-removal/reviews/#new-post" title="Rate ' . $this->get_plugin_data( $this->file )['Name'] . '">Rate the plugin</a>&nbsp;★★★★★</strong></p>';
		}
	}

	/**
	 * Render the “Block Path Traversal Attack” checkbox field.
	 *
	 * Call site:
	 * - `add_settings_field( 'fw_block_path_traversal', '', array( $this, 'fw_block_path_traversal_ui' ), 'wpmr_firewall', 'wpmr_fw' )`
	 *   in `register_settings()`.
	 *
	 * Better name: render_setting_fw_block_path_traversal()
	 *
	 * @return void
	 */

	function fw_block_path_traversal_ui() {
		$value = $this->get_fw_setting( 'fw_block_path_traversal' );
		?>
		<label><input type="checkbox" value="yes" <?php checked( 'yes', $value ); ?> name="wpmr_fw_settings[fw_block_path_traversal]" />Block Path Traversal Attack</label>
		<?php
	}

	/**
	 * Render the “Block attack via PHP file upload” checkbox field.
	 *
	 * Call site:
	 * - `add_settings_field( 'fw_disable_php_upload', '', array( $this, 'fw_disable_php_upload_ui' ), 'wpmr_firewall', 'wpmr_fw' )`
	 *   in `register_settings()`.
	 *
	 * Better name: render_setting_fw_disable_php_upload()
	 *
	 * @return void
	 */

	function fw_disable_php_upload_ui() {
		$value = $this->get_fw_setting( 'fw_disable_php_upload' );
		?>
		<label><input type="checkbox" value="yes" <?php checked( 'yes', $value ); ?> name="wpmr_fw_settings[fw_disable_php_upload]" />Block attack via PHP file upload</label>
		<?php
	}

	/**
	 * Render the “Block listing of users via API” checkbox field.
	 *
	 * Call site:
	 * - `add_settings_field( 'fw_disable_restapi_user_listing', '', array( $this, 'fw_disable_restapi_user_listing_ui' ), 'wpmr_firewall', 'wpmr_fw' )`
	 *   in `register_settings()`.
	 *
	 * Better name: render_setting_fw_disable_restapi_user_listing()
	 *
	 * @return void
	 */

	function fw_disable_restapi_user_listing_ui() {
		$value = $this->get_fw_setting( 'fw_disable_restapi_user_listing' );
		?>
		<label><input type="checkbox" value="yes" <?php checked( 'yes', $value ); ?> name="wpmr_fw_settings[fw_disable_restapi_user_listing]" />Block listing of users via API</label>
		<?php
	}

	/**
	 * Render the “Block enumeration of users” checkbox field.
	 *
	 * Call site:
	 * - `add_settings_field( 'fw_disable_user_enumeration', '', array( $this, 'fw_disable_user_enumeration_ui' ), 'wpmr_firewall', 'wpmr_fw' )`
	 *   in `register_settings()`.
	 *
	 * Better name: render_setting_fw_disable_user_enumeration()
	 *
	 * @return void
	 */

	function fw_disable_user_enumeration_ui() {
		$value = $this->get_fw_setting( 'fw_disable_user_enumeration' );
		?>
		<label><input type="checkbox" value="yes" <?php checked( 'yes', $value ); ?> name="wpmr_fw_settings[fw_disable_user_enumeration]" />Block enumeration of users by web-scrapers / bots</label>
		<?php
	}

	/**
	 * Return the regex used to detect/remove the wp-config.php WAF bootstrap block.
	 *
	 * The pattern matches the injected comment block:
	 * `// BEGIN WPMR_WAF ... // END WPMR_WAF`.
	 *
	 * Used by `patch_wp_config()` and `unpatch_wp_config()`.
	 *
	 * Better name: get_wp_config_waf_marker_regex()
	 *
	 * @return string PCRE pattern with delimiters.
	 */

	function get_config_search() {
		return '`\/\/\sBEGIN\sWPMR_WAF.*\/\/\sEND WPMR_WAF\s?+`s';
	}

	/**
	 * Inject a bootstrap include into wp-config.php to load the WAF earlier.
	 *
	 * Writes a `// BEGIN WPMR_WAF` block immediately after the opening `<?php`.
	 * Creates a `wp-config.bak.php` backup in the same directory first.
	 *
	 * Notes:
	 * - The injected code checks `file_exists()` before `@include_once`.
	 * - This method expects `$this->get_wp_config_path()` to return the absolute
	 *   path to wp-config.php (method is defined elsewhere).
	 *
	 * Better name: enable_wp_config_waf_bootstrap()
	 *
	 * @return int|false|\WP_Error|null Bytes written, false on failure, WP_Error when backup fails, or null when no changes are made.
	 */

	function patch_wp_config() {
		$file            = WPMR_PLUGIN_DIR . 'inc/waf.php';
		$config_contents = "<?php\n// BEGIN WPMR_WAF. Security Firewall Installed by https://wordpress.org/plugins/wp-malware-removal/\nif( file_exists( '$file' ) ) {\n\t@include_once '$file';\n}\n// END WPMR_WAF";
		$config_search   = $this->get_config_search();
		$wp_config_path  = $this->get_wp_config_path();
		if ( $wp_config_path ) {
			$wp_config   = file_get_contents( $wp_config_path );
			$waf_enabled = preg_match( $config_search, $wp_config );
			if ( ! $waf_enabled ) {
				$wp_config = preg_replace( '`<\?php`s', $config_contents, $wp_config, 1 );
				if ( @copy( $wp_config_path, trailingslashit( dirname( $wp_config_path ) ) . 'wp-config.bak.php' ) ) { // backup in case something goes wrong
					return file_put_contents( $wp_config_path, $wp_config, LOCK_EX );
				} else {
					return new WP_Error( 'broke', 'Can\'t make backup. Aborting!' );
				}
			}
		}
	}

	/**
	 * Remove the WAF bootstrap block from wp-config.php (if present).
	 *
	 * Searches wp-config.php for the marker regex from `get_config_search()` and
	 * removes the entire injected block.
	 *
	 * Better name: disable_wp_config_waf_bootstrap()
	 *
	 * @return int|false|null Bytes written, false on failure, or null when no changes are made.
	 */

	function unpatch_wp_config() {
		$wp_config_path = $this->get_wp_config_path();
		if ( $wp_config_path ) {
			$wp_config     = file_get_contents( $wp_config_path );
			$config_search = $this->get_config_search();
			$waf_enabled   = preg_match( $config_search, $wp_config );
			if ( $waf_enabled ) {
				$wp_config = preg_replace( $config_search, '', $wp_config );
				return file_put_contents( $wp_config_path, $wp_config, LOCK_EX );
			}
		}
	}

	/**
	 * Run the runtime Web Application Firewall (WAF) checks.
	 *
	 * Hooked very early to `plugins_loaded` (priority `-1`) so it can inspect the
	 * request before most plugins execute. The WAF exits early for:
	 * - contexts where WordPress is not ready yet (`get_option()` missing)
	 * - logged-in users
	 * - wp-admin requests
	 *
	 * When a rule matches, increments the `attacks` counter (stored in the `WPMR`
	 * option) and immediately terminates the request with `die()`.
	 *
	 * Call site:
	 * - `add_action( 'plugins_loaded', array( $this, 'waf' ), -1 );` in `wpmr.php`.
	 *
	 * Better name: maybe_block_malicious_request()
	 *
	 * @return void
	 */

	function waf() {
		if ( ! function_exists( 'get_option' ) ) { // too soon to know what settings the user wants?
			return;
		}
		if ( function_exists( 'wp_get_current_user' ) && is_user_logged_in() ) {
			return;
		}
		if ( function_exists( 'is_admin' ) && is_admin() ) { // exclude wp-admin
			return;
		}
		if ( 'no' != $this->get_fw_setting( 'fw_block_path_traversal' ) ) {
			if ( $request = $this->build_request() ) {
				$re = '/=[\s\/\.]*(\.\.|etc)\//';
				if ( preg_match( $re, $request ) ) {
					$this->update_setting( 'attacks', $this->get_setting( 'attacks' ) + 1 );
					// header( 'location: ' . WPMR_SERVER . '?wpmr_attack_info=' . $this->encode( $this->attack_info() ) . '&wpmr_attack_type=path_traversal&time=' . microtime( true ) );
					die();
				}
			}
		}
		if ( 'no' != $this->get_fw_setting( 'fw_disable_php_upload' ) ) {
			if ( $files = $this->build_files() ) {
				$re = '/name=[^\&]*\.php\&/';
				if ( preg_match( $re, $files ) ) {
					$this->update_setting( 'attacks', $this->get_setting( 'attacks' ) + 1 );
					$ref = ! empty( $_SERVER['HTTP_REFERER'] ) ? sanitize_url( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- HTTP_REFERER sanitized as URL
					// header( 'location: ' . WPMR_SERVER . '?wpmr_attack_info=' . $this->encode( $this->attack_info() ) . '&wpmr_attack_type=php_upload&time=' . microtime( true ) );
					die();
				}
			}
		}
		if ( 'no' != $this->get_fw_setting( 'fw_disable_restapi_user_listing' ) ) {
			if ( $server = $this->build_server() ) {
				$re = '/wp-json\/wp\/v2\/users(?!\/me)/i';
				if ( preg_match( $re, $server ) ) {
					$this->update_setting( 'attacks', $this->get_setting( 'attacks' ) + 1 );
					$ref = ! empty( $_SERVER['HTTP_REFERER'] ) ? sanitize_url( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- HTTP_REFERER sanitized as URL
					// header( 'location: ' . WPMR_SERVER . '?wpmr_attack_info=' . $this->encode( $this->attack_info() ) . '&wpmr_attack_type=restapi_user_listing&time=' . microtime( true ) );
					die();
				}
			}
		}
		if ( 'no' != $this->get_fw_setting( 'fw_disable_user_enumeration' ) ) {
			if ( ( $request = $this->build_request() ) && ( $server = $this->build_server() ) ) {
				$re_srv = '/\&REQUEST_URI=(?!\/wp-admin\/)/i';
				$re_req = '/\&author=[0-9]+\&/';
				if ( preg_match( $re_req, $request ) && preg_match( $re_srv, $server ) ) {
					$this->update_setting( 'attacks', $this->get_setting( 'attacks' ) + 1 );
					// header( 'location: ' . WPMR_SERVER . '?wpmr_attack_info=' . $this->encode( $this->attack_info() ) . '&wpmr_attack_type=user_enumeration&time=' . microtime( true ) );
					die();
				}
			}
		}
	}

	/**
	 * Build a query-string style payload describing the current request.
	 *
	 * Collects a small set of `$_SERVER` fields, sanitizes and URL-encodes them,
	 * and appends the site URL. Intended for sending to a remote endpoint as part
	 * of the blocked-attack redirect (currently commented out in `waf()`).
	 *
	 * Better name: build_attack_telemetry_payload()
	 *
	 * @return string
	 */

	function attack_info() {
		$info = '';
		foreach ( array( 'REMOTE_ADDR', 'HTTP_HOST', 'REQUEST_URI', 'HTTP_REFERER', 'HTTP_USER_AGENT' ) as $var ) {
			$info .= ( isset( $_SERVER[ $var ] ) ? "&wpmr_SERVER_$var=" . urlencode( sanitize_text_field( wp_unslash( $_SERVER[ $var ] ) ) ) : '' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Server data sanitized before URL encoding
		}
		return $info . '&wpmr_site_url=' . get_site_url();
	}
}
