<?php

/**
 * // credits:
 * Change Login URL codes are Forked, Modified, and Extended from
 * Nuno Morais Sarmento codes in Change wp-admin login
 * 
 * Simple Firewall codes are Forked, Modified, and Extended from
 * Jeff Star codes in BBQ Firewall
 * 
 */

namespace AdminStarterPack;

if (!defined('WPINC')) { die; }

class AspPluginSecurity {

    private $wp_login_php;
    private $options = [];
    
    private $maxAttempts = 3;
    private $lockoutDuration = 60; // in minutes
    

    public function __construct() {

        $this->options = get_option('starterpack_options', []);

        // change login url
        if ($this->options['starterpack_change_login_url_enable'] && !empty($this->options['starterpack_change_login_url'])) {
            add_action('plugins_loaded', array($this, 'initializePlugin'), 1);
            add_action('wp_loaded', array($this, 'handleWpLoaded'));
            add_filter('site_url', array($this, 'filterSiteUrl'), 10, 4);
            add_filter('wp_redirect', array($this, 'filterWpRedirect'), 10, 2);
            add_filter('site_option_welcome_email', array($this, 'filterWelcomeEmail'));
            remove_action('template_redirect', 'wp_redirect_admin_locations', 1000);
        }
        
        // add htaccess rules to protect the default login url
        if ($this->options['starterpack_change_login_url_enable'] && !empty($this->options['starterpack_change_login_url'])) {
            $this->addHtaccessLoginRules();
        } else {
            $this->removeHtaccessLoginRules();
        }
        
        // hardening wp
        if ($this->options['starterpack_hardening_wp']) {
            add_action('init', [$this, 'hardeningWP']);
            $this->addHtaccessHardeningWpRules();
        } else {
            $this->removeHtaccessHardeningWpRules();
        }
        
        // disable REST API
        if ($this->options['starterpack_disable_rest_api']) {
            add_filter('rest_endpoints', [$this, 'disable_default_endpoints']);
            add_filter('rest_authentication_errors', [$this, 'disable_wp_rest_api']);
        }
        
        //$this->forceWpHtaccess();
        add_action('init', array($this, 'forceWpHtaccess'));
        
        // ssl protection
        if ($this->options['starterpack_ssl_protection']) {
            $this->addHtaccessSSLRules();
        } else {
            $this->removeHtaccessSSLRules();
        }
        
        // hotlink protection
        if ($this->options['starterpack_hotlink_protection']) {
            $this->addHtaccessHotlinkRules();
        } else {
            $this->removeHtaccessHotlinkRules();
        }
        
        // 
        if (isset($_GET['starterpack-action']) && $_GET['starterpack-action'] === 'reset') {
            $this->removedeAllHtaccessRules();
        }
        
        // simple firewall
        if ($this->options['starterpack_simple_firewall']) {
            add_action('plugins_loaded', array($this, 'simpleFirewall'));
        }
        
        if ($this->options['starterpack_turnstile_enable'] && !empty($this->options['starterpack_turnstile_key']) && !empty($this->options['starterpack_turnstile_secret'])) {
            $turnstile_file = STARTERPACK_CLASSES_DIR . 'parts/Turnstile.php';
            if ( file_exists( $turnstile_file ) ) {
        		require_once( $turnstile_file );
        	}
        }
        
        // limit login attempts
        add_action('wp_login_failed', [$this, 'handleFailedLogin']);

    }
    
    public function removedeAllHtaccessRules() {
        // Remove the custom htaccess rules when the plugin is deactivated
        $this->removeHtaccessLoginRules();
        $this->removeHtaccessHardeningWpRules();
        $this->removeHtaccessSSLRules();
        $this->removeHtaccessHotlinkRules();
        
        flush_rewrite_rules(); // ensure the default wp htaccess intact
    }
    
    /* 
     * start change login url
     */
    
    private function useTrailingSlashes() {
        return '/' === substr(get_option('permalink_structure'), -1, 1);
    }

    private function userTrailingslashit($string) {
        return $this->useTrailingSlashes() ? trailingslashit($string) : untrailingslashit($string);
    }

    private function wpTemplateLoader() {
        global $pagenow;

        $pagenow = 'index.php';

        if (!defined('WP_USE_THEMES')) {
            define('WP_USE_THEMES', true);
        }

        wp();

        if ($_SERVER['REQUEST_URI'] === $this->userTrailingslashit(str_repeat('-/', 10))) {
            $_SERVER['REQUEST_URI'] = $this->userTrailingslashit('/wp-login-php/');
        }

        require_once(ABSPATH . WPINC . '/template-loader.php');

        die;
    }

    private function newLoginSlug() {
        return $this->options['starterpack_change_login_url'];
    }

    public function newLoginUrl($scheme = null) {
        if (get_option('permalink_structure')) {
            return $this->userTrailingslashit(home_url('/', $scheme) . $this->newLoginSlug());
        } else {
            return home_url('/', $scheme) . '?' . $this->newLoginSlug();
        }
    }

