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/core/payload.php +177 -5 1.1.10 → 1.1.13 View file →
@@ -348,8 +348,11 @@
348 348 }
349 349 $sub_entry = array(
350 350 'title' => $sub_title,
351 351 'url' => $sub_url,
352 + // The registered slug, kept for the window-tab merge
353 + // below and stripped again before the payload ships.
354 + 'slug' => (string) $row['slug'],
352 355 );
353 356 if ( $row['external'] ) {
354 357 // Consumers that route a URL into a window skip these;
355 358 // the ones that can hand a link to the browser mark
@@ -399,8 +402,47 @@
399 402 if ( $parent_is_container && $url === $parent_url ) {
400 403 continue;
401 404 }
402 405
406 + // A native window in charge of this menu owns its rows too:
407 + // whatever it offers as a tab, the dock offers as a row, same
408 + // labels and same order, each one tagged with the tab it
409 + // opens. The first tab IS the menu's own page, so it becomes
410 + // the self-label rather than a second row for the tile's own
411 + // destination. See `App::menu()`.
412 + $window_tabs = openstation_app_menu_tabs( $identity_slug );
413 + if ( $window_tabs ) {
414 + $self_label = $window_tabs[0]['label'];
415 + $claimed = array();
416 + foreach ( $window_tabs as $tab ) {
417 + if ( '' !== $tab['page'] ) {
418 + $claimed[] = $tab['page'];
419 + }
420 + }
421 + // A page this window has no tab for is still a page: a
422 + // plugin's screen registered under this menu, a taxonomy
423 + // someone added. Dropping those would make them
424 + // unreachable from the dock, so they follow the window's
425 + // own rows rather than being replaced by them.
426 + $kept = array();
427 + foreach ( $sub_items as $sub_entry ) {
428 + if ( ! in_array( $sub_entry['slug'], $claimed, true ) ) {
429 + $kept[] = $sub_entry;
430 + }
431 + }
432 + $sub_items = array();
433 + foreach ( array_slice( $window_tabs, 1 ) as $tab ) {
434 + $sub_items[] = array(
435 + 'title' => $tab['label'],
436 + 'url' => add_query_arg( 'os_tab', $tab['id'], $url ),
437 + );
438 + }
439 + $sub_items = array_merge( $sub_items, $kept );
440 + }
441 + foreach ( $sub_items as $i => $sub_entry ) {
442 + unset( $sub_items[ $i ]['slug'] );
443 + }
444 +
403 445 $dock_item = array(
404 446 'id' => sanitize_key( $item[5] ?? $item[2] ),
405 447 'title' => $title,
406 448 'icon' => $icon,
@@ -730,12 +772,13 @@
730 772
731 773 /**
732 774 * Filters whether a dock item supports multiple open windows.
733 775 *
734 - * Return true to let the user open more than one window of this page.
735 - * A "+" affordance appears on the dock icon and a "Open another" action
736 - * becomes available in the window's title-bar menu. Singletons (false)
737 - * always focus the existing window when re-opened.
776 + * Return true to advertise this page as multi-capable: an instance
777 + * rail appears under the dock icon and an "Open another" action
778 + * becomes available in the window's title-bar menu. It does not gate
779 + * the submenu, which opens a window of its own on every pick either
780 + * way; a tile click focuses the menu's open window.
738 781 *
739 782 * @param bool $multi Whether this page is multi-capable.
740 783 * @param string $menu_slug The menu slug (e.g. `edit.php?post_type=page`).
741 784 */
@@ -984,9 +1027,9 @@
984 1027 if ( ! is_string( $file ) || '' === $file ) {
985 1028 return null;
986 1029 }
987 1030 $plugins_dir = wp_normalize_path( WP_PLUGIN_DIR );
988 - $norm = wp_normalize_path( $file );
1031 + $norm = openstation_plugin_link_path( wp_normalize_path( $file ) );
989 1032 if ( 0 !== strpos( $norm, $plugins_dir . '/' ) ) {
990 1033 return null;
991 1034 }
992 1035 if ( ! function_exists( 'get_plugins' ) ) {
@@ -1455,8 +1498,9 @@
1455 1498 $norm = wp_normalize_path( (string) $frame['file'] );
1456 1499 if ( '' !== $self_dir && 0 === strpos( $norm, $self_dir ) ) {
1457 1500 continue;
1458 1501 }
1502 + $norm = openstation_plugin_link_path( $norm );
1459 1503 foreach ( $dirs as $dir ) {
1460 1504 if ( 0 === strpos( $norm, $dir ) ) {
1461 1505 return $norm;
1462 1506 }
@@ -1464,8 +1508,36 @@
1464 1508 }
1465 1509 return null;
1466 1510 }
1467 1511
1512 +/**
1513 + * Map a path PHP reported back to the plugin folder WordPress loaded
1514 + * it through.
1515 + *
1516 + * PHP resolves symlinks in file names, so a plugin folder linked into
1517 + * `plugins/` from elsewhere shows up in backtraces and reflection outside
1518 + * `WP_PLUGIN_DIR`. WordPress.com's managed plugins are linked that way:
1519 + * `plugins/woocommerce` resolves to `/wordpress/plugins/woocommerce/<version>/`.
1520 + * Core records each active plugin's link and target in
1521 + * `$wp_plugin_paths` before loading it, and `plugin_basename()` maps
1522 + * through the same list.
1523 + *
1524 + * @param string $path Normalized absolute path.
1525 + * @return string The same file under the plugin's link, or `$path`
1526 + * when no active plugin is linked from there.
1527 + */
1528 +function openstation_plugin_link_path( $path ) {
1529 + global $wp_plugin_paths;
1530 +
1531 + foreach ( (array) $wp_plugin_paths as $link => $target ) {
1532 + $target = trailingslashit( (string) $target );
1533 + if ( 0 === strpos( $path, $target ) ) {
1534 + return trailingslashit( (string) $link ) . substr( $path, strlen( $target ) );
1535 + }
1536 + }
1537 + return $path;
1538 +}
1539 +
1468 1540 add_action(
1469 1541 'registered_post_type',
1470 1542 static function ( $post_type ) {
1471 1543 openstation_record_type_registrant( $post_type, 'post_type' );
@@ -1678,8 +1750,13 @@
1678 1750 // the server's capability-gated view). See
1679 1751 // openstation_menu_signature().
1680 1752 $payload['menuSig'] = openstation_menu_signature();
1681 1753
1754 + // Each script dependency's payload once, entries carry handles.
1755 + $script_dep_payloads = array();
1756 + $payload = openstation_compact_script_deps( $payload, $script_dep_payloads );
1757 + $payload['scriptDepPayloads'] = (object) $script_dep_payloads;
1758 +
1682 1759 return $payload;
1683 1760 }
1684 1761
1685 1762 /**
@@ -1899,8 +1976,102 @@
1899 1976 return $out;
1900 1977 }
1901 1978
1902 1979 /**
1980 + * Move every entry's `scriptDeps` payloads into one map.
1981 + *
1982 + * {@see openstation_resolve_script_dependencies()} returns the full
1983 + * payload of each dependency (URL, l10n, before/after), and ~20 entry
1984 + * builders call it. A dependency shared by N entries was therefore
1985 + * serialized N times: one plugin's 5.5 KB localized object became
1986 + * ~400 KB of a 489 KB `openStationConfig` (GH#892). This replaces each
1987 + * entry's `scriptDeps` list with its handles and puts each handle's
1988 + * payload in `$map` once. The shell resolves the handles back before
1989 + * any consumer reads them; see `src/script-dep-payloads.ts`.
1990 + *
1991 + * ENTRY DEPTH ONLY. `$payload` is a payload whose top-level values are
1992 + * entry lists (`serverWidgets`, `serverCommandScripts`, ...), and only
1993 + * a `scriptDeps` sitting directly on one of those entries is touched.
1994 + * The key is the shell's there. Deeper down it is a plugin's metadata
1995 + * (`settings => [ 'scriptDeps' => ... ]`), and rewriting it would hoist
1996 + * foreign data into the map first-wins and hydrate every real
1997 + * dependency of that handle to it. Scoping by depth rather than by a
1998 + * list of keys means a new builder is covered without an edit here.
1999 + * It also leaves every other branch of the payload unassigned, so
2000 + * PHP's copy-on-write never has to copy them.
2001 + *
2002 + * Every string left in a compacted list has an entry in `$map`: a bare
2003 + * handle a builder (or a filter on one) emitted is resolved here, by
2004 + * the same rule as {@see openstation_resolve_script_dependencies()}.
2005 + * A handle with nothing to fetch and nothing to run is dropped, as it
2006 + * is there. The client can then treat a string with no map entry as a
2007 + * payload from somewhere else, not a dependency this side dropped.
2008 + *
2009 + * Runs on the finished payload, so every builder, and every filter on
2010 + * a builder's output, still sees the full shape.
2011 + *
2012 + * @param array $payload Payload whose top-level values are entry lists.
2013 + * @param array $map Handle => dependency payload, filled in place.
2014 + * @return array The payload with each entry's `scriptDeps` reduced to handles.
2015 + */
2016 +function openstation_compact_script_deps( $payload, array &$map ) {
2017 + if ( ! is_array( $payload ) ) {
2018 + return $payload;
2019 + }
2020 + foreach ( $payload as $list_key => $entries ) {
2021 + if ( ! is_array( $entries ) ) {
2022 + continue;
2023 + }
2024 + foreach ( $entries as $entry_key => $entry ) {
2025 + if ( ! is_array( $entry ) || ! isset( $entry['scriptDeps'] ) || ! is_array( $entry['scriptDeps'] ) ) {
2026 + continue;
2027 + }
2028 + $payload[ $list_key ][ $entry_key ]['scriptDeps'] = openstation_compact_script_dep_list( $entry['scriptDeps'], $map );
2029 + }
2030 + }
2031 + return $payload;
2032 +}
2033 +
2034 +/**
2035 + * One entry's `scriptDeps` list, reduced to handles.
2036 + *
2037 + * @param array $deps Dependency payloads and/or bare handles.
2038 + * @param array $map Handle => dependency payload, filled in place.
2039 + * @return array Handles, in order; anything unkeyable passes through.
2040 + */
2041 +function openstation_compact_script_dep_list( array $deps, array &$map ) {
2042 + $handles = array();
2043 + foreach ( $deps as $dep ) {
2044 + if ( is_string( $dep ) && '' !== $dep ) {
2045 + if ( ! isset( $map[ $dep ] ) ) {
2046 + $payload = openstation_resolve_script_payload( $dep );
2047 + if ( '' === $payload['url']
2048 + && empty( $payload['before'] )
2049 + && empty( $payload['after'] )
2050 + && empty( $payload['l10n'] ) ) {
2051 + continue;
2052 + }
2053 + $payload['handle'] = $dep;
2054 + $map[ $dep ] = $payload;
2055 + }
2056 + $handles[] = $dep;
2057 + continue;
2058 + }
2059 + if ( is_array( $dep ) && isset( $dep['handle'] ) && '' !== (string) $dep['handle'] ) {
2060 + $handle = (string) $dep['handle'];
2061 + if ( ! isset( $map[ $handle ] ) ) {
2062 + $map[ $handle ] = $dep;
2063 + }
2064 + $handles[] = $handle;
2065 + continue;
2066 + }
2067 + // A handle-less payload has nothing to key it by.
2068 + $handles[] = $dep;
2069 + }
2070 + return $handles;
2071 +}
2072 +
2073 +/**
1903 2074 * Resolve a registered WP script handle into the full payload the
1904 2075 * shell needs to lazy-load it without going through `wp_print_scripts()`.
1905 2076 *
1906 2077 * Returns:
@@ -2633,8 +2804,9 @@
2633 2804 'styleHandle' => $style_handle,
2634 2805 'styleInline' => $style_payload['inline'],
2635 2806 'companionStyles' => $companion_styles,
2636 2807 'tabs' => $tab_descriptors,
2808 + 'menuPages' => isset( $entry['menu_pages'] ) ? array_values( (array) $entry['menu_pages'] ) : array(),
2637 2809 );
2638 2810 }
2639 2811
2640 2812 // Append each handle's synthesized config set to its map entry —