PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.13
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.13
1.1.13 1.1.12 1.1.11 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 All 37 releases
← All changes | includes/portal.php +58 -7 1.1.5 → 1.1.13 View file →
@@ -149,8 +149,11 @@
149 149 // Site). Off-origin hits still redirect into admin so shared
150 150 // links keep working; they just don't silently mutate user-meta.
151 151 if ( $auto_enable && openstation_portal_is_same_origin_navigation() && '1' !== get_user_meta( $user_id, 'desktop_mode_mode', true ) ) {
152 152 update_user_meta( $user_id, 'desktop_mode_mode', '1' );
153 + // Same stamps + action as the AJAX toggle; the portal is the
154 + // second of the two paths that turn a user on.
155 + openstation_record_user_enabled( $user_id );
153 156 }
154 157
155 158 // Pick the page the shell opens first. An explicit `target` query
156 159 // arg — a same-origin wp-admin URL — is how
@@ -295,12 +298,14 @@
295 298 * intent flag was present. The flags stay frozen (see AGENTS.md);
296 299 * only what they resolve to moved.
297 300 *
298 301 * Narrowly scoped to bail on every automated or sub-request entry point
299 - * — AJAX, REST, cron, admin-post.php, non-GET methods — so the hook
300 - * can't corrupt a form submission or break an API call. The shell
301 - * screen itself, chromeless loads, solo boots and classic-flagged
302 - * requests pass through.
302 + * — AJAX, REST, cron, admin-post.php, non-GET methods, and sub-resource
303 + * fetches (an `<img>`, a script or an XHR whose URL is an admin page,
304 + * see {@see openstation_is_subresource_request()}) — so the hook can't
305 + * corrupt a form submission, break an API call or hand an image tag an
306 + * HTML document. The shell screen itself, chromeless loads, solo boots
307 + * and classic-flagged requests pass through.
303 308 *
304 309 * Disable via the `openstation_admin_redirect_to_portal` filter (return
305 310 * false); plain admin pages then render as classic admin and the
306 311 * desktop lives at `/openstation/` only. The alias route runs before
@@ -322,8 +327,16 @@
322 327 // A solo boot renders one window in place, wherever it landed.
323 328 if ( function_exists( 'openstation_is_solo_request' ) && openstation_is_solo_request() ) {
324 329 return;
325 330 }
331 + // The user admin (`wp-admin/user/`, multisite's dashboard for users
332 + // with no site role) renders classic. It has no shell screen of its
333 + // own, and its URLs never survive the target allowlist — before
334 + // this pass-through the redirect claimed the request anyway and
335 + // silently forwarded the user to the site desktop's default entry.
336 + if ( is_multisite() && is_user_admin() ) {
337 + return;
338 + }
326 339 if ( wp_doing_ajax() || wp_doing_cron() ) {
327 340 return;
328 341 }
329 342 if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
@@ -331,13 +344,21 @@
331 344 }
332 345 if ( ! empty( $_SERVER['REQUEST_METHOD'] ) && 'GET' !== strtoupper( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) ) ) {
333 346 return;
334 347 }
348 + // The browser says what it is fetching for. An <img>, a script or
349 + // an XHR aimed at an admin URL (Jetpack's admin-bar sparkline is
350 + // admin.php?page=stats&noheader&proxy&chart=…) is not a user
351 + // landing on a plain admin page, and forwarding it into the desktop
352 + // only swaps the bytes it asked for with the shell's HTML.
353 + if ( openstation_is_subresource_request() ) {
354 + return;
355 + }
335 356
336 357 // The "Detach to new tab" button tags its URL with this flag so the
337 358 // user can view one admin page classically without disabling desktop
338 - // mode account-wide. Only affects the single request — subsequent
339 - // navigations inside the tab lose the flag and follow normal rules.
359 + // mode account-wide. Navigations inside that tab keep it (see the
360 + // Referer check below).
340 361 if ( ! empty( $_GET[ OPENSTATION_CLASSIC_FLAG ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
341 362 return;
342 363 }
343 364
@@ -377,8 +398,15 @@
377 398 if ( ! $redirect ) {
378 399 return;
379 400 }
380 401
402 + // A navigation out of a classic tab that lost the flag stays classic,
403 + // with the flag back in the URL for the next navigation's Referer.
404 + if ( '' !== $target && openstation_is_classic_referer() ) {
405 + wp_safe_redirect( add_query_arg( OPENSTATION_CLASSIC_FLAG, '1', $target ) );
406 + exit;
407 + }
408 +
381 409 // Route 1: straight to the shell screen.
382 410 if ( openstation_portal_forward_is_redundant( $target ) ) {
383 411 /**
384 412 * Filters whether to skip the portal hop for a URL the portal
@@ -601,8 +629,18 @@
601 629 }
602 630
603 631 $file = substr( $path, strlen( $admin_path ) );
604 632 $file = ltrim( (string) $file, '/' );
633 +
634 + // The network admin's own screens live one directory down and are
635 + // resolved against their own list. Without this a network URL came
636 + // back empty and the user was quietly forwarded to the site
637 + // dashboard, which is a different admin.
638 + $network = 0 === strpos( $file, 'network/' );
639 + if ( $network ) {
640 + $file = substr( $file, strlen( 'network/' ) );
641 + }
642 +
605 643 if ( '' === $file ) {
606 644 $file = 'index.php';
607 645 }
608 646
@@ -611,9 +649,9 @@
611 649 // regex alone would accept a plausible-looking filename that
612 650 // isn't a real core admin page (e.g. `custom_admin_page.php`)
613 651 // and effectively become an open redirect to a 404 page served
614 652 // under the admin path; the explicit allowlist closes that.
615 - $target = openstation_resolve_admin_target( $file );
653 + $target = openstation_resolve_admin_target( $file, $network );
616 654 if ( is_wp_error( $target ) ) {
617 655 return '';
618 656 }
619 657
@@ -628,8 +666,21 @@
628 666 // The shell screen is where a target is opened, never a target: the
629 667 // shell would open itself in a window, and a redirect chain built
630 668 // from it would loop. Fall back to the entry resolver instead.
631 669 if ( openstation_url_is_shell_screen( $target ) ) {
670 + return '';
671 + }
672 +
673 + // `admin.php` is a bootstrap, not a page. Without a `page` arg core
674 + // falls through the last `else` in `wp-admin/admin.php`, never
675 + // requires `admin-header.php`, and answers 200 with an empty body —
676 + // so the URL becomes a window showing nothing. The allowlist above
677 + // matches filenames and cannot see the query, which is why the
678 + // check belongs here, beside the shell-screen one: both are URLs
679 + // that resolve but must not become a target. Returning '' hands the
680 + // caller back to the entry resolver (session's focused window, else
681 + // the default window, else the Dashboard).
682 + if ( openstation_url_is_page_less_admin_php( $target ) ) {
632 683 return '';
633 684 }
634 685
635 686 return $target;