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.' );
?>
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();