PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.12
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.12
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 0.8.9 All 36 releases
← All changes | includes/portal.php +49 -5 1.1.5 → 1.1.12 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,8 +344,16 @@
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 359 // mode account-wide. Only affects the single request — subsequent
@@ -601,8 +622,18 @@
601 622 }
602 623
603 624 $file = substr( $path, strlen( $admin_path ) );
604 625 $file = ltrim( (string) $file, '/' );
626 +
627 + // The network admin's own screens live one directory down and are
628 + // resolved against their own list. Without this a network URL came
629 + // back empty and the user was quietly forwarded to the site
630 + // dashboard, which is a different admin.
631 + $network = 0 === strpos( $file, 'network/' );
632 + if ( $network ) {
633 + $file = substr( $file, strlen( 'network/' ) );
634 + }
635 +
605 636 if ( '' === $file ) {
606 637 $file = 'index.php';
607 638 }
608 639
@@ -611,9 +642,9 @@
611 642 // regex alone would accept a plausible-looking filename that
612 643 // isn't a real core admin page (e.g. `custom_admin_page.php`)
613 644 // and effectively become an open redirect to a 404 page served
614 645 // under the admin path; the explicit allowlist closes that.
615 - $target = openstation_resolve_admin_target( $file );
646 + $target = openstation_resolve_admin_target( $file, $network );
616 647 if ( is_wp_error( $target ) ) {
617 648 return '';
618 649 }
619 650
@@ -628,8 +659,21 @@
628 659 // The shell screen is where a target is opened, never a target: the
629 660 // shell would open itself in a window, and a redirect chain built
630 661 // from it would loop. Fall back to the entry resolver instead.
631 662 if ( openstation_url_is_shell_screen( $target ) ) {
663 + return '';
664 + }
665 +
666 + // `admin.php` is a bootstrap, not a page. Without a `page` arg core
667 + // falls through the last `else` in `wp-admin/admin.php`, never
668 + // requires `admin-header.php`, and answers 200 with an empty body —
669 + // so the URL becomes a window showing nothing. The allowlist above
670 + // matches filenames and cannot see the query, which is why the
671 + // check belongs here, beside the shell-screen one: both are URLs
672 + // that resolve but must not become a target. Returning '' hands the
673 + // caller back to the entry resolver (session's focused window, else
674 + // the default window, else the Dashboard).
675 + if ( openstation_url_is_page_less_admin_php( $target ) ) {
632 676 return '';
633 677 }
634 678
635 679 return $target;