PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.10
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.10
1.1.10 1.1.9 1.1.8 1.1.7 1.1.6 1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.1 1.0.0 0.9.8 0.9.7 0.9.6 0.9.4 0.9.5 0.9.3 0.9.2 0.9.1 0.9.0 0.8.9 0.8.8 0.8.7 All 34 releases
← All changes | includes/core/routing.php +267 -75 0.9.81.1.10 View file →
@@ -1,10 +1,10 @@
1 1 <?php
2 2 /**
3 - * Desktop Mode — request routing helpers.
3 + * OpenStation — request routing helpers.
4 4 *
5 5 * Chromeless / classic admin-bar suppression and the
6 - * `wp_redirect` filter pair that re-stamps the desktop-mode
6 + * `wp_redirect` filter pair that re-stamps the openstation
7 7 * flags onto server-built redirects. Extracted from the
8 8 * 1,609-LOC `helpers.php` during the architecture-0.8.1 PHP
9 9 * slicing (phase 6).
10 10 *
@@ -14,20 +14,22 @@
14 14 * file before `helpers.php`, so the function definitions are
15 15 * always present by the time WordPress wants them.
16 16 *
17 17 * Functions in this file:
18 - * - {@see desktop_mode_url_is_same_admin()} — same-origin admin URL predicate
19 - * - {@see desktop_mode_resolve_admin_target()} — admin filename → URL resolver
20 - * - {@see desktop_mode_admin_target_allowlist()} — wp-admin filename allowlist
21 - * - {@see desktop_mode_is_chromeless_request()} — chromeless request detection
22 - * - {@see desktop_mode_is_classic_request()} — classic-override request detection
23 - * - {@see desktop_mode_chromeless_hide_admin_bar()} — `show_admin_bar` filter
24 - * - {@see desktop_mode_chromeless_suppress_admin_bar()} — `admin_init` action
25 - * - {@see desktop_mode_chromeless_preserve_redirect()} — `wp_redirect` filter
26 - * - {@see desktop_mode_classic_preserve_redirect()} — `wp_redirect` filter
27 - * - {@see desktop_mode_is_admin_redirect_target()} — internal predicate
18 + * - {@see openstation_url_is_same_admin()} — same-origin admin URL predicate
19 + * - {@see openstation_url_is_page_less_admin_php()} — "renders nothing" predicate
20 + * - {@see openstation_resolve_admin_target()} — admin filename → URL resolver
21 + * - {@see openstation_admin_target_allowlist()} — wp-admin filename allowlist
22 + * - {@see openstation_is_chromeless_request()} — chromeless request detection
23 + * - {@see openstation_is_classic_request()} — classic-override request detection
24 + * - {@see openstation_is_subresource_request()} — sub-resource fetch detection
25 + * - {@see openstation_chromeless_hide_admin_bar()} — `show_admin_bar` filter
26 + * - {@see openstation_chromeless_suppress_admin_bar()} — `admin_init` action
27 + * - {@see openstation_chromeless_preserve_redirect()} — `wp_redirect` filter
28 + * - {@see openstation_classic_preserve_redirect()} — `wp_redirect` filter
29 + * - {@see openstation_is_admin_redirect_target()} — internal predicate
28 30 *
29 - * @package Desktop_Mode
31 + * @package OpenStation
30 32 */
31 33
32 34 defined( 'ABSPATH' ) || exit;
33 35
@@ -44,9 +46,9 @@
44 46 *
45 47 * @param string $url URL to test.
46 48 * @return bool
47 49 */
48 -function desktop_mode_url_is_same_admin( $url ) {
50 +function openstation_url_is_same_admin( $url ) {
49 51 if ( ! is_string( $url ) || '' === $url ) {
50 52 return false;
51 53 }
52 54
@@ -74,8 +76,52 @@
74 76 return 0 === strpos( $url_path, $admin_path );
75 77 }
76 78
77 79 /**
80 + * Whether `$url` addresses `wp-admin/admin.php` with no `page` arg.
81 + *
82 + * `admin.php` is core's plugin-screen bootstrap, and the allowlist in
83 + * {@see openstation_admin_target_allowlist()} accepts it for exactly
84 + * that reason — every plugin screen in the admin lives there. Without
85 + * a `page` arg, though, there is no screen to dispatch to: core falls
86 + * through the last `else` in `wp-admin/admin.php`, fires a couple of
87 + * back-compat `load-*` hooks, and returns 200 with an empty body,
88 + * having required neither `admin-header.php` nor `admin-footer.php`.
89 + *
90 + * So the URL resolves, passes every same-origin and allowlist check,
91 + * and renders nothing. Callers that are about to turn a URL into a
92 + * window or a redirect target use this to refuse it and fall back.
93 + *
94 + * Accepts absolute URLs and request-URI-shaped paths, mirroring
95 + * {@see openstation_url_is_shell_screen()}, whose guard this sits
96 + * beside at every call site.
97 + *
98 + * @param string $url URL or path to test.
99 + * @return bool
100 + */
101 +function openstation_url_is_page_less_admin_php( $url ) {
102 + if ( ! is_string( $url ) || '' === $url ) {
103 + return false;
104 + }
105 +
106 + $path = wp_parse_url( $url, PHP_URL_PATH );
107 + if ( ! is_string( $path ) || 'admin.php' !== basename( $path ) ) {
108 + return false;
109 + }
110 +
111 + $query = wp_parse_url( $url, PHP_URL_QUERY );
112 + if ( ! is_string( $query ) || '' === $query ) {
113 + return true;
114 + }
115 +
116 + // `page=` present but empty is the same nothing: core only sets
117 + // `$plugin_page` from a non-empty `?page=`. An array (`page[]=x`)
118 + // is not a slug either.
119 + parse_str( $query, $args );
120 + return ! isset( $args['page'] ) || ! is_string( $args['page'] ) || '' === $args['page'];
121 +}
122 +
123 +/**
78 124 * Resolves an admin-page filename (e.g. `edit.php`) to its
79 125 * absolute admin URL, allowlisted against the canonical set of
80 126 * wp-admin top-level filenames.
81 127 *
@@ -84,16 +130,17 @@
84 130 * exist in the static allowlist. A regex-only check would accept
85 131 * `custom_admin_page.php` if a plugin named something that way;
86 132 * the explicit allowlist closes that.
87 133 *
88 - * @param string $file Bare admin filename (no path, no query string).
134 + * @param string $file Bare admin filename (no path, no query string).
135 + * @param bool $network Resolve against the network admin's own screens.
89 136 * @return string|WP_Error Absolute admin URL on success, `WP_Error` otherwise.
90 137 */
91 -function desktop_mode_resolve_admin_target( $file ) {
138 +function openstation_resolve_admin_target( $file, $network = false ) {
92 139 $file = is_string( $file ) ? trim( $file ) : '';
93 140 if ( '' === $file ) {
94 141 return new WP_Error(
95 - 'desktop_mode_empty_target',
142 + 'openstation_empty_target',
96 143 __( 'Admin target cannot be empty.', 'desktop-mode' )
97 144 );
98 145 }
99 146
@@ -98,9 +145,9 @@
98 145 }
99 146
100 147 if ( false !== strpos( $file, '..' ) || false !== strpos( $file, '/' ) || false !== strpos( $file, '\\' ) ) {
101 148 return new WP_Error(
102 - 'desktop_mode_invalid_target',
149 + 'openstation_invalid_target',
103 150 __( 'Admin target contains invalid path characters.', 'desktop-mode' )
104 151 );
105 152 }
106 153
@@ -109,16 +156,25 @@
109 156 // below is the final arbiter; this regex just pre-filters
110 157 // clearly bad inputs cheaply.
111 158 if ( ! preg_match( '/^[a-z0-9_-]+\.php$/i', $file ) ) {
112 159 return new WP_Error(
113 - 'desktop_mode_invalid_target',
160 + 'openstation_invalid_target',
114 161 __( 'Admin target must be a plain .php filename.', 'desktop-mode' )
115 162 );
116 163 }
117 164
118 - if ( ! in_array( strtolower( $file ), desktop_mode_admin_target_allowlist(), true ) ) {
165 + if ( $network ) {
166 + return in_array( strtolower( $file ), openstation_network_admin_target_allowlist(), true )
167 + ? network_admin_url( $file )
168 + : new WP_Error(
169 + 'openstation_unknown_target',
170 + __( 'Admin target does not exist.', 'desktop-mode' )
171 + );
172 + }
173 +
174 + if ( ! in_array( strtolower( $file ), openstation_admin_target_allowlist(), true ) ) {
119 175 return new WP_Error(
120 - 'desktop_mode_unknown_target',
176 + 'openstation_unknown_target',
121 177 __( 'Admin target does not exist.', 'desktop-mode' )
122 178 );
123 179 }
124 180
@@ -125,10 +181,47 @@
125 181 return admin_url( $file );
126 182 }
127 183
128 184 /**
185 + * Canonical `wp-admin/network/` filenames a target may resolve to.
186 + *
187 + * The network admin's own screens, and only those: the site allowlist
188 + * cannot stand in for it, since the two directories share filenames
189 + * that mean different things (`users.php` is everyone on the network
190 + * here, one site's users there).
191 + *
192 + * @return string[]
193 + */
194 +function openstation_network_admin_target_allowlist() {
195 + return array(
196 + 'index.php',
197 + 'sites.php',
198 + 'site-new.php',
199 + 'site-info.php',
200 + 'site-users.php',
201 + 'site-themes.php',
202 + 'site-settings.php',
203 + 'users.php',
204 + 'user-new.php',
205 + 'themes.php',
206 + 'theme-install.php',
207 + 'plugins.php',
208 + 'plugin-install.php',
209 + 'plugin-editor.php',
210 + 'settings.php',
211 + 'setup.php',
212 + 'upgrade.php',
213 + 'update-core.php',
214 + 'about.php',
215 + 'credits.php',
216 + 'freedoms.php',
217 + 'privacy.php',
218 + );
219 +}
220 +
221 +/**
129 222 * Returns the allowlist of canonical wp-admin top-level
130 - * filenames that {@see desktop_mode_resolve_admin_target()}
223 + * filenames that {@see openstation_resolve_admin_target()}
131 224 * accepts.
132 225 *
133 226 * Hardcoded rather than read from disk so the plugin doesn't
134 227 * depend on a particular WordPress install layout (and doesn't
@@ -137,9 +230,9 @@
137 230 * via the filter.
138 231 *
139 232 * @return string[] Lowercased filenames including extension.
140 233 */
141 -function desktop_mode_admin_target_allowlist() {
234 +function openstation_admin_target_allowlist() {
142 235 $files = array(
143 236 'about.php',
144 237 'admin-ajax.php',
145 238 'admin-footer.php',
@@ -234,9 +327,9 @@
234 327 * portal `target=` query args.
235 328 *
236 329 * @param string[] $files Default allowlist.
237 330 */
238 - $files = (array) apply_filters( 'desktop_mode_admin_target_allowlist', $files );
331 + $files = (array) apply_filters( 'openstation_admin_target_allowlist', $files );
239 332
240 333 return array_values( array_unique( array_map( 'strtolower', array_filter( $files, 'is_string' ) ) ) );
241 334 }
242 335
@@ -242,19 +335,19 @@
242 335
243 336 /**
244 337 * Checks whether the current request is a chromeless request.
245 338 *
246 - * Chromeless requests are admin pages loaded inside desktop-mode
339 + * Chromeless requests are admin pages loaded inside openstation
247 340 * windows (iframes). They render only the page content without
248 341 * the admin shell (sidebar, admin bar, footer).
249 342 *
250 343 * @return bool True if this is a chromeless (iframe) request.
251 344 */
252 -function desktop_mode_is_chromeless_request() {
253 - if ( ! desktop_mode_is_enabled() ) {
345 +function openstation_is_chromeless_request() {
346 + if ( ! openstation_is_enabled() ) {
254 347 // Only allow chromeless mode if the user actually has
255 - // desktop mode enabled. Prevents stripping admin chrome via
256 - // a bare `?desktop_mode_chromeless=1` parameter from a
348 + // OpenStation enabled. Prevents stripping admin chrome via
349 + // a bare `?openstation_chromeless=1` parameter from a
257 350 // logged-out URL.
258 351 return false;
259 352 }
260 353
@@ -260,9 +353,9 @@
260 353
261 354 // Primary signal — the explicit query flag the parent shell
262 355 // adds when opening windows.
263 356 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only request flag, no state change.
264 - if ( ! empty( $_GET['desktop_mode_chromeless'] ) && '1' === sanitize_text_field( wp_unslash( $_GET['desktop_mode_chromeless'] ) ) ) {
357 + if ( ! empty( $_GET['openstation_chromeless'] ) && '1' === sanitize_text_field( wp_unslash( $_GET['openstation_chromeless'] ) ) ) {
265 358 return true;
266 359 }
267 360
268 361 // Fallback signal — the request is a same-origin iframe load.
@@ -270,12 +363,12 @@
270 363 // the `Sec-Fetch-*` headers reliably, and they are immune to
271 364 // JavaScript spoofing (the browser sets them itself).
272 365 //
273 366 // This catches the failure mode where an internal admin
274 - // navigation drops the `?desktop_mode_chromeless=1` query flag —
367 + // navigation drops the `?openstation_chromeless=1` query flag —
275 368 // Gutenberg's `window.location` assignments, meta-refresh
276 369 // redirects, or any link the inline rewriter missed. The user
277 - // is in an iframe on the same origin, has desktop mode enabled,
370 + // is in an iframe on the same origin, has OpenStation enabled,
278 371 // so render as chromeless.
279 372 //
280 373 // `Sec-Fetch-Site: same-origin` is the cross-origin guard so a
281 374 // foreign site that iframes the wp-admin page can't trick us
@@ -289,15 +382,15 @@
289 382 : '';
290 383 if ( 'iframe' === $fetch_dest && 'same-origin' === $fetch_site ) {
291 384 /**
292 385 * Filter the Sec-Fetch fallback. Return false to require an
293 - * explicit `?desktop_mode_chromeless=1` flag; useful for environments where a
386 + * explicit `?openstation_chromeless=1` flag; useful for environments where a
294 387 * reverse proxy strips the `Sec-Fetch-*` headers and they
295 388 * can't be trusted.
296 389 *
297 390 * @param bool $allow Default true.
298 391 */
299 - return (bool) apply_filters( 'desktop_mode_chromeless_sec_fetch_fallback', true );
392 + return (bool) apply_filters( 'openstation_chromeless_sec_fetch_fallback', true );
300 393 }
301 394
302 395 return false;
303 396 }
@@ -308,31 +401,62 @@
308 401 *
309 402 * The window-chrome "Detach" action opens an admin page in a new
310 403 * browser tab with `?desktop_mode_classic=1` so the user can view
311 404 * that one page outside the desktop shell without disabling
312 - * desktop mode account-wide. The flag is a per-request override:
313 - * `desktop_mode_is_enabled()` still returns true (the user's
405 + * OpenStation account-wide. The flag is a per-request override:
406 + * `openstation_is_enabled()` still returns true (the user's
314 407 * preference hasn't changed), but the shell, shell assets, and
315 408 * body class are skipped for this request so the classic admin
316 409 * renders normally.
317 410 *
318 - * Keep this separate from `desktop_mode_is_enabled()` so the
411 + * Keep this separate from `openstation_is_enabled()` so the
319 412 * admin-bar toggle in the detached tab correctly reflects the
320 - * account state — letting the user disable desktop mode entirely
413 + * account state — letting the user disable OpenStation entirely
321 414 * from the tab if they want to.
322 415 *
323 416 * @return bool True if the request carries `?desktop_mode_classic=1`.
324 417 */
325 -function desktop_mode_is_classic_request() {
418 +function openstation_is_classic_request() {
326 419 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only request flag.
327 - if ( empty( $_GET[ DESKTOP_MODE_CLASSIC_FLAG ] ) ) {
420 + if ( empty( $_GET[ OPENSTATION_CLASSIC_FLAG ] ) ) {
328 421 return false;
329 422 }
330 423 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only request flag.
331 - return '1' === sanitize_text_field( wp_unslash( $_GET[ DESKTOP_MODE_CLASSIC_FLAG ] ) );
424 + return '1' === sanitize_text_field( wp_unslash( $_GET[ OPENSTATION_CLASSIC_FLAG ] ) );
332 425 }
333 426
334 427 /**
428 + * Checks whether the browser is fetching this request as a
429 + * sub-resource of some page rather than navigating to it.
430 + *
431 + * Admin URLs serve more than pages. Jetpack's admin-bar sparkline is
432 + * an `<img>` whose src is `admin.php?page=stats&noheader&proxy&chart=…`:
433 + * core's `admin.php` skips the header on `noheader` and the page hook
434 + * echoes PNG bytes. The Jetpack Stats screen loads its report body the
435 + * same way, over XHR. Treating such a request as "a user landing on a
436 + * plain admin page" and forwarding it into the desktop hands the
437 + * consumer an HTML document instead: the admin bar then draws a broken
438 + * image with the alt text where the chart should be.
439 + *
440 + * `Sec-Fetch-Mode` is the browser's own answer, set by the user agent
441 + * and immune to script. `navigate` is a document or frame load, the
442 + * only kind of request worth forwarding into the desktop; `cors`,
443 + * `no-cors`, `same-origin` and `websocket` are sub-resource fetches.
444 + * A missing header (an old browser, a proxy that strips it) answers
445 + * false: not known to be a sub-resource, so callers keep behaving as
446 + * they always did.
447 + *
448 + * @return bool True when the request is a sub-resource fetch.
449 + */
450 +function openstation_is_subresource_request() {
451 + if ( empty( $_SERVER['HTTP_SEC_FETCH_MODE'] ) ) {
452 + return false;
453 + }
454 + $mode = strtolower( sanitize_text_field( wp_unslash( $_SERVER['HTTP_SEC_FETCH_MODE'] ) ) );
455 + return '' !== $mode && 'navigate' !== $mode;
456 +}
457 +
458 +/**
335 459 * Disables the admin bar on chromeless (iframe) requests.
336 460 *
337 461 * Hooked on the `show_admin_bar` filter so the front-end bar path
338 462 * also sees a false return. In admin, `is_admin_bar_showing()`
@@ -337,21 +461,21 @@
337 461 * Hooked on the `show_admin_bar` filter so the front-end bar path
338 462 * also sees a false return. In admin, `is_admin_bar_showing()`
339 463 * short-circuits to true for any `is_admin()` request regardless
340 464 * of this filter, so the actual render is stopped by
341 - * {@see desktop_mode_chromeless_suppress_admin_bar()} below; this
465 + * {@see openstation_chromeless_suppress_admin_bar()} below; this
342 466 * filter is kept for completeness + tests.
343 467 *
344 468 * @param bool $show Whether the admin bar should be shown.
345 469 * @return bool
346 470 */
347 -function desktop_mode_chromeless_hide_admin_bar( $show ) {
348 - if ( desktop_mode_is_chromeless_request() ) {
471 +function openstation_chromeless_hide_admin_bar( $show ) {
472 + if ( openstation_is_chromeless_request() ) {
349 473 return false;
350 474 }
351 475 return $show;
352 476 }
353 -add_filter( 'show_admin_bar', 'desktop_mode_chromeless_hide_admin_bar' );
477 +add_filter( 'show_admin_bar', 'openstation_chromeless_hide_admin_bar' );
354 478
355 479 /**
356 480 * Suppresses the admin bar render inside chromeless iframes.
357 481 *
@@ -360,23 +484,91 @@
360 484 * `wp_admin_bar_render()` from firing on `in_admin_header`. We
361 485 * detach the render action instead and let chromeless.css hide
362 486 * the `wp-toolbar` padding on `<html>`.
363 487 */
364 -function desktop_mode_chromeless_suppress_admin_bar() {
365 - if ( desktop_mode_is_chromeless_request() ) {
488 +function openstation_chromeless_suppress_admin_bar() {
489 + if ( openstation_is_chromeless_request() ) {
366 490 remove_action( 'in_admin_header', 'wp_admin_bar_render', 0 );
367 491 remove_action( 'wp_body_open', 'wp_admin_bar_render', 0 );
368 492 }
369 493 }
370 -add_action( 'admin_init', 'desktop_mode_chromeless_suppress_admin_bar' );
494 +add_action( 'admin_init', 'openstation_chromeless_suppress_admin_bar' );
371 495
372 496 /**
497 + * Stops a window from BUILDING the admin bar it never draws.
498 + *
499 + * Removing the render above stops the markup. It does not stop the
500 + * work: `_wp_admin_bar_init()` is hooked on `admin_init`,
501 + * `is_admin_bar_showing()` short-circuits to true for any admin
502 + * request, and so every window still instantiates `WP_Admin_Bar`,
503 + * calls `initialize()`, and — the expensive part — calls
504 + * `add_menus()`, which fires `admin_bar_menu` and runs **every**
505 + * registered callback. Core's twenty-odd nodes, WooCommerce's,
506 + * Jetpack's, a host masterbar's: each one resolving links, counting
507 + * things, checking capabilities. The finished object is then dropped
508 + * on the floor, because nothing renders it.
509 + *
510 + * The shell draws a real admin bar, once. A window drawing none
511 + * should pay for none — this is the same asymmetry the asset trims
512 + * exploit, on the server side.
513 + *
514 + * **Swapping the class rather than unhooking the init** is the
515 + * careful way to do it. `remove_action( 'admin_init',
516 + * '_wp_admin_bar_init' )` would leave `$wp_admin_bar` null, and a
517 + * plugin that touches the global outside the `admin_bar_menu` hook —
518 + * bad practice, entirely real — would fatal on it. Core exposes
519 + * `wp_admin_bar_class` precisely for this, so a window gets a real
520 + * `WP_Admin_Bar` subclass that is fully functional in every respect
521 + * except that it never solicits nodes. `add_node()` still works,
522 + * `get_nodes()` still answers, the global is still an object; the
523 + * hook simply never fires.
524 + *
525 + * `initialize()` is deliberately left alone — it sets up the object's
526 + * own state and costs nothing worth reclaiming.
527 + *
528 + * @param string $class_name Admin bar class WordPress intends to instantiate.
529 + * @return string The silent subclass inside a window; `$class_name` untouched
530 + * everywhere else, and whenever the parent class is unavailable.
531 + */
532 +function openstation_chromeless_silence_admin_bar( $class_name ) {
533 + if ( ! openstation_is_chromeless_request() ) {
534 + return $class_name;
535 + }
536 +
537 + /**
538 + * Filters whether a window skips building the admin bar.
539 + *
540 + * Return false to let a window construct the bar as WordPress
541 + * normally would — for a plugin that (unusually) relies on
542 + * `admin_bar_menu` firing for a side effect rather than for the
543 + * node it adds.
544 + *
545 + * @param bool $silence Defaults to true inside windows.
546 + */
547 + if ( ! apply_filters( 'openstation_chromeless_silence_admin_bar', true ) ) {
548 + return $class_name;
549 + }
550 +
551 + // `_wp_admin_bar_init()` requires `class-wp-admin-bar.php` before
552 + // it applies this filter, so the parent is guaranteed loaded here
553 + // — and only here, which is why the subclass is required lazily
554 + // rather than at bootstrap.
555 + if ( ! class_exists( 'WP_Admin_Bar' ) ) {
556 + return $class_name;
557 + }
558 + require_once __DIR__ . '/class-openstation-silent-admin-bar.php';
559 +
560 + return 'OpenStation_Silent_Admin_Bar';
561 +}
562 +add_filter( 'wp_admin_bar_class', 'openstation_chromeless_silence_admin_bar' );
563 +
564 +/**
373 565 * Detaches core's update / maintenance nags inside chromeless iframes so
374 566 * they don't repeat in every window — the shell surfaces the update once
375 567 * instead.
376 568 */
377 -function desktop_mode_chromeless_suppress_update_nags() {
378 - if ( ! desktop_mode_is_chromeless_request() ) {
569 +function openstation_chromeless_suppress_update_nags() {
570 + if ( ! openstation_is_chromeless_request() ) {
379 571 return;
380 572 }
381 573 remove_action( 'admin_notices', 'update_nag', 3 );
382 574 remove_action( 'network_admin_notices', 'update_nag', 3 );
@@ -382,18 +574,18 @@
382 574 remove_action( 'network_admin_notices', 'update_nag', 3 );
383 575 remove_action( 'admin_notices', 'maintenance_nag', 10 );
384 576 remove_action( 'network_admin_notices', 'maintenance_nag', 10 );
385 577 }
386 -add_action( 'admin_init', 'desktop_mode_chromeless_suppress_update_nags' );
578 +add_action( 'admin_init', 'openstation_chromeless_suppress_update_nags' );
387 579
388 580 /**
389 581 * Detaches the remaining global core admin notices inside chromeless iframes
390 582 * so they don't repeat in every window — the shell re-derives and surfaces
391 - * each once (see `desktop_mode_get_core_notices()`). The update / maintenance
392 - * nags are handled by `desktop_mode_chromeless_suppress_update_nags()`.
583 + * each once (see `openstation_get_core_notices()`). The update / maintenance
584 + * nags are handled by `openstation_chromeless_suppress_update_nags()`.
393 585 */
394 -function desktop_mode_chromeless_suppress_core_notices() {
395 - if ( ! desktop_mode_is_chromeless_request() ) {
586 +function openstation_chromeless_suppress_core_notices() {
587 + if ( ! openstation_is_chromeless_request() ) {
396 588 return;
397 589 }
398 590 remove_action( 'admin_notices', 'wp_recovery_mode_nag', 1 );
399 591 remove_action( 'admin_notices', 'default_password_nag' );
@@ -400,9 +592,9 @@
400 592 remove_action( 'admin_notices', 'deactivated_plugins_notice', 5 );
401 593 remove_action( 'admin_notices', 'paused_plugins_notice', 5 );
402 594 remove_action( 'admin_notices', 'paused_themes_notice', 5 );
403 595 }
404 -add_action( 'admin_init', 'desktop_mode_chromeless_suppress_core_notices' );
596 +add_action( 'admin_init', 'openstation_chromeless_suppress_core_notices' );
405 597
406 598 /**
407 599 * Keeps core's session-expired login modal (`wp-auth-check`) out of
408 600 * chromeless iframes so the parent shell owns the single prompt.
@@ -424,18 +616,18 @@
424 616 *
425 617 * @param bool $show Whether to load the authentication check.
426 618 * @return bool
427 619 */
428 -function desktop_mode_chromeless_suppress_auth_check( $show ) {
429 - if ( desktop_mode_is_chromeless_request() ) {
620 +function openstation_chromeless_suppress_auth_check( $show ) {
621 + if ( openstation_is_chromeless_request() ) {
430 622 return false;
431 623 }
432 624 return $show;
433 625 }
434 -add_filter( 'wp_auth_check_load', 'desktop_mode_chromeless_suppress_auth_check' );
626 +add_filter( 'wp_auth_check_load', 'openstation_chromeless_suppress_auth_check' );
435 627
436 628 /**
437 - * Preserves the `desktop_mode_chromeless` flag through admin
629 + * Preserves the `openstation_chromeless` flag through admin
438 630 * redirects.
439 631 *
440 632 * A chromeless iframe can be navigated away from chromeless mode
441 633 * by any redirect that drops the query string —
@@ -450,27 +642,27 @@
450 642 * touched, and only when the current request is itself
451 643 * chromeless. Anything else passes through unchanged.
452 644 *
453 645 * @param string $location The redirect URL.
454 - * @return string The redirect URL, with `desktop_mode_chromeless=1` appended when applicable.
646 + * @return string The redirect URL, with `openstation_chromeless=1` appended when applicable.
455 647 */
456 -function desktop_mode_chromeless_preserve_redirect( $location ) {
457 - if ( empty( $location ) || ! desktop_mode_is_chromeless_request() ) {
648 +function openstation_chromeless_preserve_redirect( $location ) {
649 + if ( empty( $location ) || ! openstation_is_chromeless_request() ) {
458 650 return $location;
459 651 }
460 652
461 - if ( ! desktop_mode_is_admin_redirect_target( $location ) ) {
653 + if ( ! openstation_is_admin_redirect_target( $location ) ) {
462 654 return $location;
463 655 }
464 656
465 657 // Don't double-append if the URL already carries the flag.
466 - if ( false !== strpos( $location, 'desktop_mode_chromeless=' ) ) {
658 + if ( false !== strpos( $location, 'openstation_chromeless=' ) ) {
467 659 return $location;
468 660 }
469 661
470 - return add_query_arg( 'desktop_mode_chromeless', '1', $location );
662 + return add_query_arg( 'openstation_chromeless', '1', $location );
471 663 }
472 -add_filter( 'wp_redirect', 'desktop_mode_chromeless_preserve_redirect', 999 );
664 +add_filter( 'wp_redirect', 'openstation_chromeless_preserve_redirect', 999 );
473 665
474 666 /**
475 667 * Preserves the `desktop_mode_classic` flag through admin
476 668 * redirects.
@@ -489,24 +681,24 @@
489 681 *
490 682 * @param string $location The redirect URL.
491 683 * @return string The redirect URL, with `desktop_mode_classic=1` appended when applicable.
492 684 */
493 -function desktop_mode_classic_preserve_redirect( $location ) {
494 - if ( empty( $location ) || ! desktop_mode_is_classic_request() ) {
685 +function openstation_classic_preserve_redirect( $location ) {
686 + if ( empty( $location ) || ! openstation_is_classic_request() ) {
495 687 return $location;
496 688 }
497 689
498 - if ( ! desktop_mode_is_admin_redirect_target( $location ) ) {
690 + if ( ! openstation_is_admin_redirect_target( $location ) ) {
499 691 return $location;
500 692 }
501 693
502 - if ( false !== strpos( $location, DESKTOP_MODE_CLASSIC_FLAG . '=' ) ) {
694 + if ( false !== strpos( $location, OPENSTATION_CLASSIC_FLAG . '=' ) ) {
503 695 return $location;
504 696 }
505 697
506 - return add_query_arg( DESKTOP_MODE_CLASSIC_FLAG, '1', $location );
698 + return add_query_arg( OPENSTATION_CLASSIC_FLAG, '1', $location );
507 699 }
508 -add_filter( 'wp_redirect', 'desktop_mode_classic_preserve_redirect', 999 );
700 +add_filter( 'wp_redirect', 'openstation_classic_preserve_redirect', 999 );
509 701
510 702 /**
511 703 * Whether `$location` is a redirect target that lands inside
512 704 * wp-admin on the current site. Handles all four shapes WP core
@@ -527,9 +719,9 @@
527 719 *
528 720 * @param string $location Raw redirect URL handed to `wp_redirect`.
529 721 * @return bool
530 722 */
531 -function desktop_mode_is_admin_redirect_target( $location ) {
723 +function openstation_is_admin_redirect_target( $location ) {
532 724 $location = (string) $location;
533 725 if ( '' === $location ) {
534 726 return false;
535 727 }
@@ -556,9 +748,9 @@
556 748 return true;
557 749 }
558 750 // Absolute path NOT into wp-admin (e.g. `/`, `/wp-login.php`,
559 751 // `/wp-json/...`). Frontend or login flow — leave alone.
560 - if ( '/' === $path[ 0 ] ) {
752 + if ( '/' === $path[0] ) {
561 753 return false;
562 754 }
563 755 }
564 756