Multisite: Fix My Sites admin bar menu for large networks.#11259
Multisite: Fix My Sites admin bar menu for large networks.#11259soderlind wants to merge 1 commit intoWordPress:trunkfrom
Conversation
Three fixes for the My Sites dropdown in the WordPress admin bar:
1. Show all network sites for super admins -- get_blogs_of_user() only
returned sites with explicit _capabilities meta rows. Super admins
now get all sites via get_sites().
2. Make the dropdown scrollable (Trac #15317) -- adds max-height and
overflow-y: auto so the menu is reachable when the site list exceeds
the viewport. Fly-out submenus use position: fixed with JS-computed
coordinates to escape the scroll container's overflow clipping.
3. Eliminate switch_to_blog() overhead -- the per-site loop in
wp_admin_bar_my_sites_menu() now computes URLs directly from the
blog object. get_blogs_of_user() batch-fetches blogname and
siteurl via a single UNION ALL query instead of triggering hidden
switch_to_blog() calls through WP_Site::get_details().
Props PerS, ocean90, SergeyBiryukov, jeremyfelt, morganestes, trepmal,
austinginder, zephyr7501, sabernhardt, paaljoachim.
Fixes #15317.
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Test using WordPress PlaygroundThe changes in this pull request can previewed and tested using a WordPress Playground instance. WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser. Some things to be aware of
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
|
@westonruter ^^ |
Multisite: Fix My Sites admin bar menu for large networks
Trac ticket: #15317 (open since 2010)
Based on: Learnings from the my-sites-fix proof-of-concept plugin and the Super Admin All Sites Menu plugin.
The problem
The "My Sites" dropdown in the WordPress admin bar has three long-standing issues on multisite networks:
Super admins only see sites they are explicitly added to.
get_blogs_of_user()discovers sites by scanning user meta keys ending in_capabilities. A super admin has implicit access to all sites but only has explicit capability rows for sites they were individually added to — so the menu is missing most sites.The dropdown can't scroll past the viewport. When the site list is longer than the browser window, items below the fold are simply unreachable. There is no
max-heightoroverflowon the submenu wrapper. This is the original bug reported in #15317.switch_to_blog()is called for every site. Bothwp_admin_bar_my_sites_menu()andget_blogs_of_user()callswitch_to_blog()per site — the former explicitly in a loop, the latter implicitly viaWP_Site::get_details()when accessing$site->blognameand$site->siteurl. On a network with hundreds of sites this is a significant performance hit.What this patch changes
Files changed
get_blogs_of_user(); batch option fetch; new_batch_get_site_options()helperwp_admin_bar_my_sites_menu()Fix 1 — Show all network sites for super admins
In
get_blogs_of_user(), whenis_super_admin( $user_id )is true, the function now callsget_sites()(ordered by path, excluding archived/spam/deleted/mature) instead of scanning_capabilitiesmeta keys. This ensures the admin bar and the My Sites admin page show every site on the network.Fix 2 — Make the dropdown scrollable
A new
@media screen and (min-width: 783px)block in admin-bar.css adds:This makes the My Sites submenu scrollable when it exceeds the viewport height. The mobile menu (below 783px) is unaffected.
Caveat:
overflow-y: autoclips the nested fly-out submenus (Dashboard, New Post, etc.) that normally position themselves withmargin-left: 100%. The fix switches those fly-outs toposition: fixedand computes their coordinates in JavaScript usinggetBoundingClientRect(). The JS also clamps the fly-out's top position so it never extends below the viewport, and supports RTL layouts by positioning to the left of the parent item whendocument.documentElement.dir === 'rtl'.Fix 3 — Eliminate
switch_to_blog()overheadIn the admin bar menu loop:
wp_admin_bar_my_sites_menu()no longer callsswitch_to_blog()per site. URLs are computed directly from the blog object properties ($blog->siteurl,$blog->domain,$blog->path). Per-sitecurrent_user_can()checks are removed — these were guarding convenience links (Dashboard, New Post, Manage Comments) whose target pages enforce their own permissions.switch_to_blog()is still called when site icons are enabled, sincehas_site_icon()andget_site_icon_url()require switched context. To mitigate the cost, thewp_admin_bar_show_site_iconsfilter now defaults tofalsefor networks with more than 20 sites (previously it defaulted totrueunconditionally). The filter remains available for plugins to override.In
get_blogs_of_user(): The magic property accesses$site->blognameand$site->siteurlonWP_Siteobjects triggeredWP_Site::get_details(), which internally callsswitch_to_blog()for each site. This is replaced by a new private helper function_batch_get_site_options()that builds a singleUNION ALLquery across per-sitewp_N_optionstables to fetchblognameandsiteurlfor all sites at once. Table names are derived from$wpdb->get_blog_prefix()(a pure function with no side effects), and option names are passed through$wpdb->prepare().Backward compatibility notes
pre_get_blogs_of_userfilter (since 4.6.0) continues to work and can override the super admin behavior.get_blogs_of_userfilter continues to fire on the result.wp_admin_bar_show_site_iconsfilter (since 6.0.0) still controls site icon visibility; the only change is the default value now considers site count.myblogs_blog_actionsfilter in my-sites.php is not affected — that page's render loop still usesswitch_to_blog()to preserve backward compatibility for plugins relying on switched context._batch_get_site_options()function is marked@access privateand is not intended for external use.Testing
switch_to_blog()calls during menu render (except when site icons are enabled for ≤ 20 sites). Check via Query Monitor or$GLOBALS['_wp_switched_stack'].top: 0; left: 0.Trac ticket: https://core.trac.wordpress.org/ticket/15317
Use of AI Tools
GitHub Copilot + Opus 4.6 was used to review this fix
This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.