From 59513582a281ff95311630cefc8b7020a5084a51 Mon Sep 17 00:00:00 2001 From: David Stone Date: Wed, 15 Apr 2026 13:19:38 -0600 Subject: [PATCH] fix: close remaining network activation reliability gaps PR #852 fixed _install_network_activate() to write directly to sitemeta, but two other code paths still allowed the wizard to report success without actually network-activating the plugin: 1. ajax_network_activate() on the requirements page still called activate_plugin() which silently falls back to single-site activation when OPcache serves a stale wp-config.php. Now delegates to Multisite_Network_Installer::_install_network_activate() for the same direct sitemeta write. 2. Multisite_Setup_Admin_Page::setup_install() was missing an explicit exit after wp_send_json_error(), which could allow the response to be corrupted if headers were already partially sent. 3. Base_Installer::handle() never checked whether COMMIT succeeded. A silently-failed COMMIT would roll back the sitemeta write on connection close, but handle() still returned success. Now checks the COMMIT return value and returns WP_Error on failure. Refs #837 --- .../class-multisite-setup-admin-page.php | 13 +++++++++++++ .../class-setup-wizard-admin-page.php | 19 +++++++++---------- inc/installers/class-base-installer.php | 9 ++++++++- 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/inc/admin-pages/class-multisite-setup-admin-page.php b/inc/admin-pages/class-multisite-setup-admin-page.php index 10d994462..6a6d618eb 100644 --- a/inc/admin-pages/class-multisite-setup-admin-page.php +++ b/inc/admin-pages/class-multisite-setup-admin-page.php @@ -484,6 +484,17 @@ public function setup_install(): void { $installer = wu_request('installer', ''); $multisite_network_installer = Multisite_Network_Installer::get_instance(); $steps = $multisite_network_installer->get_steps(); + + /* + * Only handle installers that belong to the multisite network installer. + * Unknown step names fall through to the next wp_ajax handler (priority 10) + * which handles Core_Installer, Default_Content_Installer, etc. + * + * IMPORTANT: this must use `return` (not exit) so the Setup_Wizard_Admin_Page + * handler can process its own steps. But we must NOT let a known multisite + * step silently pass through — if handle() returns a non-WP_Error the step + * genuinely succeeded; only unknown steps should fall through. + */ if ( ! isset($steps[ $installer ])) { return; } @@ -492,6 +503,8 @@ public function setup_install(): void { if (is_wp_error($status)) { wp_send_json_error($status); + + exit; } wp_send_json_success(); diff --git a/inc/admin-pages/class-setup-wizard-admin-page.php b/inc/admin-pages/class-setup-wizard-admin-page.php index f17eaa16b..661f62454 100644 --- a/inc/admin-pages/class-setup-wizard-admin-page.php +++ b/inc/admin-pages/class-setup-wizard-admin-page.php @@ -153,8 +153,11 @@ public function __construct() { /** * Handles the AJAX request to network-activate Ultimate Multisite. * - * Attempts to network-activate the plugin, returning a JSON response. - * On success the caller should reload the page so the checks refresh. + * Writes directly to the sitemeta table instead of using activate_plugin() + * because is_multisite() may return false when OPcache serves a stale + * wp-config.php. activate_plugin() silently falls back to single-site + * activation in that case. The direct sitemeta write guarantees reliable + * network activation regardless of bytecode caching state. * * @since 2.3.0 * @return void @@ -169,14 +172,10 @@ public function ajax_network_activate(): void { exit; } - if ( ! function_exists('activate_plugin')) { - require_once ABSPATH . 'wp-admin/includes/plugin.php'; - } - - $result = activate_plugin(WP_ULTIMO_PLUGIN_BASENAME, '', true); - - if (is_wp_error($result)) { - wp_send_json_error($result); + try { + Multisite_Network_Installer::get_instance()->_install_network_activate(); + } catch (\Throwable $e) { + wp_send_json_error(new \WP_Error('activation-failed', $e->getMessage())); exit; } diff --git a/inc/installers/class-base-installer.php b/inc/installers/class-base-installer.php index fa578b402..84013793b 100644 --- a/inc/installers/class-base-installer.php +++ b/inc/installers/class-base-installer.php @@ -99,7 +99,14 @@ public function handle($status, $installer, $wizard) { // phpcs:ignore Generic.C return new \WP_Error($installer, $e->getMessage()); } - $wpdb->query('COMMIT'); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching + $committed = $wpdb->query('COMMIT'); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching + + if (false === $committed) { + $error_msg = $wpdb->last_error ?: __('Transaction commit failed.', 'ultimate-multisite'); + wu_log_add(\WP_Ultimo::LOG_HANDLE, "Installer '{$installer}' commit failed: {$error_msg}", LogLevel::ERROR); + + return new \WP_Error($installer, $error_msg); + } return $status; }