Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
1a7fd67
wip: optimize front-end My Sites toolbar
superdav42 Aug 10, 2026
7f697cb
fix: preserve My Sites toolbar filter
superdav42 Aug 10, 2026
1630f71
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Aug 10, 2026
4dc8761
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Aug 10, 2026
e9165dd
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Aug 11, 2026
c2689a4
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Aug 16, 2026
754d55c
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Aug 20, 2026
1291c63
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Aug 20, 2026
6ccb3bb
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Aug 20, 2026
54b54b4
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Aug 20, 2026
ddb23f4
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Aug 20, 2026
310cc68
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Aug 22, 2026
b9d86c5
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Aug 23, 2026
058555a
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Aug 25, 2026
78ff860
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Aug 28, 2026
1391537
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Aug 30, 2026
911fba8
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Sep 1, 2026
a3c5b7d
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Sep 1, 2026
95b7388
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Sep 2, 2026
731d766
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Sep 2, 2026
3220d19
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Sep 2, 2026
845d663
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Sep 2, 2026
d171387
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Sep 2, 2026
94b46e5
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Sep 2, 2026
7358d31
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Sep 2, 2026
549bf86
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Sep 2, 2026
cf98370
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Sep 2, 2026
39afcca
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Sep 2, 2026
2dcd12c
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Sep 2, 2026
de691ab
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Sep 2, 2026
8f68ee6
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Sep 3, 2026
fc3adb2
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Sep 3, 2026
a58e778
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Sep 3, 2026
45ac22b
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Sep 3, 2026
06e33b3
Merge branch 'main' into feature/auto-20260810-142725-gh1716
superdav42 Sep 3, 2026
3fe5387
fix: scope My Sites toolbar optimization
superdav42 Sep 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions inc/class-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -1427,6 +1427,17 @@ public function default_sections(): void {
]
);

$this->add_field(
'sites',
'optimize_frontend_my_sites_toolbar',
[
'title' => __('Optimize the front-end My Sites toolbar', 'ultimate-multisite'),
'desc' => __('Show only the current site in the front-end toolbar to reduce processing on large networks. The complete site list remains available from the My Sites administration page.', 'ultimate-multisite'),
'type' => 'toggle',
'default' => 0,
]
);

