| @@ -75,8 +75,44 @@ | ||
| 75 | 75 | */ |
| 76 | 76 | const OPENSTATION_SHELL_INTENT_ARG = 'intent'; |
| 77 | 77 | |
| 78 | 78 | /** |
| 79 | + * Query arg asking the shell screen to boot straight into overview: how | |
| 80 | + * a switch from another site's overview lands in this one's, tiles and | |
| 81 | + * all (on a network every site is its own OpenStation, see | |
| 82 | + * docs/multisite.md). One-shot like the two above — read here, handed | |
| 83 | + * to the shell as `landInOverview`, stripped from the address bar. | |
| 84 | + */ | |
| 85 | +const OPENSTATION_SHELL_OVERVIEW_ARG = 'openstation_overview'; | |
| 86 | + | |
| 87 | +/** | |
| 88 | + * Whether this shell-screen request asked to boot into overview. | |
| 89 | + * | |
| 90 | + * @return bool | |
| 91 | + */ | |
| 92 | +function openstation_shell_lands_in_overview() { | |
| 93 | + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only routing flag. | |
| 94 | + return openstation_is_shell_screen_request() && ! empty( $_GET[ OPENSTATION_SHELL_OVERVIEW_ARG ] ); | |
| 95 | +} | |
| 96 | + | |
| 97 | +/** | |
| 98 | + * The side this shell slides its desk in from, when it was reached by a | |
| 99 | + * switch from another origin: `next`, `prev`, or ''. A one-shot boot | |
| 100 | + * arg like the two above (`openstation_hop_from`), because the hint a | |
| 101 | + * same-origin switch leaves in sessionStorage never crosses origins. | |
| 102 | + * | |
| 103 | + * @return string | |
| 104 | + */ | |
| 105 | +function openstation_shell_arrival_direction() { | |
| 106 | + if ( ! openstation_is_shell_screen_request() ) { | |
| 107 | + return ''; | |
| 108 | + } | |
| 109 | + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only routing flag. | |
| 110 | + $from = isset( $_GET[ OPENSTATION_NETWORK_HOP_FROM_ARG ] ) && is_scalar( $_GET[ OPENSTATION_NETWORK_HOP_FROM_ARG ] ) ? sanitize_key( wp_unslash( $_GET[ OPENSTATION_NETWORK_HOP_FROM_ARG ] ) ) : ''; | |
| 111 | + return in_array( $from, array( 'next', 'prev' ), true ) ? $from : ''; | |
| 112 | +} | |
| 113 | + | |
| 114 | +/** | |
| 79 | 115 | * Builds the shell screen URL, optionally carrying a target. |
| 80 | 116 | * |
| 81 | 117 | * `$target` is an absolute same-origin admin URL or a request-URI-shaped |
| 82 | 118 | * path (`/wp-admin/edit.php?post_type=page`). Either way only its path |
| @@ -84,18 +120,37 @@ | ||
| 84 | 120 | * {@see openstation_sanitize_portal_target()}, so the parameter is |
| 85 | 121 | * never trusted from the URL alone. An empty target yields the bare |
| 86 | 122 | * screen URL and the screen resolves the entry itself. |
| 87 | 123 | * |
| 88 | - * @param string $target Admin URL to open first, or '' for none. | |
| 89 | - * @param bool $intent Whether the target is the user's navigation intent. | |
| 124 | + * The screen exists in both admins, so the URL follows the one the | |
| 125 | + * caller is in. `$network` overrides that for callers with no context | |
| 126 | + * of their own: `admin-ajax.php` is never the network admin, whatever | |
| 127 | + * the click that reached it. | |
| 128 | + * | |
| 129 | + * @param string $target Admin URL to open first, or '' for none. | |
| 130 | + * @param bool $intent Whether the target is the user's navigation intent. | |
| 131 | + * @param bool|null $network Force the network screen (true) or the site | |
| 132 | + * one (false). Null follows the request. | |
| 90 | 133 | * @return string Absolute shell screen URL. |
| 91 | 134 | */ |
| 92 | -function openstation_shell_url( $target = '', $intent = false ) { | |
| 93 | - $url = admin_url( 'admin.php?page=' . OPENSTATION_SHELL_PAGE_SLUG ); | |
| 135 | +function openstation_shell_url( $target = '', $intent = false, $network = null ) { | |
| 136 | + $target = is_string( $target ) ? openstation_shell_normalize_admin_url( $target ) : ''; | |
| 137 | + $path = '' !== $target ? wp_parse_url( $target, PHP_URL_PATH ) : ''; | |
| 94 | 138 | |
| 95 | - $target = is_string( $target ) ? openstation_shell_normalize_admin_url( $target ) : ''; | |
| 139 | + if ( null === $network ) { | |
| 140 | + // Follow the admin the TARGET lives in, falling back to the | |
| 141 | + // request's own. The network dashboard opened on the site | |
| 142 | + // screen would be one admin inside another's shell, which is | |
| 143 | + // what the bridge refuses on a link click for the same reason. | |
| 144 | + $network = is_string( $path ) && '' !== $path | |
| 145 | + ? false !== strpos( $path, '/wp-admin/network/' ) | |
| 146 | + : is_network_admin(); | |
| 147 | + } | |
| 148 | + | |
| 149 | + $screen = 'admin.php?page=' . OPENSTATION_SHELL_PAGE_SLUG; | |
| 150 | + $url = $network ? network_admin_url( $screen ) : admin_url( $screen ); | |
| 151 | + | |
| 96 | 152 | if ( '' !== $target ) { |
| 97 | - $path = wp_parse_url( $target, PHP_URL_PATH ); | |
| 98 | 153 | $query = wp_parse_url( $target, PHP_URL_QUERY ); |
| 99 | 154 | if ( is_string( $path ) && '' !== $path ) { |
| 100 | 155 | $relative = $path . ( is_string( $query ) && '' !== $query ? '?' . $query : '' ); |
| 101 | 156 | $url = add_query_arg( OPENSTATION_SHELL_TARGET_ARG, rawurlencode( $relative ), $url ); |
| @@ -187,9 +242,15 @@ | ||
| 187 | 242 | } |
| 188 | 243 | if ( function_exists( 'get_current_screen' ) ) { |
| 189 | 244 | $screen = get_current_screen(); |
| 190 | 245 | if ( $screen instanceof WP_Screen ) { |
| 191 | - return OPENSTATION_SHELL_SCREEN_ID === $screen->id; | |
| 246 | + // WordPress suffixes screen ids in the network admin, so | |
| 247 | + // the network shell is `admin_page_openstation-network`. | |
| 248 | + return in_array( | |
| 249 | + $screen->id, | |
| 250 | + array( OPENSTATION_SHELL_SCREEN_ID, OPENSTATION_SHELL_SCREEN_ID . '-network' ), | |
| 251 | + true | |
| 252 | + ); | |
| 192 | 253 | } |
| 193 | 254 | } |
| 194 | 255 | global $pagenow, $plugin_page; |
| 195 | 256 | return 'admin.php' === $pagenow |
| @@ -251,8 +312,13 @@ | ||
| 251 | 312 | add_action( "load-{$hook}", 'openstation_shell_screen_set_title' ); |
| 252 | 313 | } |
| 253 | 314 | } |
| 254 | 315 | add_action( 'admin_menu', 'openstation_register_shell_screen' ); |
| 316 | +// The network admin builds its menu from its own hook, and the desktop | |
| 317 | +// is reachable there for the same reason it is on a site: it is where | |
| 318 | +// the network's own admin pages are. `network/admin.php` routes | |
| 319 | +// `?page=` exactly as `admin.php` does. | |
| 320 | +add_action( 'network_admin_menu', 'openstation_register_shell_screen' ); | |
| 255 | 321 | |
| 256 | 322 | /** |
| 257 | 323 | * Names the shell document, before `admin-header.php` asks for a name. |
| 258 | 324 | * |
| @@ -351,19 +417,28 @@ | ||
| 351 | 417 | $intent = ! empty( $_GET[ OPENSTATION_SHELL_INTENT_ARG ] ); |
| 352 | 418 | } |
| 353 | 419 | } |
| 354 | 420 | if ( '' === $target ) { |
| 355 | - $target = openstation_portal_entry_url( get_current_user_id() ); | |
| 421 | + // The network screen has its own dashboard to fall back to; | |
| 422 | + // the saved session belongs to a site, so it would open one | |
| 423 | + // admin's window on the other's desktop. | |
| 424 | + $target = is_network_admin() | |
| 425 | + ? network_admin_url( 'index.php' ) | |
| 426 | + : openstation_portal_entry_url( get_current_user_id() ); | |
| 356 | 427 | } |
| 357 | 428 | $target = openstation_shell_normalize_admin_url( $target ); |
| 358 | 429 | |
| 359 | - // `admin_url()` alone names the directory; `$pagenow` on that | |
| 430 | + // An admin URL alone names the directory; `$pagenow` on that | |
| 360 | 431 | // request is `index.php`, and the dock derives the Dashboard's |
| 361 | - // window id from the file. Keep both sides deriving the same id. | |
| 432 | + // window id from the file. Keep both sides deriving the same id, | |
| 433 | + // naming the file inside the target's OWN admin so a network | |
| 434 | + // URL does not resolve to the site's dashboard. | |
| 362 | 435 | $path = wp_parse_url( $target, PHP_URL_PATH ); |
| 363 | 436 | if ( is_string( $path ) && '/' === substr( $path, -1 ) ) { |
| 364 | 437 | $query = wp_parse_url( $target, PHP_URL_QUERY ); |
| 365 | - $target = admin_url( 'index.php' ) . ( is_string( $query ) && '' !== $query ? '?' . $query : '' ); | |
| 438 | + $parts = explode( '?', $target, 2 ); | |
| 439 | + $target = rtrim( $parts[0], '/' ) . '/index.php' | |
| 440 | + . ( is_string( $query ) && '' !== $query ? '?' . $query : '' ); | |
| 366 | 441 | } |
| 367 | 442 | |
| 368 | 443 | return array( |
| 369 | 444 | 'url' => $target, |