<?php
if (!defined('ABSPATH')) {
	exit; //Exit if accessed directly
}

class AIOWPSecurity_Firewall_Setup_Notice {

	/**
	 * Holds reference to an instance of itself
	 *
	 * @var AIOWPSecurity_Firewall_Setup_Notice
	 */
	private static $instance = null;

	/**
	 * Holds our wp-config file wrapped in our manager class
	 *
	 * @var AIOWPSecurity_Block_WpConfig
	 */
	private $wpconfig;

	/**
	 * Holds our mu-plugin file wrapped in our manager class
	 *
	 * @var AIOWPSecurity_Block_Muplugin
	 */
	private $muplugin;

	/**
	 * Holds our bootstrap file wrapped in our manager class
	 *
	 * @var AIOWPSecurity_Block_Bootstrap
	 */
	private $bootstrap;

	/**
	 * Constants for the different notice types
	 *
	 * @var string
	 */
	const NOTICE_BOOTSTRAP     = 'manual_bootstrap';
	const NOTICE_MANUAL        = 'manual';
	const NOTICE_INSTALLED     = 'success';
	const NOTICE_DIRECTIVE_SET = 'userini_directive';

	/**
	 * Constructs our object by setting up our essential files
	 */
	private function __construct() {
		$this->bootstrap = AIOWPSecurity_Utility_Firewall::get_bootstrap_file();
		$this->wpconfig  = AIOWPSecurity_Utility_Firewall::get_wpconfig_file();
		$this->muplugin  = AIOWPSecurity_Utility_Firewall::get_muplugin_file();
		AIOWPSecurity_Utility_Firewall::get_firewall_rules_path(true); // Creates the needed directories for the first time.
	}

	/**
	 * Entry point for the dashboard notice
	 *
	 * @return void
	 */
	public function start_firewall_setup() {

		global $aio_wp_security;

		$firewall_files = array(
			'server' => AIOWPSecurity_Utility_Firewall::get_server_file(),
			'bootstrap' => $this->bootstrap,
			'wpconfig' => $this->wpconfig,
			'muplugin' => $this->muplugin,
		);

		//Check each file and update the contents if necessary
		foreach ($firewall_files as $name => $file) {
			${'is_firewall_in_'.$name} = false;

			if (AIOWPSecurity_Utility_Firewall::MANUAL_SETUP === $file) {
				continue;
			}

			${'is_firewall_in_'.$name} = $file->contains_contents();

			if (true === ${'is_firewall_in_'.$name}) {
				$file->update_contents();
			}
		}
		
		if (!$aio_wp_security->is_aiowps_admin_page()) {
			return;
		}

		if (AIOWPSecurity_Utility_Firewall::is_firewall_setup()) {
			if (true !== $is_firewall_in_server) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- variable is set in the foreach loop
				$this->render_upgrade_protection_notice();
			}
		} else {
			$this->render_automatic_setup_notice();
		}

