` whose src is `admin.php?page=stats&noheader&proxy&chart=…`: * core's `admin.php` skips the header on `noheader` and the page hook * echoes PNG bytes. The Jetpack Stats screen loads its report body the * same way, over XHR. Treating such a request as "a user landing on a * plain admin page" and forwarding it into the desktop hands the * consumer an HTML document instead: the admin bar then draws a broken * image with the alt text where the chart should be. * * `Sec-Fetch-Mode` is the browser's own answer, set by the user agent * and immune to script. `navigate` is a document or frame load, the * only kind of request worth forwarding into the desktop; `cors`, * `no-cors`, `same-origin` and `websocket` are sub-resource fetches. * A missing header (an old browser, a proxy that strips it) answers * false: not known to be a sub-resource, so callers keep behaving as * they always did. * * @return bool True when the request is a sub-resource fetch. */ function openstation_is_subresource_request() { if ( empty( $_SERVER['HTTP_SEC_FETCH_MODE'] ) ) { return false; } $mode = strtolower( sanitize_text_field( wp_unslash( $_SERVER['HTTP_SEC_FETCH_MODE'] ) ) ); return '' !== $mode && 'navigate' !== $mode; } /** * Disables the admin bar on chromeless (iframe) requests. * * Hooked on the `show_admin_bar` filter so the front-end bar path * also sees a false return. In admin, `is_admin_bar_showing()` * short-circuits to true for any `is_admin()` request regardless * of this filter, so the actual render is stopped by * {@see openstation_chromeless_suppress_admin_bar()} below; this * filter is kept for completeness + tests. * * @param bool $show Whether the admin bar should be shown. * @return bool */ function openstation_chromeless_hide_admin_bar( $show ) { if ( openstation_is_chromeless_request() ) { return false; } return $show; } add_filter( 'show_admin_bar', 'openstation_chromeless_hide_admin_bar' ); /** * Suppresses the admin bar render inside chromeless iframes. * * `is_admin_bar_showing()` unconditionally returns true in admin * context, so the `show_admin_bar` filter alone can't stop * `wp_admin_bar_render()` from firing on `in_admin_header`. We * detach the render action instead and let chromeless.css hide * the `wp-toolbar` padding on ``. */ function openstation_chromeless_suppress_admin_bar() { if ( openstation_is_chromeless_request() ) { remove_action( 'in_admin_header', 'wp_admin_bar_render', 0 ); remove_action( 'wp_body_open', 'wp_admin_bar_render', 0 ); } } add_action( 'admin_init', 'openstation_chromeless_suppress_admin_bar' ); /** * Stops a window from BUILDING the admin bar it never draws. * * Removing the render above stops the markup. It does not stop the * work: `_wp_admin_bar_init()` is hooked on `admin_init`, * `is_admin_bar_showing()` short-circuits to true for any admin * request, and so every window still instantiates `WP_Admin_Bar`, * calls `initialize()`, and — the expensive part — calls * `add_menus()`, which fires `admin_bar_menu` and runs **every** * registered callback. Core's twenty-odd nodes, WooCommerce's, * Jetpack's, a host masterbar's: each one resolving links, counting * things, checking capabilities. The finished object is then dropped * on the floor, because nothing renders it. * * The shell draws a real admin bar, once. A window drawing none * should pay for none — this is the same asymmetry the asset trims * exploit, on the server side. * * **Swapping the class rather than unhooking the init** is the * careful way to do it. `remove_action( 'admin_init', * '_wp_admin_bar_init' )` would leave `$wp_admin_bar` null, and a * plugin that touches the global outside the `admin_bar_menu` hook — * bad practice, entirely real — would fatal on it. Core exposes * `wp_admin_bar_class` precisely for this, so a window gets a real * `WP_Admin_Bar` subclass that is fully functional in every respect * except that it never solicits nodes. `add_node()` still works, * `get_nodes()` still answers, the global is still an object; the * hook simply never fires. * * `initialize()` is deliberately left alone — it sets up the object's * own state and costs nothing worth reclaiming. * * @param string $class_name Admin bar class WordPress intends to instantiate. * @return string The silent subclass inside a window; `$class_name` untouched * everywhere else, and whenever the parent class is unavailable. */ function openstation_chromeless_silence_admin_bar( $class_name ) { if ( ! openstation_is_chromeless_request() ) { return $class_name; } /** * Filters whether a window skips building the admin bar. * * Return false to let a window construct the bar as WordPress * normally would — for a plugin that (unusually) relies on * `admin_bar_menu` firing for a side effect rather than for the * node it adds. * * @param bool $silence Defaults to true inside windows. */ if ( ! apply_filters( 'openstation_chromeless_silence_admin_bar', true ) ) { return $class_name; } // `_wp_admin_bar_init()` requires `class-wp-admin-bar.php` before // it applies this filter, so the parent is guaranteed loaded here // — and only here, which is why the subclass is required lazily // rather than at bootstrap. if ( ! class_exists( 'WP_Admin_Bar' ) ) { return $class_name; } require_once __DIR__ . '/class-openstation-silent-admin-bar.php'; return 'OpenStation_Silent_Admin_Bar'; } add_filter( 'wp_admin_bar_class', 'openstation_chromeless_silence_admin_bar' ); /** * Detaches core's update / maintenance nags inside chromeless iframes so * they don't repeat in every window — the shell surfaces the update once * instead. */ function openstation_chromeless_suppress_update_nags() { if ( ! openstation_is_chromeless_request() ) { return; } remove_action( 'admin_notices', 'update_nag', 3 ); remove_action( 'network_admin_notices', 'update_nag', 3 ); remove_action( 'admin_notices', 'maintenance_nag', 10 ); remove_action( 'network_admin_notices', 'maintenance_nag', 10 ); } add_action( 'admin_init', 'openstation_chromeless_suppress_update_nags' ); /** * Detaches the remaining global core admin notices inside chromeless iframes * so they don't repeat in every window — the shell re-derives and surfaces * each once (see `openstation_get_core_notices()`). The update / maintenance * nags are handled by `openstation_chromeless_suppress_update_nags()`. */ function openstation_chromeless_suppress_core_notices() { if ( ! openstation_is_chromeless_request() ) { return; } remove_action( 'admin_notices', 'wp_recovery_mode_nag', 1 ); remove_action( 'admin_notices', 'default_password_nag' ); remove_action( 'admin_notices', 'deactivated_plugins_notice', 5 ); remove_action( 'admin_notices', 'paused_plugins_notice', 5 ); remove_action( 'admin_notices', 'paused_themes_notice', 5 ); } add_action( 'admin_init', 'openstation_chromeless_suppress_core_notices' ); /** * Keeps core's session-expired login modal (`wp-auth-check`) out of * chromeless iframes so the parent shell owns the single prompt. * * Every chromeless iframe runs its own Heartbeat, and by default * each one loads `wp-auth-check.js` + the `#wp-auth-check-wrap` * markup. When the session expires, N open windows meant N stacked * login modals — all asking for the same credentials. Returning * false from `wp_auth_check_load` here stops the modal assets from * ever loading inside iframes; the parent shell (a normal admin * page) keeps its copy and surfaces the one prompt over the whole * desktop. * * Detection is unaffected: the `wp-auth-check` heartbeat response * field is attached server-side (core hooks `wp_auth_check()` on * `heartbeat_send` / `heartbeat_nopriv_send`), so the bridge's * stale-nonce recovery in `chromeless-bridge.php` still sees the * logged-out → logged-in flip without the modal JS. * * @param bool $show Whether to load the authentication check. * @return bool */ function openstation_chromeless_suppress_auth_check( $show ) { if ( openstation_is_chromeless_request() ) { return false; } return $show; } add_filter( 'wp_auth_check_load', 'openstation_chromeless_suppress_auth_check' ); /** * Preserves the `openstation_chromeless` flag through admin * redirects. * * A chromeless iframe can be navigated away from chromeless mode * by any redirect that drops the query string — * `wp_redirect( admin_url( 'edit.php' ) )` after saving a * classic-editor post is the canonical example. The client-side * form interceptor handles the outgoing request, but the * server-built redirect URL is what the browser follows. * Re-append the flag here so the landing page stays chromeless * and the window doesn't "break out" into a nested admin. * * Scope is intentionally narrow: only same-site admin URLs are * touched, and only when the current request is itself * chromeless. Anything else passes through unchanged. * * @param string $location The redirect URL. * @return string The redirect URL, with `openstation_chromeless=1` appended when applicable. */ function openstation_chromeless_preserve_redirect( $location ) { if ( empty( $location ) || ! openstation_is_chromeless_request() ) { return $location; } if ( ! openstation_is_admin_redirect_target( $location ) ) { return $location; } // Don't double-append if the URL already carries the flag. if ( false !== strpos( $location, 'openstation_chromeless=' ) ) { return $location; } return add_query_arg( 'openstation_chromeless', '1', $location ); } add_filter( 'wp_redirect', 'openstation_chromeless_preserve_redirect', 999 ); /** * Preserves the `desktop_mode_classic` flag through admin * redirects. * * The detached-tab workflow depends on the classic flag living * on every same-tab navigation — otherwise a `wp_redirect()` * after saving a post (for instance) would drop it and the very * next page would fall back into the desktop shell. The JS * interceptor stamps the flag onto every outbound link and form, * but it can't touch server-built redirect URLs. * * Scope mirrors the chromeless preserver: only same-site * wp-admin targets, only when the current request is itself a * classic-override request, and the flag is never appended * twice. * * @param string $location The redirect URL. * @return string The redirect URL, with `desktop_mode_classic=1` appended when applicable. */ function openstation_classic_preserve_redirect( $location ) { if ( empty( $location ) || ! openstation_is_classic_request() ) { return $location; } if ( ! openstation_is_admin_redirect_target( $location ) ) { return $location; } if ( false !== strpos( $location, OPENSTATION_CLASSIC_FLAG . '=' ) ) { return $location; } return add_query_arg( OPENSTATION_CLASSIC_FLAG, '1', $location ); } add_filter( 'wp_redirect', 'openstation_classic_preserve_redirect', 999 ); /** * Whether `$location` is a redirect target that lands inside * wp-admin on the current site. Handles all four shapes WP core * actually emits: * * - Absolute, same-host: `https://example.com/wp-admin/users.php?...` * - Absolute path: `/wp-admin/users.php?...` * - Relative to wp-admin: `users.php?update=add&id=42` (used by * `user-new.php`, `edit-tags.php`, and * quite a few other core admin scripts) * - Same-host without path: `?paged=2` * * Off-site redirects (login → external SSO, e.g.) and frontend * redirects (`/`, `/?p=42`) return false so we never paint our * query flag on URLs that don't run our admin code. * * @internal * * @param string $location Raw redirect URL handed to `wp_redirect`. * @return bool */ function openstation_is_admin_redirect_target( $location ) { $location = (string) $location; if ( '' === $location ) { return false; } $parts = wp_parse_url( $location ); if ( false === $parts ) { return false; } // External host? Bail — we don't own that page. if ( ! empty( $parts['host'] ) ) { $site_host = wp_parse_url( site_url(), PHP_URL_HOST ); if ( $site_host && 0 !== strcasecmp( (string) $parts['host'], (string) $site_host ) ) { return false; } } $path = isset( $parts['path'] ) ? (string) $parts['path'] : ''; // Absolute path (URL-with-host or leading-slash variant). if ( '' !== $path ) { // `/wp-admin/foo.php` — definitive admin target. if ( false !== strpos( $path, '/wp-admin/' ) ) { return true; } // Absolute path NOT into wp-admin (e.g. `/`, `/wp-login.php`, // `/wp-json/...`). Frontend or login flow — leave alone. if ( '/' === $path[0] ) { return false; } } // Relative URL (or pure query string). Only safe to treat as // an admin target when the redirect was issued from inside // wp-admin — that's where wp_redirect( 'users.php?...' ) // actually resolves to /wp-admin/users.php?... at the // browser. is_admin() is the canonical signal. return is_admin(); }