From 6eb2053293c53c1b4e0e02501c6336448ae140ac Mon Sep 17 00:00:00 2001 From: David Stone Date: Mon, 15 Sep 2025 09:34:00 -0600 Subject: [PATCH 1/2] WiP. Add page to enable multisite. --- inc/admin-pages/class-base-admin-page.php | 2 +- .../class-multisite-setup-admin-page.php | 626 ++++++++++++++++++ .../class-setup-wizard-admin-page.php | 10 +- .../class-default-content-installer.php | 5 +- 4 files changed, 639 insertions(+), 4 deletions(-) create mode 100644 inc/admin-pages/class-multisite-setup-admin-page.php diff --git a/inc/admin-pages/class-base-admin-page.php b/inc/admin-pages/class-base-admin-page.php index 5cb9f17e..4109af08 100644 --- a/inc/admin-pages/class-base-admin-page.php +++ b/inc/admin-pages/class-base-admin-page.php @@ -216,7 +216,7 @@ public function get_id() { } /** - * Returns the appropriate capability for a this page, depending on the context. + * Returns the appropriate capability for this page, depending on the context. * * @since 2.0.0 * @return string diff --git a/inc/admin-pages/class-multisite-setup-admin-page.php b/inc/admin-pages/class-multisite-setup-admin-page.php new file mode 100644 index 00000000..dd6ef9a6 --- /dev/null +++ b/inc/admin-pages/class-multisite-setup-admin-page.php @@ -0,0 +1,626 @@ + 'capability_here' + * To add a page to the network admin (wp-admin/network), use: 'network_admin_menu' => 'capability_here' + * To add a page to the user (wp-admin/user) admin, use: 'user_admin_menu' => 'capability_here' + * + * @since 2.0.0 + * @var array + */ + protected $supported_panels = [ + 'admin_menu' => 'manage_options', + ]; + + /** + * Constructor method. + * + * @since 2.0.0 + * @return void + */ + public function __construct() { + + $this->type = 'menu'; + $this->position = 10_101_010; + $this->menu_icon = 'dashicons-wu-wp-ultimo'; + + parent::__construct(); + + add_action('admin_enqueue_scripts', [$this, 'register_scripts']); + } + + /** + * Returns the title of the page. + * + * @since 2.0.0 + * @return string Title of the page. + */ + public function get_title(): string { + return __('Enable WordPress Multisite', 'multisite-ultimate'); + } + + /** + * Returns the title of menu for this page. + * + * @since 2.0.0 + * @return string Menu label of the page. + */ + public function get_menu_title() { + return __('Multisite Ultimate', 'multisite-ultimate'); + } + + /** + * Returns the logo for the wizard. + * + * @since 2.0.0 + * @return string + */ + public function get_logo() { + return wu_get_asset('logo.webp', 'img'); + } + + /** + * Returns the sections for this Wizard. + * + * @since 2.0.0 + * @return array + */ + public function get_sections() { + + return [ + 'welcome' => [ + 'title' => __('Multisite Required', 'multisite-ultimate'), + 'description' => implode( + '

', + [ + __('WordPress Multisite is required for Multisite Ultimate to function properly.', 'multisite-ultimate'), + __('This wizard will guide you through enabling WordPress Multisite and configuring your network.', 'multisite-ultimate'), + __('We recommend creating a backup of your files and database before proceeding.', 'multisite-ultimate'), + ] + ), + 'next_label' => __('Get Started →', 'multisite-ultimate'), + 'back' => false, + ], + 'configure' => [ + 'title' => __('Network Configuration', 'multisite-ultimate'), + 'description' => __('Configure your network settings. These settings determine how your sites will be structured.', 'multisite-ultimate'), + 'next_label' => __('Create Network', 'multisite-ultimate'), + 'handler' => [$this, 'handle_configure'], + 'fields' => [$this, 'get_network_configuration_fields'], + ], + 'complete' => [ + 'title' => __('Setup Complete', 'multisite-ultimate'), + 'description' => __('WordPress Multisite setup is now complete!', 'multisite-ultimate'), + 'view' => [$this, 'section_complete'], + 'back' => false, + 'next' => false, + ], + ]; + } + + /** + * Welcome section view. + * + * @since 2.0.0 + * @return void + */ + public function section_welcome(): void { + + ?> +
+

+ +

+ +
+ +
+

+ +

+

+ +

+
    +
  1. +
  2. +
  3. +
  4. +
+
+ +
+
+
+ +
+
+

+ +

+

+ +

+
+
+
+ render_submit_box(); + } + + /** + * Returns the network configuration fields. + * + * @since 2.0.0 + * @return array + */ + public function get_network_configuration_fields() { + + $home_url = get_option('home'); + $base_domain = parse_url($home_url, PHP_URL_HOST); + $user = wp_get_current_user(); + + return [ + 'network_structure_header' => [ + 'type' => 'header', + 'title' => __('Network Structure', 'multisite-ultimate'), + 'desc' => __('Choose how you want your network sites to be organized:', 'multisite-ultimate'), + ], + 'subdomain_install' => [ + 'type' => 'radio', + 'title' => __('Site Structure', 'multisite-ultimate'), + 'desc' => __('Choose between subdirectories or subdomains for your network sites.', 'multisite-ultimate'), + 'options' => [ + 'sub0' => sprintf(__('Sites will use sub-directories like %s (Recommended)', 'multisite-ultimate'), '' . esc_html($base_domain) . '/site1'), + + 'sub1' => sprintf(__('Sites will use sub-domains like %s (Requires wildcard DNS)', 'multisite-ultimate'), 'site1.' . esc_html($base_domain) . ''), + + ], + 'default' => '0', + ], + 'network_details_header' => [ + 'type' => 'header', + 'title' => __('Network Details', 'multisite-ultimate'), + ], + 'sitename' => [ + 'type' => 'text', + 'title' => __('Network Title', 'multisite-ultimate'), + 'desc' => __('This will be the title of your network.', 'multisite-ultimate'), + 'placeholder' => __('Enter network title', 'multisite-ultimate'), + 'default' => get_option('blogname'), + ], + 'email' => [ + 'type' => 'email', + 'title' => __('Network Admin Email', 'multisite-ultimate'), + 'desc' => __('This email address will be used for network administration.', 'multisite-ultimate'), + 'placeholder' => __('Enter admin email', 'multisite-ultimate'), + 'default' => $user->user_email, + ], + 'backup_warning' => [ + 'type' => 'note', + 'desc' => '
+
+
+ +
+
+

' . __('Before You Continue', 'multisite-ultimate') . '

+

' . __('Please ensure you have a recent backup of your website files and database. The multisite setup process will modify your wp-config.php file and create new database tables.', 'multisite-ultimate') . '

+
+
+
', + ], + ]; + } + + /** + * Handles the network configuration form submission. + * + * @since 2.0.0 + * @return void + */ + public function handle_configure(): void { + + if (! current_user_can('manage_options')) { + wp_die(__('Permission denied.', 'multisite-ultimate')); + } + + $subdomain_install = (bool) wu_request('subdomain_install', 0); + $sitename = sanitize_text_field(wu_request('sitename', '')); + $email = sanitize_email(wu_request('email', '')); + + // Store values in transients for completion page + set_transient('wu_multisite_subdomain_install', $subdomain_install, 300); + set_transient('wu_multisite_sitename', $sitename, 300); + set_transient('wu_multisite_email', $email, 300); + + // Try to enable multisite + $wp_config_modified = $this->modify_wp_config(); + $network_created = false; + + if ($wp_config_modified) { + // Create the network + $network_created = $this->create_network($subdomain_install, $sitename, $email); + } + + // Store results + set_transient('wu_multisite_wp_config_modified', $wp_config_modified, 300); + set_transient('wu_multisite_network_created', $network_created, 300); + + // Redirect to completion step + wp_safe_redirect($this->get_next_section_link()); + exit; + } + + /** + * Completion section view. + * + * @since 2.0.0 + * @return void + */ + public function section_complete(): void { + + $wp_config_modified = get_transient('wu_multisite_wp_config_modified'); + $network_created = get_transient('wu_multisite_network_created'); + + if ($network_created && $wp_config_modified) : + ?> +
+
+
+ +
+
+

+ +

+

+ +

+
+
+
+ +
+ + + + +
+ display_manual_instructions(); + endif; + + // Clean up transients + delete_transient('wu_multisite_wp_config_modified'); + delete_transient('wu_multisite_network_created'); + delete_transient('wu_multisite_subdomain_install'); + delete_transient('wu_multisite_sitename'); + delete_transient('wu_multisite_email'); + } + + /** + * Display manual configuration instructions. + * + * @since 2.0.0 + * @return void + */ + protected function display_manual_instructions(): void { + + $home_url = get_option('home'); + $base_domain = parse_url($home_url, PHP_URL_HOST); + $subdomain_install = get_transient('wu_multisite_subdomain_install'); + + $wp_config_constants = "define( 'WP_ALLOW_MULTISITE', true ); +define( 'MULTISITE', true ); +define( 'SUBDOMAIN_INSTALL', " . ($subdomain_install ? 'true' : 'false') . " ); +define( 'DOMAIN_CURRENT_SITE', '{$base_domain}' ); +define( 'PATH_CURRENT_SITE', '/' ); +define( 'SITE_ID_CURRENT_SITE', 1 ); +define( 'BLOG_ID_CURRENT_SITE', 1 );"; + + $htaccess_rules = 'RewriteEngine On +RewriteRule ^index\.php$ - [L] + +# add a trailing slash to /wp-admin +RewriteRule ^([_0-9a-zA-Z-]+/)?wp-admin$ $1wp-admin/ [R=301,L] + +RewriteCond %{REQUEST_FILENAME} -f [OR] +RewriteCond %{REQUEST_FILENAME} -d +RewriteRule ^ - [L] +RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) $2 [L] +RewriteRule ^([_0-9a-zA-Z-]+/)?(.*\.php)$ $2 [L] +RewriteRule . index.php [L]'; + + ?> +
+

+ +

+
+ +
+

+ +

+

+ wp-config.php', + '/* That\'s all, stop editing! Happy publishing. */' + ); + ?> +

+
+
+
+
+ +
+

+ +

+

+ +

+
+
+
+
+ +
+
+
+ +
+
+

+ +

+

+ +

+
+
+
+ +
+ + + + +
+ add_final_multisite_constants($subdomain_install, $domain); + + return true; + } catch (Exception $e) { + return false; + } + } + + /** + * Adds the final multisite constants to wp-config.php. + * + * @since 2.0.0 + * @param bool $subdomain_install Whether subdomains are used. + * @param string $domain The main domain. + * @return bool Whether the modification was successful. + */ + protected function add_final_multisite_constants(bool $subdomain_install, string $domain): bool { + + $wp_config_path = ABSPATH . 'wp-config.php'; + + if (! file_exists($wp_config_path) || ! is_writable($wp_config_path)) { + return false; + } + + $config_content = file_get_contents($wp_config_path); + + if ($config_content === false) { + return false; + } + + // Check if MULTISITE is already defined + if (strpos($config_content, 'MULTISITE') !== false) { + return true; // Already configured + } + + $constants_to_add = "\n// Multisite Ultimate: Multisite Configuration\n"; + $constants_to_add .= "define( 'MULTISITE', true );\n"; + $constants_to_add .= "define( 'SUBDOMAIN_INSTALL', " . ($subdomain_install ? 'true' : 'false') . " );\n"; + $constants_to_add .= "define( 'DOMAIN_CURRENT_SITE', '{$domain}' );\n"; + $constants_to_add .= "define( 'PATH_CURRENT_SITE', '/' );\n"; + $constants_to_add .= "define( 'SITE_ID_CURRENT_SITE', 1 );\n"; + $constants_to_add .= "define( 'BLOG_ID_CURRENT_SITE', 1 );\n\n"; + + // Find the location to insert the constants (after WP_ALLOW_MULTISITE) + $search = "define( 'WP_ALLOW_MULTISITE', true );"; + $insert_position = strpos($config_content, $search); + + if ($insert_position !== false) { + $insert_position += strlen($search); + $new_content = substr_replace($config_content, $constants_to_add, $insert_position, 0); + return file_put_contents($wp_config_path, $new_content) !== false; + } + + return false; + } + + /** + * Register page scripts and styles. + * + * @since 2.0.0 + * @return void + */ + public function register_scripts(): void { + + if (get_current_screen()->id !== 'toplevel_page_wp-ultimo-multisite-setup') { + return; + } + + wp_add_inline_script( + 'wp-admin', + ' + // Copy to clipboard functionality + document.addEventListener("DOMContentLoaded", function() { + document.querySelectorAll("button[onclick*=\'navigator.clipboard.writeText\']").forEach(function(button) { + button.addEventListener("click", function() { + var textarea = this.nextElementSibling; + if (textarea && textarea.tagName === "TEXTAREA") { + navigator.clipboard.writeText(textarea.value).then(function() { + button.textContent = "Copied!"; + setTimeout(function() { + button.textContent = "Copy to clipboard"; + }, 2000); + }); + } + }); + }); + }); + ' + ); + } +} \ No newline at end of file diff --git a/inc/admin-pages/class-setup-wizard-admin-page.php b/inc/admin-pages/class-setup-wizard-admin-page.php index 8c9906e0..685f3ad3 100644 --- a/inc/admin-pages/class-setup-wizard-admin-page.php +++ b/inc/admin-pages/class-setup-wizard-admin-page.php @@ -221,7 +221,15 @@ public function set_settings(): void { */ public function redirect_to_wizard(): void { - if ( ! Requirements::run_setup() && wu_request('page') !== 'wp-ultimo-setup') { + // If multisite is not enabled, redirect to multisite setup page + if ( ! is_multisite() && wu_request('page') !== 'wp-ultimo-multisite-setup') { + wp_safe_redirect(admin_url('admin.php?page=wp-ultimo-multisite-setup')); + + exit; + } + + // If multisite is enabled but setup is not finished, redirect to setup wizard + if ( is_multisite() && ! Requirements::run_setup() && wu_request('page') !== 'wp-ultimo-setup') { wp_safe_redirect(wu_network_admin_url('wp-ultimo-setup')); exit; diff --git a/inc/installers/class-default-content-installer.php b/inc/installers/class-default-content-installer.php index fc278113..cc0b1030 100644 --- a/inc/installers/class-default-content-installer.php +++ b/inc/installers/class-default-content-installer.php @@ -52,8 +52,9 @@ public function init(): void { */ protected function done_creating_template_site() { - $current_site = get_current_site(); - + if (! is_multisite()) { + return false; + } $d = wu_get_site_domain_and_path('template'); return domain_exists($d->domain, $d->path, get_current_network_id()); From f844d95c59932b8b91ef7e3d6a407dd17214c886 Mon Sep 17 00:00:00 2001 From: David Stone Date: Mon, 15 Sep 2025 09:34:21 -0600 Subject: [PATCH 2/2] WiP. Add page to enable multisite. --- inc/class-wp-ultimo.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/inc/class-wp-ultimo.php b/inc/class-wp-ultimo.php index e9b68296..ac2ac33a 100644 --- a/inc/class-wp-ultimo.php +++ b/inc/class-wp-ultimo.php @@ -140,6 +140,11 @@ public function init(): void { */ new WP_Ultimo\Admin_Pages\Setup_Wizard_Admin_Page(); + /* + * Multisite Setup for non-multisite installations + */ + new WP_Ultimo\Admin_Pages\Multisite_Setup_Admin_Page(); + /* * Loads the Multisite Ultimate settings helper class. */