$this->add_field(
'sites',
'wordpress_features_heading',
Expand Down Expand Up @@ -2195,6 +2206,7 @@ public static function get_setting_defaults(): array {
// Sites
'enable_visits_limiting' => 1,
'enable_screenshot_generator' => 1,
'optimize_frontend_my_sites_toolbar' => 0,
'menu_items_plugin' => 1,
'add_new_users' => 1,
'allow_template_switching' => 1,
Expand Down
88 changes: 88 additions & 0 deletions inc/managers/class-site-manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -1159,6 +1159,12 @@ public function hide_customer_sites_from_super_admin_list($sites, $user_id, $all

global $wpdb;

if ($this->is_frontend_my_sites_toolbar_request()) {
$sites = $this->get_current_site_for_frontend_my_sites_toolbar($user_id);

return apply_filters('get_blogs_of_user', $sites, $user_id, $all); // phpcs:ignore
}

if ( ! is_super_admin()) {
return $sites;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Expand Down Expand Up @@ -1271,6 +1277,88 @@ public function hide_customer_sites_from_super_admin_list($sites, $user_id, $all
return apply_filters('get_blogs_of_user', $sites, $user_id, $all); // phpcs:ignore
}

/**
* Determines whether get_blogs_of_user is preparing the front-end My Sites toolbar.
*
* The pre_get_blogs_of_user filter is also used by wp-admin, REST requests, and
* application code. Checking that WP_Admin_Bar::initialize() directly calls
* get_blogs_of_user() keeps the optimization away from nested calls such as
* get_active_blog_for_user(), which may update the user's primary site.
*
* @since 2.15.0
* @param array|null $backtrace Call stack override used by tests.
* @return bool
*/
protected function is_frontend_my_sites_toolbar_request($backtrace = null) {

if (
! wu_get_setting('optimize_frontend_my_sites_toolbar', false)
|| is_admin()
|| wp_doing_ajax()
|| wp_doing_cron()
|| (defined('REST_REQUEST') && REST_REQUEST)
|| (defined('WP_CLI') && WP_CLI)
|| ! is_user_logged_in()
|| ! is_admin_bar_showing()
) {
return false;
}

$backtrace = $backtrace ?? debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 12); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace

foreach ($backtrace as $index => $call) {
if ('get_blogs_of_user' !== ($call['function'] ?? '')) {
continue;
}

$caller = $backtrace[ $index + 1 ] ?? [];
$caller_class = $caller['class'] ?? '';

return 'initialize' === ($caller['function'] ?? '')
&& $caller_class
&& (
\WP_Admin_Bar::class === $caller_class
|| is_subclass_of($caller_class, \WP_Admin_Bar::class, true)
);
}

return false;
}

/**
* Returns the current site in the format expected by the My Sites toolbar.
*
* @since 2.15.0
* @param int $user_id User ID whose toolbar is being prepared.
* @return object[]
*/
protected function get_current_site_for_frontend_my_sites_toolbar($user_id) {

$site = get_site(get_current_blog_id());

if (
! $site
|| (! is_super_admin($user_id) && ! is_user_member_of_blog($user_id, $site->id))
) {
return [];
}

return [
$site->id => (object) [
'userblog_id' => $site->id,
'blogname' => $site->blogname,
'domain' => $site->domain,
'path' => $site->path,
'site_id' => $site->network_id,
'siteurl' => $site->siteurl,
'archived' => $site->archived,
'mature' => $site->mature,
'spam' => $site->spam,
'deleted' => $site->deleted,
],
];
}

/**
* Delete pending sites from non-pending memberships
*
Expand Down
208 changes: 208 additions & 0 deletions tests/WP_Ultimo/Managers/Site_Manager_Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,19 @@

use WP_Ultimo\Managers\Site_Manager;

// phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound
class Frontend_My_Sites_Toolbar_Test_Manager extends Site_Manager {

protected function is_frontend_my_sites_toolbar_request($backtrace = null) {

unset($backtrace);

return true;
}
}
// phpcs:enable Generic.Files.OneObjectStructurePerFile.MultipleFound

// phpcs:ignore Generic.Files.OneObjectStructurePerFile.MultipleFound
class Site_Manager_Test extends \WP_UnitTestCase {

use Manager_Test_Trait;
Expand Down Expand Up @@ -511,7 +524,7 @@

wu_save_setting('search_and_replace', []);

add_filter('wu_search_and_replace_on_duplication', function ($settings, $from, $to) {

Check warning on line 527 in tests/WP_Ultimo/Managers/Site_Manager_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

The method parameter $to is never used

Check warning on line 527 in tests/WP_Ultimo/Managers/Site_Manager_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

The method parameter $from is never used
$settings['filter-key'] = 'filter-value';
return $settings;
}, 10, 3);
Expand Down Expand Up @@ -2943,7 +2956,7 @@

$filter_applied = false;

add_filter('get_blogs_of_user', function ($sites, $uid, $all) use (&$filter_applied) {

Check warning on line 2959 in tests/WP_Ultimo/Managers/Site_Manager_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

The method parameter $all is never used

Check warning on line 2959 in tests/WP_Ultimo/Managers/Site_Manager_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

The method parameter $uid is never used
$filter_applied = true;
return $sites;
}, 10, 3);
Expand All @@ -2958,6 +2971,201 @@
wp_set_current_user(0);
}

/**
* Test the front-end My Sites toolbar optimization applies get_blogs_of_user filter.
*/
public function test_frontend_my_sites_toolbar_optimization_applies_get_blogs_of_user_filter(): void {

$reflection = new \ReflectionClass(Frontend_My_Sites_Toolbar_Test_Manager::class);
$manager = $reflection->newInstanceWithoutConstructor();
$user_id = $this->factory()->user->create(['role' => 'administrator']);
$filter = function ($sites, $filter_user_id, $all) {

unset($all);
$sites['filtered'] = (object) ['userblog_id' => $filter_user_id];

return $sites;
};

grant_super_admin($user_id);
wp_set_current_user($user_id);
add_filter('get_blogs_of_user', $filter, 10, 3);

try {
$sites = $manager->hide_customer_sites_from_super_admin_list([], $user_id, false);

$this->assertArrayHasKey('filtered', $sites);
$this->assertSame($user_id, $sites['filtered']->userblog_id);
} finally {
remove_filter('get_blogs_of_user', $filter, 10);
revoke_super_admin($user_id);
wp_set_current_user(0);
}
}

/**
* Test the front-end My Sites toolbar optimization applies to non-super-admins.
*/
public function test_frontend_my_sites_toolbar_optimization_applies_to_non_super_admin(): void {

$reflection = new \ReflectionClass(Frontend_My_Sites_Toolbar_Test_Manager::class);
$manager = $reflection->newInstanceWithoutConstructor();
$user_id = $this->factory()->user->create(['role' => 'administrator']);

wp_set_current_user($user_id);

try {
$sites = $manager->hide_customer_sites_from_super_admin_list([], $user_id, false);

$this->assertFalse(is_super_admin($user_id));
$this->assertCount(1, $sites);
$this->assertArrayHasKey(get_current_blog_id(), $sites);
} finally {
wp_set_current_user(0);
}
}

/**
* Test front-end My Sites toolbar optimization requires the admin bar call stack.
*/
public function test_frontend_my_sites_toolbar_optimization_requires_admin_bar_initialization(): void {

$manager = $this->get_manager_instance();
$user_id = $this->factory()->user->create(['role' => 'administrator']);
$method = new \ReflectionMethod(Site_Manager::class, 'is_frontend_my_sites_toolbar_request');
$filter = function ($value, $setting) {
return 'optimize_frontend_my_sites_toolbar' === $setting ? 1 : $value;
};

wp_set_current_user($user_id);
add_filter('wu_get_setting', $filter, 10, 2);
add_filter('show_admin_bar', '__return_true');

try {
$this->assertTrue(
$method->invoke($manager, [
[
'function' => 'get_blogs_of_user',
],
[
'class' => 'WP_Admin_Bar',
'function' => 'initialize',
],
])
);
$this->assertFalse(
$method->invoke($manager, [
[
'function' => 'get_blogs_of_user',
],
[
'function' => 'get_active_blog_for_user',
],
[
'class' => 'WP_Admin_Bar',
'function' => 'initialize',
],
])
);
$this->assertFalse($method->invoke($manager, []));
} finally {
remove_filter('wu_get_setting', $filter, 10);
remove_filter('show_admin_bar', '__return_true');
wp_set_current_user(0);
}
}

/**
* Test front-end My Sites toolbar optimization supports admin bar subclasses.
*/
public function test_frontend_my_sites_toolbar_optimization_supports_admin_bar_subclasses(): void {

if ( ! class_exists('WP_Admin_Bar')) {
require_once ABSPATH . WPINC . '/class-wp-admin-bar.php';
}

$manager = $this->get_manager_instance();
$user_id = $this->factory()->user->create(['role' => 'administrator']);
$method = new \ReflectionMethod(Site_Manager::class, 'is_frontend_my_sites_toolbar_request');
$admin_bar = new class() extends \WP_Admin_Bar {};
$filter = function ($value, $setting) {
return 'optimize_frontend_my_sites_toolbar' === $setting ? 1 : $value;
};

wp_set_current_user($user_id);
add_filter('wu_get_setting', $filter, 10, 2);
add_filter('show_admin_bar', '__return_true');

try {
$this->assertTrue(
$method->invoke($manager, [
[
'function' => 'get_blogs_of_user',
],
[
'class' => get_class($admin_bar),
'function' => 'initialize',
],
])
);
} finally {
remove_filter('wu_get_setting', $filter, 10);
remove_filter('show_admin_bar', '__return_true');
wp_set_current_user(0);
}
}

/**
* Test front-end My Sites toolbar optimization remains disabled by default.
*/
public function test_frontend_my_sites_toolbar_optimization_is_disabled_by_default(): void {

$manager = $this->get_manager_instance();
$user_id = $this->factory()->user->create(['role' => 'administrator']);
$method = new \ReflectionMethod(Site_Manager::class, 'is_frontend_my_sites_toolbar_request');

wp_set_current_user($user_id);
add_filter('show_admin_bar', '__return_true');

try {
$this->assertFalse(
$method->invoke($manager, [
[
'class' => 'WP_Admin_Bar',
'function' => 'initialize',
],
])
);
} finally {
remove_filter('show_admin_bar', '__return_true');
wp_set_current_user(0);
}
}

/**
* Test the toolbar optimization supplies only the active site.
*/
public function test_frontend_my_sites_toolbar_optimization_returns_current_site_only(): void {

$manager = $this->get_manager_instance();
$method = new \ReflectionMethod(Site_Manager::class, 'get_current_site_for_frontend_my_sites_toolbar');
$user_id = $this->factory()->user->create(['role' => 'administrator']);

grant_super_admin($user_id);
wp_set_current_user($user_id);

try {
$sites = $method->invoke($manager, $user_id);

$this->assertCount(1, $sites);
$this->assertArrayHasKey(get_current_blog_id(), $sites);
$this->assertSame(get_current_blog_id(), $sites[ get_current_blog_id() ]->userblog_id);
} finally {
revoke_super_admin($user_id);
wp_set_current_user(0);
}
}

// ========================================================================
// init – demo-related hooks
// ========================================================================
Expand Down Expand Up @@ -3547,7 +3755,7 @@
wu_save_setting('network_logo', 'https://example.com/logo.png');

// Register the 'login' style so wp_add_inline_style doesn't fail.
wp_register_style('login', false);

Check warning on line 3758 in tests/WP_Ultimo/Managers/Site_Manager_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Resource version not set in call to wp_register_style(). This means new versions of the style may not always be loaded due to browser caching.

$manager = $this->get_manager_instance();

Expand Down Expand Up @@ -3798,7 +4006,7 @@

$filter_applied = false;

add_filter('wu_demo_go_live_url', function ($url, $site) use (&$filter_applied) {

Check warning on line 4009 in tests/WP_Ultimo/Managers/Site_Manager_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

The method parameter $site is never used
$filter_applied = true;
return $url;
}, 10, 2);
Expand Down
15 changes: 15 additions & 0 deletions tests/WP_Ultimo/Settings_Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,21 @@ public function test_get_setting_defaults_disables_passwordless_login() {
$this->assertSame(0, $defaults['use_passwordless_login']);
}

public function test_get_setting_defaults_disables_frontend_my_sites_toolbar_optimization() {
$defaults = Settings::get_setting_defaults();
$this->assertArrayHasKey('optimize_frontend_my_sites_toolbar', $defaults);
$this->assertSame(0, $defaults['optimize_frontend_my_sites_toolbar']);
}

public function test_sites_section_has_frontend_my_sites_toolbar_optimization_field() {
$section = $this->settings->get_section('sites');
$field = $section['fields']['optimize_frontend_my_sites_toolbar'];

$this->assertSame('toggle', $field['type']);
$this->assertSame(0, $field['default']);
$this->assertStringContainsString('My Sites administration page', $field['desc']);
}

// ------------------------------------------------------------------
// General section fields
// ------------------------------------------------------------------
Expand Down
Loading