db_path = ARKSP_Plugin::get_setting( 'geo_database_path', '' ); if ( empty( $this->db_path ) ) { // Default path in uploads directory. $upload_dir = wp_upload_dir(); $this->db_path = $upload_dir['basedir'] . '/arkhost-security-pack/IP2LOCATION-LITE-DB1.BIN'; } // Check geo-blocking immediately (constructor runs during init). // This blocks access to the entire website for blocked countries. if ( ARKSP_Plugin::get_setting( 'geo_blocking_enabled', false ) ) { $this->check_geo_access(); } } /** * Check if geo-blocking should apply. */ public function check_geo_access() { $ip = ARKSP_Helper::get_client_ip(); if ( ! $ip ) { return; } // Check if IP is whitelisted. $ip_control = arksp()->get_component( 'ip_control' ); if ( $ip_control && $ip_control->is_whitelisted( $ip ) ) { return; } // Get country code. $country_code = $this->get_country_code( $ip ); if ( ! $country_code ) { return; // Can't determine country, allow access. } // Check if country is blocked. $blocked_countries = ARKSP_Plugin::get_setting( 'geo_blocked_countries', array() ); if ( ! empty( $blocked_countries ) && in_array( $country_code, $blocked_countries, true ) ) { ARKSP_Activity_Log::log( ARKSP_Activity_Log::EVENT_GEO_BLOCKED, $ip, null, sprintf( /* translators: %s: Country code */ __( 'Blocked country: %s', 'arkhost-security-pack' ), $country_code ) ); $this->block_access( $country_code ); } } /** * Get country code for an IP address. * * @param string $ip IP address. * @return string|null Two-letter country code or null. */ public function get_country_code( $ip ) { // Check cache first. if ( isset( $this->cache[ $ip ] ) ) { return $this->cache[ $ip ]; } // Check transient cache. $cache_key = 'arksp_geo_' . md5( $ip ); $cached = get_transient( $cache_key ); if ( false !== $cached ) { $this->cache[ $ip ] = $cached; return $cached; } $country_code = null; // Try IP2Location database first. $country_code = $this->lookup_ip2location( $ip ); // Fallback to PHP geoip extension. if ( ! $country_code && function_exists( 'geoip_country_code_by_name' ) ) { // phpcs:ignore PHPCompatibility.FunctionUse.RemovedFunctions.geoip_country_code_by_nameDeprecated $country_code = @geoip_country_code_by_name( $ip ); } // Cache the result. if ( $country_code ) { $country_code = strtoupper( $country_code ); $this->cache[ $ip ] = $country_code; set_transient( $cache_key, $country_code, HOUR_IN_SECONDS ); } return $country_code; } /** * Lookup IP using IP2Location database. * * @param string $ip IP address. * @return string|null */ private function lookup_ip2location( $ip ) { if ( ! file_exists( $this->db_path ) ) { return null; } // Load the database reader. if ( null === $this->db ) { require_once ARKSP_PLUGIN_DIR . 'includes/class-arksp-ip2location.php'; $this->db = new ARKSP_IP2Location( $this->db_path ); } if ( ! $this->db ) { return null; } $record = $this->db->lookup( $ip ); if ( $record && ! empty( $record['country_code'] ) && '-' !== $record['country_code'] ) { return $record['country_code']; } return null; } /** * Block access for geo-blocked countries. * * @param string $country_code Country code. */ private function block_access( $country_code ) { status_header( 403 ); nocache_headers(); $countries = ARKSP_Helper::get_countries(); $country_name = isset( $countries[ $country_code ] ) ? $countries[ $country_code ] : $country_code; $message = sprintf( /* translators: %s: Country name */ __( 'Access from %s is not permitted.', 'arkhost-security-pack' ), $country_name ); if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { wp_send_json_error( array( 'message' => $message ), 403 ); } wp_die( esc_html( $message ), esc_html__( 'Access Denied', 'arkhost-security-pack' ), array( 'response' => 403, 'back_link' => false, ) ); } /** * Check if geo-database exists. * * @return bool */ public function database_exists() { return file_exists( $this->db_path ); } /** * Get database info. * * @return array */ public function get_database_info() { $info = array( 'exists' => false, 'path' => $this->db_path, 'size' => 0, 'modified' => null, 'type' => '', ); if ( file_exists( $this->db_path ) ) { $info['exists'] = true; $info['size'] = filesize( $this->db_path ); $info['modified'] = filemtime( $this->db_path ); // Try to get database type. if ( null === $this->db ) { require_once ARKSP_PLUGIN_DIR . 'includes/class-arksp-ip2location.php'; $this->db = new ARKSP_IP2Location( $this->db_path ); } if ( $this->db ) { $info['type'] = $this->db->get_database_type(); } } return $info; } /** * Download IP2Location Lite database. * * @param string $download_token IP2Location download token (optional for LITE). * @return bool|WP_Error */ public function download_database( $download_token = '' ) { // IP2Location LITE DB1 (Country only) - free, no token required. $download_url = 'https://download.ip2location.com/lite/IP2LOCATION-LITE-DB1.BIN.ZIP'; // If token provided, use the authenticated endpoint. if ( ! empty( $download_token ) ) { $download_url = 'https://www.ip2location.com/download/?token=' . urlencode( $download_token ) . '&file=DB1LITEBIN'; } // Create data directory in uploads. $upload_dir = wp_upload_dir(); $data_dir = $upload_dir['basedir'] . '/arkhost-security-pack'; if ( ! file_exists( $data_dir ) ) { wp_mkdir_p( $data_dir ); } // Download the file. $tmp_file = download_url( $download_url, 300 ); if ( is_wp_error( $tmp_file ) ) { return $tmp_file; } // Check if it's a ZIP file. $finfo = finfo_open( FILEINFO_MIME_TYPE ); $mime = finfo_file( $finfo, $tmp_file ); finfo_close( $finfo ); $target_path = $data_dir . '/IP2LOCATION-LITE-DB1.BIN'; if ( 'application/zip' === $mime || 'application/x-zip-compressed' === $mime ) { // Extract ZIP. $zip = new ZipArchive(); if ( true === $zip->open( $tmp_file ) ) { // Find the BIN file. for ( $i = 0; $i < $zip->numFiles; $i++ ) { $filename = $zip->getNameIndex( $i ); if ( preg_match( '/\.BIN$/i', $filename ) ) { $content = $zip->getFromIndex( $i ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents file_put_contents( $target_path, $content ); break; } } $zip->close(); } // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink unlink( $tmp_file ); } else { // Direct BIN file. // phpcs:ignore WordPress.WP.AlternativeFunctions.rename_rename rename( $tmp_file, $target_path ); } if ( file_exists( $target_path ) ) { // Update database path setting. ARKSP_Plugin::update_setting( 'geo_database_path', $target_path ); return true; } return new WP_Error( 'download_failed', __( 'Failed to download or extract database.', 'arkhost-security-pack' ) ); } }