JezK
Edit File: firewall-manager.php
<?php defined( 'ABSPATH' ) || die(); use RSSSL\Security\RSSSL_Htaccess_File_Manager; use RSSSL\Pro\Security\WordPress\Rsssl_Geo_Block; /** * Class to handle the creation and include of the firewall */ class rsssl_firewall_manager { /** * Marker string for .htaccess rules related to auto prepend file. */ private const HTACCESS_MARKER_PREPEND = 'Really Simple Auto Prepend File'; /** * Firewall object */ private static rsssl_firewall_manager $this; /** * The htaccess manager */ public RSSSL_Htaccess_File_Manager $htaccessManager; /** * File * * @var string */ private $file; /** * If we can use a dynamic path * * @var bool */ private $dynamic_path; /** * Path to the firewall.php file, filterable. * * @var string */ private string $firewall_file_path; /** * Rules to add to the firewall. * * @var string */ private $rules = ''; /** * The WP_Filesystem instance, used for file operations. * */ private $wp_filesystem; public function __construct(RSSSL_Htaccess_File_Manager $htaccessManager) { if ( isset( self::$this ) ) { wp_die(); } self::$this = $this; $this->htaccessManager = $htaccessManager; // Set dynamic path detection dynamically to handle environment changes $this->dynamic_path = $this->get_dynamic_path(); // Determine firewall.php path, allowing custom content dir or fallback. if ( $this->dynamic_path ) { $wpContentPath = ABSPATH . 'wp-content/'; } else { $wpContentPath = WP_CONTENT_DIR . '/'; } $this->firewall_file_path = apply_filters( 'rsssl_firewall_file_path', $wpContentPath . 'firewall.php' ); // Set the file path dynamically so we can detect WP_CONTENT_DIR changes $this->file = $this->get_advanced_headers_path(); // Keep firewall generation aligned with the shared rule rebuild lifecycle. add_action( 'rsssl_update_advanced_headers', array( $this, 'update_advanced_headers' ), 10 ); add_action( 'rsssl_after_saved_fields', array( $this, 'install' ), 100 ); add_action( 'rsssl_deactivate', array( $this, 'uninstall' ), 20 ); add_filter( 'rsssl_htaccess_security_rules', array( $this, 'add_prepend_file_htaccess_rule' ), 5 ); // Proactively check for environment changes on admin loads add_action( 'admin_init', array( $this, 'maybe_regenerate_firewall' ), 5 ); add_filter( 'rsssl_notices', array( $this, 'notices' ) ); // WP Rocket changes whether auto-prepend should stay in the shared root rule set. add_action( 'rocket_activation', array( $this, 'queue_prepend_file_htaccess_removal' ) ); add_action( 'rocket_deactivation', array( $this, 'queue_prepend_file_htaccess_sync' ) ); if ( ! defined( 'RSSSL_IS_WP_ENGINE' ) ) { define( 'RSSSL_IS_WP_ENGINE', isset( $_SERVER['IS_WPE'] ) ); } if ( ! defined( 'RSSSL_IS_FLYWHEEL' ) ) { define( 'RSSSL_IS_FLYWHEEL', isset( $_SERVER['SERVER_SOFTWARE'] ) && strpos( $_SERVER['SERVER_SOFTWARE'], 'Flywheel/' ) === 0 ); } if ( ! defined( 'RSSSL_IS_PRESSABLE' ) ) { define( 'RSSSL_IS_PRESSABLE', ( defined( 'IS_ATOMIC' ) && IS_ATOMIC ) || ( defined( 'IS_PRESSABLE' ) && IS_PRESSABLE ) ); } } /** * Main installer for the firewall file * * @return void */ public function install(): void { $this->sync_advanced_headers_state( true ); } /** * Refresh the advanced-headers / firewall files without directly queueing root `.htaccess` changes. * * This is used for advanced-headers-only updates such as CSP learning-mode writes where the file contents * need to change but the shared `.htaccess` flow should not be touched. */ public function update_advanced_headers(): void { $this->sync_advanced_headers_state( false ); } /** * Synchronize the advanced-headers bootstrap files and, when applicable, request a shared root `.htaccess` update. */ private function sync_advanced_headers_state( bool $maybe_queue_root_htaccess_update ): void { // Don't regenerate files during deactivation if ( doing_action( 'rsssl_deactivate' ) ) { return; } if ( ! rsssl_admin_logged_in() ) { return; } if ( wp_doing_ajax() ) { return; } $this->rules = apply_filters( 'rsssl_firewall_rules', '' ); $has_rules = ! empty( trim( $this->rules ) ); // `true` means "keep the root prepend state aligned when applicable": queue removal when no rules remain, // otherwise only queue a sync for an existing root `.htaccess` target outside `plugins_loaded`. if ( ! $has_rules ) { if ( $maybe_queue_root_htaccess_update ) { $this->queue_prepend_file_htaccess_removal(); } $this->remove_prepend_file_in_wp_config(); $this->remove_auto_prepend_file_in_user_ini(); return; } if ( ! $this->update_firewall( $this->rules ) ) { return; } $this->include_prepend_file_in_wp_config(); $should_queue_root_htaccess_sync = $maybe_queue_root_htaccess_update && $this->has_root_htaccess_file() && current_filter() !== 'plugins_loaded'; if ( $should_queue_root_htaccess_sync ) { // Avoid queueing the shared root sync during `plugins_loaded` // later admin-side flows can request it when applicable. $this->queue_prepend_file_htaccess_sync(); } if ( $this->has_user_ini_file() ) { $this->include_prepend_file_in_user_ini(); } } /** * Remove file and file inclusions * * @return void */ public function uninstall(): void { if ( ! rsssl_user_can_manage() ) { return; } if ( wp_doing_ajax() ) { return; } $this->remove_prepend_file_in_wp_config(); $this->remove_auto_prepend_file_in_user_ini(); $this->empty_file(); $this->delete_test_file(); // Delete firewall.php file using the existing handler if ( class_exists( '\RSSSL\Pro\Security\WordPress\Firewall\Rsssl_Firewall_File_Handler' ) ) { $firewall_handler = new \RSSSL\Pro\Security\WordPress\Firewall\Rsssl_Firewall_File_Handler(); $firewall_handler->delete(); } } /** * Proactively check for environment changes on admin loads * This ensures firewall regeneration after site clones/migrations * * @return void */ public function maybe_regenerate_firewall(): void { if ( ! rsssl_user_can_manage() ) { return; } // Only check if we have firewall rules that need to be active if ( ! $this->has_rules() ) { return; } // Only run the check if environment has changed if ( $this->should_regenerate_firewall() ) { // Trigger the full installation process for firewall.php $this->install(); // Also generate the Geo Block firewall settings $fireWallSettingIsEnabled = rsssl_get_option( 'enable_firewall', false ); if ( $fireWallSettingIsEnabled ) { $geoBlock = Rsssl_Geo_Block::get_instance(); $geoBlock->generate_firewall_rules(); } } } /** * Check if our firewall file exists * * @param string $file // filename, including path * * @return bool */ private function file_exists( string $file ): bool { $wp_filesystem = $this->get_file_system(); // Use WP Filesystem if available, otherwise fall back to direct operations return $wp_filesystem ? $wp_filesystem->is_file( $file ) : file_exists( $file ); } /** * Get the WP_Filesystem instance with lazy loading * * @return false|WP_Filesystem_Base */ private function get_file_system() { // Return cached instance if available if ( $this->wp_filesystem !== null ) { return $this->wp_filesystem; } if ( ! function_exists( 'WP_Filesystem' ) ) { include_once ABSPATH . 'wp-admin/includes/file.php'; } if ( false === ( $creds = request_filesystem_credentials( site_url(), '', false, false, null ) ) ) { $this->wp_filesystem = false; return false; // stop processing here. } global $wp_filesystem; if ( ! WP_Filesystem( $creds ) ) { // request_filesystem_credentials(site_url(), '', true, false, null);//phpcs:ingore $this->wp_filesystem = false; return false; } // Cache the instance $this->wp_filesystem = $wp_filesystem; return $wp_filesystem; } /** * Update the file that contains the firewall rules, advanced-headers.php * * @param string $rules //rules to add to the firewall. * * @return bool Whether the firewall file is readable after the update. */ public function update_firewall( string $rules ): bool { if ( ! rsssl_admin_logged_in() ) { return false; } $contents = '<?php' . "\n"; $contents .= '/**' . "\n"; $contents .= '* This file is created by Really Simple Security' . "\n"; $contents .= '*/' . "\n\n"; $contents .= 'if (defined("SHORTINIT") && SHORTINIT) return;' . "\n\n"; $contents .= '$base_path = dirname(__FILE__);' . "\n"; $contents .= 'if( file_exists( $base_path . "/rsssl-safe-mode.lock" ) ) {' . "\n"; $contents .= ' if ( ! defined( "RSSSL_SAFE_MODE" ) ) {' . "\n"; $contents .= ' define( "RSSSL_SAFE_MODE", true );' . "\n"; $contents .= ' }' . "\n"; $contents .= ' return;' . "\n"; $contents .= '}' . "\n\n"; // allow disabling of headers for detection purposes. $contents .= 'if ( isset($_GET["rsssl_header_test"]) && (int) $_GET["rsssl_header_test"] === ' . $this->get_headers_nonce() . ' ) return;' . "\n\n"; //if already included at some point, don't execute again. $contents .= 'if ( defined("RSSSL_HEADERS_ACTIVE" ) ) return;' . "\n"; $contents .= 'define( "RSSSL_HEADERS_ACTIVE", true );' . "\n"; // If the main firewall (firewall.php) is enabled, add the include directive for it. if ( rsssl_get_option( 'enable_firewall', false ) ) { $firewallFilePath = $this->firewall_file_path; $contents .= 'if ( file_exists( "' . $firewallFilePath . '" ) ) {' . "\n"; $contents .= ' require_once "' . $firewallFilePath . '";' . "\n"; $contents .= '}' . "\n\n"; } $contents .= "//RULES START\n" . $rules; $this->put_contents( $this->file, $contents ); return $this->is_readable( $this->file ); } /** * Save data * * @param string $file //filename, including path. * @param string $contents //data to save. * * @return void */ private function put_contents( $file, $contents ): void { if ( ! rsssl_admin_logged_in() ) { return; } // Check if file is writable (or doesn't exist yet, which is fine) if ( $this->file_exists( $file ) && ! $this->is_writable( $file ) ) { return; } // Check if directory is writable before attempting to create new file $directory = dirname( $file ); if ( ! $this->file_exists( $file ) && ! $this->is_writable( $directory ) ) { return; } $wp_filesystem = $this->get_file_system(); if ( $wp_filesystem === false ) { // Double-check directory writability before fallback to prevent PHP warnings if ( ! is_writable( $directory ) ) { return; } file_put_contents( $file, $contents );//phpcs:ignore return; } $wp_filesystem->put_contents( $file, $contents ); // Only chmod files other than .htaccess and wp-config.php if ( strpos($file, 'htaccess') === false && strpos($file, 'wp-config.php') === false ) { $wp_filesystem->chmod( $file, 0644 ); } } /** * Get the contents of a file * * @param string $file //filename, including path. * * @return string */ private function get_contents( string $file ): string { if ( ! $this->is_readable( $file ) ) { return ''; } $wp_filesystem = $this->get_file_system(); if ( $wp_filesystem === false ) { return file_exists( $file ) ? file_get_contents( $file ) : '';//phpcs:ignore } $result = $wp_filesystem->get_contents( $file ); return $result ? $result : ''; } /** * Empty the advanced-headers.php file instead of deleting it. * This prevents 500 errors when user.ini is cached and still references this file. * * @return void */ private function empty_file(): void { if ( ! rsssl_user_can_manage() ) { return; } $contents = <<<PHP <?php // This file was created by Really Simple Security // It is no longer used and safe to delete PHP; $this->put_contents( $this->file, $contents ); } /** * Get the path to the advanced-headers-test.php file. * * @return string */ private function get_test_file_path(): string { return WP_CONTENT_DIR . '/advanced-headers-test.php'; } /** * Delete the advanced-headers-test.php file if it exists. * * @return void */ private function delete_test_file(): void { if ( ! rsssl_user_can_manage() ) { return; } $test_file = $this->get_test_file_path(); if ( ! file_exists( $test_file ) ) { return; } $wp_filesystem = $this->get_file_system(); if ( $wp_filesystem === false ) { unlink( $test_file );//phpcs:ignore return; } $wp_filesystem->delete( $test_file ); } /** * Get the home path * * @return string */ public function get_home_path(): string { if ( ! function_exists( 'get_home_path' ) ) { include_once ABSPATH . 'wp-admin/includes/file.php'; } if ( defined('RSSSL_IS_FLYWHEEL') && RSSSL_IS_FLYWHEEL && isset( $_SERVER['DOCUMENT_ROOT'] ) ) { return trailingslashit( $this->sanitize_path( wp_unslash( $_SERVER['DOCUMENT_ROOT'] ) ) ); } return get_home_path(); } /** * Sanitize a path * * @param string $path //string to sanitize. * * @return string */ private function sanitize_path( $path ): string { // prevent path traversal. return str_replace( '../', '/', realpath( sanitize_text_field( $path ) ) ); } /** * Check whether the shared root `.htaccess` target already exists. * The prepend rule can only be managed through the centralized root writer when there is a real file to update. * * @return bool */ private function has_root_htaccess_file(): bool { return $this->file_exists( $this->htaccessManager->get_root_htaccess_target_path() ); } /** * Add the prepend rule to the centralized root .htaccess rule batch. * * @param array $rules * * @return array */ public function add_prepend_file_htaccess_rule( array $rules ): array { $rule = $this->get_root_htaccess_prepend_rule_definition(); if ( empty( $rule ) ) { return $rules; } $rules[] = $rule; return $rules; } /** * Return the root-rule definition for the auto-prepend include when the file/config state is ready. */ private function get_root_htaccess_prepend_rule_definition(): array { if ( $this->htaccessManager->determineExistingRootHtaccessFilePath() === '' ) { return []; } if ( ! $this->file_exists( $this->file ) ) { return []; } if ( ! $this->wp_config_contains_latest() ) { return []; } return [ 'identifier' => self::HTACCESS_MARKER_PREPEND, 'rules' => $this->build_prepend_file_htaccess_rules(), ]; } /** * Queue the centralized `.htaccess` writer so prepend inclusion follows the shared commit path. * * @return void */ public function queue_prepend_file_htaccess_sync(): void { if ( ! function_exists( 'rsssl_request_managed_htaccess_rebuild' ) ) { return; } rsssl_request_managed_htaccess_rebuild(); } /** * Build the literal auto-prepend `.htaccess` lines, including any matching `user.ini` protection block. */ private function build_prepend_file_htaccess_rules() : string { if ( defined('RSSSL_HTACCESS_SKIP_AUTO_PREPEND') && RSSSL_HTACCESS_SKIP_AUTO_PREPEND ) { return ''; } if (isset(RSSSL()->server) ) { $config = RSSSL()->server->auto_prepend_config(); } else { $config = get_option('rsssl_auto_prepend_config'); if (empty($config)) { return ''; } } $file = addcslashes($this->file, "'"); switch ($config) { case 'litespeed': $rules = array( '<IfModule LiteSpeed>', 'php_value auto_prepend_file ' . $file , '</IfModule>', '<IfModule lsapi_module>', 'php_value auto_prepend_file ' . $file, '</IfModule>', ); break; case 'apache-mod_php': default: $rules = array( '<IfModule mod_php7.c>', 'php_value auto_prepend_file ' . $file , '</IfModule>', '<IfModule mod_php.c>', 'php_value auto_prepend_file ' . $file, '</IfModule>', ); } $userIni = ini_get('user_ini.filename'); if ($userIni) { array_push( $rules, sprintf( '<Files "%s">', addcslashes( $userIni, '"' ) ), '<IfModule mod_authz_core.c>', 'Require all denied', '</IfModule>', '<IfModule !mod_authz_core.c>', 'Order deny,allow', 'Deny from all', '</IfModule>', '</Files>' ); } return implode( "\n", $rules ); } /** * Include the file in the wp-config * * @return void */ private function include_prepend_file_in_wp_config(): void { if ( ! rsssl_user_can_manage() ) { return; } $file = $this->wpconfig_path(); if ( empty( $file ) ) { update_option( 'rsssl_firewall_error', 'wpconfig-notfound', false ); return; } $content = $this->get_contents( $file ); if ( strpos( $content, 'advanced-headers.php' ) === false ) { $rule = $this->get_wp_config_rule(); // if RSSSL comment is found, insert after. $rsssl_comment = '//END Really Simple Security Server variable fix'; if ( strpos( $content, $rsssl_comment ) !== false ) { $pos = strrpos( $content, $rsssl_comment ); $updated = substr_replace( $content, $rsssl_comment . "\n" . $rule . "\n", $pos, strlen( $rsssl_comment ) ); } else { $updated = preg_replace( '/<\?php/', "<?php\n" . $rule . "\n", $content, 1 ); } if ( strpos( $updated, "\n\n\n" ) !== false ) { $updated = str_replace( "\n\n\n", "\n\n", $updated ); } $this->put_contents( $file, $updated ); } // save errors. if ( $this->is_writable( WP_CONTENT_DIR ) && ( $this->is_writable( $file ) || strpos( $content, 'advanced-headers.php' ) !== false ) ) { update_option( 'rsssl_firewall_error', false, false ); } elseif ( ! $this->is_writable( $file ) ) { update_option( 'rsssl_firewall_error', 'wpconfig-notwritable', false ); } elseif ( ! $this->is_writable( WP_CONTENT_DIR ) ) { update_option( 'rsssl_firewall_error', 'advanced-headers-notwritable', false ); } } /** * Queue the centralized `.htaccess` writer so the prepend block is removed through the shared commit path. * * @return void */ public function queue_prepend_file_htaccess_removal(): void { if ( ! function_exists( 'rsssl_request_managed_htaccess_rebuild' ) ) { return; } rsssl_request_managed_htaccess_rebuild(); } /** * Remove the prepend file from the config * * @return void */ private function remove_prepend_file_in_wp_config(): void { if ( ! rsssl_user_can_manage() ) { return; } $file = $this->wpconfig_path(); if ( empty( $file ) ) { return; } if ( $this->is_writable( $file ) ) { $content = $this->get_contents( $file ); $rule = $this->get_wp_config_rule(); if ( strpos( $content, $rule ) !== false ) { $content = str_replace( $rule, '', $content ); if ( strpos( $content, "\n\n\n" ) !== false ) { $content = str_replace( "\n\n\n", "\n\n", $content ); } $this->put_contents( $file, $content ); } } } /** * Check if a file exists and is readable without triggering filesystem warnings. * * @param string $file // filename, including path. * * @return bool */ private function is_writable( $file ): bool { $wp_filesystem = $this->get_file_system(); // Use WP Filesystem if available, otherwise fall back to direct operations return $wp_filesystem ? $wp_filesystem->is_writable( $file ) : is_writable( $file );//phpcs:ignore } /** * Wrapper function * * @param string $file // filename, including path. * * @return bool */ private function is_readable( string $file ): bool { if ( empty( $file ) || ! $this->file_exists( $file ) ) { return false; } $wp_filesystem = $this->get_file_system(); // Readability checks can emit warnings for missing files, so file_exists() is checked first. return $wp_filesystem ? $wp_filesystem->is_readable( $file ) : is_readable( $file );//phpcs:ignore } /** * This class has it's own settings page, to ensure it can always be called * * @return bool */ public function is_settings_page() { if ( rsssl_is_logged_in_rest() ) { return true; } if ( isset( $_GET['page'] ) && 'really-simple-security' === $_GET['page'] ) {//phpcs:ignore return true; } return false; } /** * Generate and return a random nonce * * @return int */ public function get_headers_nonce() { if ( ! get_site_option( 'rsssl_header_detection_nonce' ) ) { update_site_option( 'rsssl_header_detection_nonce', wp_rand( 1000, 999999999 ) ); } return (int) get_site_option( 'rsssl_header_detection_nonce' ); } /** * Check if any rules were added * * @return bool */ public function has_rules() { $this->rules = apply_filters( 'rsssl_firewall_rules', '' ); return ! empty( trim( $this->rules ) ); } /** * Get the status for the firewall rules writing * * @return false|string */ public function firewall_write_error() { return get_site_option( 'rsssl_firewall_error' ); } /** * Get the status for the firewall * * @return bool */ public function firewall_active_error() { if ( ! $this->has_rules() ) { return false; } return ! defined( 'RSSSL_HEADERS_ACTIVE' ); } /** * Show some notices * * @param array $notices //array of notices. * * @return array */ public function notices( $notices ) { $notices['firewall-error'] = array( 'callback' => 'RSSSL_SECURITY()->firewall_manager->firewall_write_error', 'score' => 5, 'output' => array( 'wpconfig-notwritable' => array( 'title' => __( 'Firewall', 'really-simple-ssl' ), 'msg' => __( 'A firewall rule was enabled, but the wp-config.php is not writable.', 'really-simple-ssl' ) . ' ' . __( 'Please set the wp-config.php to writable until the rule has been written', 'really-simple-ssl' ), 'icon' => 'open', 'dismissible' => true, ), 'advanced-headers-notwritable' => array( 'title' => __( 'Firewall', 'really-simple-ssl' ), 'msg' => __( 'A firewall rule was enabled, but /the wp-content/ folder is not writable.', 'really-simple-ssl' ) . ' ' . __( 'Please set the wp-content folder to writable:', 'really-simple-ssl' ), 'icon' => 'open', 'dismissible' => true, ), ), 'show_with_options' => array( 'disable_http_methods', ), ); $notices['firewall-active'] = array( 'condition' => array( 'RSSSL_SECURITY()->firewall_manager->firewall_active_error' ), 'callback' => '_true_', 'score' => 5, 'output' => array( 'true' => array( 'title' => __( 'Firewall', 'really-simple-ssl' ), 'msg' => __( 'A firewall rule was enabled, but the firewall does not seem to get loaded correctly.', 'really-simple-ssl' ) . ' ' . __( 'Please check if the advanced-headers.php file is included in the wp-config.php, and exists in the wp-content folder', 'really-simple-ssl' ), 'icon' => 'open', 'dismissible' => true, ), ), 'show_with_options' => array( 'disable_http_methods', ), ); return $notices; } /** * // As WP_CONTENT_DIR is not defined at this point in the wp-config, we can't use that. * // for those setups where the WP_CONTENT_DIR is not in the default location, we hardcode the path. * * @return string */ public function get_wp_config_rule() { if ( $this->dynamic_path ) { $rule = 'if (!defined("RSSSL_HEADERS_ACTIVE") && file_exists( ABSPATH . "wp-content/advanced-headers.php")) {' . "\n"; $rule .= "\t" . 'require_once ABSPATH . "wp-content/advanced-headers.php";' . "\n" . '}'; } else { $rule = 'if (!defined("RSSSL_HEADERS_ACTIVE") && file_exists(\'' . WP_CONTENT_DIR . '/advanced-headers.php\')) {' . "\n"; $rule .= "\t" . 'require_once \'' . WP_CONTENT_DIR . '/advanced-headers.php\';' . "\n" . '}'; } return $rule; } /** * Check if the wp-config contains the if constant condition, to prevent duplicate loading. If not, try upgrading. If that fails, skip. * Wrapper function added for clearer purpose in code * * @return bool */ private function wp_config_contains_latest(): bool { return $this->update_wp_config_rule(); } /** * Called in upgrade.php, to upgrade older rules to the latest. * Returns true if the wpconfig contains the upgraded lines * * @return bool */ public function update_wp_config_rule(): bool { $file = $this->wpconfig_path(); if ( ! $file ) { return false; } $content = $this->get_contents( $file ); $find = '(file_exists( ABSPATH . "wp-content/advanced-headers.php"))'; if ( false !== strpos( $content, $find ) ) { if ( ! $this->is_writable( $file ) ) { return false; } $replace = '(!defined("RSSSL_HEADERS_ACTIVE") && file_exists( ABSPATH . "wp-content/advanced-headers.php"))'; $content = str_replace( $find, $replace, $content ); $this->put_contents( $file, $content ); } return true; } /** * Admin is not always loaded here, so we define our own function * * @return string|null */ public function wpconfig_path() { // Allow the wp-config.php path to be overridden via a filter. $filtered_path = apply_filters( 'rsssl_wpconfig_path', '' ); // If a filtered path is provided, validate it. if ( ! empty( $filtered_path ) ) { $directory = dirname( $filtered_path ); // Ensure the directory exists before checking for the file. if ( is_dir( $directory ) && $this->file_exists( $filtered_path ) ) { return $filtered_path; } } // Limit number of iterations to 5. $i = 0; $maxiterations = 5; $dir = ABSPATH; do { ++ $i; if ( $this->file_exists( $dir . 'wp-config.php' ) ) { return $dir . 'wp-config.php'; } } while ( ( $dir = realpath( "$dir/.." ) ) && ( $i < $maxiterations ) );//phpcs:ignore return ''; } /** * Clear the headers * * @return void */ public function remove_advanced_headers() { $this->uninstall(); } /** * Check if the firewall file should be regenerated * This detects environment changes like WP Engine clones * Also returns true if the file does not exist yet * * @return bool */ private function should_regenerate_firewall(): bool { if ( ! $this->file_exists( $this->file ) ) { return true; } // Check if we have stored environment signature $stored_signature = get_option( 'rsssl_firewall_environment_signature' ); $current_signature = $this->get_environment_signature(); // If no stored signature, store it and regenerate if ( ! $stored_signature ) { update_option( 'rsssl_firewall_environment_signature', $current_signature, false ); return true; } // If signature changed, update it and regenerate if ( $stored_signature !== $current_signature ) { update_option( 'rsssl_firewall_environment_signature', $current_signature, false ); return true; } return false; } /** * Generate a signature of the current environment * Used to detect when the site has been cloned or migrated * * @return string */ private function get_environment_signature(): string { $signature_parts = array( WP_CONTENT_DIR, ABSPATH, get_home_url(), get_site_url(), ); return md5( implode( '|', $signature_parts ) ); } /** * Get the advanced headers file path * Always uses WP_CONTENT_DIR which is dynamically set by WordPress * * @return string */ private function get_advanced_headers_path(): string { return WP_CONTENT_DIR . '/advanced-headers.php'; } /** * Check if we can use a dynamic path for the advanced headers file * @return string */ private function get_dynamic_path(): string { return WP_CONTENT_DIR === ABSPATH . 'wp-content'; } /** * Check if a user.ini file exists or is in user. * * @return bool */ private function has_user_ini_file():bool { $userIni = ini_get('user_ini.filename'); if ( $userIni ) { return true; } return false; } /** * Add auto prepend file to user.ini * * @return void */ private function include_prepend_file_in_user_ini():void{ if ( ! rsssl_user_can_manage() ) { return; } if ( defined('RSSSL_HTACCESS_SKIP_AUTO_PREPEND') && RSSSL_HTACCESS_SKIP_AUTO_PREPEND ) { return; } $config = RSSSL()->server->auto_prepend_config(); if ( !$this->has_user_ini_file() ) { return; } $autoPrependIni = ''; $userIniPath = $this->get_user_ini_path(); if ( empty( $userIniPath ) ) { return; } // .user.ini configuration switch ($config) { case 'cgi': case 'nginx': case 'apache-suphp': case 'litespeed': case 'iis': $autoPrependIni = sprintf("; BEGIN Really Simple Auto Prepend File auto_prepend_file = '%s' ; END Really Simple Auto Prepend File", addcslashes($this->file, "'")); break; } if ( empty( $autoPrependIni ) ) { return; } $userIniContent = $this->get_contents( $userIniPath ); $updatedUserIniContent = $autoPrependIni; if ( $userIniContent !== '' ) { $updatedUserIniContent = str_replace( 'auto_prepend_file', ';auto_prepend_file', $userIniContent ); $regex = '/; BEGIN Really Simple Auto Prepend File.*?; END Really Simple Auto Prepend File/is'; if ( preg_match( $regex, $updatedUserIniContent ) === 1 ) { $replacement = preg_replace( $regex, $autoPrependIni, $updatedUserIniContent ); if ( ! is_string( $replacement ) ) { return; } $updatedUserIniContent = $replacement; } else { $updatedUserIniContent .= "\n" . $autoPrependIni; } } if ( $updatedUserIniContent === $userIniContent ) { return; } $this->put_contents( $userIniPath, $updatedUserIniContent ); } /** * Get the user.ini path * * @return false|string */ public function get_user_ini_path() { $userIni = ini_get('user_ini.filename'); if ($userIni) { return $this->get_home_path() . $userIni; } return false; } /** * Remove the added auto prepend file * * @return void */ private function remove_auto_prepend_file_in_user_ini() { if ( ! rsssl_user_can_manage() ) { return; } if ( ! $this->has_user_ini_file() ) { return; } $userIniPath = $this->get_user_ini_path(); if ( empty( $userIniPath ) ) { return; } if ( ! $this->is_readable( $userIniPath ) ) { return; } $userIniContent = $this->get_contents( $userIniPath ); $updatedUserIniContent = preg_replace( '/; BEGIN Really Simple Auto Prepend File.*?; END Really Simple Auto Prepend File/is', '', $userIniContent ); if ( ! is_string( $updatedUserIniContent ) ) { return; } $updatedUserIniContent = str_replace( 'auto_prepend_file', ';auto_prepend_file', $updatedUserIniContent ); if ( $updatedUserIniContent === $userIniContent ) { return; } $this->put_contents( $userIniPath, $updatedUserIniContent ); } }
Paket Wisata Karimunjawa Terlengkap & Harga Terbaik 2026
https://validator.w3.org/feed/docs/rss2.html
Promo Paket Wisata Karimunjawa
Info Harga dan Paket Liburan ke Karimunjawa
Pulau Karimun Jawa
Paket Mancing Karimunjawa
Paket Trip Karimunjawa
Pelabuhan Karimunjawa
Kuliner Khas Karimunjawa
Pantai Bobby Karimunjawa
Whales: Giants of the Ocean
Whales: Giants of the Ocean
Taka Sendok Karimunjawa
Contact US
Informasi Perjalanan Paket Travel Wisata Karimunjawa
Rental Mobil Karimunjawa
Paket Open Trip Karimunjawa
Paket Snorkeling Karimunjawa
Sama Sama Homestay Karimunjawa
Paket Tour Karimunjawa Jepara
Informasi Biaya Liburan ke Karimunjawa
Paket Wisata Karimunjawa 4 Hari 3 Malam
Oleh Oleh Khas Karimunjawa
Promo Open Trip Karimun Jawa
Tentang Kami
Sunset Karimunjawa
Tiket Kapal Karimunjawa
Informasi Reservasi Hotel Resort Java Paradise Karimunjawa
Taman Nasional Karimunjawa
Informasi Harga Paket Wisata Karimunjawa Murah
Informasi Jadwal Kapal Pelni Semarang ke Karimunjawa
Informasi Jadwal Kapal Karimunjawa Ferry Siginjai
Paket Wisata Karimunjawa Rombongan
Tiket Kapal
Rincian Biaya Wisata Karimunjawa
Paket Wisata Karimunjawa 2 Hari 1 Malam
Booking
Informasi Jadwal Kapal Express Karimunjawa
Paket Diving Karimunjawa
Informasi Harga Paket Wisata Karimunjawa 3 Hari 2 Malam
Harga Paket Travel Karimunjawa
Private Trip Karimunjawa
Paket Wisata Karimunjawa dari Solo
Paket Homestay RGJS Karimunjawa
Cara ke Karimunjawa
Pantai Sunset Beach Karimunjawa
Sitemap
Paket Regular Karimunjawa 3 Hari 2 Malam
Cuaca Karimun Jawa
Karimunjawa Dimana
Narayana Resort Karimunjawa
Pantai Laendra Karimunjawa