Skip to content

Multisite Support

mab056 edited this page Feb 15, 2026 · 2 revisions

Multisite Support

Overview

The plugin supports both single-site and multisite WordPress installations. The main difference is in the uninstall phase: on a multisite network, data cleanup is performed for every blog in the network.

Cron, options, and transients are per-blog in WordPress architecture, so activation and deactivation do not require multisite-specific logic.

What Changes in Multisite

Phase Single-site Multisite
Activation Activator::activate() Identical (cron and options are per-blog)
Deactivation Activator::deactivate() Identical
Uninstall Uninstaller::uninstall_single() Uninstaller::uninstall_network() → iterates all blogs

Network Uninstall

src/Core/Uninstaller.php

The uninstall() method automatically detects the installation type:

public function uninstall(): void {
    if ( is_multisite() ) {
        $this->uninstall_network();
    } else {
        $this->uninstall_single();
    }
}

uninstall_network() iterates over all blogs in the network:

private function uninstall_network(): void {
    $blog_ids = get_sites( [ 'fields' => 'ids' ] );

    foreach ( $blog_ids as $blog_id ) {
        switch_to_blog( $blog_id );
        $this->uninstall_single();
        restore_current_blog();
    }
}

For each blog, the same cleanup as single-site is performed:

  • Delete plugin options (ops_health_activated_at, ops_health_version, ops_health_latest_results, ops_health_alert_settings, ops_health_alert_log)
  • Remove cron hook (ops_health_run_checks)
  • Delete fixed transients (ops_health_cron_check, ops_health_admin_notice, ops_health_alert_notice)
  • Delete dynamic cooldown transients via $wpdb LIKE query (_transient_ops_health_alert_cooldown_%)

uninstall.php Fallback

uninstall.php

When the Composer autoloader is unavailable (e.g., after a corrupted update), uninstall.php has an inline fallback with three branches:

1. vendor/autoload.php exists → uses Uninstaller class (handles multisite internally)
2. elseif is_multisite()     → inline fallback with blog iteration
3. else                       → inline single-site fallback

The multisite fallback replicates the same pattern as Uninstaller::uninstall_network():

$ops_health_blog_ids = get_sites( [ 'fields' => 'ids' ] );
foreach ( $ops_health_blog_ids as $ops_health_blog_id ) {
    switch_to_blog( $ops_health_blog_id );
    ops_health_uninstall_single_site( $wpdb );
    restore_current_blog();
}

Note: variables in the fallback use the $ops_health_ prefix to comply with the WPCS PrefixAllGlobals rule.

Multisite Testing

Infrastructure

tests/bootstrap.php supports the WP_TESTS_MULTISITE environment variable:

if ( getenv( 'WP_TESTS_MULTISITE' ) ) {
    define( 'WP_TESTS_MULTISITE', true );
}

When active, the WordPress Test Suite loads the environment as a multisite network.

Commands

Command Description
composer test:integration:multisite Integration tests in multisite mode
composer test:coverage:multisite Multisite integration coverage (clover XML)
composer test:coverage 3 suites: unit + integration + multisite

Dedicated Tests

tests/Integration/Core/UninstallerTest.php contains 3 multisite tests with guard:

if ( ! is_multisite() ) {
    $this->markTestSkipped( 'Requires multisite.' );
}

The multisite tests verify:

  • uninstall_network() iterates over all blogs
  • Options and transients are removed for each blog
  • restore_current_blog() restores the original context

Combined Coverage

The is_multisite() branch in uninstall() creates a condition that can only be 100% covered by combining:

  • Integration single-site: covers the else branch (non-multisite)
  • Integration multisite: covers the if branch (multisite)

The clover XML reports are separate:

  • coverage/clover-integration.xml (single-site)
  • coverage/clover-multisite.xml (multisite)

Codecov automatically merges the reports thanks to the carryforward: true configuration.

Clone this wiki locally