    public function initializePlugin() {

        global $pagenow;

        $request = parse_url(rawurldecode($_SERVER['REQUEST_URI']));

        if (
            (strpos(rawurldecode($_SERVER['REQUEST_URI']), 'wp-login.php') !== false
                || (isset($request['path']) && untrailingslashit($request['path']) === site_url('wp-login', 'relative')))
            && !is_admin()
        ) {

            $this->wp_login_php = true;

            $_SERVER['REQUEST_URI'] = $this->userTrailingslashit('/' . str_repeat('-/', 10));

            $pagenow = 'index.php';

        } elseif (
            (isset($request['path']) && untrailingslashit($request['path']) === home_url($this->newLoginSlug(), 'relative'))
            || (!get_option('permalink_structure')
                && isset($_GET[$this->newLoginSlug()])
                && empty($_GET[$this->newLoginSlug()]))
        ) {

            $pagenow = 'wp-login.php';

        } elseif (
            (strpos(rawurldecode($_SERVER['REQUEST_URI']), 'wp-register.php') !== false
                || (isset($request['path']) && untrailingslashit($request['path']) === site_url('wp-register', 'relative')))
            && !is_admin()
        ) {

            $this->wp_login_php = true;

            $_SERVER['REQUEST_URI'] = $this->userTrailingslashit('/' . str_repeat('-/', 10));

            $pagenow = 'index.php';
        }

    }

