sunrise_file_path = WP_CONTENT_DIR . '/sunrise.php'; $this->wp_config_path = ABSPATH . 'wp-config.php'; $upload_dir = wp_upload_dir(); $this->config_dir_path = $upload_dir['basedir'] . '/adamszokol-onion-service'; $this->config_file_path = $this->config_dir_path . '/wp-onion-service-config.php'; register_activation_hook( __FILE__, [ $this, 'activate' ] ); add_action( 'plugins_loaded', [ $this, 'init' ] ); } public function activate() { add_option( $this->setup_option_name, true ); } public function init() { $this->add_plugin_hooks(); } private function add_plugin_hooks() { $menu_hook = is_multisite() ? 'network_admin_menu' : 'admin_menu'; add_action( $menu_hook, [ $this, 'add_admin_page' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] ); add_action( 'admin_post_adam_save', [ $this, 'handle_form_save' ] ); add_action( 'admin_post_adam_delete', [ $this, 'handle_form_delete' ] ); add_action( 'wp_ajax_adam_search_sites', [ $this, 'ajax_search_sites' ] ); add_action( 'template_redirect', [ $this, 'send_onion_location_header' ] ); } public function add_admin_page() { $page_title = __( 'Onion Service Settings', 'adamszokol-onion-service' ); $menu_title = __( 'Onion Service', 'adamszokol-onion-service' ); $capability = is_multisite() ? 'manage_network_options' : 'manage_options'; $menu_slug = 'adamszokol-onion-service-settings'; $function = [ $this, 'render_settings_page' ]; $icon = 'dashicons-shield-alt'; $this->admin_page_hook = add_menu_page( $page_title, $menu_title, $capability, $menu_slug, $function, $icon, 82 ); } public function enqueue_admin_assets( $hook_suffix ) { if ( $hook_suffix !== $this->admin_page_hook ) { return; } wp_enqueue_style( 'adam-onion-admin-style', plugin_dir_url( __FILE__ ) . 'css/admin-style.css', [], '1.0.0' // Original version ); wp_enqueue_script( 'adam-onion-admin-js', plugin_dir_url( __FILE__ ) . 'js/admin-script.js', [ 'jquery' ], '1.0.0', // Original version true ); // Localize script for AJAX data and i18n strings wp_localize_script( 'adam-onion-admin-js', 'adamOnionData', [ 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'adam_search_nonce' ), 'i18n' => [ 'searching' => esc_html__( 'Searching...', 'adamszokol-onion-service' ), 'noSites' => esc_html__( 'No sites found.', 'adamszokol-onion-service' ), 'reqFailed' => esc_html__( 'Request failed.', 'adamszokol-onion-service' ), ], ]); } private function get_adam_setting( $key, $default = false ) { $option_name = 'adam_' . $key; return is_multisite() ? get_site_option( $option_name, $default ) : get_option( $option_name, $default ); } private function update_adam_setting( $key, $value ) { $option_name = 'adam_' . $key; return is_multisite() ? update_site_option( $option_name, $value ) : update_option( $option_name, $value ); } public function render_settings_page() { $capability = is_multisite() ? 'manage_network_options' : 'manage_options'; if ( ! current_user_can( $capability ) ) return; if ( get_option( $this->setup_option_name ) ) { $this->write_static_config_file( true ); // Write default config $this->manage_sunrise(); // Write sunrise & check wp-config delete_option( $this->setup_option_name ); } settings_errors( 'adam_messages' ); $this->show_config_warnings(); $onion_map = $this->get_adam_setting( 'map', [] ); $is_disabled = $this->get_adam_setting( 'service_disabled', 0 ); $disabled_message = $this->get_adam_setting( 'disabled_message', 'This Onion Service is temporarily disabled.' ); ?>

$onion_url ) : ?>
update_adam_setting( 'service_disabled', isset( $_POST['service_disabled'] ) ? 1 : 0 ); $this->update_adam_setting( 'disabled_message', isset( $_POST['disabled_message'] ) ? wp_kses_post( wp_unslash( $_POST['disabled_message'] ) ) : '' ); $blog_id = isset( $_POST['blog_id'] ) ? absint( $_POST['blog_id'] ) : 0; $onion_url = isset( $_POST['onion_url'] ) ? sanitize_text_field( wp_unslash( $_POST['onion_url'] ) ) : ''; if ( $blog_id > 0 && ! empty( $onion_url ) && strpos( $onion_url, '.onion' ) !== false ) { $onion_map = $this->get_adam_setting( 'map', [] ); $onion_map[ $blog_id ] = $onion_url; $this->update_adam_setting( 'map', $onion_map ); } $this->write_static_config_file(); $this->manage_sunrise(); // Regenerate sunrise.php after saving add_settings_error( 'adam_messages', 'settings_saved', __( 'Settings saved.', 'adamszokol-onion-service' ), 'updated' ); wp_safe_redirect( wp_get_referer() ); exit; } public function handle_form_delete() { $capability = is_multisite() ? 'manage_network_options' : 'manage_options'; $blog_id_to_delete = isset( $_POST['blog_id_to_delete'] ) ? absint( $_POST['blog_id_to_delete'] ) : 0; $nonce = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : ''; if ( ! current_user_can( $capability ) || $blog_id_to_delete === 0 || ! wp_verify_nonce( $nonce, 'adam_delete_action_' . $blog_id_to_delete ) ) { wp_die( esc_html__( 'Permission denied.', 'adamszokol-onion-service' ) ); } $onion_map = $this->get_adam_setting( 'map', [] ); if ( isset( $onion_map[ $blog_id_to_delete ] ) ) { unset( $onion_map[ $blog_id_to_delete ] ); $this->update_adam_setting( 'map', $onion_map ); $this->write_static_config_file(); $this->manage_sunrise(); // Regenerate sunrise.php after deleting add_settings_error( 'adam_messages', 'setting_deleted', __( 'Mapping deleted.', 'adamszokol-onion-service' ), 'updated' ); } wp_safe_redirect( wp_get_referer() ); exit; } public function ajax_search_sites() { check_ajax_referer( 'adam_search_nonce', 'nonce' ); $capability = is_multisite() ? 'manage_network_options' : 'manage_options'; if ( ! current_user_can( $capability ) ) wp_send_json_error( 'Permission denied.' ); $search_term = isset( $_POST['search'] ) ? sanitize_text_field( wp_unslash( $_POST['search'] ) ) : ''; $sites = []; if ( is_multisite() ) { $results = get_sites([ 'number' => 10, 'search' => $search_term, // Use built-in search ]); if ( empty($results) && !empty($search_term) ) { // Fallback to manual search if default search is empty $results = get_sites([ 'number' => 10, 'domain__like' => $search_term, ]); } foreach ( $results as $site ) { $site_details = get_site( $site->blog_id ); if ( $site_details ) { $sites[] = [ 'id' => $site_details->blog_id, 'name' => $site_details->blogname . " ({$site_details->domain}{$site_details->path})" ]; } } } else { if ( empty($search_term) || stristr( get_bloginfo( 'name' ), $search_term ) || stristr( get_bloginfo( 'url' ), $search_term ) ) { $sites[] = [ 'id' => 1, 'name' => get_bloginfo( 'name' ) ]; } } wp_send_json_success( $sites ); } public function send_onion_location_header() { if ( is_admin() || headers_sent() || ( defined('DOING_AJAX') && DOING_AJAX ) ) return; $http_host = isset( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : ''; if ( strpos( $http_host, '.onion' ) !== false ) return; $current_blog_id = get_current_blog_id(); $onion_map = $this->get_adam_setting( 'map', [] ); if ( isset( $onion_map[ $current_blog_id ] ) && ! $this->get_adam_setting( 'service_disabled', 0 ) ) { $onion_url = sanitize_text_field( $onion_map[ $current_blog_id ] ); if ( ! preg_match( '#^https?://#', $onion_url ) ) { $onion_url = 'http://' . $onion_url; } header( 'Onion-Location: ' . $onion_url ); } } private function init_filesystem() { global $wp_filesystem; if ( ! $wp_filesystem ) { if ( ! function_exists( 'WP_Filesystem' ) ) { require_once( ABSPATH . 'wp-admin/includes/file.php' ); } WP_Filesystem(); } return (bool) $wp_filesystem; } private function write_static_config_file( $is_activation_setup = false ) { if ( ! $this->init_filesystem() ) { add_settings_error( 'adam_messages', 'filesystem_error', __( 'Could not initialize filesystem to write config.', 'adamszokol-onion-service' ), 'error' ); return false; } global $wp_filesystem; $onion_map = []; $is_disabled = 0; $disabled_message = ''; if ( $is_activation_setup ) { $onion_map = []; $is_disabled = 0; $disabled_message = addslashes( 'This Onion Service is temporarily disabled.' ); } else { // On settings save, read from the database. $onion_map = $this->get_adam_setting( 'map', [] ); $is_disabled = (int) $this->get_adam_setting( 'service_disabled', 0 ); $disabled_message = addslashes( $this->get_adam_setting( 'disabled_message', 'This Onion Service is temporarily disabled.' ) ); } $domains_array_php = ''; foreach( array_flip( $onion_map ) as $onion_domain => $blog_id ) { $domains_array_php .= " '" . addslashes($onion_domain) . "' => " . (int)$blog_id . ",\n"; } $php_code = " [\n" . $domains_array_php . " ],\n"; $php_code .= " 'is_disabled' => $is_disabled,\n"; $php_code .= " 'disabled_message' => '$disabled_message',\n"; $php_code .= "];\n"; // Use directory path and create it if it doesn't exist if ( ! $wp_filesystem->is_dir( $this->config_dir_path ) ) { if ( ! $wp_filesystem->mkdir( $this->config_dir_path, FS_CHMOD_DIR ) ) { add_settings_error( 'adam_messages', 'mkdir_error', __( 'Could not create config directory.', 'adamszokol-onion-service' ), 'error' ); return false; } } if ( ! $wp_filesystem->put_contents( $this->config_file_path, $php_code, FS_CHMOD_FILE ) ) { add_settings_error( 'adam_messages', 'write_error', __( 'Could not write config file.', 'adamszokol-onion-service' ), 'error' ); return false; } return true; } private function manage_sunrise() { if ( ! $this->init_filesystem() ) { return; } global $wp_filesystem; $sunrise_defined = defined( 'SUNRISE' ) && SUNRISE; $config_file_string = "'" . addslashes( $this->config_file_path ) . "'"; $sunrise_content = ""; // Only write sunrise.php if it doesn't exist or content is different $current_sunrise_content = ''; if ( $wp_filesystem->exists( $this->sunrise_file_path ) ) { $current_sunrise_content = $wp_filesystem->get_contents( $this->sunrise_file_path ); } if ( $current_sunrise_content !== $sunrise_content ) { $wp_filesystem->put_contents( $this->sunrise_file_path, $sunrise_content, FS_CHMOD_FILE ); } // Now, check and define SUNRISE in wp-config.php if needed if ( ! $sunrise_defined && $wp_filesystem->is_writable( $this->wp_config_path ) ) { $config_content = $wp_filesystem->get_contents( $this->wp_config_path ); if ( $config_content !== false && strpos( $config_content, 'SUNRISE' ) === false ) { $insert_line = "define( 'SUNRISE', true );"; $stop_editing_line = "/* That's all, stop editing! Happy publishing. */"; if ( strpos( $config_content, $stop_editing_line ) !== false ) { $config_content = str_replace( $stop_editing_line, $insert_line . "\n\n" . $stop_editing_line, $config_content ); } else { // Fallback: insert after put_contents( $this->wp_config_path, $config_content, FS_CHMOD_FILE ); } } } private function show_config_warnings() { if ( ! $this->init_filesystem() ) { echo '

' . esc_html__( 'Filesystem Error:', 'adamszokol-onion-service' ) . ' ' . esc_html__( 'The plugin could not initialize the WordPress Filesystem. Please ensure your file permissions are set correctly or define FS_METHOD in wp-config.php.', 'adamszokol-onion-service' ) . '

'; return; } global $wp_filesystem; if ( ! defined( 'SUNRISE' ) || ! SUNRISE ) { // translators: %s is a line of code. $message = sprintf( __( 'The SUNRISE constant is not defined or is false in your wp-config.php file. The plugin attempted to add it, but may have failed. Please add the following line to your wp-config.php manually if the site is still not working: %s', 'adamszokol-onion-service' ), '
define( \'SUNRISE\', true );' ); echo '

' . esc_html__( 'Action Required:', 'adamszokol-onion-service' ) . ' ' . wp_kses_post( $message ) . '

'; } if ( ! $wp_filesystem->exists( $this->sunrise_file_path ) ) { echo '

' . esc_html__( 'Warning:', 'adamszokol-onion-service' ) . ' ' . esc_html__( 'The sunrise.php file is missing.', 'adamszokol-onion-service' ) . '

'; } $config_dir = $this->config_dir_path; if ( ! $wp_filesystem->is_dir( $config_dir ) && ! $wp_filesystem->is_writable( dirname( $config_dir ) ) ) { // translators: %s is a directory path. $message = sprintf( __( 'The parent directory (%s) is not writable. The plugin cannot create its configuration directory.', 'adamszokol-onion-service' ), esc_html( dirname( $config_dir ) ) ); echo '

' . esc_html__( 'Warning:', 'adamszokol-onion-service' ) . ' ' . wp_kses_post( $message ) . '

'; } elseif ( $wp_filesystem->is_dir( $config_dir ) && ! $wp_filesystem->is_writable( $config_dir ) ) { // translators: %s is a directory path. $message = sprintf( __( 'The config directory (%s) is not writable. The plugin cannot save its configuration file.', 'adamszokol-onion-service' ), esc_html( $config_dir ) ); echo '

' . esc_html__( 'Warning:', 'adamszokol-onion-service' ) . ' ' . wp_kses_post( $message ) . '

'; } if ( ! $wp_filesystem->exists( $this->config_file_path ) ) { echo '

' . esc_html__( 'Note:', 'adamszokol-onion-service' ) . ' ' . esc_html__( 'The configuration file does not exist yet. Please save your settings to create it.', 'adamszokol-onion-service' ) . '

'; } } } // Get the plugin instance. Adam_Onion_Service_Plugin::instance();