		$this->render_notices();
	}

	/**
	 * Will execute when the user presses 'Set up now' button
	 *
	 * @return void
	 */
	public function do_setup() {

		$is_inserted_firewall_file = false;

		$is_inserted_bootstrap_file = $this->bootstrap->contains_contents();
		if (true !== $is_inserted_bootstrap_file) {
			$is_inserted_bootstrap_file = $this->bootstrap->insert_contents();

			if (true !== $is_inserted_bootstrap_file) {
				$this->log_wp_error($is_inserted_bootstrap_file);
				$this->show_notice(self::NOTICE_BOOTSTRAP);
				return;
			}

		}
		
		$firewall_file = AIOWPSecurity_Utility_Firewall::get_server_file();
		
		if ($firewall_file instanceof AIOWPSecurity_Block_Userini) {

			$directive = AIOWPSecurity_Utility_Firewall::get_already_set_directive($firewall_file);
			
			if (!empty($directive)) {
				
				if (AIOWPSecurity_Utility_Firewall::get_bootstrap_path() === $directive) {
					$is_inserted_firewall_file = true;
				} else {
					$this->show_notice(self::NOTICE_DIRECTIVE_SET, array('directive' => $directive));
				}

			} else {
				$is_inserted_firewall_file = $firewall_file->insert_contents();
			}

		} else {

			if (AIOWPSecurity_Utility_Firewall::MANUAL_SETUP !== $firewall_file) {
				$is_inserted_firewall_file = $firewall_file->insert_contents(); // attempts to insert firewall into required file
			}
		}

		//Set up the firewall in the wp-config file
		$is_inserted_wpconfig = $this->wpconfig->contains_contents();
		if (true !== $is_inserted_wpconfig) {
			$is_inserted_wpconfig = $this->wpconfig->insert_contents();
		}
		$this->log_wp_error($is_inserted_wpconfig);

		//Set up the firewall in the mu-plugin
		$is_inserted_muplugin = $this->muplugin->contains_contents();
		if (true !== $is_inserted_muplugin) {
			$is_inserted_muplugin = $this->muplugin->insert_contents();
		}
		if (false === $is_inserted_muplugin) {
			$this->log_wp_error(new \WP_Error(
				'file-mu-plugin-failed',
				'Unable to create the mu-plugin',
				$this->muplugin
			));
		}
		$this->log_wp_error($is_inserted_muplugin);

		if (true === $is_inserted_firewall_file) {
			$this->show_notice(self::NOTICE_INSTALLED);
		} else {
			$this->log_wp_error($is_inserted_firewall_file);
			$this->show_notice(self::NOTICE_MANUAL);
		}

	}

	/**
	 * Dismisses the notice.
	 *
	 * @return void
	 */
	private function do_dismiss() {
		global $aio_wp_security;

		$aio_wp_security->configs->set_value('aios_firewall_dismiss', true, true);
	}

	/**
	 * Checks whether the notice is dismissed
	 *
	 * @return boolean
	 */
	private function is_dismissed() {
		global $aio_wp_security;
		return (true === $aio_wp_security->configs->get_value('aios_firewall_dismiss'));
		
	}

	/**
	 * Handles the form submission for the 'Set up now' notice
	 *
	 * @return void
	 */
	public function handle_setup_form() {
		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce check occurs here.
		$nonce = isset($_POST['_wpnonce']) ? sanitize_key(wp_unslash($_POST['_wpnonce'])) : '';
		$result = AIOWPSecurity_Utility_Permissions::check_nonce_and_user_cap($nonce, 'aiowpsec-firewall-setup');
		if (!is_wp_error($result)) {
			$this->do_setup();
			$this->do_redirect();
		}
	}

	/**
	 * Handles the dismiss form
	 *
	 * @return void
	 */
	public function handle_dismiss_form() {
		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce check occurs here.
		$nonce = isset($_POST['_wpnonce']) ? sanitize_key(wp_unslash($_POST['_wpnonce'])) : '';
		$result = AIOWPSecurity_Utility_Permissions::check_nonce_and_user_cap($nonce, 'aiowpsec-firewall-setup-dismiss');
		if (!is_wp_error($result)) {
			$this->do_dismiss();
			$this->do_redirect();
		}
	}

	/**
	 * Handles the form that downgrades the firewall's protection.
	 *
	 * @return void
	 */
	public function handle_downgrade_protection_form() {
		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce check occurs here.
		$nonce = isset($_POST['_wpnonce']) ? sanitize_key(wp_unslash($_POST['_wpnonce'])) : '';
		$result = AIOWPSecurity_Utility_Permissions::check_nonce_and_user_cap($nonce, 'aiowpsec-firewall-downgrade');
		if (!is_wp_error($result)) {
			AIOWPSecurity_Utility_Firewall::remove_firewall();
			$this->do_redirect();
		}
	}

	/**
	 * Handles the redirect
	 *
	 * @return void
	 */
	private function do_redirect() {

		// Go back to the previous page and tab if set
		if (isset($_POST['_wp_http_referer'])) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- No nonce.
			
			$matches = array();
			// phpcs:ignore WordPress.Security.NonceVerification.Missing -- No nonce.
			if (preg_match('/\?page='.AIOWPSEC_MENU_SLUG_PREFIX.'(?<page>.*)(&tab=(?<tab>.*))?$/m', esc_url_raw(wp_unslash($_POST['_wp_http_referer'])), $matches)) {
					$url = 'admin.php?page='.AIOWPSEC_MENU_SLUG_PREFIX;

					if (isset($matches['page'])) {
						$url .= sanitize_text_field($matches['page']);

						if (isset($matches['tab'])) {
							$url .= '&tab='.sanitize_text_field($matches['tab']);
						}
					}
					
					AIOWPSecurity_Utility::redirect_to_url(admin_url(esc_url_raw($url)));
			}

		}
	
		AIOWPSecurity_Utility::redirect_to_url(admin_url('admin.php?page='.AIOWPSEC_MENU_SLUG_PREFIX));
	}

	/**
	 * Wrapper function to log WP_Errors to debug log
	 *
	 * @param WP_Error $wp_error - Our error which gets logged
	 * @return void
	 */
	private function log_wp_error($wp_error) {

		if (is_wp_error($wp_error)) {
			global $aio_wp_security;

			$error_message = $wp_error->get_error_message();
			$error_message .= ' - ';
			$error_message .= $wp_error->get_error_data();
			$aio_wp_security->debug_logger->log_debug($error_message, 4);
		}
	}

	/**
	 * Sets the flags to show notices
	 *
	 * @param string $type   - the type of notice we want to set
	 * @param array  $values - any values that need to be passed
	 * @return void
	 */
	private function show_notice($type, $values = array()) {
		global $aio_wp_security;

		$aio_wp_security->configs->set_value('firewall_notice_'.$type, true);

		if (!empty($values)) {
			$aio_wp_security->configs->set_value('firewall_notice_values', $values);
		}
		
		$aio_wp_security->configs->save_config();
	}

	/**
	 * Renders any necessary notices
	 *
	 * @return void
	 */
	public function render_notices() {
		global $aio_wp_security;

		$notices = array(
			self::NOTICE_BOOTSTRAP,
			self::NOTICE_MANUAL,
			self::NOTICE_INSTALLED,
			self::NOTICE_DIRECTIVE_SET,
		);

		foreach ($notices as $notice) {
			if ($aio_wp_security->configs->get_value('firewall_notice_'.$notice)) {
				
				switch ($notice) {
					case self::NOTICE_BOOTSTRAP:
						$this->render_bootstrap_notice();
						break;
					case self::NOTICE_MANUAL:
						if (!$this->any_pending_notices(self::NOTICE_MANUAL)) {
							$this->render_manual_setup_notice();
						}
						break;
					case self::NOTICE_INSTALLED:
						$this->render_firewall_installed_notice();
						break;
					case self::NOTICE_DIRECTIVE_SET:
						$values = $aio_wp_security->configs->get_value('firewall_notice_values');
						$this->render_userini_directive_set_notice($values['directive']);
						$aio_wp_security->configs->delete_value('firewall_notice_values');
						break;
				}

				$aio_wp_security->configs->delete_value('firewall_notice_'.$notice);
			}
		}

		$aio_wp_security->configs->save_config();
	}

	/**
	 * Detects if we have any notices pending to display
	 *
	 * @param string ...$exclude - do not check the status of these notices
	 *
	 * @return boolean
	 */
	private function any_pending_notices(...$exclude) {
		global $aio_wp_security;

		$notices = array(
			self::NOTICE_BOOTSTRAP,
			self::NOTICE_MANUAL,
			self::NOTICE_INSTALLED,
			self::NOTICE_DIRECTIVE_SET,
		);
		$notices = array_diff($notices, $exclude);
		
		foreach ($notices as $notice) {
			if (true === $aio_wp_security->configs->get_value('firewall_notice_'.$notice)) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Notice is shown if we are unable to write to the bootstrap file
	 *
	 * @return void
	 */
	private function render_bootstrap_notice() {
		?>
			<div class="notice notice-error is-dismissible">
				<p>
					<strong><?php esc_html_e('All In One Security', 'all-in-one-wp-security-and-firewall'); ?></strong>
				</p>
				<p><?php esc_html_e('We were unable to create the file necessary to give you the highest level of protection.', 'all-in-one-wp-security-and-firewall');?></p>
				<p><?php esc_html_e('Your firewall will have reduced protection which means some of your firewall\'s functionality will be unavailable.', 'all-in-one-wp-security-and-firewall');?></p>
				<p><?php esc_html_e('If you would like to manually set up the necessary file, please follow these steps:', 'all-in-one-wp-security-and-firewall');?></p>
				<p>
					<?php
						/* translators: %s Bootstrap file name. */
						printf(esc_html__('1. Create a file with the name %s in the same directory as your WordPress install is in, i.e.:', 'all-in-one-wp-security-and-firewall'), esc_html(pathinfo($this->bootstrap, PATHINFO_BASENAME)));
					?>
				</p>
				<pre style='max-width: 100%;background-color: #f0f0f0;border:#ccc solid 1px;padding: 10px;white-space:pre-wrap;'><?php echo esc_html($this->bootstrap); ?></pre>
				<p><?php esc_html_e('2. Paste in the following code:', 'all-in-one-wp-security-and-firewall');?></p>
				<pre style='max-width: 100%;background-color: #f0f0f0;border:#ccc solid 1px;padding: 10px;white-space:pre-wrap;'><?php echo esc_html(htmlentities($this->bootstrap->get_contents())); ?></pre>
				<p><?php esc_html_e('3. Save the file and press the \'Try again\' button below:', 'all-in-one-wp-security-and-firewall');?></p>
		<?php
		$this->render_try_again_button();
		$this->render_manual_notice_footer();
	}

	/**
	 * Notice is shown if auto_prepend_file directive is already set in user.ini
	 *
	 * @param string $directive_value
	 * @return void
	 */
	private function render_userini_directive_set_notice($directive_value) {

		$firewall_file = AIOWPSecurity_Utility_Firewall::get_server_file();

		$this->render_manual_notice_header();
		?>
			<p>
				<?php esc_html_e('1. Open the following file:', 'all-in-one-wp-security-and-firewall'); ?>
			</p>
			<p><code><?php echo esc_html($firewall_file); ?></code></p>

			<?php if (empty($directive_value)) {?>
				<p>
					<?php esc_html_e('2. Look for the auto_prepend_file directive.', 'all-in-one-wp-security-and-firewall'); ?>
				</p>
			<?php } else {?>
				<p>
					<?php esc_html_e('2. Look for the following:', 'all-in-one-wp-security-and-firewall'); ?>
				</p>
				<pre style='max-width: 100%;background-color: #f0f0f0;border:#ccc solid 1px;padding: 10px;white-space:pre-wrap;'><?php echo "auto_prepend_file='".esc_html($directive_value)."'";?></pre>
			<?php } ?>
			
			<p>
				<?php esc_html_e('3. Change it to the following:', 'all-in-one-wp-security-and-firewall'); ?>
			</p>
				<pre style='max-width: 100%;background-color: #f0f0f0;border:#ccc solid 1px;padding: 10px;white-space:pre-wrap;'><?php echo esc_html(AIOWPSecurity_Utility_Firewall::get_server_file()->get_contents()); ?></pre>
			<p>
				<?php echo esc_html__('4. Save the file  and press the \'Try again\' button below:', 'all-in-one-wp-security-and-firewall').' '.esc_html__('You may have to wait up to 5 minutes before the settings take effect.', 'all-in-one-wp-security-and-firewall'); ?>
			</p>
		<?php
		$this->render_try_again_button();
		$this->render_manual_notice_footer();
	}

	/**
	 * Shows when the firewall has successfully installed
	 *
	 * @return void
	 */
	private function render_firewall_installed_notice() {
		global $aio_wp_security;

		$aio_wp_security->include_template('notices/firewall-installed-notice.php', false);
	}

	/**
	 * Renders the 'manual setup' dashboard notice
	 *
	 * @return void
	 */
	private function render_manual_setup_notice() {

		$firewall_file = AIOWPSecurity_Utility_Firewall::get_server_file();
		
		if (AIOWPSecurity_Utility_Firewall::MANUAL_SETUP === $firewall_file) {
			//Show users how to manually add the firewall via php.ini if we can't detect their server
			$bootstrap_path = AIOWPSecurity_Utility_Firewall::get_bootstrap_path();

			$this->render_manual_notice_header();
			?>
				<p>
					<?php esc_html_e('1. Open your php.ini file.', 'all-in-one-wp-security-and-firewall'); ?>
				</p>
				<p>
					<?php esc_html_e('2. Set the auto_prepend_file directive like below:', 'all-in-one-wp-security-and-firewall'); ?>
				</p>
					<pre style='max-width: 100%;background-color: #f0f0f0;border:#ccc solid 1px;padding: 10px;white-space:pre-wrap;'><?php echo "auto_prepend_file='".esc_html($bootstrap_path)."'";?></pre>
				<p>
					<?php echo esc_html__('3. Restart the webserver and refresh the page', 'all-in-one-wp-security-and-firewall') . ' ' . esc_html__('You may have to wait up to 5 minutes before the settings take effect.', 'all-in-one-wp-security-and-firewall'); ?>
				</p>
			<?php
			$this->render_manual_notice_footer();
		} else {
			//Show users how to manually add the firewall via their own server file
			$this->render_manual_notice_header();
			$firewall_file_name = pathinfo($firewall_file, PATHINFO_BASENAME);
			?>
				<p>
					<?php
						/* translators: %s Firewall file name. */
						printf(esc_html__('1. Create a file with the name %s in the same directory as your WordPress install is in, i.e.:', 'all-in-one-wp-security-and-firewall'), esc_html($firewall_file_name));
					?>
					<p><code><?php echo esc_html($firewall_file); ?></code></p>
				</p>
				<p>
					<?php esc_html_e('2. Paste in the following directives:', 'all-in-one-wp-security-and-firewall'); ?>
				</p>
					<pre style='max-width: 100%;background-color: #f0f0f0;border:#ccc solid 1px;padding: 10px;white-space:pre-wrap;'><?php echo esc_html(htmlentities($firewall_file->get_contents())); ?></pre>
				<p>
					<?php echo esc_html__('3. Save the file and press the \'Try again\' button below:', 'all-in-one-wp-security-and-firewall'); ?>
				</p>
			<?php
			$this->render_try_again_button();
			$this->render_manual_notice_footer();
		}
	}

	/**
	 * The header for notices that require manual intervention
	 *
	 * @return void
	 */
	private function render_manual_notice_header() {
		?>
			<div class="notice notice-warning is-dismissible">
			<p>
				<strong><?php esc_html_e('All In OneSecurity', 'all-in-one-wp-security-and-firewall'); ?></strong>
			</p>
			<p>
				<?php echo esc_html__('We were unable to set up your firewall with the highest level of protection.', 'all-in-one-wp-security-and-firewall').' '.
						   esc_html__('Your firewall will have reduced functionality.', 'all-in-one-wp-security-and-firewall');
				?>
			</p>
			<p>
				<?php esc_html_e('To give your site the highest level of protection, please follow these steps:', 'all-in-one-wp-security-and-firewall'); ?>
			</p>
		<?php
	}

	/**
	 * The footer for notices that require manual intervention
	 *
	 * @return void
	 */
	private function render_manual_notice_footer() {
		?>
			<p>
				<strong><?php esc_html_e('Note: if you\'re unable to perform any of the aforementioned steps, please ask your web hosting provider for further assistance.', 'all-in-one-wp-security-and-firewall'); ?></strong>
			</p>
			</div>
		<?php
	}

	/**
	 * Render Try again button.
	 *
	 * @return void
	 */
	private function render_try_again_button() {
		?>
		<form action="<?php echo esc_url(admin_url('admin-post.php')); ?>" method="POST">
			<?php wp_nonce_field('aiowpsec-firewall-setup'); ?>
			<input type="hidden" name="action" value="aiowps_firewall_setup">
			<div style="padding-top: 10px; padding-bottom: 10px;">
					<input class="button button-primary" type="submit" name="btn_try_again" value="<?php esc_html_e('Try again', 'all-in-one-wp-security-and-firewall'); ?>">
			</div>
		</form>
		<?php
	}

	/**
	 * Renders the warning that users do not have the highest level of protection
	 *
	 * @return void
	 */
	private function render_upgrade_protection_notice() {

		if ($this->should_not_show_notice()) {
			return;
		}
		?>
			<div class="notice notice-warning">
				<form action="<?php echo esc_url(admin_url('admin-post.php')); ?>" method="POST">
					<?php wp_nonce_field('aiowpsec-firewall-setup'); ?>
					<input type="hidden" name="action" value="aiowps_firewall_setup">
					<p>
						<?php esc_html_e('We have detected that your AIOS firewall is not fully installed, and therefore does not have the highest level of protection.', 'all-in-one-wp-security-and-firewall');?>
						<?php echo ' ' . esc_html__('Your firewall will have reduced functionality until it has been upgraded.', 'all-in-one-wp-security-and-firewall');?>
						<div style="padding-top: 10px;">
							<input class="button button-primary" type="submit" name="btn_upgrade_now" value="<?php esc_html_e('Upgrade your protection now', 'all-in-one-wp-security-and-firewall'); ?>">
						</div>
					</p>
				</form>

			</div>

		<?php
	}

	/**
	 * Whether the firewall notice should not be shown.
	 *
	 * @return boolean True if the firewall notice should not be shown otherwise false.
	 */
	private function should_not_show_notice() {
		if (!is_main_site()) {
			return true;
		}

		if (!AIOWPSecurity_Utility_Permissions::has_manage_cap()) {
			return true;
		}

		if ($this->is_dismissed() && !AIOWPSecurity_Utility_Firewall::is_firewall_page()) {
			return true;
		}
		
		if ($this->any_pending_notices()) {
			return true; //only display if there are no other notices waiting to be displayed
		}

		return false;
	}

	/**
	 * Renders the 'Set up now' dashboard notice
	 *
	 * @return void
	 */
	private function render_automatic_setup_notice() {
		global $aio_wp_security;

		if ($this->should_not_show_notice()) {
			return;
		}
		$aio_wp_security->include_template('notices/firewall-setup-notice.php', false, array('show_dismiss' => !AIOWPSecurity_Utility_Firewall::is_firewall_page()));
	}

	/**
	 * Ensures only one instance of the class can be created (singleton)
	 *
	 * @return AIOWPSecurity_Firewall_Setup_Notice|null
	 */
	public static function get_instance() {

		if (null === self::$instance) {
			self::$instance = new self();
		}

		return self::$instance;
	}
}