    public function handleWpLoaded() {
        global $pagenow;

        if (is_admin() && !is_user_logged_in() && !defined('DOING_AJAX')) {

            wp_safe_redirect('/');
            die();
            wp_die(__('You must log in to access the admin area.', 'starterpack'));
        }

        $request = parse_url(rawurldecode($_SERVER['REQUEST_URI']));

        if (
            $pagenow === 'wp-login.php' &&
            $request['path'] !== $this->userTrailingslashit($request['path']) &&
            get_option('permalink_structure')
        ) {
            wp_safe_redirect($this->userTrailingslashit($this->newLoginUrl()) . (!empty($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : ''));
            die;
        } elseif ($this->wp_login_php) {
            if (
                ($referer = wp_get_referer()) &&
                strpos($referer, 'wp-activate.php') !== false &&
                ($referer = parse_url($referer)) &&
                !empty($referer['query'])
            ) {
                parse_str($referer['query'], $referer);

                if (
                    !empty($referer['key']) &&
                    ($result = wpmu_activate_signup($referer['key'])) &&
                    is_wp_error($result) && (
                        $result->get_error_code() === 'already_active' ||
                        $result->get_error_code() === 'blog_taken'
                    )
                ) {
                    wp_safe_redirect($this->newLoginUrl() . (!empty($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : ''));
                    die;
                }
            }

            $this->wpTemplateLoader();
        } elseif ($pagenow === 'wp-login.php') {
            global $error, $interim_login, $action, $user_login;

            @require_once ABSPATH . 'wp-login.php';

            die;
        }
    }

    public function filterSiteUrl($url, $path, $scheme, $blog_id) {
        return $this->filterWpLoginPhp($url, $scheme);
    }

    public function filterWpRedirect($location, $status) {
        return $this->filterWpLoginPhp($location);
    }

    public function filterWpLoginPhp($url, $scheme = null) {
        if (strpos($url, 'wp-login.php') !== false) {
            if (is_ssl()) {
                $scheme = 'https';
            }

            $args = explode('?', $url);

            if (isset($args[1])) {
                parse_str($args[1], $args);
                $url = add_query_arg($args, $this->newLoginUrl($scheme));
            } else {
                $url = $this->newLoginUrl($scheme);
            }
        }

        return $url;
    }

    public function filterWelcomeEmail($value) {
        return $value = str_replace(network_site_url('wp-login.php', 'login'), $this->newLoginUrl(), $value);
    }
    
    private function addHtaccessLoginRules() {
        $htaccess_content = "# START DENY ACCESS TO WP DEFAULT LOGIN\n";
        $htaccess_content .= "RewriteCond %{REQUEST_URI} ^/(wp-login\.php|wp-admin/).* [NC]\n";
        $htaccess_content .= "RewriteCond %{HTTP_COOKIE} !^.*wordpress_logged_in.*$ [NC]\n"; // use the logged_in cookie to ensure wp-admin access after sucessfully logged-in using the custom login url
        $htaccess_content .= "RewriteCond %{REQUEST_URI} !^/wp-admin/admin-ajax.php$ [NC]\n"; // exclude admin-ajax.php to ensure woocommerce quick view working
        $htaccess_content .= "RewriteRule !^(wp-(content|includes|admin/css|admin/js).*) - [R=401,L]\n"; // make sure wp-admin and wp-login.php gets to 401 (un-authorized page) and exclude deny access to wp-content and wp-includes folder to ensure the custom login url sucessfully request the required css and js for the login page
	    $htaccess_content .= "ErrorDocument 401 \"Sorry, you've entered the wrong place.\"\n"; // custom message for the 401 page
	    $htaccess_content .= "# END DENY ACCESS TO WP DEFAULT LOGIN\n";
	
	    $htaccess_file = ABSPATH . '.htaccess';
	    $current_htaccess = file_get_contents($htaccess_file);
	
	    // Add the new rules if they don't already exist
	    if (strpos($current_htaccess, '# START DENY ACCESS TO WP DEFAULT LOGIN') === false) {
	        $current_htaccess .= "\n" . $htaccess_content;
	        file_put_contents($htaccess_file, $current_htaccess);
	    }
	    
	    flush_rewrite_rules(); // ensure the default wp htaccess intact

    }

    private function removeHtaccessLoginRules() {
        $htaccess_file = ABSPATH . '.htaccess';
        $current_htaccess = file_get_contents($htaccess_file);

        // Remove the rules if they exist
        $current_htaccess = preg_replace("/\n# START DENY ACCESS TO WP DEFAULT LOGIN.*# END DENY ACCESS TO WP DEFAULT LOGIN\n/s", '', $current_htaccess);

        file_put_contents($htaccess_file, $current_htaccess);
        
        flush_rewrite_rules(); // ensure the default wp htaccess intact
    }
    
    /* 
     * start hardening WP
     */
    
    public function hardeningWP() {
        
        add_filter('xmlrpc_enabled', '__return_false');
        remove_action('wp_head', 'rsd_link');
        remove_action('wp_head', 'wlwmanifest_link');
        remove_action('wp_head', 'wp_generator');
        add_filter('wp_headers', [$this, 'disable_x_pingback']);
        add_action('template_redirect', [$this, 'link_rel_buffer_start'], -1);
        add_action('get_header', [$this, 'link_rel_buffer_start']);
        add_action('wp_head', [$this, 'link_rel_buffer_end'], 999);
        add_filter('xmlrpc_methods', [$this, 'remove_xmlrpc_methods']);
        add_filter('xmlrpc_methods', [$this, 'stop_pings']);
        add_action('do_feed', [$this, 'disableAllFeeds'], 1);
        add_action('do_feed_rdf', [$this, 'disableAllFeeds'], 1);
        add_action('do_feed_rss', [$this, 'disableAllFeeds'], 1);
        add_action('do_feed_rss2', [$this, 'disableAllFeeds'], 1);
        add_action('do_feed_atom', [$this, 'disableAllFeeds'], 1);
        add_action('do_feed_rss2_comments', [$this, 'disableAllFeeds'], 1);
        add_action('do_feed_atom_comments', [$this, 'disableAllFeeds'], 1);
        add_filter('wp_resource_hints', [$this, 'remove_dns_prefetch'], 10, 2);
        
        if (!is_user_logged_in() ) {
            remove_action('wp_head', 'feed_links', 2); // remove feed links
            remove_action('wp_head', 'feed_links_extra', 3); // remove feed links
            remove_action( 'wp_head', 'rest_output_link_wp_head' ); // remove links to api.w.org
            remove_action( 'rest_api_init', 'wp_oembed_register_route' ); // block wp-json/oembed/1.0/
            remove_action( 'wp_head', 'wp_oembed_add_discovery_links' ); // Remove oEmbed discovery links.
            remove_action( 'wp_head', 'wp_oembed_add_host_js' ); // Remove oEmbed-specific JavaScript from the front-end and back-end.
            remove_filter( 'pre_oembed_result', 'wp_filter_pre_oembed_result', 10 ); // Remove filter of the oEmbed result before any HTTP requests are made.
	        add_filter( 'embed_oembed_discover', '__return_false' ); // Turn off oEmbed auto discovery.
            add_filter( 'rewrite_rules_array', [$this,'disable_embeds_rewrites'] ); // Remove all embeds rewrite rules.
            add_filter( 'rest_endpoints', [$this,'disable_embeds_remove_embed_endpoint'] ); // Remove the oembed/1.0/embed REST route.
            add_filter( 'oembed_response_data', [$this,'disable_embeds_filter_oembed_response_data'] );// Disable handling of internal embeds in oembed/1.0/proxy REST route.
        }
        
    }
    
    public function disableAllFeeds() {
        $error_message = 'Sorry, there are currently no feeds available for you.';
        status_header(401); // Set the 401 Unauthorized status header
        echo $error_message; // Output the error message
        exit;
    }
    
    public function remove_dns_prefetch($hints, $relation_type) {
        if ('dns-prefetch' === $relation_type && !is_admin()) {
            foreach ($hints as $key => $hint) {
                if (strpos($hint, 's.w.org') !== false) {
                    unset($hints[$key]);
                }
            }
        }
        return $hints;
    }
    
    public function disable_x_pingback($headers) {
        unset($headers['X-Pingback']);
        return $headers;
    }

    public function link_rel_buffer_callback($buffer) {
        $buffer = preg_replace('/<link.*?rel=("|\')pingback("|\').*?href=("|\')(.*?)("|\')(.*?)?\/?>/i', '', $buffer);
        return $buffer;
    }

    public function link_rel_buffer_start() {
        ob_start([$this, 'link_rel_buffer_callback']);
    }

    public function link_rel_buffer_end() {
        ob_flush();
    }

    public function remove_xmlrpc_methods($methods) {
        return [];
    }

    public function stop_pings($methods) {
        unset($methods['pingback.ping']);
        return $methods;
    }
    
    public function disable_embeds_remove_embed_endpoint( $endpoints ) {
    	unset( $endpoints['/oembed/1.0/embed'] );
    	return $endpoints;
    }
    
    public function disable_embeds_filter_oembed_response_data( $data ) {
    	if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
    		return false;
    	}
    	return $data;
    }
    
    public function disable_embeds_rewrites( $rules ) {
    	foreach ( $rules as $rule => $rewrite ) {
    		if ( false !== strpos( $rewrite, 'embed=true' ) ) {
    			unset( $rules[ $rule ] );
    		}
    	}
    	return $rules;
    }

    public function addHtaccessHardeningWpRules() {
        $htaccess_content = "# START HARDENING WP\n";
        $htaccess_content .= "# From https://wordpress.org/documentation/article/hardening-wordpress/\n";
		$htaccess_content .= "<IfModule mod_rewrite.c>\n";
		$htaccess_content .= "RewriteBase /\n";
		$htaccess_content .= "RewriteRule ^wp-admin/includes/ - [F,L]\n";
		$htaccess_content .= "RewriteRule !^wp-includes/ - [S=3]\n";
		$htaccess_content .= "RewriteRule ^wp-includes/[^/]+\.php$ - [F,L]\n";
		$htaccess_content .= "RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [F,L]\n";
		$htaccess_content .= "RewriteRule ^wp-includes/theme-compat/ - [F,L]\n";
		$htaccess_content .= "<IfModule mod_rewrite.c>\n";
		$htaccess_content .= "# Disable directory browsing\n";
		$htaccess_content .= "Options -Indexes\n";
		$htaccess_content .= "# Deny access to important files\n";
		$htaccess_content .= "<FilesMatch \"^(wp-config\.php|wp-config-sample\.php|xmlrpc\.php|autodiscover\.xml|install\.php|wp-cron\.php|wp-activate\.php|wp-blog-header\.php|php\.ini|wp-trackback\.php|wp-signup\.php|wp-content/.*\.php|readme\.html|license\.txt|~ \\\\.htaccess)$\">\n";
		$htaccess_content .= "Order Allow,Deny\n";
		$htaccess_content .= "Deny from all\n";
		$htaccess_content .= "Satisfy all\n";
		$htaccess_content .= "</FilesMatch>\n";
		$htaccess_content .= "# Set Security Headers\n";
		$htaccess_content .= "<IfModule mod_headers.c>\n";
		$htaccess_content .= "Header set X-XSS-Protection \"1; mode=block\"\n";
		$htaccess_content .= "Header set X-Content-Type-Options \"nosniff\"\n";
        $htaccess_content .= "Header set X-Frame-Options \"SAMEORIGIN\"\n";
        $htaccess_content .= "Header set Referrer-Policy \"strict-origin-when-cross-origin\"\n";
        $htaccess_content .= "Header always unset X-Powered-By\n";
        $htaccess_content .= "Header always unset Server\n";
        $htaccess_content .= "Header always unset Link\n";
        $htaccess_content .= "Header always unset X-Pingback\n";
		$htaccess_content .= "</IfModule>\n";
		$htaccess_content .= "# Block »ReallyLongRequest« Bandit\n";
        $htaccess_content .= "RewriteCond %{REMOTE_ADDR} ^137\.226\.113\.[0-9]{1,3}$\n";
        $htaccess_content .= "RewriteRule ^ - [F,L]\n";
		$htaccess_content .= "# Disable Server Signature\n";
		$htaccess_content .= "ServerSignature Off\n";
        $htaccess_content .= "# END HARDENING WP\n";
        
        $htaccess_file = ABSPATH . '.htaccess';
        $current_htaccess = file_get_contents($htaccess_file);

        // Add the new rules if they don't already exist
        if (strpos($current_htaccess, '# START HARDENING WP') === false) {
            $current_htaccess .= "\n" . $htaccess_content;
            file_put_contents($htaccess_file, $current_htaccess);
        }
        
        flush_rewrite_rules(); // ensure the default wp htaccess intact
    }

    public function removeHtaccessHardeningWpRules() {
        $htaccess_file = ABSPATH . '.htaccess';
        $current_htaccess = file_get_contents($htaccess_file);

        // Remove the rules if they exist
        $current_htaccess = preg_replace("/\n# START HARDENING WP.*# END HARDENING WP\n/s", '', $current_htaccess);

        file_put_contents($htaccess_file, $current_htaccess);
        
        flush_rewrite_rules(); // ensure the default wp htaccess intact
    }

		
	public function disable_default_endpoints( $endpoints ) {
	    
    	// Disable REST API for non logged-in users
    	// read this to potential change https://stackoverflow.com/questions/41191655/safely-disable-wp-rest-api
		$endpoints_to_remove = array(
	        //'/wp/v2', // *uncomment if we're not using ajax live search
	        //'/wp/v2/media', // *uncomment if we're not using ajax live search
	        //'/wp/v2/posts', // *uncomment if we're not using ajax live search
	        //'/wp/v2/pages', // *uncomment if we're not using ajax live search
	        '/wp/v2/types',
	        '/wp/v2/menus',
	        '/wp/v2/statuses',
	        '/wp/v2/taxonomies',
	        '/wp/v2/tags',
	        '/wp/v2/users',
	        '/wp/v2/comments',
	        '/wp/v2/settings',
	        '/wp/v2/themes',
	        '/wp/v2/plugins',
	        '/wp/v2/sidebars',
	        '/wp/v2/widgets',
	        '/wp/v2/blocks',
	        '/wp/v2/block-types',
	        '/wp/v2/block-patterns',
	        '/wp/v2/block-patterns/patterns',
	        '/wp/v2/menu-locations',
	        '/wp/v2/block-renderer',
	        '/wp/v2/search',
	        '/wp/v2/categories'
		);

		if ( ! is_user_logged_in() ) {
    		foreach ( $endpoints_to_remove as $rem_endpoint ) {
    			foreach ( $endpoints as $maybe_endpoint => $object ) {
    			if ( stripos( $maybe_endpoint, $rem_endpoint ) !== false ) {
    			unset( $endpoints[ $maybe_endpoint ] );
    			}
    			}
    		}
		}
		
		return $endpoints;
	}

	public function disable_wp_rest_api($access) {
        if (!is_user_logged_in() && !$this->disable_wp_rest_api_allow_access()) {
            $client = STARTERPACK_CLIENTAGENTNAME;
            if (!$client) {
                $message = apply_filters('disable_wp_rest_api_error', __('REST API restricted to authenticated users only.', 'disable-wp-rest-api'));
            } else {
                $message = __('REST API restricted by ');
                $message .= esc_html($client);
                $message .= __(' to authenticated users only.');
            }
            return new \WP_Error('login_required', $message, array('status' => rest_authorization_required_code()));
        }
        return $access;
    }
    
    public function disable_wp_rest_api_allow_access() {
        $post_var   = false;
        $server_var = false;
    
        // Check if it's a bot based on the user agent string
        $user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
        $is_bot = $this->is_bot_user_agent($user_agent);
    
        // If it's a bot, return false to block all access
        if ($is_bot) {
            return false;
        }
    
        if (!empty($post_var)) {
            $post_vars = is_array($post_var) ? $post_var : array($post_var);
            foreach ($post_vars as $var) {
                if (isset($_POST[$var]) && !empty($_POST[$var])) {
                    return true;
                }
            }
        }
    
        if (!empty($server_var)) {
            $server_vars = is_array($server_var) ? $server_var : array($server_var);
            foreach ($server_vars as $var) {
                if (isset($_SERVER['REQUEST_URI']) && $_SERVER['REQUEST_URI'] === $var) {
                    return true;
                }
            }
        }
    
        // Allow access to REST API for live search
        if (isset($_SERVER['REQUEST_URI']) && (
            strpos($_SERVER['REQUEST_URI'], '/wp/v2/') !== false ||
            strpos($_SERVER['REQUEST_URI'], '/wp/v2/media/') !== false ||
            strpos($_SERVER['REQUEST_URI'], '/wp/v2/posts/') !== false ||
            strpos($_SERVER['REQUEST_URI'], '/wp/v2/pages/') !== false
        )) {
            return true;
        }
    
        return false;
    }

    public function is_bot_user_agent($user_agent) {
        $bots = array(
            'bot',
            'crawl',
            'spider',
            'slurp',
        );
    
        foreach ($bots as $bot) {
            if (stripos($user_agent, $bot) !== false) {
                return true;
            }
        }
    
        return false;
    }
    
    /* 
     * start ssl protection
     */
    
    private function addHtaccessSSLRules() {
	        
        $htaccess_content = "# START SSL RULES\n";
        $htaccess_content .= "RewriteCond %{HTTPS} off\n";
        $htaccess_content .= "RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]\n";
	    $htaccess_content .= "# END SSL RULES\n";
	
	    $htaccess_file = ABSPATH . '.htaccess';
	    $current_htaccess = file_get_contents($htaccess_file);
	
	    // Add the new rules if they don't already exist
	    if (strpos($current_htaccess, '# START SSL RULES') === false) {
	        $current_htaccess .= "\n" . $htaccess_content;
	        file_put_contents($htaccess_file, $current_htaccess);
	    }
	    
	    flush_rewrite_rules(); // ensure the default wp htaccess intact

    }

    private function removeHtaccessSSLRules() {
        $htaccess_file = ABSPATH . '.htaccess';
        $current_htaccess = file_get_contents($htaccess_file);

        // Remove the rules if they exist
        $current_htaccess = preg_replace("/\n# START SSL RULES.*# END SSL RULES\n/s", '', $current_htaccess);

        file_put_contents($htaccess_file, $current_htaccess);
        
        flush_rewrite_rules(); // ensure the default wp htaccess intact
    }
    
    /* 
     * start hotlink protection
     */
    
    private function addHtaccessHotlinkRules() {
        
        $site_url = get_bloginfo('url');
        $site_url = preg_replace('#^https?://#i', '', $site_url);
	        
        $htaccess_content = "# START HOTLINK PROTECTION\n";
	    $htaccess_content .= "RewriteCond %{HTTP_REFERER} !^$\n";
	    $htaccess_content .= "RewriteCond %{HTTP_REFERER} !^http(s)?://(www\.)?" . $site_url . " [NC]\n";
	    $htaccess_content .= "RewriteRule \.(jpg|jpeg|png|webp|gif|svg|mp4|mpeg|mov|avi|mp3|pdf)$ - [NC,R=401,L]\n";
	    $htaccess_content .= "# END HOTLINK PROTECTION\n";
	
	    $htaccess_file = ABSPATH . '.htaccess';
	    $current_htaccess = file_get_contents($htaccess_file);
	
	    // Add the new rules if they don't already exist
	    if (strpos($current_htaccess, '# START HOTLINK PROTECTION') === false) {
	        $current_htaccess .= "\n" . $htaccess_content;
	        file_put_contents($htaccess_file, $current_htaccess);
	    }
	    
	    flush_rewrite_rules(); // ensure the default wp htaccess intact

    }

    private function removeHtaccessHotlinkRules() {
        $htaccess_file = ABSPATH . '.htaccess';
        $current_htaccess = file_get_contents($htaccess_file);

        // Remove the rules if they exist
        $current_htaccess = preg_replace("/\n# START HOTLINK PROTECTION.*# END HOTLINK PROTECTION\n/s", '', $current_htaccess);

        file_put_contents($htaccess_file, $current_htaccess);
        
        flush_rewrite_rules(); // ensure the default wp htaccess intact
    }
    
    public function forceWpHtaccess() {
        
        global $wp_rewrite;
        
        if (is_file(ABSPATH . '.htaccess')) {
            
            $home_path = ABSPATH;
            $htaccess = ABSPATH . '.htaccess';
 
            if ( ! function_exists( 'got_mod_rewrite' ) ) {
        		require_once ABSPATH . 'wp-admin/includes/misc.php';
        	}
  
            /*
             * Check for write access to the file.
             */
            if ( ( ! file_exists( $htaccess ) && is_writable( $home_path ) && $wp_rewrite->using_mod_rewrite_permalinks() ) || is_writable( $htaccess ) ) {
                if ( got_mod_rewrite() ) {
                    $rules = explode( "\n", $wp_rewrite->mod_rewrite_rules() );
                    return insert_with_markers( $htaccess, 'WordPress', $rules );
                }
            }
         
            return false;
        
        }

    }
    
    /* 
     * start simple firewall
     */
    
    public function simpleFirewall() {
        
        $request_uri_array  = apply_filters('request_uri_items', array('\/\.env', '\s', '<', '>', '\^', '`', '@@', '\?\?', '\/&&', '\\', '\/=', '\/:\/', '\/\/\/', '\.\.\.', '\/\*(.*)\*\/', '\+\+\+', '\{0\}', '0x00', '%00', '\(\/\(', '(\/|;|=|,)nt\.', '@eval', 'eval\(', 'union(.*)select', '\(null\)', 'base64_', '(\/|%2f)localhost', '(\/|%2f)pingserver', 'wp-config\.php', '(\/|\.)(s?ftp-?)?conf(ig)?(uration)?\.', '\/wwwroot', '\/makefile', 'crossdomain\.', 'self\/environ', 'usr\/bin\/perl', 'var\/lib\/php', 'etc\/passwd', '\/https:', '\/http:', '\/ftp:', '\/file:', '\/php:', '\/cgi\/', '\.asp', '\.bak', '\.bash', '\.bat', '\.cfg', '\.cgi', '\.cmd', '\.conf', '\.db', '\.dll', '\.ds_store', '\.exe', '\/\.git', '\.hta', '\.htp', '\.init?', '\.jsp', '\.mysql', '\.pass', '\.pwd', '\.sql', '\/\.svn', '\.exec\(', '\)\.html\(', '\{x\.html\(', '\.php\([0-9]+\)', '(benchmark|sleep)(\s|%20)*\(', '\/(db|mysql)-?admin', '\/document_root', '\/error_log', 'indoxploi', '\/sqlpatch', 'xrumer', 'www\.(.*)\.cn', '%3Cscript', '\/vbforum(\/)?', '\/vbulletin(\/)?', '\{\$itemURL\}', '(\/bin\/)(cc|chmod|chsh|cpp|echo|id|kill|mail|nasm|perl|ping|ps|python|tclsh)(\/)?$', '((curl_|shell_)?exec|(f|p)open|function|fwrite|leak|p?fsockopen|passthru|phpinfo|posix_(kill|mkfifo|setpgid|setsid|setuid)|proc_(close|get_status|nice|open|terminate)|system)(.*)(\()(.*)(\))', '(\/)(^$|0day|c99|configbak|curltest|db|index\.php\/index|(my)?sql|(php|web)?shell|php-?info|temp00|vuln|webconfig)(\.php)'));
    	$query_string_array = apply_filters('query_string_items', array('\(0x', '0x3c62723e', ';!--=', '\(\)\}', ':;\};', '\.\.\/', '\/\*\*\/', '127\.0\.0\.1', 'localhost', 'loopback', '%0a', '%0d', '%00', '%2e%2e', '%0d%0a', '@copy', 'concat(.*)(\(|%28)', 'allow_url_(fopen|include)', '(c99|php|web)shell', 'auto_prepend_file', 'disable_functions?', 'gethostbyname', 'input_file', 'execute', 'safe_mode', 'file_(get|put)_contents', 'mosconfig', 'open_basedir', 'outfile', 'proc_open', 'root_path', 'user_func_array', 'path=\.', 'mod=\.', '(globals|request)(=|\[)', 'f(fclose|fgets|fputs|fsbuff)', '\$_(env|files|get|post|request|server|session)', '(\+|%2b)(concat|delete|get|select|union)(\+|%2b)', '(cmd|command)(=|%3d)(chdir|mkdir)', '(absolute_|base|root_)(dir|path)(=|%3d)(ftp|https?)', '(s)?(ftp|inurl|php)(s)?(:(\/|%2f|%u2215)(\/|%2f|%u2215))', '(\/|%2f)(=|%3d|\$&|_mm|cgi(\.|-)|inurl(:|%3a)(\/|%2f)|(mod|path)(=|%3d)(\.|%2e))', '(<|>|\'|")(.*)(\/\*|alter|base64|benchmark|cast|char|concat|convert|create|declare|delete|drop|encode|exec|fopen|function|html|insert|md5|request|script|select|set|union|update)'));
    	$user_agent_array   = apply_filters('user_agent_items', array('&lt;', '%0a', '%0d', '%27', '%3c', '%3e', '%00', '0x00', '\/bin\/bash', '360Spider', 'acapbot', 'acoonbot', 'alexibot', 'asterias', 'attackbot', 'backdorbot', 'base64_decode', 'becomebot', 'binlar', 'blackwidow', 'blekkobot', 'blexbot', 'blowfish', 'bullseye', 'bunnys', 'butterfly', 'careerbot', 'casper', 'checkpriv', 'cheesebot', 'cherrypick', 'chinaclaw', 'choppy', 'clshttp', 'cmsworld', 'copernic', 'copyrightcheck', 'cosmos', 'crescent', 'cy_cho', 'datacha', 'demon', 'diavol', 'discobot', 'disconnect', 'dittospyder', 'dotbot', 'dotnetdotcom', 'dumbot', 'emailcollector', 'emailsiphon', 'emailwolf', 'eval\(', 'exabot', 'extract', 'eyenetie', 'feedfinder', 'flaming', 'flashget', 'flicky', 'foobot', 'g00g1e', 'getright', 'gigabot', 'go-ahead-got', 'gozilla', 'grabnet', 'grafula', 'harvest', 'heritrix', 'httrack', 'icarus6j', 'jetbot', 'jetcar', 'jikespider', 'kmccrew', 'leechftp', 'libweb', 'linkextractor', 'linkscan', 'linkwalker', 'loader', 'lwp-download', 'masscan', 'miner', 'majestic', 'md5sum', 'mechanize', 'mj12bot', 'morfeus', 'moveoverbot', 'netmechanic', 'netspider', 'nicerspro', 'nikto', 'nutch', 'octopus', 'pagegrabber', 'planetwork', 'postrank', 'proximic', 'purebot', 'pycurl', 'queryn', 'queryseeker', 'radian6', 'radiation', 'realdownload', 'remoteview', 'rogerbot', 'scooter', 'seekerspider', 'semalt', '(c99|php|web)shell', 'shellshock', 'siclab', 'sindice', 'sistrix', 'sitebot', 'site(.*)copier', 'siteexplorer', 'sitesnagger', 'skygrid', 'smartdownload', 'snoopy', 'sosospider', 'spankbot', 'spbot', 'sqlmap', 'stackrambler', 'stripper', 'sucker', 'surftbot', 'sux0r', 'suzukacz', 'suzuran', 'takeout', 'teleport', 'telesoft', 'true_robots', 'turingos', 'turnit', 'unserialize', 'vampire', 'vikspider', 'voideye', 'webleacher', 'webreaper', 'webstripper', 'webvac', 'webviewer', 'webwhacker', 'winhttp', 'wwwoffle', 'woxbot', 'xaldon', 'xxxyy', 'yamanalab', 'yioopbot', 'youda', 'zeus', 'zmeu', 'zyborg'));
    	$referrer_array     = apply_filters('referrer_items', array('blue\s?pill', 'ejaculat', 'erectile', 'erections', 'hoodia', 'huronriver', 'impotence', 'levitra', 'libido', 'lipitor', 'phentermin', 'pro[sz]ac', 'sandyauer', 'semalt\.com', 'todaperfeita', 'tramadol', 'ultram', 'unicauca', 'valium', 'viagra', 'vicodin', 'xanax', 'ypxaieo'));
    	$post_array         = apply_filters('post_items', array('<%=', '\+\/"\/\+\/', '(<|%3C|&lt;?|u003c|x3c)script', 'src=#\s', '(href|src)="javascript:', '(href|src)=javascript:', '(href|src)=`javascript:'));

        $request_uri_string  = '';
        $query_string_string = '';
        $user_agent_string   = '';
        $referrer_string     = '';

        $long_requests   = true;
		$long_req_length = 2000;
		$post_scanning   = false;

        if (isset($_SERVER['REQUEST_URI']) && !empty($_SERVER['REQUEST_URI']))     $request_uri_string  = $_SERVER['REQUEST_URI'];
        if (isset($_SERVER['QUERY_STRING']) && !empty($_SERVER['QUERY_STRING']))   $query_string_string = $_SERVER['QUERY_STRING'];
        if (isset($_SERVER['HTTP_USER_AGENT']) && !empty($_SERVER['HTTP_USER_AGENT'])) $user_agent_string   = $_SERVER['HTTP_USER_AGENT'];
        if (isset($_SERVER['HTTP_REFERER']) && !empty($_SERVER['HTTP_REFERER'])) $referrer_string     = $_SERVER['HTTP_REFERER'];

        $matches = array();

        if ($long_requests && (strlen($request_uri_string) > $long_req_length || strlen($referrer_string) > $long_req_length)) {
            $this->simpleFirewallResponse(array($long_req_length));
        }

        if ($request_uri_string && preg_match('/'. implode('|', $request_uri_array) .'/i', $request_uri_string, $matches)) {
            $this->simpleFirewallResponse($matches);
        }

        if ($query_string_string && preg_match('/'. implode('|', $query_string_array) .'/i', $query_string_string, $matches)) {
            $this->simpleFirewallResponse($matches);
        }

        if ($user_agent_string && preg_match('/'. implode('|', $user_agent_array) .'/i', $user_agent_string, $matches)) {
            $this->simpleFirewallResponse($matches);
        }

        if ($referrer_string && preg_match('/'. implode('|', $referrer_array) .'/i', $referrer_string, $matches)) {
            $this->simpleFirewallResponse($matches);
        }

        if ($post_scanning && isset($_POST)) {
            foreach ($_POST as $key => $value) {
                $value = $this->simpleFirewallGetString($value);
                if (empty($value)) continue;
                if (preg_match('/'. implode('|', $post_array) .'/i', $value, $matches)) {
                    $this->simpleFirewallResponse($matches);
                    break;
                }
            }
        }
    }

    public function simpleFirewallResponse($matches) {
        do_action('simpleFirewallResponse', $matches);
        $matches = isset($matches[0]) ? $matches[0] : null;
        if ($matches && apply_filters('simpleFirewallMatchLogging', false)) error._log('simpleFirewallMatch: Matched pattern - '.$matches);
        $this->simpleFirewallOutput();
    }
    
    public function simpleFirewallOutput() {
        header('HTTP/1.1 403 Forbidden');
        header('Content-Type: text/plain');
        die('403 Forbidden. Sorry, you\'ve entered the wrong place.');
    }

    public function simpleFirewallGetString($string) {
        if (is_array($string)) {
            foreach ($string as $key => $value) {
                $string[$key] = $this->simpleFirewallGetString($value);
            }
        } elseif (is_string($string)) {
            $string = urldecode($string);
            if (preg_match_all('/[a-zA-Z0-9]/', $string, $matches)) {
                $string = implode('', $matches[0]);
            } else {
                $string = '';
            }
        }
        return $string;
    }
    
    /* 
     * start limit login attempts
     */
    
    public function handleFailedLogin($username) {
        $ip = $this->getIpAddress();
        $attempts = $this->getLoginAttempts($ip);

        if ($attempts >= $this->maxAttempts) {
            $this->lockoutIp($ip);
            $this->sendLockoutEmail($ip);
            $this->showLockoutWarning();
        } else {
            $this->increaseLoginAttempts($ip);
        }
    }

    private function getIpAddress() {
        if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
            $ip = $_SERVER['HTTP_CLIENT_IP'];
        } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
        } else {
            $ip = $_SERVER['REMOTE_ADDR'];
        }

        return $ip;
    }

    private function getLoginAttempts($ip) {
        $attempts = get_transient('login_attempts_' . $ip);

        return false !== $attempts ? $attempts : 0;
    }

    private function increaseLoginAttempts($ip) {
        $attempts = $this->getLoginAttempts($ip);
        $attempts++;
        set_transient('login_attempts_' . $ip, $attempts, $this->lockoutDuration * MINUTE_IN_SECONDS);
    }

    private function lockoutIp($ip) {
        set_transient('login_lockout_' . $ip, true, $this->lockoutDuration * MINUTE_IN_SECONDS);
    }

    private function isIpLockedOut($ip) {
        return get_transient('login_lockout_' . $ip);
    }

    private function sendLockoutEmail($ip) {
        $adminEmail = get_option('admin_email');
        $subject = 'Login Lockout Alert';
        $message = "The IP address {$ip} has reached the maximum login attempts. It has been locked out for {$this->lockoutDuration} minutes.";

        wp_mail($adminEmail, $subject, $message);
    }

    private function showLockoutWarning() {
        //wp_die('Too many retries, please try again in ' . $this->lockoutDuration . ' minutes.');
        header('HTTP/1.1 403 Forbidden');
        header('Content-Type: text/plain');
        die('Too many failed attempts, please try again in ' . $this->lockoutDuration . ' minutes.');
    }

}